Refactor: 이미지 벌크 업로드
This commit is contained in:
parent
6a32b071ff
commit
402620130b
@ -56,12 +56,10 @@ public class ImageController {
|
|||||||
@Operation(summary = "이미지에 대한 Presigned ", description = "이미지에 대한 Presigned 주소를 받아옵니다.")
|
@Operation(summary = "이미지에 대한 Presigned ", description = "이미지에 대한 Presigned 주소를 받아옵니다.")
|
||||||
@SwaggerApiError({ErrorCode.BAD_REQUEST, ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR})
|
@SwaggerApiError({ErrorCode.BAD_REQUEST, ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR})
|
||||||
public List<ImagePresignedUrlResponse> uploadFolderByPresignedImage(
|
public List<ImagePresignedUrlResponse> uploadFolderByPresignedImage(
|
||||||
@CurrentUser final Integer memberId,
|
|
||||||
@RequestBody final List<ImageMetaRequest> imageMetaList,
|
@RequestBody final List<ImageMetaRequest> imageMetaList,
|
||||||
@PathVariable("project_id") final Integer projectId,
|
@PathVariable("project_id") final Integer projectId,
|
||||||
@PathVariable("folder_id") final Integer folderId) {
|
@PathVariable("folder_id") final Integer folderId) {
|
||||||
log.debug("requestImageList {}", imageMetaList);
|
return imageService.uploadFolderByPresignedImage(imageMetaList, projectId, folderId);
|
||||||
return imageService.uploadFolderByPresignedImage(memberId, imageMetaList, projectId, folderId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/folders/{folder_id}/images/{image_id}")
|
@GetMapping("/folders/{folder_id}/images/{image_id}")
|
||||||
@ -81,7 +79,6 @@ public class ImageController {
|
|||||||
@Operation(summary = "이미지 폴더 이동", description = "이미지가 위치한 폴더를 변경합니다.")
|
@Operation(summary = "이미지 폴더 이동", description = "이미지가 위치한 폴더를 변경합니다.")
|
||||||
@SwaggerApiError({ErrorCode.BAD_REQUEST, ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR, ErrorCode.PARTICIPANT_EDITOR_UNAUTHORIZED})
|
@SwaggerApiError({ErrorCode.BAD_REQUEST, ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR, ErrorCode.PARTICIPANT_EDITOR_UNAUTHORIZED})
|
||||||
public void moveFolderImage(
|
public void moveFolderImage(
|
||||||
@CurrentUser final Integer memberId,
|
|
||||||
@PathVariable("folder_id") final Integer folderId,
|
@PathVariable("folder_id") final Integer folderId,
|
||||||
@PathVariable("project_id") final Integer projectId,
|
@PathVariable("project_id") final Integer projectId,
|
||||||
@PathVariable("image_id") final Long imageId,
|
@PathVariable("image_id") final Long imageId,
|
||||||
@ -98,7 +95,6 @@ public class ImageController {
|
|||||||
@PathVariable("folder_id") final Integer folderId,
|
@PathVariable("folder_id") final Integer folderId,
|
||||||
@PathVariable("project_id") final Integer projectId,
|
@PathVariable("project_id") final Integer projectId,
|
||||||
@PathVariable("image_id") final Long imageId) {
|
@PathVariable("image_id") final Long imageId) {
|
||||||
log.debug("project: {} , folder: {}, 삭제하려는 이미지: {}, 현재 로그인 중인 사용자 : {}", projectId, folderId, imageId);
|
|
||||||
imageService.deleteImage(projectId, folderId, imageId);
|
imageService.deleteImage(projectId, folderId, imageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.worlabel.domain.image.repository;
|
||||||
|
|
||||||
|
import com.worlabel.domain.image.entity.Image;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ImageBulkRepository {
|
||||||
|
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void saveAll(List<Image> imageList) {
|
||||||
|
String sql = "INSERT INTO project_image (image_extension,folder_id,image_key,status,image_title)" +
|
||||||
|
" values (?, ?, ?, ?, ?)";
|
||||||
|
|
||||||
|
int batchSize = 1000; // 배치 크기 설정
|
||||||
|
for (int i = 0; i < imageList.size(); i += batchSize) {
|
||||||
|
List<Image> batchList = imageList.subList(i, Math.min(i + batchSize, imageList.size()));
|
||||||
|
|
||||||
|
jdbcTemplate.batchUpdate(sql,
|
||||||
|
batchList,
|
||||||
|
batchList.size(),
|
||||||
|
(PreparedStatement ps, Image image) -> {
|
||||||
|
ps.setString(1, image.getExtension()); // image_extension (String)
|
||||||
|
ps.setInt(2, image.getFolder().getId()); // folder_id (Integer)
|
||||||
|
ps.setString(3, image.getImageKey()); // image_key (String)
|
||||||
|
ps.setString(4, image.getStatus().name()); // status (String or Enum)
|
||||||
|
ps.setString(5, image.getTitle()); // image_title (String)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,6 +2,7 @@ package com.worlabel.domain.image.service;
|
|||||||
|
|
||||||
import com.worlabel.domain.folder.entity.Folder;
|
import com.worlabel.domain.folder.entity.Folder;
|
||||||
import com.worlabel.domain.image.entity.Image;
|
import com.worlabel.domain.image.entity.Image;
|
||||||
|
import com.worlabel.domain.image.repository.ImageBulkRepository;
|
||||||
import com.worlabel.domain.image.repository.ImageRepository;
|
import com.worlabel.domain.image.repository.ImageRepository;
|
||||||
import com.worlabel.global.exception.CustomException;
|
import com.worlabel.global.exception.CustomException;
|
||||||
import com.worlabel.global.exception.ErrorCode;
|
import com.worlabel.global.exception.ErrorCode;
|
||||||
@ -25,6 +26,7 @@ public class ImageAsyncService {
|
|||||||
|
|
||||||
private final S3UploadService s3UploadService;
|
private final S3UploadService s3UploadService;
|
||||||
private final ImageRepository imageRepository;
|
private final ImageRepository imageRepository;
|
||||||
|
private final ImageBulkRepository imageBulkRepository;
|
||||||
private final ThreadPoolTaskExecutor imageUploadExecutor;
|
private final ThreadPoolTaskExecutor imageUploadExecutor;
|
||||||
|
|
||||||
@Async("imageUploadExecutor")
|
@Async("imageUploadExecutor")
|
||||||
@ -72,4 +74,10 @@ public class ImageAsyncService {
|
|||||||
private String getExtension(String fileName) {
|
private String getExtension(String fileName) {
|
||||||
return fileName.substring(fileName.lastIndexOf(".") + 1);
|
return fileName.substring(fileName.lastIndexOf(".") + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Async("imageUploadExecutor")
|
||||||
|
public void saveImages(final List<Image> imageList) {
|
||||||
|
imageBulkRepository.saveAll(imageList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import com.worlabel.domain.folder.repository.FolderRepository;
|
|||||||
import com.worlabel.domain.image.entity.Image;
|
import com.worlabel.domain.image.entity.Image;
|
||||||
import com.worlabel.domain.image.entity.LabelStatus;
|
import com.worlabel.domain.image.entity.LabelStatus;
|
||||||
import com.worlabel.domain.image.entity.dto.*;
|
import com.worlabel.domain.image.entity.dto.*;
|
||||||
|
import com.worlabel.domain.image.repository.ImageBulkRepository;
|
||||||
import com.worlabel.domain.image.repository.ImageRepository;
|
import com.worlabel.domain.image.repository.ImageRepository;
|
||||||
import com.worlabel.domain.participant.entity.PrivilegeType;
|
import com.worlabel.domain.participant.entity.PrivilegeType;
|
||||||
import com.worlabel.domain.project.entity.Project;
|
import com.worlabel.domain.project.entity.Project;
|
||||||
@ -22,6 +23,7 @@ import org.apache.commons.compress.archivers.ArchiveEntry;
|
|||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
||||||
import org.apache.commons.compress.utils.IOUtils;
|
import org.apache.commons.compress.utils.IOUtils;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@ -321,13 +323,12 @@ public class ImageService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@CheckPrivilege(PrivilegeType.EDITOR)
|
@CheckPrivilege(PrivilegeType.EDITOR)
|
||||||
public List<ImagePresignedUrlResponse> uploadFolderByPresignedImage(final Integer memberId,
|
public List<ImagePresignedUrlResponse> uploadFolderByPresignedImage(final List<ImageMetaRequest> imageMetaList,
|
||||||
final List<ImageMetaRequest> imageMetaList,
|
|
||||||
final Integer projectId,
|
final Integer projectId,
|
||||||
final Integer folderId) {
|
final Integer folderId) {
|
||||||
Folder folder = getOrCreateFolder(folderId, projectId);
|
Folder folder = getOrCreateFolder(folderId, projectId);
|
||||||
List<ImagePresignedUrlResponse> presignedUrls = new ArrayList<>();
|
List<ImagePresignedUrlResponse> presignedUrls = new ArrayList<>();
|
||||||
|
List<Image> imageList = new ArrayList<>();
|
||||||
for (ImageMetaRequest meta : imageMetaList) {
|
for (ImageMetaRequest meta : imageMetaList) {
|
||||||
// UUID 생성 및 이미지 Key 지정
|
// UUID 생성 및 이미지 Key 지정
|
||||||
String key = UUID.randomUUID().toString().substring(0,13);
|
String key = UUID.randomUUID().toString().substring(0,13);
|
||||||
@ -336,21 +337,22 @@ public class ImageService {
|
|||||||
|
|
||||||
// Presigned URL 생성
|
// Presigned URL 생성
|
||||||
String presignedUrl = s3UploadService.generatePresignedUrl(key, extension);
|
String presignedUrl = s3UploadService.generatePresignedUrl(key, extension);
|
||||||
log.debug("presignedUrl {}", presignedUrl);
|
|
||||||
|
|
||||||
// DB에 이미지 메타데이터 저장
|
// DB에 이미지 메타데이터 저장
|
||||||
Image image = Image.of(fileName, s3UploadService.addBucketPrefix(key), extension, folder);
|
Image image = Image.of(fileName, s3UploadService.addBucketPrefix(key), extension, folder);
|
||||||
imageRepository.save(image);
|
imageList.add(image);
|
||||||
|
|
||||||
// Presigned URL과 함께 응답 데이터 생성
|
// Presigned URL과 함께 응답 데이터 생성
|
||||||
ImagePresignedUrlResponse response = ImagePresignedUrlResponse.of(meta.getId(), presignedUrl);
|
ImagePresignedUrlResponse response = ImagePresignedUrlResponse.of(meta.getId(), presignedUrl);
|
||||||
presignedUrls.add(response);
|
presignedUrls.add(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
imageAsyncService.saveImages(imageList);
|
||||||
|
log.debug("이미지 개수 {}",presignedUrls.size());
|
||||||
return presignedUrls;
|
return presignedUrls;
|
||||||
}
|
}
|
||||||
// 이미지 가져오면서 프로젝트 소속 여부를 확인
|
|
||||||
|
|
||||||
|
// 이미지 가져오면서 프로젝트 소속 여부를 확인
|
||||||
private Image getImageByIdAndFolderIdAndFolderProjectId(final Integer folderId, final Long imageId, final Integer projectId) {
|
private Image getImageByIdAndFolderIdAndFolderProjectId(final Integer folderId, final Long imageId, final Integer projectId) {
|
||||||
return imageRepository.findByIdAndFolderIdAndFolderProjectId(imageId, folderId, projectId)
|
return imageRepository.findByIdAndFolderIdAndFolderProjectId(imageId, folderId, projectId)
|
||||||
.orElseThrow(() -> new CustomException(ErrorCode.DATA_NOT_FOUND));
|
.orElseThrow(() -> new CustomException(ErrorCode.DATA_NOT_FOUND));
|
||||||
|
Loading…
Reference in New Issue
Block a user