diff --git a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java index deb44cfd..963396d5 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.coyote.BadRequestException; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.http.MediaType; @@ -366,7 +367,7 @@ public class LabelAllocateApiController { @Schema( type = "string", format = "uuid", - example = "69c4e56c-e0bf-4742-9225-bba9aae39052")) + example = "6d8d49dc-0c9d-4124-adc7-b9ca610cc394")) }) @ApiResponses( value = { @@ -382,9 +383,13 @@ public class LabelAllocateApiController { }) @GetMapping("/download/{uuid}") public ResponseEntity download( - @Parameter(example = "69c4e56c-e0bf-4742-9225-bba9aae39052") @PathVariable UUID uuid) + @Parameter(example = "6d8d49dc-0c9d-4124-adc7-b9ca610cc394") @PathVariable UUID uuid) throws IOException { + if (!labelAllocateService.isDownloadable(uuid)) { + throw new BadRequestException(); + } + String uid = labelAllocateService.findLearnUid(uuid); Path zipPath = Paths.get(responsePath).resolve(uid + ".zip"); @@ -407,7 +412,7 @@ public class LabelAllocateApiController { @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) public ApiResponseDto> downloadAudit( - @Parameter(description = "UUID", example = "69c4e56c-e0bf-4742-9225-bba9aae39052") + @Parameter(description = "UUID", example = "6d8d49dc-0c9d-4124-adc7-b9ca610cc394") @PathVariable UUID uuid, // @Parameter(description = "다운로드일 시작", example = "2025-01-01") @RequestParam(required = @@ -435,4 +440,25 @@ public class LabelAllocateApiController { return ApiResponseDto.ok(labelAllocateService.getDownloadAudit(searchReq, downloadReq)); } + + @Operation(summary = "다운로드 가능여부 조회", description = "다운로드 가능여부 조회 API") + @GetMapping(value = "/download-check/{uuid}") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "검색 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Page.class))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + public ApiResponseDto isDownloadable( + @Parameter(description = "UUID", example = "6d8d49dc-0c9d-4124-adc7-b9ca610cc394") + @PathVariable + UUID uuid) { + return ApiResponseDto.ok(labelAllocateService.isDownloadable(uuid)); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java index f07f34ca..2a5e4c97 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java @@ -359,4 +359,15 @@ public class LabelAllocateDto { @Schema(description = "작업기간 종료일") private ZonedDateTime projectCloseDttm; } + + @Getter + @Setter + @AllArgsConstructor + @NoArgsConstructor + public static class InferenceLearnDto { + private UUID analUuid; + private String learnUid; + private String analState; + private Long analId; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java index 067d562d..25f9d5ad 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java @@ -290,4 +290,14 @@ public class LabelAllocateService { AuditLogDto.searchReq searchReq, DownloadReq downloadReq) { return auditLogCoreService.findLogByAccount(searchReq, downloadReq); } + + /** + * 다운로드 가능 여부 조회 + * + * @param uuid + * @return + */ + public boolean isDownloadable(UUID uuid) { + return labelAllocateCoreService.isDownloadable(uuid); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java index 21c0dd10..482e991e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java @@ -4,6 +4,8 @@ import com.kamco.cd.kamcoback.common.exception.CustomApiException; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceLearnDto; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveInfo; @@ -14,11 +16,16 @@ import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.ProjectInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; +import com.kamco.cd.kamcoback.postgres.repository.batch.BatchStepHistoryRepository; import com.kamco.cd.kamcoback.postgres.repository.label.LabelAllocateRepository; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.LocalDate; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -28,6 +35,10 @@ import org.springframework.stereotype.Service; public class LabelAllocateCoreService { private final LabelAllocateRepository labelAllocateRepository; + private final BatchStepHistoryRepository batchStepHistoryRepository; + + @Value("${file.dataset-response}") + private String responsePath; public List fetchNextIds(Long lastId, Long batchSize, UUID uuid) { return labelAllocateRepository.fetchNextIds(lastId, batchSize, uuid); @@ -237,6 +248,30 @@ public class LabelAllocateCoreService { return labelAllocateRepository.findLabelingIngProcessCnt(); } + public boolean isDownloadable(UUID uuid) { + InferenceLearnDto dto = labelAllocateRepository.findLabelingIngProcessId(uuid); + + if (dto == null) { + return false; + } + + // 파일이 있는지만 확인 + Path path = Paths.get(responsePath).resolve(dto.getLearnUid() + ".zip"); + + if (!Files.exists(path) || !Files.isRegularFile(path)) { + // 실제 파일만 true (디렉터리는 제외) + return false; + } + + // 다운로드 확인할 학습데이터가 라벨링중인 경우 파일 생성여부가 정상인지 확인 + if (dto.getAnalState().equals(LabelMngState.ASSIGNED.getId()) + || dto.getAnalState().equals(LabelMngState.ING.getId())) { + return batchStepHistoryRepository.isDownloadable(dto.getAnalId()); + } + + return true; + } + public String findLearnUid(UUID uuid) { return labelAllocateRepository .findLearnUid(uuid) diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/BatchStepHistoryEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/BatchStepHistoryEntity.java new file mode 100644 index 00000000..77a3f4fe --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/BatchStepHistoryEntity.java @@ -0,0 +1,62 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Table(name = "batch_step_history") +public class BatchStepHistoryEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @NotNull + @Column(name = "anal_uid", nullable = false) + private Long analUid; + + @Size(max = 255) + @NotNull + @Column(name = "result_uid", nullable = false) + private String resultUid; + + @Size(max = 100) + @NotNull + @Column(name = "step_name", nullable = false, length = 100) + private String stepName; + + @Size(max = 50) + @NotNull + @Column(name = "status", nullable = false, length = 50) + private String status; + + @Column(name = "error_message", length = Integer.MAX_VALUE) + private String errorMessage; + + @NotNull + @Column(name = "started_dttm", nullable = false) + private LocalDateTime startedDttm; + + @Column(name = "completed_dttm") + private LocalDateTime completedDttm; + + @NotNull + @Column(name = "created_dttm", nullable = false) + private LocalDateTime createdDttm; + + @NotNull + @Column(name = "updated_dttm", nullable = false) + private LocalDateTime updatedDttm; +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/batch/BatchStepHistoryRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/batch/BatchStepHistoryRepository.java new file mode 100644 index 00000000..2320f198 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/batch/BatchStepHistoryRepository.java @@ -0,0 +1,7 @@ +package com.kamco.cd.kamcoback.postgres.repository.batch; + +import com.kamco.cd.kamcoback.postgres.entity.BatchStepHistoryEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BatchStepHistoryRepository + extends JpaRepository, BatchStepHistoryRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/batch/BatchStepHistoryRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/batch/BatchStepHistoryRepositoryCustom.java new file mode 100644 index 00000000..368e64b5 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/batch/BatchStepHistoryRepositoryCustom.java @@ -0,0 +1,5 @@ +package com.kamco.cd.kamcoback.postgres.repository.batch; + +public interface BatchStepHistoryRepositoryCustom { + boolean isDownloadable(Long analUid); +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/batch/BatchStepHistoryRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/batch/BatchStepHistoryRepositoryImpl.java new file mode 100644 index 00000000..c45d1bde --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/batch/BatchStepHistoryRepositoryImpl.java @@ -0,0 +1,37 @@ +package com.kamco.cd.kamcoback.postgres.repository.batch; + +import com.kamco.cd.kamcoback.postgres.entity.QBatchStepHistoryEntity; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class BatchStepHistoryRepositoryImpl implements BatchStepHistoryRepositoryCustom { + private final JPAQueryFactory queryFactory; + + @Override + public boolean isDownloadable(Long analUid) { + QBatchStepHistoryEntity h = QBatchStepHistoryEntity.batchStepHistoryEntity; + + boolean startedExists = + queryFactory + .selectOne() + .from(h) + .where( + h.analUid.eq(analUid), h.stepName.eq("zipResponseStep"), h.status.eq("STARTED")) + .fetchFirst() + != null; + + boolean successExists = + queryFactory + .selectOne() + .from(h) + .where( + h.analUid.eq(analUid), h.stepName.eq("zipResponseStep"), h.status.eq("SUCCESS")) + .fetchFirst() + != null; + + return successExists && !startedExists; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java index f70e71d1..39e94a7d 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java @@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.postgres.repository.label; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceLearnDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveInfo; @@ -106,5 +107,7 @@ public interface LabelAllocateRepositoryCustom { Long findLabelingIngProcessCnt(); + InferenceLearnDto findLabelingIngProcessId(UUID uuid); + Optional findLearnUid(UUID uuid); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java index 7f099c97..34624974 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java @@ -14,6 +14,7 @@ import com.kamco.cd.kamcoback.common.enums.StatusType; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceLearnDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState; @@ -1827,6 +1828,23 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .fetchOne(); } + @Override + public InferenceLearnDto findLabelingIngProcessId(UUID uuid) { + return queryFactory + .select( + Projections.constructor( + InferenceLearnDto.class, + mapSheetAnalInferenceEntity.uuid, + mapSheetLearnEntity.uid, + mapSheetAnalInferenceEntity.analState, + mapSheetAnalInferenceEntity.id)) + .from(mapSheetLearnEntity) + .join(mapSheetAnalInferenceEntity) + .on(mapSheetAnalInferenceEntity.learnId.eq(mapSheetLearnEntity.id)) + .where(mapSheetAnalInferenceEntity.uuid.eq(uuid)) + .fetchOne(); + } + @Override public Optional findLearnUid(UUID uuid) { return Optional.ofNullable(