[스프링/Spring] MultipartFile을 이용한 파일 업로드/다운로드 예제

dongbrown·2024년 9월 13일

Spring

목록 보기
10/23

실제 프로젝트에서 파일과 이미지를 다루는 방법을 구체적인 예제를 통해 알아보겠습니다. 상품 관리 기능을 통해 파일 업로드/다운로드와 이미지 표시 기능을 구현해보겠습니다.

1. 프로젝트 요구사항

  • 상품 관리 기능 구현
    • 상품 이름
    • 첨부 파일(단일)
    • 이미지 파일(다중)
  • 첨부 파일 업로드/다운로드 기능
  • 업로드한 이미지를 웹 브라우저에서 확인

2. 도메인 설계

상품(Item) 도메인

@Data
public class Item {
    private Long id;
    private String itemName;
    private UploadFile attachFile;
    private List<UploadFile> imageFiles;
}

파일 정보(UploadFile) 도메인

@Data
public class UploadFile {
    private String uploadFileName;  // 고객이 업로드한 파일명
    private String storeFileName;   // 서버 내부에서 관리하는 파일명
    
    public UploadFile(String uploadFileName, String storeFileName) {
        this.uploadFileName = uploadFileName;
        this.storeFileName = storeFileName;
    }
}

중요: 고객이 업로드한 파일명을 그대로 서버에 저장하면 안됩니다. 파일명 충돌을 방지하기 위해 서버는 내부적으로 별도의 파일명을 사용해야 합니다.

3. 파일 저장 로직 구현

@Component
public class FileStore {
    @Value("${file.dir}")
    private String fileDir;

    // 파일 저장 및 파일 정보 객체 생성
    public UploadFile storeFile(MultipartFile multipartFile) throws IOException {
        if (multipartFile.isEmpty()) {
            return null;
        }

        String originalFilename = multipartFile.getOriginalFilename();
        String storeFileName = createStoreFileName(originalFilename);
        multipartFile.transferTo(new File(getFullPath(storeFileName)));
        
        return new UploadFile(originalFilename, storeFileName);
    }

    // UUID를 사용한 파일명 생성
    private String createStoreFileName(String originalFilename) {
        String ext = extractExt(originalFilename);
        String uuid = UUID.randomUUID().toString();
        return uuid + "." + ext;
    }

    // 파일 확장자 추출
    private String extractExt(String originalFilename) {
        int pos = originalFilename.lastIndexOf(".");
        return originalFilename.substring(pos + 1);
    }
}

4. 컨트롤러 구현

폼 데이터 처리를 위한 DTO

@Data
public class ItemForm {
    private Long itemId;
    private String itemName;
    private List<MultipartFile> imageFiles;
    private MultipartFile attachFile;
}

컨트롤러

@Slf4j
@Controller
@RequiredArgsConstructor
public class ItemController {
    private final ItemRepository itemRepository;
    private final FileStore fileStore;

    // 상품 등록 폼
    @GetMapping("/items/new")
    public String newItem(@ModelAttribute ItemForm form) {
        return "item-form";
    }

    // 상품 저장
    @PostMapping("/items/new")
    public String saveItem(@ModelAttribute ItemForm form, RedirectAttributes redirectAttributes) throws IOException {
        // 파일 저장
        UploadFile attachFile = fileStore.storeFile(form.getAttachFile());
        List<UploadFile> storeImageFiles = fileStore.storeFiles(form.getImageFiles());

        // DB 저장
        Item item = new Item();
        item.setItemName(form.getItemName());
        item.setAttachFile(attachFile);
        item.setImageFiles(storeImageFiles);
        itemRepository.save(item);

        return "redirect:/items/{itemId}";
    }

    // 이미지 조회
    @ResponseBody
    @GetMapping("/images/{filename}")
    public Resource downloadImage(@PathVariable String filename) throws MalformedURLException {
        return new UrlResource("file:" + fileStore.getFullPath(filename));
    }

    // 파일 다운로드
    @GetMapping("/attach/{itemId}")
    public ResponseEntity<Resource> downloadAttach(@PathVariable Long itemId) throws MalformedURLException {
        Item item = itemRepository.findById(itemId);
        String storeFileName = item.getAttachFile().getStoreFileName();
        String uploadFileName = item.getAttachFile().getUploadFileName();
        
        UrlResource resource = new UrlResource("file:" + fileStore.getFullPath(storeFileName));
        
        String encodedUploadFileName = UriUtils.encode(uploadFileName, StandardCharsets.UTF_8);
        String contentDisposition = "attachment; filename=\"" + encodedUploadFileName + "\"";
        
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
                .body(resource);
    }
}

5. 주요 특징 및 구현 포인트

  1. 파일명 관리

    • 고객이 업로드한 파일명과 서버 내부 파일명을 구분하여 관리
    • UUID를 사용하여 유니크한 파일명 생성
  2. 다중 파일 업로드

    • multiple 속성을 사용하여 여러 이미지 파일 동시 업로드 지원
    • List<MultipartFile>로 다중 파일 처리
  3. 파일 다운로드

    • Content-Disposition 헤더를 통한 다운로드 파일명 지정
    • 한글 파일명을 위한 인코딩 처리
  4. 이미지 표시

    • UrlResource를 통한 이미지 파일 스트리밍
    • 브라우저에서 이미지 직접 표시

정리

이 예제를 통해 실무에서 자주 사용되는 파일 업로드/다운로드 기능을 구현해보았습니다. 특히 다음 사항들을 고려하여 구현했습니다:

  • 파일명 충돌 방지를 위한 UUID 사용
  • 다중 파일 업로드 지원
  • 파일 다운로드 시 한글 파일명 처리
  • 이미지 파일의 웹 브라우저 표시

이러한 구현 방식은 실제 프로젝트에서도 그대로 활용할 수 있습니다.

0개의 댓글