본문 바로가기
Spring

[6] 이미지 파일 업로드

by 민지기il 2025. 3. 7.

[1] 파일 업로드 구현

[1-1] application.properties 설정

spring.servlet.multipart.max-request-size=30MB

spring.servlet.multipart.max-file-size=10MB

org.zerock.upload.path=upload

[1-2] ProductDTO

@Data
@NoArgsConstructor
@Builder
@AllArgsConstructor
public class ProductDTO {
    private Long pno;
    private String pname;
    private int price;
    private String pdesc;
    private boolean delFlag; // 실제 삭제가 아니라 삭제인 것처럼
    @Builder.Default
    private List<MultipartFile> files = new ArrayList<>();
    @Builder.Default
    private List<String> uploadedFileNames = new ArrayList<>();
}

[1-3] CustomFileUtil -- 사용자가 업로드한 파일을 서버의 특정 폴더(uploadPath)에 저장함

@Component
@Log4j2
@RequiredArgsConstructor //의존성 주입
public class CustomFileUtil {
    @Value("${org.zerock.upload.path}")
    private String uploadPath;

    @PostConstruct
    public void init(){
        File tempFolder = new File(uploadPath);
        if(!tempFolder.exists()){
            tempFolder.mkdir();
        }
        uploadPath = tempFolder.getAbsolutePath();
        log.info("-----------");
        log.info(uploadPath);
    }
    public List<String> saveFiles(List<MultipartFile> files) throws RuntimeException{
        if(files == null || files.size() ==0){
            return List.of(); //빈 거 반환
        }
        List<String> uploadNames = new ArrayList<>();
        for(MultipartFile file: files){
            String savedName = UUID.randomUUID().toString()+"_"+file.getOriginalFilename();
            Path savePath = Paths.get(uploadPath, savedName);
            try {
                Files.copy(file.getInputStream(), savePath); // file 저장
                uploadNames.add(savedName);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } //end for

        return uploadNames;
    }

}

@Component: 다른 클래스에서 @Autowired나 @RequiredArgsConstructor 등을 통해 의존성을 주입받음

@PostConstruct: 스프링이 이 클래스를 생성한 후 자동으로 실행되는 초기화 메서드

uploadNames 리스트에 저장된 파일 이름을 저장함

UUID.randomUUID().toString(): 중복되지 않는 파일명 생성

Files.copy(file.getInputStream(), savePath)로 실제 파일을 저장

[1-4] 파일 업로드 확인

@RestController
@Log4j2
@RequiredArgsConstructor
@RequestMapping("/api/products")
public class ProductController {
    private final CustomFileUtil fileUtil;
    @PostMapping("/")
    public Map<String, String> register(ProductDTO productDTO){
        log.info("register: "+productDTO);
        List<MultipartFile> files = productDTO.getFiles();
        List<String> uploadedFileNames = fileUtil.saveFiles(files);
        productDTO.setUploadedFileNames(uploadedFileNames);

        log.info(uploadedFileNames);
        return Map.of("RESULT", "SUCCESS");
    }
}

[1-5] 썸네일 생성

build gradle에 implement

implementation group: 'net.coobird', name: 'thumbnailator', version: '0.4.19'

            try {
                Files.copy(file.getInputStream(), savePath); // 원본 file 저장
                //thumbnail 생성
                String contentType = file.getContentType(); //mime type
                //이미지 파일이면
                if(contentType != null || contentType.startsWith("image")){
                    Path thumbnailPath = Paths.get(uploadPath, "s_"+savedName);
                    Thumbnails.of(savePath.toFile()).size(200,200).toFile(thumbnailPath.toFile());
                }

                uploadNames.add(savedName);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

[2] 파일 조회

[2-1] CustomFileUtil

    public ResponseEntity<Resource> getFile(String fileName){

        Resource resource = new FileSystemResource(uploadPath + File.separator + fileName);
        if(!resource.isReadable()){
            resource = new FileSystemResource(uploadPath+File.separator+"b96946e0-2fd5-4d44-ae6e-eb9266c64250_갱얼쥐.jpg");
        }

        HttpHeaders headers = new HttpHeaders();
        try {
            headers.add("Content-Type", Files.probeContentType(resource.getFile().toPath()));
        } catch(IOException e){
            throw  new RuntimeException(e);
        }
        return ResponseEntity.ok().headers(headers).body(resource);
    }

[2-2] ProductController

    @GetMapping("/view/{fileName}")
    public ResponseEntity<Resource> viewFileGet(@PathVariable("fileName") String fileName){
        return fileUtil.getFile(fileName);
    }

파일 이름 앞에 s_ 있으면 썸네일 파일 확인 가능

Files.probeContentType(resource.getFile().toPath()): 파일의 MIME 타입을 자동으로 감지하여 "Content-Type" 헤더에 추가

ex) .jpg, .png → image/jpeg, image/png

.pdf → application/pdf

.txt → text/plain

브라우저가 Content-Type을 보고 파일을 이미지로 표시할지, 다운로드할지 결정함

 

[3] 파일의 이름을 찾아서 삭제

public void deleteFiles(List<String> fileNames) {
        if (fileNames == null || fileNames.size() == 0) {
            return;
        }
        fileNames.forEach(fileName -> {
            String thumbnailFileName = "s_" + fileName;
            Path thumbnailPath = Paths.get(uploadPath, thumbnailFileName);
            Path filePath = Paths.get(uploadPath, fileName);

            try {
                Files.deleteIfExists(filePath);
                Files.deleteIfExists(thumbnailPath);
            } catch (IOException e) {
                throw new RuntimeException(e.getMessage());
            }
        });
    }

 

'Spring' 카테고리의 다른 글

[9] 상품 생성  (0) 2025.03.18
[7] 소셜 로그인 구현  (0) 2025.03.12
[6] Todo get, register, modify, remove  (0) 2025.03.07
[5] 페이지 처리 controller 동작  (0) 2025.03.06
[4] 페이지 처리하는 DTO 설계  (0) 2025.03.05