diff --git a/src/main/java/com/kamco/cd/kamcoback/common/download/DownloadExecutor.java b/src/main/java/com/kamco/cd/kamcoback/common/download/DownloadExecutor.java new file mode 100644 index 00000000..edb80175 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/common/download/DownloadExecutor.java @@ -0,0 +1,48 @@ +package com.kamco.cd.kamcoback.common.download; + +import com.kamco.cd.kamcoback.common.download.dto.DownloadSpec; +import com.kamco.cd.kamcoback.common.utils.UserUtil; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; + +@Service +@RequiredArgsConstructor +public class DownloadExecutor { + + private final UserUtil userUtil; + + public ResponseEntity stream(DownloadSpec spec) throws IOException { + + if (!Files.isReadable(spec.filePath())) { + return ResponseEntity.notFound().build(); + } + + StreamingResponseBody body = + os -> { + try (InputStream in = Files.newInputStream(spec.filePath())) { + in.transferTo(os); + os.flush(); + } catch (Exception e) { + // 고용량은 중간 끊김 흔하니까 throw 금지 + } + }; + + String fileName = + spec.downloadName() != null + ? spec.downloadName() + : spec.filePath().getFileName().toString(); + + return ResponseEntity.ok() + .contentType( + spec.contentType() != null ? spec.contentType() : MediaType.APPLICATION_OCTET_STREAM) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"") + .body(body); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/common/download/DownloadPaths.java b/src/main/java/com/kamco/cd/kamcoback/common/download/DownloadPaths.java new file mode 100644 index 00000000..b4d3fca0 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/common/download/DownloadPaths.java @@ -0,0 +1,19 @@ +package com.kamco.cd.kamcoback.common.download; + +import org.springframework.util.AntPathMatcher; + +public final class DownloadPaths { + private DownloadPaths() {} + + public static final String[] PATTERNS = { + "/api/inference/download/**", "/api/training-data/stage/download/**" + }; + + public static boolean matches(String uri) { + AntPathMatcher m = new AntPathMatcher(); + for (String p : PATTERNS) { + if (m.match(p, uri)) return true; + } + return false; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/common/download/dto/DownloadSpec.java b/src/main/java/com/kamco/cd/kamcoback/common/download/dto/DownloadSpec.java new file mode 100644 index 00000000..bc3b5570 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/common/download/dto/DownloadSpec.java @@ -0,0 +1,12 @@ +package com.kamco.cd.kamcoback.common.download.dto; + +import java.nio.file.Path; +import java.util.UUID; +import org.springframework.http.MediaType; + +public record DownloadSpec( + UUID uuid, // 다운로드 식별(로그/정책용) + Path filePath, // 실제 파일 경로 + String downloadName, // 사용자에게 보일 파일명 + MediaType contentType // 보통 OCTET_STREAM + ) {} diff --git a/src/main/java/com/kamco/cd/kamcoback/config/FileDownloadInteceptor.java b/src/main/java/com/kamco/cd/kamcoback/config/FileDownloadInteceptor.java index c800c690..62687e9e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/FileDownloadInteceptor.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/FileDownloadInteceptor.java @@ -1,8 +1,8 @@ package com.kamco.cd.kamcoback.config; import com.fasterxml.jackson.databind.ObjectMapper; -import com.kamco.cd.kamcoback.auth.CustomUserDetails; import com.kamco.cd.kamcoback.common.utils.HeaderUtil; +import com.kamco.cd.kamcoback.common.utils.UserUtil; import com.kamco.cd.kamcoback.config.api.ApiLogFunction; import com.kamco.cd.kamcoback.menu.dto.MenuDto; import com.kamco.cd.kamcoback.menu.service.MenuService; @@ -17,7 +17,6 @@ import java.util.Objects; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; @@ -27,24 +26,39 @@ public class FileDownloadInteceptor implements HandlerInterceptor { private final AuditLogRepository auditLogRepository; private final MenuService menuService; + private final UserUtil userUtil; @Autowired private ObjectMapper objectMapper; - public FileDownloadInteceptor(AuditLogRepository auditLogRepository, MenuService menuService) { + public FileDownloadInteceptor( + AuditLogRepository auditLogRepository, MenuService menuService, UserUtil userUtil) { this.auditLogRepository = auditLogRepository; this.menuService = menuService; + this.userUtil = userUtil; } @Override - public void afterCompletion( - HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + public boolean preHandle( + HttpServletRequest request, HttpServletResponse response, Object handler) { + if (!request.getRequestURI().contains("/download")) return true; + + if (request.getDispatcherType() != jakarta.servlet.DispatcherType.REQUEST) { + return true; + } + + saveLog(request, response); + + return true; + } + + private void saveLog(HttpServletRequest request, HttpServletResponse response) { // 파일 다운로드 API만 필터링 if (!request.getRequestURI().contains("/download")) { return; } - Long userId = extractUserId(request); + Long userId = userUtil.getId(); String ip = ApiLogFunction.getClientIp(request); List list = menuService.getFindAll(); @@ -81,12 +95,4 @@ public class FileDownloadInteceptor implements HandlerInterceptor { auditLogRepository.save(log); } - - private Long extractUserId(HttpServletRequest request) { - if (request.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth - && auth.getPrincipal() instanceof CustomUserDetails userDetails) { - return userDetails.getMember().getId(); - } - return null; - } } diff --git a/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java b/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java index 2fb856ee..0d67dbfa 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java @@ -3,6 +3,8 @@ package com.kamco.cd.kamcoback.config; import com.kamco.cd.kamcoback.auth.CustomAuthenticationProvider; import com.kamco.cd.kamcoback.auth.JwtAuthenticationFilter; import com.kamco.cd.kamcoback.auth.MenuAuthorizationManager; +import com.kamco.cd.kamcoback.common.download.DownloadPaths; +import jakarta.servlet.DispatcherType; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -44,9 +46,11 @@ public class SecurityConfig { .authorizeHttpRequests( auth -> auth + // .requestMatchers("/chunk_upload_test.html").authenticated() .requestMatchers("/monitor/health", "/monitor/health/**") .permitAll() + // 맵시트 영역 전체 허용 (우선순위 최상단) .requestMatchers("/api/mapsheet/**") .permitAll() @@ -67,13 +71,25 @@ public class SecurityConfig { .requestMatchers("/api/test/review") .hasAnyRole("ADMIN", "REVIEWER") + // ASYNC/ERROR 재디스패치는 막지 않기 (다운로드/스트리밍에서 필수) + .dispatcherTypeMatchers(DispatcherType.ASYNC, DispatcherType.ERROR) + .permitAll() + + // 다운로드는 인증 필요 + .requestMatchers(HttpMethod.GET, DownloadPaths.PATTERNS) + .authenticated() + // 메뉴 등록 ADMIN만 가능 .requestMatchers(HttpMethod.POST, "/api/menu/auth") .hasAnyRole("ADMIN") + + // 에러 경로는 항상 허용 (이미 있지만 유지) .requestMatchers("/error") .permitAll() + + // preflight 허용 .requestMatchers(HttpMethod.OPTIONS, "/**") - .permitAll() // preflight 허용 + .permitAll() .requestMatchers( "/api/auth/signin", "/api/auth/refresh", @@ -90,6 +106,7 @@ public class SecurityConfig { "/api/layer/tile-url", "/api/layer/tile-url-year") .permitAll() + // 로그인한 사용자만 가능 IAM .requestMatchers( "/api/user/**", @@ -98,16 +115,11 @@ public class SecurityConfig { "/api/training-data/label/**", "/api/training-data/review/**") .authenticated() - .anyRequest() - .access(menuAuthorizationManager) - // .authenticated() - ) - .addFilterBefore( - jwtAuthenticationFilter, - UsernamePasswordAuthenticationFilter - .class) // 요청 들어오면 먼저 JWT 토큰 검사 후 security context 에 사용자 정보 저장. - ; + // 나머지는 메뉴권한 + .anyRequest() + .access(menuAuthorizationManager)) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @@ -118,23 +130,18 @@ public class SecurityConfig { return configuration.getAuthenticationManager(); } - /** - * CORS 설정 - * - * @return - */ + /** CORS 설정 */ @Bean public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration config = new CorsConfiguration(); // CORS 객체 생성 - config.setAllowedOriginPatterns(List.of("*")); // 도메인 허용 + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOriginPatterns(List.of("*")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); - config.setAllowedHeaders(List.of("*")); // 헤더요청 Authorization, Content-Type, X-Custom-Header - config.setAllowCredentials(true); // 쿠키, Authorization 헤더, Bearer Token 등 자격증명 포함 요청을 허용할지 설정 + config.setAllowedHeaders(List.of("*")); + config.setAllowCredentials(true); config.setExposedHeaders(List.of("Content-Disposition")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - /** "/**" → 모든 API 경로에 대해 이 CORS 규칙을 적용 /api/** 같이 특정 경로만 지정 가능. */ - source.registerCorsConfiguration("/**", config); // CORS 정책을 등록 + source.registerCorsConfiguration("/**", config); return source; } diff --git a/src/main/java/com/kamco/cd/kamcoback/config/WebConfig.java b/src/main/java/com/kamco/cd/kamcoback/config/WebConfig.java index 51509e20..47acf57e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/WebConfig.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/WebConfig.java @@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; +import com.kamco.cd.kamcoback.common.download.DownloadPaths; import com.kamco.cd.kamcoback.common.utils.geometry.GeometryDeserializer; import com.kamco.cd.kamcoback.common.utils.geometry.GeometrySerializer; import org.locationtech.jts.geom.Geometry; @@ -39,9 +40,6 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { - registry - .addInterceptor(fileDownloadInteceptor) - .addPathPatterns("/api/inference/download/**") // 추론 파일 다운로드 - .addPathPatterns("/api/training-data/stage/download/**"); // 학습데이터 다운로드 + registry.addInterceptor(fileDownloadInteceptor).addPathPatterns(DownloadPaths.PATTERNS); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFilter.java b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFilter.java index 944dc43f..7a7f7ee0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFilter.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFilter.java @@ -1,5 +1,6 @@ package com.kamco.cd.kamcoback.config.api; +import com.kamco.cd.kamcoback.common.download.DownloadPaths; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -16,6 +17,14 @@ public class ApiLogFilter extends OncePerRequestFilter { protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + String uri = request.getRequestURI(); + + if (DownloadPaths.matches(uri)) { + filterChain.doFilter(request, response); + return; + } + ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response); diff --git a/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/ChngDetectMastDto.java b/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/ChngDetectMastDto.java index 0bbb52bf..67ab78da 100644 --- a/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/ChngDetectMastDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/gukyuin/dto/ChngDetectMastDto.java @@ -247,45 +247,45 @@ public class ChngDetectMastDto { @AllArgsConstructor public static class RlbDtctMastDto { - private String pnuDtctId; - private String pnu; - private String lrmSyncYmd; - private String pnuSyncYmd; - private String mpqdNo; // 도엽번호 + private String pnuDtctId; // PNU탐지ID + private String pnu; // PNU코드(19자리) + private String lrmSyncYmd; // 지적도동기화일자(YYYYMMDD) + private String pnuSyncYmd; // PNU동기화일자(YYYYMMDD) + private String mpqdNo; // 도곽번호 private String cprsYr; // 비교년도 private String crtrYr; // 기준년도 - private String chnDtctSno; // 회차 - private String chnDtctId; + private String chnDtctSno; // 회차, 변화탐지순번 + private String chnDtctId; // 변화탐지ID(UUID) - private String chnDtctMstId; - private String chnDtctObjtId; - private String chnDtctContId; - private String chnCd; - private String chnDtctProb; + private String chnDtctMstId; // 변화탐지마스터ID + private String chnDtctObjtId; // 변화탐지객체ID + private String chnDtctContId; // 변화탐지내용ID + private String chnCd; // 변화코드 + private String chnDtctProb; // 변화탐지정확도(0~1) private String bfClsCd; // 이전분류코드 - private String bfClsProb; // 이전분류정확도 + private String bfClsProb; // 이전분류정확도(0~1) private String afClsCd; // 이후분류코드 - private String afClsProb; // 이후분류정확도 + private String afClsProb; // 이후분류정확도(0~1) - private String pnuSqms; - private String pnuDtctSqms; - private String chnDtctSqms; - private String stbltYn; - private String incyCd; - private String incyRsnCont; - private String lockYn; - private String lblYn; - private String chgYn; - private String rsatctNo; - private String rmk; + private String pnuSqms; // PNU면적(㎡) + private String pnuDtctSqms; // PNU탐지면적(㎡) + private String chnDtctSqms; // 변화탐지면적(㎡) + private String stbltYn; // 적합여부(Y/N) - 안정성 (Y:부적합, N:적합) + private String incyCd; // 부적합코드 + private String incyRsnCont; // 부적합사유내용 + private String lockYn; // 잠금여부(Y/N) + private String lblYn; // 라벨여부(Y/N) + private String chgYn; // 변경여부(Y/N) + private String rsatctNo; // 부동산등기번호 + private String rmk; // 비고 private String crtDt; // 생성일시 private String crtEpno; // 생성사원번호 private String crtIp; // 생성사원아이피 - private String chgDt; - private String chgEpno; - private String chgIp; + private String chgDt; // 변경일시 + private String chgEpno; // 변경자사번 + private String chgIp; // 변경자IP private String delYn; // 삭제여부 } diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiController.java b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiController.java index bd5ff6da..8fb05ffd 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiController.java @@ -1,5 +1,7 @@ package com.kamco.cd.kamcoback.inference; +import com.kamco.cd.kamcoback.common.download.DownloadExecutor; +import com.kamco.cd.kamcoback.common.download.dto.DownloadSpec; import com.kamco.cd.kamcoback.common.exception.CustomApiException; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto; @@ -17,6 +19,7 @@ import com.kamco.cd.kamcoback.model.dto.ModelMngDto; import com.kamco.cd.kamcoback.model.service.ModelMngService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -25,17 +28,13 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDate; import java.util.List; import java.util.Map; import java.util.UUID; import lombok.RequiredArgsConstructor; -import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.Resource; import org.springframework.data.domain.Page; -import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -46,6 +45,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; @Tag(name = "추론관리", description = "추론관리 API") @RequestMapping("/api/inference") @@ -56,6 +56,7 @@ public class InferenceResultApiController { private final InferenceResultService inferenceResultService; private final MapSheetMngService mapSheetMngService; private final ModelMngService modelMngService; + private final DownloadExecutor downloadExecutor; @Operation(summary = "추론관리 목록", description = "어드민 홈 > 추론관리 > 추론관리 > 추론관리 목록") @ApiResponses( @@ -328,7 +329,21 @@ public class InferenceResultApiController { return ApiResponseDto.ok(geomList); } - @Operation(summary = "shp 파일 다운로드", description = "추론관리 분석결과 shp 파일 다운로드") + @Operation( + summary = "shp 파일 다운로드", + description = "추론관리 분석결과 shp 파일 다운로드", + parameters = { + @Parameter( + name = "kamco-download-uuid", + in = ParameterIn.HEADER, + required = true, + description = "다운로드 요청 UUID", + schema = + @Schema( + type = "string", + format = "uuid", + example = "69c4e56c-e0bf-4742-9225-bba9aae39052")) + }) @ApiResponses( value = { @ApiResponse( @@ -341,15 +356,14 @@ public class InferenceResultApiController { @ApiResponse(responseCode = "404", description = "파일 없음", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) - @GetMapping(value = "/download/{uuid}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - public ResponseEntity downloadShp( - @Parameter(description = "uuid", example = "0192efc6-9ec2-43ee-9a90-5b73e763c09f") - @PathVariable - UUID uuid) + @GetMapping(value = "/download/{uuid}") + public ResponseEntity download( + @Parameter(example = "69c4e56c-e0bf-4742-9225-bba9aae39052") @PathVariable UUID uuid) throws IOException { String path; String uid; + try { Map map = inferenceResultService.shpDownloadPath(uuid); path = String.valueOf(map.get("path")); @@ -360,24 +374,11 @@ public class InferenceResultApiController { Path zipPath = Path.of(path); - if (!Files.exists(zipPath) || !Files.isReadable(zipPath)) { - return ResponseEntity.notFound().build(); - } - - FileSystemResource resource = new FileSystemResource(zipPath); - - String filename = uid + ".zip"; - - long fileSize = Files.size(zipPath); - - return ResponseEntity.ok() - .contentType(MediaType.APPLICATION_OCTET_STREAM) - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") - .contentLength(fileSize) - .body(resource); + return downloadExecutor.stream( + new DownloadSpec(uuid, zipPath, uid + ".zip", MediaType.APPLICATION_OCTET_STREAM)); } - @Operation(summary = "shp 파일 다운로드 이력", description = "추론관리 분석결과 shp 파일 다운로드 이력") + @Operation(summary = "shp 파일 다운로드 이력 조회", description = "추론관리 분석결과 shp 파일 다운로드 이력 조회") @GetMapping(value = "/download-audit/{uuid}") @ApiResponses( value = { @@ -392,19 +393,20 @@ public class InferenceResultApiController { @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) public ApiResponseDto> downloadAudit( - @Parameter(description = "UUID", example = "0192efc6-9ec2-43ee-9a90-5b73e763c09f") + @Parameter(description = "UUID", example = "69c4e56c-e0bf-4742-9225-bba9aae39052") @PathVariable UUID uuid, @Parameter(description = "다운로드일 시작", example = "2025-01-01") @RequestParam(required = false) LocalDate strtDttm, - @Parameter(description = "다운로드일 종료", example = "2026-01-01") @RequestParam(required = false) + @Parameter(description = "다운로드일 종료", example = "2026-04-01") @RequestParam(required = false) LocalDate endDttm, - @Parameter(description = "키워드", example = "관리자") @RequestParam(required = false) + @Parameter(description = "키워드", example = "") @RequestParam(required = false) String searchValue, @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") int page, @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") int size) { + AuditLogDto.searchReq searchReq = new searchReq(); searchReq.setPage(page); searchReq.setSize(size); @@ -413,8 +415,7 @@ public class InferenceResultApiController { downloadReq.setStartDate(strtDttm); downloadReq.setEndDate(endDttm); downloadReq.setSearchValue(searchValue); - downloadReq.setMenuId("22"); - downloadReq.setRequestUri("/api/inference/download-audit"); + downloadReq.setRequestUri("/api/inference/download-audit/download/" + uuid); return ApiResponseDto.ok(inferenceResultService.getDownloadAudit(searchReq, downloadReq)); } 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 37e3ce27..963396d5 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -1,5 +1,7 @@ package com.kamco.cd.kamcoback.label; +import com.kamco.cd.kamcoback.common.download.DownloadExecutor; +import com.kamco.cd.kamcoback.common.download.dto.DownloadSpec; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; @@ -9,25 +11,39 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.WorkHistoryDto; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.UpdateClosedRequest; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; import com.kamco.cd.kamcoback.label.service.LabelAllocateService; +import com.kamco.cd.kamcoback.log.dto.AuditLogDto; +import com.kamco.cd.kamcoback.log.dto.AuditLogDto.DownloadReq; +import com.kamco.cd.kamcoback.log.dto.AuditLogDto.searchReq; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; 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; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; @Slf4j @Tag(name = "라벨링 작업 관리", description = "라벨링 작업 배정 및 통계 조회 API") @@ -37,6 +53,10 @@ import org.springframework.web.bind.annotation.RestController; public class LabelAllocateApiController { private final LabelAllocateService labelAllocateService; + private final DownloadExecutor downloadExecutor; + + @Value("${file.dataset-response}") + private String responsePath; @Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.") @ApiResponses( @@ -333,4 +353,112 @@ public class LabelAllocateApiController { public ApiResponseDto labelingIngProcessCnt() { return ApiResponseDto.ok(labelAllocateService.findLabelingIngProcessCnt()); } + + @Operation( + summary = "라벨 파일 다운로드", + description = "라벨 파일 다운로드", + 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("/download/{uuid}") + public ResponseEntity download( + @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"); + + return downloadExecutor.stream( + new DownloadSpec(uuid, zipPath, uid + ".zip", MediaType.APPLICATION_OCTET_STREAM)); + } + + @Operation(summary = "라벨 파일 다운로드 이력 조회", description = "라벨 파일 다운로드 이력 조회") + @GetMapping(value = "/download-audit/{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> downloadAudit( + @Parameter(description = "UUID", example = "6d8d49dc-0c9d-4124-adc7-b9ca610cc394") + @PathVariable + UUID uuid, + // @Parameter(description = "다운로드일 시작", example = "2025-01-01") @RequestParam(required = + // false) + // LocalDate strtDttm, + // @Parameter(description = "다운로드일 종료", example = "2026-04-01") @RequestParam(required = + // false) + // LocalDate endDttm, + // @Parameter(description = "키워드", example = "") @RequestParam(required = false) + // String searchValue, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") + int page, + @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") + int size) { + + AuditLogDto.searchReq searchReq = new searchReq(); + searchReq.setPage(page); + searchReq.setSize(size); + DownloadReq downloadReq = new DownloadReq(); + downloadReq.setUuid(uuid); + // downloadReq.setStartDate(strtDttm); + // downloadReq.setEndDate(endDttm); + // downloadReq.setSearchValue(searchValue); + downloadReq.setRequestUri("/api/training-data/stage/download/" + uuid); + + 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 2cb5401d..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 @@ -16,10 +16,14 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.searchReq; 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.WorkerListResponse; +import com.kamco.cd.kamcoback.log.dto.AuditLogDto; +import com.kamco.cd.kamcoback.log.dto.AuditLogDto.DownloadReq; +import com.kamco.cd.kamcoback.postgres.core.AuditLogCoreService; import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; import java.util.List; import java.util.Objects; import java.util.UUID; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -28,13 +32,11 @@ import org.springframework.transaction.annotation.Transactional; @Slf4j @Service @Transactional +@RequiredArgsConstructor public class LabelAllocateService { private final LabelAllocateCoreService labelAllocateCoreService; - - public LabelAllocateService(LabelAllocateCoreService labelAllocateCoreService) { - this.labelAllocateCoreService = labelAllocateCoreService; - } + private final AuditLogCoreService auditLogCoreService; /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 @@ -273,4 +275,29 @@ public class LabelAllocateService { public Long findLabelingIngProcessCnt() { return labelAllocateCoreService.findLabelingIngProcessCnt(); } + + public String findLearnUid(UUID uuid) { + return labelAllocateCoreService.findLearnUid(uuid); + } + + /** + * 다운로드 이력 조회 + * + * @param searchReq 페이징 + * @param downloadReq 조회조건 + */ + public Page getDownloadAudit( + 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/menu/service/MenuService.java b/src/main/java/com/kamco/cd/kamcoback/menu/service/MenuService.java index bf2db12f..3a0ebcca 100644 --- a/src/main/java/com/kamco/cd/kamcoback/menu/service/MenuService.java +++ b/src/main/java/com/kamco/cd/kamcoback/menu/service/MenuService.java @@ -9,8 +9,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service +@Transactional(readOnly = true) @RequiredArgsConstructor public class MenuService { 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 1ee3838b..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 @@ -1,8 +1,11 @@ package com.kamco.cd.kamcoback.postgres.core; +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; @@ -13,12 +16,18 @@ 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; @Service @@ -26,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); @@ -234,4 +247,34 @@ public class LabelAllocateCoreService { public Long findLabelingIngProcessCnt() { 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) + .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND)); + } } 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 c5d5aba3..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; @@ -15,6 +16,7 @@ import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; import java.time.LocalDate; import java.util.List; +import java.util.Optional; import java.util.UUID; import org.springframework.data.domain.Page; @@ -104,4 +106,8 @@ public interface LabelAllocateRepositoryCustom { void updateAnalInferenceMngState(UUID uuid, String status); 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 f50ffb83..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 @@ -6,6 +6,7 @@ import static com.kamco.cd.kamcoback.postgres.entity.QLabelingLabelerEntity.labe import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; import com.kamco.cd.kamcoback.common.enums.ImageryFitStatus; @@ -13,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; @@ -49,6 +51,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -1824,4 +1827,33 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto LabelMngState.ASSIGNED.getId(), LabelMngState.ING.getId())) .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( + queryFactory + .select(mapSheetLearnEntity.uid) + .from(mapSheetLearnEntity) + .innerJoin(mapSheetAnalInferenceEntity) + .on(mapSheetAnalInferenceEntity.learnId.eq(mapSheetLearnEntity.id)) + .where(mapSheetAnalInferenceEntity.uuid.eq(uuid)) + .fetchOne()); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepositoryImpl.java index 3ad03117..dbcd99e0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepositoryImpl.java @@ -168,13 +168,13 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport whereBuilder.and(auditLogEntity.eventStatus.ne(EventStatus.valueOf("FAILED"))); whereBuilder.and(auditLogEntity.eventType.eq(EventType.valueOf("DOWNLOAD"))); - if (req.getMenuId() != null && !req.getMenuId().isEmpty()) { - whereBuilder.and(auditLogEntity.menuUid.eq(req.getMenuId())); - } + // if (req.getMenuId() != null && !req.getMenuId().isEmpty()) { + // whereBuilder.and(auditLogEntity.menuUid.eq(req.getMenuId())); + // } if (req.getUuid() != null) { - whereBuilder.and(auditLogEntity.requestUri.contains("/api/inference/download/")); - whereBuilder.and(auditLogEntity.requestUri.endsWith(String.valueOf(req.getUuid()))); + whereBuilder.and(auditLogEntity.requestUri.contains(req.getRequestUri())); + whereBuilder.and(auditLogEntity.downloadUuid.eq(req.getUuid())); } if (req.getSearchValue() != null && !req.getSearchValue().isEmpty()) { diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/trainingdata/TrainingDataReviewRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/trainingdata/TrainingDataReviewRepositoryImpl.java index 187481ed..23b43d3a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/trainingdata/TrainingDataReviewRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/trainingdata/TrainingDataReviewRepositoryImpl.java @@ -483,7 +483,7 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport queryFactory .select(memberEntity.name) .from(memberEntity) - .where(memberEntity.userId.eq(assignment.toDto().getWorkerUid())) + .where(memberEntity.employeeNo.eq(assignment.toDto().getWorkerUid())) .fetchFirst(); if (workerName == null) { workerName = "";