Feat: 이미지 폴더채로 업로드 구현 - S11P21S002-182
This commit is contained in:
parent
ae2a55434f
commit
7ad391741c
@ -1,5 +1,6 @@
|
|||||||
package com.worlabel.domain.image.controller;
|
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.entity.dto.*;
|
||||||
import com.worlabel.domain.image.service.ImageService;
|
import com.worlabel.domain.image.service.ImageService;
|
||||||
import com.worlabel.global.annotation.CurrentUser;
|
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.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -37,6 +39,17 @@ public class ImageController {
|
|||||||
imageService.uploadImageList(imageList, folderId, projectId, memberId);
|
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}")
|
@GetMapping("/{image_id}")
|
||||||
@SwaggerApiSuccess(description = "이미지를 단일 조회합니다.")
|
@SwaggerApiSuccess(description = "이미지를 단일 조회합니다.")
|
||||||
@Operation(summary = "이미지 단일 조회", 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.entity.Folder;
|
||||||
import com.worlabel.domain.folder.repository.FolderRepository;
|
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.dto.DetailImageResponse;
|
import com.worlabel.domain.image.entity.dto.*;
|
||||||
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.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.repository.ProjectRepository;
|
||||||
import com.worlabel.global.annotation.CheckPrivilege;
|
import com.worlabel.global.annotation.CheckPrivilege;
|
||||||
import com.worlabel.global.exception.CustomException;
|
import com.worlabel.global.exception.CustomException;
|
||||||
import com.worlabel.global.exception.ErrorCode;
|
import com.worlabel.global.exception.ErrorCode;
|
||||||
@ -19,7 +18,14 @@ import org.springframework.stereotype.Service;
|
|||||||
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.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.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@ -30,6 +36,9 @@ public class ImageService {
|
|||||||
private final S3UploadService s3UploadService;
|
private final S3UploadService s3UploadService;
|
||||||
private final ImageRepository imageRepository;
|
private final ImageRepository imageRepository;
|
||||||
private final FolderRepository folderRepository;
|
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++) {
|
for (int order = 0; order < imageList.size(); order++) {
|
||||||
MultipartFile file = imageList.get(order);
|
MultipartFile file = imageList.get(order);
|
||||||
String extension = getExtension(file);
|
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);
|
Image image = Image.of(file.getOriginalFilename(), imageKey, extension, order, folder);
|
||||||
imageRepository.save(image);
|
imageRepository.save(image);
|
||||||
}
|
}
|
||||||
@ -105,6 +114,86 @@ public class ImageService {
|
|||||||
save(imageId, labelRequest.getData());
|
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) {
|
private void save(final long imageId, final String data) {
|
||||||
Image image = imageRepository.findById(imageId)
|
Image image = imageRepository.findById(imageId)
|
||||||
.orElseThrow(() -> new CustomException(ErrorCode.DATA_NOT_FOUND));
|
.orElseThrow(() -> new CustomException(ErrorCode.DATA_NOT_FOUND));
|
||||||
@ -117,14 +206,14 @@ public class ImageService {
|
|||||||
String fileName = file.getOriginalFilename();
|
String fileName = file.getOriginalFilename();
|
||||||
return fileName.substring(fileName.lastIndexOf(".") + 1); // 파일 확장자
|
return fileName.substring(fileName.lastIndexOf(".") + 1); // 파일 확장자
|
||||||
}
|
}
|
||||||
|
|
||||||
// 폴더 가져오기
|
// 폴더 가져오기
|
||||||
|
|
||||||
private Folder getFolder(final Integer folderId) {
|
private Folder getFolder(final Integer folderId) {
|
||||||
return folderRepository.findById(folderId)
|
return folderRepository.findById(folderId)
|
||||||
.orElseThrow(() -> new CustomException(ErrorCode.DATA_NOT_FOUND));
|
.orElseThrow(() -> new CustomException(ErrorCode.DATA_NOT_FOUND));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 이미지 가져오면서 프로젝트 소속 여부를 확인
|
// 이미지 가져오면서 프로젝트 소속 여부를 확인
|
||||||
|
|
||||||
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