반응형
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);
}
}
반응형
'IT > development' 카테고리의 다른 글
[springBoot] 세션 인증 로그인/로그아웃 (0) | 2023.05.08 |
---|---|
[springBoot] 페이지네이션 처리(feat. Oracle) (0) | 2023.05.07 |
[springBoot] UrlResource 사용 시 경로 에러 (0) | 2023.05.07 |
[mybatis] selectkey값 return(Oracle) (0) | 2023.05.06 |
[mybatis] 검색(Oracle) (0) | 2023.05.06 |