개발 환경
aws s3 2.2.6
spring boot 3.x.x
1. s3 버킷 만들기
aws s3로 들어가서 "버킷 만들기" 클릭
-객체 소유권
인텔리제이로 들어가기 위해서는 키로 들어가야하는데 이떄는 ACL을 설정해줘 한다
- 이 버킷의 퍼블릭 액세스 차단 설정
public으로 다 연다( 모든 퍼블릭 액세스 차단 해체)
나머지는 변경 사항 없음
2. 버킷 정책 추가하기
버킷에 들어가 권한 클릭->정책 편집->정책 생성기
Principle => *
action => DeleteObject, GetObject, PutObject 설정
resource => 버킷->속성-> Amazon 리소스 이름(ARN)에 있는 링크에다 + /*(중요)
ex) arn:aws:s3:::example이면 arn:aws:s3:::cotato/*로 적는다
3. 의존성 주입build.gradle에 추가
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
application.properties
cloud:
aws:
s3:
bucket: ${bucket_address}
stack.auto: false
region.static: ap-northeast-2
credentials:
accessKey: ${S3_ACCESS_KEY}
secretKey: ${S3_ACCESS_PASSWORD}
bucket_address는 주소 => arn:aws:s3:::example면 example만 적는다
4. S3 config
package cotato.csquiz.global.S3;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import com.amazonaws.services.s3.AmazonS3Client;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Slf4j
@Configuration
@NoArgsConstructor
public class S3Config {
@Value("${cloud.aws.credentials.accessKey}")
private String accessKey;
@Value("${cloud.aws.credentials.secretKey}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.build();
}
}
S3Uploader.java
package cotato.csquiz.global.S3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import cotato.csquiz.exception.ErrorCode;
import cotato.csquiz.exception.ImageException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Optional;
@Slf4j
@RequiredArgsConstructor
@Component
public class S3Uploader {
private final AmazonS3Client amazonS3;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
public String uploadFiles(MultipartFile multipartFile, String dirName) throws ImageException{
log.info("upload Files {}",multipartFile);
File uploadFile = convert(multipartFile)
.orElseThrow(() -> new ImageException(ErrorCode.IMAGE_PROCESSING_FAIL));
return upload(uploadFile, dirName);
}
private String upload(File uploadFile, String dirName) {
String fileName = dirName + "/" + uploadFile.getName();
String uploadUrl = putS3(uploadFile, fileName);
removeNewFile(uploadFile);
log.info(uploadUrl);
return uploadUrl;
}
private void removeNewFile(File targetFile) {
if (targetFile.delete()) {
log.info("삭제 완료");
}else{
log.info("삭제 에러");
}
}
private String putS3(File uploadFile, String fileName) {
amazonS3.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3.getUrl(bucket, fileName).toString();
}
private Optional<File> convert(MultipartFile file) throws ImageException {
File convertFile = new File(System.getProperty("user.dir") + "/" + file.getOriginalFilename());
log.info("original file name: {}",file.getOriginalFilename());
try {
log.info("convert try start");
if (convertFile.createNewFile()) { // 바로 위에서 지정한 경로에 File이 생성됨 (경로가 잘못되었다면 생성 불가능)
FileOutputStream fos = new FileOutputStream(convertFile); // FileOutputStream 데이터를 파일에 바이트 스트림으로 저장하기 위함
fos.write(file.getBytes());
fos.close();
log.info("convert to "+convertFile);
return Optional.of(convertFile);
}
} catch (IOException e) {
log.info("convert 실패");
throw new ImageException(ErrorCode.IMAGE_PROCESSING_FAIL);
}
log.info("convert empty");
return Optional.empty();
}
}
*주의 사항
1. Controller에서 사진은 MultipartFile로 받는다.
@Getter
public class SessionPhotoUrlRequest {
private long sessionId;
private MultipartFile sessionImage;
}
2. Controller에서 @RequestBody로는 파일을 보낼 수 없어서 @ModelAttribute을 사용해야한다.
3. Mapping을 할 때 consumes 파라미터를 multipart/form-data로 설정해야한다.(파일을 보낼 수 있는 파일 형태)
@PatchMapping(value = "/photoUrl", consumes = "multipart/form-data")
public ResponseEntity<?> PhotoUrl(@ModelAttribute PhotoUrlRequest request) throws ImageException{
//코드
}
PostMan 사용시
body/form-data형태로 보내야 한다.
Json형태의 데이터는 Context-type을 application/json로 해야한다.
'백엔드 개발 노트' 카테고리의 다른 글
[디자인 패턴] 팩토리 패턴을 이용한 OCP 지키는 코드 짜기 (0) | 2024.05.08 |
---|---|
[JAVA] JAVA Enum field를 이용한 Enum 그룹화하기 (0) | 2024.05.08 |
[디자인 패턴] 확장성에 용이한 코드 짜기 (0) | 2024.05.08 |