스프링부트 S3 이미지 업로드
1. S3 버킷 만들기
aws 에서 버킷을 생성한다. 👉 버킷 생성 링크
이 때 설정해주어야 할 내용은 다음과 같다.
2. 권한 설정
S3 버킷의 이미지를 외부에서 접근 가능하게 만들려면 버킷 정책을 수정해야 한다.
버킷 정책은 실행중인 버킷의 권한 탭에서 설정할 수 있다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/*"
}
]
}
3. 스프링부트에서 S3 접근하기
1. application.yml 파일 수정
aws 콘솔에 로그인 한 후 엑세스키를 만들 수 있다.
cloud:
aws:
s3:
bucket: [버킷 이름]
credentials:
access-key: [엑세스 키]
secret-key: [시크릿 키]
region:
static: ap-northeast-2
auto: false
stack:
auto: false
이제 이를 스프링부트 프로젝트의 application.yml 파일에 추가한다.
2. S3Config
package com.sparta.fmdelivery.config;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@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 awsCredentials= new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}
}
이 설정으로 애플리케이션은 지정된 AWS 계정과 리전의 S3 서비스에 접근할 수 있게한다.
이 파일에서 설정하는 내용은 다음과 같다.
- AWS 접근 키, 비밀 키, 리전 정보를 환경 설정에서 가져온다
- AmazonS3Client 빈을 생성한다.
- 이 클라이언트는 앱에서 S3와 상호작용할 때 사용한다.
3. Controller
@PostMapping(value = "/menus", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponse<MenuResponse> createMenu(
@Auth AuthUser authUser,
@RequestPart("request") MenuRequest request,
@RequestParam("image") MultipartFile image) {
return ApiResponse.onSuccess(menuService.createMenu(authUser, request, image));
}
이 코드는 메뉴를 생성하는 REST API 엔드포인트이다. 다음과 같은 세 가지 정보를 파라미터로 받는다.
- 인증된 사용자 정보 (AuthUser)
- 메뉴 생성 요청 데이터 (MenuRequest)
- 메뉴 이미지 파일 (MultipartFile)
특히 이미지 파일은 MultipartFile로 받게 되고, JSON과 함께 요청하려면 @RequestPart 어노테이션을 사용해야 한다.
4. Service
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.sparta.fmdelivery.apipayload.status.ErrorStatus;
import com.sparta.fmdelivery.domain.common.dto.AuthUser;
import com.sparta.fmdelivery.domain.menu.dto.MenuRequest;
import com.sparta.fmdelivery.domain.menu.dto.MenuResponse;
import com.sparta.fmdelivery.domain.menu.entity.Menu;
import com.sparta.fmdelivery.domain.menu.repository.MenuRepository;
import com.sparta.fmdelivery.domain.shop.entitiy.Shop;
import com.sparta.fmdelivery.domain.shop.repository.ShopRepository;
import com.sparta.fmdelivery.exception.ApiException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.ion.IonException;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
public class MenuService {
private final MenuRepository menuRepository;
private final ShopRepository shopRepository;
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
private String MENU_IMG_DIR = "menu/";
/**
* 메뉴 생성
* @param authUser
* @param request
* @param multipartFile
* @return MenuResponse
*/
@Transactional
public MenuResponse createMenu(AuthUser authUser, MenuRequest request, MultipartFile multipartFile) {
Shop shop = getValidatedShop(request.getShopId(), authUser);
// 파일 업로드 처리
String imageUrl = uploadImageToS3(multipartFile);
// Menu 엔티티 생성
Menu menu = new Menu(request, shop, imageUrl); // 메뉴 엔티티에 이미지 URL 추가
return MenuResponse.fromEntity(menuRepository.save(menu));
}
/**
* 이미지 업로드 (임시파일 생성하지 않음)
* @param multipartFile
* @return
*/
private String uploadImageToS3(MultipartFile multipartFile) {
try {
String fileName = MENU_IMG_DIR + UUID.randomUUID() + "_" + multipartFile.getOriginalFilename();
// 메타데이터 설정 (파일 크기와 콘텐츠 타입)
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(multipartFile.getSize());
metadata.setContentType(multipartFile.getContentType());
// S3에 파일 업로드
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, multipartFile.getInputStream(), metadata));
// 업로드된 파일의 URL 반환
return amazonS3Client.getUrl(bucket, fileName).toString();
} catch (IOException e) {
throw new ApiException(ErrorStatus._FILE_UPLOAD_ERROR);
}
}
}
이 코드는 S3를 사용하여 이미지를 저장하고, 저장된 이미지의 URL을 메뉴 정보와 함께 데이터베이스에 저장하는 방식으로 동작한다.
4. API 테스트
Postman 을 사용해서 테스트 할 때는
request dto의 내용은 @RequestPart의 value로 들어갔던 값을 Key 부분에 넣어주고 Value부분에 Json을 넣어준다. 그리고 반드시 Content-Type에 application/json으로 설정해주어야 한다.
이미지 파일은 @RequestParam 부분의 value로 들어갔던 값을 Key 부분에 넣어주고 Value 부분에 파일을 첨부하면 된다.