-
Notifications
You must be signed in to change notification settings - Fork 8
refactor: s3 버전 업그레이드 및 로직 수정 #608
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 1 commit
8bc53a7
6210cac
4a020d6
e799840
18b9039
a709bdf
1bd38ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,19 +3,19 @@ | |
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public enum ImgType { | ||
| public enum UploadType { | ||
| PROFILE("profile"), | ||
| GPA("gpa"), | ||
| LANGUAGE_TEST("language"), | ||
| COMMUNITY("community"), | ||
| NEWS("news"), | ||
| CHAT("chat"), | ||
| CHAT("chat/files"), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이미 |
||
| MENTOR_PROOF("mentor-proof"), | ||
| ; | ||
|
|
||
| private final String type; | ||
|
|
||
| ImgType(String type) { | ||
| UploadType(String type) { | ||
| this.type = type; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,51 +1,49 @@ | ||
| package com.example.solidconnection.s3.service; | ||
| package com.example.solidconnection.s3.service; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| import static com.example.solidconnection.common.exception.ErrorCode.S3_CLIENT_EXCEPTION; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.S3_CLIENT_EXCEPTION; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; | ||
|
|
||
| import com.amazonaws.AmazonServiceException; | ||
| import com.amazonaws.SdkClientException; | ||
| import com.amazonaws.services.s3.AmazonS3Client; | ||
| import com.amazonaws.services.s3.model.CannedAccessControlList; | ||
| import com.amazonaws.services.s3.model.ObjectMetadata; | ||
| import com.amazonaws.services.s3.model.PutObjectRequest; | ||
| import com.example.solidconnection.common.exception.CustomException; | ||
| import java.io.IOException; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.scheduling.annotation.Async; | ||
| import org.springframework.scheduling.annotation.EnableAsync; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
| import com.example.solidconnection.common.exception.CustomException; | ||
| import java.io.IOException; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.scheduling.annotation.Async; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
| import software.amazon.awssdk.core.exception.SdkException; | ||
| import software.amazon.awssdk.core.sync.RequestBody; | ||
| import software.amazon.awssdk.services.s3.S3Client; | ||
| import software.amazon.awssdk.services.s3.model.ObjectCannedACL; | ||
| import software.amazon.awssdk.services.s3.model.PutObjectRequest; | ||
| import software.amazon.awssdk.services.s3.model.S3Exception; | ||
|
|
||
| @Component | ||
| @EnableAsync | ||
| @Slf4j | ||
| public class FileUploadService { | ||
| @Component | ||
| @Slf4j | ||
| @RequiredArgsConstructor | ||
| public class FileUploadService { | ||
|
|
||
| private final AmazonS3Client amazonS3; | ||
| private final S3Client s3Client; | ||
|
|
||
| public FileUploadService(AmazonS3Client amazonS3) { | ||
| this.amazonS3 = amazonS3; | ||
| } | ||
| @Async | ||
| public void uploadFile(String bucket, String fileName, MultipartFile multipartFile) { | ||
|
Comment on lines
+27
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# `@EnableAsync` 설정 확인
rg -n "@EnableAsync" --type javaRepository: solid-connection/solid-connect-server Length of output: 287 🏁 Script executed: #!/bin/bash
# 두 파일의 내용 확인 - `@EnableAsync` 주변 컨텍스트
echo "=== UpdateViewCountScheduler.java ==="
sed -n '1,30p' src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java
echo -e "\n=== UpdateViewCountService.java ==="
sed -n '1,30p' src/main/java/com/example/solidconnection/community/post/service/UpdateViewCountService.javaRepository: solid-connection/solid-connect-server Length of output: 2450
다음과 같은 사항을 확인했습니다:
🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 나중에 한 번에 바꿔야 할 것 같네 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
필요하시다면 추적을 위한 GitHub 이슈를 생성해 드릴까요? 이슈에 현재 상황과 개선 방향을 정리해 두면 나중에 작업하실 때 도움이 될 것 같습니다. |
||
| try { | ||
| PutObjectRequest putObjectRequest = PutObjectRequest.builder() | ||
| .bucket(bucket) | ||
| .key(fileName) | ||
| .contentType(multipartFile.getContentType()) | ||
| .contentLength(multipartFile.getSize()) | ||
| .build(); | ||
|
|
||
| @Async | ||
| public void uploadFile(String bucket, String fileName, MultipartFile multipartFile) { | ||
| // 메타데이터 생성 | ||
| String contentType = multipartFile.getContentType(); | ||
| ObjectMetadata metadata = new ObjectMetadata(); | ||
| metadata.setContentType(contentType); | ||
| metadata.setContentLength(multipartFile.getSize()); | ||
| s3Client.putObject(putObjectRequest, | ||
| RequestBody.fromInputStream(multipartFile.getInputStream(), multipartFile.getSize())); | ||
|
|
||
| try { | ||
| amazonS3.putObject(new PutObjectRequest(bucket, fileName, multipartFile.getInputStream(), metadata) | ||
| .withCannedAcl(CannedAccessControlList.PublicRead)); | ||
| log.info("이미지 업로드 정상적 완료 thread: {}", Thread.currentThread().getName()); | ||
| } catch (AmazonServiceException e) { | ||
| log.error("이미지 업로드 중 s3 서비스 예외 발생 : {}", e.getMessage()); | ||
| throw new CustomException(S3_SERVICE_EXCEPTION); | ||
| } catch (SdkClientException | IOException e) { | ||
| log.error("이미지 업로드 중 s3 클라이언트 예외 발생 : {}", e.getMessage()); | ||
| throw new CustomException(S3_CLIENT_EXCEPTION); | ||
| log.info("파일 업로드 정상 완료 thread: {}", Thread.currentThread().getName()); | ||
| } catch (S3Exception e) { | ||
| log.error("S3 서비스 예외 발생 : {}", e.awsErrorDetails().errorMessage()); | ||
| throw new CustomException(S3_SERVICE_EXCEPTION); | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } catch (SdkException | IOException e) { | ||
| log.error("S3 클라이언트 또는 IO 예외 발생 : {}", e.getMessage()); | ||
| throw new CustomException(S3_CLIENT_EXCEPTION); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,10 +7,6 @@ | |
| import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; | ||
|
|
||
| import com.amazonaws.AmazonServiceException; | ||
| import com.amazonaws.SdkClientException; | ||
| import com.amazonaws.services.s3.AmazonS3Client; | ||
| import com.amazonaws.services.s3.model.DeleteObjectRequest; | ||
| import com.example.solidconnection.common.exception.CustomException; | ||
| import com.example.solidconnection.s3.domain.ImgType; | ||
| import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; | ||
|
|
@@ -23,21 +19,22 @@ | |
| import java.util.Objects; | ||
| import java.util.UUID; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
| import software.amazon.awssdk.core.exception.SdkException; | ||
| import software.amazon.awssdk.services.s3.S3Client; | ||
| import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; | ||
| import software.amazon.awssdk.services.s3.model.S3Exception; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class S3Service { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(S3Service.class); | ||
| private static final long MAX_FILE_SIZE_MB = 1024 * 1024 * 5; | ||
|
|
||
| private final AmazonS3Client amazonS3; | ||
| private final S3Client s3Client; | ||
| private final SiteUserRepository siteUserRepository; | ||
| private final FileUploadService fileUploadService; | ||
| private final ThreadPoolTaskExecutor asyncExecutor; | ||
|
|
@@ -56,22 +53,23 @@ public class S3Service { | |
| * - 5mb 미만의 파일은 바로 업로드한다. | ||
| * */ | ||
| public UploadedFileUrlResponse uploadFile(MultipartFile multipartFile, ImgType imageFile) { | ||
| // 파일 검증 | ||
| validateImgFile(multipartFile); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 PR의 작업 내용은 아니지만,
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여유 되시면 이 내용도 적용 부탁드립니다 |
||
| // 파일 이름 생성 | ||
| UUID randomUUID = UUID.randomUUID(); | ||
| String fileName = imageFile.getType() + "/" + randomUUID; | ||
| // 파일업로드 비동기로 진행 | ||
| if (multipartFile.getSize() >= MAX_FILE_SIZE_MB) { | ||
| asyncExecutor.submit(() -> { | ||
| fileUploadService.uploadFile(bucket, "origin/" + fileName, multipartFile); | ||
| }); | ||
| } else { | ||
| asyncExecutor.submit(() -> { | ||
| fileUploadService.uploadFile(bucket, fileName, multipartFile); | ||
| }); | ||
| } | ||
| return new UploadedFileUrlResponse(fileName); | ||
| String extension = getFileExtension(Objects.requireNonNull(multipartFile.getOriginalFilename())); | ||
| String baseFileName = randomUUID + "." + extension; | ||
| String fileName = imageFile.getType() + "/" + baseFileName; | ||
| final boolean isLargeFile = multipartFile.getSize() >= MAX_FILE_SIZE_MB && imageFile != ImgType.CHAT; | ||
|
|
||
| final String uploadPath = isLargeFile ? "original/" + fileName : fileName; | ||
| final String returnPath = isLargeFile | ||
| ? "resize/" + fileName.substring(0, fileName.lastIndexOf('.')) + ".webp" | ||
| : fileName; | ||
|
|
||
| asyncExecutor.submit(() -> { | ||
| fileUploadService.uploadFile(bucket, uploadPath, multipartFile); | ||
| }); | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| return new UploadedFileUrlResponse(returnPath); | ||
| } | ||
|
|
||
| public List<UploadedFileUrlResponse> uploadFiles(List<MultipartFile> multipartFile, ImgType imageFile) { | ||
|
|
@@ -125,12 +123,14 @@ public void deletePostImage(String url) { | |
|
|
||
| private void deleteFile(String fileName) { | ||
| try { | ||
| amazonS3.deleteObject(new DeleteObjectRequest(bucket, fileName)); | ||
| } catch (AmazonServiceException e) { | ||
| log.error("파일 삭제 중 s3 서비스 예외 발생 : {}", e.getMessage()); | ||
| DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder() | ||
| .bucket(bucket) | ||
| .key(fileName) | ||
| .build(); | ||
| s3Client.deleteObject(deleteObjectRequest); | ||
| } catch (S3Exception e) { | ||
| throw new CustomException(S3_SERVICE_EXCEPTION); | ||
| } catch (SdkClientException e) { | ||
| log.error("파일 삭제 중 s3 클라이언트 예외 발생 : {}", e.getMessage()); | ||
| } catch (SdkException e) { | ||
| throw new CustomException(S3_CLIENT_EXCEPTION); | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저에게는 UploadType이 직관적으로는 업로드한 파일이 어떤 타입인지, 즉 파일 확장자를 구별하는 걸로 느껴집니다..!
해당 enum의 목적이 어떤 기능(도메인)에서 사용되는 기능인지를 구분하고, 저장하는 파일의 basePath를 결정지어야 하는 책임이 있다고 생각해서 다른 네이밍이 좋을 것 같다는 조심스런 의견을 드립니다 ㅎㅎ
(추신 : 제미나이가 추천한 이름은
UploadPath,UploadCategory였습니다..!)