클론코딩 4일차. 오늘 TIL에는 이미지 업로드 기능에 대해 써보려한다.
프로젝트중 이미지 업로드 기능을 구현하기 위해, 서버에 업로드, DB에 업로드 와 같이 여러 방식을 시도해봤으나 가장 무난하게 사용할 수 있는 방식인 S3에 업로드 하는 방식을 이용해 구현했다.
구현 과정
// 상품 작성
@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에서 진행한다.
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_원래파일이름" 으로 파일 이름을 정하고 이미지를 등록한다.
@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으로 등록
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에 비밀로 설정.
@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만 처리.