스프링부트 S3 이미지 업로드

마라탕천재 ㅣ 2024. 9. 23. 20:00

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 엔드포인트이다. 다음과 같은 세 가지 정보를 파라미터로 받는다.

  1. 인증된 사용자 정보 (AuthUser)
  2. 메뉴 생성 요청 데이터 (MenuRequest)
  3. 메뉴 이미지 파일 (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 부분에 파일을 첨부하면 된다.