Framework/Spring

Spring 첨부파일 업로드 구현하기

MINGYUM 2022. 3. 3. 23:23

쓰던거 날려서 다시 쓰기 😊

Spring Gradle 환경에서 첨부파일 구현하는 작업을 해보겠다. 

프론트 단에서는 Axios를 사용하고 있으므로 JSON 데이터로 반환하는 Response 방식을 따라갈 것이고, 

Controller에서 Upload를 처리할 수 있도록 테스트까지 진행하려 한다. 

 

1. Configuration 설정

(1) WebConfig 

AbstaractDispatcherServletInitializer를 상속받아 WebConfig를 구현한다. 

customizeReegistration을 Override하여 내부에서 MultiPargConfigElement 객체를 만들어주고

파일이 저장될 location, maxFileSize, maxRequestSize, fileSIezThresHold를 설정해준다. 

@Configuration
public class WebConfig extends AbstractDispatcherServletInitializer {

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration){
        registration.setInitParameter("throwsExceptionNoHandlerFound", "true");

        MultipartConfigElement multipartConfigElement = new MultipartConfigElement("C:\\upload\\temp", 20971520,41943040,20971520);
        registration.setMultipartConfig(multipartConfigElement);
    }
}

 

(2) ServeltConfig

MultipartResovler를 빈으로 등록해준다. 

@Configuration
public class ServletConfig {
    @Bean
    public MultipartResolver multipartResolver(){
        StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
        return resolver;
    }
}

 

2. UploadController

 

(1) 기초 UploadController 설계

 @PostMapping(value = "/uploadAjaxAction", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<List<AttachFileDto>> uploadAjaxPost(MultipartFile[] uploadFile){

        String uploadFolder = "C:\\upload";

        for(MultipartFile multipartFile : uploadFile){

            String uploadFileName = multipartFile.getOriginalFilename();
            
            File saveFile = new File(uploadFolder, uploadFileName);

            try{
                multipartFile.transferTo(saveFile);
            } catch (Exception e){
                e.printStackTrace();
            } // end catch
        } // end for
        return new ResponseEntity<>(list, HttpStatus.OK);
    }

multipartFile, 즉 매개변수인 uploadFile에 들어가게 되는 객체는 try문에서 transferTo 함수로 저장되는 

saveFile이다. 

saveFile 객체를 만들 때는 uploadFolder, uploadFileName 두 변수가 필요하다. 

 

 

3. 파일 업로드 상세 처리

(1) 폴더 나누기

    // 년/월/일 폴더의 생성으로 한 폴더에 너무 많은 파일이 들어가지 않도록 제어
    private String getFolder(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        Date date = new Date();

        String str = sdf.format(date);

        return str.replace("-", File.separator);
    }

saveFile 객체를 만들 때 사용하는 uploadFolder의 경로를 변경해서, 

현재 년/월/일에 따라 다르게 폴더가 설정되도록 한다. 

아래 리턴하는 부분에서 File.separator은 \과 같은 파일 경로 구분자이다. 

 

(2) 중복 방지 

이름 중복이 되었을 경우 파일이 치환되는 상황을 막기 위해서

UUID를 생성하여 파일 이름으로 저장한다. 

  UUID uuid = UUID.randomUUID();

  uploadFileName = uuid.toString() + "_" + uploadFileName;

다음과 같은 로직을 사용해 랜덤으로 생성한 UUID를 덧붙여 uploadFileName을 설정한다. 

 

이 로직들을 합친 UploadController는 다음과 같다.

    @PostMapping(value = "/uploadAjaxAction", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<List<AttachFileDto>> uploadAjaxPost(MultipartFile[] uploadFile){

        String uploadFolder = "C:\\upload";

        String uploadFolderPath = getFolder();
        File uploadPath = new File(uploadFolder, uploadFolderPath); // 오늘 날짜의 경로를 문자열로 생성

        if (uploadPath.exists() == false) {
            uploadPath.mkdirs();
        }

        for(MultipartFile multipartFile : uploadFile){

            String uploadFileName = multipartFile.getOriginalFilename();

            // IE has file path
            uploadFileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
            attachDto.setFileName(uploadFileName);

            // random한 UUID를 생성하여 원본 이름과 같은 파일들이 다른 이름으로 생성되도록 함.
            UUID uuid = UUID.randomUUID();

            uploadFileName = uuid.toString() + "_" + uploadFileName;

            try{
                File saveFile = new File(uploadPath, uploadFileName);
                multipartFile.transferTo(saveFile);
            } catch (Exception e){
                e.printStackTrace();
            } // end catch
        } // end for
        return new ResponseEntity<>(list, HttpStatus.OK);
    }

 

4. 썸네일 이미지 생성

 

썸네일을 설정하기 위해서 우선 첨부파일이 이미지 파일인지 확인해야 한다. 

    // 파일이 이미지인지 확인하는 함수
    private boolean checkImageType(File file){
        try{
            String contentType = Files.probeContentType(file.toPath());

            return contentType.startsWith("image");
        } catch (IOException e){
            e.printStackTrace();
        }
        return false;
    }

파일의 contentType을 가져와서 "image"로 시작하는 문자열이라면 true를 반환한다. 

 

이제 이미지 파일이라면 썸네일을 생성하도록 해야한다. 

   try{
                File saveFile = new File(uploadPath, uploadFileName);
                multipartFile.transferTo(saveFile);

                //check image type file
                if(checkImageType(saveFile)){
                    FileOutputStream thumbnail = new FileOutputStream(new File(uploadPath, "s_" + uploadFileName));
                    // thumbnail은 s_로 시작하는 이름으로 파일이 생성된다.
                    Thumbnailator.createThumbnail(multipartFile.getInputStream(), thumbnail, 100, 100);
                    thumbnail.close();
                }

            } catch (Exception e){
                e.printStackTrace();
            } // end catch
        } // end for

 

FileOutputStream으로 데이터를 파일에 바이트 스트림으로 저장한다. 

생성자에 File객체를 uploadPath, 's_'로 시작하는 파일이름으로 지정해서 매개변수에 넣어주면 thumbnail 객체가 생성된다.

플러그인으로 추가한 Thumbnailator 의 createThumbnail 함수에 

받아온 파일의 InputStream과 thumbnail FileOutputStream 객체, 그리고 width와 height를 설정해주면 

이미지 파일 업로드 시 s_로 시작하는 썸네일 파일이 따로 저장되는 것을 확인할 수 있다. 

 

 

5. dto 객체 생성하여 처리

    @PostMapping(value = "/uploadAjaxAction", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<List<AttachFileDto>> uploadAjaxPost(MultipartFile[] uploadFile){
        List<AttachFileDto> list = new ArrayList<>();

        log.info("update ajax post .... ");
        String uploadFolder = "C:\\upload";

        String uploadFolderPath = getFolder();
        File uploadPath = new File(uploadFolder, uploadFolderPath); // 오늘 날짜의 경로를 문자열로 생성

        if (uploadPath.exists() == false) {
            uploadPath.mkdirs();
        }

        for(MultipartFile multipartFile : uploadFile) {
            AttachFileDto attachDto  = new AttachFileDto();

            String uploadFileName = multipartFile.getOriginalFilename();

            // IE has file path
            uploadFileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1);
            attachDto.setFileName(uploadFileName);

            // random한 UUID를 생성하여 원본 이름과 같은 파일들이 다른 이름으로 생성되도록 함.
            UUID uuid = UUID.randomUUID();

            uploadFileName = uuid.toString() + "_" + uploadFileName;

            try{
                File saveFile = new File(uploadPath, uploadFileName);
                multipartFile.transferTo(saveFile);

                attachDto.setUuid(uuid.toString());
                attachDto.setUploadPath(uploadFolderPath);

                //check image type file
                if(checkImageType(saveFile)){
                    attachDto.setImage(true);

                    FileOutputStream thumbnail = new FileOutputStream(new File(uploadPath, "s_" + uploadFileName));
                    // thumbnail은 s_로 시작하는 이름으로 파일이 생성된다.
                    Thumbnailator.createThumbnail(multipartFile.getInputStream(), thumbnail, 100, 100);
                    thumbnail.close();
                }

                list.add(attachDto);

            } catch (Exception e){
                e.printStackTrace();
            } // end catch
        } // end for
        return new ResponseEntity<>(list, HttpStatus.OK);
    }

attachFileDto 리스트를 JSON 데이터를 반환하는 방식으로 바꾼다. 

 

브라우저 단에서 function showUploadFile()을 만들어 파일이 아닌 경우에 파일 아이콘을 보여줄 수 있다. 

 

이미지 파일의 경우 썸네일 파일을 보여주어야하는데,

해당 파일의 경로와 uuid가 붙은 파일의 이름이 필요하므로 서버에서 '파일의 경로' + 's_' + 'uuid가 붙은 파일 이름' 을 불러와 <img> 태그를 사용해 처리한다. 

 

    @GetMapping("/display")
    @ResponseBody
    public ResponseEntity<byte[]> getFile(String fileName){
        File file = new File("C:\\upload\\" + fileName);
        ResponseEntity<byte[]> result = null;

        try {
            HttpHeaders header = new HttpHeaders();
            header.add("Content-Type", Files.probeContentType(file.toPath()));
            result = new ResponseEntity<>(FileCopyUtils.copyToByteArray(file), header, HttpStatus.OK);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

브라우저 단에서 encodeURIComponent() 를 이용해서 URI 호출에 적합한 문자열로 인코딩해야한다.

 

다음 단계는 첨부파일 다운로드 😊