[S3] Spring에서 S3로 파일업로드 및 테스트
Spring Boot와 파일서버로 사용하는 AWS S3를 연동할 것이다.
S3 버킷 생성
→ Spring을 통해 S3에 파일을 업로드하기 위해서는 ACL을 활성화해야 한다. 비활성화 상태라면
AmazonS3Exception: The bucket does not allow ACL
이 발생한다
- 버킷 정책설정
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::myapp-file/*"
}
]
}
- IAM 사용자 생성
설정
build.gradle
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
application.yml
cloud:
aws:
credentials:
access-key: AKIARAVCBSDFBM5U3M4K6
secret-key: lkRQsFSDFDb8f0DePAJdY4gQuXxKca
s3:
bucket: myapp-file
region:
static: ap-northeast-2
stack:
auto: false
→ IAM에서 생성한 사용자의 access-key
와 secret-key
를 입력한다
→ 해당 S3 버킷의 이름s3.bucket
과 지역region.static
을 입력한다
Sptring
AwsS3Config
@Configuration
public class AwsS3Config {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.build();
}
}
AwsS3Uploader
@Log4j2
@RequiredArgsConstructor
@Component
public class AwsS3Uploader {
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
public String bucket;
public String upload(MultipartFile multipartFile, String dirName) throws IOException {
File uploadFile = convert(multipartFile) // 파일 생성
.orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File convert fail"));
return upload(uploadFile, dirName);
}
private String upload(File uploadFile, String dirName) {
String fileName = dirName + "/" + UUID.randomUUID() + uploadFile.getName();
String uploadImageUrl = putS3(uploadFile, fileName); // s3로 업로드
removeNewFile(uploadFile);
return uploadImageUrl;
}
// 1. 로컬에 파일생성
private Optional<File> convert(MultipartFile file) throws IOException {
File convertFile = new File(file.getOriginalFilename());
if (convertFile.createNewFile()) {
try (FileOutputStream fos = new FileOutputStream(convertFile)) {
fos.write(file.getBytes());
}
return Optional.of(convertFile);
}
return Optional.empty();
}
// 2. S3에 파일업로드
private String putS3(File uploadFile, String fileName) {
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead));
log.info("File Upload : " + fileName);
return amazonS3Client.getUrl(bucket, fileName).toString();
}
// 3. 로컬에 생성된 파일삭제
private void removeNewFile(File targetFile) {
if (targetFile.delete()) {
log.info("File delete success");
return;
}
log.info("File delete fail");
}
public void delete(String fileName) {
log.info("File Delete : " + fileName);
amazonS3Client.deleteObject(bucket, fileName);
}
}
- 순서
- 클라이언트로 부터 입력받은
MultipartFile
를 S3에 업로드 하기 위해File
로 변환한다 (로컬에 파일생성) - AWS 라이브러리를 사용해 로컬에 생성된 파일을 S3에 업로드 한다
- S3 업로드를 위해 로컬에 생성했던 파일을 삭제한다
- 클라이언트로 부터 입력받은
FileController
@RequiredArgsConstructor
@RestController
public class FileController {
private final AwsS3Uploader awsS3Uploader;
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile multipartFile) throws IOException {
String fileName = awsS3Uploader.upload(multipartFile, "test");
return fileName;
}
}
테스트
- S3 Mock을 사용해서 로컬 환경에서 S3 버킷으로의 업로드 테스트가 가능하다.
- findify의
S3Mock
을 사용 할 것이다.
testImplementation 'io.findify:s3mock_2.13:0.2.6'
AwsS3MockConfig
- 테스트를 위한 설정파일
@TestConfiguration
public class AwsS3MockConfig {
@Value("${cloud.aws.s3.bucket}")
public String bucket;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public S3Mock s3Mock() {
return new S3Mock.Builder().withPort(8001).withInMemoryBackend().build();
}
@Bean
public AmazonS3 amazonS3(S3Mock s3Mock){
s3Mock.start();
AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration("http://localhost:8001", region);
AmazonS3 client = AmazonS3ClientBuilder
.standard()
.withPathStyleAccessEnabled(true)
.withEndpointConfiguration(endpoint)
.withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials()))
.build();
client.createBucket(bucket);
return client;
}
}
AwsS3UploaderTest
- 앞서 구현한 S3 버킷에 업로드하는 컴포넌트의 테스트 파일
-
MockMultipartFile
을 사용하여 업로드
@Import(AWSS3MockConfig.class)
@SpringBootTest
class AwsS3UploaderTest {
@Autowired
private S3Mock s3Mock;
@Autowired
private AwsS3Uploader awsS3Uploader;
@AfterEach
public void tearDown() {
s3Mock.stop();
}
@Test
void upload() throws IOException {
// given
String path = "test.png";
String contentType = "image/png";
String dirName = "test";
MockMultipartFile file = new MockMultipartFile("test", path, contentType, "test".getBytes());
// when
String urlPath = awsS3Uploader.upload(file, dirName);
// then
assertThat(urlPath).contains(path);
assertThat(urlPath).contains(dirName);
}
}