잘못된 점이나 부족한 부분이 있다면 언제든 지적 부탁드립니다
0️⃣ 버킷 만들기
AWS S3 버킷 만들기
📕 S3란 ? Simple Storage Service의 약자로 말그대로 AWS에서 제공해주는 파일 서버이다. 📗 S3 용어 정리 Buckets Amazon S3에서 생성되는 최상위의 디렉토리이며, Amazon S3에 저장된 객체의 컨테이너다. S3상
zer0silver.tistory.com
버킷이 없다면 먼저 버킷을 만들어주세요!
1️⃣ Spring Boot 프로젝트와 S3 연동하기
우선 build.gradle에 다음과 같이 의존성을 추가해줘야 합니다.
// Spring Cloud AWS
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
다음으로는 버킷에 접근하기 위한 사용자를 추가해줘야합니다.
IAM의 사용자 탭에 접근해 사용자 추가 버튼 클릭
사용자 이름을 적어주고, 액세스 유형은 키-프로그래밍 방식 !
AWS S3와 관련된 모든 작업을 할 수 있어야하므로 AmazonS3FullAccess 정책을 선택해주었다.
태그는 따로 추가하지 않았고
마지막으로 내용을 검토하고 사용자 만들기를 눌러준다
그다음 .csv를 다운로드 해준다 !
여기서 다운받은 사용자 키는 잘 보관해주세용
다운받은 csv 파일을 열면 아래와 같이 액세스 키와 시크릿 키가 담겨있습니다.
이제부터 주의할 점
발급받은 키를 유출 시키면 안됩니다!
이미 저는 application.yml 에 access key와 secret key를 그대로 명시해 깃허브에 올렸다가 .. 사용자키 정지 ..?를 먹었습니다. 그러면 당연히 해당 키로 버킷에 접근하지 못하게되기 때문에 에러가 발생하게 된답니다💥
해결방법은 그냥 사용자를 삭제하고 다시 만들어주면 되긴 하지만 ! 이런 바보같은 짓은 저만 하는 걸로 합시다 ..
그래서 제가 택한 방법은 사용자키를 환경 변수 설정하는 것입니다.
인텔리제이에서는 다음과 같이 환경 변수를 설정할 수 있습니다.
그 다음 application.yml에 다음과 같이 설정을 추가해줍니다.
cloud:
aws:
s3:
bucket: ${secret.aws.s3.bucket}
stack:
auto: false
credentials:
access-key: ${secret.aws.credentials.access}
secret-key: ${secret.aws.credentials.secret}
드디어 스프링 부트와 S3의 연동은 끝났습니다. 코드를 작성하러 가봅시당💨
2️⃣ 파일 업로드 및 삭제
저는 파일 업로드 로직을 만들어서 게시글 생성/삭제, 프로필 사진 등록/삭제 시 이용하도록 할 예정이었기 때문에
fileUploadUtil.java를 만들고 거기에 .. 코드를 작성했습니다.
S3에는 같은 파일명은 가진 파일이 업로드 되면 덮어쓰기가 된답니다.
그래서 겹치지 않는 파일명을 가질 수 있도록 파일명을 작명해주는 함수를 다음과 같이 만들어줬습니다.
💡 파일명 생성
/**
* 파일명 생성
* @param category 파일의 유형
* @param originalFileName 파일의 이름
* @return 작명된 파일 이름
*/
private String createFileName(String category, String originalFileName) {
int fileExtensionIndex = originalFileName.lastIndexOf(".");
String fileExtension = originalFileName.substring(fileExtensionIndex);
String fileName = originalFileName.substring(0, fileExtensionIndex);
String random = String.valueOf(UUID.randomUUID());
return category + "/" + fileName + "_" + random + fileExtension;
}
💡 파일 업로드
/**
* @param category 파일의 유형
* @param multipartFile 넘겨받은 파일
* @return 업로드된 파일의 접근 URL + 파일명
*/
public FileUploadResponse uploadFile(String category, MultipartFile multipartFile) throws IOException {
// 파일명
String fileName = createFileName(category, multipartFile.getOriginalFilename());
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(multipartFile.getContentType());
// S3에 업로드
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, multipartFile.getInputStream(), objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
// res dto 생성
FileUploadResponse response = new FileUploadResponse(amazonS3Client.getUrl(bucket, fileName).toString(), fileName);
return response;
}
/**
* 파일 업로드시 반환되는 DTO입니다.
*/
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class FileUploadResponse {
private String fileUrl; // 파일 접근 URL
private String filePath; // 삭제 시 필요한 path
}
파일 업로드 시 반환되는 DTO는 위와 같습니다.
fileUrl은 S3에 업로드된 파일에 접근할 수 있는 url이고,
filePath는 파일 삭제 시 파일의 '디렉토리/파일명'이 담긴 주소가 필요해 파일 등록 시 함께 반환하도록 하였습니다.
💡 파일 삭제
/**
* 파일 삭제
* @param filePath
*/
public void deleteFile(String filePath) {
amazonS3Client.deleteObject(new DeleteObjectRequest(bucket, filePath));
}
💡 전체 코드
@RequiredArgsConstructor
@Component
public class FileUploadUtil {
// AWS S3 bucket 이름
@Value("${cloud.aws.s3.bucket}")
private String bucket;
// AWS S3 서버와 통신하는 Client 객체
private final AmazonS3Client amazonS3Client;
/**
* @param category 파일의 유형
* @param multipartFile 넘겨받은 파일
* @return 업로드된 파일의 접근 URL
*/
public FileUploadResponse uploadFile(String category, MultipartFile multipartFile) throws IOException {
// 파일명
String fileName = createFileName(category, multipartFile.getOriginalFilename());
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(multipartFile.getContentType());
// S3에 업로드
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, multipartFile.getInputStream(), objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
// res dto 생성
FileUploadResponse response = new FileUploadResponse(amazonS3Client.getUrl(bucket, fileName).toString(), fileName);
return response;
}
/**
* 파일명 생성
* @param category 파일의 유형
* @param originalFileName 파일의 이름
* @return 작명된 파일 이름
*/
private String createFileName(String category, String originalFileName) {
int fileExtensionIndex = originalFileName.lastIndexOf(".");
String fileExtension = originalFileName.substring(fileExtensionIndex);
String fileName = originalFileName.substring(0, fileExtensionIndex);
String random = String.valueOf(UUID.randomUUID());
return category + "/" + fileName + "_" + random + fileExtension;
}
/**
* 파일 삭제
* @param filePath
*/
public void deleteFile(String filePath) {
amazonS3Client.deleteObject(new DeleteObjectRequest(bucket, filePath));
}
}
3️⃣ 테스트
테스트를 위해 간단한 아래와 같이 테스트용 컨트롤러를 만들어보았습니다.
fileUploadUtil.java에서 @Component을 @Service 어노테이션으로 변경해줘야 사용이 가능합니다.
그저 테스트를 위한 컨트롤러이기 때문에 테스트가 끝나면 컨트롤러와 @Service 어노테이션은 삭제하는 걸 추천드립니다
/**
* FileUploadUtil 테스트용 컨트롤러
*/
@RequiredArgsConstructor
@RestController
public class FileController {
private final FileUploadUtil fileUploadUtil;
/**
* 파일 업로드
* - category 파일의 유형을 의미합니다. category를 image로 할 경우 S3 상에서 image 폴더에 들어가게 됩니다.
* - multipartFile 저장할 파일
* @return 생성된 파일 URL
*/
@PostMapping("/test/file/upload")
public String uploadFile(@RequestParam("category") String category, @RequestParam("file") MultipartFile multipartFile) throws IOException {
public FileUploadResponse uploadFile(@RequestParam("category") String category, @RequestParam("file") MultipartFile multipartFile) throws IOException {
return fileUploadUtil.uploadFile(category, multipartFile);
}
/**
* 파일 삭제
* - filePath (ex. image/fileName.png)
* @return 성공메세지
*/
@PostMapping("/test/file/delete")
public String deleteFile(@RequestParam String filePath) {
fileUploadUtil.deleteFile(filePath);
return "삭제에 성공했습니다.";
}
}
1) 업로드 테스트
포스트맨 실행 결과 다음과 같이 버킷 이미지 디렉토리 아래 파일이 제대로 업로드 되었다!
2) 삭제 테스트
삭제도 마찬가지로 버킷에서 제대로 삭제된다
.
.
그럼 끝 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
끝까지 읽어주셔서 감사합니다
'Server > Spring boot' 카테고리의 다른 글
docker-compose 로 Spring boot + MariaDB 개발 환경 구축하기 (0) | 2023.03.16 |
---|---|
[Querydsl Expressions] Querydsl에서 date format 하기 (0) | 2023.02.07 |
[Swagger 3] Swagger2에서 달라진 것들 (0) | 2022.11.11 |
인텔리제이로 스프링 부트 시작하기 (0) | 2022.08.29 |
[Spring boot] Error : cannot deserialize from Object value (0) | 2022.05.18 |