Search
Duplicate

๊ฒŒ์‹œํŒ - ํŒŒ์ผ ์—…๋กœ๋“œ

๊ฒŒ์‹œํŒ - ํŒŒ์ผ ์—…๋กœ๋“œ

์Šคํƒ€ ์ข€ ๋ˆŒ๋Ÿฌ์ฃผ์„ธ์š”

Spring Boot (Back-End)

Spring Boot ๋กœ ํŒŒ์ผ ์—…๋กœ๋“œ, ์กฐํšŒ, ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

๊ตฌํ˜„ ๊ธฐ๋Šฅ

โ€ข
ํŒŒ์ผ ์—…๋กœ๋“œ
โ—ฆ
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋“ฑ๋ก
โ—ฆ
ํŒŒ์ผ ์‹œ์Šคํ…œ์— ๋“ฑ๋ก
โ€ข
๏ธŽ ํŒŒ์ผ ์กฐํšŒ
โ—ฆ
ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ
โ—ฆ
๋‹ค์šด๋กœ๋“œ
โ—ฆ
์ธ๋„ค์ผ ์ด๋ฏธ์ง€ ๋ณด๊ธฐ
โ€ข
ํŒŒ์ผ ์‚ญ์ œ
โ—ฆ
๊ฒŒ์‹œ๊ธ€์— ์ข…์†๋œ ์—ฌ๋Ÿฌ ํŒŒ์ผ ์‚ญ์ œ
โ—ฆ
์„ ํƒ ํŒŒ์ผ ์‚ญ์ œ
โ—ฆ
๊ฐœ๋ณ„ ํŒŒ์ผ ์‚ญ์ œ

React (Front-End)

React ๋กœ ํŒŒ์ผ ์—…๋กœ๋“œ, ์กฐํšŒ, ์‚ญ์ œ UI ๋ฐ API ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
โ€ข
ํŒŒ์ผ ์—…๋กœ๋“œ
โ—ฆ
ํŒŒ์ผ ๋“ฑ๋ก UI ์ถ”๊ฐ€
โ—ฆ
ํŒŒ์ผ ์—…๋กœ๋“œ ์š”์ฒญ API
โ€ข
๏ธŽ ํŒŒ์ผ ์กฐํšŒ
โ—ฆ
ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ UI ๋ฐ API ์ถ”๊ฐ€
โ—ฆ
๋‹ค์šด๋กœ๋“œ UI ๋ฐ API ์ถ”๊ฐ€
โ—ฆ
์ธ๋„ค์ผ ์ด๋ฏธ์ง€ UI ์ถ”๊ฐ€
โ€ข
ํŒŒ์ผ ์‚ญ์ œ
โ—ฆ
๊ฒŒ์‹œ๊ธ€์— ์ข…์†๋œ ์—ฌ๋Ÿฌ ํŒŒ์ผ ์‚ญ์ œ (๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ ์‹œ, ๋ฐฑ์—”๋“œ์—์„œ ์ฒ˜๋ฆฌํ•จ)
โ—ฆ
์‚ญ์ œํ•  ํŒŒ์ผ ์ฒดํฌ๋ฐ•์Šค UI ์ถ”๊ฐ€ ๋ฐ ์ˆ˜์ • ์š”์ฒญ ์‹œ, ์‚ญ์ œํ•  ํŒŒ์ผ ๋ฒˆํ˜ธ ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
โ—ฆ
๊ฐœ๋ณ„ ํŒŒ์ผ ์‚ญ์ œ UI ๋ฐ API ์ถ”๊ฐ€

Spring Boot (Back-End)

Spring Boot ๋กœ ํŒŒ์ผ ์—…๋กœ๋“œ, ์กฐํšŒ, ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

ํŒŒ์ผ ์—…๋กœ๋“œ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋“ฑ๋ก

1.
๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค - ํ…Œ์ด๋ธ” ์ƒ์„ฑ
2.
SQL ์ฟผ๋ฆฌ Mapper ์ž‘์„ฑ
3.
DTO
4.
Mapper
5.
Service
6.
Controller

๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค - ํ…Œ์ด๋ธ” ์ƒ์„ฑ

CREATE TABLE `file` ( `no` int NOT NULL AUTO_INCREMENT, -- ํŒŒ์ผ ๋ฒˆํ˜ธ (์ž๋™์ฆ๊ฐ€) `parent_table` varchar(45) NOT NULL, -- ๋ถ€๋ชจ ํ…Œ์ด๋ธ”๋ช… (์˜ˆ: 'board') `parent_no` int NOT NULL, -- ๋ถ€๋ชจ ํ…Œ์ด๋ธ”์—์„œ์˜ ๋ฒˆํ˜ธ `file_name` text NOT NULL, -- ์ €์žฅ๋œ ํŒŒ์ผ๋ช… `origin_name` text, -- ์›๋ณธ ํŒŒ์ผ๋ช… `file_path` text NOT NULL, -- ํŒŒ์ผ ๊ฒฝ๋กœ `file_size` int NOT NULL DEFAULT '0', -- ํŒŒ์ผ ํฌ๊ธฐ (๊ธฐ๋ณธ๊ฐ’ 0) `reg_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, -- ๋“ฑ๋ก์ผ์‹œ `upd_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, -- ์ˆ˜์ •์ผ์‹œ `file_code` int NOT NULL DEFAULT '0', -- ํŒŒ์ผ ์ฝ”๋“œ (๊ธฐ๋ณธ๊ฐ’ 0) PRIMARY KEY (`no`) -- ์ฃผํ‚ค ์„ค์ • ) COMMENT='ํŒŒ์ผ';
SQL
๋ณต์‚ฌ

SQL ์ฟผ๋ฆฌ Mapper ์ž‘์„ฑ

โ€ข
FileMapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace="๋งคํผ ์ธํ„ฐํŽ˜์ด์Šค ๊ฒฝ๋กœ" --> <mapper namespace="com.joeun.server.mapper.FileMapper"> <!-- ํŒŒ์ผ ๋ชฉ๋ก --> <select id="list" resultType="Files"> SELECT * FROM file ORDER BY reg_date DESC </select> <!-- ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ํ…Œ์ด๋ธ” ๊ธฐ์ค€ --> <!-- * ํŒŒ์ผ์ด ์ข…์†๋˜๋Š” ํ…Œ์ด๋ธ”์„ ๊ธฐ์ค€์œผ๋กœ ํŒŒ์ผ ๋ชฉ๋ก์„ ์กฐํšŒ --> <!-- * ๊ฒŒ์‹œ๊ธ€ ๋ฒˆํ˜ธ 10 ๐Ÿ“„ ํŒŒ์ผ ๋ฒˆํ˜ธ 1 ๐Ÿ“„ ํŒŒ์ผ ๋ฒˆํ˜ธ 2 ๐Ÿ“„ ํŒŒ์ผ ๋ฒˆํ˜ธ 3 --> <select id="listByParent" resultType="Files"> SELECT * FROM file WHERE parent_table = #{parentTable} AND parent_no = #{parentNo} ORDER BY reg_date DESC </select> <!-- ํŒŒ์ผ ์กฐํšŒ --> <select id="select" resultType="Files"> SELECT * FROM file WHERE no = #{no} </select> <!-- ํŒŒ์ผ ๋“ฑ๋ก --> <insert id="insert"> INSERT INTO file( parent_table, parent_no, file_name, origin_name, file_path, file_size, file_code ) VALUES ( #{parentTable}, #{parentNo}, #{fileName}, #{originName}, #{filePath}, #{fileSize}, #{fileCode} ) </insert> <!-- ํŒŒ์ผ ์ˆ˜์ • --> <update id="update"> UPDATE file SET parent_table = #{parentTable} ,parent_no = #{parentNo} ,file_name = #{fileName} ,origin_name = #{originName} ,file_path = #{filePath} ,file_size = #{fileSize} ,file_code = #{fileCode} WHERE no = #{no} </update> <!-- ํŒŒ์ผ ์‚ญ์ œ --> <delete id="delete"> DELETE FROM file WHERE no = #{no} </delete> <!-- ํŒŒ์ผ ๋ชฉ๋ก ์‚ญ์ œ - ๋ถ€๋ชจ ํ…Œ์ด๋ธ” ๊ธฐ์ค€ ํŒŒ์ผ ๋ชฉ๋ก ์‚ญ์ œ --> <delete id="deleteByParent"> DELETE FROM file WHERE parent_table = #{parentTable} AND parent_no = #{parentNo} </delete> <!-- ๊ฒŒ์‹œ๊ธ€ ๋ฒˆํ˜ธ ์ตœ๋Œ“๊ฐ’ --> <select id="maxPk" resultType="int"> SELECT MAX(no) FROM file </select> </mapper>
SQL
๋ณต์‚ฌ

DTO

โ€ข
Files.java
package com.joeun.server.dto; import java.util.Date; import lombok.Data; @Data public class Files { private int no; private String parentTable; private int parentNo; private String fileName; private String originName; private String filePath; private long fileSize; private Date regDate; private Date updDate; private int fileCode; }
Java
๋ณต์‚ฌ

Mapper

โ€ข
FileMapper.java
package com.joeun.server.mapper; import java.util.List; import org.apache.ibatis.annotations.Mapper; import com.joeun.server.dto.Files; @Mapper public interface FileMapper { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throws Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ public int delete(int no) throws Exception; // ํŒŒ์ผ ๋ฒˆํ˜ธ(๊ธฐ๋ณธํ‚ค) ์ตœ๋Œ“๊ฐ’ public int maxPk() throws Exception; // ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ๊ธฐ์ค€ public List<Files> listByParent(Files file) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ - ๋ถ€๋ชจ ๊ธฐ์ค€ public int deleteByParent(Files file) throws Exception; }
Java
๋ณต์‚ฌ

Service

โ€ข
FileService.java
โ€ข
FileServiceImpl.java
โ€ข
BoardServiceImpl.java
โ€ข
FileService.java
package com.joeun.server.service; import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import jakarta.servlet.http.HttpServletResponse; public interface FileService { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throws Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Files file, List<MultipartFile> fileList) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int upload(Files file) throws Exception; }
Java
๋ณต์‚ฌ
โ€ข
FileServiceImpl.java
package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file ); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); // DB ์— ๋ฐ์ดํ„ฐ ๋“ฑ๋ก result = fileMapper.insert(uploadedFile); return result; } }
Java
๋ณต์‚ฌ
โ€ข
BoardServiceImpl.java
package com.joeun.server.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Board; import com.joeun.server.dto.Files; import com.joeun.server.mapper.BoardMapper; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class BoardServiceImpl implements BoardService { @Autowired private BoardMapper boardMapper; @Autowired private FileService fileService; @Override public List<Board> list() throws Exception { List<Board> boardList = boardMapper.list(); return boardList; } @Override public Board select(int no) throws Exception { Board board = boardMapper.select(no); return board; } @Override @Transactional public int insert(Board board) throws Exception { int result = boardMapper.insert(board); // ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๋ฐ์ดํ„ฐ์˜ pk ๊ฐ€์ ธ์˜ด int parentNo = boardMapper.maxPk(); board.setNo(parentNo); // โœ…(New) ํŒŒ์ผ ์—…๋กœ๋“œ result += uploadFiles(board); return result; } @Override public int update(Board board) throws Exception { int result = boardMapper.update(board); // ํŒŒ์ผ ์—…๋กœ๋“œ result += uploadFiles(board); return result; } @Override public int delete(int no) throws Exception { int result = boardMapper.delete(no); return result; } @Override public int updateViews(int count, int no) throws Exception { int result = boardMapper.updateViews(count, no); return result; } // โœ…(New) ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Board board) throws Exception { String parentTable = "board"; int parentNo = board.getNo(); int result = 0; List<MultipartFile> fileList = board.getFiles(); if( fileList != null && !fileList.isEmpty() ) { Files fileInfo = new Files(); fileInfo.setParentTable(parentTable); fileInfo.setParentNo(parentNo); result = fileService.uploadFiles(fileInfo, fileList); } return result; } }
Java
๋ณต์‚ฌ

Controller

โ€ข
FileController.java
package com.joeun.server.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import lombok.extern.slf4j.Slf4j; @Slf4j @Controller @RequestMapping("/file") public class FileController { }
Java
๋ณต์‚ฌ

ํŒŒ์ผ ์‹œ์Šคํ…œ์— ๋“ฑ๋ก

1.
DTO
2.
Service

DTO

โ€ข
Files.java
package com.joeun.server.dto; import java.util.Date; import org.springframework.web.multipart.MultipartFile; import lombok.Data; @Data public class Files { private int no; private String parentTable; private int parentNo; private String fileName; private String originName; private String filePath; private long fileSize; private Date regDate; private Date updDate; private int fileCode; MultipartFile file; // โœ… ์ถ”๊ฐ€ }
Java
๋ณต์‚ฌ

Service

โ€ข
FileServiceImpl.java
์ถ”๊ฐ€ํ•  ์ฝ”๋“œ
// โœ…(New) ํŒŒ์ผ์—…๋กœ๋“œ // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); byte[] fileData = file.getBytes(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // โœ…(New) ํŒŒ์ผ์—…๋กœ๋“œ // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); result = fileMapper.insert(uploadedFile); return result; } }
Java
๋ณต์‚ฌ

๏ธŽ ํŒŒ์ผ ์กฐํšŒ

ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ

โ€ข
FileService.java
โ€ข
FileServiceImpl.java
โ€ข
BoardServiceImpl.java
โ€ข
BoardController.java

FileService.java

package com.joeun.server.service; import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import jakarta.servlet.http.HttpServletResponse; public interface FileService { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throws Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Files file, List<MultipartFile> fileList) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int upload(Files file) throws Exception; // โœ…(New) ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ๊ธฐ์ค€ public List<Files> listByParent(Files file) throws Exception; }
Java
๋ณต์‚ฌ

FileServiceImpl.java

package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; } @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); byte[] fileData = file.getBytes(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); result = fileMapper.insert(uploadedFile); return result; } // โœ… (New) - ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } }
Java
๋ณต์‚ฌ

BoardController.java

์ถ”๊ฐ€ํ•  ์ฝ”๋“œ
@GetMapping("/{no}") public ResponseEntity<?> getOne(@PathVariable Integer no, Files files) { log.info("[GET] - /boards/" + no + " - ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ"); try { Board board = boardService.select(no); files.setParentTable("board"); files.setParentNo(no); List<Files> fileList = fileService.listByParent(files); // ํŒŒ์ผ ์ •๋ณด Map<String, Object> response = new HashMap<>(); response.put("board", board); response.put("fileList", fileList); if( board == null ) { board = new Board(); board.setTitle("๋ฐ์ดํ„ฐ ์—†์Œ"); } return new ResponseEntity<>(response, HttpStatus.OK); } catch (Exception e) { log.error(null, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } }
Java
๋ณต์‚ฌ
package com.joeun.server.controller; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Board; import com.joeun.server.dto.Files; import com.joeun.server.service.BoardService; import com.joeun.server.service.FileService; import lombok.extern.slf4j.Slf4j; @Slf4j @RestController @RequestMapping("/boards") public class BoardController { @Autowired private BoardService boardService; @Autowired private FileService fileService; // ๐Ÿ‘ฉโ€๐Ÿ’ป CRUD ๋ฉ”์†Œ๋“œ ์ž๋™ ์ƒ์„ฑ : sp-crud // ๐Ÿ‘ฉโ€๐Ÿ’ป ์ž๋™ import : alt + shift + O // ๐Ÿ‘ฉโ€๐Ÿ’ป ํ•œ ์ค„ ์‚ญ์ œ : ctrl + shift + K @GetMapping() public ResponseEntity<?> getAll() { log.info("[GET] - /boards - ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก"); try { List<Board> boardList = boardService.list(); if( boardList == null ) log.info("์กฐํšŒ๋œ ๊ฒŒ์‹œ๊ธ€ ์—†์Œ"); else log.info("๊ฒŒ์‹œ๊ธ€ ์ˆ˜ : " + boardList.size()); return new ResponseEntity<>(boardList, HttpStatus.OK); } catch (Exception e) { log.error(null, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @GetMapping("/{no}") public ResponseEntity<?> getOne(@PathVariable Integer no, Files files) { log.info("[GET] - /boards/" + no + " - ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ"); try { Board board = boardService.select(no); files.setParentTable("board"); files.setParentNo(no); List<Files> fileList = fileService.listByParent(files); // ํŒŒ์ผ ์ •๋ณด Map<String, Object> response = new HashMap<>(); response.put("board", board); response.put("fileList", fileList); if( board == null ) { board = new Board(); board.setTitle("๋ฐ์ดํ„ฐ ์—†์Œ"); } return new ResponseEntity<>(response, HttpStatus.OK); } catch (Exception e) { log.error(null, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @PostMapping() // public ResponseEntity<?> create(@RequestBody Board board) { // Content-Type : application/json public ResponseEntity<?> create(Board board) { // Content-Type : multipart/form-data log.info("[POST] - /boards - ๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก"); log.info("board : " + board.toString()); List<MultipartFile> files = board.getFiles(); if( files != null ) for (MultipartFile file : files) { log.info("file : " + file.getOriginalFilename()); } try { int result = boardService.insert(board); if( result > 0 ) return new ResponseEntity<>("๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก ์™„๋ฃŒ", HttpStatus.CREATED); // 201 else return new ResponseEntity<>("๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก ์‹คํŒจ", HttpStatus.OK); } catch (Exception e) { log.error(null, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @PutMapping() // public ResponseEntity<?> update(@RequestBody Board board) { // Content-Type : application/json public ResponseEntity<?> update(Board board) { // Content-Type : multipart/form-data log.info("[PUT] - /boards - ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •"); try { int result = boardService.update(board); log.info("์ˆ˜์ • : " + board); if( result > 0 ) return new ResponseEntity<>("๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • ์™„๋ฃŒ", HttpStatus.OK); else return new ResponseEntity<>("๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • ์‹คํŒจ", HttpStatus.OK); } catch (Exception e) { log.error(null, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @DeleteMapping("/{no}") public ResponseEntity<?> destroy(@PathVariable Integer no) { log.info("[DELETE] - /boards/" + no + " - ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ"); try { int result = boardService.delete(no); if( result > 0 ) return new ResponseEntity<>("๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ ์™„๋ฃŒ", HttpStatus.OK); else return new ResponseEntity<>("๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ ์‹คํŒจ", HttpStatus.OK); } catch (Exception e) { log.error(null, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } }
Java
๋ณต์‚ฌ

๋‹ค์šด๋กœ๋“œ

โ€ข
FileService.java
โ€ข
FileServiceImpl.java
โ€ข
FileController.java

FileService.java

package com.joeun.server.service; import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import jakarta.servlet.http.HttpServletResponse; public interface FileService { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throws Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Files file, List<MultipartFile> fileList) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int upload(Files file) throws Exception; // ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ๊ธฐ์ค€ public List<Files> listByParent(Files file) throws Exception; // โœ…(New) - ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ public int download(int no, HttpServletResponse response) throws Exception; }
Java
๋ณต์‚ฌ

FileServiceImpl.java

package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; } @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); byte[] fileData = file.getBytes(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); result = fileMapper.insert(uploadedFile); return result; } // ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int download(int fileNo, HttpServletResponse response) throws Exception { // result // 0 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์ฒ˜๋ฆฌ ์‹คํŒจ // 1 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์„ฑ๊ณต Files file = fileMapper.select(fileNo); if( file == null ) { // BAD_REQUEST : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ์•Œ๋ ค์ฃผ๋Š” ์ƒํƒœ์ฝ”๋“œ // response.setStatus(response.SC_BAD_REQUEST); return 0; } String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ String fileName = file.getFileName(); // ํŒŒ์ผ ์ด๋ฆ„ // ๋‹ค์šด๋กœ๋“œ ์‘๋‹ต์„ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-stream // - Content-Disposition : attachment, filename="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ // - ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); // - ํŒŒ์ผ ์ถœ๋ ฅ ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); fis.close(); sos.close(); return 1; } }
Java
๋ณต์‚ฌ

FileController.java

package com.joeun.server.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import lombok.extern.slf4j.Slf4j; @Slf4j @Controller @RequestMapping("/file") public class FileController { @Autowired private FileService fileService; /** * ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ * @param fileNo * @param response * @throws Exception */ @GetMapping("/{no}") public void fileDownload(@PathVariable("no") int no ,HttpServletResponse response) throws Exception { // ํŒŒ์ผ ์กฐํšŒ Files file = fileService.select(no); // ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด, if( file == null ) { // ์‘๋‹ต ์ƒํƒœ์ฝ”๋“œ : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ƒํƒœ์ฝ”๋“œ response.setStatus(HttpServletResponse.SC_BAD_REQUEST); return; } String fileName = file.getFileName(); // ํŒŒ์ผ ๋ช… String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-straem // - Content-Disposition : attachment; fileanme="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); } }
Java
๋ณต์‚ฌ

์ธ๋„ค์ผ ์ด๋ฏธ์ง€ ๋ณด๊ธฐ

โ€ข
FileController.java

FileController.java

package com.joeun.server.controller; import java.io.File; import java.io.FileInputStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.FileCopyUtils; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.joeun.server.dto.Files; import com.joeun.server.service.FileService; import com.joeun.server.utils.MediaUtil; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Controller @RequestMapping("/file") public class FileController { @Autowired private FileService fileService; @Autowired private ResourceLoader resourceLoader; /** * ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ * @param fileNo * @param response * @throws Exception */ @GetMapping("/{no}") public void fileDownload(@PathVariable("no") int no ,HttpServletResponse response) throws Exception { // ํŒŒ์ผ ์กฐํšŒ Files file = fileService.select(no); // ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด, if( file == null ) { // ์‘๋‹ต ์ƒํƒœ์ฝ”๋“œ : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ƒํƒœ์ฝ”๋“œ response.setStatus(HttpServletResponse.SC_BAD_REQUEST); return; } String fileName = file.getFileName(); // ํŒŒ์ผ ๋ช… String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-straem // - Content-Disposition : attachment; fileanme="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); } /** * ์ด๋ฏธ์ง€ ์ธ๋„ค์ผ * @param no * @param response * @throws Exception */ @GetMapping("/img/{no}") public void showImg(@PathVariable Integer no, HttpServletResponse response) throws Exception { Files file = fileService.select(no); String filePath = (file != null) ? file.getFilePath() : null; Resource resource = resourceLoader.getResource("classpath:static/img/no-image.png"); File imgFile; if (filePath == null || !(imgFile = new File(filePath)).exists()) { // ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ํŒŒ์ผ ๊ฒฝ๋กœ๊ฐ€ null์ธ ๊ฒฝ์šฐ imgFile = resource.getFile(); } String ext = filePath.substring(filePath.lastIndexOf(".") + 1); MediaType mType = MediaUtil.getMediaType(ext); if (mType == null) { // ์ด๋ฏธ์ง€ ํƒ€์ž…์ด ์•„๋‹ ๊ฒฝ์šฐ response.setContentType(MediaType.IMAGE_PNG_VALUE); // ๊ธฐ๋ณธ์ ์œผ๋กœ PNG๋กœ ์„ค์ • imgFile = resource.getFile(); } else { // ์ด๋ฏธ์ง€ ํƒ€์ž…์ผ ๊ฒฝ์šฐ response.setContentType(mType.toString()); } FileInputStream fis = new FileInputStream(imgFile); ServletOutputStream sos = response.getOutputStream(); FileCopyUtils.copy(fis, sos); } }
Java
๋ณต์‚ฌ

ํŒŒ์ผ ์‚ญ์ œ

๊ฒŒ์‹œ๊ธ€์— ์ข…์†๋œ ์—ฌ๋Ÿฌ ํŒŒ์ผ ์‚ญ์ œ

โ€ข
FileService.java
โ€ข
FileServiceImpl.java
โ€ข
BoardServiceImpl.java
โ€ข
FileService.java
package com.joeun.server.service; import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import jakarta.servlet.http.HttpServletResponse; public interface FileService { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throwsdeleteByParent Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ public int delete(int no) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Files file, List<MultipartFile> fileList) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int upload(Files file) throws Exception; // ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ๊ธฐ์ค€ public List<Files> listByParent(Files file) throws Exception; // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ public int download(int no, HttpServletResponse response) throws Exception; // โœ…(New) ํŒŒ์ผ ์‚ญ์ œ - ๋ถ€๋ชจ ๊ธฐ์ค€ public int deleteByParent(Files fileInfo) throws Exception; }
Java
๋ณต์‚ฌ
โ€ข
FileServiceImpl.java
์ถ”๊ฐ€ํ•  ์ฝ”๋“œ
@Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; }
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; } @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); byte[] fileData = file.getBytes(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); result = fileMapper.insert(uploadedFile); return result; } // ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int download(int fileNo, HttpServletResponse response) throws Exception { // result // 0 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์ฒ˜๋ฆฌ ์‹คํŒจ // 1 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์„ฑ๊ณต Files file = fileMapper.select(fileNo); if( file == null ) { // BAD_REQUEST : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ์•Œ๋ ค์ฃผ๋Š” ์ƒํƒœ์ฝ”๋“œ // response.setStatus(response.SC_BAD_REQUEST); return 0; } String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ String fileName = file.getFileName(); // ํŒŒ์ผ ์ด๋ฆ„ // ๋‹ค์šด๋กœ๋“œ ์‘๋‹ต์„ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-stream // - Content-Disposition : attachment, filename="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ // - ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); // - ํŒŒ์ผ ์ถœ๋ ฅ ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); fis.close(); sos.close(); return 1; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } }
Java
๋ณต์‚ฌ
โ€ข
BoardServiceImpl.java
์ถ”๊ฐ€ํ•  ์ฝ”๋“œ
@Override @Transactional public int delete(int no) throws Exception { int result = boardMapper.delete(no); String parentTable = "board"; int parentNo = no; if( result > 0 ) { Files fileInfo = new Files(); fileInfo.setParentTable(parentTable); fileInfo.setParentNo(parentNo); int fileResult = fileService.deleteByParent(fileInfo); result = fileResult; } return result; }
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Board; import com.joeun.server.dto.Files; import com.joeun.server.mapper.BoardMapper; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class BoardServiceImpl implements BoardService { @Autowired private BoardMapper boardMapper; @Autowired private FileService fileService; @Override public List<Board> list() throws Exception { List<Board> boardList = boardMapper.list(); return boardList; } @Override public Board select(int no) throws Exception { Board board = boardMapper.select(no); return board; } @Override @Transactional public int insert(Board board) throws Exception { int result = boardMapper.insert(board); // ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๋ฐ์ดํ„ฐ์˜ pk ๊ฐ€์ ธ์˜ด int parentNo = boardMapper.maxPk(); board.setNo(parentNo); result += uploadFiles(board); return result; } @Override public int update(Board board) throws Exception { int result = boardMapper.update(board); // ํŒŒ์ผ ์—…๋กœ๋“œ result += uploadFiles(board); return result; } @Override public int updateViews(int count, int no) throws Exception { int result = boardMapper.updateViews(count, no); return result; } public int uploadFiles(Board board) throws Exception { String parentTable = "board"; int parentNo = board.getNo(); int result = 0; List<MultipartFile> fileList = board.getFiles(); if( fileList != null && !fileList.isEmpty() ) { Files fileInfo = new Files(); fileInfo.setParentTable(parentTable); fileInfo.setParentNo(parentNo); result = fileService.uploadFiles(fileInfo, fileList); } return result; } @Override @Transactional public int delete(int no) throws Exception { int result = boardMapper.delete(no); String parentTable = "board"; int parentNo = no; // โœ…(New) - ๊ฒŒ์‹œ๊ธ€์— ์ข…์†๋œ ํŒŒ์ผ ์‚ญ์ œ if( result > 0 ) { Files fileInfo = new Files(); fileInfo.setParentTable(parentTable); fileInfo.setParentNo(parentNo); int fileResult = fileService.deleteByParent(fileInfo); result = fileResult; } return result; } }
Java
๋ณต์‚ฌ

์„ ํƒ ํŒŒ์ผ ์‚ญ์ œ

โ€ข
FileService.java
โ€ข
FileServiceImpl.java
โ€ข
BoardServiceImpl.java
โ€ข
FileService.java
package com.joeun.server.service; import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import jakarta.servlet.http.HttpServletResponse; public interface FileService { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throws Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ public int delete(int no) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Files file, List<MultipartFile> fileList) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int upload(Files file) throws Exception; // ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ๊ธฐ์ค€ public List<Files> listByParent(Files file) throws Exception; // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ public int download(int no, HttpServletResponse response) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ - ๋ถ€๋ชจ ๊ธฐ์ค€ public int deleteByParent(Files fileInfo) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ - noList ๊ธฐ์ค€ public int deleteByNoList(List<Integer> noList) throws Exception; }
Java
๋ณต์‚ฌ
โ€ข
FileServiceImpl.java
์ถ”๊ฐ€ํ•  ์ฝ”๋“œ
@Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; }
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; } @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); byte[] fileData = file.getBytes(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); result = fileMapper.insert(uploadedFile); return result; } // ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int download(int fileNo, HttpServletResponse response) throws Exception { // result // 0 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์ฒ˜๋ฆฌ ์‹คํŒจ // 1 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์„ฑ๊ณต Files file = fileMapper.select(fileNo); if( file == null ) { // BAD_REQUEST : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ์•Œ๋ ค์ฃผ๋Š” ์ƒํƒœ์ฝ”๋“œ // response.setStatus(response.SC_BAD_REQUEST); return 0; } String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ String fileName = file.getFileName(); // ํŒŒ์ผ ์ด๋ฆ„ // ๋‹ค์šด๋กœ๋“œ ์‘๋‹ต์„ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-stream // - Content-Disposition : attachment, filename="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ // - ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); // - ํŒŒ์ผ ์ถœ๋ ฅ ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); fis.close(); sos.close(); return 1; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } }
Java
๋ณต์‚ฌ
โ€ข
BoardServiceImpl.java
์ถ”๊ฐ€ํ•  ์ฝ”๋“œ
@Override public int update(Board board) throws Exception { int result = boardMapper.update(board); // ํŒŒ์ผ ์—…๋กœ๋“œ result += uploadFiles(board); // ๊ฐœ๋ณ„ ํŒŒ์ผ ์‚ญ์ œ List<Integer> deleteFileNoList = board.getDeleteFileNoList(); result += fileService.deleteByNoList(deleteFileNoList); return result; }
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Board; import com.joeun.server.dto.Files; import com.joeun.server.mapper.BoardMapper; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class BoardServiceImpl implements BoardService { @Autowired private BoardMapper boardMapper; @Autowired private FileService fileService; @Override public List<Board> list() throws Exception { List<Board> boardList = boardMapper.list(); return boardList; } @Override public Board select(int no) throws Exception { Board board = boardMapper.select(no); return board; } @Override @Transactional public int insert(Board board) throws Exception { int result = boardMapper.insert(board); // ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๋ฐ์ดํ„ฐ์˜ pk ๊ฐ€์ ธ์˜ด int parentNo = boardMapper.maxPk(); board.setNo(parentNo); result += uploadFiles(board); return result; } @Override public int update(Board board) throws Exception { int result = boardMapper.update(board); // ํŒŒ์ผ ์—…๋กœ๋“œ result += uploadFiles(board); // โœ…(New) ๊ฐœ๋ณ„ ํŒŒ์ผ ์‚ญ์ œ List<Integer> deleteFileNoList = board.getDeleteFileNoList(); result += fileService.deleteByNoList(deleteFileNoList); return result; } @Override public int updateViews(int count, int no) throws Exception { int result = boardMapper.updateViews(count, no); return result; } public int uploadFiles(Board board) throws Exception { String parentTable = "board"; int parentNo = board.getNo(); int result = 0; List<MultipartFile> fileList = board.getFiles(); if( fileList != null && !fileList.isEmpty() ) { Files fileInfo = new Files(); fileInfo.setParentTable(parentTable); fileInfo.setParentNo(parentNo); result = fileService.uploadFiles(fileInfo, fileList); } return result; } @Override @Transactional public int delete(int no) throws Exception { int result = boardMapper.delete(no); String parentTable = "board"; int parentNo = no; if( result > 0 ) { Files fileInfo = new Files(); fileInfo.setParentTable(parentTable); fileInfo.setParentNo(parentNo); int fileResult = fileService.deleteByParent(fileInfo); result = fileResult; } return result; } }
Java
๋ณต์‚ฌ

๊ฐœ๋ณ„ ํŒŒ์ผ ์‚ญ์ œ

โ€ข
FileService.java
โ€ข
FileServiceImpl.java
โ€ข
FileController.java

FileService.java

// ํŒŒ์ผ ์‚ญ์ œ public int delete(int no) throws Exception;
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import jakarta.servlet.http.HttpServletResponse; public interface FileService { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throws Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ public int delete(int no) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Files file, List<MultipartFile> fileList) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int upload(Files file) throws Exception; // ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ๊ธฐ์ค€ public List<Files> listByParent(Files file) throws Exception; // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ public int download(int no, HttpServletResponse response) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ - ๋ถ€๋ชจ ๊ธฐ์ค€ public int deleteByParent(Files fileInfo) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ - noList ๊ธฐ์ค€ public int deleteByNoList(List<Integer> noList) throws Exception; }
Java
๋ณต์‚ฌ

FileServiceImpl.java

@Override public int delete(int no) throws Exception { Files file = fileMapper.select(no); if( file == null ) return 0; String filePath = file.getFilePath(); File deleteFile = new File(filePath); if( !deleteFile.exists() ) return 0; boolean deleted = deleteFile.delete(); int result = 0; if( deleted ) { result = fileMapper.delete(no); } return result; }
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; } @Override public int delete(int no) throws Exception { Files file = fileMapper.select(no); if( file == null ) return 0; String filePath = file.getFilePath(); File deleteFile = new File(filePath); if( !deleteFile.exists() ) return 0; boolean deleted = deleteFile.delete(); int result = 0; if( deleted ) { result = fileMapper.delete(no); } return result; } @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int download(int fileNo, HttpServletResponse response) throws Exception { // result // 0 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์ฒ˜๋ฆฌ ์‹คํŒจ // 1 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์„ฑ๊ณต Files file = fileMapper.select(fileNo); if( file == null ) { // BAD_REQUEST : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ์•Œ๋ ค์ฃผ๋Š” ์ƒํƒœ์ฝ”๋“œ // response.setStatus(response.SC_BAD_REQUEST); return 0; } String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ String fileName = file.getFileName(); // ํŒŒ์ผ ์ด๋ฆ„ // ๋‹ค์šด๋กœ๋“œ ์‘๋‹ต์„ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-stream // - Content-Disposition : attachment, filename="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ // - ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); // - ํŒŒ์ผ ์ถœ๋ ฅ ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); // byte[] buffer = new byte[1024]; // 1024bytes = 1KB ๋‹จ์œ„ ๋ฒ„ํผ // int data; // while( (data = fis.read(buffer)) != -1 ) { // 1KB ์”ฉ ํŒŒ์ผ์ž…๋ ฅ // sos.write(buffer, 0, data); // 1KB ์”ฉ ํŒŒ์ผ์ถœ๋ ฅ // } fis.close(); sos.close(); return 1; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); byte[] fileData = file.getBytes(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); result = fileMapper.insert(uploadedFile); return result; } }
Java
๋ณต์‚ฌ

FileController.java

package com.joeun.server.controller; import java.io.File; import java.io.FileInputStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.FileCopyUtils; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.joeun.server.dto.Files; import com.joeun.server.service.FileService; import com.joeun.server.utils.MediaUtil; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Controller @RequestMapping("/file") public class FileController { @Autowired private FileService fileService; @Autowired private ResourceLoader resourceLoader; /** * ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ * @param fileNo * @param response * @throws Exception */ @GetMapping("/{no}") public void fileDownload(@PathVariable("no") int no ,HttpServletResponse response) throws Exception { // ํŒŒ์ผ ์กฐํšŒ Files file = fileService.select(no); // ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด, if( file == null ) { // ์‘๋‹ต ์ƒํƒœ์ฝ”๋“œ : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ƒํƒœ์ฝ”๋“œ response.setStatus(HttpServletResponse.SC_BAD_REQUEST); return; } String fileName = file.getFileName(); // ํŒŒ์ผ ๋ช… String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-straem // - Content-Disposition : attachment; fileanme="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); } /** * ์ด๋ฏธ์ง€ ์ธ๋„ค์ผ * @param no * @param response * @throws Exception */ @GetMapping("/img/{no}") public void showImg(@PathVariable Integer no, HttpServletResponse response) throws Exception { Files file = fileService.select(no); String filePath = (file != null) ? file.getFilePath() : null; Resource resource = resourceLoader.getResource("classpath:static/img/no-image.png"); File imgFile; if (filePath == null || !(imgFile = new File(filePath)).exists()) { // ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ํŒŒ์ผ ๊ฒฝ๋กœ๊ฐ€ null์ธ ๊ฒฝ์šฐ imgFile = resource.getFile(); } String ext = filePath.substring(filePath.lastIndexOf(".") + 1); MediaType mType = MediaUtil.getMediaType(ext); if (mType == null) { // ์ด๋ฏธ์ง€ ํƒ€์ž…์ด ์•„๋‹ ๊ฒฝ์šฐ response.setContentType(MediaType.IMAGE_PNG_VALUE); // ๊ธฐ๋ณธ์ ์œผ๋กœ PNG๋กœ ์„ค์ • imgFile = resource.getFile(); } else { // ์ด๋ฏธ์ง€ ํƒ€์ž…์ผ ๊ฒฝ์šฐ response.setContentType(mType.toString()); } FileInputStream fis = new FileInputStream(imgFile); ServletOutputStream sos = response.getOutputStream(); FileCopyUtils.copy(fis, sos); } /** * ํŒŒ์ผ ์‚ญ์ œ * @param file * @return * @throws Exception */ @DeleteMapping("/{no}") public ResponseEntity<String> deleteFile(@PathVariable Integer no) throws Exception { log.info("[DELETE] - /file"); int fileNo = no; log.info("fileNo : " + fileNo); if( fileNo == 0 ) return new ResponseEntity<String>("FAIL", HttpStatus.BAD_REQUEST); int result = fileService.delete(fileNo); if( result == 0 ) return new ResponseEntity<String>("FAIL", HttpStatus.OK); return new ResponseEntity<String>("SUCCESS", HttpStatus.OK); } }
Java
๋ณต์‚ฌ

React (Front-End)

React ๋กœ ํŒŒ์ผ ์—…๋กœ๋“œ, ์กฐํšŒ, ์‚ญ์ œ UI ๋ฐ API ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

ํŒŒ์ผ ์—…๋กœ๋“œ

ํŒŒ์ผ ๋“ฑ๋ก UI ์ถ”๊ฐ€

BoardInsertForm.jsx

import React, { useState } from 'react' import { Link } from 'react-router-dom' import './BoardInsertForm.css' const BoardInsertForm = ({ onInsert }) => { // state ์„ค์ • const [title, setTitle] = useState(''); const [writer, setWriter] = useState(''); const [content, setContent] = useState(''); const [files, setFiles] = useState(null); // โœ… files state ์ถ”๊ฐ€ const handleChangeTitle = (e) => { setTitle(e.target.value) } const handleChangeWriter = (e) => { setWriter(e.target.value) } const handleChangeContent = (e) => { setContent(e.target.value) } // โœ… ํŒŒ์ผ ํ•ธ๋“ค๋Ÿฌ ์ถ”๊ฐ€ const handleFileChange = (e) => { setFiles(e.target.files); }; const onSubmit = () => { const formData = new FormData(); formData.append('title', title); formData.append('writer', writer); formData.append('content', content); const headers = { headers: { 'Content-Type' : 'multipart/form-data', }, }; if (files) { for (let i = 0; i < files.length; i++) { formData.append(`files[${i}]`, files[i]); } } // onInsert(title, writer, content) // onInsert({title,writer,content,files}, headers) // formData ์‚ฌ์šฉ โŒ onInsert(formData, headers) // formData ์‚ฌ์šฉ โญ• } return ( <div className='container'> <h1 className='title'>๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก</h1> <table className='table'> <tbody> <tr> <td>์ œ๋ชฉ</td> <td> <input type="text" className='form-input' value={title} onChange={handleChangeTitle} /> </td> </tr> <tr> <td>์ž‘์„ฑ์ž</td> <td> <input type="text" className='form-input' value={writer} onChange={handleChangeWriter} /> </td> </tr> <tr> <td colSpan={2}>๋‚ด์šฉ</td> </tr> <tr> <td colSpan={2}> <textarea cols="40" rows="10" className='form-input' value={content} onChange={handleChangeContent} ></textarea> </td> </tr> <tr> <td>ํŒŒ์ผ</td> <td> <input type='file' onChange={handleFileChange} multiple /> </td> </tr> </tbody> </table> <div className='btn-box'> <Link to="/boards" className='btn'>๋ชฉ๋ก</Link> <button onClick={ onSubmit } className='btn'>๋“ฑ๋ก</button> </div> </div> ) } export default BoardInsertForm
JavaScript
๋ณต์‚ฌ

ํŒŒ์ผ ์—…๋กœ๋“œ ์š”์ฒญ API

files.js

import axios from 'axios'; // ์—…๋กœ๋“œ export const upload = (formData, headers) => axios.post(`/file/upload`, formData, headers)
JavaScript
๋ณต์‚ฌ

๏ธŽ ํŒŒ์ผ ์กฐํšŒ

ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ UI ๋ฐ API ์ถ”๊ฐ€

BoardReadContainer.jsx

import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import * as boards from '../apis/boards'; import BoardRead from '../components/BoardRead/BoardRead'; // ๐Ÿ‘ฉโ€๐Ÿ’ป ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ const BoardReadContainer = () => { const { no } = useParams() const [board, setBoard] = useState({}); const [fileList, setFileList] = useState([]); const [isLoading, setLoading] = useState(false); const getBoard = async () => { setLoading(true) try { const response = await boards.select(no); const data = response.data; console.log(data); const board = data.board; const fileList = data.fileList; setBoard(board); setFileList(fileList); } catch(e) { console.log(e); } setLoading(false) } useEffect( () => { getBoard() },[]) return (<BoardRead no={no} board={board} fileList={fileList} isLoading={isLoading} />) } export default BoardReadContainer
JavaScript
๋ณต์‚ฌ

BoardRead.jsx

import React, { useEffect } from 'react'; import { Link } from 'react-router-dom'; import * as format from '../../apis/format'; import './BoardRead.css'; const BoardRead = ({ no, board, fileList, isLoading }) => { return ( <div className='container'> <h1 className='title'>๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ</h1> <h3>๋ฒˆํ˜ธ : {no}</h3> <hr/> { isLoading && <div> <img src="/img/loading.webp" alt="loading" /> </div> } { !isLoading && board && ( <table border={1} className='table'> <tbody> <tr> <td>๋ฒˆํ˜ธ</td> <td> <input type="text" className='form-input' value={board.no} readOnly /> </td> </tr> <tr> <td>๋“ฑ๋ก์ผ์ž</td> <td> <input type="text" className='form-input' value={format.formatDate( board.regDate )} readOnly /> </td> </tr> <tr> <td>์ œ๋ชฉ</td> <td> <input type="text" className='form-input' value={board.title} readOnly /> </td> </tr> <tr> <td>์ž‘์„ฑ์ž</td> <td> <input type="text" className='form-input' value={board.writer} readOnly /> </td> </tr> <tr> <td colSpan={2}>๋‚ด์šฉ</td> </tr> <tr> <td colSpan={2}> <textarea cols="40" rows="10" className='form-input' value={board.content} readOnly></textarea> </td> </tr> <tr> <td colSpan={2}>ํŒŒ์ผ</td> </tr> <tr> <td colSpan={2}> { fileList.map( (file) => ( <div className='file-box' key={file.no}> <div className="item"> <img src={`/file/img/${file.no}`} alt={file.fileName} /> <span>{file.originName}({ format.byteToUnit(file.fileSize) })</span> </div> <div className="item"> </div> </div> ))} </td> </tr> </tbody> </table> )} <hr /> <div className="btn-box"> <Link className='btn' to="/boards">๋ชฉ๋ก</Link> <Link className='btn' to={`/boards/update/${no}`}>์ˆ˜์ •</Link> </div> </div> ) } export default BoardRead
JavaScript
๋ณต์‚ฌ

๋‹ค์šด๋กœ๋“œ UI ๋ฐ API ์ถ”๊ฐ€

BoardRead.jsx

import React, { useEffect } from 'react'; import { Link } from 'react-router-dom'; import * as format from '../../apis/format'; import './BoardRead.css'; const BoardRead = ({ no, board, fileList, isLoading }) => { return ( <div className='container'> <h1 className='title'>๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ</h1> <h3>๋ฒˆํ˜ธ : {no}</h3> <hr/> { isLoading && <div> <img src="/img/loading.webp" alt="loading" /> </div> } { !isLoading && board && ( <table border={1} className='table'> <tbody> <tr> <td>๋ฒˆํ˜ธ</td> <td> <input type="text" className='form-input' value={board.no} readOnly /> </td> </tr> <tr> <td>๋“ฑ๋ก์ผ์ž</td> <td> <input type="text" className='form-input' value={format.formatDate( board.regDate )} readOnly /> </td> </tr> <tr> <td>์ œ๋ชฉ</td> <td> <input type="text" className='form-input' value={board.title} readOnly /> </td> </tr> <tr> <td>์ž‘์„ฑ์ž</td> <td> <input type="text" className='form-input' value={board.writer} readOnly /> </td> </tr> <tr> <td colSpan={2}>๋‚ด์šฉ</td> </tr> <tr> <td colSpan={2}> <textarea cols="40" rows="10" className='form-input' value={board.content} readOnly></textarea> </td> </tr> <tr> <td colSpan={2}>ํŒŒ์ผ</td> </tr> <tr> <td colSpan={2}> { fileList.map( (file) => ( <div className='file-box' key={file.no}> <div className="item"> <img src={`/file/img/${file.no}`} alt={file.fileName} /> <span>{file.originName}({ format.byteToUnit(file.fileSize) })</span> </div> <div className="item"> <button className="btn" onClick={() => handleDownload(file.no, file.originName)}>๋‹ค์šด๋กœ๋“œ</button> </div> </div> ))} </td> </tr> </tbody> </table> )} <hr /> <div className="btn-box"> <Link className='btn' to="/boards">๋ชฉ๋ก</Link> <Link className='btn' to={`/boards/update/${no}`}>์ˆ˜์ •</Link> </div> </div> ) } export default BoardRead
JavaScript
๋ณต์‚ฌ

files.js

JavaScript
๋ณต์‚ฌ

์ธ๋„ค์ผ ์ด๋ฏธ์ง€ UI ์ถ”๊ฐ€

ํŒŒ์ผ ์‚ญ์ œ

์‚ญ์ œํ•  ํŒŒ์ผ ์ฒดํฌ๋ฐ•์Šค UI ์ถ”๊ฐ€ ๋ฐ ์ˆ˜์ • ์š”์ฒญ ์‹œ, ์‚ญ์ œํ•  ํŒŒ์ผ ๋ฒˆํ˜ธ ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€

๊ฐœ๋ณ„ ํŒŒ์ผ ์‚ญ์ œ UI ๋ฐ API ์ถ”๊ฐ€

๊ฒŒ์‹œํŒ - ํŒŒ์ผ ์—…๋กœ๋“œ

Spring Boot (Back-End)

Spring Boot ๋กœ ํŒŒ์ผ ์—…๋กœ๋“œ, ์กฐํšŒ, ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

๊ตฌํ˜„ ๊ธฐ๋Šฅ

โ€ข
ํŒŒ์ผ ์—…๋กœ๋“œ
โ—ฆ
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋“ฑ๋ก
โ—ฆ
ํŒŒ์ผ ์‹œ์Šคํ…œ์— ๋“ฑ๋ก
โ€ข
๏ธŽ ํŒŒ์ผ ์กฐํšŒ
โ—ฆ
ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ
โ—ฆ
๋‹ค์šด๋กœ๋“œ
โ—ฆ
์ธ๋„ค์ผ ์ด๋ฏธ์ง€ ๋ณด๊ธฐ
โ€ข
ํŒŒ์ผ ์‚ญ์ œ
โ—ฆ
๊ฒŒ์‹œ๊ธ€์— ์ข…์†๋œ ์—ฌ๋Ÿฌ ํŒŒ์ผ ์‚ญ์ œ
โ—ฆ
์„ ํƒ ํŒŒ์ผ ์‚ญ์ œ
โ—ฆ
๊ฐœ๋ณ„ ํŒŒ์ผ ์‚ญ์ œ

React (Front-End)

React ๋กœ ํŒŒ์ผ ์—…๋กœ๋“œ, ์กฐํšŒ, ์‚ญ์ œ UI ๋ฐ API ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
โ€ข
ํŒŒ์ผ ์—…๋กœ๋“œ
โ—ฆ
ํŒŒ์ผ ๋“ฑ๋ก UI ์ถ”๊ฐ€
โ—ฆ
ํŒŒ์ผ ์—…๋กœ๋“œ ์š”์ฒญ API
โ€ข
๏ธŽ ํŒŒ์ผ ์กฐํšŒ
โ—ฆ
ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ UI ๋ฐ API ์ถ”๊ฐ€
โ—ฆ
๋‹ค์šด๋กœ๋“œ UI ๋ฐ API ์ถ”๊ฐ€
โ—ฆ
์ธ๋„ค์ผ ์ด๋ฏธ์ง€ UI ์ถ”๊ฐ€
โ€ข
ํŒŒ์ผ ์‚ญ์ œ
โ—ฆ
๊ฒŒ์‹œ๊ธ€์— ์ข…์†๋œ ์—ฌ๋Ÿฌ ํŒŒ์ผ ์‚ญ์ œ (๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ ์‹œ, ๋ฐฑ์—”๋“œ์—์„œ ์ฒ˜๋ฆฌํ•จ)
โ—ฆ
์‚ญ์ œํ•  ํŒŒ์ผ ์ฒดํฌ๋ฐ•์Šค UI ์ถ”๊ฐ€ ๋ฐ ์ˆ˜์ • ์š”์ฒญ ์‹œ, ์‚ญ์ œํ•  ํŒŒ์ผ ๋ฒˆํ˜ธ ๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
โ—ฆ
๊ฐœ๋ณ„ ํŒŒ์ผ ์‚ญ์ œ UI ๋ฐ API ์ถ”๊ฐ€

Spring Boot (Back-End)

Spring Boot ๋กœ ํŒŒ์ผ ์—…๋กœ๋“œ, ์กฐํšŒ, ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

ํŒŒ์ผ ์—…๋กœ๋“œ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋“ฑ๋ก

1.
๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค - ํ…Œ์ด๋ธ” ์ƒ์„ฑ
2.
SQL ์ฟผ๋ฆฌ Mapper ์ž‘์„ฑ
3.
DTO
4.
Mapper
5.
Service
6.
Controller

๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค - ํ…Œ์ด๋ธ” ์ƒ์„ฑ

CREATE TABLE `file` ( `no` int NOT NULL AUTO_INCREMENT, -- ํŒŒ์ผ ๋ฒˆํ˜ธ (์ž๋™์ฆ๊ฐ€) `parent_table` varchar(45) NOT NULL, -- ๋ถ€๋ชจ ํ…Œ์ด๋ธ”๋ช… (์˜ˆ: 'board') `parent_no` int NOT NULL, -- ๋ถ€๋ชจ ํ…Œ์ด๋ธ”์—์„œ์˜ ๋ฒˆํ˜ธ `file_name` text NOT NULL, -- ์ €์žฅ๋œ ํŒŒ์ผ๋ช… `origin_name` text, -- ์›๋ณธ ํŒŒ์ผ๋ช… `file_path` text NOT NULL, -- ํŒŒ์ผ ๊ฒฝ๋กœ `file_size` int NOT NULL DEFAULT '0', -- ํŒŒ์ผ ํฌ๊ธฐ (๊ธฐ๋ณธ๊ฐ’ 0) `reg_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, -- ๋“ฑ๋ก์ผ์‹œ `upd_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, -- ์ˆ˜์ •์ผ์‹œ `file_code` int NOT NULL DEFAULT '0', -- ํŒŒ์ผ ์ฝ”๋“œ (๊ธฐ๋ณธ๊ฐ’ 0) PRIMARY KEY (`no`) -- ์ฃผํ‚ค ์„ค์ • ) COMMENT='ํŒŒ์ผ';
SQL
๋ณต์‚ฌ

SQL ์ฟผ๋ฆฌ Mapper ์ž‘์„ฑ

โ€ข
FileMapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace="๋งคํผ ์ธํ„ฐํŽ˜์ด์Šค ๊ฒฝ๋กœ" --> <mapper namespace="com.joeun.server.mapper.FileMapper"> <!-- ํŒŒ์ผ ๋ชฉ๋ก --> <select id="list" resultType="Files"> SELECT * FROM file ORDER BY reg_date DESC </select> <!-- ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ํ…Œ์ด๋ธ” ๊ธฐ์ค€ --> <!-- * ํŒŒ์ผ์ด ์ข…์†๋˜๋Š” ํ…Œ์ด๋ธ”์„ ๊ธฐ์ค€์œผ๋กœ ํŒŒ์ผ ๋ชฉ๋ก์„ ์กฐํšŒ --> <!-- * ๊ฒŒ์‹œ๊ธ€ ๋ฒˆํ˜ธ 10 ๐Ÿ“„ ํŒŒ์ผ ๋ฒˆํ˜ธ 1 ๐Ÿ“„ ํŒŒ์ผ ๋ฒˆํ˜ธ 2 ๐Ÿ“„ ํŒŒ์ผ ๋ฒˆํ˜ธ 3 --> <select id="listByParent" resultType="Files"> SELECT * FROM file WHERE parent_table = #{parentTable} AND parent_no = #{parentNo} ORDER BY reg_date DESC </select> <!-- ํŒŒ์ผ ์กฐํšŒ --> <select id="select" resultType="Files"> SELECT * FROM file WHERE no = #{no} </select> <!-- ํŒŒ์ผ ๋“ฑ๋ก --> <insert id="insert"> INSERT INTO file( parent_table, parent_no, file_name, origin_name, file_path, file_size, file_code ) VALUES ( #{parentTable}, #{parentNo}, #{fileName}, #{originName}, #{filePath}, #{fileSize}, #{fileCode} ) </insert> <!-- ํŒŒ์ผ ์ˆ˜์ • --> <update id="update"> UPDATE file SET parent_table = #{parentTable} ,parent_no = #{parentNo} ,file_name = #{fileName} ,origin_name = #{originName} ,file_path = #{filePath} ,file_size = #{fileSize} ,file_code = #{fileCode} WHERE no = #{no} </update> <!-- ํŒŒ์ผ ์‚ญ์ œ --> <delete id="delete"> DELETE FROM file WHERE no = #{no} </delete> <!-- ํŒŒ์ผ ๋ชฉ๋ก ์‚ญ์ œ - ๋ถ€๋ชจ ํ…Œ์ด๋ธ” ๊ธฐ์ค€ ํŒŒ์ผ ๋ชฉ๋ก ์‚ญ์ œ --> <delete id="deleteByParent"> DELETE FROM file WHERE parent_table = #{parentTable} AND parent_no = #{parentNo} </delete> <!-- ๊ฒŒ์‹œ๊ธ€ ๋ฒˆํ˜ธ ์ตœ๋Œ“๊ฐ’ --> <select id="maxPk" resultType="int"> SELECT MAX(no) FROM file </select> </mapper>
SQL
๋ณต์‚ฌ

DTO

โ€ข
Files.java
package com.joeun.server.dto; import java.util.Date; import lombok.Data; @Data public class Files { private int no; private String parentTable; private int parentNo; private String fileName; private String originName; private String filePath; private long fileSize; private Date regDate; private Date updDate; private int fileCode; }
Java
๋ณต์‚ฌ

Mapper

โ€ข
FileMapper.java
package com.joeun.server.mapper; import java.util.List; import org.apache.ibatis.annotations.Mapper; import com.joeun.server.dto.Files; @Mapper public interface FileMapper { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throws Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ public int delete(int no) throws Exception; // ํŒŒ์ผ ๋ฒˆํ˜ธ(๊ธฐ๋ณธํ‚ค) ์ตœ๋Œ“๊ฐ’ public int maxPk() throws Exception; // ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ๊ธฐ์ค€ public List<Files> listByParent(Files file) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ - ๋ถ€๋ชจ ๊ธฐ์ค€ public int deleteByParent(Files file) throws Exception; }
Java
๋ณต์‚ฌ

Service

โ€ข
FileService.java
โ€ข
FileServiceImpl.java
โ€ข
BoardServiceImpl.java
โ€ข
FileService.java
package com.joeun.server.service; import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import jakarta.servlet.http.HttpServletResponse; public interface FileService { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throws Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Files file, List<MultipartFile> fileList) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int upload(Files file) throws Exception; }
Java
๋ณต์‚ฌ
โ€ข
FileServiceImpl.java
package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file ); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); // DB ์— ๋ฐ์ดํ„ฐ ๋“ฑ๋ก result = fileMapper.insert(uploadedFile); return result; } }
Java
๋ณต์‚ฌ
โ€ข
BoardServiceImpl.java
package com.joeun.server.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Board; import com.joeun.server.dto.Files; import com.joeun.server.mapper.BoardMapper; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class BoardServiceImpl implements BoardService { @Autowired private BoardMapper boardMapper; @Autowired private FileService fileService; @Override public List<Board> list() throws Exception { List<Board> boardList = boardMapper.list(); return boardList; } @Override public Board select(int no) throws Exception { Board board = boardMapper.select(no); return board; } @Override @Transactional public int insert(Board board) throws Exception { int result = boardMapper.insert(board); // ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๋ฐ์ดํ„ฐ์˜ pk ๊ฐ€์ ธ์˜ด int parentNo = boardMapper.maxPk(); board.setNo(parentNo); // โœ…(New) ํŒŒ์ผ ์—…๋กœ๋“œ result += uploadFiles(board); return result; } @Override public int update(Board board) throws Exception { int result = boardMapper.update(board); // ํŒŒ์ผ ์—…๋กœ๋“œ result += uploadFiles(board); return result; } @Override public int delete(int no) throws Exception { int result = boardMapper.delete(no); return result; } @Override public int updateViews(int count, int no) throws Exception { int result = boardMapper.updateViews(count, no); return result; } // โœ…(New) ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Board board) throws Exception { String parentTable = "board"; int parentNo = board.getNo(); int result = 0; List<MultipartFile> fileList = board.getFiles(); if( fileList != null && !fileList.isEmpty() ) { Files fileInfo = new Files(); fileInfo.setParentTable(parentTable); fileInfo.setParentNo(parentNo); result = fileService.uploadFiles(fileInfo, fileList); } return result; } }
Java
๋ณต์‚ฌ

Controller

โ€ข
FileController.java
package com.joeun.server.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import lombok.extern.slf4j.Slf4j; @Slf4j @Controller @RequestMapping("/file") public class FileController { }
Java
๋ณต์‚ฌ

ํŒŒ์ผ ์‹œ์Šคํ…œ์— ๋“ฑ๋ก

1.
DTO
2.
Service

DTO

โ€ข
Files.java
package com.joeun.server.dto; import java.util.Date; import org.springframework.web.multipart.MultipartFile; import lombok.Data; @Data public class Files { private int no; private String parentTable; private int parentNo; private String fileName; private String originName; private String filePath; private long fileSize; private Date regDate; private Date updDate; private int fileCode; MultipartFile file; // โœ… ์ถ”๊ฐ€ }
Java
๋ณต์‚ฌ

Service

โ€ข
FileServiceImpl.java
์ถ”๊ฐ€ํ•  ์ฝ”๋“œ
// โœ…(New) ํŒŒ์ผ์—…๋กœ๋“œ // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); byte[] fileData = file.getBytes(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // โœ…(New) ํŒŒ์ผ์—…๋กœ๋“œ // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); result = fileMapper.insert(uploadedFile); return result; } }
Java
๋ณต์‚ฌ

๏ธŽ ํŒŒ์ผ ์กฐํšŒ

ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ

โ€ข
FileService.java
โ€ข
FileServiceImpl.java
โ€ข
BoardServiceImpl.java
โ€ข
BoardController.java

FileService.java

package com.joeun.server.service; import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import jakarta.servlet.http.HttpServletResponse; public interface FileService { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throws Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Files file, List<MultipartFile> fileList) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int upload(Files file) throws Exception; // โœ…(New) ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ๊ธฐ์ค€ public List<Files> listByParent(Files file) throws Exception; }
Java
๋ณต์‚ฌ

FileServiceImpl.java

package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; } @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); byte[] fileData = file.getBytes(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); result = fileMapper.insert(uploadedFile); return result; } // โœ… (New) - ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } }
Java
๋ณต์‚ฌ

BoardController.java

์ถ”๊ฐ€ํ•  ์ฝ”๋“œ
@GetMapping("/{no}") public ResponseEntity<?> getOne(@PathVariable Integer no, Files files) { log.info("[GET] - /boards/" + no + " - ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ"); try { Board board = boardService.select(no); files.setParentTable("board"); files.setParentNo(no); List<Files> fileList = fileService.listByParent(files); // ํŒŒ์ผ ์ •๋ณด Map<String, Object> response = new HashMap<>(); response.put("board", board); response.put("fileList", fileList); if( board == null ) { board = new Board(); board.setTitle("๋ฐ์ดํ„ฐ ์—†์Œ"); } return new ResponseEntity<>(response, HttpStatus.OK); } catch (Exception e) { log.error(null, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } }
Java
๋ณต์‚ฌ
package com.joeun.server.controller; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Board; import com.joeun.server.dto.Files; import com.joeun.server.service.BoardService; import com.joeun.server.service.FileService; import lombok.extern.slf4j.Slf4j; @Slf4j @RestController @RequestMapping("/boards") public class BoardController { @Autowired private BoardService boardService; @Autowired private FileService fileService; // ๐Ÿ‘ฉโ€๐Ÿ’ป CRUD ๋ฉ”์†Œ๋“œ ์ž๋™ ์ƒ์„ฑ : sp-crud // ๐Ÿ‘ฉโ€๐Ÿ’ป ์ž๋™ import : alt + shift + O // ๐Ÿ‘ฉโ€๐Ÿ’ป ํ•œ ์ค„ ์‚ญ์ œ : ctrl + shift + K @GetMapping() public ResponseEntity<?> getAll() { log.info("[GET] - /boards - ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก"); try { List<Board> boardList = boardService.list(); if( boardList == null ) log.info("์กฐํšŒ๋œ ๊ฒŒ์‹œ๊ธ€ ์—†์Œ"); else log.info("๊ฒŒ์‹œ๊ธ€ ์ˆ˜ : " + boardList.size()); return new ResponseEntity<>(boardList, HttpStatus.OK); } catch (Exception e) { log.error(null, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @GetMapping("/{no}") public ResponseEntity<?> getOne(@PathVariable Integer no, Files files) { log.info("[GET] - /boards/" + no + " - ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ"); try { Board board = boardService.select(no); files.setParentTable("board"); files.setParentNo(no); List<Files> fileList = fileService.listByParent(files); // ํŒŒ์ผ ์ •๋ณด Map<String, Object> response = new HashMap<>(); response.put("board", board); response.put("fileList", fileList); if( board == null ) { board = new Board(); board.setTitle("๋ฐ์ดํ„ฐ ์—†์Œ"); } return new ResponseEntity<>(response, HttpStatus.OK); } catch (Exception e) { log.error(null, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @PostMapping() // public ResponseEntity<?> create(@RequestBody Board board) { // Content-Type : application/json public ResponseEntity<?> create(Board board) { // Content-Type : multipart/form-data log.info("[POST] - /boards - ๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก"); log.info("board : " + board.toString()); List<MultipartFile> files = board.getFiles(); if( files != null ) for (MultipartFile file : files) { log.info("file : " + file.getOriginalFilename()); } try { int result = boardService.insert(board); if( result > 0 ) return new ResponseEntity<>("๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก ์™„๋ฃŒ", HttpStatus.CREATED); // 201 else return new ResponseEntity<>("๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก ์‹คํŒจ", HttpStatus.OK); } catch (Exception e) { log.error(null, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @PutMapping() // public ResponseEntity<?> update(@RequestBody Board board) { // Content-Type : application/json public ResponseEntity<?> update(Board board) { // Content-Type : multipart/form-data log.info("[PUT] - /boards - ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •"); try { int result = boardService.update(board); log.info("์ˆ˜์ • : " + board); if( result > 0 ) return new ResponseEntity<>("๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • ์™„๋ฃŒ", HttpStatus.OK); else return new ResponseEntity<>("๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • ์‹คํŒจ", HttpStatus.OK); } catch (Exception e) { log.error(null, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @DeleteMapping("/{no}") public ResponseEntity<?> destroy(@PathVariable Integer no) { log.info("[DELETE] - /boards/" + no + " - ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ"); try { int result = boardService.delete(no); if( result > 0 ) return new ResponseEntity<>("๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ ์™„๋ฃŒ", HttpStatus.OK); else return new ResponseEntity<>("๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ ์‹คํŒจ", HttpStatus.OK); } catch (Exception e) { log.error(null, e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } }
Java
๋ณต์‚ฌ

๋‹ค์šด๋กœ๋“œ

โ€ข
FileService.java
โ€ข
FileServiceImpl.java
โ€ข
FileController.java

FileService.java

package com.joeun.server.service; import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import jakarta.servlet.http.HttpServletResponse; public interface FileService { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throws Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Files file, List<MultipartFile> fileList) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int upload(Files file) throws Exception; // ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ๊ธฐ์ค€ public List<Files> listByParent(Files file) throws Exception; // โœ…(New) - ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ public int download(int no, HttpServletResponse response) throws Exception; }
Java
๋ณต์‚ฌ

FileServiceImpl.java

package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; } @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); byte[] fileData = file.getBytes(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); result = fileMapper.insert(uploadedFile); return result; } // ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int download(int fileNo, HttpServletResponse response) throws Exception { // result // 0 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์ฒ˜๋ฆฌ ์‹คํŒจ // 1 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์„ฑ๊ณต Files file = fileMapper.select(fileNo); if( file == null ) { // BAD_REQUEST : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ์•Œ๋ ค์ฃผ๋Š” ์ƒํƒœ์ฝ”๋“œ // response.setStatus(response.SC_BAD_REQUEST); return 0; } String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ String fileName = file.getFileName(); // ํŒŒ์ผ ์ด๋ฆ„ // ๋‹ค์šด๋กœ๋“œ ์‘๋‹ต์„ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-stream // - Content-Disposition : attachment, filename="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ // - ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); // - ํŒŒ์ผ ์ถœ๋ ฅ ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); fis.close(); sos.close(); return 1; } }
Java
๋ณต์‚ฌ

FileController.java

package com.joeun.server.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import lombok.extern.slf4j.Slf4j; @Slf4j @Controller @RequestMapping("/file") public class FileController { @Autowired private FileService fileService; /** * ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ * @param fileNo * @param response * @throws Exception */ @GetMapping("/{no}") public void fileDownload(@PathVariable("no") int no ,HttpServletResponse response) throws Exception { // ํŒŒ์ผ ์กฐํšŒ Files file = fileService.select(no); // ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด, if( file == null ) { // ์‘๋‹ต ์ƒํƒœ์ฝ”๋“œ : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ƒํƒœ์ฝ”๋“œ response.setStatus(HttpServletResponse.SC_BAD_REQUEST); return; } String fileName = file.getFileName(); // ํŒŒ์ผ ๋ช… String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-straem // - Content-Disposition : attachment; fileanme="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); } }
Java
๋ณต์‚ฌ

์ธ๋„ค์ผ ์ด๋ฏธ์ง€ ๋ณด๊ธฐ

โ€ข
FileController.java

FileController.java

package com.joeun.server.controller; import java.io.File; import java.io.FileInputStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.FileCopyUtils; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.joeun.server.dto.Files; import com.joeun.server.service.FileService; import com.joeun.server.utils.MediaUtil; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Controller @RequestMapping("/file") public class FileController { @Autowired private FileService fileService; @Autowired private ResourceLoader resourceLoader; /** * ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ * @param fileNo * @param response * @throws Exception */ @GetMapping("/{no}") public void fileDownload(@PathVariable("no") int no ,HttpServletResponse response) throws Exception { // ํŒŒ์ผ ์กฐํšŒ Files file = fileService.select(no); // ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด, if( file == null ) { // ์‘๋‹ต ์ƒํƒœ์ฝ”๋“œ : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ƒํƒœ์ฝ”๋“œ response.setStatus(HttpServletResponse.SC_BAD_REQUEST); return; } String fileName = file.getFileName(); // ํŒŒ์ผ ๋ช… String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-straem // - Content-Disposition : attachment; fileanme="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); } /** * ์ด๋ฏธ์ง€ ์ธ๋„ค์ผ * @param no * @param response * @throws Exception */ @GetMapping("/img/{no}") public void showImg(@PathVariable Integer no, HttpServletResponse response) throws Exception { Files file = fileService.select(no); String filePath = (file != null) ? file.getFilePath() : null; Resource resource = resourceLoader.getResource("classpath:static/img/no-image.png"); File imgFile; if (filePath == null || !(imgFile = new File(filePath)).exists()) { // ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ํŒŒ์ผ ๊ฒฝ๋กœ๊ฐ€ null์ธ ๊ฒฝ์šฐ imgFile = resource.getFile(); } String ext = filePath.substring(filePath.lastIndexOf(".") + 1); MediaType mType = MediaUtil.getMediaType(ext); if (mType == null) { // ์ด๋ฏธ์ง€ ํƒ€์ž…์ด ์•„๋‹ ๊ฒฝ์šฐ response.setContentType(MediaType.IMAGE_PNG_VALUE); // ๊ธฐ๋ณธ์ ์œผ๋กœ PNG๋กœ ์„ค์ • imgFile = resource.getFile(); } else { // ์ด๋ฏธ์ง€ ํƒ€์ž…์ผ ๊ฒฝ์šฐ response.setContentType(mType.toString()); } FileInputStream fis = new FileInputStream(imgFile); ServletOutputStream sos = response.getOutputStream(); FileCopyUtils.copy(fis, sos); } }
Java
๋ณต์‚ฌ

ํŒŒ์ผ ์‚ญ์ œ

๊ฒŒ์‹œ๊ธ€์— ์ข…์†๋œ ์—ฌ๋Ÿฌ ํŒŒ์ผ ์‚ญ์ œ

โ€ข
FileService.java
โ€ข
FileServiceImpl.java
โ€ข
BoardServiceImpl.java
โ€ข
FileService.java
package com.joeun.server.service; import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import jakarta.servlet.http.HttpServletResponse; public interface FileService { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throwsdeleteByParent Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ public int delete(int no) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Files file, List<MultipartFile> fileList) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int upload(Files file) throws Exception; // ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ๊ธฐ์ค€ public List<Files> listByParent(Files file) throws Exception; // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ public int download(int no, HttpServletResponse response) throws Exception; // โœ…(New) ํŒŒ์ผ ์‚ญ์ œ - ๋ถ€๋ชจ ๊ธฐ์ค€ public int deleteByParent(Files fileInfo) throws Exception; }
Java
๋ณต์‚ฌ
โ€ข
FileServiceImpl.java
์ถ”๊ฐ€ํ•  ์ฝ”๋“œ
@Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; }
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; } @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); byte[] fileData = file.getBytes(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); result = fileMapper.insert(uploadedFile); return result; } // ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int download(int fileNo, HttpServletResponse response) throws Exception { // result // 0 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์ฒ˜๋ฆฌ ์‹คํŒจ // 1 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์„ฑ๊ณต Files file = fileMapper.select(fileNo); if( file == null ) { // BAD_REQUEST : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ์•Œ๋ ค์ฃผ๋Š” ์ƒํƒœ์ฝ”๋“œ // response.setStatus(response.SC_BAD_REQUEST); return 0; } String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ String fileName = file.getFileName(); // ํŒŒ์ผ ์ด๋ฆ„ // ๋‹ค์šด๋กœ๋“œ ์‘๋‹ต์„ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-stream // - Content-Disposition : attachment, filename="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ // - ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); // - ํŒŒ์ผ ์ถœ๋ ฅ ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); fis.close(); sos.close(); return 1; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } }
Java
๋ณต์‚ฌ
โ€ข
BoardServiceImpl.java
์ถ”๊ฐ€ํ•  ์ฝ”๋“œ
@Override @Transactional public int delete(int no) throws Exception { int result = boardMapper.delete(no); String parentTable = "board"; int parentNo = no; if( result > 0 ) { Files fileInfo = new Files(); fileInfo.setParentTable(parentTable); fileInfo.setParentNo(parentNo); int fileResult = fileService.deleteByParent(fileInfo); result = fileResult; } return result; }
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Board; import com.joeun.server.dto.Files; import com.joeun.server.mapper.BoardMapper; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class BoardServiceImpl implements BoardService { @Autowired private BoardMapper boardMapper; @Autowired private FileService fileService; @Override public List<Board> list() throws Exception { List<Board> boardList = boardMapper.list(); return boardList; } @Override public Board select(int no) throws Exception { Board board = boardMapper.select(no); return board; } @Override @Transactional public int insert(Board board) throws Exception { int result = boardMapper.insert(board); // ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๋ฐ์ดํ„ฐ์˜ pk ๊ฐ€์ ธ์˜ด int parentNo = boardMapper.maxPk(); board.setNo(parentNo); result += uploadFiles(board); return result; } @Override public int update(Board board) throws Exception { int result = boardMapper.update(board); // ํŒŒ์ผ ์—…๋กœ๋“œ result += uploadFiles(board); return result; } @Override public int updateViews(int count, int no) throws Exception { int result = boardMapper.updateViews(count, no); return result; } public int uploadFiles(Board board) throws Exception { String parentTable = "board"; int parentNo = board.getNo(); int result = 0; List<MultipartFile> fileList = board.getFiles(); if( fileList != null && !fileList.isEmpty() ) { Files fileInfo = new Files(); fileInfo.setParentTable(parentTable); fileInfo.setParentNo(parentNo); result = fileService.uploadFiles(fileInfo, fileList); } return result; } @Override @Transactional public int delete(int no) throws Exception { int result = boardMapper.delete(no); String parentTable = "board"; int parentNo = no; // โœ…(New) - ๊ฒŒ์‹œ๊ธ€์— ์ข…์†๋œ ํŒŒ์ผ ์‚ญ์ œ if( result > 0 ) { Files fileInfo = new Files(); fileInfo.setParentTable(parentTable); fileInfo.setParentNo(parentNo); int fileResult = fileService.deleteByParent(fileInfo); result = fileResult; } return result; } }
Java
๋ณต์‚ฌ

์„ ํƒ ํŒŒ์ผ ์‚ญ์ œ

โ€ข
FileService.java
โ€ข
FileServiceImpl.java
โ€ข
BoardServiceImpl.java
โ€ข
FileService.java
package com.joeun.server.service; import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import jakarta.servlet.http.HttpServletResponse; public interface FileService { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throws Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ public int delete(int no) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Files file, List<MultipartFile> fileList) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int upload(Files file) throws Exception; // ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ๊ธฐ์ค€ public List<Files> listByParent(Files file) throws Exception; // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ public int download(int no, HttpServletResponse response) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ - ๋ถ€๋ชจ ๊ธฐ์ค€ public int deleteByParent(Files fileInfo) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ - noList ๊ธฐ์ค€ public int deleteByNoList(List<Integer> noList) throws Exception; }
Java
๋ณต์‚ฌ
โ€ข
FileServiceImpl.java
์ถ”๊ฐ€ํ•  ์ฝ”๋“œ
@Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; }
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; } @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); byte[] fileData = file.getBytes(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); result = fileMapper.insert(uploadedFile); return result; } // ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int download(int fileNo, HttpServletResponse response) throws Exception { // result // 0 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์ฒ˜๋ฆฌ ์‹คํŒจ // 1 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์„ฑ๊ณต Files file = fileMapper.select(fileNo); if( file == null ) { // BAD_REQUEST : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ์•Œ๋ ค์ฃผ๋Š” ์ƒํƒœ์ฝ”๋“œ // response.setStatus(response.SC_BAD_REQUEST); return 0; } String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ String fileName = file.getFileName(); // ํŒŒ์ผ ์ด๋ฆ„ // ๋‹ค์šด๋กœ๋“œ ์‘๋‹ต์„ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-stream // - Content-Disposition : attachment, filename="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ // - ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); // - ํŒŒ์ผ ์ถœ๋ ฅ ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); fis.close(); sos.close(); return 1; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } }
Java
๋ณต์‚ฌ
โ€ข
BoardServiceImpl.java
์ถ”๊ฐ€ํ•  ์ฝ”๋“œ
@Override public int update(Board board) throws Exception { int result = boardMapper.update(board); // ํŒŒ์ผ ์—…๋กœ๋“œ result += uploadFiles(board); // ๊ฐœ๋ณ„ ํŒŒ์ผ ์‚ญ์ œ List<Integer> deleteFileNoList = board.getDeleteFileNoList(); result += fileService.deleteByNoList(deleteFileNoList); return result; }
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Board; import com.joeun.server.dto.Files; import com.joeun.server.mapper.BoardMapper; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class BoardServiceImpl implements BoardService { @Autowired private BoardMapper boardMapper; @Autowired private FileService fileService; @Override public List<Board> list() throws Exception { List<Board> boardList = boardMapper.list(); return boardList; } @Override public Board select(int no) throws Exception { Board board = boardMapper.select(no); return board; } @Override @Transactional public int insert(Board board) throws Exception { int result = boardMapper.insert(board); // ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๋ฐ์ดํ„ฐ์˜ pk ๊ฐ€์ ธ์˜ด int parentNo = boardMapper.maxPk(); board.setNo(parentNo); result += uploadFiles(board); return result; } @Override public int update(Board board) throws Exception { int result = boardMapper.update(board); // ํŒŒ์ผ ์—…๋กœ๋“œ result += uploadFiles(board); // โœ…(New) ๊ฐœ๋ณ„ ํŒŒ์ผ ์‚ญ์ œ List<Integer> deleteFileNoList = board.getDeleteFileNoList(); result += fileService.deleteByNoList(deleteFileNoList); return result; } @Override public int updateViews(int count, int no) throws Exception { int result = boardMapper.updateViews(count, no); return result; } public int uploadFiles(Board board) throws Exception { String parentTable = "board"; int parentNo = board.getNo(); int result = 0; List<MultipartFile> fileList = board.getFiles(); if( fileList != null && !fileList.isEmpty() ) { Files fileInfo = new Files(); fileInfo.setParentTable(parentTable); fileInfo.setParentNo(parentNo); result = fileService.uploadFiles(fileInfo, fileList); } return result; } @Override @Transactional public int delete(int no) throws Exception { int result = boardMapper.delete(no); String parentTable = "board"; int parentNo = no; if( result > 0 ) { Files fileInfo = new Files(); fileInfo.setParentTable(parentTable); fileInfo.setParentNo(parentNo); int fileResult = fileService.deleteByParent(fileInfo); result = fileResult; } return result; } }
Java
๋ณต์‚ฌ

๊ฐœ๋ณ„ ํŒŒ์ผ ์‚ญ์ œ

โ€ข
FileService.java
โ€ข
FileServiceImpl.java
โ€ข
FileController.java

FileService.java

// ํŒŒ์ผ ์‚ญ์ œ public int delete(int no) throws Exception;
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import jakarta.servlet.http.HttpServletResponse; public interface FileService { // ํŒŒ์ผ ๋ชฉ๋ก public List<Files> list() throws Exception; // ํŒŒ์ผ ์กฐํšŒ public Files select(int no) throws Exception; // ํŒŒ์ผ ๋“ฑ๋ก public int insert(Files file) throws Exception; // ํŒŒ์ผ ์ˆ˜์ • public int update(Files file) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ public int delete(int no) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int uploadFiles(Files file, List<MultipartFile> fileList) throws Exception; // ํŒŒ์ผ ์—…๋กœ๋“œ public int upload(Files file) throws Exception; // ํŒŒ์ผ ๋ชฉ๋ก - ๋ถ€๋ชจ ๊ธฐ์ค€ public List<Files> listByParent(Files file) throws Exception; // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ public int download(int no, HttpServletResponse response) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ - ๋ถ€๋ชจ ๊ธฐ์ค€ public int deleteByParent(Files fileInfo) throws Exception; // ํŒŒ์ผ ์‚ญ์ œ - noList ๊ธฐ์ค€ public int deleteByNoList(List<Integer> noList) throws Exception; }
Java
๋ณต์‚ฌ

FileServiceImpl.java

@Override public int delete(int no) throws Exception { Files file = fileMapper.select(no); if( file == null ) return 0; String filePath = file.getFilePath(); File deleteFile = new File(filePath); if( !deleteFile.exists() ) return 0; boolean deleted = deleteFile.delete(); int result = 0; if( deleted ) { result = fileMapper.delete(no); } return result; }
Java
๋ณต์‚ฌ
package com.joeun.server.service; import java.io.File; import java.io.FileInputStream; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import com.joeun.server.dto.Files; import com.joeun.server.mapper.FileMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class FileServiceImpl implements FileService { @Autowired private FileMapper fileMapper; @Value("${upload.path}") // application.properties ์— ์„ค์ •ํ•œ ์—…๋กœ๋“œ ๊ฒฝ๋กœ ์†์„ฑ๋ช… private String uploadPath; // ์—…๋กœ๋“œ ๊ฒฝ๋กœ @Override public List<Files> list() throws Exception { List<Files> fileList = fileMapper.list(); return fileList; } @Override public Files select(int no) throws Exception { Files file = fileMapper.select(no); return file; } @Override public int insert(Files file) throws Exception { int result = fileMapper.insert(file); return result; } @Override public int update(Files file) throws Exception { int result = fileMapper.update(file); return result; } @Override public int delete(int no) throws Exception { Files file = fileMapper.select(no); if( file == null ) return 0; String filePath = file.getFilePath(); File deleteFile = new File(filePath); if( !deleteFile.exists() ) return 0; boolean deleted = deleteFile.delete(); int result = 0; if( deleted ) { result = fileMapper.delete(no); } return result; } @Override public List<Files> listByParent(Files file) throws Exception { List<Files> fileList = fileMapper.listByParent(file); return fileList; } @Override public int deleteByParent(Files fileInfo) throws Exception { int result = 0; List<Files> fileList = fileMapper.listByParent(fileInfo); for (Files file : fileList) { File deleteFile = new File(file.getFilePath()); if( !deleteFile.exists() ) continue; if( !deleteFile.delete() ) continue; int deleted = fileMapper.delete(file.getNo()); if( deleted > 0 ) result++; } log.info(result + "๊ฐœ์˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int download(int fileNo, HttpServletResponse response) throws Exception { // result // 0 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์ฒ˜๋ฆฌ ์‹คํŒจ // 1 : ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์„ฑ๊ณต Files file = fileMapper.select(fileNo); if( file == null ) { // BAD_REQUEST : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ์•Œ๋ ค์ฃผ๋Š” ์ƒํƒœ์ฝ”๋“œ // response.setStatus(response.SC_BAD_REQUEST); return 0; } String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ String fileName = file.getFileName(); // ํŒŒ์ผ ์ด๋ฆ„ // ๋‹ค์šด๋กœ๋“œ ์‘๋‹ต์„ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-stream // - Content-Disposition : attachment, filename="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ // - ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); // - ํŒŒ์ผ ์ถœ๋ ฅ ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); // byte[] buffer = new byte[1024]; // 1024bytes = 1KB ๋‹จ์œ„ ๋ฒ„ํผ // int data; // while( (data = fis.read(buffer)) != -1 ) { // 1KB ์”ฉ ํŒŒ์ผ์ž…๋ ฅ // sos.write(buffer, 0, data); // 1KB ์”ฉ ํŒŒ์ผ์ถœ๋ ฅ // } fis.close(); sos.close(); return 1; } @Override public int uploadFiles(Files fileInfo, List<MultipartFile> fileList) throws Exception { int result = 0; for (MultipartFile file : fileList) { result += uploadFile( fileInfo, file); } log.info(result + "๊ฐœ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค."); return result; } @Override public int deleteByNoList(List<Integer> deleteFileNoList) throws Exception { int result = 0; if( deleteFileNoList != null && !deleteFileNoList.isEmpty() ) for (Integer deleteFileNo : deleteFileNoList) { if( deleteFileNo == null ) continue; fileMapper.delete(deleteFileNo); log.info(deleteFileNo + "๋ฒˆ ํŒŒ์ผ ์‚ญ์ œ"); result++; } return result; } @Override @Transactional public int upload(Files file) throws Exception { int result = uploadFile(file, file.getFile()); if( result > 0 ) result = fileMapper.maxPk(); return result; } public int uploadFile(Files fileInfo, MultipartFile file) throws Exception { int result = 0; if( file.isEmpty() ) return result; // ํŒŒ์ผ ์ •๋ณด : ์›๋ณธํŒŒ์ผ๋ช…, ํŒŒ์ผ ์šฉ๋Ÿ‰, ํŒŒ์ผ ๋ฐ์ดํ„ฐ String originName = file.getOriginalFilename(); long fileSize = file.getSize(); byte[] fileData = file.getBytes(); // ์—…๋กœ๋“œ ๊ฒฝ๋กœ // ํŒŒ์ผ๋ช… ์ค‘๋ณต ๋ฐฉ์ง€ ๋ฐฉ๋ฒ•(์ •์ฑ…) // - ๋‚ ์งœ_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // - UID_ํŒŒ์ผ๋ช….ํ™•์žฅ์ž // UID_๊ฐ•์•„์ง€.png String fileName = UUID.randomUUID().toString() + "_" + originName; // c:/upload/UID_๊ฐ•์•„์ง€.png String filePath = uploadPath + "/" + fileName; // - ์„œ๋ฒ„ ์ธก, ํŒŒ์ผ ์‹œ์Šคํ…œ์— ํŒŒ์ผ ๋ณต์‚ฌ File uploadFile = new File(uploadPath, fileName); FileCopyUtils.copy(fileData, uploadFile); // ํŒŒ์ผ ์—…๋กœ๋“œ // - DB ์— ํŒŒ์ผ ์ •๋ณด ๋“ฑ๋ก Files uploadedFile = new Files(); uploadedFile.setParentTable(fileInfo.getParentTable()); uploadedFile.setParentNo(fileInfo.getParentNo()); uploadedFile.setFileName(fileName); uploadedFile.setFilePath(filePath); uploadedFile.setOriginName(originName); uploadedFile.setFileSize(fileSize); uploadedFile.setFileCode(0); result = fileMapper.insert(uploadedFile); return result; } }
Java
๋ณต์‚ฌ

FileController.java

package com.joeun.server.controller; import java.io.File; import java.io.FileInputStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.FileCopyUtils; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.joeun.server.dto.Files; import com.joeun.server.service.FileService; import com.joeun.server.utils.MediaUtil; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j @Controller @RequestMapping("/file") public class FileController { @Autowired private FileService fileService; @Autowired private ResourceLoader resourceLoader; /** * ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ * @param fileNo * @param response * @throws Exception */ @GetMapping("/{no}") public void fileDownload(@PathVariable("no") int no ,HttpServletResponse response) throws Exception { // ํŒŒ์ผ ์กฐํšŒ Files file = fileService.select(no); // ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด, if( file == null ) { // ์‘๋‹ต ์ƒํƒœ์ฝ”๋“œ : 400, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ž˜๋ชป๋˜์—ˆ์Œ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ƒํƒœ์ฝ”๋“œ response.setStatus(HttpServletResponse.SC_BAD_REQUEST); return; } String fileName = file.getFileName(); // ํŒŒ์ผ ๋ช… String filePath = file.getFilePath(); // ํŒŒ์ผ ๊ฒฝ๋กœ // ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•œ ํ—ค๋” ์„ธํŒ… // - ContentType : application/octet-straem // - Content-Disposition : attachment; fileanme="ํŒŒ์ผ๋ช….ํ™•์žฅ์ž" response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // ํŒŒ์ผ ์ž…๋ ฅ File downloadFile = new File(filePath); FileInputStream fis = new FileInputStream(downloadFile); ServletOutputStream sos = response.getOutputStream(); // ๋‹ค์šด๋กœ๋“œ FileCopyUtils.copy(fis, sos); } /** * ์ด๋ฏธ์ง€ ์ธ๋„ค์ผ * @param no * @param response * @throws Exception */ @GetMapping("/img/{no}") public void showImg(@PathVariable Integer no, HttpServletResponse response) throws Exception { Files file = fileService.select(no); String filePath = (file != null) ? file.getFilePath() : null; Resource resource = resourceLoader.getResource("classpath:static/img/no-image.png"); File imgFile; if (filePath == null || !(imgFile = new File(filePath)).exists()) { // ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ํŒŒ์ผ ๊ฒฝ๋กœ๊ฐ€ null์ธ ๊ฒฝ์šฐ imgFile = resource.getFile(); } String ext = filePath.substring(filePath.lastIndexOf(".") + 1); MediaType mType = MediaUtil.getMediaType(ext); if (mType == null) { // ์ด๋ฏธ์ง€ ํƒ€์ž…์ด ์•„๋‹ ๊ฒฝ์šฐ response.setContentType(MediaType.IMAGE_PNG_VALUE); // ๊ธฐ๋ณธ์ ์œผ๋กœ PNG๋กœ ์„ค์ • imgFile = resource.getFile(); } else { // ์ด๋ฏธ์ง€ ํƒ€์ž…์ผ ๊ฒฝ์šฐ response.setContentType(mType.toString()); } FileInputStream fis = new FileInputStream(imgFile); ServletOutputStream sos = response.getOutputStream(); FileCopyUtils.copy(fis, sos); } /** * ํŒŒ์ผ ์‚ญ์ œ * @param file * @return * @throws Exception */ @DeleteMapping("/{no}") public ResponseEntity<String> deleteFile(@PathVariable Integer no) throws Exception { log.info("[DELETE] - /file"); int fileNo = no; log.info("fileNo : " + fileNo); if( fileNo == 0 ) return new ResponseEntity<String>("FAIL", HttpStatus.BAD_REQUEST); int result = fileService.delete(fileNo); if( result == 0 ) return new ResponseEntity<String>("FAIL", HttpStatus.OK); return new ResponseEntity<String>("SUCCESS", HttpStatus.OK); } }
Java
๋ณต์‚ฌ

React (Front-End)

React ๋กœ ํŒŒ์ผ ์—…๋กœ๋“œ, ์กฐํšŒ, ์‚ญ์ œ UI ๋ฐ API ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

ํŒŒ์ผ ์—…๋กœ๋“œ

ํŒŒ์ผ ๋“ฑ๋ก UI ์ถ”๊ฐ€

BoardInsertForm.jsx

import React, { useState } from 'react' import { Link } from 'react-router-dom' import './BoardInsertForm.css' const BoardInsertForm = ({ onInsert }) => { // state ์„ค์ • const [title, setTitle] = useState(''); const [writer, setWriter] = useState(''); const [content, setContent] = useState(''); const [files, setFiles] = useState(null); // โœ… files state ์ถ”๊ฐ€ const handleChangeTitle = (e) => { setTitle(e.target.value) } const handleChangeWriter = (e) => { setWriter(e.target.value) } const handleChangeContent = (e) => { setContent(e.target.value) } // โœ… ํŒŒ์ผ ํ•ธ๋“ค๋Ÿฌ ์ถ”๊ฐ€ const handleFileChange = (e) => { setFiles(e.target.files); }; const onSubmit = () => { const formData = new FormData(); formData.append('title', title); formData.append('writer', writer); formData.append('content', content); const headers = { headers: { 'Content-Type' : 'multipart/form-data', }, }; if (files) { for (let i = 0; i < files.length; i++) { formData.append(`files[${i}]`, files[i]); } } // onInsert(title, writer, content) // onInsert({title,writer,content,files}, headers) // formData ์‚ฌ์šฉ โŒ onInsert(formData, headers) // formData ์‚ฌ์šฉ โญ• } return ( <div className='container'> <h1 className='title'>๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก</h1> <table className='table'> <tbody> <tr> <td>์ œ๋ชฉ</td> <td> <input type="text" className='form-input' value={title} onChange={handleChangeTitle} /> </td> </tr> <tr> <td>์ž‘์„ฑ์ž</td> <td> <input type="text" className='form-input' value={writer} onChange={handleChangeWriter} /> </td> </tr> <tr> <td colSpan={2}>๋‚ด์šฉ</td> </tr> <tr> <td colSpan={2}> <textarea cols="40" rows="10" className='form-input' value={content} onChange={handleChangeContent} ></textarea> </td> </tr> <tr> <td>ํŒŒ์ผ</td> <td> <input type='file' onChange={handleFileChange} multiple /> </td> </tr> </tbody> </table> <div className='btn-box'> <Link to="/boards" className='btn'>๋ชฉ๋ก</Link> <button onClick={ onSubmit } className='btn'>๋“ฑ๋ก</button> </div> </div> ) } export default BoardInsertForm
JavaScript
๋ณต์‚ฌ

ํŒŒ์ผ ์—…๋กœ๋“œ ์š”์ฒญ API

files.js

import axios from 'axios'; // ์—…๋กœ๋“œ export const upload = (formData, headers) => axios.post(`/file/upload`, formData, headers)
JavaScript
๋ณต์‚ฌ

๏ธŽ ํŒŒ์ผ ์กฐํšŒ

ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ UI ๋ฐ API ์ถ”๊ฐ€

BoardReadContainer.jsx

import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import * as boards from '../apis/boards'; import BoardRead from '../components/BoardRead/BoardRead'; // ๐Ÿ‘ฉโ€๐Ÿ’ป ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ const BoardReadContainer = () => { const { no } = useParams() const [board, setBoard] = useState({}); const [fileList, setFileList] = useState([]); const [isLoading, setLoading] = useState(false); const getBoard = async () => { setLoading(true) try { const response = await boards.select(no); const data = response.data; console.log(data); const board = data.board; const fileList = data.fileList; setBoard(board); setFileList(fileList); } catch(e) { console.log(e); } setLoading(false) } useEffect( () => { getBoard() },[]) return (<BoardRead no={no} board={board} fileList={fileList} isLoading={isLoading} />) } export default BoardReadContainer
JavaScript
๋ณต์‚ฌ

BoardRead.jsx

import React, { useEffect } from 'react'; import { Link } from 'react-router-dom'; import * as format from '../../apis/format'; import './BoardRead.css'; const BoardRead = ({ no, board, fileList, isLoading }) => { return ( <div className='container'> <h1 className='title'>๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ</h1> <h3>๋ฒˆํ˜ธ : {no}</h3> <hr/> { isLoading && <div> <img src="/img/loading.webp" alt="loading" /> </div> } { !isLoading && board && ( <table border={1} className='table'> <tbody> <tr> <td>๋ฒˆํ˜ธ</td> <td> <input type="text" className='form-input' value={board.no} readOnly /> </td> </tr> <tr> <td>๋“ฑ๋ก์ผ์ž</td> <td> <input type="text" className='form-input' value={format.formatDate( board.regDate )} readOnly /> </td> </tr> <tr> <td>์ œ๋ชฉ</td> <td> <input type="text" className='form-input' value={board.title} readOnly /> </td> </tr> <tr> <td>์ž‘์„ฑ์ž</td> <td> <input type="text" className='form-input' value={board.writer} readOnly /> </td> </tr> <tr> <td colSpan={2}>๋‚ด์šฉ</td> </tr> <tr> <td colSpan={2}> <textarea cols="40" rows="10" className='form-input' value={board.content} readOnly></textarea> </td> </tr> <tr> <td colSpan={2}>ํŒŒ์ผ</td> </tr> <tr> <td colSpan={2}> { fileList.map( (file) => ( <div className='file-box' key={file.no}> <div className="item"> <span>{file.originName}({ format.byteToUnit(file.fileSize) })</span> </div> <div className="item"> </div> </div> ))} </td> </tr> </tbody> </table> )} <hr /> <div className="btn-box"> <Link className='btn' to="/boards">๋ชฉ๋ก</Link> <Link className='btn' to={`/boards/update/${no}`}>์ˆ˜์ •</Link> </div> </div> ) } export default BoardRead
JavaScript
๋ณต์‚ฌ

๋‹ค์šด๋กœ๋“œ UI ๋ฐ API ์ถ”๊ฐ€

BoardRead.jsx

<button className="btn" onClick={() => handleDownload(file.no, file.originName)}>๋‹ค์šด๋กœ๋“œ</button>
JavaScript
๋ณต์‚ฌ
import React, { useEffect } from 'react'; import { Link } from 'react-router-dom'; import * as format from '../../apis/format'; import './BoardRead.css'; const BoardRead = ({ no, board, fileList, isLoading }) => { return ( <div className='container'> <h1 className='title'>๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ</h1> <h3>๋ฒˆํ˜ธ : {no}</h3> <hr/> { isLoading && <div> <img src="/img/loading.webp" alt="loading" /> </div> } { !isLoading && board && ( <table border={1} className='table'> <tbody> <tr> <td>๋ฒˆํ˜ธ</td> <td> <input type="text" className='form-input' value={board.no} readOnly /> </td> </tr> <tr> <td>๋“ฑ๋ก์ผ์ž</td> <td> <input type="text" className='form-input' value={format.formatDate( board.regDate )} readOnly /> </td> </tr> <tr> <td>์ œ๋ชฉ</td> <td> <input type="text" className='form-input' value={board.title} readOnly /> </td> </tr> <tr> <td>์ž‘์„ฑ์ž</td> <td> <input type="text" className='form-input' value={board.writer} readOnly /> </td> </tr> <tr> <td colSpan={2}>๋‚ด์šฉ</td> </tr> <tr> <td colSpan={2}> <textarea cols="40" rows="10" className='form-input' value={board.content} readOnly></textarea> </td> </tr> <tr> <td colSpan={2}>ํŒŒ์ผ</td> </tr> <tr> <td colSpan={2}> { fileList.map( (file) => ( <div className='file-box' key={file.no}> <div className="item"> <span>{file.originName}({ format.byteToUnit(file.fileSize) })</span> </div> <div className="item"> <button className="btn" onClick={() => handleDownload(file.no, file.originName)}>๋‹ค์šด๋กœ๋“œ</button> </div> </div> ))} </td> </tr> </tbody> </table> )} <hr /> <div className="btn-box"> <Link className='btn' to="/boards">๋ชฉ๋ก</Link> <Link className='btn' to={`/boards/update/${no}`}>์ˆ˜์ •</Link> </div> </div> ) } export default BoardRead
JavaScript
๋ณต์‚ฌ

files.js

import axios from 'axios'; // ์—…๋กœ๋“œ export const upload = (formData, headers) => axios.post(`/file/upload`, formData, headers) // ๋‹ค์šด๋กœ๋“œ export const download = (fileNo) => axios.get(`/file/${fileNo}`, {responseType: 'blob',} )
JavaScript
๋ณต์‚ฌ

์ธ๋„ค์ผ ์ด๋ฏธ์ง€ UI ์ถ”๊ฐ€

BoardRead.jsx

<img src={`/file/img/${file.no}`} alt={file.fileName} />
JavaScript
๋ณต์‚ฌ
import React, { useEffect } from 'react'; import { Link } from 'react-router-dom'; import * as format from '../../apis/format'; import './BoardRead.css'; const BoardRead = ({ no, board, fileList, isLoading }) => { return ( <div className='container'> <h1 className='title'>๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ</h1> <h3>๋ฒˆํ˜ธ : {no}</h3> <hr/> { isLoading && <div> <img src="/img/loading.webp" alt="loading" /> </div> } { !isLoading && board && ( <table border={1} className='table'> <tbody> <tr> <td>๋ฒˆํ˜ธ</td> <td> <input type="text" className='form-input' value={board.no} readOnly /> </td> </tr> <tr> <td>๋“ฑ๋ก์ผ์ž</td> <td> <input type="text" className='form-input' value={format.formatDate( board.regDate )} readOnly /> </td> </tr> <tr> <td>์ œ๋ชฉ</td> <td> <input type="text" className='form-input' value={board.title} readOnly /> </td> </tr> <tr> <td>์ž‘์„ฑ์ž</td> <td> <input type="text" className='form-input' value={board.writer} readOnly /> </td> </tr> <tr> <td colSpan={2}>๋‚ด์šฉ</td> </tr> <tr> <td colSpan={2}> <textarea cols="40" rows="10" className='form-input' value={board.content} readOnly></textarea> </td> </tr> <tr> <td colSpan={2}>ํŒŒ์ผ</td> </tr> <tr> <td colSpan={2}> { fileList.map( (file) => ( <div className='file-box' key={file.no}> <div className="item"> <img src={`/file/img/${file.no}`} alt={file.fileName} /> <span>{file.originName}({ format.byteToUnit(file.fileSize) })</span> </div> <div className="item"> <button className="btn" onClick={() => handleDownload(file.no, file.originName)}>๋‹ค์šด๋กœ๋“œ</button> </div> </div> ))} </td> </tr> </tbody> </table> )} <hr /> <div className="btn-box"> <Link className='btn' to="/boards">๋ชฉ๋ก</Link> <Link className='btn' to={`/boards/update/${no}`}>์ˆ˜์ •</Link> </div> </div> ) } export default BoardRead
JavaScript
๋ณต์‚ฌ

ํŒŒ์ผ ์‚ญ์ œ

์‚ญ์ œํ•  ํŒŒ์ผ ์ฒดํฌ๋ฐ•์Šค UI ์ถ”๊ฐ€, ํŒŒ์ผ ์‚ญ์ œ ๋ฒ„ํŠผ UI ๋ฐ API ์ถ”๊ฐ€

import React, { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import * as format from '../../apis/format' const BoardUpdateForm = ({ no, board, fileList, onUpdate, onDelete, isLoading, onDownload, onDeleteFile }) => { // state ์„ค์ • const [title, setTitle] = useState('') const [writer, setWriter] = useState('') const [content, setContent] = useState('') const [files, setFiles] = useState(null); // โœ… files state ์ถ”๊ฐ€ const [fileNoList, setFileList] = useState([]) // โœ… ํŒŒ์ผ ์„ ํƒ ์‚ญ์ œ - deleteFileNoList const handleChangeTitle = (e) => { setTitle(e.target.value) } const handleChangeWriter = (e) => { setWriter(e.target.value) } const handleChangeContent = (e) => { setContent(e.target.value) } const onSubmit = () => { const formData = new FormData(); formData.append('no', no); formData.append('title', title); formData.append('writer', writer); formData.append('content', content); console.log(`fileNoList --------------------------------------`); console.log(fileNoList); formData.append('deleteFileNoList', fileNoList); const headers = { headers: { 'Content-Type' : 'multipart/form-data', }, }; if (files) { for (let i = 0; i < files.length; i++) { formData.append(`files[${i}]`, files[i]); } } // onUpdate(no, title, writer, content); // onUpdate({no, title, writer, content}, headers); onUpdate(formData, headers); } const handleDelete = () => { const check = window.confirm('์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?') if( check ) onDelete(no) } const handleDownload = (fileNo, fileName) => { onDownload(fileNo, fileName) } const handleDeleteFile =(fileNo) => { const check = window.confirm('์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?') if( check ) onDeleteFile(fileNo) } // โœ… ํŒŒ์ผ ํ•ธ๋“ค๋Ÿฌ ์ถ”๊ฐ€ const handleFileChange = (e) => { setFiles(e.target.files); }; // ํŒŒ์ผ ๋ฒˆํ˜ธ ์ฒดํฌ const checkFileNo = (no) => { setFileList( [...fileNoList, no] ) } useEffect( () => { if( board ) { setTitle(board.title); setWriter(board.writer) setContent(board.content) } }, [board]) return ( <div className='container'> <h1 className='title'>๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •</h1> <h3>๋ฒˆํ˜ธ : {no}</h3> <hr/> { isLoading && <div> <img src="/img/loading.webp" alt="loading" /> </div> } { !isLoading && board && ( <table border={1} className='table'> <tbody> <tr> <td>๋ฒˆํ˜ธ</td> <td> <input type="text" className='form-input' value={board.no} readOnly /> </td> </tr> <tr> <td>๋“ฑ๋ก์ผ์ž</td> <td> <input type="text" className='form-input' value={ format.formatDate(board.regDate) } readOnly /> </td> </tr> <tr> <td>์ œ๋ชฉ</td> <td> <input type="text" className='form-input' value={title} onChange={handleChangeTitle} /> </td> </tr> <tr> <td>์ž‘์„ฑ์ž</td> <td> <input type="text" className='form-input' value={writer} onChange={handleChangeWriter} /> </td> </tr> <tr> <td colSpan={2}>๋‚ด์šฉ</td> </tr> <tr> <td colSpan={2}> <textarea cols="40" rows="10" className='form-input' value={content} onChange={handleChangeContent}></textarea> </td> </tr> <tr> <td>ํŒŒ์ผ ์—…๋กœ๋“œ</td> <td> <input type='file' onChange={handleFileChange} multiple /> </td> </tr> { fileList && fileList.length > 0 && <tr> <td>์‚ญ์ œ</td> <td>ํŒŒ์ผ</td> </tr> } { fileList.length == 0 && <tr> <td colSpan={2} align='center'> ์—…๋กœ๋“œ๋œ ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค. </td> </tr> } { fileList.map( (file) => ( <tr> <td> <input type="checkbox" onChange={ () => checkFileNo(file.no) } /> </td> <td> <div className='file-box' key={file.no}> <div className="item"> <img src={`/file/img/${file.no}`} alt={file.fileName} /> <span>{file.originName}({ format.byteToUnit(file.fileSize) })</span> </div> <div className="item"> <button className="btn" onClick={() => handleDownload(file.no, file.originName)}>๋‹ค์šด๋กœ๋“œ</button> <button className="btn" onClick={() => handleDeleteFile(file.no)}>์‚ญ์ œ</button> </div> </div> </td> </tr> ))} </tbody> </table> )} <hr /> <div className="btn-box"> <div className="item"> <Link to="/boards" className='btn'>๋ชฉ๋ก</Link> </div> <div className="item"> <button className='btn' onClick={ handleDelete }>์‚ญ์ œ</button> <button className='btn' tton onClick={ onSubmit }>์ˆ˜์ •</button> </div> </div> </div> ) } export default BoardUpdateForm
JavaScript
๋ณต์‚ฌ

Github (์†Œ์Šค์ฝ”๋“œ)