Merge branch 'be/feat/s3/59-s3' into 'be/develop'

Feat: S3 이미지 업로드 서비스 - S11P21S002-59

See merge request s11-s-project/S11P21S002!23
This commit is contained in:
김태수 2024-08-30 10:53:29 +09:00
commit 617ad12dd2
5 changed files with 151 additions and 8 deletions

View File

@ -59,9 +59,13 @@ dependencies {
// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
//Swagger
// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
// AWS
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
// Test
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
testImplementation 'org.mockito:mockito-core:3.9.0'
testImplementation 'org.mockito:mockito-junit-jupiter:3.9.0'

View File

@ -53,18 +53,13 @@ public class JwtTokenService {
}
public JwtToken generateTokenByRefreshToken(String refreshToken) throws Exception {
log.debug("생성");
if (isTokenExpired(refreshToken)) {
throw new CustomException(ErrorCode.REFRESH_TOKEN_EXPIRED);
}
if (!isRefreshToken(refreshToken)) {
throw new CustomException(ErrorCode.INVALID_REFRESH_TOKEN);
}
log.debug("유효성 통과");
Claims claims = parseClaims(refreshToken);
String username = claims.getSubject();
int memberId = claims.get("id", Integer.class);

View File

@ -0,0 +1,46 @@
package com.worlabel.global.config;
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 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.accessKey}")
private String accessKey;
/**
* 시크릿
*/
@Value("${cloud.aws.credentials.secretKey}")
private String secretKey;
/**
* 지역
*/
@Value("${cloud.aws.region}")
private String region;
/**
* 사용자 정보가 담긴 AmazonS3 Bean 주입
*/
@Bean
public AmazonS3 amazonS3() {
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder.
standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region)
.build();
}
}

View File

@ -14,6 +14,7 @@ public enum ErrorCode {
BAD_REQUEST(HttpStatus.BAD_REQUEST, 1002, "잘못된 요청입니다. 요청을 확인해주세요."),
EMPTY_REQUEST_PARAMETER(HttpStatus.BAD_REQUEST, 1003, "필수 요청 파라미터가 입력되지 않았습니다."),
INVALID_URL(HttpStatus.BAD_REQUEST, 1004, "제공하지 않는 주소입니다. 확인해주세요"),
FAIL_TO_CREATE_FILE(HttpStatus.BAD_REQUEST,1005 ,"파일 업로드에 실패하였습니다. 다시 한번 확인해주세요"),
// Auth & User - 2000
USER_NOT_FOUND(HttpStatus.NOT_FOUND, 2000, "해당 ID의 사용자를 찾을 수 없습니다."),
@ -34,8 +35,8 @@ public enum ErrorCode {
PROJECT_NOT_FOUND(HttpStatus.NOT_FOUND, 4000, "프로젝트를 찾을 수 없습니다"),
// Participant - 5000
PARTICIPANT_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, 5000, "해당 프로젝트에 접근 권한이 없습니다.")
;
PARTICIPANT_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, 5000, "해당 프로젝트에 접근 권한이 없습니다."),
;
private final HttpStatus status;

View File

@ -0,0 +1,97 @@
package com.worlabel.global.service;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.util.IOUtils;
import com.worlabel.global.exception.CustomException;
import com.worlabel.global.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Objects;
import java.util.UUID;
@Slf4j
@Service
@RequiredArgsConstructor
public class S3UploadService {
/**
* S3 버킷 이름
*/
@Value("${cloud.aws.s3.bucket}")
private String bucket;
/**
* S3 인스턴스
*/
private final AmazonS3 amazonS3;
/**
* prefix 주소
*/
@Value("${cloud.aws.url}")
private String url;
/**
* 파일이 존재하는지 확인
*/
public String upload(MultipartFile image) {
if (image.isEmpty() || Objects.isNull(image.getOriginalFilename())) {
throw new CustomException(ErrorCode.EMPTY_FILE);
}
return url + uploadImage(image);
}
/**
* 파일 업로드
*/
private String uploadImage(MultipartFile image) {
try {
return uploadToS3(image);
} catch (IOException e) {
throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE);
}
}
/**
* AWS S3 이미지 업로드
*/
private String uploadToS3(MultipartFile image) throws IOException {
String originalFileName = image.getOriginalFilename(); // 원본 파일 이름
String extension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1); // 파일 확장자
// UUID를 사용하여 고유한 파일 이름 생성
String s3FileName = UUID.randomUUID().toString().substring(0, 14);
// MultipartFile의 InputStream을 가져온 , 바이트 배열로 변환
byte[] bytes = IOUtils.toByteArray(image.getInputStream());
ObjectMetadata metadata = new ObjectMetadata(); // S3에 업로드할 파일의 메타데이터 설정
metadata.setContentType("image/" + extension); // 콘텐츠 타입 설정
metadata.setContentLength(bytes.length); // 콘텐츠 길이 설정
log.debug("metadata : {}",metadata);
// 바이트 배열을 사용하여 ByteArrayInputStream 생성
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) {
// S3에 파일 업로드 요청 생성
PutObjectRequest putRequest = new PutObjectRequest(bucket, s3FileName, byteArrayInputStream, metadata);
log.debug("요청 : {}",putRequest);
amazonS3.putObject(putRequest); // S3 파일 업로드
} catch (Exception e) {
e.printStackTrace();
throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE);
}
URL url = amazonS3.getUrl(bucket, s3FileName);
log.debug("url :{}",url);
return url.getPath();
}
}