Project

[LogicList] SpringBoot AWS S3 저장

leeseokwoon 2022. 9. 23. 11:17
반응형

Spring Boot에서 AWS S3저장

  1. MultipartFile 또는 File 방식으로 이미지 전달받음
  2. AWS S3 퍼블릭 버킷을 생성하여 전달
  3. AWS S3 관련 계정 정보는 소스 코드 외 관리

MultipartFile 또는 File 방식으로 이미지 전달받음

S3UploaderController

package swlee.logiclist.controller;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import swlee.logiclist.utils.S3UploaderService;

import java.io.IOException;
@Slf4j
@RequiredArgsConstructor
@Controller
public class S3UploaderController {
    private final S3UploaderService s3UploaderService;

    @GetMapping("/image")
    public String image() {
        return "image-upload";
    }
//
//    @GetMapping("/video")
//    public String video() {
//        return "video-upload";
//    }

    @PostMapping("/image-upload")
    @ResponseBody
    public String imageUpload(@RequestParam("data") MultipartFile multipartFile) throws IOException {
        log.info("imageUpload IN");
        String result = s3UploaderService.upload(multipartFile, "logiclist", "image");
        //JSON Response
        Gson gson = new Gson();
        // Json key, value 추가
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("result", result);
        // JsonObject를 Json 문자열로 변환
        String jsonStr = gson.toJson(jsonObject);
        // 생성된 Json 문자열 출력
        log.info("JSONSTR::{}",jsonStr);
        return jsonStr;

    }
    
}

S3UploaderService

package swlee.logiclist.utils;

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;

@Service
@Slf4j
public class S3UploaderService {

//    private static final Logger log = org.slf4j.LoggerFactory.getLogger(S3UploaderService.class);
    // local, development 등 현재 프로파일
    @Value("${spring.environment}")
    private String environment;

    // 파일이 저장되는 경로
    @Value("${spring.file-dir}")
    private String rootDir;
    private String fileDir;

    private final AmazonS3Client amazonS3Client;

    public S3UploaderService(AmazonS3Client amazonS3Client) {
        this.amazonS3Client = amazonS3Client;
    }

    /**
     * 서버가 시작할 때 프로파일에 맞는 파일 경로를 설정해줌
     */
    @PostConstruct
    private void init(){
        if(environment.equals("local")){
            this.fileDir = System.getProperty("user.dir") + this.rootDir;
        }
        else if(environment.equals("development")){
            this.fileDir = this.rootDir;
        }

    }

    public String upload(MultipartFile multipartFile, String bucket, String dirName) throws IOException {
        File uploadFile = convert(multipartFile)  // 파일 변환할 수 없으면 에러
                .orElseThrow(() -> new IllegalArgumentException("error: MultipartFile -> File convert fail"));

        return upload(uploadFile, bucket, dirName);
    }

    // S3로 파일 업로드하기
    private String upload(File uploadFile, String bucket, String dirName) {
        String fileName = dirName + "/" + UUID.randomUUID() + uploadFile.getName();   // S3에 저장된 파일 이름
        String uploadImageUrl = putS3(uploadFile, bucket, fileName); // s3로 업로드
        removeNewFile(uploadFile);
        return uploadImageUrl;
    }

    // S3로 업로드
    private String putS3(File uploadFile, String bucket, String fileName) {
        amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead));
        return amazonS3Client.getUrl(bucket, fileName).toString();
    }

    // 로컬에 저장된 이미지 지우기
    private void removeNewFile(File targetFile) {
        if (targetFile.delete()) {
            log.info("File delete success");
            return;
        }
        log.info("File delete fail");
    }

    /**
     * @param multipartFile
     * 로컬에 파일 저장하기
     */
    private Optional<File> convert(MultipartFile multipartFile) throws IOException {
        if (multipartFile.isEmpty()) {
            return Optional.empty();
        }

        String originalFilename = multipartFile.getOriginalFilename();
        String storeFileName = createStoreFileName(originalFilename);

        //파일 업로드
        File file = new File(fileDir+storeFileName);
        multipartFile.transferTo(file);

        return Optional.of(file);
    }

    /**
     * @description 파일 이름이 이미 업로드된 파일들과 겹치지 않게 UUID를 사용한다.
     * @param originalFilename 원본 파일 이름
     * @return 파일 이름
     */
    private String createStoreFileName(String originalFilename) {
        String ext = extractExt(originalFilename);
        String uuid = UUID.randomUUID().toString();
        return uuid + "." + ext;
    }

    /**
     * @description 사용자가 업로드한 파일에서 확장자를 추출한다.
     *
     * @param originalFilename 원본 파일 이름
     * @return 파일 확장자
     */
    private String extractExt(String originalFilename) {
        int pos = originalFilename.lastIndexOf(".");
        return originalFilename.substring(pos + 1);
    }

}

application.yml

실제 운영환경과 개발환경 분리 

spring:
  profiles:
    group:
      "local": "local, common"
      "development": "development,common"
    active: local

---
# 공통
spring:
  config:
    activate:
      on-profile: "common"
  servlet:
    multipart:
      max-file-size: 100MB

# s3에 필요한 정보
cloud:
  aws:
    region:
      static: ap-northeast-2
    s3:
      bucket: logiclist
    stack:
      auto: false

logging:
  level:
    com:
      amazonaws:
        util:
          EC2MetadataUtils: error

---
# 로컬 환경
spring:
  environment: "local"
  config:
    activate:
      on-profile: "local"
  file-dir: /src/main/resources/static/files/
---
# 배포 환경
spring:
  environment: "development"
  config:
    activate:
      on-profile: "development"
  file-dir: /home/ec2-user/files/

 

aws.yml - S3 버킷에 대한 권한을 가지고 있는 IAM 생성 후 관련 credentials 기재

cloud:
  aws:
    credentials:
      access-key: 사용자 액세스 키
      secret-key: 사용자 시크릿 키
반응형