이번 velog에서는 스프링부트와 s3 이용해서 구현하는 부분을 다뤄볼게요
s3 기본 설정이 궁금하다? -> https://velog.io/@juice/Springboot-S3로-이미지-업로드-aws-설정
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
s3 이용하기 위해 dependency 추가해줍니다
# AWS S3
cloud.aws.s3.bucket= 생성한 bucket의 이름
cloud.aws.region.static=ap-northeast-2 //지역 : 서울
cloud.aws.credentials.accessKey= 발급 받은 accessKey
cloud.aws.credentials.secretKey= 발급 받은 secretKey
cloud.aws.stack.auto=false
spring.servlet.multipart.max-file-size=10MB //한번에 업로드할 수 있는 파일최대크기
spring.servlet.multipart.max-request-size=10MB //HTTP 요청으로 한번에 업로드 될 수 있는 파일크기
EC2에서 Spring Cloud 프로젝트를 실행시키면 기본으로 CloudFormation 구성을 시작하기 때문에 설정한 CloudFormation이 없으면 프로젝트 실행이 되지 않는다. 해당 기능을 사용하지 않도록 false로 설정.
🤙 application.properties에 있는 우리의 정보 보호위해 .gitignore에 application.properties에 추가하기
FrontEnd -> BackEnd 요청을 보낼 때 HTTP Request를 통해서 전송되고, 데이타는 request의 Body에 포함되서 전송된다. Body에 들어가는 타입은 header의 Content-type을 통해서 결정된다.
img 파일 전송을 하려면
multipart/form-data 로 지정해주기 때문에 프런트에서 오는 이미지는 multipart type이라고 생각하면 된다.
config라는 폴더 만들고, 스프링부트에서 s3를 사용하기 위한 설정들을 정의하는 Java 클래스를 만들어줍시다.
@Configuration
public class S3Config {
//application.properties에 있는 key값을 지정한다
@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 이용해 s3에 접근해서 이미지 업로드, 불러오기
@Bean
public AmazonS3Client amazonS3Client(){
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}
}
import com.amazonaws.services.s3.AmazonS3Client;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Optional;
@Service
@Slf4j
@RequiredArgsConstructor
public class S3Service {
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
public String upload(MultipartFile multipartFile, String dirName) throws IOException {
File uploadFile = convert(multipartFile).orElseThrow(() -> new CustomException(ExceptionCode.FILE_TRANSFORM_FAILED));
return upload(uploadFile, dirName);
}
private String upload(File uploadFile, String dirName) {
String fileName = dirName + "/" + uploadFile.getName();
String uploadImageUrl = putS3(uploadFile, fileName);
removeNewFile(uploadFile);
return uploadImageUrl;
}
private String putS3(File uploadFile, String fileName) {
// public Read권한으로 업로드한다
amazonS3Client.putObject(bucket, fileName, uploadFile);
// File의 url 을 리턴한다
return amazonS3Client.getUrl(bucket, fileName).toString();
}
private void removeNewFile(File targetFile) {
if (targetFile.delete()) {
log.info("파일이 삭제되었습니다");
} else {
log.info("파일이 삭제되지 못했습니다");
}
}
public Optional<File> convert(MultipartFile file) throws IOException {
// 기존 파일이름으로 새로운 파일을 생성, 이 객체는 프로그램의 루트 디렉토리에 생성된다
File convertFile = new File(file.getOriginalFilename());
// 해당 경로에 파일이 없으면 새로 생성
if (convertFile.createNewFile()) { // 해당 경로에 파일이 없을 경우, 새 파일 생성
try (FileOutputStream fos = new FileOutputStream(convertFile)) {
// multipartFile의 내용을 byte로 가져와서 write
fos.write(file.getBytes());
}
return Optional.of(convertFile);
}
// 새파일 생성 실패하면 빈 Optional 객체를 반환
return Optional.empty();
}
}
위 코드에서 Exception은 저의 custom exception이여서 사용하시려면 new illegalargumentexception 사용하시면 됩니다.
@Getter
@Builder
public static class ImageRet{
private String imageUrl;
}
프런트에게 우리가 이미지 저장해놓은 위치를 돌려줄거기 때문에 string값 하나만 넣어놉니다
@PostMapping(value = "/{id}/uploadImage", consumes = "multipart/form-data")
@Operation(summary = "프로필 이미지 변경 api")
public ResponseEntity<UserResponseDto.ImageRet> uploadImage(@PathVariable Long id,@RequestPart MultipartFile image) throws IOException {
UserResponseDto.ImageRet ret = userService.updateImage(id, image);
return new ResponseEntity<>(ret,HttpStatus.OK);
}
저는 userId 찾아서 user를 찾고, 프로필 변경하는 로직 처리하는 controller입니다.
public UserResponseDto.ImageRet updateImage(Long userId, MultipartFile image) throws IOException {
User user = userRepo.findById(userId).orElseThrow(() -> new CustomException(ExceptionCode.USERID_NOT_FOUND));
String imageUrl = s3Service.upload(image, "UserProfileImage");
// 따로 이미지 폴더 저장하는 방법
// String imageUrl = s3Service.upload(image, "image"+userId.toString());
user.setImage(imageUrl);
userRepo.save(user);
return UserResponseDto.ImageRet.builder()
.imageUrl(imageUrl).build();
}
현재 이미지는 없는걸 확인할 수 있습니다
swagger에서 테스트 해보면?
제가 만들어놓은 response dto에서 저장된 이미지의 url을 돌려줘서 저 url 들어가면 이미지가 나옵니다.
s3에도 이미지 잘 업로드 된걸 확인할 수 있습니다~!!