December 03, 2021
이번 포스팅은 스프링에서 AWS S3 파일 업로드하는 방법입니다.
주로 이미지 파일을 올릴 때 많이 사용되곤 합니다.
implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.3.1'
Spring-Cloud-AWS 의존성을 추가합니다.
# AWS Account Credentials (AWS 접근 키)
cloud.aws.credentials.accessKey={액세스키}
cloud.aws.credentials.secretKey={액세스 시크릿 키}
# AWS S3 bucket Info (S3 버킷정보)
cloud.aws.s3.bucket={S3 버킷 이름)
cloud.aws.region.static=ap-northeast-2 (S3 버킷 지역)
cloud.aws.stack.auto=false
# file upload max size (파일 업로드 크기 설정)
spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB
spring.servlet.multipart.max-file-size
: 파일 하나당 크기spring.servlet.multipart.max-request-size
: 전송하려는 총 파일들의 크기false
를 등록합니다. @PostMapping("/upload")
public String uploadFile(
@RequestParam("category") String category,
@RequestPart(value = "file") MultipartFile multipartFile) {
return awsS3Service.uploadFileV1(category, multipartFile);
}
@RequestPart
애너테이션을 이용해서 multipart/form-data 요청을 받습니다.@Slf4j
@RequiredArgsConstructor
@Service
public class AwsS3Service {
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucketName;
public String uploadFileV1(String category, MultipartFile multipartFile) {
validateFileExists(multipartFile);
String fileName = CommonUtils.buildFileName(category, multipartFile.getOriginalFilename());
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(multipartFile.getContentType());
try (InputStream inputStream = multipartFile.getInputStream()) {
amazonS3Client.putObject(new PutObjectRequest(bucketName, fileName, inputStream, objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
} catch (IOException e) {
throw new FileUploadFailedException();
}
return amazonS3Client.getUrl(bucketName, fileName).toString();
}
private void validateFileExists(MultipartFile multipartFile) {
if (multipartFile.isEmpty()) {
throw new EmptyFileException();
}
}
bucketName
: 버킷이름을 받습니다.validateFileExists
: 파일이 들어있는지 확인하는 메서드CannedAccessControlList.PublicRead
: 퍼블릭으로 할 것인지 프라이빗으로 할 건지 선택이 가능합니다. /**
* 파일 업로드 용량 초과시 발생
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
protected ResponseEntity<ErrorResponse> handleMaxUploadSizeExceededException(
MaxUploadSizeExceededException e) {
log.info("handleMaxUploadSizeExceededException", e);
ErrorResponse response = ErrorResponse.of(ErrorCode.FILE_SIZE_EXCEED);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
private static final String FILE_EXTENSION_SEPARATOR = ".";
public static String buildFileName(String category, String originalFileName) {
int fileExtensionIndex = originalFileName.lastIndexOf(FILE_EXTENSION_SEPARATOR);
String fileExtension = originalFileName.substring(fileExtensionIndex);
String fileName = originalFileName.substring(0, fileExtensionIndex);
String now = String.valueOf(System.currentTimeMillis());
return category + CATEGORY_PREFIX + fileName + TIME_SEPARATOR + now + fileExtension;
}
파일이름 생성할 때 사용하는 Utils
fileExtensionIndex
: 파일 확장자 구분선fileExtension
: 파일 확장자fileName
: 파일 이름now
: 파일 업로드 시간파일이름은 다음처럼 나타납니다.
포스트맨을 이용하여 파일 업로드 테스트를 합니다.
파일 업로드 성공했습니다.
비어있는 파일 보내기 예외처리 확인!
업로드 용량 크기 예외처리 확인!
@GetMapping("/download")
public ResponseEntity<ByteArrayResource> downloadFile(
@RequestParam("resourcePath") String resourcePath) {
byte[] data = awsS3Service.downloadFileV1(resourcePath);
ByteArrayResource resource = new ByteArrayResource(data);
HttpHeaders headers = buildHeaders(resourcePath, data);
return ResponseEntity
.ok()
.headers(headers)
.body(resource);
}
private HttpHeaders buildHeaders(String resourcePath, byte[] data) {
HttpHeaders headers = new HttpHeaders();
headers.setContentLength(data.length);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDisposition(CommonUtils.createContentDisposition(resourcePath));
return headers;
}
buildHeaders
: 헤더 설정
setContentType(MediaType.APPLICATION_OCTET_STREAM)
setContentDisposition(CommonUtils.createContentDisposition(resourcePath)
public byte[] downloadFileV1(String resourcePath) {
validateFileExistsAtUrl(resourcePath);
S3Object s3Object = amazonS3Client.getObject(bucketName, resourcePath);
S3ObjectInputStream inputStream = s3Object.getObjectContent();
try {
return IOUtils.toByteArray(inputStream);
} catch (IOException e) {
throw new FileDownloadFailedException();
}
}
private void validateFileExistsAtUrl(String resourcePath) {
if (!amazonS3Client.doesObjectExist(bucketName, resourcePath)) {
throw new FileNotFoundException();
}
}
validateFileExistsAtUrl
: Url에 파일이 있는지 확인하는 메서드 private static final String CATEGORY_PREFIX = "/";
private static final String TIME_SEPARATOR = "_";
private static final int UNDER_BAR_INDEX = 1;
public static ContentDisposition createContentDisposition(String categoryWithFileName) {
String fileName = categoryWithFileName.substring(
categoryWithFileName.lastIndexOf(CATEGORY_PREFIX) + UNDER_BAR_INDEX);
return ContentDisposition.builder("attachment")
.filename(fileName, StandardCharsets.UTF_8)
.build();
}
fileName
: 카테고리(Prefix) + 카테고리 다음 언더바까지 잘라 파일이름을 만듭니다. @PostMapping("/upload")
public FileUploadResponse uploadFile(
@RequestParam("category") String category,
@RequestPart(value = "file") List<MultipartFile> multipartFiles,
Authentication authentication) {
long userId = userService.retrieveUserIdByUsername(authentication.getName());
log.info("upload userId: {}", userId);
return awsS3Service.uploadFile(userId, category, multipartFiles);
}
@RequestPart
의 파라미터를 List<MultipartFile>
로 받습니다. public FileUploadResponse uploadFile(long userId, String category, List<MultipartFile> multipartFiles) {
List<String> fileUrls = new ArrayList<>();
// 파일 업로드 갯수를 정합니다(10개 이하로 정의)
for (MultipartFile multipartFile : multipartFiles) {
if (fileUrls.size() > 10) {
throw new FileCountExceedException();
}
String fileName = PlandPMSUtils.buildFileName(userId, category, multipartFile.getOriginalFilename());
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(multipartFile.getContentType());
try (InputStream inputStream = multipartFile.getInputStream()) {
amazonS3Client.putObject(new PutObjectRequest(bucketName, fileName, inputStream, objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
fileUrls.add(FILE_URL_PROTOCOL + bucketName + "/" + fileName);
} catch (IOException e) {
throw new FileUploadFailedException(e);
}
}
return new FileUploadResponse(fileUrls);
}
Spring Cloud AWS S3 연동 및 파일 업로드
OKKY | Content-Disposition의 한글 filename이 깨지는 이유? (추가. 인코딩된 파일이름 -> 한글 파일이름)
How to return downloaded file name in Cyrillic?
Spring Boot With Amazon S3 : File Upload & Download Example | S3 Bucket | JavaTechie