-
Notifications
You must be signed in to change notification settings - Fork 3
[feature] 구글 드라이브 업로드 기능 구현 #369
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -38,4 +38,4 @@ out/ | |
|
|
||
|
|
||
| application.properties | ||
| moadong.json | ||
| moadong.json | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package moadong.gcs.domain; | ||
| package moadong.media.domain; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package moadong.gcs.dto; | ||
| package moadong.media.dto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package moadong.media.service; | ||
|
|
||
| import java.util.List; | ||
| import moadong.club.entity.Club; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
|
|
||
| public interface ClubImageService { | ||
|
|
||
| String uploadLogo(String clubId, MultipartFile file); | ||
|
|
||
| void deleteLogo(String clubId); | ||
|
|
||
| String uploadFeed(String clubId, MultipartFile file); | ||
|
|
||
| void updateFeeds(String clubId, List<String> newFeedImageList); | ||
|
|
||
| void deleteFile(Club club, String filePath); | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,171 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package moadong.media.service; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import static moadong.media.util.ClubImageUtil.containsKorean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.google.api.client.http.FileContent; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.google.api.services.drive.Drive; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.google.api.services.drive.model.File; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.google.api.services.drive.model.Permission; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Collections; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import moadong.club.entity.Club; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import moadong.club.repository.ClubRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import moadong.global.exception.ErrorCode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import moadong.global.exception.RestApiException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import moadong.global.util.ObjectIdConverter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import moadong.global.util.RandomStringUtil; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import moadong.media.domain.FileType; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.bson.types.ObjectId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.beans.factory.annotation.Value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.web.multipart.MultipartFile; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class GoogleDriveClubImageService implements ClubImageService { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Value("${google.drive.share-file-id}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String shareFileId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Value("${server.feed.max-count}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private int MAX_FEED_COUNT; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final Drive googleDrive; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final ClubRepository clubRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public String uploadLogo(String clubId, MultipartFile file) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ObjectId objectId = ObjectIdConverter.convertString(clubId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Club club = clubRepository.findClubById(objectId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (club.getClubRecruitmentInformation().getLogo() != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deleteFile(club, club.getClubRecruitmentInformation().getLogo()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String filePath = uploadFile(clubId, file, FileType.LOGO); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| club.updateLogo(filePath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clubRepository.save(club); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return filePath; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+37
to
+51
Contributor
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. 🛠️ Refactor suggestion 로고 업로드 메서드 구현 검토 로고 업로드 로직은 잘 구현되어 있으나, 트랜잭션 관리가 필요할 수 있습니다. 현재 파일 업로드와 클럽 정보 업데이트가 별도 작업으로 진행되고 있어, 한 작업이 실패할 경우 일관성 문제가 발생할 수 있습니다. 메서드에 + @Transactional
@Override
public String uploadLogo(String clubId, MultipartFile file) {📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void deleteLogo(String clubId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ObjectId objectId = ObjectIdConverter.convertString(clubId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Club club = clubRepository.findClubById(objectId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (club.getClubRecruitmentInformation().getLogo() != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deleteFile(club, club.getClubRecruitmentInformation().getLogo()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| club.updateLogo(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clubRepository.save(club); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+53
to
+64
Contributor
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. 🛠️ Refactor suggestion 로고 삭제 메서드 구현 검토
+ @Transactional
@Override
public void deleteLogo(String clubId) {📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public String uploadFeed(String clubId, MultipartFile file) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ObjectId objectId = ObjectIdConverter.convertString(clubId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int feedImagesCount = clubRepository.findClubById(objectId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .getClubRecruitmentInformation().getFeedImages().size(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (feedImagesCount + 1 > MAX_FEED_COUNT) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new RestApiException(ErrorCode.TOO_MANY_FILES); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return uploadFile(clubId, file, FileType.FEED); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+66
to
+77
Contributor
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. 피드 업로드 메서드 검토 피드 이미지 업로드 로직은 잘 구현되었으나, 클럽 엔티티 저장 과정이 누락되어 있습니다. 현재 코드는 파일을 업로드하지만 클럽 엔티티의 피드 이미지 리스트를 업데이트하지 않고 있습니다. 피드 이미지 URL을 클럽 엔티티에 추가하고 저장하는 로직을 추가해야 합니다: @Override
+ @Transactional
public String uploadFeed(String clubId, MultipartFile file) {
ObjectId objectId = ObjectIdConverter.convertString(clubId);
- int feedImagesCount = clubRepository.findClubById(objectId)
+ Club club = clubRepository.findClubById(objectId)
.orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));
- .getClubRecruitmentInformation().getFeedImages().size();
+ int feedImagesCount = club.getClubRecruitmentInformation().getFeedImages().size();
if (feedImagesCount + 1 > MAX_FEED_COUNT) {
throw new RestApiException(ErrorCode.TOO_MANY_FILES);
}
- return uploadFile(clubId, file, FileType.FEED);
+ String filePath = uploadFile(clubId, file, FileType.FEED);
+ List<String> feedImages = new ArrayList<>(club.getClubRecruitmentInformation().getFeedImages());
+ feedImages.add(filePath);
+ club.updateFeedImages(feedImages);
+ clubRepository.save(club);
+ return filePath;
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void updateFeeds(String clubId, List<String> newFeedImageList) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ObjectId objectId = ObjectIdConverter.convertString(clubId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Club club = clubRepository.findClubById(objectId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (newFeedImageList.size() > MAX_FEED_COUNT) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new RestApiException(ErrorCode.TOO_MANY_FILES); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<String> feedImages = club.getClubRecruitmentInformation().getFeedImages(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (feedImages != null && !feedImages.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deleteFeedImages(club, feedImages, newFeedImageList); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| club.updateFeedImages(newFeedImageList); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clubRepository.save(club); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+79
to
+95
Contributor
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. 🛠️ Refactor suggestion 피드 이미지 업데이트 메서드 검토 피드 이미지 업데이트 로직은 전반적으로 잘 구현되어 있습니다. 트랜잭션 관리가 필요합니다. + @Transactional
@Override
public void updateFeeds(String clubId, List<String> newFeedImageList) {📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private void deleteFeedImages(Club club, List<String> feedImages, List<String> newFeedImages) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (String feedsImage : feedImages) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!newFeedImages.contains(feedsImage)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deleteFile(club, feedsImage); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void deleteFile(Club club, String filePath) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //"https://drive.google.com/file/d/{fileId}/view" -> {fileId} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String fileId = filePath.split("/")[5]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| googleDrive.files() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .delete(fileId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .setSupportsAllDrives(true) // 공유 드라이브(Shared Drive)도 지원할 경우 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .execute(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (IOException e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new RestApiException(ErrorCode.IMAGE_DELETE_FAILED); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+105
to
+117
Contributor
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. 파일 삭제 메서드 구현 관련 중요 이슈 현재 파일 ID 추출 방식은 Google Drive URL 형식에 의존하고 있어 Google Drive가 URL 형식을 변경하면 문제가 발생할 수 있습니다. 또한 split 인덱스를 하드코딩하는 것은 안전하지 않습니다. 정규식 또는 URL 파싱 라이브러리를 사용하여 더 안정적으로 fileId를 추출하도록 개선하세요: - //"https://drive.google.com/file/d/{fileId}/view" -> {fileId}
- String fileId = filePath.split("/")[5];
+ // "https://drive.google.com/file/d/{fileId}/view" -> {fileId}
+ String fileId;
+ try {
+ java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("https://drive\\.google\\.com/file/d/([^/]+)/view");
+ java.util.regex.Matcher matcher = pattern.matcher(filePath);
+ if (matcher.find()) {
+ fileId = matcher.group(1);
+ } else {
+ throw new RestApiException(ErrorCode.INVALID_FILE_URL);
+ }
+ } catch (Exception e) {
+ throw new RestApiException(ErrorCode.INVALID_FILE_URL);
+ }추가로 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String uploadFile(String clubId, MultipartFile file, FileType fileType) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (file == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new RestApiException(ErrorCode.FILE_NOT_FOUND); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // MultipartFile → java.io.File 변환 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| java.io.File tempFile; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tempFile = java.io.File.createTempFile("upload-", file.getOriginalFilename()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| file.transferTo(tempFile); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (IOException e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new RestApiException(ErrorCode.FILE_TRANSFER_ERROR); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 메타데이터 생성 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| File fileMetadata = new File(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String fileName = file.getOriginalFilename(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (containsKorean(fileName)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileName = RandomStringUtil.generateRandomString(10); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileMetadata.setName(clubId + "/" + fileType + "/" + fileName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileMetadata.setMimeType(file.getContentType()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 공유 ID 설정 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileMetadata.setParents(Collections.singletonList(shareFileId)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 파일 업로드 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FileContent mediaContent = new FileContent(file.getContentType(), tempFile); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 전체 공개 권한 설정 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Permission publicPermission = new Permission() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .setType("anyone") // 누구나 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .setRole("reader"); // 읽기 권한 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| File uploadedFile; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploadedFile= googleDrive.files().create(fileMetadata, mediaContent) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .setFields("id") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .execute(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| googleDrive.permissions().create(uploadedFile.getId(), publicPermission) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .setFields("id") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .execute(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new RestApiException(ErrorCode.IMAGE_UPLOAD_FAILED); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }finally { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 임시 파일 삭제 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tempFile.delete(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 공유 링크 반환 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String publicUrl = "https://drive.google.com/file/d/" + uploadedFile.getId() + "/view"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return publicUrl; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+119
to
+169
Contributor
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. 🛠️ Refactor suggestion 파일 업로드 메서드 구현 검토 파일 업로드 메서드는 전반적으로 잘 구현되어 있지만, 몇 가지 개선 사항이 있습니다:
파일 크기 및 형식 검증을 추가하고, 임시 파일 관리를 개선하세요: private String uploadFile(String clubId, MultipartFile file, FileType fileType) {
if (file == null) {
throw new RestApiException(ErrorCode.FILE_NOT_FOUND);
}
+
+ // 파일 크기 및 형식 검증
+ if (file.getSize() > 10 * 1024 * 1024) { // 예: 10MB 제한
+ throw new RestApiException(ErrorCode.FILE_SIZE_EXCEEDED);
+ }
+
+ // 지원되는 형식 검증 (이미지 파일만 허용하는 예시)
+ String contentType = file.getContentType();
+ if (contentType == null || !contentType.startsWith("image/")) {
+ throw new RestApiException(ErrorCode.UNSUPPORTED_FILE_TYPE);
+ }
+
// MultipartFile → java.io.File 변환
- java.io.File tempFile;
+ java.io.File tempFile = null;
try {
tempFile = java.io.File.createTempFile("upload-", file.getOriginalFilename());
file.transferTo(tempFile);또한 파일명 처리 로직을 개선하세요: // 메타데이터 생성
File fileMetadata = new File();
String fileName = file.getOriginalFilename();
- if (containsKorean(fileName)) {
+ // 한글뿐만 아니라 모든 특수문자에 대비하여 파일명 정리
+ if (fileName == null || !fileName.matches("[a-zA-Z0-9._-]+")) {
fileName = RandomStringUtil.generateRandomString(10);
} |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package moadong.media.util; | ||
|
|
||
| import java.text.Normalizer; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| public class ClubImageUtil { | ||
|
|
||
| public static boolean containsKorean(String text) { | ||
| text = Normalizer.normalize(text, Normalizer.Form.NFC); | ||
| return Pattern.matches(".*[ㄱ-ㅎㅏ-ㅣ가-힣]+.*", text); | ||
| } | ||
| } |
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.
💡 Verification agent
🧩 Analysis chain
@service 어노테이션이 주석 처리된 이유 확인 필요
@Service어노테이션이 주석 처리되어 있습니다. 이렇게 되면 Spring이 이 클래스를 서비스 빈으로 자동 등록하지 않아 의존성 주입에 문제가 발생할 수 있습니다.가능한 이유:
의도적인 경우가 아니라면 주석을 제거하거나, 조건부 빈 등록(
@ConditionalOnProperty)을 사용하는 것이 좋습니다.다음 스크립트로 ClubImageService를 주입받는 곳이 있는지 확인해보세요:
🏁 Script executed:
Length of output: 659
GcsClubImageService에 @service 주석 해제 및 조건부 빈 등록 검토
현재
GcsClubImageService클래스 상단의@Service어노테이션이 주석 처리되어 있어 Spring 컨텍스트에 빈으로 등록되지 않습니다. 이로 인해ClubImageController에서ClubImageService를 주입받을 때 GCS 구현체가 등록되지 않아 의도와 다른 구현체가 선택되거나 주입 오류가 발생할 수 있습니다.검증 결과:
ClubImageController(backend/src/main/java/moadong/media/controller/ClubImageController.java)에서private final ClubImageService clubImageService;를 통해 주입받고 있습니다.GoogleDriveClubImageService는 빈으로 등록되나,GcsClubImageService는 등록되지 않습니다.수정 제안:
GcsClubImageService(backend/src/main/java/moadong/media/service/GcsClubImageService.java:22–23)에서@Service주석을 제거@ConditionalOnProperty("club.image.storage","gcs")등을 추가