스프링부트 S3 이미지 저장

Juice🌱·2024년 2월 17일
1

SpringBoot

목록 보기
7/7
post-thumbnail

이번 velog에서는 스프링부트와 s3 이용해서 구현하는 부분을 다뤄볼게요

s3 기본 설정이 궁금하다? -> https://velog.io/@juice/Springboot-S3로-이미지-업로드-aws-설정

Build.gradle

implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

s3 이용하기 위해 dependency 추가해줍니다

application.properties

# 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로 설정.

Git을 이용해서 프로젝트 하는 경우

🤙 application.properties에 있는 우리의 정보 보호위해 .gitignore에 application.properties에 추가하기

Img의 type

FrontEnd -> BackEnd 요청을 보낼 때 HTTP Request를 통해서 전송되고, 데이타는 request의 Body에 포함되서 전송된다. Body에 들어가는 타입은 header의 Content-type을 통해서 결정된다.
img 파일 전송을 하려면
multipart/form-data 로 지정해주기 때문에 프런트에서 오는 이미지는 multipart type이라고 생각하면 된다.

Configuration 추가

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();
    }
}

S3Service.java

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 사용하시면 됩니다.

ResponseDto

@Getter
    @Builder
    public static class ImageRet{
        private String imageUrl;
    }

프런트에게 우리가 이미지 저장해놓은 위치를 돌려줄거기 때문에 string값 하나만 넣어놉니다

Controller.java

@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입니다.

updateImage 처리하는 서비스

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에서 이미지 추가

현재 이미지는 없는걸 확인할 수 있습니다

swagger에서 테스트 해보면?

제가 만들어놓은 response dto에서 저장된 이미지의 url을 돌려줘서 저 url 들어가면 이미지가 나옵니다.

s3에도 이미지 잘 업로드 된걸 확인할 수 있습니다~!!

profile
선한 영향력으로 세상을 변화시키는 새싹개발자

0개의 댓글

관련 채용 정보