Merge branch 'be/refactor/image' into 'be/develop'

Fix: Zip파일 업로드 오류 해결

See merge request s11-s-project/S11P21S002!232
This commit is contained in:
홍창기 2024-09-29 19:21:04 +09:00
commit b94420a91f
4 changed files with 27 additions and 14 deletions

View File

@ -19,7 +19,7 @@ public class Image extends BaseEntity {
*/ */
@Id @Id
@Column(name = "project_image_id", nullable = false) @Column(name = "project_image_id", nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
/** /**

View File

@ -11,10 +11,11 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -27,15 +28,15 @@ public class ImageAsyncService {
private final ImageRepository imageRepository; private final ImageRepository imageRepository;
private final ThreadPoolTaskExecutor imageUploadExecutor; private final ThreadPoolTaskExecutor imageUploadExecutor;
@Transactional
@Async("imageUploadExecutor") @Async("imageUploadExecutor")
public CompletableFuture<Void> asyncImageUpload(final List<MultipartFile> imageList, final Folder folder, final Integer projectId) { @Transactional(propagation = Propagation.REQUIRES_NEW)
public CompletableFuture<Void> asyncImageUpload(final List<MultipartFile> imageFileList, final Folder folder, final Integer projectId) {
log.debug("현재 스레드 - {} 업로드 파일 개수 - {}, 현재 작업 큐 용량 - {}", log.debug("현재 스레드 - {} 업로드 파일 개수 - {}, 현재 작업 큐 용량 - {}",
Thread.currentThread().getName(), Thread.currentThread().getName(),
imageList.size(), imageFileList.size(),
imageUploadExecutor.getThreadPoolExecutor().getQueue().size()); // 큐에 쌓인 작업 출력); imageUploadExecutor.getThreadPoolExecutor().getQueue().size()); // 큐에 쌓인 작업 출력);
imageList.forEach(file -> { imageFileList.forEach(file -> {
try{ try{
String extension = getExtension(file.getOriginalFilename()); String extension = getExtension(file.getOriginalFilename());
String imageKey = s3UploadService.uploadImageFile(file, extension, projectId); String imageKey = s3UploadService.uploadImageFile(file, extension, projectId);
@ -51,10 +52,12 @@ public class ImageAsyncService {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void createImage(MultipartFile file, String imageKey, Folder folder) { public void createImage(MultipartFile file, String imageKey, Folder folder) {
try { try {
String name = file.getOriginalFilename(); String name = file.getOriginalFilename();
String extension = getExtension(name); String extension = getExtension(name);
log.debug("image 파일 {} {} {} {}", name, extension, imageKey, folder.getId());
Image image = Image.of(name, imageKey, extension, folder); Image image = Image.of(name, imageKey, extension, folder);
imageRepository.save(image); imageRepository.save(image);
}catch (Exception e){ }catch (Exception e){

View File

@ -20,6 +20,7 @@ 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.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -55,6 +56,9 @@ public class ImageService {
@CheckPrivilege(value = PrivilegeType.EDITOR) @CheckPrivilege(value = PrivilegeType.EDITOR)
public void uploadImageList(final List<MultipartFile> imageList, final Integer folderId, final Integer projectId) { public void uploadImageList(final List<MultipartFile> imageList, final Integer folderId, final Integer projectId) {
Folder folder = getOrCreateFolder(folderId, projectId); Folder folder = getOrCreateFolder(folderId, projectId);
folderRepository.flush();
log.debug("folder Id {}, Project Id {}",folder.getId(), folder.getProject().getId());
long prev = System.currentTimeMillis(); long prev = System.currentTimeMillis();
// 동적 배치 크기 계산 // 동적 배치 크기 계산
@ -92,9 +96,6 @@ public class ImageService {
public void uploadFolderWithImages(final MultipartFile zipFile, final Integer projectId, final Integer folderId) throws IOException { public void uploadFolderWithImages(final MultipartFile zipFile, final Integer projectId, final Integer folderId) throws IOException {
log.debug("파일 크기: {}, 기존 파일 이름: {} ", zipFile.getSize(), zipFile.getOriginalFilename()); log.debug("파일 크기: {}, 기존 파일 이름: {} ", zipFile.getSize(), zipFile.getOriginalFilename());
Project project = getProject(projectId); // 현재 프로젝트
Folder rootFolder = folderRepository.findById(folderId).orElse(null); // 부모 프로젝트
Path tmpDir = null; Path tmpDir = null;
try { try {
String originalFilename = zipFile.getOriginalFilename(); String originalFilename = zipFile.getOriginalFilename();
@ -106,6 +107,9 @@ public class ImageService {
String zipFolderName = originalFilename.substring(0, originalFilename.lastIndexOf(".")); String zipFolderName = originalFilename.substring(0, originalFilename.lastIndexOf("."));
tmpDir = Files.createTempDirectory(zipFolderName); tmpDir = Files.createTempDirectory(zipFolderName);
Project project = getProject(projectId);
Folder rootFolder = getOrCreateFolder(folderId, projectId);
unzip(zipFile, tmpDir.toString()); unzip(zipFile, tmpDir.toString());
processFolderRecursively(tmpDir.toFile(), rootFolder, project); processFolderRecursively(tmpDir.toFile(), rootFolder, project);
} finally { } finally {
@ -151,6 +155,7 @@ public class ImageService {
Folder currentFolder = createFolderAndSave(file.getName(), parentFolder, project); Folder currentFolder = createFolderAndSave(file.getName(), parentFolder, project);
processFolderRecursively(file, currentFolder, project); processFolderRecursively(file, currentFolder, project);
} else if (isImageFile(file)) { } else if (isImageFile(file)) {
log.debug("파일 {}", file.getName());
uploadAndSave(file, parentFolder, project); uploadAndSave(file, parentFolder, project);
} }
} }
@ -295,6 +300,7 @@ public class ImageService {
* 폴더 생성 또는 가져오기 * 폴더 생성 또는 가져오기
* 폴더 생성 작업이 있음으로 트랜잭션 보호 * 폴더 생성 작업이 있음으로 트랜잭션 보호
*/ */
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Folder getOrCreateFolder(Integer folderId, Integer projectId) { public Folder getOrCreateFolder(Integer folderId, Integer projectId) {
if (folderId != 0) { if (folderId != 0) {
return getFolder(folderId); return getFolder(folderId);
@ -333,11 +339,13 @@ public class ImageService {
/** /**
* File 업로드 저장 * File 업로드 저장
*/ */
private void uploadAndSave(File file, Folder folder, Project project) { public void uploadAndSave(File file, Folder folder, Project project) {
try { try {
String key = uploadToS3(file, project.getId()); String key = uploadToS3(file, project.getId());
log.debug("업로드 {}", key);
saveImage(file, key, folder); saveImage(file, key, folder);
} catch (Exception e) { } catch (Exception e) {
log.error("", e);
throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE, "이미지 업로드 중 오류 발생"); throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE, "이미지 업로드 중 오류 발생");
} }
} }
@ -361,9 +369,11 @@ public class ImageService {
*/ */
public void saveImage(final File file, final String key, final Folder folder) { public void saveImage(final File file, final String key, final Folder folder) {
try { try {
log.debug("파일 생성 완료 : {} {}", file.getName(), key);
Image image = createImage(file.getName(), key, folder); Image image = createImage(file.getName(), key, folder);
imageRepository.save(image); imageRepository.save(image);
} catch (Exception e) { } catch (Exception e) {
log.error("error ", e);
s3UploadService.deleteImageFromS3(key); s3UploadService.deleteImageFromS3(key);
throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE, "에러 발생"); throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE, "에러 발생");
} }
@ -374,8 +384,7 @@ public class ImageService {
*/ */
public String uploadToS3(final MultipartFile file, final Integer projectId) { public String uploadToS3(final MultipartFile file, final Integer projectId) {
String extension = getExtension(file.getOriginalFilename()); String extension = getExtension(file.getOriginalFilename());
// return s3UploadService.uploadImageFile(file, extension, projectId); return s3UploadService.uploadImageFile(file, extension, projectId);
return "";
} }
/** /**
@ -383,6 +392,7 @@ public class ImageService {
*/ */
private String uploadToS3(final File file, final Integer projectId) { private String uploadToS3(final File file, final Integer projectId) {
String extension = getExtension(file.getName()); String extension = getExtension(file.getName());
log.debug("S3에서 업로드 현황 {}, {}, {}", file.getName(), extension, file.getTotalSpace());
return s3UploadService.uploadImageFile(file, extension, projectId); return s3UploadService.uploadImageFile(file, extension, projectId);
} }

View File

@ -96,8 +96,8 @@ public class S3UploadService {
*/ */
public String uploadImageFile(final File file, final String extension, final Integer projectId) { public String uploadImageFile(final File file, final String extension, final Integer projectId) {
try (InputStream inputStream = new FileInputStream(file)) { try (InputStream inputStream = new FileInputStream(file)) {
// return uploadImageToS3(inputStream, extension, projectId); return uploadImageToS3(inputStream, extension, projectId);
return ""; // return "";
} catch (IOException e) { } catch (IOException e) {
log.debug("이미지 업로드에서 에러 발생 ", e); log.debug("이미지 업로드에서 에러 발생 ", e);
throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE); throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE);