로컬 파일업로드 S3로 전환하기
들어가기에 앞서
처음 파일업로드를 구현할 때는 로컬에 파일을 저장하고 로컬에서 가져와서 보여주도록 구현했다. 하지만 ec2를 사용해서 배포하면서 로컬에 저장할 수는 없기 때문에 S3에 저장하고 불러오는 방식으로 변환하고자 한다.
IAM과 S3 설정
IAM설정은 2가지가 있다.
- 사용자 : AWS가 아닌 외부에서 AWS로 접근하기 위한 권한
- 역할 : AWS끼리 접근하기 위한 권한
지금은 외부에서 AWS로 접근하는 것이기 때문에 사용자 추가를 해주어야 한다.
그리고 S3안에서 사용 할 bucket을 만들어야 한다. 하나의 프로젝트, 덩어리, 폴더.... 라는 느낌으로 이해하면 될 것이다.
S3와 IAM설정은 [AWS] - S3 사용을 위한 IAM과 S3설정 를 참고하면 된다.
라이브러리 추가 및 설정
이제 본격적으로 코드를 추가해보도록 하자.
implementation('org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE')
먼저 라이브러리를 추가해준다. 필자는 gradle을 사용하였다.
다음으로 application-aws.yml을 생성해줄 것이다. 하나의 application파일에 담아도 되지만 필자는 aws설정은 따로 구분하기 위해 분리했다.
## application-aws.yml
cloud:
aws:
s3:
bucket: [생성한 bucket이름]
region:
static: ap-northeast-2
stack:
auto: false
credentials:
instance-profile: false
access-key: [버킷 생성 시 받은 access-key]
secret-key: [버킷 생성 시 받은 secret-key]
S3에서 생성한 버킷 이름을 넣어준다.
IAM을 추가하고 cvs파일을 저장했을 것이다. 그 안에 access-key와 secret-key가 들어있다. 그 값을 입력하면 된다.
해당 폴더는 반드시 .gitignore에 등록해야 한다. 중요한 key정보가 유출되면 큰일나기 때문이다.
비즈니스 로직 작성
기존의 코드는 완전히 없애버리고 새로운 코드로만 작성하도록 하겠다.
가장 먼저 AWSS3Config를 만들어서 S3에 파일을 저장할 때 사용하는 빈을 만들어준다.
import org.springframework.beans.factory.annotation.Value;
@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 awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}
}
@Value는 반드시 spring으로 등록해야 한다. 위와 같이 등록하면 application-aws.yml에서 등록해놓은 값을 가져올 수 있다.
이제 파일업로드를 시작할 FileUploadService를 만들어주자.
@RequiredArgsConstructor
@Service
public class FileUploadService {
private final FileHandler FileHandlerS3;
public FileDto storeFile(MultipartFile multipartFile) {
if (multipartFile.isEmpty()) {
return null;
}
String storedName = createStoreFileName(multipartFile.getOriginalFilename());
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(multipartFile.getSize());
objectMetadata.setContentType(multipartFile.getContentType());
try (InputStream inputStream = multipartFile.getInputStream()) {
FileHandlerS3.uploadFile(inputStream, objectMetadata, storedName);
} catch (IOException e) {
throw new IllegalArgumentException();
}
return new FileDto(multipartFile.getOriginalFilename(), storedName);
}
public List<FileDto> storeFiles(List<MultipartFile> multipartFiles) {
ArrayList<FileDto> resultDto = new ArrayList<>();
for (MultipartFile multipartFile : multipartFiles) {
if (!multipartFile.isEmpty()) {
FileDto fileDto = storeFile(multipartFile);
if(fileDto != null) resultDto.add(fileDto);
}
}
return resultDto;
}
private String createStoreFileName(String originalName) {
return Long.toString(System.nanoTime()) + originalName;
}
}
필자는 파일을 저장할 떄 사용하는 임베디드 타입의 객체를 하나 생성해놓았다.
@Getter
@NoArgsConstructor
@Embeddable
public class FileDto {
private String originalFileName;
private String storedFileName;
public FileDto(String originalFileName, String storedFileName) {
this.originalFileName = originalFileName;
this.storedFileName = storedFileName;
}
}
FileUploadService를 보면 FileHandler를 주입받고 있는데 FileHandler는 인터페이스로 만들어서 추후에 S3가 아닌 다른 방법을 사용해도 확장할 수 있도록 만들었다.
FileHandler를 만들자.
public interface FileHandler {
String uploadFile(InputStream inputStream, ObjectMetadata objectMetadata, String storedName);
String getFileUrl(String storedName);
}
- uploadFile은 파일을 S3에 저장하는 메소드이다.
- getFileUrl은 저장된 파일의 주소를 가져오는 메소드이다. 화면에서 이미지를 보여줄 때 사용될 것이다.
구현체로 FileHandlerS3를 만들어주자.
@RequiredArgsConstructor
@Component
public class FileHandlerS3 implements FileHandler {
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
@Value("${awsFile.dir}")
private String filePath;
@Override
public String uploadFile(InputStream inputStream, ObjectMetadata objectMetadata, String storedName) {
amazonS3Client.putObject(new PutObjectRequest(bucket, filePath+storedName, inputStream, objectMetadata).withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, filePath+storedName).toString();
}
@Override
public String getFileUrl(String storedName) {
return amazonS3Client.getUrl(bucket, filePath+storedName).toString();
}
}
FileHandler를 상속받고 해당 메소드들을 작성해준다.
필자의 경우는 S3안에 images라는 폴더를 만들어서 그 안에 이미지들을 저장하도록 했다. 이 images라는 값을 application-aws.yml에 awsFile.dir로 지정했고 여기서 그 값을 받아서 사용하고 있다. 만약 폴더를 만들지 않고 바로 저장하고자 한다면 filePath부분은 생략하고 진행해도 무방하다.
이제 업로드를 위한 모든 코드가 완성됐다. 컨트롤러와 서비스의 코드는 따로 올리지는 않겠다.
이제 마지막으로 파일을 보여주는 코드를 만들어보자.
사실 FileHandlerS3에서 만든 getFileUrl을 사용하면 S3에 저장된 이미지에 대한 주소를 바로 가져올 수 있다.
S3에 저장된 이미지를 클릭하면 나오는 URL이다.
이 URL을 <img src="이미지 URL"> 여기에 넣어주기만 하면 바로 사진을 가져와서 보여줄 수 있다.
필자의 경우는 컨트롤러마다 URL을 model로 넘겨주는 것보다 하나의 클래스에서 컨트롤 할 수 있도록 만들었다.
전체 코드는 깃허브에서 볼 수 있다.
https://github.com/ssung0810/travelDiary
GitHub - ssung0810/travelDiary: 여행일지를 작성할 수 있는 프로그램을 만드는 토이프로젝트
여행일지를 작성할 수 있는 프로그램을 만드는 토이프로젝트. Contribute to ssung0810/travelDiary development by creating an account on GitHub.
github.com
'토이 프로젝트 > 여행일지작성' 카테고리의 다른 글
TravelDiary 프로젝트 회고 (0) | 2022.06.28 |
---|---|
Git-flow사용해보기 (0) | 2022.06.23 |
JPQL에서 QueryDSL로 전환하기 (0) | 2022.06.23 |
프로젝트를 시작하면서 (0) | 2022.03.24 |
댓글
이 글 공유하기
다른 글
-
TravelDiary 프로젝트 회고
TravelDiary 프로젝트 회고
2022.06.28 -
Git-flow사용해보기
Git-flow사용해보기
2022.06.23 -
JPQL에서 QueryDSL로 전환하기
JPQL에서 QueryDSL로 전환하기
2022.06.23 -
프로젝트를 시작하면서
프로젝트를 시작하면서
2022.03.24