Springboot에서 파일(사진, 영상 등)을 외부 저장소(AWS S3)에 업로드할 때.
1. aws 계정 생성 → 처음이라면 프리티어로 5GB까지 업로드가 무료
2. aws s3 버킷 생성
이 과정에서 ACL 접근을 허용할꺼냐 이런거 물어보는 것도 Enable ACL 선택!
create bucket을 선택하면 끝!
생성된 bucket을 선택하여 Permissions → Bucket Policy 의 아래의 내용 추가
3. IAM 계정 생성
상단 네브바의 계정 → Security credentials → Access management → Users → Add users
4. IAM access key, secret key 발급
springboot에서 S3에 연결할 때, 내가 접근 권한이 있는 사용자라는 것을 알려주는 방법
5. Springboot와 S3 연동
(1) build.gradle 에 spring-cloud-starter-aws 의존성 추가
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-aws
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.1.RELEASE'
(2) application.yml 에 하위 내용 추가
spring:
servlet:
multipart:
max-file-size: 50MB
max-request-size: 50MB
cloud:
aws:
s3:
bucket: mention-bucket.bucket
region:
static: ap-northeast-2
auto: false
stack:
auto: false
credentials:
access-key: {발급받은 access key}
secret-key: {발급받은 secret key}
(3) {모듈명}Application에 static 변수 추가
@SpringBootApplication
@EnableEurekaClient
public class TeamServiceApplication {
static {
System.setProperty("com.amazonaws.sdk.disableEc2Metadata", "true");
}
public static void main(String[] args) {
SpringApplication.run(TeamServiceApplication.class, args);
}
}
(4) S3Config.java 추가
package com.ssafy.teamservice.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 iamAccessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String iamSecretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3Client amazonS3Client(){
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(iamAccessKey, iamSecretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region).enablePathStyleAccess()
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}
}
(5) S3Uploader.java 추가
package com.ssafy.teamservice.utils;
import com.amazonaws.AmazonServiceException;
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.Autowired;
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;
import java.util.UUID;
@Component
@Slf4j
public class S3Uploader {
@Autowired
private AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
/**
* 로컬 경로에 저장
*/
public String uploadFileToS3(MultipartFile multipartFile, String filePath) {
// MultipartFile -> File 로 변환
File uploadFile = null;
try {
uploadFile = convert(multipartFile)
.orElseThrow(() -> new IllegalArgumentException("[error]: MultipartFile -> 파일 변환 실패"));
} catch (IOException e) {
throw new RuntimeException(e);
}
// S3에 저장된 파일 이름
String fileName = filePath + "/" + UUID.randomUUID();
// s3로 업로드 후 로컬 파일 삭제
String uploadImageUrl = putS3(uploadFile, fileName);
removeNewFile(uploadFile);
return uploadImageUrl;
}
/**
* S3로 업로드
* @param uploadFile : 업로드할 파일
* @param fileName : 업로드할 파일 이름
* @return 업로드 경로
*/
public String putS3(File uploadFile, String fileName) {
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(
CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, fileName).toString();
}
/**
* S3에 있는 파일 삭제
* 영어 파일만 삭제 가능 -> 한글 이름 파일은 안됨
*/
public void deleteS3(String filePath) throws Exception {
try{
String key = filePath.substring(56); // 폴더/파일.확장자
try {
amazonS3Client.deleteObject(bucket, key);
} catch (AmazonServiceException e) {
log.info(e.getErrorMessage());
}
} catch (Exception exception) {
log.info(exception.getMessage());
}
log.info("[S3Uploader] : S3에 있는 파일 삭제");
}
/**
* 로컬에 저장된 파일 지우기
* @param targetFile : 저장된 파일
*/
private void removeNewFile(File targetFile) {
if (targetFile.delete()) {
log.info("[파일 업로드] : 파일 삭제 성공");
return;
}
log.info("[파일 업로드] : 파일 삭제 실패");
}
/**
* 로컬에 파일 업로드 및 변환
* @param file : 업로드할 파일
*/
private Optional<File> convert(MultipartFile file) throws IOException {
// 로컬에서 저장할 파일 경로 : user.dir => 현재 디렉토리 기준
String dirPath = System.getProperty("user.dir") + "/" + file.getOriginalFilename();
File convertFile = new File(dirPath);
if (convertFile.createNewFile()) {
// FileOutputStream 데이터를 파일에 바이트 스트림으로 저장
try (FileOutputStream fos = new FileOutputStream(convertFile)) {
fos.write(file.getBytes());
}
return Optional.of(convertFile);
}
return Optional.empty();
}
}
(6) Controller.java
// Service.java
package com.ssafy.teamservice.service;
import com.ssafy.teamservice.utils.S3Uploader;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@Service
public class TeamServiceImpl implements TeamService{
private final S3Uploader s3Uploader;
public TeamServiceImpl(S3Uploader s3Uploader) {
this.s3Uploader = s3Uploader;
}
@Override
@Transactional
public void createTeam(String name, MultipartFile file) {
String url = "";
if(file != null) url = s3Uploader.uploadFileToS3(file, "static/team-image");
}
}
"static/team-image"는 S3 버킷 내에서 파일을 저장할 폴더를 지정해주면 된다.
이미 폴더가 존재하면 해당 폴더 하위에 파일을 저장하고, 존재하지 않는다면 폴더를 생성하여 저장한다.
// Controller.java
/**
* 그룹(팀) 생성
* @param name
* @param file
* @return
*/
@PostMapping(path = "/teams", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity createTeam(
@RequestPart(value = "name") String name,
@RequestPart(value = "file", required = false) MultipartFile file
){
teamServiceImpl.createTeam(name, file);
return new ResponseEntity(null, HttpStatus.OK);
}
(7) Postman 테스트
(8) 클라이언트 입력 : React 기준
→ formData 형식으로 받아서 백엔드에 넘겨야 한다.
let testData = {
name: '그룹 생성 1'
}
const formData = new FormData()
// 기본 정보
formData.append(
'info',
new Blob([JSON.stringify(testData)], {
type: 'application/json'
})
)
// 파일 정보
formData.append('file', values.stack)
await axios
.post(`/team-service/teams`, formData, {
headers: {
'Content-Type': `multipart/form-data`
}
})
.then(() => console.log('[그룹 생성] >> 성공'))
.catch((error) => {
console.error(error)
})
}
'Back-end' 카테고리의 다른 글
[아키텍처] 마이크로 서비스 아키텍처(MSA) (0) | 2023.04.23 |
---|---|
[Java: JPA] 01. JPA와 환경설정 (0) | 2023.01.28 |
[🫥 오류 해결] WebSecurityConfigurerAdapter, authorizeRequest() deprecated (0) | 2023.01.07 |
[Java:Spring Framework] 01. 스프링의 개념 (1) | 2022.12.17 |