Skip to content

Commit

Permalink
Merge pull request #48 from FinalDoubleTen/develop
Browse files Browse the repository at this point in the history
S3
  • Loading branch information
yuhyun1 authored Jan 3, 2024
2 parents 68794b5 + 397ac39 commit d0fe1e2
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 2 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ dependencies {
// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2', 'io.jsonwebtoken:jjwt-jackson:0.11.2'
//s3 추가
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

implementation 'org.springframework.boot:spring-boot-starter-security'
compileOnly 'org.projectlombok:lombok'
Expand Down
27 changes: 26 additions & 1 deletion src/main/java/org/tenten/tentenbe/config/S3Config.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
package org.tenten.tentenbe.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 accessKey;

@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;

@Value("${cloud.aws.region.static}")
private String region;

@Bean
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region)
.build();
}

}
11 changes: 11 additions & 0 deletions src/main/java/org/tenten/tentenbe/global/s3/ImageUploadDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.tenten.tentenbe.global.s3;

import lombok.Builder;

@Builder
public record ImageUploadDto(
String imageUrl,
String message

) {
}
46 changes: 46 additions & 0 deletions src/main/java/org/tenten/tentenbe/global/s3/S3Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.tenten.tentenbe.global.s3;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.coyote.BadRequestException;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.tenten.tentenbe.global.response.GlobalDataResponse;

import static org.tenten.tentenbe.global.common.constant.ResponseConstant.SUCCESS;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/images")
@Tag(name = "이미지 관련 API", description = "S3 이미지 업로드 API 입니다.")
@Slf4j
public class S3Controller {
private final S3Uploader s3Uploader;

@Operation(
summary = "이미지 파일 업로드 API",
description = "MultipartFile 형태의 이미지 파일을 'images'라는 키로 form-data 형태로 전송해주세요. 이 API는 전송된 이미지를 S3에 저장하고, 저장된 이미지의 URL을 반환합니다."
)
@PostMapping(value = "", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<GlobalDataResponse<ImageUploadDto>> uploadImage(@RequestParam("images") MultipartFile multipartFile) throws BadRequestException {
try {
String uploadedUrl = s3Uploader.uploadFiles(multipartFile, "static");
ImageUploadDto imageUpload = ImageUploadDto.builder()
.imageUrl(uploadedUrl)
.message("이미지 업로드에 성공했습니다.")
.build();
return ResponseEntity.ok(GlobalDataResponse.ok(SUCCESS, imageUpload));
} catch (Exception e) {
throw new BadRequestException("잘못된 요청입니다.");
}
}


}
70 changes: 70 additions & 0 deletions src/main/java/org/tenten/tentenbe/global/s3/S3Uploader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.tenten.tentenbe.global.s3;

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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;

@RequiredArgsConstructor
@Component
@Slf4j
public class S3Uploader {

private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucket;

// MultipartFile을 전달받아 File로 전환한 후 S3에 업로드
public String uploadFiles(MultipartFile multipartFile, String dirName) throws IOException {
File uploadFile = convert(multipartFile) // 파일 변환할 수 없으면 에러
.orElseThrow(() -> new IllegalArgumentException("error: MultipartFile -> File 전환 실패"));
return upload(uploadFile, dirName);
}

private String upload(File uploadFile, String filePath) {
String fileName = filePath + "/" + UUID.randomUUID() + uploadFile.getName(); // S3에 저장된 파일 이름
String uploadImageUrl = putS3(uploadFile, fileName);
removeNewFile(uploadFile);
return uploadImageUrl; // 업로드된 파일의 S3 URL 주소 반환
}

// S3로 업로드
private String putS3(File uploadFile, String fileName) {
amazonS3Client.putObject(
new PutObjectRequest(bucket, fileName, uploadFile)
.withCannedAcl(CannedAccessControlList.PublicRead)); // PublicRead 권한으로 업로드 됨
return amazonS3Client.getUrl(bucket, fileName).toString();
}

// 로컬에 생성된 File 삭제 (MultipartFile -> File 전환 하며 로컬에 파일 생성됨)
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(System.getProperty("user.dir") + "/" + file.getOriginalFilename());
if (convertFile.createNewFile()) { // 바로 위에서 지정한 경로에 File이 생성됨 (경로가 잘못되었다면 생성 불가능)
try (FileOutputStream fos = new FileOutputStream(convertFile)) { // FileOutputStream 데이터를 파일에 바이트 스트림으로 저장하기 위함
fos.write(file.getBytes());
}
return Optional.of(convertFile);
}
return Optional.empty();
}

}
15 changes: 14 additions & 1 deletion src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,17 @@ open-api:
key: ${OPEN_API_KEY}

jwt:
secret: ${JWT_SECRET}
secret: ${JWT_SECRET}

#s3
cloud:
aws:
credentials:
access-key: ${aws.credentials.access.key}
secret-key: ${aws.credentials.secret.key}
s3:
bucket: ${aws.s3.bucket}
region:
static: ap-northeast-2
stack:
auto: 'false'

0 comments on commit d0fe1e2

Please sign in to comment.