Skip to content

Commit

Permalink
feat: s3 이미지 업로드 기능 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
poi1649 committed Oct 3, 2023
1 parent 268a196 commit 4c1ee38
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 0 deletions.
1 change: 1 addition & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ dependencies {
// aws
implementation platform('software.amazon.awssdk:bom:2.20.56')
implementation 'software.amazon.awssdk:cloudwatch'
implementation 'software.amazon.awssdk:s3'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.yigongil.backend.application;

import com.yigongil.backend.exception.ImageToBytesException;
import com.yigongil.backend.exception.InvalidImageExtensionException;
import java.io.IOException;
import java.util.Arrays;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

@Service
public class ImageResourceService {

private final String BUCKET_NAME;
private final String CLOUD_FRONT_DOMAIN_NAME;
private final S3Client S3_CLIENT;

public ImageResourceService(
@Value("${aws.region}") String region,
@Value("${aws.s3.bucket-name}") String bucketName,
@Value("${aws.cloud-front-domain}") String cloudFrontDomainName
) {
BUCKET_NAME = bucketName;
CLOUD_FRONT_DOMAIN_NAME = cloudFrontDomainName;
S3_CLIENT = S3Client.builder()
.region(Region.of(region))
.build();
}

public String uploadImage(MultipartFile image) {
String key = createKey(ImageExtension.from(image.getOriginalFilename()));
try {
S3_CLIENT.putObject(
PutObjectRequest.builder()
.bucket(BUCKET_NAME)
.key(key)
.build(),
RequestBody.fromBytes(image.getBytes())
);
return CLOUD_FRONT_DOMAIN_NAME + "/" + key;
} catch (IOException e) {
throw new ImageToBytesException(e);
}
}

private String createKey(ImageExtension extension) {
return UUID.randomUUID() + extension.toString();
}

enum ImageExtension {

JPG(".jpg"),
JPEG(".jpeg"),
PNG(".png");

private final String extension;

ImageExtension(String extension) {
this.extension = extension;
}

public static ImageExtension from(String fileName) {
return Arrays.stream(values())
.filter(value -> fileName.endsWith(value.extension))
.findAny()
.orElseThrow(() -> new InvalidImageExtensionException("Invalid image extension", fileName));
}

@Override
public String toString() {
return extension;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.yigongil.backend.exception;

import java.io.IOException;
import org.springframework.http.HttpStatus;

public class ImageToBytesException extends HttpException {

public ImageToBytesException(IOException e) {
super(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), "Failed to convert image to bytes");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.yigongil.backend.exception;

import org.springframework.http.HttpStatus;

public class InvalidImageExtensionException extends HttpException {

public InvalidImageExtensionException(String message, String input) {
super(HttpStatus.BAD_REQUEST, message, input);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.yigongil.backend.ui;

import com.yigongil.backend.application.ImageResourceService;
import java.net.URI;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController("/v1/images")
public class ImageResourceController {

private final ImageResourceService imageResourceService;

public ImageResourceController(ImageResourceService imageResourceService) {
this.imageResourceService = imageResourceService;
}

@PostMapping
public ResponseEntity<Void> uploadImage(@RequestPart MultipartFile image) {
return ResponseEntity.created(URI.create(imageResourceService.uploadImage(image)))
.build();
}
}

0 comments on commit 4c1ee38

Please sign in to comment.