File Upload 시나리오
- 클라이언트에게 Multipart-form/data 형식으로 파일을 전송 받는다. 이 때 파일의 데이터 타입은 MultipartFile이다.
- S3Uploader를 통해 파일을 S3에 업로드하고, 파일이 저장된 URL을 DB에 저장한다.
- 클라이언트가 파일을 요청 시 파일이 아닌 파일이 저장된 경로를 반환한다.
- 따라서 클라이언트로부터 데이터를 받을 때는 MultipartFile 데이터 타입으로 받지만, 반환할 땐 String 타입으로 반환한다.
S3 버킷 생성과 IAM 사용자 생성이 이미 이루어져있다고 가정하고 시작한다.
의존성 추가 (gradle)
#build.gradle
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
application.properties 수정
cloud.aws.credentials.accessKey=엑세스 키 ID (AWS S3에서 발급 받은 키)
cloud.aws.credentials.secretKey=비밀 엑세스 키 (AWS S3에서 발급 받은 키)
cloud.aws.stack.auto=false
# AWS S3 Service bucket
cloud.aws.s3.bucket=버킷이름 (자신이 설정한 버킷이름)
cloud.aws.region.static=ap-northeast-2 (버킷 지역(서울은 ap-northeast-2))
accessKey, secretKey는 외부에 노출되면 안되기 때문에 꼭 .gitignore 해야한다
AWS S3 Config
@Component
public class AwsS3Config {
@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
public BasicAWSCredentials basicAWSCredentials() {
return new BasicAWSCredentials(accessKey, secretKey);
}
@Bean
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.build();
}
}
AmazonS3Client는 aws.yml에서 정의한 accessKey, secretKey, region 등을 이용하여 만든 클라이언트 객체이다. 이 객체를 통해 S3 버킷에 접근할 수 있다.
S3Uploader.java
S3에 정적 파일을 올리는 역할을 하는 S3Uploader.java 파일이다.
@Slf4j
@RequiredArgsConstructor
@Component
public class S3Uploader {
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
public String upload(MultipartFile multipartFile, String dirName) throws IOException {
File uploadFile = convert(multipartFile)
.orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File로 전환이 실패했습니다."));
return upload(uploadFile, dirName);
}
public String upload(File uploadFile, String dirName) {
String fileName = dirName + "/" + uploadFile.getName();
String uploadImageUrl = putS3(uploadFile, fileName);
removeNewFile(uploadFile);
return uploadImageUrl;
}
private String putS3(File uploadFile, String fileName) {
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, fileName).toString();
}
private void removeNewFile(File targetFile) {
if (targetFile.delete()) {
log.info("파일이 삭제되었습니다.");
} else {
log.info("파일이 삭제되지 못했습니다.");
}
}
private Optional<File> convert(MultipartFile file) throws IOException {
File convertFile = new File(file.getOriginalFilename());
System.out.println("convertFile = " + convertFile);
if(convertFile.createNewFile()) {
try (FileOutputStream fos = new FileOutputStream(convertFile)) {
fos.write(file.getBytes());
}
return Optional.of(convertFile);
}
return Optional.empty();
}
public String delete(String filePath){
amazonS3Client.deleteObject(new DeleteObjectRequest(bucket, filePath));
return "delete Success";
}
}
참고로 @RequiredArgsConstructor는 final 멤버변수가 있으면 생성자 항목에 포함시킨다. AmazonS3Client amazonS3Client만 final에 있으니 생성자에 AmazonS3Client amazonS3Client만 포함된다. 반대로 String bucket는 생성자 항목에 포함되지 않는다.
생성자에 포함된 amazonS3Client는 DI를 받게되며, bucket는 평범한 멤버변수로 남는다.
코드의 순서는 간단하다.
1. MultipartFile을 전달 받는다.
2. S3에 전달할 수 있도록 MultiPartFile을 File로 전환한다. (S3에 MultipartFile 타입은 전송이 안된다)
3. 전환된 File을 S3에 public 읽기 권한으로 put (외부에서 파일을 읽을 수 있도록 하기 위해)
4. 로컬에 생성된 File 삭제
5. 업로드된 파일의 S3 URL 주소를 반환
참고로 , dirName은 S3에 생성된 디렉토리를 나타낸다
참고
https://jojoldu.tistory.com/300
File Upload 시나리오
- 클라이언트에게 Multipart-form/data 형식으로 파일을 전송 받는다. 이 때 파일의 데이터 타입은 MultipartFile이다.
- S3Uploader를 통해 파일을 S3에 업로드하고, 파일이 저장된 URL을 DB에 저장한다.
- 클라이언트가 파일을 요청 시 파일이 아닌 파일이 저장된 경로를 반환한다.
- 따라서 클라이언트로부터 데이터를 받을 때는 MultipartFile 데이터 타입으로 받지만, 반환할 땐 String 타입으로 반환한다.
S3 버킷 생성과 IAM 사용자 생성이 이미 이루어져있다고 가정하고 시작한다.
의존성 추가 (gradle)
#build.gradle implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
application.properties 수정
cloud.aws.credentials.accessKey=엑세스 키 ID (AWS S3에서 발급 받은 키) cloud.aws.credentials.secretKey=비밀 엑세스 키 (AWS S3에서 발급 받은 키) cloud.aws.stack.auto=false # AWS S3 Service bucket cloud.aws.s3.bucket=버킷이름 (자신이 설정한 버킷이름) cloud.aws.region.static=ap-northeast-2 (버킷 지역(서울은 ap-northeast-2))
accessKey, secretKey는 외부에 노출되면 안되기 때문에 꼭 .gitignore 해야한다
AWS S3 Config
@Component public class AwsS3Config { @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 public BasicAWSCredentials basicAWSCredentials() { return new BasicAWSCredentials(accessKey, secretKey); } @Bean public AmazonS3Client amazonS3Client() { BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); return (AmazonS3Client) AmazonS3ClientBuilder.standard() .withRegion(region) .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) .build(); } }
AmazonS3Client는 aws.yml에서 정의한 accessKey, secretKey, region 등을 이용하여 만든 클라이언트 객체이다. 이 객체를 통해 S3 버킷에 접근할 수 있다.
S3Uploader.java
S3에 정적 파일을 올리는 역할을 하는 S3Uploader.java 파일이다.
@Slf4j @RequiredArgsConstructor @Component public class S3Uploader { private final AmazonS3Client amazonS3Client; @Value("${cloud.aws.s3.bucket}") private String bucket; public String upload(MultipartFile multipartFile, String dirName) throws IOException { File uploadFile = convert(multipartFile) .orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File로 전환이 실패했습니다.")); return upload(uploadFile, dirName); } public String upload(File uploadFile, String dirName) { String fileName = dirName + "/" + uploadFile.getName(); String uploadImageUrl = putS3(uploadFile, fileName); removeNewFile(uploadFile); return uploadImageUrl; } private String putS3(File uploadFile, String fileName) { amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead)); return amazonS3Client.getUrl(bucket, fileName).toString(); } private void removeNewFile(File targetFile) { if (targetFile.delete()) { log.info("파일이 삭제되었습니다."); } else { log.info("파일이 삭제되지 못했습니다."); } } private Optional<File> convert(MultipartFile file) throws IOException { File convertFile = new File(file.getOriginalFilename()); System.out.println("convertFile = " + convertFile); if(convertFile.createNewFile()) { try (FileOutputStream fos = new FileOutputStream(convertFile)) { fos.write(file.getBytes()); } return Optional.of(convertFile); } return Optional.empty(); } public String delete(String filePath){ amazonS3Client.deleteObject(new DeleteObjectRequest(bucket, filePath)); return "delete Success"; } }
참고로 @RequiredArgsConstructor는 final 멤버변수가 있으면 생성자 항목에 포함시킨다. AmazonS3Client amazonS3Client만 final에 있으니 생성자에 AmazonS3Client amazonS3Client만 포함된다. 반대로 String bucket는 생성자 항목에 포함되지 않는다.
생성자에 포함된 amazonS3Client는 DI를 받게되며, bucket는 평범한 멤버변수로 남는다.
코드의 순서는 간단하다.
1. MultipartFile을 전달 받는다.
2. S3에 전달할 수 있도록 MultiPartFile을 File로 전환한다. (S3에 MultipartFile 타입은 전송이 안된다)
3. 전환된 File을 S3에 public 읽기 권한으로 put (외부에서 파일을 읽을 수 있도록 하기 위해)
4. 로컬에 생성된 File 삭제
5. 업로드된 파일의 S3 URL 주소를 반환
참고로 , dirName은 S3에 생성된 디렉토리를 나타낸다
참고
https://jojoldu.tistory.com/300