daniel 작업 내용 커밋
This commit is contained in:
@@ -37,6 +37,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@@ -366,4 +367,42 @@ public class ModelTrainDetailApiController {
|
||||
@Parameter(description = "모델 uuid") @PathVariable UUID uuid) {
|
||||
return ApiResponseDto.ok(modelTrainDetailService.cleanup(uuid));
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "학습 결과 ZIP 파일 전체 다운로드",
|
||||
description =
|
||||
"모델 UUID에 해당하는 모든 ZIP 파일 목록을 조회하고" + "생성된 모든 학습데이터의 ZIP 파일을 하나의 ZIP 파일로 압축하여 다운로드",
|
||||
parameters = {
|
||||
@Parameter(
|
||||
name = "kamco-download-uuid",
|
||||
in = ParameterIn.HEADER,
|
||||
required = true,
|
||||
description = "다운로드 요청 UUID",
|
||||
schema =
|
||||
@Schema(
|
||||
type = "string",
|
||||
format = "uuid",
|
||||
example = "6d8d49dc-0c9d-4124-adc7-b9ca610cc394"))
|
||||
})
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "학습데이터 zip파일 다운로드",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/octet-stream",
|
||||
schema = @Schema(type = "string", format = "binary"))),
|
||||
@ApiResponse(responseCode = "404", description = "모델 또는 파일 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@GetMapping("/downloadzip/{uuid}")
|
||||
public ResponseEntity<?> downloadZip(
|
||||
@Parameter(description = "모델 UUID") @PathVariable UUID uuid,
|
||||
@Parameter(hidden = true) @RequestHeader("kamco-download-uuid") String downloadUuid,
|
||||
HttpServletRequest request)
|
||||
throws IOException {
|
||||
|
||||
return modelTrainDetailService.downloadZipFile(uuid, downloadUuid, request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,4 +266,73 @@ public class ModelTrainDetailDto {
|
||||
private Boolean fileExistsYn;
|
||||
private String fileName;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(name = "ZipFileListResponse", description = "ZIP 파일 목록 조회 응답")
|
||||
public static class ZipFileListResponse {
|
||||
|
||||
@Schema(description = "모델 UUID", example = "2c5cdd07-5b9b-44ad-a063-8dead628b45f")
|
||||
private String modelUuid;
|
||||
|
||||
@Schema(description = "모델 번호", example = "G2")
|
||||
private String modelNo;
|
||||
|
||||
@Schema(description = "현재 모델 버전", example = "G2_000001")
|
||||
private String modelVer;
|
||||
|
||||
@Schema(
|
||||
description = "기본 경로",
|
||||
example = "/home/kcomu/data/response/2c5cdd07-5b9b-44ad-a063-8dead628b45f")
|
||||
private String basePath;
|
||||
|
||||
@Schema(description = "ZIP 파일 목록")
|
||||
private List<ZipFileInfo> zipFiles;
|
||||
|
||||
@Schema(description = "총 파일 개수", example = "3")
|
||||
private Integer totalFiles;
|
||||
|
||||
@Schema(description = "전체 파일 크기 (bytes)", example = "4048640000")
|
||||
private Long totalSize;
|
||||
|
||||
@Schema(description = "전체 파일 크기 (포맷)", example = "3.77 GB")
|
||||
private String totalSizeFormatted;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(name = "ZipFileInfo", description = "ZIP 파일 상세 정보")
|
||||
public static class ZipFileInfo {
|
||||
|
||||
@Schema(description = "파일명", example = "G2.G2_000001.{uuid}.zip")
|
||||
private String fileName;
|
||||
|
||||
@Schema(description = "파일 전체 경로")
|
||||
private String filePath;
|
||||
|
||||
@Schema(description = "모델 버전", example = "G2_000001")
|
||||
private String version;
|
||||
|
||||
@Schema(description = "파일 크기 (bytes)", example = "1349516895")
|
||||
private Long fileSize;
|
||||
|
||||
@Schema(description = "파일 크기 (포맷)", example = "1.26 GB")
|
||||
private String fileSizeFormatted;
|
||||
|
||||
@Schema(description = "최종 수정 시간", example = "2026-03-10T23:19:24.347+09:00")
|
||||
@JsonFormatDttm
|
||||
private ZonedDateTime lastModified;
|
||||
|
||||
@Schema(description = "현재 모델 버전 여부", example = "true")
|
||||
private Boolean isCurrent;
|
||||
|
||||
@Schema(description = "다운로드 URL")
|
||||
private String downloadUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.kamco.cd.training.model.service;
|
||||
|
||||
import com.kamco.cd.training.common.download.RangeDownloadResponder;
|
||||
import com.kamco.cd.training.common.enums.ModelType;
|
||||
import com.kamco.cd.training.common.enums.TrainStatusType;
|
||||
import com.kamco.cd.training.common.exception.CustomApiException;
|
||||
@@ -16,12 +17,15 @@ import com.kamco.cd.training.model.dto.ModelTrainDetailDto.ModelTrainMetrics;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainDetailDto.ModelValidationMetrics;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainDetailDto.TransferDetailDto;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainDetailDto.TransferHyperSummary;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainDetailDto.ZipFileInfo;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainDetailDto.ZipFileListResponse;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainMngDto;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainMngDto.Basic;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainMngDto.CleanupResult;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainMngDto.ModelProgressStepDto;
|
||||
import com.kamco.cd.training.postgres.core.ModelTrainDetailCoreService;
|
||||
import com.kamco.cd.training.postgres.core.ModelTrainMngCoreService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.FileVisitOption;
|
||||
@@ -32,7 +36,10 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@@ -41,6 +48,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -52,6 +60,7 @@ public class ModelTrainDetailService {
|
||||
|
||||
private final ModelTrainDetailCoreService modelTrainDetailCoreService;
|
||||
private final ModelTrainMngCoreService mngCoreService;
|
||||
private final RangeDownloadResponder rangeDownloadResponder;
|
||||
|
||||
@Value("${train.docker.response_dir}")
|
||||
private String responseDir;
|
||||
@@ -434,4 +443,363 @@ public class ModelTrainDetailService {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 모델 UUID로 ZIP 파일 목록 조회 및 다운로드 링크 생성
|
||||
*
|
||||
* @param uuid 모델 UUID
|
||||
* @param downloadUuid 다운로드 추적 UUID
|
||||
* @return ZIP 파일 목록 및 다운로드 링크
|
||||
*/
|
||||
public ZipFileListResponse getZipFileList(UUID uuid, String downloadUuid) {
|
||||
log.info("ZIP 파일 목록 조회 시작: modelUuid={}, downloadUuid={}", uuid, downloadUuid);
|
||||
|
||||
// 1. 모델 정보 조회
|
||||
Basic modelInfo;
|
||||
try {
|
||||
modelInfo = findByModelByUUID(uuid);
|
||||
if (modelInfo == null) {
|
||||
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "모델을 찾을 수 없습니다: " + uuid);
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
log.error("모델 조회 실패: {}", uuid, e);
|
||||
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "모델을 찾을 수 없습니다: " + uuid);
|
||||
}
|
||||
|
||||
// 2. 실제 디렉토리 경로 찾기 (uuid 또는 uuid-out)
|
||||
Path baseDir = findActualBasePath(uuid);
|
||||
|
||||
if (baseDir == null || !Files.exists(baseDir)) {
|
||||
log.warn(
|
||||
"디렉토리를 찾을 수 없음: modelUuid={}, 시도한 경로: {} 또는 {}-out",
|
||||
uuid,
|
||||
responseDir + "/" + uuid,
|
||||
responseDir + "/" + uuid);
|
||||
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "모델 결과 디렉토리가 존재하지 않습니다.");
|
||||
}
|
||||
|
||||
log.info("디렉토리 발견: basePath={}", baseDir.toString());
|
||||
|
||||
// 3. ZIP 파일 목록 검색
|
||||
List<ZipFileInfo> zipFiles = new ArrayList<>();
|
||||
long totalSize = 0L;
|
||||
|
||||
try (Stream<Path> stream = Files.list(baseDir)) {
|
||||
List<Path> files =
|
||||
stream
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(p -> p.getFileName().toString().endsWith(".zip"))
|
||||
.filter(p -> p.getFileName().toString().contains(uuid.toString()))
|
||||
.sorted(
|
||||
Comparator.comparing(p -> p.getFileName().toString(), Comparator.reverseOrder()))
|
||||
.toList();
|
||||
|
||||
for (Path file : files) {
|
||||
String fileName = file.getFileName().toString();
|
||||
long fileSize = Files.size(file);
|
||||
totalSize += fileSize;
|
||||
|
||||
// 파일명에서 버전 추출
|
||||
String version = extractVersionFromZipFileName(fileName);
|
||||
boolean isCurrent = version.equals(modelInfo.getModelVer());
|
||||
|
||||
// 다운로드 URL 생성
|
||||
String downloadUrl = String.format("/api/models/download/%s?file=%s", uuid, fileName);
|
||||
|
||||
zipFiles.add(
|
||||
ZipFileInfo.builder()
|
||||
.fileName(fileName)
|
||||
.filePath(file.toString())
|
||||
.version(version)
|
||||
.fileSize(fileSize)
|
||||
.fileSizeFormatted(formatFileSize(fileSize))
|
||||
.lastModified(
|
||||
Files.getLastModifiedTime(file).toInstant().atZone(ZoneId.systemDefault()))
|
||||
.isCurrent(isCurrent)
|
||||
.downloadUrl(downloadUrl)
|
||||
.build());
|
||||
}
|
||||
|
||||
log.info("ZIP 파일 {}개 발견", zipFiles.size());
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("ZIP 파일 목록 조회 실패: {}", baseDir, e);
|
||||
throw new CustomApiException(
|
||||
"INTERNAL_SERVER_ERROR", HttpStatus.INTERNAL_SERVER_ERROR, "ZIP 파일 목록 조회 실패");
|
||||
}
|
||||
|
||||
return ZipFileListResponse.builder()
|
||||
.modelUuid(uuid.toString())
|
||||
.modelNo(modelInfo.getModelNo())
|
||||
.modelVer(modelInfo.getModelVer())
|
||||
.basePath(baseDir.toString())
|
||||
.zipFiles(zipFiles)
|
||||
.totalFiles(zipFiles.size())
|
||||
.totalSize(totalSize)
|
||||
.totalSizeFormatted(formatFileSize(totalSize))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 실제 디렉토리 경로 찾기 (uuid 또는 uuid-out)
|
||||
*
|
||||
* @param uuid 모델 UUID
|
||||
* @return 실제 경로 또는 null
|
||||
*/
|
||||
private Path findActualBasePath(UUID uuid) {
|
||||
// 1순위: {uuid}-out
|
||||
Path pathWithSuffix = Paths.get(responseDir, uuid + "-out");
|
||||
if (Files.exists(pathWithSuffix) && Files.isDirectory(pathWithSuffix)) {
|
||||
log.debug("경로 발견: {} (suffix -out 포함)", pathWithSuffix);
|
||||
return pathWithSuffix;
|
||||
}
|
||||
|
||||
// 2순위: {uuid}
|
||||
Path pathWithoutSuffix = Paths.get(responseDir, uuid.toString());
|
||||
if (Files.exists(pathWithoutSuffix) && Files.isDirectory(pathWithoutSuffix)) {
|
||||
log.debug("경로 발견: {} (suffix 없음)", pathWithoutSuffix);
|
||||
return pathWithoutSuffix;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ZIP 파일명에서 버전 추출 예: G2.G2_000001.{uuid}.zip → G2_000001
|
||||
*
|
||||
* @param fileName ZIP 파일명
|
||||
* @return 모델 버전
|
||||
*/
|
||||
private String extractVersionFromZipFileName(String fileName) {
|
||||
// 패턴: {modelNo}.{modelVer}.{uuid}.zip
|
||||
// 예: G2.G2_000001.d7dd54e9-1f20-46a7-9c26-89287a7d61b0.zip
|
||||
|
||||
String[] parts = fileName.split("\\.");
|
||||
|
||||
// parts[0] = G2 (modelNo)
|
||||
// parts[1] = G2_000001 (modelVer)
|
||||
// parts[2-7] = uuid parts
|
||||
// parts[8] = zip
|
||||
|
||||
if (parts.length >= 2) {
|
||||
return parts[1]; // G2_000001
|
||||
}
|
||||
|
||||
// fallback: 전체 파일명에서 .zip 제거
|
||||
return fileName.replace(".zip", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 파일 크기를 읽기 쉬운 형식으로 변환
|
||||
*
|
||||
* @param size 파일 크기 (bytes)
|
||||
* @return 포맷된 문자열 (예: "1.26 GB")
|
||||
*/
|
||||
private String formatFileSize(long size) {
|
||||
if (size < 1024) {
|
||||
return size + " B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
return String.format("%.2f KB", size / 1024.0);
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
return String.format("%.2f MB", size / (1024.0 * 1024.0));
|
||||
} else {
|
||||
return String.format("%.2f GB", size / (1024.0 * 1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 모델 UUID로 모든 ZIP 파일 다운로드 (여러 파일이 있으면 하나의 zip으로 묶어서)
|
||||
*
|
||||
* @param uuid 모델 UUID
|
||||
* @param downloadUuid 다운로드 추적 UUID
|
||||
* @param request HTTP 요청
|
||||
* @return ZIP 파일 다운로드 응답
|
||||
*/
|
||||
public ResponseEntity<?> downloadZipFile(
|
||||
UUID uuid, String downloadUuid, HttpServletRequest request) throws IOException {
|
||||
log.info("ZIP 파일 다운로드 시작: modelUuid={}, downloadUuid={}", uuid, downloadUuid);
|
||||
|
||||
// 1. 모델 정보 조회
|
||||
Basic modelInfo;
|
||||
try {
|
||||
modelInfo = findByModelByUUID(uuid);
|
||||
if (modelInfo == null) {
|
||||
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "모델을 찾을 수 없습니다: " + uuid);
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
log.warn("모델 조회 실패: {}", uuid);
|
||||
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "모델을 찾을 수 없습니다: " + uuid);
|
||||
}
|
||||
|
||||
// 2. 실제 디렉토리 경로 찾기 (uuid 또는 uuid-out)
|
||||
Path baseDir = findActualBasePath(uuid);
|
||||
|
||||
if (baseDir == null || !Files.exists(baseDir)) {
|
||||
log.warn(
|
||||
"디렉토리를 찾을 수 없음: modelUuid={}, 시도한 경로: {} 또는 {}-out",
|
||||
uuid,
|
||||
responseDir + "/" + uuid,
|
||||
responseDir + "/" + uuid);
|
||||
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "모델 결과 디렉토리가 존재하지 않습니다.");
|
||||
}
|
||||
|
||||
log.info("디렉토리 발견: basePath={}", baseDir);
|
||||
|
||||
// 3. 모든 ZIP 파일 찾기
|
||||
List<Path> zipFiles = findAllZipFiles(baseDir, uuid);
|
||||
|
||||
if (zipFiles.isEmpty()) {
|
||||
log.warn("ZIP 파일을 찾을 수 없음: modelUuid={}, basePath={}", uuid, baseDir);
|
||||
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "ZIP 파일을 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
log.info(
|
||||
"ZIP 파일 {}개 발견: basePath={}, files={}",
|
||||
zipFiles.size(),
|
||||
baseDir,
|
||||
zipFiles.stream().map(p -> p.getFileName().toString()).toList());
|
||||
|
||||
// 4. 파일이 1개면 바로 다운로드
|
||||
if (zipFiles.size() == 1) {
|
||||
Path zipPath = zipFiles.get(0);
|
||||
log.info(
|
||||
"ZIP 파일 1개 다운로드: fileName={}, fileSize={} bytes, basePath={}",
|
||||
zipPath.getFileName(),
|
||||
Files.size(zipPath),
|
||||
baseDir);
|
||||
String downloadFileName = zipPath.getFileName().toString();
|
||||
return rangeDownloadResponder.buildZipResponse(zipPath, downloadFileName, request);
|
||||
}
|
||||
|
||||
// 5. 파일이 여러 개면 하나의 zip으로 묶어서 다운로드
|
||||
log.info("여러 ZIP 파일을 하나로 묶어서 다운로드: 총 {}개 파일", zipFiles.size());
|
||||
|
||||
String combinedZipName = modelInfo.getModelNo() + "." + modelInfo.getModelVer() + ".all.zip";
|
||||
Path tempZipPath = createCombinedZipFile(zipFiles, combinedZipName, uuid, baseDir);
|
||||
|
||||
try {
|
||||
long totalSize = Files.size(tempZipPath);
|
||||
log.info(
|
||||
"통합 ZIP 파일 생성 완료: fileName={}, fileSize={} bytes, basePath={}",
|
||||
tempZipPath.getFileName(),
|
||||
totalSize,
|
||||
baseDir);
|
||||
|
||||
ResponseEntity<?> response =
|
||||
rangeDownloadResponder.buildZipResponse(tempZipPath, combinedZipName, request);
|
||||
|
||||
// 다운로드 완료 후 임시 파일 삭제 (비동기)
|
||||
deleteTempFileAsync(tempZipPath);
|
||||
|
||||
return response;
|
||||
|
||||
} catch (IOException e) {
|
||||
log.warn("통합 ZIP 파일 처리 실패: {}", tempZipPath, e);
|
||||
// 에러 발생 시 임시 파일 삭제
|
||||
try {
|
||||
Files.deleteIfExists(tempZipPath);
|
||||
Path tempDir = tempZipPath.getParent();
|
||||
if (tempDir != null && Files.exists(tempDir)) {
|
||||
Files.deleteIfExists(tempDir);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
log.warn("임시 파일 삭제 실패: {}", tempZipPath, ex);
|
||||
}
|
||||
throw new CustomApiException(
|
||||
"INTERNAL_SERVER_ERROR", HttpStatus.INTERNAL_SERVER_ERROR, "ZIP 파일 생성 실패");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 ZIP 파일 찾기
|
||||
*
|
||||
* @param baseDir 기본 디렉토리
|
||||
* @param uuid 모델 UUID
|
||||
* @return ZIP 파일 목록 (최신순 정렬)
|
||||
*/
|
||||
private List<Path> findAllZipFiles(Path baseDir, UUID uuid) {
|
||||
try (Stream<Path> stream = Files.list(baseDir)) {
|
||||
return stream
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(p -> p.getFileName().toString().endsWith(".zip"))
|
||||
.filter(p -> p.getFileName().toString().contains(uuid.toString()))
|
||||
.sorted(
|
||||
Comparator.comparing(
|
||||
p -> {
|
||||
try {
|
||||
return Files.getLastModifiedTime(p);
|
||||
} catch (IOException e) {
|
||||
return FileTime.fromMillis(0);
|
||||
}
|
||||
},
|
||||
Comparator.reverseOrder()))
|
||||
.toList();
|
||||
} catch (IOException e) {
|
||||
log.warn("ZIP 파일 검색 실패: basePath={}", baseDir, e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 여러 ZIP 파일을 하나의 ZIP으로 묶기
|
||||
*
|
||||
* @param zipFiles 원본 ZIP 파일 목록
|
||||
* @param combinedZipName 통합 ZIP 파일명
|
||||
* @param uuid 모델 UUID
|
||||
* @param baseDir 기본 디렉토리 (로그용)
|
||||
* @return 통합 ZIP 파일 경로
|
||||
*/
|
||||
private Path createCombinedZipFile(
|
||||
List<Path> zipFiles, String combinedZipName, UUID uuid, Path baseDir) throws IOException {
|
||||
// 임시 디렉토리에 통합 ZIP 생성
|
||||
Path tempDir = Files.createTempDirectory("kamco-download-" + uuid);
|
||||
Path combinedZipPath = tempDir.resolve(combinedZipName);
|
||||
|
||||
log.debug("통합 ZIP 생성 시작: tempPath={}, 원본 파일 {}개", combinedZipPath, zipFiles.size());
|
||||
|
||||
try (java.util.zip.ZipOutputStream zos =
|
||||
new java.util.zip.ZipOutputStream(Files.newOutputStream(combinedZipPath))) {
|
||||
|
||||
for (Path zipFile : zipFiles) {
|
||||
String entryName = zipFile.getFileName().toString();
|
||||
log.debug("ZIP에 파일 추가: {}", entryName);
|
||||
|
||||
java.util.zip.ZipEntry entry = new java.util.zip.ZipEntry(entryName);
|
||||
zos.putNextEntry(entry);
|
||||
|
||||
Files.copy(zipFile, zos);
|
||||
zos.closeEntry();
|
||||
}
|
||||
|
||||
zos.finish();
|
||||
}
|
||||
|
||||
log.debug("통합 ZIP 생성 완료: {}", combinedZipPath);
|
||||
return combinedZipPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 임시 파일 비동기 삭제
|
||||
*
|
||||
* @param tempFile 삭제할 임시 파일
|
||||
*/
|
||||
private void deleteTempFileAsync(Path tempFile) {
|
||||
new Thread(
|
||||
() -> {
|
||||
try {
|
||||
// 다운로드 완료 후 대기
|
||||
Thread.sleep(10000); // 10초 대기
|
||||
Files.deleteIfExists(tempFile);
|
||||
// 임시 디렉토리도 삭제
|
||||
Path tempDir = tempFile.getParent();
|
||||
if (tempDir != null && Files.exists(tempDir)) {
|
||||
Files.deleteIfExists(tempDir);
|
||||
}
|
||||
log.debug("임시 파일 삭제 완료: {}", tempFile);
|
||||
} catch (Exception e) {
|
||||
log.warn("임시 파일 삭제 실패: {}", tempFile, e);
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user