53 Commits

Author SHA1 Message Date
0425a6486d Merge pull request 'shp 테스트' (#160) from feat/infer_dev_260211 into develop
Reviewed-on: #160
2026-03-25 14:07:21 +09:00
241c7222d1 shp 테스트 2026-03-25 14:07:06 +09:00
71e4ab14bd Merge pull request 'shp 테스트' (#159) from feat/infer_dev_260211 into develop
Reviewed-on: #159
2026-03-25 14:01:26 +09:00
52da4dafc3 shp 테스트 2026-03-25 14:01:02 +09:00
24d615174d Merge pull request '토근 정보 변경' (#158) from feat/infer_dev_260211 into develop
Reviewed-on: #158
2026-03-19 13:48:56 +09:00
12b0f0867d 토근 정보 변경 2026-03-19 13:48:14 +09:00
283d906da6 Merge pull request '토큰시간 변경' (#157) from feat/infer_dev_260211 into develop
Reviewed-on: #157
2026-03-19 10:31:05 +09:00
360b451c38 토큰시간 변경 2026-03-19 10:30:42 +09:00
80e281cb99 Merge pull request '개발서버 토큰 시간 변경' (#156) from feat/infer_dev_260211 into develop
Reviewed-on: #156
2026-03-18 14:02:29 +09:00
b07bc38ee8 개발서버 토큰 시간 변경 2026-03-18 14:01:56 +09:00
e4c1c76b2b Merge pull request '재할당 transactional 추가' (#155) from feat/infer_dev_260211 into develop
Reviewed-on: #155
2026-03-17 17:47:43 +09:00
01b64eeca7 재할당 transactional 추가 2026-03-17 17:47:15 +09:00
516f949a37 Merge pull request 'spotless 적용' (#154) from feat/infer_dev_260211 into develop
Reviewed-on: #154
2026-03-17 14:00:22 +09:00
b6ed3b57ef spotless 적용 2026-03-17 14:00:04 +09:00
9eebf23774 Merge pull request 'dev 토큰 유효시간 변경, 인증 Log 추가한거 삭제' (#153) from feat/infer_dev_260211 into develop
Reviewed-on: #153
2026-03-17 13:55:53 +09:00
4f742edf8b dev 토큰 유효시간 변경, 인증 Log 추가한거 삭제 2026-03-17 13:55:30 +09:00
0aa415cf3a Merge pull request '토큰 생성 로그 추가' (#152) from feat/infer_dev_260211 into develop
Reviewed-on: #152
2026-03-17 13:33:34 +09:00
884b635585 토큰 생성 로그 추가 2026-03-17 13:33:17 +09:00
6861f6b8b6 Merge pull request '메뉴 권한 Log 추가' (#151) from feat/infer_dev_260211 into develop
Reviewed-on: #151
2026-03-17 13:22:06 +09:00
a97af0d4dd 메뉴 권한 Log 추가 2026-03-17 13:21:46 +09:00
9297d19e24 Merge pull request 'refresh 토큰 인증 로그 추가' (#150) from feat/infer_dev_260211 into develop
Reviewed-on: #150
2026-03-17 12:46:12 +09:00
65c38b3083 refresh 토큰 인증 로그 추가 2026-03-17 12:45:56 +09:00
24dca652f0 Merge pull request 'refresh 토큰 재발급 로그 추가' (#149) from feat/infer_dev_260211 into develop
Reviewed-on: #149
2026-03-17 12:26:31 +09:00
193cd449a8 refresh 토큰 재발급 로그 추가 2026-03-17 12:23:45 +09:00
dean
0efde4e5bb access-token 2026-03-17 07:49:13 +09:00
dean
548d82da12 access-token 2026-03-17 07:20:08 +09:00
536ff8fc65 추론 종료 async 수정 2026-03-13 15:52:20 +09:00
1dc1ce741e 추론 종료 비동기 호출 2026-03-13 15:38:11 +09:00
d21ed61666 hello 2026-03-12 08:22:39 +09:00
c9a1007c21 hello 2026-03-12 08:08:59 +09:00
413afb0b7c shp파일 바꾸는로직정리 2026-03-12 08:08:11 +09:00
e69eccc82b shp파일 바꾸는로직정리 2026-03-12 07:57:14 +09:00
828a4c5dca shp파일 바꾸는로직정리 2026-03-12 07:31:54 +09:00
5d417d85ff Merge pull request '국유인연동 알럿 문구 변경' (#148) from feat/infer_dev_260211 into develop
Reviewed-on: #148
2026-03-09 10:52:25 +09:00
614d6da695 국유인연동 알럿 문구 변경 2026-03-09 10:51:57 +09:00
8d45e91982 Merge pull request '국유인연동 : 라벨링 진행중 회차 uuid 추가' (#147) from feat/infer_dev_260211 into develop
Reviewed-on: #147
2026-03-09 10:10:52 +09:00
e1febf5863 국유인연동 : 라벨링 진행중 회차 uuid 추가 2026-03-09 10:10:08 +09:00
97fb659f15 enum @notBlank제거 2026-03-07 01:43:07 +09:00
ebb48c3f57 enum @notBlank제거 2026-03-07 01:41:34 +09:00
a35b4b8f59 enum @notBlank제거 2026-03-07 01:24:55 +09:00
0a53e186dc enum @notBlank제거 2026-03-07 01:21:30 +09:00
f3c7c5e8e8 enum @notBlank제거 2026-03-07 01:13:28 +09:00
c505e9b740 @enumvalid제거 2026-03-07 01:11:56 +09:00
8c2f2eff1b enum @notBlank제거 2026-03-07 01:08:21 +09:00
ade8bfa76a Merge pull request 'feat/dean/polishing_2' (#146) from feat/dean/polishing_2 into develop
Reviewed-on: #146
2026-03-07 01:02:58 +09:00
3752b83292 인퍼런스실행 정리 2026-03-07 00:56:51 +09:00
9f31f661fc 원복 2026-03-06 22:12:54 +09:00
b477928261 Merge pull request 'testing 테이블 조회 조건 추가' (#145) from feat/infer_dev_260211 into develop
Reviewed-on: #145
2026-03-06 19:51:41 +09:00
f4f75f353c testing 테이블 조회 조건 추가 2026-03-06 19:51:08 +09:00
f977e4be7c Merge pull request 'shp 파일 생성중 오류 발생시 중지되게 수정' (#144) from feat/infer_dev_260211 into develop
Reviewed-on: #144
2026-03-06 19:29:27 +09:00
573da5b53a shp 파일 생성중 오류 발생시 중지되게 수정 2026-03-06 19:21:08 +09:00
bd72852556 Merge pull request '추론실행 오류 수정' (#143) from feat/infer_dev_260211 into develop
Reviewed-on: #143
2026-03-06 18:54:16 +09:00
e4b904606f 추론실행 오류 수정 2026-03-06 18:53:38 +09:00
31 changed files with 489 additions and 188 deletions

View File

@@ -37,7 +37,6 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
// JWT 토큰을 검증하고, 인증된 사용자로 SecurityContext에 등록
if (token != null && jwtTokenProvider.isValidToken(token)) {
String username = jwtTokenProvider.getSubject(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

View File

@@ -8,11 +8,13 @@ import jakarta.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import javax.crypto.SecretKey;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/** 토큰 생성 */
@Component
@Log4j2
public class JwtTokenProvider {
@Value("${jwt.secret}")
@@ -34,11 +36,13 @@ public class JwtTokenProvider {
// Access Token 생성
public String createAccessToken(String subject) {
log.info("TOKEN VALIDITY = {}", accessTokenValidityInMs);
return createToken(subject, accessTokenValidityInMs);
}
// Refresh Token 생성
public String createRefreshToken(String subject) {
log.info("REFRESH TOKEN VALIDITY = {}", refreshTokenValidityInMs);
return createToken(subject, refreshTokenValidityInMs);
}

View File

@@ -58,6 +58,7 @@ public class MenuAuthorizationManager implements AuthorizationManager<RequestAut
for (MenuEntity menu : allowedMenus) {
String baseUri = menu.getMenuUrl();
if (baseUri == null || baseUri.isBlank()) {
continue;
}

View File

@@ -151,7 +151,7 @@ public class GeoJsonValidator {
Set<String> foundUnique = new HashSet<>();
// 중복된 scene_id 목록 (샘플 로그 출력용이라 순서 유지 가능한 LinkedHashSet 사용)
// Set<String> duplicates = new LinkedHashSet<>();
Set<String> duplicates = new LinkedHashSet<>();
// scene_id가 null 또는 blank인 feature의 개수 (데이터 이상)
int nullIdCount = 0;
@@ -184,11 +184,10 @@ public class GeoJsonValidator {
continue;
}
foundUnique.add(sceneId);
// foundUnique.add(sceneId)가 false면 "이미 같은 값이 있었다"는 뜻 => 중복
// if (!foundUnique.add(sceneId)) {
// duplicates.add(sceneId);
// }
if (!foundUnique.add(sceneId)) {
duplicates.add(sceneId);
}
}
// =========================================================
@@ -232,7 +231,7 @@ public class GeoJsonValidator {
requested.size(), // 요청 도엽 유니크 수
foundUnique.size(), // GeoJSON에서 발견된 scene_id 유니크 수
nullIdCount, // scene_id가 비어있는 feature 수
0, // 중복 scene_id 종류 수
duplicates.size(), // 중복 scene_id 종류 수
missing.size(), // 요청했지만 빠진 도엽 수
extra.size()); // 요청하지 않았는데 들어온 도엽 수
@@ -261,12 +260,12 @@ public class GeoJsonValidator {
// - 요청 문법은 맞지만(파일은 있고 JSON도 읽힘),
// 내용(정합성)이 요구사항을 만족하지 못하는 경우에 적합.
// =========================================================
if (!missing.isEmpty() || !extra.isEmpty() || nullIdCount > 0) {
if (!missing.isEmpty() || !extra.isEmpty() || !duplicates.isEmpty() || nullIdCount > 0) {
throw new ResponseStatusException(
HttpStatus.UNPROCESSABLE_ENTITY,
String.format(
"GeoJSON validation failed: missing=%d, extra=%d, duplicates=%d, nullId=%d",
missing.size(), extra.size(), 0, nullIdCount));
missing.size(), extra.size(), duplicates.size(), nullIdCount));
}
// 모든 조건을 통과하면 정상

View File

@@ -1,23 +1,45 @@
package com.kamco.cd.kamcoback.common.service;
import com.kamco.cd.kamcoback.config.InferenceProperties;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Log4j2
// 0312
@RequiredArgsConstructor
@Component
public class ExternalJarRunner {
@Value("${spring.profiles.active}")
private String profile;
// 0312
private final InferenceProperties inferenceProperties;
private static final long TIMEOUT_MINUTES = TimeUnit.DAYS.toMinutes(3);
// java -jar build/libs/shp-exporter.jar --batch --geoserver.enabled=true
// --converter.inference-id=qq99999 --converter.batch-ids[0]=111
// 0312 shp 파일 배치를 통해 생성
public void run(String inferenceLearningId, List<Long> batchIds) {
// JAR 경로 (shape파일 생성용)
String jarPathV2 = inferenceProperties.getJarPathV2();
List<String> args = new ArrayList<>();
args.add(" --spring.profiles.active=" + profile);
args.add(" --batch");
args.add(" --geoserver.enabled=true");
args.add(" --converter.inference-id=" + inferenceLearningId);
batchIds.forEach(batchId -> args.add(" --converter.batch-ids[" + args.size() + "]=" + batchId));
execJar(jarPathV2, args);
}
/**
* shp 파일 생성
*
@@ -74,7 +96,8 @@ public class ExternalJarRunner {
cmd.add("-jar");
cmd.add(jarPath);
cmd.addAll(args);
// 0312
log.info("exec jar command: {}", cmd);
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(true);
@@ -104,6 +127,7 @@ public class ExternalJarRunner {
} catch (Exception e) {
log.error("jar execution error. output=\n{}", out, e);
throw new RuntimeException("jar execution error\n" + out, e);
}
}

View File

@@ -16,5 +16,7 @@ public class InferenceProperties {
private String batchUrl;
private String geojsonDir;
private String jarPath;
// 0312
private String jarPathV2;
private String inferenceServerName;
}

View File

@@ -71,6 +71,10 @@ public class SecurityConfig {
.requestMatchers("/api/test/review")
.hasAnyRole("ADMIN", "REVIEWER")
// shapefile 생성 테스트 API - 인증 없이 접근 가능
.requestMatchers("/api/test/make-shapefile")
.permitAll()
// ASYNC/ERROR 재디스패치는 막지 않기 (다운로드/스트리밍에서 필수)
.dispatcherTypeMatchers(DispatcherType.ASYNC, DispatcherType.ERROR)
.permitAll()

View File

@@ -14,9 +14,9 @@ public class GukYuinDto {
public enum GukYuinLinkFailCode implements EnumType {
OK("연동 가능"),
NOT_FOUND("대상 회차가 없습니다."),
SCOPE_PART_NOT_ALLOWED("부분 도엽은 연동 불가능 합니다."),
HAS_RUNNING_INFERENCE("라벨링 진행 중 회차가 있습니다."),
OTHER_GUKYUIN_IN_PROGRESS("국유in 연동 진행 중 회차가 있습니다.");
SCOPE_PART_NOT_ALLOWED("부분 도엽 추론 결과는 연동 할 수 없습니다."),
HAS_RUNNING_INFERENCE("라벨링 진행중 회차가 있습니다.\n진행중인 라벨링 작업을 종료하신 후 다시 연동해주세요."),
OTHER_GUKYUIN_IN_PROGRESS("국유in 연동 진행중입니다. 선행 연동 작업이 종료된 후 진행할 수 있습니다.");
private final String desc;
@@ -36,8 +36,9 @@ public class GukYuinDto {
public static class GukYuinLinkableRes {
private boolean linkable;
// private GukYuinLinkFailCode code;
private GukYuinLinkFailCode code;
private String message;
private UUID inferenceUuid;
}
// Repository가 반환할 Fact(조회 결과)
@@ -45,7 +46,8 @@ public class GukYuinDto {
boolean existsLearn,
boolean isPartScope,
boolean hasRunningInference,
boolean hasOtherUnfinishedGukYuin) {}
boolean hasOtherUnfinishedGukYuin,
UUID inferenceUuid) {}
@Getter
@Setter

View File

@@ -237,9 +237,12 @@ public class GukYuinApiService {
GukYuinLinkFailCode code = decideCode(f);
GukYuinLinkableRes res = new GukYuinLinkableRes();
// res.setCode(code);
res.setCode(code);
res.setLinkable(code == GukYuinLinkFailCode.OK);
res.setMessage(code.getDesc());
if (code == GukYuinLinkFailCode.HAS_RUNNING_INFERENCE) {
res.setInferenceUuid(f.inferenceUuid());
}
return res;
}

View File

@@ -8,6 +8,7 @@ import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceServerStatusDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceStatusDetailDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.ResultList;
import com.kamco.cd.kamcoback.inference.service.InferenceAsyncService;
import com.kamco.cd.kamcoback.inference.service.InferenceResultService;
import com.kamco.cd.kamcoback.log.dto.AuditLogDto;
import com.kamco.cd.kamcoback.log.dto.AuditLogDto.DownloadReq;
@@ -55,6 +56,7 @@ import org.springframework.web.bind.annotation.RestController;
public class InferenceResultApiController {
private final InferenceResultService inferenceResultService;
private final InferenceAsyncService inferenceAsyncService;
private final MapSheetMngService mapSheetMngService;
private final ModelMngService modelMngService;
private final RangeDownloadResponder rangeDownloadResponder;
@@ -176,7 +178,8 @@ public class InferenceResultApiController {
})
@DeleteMapping("/end")
public ApiResponseDto<UUID> getInferenceGeomList() {
UUID uuid = inferenceResultService.deleteInferenceEnd();
// UUID uuid = inferenceResultService.deleteInferenceEnd();
UUID uuid = inferenceAsyncService.asyncInferenceEnd();
return ApiResponseDto.ok(uuid);
}

View File

@@ -87,6 +87,7 @@ public class InferenceResultDto {
READY("대기"),
IN_PROGRESS("진행중"),
END("완료"),
END_FAIL("종료실패"),
FORCED_END("강제종료");
private final String desc;
@@ -274,11 +275,10 @@ public class InferenceResultDto {
private String mapSheetScope;
@Schema(description = "탐지 데이터 옵션 - 추론제외(EXCL), 이전 년도 도엽 사용(PREV)", example = "EXCL")
@NotBlank
@EnumValid(
enumClass = DetectOption.class,
message = "탐지 데이터 옵션은 '추론제외', '이전 년도 도엽 사용' 만 사용 가능합니다.")
private String detectOption;
// @EnumValid(
// enumClass = DetectOption.class,
// message = "탐지 데이터 옵션은 '추론제외', '이전 년도 도엽 사용' 만 사용 가능합니다.")
private DetectOption detectOption;
@Schema(description = "5k 도협 번호 목록", example = "[33605,33606, 33610, 34802, 35603, 35611]")
@NotNull
@@ -684,6 +684,7 @@ public class InferenceResultDto {
@NoArgsConstructor
@AllArgsConstructor
public static class MapSheetFallbackYearDto {
private String mapSheetNum;
private Integer mngYyyy;
}

View File

@@ -0,0 +1,117 @@
package com.kamco.cd.kamcoback.inference.service;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.common.inference.service.InferenceCommonService;
import com.kamco.cd.kamcoback.common.utils.UserUtil;
import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient;
import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient.ExternalCallResult;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SaveInferenceAiDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Status;
import com.kamco.cd.kamcoback.postgres.core.AuditLogCoreService;
import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService;
import com.kamco.cd.kamcoback.postgres.core.MapSheetMngCoreService;
import com.kamco.cd.kamcoback.postgres.core.ModelMngCoreService;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/** 추론 관리 */
@Service
@Slf4j
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class InferenceAsyncService {
private final InferenceResultCoreService inferenceResultCoreService;
private final MapSheetMngCoreService mapSheetMngCoreService;
private final ModelMngCoreService modelMngCoreService;
private final AuditLogCoreService auditLogCoreService;
private final InferenceCommonService inferenceCommonService;
private final ExternalHttpClient externalHttpClient;
private final UserUtil userUtil;
@Value("${inference.batch-url}")
private String batchUrl;
@Value("${inference.inference-server-name}")
private String inferenceServerName;
@Value("${file.dataset-dir}")
private String datasetDir;
@Value("${spring.profiles.active}")
private String activeEnv;
@Value("${inference.geojson-dir}")
private String inferenceDir;
// 0313
@Transactional
public UUID asyncInferenceEnd() {
SaveInferenceAiDto dto = inferenceResultCoreService.getProcessing();
if (dto == null) {
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND);
}
this.deleteInferenceEndAsync(dto); // 비동기 종료 호출
return dto.getUuid();
}
// 0313
@Async("inferenceEndExecutor")
@Transactional
public void deleteInferenceEndAsync(SaveInferenceAiDto dto) {
Long batchId = dto.getBatchId();
String url = batchUrl + "/" + batchId;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
try {
log.info("[추론 종료 비동기 시작] uuid={}, batchId={}", dto.getUuid(), batchId);
ExternalCallResult<String> result =
externalHttpClient.callLong(url, HttpMethod.DELETE, dto, headers, String.class);
if (!result.success()) {
log.error("[추론 종료 실패] 외부 API 호출 실패. uuid={}, batchId={}", dto.getUuid(), batchId);
SaveInferenceAiDto failRequest = new SaveInferenceAiDto();
failRequest.setUuid(dto.getUuid());
failRequest.setStatus(Status.END_FAIL.getId()); // TODO: 종료실패 상태 추가하는 게 맞는지?
failRequest.setUpdateUid(userUtil.getId());
failRequest.setInferEndDttm(ZonedDateTime.now());
inferenceResultCoreService.update(failRequest);
return;
}
SaveInferenceAiDto request = new SaveInferenceAiDto();
request.setStatus(Status.FORCED_END.getId());
request.setUuid(dto.getUuid());
request.setUpdateUid(userUtil.getId());
request.setInferEndDttm(ZonedDateTime.now());
inferenceResultCoreService.update(request);
Long learnId = inferenceResultCoreService.getInferenceLearnIdByUuid(dto.getUuid());
inferenceResultCoreService.upsertGeomData(learnId);
log.info("[추론 종료 비동기 완료] uuid={}, batchId={}", dto.getUuid(), batchId);
} catch (Exception e) {
log.error("[추론 종료 비동기 예외] uuid={}, batchId={}", dto.getUuid(), batchId, e);
}
}
}

View File

@@ -56,7 +56,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpHeaders;
@@ -68,7 +68,7 @@ import org.springframework.transaction.annotation.Transactional;
/** 추론 관리 */
@Service
@Log4j2
@Slf4j
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class InferenceResultService {
@@ -129,47 +129,39 @@ public class InferenceResultService {
@Transactional
public UUID run(InferenceResultDto.RegReq req) {
log.info("inference start request = {}", req);
if (req.getDetectOption().equals(DetectOption.EXCL.getId())) {
DetectOption detectOption = req.getDetectOption();
if (detectOption == DetectOption.EXCL) {
// 추론 제외 일때 EXCL
return runExcl(req);
}
// 이전연도 도엽 사용 일때 PREV
return runPrev(req);
}
/**
* 변화탐지 옵션 추론제외 실행
* 변화탐지 [옵션 추론제외 실행]
*
* @param req
* @return
*/
public UUID runExcl(InferenceResultDto.RegReq req) {
// TODO 쿼리로 한번에 할수 있게 수정해야하나..
// 기준연도 실행가능 도엽 조회
List<MngListDto> targetMngList =
mapSheetMngCoreService.getMapSheetMngHst(
req.getTargetYyyy(), req.getMapSheetScope(), req.getMapSheetNum());
// List<MngListDto> mngList =
// mapSheetMngCoreService.findExecutableSheets(
// req.getCompareYyyy(),
// req.getTargetYyyy(),
// req.getMapSheetScope(),
// req.getMapSheetNum());
mapSheetMngCoreService.getMapSheetMngHst(req.getTargetYyyy(), req.getMapSheetNum());
if (targetMngList == null || targetMngList.isEmpty()) {
throw new CustomApiException("NOT_FOUND_MAP_SHEET_NUM", HttpStatus.NOT_FOUND);
}
log.info("targetMngList size = {}", targetMngList.size());
// 비교연도 실행가능 도엽 조회
List<MngListDto> compareMngList =
mapSheetMngCoreService.getMapSheetMngHst(
req.getCompareYyyy(), req.getMapSheetScope(), req.getMapSheetNum());
mapSheetMngCoreService.getMapSheetMngHst(req.getCompareYyyy(), req.getMapSheetNum());
if (compareMngList == null || compareMngList.isEmpty()) {
throw new CustomApiException("NOT_FOUND_COMPARE_YEAR", HttpStatus.NOT_FOUND);
}
log.info("compareMngList size = {}", compareMngList.size());
// compare 도엽번호 Set 구성
Set<String> compareSet =
@@ -211,14 +203,14 @@ public class InferenceResultService {
log.info(
"""
===== MapSheet Year Comparison =====
target Total: {}
compare Total: {}
Intersection: {}
target Only (Excluded): {}
compare Only: {}
====================================
""",
===== MapSheet Year Comparison =====
target Total: {}
compare Total: {}
Intersection: {}
target Only (Excluded): {}
compare Only: {}
====================================
""",
targetTotal,
compareTotal,
intersection,
@@ -269,28 +261,31 @@ public class InferenceResultService {
*/
@Transactional
public UUID runPrev(InferenceResultDto.RegReq req) {
// TODO 쿼리로 한번에 할수 있게 수정해야하나..
// 기준연도 실행가능 도엽 조회
List<MngListDto> targetMngList =
mapSheetMngCoreService.getMapSheetMngHst(
req.getTargetYyyy(), req.getMapSheetScope(), req.getMapSheetNum());
Integer targetYyyy = req.getTargetYyyy();
Integer compareYyyy = req.getCompareYyyy();
String mapSheetScope = req.getMapSheetScope();
log.info("[{}|{}}] ,{}", compareYyyy, targetYyyy, mapSheetScope);
// 기준연도 실행가능 도엽 조회[AFTER]
List<MngListDto> targetMngList =
mapSheetMngCoreService.getMapSheetMngHst(targetYyyy, req.getMapSheetNum());
log.info("[runPrev] targetMngList size = {}", targetMngList.size());
if (targetMngList == null || targetMngList.isEmpty()) {
throw new CustomApiException("NOT_FOUND_TARGET_YEAR", HttpStatus.NOT_FOUND);
}
// 비교연도 실행가능 도엽 조회
List<MngListDto> compareMngList =
mapSheetMngCoreService.getMapSheetMngHst(
req.getCompareYyyy(), req.getMapSheetScope(), req.getMapSheetNum());
mapSheetMngCoreService.getMapSheetMngHst(compareYyyy, req.getMapSheetNum());
log.info("[runPrev] compareMngList size = {}", compareMngList.size());
if (compareMngList == null || compareMngList.isEmpty()) {
throw new CustomApiException("NOT_FOUND_COMPARE_YEAR", HttpStatus.NOT_FOUND);
}
log.info("targetMngList size = {}", targetMngList.size());
log.info("compareMngList size = {}", compareMngList.size());
log.info("Difference in count = {}", targetMngList.size() - compareMngList.size());
log.info("[runPrev] Difference in count = {}", targetMngList.size() - compareMngList.size());
// 로그용 원본 카운트 (이전도엽 추가 전)
int targetTotal = targetMngList.size();
@@ -311,14 +306,14 @@ public class InferenceResultService {
.filter(num -> !compareSet0.contains(num))
.toList();
log.info("targetOnlyMapSheetNums in count = {}", targetOnlyMapSheetNums.size());
log.info("[runPrev] targetOnlyMapSheetNums in count = {}", targetOnlyMapSheetNums.size());
// 이전연도 초회 추가
compareMngList.addAll(
mapSheetMngCoreService.findFallbackCompareYearByMapSheets(
req.getCompareYyyy(), targetOnlyMapSheetNums));
compareYyyy, targetOnlyMapSheetNums));
log.info("fallback compare size= {}", compareMngList.size());
log.info("[runPrev] fallback compare size= {}", compareMngList.size());
// 이전연도 추가 후 compare 총 개수
int compareTotalAfterFallback = compareMngList.size();
@@ -362,15 +357,15 @@ public class InferenceResultService {
log.info(
"""
===== MapSheet Year Comparison =====
target Total: {}
compare Total(before fallback): {}
compare Total(after fallback): {}
Intersection: {}
target Only (Excluded): {}
compare Only: {}
====================================
""",
===== MapSheet Year Comparison =====
target Total: {}
compare Total(before fallback): {}
compare Total(after fallback): {}
Intersection: {}
target Only (Excluded): {}
compare Only: {}
====================================
""",
targetTotal,
compareTotalBeforeFallback,
compareTotalAfterFallback,
@@ -385,18 +380,12 @@ public class InferenceResultService {
// compare 기준 geojson 생성
Scene compareScene =
getSceneInference(
compareMngList,
req.getCompareYyyy().toString(),
req.getMapSheetScope(),
req.getDetectOption());
compareMngList, compareYyyy.toString(), mapSheetScope, req.getDetectOption());
// target 기준 geojson 생성
Scene targetScene =
getSceneInference(
req.getTargetYyyy().toString(),
mapSheetNums,
req.getMapSheetScope(),
req.getDetectOption());
targetYyyy.toString(), mapSheetNums, mapSheetScope, req.getDetectOption());
log.info("비교년도 geojson 파일 validation ===== {}", compareScene.getFilePath());
GeoJsonValidator.validateWithRequested(compareScene.getFilePath(), mapSheetNums);
@@ -672,7 +661,7 @@ public class InferenceResultService {
* @return
*/
private Scene getSceneInference(
String yyyy, List<String> mapSheetNums, String mapSheetScope, String detectOption) {
String yyyy, List<String> mapSheetNums, String mapSheetScope, DetectOption detectOption) {
// geojson 생성시 필요한 영상파일 정보 조회
List<ImageFeature> features =
@@ -698,7 +687,7 @@ public class InferenceResultService {
* @return
*/
private Scene getSceneInference(
List<MngListDto> yearDtos, String yyyy, String mapSheetScope, String detectOption) {
List<MngListDto> yearDtos, String yyyy, String mapSheetScope, DetectOption detectOption) {
List<ImageFeature> features =
mapSheetMngCoreService.loadSceneInferenceByFallbackYears(yearDtos);
@@ -983,7 +972,10 @@ public class InferenceResultService {
* @return Scene
*/
private Scene writeSceneGeoJson(
String yyyy, String mapSheetScope, String detectOption, List<ImageFeature> sceneInference) {
String yyyy,
String mapSheetScope,
DetectOption detectOption,
List<ImageFeature> sceneInference) {
boolean isAll = MapSheetScope.ALL.getId().equals(mapSheetScope);
String optionSuffix = buildOptionSuffix(detectOption);
@@ -1032,9 +1024,13 @@ public class InferenceResultService {
* @param detectOption
* @return
*/
private String buildOptionSuffix(String detectOption) {
if (DetectOption.EXCL.getId().equals(detectOption)) return "_EXCL";
if (DetectOption.PREV.getId().equals(detectOption)) return "_PREV";
private String buildOptionSuffix(DetectOption detectOption) {
if (DetectOption.EXCL == detectOption) {
return "_EXCL";
}
if (DetectOption.PREV == detectOption) {
return "_PREV";
}
return "";
}
}

View File

@@ -157,6 +157,7 @@ public class LabelAllocateService {
return labelAllocateCoreService.findInferenceDetail(uuid);
}
@Transactional
public ApiResponseDto.ResponseObj allocateMove(
Integer totalCnt, String uuid, List<String> targetUsers, String userId) {

View File

@@ -169,6 +169,7 @@ public class AuthController {
if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) {
throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다.");
}
String username = jwtTokenProvider.getSubject(refreshToken);
// Redis에 저장된 RefreshToken과 일치하는지 확인

View File

@@ -47,7 +47,7 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
@@ -55,7 +55,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Log4j2
@Slf4j
@RequiredArgsConstructor
public class InferenceResultCoreService {
@@ -120,7 +120,7 @@ public class InferenceResultCoreService {
mapSheetLearnEntity.setCompareYyyy(req.getCompareYyyy());
mapSheetLearnEntity.setTargetYyyy(req.getTargetYyyy());
mapSheetLearnEntity.setMapSheetScope(req.getMapSheetScope());
mapSheetLearnEntity.setDetectOption(req.getDetectOption());
mapSheetLearnEntity.setDetectOption(req.getDetectOption().getId());
mapSheetLearnEntity.setCreatedUid(userUtil.getId());
mapSheetLearnEntity.setMapSheetCnt(mapSheetName);
mapSheetLearnEntity.setDetectingCnt(0L);

View File

@@ -345,16 +345,15 @@ public class MapSheetMngCoreService {
* 변화탐지 실행 가능 비교년도 조회
*
* @param mngYyyy 비교년도
* @param mapId 5k 도엽번호
* @param mapIds 5k 도엽번호
* @return List<MngListCompareDto>
*/
public List<MngListCompareDto> getByHstMapSheetCompareList(int mngYyyy, List<String> mapId) {
return mapSheetMngYearRepository.findByHstMapSheetCompareList(mngYyyy, mapId);
public List<MngListCompareDto> getByHstMapSheetCompareList(int mngYyyy, List<String> mapIds) {
return mapSheetMngYearRepository.findByHstMapSheetCompareList(mngYyyy, mapIds);
}
public List<MngListDto> getMapSheetMngHst(
Integer year, String mapSheetScope, List<String> mapSheetNum) {
return mapSheetMngRepository.getMapSheetMngHst(year, mapSheetScope, mapSheetNum);
public List<MngListDto> getMapSheetMngHst(Integer year, List<String> mapSheetNums50k) {
return mapSheetMngRepository.getMapSheetMngHst(year, mapSheetNums50k);
}
/**

View File

@@ -53,8 +53,6 @@ import lombok.NoArgsConstructor;
* system leveraging 1:5k map data.
*/
@Getter
// entity의 접근제어를 위해 @setter를 사용 x
// @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
// 영상관리이력
@@ -92,7 +90,7 @@ public class MapSheetMngHstEntity extends CommonDateEntity {
private Integer scaleRatio;
@Column(name = "data_state", length = 20)
private String dataState;
private String dataState; // DONE,NOTYET 둘중하나임 같은연도는 같은값
@Column(name = "data_state_dttm")
private ZonedDateTime dataStateDttm;
@@ -165,13 +163,4 @@ public class MapSheetMngHstEntity extends CommonDateEntity {
@Column(name = "upload_id")
private String uploadId;
// 파일정보 업데이트
public void updateFileInfos(Long tifSizeBytes, Long tfwSizeBytes) {
tifSizeBytes = tifSizeBytes == null ? 0L : tifSizeBytes;
tfwSizeBytes = tfwSizeBytes == null ? 0L : tfwSizeBytes;
this.tifSizeBytes = tifSizeBytes;
this.tfwSizeBytes = tfwSizeBytes;
this.totalSizeBytes = tifSizeBytes + tfwSizeBytes;
}
}

View File

@@ -23,11 +23,12 @@ public class InferenceResultsTestingRepositoryImpl
.select(inferenceResultsTestingEntity)
.from(inferenceResultsTestingEntity)
.where(
inferenceResultsTestingEntity
.batchId
.in(batchIds)
.and(inferenceResultsTestingEntity.afterC.isNotNull())
.and(inferenceResultsTestingEntity.afterP.isNotNull()))
inferenceResultsTestingEntity.batchId.in(batchIds),
inferenceResultsTestingEntity.afterC.isNotNull(),
inferenceResultsTestingEntity.afterP.isNotNull(),
inferenceResultsTestingEntity.beforeC.isNotNull(),
inferenceResultsTestingEntity.beforeP.isNotNull(),
inferenceResultsTestingEntity.probability.isNotNull())
.fetch();
}
@@ -44,7 +45,10 @@ public class InferenceResultsTestingRepositoryImpl
.where(
inferenceResultsTestingEntity.batchId.in(batchIds),
inferenceResultsTestingEntity.afterC.isNotNull(),
inferenceResultsTestingEntity.afterP.isNotNull())
inferenceResultsTestingEntity.afterP.isNotNull(),
inferenceResultsTestingEntity.beforeC.isNotNull(),
inferenceResultsTestingEntity.beforeP.isNotNull(),
inferenceResultsTestingEntity.probability.isNotNull())
.fetchOne();
return cnt == null ? 0L : cnt;
@@ -62,11 +66,12 @@ public class InferenceResultsTestingRepositoryImpl
inferenceResultsTestingEntity.afterYear.max()))
.from(inferenceResultsTestingEntity)
.where(
inferenceResultsTestingEntity
.batchId
.in(batchIds)
.and(inferenceResultsTestingEntity.afterC.isNotNull())
.and(inferenceResultsTestingEntity.afterP.isNotNull()))
inferenceResultsTestingEntity.batchId.in(batchIds),
inferenceResultsTestingEntity.afterC.isNotNull(),
inferenceResultsTestingEntity.afterP.isNotNull(),
inferenceResultsTestingEntity.beforeC.isNotNull(),
inferenceResultsTestingEntity.beforeP.isNotNull(),
inferenceResultsTestingEntity.probability.isNotNull())
.groupBy(
inferenceResultsTestingEntity.batchId,
inferenceResultsTestingEntity.modelVersion,

View File

@@ -519,7 +519,7 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
.fetchOne();
if (learn == null) {
return new GukYuinLinkFacts(false, false, false, false);
return new GukYuinLinkFacts(false, false, false, false, null);
}
// 부분 도엽 실행인지 확인
@@ -529,19 +529,21 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
QMapSheetLearnEntity learn2 = new QMapSheetLearnEntity("learn2");
QMapSheetLearnEntity learnQ = QMapSheetLearnEntity.mapSheetLearnEntity;
// 실행중인 추론 있는지 확인
boolean hasRunningInference =
// 현재 국유인 연동하려는 추론의 비교년도,기준년도와 같은 회차 중, 할당되거나 진행중인 학습데이터 uuid 조회
// ex. 2022-2023년도 9회차 학습데이터 제작 진행중 -> 10회차 연동하려고 할 시, 먼저 9회차를 종료해야 함
UUID runningInferenceUuid =
queryFactory
.selectOne()
.from(inf)
.join(learn2)
.on(inf.learnId.eq(learn2.id))
.where(
learn2.compareYyyy.eq(learn.getCompareYyyy()),
learn2.targetYyyy.eq(learn.getTargetYyyy()),
inf.analState.in("ASSIGNED", "ING"))
.fetchFirst()
!= null;
.select(inf.uuid)
.from(inf)
.join(learn2)
.on(inf.learnId.eq(learn2.id))
.where(
learn2.compareYyyy.eq(learn.getCompareYyyy()),
learn2.targetYyyy.eq(learn.getTargetYyyy()),
inf.analState.in("ASSIGNED", "ING"))
.fetchFirst();
boolean hasRunningInference = runningInferenceUuid != null;
// 국유인 작업 진행중 있는지 확인
boolean hasOtherUnfinishedGukYuin =
@@ -556,6 +558,7 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
.fetchFirst()
!= null;
return new GukYuinLinkFacts(true, isPartScope, hasRunningInference, hasOtherUnfinishedGukYuin);
return new GukYuinLinkFacts(
true, isPartScope, hasRunningInference, hasOtherUnfinishedGukYuin, runningInferenceUuid);
}
}

View File

@@ -141,12 +141,13 @@ public interface MapSheetMngRepositoryCustom {
void insertMapSheetMngTile(@Valid AddReq addReq);
/**
* 연도 조건으로 도엽번호 조회
* 연도별 도엽 목록 조회
*
* @param year 연도
* @return 추론 가능한 도엽 정보
* @param year 관리연도
* @param mapSheetNums50k 50k 도엽번호 리스트 (null 또는 empty인 경우 전체 조회)
* @return 도엽 목록
*/
List<MngListDto> getMapSheetMngHst(Integer year, String mapSheetScope, List<String> mapSheetNum);
List<MngListDto> getMapSheetMngHst(Integer year, List<String> mapSheetNums50k);
/**
* 비교연도 사용 가능한 이전도엽을 조회한다.

View File

@@ -1,5 +1,6 @@
package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx50kEntity.mapInkx50kEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngEntity.mapSheetMngEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngFileEntity.mapSheetMngFileEntity;
@@ -10,12 +11,12 @@ import static com.querydsl.core.types.dsl.Expressions.nullExpression;
import com.kamco.cd.kamcoback.common.enums.CommonUseStatus;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetScope;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.AddReq;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.YearSearchReq;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapInkx50kEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngFileEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity;
@@ -27,7 +28,6 @@ import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberExpression;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
@@ -50,7 +50,6 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
implements MapSheetMngRepositoryCustom {
private final JPAQueryFactory queryFactory;
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
@PersistenceContext private EntityManager em;
@@ -1084,11 +1083,27 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
mapSheetMngHstEntity.mapSheetNum));
}
/**
* 영상데이터관리 > 연도별 도엽 목록 조회
*
* @param year 관리연도
* @param mapSheetNums50k 50k 도엽번호 리스트 (null 또는 empty인 경우 전체 조회)
* @return 도엽 목록
*/
@Override
public List<MngListDto> getMapSheetMngHst(
Integer year, String mapSheetScope, List<String> mapSheetNum) {
public List<MngListDto> getMapSheetMngHst(Integer year, List<String> mapSheetNums50k) {
/*
검색조건:
- ✅ 데이터 처리 완료(data_state='DONE')
- ✅ 동기화 완료(sync_state='DONE' OR sync_check_state='DONE')
- ✅ 추론 사용(use_inference='USE')
- ✅ 지정 연도(mng_yyyy=year)
- ✅ 완료된 TIF 파일 존재
- ✅ 사용 중인 도엽만(mapInkx5k.useInference='USE')
- ✅ 50k 도엽번호로 필터링 (mapSheetNums50k가 있는 경우)
*/
BooleanBuilder whereBuilder = new BooleanBuilder();
whereBuilder.and(mapSheetMngHstEntity.mngYyyy.eq(year));
whereBuilder.and(mapSheetMngHstEntity.dataState.eq("DONE"));
whereBuilder.and(
mapSheetMngHstEntity
@@ -1097,39 +1112,20 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.or(mapSheetMngHstEntity.syncCheckState.eq("DONE")));
whereBuilder.and(mapSheetMngHstEntity.useInference.eq("USE"));
whereBuilder.and(mapSheetMngHstEntity.mngYyyy.eq(year));
// TIF 파일 존재 여부 확인
whereBuilder.and(
JPAExpressions.selectOne()
.from(mapSheetMngFileEntity)
.where(
mapSheetMngFileEntity
.hstUid
.eq(mapSheetMngHstEntity.hstUid) // FK 관계에 맞게 유지
.eq(mapSheetMngHstEntity.hstUid)
.and(mapSheetMngHstEntity.mngYyyy.eq(year))
.and(mapSheetMngFileEntity.fileExt.eq("tif"))
.and(mapSheetMngFileEntity.fileState.eq("DONE")))
.and(mapSheetMngFileEntity.fileState.eq("DONE"))
.and(mapSheetMngFileEntity.fileDel.eq(false)))
.exists());
BooleanBuilder likeBuilder = new BooleanBuilder();
if (MapSheetScope.PART.getId().equals(mapSheetScope)) {
List<String> list = mapSheetNum;
if (list == null || list.isEmpty()) {
return List.of();
}
for (String prefix : list) {
if (prefix == null || prefix.isBlank()) {
continue;
}
likeBuilder.or(mapSheetMngHstEntity.mapSheetNum.like(prefix.trim() + "%"));
}
}
if (likeBuilder.hasValue()) {
whereBuilder.and(likeBuilder);
}
return queryFactory
.select(
Projections.constructor(
@@ -1146,7 +1142,8 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.mapidcdNo
.eq(mapSheetMngHstEntity.mapSheetNum)
.and(mapInkx5kEntity.useInference.eq(CommonUseStatus.USE)))
.where(whereBuilder)
.innerJoin(mapInkx5kEntity.mapInkx50k, mapInkx50kEntity)
.where(whereBuilder, inScenes50(mapInkx50kEntity, mapSheetNums50k))
.fetch();
}
@@ -1178,7 +1175,12 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
h.syncState.eq("DONE").or(h.syncCheckState.eq("DONE")),
JPAExpressions.selectOne()
.from(f)
.where(f.hstUid.eq(h.hstUid), f.fileExt.eq("tif"), f.fileState.eq("DONE"))
.where(
f.hstUid.eq(h.hstUid),
f.mngYyyy.eq(year),
f.fileExt.eq("tif"),
f.fileState.eq("DONE"),
f.fileDel.eq(false))
.exists(),
// mapSheetNum별 최대 mngYyyy인 행만 남김
@@ -1195,9 +1197,19 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.from(f2)
.where(
f2.hstUid.eq(h2.hstUid),
f2.mngYyyy.eq(year),
f2.fileExt.eq("tif"),
f2.fileState.eq("DONE"))
f2.fileState.eq("DONE"),
f2.fileDel.eq(false))
.exists())))
.fetch();
}
// 도엽번호(1:50k) IN 쿼리조건
private BooleanExpression inScenes50(QMapInkx50kEntity mapInkx50k, List<String> sceneIds) {
if (sceneIds == null || sceneIds.isEmpty()) {
return null;
}
return mapInkx50k.mapidcdNo.in(sceneIds);
}
}

View File

@@ -88,17 +88,33 @@ public class MapSheetMngYearRepositoryImpl implements MapSheetMngYearRepositoryC
*/
@Override
public List<MngListCompareDto> findByHstMapSheetCompareList(int mngYyyy, List<String> mapIds) {
QMapSheetMngYearYnEntity y = QMapSheetMngYearYnEntity.mapSheetMngYearYnEntity;
QMapSheetMngYearYnEntity mapSheetMngYearYn = QMapSheetMngYearYnEntity.mapSheetMngYearYnEntity;
// SELECT
// concat(?, '') as col_0_0_, -- 파라미터 mngYyyy (문자열)
// m.map_sheet_num as col_1_0_, -- 도엽번호
// MAX(m.mng_yyyy) as col_2_0_ -- 최대 관리연도
// FROM tb_map_sheet_mng_year_yn m
// WHERE m.map_sheet_num IN (?, ?, ..., ?) -- mapIds 리스트
// AND m.yn = 'Y' -- 파일 존재 여부
// AND m.mng_yyyy <= ? -- 기준연도 이하만
// GROUP BY m.map_sheet_num
StringExpression mngYyyyStr = Expressions.stringTemplate("concat({0}, '')", mngYyyy);
return queryFactory
.select(
Projections.constructor(
MngListCompareDto.class, mngYyyyStr, y.id.mapSheetNum, y.id.mngYyyy.max()))
.from(y)
.where(y.id.mapSheetNum.in(mapIds), y.yn.eq("Y"), y.id.mngYyyy.loe(mngYyyy))
.groupBy(y.id.mapSheetNum)
MngListCompareDto.class,
mngYyyyStr,
mapSheetMngYearYn.id.mapSheetNum,
mapSheetMngYearYn.id.mngYyyy.max()))
.from(mapSheetMngYearYn)
.where(
mapSheetMngYearYn.id.mapSheetNum.in(mapIds),
mapSheetMngYearYn.yn.eq("Y"),
mapSheetMngYearYn.id.mngYyyy.loe(mngYyyy))
.groupBy(mapSheetMngYearYn.id.mapSheetNum)
.fetch();
}

View File

@@ -21,6 +21,17 @@ public class AsyncConfig {
return ex;
}
@Bean(name = "makeShapeFile")
public Executor makeShapeFileExecutor() {
ThreadPoolTaskExecutor ex = new ThreadPoolTaskExecutor();
ex.setCorePoolSize(2);
ex.setMaxPoolSize(4);
ex.setQueueCapacity(50);
ex.setThreadNamePrefix("makeShapeFile-");
ex.initialize();
return ex;
}
@Bean(name = "auditLogExecutor")
public Executor auditLogExecutor() {
ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
@@ -31,4 +42,16 @@ public class AsyncConfig {
exec.initialize();
return exec;
}
// 0313
@Bean(name = "inferenceEndExecutor")
public Executor inferenceEndExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("inference-async-");
executor.initialize();
return executor;
}
}

View File

@@ -255,8 +255,9 @@ public class MapSheetInferenceJobService {
// 추론 종료일때 shp 파일 생성
String batchIdStr = batchIds.stream().map(String::valueOf).collect(Collectors.joining(","));
// shp 파일 비동기 생성
shpPipelineService.runPipeline(jarPath, datasetDir, batchIdStr, sheet.getUid());
// 0312 shp 파일 비동기 생성 (바꿔주세요)
shpPipelineService.makeShapeFile(sheet.getUid(), batchIds);
// shpPipelineService.runPipeline(jarPath, datasetDir, batchIdStr, sheet.getUid());
}
/**

View File

@@ -3,7 +3,7 @@ package com.kamco.cd.kamcoback.scheduler.service;
import com.kamco.cd.kamcoback.common.service.ExternalJarRunner;
import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService;
import com.kamco.cd.kamcoback.scheduler.config.ShpKeyLock;
import java.nio.file.Paths;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.scheduling.annotation.Async;
@@ -18,11 +18,40 @@ public class ShpPipelineService {
private final ExternalJarRunner externalJarRunner;
private final ShpKeyLock shpKeyLock;
// 0312 shp 파일 비동기 생성
@Async("makeShapeFile")
public void makeShapeFile(String inferenceId, List<Long> batchIds) {
if (!shpKeyLock.tryLock(inferenceId)) {
log.info("");
log.info("============================================================");
log.info("SHP pipeline already running. inferenceId={}", inferenceId);
log.info("============================================================");
try {
log.info("SHP pipeline already start. inferenceId={}", inferenceId);
externalJarRunner.run(inferenceId, batchIds);
} catch (Exception e) {
log.error("SHP pipeline failed. inferenceId={}", inferenceId, e);
// TODO 실패 상태 업데이트 로직 추가
} finally {
log.info("============================================================");
log.info("SHP pipeline DONE. inferenceId={}", inferenceId);
log.info("============================================================");
log.info("");
shpKeyLock.unlock(inferenceId);
}
}
}
/**
* shp 파일 생성 1. merge 생성 2. 생성된 merge shp 파일로 geoserver 등록, 3.도엽별로 shp 생성
*
* @param jarPath 실행 jar 파일 경로
* @param datasetDir shp 파일이 생성될 경로
* @param batchIds 추론 batch id = 12,13,14
* @param inferenceId 추론 uid 32자
*/
@Async("shpExecutor")
public void runPipeline(String jarPath, String datasetDir, String batchIds, String inferenceId) {
//
// batchIds.split(",")
// inferenceResultCoreService.getInferenceResultCnt();
// inferenceId 기준 동시 실행 제한
if (!shpKeyLock.tryLock(inferenceId)) {
@@ -31,24 +60,33 @@ public class ShpPipelineService {
}
try {
log.info("");
log.info("============================================================");
log.info("SHP pipeline started. inferenceId={}", inferenceId);
log.info("============================================================");
// uid 기준 merge shp, geojson 파일 생성
externalJarRunner.run(jarPath, batchIds, inferenceId, "", "MERGED");
// uid 기준 shp 파일 geoserver 등록
String register =
Paths.get(datasetDir, inferenceId, "merge", inferenceId + ".shp").toString();
log.info("register={}", register);
externalJarRunner.run(jarPath, register, inferenceId);
// uid 기준 도엽별 shp, geojson 파일 생성
externalJarRunner.run(jarPath, batchIds, inferenceId, "", "RESOLVE");
log.info("SHP pipeline finished. inferenceId={}", inferenceId);
// String register =
// Paths.get(datasetDir, inferenceId, "merge", inferenceId + ".shp").toString();
// log.info("register={}", register);
// externalJarRunner.run(jarPath, register, inferenceId);
//
// // uid 기준 도엽별 shp, geojson 파일 생성
// externalJarRunner.run(jarPath, batchIds, inferenceId, "", "RESOLVE");
//
// log.info("SHP pipeline finished. inferenceId={}", inferenceId);
} catch (Exception e) {
log.error("SHP pipeline failed. inferenceId={}", inferenceId, e);
// TODO 실패 상태 업데이트 로직 추가
} finally {
log.info("============================================================");
log.info("SHP pipeline DONE. inferenceId={}", inferenceId);
log.info("============================================================");
shpKeyLock.unlock(inferenceId);
}
}

View File

@@ -0,0 +1,50 @@
package com.kamco.cd.kamcoback.test;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.scheduler.service.ShpPipelineService;
import io.swagger.v3.oas.annotations.Operation;
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 java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.web.ErrorResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "test shape api", description = "test shape api")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/test")
public class TestShapeApiController {
private final ShpPipelineService shpPipelineService;
@Operation(
summary = "shapefile 생성 테스트",
description = "지정된 inference ID와 batch ID 목록으로 shapefile을 생성합니다.")
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "shapefile 생성 요청 성공",
content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(
responseCode = "400",
description = "잘못된 요청 데이터",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(
responseCode = "500",
description = "서버 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@GetMapping("/make-shapefile")
public ApiResponseDto<String> makeShapeFile(
@RequestParam String inferenceId, @RequestParam List<Long> batchIds) {
shpPipelineService.makeShapeFile(inferenceId, batchIds);
return ApiResponseDto.ok("Shapefile 생성이 시작되었습니다. inferenceId: " + inferenceId);
}
}

View File

@@ -59,8 +59,8 @@ jwt:
secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b"
access-token-validity-in-ms: 86400000 # 1일
refresh-token-validity-in-ms: 604800000 # 7일
#access-token-validity-in-ms: 60000 # 1
#refresh-token-validity-in-ms: 300000 # 5
#access-token-validity-in-ms: 300000 # 5
#refresh-token-validity-in-ms: 600000 # 10
token:
refresh-cookie-name: kamco-dev # 개발용 쿠키 이름
@@ -100,6 +100,7 @@ inference:
url: http://192.168.2.183:8000/jobs
batch-url: http://192.168.2.183:8000/batches
jar-path: ${inference.nfs}/repo/jar/shp-exporter.jar
jar-path-v2: ${inference.nfs}/repo/jar/shp-exporter-v2.jar
inference-server-name: server1,server2,server3,server4
output-dir: ${inference.nfs}/model_output/export

View File

@@ -78,6 +78,7 @@ inference:
url: http://10.100.0.11:8000/jobs
batch-url: http://10.100.0.11:8000/batches
jar-path: jar/shp-exporter.jar
jar-path-v2: jar/shp-exporter-v2.jar
inference-server-name: server1,server2,server3,server4
output-dir: ${inference.nfs}/model_output/export

View File

@@ -95,6 +95,8 @@ inference:
url: http://172.16.4.56:8000/jobs
batch-url: http://172.16.4.56:8000/batches
jar-path: ${inference.nfs}/repo/jar/shp-exporter.jar
# //0312
jar-path-v2: ${inference.nfs}/repo/jar/shp-exporter-v2.jar
inference-server-name: server1,server2,server3,server4
output-dir: ${inference.nfs}/model_output/export

View File

@@ -88,3 +88,6 @@ inference:
nfs: /kamco-nfs
geojson-dir: ${inference.nfs}/requests/ # 추론실행을 위한 파일생성경로
jar-path: ${inference.nfs}/repo/jar/shp-exporter.jar
# //0312
jar-path-v2: ${inference.nfs}/repo/jar/shp-exporter-v2.jar