[Spring] Spring Boot + gradle + S3 + React.js 이미지 업로드 구현하기 - 1 (백엔드 구현)
안녕하세요. 오늘은 프로필 사진을 업로드 할 수 있는 기능을 구현해볼거예요
환경은
Spring Boot 2.5.x
gradle
react
입니다. 이번 포스팅에서는 React.js는 다루지 않을거고요,
스프링 부트에서 S3로 연동하는 방법과 로직을 작성할겁니다.
기본 의존성 설정
//spring-cloud-starter-aws
compileOnly 'org.springframework.cloud:spring-cloud-starter-aws:2.0.2.RELEASE'
implementation platform('com.amazonaws:aws-java-sdk-bom:1.11.228')
implementation 'com.amazonaws:aws-java-sdk-s3'
// https://mvnrepository.com/artifact/commons-io/commons-io
implementation 'commons-io:commons-io:2.6'
//commons-io 는 File을 처리하기 위함
S3를 연동하기 이전에, AWS에서 버킷 생성과 IAM설정을 해줘야한다.
해당글에서는 저부분은 다루지 않으니 다른 블로그를 참고하길 바란다.
https://velog.io/@jinseoit/AWS-S3-bucket
저는 이분꺼를 보고했습니다.
S3에서 성공적으로 사용자 설정과 버킷 생성을 하고 나면,
Spring boot에서 사용해야할 정보를 입력해야한다.
application-credentials.yml
credentials이라는 yml을 파일을 따로 만들어 정보를 넣어줬다.
application-aws.yml
aws이라는 yml파일에는 aws정보를 써줬다.
cloud:
aws:
s3:
bucket: [버킷이름써주세요]
# folder:
# [VARIABLE]: [VALUE]
region:
static: ap-northeast-1
stack:
auto: false
application.yml
파일에는 위에서 작성한 application-aws.yml
파일과 application-credentials.yml
파일을 포함할 수 있도록 설정을 해주어야한다.
application.yml
파일
spring:
profiles:
include:
- aws
- credentials
servlet:
multipart:
enabled: true
max-file-size: 20MB
max-request-size: 20MB
이렇게 작성하면 aws, credentials 파일이 포함된다.
저 aws, credentials 파일은 꼭 gitignore
설정을 해줘야함!!
밑에는 S3사진의 크기를 제한하기 위한 설정이다.
이제 코드를 작성하러 가자.
AWSConfiguration
package com.record.backend.aws;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
@Configuration
public class AWSConfiguration {
@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 awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client)AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}
}
amazonS3Client()
를 통해 내 아마존의 아이디와 시크릿 키를 주입하고 레젼을 설정해준다.
이렇게 하면 S3에 넣기 빼기 권한 수행이 가능하다.
S3Uploader
일단 S3 upload를 위한 Service
단이라고 생각하면 된다.
이 클래스는 다른 분들의 블로그를 많이 참고했는데,
나는 React와 연동을 할 것이기 때문에, 파일을 업로드했을때 넘겨주는 값을 ResponseEntity
로 넘겨주기 위해 Dto클래스를 반환했다.
package com.record.backend.aws;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.record.backend.domain.user.User;
import com.record.backend.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
@Component
public class S3Uploader {
private final AmazonS3Client amazonS3Client;
private final UserRepository userRepository;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
public FileUploadResponse upload(Long userId, MultipartFile multipartFile, String dirName) throws IOException {
File uploadFile = convert(multipartFile)
.orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File로 전환이 실패했습니다."));
return upload(userId, uploadFile, dirName);
}
private FileUploadResponse upload(Long userId, File uploadFile, String dirName) {
String fileName = dirName + "/" + uploadFile.getName();
String uploadImageUrl = putS3(uploadFile, fileName);
removeNewFile(uploadFile);
//사용자의 프로필을 등록하는 것이기때문에, User 도메인에 setProfile을 해주는 코드.
//이 부분은 그냥 업로드만 필요하다면 필요없는 부분이다.
User user = userRepository.findById(userId).get();
user.setProfilePhoto(uploadImageUrl);
//FileUploadResponse DTO로 반환해준다.
return new FileUploadResponse(fileName, uploadImageUrl);
//return uploadImageUrl;
}
private String putS3(File uploadFile, 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("파일이 삭제되었습니다.");
} else {
log.info("파일이 삭제되지 못했습니다.");
}
}
private Optional<File> convert(MultipartFile file) throws IOException {
File convertFile = new File(file.getOriginalFilename());
if(convertFile.createNewFile()) {
try (FileOutputStream fos = new FileOutputStream(convertFile)) {
fos.write(file.getBytes());
}
return Optional.of(convertFile);
}
return Optional.empty();
}
}
코드의 순서는
- MultipartFile을 전달 받고
- S3에 전달할 수 있도록 MultiPartFile을 File로 전환
- S3는 Multipartfile 타입은 전송이 안되기때문
- 전환된 File을 S3에 public 권한으로 put
- 외부에서 정적 파일을 읽을 수 있도록 하기 위함
- 로컬에서 생성된 File 삭제
- Multipartfile -> File로 전환되면서 로컬에 파일 생성된 것을 삭제합니다.
- 업로드 된 파일의 FileName과 S3 URL 주소를 dto로 반환합니다.
위의 코드에서 dirName
은 S3에 생성된 디렉토리를 말하는것인데,
나는 profile
이라는 폴더를 사용한다.
FileUploadResponse (DTO)
앞단에 ResponseEntity
를 사용하여 DTO로 반환할 것이기 때문에,
DTO클래스를 넘겨줬다.
@Getter @Setter
public class FileUploadResponse {
String fileName;
String url;
public FileUploadResponse(String fileName, String url) {
this.fileName = fileName;
this.url = url;
}
}
Controller
유저 객체에 사진넣는거까지 있는 내 코드
@RestController
@RequiredArgsConstructor
public class UserApiController {
//저는 사용자 프로필을 업로드하기위해사용했으므로 이름이 UserApiController입니다.
private final S3Uploader s3Uploader;
//유저 프로필 업로드
@PostMapping("/users/profilePhoto")
public ResponseEntity<?> uploadProfilePhoto(@PathVariable("user_id") Long userId, @RequestParam("profilePhoto") MultipartFile multipartFile) throws IOException {
//S3 Bucket 내부에 "/profile"
FileUploadResponse profile = s3Uploader.upload(userId, multipartFile, "profile");
return ResponseEntity.ok(profile);
}
}
나는 해당 유저의 프로필을 업로드하는 컨트롤러를 작성하였기 때문에
저렇게 user_id
를 받아오는데, 그래서 service
단 코드를 보면 upload
메서드에
User user = userRepository.findById(userId).get();
user.setProfilePhoto(uploadImageUrl);
이 부분이 있는 것이다.
저 유저설정하는 부분없이 그냥 업로드 기능만 있는 코드는
유저고 뭐고 그냥 업로드만 하는 코드
@RestController
@RequiredArgsConstructor
public class UserApiController {
//저는 사용자 프로필을 업로드하기위해사용했으므로 이름이 UserApiController입니다.
private final S3Uploader s3Uploader;
//유저 프로필 업로드
@PostMapping("/users/profilePhoto")
public String uploadProfilePhoto(@RequestParam("profilePhoto") MultipartFile multipartFile) throws IOException {
//S3 Bucket 내부에 "/profile" 폴더
return s3Uploader.upload(multipartFile, "profile");
//이렇게바꾸면 된다. 대신 서비스단에서 파라미터 주의 ㅋㅋ 바까주셈
}
}
여기까지 작성완이다.
postman으로 확인
작성한 api대로 http://localhost:8080/users/profilePhoto
로 post요청을 보내면,
리턴으로 FileUploadResponse
인
fileName, url을 받아온다.
다음글에서는 이렇게 업로드하고 받아오는걸 작성해보겟읍니다~!~!~
Author And Source
이 문제에 관하여([Spring] Spring Boot + gradle + S3 + React.js 이미지 업로드 구현하기 - 1 (백엔드 구현)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@modsiw/Spring-Spring-Boot-gradle-S3-React.js-이미지-업로드-구현하기-1-백엔드-구현저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)