IT/development

[springBoot] 첨부파일 업/다운로드(Oracle)

알 수 없는 사용자 2023. 5. 7. 09:03
반응형
CREATE TABLE "FILE_INFO" 
   ("FILE_NO" NUMBER(10,0) NOT NULL ENABLE, 
	"BOARD_NO" NUMBER(10,0) NOT NULL ENABLE, 
	"ORG_FILE_NAME" VARCHAR2(50) NOT NULL ENABLE, 
	"FILE_PATH" VARCHAR2(200) NOT NULL ENABLE, 
	"FILE_SIZE" NUMBER(20,0) NOT NULL ENABLE, 
	"CREATE_DATE" DATE NOT NULL ENABLE, 
	"UPDATE_DATE" DATE DEFAULT SYSDATE, 
	"DEL_YN" CHAR(1) DEFAULT 'N' NOT NULL ENABLE, 
	 CONSTRAINT "FILE_PK" PRIMARY KEY ("FILE_NO", "BOARD_NO")
);

COMMENT ON COLUMN FILE_INFO.FILE_NO IS '파일 순번(시퀀스:FILE_NO_SEQ)';
COMMENT ON COLUMN FILE_INFO.BOARD_NO IS '게시글 순번';
COMMENT ON COLUMN FILE_INFO.ORG_FILE_NAME IS '원본 파일명';
COMMENT ON COLUMN FILE_INFO.FILE_PATH IS '파일 경로';
COMMENT ON COLUMN FILE_INFO.FILE_SIZE IS '파일 사이즈';
COMMENT ON COLUMN FILE_INFO.CREATE_DATE IS '등록일';
COMMENT ON COLUMN FILE_INFO.UPDATE_DATE IS '수정일';
COMMENT ON COLUMN FILE_INFO.DEL_YN IS '삭제여부';

--시퀀스
CREATE SEQUENCE FILE_NO_SEQ INCREMENT BY 1 MINVALUE 0 NOCYCLE NOCACHE NOORDER ;
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://www.mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="study.example.thboard.mapper.FileMapper">

    <!--파일정보 저장-->
    <insert id="insertFile" parameterType="FileVo">
        /* 파일정보 저장 */
        INSERT INTO FILE_INFO
            (
                FILE_NO
              , BOARD_NO
              , ORG_FILE_NAME
              , FILE_PATH
              , FILE_SIZE
              , CREATE_DATE
            )
        VALUES
            (
                FILE_NO_SEQ.NEXTVAL
              , #{boardNo}
              , #{orgFileName}
              , #{filePath}
              , #{fileSize}
              , SYSDATE
            )
    </insert>

    <!--파일 상세-->
    <select id="selectFileDetail" parameterType="map" resultType="FileVo">
        /* 파일정보 상세 조회 */
        SELECT FILE_NO AS fileNo
             , BOARD_NO AS boardNo
             , ORG_FILE_NAME AS orgFileName
             , FILE_PATH AS filePath
             , FILE_SIZE AS fileSize
             , DEL_YN AS delYn
             , CREATE_DATE AS createDate
             , UPDATE_DATE AS updateDate
          FROM FILE_INFO
         WHERE 1=1
           AND FILE_NO = #{fileNo}
<!--           AND BOARD_NO = #{boardNo}-->
         ORDER BY CREATE_DATE DESC
    </select>

    <!--파일 목록 조회-->
    <select id="selectFileList" parameterType="map" resultType="FileVo">
        /* 파일 목록 조회 */
        SELECT FILE_NO AS fileNo
             , BOARD_NO AS boardNo
             , ORG_FILE_NAME AS orgFileName
             , FILE_PATH AS filePath
             , FILE_SIZE AS fileSize
             , DEL_YN AS delYn
             , CREATE_DATE AS createDate
             , UPDATE_DATE AS updateDate
          FROM FILE_INFO
         WHERE 1=1
           AND BOARD_NO = #{boardNo}
         ORDER BY CREATE_DATE DESC
    </select>

    <!--파일정보 수정-->
    <update id="updateFile" parameterType="FileVo">
        /* 게시글 수정 */
        UPDATE FILE_INFO
           SET ORG_FILE_NAME = #{orgFileName}
             , FILE_PATH = #{filePath}
             , FILE_SIZE = #{fileSize}
             , UPDATE_DATE = SYSDATE
         WHERE FILE_NO = #{fileNo}
           AND BOARD_NO = #{boardNo}
    </update>

    <!--파일 삭제-->
    <update id="deleteFile" parameterType="map">
        /* 파일 삭제 */
        UPDATE FILE_INFO
           SET DEL_YN = 'Y'
             , UPDATE_DATE = SYSDATE
         WHERE FILE_NO = #{fileNo}
           AND BOARD_NO = #{boardNo}
    </update>

</mapper>
package study.example.thboard.vo;

import lombok.Data;

@Data
//첨부파일 관련 vo
public class FileVo extends CommonVo{
    private int fileNo;
    private int boardNo;
    private String orgFileName;
    private String filePath;
    private int fileSize;
    private String delYn;
}
package study.example.thboard.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import study.example.thboard.mapper.FileMapper;
import study.example.thboard.vo.FileVo;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;

@Service
@Slf4j
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class FileService {

    private final FileMapper fileMapper;

    @Value("${file.path}")
    private String filePath;

    /**
     * 파일 업로드
     * @param files
     * @param boardNo
     * @return
     */
    @Transactional
    public void saveFile(MultipartFile files, int boardNo) throws IOException, Exception{
        //원본 파일 이름
        String orgFileName = files.getOriginalFilename();
        //파일 uuid
        String uuid = UUID.randomUUID().toString();
        //파일 확장자
        String extension = orgFileName.substring(orgFileName.lastIndexOf("."));
        //서버에 저장될 파일명
        String saveFileName = uuid + extension;
        //파일 저장 경로
        String path = filePath + saveFileName;
        //파일 저장
        files.transferTo(new File(path));
        //파일정보 DB 저장
        FileVo fileVo = new FileVo();
        fileVo.setOrgFileName(orgFileName);
        fileVo.setFileSize((int) files.getSize());
        fileVo.setBoardNo(boardNo);
        fileVo.setFilePath(path);
        fileMapper.insertFile(fileVo);
    }

    /**
     * 파일 정보 상세
     * @param fileNo
     * @return
     */
    public FileVo getFileDetail(int fileNo) throws Exception{
        return fileMapper.selectFileDetail(fileNo);
    }

    /**
     * 파일 목록 조회
     * @param boardNo
     * @return
     */
    public List<FileVo> getFileList(int boardNo) throws Exception{
        return fileMapper.selectFileList(boardNo);
    }

}
package study.example.thboard.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import study.example.thboard.vo.FileVo;

import java.util.List;

@Repository @Mapper
public interface FileMapper {

    /* 파일 정보 저장 */
    void insertFile(FileVo fileVo);

    /* 파일 정보 상세 */
//    FileVo selectFileDetail(@Param("fileNo") int fileNo, @Param("boardNo") int boardNo);
    FileVo selectFileDetail(@Param("fileNo") int fileNo);

    /* 파일 목록 조회 */
    List<FileVo> selectFileList(@Param("boardNo") int boardNo);

    /* 파일 정보 수정 */
    void updateFile(FileVo fileVo);

    /* 파일 정보 삭제 */  
    void deleteFile(@Param("fileNo") int fileNo, @Param("boardNo") int boardNo);


}
package study.example.thboard.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriUtils;
import study.example.thboard.service.FileService;
import study.example.thboard.vo.FileVo;

import java.nio.charset.StandardCharsets;
import java.util.List;

@Controller
@RequiredArgsConstructor
@Slf4j
public class FileController {

    private final FileService fileService;

    //파일 다운로드
    @GetMapping("/download/{fileNo}")
    public ResponseEntity<Resource> downloadFile(@PathVariable int fileNo) throws Exception {
		//화면에서 넘긴 파일 순번으로 테이블에서 파일 정보 조회
        FileVo fileInfo = fileService.getFileDetail(fileNo);
		//UrlResource 이용해 파일 경로 읽음
        UrlResource resource = new UrlResource("file:" + fileInfo.getFilePath());
		//원본파일명 UTF-8 인코딩
        String encodedFileName = UriUtils.encode(fileInfo.getOrgFileName(), StandardCharsets.UTF_8);
        //다운로드 시 대화상자 표시
        String contentDisposition = "attachment; filename=\"" + encodedFileName + "\"";
		//http header와 body에 파일데이터 전달
        return ResponseEntity
                .ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
                .body(resource);
    }
}
반응형