diff --git a/backend/build.gradle b/backend/build.gradle index 6d2f07e..e7608f8 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -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' diff --git a/backend/src/main/java/com/worlabel/domain/auth/service/JwtTokenService.java b/backend/src/main/java/com/worlabel/domain/auth/service/JwtTokenService.java index fc3e705..22d4ab8 100644 --- a/backend/src/main/java/com/worlabel/domain/auth/service/JwtTokenService.java +++ b/backend/src/main/java/com/worlabel/domain/auth/service/JwtTokenService.java @@ -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); diff --git a/backend/src/main/java/com/worlabel/global/config/S3Config.java b/backend/src/main/java/com/worlabel/global/config/S3Config.java new file mode 100644 index 0000000..eab18bb --- /dev/null +++ b/backend/src/main/java/com/worlabel/global/config/S3Config.java @@ -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(); + } +} + diff --git a/backend/src/main/java/com/worlabel/global/exception/ErrorCode.java b/backend/src/main/java/com/worlabel/global/exception/ErrorCode.java index 767b4a9..9372d1d 100644 --- a/backend/src/main/java/com/worlabel/global/exception/ErrorCode.java +++ b/backend/src/main/java/com/worlabel/global/exception/ErrorCode.java @@ -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; diff --git a/backend/src/main/java/com/worlabel/global/service/S3UploadService.java b/backend/src/main/java/com/worlabel/global/service/S3UploadService.java new file mode 100644 index 0000000..31479b6 --- /dev/null +++ b/backend/src/main/java/com/worlabel/global/service/S3UploadService.java @@ -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(); + } +}