From 12aa3ae5a3779d36159f736fc446689a30e2d290 Mon Sep 17 00:00:00 2001 From: sohot8653 Date: Tue, 26 Aug 2025 13:31:03 +0900 Subject: [PATCH] =?UTF-8?q?[=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EA=B0=9C=EC=84=A0]=20FileUploadResponseDto=20?= =?UTF-8?q?=EB=B0=8F=20MultipleFileUploadResponseDto=EC=97=90=20groupOid?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80,=20FileServiceImpl?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=9D=91=EB=8B=B5=20DTO=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20=EB=B0=8F?= =?UTF-8?q?=20createUploadResponse=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0.=20FileUtils=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EC=97=90=20=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=A0=9C=EA=B1=B0=EB=A1=9C=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/base/file/dto/FileUploadDto.java | 16 ++ .../base/file/dto/FileUploadResponseDto.java | 3 + .../dto/MultipleFileUploadRequestDto.java | 2 + .../dto/MultipleFileUploadResponseDto.java | 1 + .../base/file/service/FileServiceImpl.java | 23 +- .../bio_backend/global/utils/FileUtils.java | 267 +++--------------- 6 files changed, 81 insertions(+), 231 deletions(-) create mode 100644 src/main/java/com/bio/bio_backend/domain/base/file/dto/FileUploadDto.java diff --git a/src/main/java/com/bio/bio_backend/domain/base/file/dto/FileUploadDto.java b/src/main/java/com/bio/bio_backend/domain/base/file/dto/FileUploadDto.java new file mode 100644 index 0000000..ac202b1 --- /dev/null +++ b/src/main/java/com/bio/bio_backend/domain/base/file/dto/FileUploadDto.java @@ -0,0 +1,16 @@ +package com.bio.bio_backend.domain.base.file.dto; + +import lombok.Builder; +import lombok.Data; +import java.util.List; + +@Data +@Builder +public class FileUploadDto { + private Long groupOid; + private List files; // 파일 정보들 + private int totalCount; + private int successCount; + private int failureCount; + private List errorMessages; +} diff --git a/src/main/java/com/bio/bio_backend/domain/base/file/dto/FileUploadResponseDto.java b/src/main/java/com/bio/bio_backend/domain/base/file/dto/FileUploadResponseDto.java index 56162fd..f7fe9e1 100644 --- a/src/main/java/com/bio/bio_backend/domain/base/file/dto/FileUploadResponseDto.java +++ b/src/main/java/com/bio/bio_backend/domain/base/file/dto/FileUploadResponseDto.java @@ -1,12 +1,15 @@ package com.bio.bio_backend.domain.base.file.dto; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; import lombok.Data; @Data @Builder +@JsonInclude(JsonInclude.Include.NON_NULL) public class FileUploadResponseDto { private Long oid; + private Long groupOid; private String originalFileName; private String downloadUrl; } diff --git a/src/main/java/com/bio/bio_backend/domain/base/file/dto/MultipleFileUploadRequestDto.java b/src/main/java/com/bio/bio_backend/domain/base/file/dto/MultipleFileUploadRequestDto.java index 91d668f..d86cba8 100644 --- a/src/main/java/com/bio/bio_backend/domain/base/file/dto/MultipleFileUploadRequestDto.java +++ b/src/main/java/com/bio/bio_backend/domain/base/file/dto/MultipleFileUploadRequestDto.java @@ -1,10 +1,12 @@ package com.bio.bio_backend.domain.base.file.dto; +import lombok.Builder; import lombok.Data; import org.springframework.web.multipart.MultipartFile; import java.util.List; @Data +@Builder public class MultipleFileUploadRequestDto { private List files; private String description; diff --git a/src/main/java/com/bio/bio_backend/domain/base/file/dto/MultipleFileUploadResponseDto.java b/src/main/java/com/bio/bio_backend/domain/base/file/dto/MultipleFileUploadResponseDto.java index c89529f..f6792c0 100644 --- a/src/main/java/com/bio/bio_backend/domain/base/file/dto/MultipleFileUploadResponseDto.java +++ b/src/main/java/com/bio/bio_backend/domain/base/file/dto/MultipleFileUploadResponseDto.java @@ -8,6 +8,7 @@ import java.util.List; @Builder public class MultipleFileUploadResponseDto { private List files; + private Long groupOid; private int totalCount; private int successCount; private int failureCount; diff --git a/src/main/java/com/bio/bio_backend/domain/base/file/service/FileServiceImpl.java b/src/main/java/com/bio/bio_backend/domain/base/file/service/FileServiceImpl.java index 2778f65..99b1af7 100644 --- a/src/main/java/com/bio/bio_backend/domain/base/file/service/FileServiceImpl.java +++ b/src/main/java/com/bio/bio_backend/domain/base/file/service/FileServiceImpl.java @@ -57,7 +57,12 @@ public class FileServiceImpl implements FileService { File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), generateOid()); // 응답 DTO 생성 및 반환 - return createUploadResponse(savedFile); + return FileUploadResponseDto.builder() + .oid(savedFile.getOid()) + .groupOid(savedFile.getGroupOid()) + .originalFileName(savedFile.getOriginalFileName()) + .downloadUrl(contextPath + "/files/download/" + savedFile.getOid()) + .build(); } @Override @@ -90,7 +95,11 @@ public class FileServiceImpl implements FileService { // 단일 파일 업로드 처리 File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), groupOid); - FileUploadResponseDto uploadedFile = createUploadResponse(savedFile); + FileUploadResponseDto uploadedFile = FileUploadResponseDto.builder() + .oid(savedFile.getOid()) + .originalFileName(savedFile.getOriginalFileName()) + .downloadUrl(contextPath + "/files/download/" + savedFile.getOid()) + .build(); uploadedFiles.add(uploadedFile); successCount++; @@ -105,8 +114,8 @@ public class FileServiceImpl implements FileService { } } - // 다중 파일 업로드 결과 반환 return MultipleFileUploadResponseDto.builder() + .groupOid(groupOid) .files(uploadedFiles) .totalCount(files.size()) .successCount(successCount) @@ -157,14 +166,6 @@ public class FileServiceImpl implements FileService { .groupOid(groupOid) .build(); } - - private FileUploadResponseDto createUploadResponse(File savedFile) { - return FileUploadResponseDto.builder() - .oid(savedFile.getOid()) - .originalFileName(savedFile.getOriginalFileName()) - .downloadUrl(contextPath + "/files/download/" + savedFile.getOid()) - .build(); - } @Override public File getFileByOid(Long oid) { diff --git a/src/main/java/com/bio/bio_backend/global/utils/FileUtils.java b/src/main/java/com/bio/bio_backend/global/utils/FileUtils.java index ae0bbf9..ec06f9f 100644 --- a/src/main/java/com/bio/bio_backend/global/utils/FileUtils.java +++ b/src/main/java/com/bio/bio_backend/global/utils/FileUtils.java @@ -2,21 +2,32 @@ package com.bio.bio_backend.global.utils; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.UUID; +import org.springframework.stereotype.Component; +import lombok.RequiredArgsConstructor; +import com.bio.bio_backend.domain.base.file.dto.FileUploadDto; +import com.bio.bio_backend.domain.base.file.dto.FileUploadResponseDto; +import com.bio.bio_backend.domain.base.file.dto.MultipleFileUploadRequestDto; +import com.bio.bio_backend.domain.base.file.dto.MultipleFileUploadResponseDto; +import com.bio.bio_backend.domain.base.file.service.FileService; +import java.util.List; +import com.bio.bio_backend.domain.base.file.dto.FileUploadRequestDto; /** * 파일 관련 유틸리티 클래스 */ +@Component +@RequiredArgsConstructor public class FileUtils { + private final FileService fileService; + /** * 파일 유효성 검사 */ @@ -35,32 +46,6 @@ public class FileUtils { } } - /** - * 업로드 디렉토리 생성 - */ - public static Path createUploadDirectory(String uploadPath) throws IOException { - Path uploadDir = Paths.get(uploadPath); - if (!Files.exists(uploadDir)) { - Files.createDirectories(uploadDir); - } - return uploadDir; - } - - /** - * 년월일 기반 업로드 디렉토리 생성 - * 예: uploads/2024/01/15/ - */ - public static Path createDateBasedUploadDirectory(String baseUploadPath) throws IOException { - LocalDate today = LocalDate.now(); - String yearMonthDay = today.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); - - Path dateBasedPath = Paths.get(baseUploadPath, yearMonthDay); - if (!Files.exists(dateBasedPath)) { - Files.createDirectories(dateBasedPath); - } - return dateBasedPath; - } - /** * 년월 기반 업로드 디렉토리 생성 * 예: uploads/2024/01/ @@ -76,35 +61,6 @@ public class FileUtils { return yearMonthPath; } - /** - * 년 기반 업로드 디렉토리 생성 - * 예: uploads/2024/ - */ - public static Path createYearUploadDirectory(String baseUploadPath) throws IOException { - LocalDate today = LocalDate.now(); - String year = today.format(DateTimeFormatter.ofPattern("yyyy")); - - Path yearPath = Paths.get(baseUploadPath, year); - if (!Files.exists(yearPath)) { - Files.createDirectories(yearPath); - } - return yearPath; - } - - /** - * 지정된 날짜로 업로드 디렉토리 생성 - * 예: uploads/2024/01/15/ - */ - public static Path createDateBasedUploadDirectory(String baseUploadPath, LocalDate date) throws IOException { - String yearMonthDay = date.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); - - Path dateBasedPath = Paths.get(baseUploadPath, yearMonthDay); - if (!Files.exists(dateBasedPath)) { - Files.createDirectories(dateBasedPath); - } - return dateBasedPath; - } - /** * 파일 확장자 추출 */ @@ -127,7 +83,7 @@ public class FileUtils { */ public static Path saveFileToDisk(MultipartFile multipartFile, Path uploadDir, String storedFileName) throws IOException { Path targetLocation = uploadDir.resolve(storedFileName); - Files.copy(multipartFile.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING); + Files.copy(multipartFile.getInputStream(), targetLocation, java.nio.file.StandardCopyOption.REPLACE_EXISTING); return targetLocation; } @@ -137,174 +93,45 @@ public class FileUtils { public static String cleanFileName(String originalFileName) { return StringUtils.cleanPath(originalFileName); } + /** - * 파일 크기를 사람이 읽기 쉬운 형태로 변환 + * 파일 업로드 (단일/다중 파일 모두 지원) + * 사용 예시: + * // 단일 파일 업로드 + * FileUploadDto fileResult = fileUtils.uploadFile( + * requestDto.getFile(), + * "프로필 이미지" + * ); + * member.setFileGroupId(fileResult.getGroupOid()); + * // 다중 파일 업로드 + * FileUploadDto filesResult = fileUtils.uploadFiles( + * requestDto.getFiles(), + * "게시판 첨부파일: " + board.getTitle() + * ); + * board.setFileGroupId(filesResult.getGroupOid()); */ - public static String formatFileSize(long bytes) { - if (bytes < 1024) return bytes + " B"; - if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0); - if (bytes < 1024 * 1024 * 1024) return String.format("%.1f MB", bytes / (1024.0 * 1024.0)); - return String.format("%.1f GB", bytes / (1024.0 * 1024.0 * 1024.0)); + public FileUploadDto uploadFile(MultipartFile file, String description) { + // 단일 파일도 List로 감싸서 다중 파일 업로드 방식 사용 + List files = List.of(file); + return uploadFiles(files, description); } - /** - * 파일 확장자로부터 MIME 타입 추정 - */ - public static String getMimeTypeFromExtension(String fileName) { - if (fileName == null) return "application/octet-stream"; + public FileUploadDto uploadFiles(List files, String description) { + MultipleFileUploadRequestDto requestDto = MultipleFileUploadRequestDto.builder() + .files(files) + .description(description) + .build(); - String extension = extractFileExtension(fileName).toLowerCase(); - switch (extension) { - case ".txt": return "text/plain"; - case ".html": case ".htm": return "text/html"; - case ".css": return "text/css"; - case ".js": return "application/javascript"; - case ".json": return "application/json"; - case ".xml": return "application/xml"; - case ".pdf": return "application/pdf"; - case ".zip": return "application/zip"; - case ".jpg": case ".jpeg": return "image/jpeg"; - case ".png": return "image/png"; - case ".gif": return "image/gif"; - case ".bmp": return "image/bmp"; - case ".svg": return "image/svg+xml"; - case ".mp4": return "video/mp4"; - case ".avi": return "video/x-msvideo"; - case ".mp3": return "audio/mpeg"; - case ".wav": return "audio/wav"; - default: return "application/octet-stream"; - } - } - - /** - * 안전한 파일명 생성 (특수문자 제거) - */ - public static String createSafeFileName(String originalFileName) { - if (originalFileName == null) return ""; + MultipleFileUploadResponseDto response = fileService.uploadMultipleFiles(requestDto); - // 특수문자 제거 및 공백을 언더스코어로 변경 - String safeName = originalFileName - .replaceAll("[^a-zA-Z0-9가-힣._-]", "_") - .replaceAll("_+", "_") - .trim(); - - // 파일명이 너무 길면 자르기 - if (safeName.length() > 100) { - String extension = extractFileExtension(safeName); - safeName = safeName.substring(0, 100 - extension.length()) + extension; - } - - return safeName; - } - - /** - * 파일이 이미지인지 확인 - */ - public static boolean isImageFile(String fileName) { - if (fileName == null) return false; - - String extension = extractFileExtension(fileName).toLowerCase(); - return extension.matches("\\.(jpg|jpeg|png|gif|bmp|svg|webp)$"); - } - - /** - * 파일이 문서인지 확인 - */ - public static boolean isDocumentFile(String fileName) { - if (fileName == null) return false; - - String extension = extractFileExtension(fileName).toLowerCase(); - return extension.matches("\\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|rtf)$"); - } - - /** - * 파일이 압축파일인지 확인 - */ - public static boolean isArchiveFile(String fileName) { - if (fileName == null) return false; - - String extension = extractFileExtension(fileName).toLowerCase(); - return extension.matches("\\.(zip|rar|7z|tar|gz|bz2)$"); - } - - /** - * 현재 날짜의 년월일 문자열 반환 - * 예: "2024/01/15" - */ - public static String getCurrentDatePath() { - LocalDate today = LocalDate.now(); - return today.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); - } - - /** - * 지정된 날짜의 년월일 문자열 반환 - * 예: "2024/01/15" - */ - public static String getDatePath(LocalDate date) { - return date.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); - } - - /** - * 파일 경로에서 년월일 정보 추출 - * 예: "uploads/2024/01/15/file.txt" -> "2024/01/15" - */ - public static String extractDateFromPath(String filePath) { - if (filePath == null || filePath.isEmpty()) { - return ""; - } - - // 정규식으로 년/월/일 패턴 찾기 - java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("(\\d{4}/\\d{2}/\\d{2})"); - java.util.regex.Matcher matcher = pattern.matcher(filePath); - - if (matcher.find()) { - return matcher.group(1); - } - - return ""; - } - - /** - * 년월일 폴더 구조가 유효한지 확인 - * 예: "2024/01/15" -> true, "2024/13/45" -> false - */ - public static boolean isValidDatePath(String datePath) { - if (datePath == null || datePath.isEmpty()) { - return false; - } - - try { - String[] parts = datePath.split("/"); - if (parts.length != 3) { - return false; - } - - int year = Integer.parseInt(parts[0]); - int month = Integer.parseInt(parts[1]); - int day = Integer.parseInt(parts[2]); - - // 년도 범위 체크 (1900 ~ 2100) - if (year < 1900 || year > 2100) { - return false; - } - - // 월 범위 체크 (1 ~ 12) - if (month < 1 || month > 12) { - return false; - } - - // 일 범위 체크 (1 ~ 31) - if (day < 1 || day > 31) { - return false; - } - - // 실제 존재하는 날짜인지 확인 - LocalDate.of(year, month, day); - return true; - - } catch (Exception e) { - return false; - } + return FileUploadDto.builder() + .groupOid(response.getGroupOid()) + .files(response.getFiles()) + .totalCount(response.getTotalCount()) + .successCount(response.getSuccessCount()) + .failureCount(response.getFailureCount()) + .errorMessages(response.getErrorMessages()) + .build(); } }