Skip to content

Commit

Permalink
Feat: aws s3 서비스 추가
Browse files Browse the repository at this point in the history
Related to: #52
  • Loading branch information
haeyonghahn committed Mar 4, 2024
1 parent 5e04a07 commit 7800e2b
Show file tree
Hide file tree
Showing 18 changed files with 316 additions and 70 deletions.
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,17 @@ dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-sleuth'
// AWS
implementation platform('software.amazon.awssdk:bom:2.17.53')
implementation 'commons-io:commons-io:2.13.0'
implementation 'software.amazon.awssdk:aws-core'
implementation 'software.amazon.awssdk:sdk-core'
implementation 'software.amazon.awssdk:sts'
implementation 'software.amazon.awssdk:s3'
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mockito:mockito-core:3.+'
testImplementation "org.testcontainers:testcontainers:1.19.0"
testImplementation "org.testcontainers:localstack:1.19.0"
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}
Expand Down
2 changes: 1 addition & 1 deletion db/migration/V0__init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ CREATE TABLE IF NOT EXISTS `record` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `image` (
`image_id` bigint(20) NOT NULL AUTO_INCREMENT,
`image_id` varchar(255) NOT NULL,
`image_path` varchar(255) DEFAULT NULL,
`upload_time` datetime(6) DEFAULT NULL,
`record_number` varchar(255) DEFAULT NULL,
Expand Down
17 changes: 17 additions & 0 deletions infra/local/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@ services:
networks:
my-network:
ipv4_address: 172.28.0.4
local-aws:
image: localstack/localstack:0.11.2
volumes:
- ./localstack/s3/init-s3-bucket.sh:/docker-entrypoint-initaws.d/init-s3-bucket.sh
environment:
- SERVICES=s3
- DEBUG=1
- PORT_WEB_UI=4567
- AWS_ACCESS_KEY_ID=localstack-access-key
- AWS_SECRET_ACCESS_KEY=localstack-secret-key
- AWS_DEFAULT_REGION=ap-northeast-2
- USE_SSL=0
ports:
- '4566:4566' # localstack port
networks:
my-network:
ipv4_address: 172.28.0.8

volumes:
db_master_vol:
Expand Down
3 changes: 3 additions & 0 deletions infra/local/localstack/s3/init-s3-bucket.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
awslocal s3 mb s3://record-bucket;
awslocal s3 ls;
33 changes: 0 additions & 33 deletions src/main/java/com/dnd/gooding/common/filestore/api/FileCreate.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,39 +1,60 @@
package com.dnd.gooding.common.filestore.infra;

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.DeleteObjectRequest;
import com.amazonaws.services.s3.model.PutObjectRequest;
import java.io.File;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;

@Slf4j
@Component
@Service
public class S3ServiceStore {

private final AmazonS3Client amazonS3Client;
private final S3Client s3Client;

@Value("${cloud.aws.s3.bucket}")
private String bucket;

public S3ServiceStore(AmazonS3Client amazonS3Client) {
this.amazonS3Client = amazonS3Client;
public S3ServiceStore(S3Client s3Client) {
this.s3Client = s3Client;
}

@Transactional
public void delete(String key) {
DeleteObjectRequest deleteObjectRequest = new DeleteObjectRequest(bucket, key);
amazonS3Client.deleteObject(deleteObjectRequest);
public String putFile(String key, File file) {
s3Client.putObject(
(req) -> {
req.bucket(bucket);
req.key(key);
},
RequestBody.fromFile(file));

return s3Client.utilities().getUrl(request -> request.bucket(bucket).key(key)).toString();
}

public File getFile(String key) {
File file = new File(key);
ResponseInputStream<GetObjectResponse> s3Object =
s3Client.getObject(
(req) -> {
req.bucket(bucket);
req.key(key);
});

try {
FileUtils.writeByteArrayToFile(file, s3Object.readAllBytes());
} catch (Exception e) {
throw new RuntimeException(e);
}
return file;
}

@Transactional
public String create(File file) {
amazonS3Client.putObject(
new PutObjectRequest(bucket, file.getName(), file)
.withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, file.getName()).toString();
public void deleteFile(String key) {
DeleteObjectRequest deleteObjectRequest =
DeleteObjectRequest.builder().bucket(bucket).key(key).build();
s3Client.deleteObject(deleteObjectRequest);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.dnd.gooding.record.command.application;

import com.dnd.gooding.common.filestore.api.FileCreate;
import com.dnd.gooding.record.command.domain.*;
import com.dnd.gooding.record.command.domain.Record;
import com.dnd.gooding.util.FileCreateUtil;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
Expand Down Expand Up @@ -30,10 +31,14 @@ public void create(RecordRequest recordRequest) throws IOException {

List<Image> images = new ArrayList<>();
for (MultipartFile multipartFile : recordRequest.getFiles()) {
// File file = FileCreate.convert(multipartFile);
// String fileUrl = fileService.createFile(file);
String fileUrl = FileCreate.convert(multipartFile);
Image image = Image.builder().path(fileUrl).uploadTime(LocalDateTime.now()).build();
File file = FileCreateUtil.convert(multipartFile);
String path = fileService.putFile(file.getName(), file);
Image image =
Image.builder()
.id(ImageId.of(file.getName()))
.path(path)
.uploadTime(LocalDateTime.now())
.build();
images.add(image);
}

Expand Down Expand Up @@ -64,6 +69,9 @@ public void create(RecordRequest recordRequest) throws IOException {
public void delete(String recordNo) {
Record record =
recordRepository.findById(RecordNo.of(recordNo)).orElseThrow(NoRecordException::new);
for (Image image : record.getImages()) {
fileService.deleteFile(image.getId().getId());
}
recordRepository.delete(record);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@

public interface FileService {

String createFile(File file);
String putFile(String key, File file);

void deleteFile(String key);
}
12 changes: 7 additions & 5 deletions src/main/java/com/dnd/gooding/record/command/domain/Image.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@
@Table(name = "image")
public class Image {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "image_id")
private Long id;
@EmbeddedId private ImageId id;

@Column(name = "image_path")
private String path;
Expand All @@ -22,11 +19,16 @@ public class Image {
protected Image() {}

@Builder
public Image(String path, LocalDateTime uploadTime) {
public Image(ImageId id, String path, LocalDateTime uploadTime) {
this.id = id;
this.path = path;
this.uploadTime = uploadTime;
}

public ImageId getId() {
return id;
}

public String getPath() {
return path;
}
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/com/dnd/gooding/record/command/domain/ImageId.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.dnd.gooding.record.command.domain;

import java.io.Serializable;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Embeddable;

@Embeddable
public class ImageId implements Serializable {

@Column(name = "image_id")
private String id;

protected ImageId() {}

public ImageId(String id) {
this.id = id;
}

public String getId() {
return id;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ImageId imageId = (ImageId) o;
return Objects.equals(id, imageId.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}

public static ImageId of(String id) {
return new ImageId(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ public FileServiceImpl(S3ServiceStore s3ServiceStore) {
}

@Override
public String createFile(File file) {
return s3ServiceStore.create(file);
public String putFile(String key, File file) {
return s3ServiceStore.putFile(key, file);
}

@Override
public void deleteFile(String key) {
s3ServiceStore.deleteFile(key);
}
}
6 changes: 3 additions & 3 deletions src/main/java/com/dnd/gooding/record/query/dto/ImageData.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class ImageData {

@Id
@Column(name = "image_id")
private Long id;
private String id;

@Column(name = "image_path")
private String path;
Expand All @@ -31,14 +31,14 @@ public class ImageData {
protected ImageData() {}

@Builder
public ImageData(Long id, String path, LocalDateTime uploadTime, String recordNumber) {
public ImageData(String id, String path, LocalDateTime uploadTime, String recordNumber) {
this.id = id;
this.path = path;
this.uploadTime = uploadTime;
this.recordNumber = recordNumber;
}

public Long getId() {
public String getId() {
return id;
}

Expand Down
53 changes: 53 additions & 0 deletions src/main/java/com/dnd/gooding/springconfig/s3/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.dnd.gooding.springconfig.s3;

import java.net.URI;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

@Configuration
public class S3Config {

@Value("${cloud.aws.endpoint}")
private String awsEndpoint;

@Value("${cloud.aws.credentials.access-key}")
private String accessKey;

@Value("${cloud.aws.credentials.secret-key}")
private String secretAccessKey;

/**
* AWS SDK에서 AWS 서비스를 사용하기 위해 별도의 인증 과정을 거친다.
*
* @return
*/
@Bean
public AwsCredentialsProvider awsCredentialsProvider() {
return AwsCredentialsProviderChain.builder()
.reuseLastProviderEnabled(true)
.credentialsProviders(
List.of(
DefaultCredentialsProvider.create(),
StaticCredentialsProvider.create(
AwsBasicCredentials.create(accessKey, secretAccessKey))))
.build();
}

@Bean
public S3Client s3Client() {
return S3Client.builder()
.credentialsProvider(awsCredentialsProvider())
.region(Region.AP_NORTHEAST_2)
.endpointOverride(URI.create(awsEndpoint))
.build();
}
}
Loading

0 comments on commit 7800e2b

Please sign in to comment.