본문 바로가기
프로젝트/Recipository

[Dev] 22.12.05. File Upload (임시, 재개발 예정)

by 규글 2022. 12. 5.

 이번에는 게시글 form에 넣을 수 있는 input type file을 받아 저장하는 방법을 간단히 구현만 해두고 넘어가려고 한다. 간단히 구현만 해놓는 이유는 후에 AWS에 올릴 때 저장 방식이 달라질 것으로 생각하고 있기 때문이다.

 

		String realPath=request.getServletContext().getRealPath("/upload");
        
		C:\Users\kyuhwan\AppData\Local\Temp\tomcat-docbase.8080.7322970183685590318\upload

 우선 국비 과정에서의 프로젝트 중에 내용을 가져왔다. 당시에는 개발 tool로 eclipse를 사용했고, file upload를 위한 WebContent 하위의 경로를 얻기 위해 ServletContext의 getRealPath( ) method를 사용해서 작업을 진행했다. 이 method는 project의 WebContent directory의 upload 에 대한 real path를 return 했었지만, 지금은 그렇지 않다. 게다가 지금은 프로젝트를 run 할 때마다 저장 공간이 바뀌는 문제가 있다.(어떤 차이로 인한 것인지는 의문이다.) 물론 가장 큰 것은 project에 WebContent 이나 wepapp 이 존재하지 않는다는 점이다.

 

 한 블로그에서 이에 대한 내용을 언급하고 있었다.[각주:1] 해당 블로그에서는 Spring Boot를 이용할 때 사용되는 resources directory에 주로 css, js, image 등의 static resoureces를 저장하는데, getRealPath( ) method는 webapp directory를 우선으로 찾으며 그것이 없을 경우 임시 경로로 찾아가기 때문에 Spring Boot에서는 권장되지 않는다고 말한다. 그래서 편법으로 resourecs 를 없애고 webapp으로 대체하는 방안이 있다고는 하는데, 이러면 다음과 같은 문제가 발생한다고 말하면서 프로젝트의 바깥에 저장하는 편이 현명하다고 언급한다.

  • 기존에 사용하던 resource에 대한 경로를 전부 수정해주어야 한다.
  • Spring Boot는 jar로 배포되는데, webapp 때문에 정상 배포가 불가능하다. (이것이 가장 큰 문제)
  • 재배포 시 project 내의 FileOutputStream의 모든 변경 사항(새 파일 혹은 편집된 파일)이 손실된다.
  • 상대 경로가 매번 다르기때문에 project의 이식이 쉽지 않다.
  • 애초에 getRealPath 자체가 활용되는 경우가 거의 없다.

 

 어차피 지금은 임시로 작성할 부분이기도 하므로 따로 upload 한 file을 저장할 directory를 만들어서 그곳에 저장을 하는 방식을 택하기로 했다.

 

RecipeServiceImpl.java

    @Value("${file.directory}")
    private String savePath;
    
    @Transactional
    @Override
    public boolean write(RecipeDto recipeDto, MultipartFile imageFile) {
        if(!imageFile.isEmpty()){
            // application.properties 에 작성한 save path
            String savePath = this.savePath + File.separator;
            File file = new File(savePath);
            // 해당 경로에 directory가 없을 시 만듦
            if(!file.exists()){
                file.mkdir();
            }

            // 넘겨받은 file name
            String originFileName = imageFile.getOriginalFilename();
            // save file name에 사용할 UUID String
            String uuid = UUID.randomUUID().toString();
            // save file name
            String saveFileName = uuid + originFileName;

            try {
                // directory에 upload file save
                imageFile.transferTo(new File(savePath + saveFileName));
                recipeDto.setImagePath(savePath + saveFileName);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        Recipe recipe = recipeDto.toEntity();
        recipe.setMenuAtLink();

        recipeRepository.save(recipe);
        linkRepository.saveAll(recipe.getLink());

        return false;
    }

 

application.properties

# upload 한 file을 저장할 save directory 설정
file.directory=C:/Users/kyuhwan/Desktop/Projects/recipository_upload

 우선 application.properties에 위와 같이 upload한 file을 저장할 directory 정보를 적어둔다. 이는 @Value annotation을 통해 전달 받아서 Service logic에서 사용된다. 이때 사용되는 save path 에는 File.separator 를 붙여주는데, 이는 window에서의 "/" 를 hard coding 하는 대신에 각 운영 체제에 맞는 separator를 넣어준다. Service logic에는 Controller를 통해 받은 MultipartFile type의 image file을 넘겨받는다. 이때 우선적으로 해당 file이 존재하는지 먼저 확인 후에, 존재할 경우 file을 save하는 과정으로 거치도록 if문 안쪽으로 넣어주었다. 만약 file이 존재할 경우 설정해준 경로에 directory가 없을 시 directory를 만들도록 했다.

 

 이어서 본격적으로 file을 save하는 logic이다. 우선 image file의 original file name을 받는다. 이때 file의 original name을 그대로 save name으로 동일하게 할 경우, 같은 이름의 image file을 올렸을 때 file이 중복되므로 이 둘은 서로 다르게 해야한다. 이전 국비 과정에서의 프로젝트에서는 System.currentTimeMillis( ) method를 사용해서 시간 정보를 통해 save file name을 다르게 해줬지만 이번에는 새롭게 UUID 라는 친구를 이용해보았다. UUID (Universally Unique IDentifier : 범용 고유 식별자) 는 사전적으로 소프트웨어 구축에 사용되는 식별자의 표준이라는 의미를 가진다. 네트워크 상에서 서로 다른 개체를 구분하기 위해서는 각각의 고유한 이름이 필요한 것이며 이를 위해 탄생한 것이라고 한다. UUID의 표준에 따라 이름을 부여한다면 그 고유성을 완벽히 보장한다고는 할 수 없으나 실제로는 거의 중복될 가능성이 없다고 인정되어 많이 사용된다고 한다. 이 UUID를 original file name의 앞에 붙여서 저장하면 그 중복될 가능성이 더 적어진다고 할 수 있겠다.

 

  Naming에 대한 setting 후에는 MultipartFile 객체의 transferTo( ) method를 사용해서 image file을 저장한다. 그리고 저장한 image file의 path를 Dto 객체에 setting 하고 Entity로 전환한 뒤 repository에 save 하는 과정을 거친다.

 

 이제 다음 게시글에서는 작성한 게시물의 내용 중에 작성자의 정보를 setting 하는 과정을 기록하고, 이후 참고할 몇 가지 블로그들을 기록한 후에 client 측의 html과 js, server 측의 controller와 service의 흐름을 총괄하여 기록하겠다.

 

Reference

댓글