S3를 자바로 업로드하고 이미지 확인해보기
오늘은 AWS S3를 사용해서 이미지 파일을 업로드하고, 또 확인하는 방법을 알아보자.
필요한 것들!
1) AWS에 접속할 액세스키, 시크릿키 생성하기
2) S3 버킷 생성하기
Amazon S3란?
AWS에서 제공하는 파일 서버다. 사용자 요구사항에 따라 다양한 스토리지 클래스를 제공한다.
예를 들어, 자주 액세스해야하는 기본 파일 업로드, 다운로드를 해야하는 경우에는 S3 Standard에 저장하고, 파일을 업로드한 후, 액세스 빈도가 낮다면 S3 Standard-IA 또는 S3 One Zone-IA에 저장하도록 구성할 수 있다.
(굳이 이렇게 하는 이유는 가격 절감을 위해서임)
이렇게 요구사항에 따라 다양하게 구성하여 가격 절감을 노릴 수 있다.
만약 진짜 데이터 보관용으로만 사용할 경우에는 (액세스 빈도가 매우 작다면) S3 Glacier Flexible Retrieval 및 S3 Glacier Deep Archive에 가장 낮은 비용으로 보관할 수 있다.
나는 아래와 같이 리팩토링할 예정이다.
- [ 기존 ]이미지 업로드시, 로컬 서버 내부에 이미지가 저장되고, 해당 URL을 DB에 저장하던 구조
- [ S3 사용시 ]이미지 파일을 S3 버킷에 저장한 후, 해당 이미지 URL을 DB에 저장
코드 작성하기
Maven의 경우
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk</artifactId>
<version>1.11.1000</version>
</dependency>
Gradle의 경우
dependencies {
implementation platform('com.amazonaws:aws-java-sdk-bom:1.11.1000')
// Declare individual SDK dependencies without version
...
}
위처럼 Dependecies를 설정한 후, AWS에서 제공하는 S3 코드를 사용할 수 있다.
application.properties
AWS는 보안에 철저한 클라우드다.
IAM에서 발급했던 액세스키가 없으면 서비스에 접근할 수 없다.
만약 git repo를 사용중이라면 accessKey, secretKey는 외부에 노출되면 안되기 때문에 꼭 .gitignore 설정을 해두어야한다.
난 S3를 서울 리전(ap-northeast-2)에 생성했으니 아래처럼 설정해주었다.
cloud.aws.credentials.accessKey=엑세스 키 ID (AWS에서 발급 받은 키)
cloud.aws.credentials.secretKey=비밀 엑세스 키 (AWS에서 발급 받은 키)
cloud.aws.stack.auto=false
cloud.aws.s3.bucket=버킷이름 (자신이 설정한 버킷이름)
cloud.aws.region.static=ap-northeast-2 (버킷 지역(서울은 ap-northeast-2))
FileUpload 시나리오
- 클라이언트에게 Multipart-form/data 형식으로 파일을 전송 받을 것임. 이 때 파일의 데이터 타입은 MultipartFile이다.
- FileUploadServer/S3FileUploadSerivce를 통해 파일을 S3에 업로드하고, 파일이 저장된 URL을 DB에 저장하기 (DB 저장은 코드 생략합니다)
- 클라이언트가 파일을 요청하면 파일이 저장된 경로를 반환한다.
- 즉, 클라이언트로부터 데이터를 받을때는 MultipartFile 형태, 반환할때는 String 타입으로 반환.
S3 Config Bean 설정
@Configuration으로 S3 빈 설정해주기. 그리고 영원히 우려먹기
@Configuration
public class S3Config {
@Value("${cloud.aws.credentials.accessKey}")
private String accessKey;
@Value("${cloud.aws.credentials.secretKey}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
@Primary
public BasicAWSCredentials awsCredentialsProvider() {
return new BasicAWSCredentials(accessKey, secretKey);
}
@Bean
public AmazonS3 amazonS3() {
return AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentialsProvider()))
.build();
}
}
FileUploadService
S3에 이미지 파일을 업로드하는 upload(), 이미지를 올린 후 URL을 반환하는 getS3(), S3에 올린 이미지 파일을 삭제하는 delete()로 메소드는 총 3개로 이루어져있다. 자세한 내용은 아래에..
@RequiredArgsConstructor
@Service
@Slf4j
public class S3FileUploadService {
@Value("${cloud.aws.s3.bucket}")
private String bucket;
@Value("${cloud.aws.s3.bucket.url}")
private String defaultUrl;
private final S3Config s3Config;
public String upload(MultipartFile uploadFile, String folderName) throws IOException {
String originName = uploadFile.getOriginalFilename().replace(" ","_");
assert originName != null;
final String ext = originName.substring(originName.lastIndexOf('.'));
final String saveFileName = getUuid() + ext;
File file = new File(System.getProperty("user.dir")+ saveFileName);
log.info(String.valueOf(file));
uploadFile.transferTo(file);
s3Config.amazonS3().putObject(new PutObjectRequest(bucket, folderName, file));
file.delete();
return getS3(bucket, folderName);
}
public void deleteS3Object(String folderName) throws AmazonServiceException {
log.info(folderName.replace(defaultUrl,""));
s3Config.amazonS3().deleteObject(bucket, folderName.replace(defaultUrl,""));
}
private String getS3(String bucket, String fileName) {
return s3Config.amazonS3().getUrl(bucket, fileName).toString();
}
private static String getUuid() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
- upload()의 경우, 클라이언트가 제공한 파일과, 버킷 내부에 생성할 폴더 이름을 받으면 getS3() 내부 함수를 통해 S3 URL을 리턴한다.
- deleteS3Object()의 경우, 폴더 이름을 통해 S3 이미지를 삭제한다. 즉 DB에 저장한 S3 객체 키값으로 객체를 삭제한다.
S3Controller
upload만 연결시켜둔 S3 Controller 기본 세팅이다.
파일을 올린 후, 요청을 보내면 버킷 내부의 "new" 폴더에 이미지가 저장된다.
@Tag(name = "S3",description = "S3 API")
@Slf4j
@RestController
@RequestMapping(value = "/s3")
@RequiredArgsConstructor
public class S3Controller extends BaseController {
private final S3FileUploadService s3FileUploadService;
@PostMapping(value = "/upload",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public CommonResult upload(
@RequestPart(value = "file") MultipartFile file)
throws IOException{
return result(s3FileUploadService.upload(file, "new"));
}
}