SpringBoot에서 AWS S3 파일 업로드 및 다운로드 유틸 개발
application.properties 파일 수정
S3 버킷, 액세스 키 정보 추가
### AWS AccessKey ###
cloud.aws.credentials.access-key=*****PNGQDJOTPZ5RQFE
cloud.aws.credentials.secret-key=*****WX23F/l2M1wSGApyP+hLQpQCMi5kbMHqjax
cloud.aws.s3.bucket.name=버킷명
#aws.s3.bucket.url=https://버킷명.s3.ap-northeast-2.amazonaws.com
build.gradle 파일 수정
AWS dependencies 추가
implementation 'com.amazonaws:aws-java-sdk:1.12.328'
S3 파일 업로드 및 다운로드 유틸 개발
S3Util.java
package com.chunjae.archive_cms.common.util;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.ListObjectsRequest;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectInputStream;
import com.amazonaws.util.IOUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;
@Slf4j
@Component
public class S3Util {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${cloud.aws.s3.bucket.name}")
private String bucketName;
public AmazonS3 s3Client;
@PostConstruct
public void S3Util() {
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
this.s3Client = AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion("ap-northeast-2")
.build();
}
public Map<String, Object> uploadFile(String s3FilePath, File file) throws Exception {
Map<String, Object> res = new HashMap<>();
String s3FileName = UUID.randomUUID().toString();
// 확장자 추가
String orgFileName = file.getName();
String fileExt = orgFileName.substring(orgFileName.lastIndexOf(".") + 1);
s3FileName = s3FileName + "." + fileExt;
// S3 파일 업로드
s3Client.putObject(bucketName, s3FilePath + "/" + s3FileName, file);
res.put("s3FilePath", s3FilePath);
res.put("s3FileName", s3FileName);
res.put("s3FileSize", file.length());
res.put("orgFileName", orgFileName);
return res;
}
public Map<String, Object> uploadFile(String s3FilePath, MultipartFile multipartFile) throws Exception {
File file = convertMultipartFileToFile(multipartFile);
Map<String, Object> res = new HashMap<>();
String s3FileName = UUID.randomUUID().toString();
// 확장자 추가
String orgFileName = file.getName();
String fileExt = orgFileName.substring(orgFileName.lastIndexOf(".") + 1);
s3FileName = s3FileName + "." + fileExt;
// S3 파일 업로드
s3Client.putObject(bucketName, s3FilePath + "/" + s3FileName, file);
// 파일 삭제
file.delete();
res.put("s3FilePath", s3FilePath);
res.put("s3FileName", s3FileName);
res.put("s3FileSize", file.length());
res.put("orgFileName", orgFileName);
return res;
}
public File convertMultipartFileToFile(MultipartFile mFile) throws Exception {
File file = new File(mFile.getOriginalFilename());
FileOutputStream fos = new FileOutputStream(file);
fos.write(mFile.getBytes());
// FileOutputStream을 닫아주지 않으면 추후 file 삭제 등의 처리가 정상 동작하지 않을 수 있다.
fos.close();
return file;
}
public Map<String, Object> getSignedUrl(String key) throws Exception {
Map<String, Object> res = new HashMap<>();
long remainingTime = 1000L * 60 * 10;
Date expiry = new Date(System.currentTimeMillis() + remainingTime);
// "/"로 시작하면 파일을 가져올 때에 오류가 발생한다.
if (key.startsWith("/")) {
key = key.substring(1);
}
URL url = s3Client.generatePresignedUrl(bucketName, key, expiry);
res.put("file_url", url);
res.put("file_expiry", expiry);
return res;
}
public void downloadFile(String s3FilePath, String fileName, HttpServletResponse response) throws Exception {
// 다운로드 파일명을 null로 보내면 S3 파일명으로 다운로드
if(fileName == null) {
fileName = s3FilePath.substring(s3FilePath.lastIndexOf("/") + 1);
}
S3Object fullObject = s3Client.getObject(bucketName, s3FilePath);
S3ObjectInputStream objectInputStream = fullObject.getObjectContent();
byte[] bytes = IOUtils.toByteArray(objectInputStream);
fileName = URLEncoder.encode(fileName, "UTF-8");
fileName = fileName.replaceAll("\\+", "%20");
response.setContentType("application/octet-stream;charset=UTF-8");
response.setHeader("Content-Transfer-Encoding", "binary");
response.setHeader( "Content-Disposition", "attachment; filename=\"" + fileName + "\";" );
response.setHeader("Content-Length", String.valueOf(fullObject.getObjectMetadata().getContentLength()));
response.setHeader("Set-Cookie", "fileDownload=true; path=/");
FileCopyUtils.copy(bytes, response.getOutputStream());
}
public Integer getMaxFolderName(String folderName) throws Exception {
ListObjectsRequest listObjectsRequest = new ListObjectsRequest()
.withBucketName(bucketName)
.withDelimiter("/")
.withPrefix(folderName);
ObjectListing objectListing = s3Client.listObjects(listObjectsRequest);
List<Integer> folderNameList = new ArrayList<>();
for (String commonPrefixes : objectListing.getCommonPrefixes()) {
folderNameList.add(Integer.valueOf(commonPrefixes.split("/")[1]));
}
Integer maxFolderName = 0;
if(folderNameList.size() > 0) {
maxFolderName = Collections.max(folderNameList);
}
return maxFolderName;
}
}
uploadFile 함수는 File, MultipartFile 모두 받을 수 있도록 오버로딩 하였습니다.
S3 파일 업로드 예시
S3 파일 업로드 호출부
// 폴더 경로에 몇 번째 폴더까지 있는지 확인 후 새 폴더명 번호 생성
int folderCnt = 0;
folderCnt = s3Util.getMaxFolderName("폴더명/") +1;
// S3 파일 업로드
Map<String, Object> fileUploadResult = s3Util.uploadFile("폴더명/" + folderCnt + "/하위폴더명", file);
평소 업로드 폴더는 월별로 관리하고, 일괄 업로드 시 순번 폴더를 활용하면 좋습니다.
S3 파일 URL 사용 예시
S3 파일 URL 생성 호출부
Map<String, Object> urlMap = s3Util.getSignedUrl("폴더명/하위폴더명/파일명.확장자");
urlMap.get("file_url");
S3 파일 URL 사용
<img src="${SignedUrl}"/>
JSP에서 이미지 경로로 S3 파일 URL을 사용하는 예시입니다.
S3 파일 다운로드 예시
S3 파일 다운로드 호출부
@GetMapping("/downloadS3File")
public void downloadS3File(@RequestParam Map<String, Object> param, HttpServletResponse response) throws Exception {
// 2번째 파라미터 null = S3 파일명 그대로 다운로드
boolean success = s3Util.downloadFile("폴더명/하위폴더명/파일명.확장자", "바꿀 파일명.확장자", response);
log.info("파일 다운 여부 : " + success);
}
S3 파일을 다운로드하는 Test Controller 예시입니다.
Leave a comment