본문 바로가기
728x90
반응형

 

게시판 기능에 파일업로드도 추가해보도록 한다.

 

    @Autowired
    FileService fileService;
 
    private static String UPLOAD_FOLDER = "D:/logs/";
    private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
    @PostMapping("/Modify")
    public ModelAndView modifyPost(HttpServletRequest request, HttpServletResponse response, @RequestParam("file") MultipartFile file) throws UnsupportedEncodingException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=UTF-8");
        request.setCharacterEncoding("UTF-8");
       
        String num = request.getParameter("num");
        Post post = postService.getPost(num);

        post.setAuthor(request.getParameter("author"));
        post.setContents(request.getParameter("contents"));
        post.setTitle(request.getParameter("title"));
        post.setNum(request.getParameter("num"));

        if (!file.isEmpty()){
            try{
                FileInfo fileInfo = new FileInfo();
                String originalFilename = file.getOriginalFilename();
                // String fileExt = FilenameUtils.getExtension(file.getOriginalFilename());
                String fileExt = originalFilename.contains(".")
                               ? originalFilename.substring(originalFilename.lastIndexOf('.') + 1)
                               : "";
                //랜덤 이름 생성 (겹치지 않기 위해서)
                String storedFilename = UUID.randomUUID().toString() + "." + fileExt;
                Path path = Paths.get(UPLOAD_FOLDER, storedFilename);
               
                byte[] bytes = file.getBytes();
                //실제 파일 저장
                Files.write(path, bytes);

                fileInfo.setOriginalFilename(originalFilename);
                fileInfo.setStoredFilename(storedFilename);
                fileInfo.setFileExt(fileExt);
                fileInfo.setFilePath(UPLOAD_FOLDER);
                fileInfo.setPostId(post.getNum());


                // fileService.insertFile(fileInfo);
                postService.modifyPost(post, fileInfo);

            }catch (IOException e){
                e.printStackTrace();
            }
        }else{
            postService.modifyPost(post);
        }

        mav.setViewName("redirect:/Content?num=" + post.getNum());


        return mav;
    }

    @PostMapping("/Write")    
    public ModelAndView writePost(@ModelAttribute Post post, HttpServletResponse response, @RequestParam("file") MultipartFile file) throws UnsupportedEncodingException{

        System.out.println("test");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=UTF-8");

        System.out.println(file + "dd " + !file.isEmpty());


        if (!file.isEmpty()){
            try{
                FileInfo fileInfo = new FileInfo();
                String originalFilename = file.getOriginalFilename();
                String fileExt = originalFilename.contains(".")
                               ? originalFilename.substring(originalFilename.lastIndexOf('.') + 1)
                               : "";
                //랜덤 이름 생성 (겹치지 않기 위해서)
                String storedFilename = UUID.randomUUID().toString() + "." + fileExt;
                Path path = Paths.get(UPLOAD_FOLDER, storedFilename);
               
                byte[] bytes = file.getBytes();
                //실제 파일 저장
                Files.write(path, bytes);

                fileInfo.setOriginalFilename(originalFilename);
                fileInfo.setStoredFilename(storedFilename);
                fileInfo.setFileExt(fileExt);
                fileInfo.setFilePath(UPLOAD_FOLDER);
                postService.inserPost(post, fileInfo);

            }catch (IOException e){
                e.printStackTrace();
            }
        }else{
            postService.inserPost(post);
        }
       
        mav.setViewName("redirect:/");

        return mav;
    }
   

 

새롭게 추가한 부분은 if 문 file.isEmpty() 부분이다.

파일이 있으면 파일 업로드 로직을 수행하게 된다.

 

상단에 static 변수와 FileService 도 주입시켜주었다.

 

File VO(model) 객체도 생성하였는데

package com.example.post.model;

import lombok.Data;

@Data
public class FileInfo {
    private String fileId;
    private String postId;
    private String originalFilename;
    private String storedFilename;
    private String filePath;
    private String fileExt;
}

 

fileId, postId(게시글), original(업로드 시 파일이름), stored(저장시 파일이름), 파일경로, Ext(확장자) 이다.

 

위 메서드에 보면 storedFileName 을 무작위로 생성하여, fileExt(확장자)와 더해 저장하게 되는데

무작위로 생성하여 굳이 저장시 파일 이름을 만드는 이유는 업로드 파일이름이 겹칠 수 있어 다운로드때 문제가 발생할 수 있기 때문이다.

 

CREATE TABLE FileInfo (
    fileId INT IDENTITY(1,1) PRIMARY KEY,
    postId INT NOT NULL,
    originalFilename NVARCHAR(255) NOT NULL,
    storedFilename NVARCHAR(255) NOT NULL,
    filePath NVARCHAR(500) NOT NULL,
    fileExt NVARCHAR(10),
    FOREIGN KEY (postId) REFERENCES test1(num)
);

 

테이블 생성은 이렇게 했다.

 

 

다운로드 컨트롤러는 내가 따로만들었는데..  파일컨트롤러로 구분하는게 더 좋은지 아직 방법은 잘 모르겠다.

 

controller/DownloadController.java

 

package com.example.post.controller;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.post.model.FileInfo;
import com.example.post.service.FileService;


@Controller
@RequestMapping("/Download")
public class DownloadController {
   
    private static String UPLOAD_FOLDER = "D:/logs/";

    @Autowired FileService fileService;


    @GetMapping("/{fileId}")
    public ResponseEntity<Resource> downloadFile(@PathVariable("fileId") int fileId) throws MalformedURLException, UnsupportedEncodingException {


        FileInfo fileInfo = fileService.selectFileInfo(fileId);

        if(fileInfo == null){
            return ResponseEntity.notFound().build();
        }

        Path path = Paths.get(fileInfo.getFilePath(), fileInfo.getStoredFilename());
        Resource resource = new UrlResource(path.toUri());

        if(!resource.exists()){
            return ResponseEntity.notFound().build();
        }

        // 파일명을 UTF-8로 인코딩
        String encodedFileName = URLEncoder.encode(fileInfo.getOriginalFilename(), StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
        String contentDisposition = "attachment; filename*=UTF-8''" + encodedFileName;

        //응답헤더에 CONTETN_DISPOSITION -> 첨부파일이라고 알려줌, attachment; 다음나오는것 다운로드, .body(resource)->resource 에 파일내용 담아감
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
                .body(resource);
    }
   
}

 

 


 

컨트롤러를 다 만들었으니 Service 수정을 해본다.

 

우선 업로드시에 파일도 추가하여야 하므로 PostService 부터 수정한다.

    @Autowired FileDao fileDao;

    @Autowired FileService fileService;

    @Transactional
    public void inserPost(Post post, FileInfo fileInfo) {
        postDao.insertPost(post);
        fileInfo.setPostId(post.getNum());
        fileDao.insertFile(fileInfo);
    }

 

Transactional 처리를 해준다.

 

    @Transactional
    public void deletePost(String num) throws IOException {
        commentDao.deleteCommentByPostId(num);
        List<FileInfo> fileInfoList = fileDao.selectFileByPostId(num);
        if(!fileInfoList.isEmpty()){
            for(FileInfo fileInfo : fileInfoList){
                System.out.println(fileInfo + " \n\n\n\n");
                fileService.deleteFile(fileInfo);
            }
        }
        //외래키때문에 맨마지막에 삭제해야함.
        postDao.deletePost(num);
    }
    @Transactional
    public void modifyPost(Post post, FileInfo fileInfo) throws IOException {
        postDao.modifyPost(post);
        String fileId = fileDao.selectFileByPostId(post.getNum()).get(0).getFileId();
        System.out.println(fileId);
        fileInfo.setFileId(fileId);
        fileService.deleteFile(fileInfo);
        fileDao.insertFile(fileInfo);
    }

 

그외에 수정, 삭제에도 추가해준다.

외래키가 설정되어있으므로, 파일부터 지우고 게시글을 지울수 있도록 한다.

 

modify 의 경우 파일id 를 얻어온 뒤 로직 수행


 

service package 에 FileService.java 도 생성해준다.

 

package com.example.post.service;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.post.dao.FileDao;
import com.example.post.model.FileInfo;

@Service
public class FileService {

    @Autowired
    FileDao fileDao;

   
    public void insertFile(FileInfo fileInfo) {
        fileDao.insertFile(fileInfo);
    }


    public FileInfo selectFileInfo(int fileId) {
        return fileDao.selectFile(fileId);
    }


    public List<FileInfo> selectFileByPostId(String num) {
        return fileDao.selectFileByPostId(num);
    }

    public void deleteFile(FileInfo fileInfo) throws IOException{

        Files.delete(Paths.get(fileInfo.getFilePath() + "" + fileInfo.getStoredFilename()));        
        fileDao.deleteFile(fileInfo.getFileId());
    }
   
}

 

delete 의 경우 실제 파일도 지워야하므로, Files.delete(path) 를 통해 실제 경로 파일을 삭제하고, DB 경로도 지운다.

이제보니 Files.delete 결과를 제대로 받고나서 Dao 수행하는게 맞는거같다.

 


 

dao package 에도 Dao 를 하나더 만들어준다.

FileDao.java

 

package com.example.post.dao;

import java.util.List;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.example.post.model.FileInfo;

@Repository
public class FileDao {

    @Autowired
    private SqlSessionTemplate sqlSession;


    public void insertFile(FileInfo fileInfo) {
        sqlSession.insert("FileMapper.insertFile", fileInfo);
    }


    public FileInfo selectFile(int fileId) {
        return sqlSession.selectOne("FileMapper.selectFile", fileId);
    }


    public List<FileInfo> selectFileByPostId(String num) {
        return sqlSession.selectList("FileMapper.selectFileByPostId", Integer.parseInt(num));
    }


    public void deleteFile(String fileId) {
        sqlSession.delete("FileMapper.deleteFile", Integer.parseInt(fileId));
    }
   
}

 

새로운 mapper 를 작성할것이기에 FileMapper.~~~ 로 지정하였다.

 

아래는 mmapper-file.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">
<mapper namespace="FileMapper">

<insert id="insertFile" parameterType="com.example.post.model.FileInfo">
  INSERT INTO FILEINFO1 (postId, originalFilename, storedFilename, filePath, fileExt)
         VALUES (#{postId}, #{originalFilename}, #{storedFilename}, #{filePath}, #{fileExt})
</insert>

<select id="selectFileByPostId" parameterType="int" resultType="com.example.post.model.FileInfo">
  SELECT * FROM FILEINFO1 WHERE POSTID = #{num}
</select>

<select id="selectFile" parameterType="int" resultType="com.example.post.model.FileInfo">
  SELECT * FROM FILEINFO1 WHERE FILEID = #{num}
</select>


<delete id="deleteFile" parameterType="int">
  DELETE FROM FILEINFO1 WHERE fileId = #{fileId}
</delete>

</mapper>

 

파일 관련 CRUD 작업


 

실제 jsp 에도 수정을 통해 파일 업로드/다운로드 로직을 추가해준다.

 

     
        <c:when test="${status == 1}">
    <form id="postForm" action="${pageContext.request.contextPath}/Modify?num=${post.num}" method="post" accept-charset="utf-8" enctype="multipart/form-data">
       
        <label for="title">글번호:</label>
        <input type="text" id="num" name="num" value="${post.num}" readonly><br><br>
       
        <label for="title">제목:</label>
        <input type="text" id="title" name="title" value="${post.title}"><br><br>

        <label for="author">작성자:</label>
        <input type="text" id="author" name="author" value="${post.author}"><br><br>

        <label for="file">파일첨부:</label>
        <input type="file" id="file" name="file"><br><br>

        <label for="contents">내용:</label><br>
        <textarea id="contents" name="contents">${post.contents}</textarea><br><br>
        <input type="submit" value="수정">
    </form>
 
...중략...
 

<script>

document.getElementById('postForm').addEventListener('submit', function(event) {
    const fileInput = document.getElementById("file");
    const maxSize = 10 * 1024 * 1024;
    if (fileInput.files.length > 0){
        if(fileInput.files[0].size > maxSize){
            alert("10MB 이하만 업로드 가능합니다.")
            event.preventDefault();
        }
    }
});


</script>
   

 

Write.jsp, post.jsp 둘다 파일 업로드 내용을 추가시켜준다.

10MB 이상은 업로드가 불가하기에 eventListener 등록을 통해 미리 제출을 방지한다.

728x90
반응형

한걸음 한걸음

개인적인 기록