TIL

SpringBoot에서 첨부파일 처리하기 AWS S3

everyday-spring 2024. 10. 18. 13:30
cloud:
  aws:
    credentials:
      access-key: ${aws_access_key}
      secret-key: ${aws_secret_key}
    region:
      static: ${aws_region}

미리 만들어 둔 AWS S3와 연결하기 위해 필요한 값들을 지정했다

두개의 키는 IAM 계정의 정보

@PostMapping
    public ResponseEntity<ApiResponse<?>> createCard(
            @RequestPart("data") CreateCardDto.Request request,
            @RequestPart("file") MultipartFile file,
            @AuthenticationPrincipal AuthUser authUser)
            throws IOException {

        Card card = cardService.createCard(authUser, request, file);

        return ResponseEntity.ok(ApiResponse.success(new CreateCardDto.Response(card)));
    }

 

Controller 에서는 파일과 JSON 데이터를 동시에 처리할 수 있는 @RequestPart 를 사용

 

public String uploadFile(MultipartFile file) throws IOException {
        // 파일 검증 (크기와 형식)
        validateFile(file);

        // 고유한 파일 이름 생성
        String fileName = generateFileName(file);

        try {
            // S3에 파일 업로드
            s3Client.putObject(
                    PutObjectRequest.builder().bucket(bucketName).key(fileName).build(),
                    RequestBody.fromInputStream(file.getInputStream(), file.getSize()));
        } catch (S3Exception e) {
            throw new InvalidRequestException("Failed to upload the file to S3");
        }

        return fileName;
    }

 

입력받은 파일은  파일 업로드와 데이터 전송을 효율적으로 처리할 수 있는 MultipartFile로 처리

이미지, PDF, CSV 등 여러 종류의 파일을 처리할 수 있는 Spring에서 파일을 간편하게 처리할 수 있는 고수준의 유틸리티

 

이 외의 파일 업로드를 처리하기 위한 클래스는

  • Servlet API (HttpServletRequest 사용)
  • Apache Commons FileUpload
  • Standard InputStream/OutputStream
  • Spring WebFlux (Reactive Programming)
// 파일 검증 로직
    private void validateFile(MultipartFile file) {
        if (file.isEmpty()) {
            throw new InvalidRequestException("파일이 비어 있습니다.");
        }

        // 파일 크기 제한 (5MB 이하)
        if (file.getSize() > 5 * 1024 * 1024) { // 5MB 제한
            throw new InvalidRequestException("파일 크기는 최대 5MB입니다.");
        }

        // 지원되는 MIME 타입 목록
        String contentType = file.getContentType();
        if (!isSupportedContentType(contentType)) {
            throw new InvalidRequestException("지원되지 않는 파일 형식입니다.");
        }
    }

    // 지원되는 파일 형식 확인
    private boolean isSupportedContentType(String contentType) {
        return contentType.equals("image/jpeg")
                || contentType.equals("image/png")
                || contentType.equals("application/pdf")
                || contentType.equals("text/csv");
    }

파일 검증 메소드로 업로드 파일 크기와 형식을 검증

AWS의 기본 업로드 제한이 사실 1MB로 제한되어 있어서 최대 5MB는 아니다...

 

// 고유한 파일 이름 생성
    private String generateFileName(MultipartFile file) {
        return UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
    }

파일 이름 충돌과, 보안을 위한 파일 이름 생성 메소드

UUID와 원본 파일 이름을 조합하여 고유한 파일 이름을 생성

 

import java.io.IOException;
import java.util.UUID;

import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.sparta.springtrello.domain.common.exception.InvalidRequestException;

import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;

@Service
public class FileUploadService {

    private final S3Client s3Client;
    private final String bucketName = "버킷의이름을이곳에넣는다"; // S3 버킷 이름

    public FileUploadService(S3Client s3Client) {
        this.s3Client = s3Client;
    }

    public String uploadFile(MultipartFile file) throws IOException {
        // 파일 검증 (크기와 형식)
        validateFile(file);

        // 고유한 파일 이름 생성
        String fileName = generateFileName(file);

        try {
            // S3에 파일 업로드
            s3Client.putObject(
                    PutObjectRequest.builder().bucket(bucketName).key(fileName).build(),
                    RequestBody.fromInputStream(file.getInputStream(), file.getSize()));
        } catch (S3Exception e) {
            throw new InvalidRequestException("Failed to upload the file to S3");
        }

        return fileName;
    }

    // 파일 검증 로직
    private void validateFile(MultipartFile file) {
        if (file.isEmpty()) {
            throw new InvalidRequestException("파일이 비어 있습니다.");
        }

        // 파일 크기 제한 (5MB 이하)
        if (file.getSize() > 5 * 1024 * 1024) { // 5MB 제한
            throw new InvalidRequestException("파일 크기는 최대 5MB입니다.");
        }

        // 지원되는 MIME 타입 목록
        String contentType = file.getContentType();
        if (!isSupportedContentType(contentType)) {
            throw new InvalidRequestException("지원되지 않는 파일 형식입니다.");
        }
    }

    // 지원되는 파일 형식 확인
    private boolean isSupportedContentType(String contentType) {
        return contentType.equals("image/jpeg")
                || contentType.equals("image/png")
                || contentType.equals("application/pdf")
                || contentType.equals("text/csv");
    }

    // 고유한 파일 이름 생성
    private String generateFileName(MultipartFile file) {
        return UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
    }
}

전체 코드

String fileUrl = fileUploadService.uploadFile(file);

card.addFile(file.getOriginalFilename(), fileUrl);

Service에서는 파일 이름과 정상 업로드 후 출력된 url값을 DB에 저장한다