마지막 프로젝트를 진행중이며 유저가 업로드 하는 이미지 파일을 S3 클라우드에 저장하는 방식을 사용헤보기로 했다.
먼저 AWS S3 Bucket을 생성하고 코드 구현을 했다.
1) 버킷 이름과 리전 설정
2) 퍼블릭 액세스 설정
※ 외부에 S3을 공개할 경우 모든 퍼블릭 액세스 차단을 체크 해제하고, 공개하지 않는다면 체크를 해주면 된다.
3) 버킷 정책 생성
버킷 생성기에 들어가면 아래와 같은 화면이 나올거고 해당 항목들을 채워 나간다.
Select Type of Policy - S3 Bucket Policy
Effect - Allow
Principal - *
Action - GetObject, PutObject
ARN - arn:aws:s3:::{버킷이름}/*
Add Statement 클릭 -> 아래 Generate Policy를 클릭하여 생성된 정책을 복사 후
S3 정책 편집에 돌아가서 수정해주면 된다.
4) access key 발급
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.1.RELEASE'
ccloud.aws.credentials.accessKey= 사용자 엑세스 키
cloud.aws.credentials.secretKey= 비밀 엑세스 키
cloud.aws.stack.auto=false
# AWS S3 Service bucket
cloud.aws.s3.bucket=버킷이름 (자신이 설정한 버킷이름)
cloud.aws.region.static=ap-northeast-2 (버킷 지역/서울)
# AWS S3 Bucket URL
cloud.aws.s3.bucket.url=https://s3.ap-northeast-2.amazonaws.com/버킷이름
@Configuration
public class S3Config {
@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();
}
}
public class S3Uploader {
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
public String bucket; // S3 버킷 이름
// 전달 받은 데이터를 바로 S3에 업로드하는 메소드
// 1. 사전 준비 - 메타데이터와 파일명 생성
// 2. S3에 전달 받은 파일 업로드
// 3. S3에 저장된 파일 이름과 주소 반환
// 파라미터로 multipartFile(업로드하려는 파일)과 dirName(이 파일을 업로드하고 싶은 S3 버킷의 폴더 이름)을 받는다.
public HashMap<String, String> uploadImage(MultipartFile multipartFile, String dirName) throws IOException {
// 0. 이미지 파일인지 체크
isImage(multipartFile);
// 1. 사전 준비
// 1-1 메타데이터 생성
// InputStream을 통해 Byte만 전달되고 해당 파일에 대한 정보가 없기 때문에 파일의 정보를 담은 메타데이터가 필요하다.
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(multipartFile.getContentType());
metadata.setContentLength(multipartFile.getSize());
// 1-2 S3에 저장할 파일명 생성
// UUID 사용 이유 : 이름이 같은 파일들이 서로 덮어쓰지 않고 구분될 수 있도록
String fileName = createFileName(multipartFile);
String uploadImageName = dirName + "/" + UUID.randomUUID() + fileName;
// 2. s3로 업로드
amazonS3Client.putObject(new PutObjectRequest(bucket,uploadImageName, multipartFile.getInputStream(),metadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
// S3에 업로드한 이미지의 주소를 받아온다.
String uploadImageUrl = amazonS3Client.getUrl(bucket, uploadImageName).toString();
// 4. S3에 저장된 파일 이름과 주소 반환
HashMap<String, String> imgInfo = new HashMap<>();
imgInfo.put("fileName", uploadImageName);
imgInfo.put("img",uploadImageUrl);
return imgInfo;
}
// 파일 삭제하기
public void deleteImageFile(String fileName) {
amazonS3Client.deleteObject(bucket, fileName);
}
// 파일 이름 생성 메소드
private String createFileName(MultipartFile multipartFile) {
String name = multipartFile.getOriginalFilename();
String ext = "";
// 확장자와 파일명 분리
if (name.contains(".")) {
int position = name.lastIndexOf(".");
ext = name.substring(position+1);
name = name.substring(0,position);
}
// 파일 이름의 길이가 길면 100자로 자르기 (디비의 varchar(255) 제한에 걸리지 않으려고)
if (name.length()>100){
name = name.substring(0,100);
}
// S3에 저장할 파일 이름 생성
String fileName = !ext.equals("")?name+"."+ext:name;
return fileName;
}
// 이미지 파일인지 확인하는 메소드
private void isImage(MultipartFile multipartFile) throws IOException {
// tika를 이용해 파일 MIME 타입 체크
// 파일명에 .jpg 식으로 붙는 확장자는 없앨 수도 있고 조작도 가능하므로 MIME 타입을 체크하는 것이 좋다.
Tika tika = new Tika();
String mimeType = tika.detect(multipartFile.getInputStream());
// MIME타입이 이미지가 아니면 exception 발생생
if (!mimeType.startsWith("image/")) {
throw new CustomException(ErrorCode.NOT_IMAGES);
}
}
}
@RequiredArgsConstructor
@RestController
public class AwsController {
private final S3Uploader s3Uploader;
@PostMapping("/images/upload")
public HashMap<String,String> upload(@RequestParam("images") MultipartFile multipartFile) throws IOException {
String imgUrl = "";
String fileName = "";
HashMap<String,String> imgInfo = s3Uploader.upload(multipartFile, "static");
imgUrl = imgInfo.get("img");
fileName = imgInfo.get("fileName");
HashMap<String,String> imageUrl = new HashMap<>();
imageUrl.put("url",imgUrl);
return imageUrl;
}
// 사진 삭제 테스트
@DeleteMapping("/image/delete")
public String deleteimage(@RequestParam("path") String fileName) {
s3Uploader.deleteFile(fileName);
return "삭제완료";
}
}