본문 바로가기

웹 개발/Spring

[Web_Spring] 20

실습(댓글)

- ex03

 

 

1. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>ex03</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ex03</name> 
    <description>attachment</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- JSON -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.2</version>
        </dependency>
        <dependency>
            <groupId>net.coobird</groupId>
            <artifactId>thumbnailator</artifactId>
            <version>0.4.8</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

 

 

 

2. src/main/resource/application.properties​

#server port
server.port=10004

#multipart
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=40MB
spring.servlet.multipart.max-request-size=100MB
spring.servlet.multipart.file-size-threshold=100MB

 

 

 

3. src/main/java/com.example.ex03/domain/vo/AttachFileVO.java​

package com.example.ex03.domain.vo;

import lombok.Data;
import org.springframework.stereotype.Component;

@Component
@Data
public class AttachFileVO {
    private String fileName;
    private String originalFileName;
    private String uploadDirectory;
    private boolean image;
}

 

 

 

4. src/main/resource/templates/upload/uploadForm.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>파일 업로드(form태그)</title>
</head> 
<body>
<form action="/upload/uploadForm" method="post" enctype="multipart/form-data">
    <input type="file" name="files" multiple>
    <button>Submit</button>
</form>
</body>
</html>

 

 

 

5. src/main/resource/templates/upload/uploadAjax.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>파일 업로드(Ajax)</title>
    <style>
        .result {
            width: 100%;
        }

        .result ul {
            display: flex;
            justify-content: center;
        }

        .result ul li {
            list-style: none;
            padding: 15px;
        }
    </style>
</head>
<body>
<div class="upload">
    <input type="file" name="files" multiple>
</div>

<div class="result">
    <ul></ul>
</div>

<button id="upload">Submit</button>
</body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
    let regex = new RegExp("(.*?)\.(exe|sh|zip|alz)$");
    let maxSize = 5242880; // 5MB
    const result = $("div.result ul");

    function checkExtension(fileName, fileSize){
        if(regex.test(fileName)){
            alert("(" + fileName + ")업로드 할 수 없는 파일의 형식입니다.")
            return false;
        }

        if(fileSize >= maxSize){
            alert("(" + fileName + ")파일 사이즈 초과")
            return false;
        }

        return true;
    }

    $("#upload").on("click", function(e){
        let formData = new FormData();
        let input = $("input[name='files']");
        let files = input[0].files;
        // console.log(files);
        for(let i=0; i<files.length; i++){
            if(checkExtension(files[i].name, files[i].size)){
                formData.append("files", files[i]);
            }
        }

        $.ajax({
            url: "uploadAjax",
            type: "post",
            data: formData,
            processData: false,
            contentType: false,
            success: function(fileList){
                showUploadFile(fileList);
            }
        });
    });

    function showUploadFile(fileList){
        let str = "";
        $.each(fileList, function(i, file){
            str += "<li><a href='/upload/download?path=" + file.uploadDirectory + "/" + file.fileName + "'>";
            str += file.image ? "<img src='/upload/display?path=" + file.uploadDirectory + "/t_" + file.fileName + "'>"
                : "<img src='/images/attach.png' width='100'>";
            str += "</a>" + file.originalFileName;
            str += "<span data-path='" + file.uploadDirectory + "/t_" + file.fileName + "' style='cursor: pointer'>x</span>"
            str += "</li>";
        });

        result.append(str);
    }

    $(".result").on("click", "span", function(e){
        let path = $(this).data("path");
        let li = $(this).closest("li");
        $.ajax({
            url: "delete",
            type: "delete",
            data: {path: path},
            success: function(){
                li.remove();
            }
        });
    });

</script>
</html>

 

 

 

6. src/main/java/com.example.ex03/controller/UploadController.java​

package com.example.ex03.controller;

import com.example.ex03.domain.vo.AttachFileVO;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnailator;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

// 1. 동일한 이름으로 파일이 업로드 되면, 기존 파일 삭제
// 2. 이미지 파일의 경우 원본 파일의 용량이 클 수 있기 때문에 썸네일 이미지가 필요하다.
// 3. 첨부파일 공격에 대비하기 위한 업로드 파일의 확장자 제한

@Controller
@Slf4j
@RequestMapping("/upload/*")
public class UploadController {
    //    form태그를 사용한 파일 업로드
    @GetMapping("/uploadForm")
    public void uploadForm(){
        log.info("upload form");
    }

    @PostMapping("/uploadForm")
    public void uploadForm(MultipartFile[] files) throws IOException {
        String rootDirectory = "C:/upload";

        for (MultipartFile file : files){
            log.info("------------------------------------");
            log.info("upload file name : " + file.getOriginalFilename());
            log.info("upload file size : " + file.getSize());

            File saveFile = new File(rootDirectory, file.getOriginalFilename());
            file.transferTo(saveFile);
        }
    }

    //    ajax를 사용한 파일 업로드
    @GetMapping("/uploadAjax")
    public void uploadAjax(){
        log.info("upload ajax");
    }

    @ResponseBody //REST
    @PostMapping("/uploadAjax")
    public List<AttachFileVO> uploadAjax(MultipartFile[] files) throws IOException{
        List<AttachFileVO> fileList = new ArrayList<>();
        String rootDirectory = "C:/upload";

        File uploadDirectory = new File(rootDirectory, getDateDirectory());
        if(!uploadDirectory.exists()) {uploadDirectory.mkdirs();}

        for (MultipartFile file : files){
            log.info("------------------------------------");
            log.info("upload file name : " + file.getOriginalFilename());
            log.info("upload file size : " + file.getSize());
            AttachFileVO attachFileVO = new AttachFileVO();

            UUID uuid = UUID.randomUUID();
            String fileName = uuid.toString() + "_" + file.getOriginalFilename();

            attachFileVO.setOriginalFileName(file.getOriginalFilename());
            attachFileVO.setFileName(fileName);
            attachFileVO.setUploadDirectory(getDateDirectory());

            File saveFile = new File(uploadDirectory, fileName);
            file.transferTo(saveFile);

            if(checkImageType(saveFile)){
                FileOutputStream thumbnail = new FileOutputStream(new File(uploadDirectory, "t_" + fileName));
//                MultipartFile객체를 통해 바로 파일을 가져올 경우,
//                임시로 저장될 영역을 임계 영역이라 한다.
//                apllication.properties에서 임계 영역에 대한 용량을 설정해 주어야
//                그 영역에 먼저 업로드 후 inputStream()을 가져올 수 있다.
                Thumbnailator.createThumbnail(file.getInputStream(), thumbnail, 100, 100);
                thumbnail.close();
                attachFileVO.setImage(true);
            }
            fileList.add(attachFileVO);
        }
        return fileList;
    }

    @GetMapping("display")
    @ResponseBody
    public byte[] getFile(String path) throws IOException {
        return FileCopyUtils.copyToByteArray(new File("C:/upload/" + path));
    }

    @GetMapping("/download")
    @ResponseBody
    public ResponseEntity<Resource> download(String path) throws UnsupportedEncodingException {
        Resource resource = new FileSystemResource("C:/upload/" + path);
        String name = resource.getFilename();
        name = name.substring(name.indexOf("_") + 1);
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attachment; filename=" + new String(name.getBytes("UTF-8"), "ISO-8859-1"));
        return new ResponseEntity<Resource>(resource, headers, HttpStatus.OK);
    }

    @DeleteMapping("/delete")
    @ResponseBody
    public void deleteFile(String path){
//        썸네일 삭제
        File file = new File("C:/upload", path);
        if(file.exists()) {file.delete();}

//        원본파일 삭제
        file = new File(file.getPath().replace("t_", ""));
        if(file.exists()) {file.delete();}
    }

    private String getDateDirectory(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        Date date = new Date();
        String directory = sdf.format(date);
        return directory;
    }

    private boolean checkImageType(File file) throws IOException{
        return Files.probeContentType(file.toPath()).startsWith("image");
    }
}

 

 

 

 

'웹 개발 > Spring' 카테고리의 다른 글

[Web_Spring] 22  (0) 2022.07.04
[Web_Spring] 21  (0) 2022.07.03
[Web_Spring] 23  (0) 2022.07.01
[Web_Spring] 19  (0) 2022.07.01
[Web_Spring] 18  (0) 2022.06.30