본문 바로가기

백엔드 개발 노트

S3를 이용한 사진 등록하기

개발 환경

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로 해야한다.