[파일 업로드 개선] FileUploadResponseDto 및 MultipleFileUploadResponseDto에 groupOid 필드 추가, FileServiceImpl에서 응답 DTO 생성 로직 수정 및 createUploadResponse 메서드 제거. FileUtils 클래스에 파일 업로드 메서드 추가 및 불필요한 메서드 제거로 코드 정리.
This commit is contained in:
@@ -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<FileUploadResponseDto> files; // 파일 정보들
|
||||||
|
private int totalCount;
|
||||||
|
private int successCount;
|
||||||
|
private int failureCount;
|
||||||
|
private List<String> errorMessages;
|
||||||
|
}
|
@@ -1,12 +1,15 @@
|
|||||||
package com.bio.bio_backend.domain.base.file.dto;
|
package com.bio.bio_backend.domain.base.file.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
public class FileUploadResponseDto {
|
public class FileUploadResponseDto {
|
||||||
private Long oid;
|
private Long oid;
|
||||||
|
private Long groupOid;
|
||||||
private String originalFileName;
|
private String originalFileName;
|
||||||
private String downloadUrl;
|
private String downloadUrl;
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
package com.bio.bio_backend.domain.base.file.dto;
|
package com.bio.bio_backend.domain.base.file.dto;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@Builder
|
||||||
public class MultipleFileUploadRequestDto {
|
public class MultipleFileUploadRequestDto {
|
||||||
private List<MultipartFile> files;
|
private List<MultipartFile> files;
|
||||||
private String description;
|
private String description;
|
||||||
|
@@ -8,6 +8,7 @@ import java.util.List;
|
|||||||
@Builder
|
@Builder
|
||||||
public class MultipleFileUploadResponseDto {
|
public class MultipleFileUploadResponseDto {
|
||||||
private List<FileUploadResponseDto> files;
|
private List<FileUploadResponseDto> files;
|
||||||
|
private Long groupOid;
|
||||||
private int totalCount;
|
private int totalCount;
|
||||||
private int successCount;
|
private int successCount;
|
||||||
private int failureCount;
|
private int failureCount;
|
||||||
|
@@ -57,7 +57,12 @@ public class FileServiceImpl implements FileService {
|
|||||||
File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), generateOid());
|
File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), generateOid());
|
||||||
|
|
||||||
// 응답 DTO 생성 및 반환
|
// 응답 DTO 생성 및 반환
|
||||||
return createUploadResponse(savedFile);
|
return FileUploadResponseDto.builder()
|
||||||
|
.oid(savedFile.getOid())
|
||||||
|
.groupOid(savedFile.getGroupOid())
|
||||||
|
.originalFileName(savedFile.getOriginalFileName())
|
||||||
|
.downloadUrl(contextPath + "/files/download/" + savedFile.getOid())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -90,7 +95,11 @@ public class FileServiceImpl implements FileService {
|
|||||||
|
|
||||||
// 단일 파일 업로드 처리
|
// 단일 파일 업로드 처리
|
||||||
File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), groupOid);
|
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);
|
uploadedFiles.add(uploadedFile);
|
||||||
successCount++;
|
successCount++;
|
||||||
@@ -105,8 +114,8 @@ public class FileServiceImpl implements FileService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 다중 파일 업로드 결과 반환
|
|
||||||
return MultipleFileUploadResponseDto.builder()
|
return MultipleFileUploadResponseDto.builder()
|
||||||
|
.groupOid(groupOid)
|
||||||
.files(uploadedFiles)
|
.files(uploadedFiles)
|
||||||
.totalCount(files.size())
|
.totalCount(files.size())
|
||||||
.successCount(successCount)
|
.successCount(successCount)
|
||||||
@@ -158,14 +167,6 @@ public class FileServiceImpl implements FileService {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileUploadResponseDto createUploadResponse(File savedFile) {
|
|
||||||
return FileUploadResponseDto.builder()
|
|
||||||
.oid(savedFile.getOid())
|
|
||||||
.originalFileName(savedFile.getOriginalFileName())
|
|
||||||
.downloadUrl(contextPath + "/files/download/" + savedFile.getOid())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File getFileByOid(Long oid) {
|
public File getFileByOid(Long oid) {
|
||||||
return fileRepository.findByOidAndUseFlagTrue(oid)
|
return fileRepository.findByOidAndUseFlagTrue(oid)
|
||||||
|
@@ -2,21 +2,32 @@ package com.bio.bio_backend.global.utils;
|
|||||||
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.UUID;
|
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 {
|
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/
|
* 예: uploads/2024/01/
|
||||||
@@ -76,35 +61,6 @@ public class FileUtils {
|
|||||||
return yearMonthPath;
|
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 {
|
public static Path saveFileToDisk(MultipartFile multipartFile, Path uploadDir, String storedFileName) throws IOException {
|
||||||
Path targetLocation = uploadDir.resolve(storedFileName);
|
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;
|
return targetLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,173 +94,44 @@ public class FileUtils {
|
|||||||
return StringUtils.cleanPath(originalFileName);
|
return StringUtils.cleanPath(originalFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 파일 크기를 사람이 읽기 쉬운 형태로 변환
|
|
||||||
*/
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 파일 확장자로부터 MIME 타입 추정
|
* 파일 업로드 (단일/다중 파일 모두 지원)
|
||||||
|
* 사용 예시:
|
||||||
|
* // 단일 파일 업로드
|
||||||
|
* 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 getMimeTypeFromExtension(String fileName) {
|
public FileUploadDto uploadFile(MultipartFile file, String description) {
|
||||||
if (fileName == null) return "application/octet-stream";
|
// 단일 파일도 List로 감싸서 다중 파일 업로드 방식 사용
|
||||||
|
List<MultipartFile> files = List.of(file);
|
||||||
String extension = extractFileExtension(fileName).toLowerCase();
|
return uploadFiles(files, description);
|
||||||
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 FileUploadDto uploadFiles(List<MultipartFile> files, String description) {
|
||||||
* 안전한 파일명 생성 (특수문자 제거)
|
MultipleFileUploadRequestDto requestDto = MultipleFileUploadRequestDto.builder()
|
||||||
*/
|
.files(files)
|
||||||
public static String createSafeFileName(String originalFileName) {
|
.description(description)
|
||||||
if (originalFileName == null) return "";
|
.build();
|
||||||
|
|
||||||
// 특수문자 제거 및 공백을 언더스코어로 변경
|
MultipleFileUploadResponseDto response = fileService.uploadMultipleFiles(requestDto);
|
||||||
String safeName = originalFileName
|
|
||||||
.replaceAll("[^a-zA-Z0-9가-힣._-]", "_")
|
|
||||||
.replaceAll("_+", "_")
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
// 파일명이 너무 길면 자르기
|
return FileUploadDto.builder()
|
||||||
if (safeName.length() > 100) {
|
.groupOid(response.getGroupOid())
|
||||||
String extension = extractFileExtension(safeName);
|
.files(response.getFiles())
|
||||||
safeName = safeName.substring(0, 100 - extension.length()) + extension;
|
.totalCount(response.getTotalCount())
|
||||||
}
|
.successCount(response.getSuccessCount())
|
||||||
|
.failureCount(response.getFailureCount())
|
||||||
return safeName;
|
.errorMessages(response.getErrorMessages())
|
||||||
}
|
.build();
|
||||||
|
|
||||||
/**
|
|
||||||
* 파일이 이미지인지 확인
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user