ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 첨부파일 처리
    Kotlin 2021. 5. 2. 15:46

    첨부파일 등록, 수정, 삭제에 대한 처리

     

     

    ※ 개발 스펙

    • 개발도구 : 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

    댓글

Designed by Tistory.