Feat: 이미지 폴더채로 업로드 구현 - S11P21S002-182
This commit is contained in:
parent
ae2a55434f
commit
7ad391741c
@ -1,5 +1,6 @@
|
||||
package com.worlabel.domain.image.controller;
|
||||
|
||||
import com.worlabel.domain.folder.entity.dto.FolderResponse;
|
||||
import com.worlabel.domain.image.entity.dto.*;
|
||||
import com.worlabel.domain.image.service.ImageService;
|
||||
import com.worlabel.global.annotation.CurrentUser;
|
||||
@ -14,6 +15,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@ -37,6 +39,17 @@ public class ImageController {
|
||||
imageService.uploadImageList(imageList, folderId, projectId, memberId);
|
||||
}
|
||||
|
||||
@PostMapping("/upload")
|
||||
@SwaggerApiSuccess(description = "폴더와 이미지 파일을 성공적으로 업로드합니다.")
|
||||
@Operation(summary = "폴더 업로드", description = "폴더와 이미지 파일을 업로드합니다.")
|
||||
@SwaggerApiError({ErrorCode.BAD_REQUEST, ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR})
|
||||
public void uploadFolder(
|
||||
@RequestParam("folderZip") MultipartFile folderZip,
|
||||
@PathVariable("project_id") Integer projectId,
|
||||
@RequestParam(value = "parentId", defaultValue = "0") Integer parentId) throws IOException {
|
||||
imageService.uploadFolderWithImages(folderZip, projectId, parentId);
|
||||
}
|
||||
|
||||
@GetMapping("/{image_id}")
|
||||
@SwaggerApiSuccess(description = "이미지를 단일 조회합니다.")
|
||||
@Operation(summary = "이미지 단일 조회", description = "이미지 정보를 단일 조회합니다.")
|
||||
|
@ -0,0 +1,12 @@
|
||||
package com.worlabel.domain.image.entity.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class FolderUploadRequest {
|
||||
|
||||
private MultipartFile folder; // 폴더를 압축 파일로 받을 수 있음 (zip 등)
|
||||
}
|
@ -3,12 +3,11 @@ package com.worlabel.domain.image.service;
|
||||
import com.worlabel.domain.folder.entity.Folder;
|
||||
import com.worlabel.domain.folder.repository.FolderRepository;
|
||||
import com.worlabel.domain.image.entity.Image;
|
||||
import com.worlabel.domain.image.entity.dto.DetailImageResponse;
|
||||
import com.worlabel.domain.image.entity.dto.ImageLabelRequest;
|
||||
import com.worlabel.domain.image.entity.dto.ImageResponse;
|
||||
import com.worlabel.domain.image.entity.dto.ImageStatusRequest;
|
||||
import com.worlabel.domain.image.entity.dto.*;
|
||||
import com.worlabel.domain.image.repository.ImageRepository;
|
||||
import com.worlabel.domain.participant.entity.PrivilegeType;
|
||||
import com.worlabel.domain.project.entity.Project;
|
||||
import com.worlabel.domain.project.repository.ProjectRepository;
|
||||
import com.worlabel.global.annotation.CheckPrivilege;
|
||||
import com.worlabel.global.exception.CustomException;
|
||||
import com.worlabel.global.exception.ErrorCode;
|
||||
@ -19,7 +18,14 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@ -30,6 +36,9 @@ public class ImageService {
|
||||
private final S3UploadService s3UploadService;
|
||||
private final ImageRepository imageRepository;
|
||||
private final FolderRepository folderRepository;
|
||||
private final ProjectRepository projectRepository;
|
||||
|
||||
private static int orderCount = 0;
|
||||
|
||||
/**
|
||||
* 이미지 리스트 업로드
|
||||
@ -40,7 +49,7 @@ public class ImageService {
|
||||
for (int order = 0; order < imageList.size(); order++) {
|
||||
MultipartFile file = imageList.get(order);
|
||||
String extension = getExtension(file);
|
||||
String imageKey = s3UploadService.upload(file,extension, projectId);
|
||||
String imageKey = s3UploadService.upload(file, extension, projectId);
|
||||
Image image = Image.of(file.getOriginalFilename(), imageKey, extension, order, folder);
|
||||
imageRepository.save(image);
|
||||
}
|
||||
@ -105,6 +114,86 @@ public class ImageService {
|
||||
save(imageId, labelRequest.getData());
|
||||
}
|
||||
|
||||
// 폴더 압축 파일을 받아 폴더와 이미지 파일을 저장하는 메서드
|
||||
public void uploadFolderWithImages(MultipartFile folderZip, Integer projectId, Integer parentId) throws IOException {
|
||||
orderCount = 0;
|
||||
// 프로젝트 정보 가져오기
|
||||
Project project = projectRepository.findById(projectId)
|
||||
.orElseThrow(() -> new CustomException(ErrorCode.PROJECT_NOT_FOUND));
|
||||
|
||||
// 압축 해제 경로 설정 (임시 폴더에 압축 해제)
|
||||
Path tempDir = Files.createTempDirectory("uploadedFolder");
|
||||
unzip(folderZip, tempDir.toString());
|
||||
|
||||
// 부모 폴더가 최상위인지 확인
|
||||
Folder parentFolder = (parentId == 0) ? null : folderRepository.findById(parentId)
|
||||
.orElseThrow(() -> new CustomException(ErrorCode.DATA_NOT_FOUND));
|
||||
|
||||
// 압축 풀린 폴더를 재귀적으로 탐색하여 하위 폴더 및 이미지 파일을 저장
|
||||
processFolderRecursively(tempDir.toFile(), parentFolder, project);
|
||||
}
|
||||
|
||||
// 폴더 내부 구조를 재귀적으로 탐색하여 저장
|
||||
private void processFolderRecursively(File directory, Folder parentFolder, Project project) {
|
||||
if (directory.exists() && directory.isDirectory()) {
|
||||
Folder currentFolder = Folder.of(directory.getName(), parentFolder, project);
|
||||
folderRepository.save(currentFolder);
|
||||
|
||||
File[] files = directory.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
// 하위 폴더인 경우 재귀 호출
|
||||
processFolderRecursively(file, currentFolder, project);
|
||||
} else if (isImageFile(file)) {
|
||||
// 이미지 파일인 경우
|
||||
String fileName = file.getName();
|
||||
String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
|
||||
|
||||
// todo MultipartFile 업 캐스팅 하면 안됨 이거 수정해야함
|
||||
String imageKey = s3UploadService.upload((MultipartFile) file, extension, project.getId());
|
||||
|
||||
Image image = Image.of(file.getName(), imageKey, extension, orderCount++, currentFolder);
|
||||
imageRepository.save(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 이미지 파일인지 확인하는 메서드
|
||||
private boolean isImageFile(File file) {
|
||||
String fileName = file.getName().toLowerCase();
|
||||
return fileName.endsWith(".jpg") || fileName.endsWith(".png") || fileName.endsWith(".jpeg");
|
||||
}
|
||||
|
||||
// 압축 파일을 임시 폴더에 압축 해제하는 메서드
|
||||
private void unzip(MultipartFile zipFile, String destDir) throws IOException {
|
||||
try (ZipInputStream zis = new ZipInputStream(zipFile.getInputStream())) {
|
||||
ZipEntry zipEntry;
|
||||
while ((zipEntry = zis.getNextEntry()) != null) {
|
||||
Path newPath = zipSlipProtect(zipEntry, Paths.get(destDir));
|
||||
if (zipEntry.isDirectory()) {
|
||||
Files.createDirectories(newPath);
|
||||
} else {
|
||||
Files.createDirectories(newPath.getParent());
|
||||
Files.copy(zis, newPath);
|
||||
}
|
||||
zis.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 보안 보호를 위해 압축 파일 경로를 보호하는 메서드
|
||||
private Path zipSlipProtect(ZipEntry zipEntry, Path targetDir) throws IOException {
|
||||
Path targetDirResolved = targetDir.resolve(zipEntry.getName());
|
||||
Path normalizePath = targetDirResolved.normalize();
|
||||
if (!normalizePath.startsWith(targetDir)) {
|
||||
throw new IOException("Zip entry is outside of the target dir: " + zipEntry.getName());
|
||||
}
|
||||
return normalizePath;
|
||||
}
|
||||
|
||||
private void save(final long imageId, final String data) {
|
||||
Image image = imageRepository.findById(imageId)
|
||||
.orElseThrow(() -> new CustomException(ErrorCode.DATA_NOT_FOUND));
|
||||
@ -117,14 +206,14 @@ public class ImageService {
|
||||
String fileName = file.getOriginalFilename();
|
||||
return fileName.substring(fileName.lastIndexOf(".") + 1); // 파일 확장자
|
||||
}
|
||||
|
||||
// 폴더 가져오기
|
||||
|
||||
private Folder getFolder(final Integer folderId) {
|
||||
return folderRepository.findById(folderId)
|
||||
.orElseThrow(() -> new CustomException(ErrorCode.DATA_NOT_FOUND));
|
||||
}
|
||||
|
||||
// 이미지 가져오면서 프로젝트 소속 여부를 확인
|
||||
|
||||
private Image getImageByIdAndFolderIdAndFolderProjectId(final Integer folderId, final Long imageId, final Integer projectId) {
|
||||
return imageRepository.findByIdAndFolderIdAndFolderProjectId(imageId, folderId, projectId)
|
||||
.orElseThrow(() -> new CustomException(ErrorCode.DATA_NOT_FOUND));
|
||||
|
Loading…
Reference in New Issue
Block a user