transferTo 사용 시 중복 업로드 불가 (+ 해결 방법)

2023. 11. 13. 16:20기술 창고/정보 창고

728x90
SMALL

흔히 Spring에서 transferTo를 사용하여 파일들을 업로드하는데 단일 경로에 업로드시키는 것은 가능합니다.

그러면 궁금증이 생깁니다.

다른 경로에도 동시에 업로드할 수 있을까?

 

결과적으로 말하면 불가능합니다.

한 파일에 tranferTo 함수를 적용하여 업로드 하게 되면 해당 파일은 업로드 시킨 해당 경로가 고정으로 지정됩니다.

백문이 불여일견이니 코드를 실행하면서 확인해보겠습니다.

 

우선 음악 파일을 업로드하고 해당 음악에 따른 1분 짜리 preview 음악을 또 업로드한다고 가정해보겠습니다.

 

 

상황 1. transferTo 중복 사용으로 다중 업로드 경로 지정

Controller

@PostMapping("/test/transfer")
    public ResponseEntity<ResponseBody> testTransferTo(@RequestPart("testMusic") List<MultipartFile> testMusics) throws CannotReadException, TagException, InvalidAudioFrameException, ReadOnlyFileException, IOException {

        String message = "";

        if(musicUpload.testTransferTo(testMusics)){
            log.info("[SUCCESS] 정상적으로 업로드 되었습니다.");
            message = "[SUCCESS] 정상적으로 업로드 되었습니다.";

        }else{
            log.info("[ERROR] 업로드 되지 않았습니다.");
            message = "[ERROR] 업로드 되지 않았습니다.";
        }

        return new ResponseEntity<>(new ResponseBody(StatusCode.OK, message), HttpStatus.OK);
    }

controller에 음악 등록 api 를 만들어주었습니다.

여러 음악 파일(MultipartFile) 데이터들을 리스트로 testMusic 라는 이름을 가진 데이터로 받아옵니다.

이 음악 파일들을 service 단으로 넘겨 줍니다.

 

 

Service

public HashMap<String, String> testTransferTo(HttpServletRequest request, List<MultipartFile> multipartFile) throws IOException, CannotReadException, TagException, InvalidAudioFrameException, ReadOnlyFileException {
    // 업로드 경로
    String musicDir = "C:" + File.separator + "BackEnd_Project" + File.separator + "MusicIsMyLife" + File.separator + "src" + File.separator + "main" + File.separator + "webapp" + File.separator + "music" + File.separator;
    String previewDir = "C:" + File.separator + "BackEnd_Project" + File.separator + "MusicIsMyLife" + File.separator + "src" + File.separator + "main" + File.separator + "webapp" + File.separator + "music" + File.separator + "preview" + File.separator;
    
    for(MultipartFile eachFile : multipartFile){
       // 업로드할 음악 파일의 진짜 이름 추출
       String originalFilename = eachFile.getOriginalFilename();
       // 난수화된 파일 이름과 확장자를 합친 파일명 추출
       String serverUploadFileName = createServerFileName(originalFilename);

       // 업로드한 mp3 파일과 경로를 합친 업로드 경로를 File 객체에 넣어 생성
       File musicFile = new File(getFullPath(serverUploadFileName, musicDir));

       // multipartfile에서 지원하는 transferTo 함수 사용. (해당 파일을 지정한 경로로 전송)
       eachFile.transferTo(musicFile);
      
       // 업로드한 mp3 파일과 경로를 합친 업로드 경로를 File 객체에 넣어 생성
       File previewFile = new File(getFullPath(serverUploadFileName, previewDir));

       // multipartfile에서 지원하는 transferTo 함수 사용. (해당 파일을 지정한 경로로 전송)
       eachFile.transferTo(previewFile);
    }
    
    return true;
}

음악 파일 여러 개를 받고 해당 파일 마다 본 음악 파일을 저장하고 따로 또 preview 음악 파일을 업로드하게끔 해주었습니다.

 

- musicDir : 본 음악 파일 업로드 경로

- previewDir : 프리뷰 음악 파일 업로드 경로

 

(1) for문을 돌리면서 각 음악 파일 마다 파일 명을 추출

(2) 해당 파일 명을 난수화

(3) File 객체를 사용해서 본 음악 파일 업로드 경로, 프리뷰 음악 파일 업로드 경로를 넣어 생성

(4) 각 파일 마다 transferTo 함수를 사용하여 두 곳의 경로에 업로드를 시도

 

이제 어플리케이션을 실행하여 PostMan으로 확인해보겠습니다.

 

testMusic 이라는 요청 변수명으로 3개의 음악 파일을 넣었으며 Content-Type은 당연히 Multipart/form-data 입니다.

이제 실행하면,

 

지정된 경로를 찾을 수 없다는 에러가 발생합니다.

이는 이미 처음에 본 음악 파일이 transferTo 함수를 통해 업로드가 되어 지정되었는데 다시 또다른 경로로 업로드를 시도하려고 하여 불가능하다는 에러입니다.

첫 번째 에러 문구를 보면 music\afulblafb.mp3 파일로 되어있는데 이것이 바로 처음에 musicDir 변수로 지정해둔 본 음악 파일 업로드 경로입니다.

previewDir 업로드 경로로 재차 시도되었을 때 에러가 발생하는 것이지요.

 

 

 

상황 2. 음악 파일 자체를 새로운 객체로 추가 생성 후 업로드 시도

이번에는 다르게 시도해보았습니다.

처음에 음악 파일들을 요청받고 해당 음악 파일들을 새로운 LIst<MultipartFile> 객체로 새롭게 추가 생성해주어 각자 따로 transferTo로 업로드를 시도합니다.

 

Controller

@PostMapping("/test/transfer")
public ResponseEntity<ResponseBody> testTransferTo(@RequestPart("testMusic") List<MultipartFile> testMusics) throws CannotReadException, TagException, InvalidAudioFrameException, ReadOnlyFileException, IOException {

    String message = "";

    if(musicUpload.testTransferTo(testMusics)){
        log.info("[SUCCESS] 정상적으로 업로드 되었습니다.");
        message = "[SUCCESS] 정상적으로 업로드 되었습니다.";

    }else{
        log.info("[ERROR] 업로드 되지 않았습니다.");
        message = "[ERROR] 업로드 되지 않았습니다.";
    }

    return new ResponseEntity<>(new ResponseBody(StatusCode.OK, message), HttpStatus.OK);
}

 

Service

public boolean testTransferTo(List<MultipartFile> multipartFile) throws IOException, CannotReadException, TagException, InvalidAudioFrameException, ReadOnlyFileException {
    // 업로드 경로
    String musicDir = "C:" + File.separator + "BackEnd_Project" + File.separator + "MusicIsMyLife" + File.separator + "src" + File.separator + "main" + File.separator + "webapp" + File.separator + "music" + File.separator;
    String previewDir = "C:" + File.separator + "BackEnd_Project" + File.separator + "MusicIsMyLife" + File.separator + "src" + File.separator + "main" + File.separator + "webapp" + File.separator + "music" + File.separator + "preview" + File.separator;

    // 새로운 리스트 객체로 분리하여 추가 생성
    List<MultipartFile> copyMusic = multipartFile;

    // 처음에 전달받은 음악 파일들
    for(MultipartFile eachFile : multipartFile){
        // 업로드할 음악 파일의 진짜 이름 추출
        String originalFilename = eachFile.getOriginalFilename();
        // 난수화된 파일 이름과 확장자를 합친 파일명 추출
        String serverUploadFileName = createServerFileName(originalFilename);

        // 업로드한 mp3 파일과 경로를 합친 업로드 경로를 File 객체에 넣어 생성
        File musicFile = new File(getFullPath(serverUploadFileName, musicDir));
        // multipartfile에서 지원하는 transferTo 함수 사용. (해당 파일을 지정한 경로로 전송)
        eachFile.transferTo(musicFile);

    }

    // 새로운 객체로 분리한 음악 파일들
    for(MultipartFile eachCopyFile : copyMusic){
        // 업로드할 음악 파일의 진짜 이름 추출
        String originalFilename = eachCopyFile.getOriginalFilename();
        // 난수화된 파일 이름과 확장자를 합친 파일명 추출
        String serverUploadFileName = createServerFileName(originalFilename);

        // 업로드한 mp3 파일과 경로를 합친 업로드 경로를 File 객체에 넣어 생성
        File previewFile = new File(getFullPath(serverUploadFileName, previewDir));
        // multipartfile에서 지원하는 transferTo 함수 사용. (해당 파일을 지정한 경로로 전송)
        eachCopyFile.transferTo(previewFile);
    }

    return true;
}

List<MultipartFile> copyMusics 라는 새로운 리스트 형  multipartfile 객체를 만들어 전달받은 음악 파일들을 추가로 생성해주었습니다.

 

처음에 전달받은 기존의 음악 파일들을 for문을 돌려 musicDir 경로에 업로드 하게끔 해주었고, 새로 추가한 copyMusics 도 마찬가지로 for문으로 돌려가면서 previewDir 경로로 업로드를 시도해줍니다.

 

이제 다시 PostMan으로 확인해보겠습니다.

 

 

다시 시도했더니 동일한 에러가 발생하였습니다.

이 두 가지의 케이스로 확실히 알 수 있는 점이 있습니다.

 

1. 전달받은 파일은 새롭게 객체에 넣어서 추가로 생성하여 여러개 생성한다고 하더라도 동일한 파일 하나를 바라보고 있다.

2. 1번의 이유로 tranferTo 함수를 통해 업로드를 여러 번 중복으로 사용할 수 없다.

 

즉, 종합하자면 전달받은 파일들은 여러 개 추가로 새로운 객체로 생성하더라도 하나의 파일 객체를 동일하게 바라보고 있다.

그렇다면 지금처럼 음악파일도 있고 프리뷰 파일도 업로드 해야할 경우 어떻게 처리해야하나라고 생각했을 때, 생각해볼 수 있는 방법이 있습니다.

 

바로 이 전달받은 음악 파일 자체의 복사본 하나를 만드는 것입니다.

새로운 객체로 생성하는 것이 아니라 복사본 자체를 하나 만들면 해당 파일은 독립적인 개체로서 운영될 것입니다.

 

 

해결 방법

Service

public HashMap<String, String> testTransferTo(HttpServletRequest request, List<MultipartFile> multipartFile) throws IOException, CannotReadException, TagException, InvalidAudioFrameException, ReadOnlyFileException {
    // 업로드 경로
    String musicDir = "C:" + File.separator + "BackEnd_Project" + File.separator + "MusicIsMyLife" + File.separator + "src" + File.separator + "main" + File.separator + "webapp" + File.separator + "music" + File.separator;
    String previewDir = "C:" + File.separator + "BackEnd_Project" + File.separator + "MusicIsMyLife" + File.separator + "src" + File.separator + "main" + File.separator + "webapp" + File.separator + "music" + File.separator + "preview" + File.separator;
    
    for(MultipartFile eachFile : multipartFile){
       // 업로드할 음악 파일의 진짜 이름 추출
       String originalFilename = eachFile.getOriginalFilename();
       // 난수화된 파일 이름과 확장자를 합친 파일명 추출
       String serverUploadFileName = createServerFileName(originalFilename);

       // 업로드한 mp3 파일과 경로를 합친 업로드 경로를 File 객체에 넣어 생성
       File musicFile = new File(getFullPath(serverUploadFileName, musicDir));

       // multipartfile에서 지원하는 transferTo 함수 사용. (해당 파일을 지정한 경로로 전송)
       eachFile.transferTo(musicFile);
       
       // copy 함수 첫 번째 파라미터 : 미리 업로드된 본 음악 파일의 경로
       // copy 함수 두 번째 파라미터 : Paths.get 으로 복사된 파일 명을 포함한 복사본이 업로드될 경로
       // copy 함수 세 번째 파라미터 : StandardCopyOption.REPLACE_EXISTING (이미 복사 파일이 존재할 경우 교체하는 옵션)
       Files.copy(musicFile.toPath(), Paths.get(getFullPath(serverUploadFileName, previewDir)), StandardCopyOption.REPLACE_EXISTING);
    }
    
    return true;
}

Files.copy 함수를 사용하여 복사본을 만들 수 있습니다.

copy 함수에 첫 번째 파라미터는 기존에 처음 업로드 되었던 음악 파일의 업로드 경로 (toPath())가 들어가고,

두 번째 파라미터는 Paths.get 함수를 사용하여 복사본이 업로드될 복사 파일명을 포함한 경로가 들어가게 됩니다.

세 번째 파라미터는 옵션이 들어가는데 저는 StandardCopyOption.REPLACE_EXISTING 옵션을 넣어 만약 복사본이 존재할 경우 교체하는 옵션을 적용하였습니다.

 

정리하면 이미 업로드된 파일의 경로를 가져와서 해당 경로에 존재하는 파일을 조회한 뒤 복사하여 업로드 하고자 하는 경로로 복사를 시도합니다.

복사를 시도할 때 세 번째 파라미터에 들어간 옵션에 따라 조건이 적용됩니다.

 

이제 업로드 기능을 실행시켜 정상적으로 들어갔는지 확인해줍시다.

 

 

실행시키면 파일 질라를 통해 두 개의 경로에 정상적으로 업로드 된 것을 확인할 수 있습니다.

 

728x90
반응형
LIST