IT/development

[springBoot] ์ฒจ๋ถ€ํŒŒ์ผ ์—…/๋‹ค์šด๋กœ๋“œ(Oracle)

์•Œ ์ˆ˜ ์—†๋Š” ์‚ฌ์šฉ์ž 2023. 5. 7.
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);
    }
}

๋Œ“๊ธ€