230328 TIL #44 @NotNull / S3를 통한 이미지 업로드

김춘복·2023년 3월 28일
0

TIL : Today I Learned

목록 보기
44/543
post-custom-banner

230328 Today I Learned

클론코딩 4일차. 오늘 TIL에는 이미지 업로드 기능에 대해 써보려한다.


@NotNull

  • 모든 타입에 null 허용 x

@NotEmpty

  • 문자(or 문자열), Collection, Map, Array에 null과 빈값 허용 x

@NotBlank

  • CharSequence(문자, 문자열)타입에 null과 빈값, 공백을 허용x.
    최소 1자 이상은 있어야 한다.

S3를 이용한 이미지 업로드

  • 프로젝트중 이미지 업로드 기능을 구현하기 위해, 서버에 업로드, DB에 업로드 와 같이 여러 방식을 시도해봤으나 가장 무난하게 사용할 수 있는 방식인 S3에 업로드 하는 방식을 이용해 구현했다.

  • 구현 과정

  1. Controller
// 상품 작성
    @PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public MessageResponseDto createProduct(
            @RequestParam("image") MultipartFile image,
            @RequestPart("dto") @Valid ProductRequestDto productRequestDto,
            @AuthenticationPrincipal UserDetailsImpl userDetails) throws IOException {
        validateImage(image);
        return productService.createProduct(productRequestDto, userDetails.getUser(), image);
    }

//    이미지 유효성 검사
    private void validateImage(MultipartFile image) {
    	if (image.isEmpty()) {
        	throw new IllegalStateException("상품 이미지를 업로드해주세요.");
        }
        if (image.getSize() > MAX_FILE_SIZE) {
            throw new IllegalStateException("파일 사이즈가 최대 사이즈(5MB)를 초과합니다.");
        }

        if (!ALLOWED_IMAGE_CONTENT_TYPES.contains(image.getContentType())) {
            throw new IllegalStateException("파일 형식은 JPEG, JPG, PNG, GIF 중 하나여야 합니다.");
        }
    }

이미지는 multipartFile로 받고 나머지 값들은 dto로 받아서 상품작성 시 값들을 한번에 다 받는다. 이미지 형식이나 크기에 대한 유효성 검사는 validateImage에서 진행한다.

  1. Service
	private final ProductRepository productRepository;
    private final S3Client s3Client;
    private final String bucketName;

    //    상품 작성
    @Transactional
    public MessageResponseDto createProduct(ProductRequestDto productRequestDto, User user, MultipartFile image) throws IOException {
        String key = UUID.randomUUID().toString()+ "_" + image.getOriginalFilename();; // 또는 다른 고유한 키 생성 방법

        PutObjectRequest request = PutObjectRequest.builder()
                .bucket(bucketName)
                .key("uploaded-image/" + key)
                .contentType(image.getContentType())
                .contentLength(image.getSize())
                .build();

        s3Client.putObject(request, RequestBody.fromInputStream(image.getInputStream(), image.getSize()));

        productRepository.saveAndFlush(new Product(productRequestDto, user, key));
        return new MessageResponseDto(HttpStatus.OK, "상품이 등록되었습니다.");
    }

"UUID_원래파일이름" 으로 파일 이름을 정하고 이미지를 등록한다.

  1. S3Config
@Configuration
public class S3Config {

    @Value("${cloud.aws.region.static}")
    private String region;

    @Value("${cloud.aws.s3.bucket}")
    private String bucketName;

    @Value("${cloud.aws.credentials.access-key}")
    private String accessKeyId;

    @Value("${cloud.aws.credentials.secret-key}")
    private String secretAccessKey;

    @Bean
    public Region s3Region() {
        return Region.of(region);
    }

    @Bean
    public String s3BucketName() {
        return bucketName;
    }

    @Bean
    public S3Client s3Client(Region region) {
        AwsBasicCredentials awsCredentials = AwsBasicCredentials.create(accessKeyId, secretAccessKey);
        return S3Client.builder()
                .region(region)
                .credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
                .build();
    }

}

S3에 대한 설정을 만들고 @Bean으로 등록

  1. application.properties
cloud.aws.stack.auto=false
cloud.aws.region.static=내AWS지역
cloud.aws.credentials.access-key=${AWS_ACCESS_KEY_ID}
cloud.aws.credentials.secret-key=${AWS_SECRET_ACCESS_KEY}

# AWS S3 bucket Info
cloud.aws.s3.bucket=내버킷이름

# file upload max size
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=5MB

위의 S3Config에 쓰이는 변수들을 설정. 비밀로 해야 할 것은 intelli J 와 github secrets에 비밀로 설정.

  • 상품 수정 service 메서드
    @Transactional
    public MessageResponseDto update(Long pdid, ProductRequestDto productRequestDto, User user, MultipartFile image) throws IOException {
        Product product = productRepository.findById(pdid).orElseThrow(
                () -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다.")
        );
        if (isMatchUser(product, user) || user.getRole() == UserRoleEnum.ADMIN) {
//           기존 이미지 삭제
            String img = product.getImg();
            String div = "uploaded-image";
            s3Client.deleteObject(DeleteObjectRequest.builder().bucket(bucketName).key(div + "/" + img).build());
//            새 이미지 등록
            String key = UUID.randomUUID().toString() + "_" + image.getOriginalFilename(); // 또는 다른 고유한 키 생성 방법

            PutObjectRequest request = PutObjectRequest.builder()
                    .bucket(bucketName)
                    .key("uploaded-image/" + key)
                    .contentType(image.getContentType())
                    .contentLength(image.getSize())
                    .build();

            s3Client.putObject(request, RequestBody.fromInputStream(image.getInputStream(), image.getSize()));
//          업데이트 메서드
            product.update(productRequestDto, key);
            return new MessageResponseDto(HttpStatus.OK, "게시글이 수정 되었습니다.");
        }
        throw new IllegalArgumentException("해당 권한이 없습니다");

    }

기존 이미지를 삭제하고 새이미지를 등록하는 메서드. 수정하려는 이미지값이 없을때는 따로 메서드를 만들어 dto만 처리.

프로젝트 github 경로

profile
Backend Dev / Data Engineer
post-custom-banner

0개의 댓글