-
첨부파일 등록, 수정, 삭제에 대한 처리
※ 개발 스펙
- 개발도구 : intelliJ,
- 빌드도구 : gradle,
- 서버언어 : kotlin,
- 프론트언어 : Thymeleaf,
- SQL : MySql ( + MyBatis )
FileInfoModel
data class FileInfoModel ( var f_order: String? = null, // 파일 순번 var b_order: String? = null, // 게시판 순번 var f_name: String? = null, // 파일 이름 var f_path: String? = null // 파일 경로 )
BoardMapper.xml
<!-- 첨부파일 목록 --> <select id="fileList" parameterType="String" resultType="kr.or.korea.model.FileInfoModel"> SELECT f_order, -- 파일 순번 b_order, -- 게시판 순번 f_name, -- 파일 이름 f_path -- 파일 경로 FROM file_list WHERE b_order = #{b_order} <if test="f_order != null and f_order != ''"><!-- 파일 순번 --> AND f_order = #{f_order} </if> </select> <!-- 첨부파일 등록 --> <insert id="fileInsert" parameterType="kr.or.korea.model.FileInfoModel"> INSERT INTO file_list ( f_order, b_order, f_name, f_path ) VALUES ( ( SELECT IFNULL(MAX(f_order)+1, 1) FROM file_list fl WHERE b_order = #{b_order} ), #{b_order}, if(instr(#{f_name}, '\\') = '0', #{f_name}, SUBSTRING_INDEX(#{f_name}, '\\', -1)), #{f_path} ) </insert> <!-- 첨부파일 삭제 --> <delete id="fileDelete" parameterType="String"> DELETE FROM file_list WHERE b_order = #{b_order} AND f_order = #{f_order} </delete>
BoardMapper.kt (인터페이스)
/** * 첨부파일 목록 * */ @Throws(Exception::class) fun fileList(b_order: String, f_order: String) : List<FileInfoModel> /** * 첨부파일 등록 * */ @Throws(Exception::class) fun fileInsert(param: FileInfoModel) /** * 첨부파일 삭제 * */ @Throws(Exception::class) fun fileDelete(b_order: String, f_order: String)
BoardService.kt
@Service class BoardService { @Autowired private lateinit var mapper : BoardMapper @Autowired private lateinit var session : HttpSession @Autowired lateinit var env : Environment /** * 첨부파일 목록 * @throws Exception */ @Throws(Exception::class) fun fileList(b_order: String, f_order: String) : List<FileInfoModel> { return mapper.fileList(b_order, f_order) } /** * 첨부파일 등록 * @throws Exception */ @Throws(Exception::class) @Transactional(rollbackFor = [(Exception::class)]) fun fileInsert(b_order: String, files: List<MultipartFile>) { var fileData = FileInfoModel() fileData.b_order = b_order // 파일 저장 위치 val getYear = LocalDateTime.now().year val fileUploadDir: String = env.getProperty("file.upload.dir") + getYear + File.separator if (File(fileUploadDir).exists().not()) { File(fileUploadDir).mkdirs() } // 파일 업로드 for (item in files) { val fileUploadName : String = UUID.randomUUID().toString() val fileUploadPath : String = fileUploadDir + fileUploadName val originName : String? = item.originalFilename fileData.f_name = originName fileData.f_path = fileUploadPath item.transferTo(File(fileUploadPath)) mapper.fileInsert(fileData) } } /** * 첨부파일 삭제 * @throws Exception */ @Throws(Exception::class) @Transactional(rollbackFor = [(Exception::class)]) fun fileDelete(b_order: String, f_order: String) { val finfo = this.fileList(b_order, f_order) if(finfo != null && Files.exists(Paths.get(finfo[0].f_path))){ // 실제 서버에 저장된 파일 삭제 Files.delete(Paths.get(finfo[0].f_path)) // 데이터베이스에서 삭제 mapper.fileDelete(b_order, f_order) } } /** * 게시글 등록 * @throws Exception */ @Throws(Exception::class) @Transactional(rollbackFor = [(Exception::class)]) fun boardInsert(param: BoardModel, files: List<MultipartFile>?): JSONObject { // 게시글 등록 param.id = session.getAttribute("id").toString() mapper.boardInsert(param) // 첨부파일 등록 if (files != null) { this.fileInsert(param.b_order!!, files) } return JSONObject() } /** * 게시글 수정 * @throws Exception */ @Throws(Exception::class) @Transactional(rollbackFor = [(Exception::class)]) fun boardModify(param: BoardModel, files: List<MultipartFile>?, f_order: String): JSONObject { // 게시글 수정 mapper.boardModify(param) // 첨부파일 삭제 if (f_order != "") { val f_order = f_order.split(",") for (item in f_order) { this.fileDelete(param.b_order!!, item) } } // 첨부파일 등록 if (files != null) { this.fileInsert(param.b_order!!, files) } return JSONObject() } }
BoardController.kt
@Controller class BoardController { @Autowired private lateinit var service: BoardService /** * 첨부파일 다운로드 * @throws Exception */ @RequestMapping(value = [("/file/download")]) @ResponseBody @Throws(Exception::class) fun webFileDownload( response: HttpServletResponse, @RequestParam(value = "b_order", required = true) b_order: String, @RequestParam(value = "f_order", required = true) f_order: String ): ByteArray { val finfo = service.fileList(b_order, f_order) if (finfo != null) { if (Files.exists(Paths.get(finfo[0].f_path))) { val ins = FileInputStream(File(finfo[0].f_path)) val temp: ByteArray = IOUtils.toByteArray(ins) ins.close() response.characterEncoding = "UTF-8" response.contentType = "application/octet-stream" response.setHeader("Expires", "-1;") response.setHeader("Pragma", "no-cache;") response.setHeader("Content-Transfer-Encoding", "binary;") response.setHeader("Content-Disposition", "Attachment;Filename=" + URLEncoder.encode(finfo[0].f_name, "UTF-8")) response.outputStream.write(temp) return temp } } return ByteArray(0) } /** * 게시글 등록 처리 * @throws Exception */ @RequestMapping(value = [("/board/write/submit")], method = [(RequestMethod.POST)]) @ResponseBody @Throws(Exception::class) fun boardInsert(param: BoardModel, @RequestParam("files") files: List<MultipartFile>?) : JSONObject { return service.boardInsert(param, files) } /** * 게시글 수정 처리 * @throws Exception */ @RequestMapping(value = [("/board/modify/submit")], method = [(RequestMethod.POST)]) @ResponseBody @Throws(Exception::class) fun boardModify(param: BoardModel, files: List<MultipartFile>?, f_order: String) : JSONObject { return service.boardModify(param, files, f_order) } }
글 등록할 땐 첨부파일을 등록한다.
글 상세화면에서는 첨부파일을 다운로드 할 수 있다.
글 수정할 땐 기존 첨부파일을 목록형태로 보여주고 첨부파일 하나를 삭제하면 UI에서만 삭제되고
글 수정 버튼을 누를 시에만 서버에 실제 파일이 삭제되도록 구현했다.
그리고 새로운 첨부파일도 추가할 수 있다.
그러므로 서버로직에서는 삭제된 파일을 삭제하는 로직 하나, 새로운 첨부파일을 등록하는 로직하나가 수정로직에 들어가 있다.
register.html
<!-- 글 등록 --> <div class="container"> <form id="registerForm" name="registerForm" method="post" enctype="multipart/form-data"> <!--/* 파일첨부 */--> <div id="fileArea"> <div class="filebox"> <input type="file" id="file1" name="files" onchange="handleFile(this);"> <input type="button" onclick="cancelFile(this);" value="삭제"> </div> <div class="filebox"> <input type="file" id="file2" name="files" onchange="handleFile(this);"> <input type="button" onclick="cancelFile(this);" value="삭제"> </div> </div> </form> </div> <!-- 글 상세 --> <div th:if="not#lists.isEmpty(FILE)}"> <!--/* 첨부파일 목록 */--> <div th:each="file : ${FILE}"> <label th:text="${file['f_name']}"></label> <a th:href="@{/file/download(b_order=${INFO['b_order']}, f_order=${file['f_order']})}"> <span>다운로드</span> </a> </div> </div> <!-- 글 수정 --> <div class="container"> <form id="modifyForm" name="modifyForm" method="post" enctype="multipart/form-data"> <input type="hidden" id="b_order" name="b_order" th:value="${INFO['b_order']}"><!-- 게시글 번호 --> <input type="hidden" id="f_order" name="f_order"><!-- 삭제한 첨부파일 번호 --> <!--/* 파일첨부 */--> <div id="fileArea"> <!--/* 기존에 첨부된 파일 목록 */--> <div th:if="${not#lists.isEmpty(FILE)}" th:each="file : ${FILE}"> <label th:text="${file['f_name']}"></label> <a th:onclick="deleteFile( [[${file['f_order']}]] , this);"> <span>삭제</span> </a> </div> <!--/* 파일 추가 */--> <div th:if="${#lists.isEmpty(FILE) or #lists.size(FILE) < 5}"> <input type="file" id="file1" name="files" onchange="handleFile(this);"> <input type="button" onclick="cancelFile(this);" value="삭제"> </div> </div> </form> </div>
javascript
/* 첨부파일 취소 (input file 에서 첨부한 파일 삭제) */ function cancelFile(obj) { $(obj).prevAll().val(""); }, /* 첨부파일 삭제 (서버에서 삭제하기 위해 삭제한 파일번호 저장) */ function deleteFile(order, obj) { fileArr.push(order); // 삭제한 첨부파일 번호 저장 obj.parentNode.remove(); // 화면에서 삭제 }, /* 파일 검증 */ function handleFile(obj) { var fileName = obj.value.substring(obj.value.lastIndexOf('\\')+1, obj.length); var fileExt = obj.value.substring(obj.value.lastIndexOf('.')+1); // 확장자 // 파일명 길이 체크 if (fileName.length > 100) { obj.value = ''; alert("[ERR] 파일명이 너무 길어 첨부할 수 없습니다."); return; // 파일 용량 체크 } else if (obj.files && obj.files[0].size > (100 * 1024 * 1024)) { obj.value = ''; alert("[ERR] 최대 파일 용량인 100MB를 초과했습니다."); return; // 확장자 null 체크 } else if (obj.value.lastIndexOf('.') == -1) { obj.value = ''; alert("[ERR] 확장자가 없는 파일입니다."); return; // 특정 확장자 제외 } else if (fileExt.toLowerCase() == "exe" || fileExt.toLowerCase() == "com" || fileExt.toLowerCase() == "bat" || fileExt.toLowerCase() == "msi") { obj.value = ''; alert("[ERR] 첨부가 불가능한 파일입니다."); return; } else { // 파일명 변경 obj.value = fileName; } }, /* 글 수정 폼전송 */ var fileArr = new Array(); function modifySubmit() { document.getElementById('f_order').value = fileArr; // 삭제한 첨부파일 번호 저장 $('#modifyForm').ajaxForm({ url: '/board/modify/submit', type: 'POST', dataType: 'json', beforeSubmit: function (data, frm, opt) { return true; }, success: function (responseText, statusText) { document.location.href = '/board/detail?order=' + INFO['b_order'] + '&page=' + PAGE; }, error: function () { alert('에러발생'); return; } }).submit(); }
'Kotlin' 카테고리의 다른 글
[Kotlin] mybatis <foreach> 를 사용할 때 배열 전달하기 (0) 2021.08.24 [Kotlin] 배열 선언, 초기화 (0) 2021.05.21