68 Commits

Author SHA1 Message Date
5a82a9507a Merge pull request 'unfit 업데이트 수정' (#127) from feat/infer_dev_260211 into develop
Reviewed-on: #127
2026-03-04 13:21:37 +09:00
b9a268d43a unfit 업데이트 수정 2026-03-04 13:20:55 +09:00
4d1ea53d8f 변화탐지 pnu로도 조회 가능하게 하기 2026-03-04 08:04:04 +09:00
278d5d20e8 restTemplate read timeout, connect timeout 늘리기 2026-03-03 17:07:19 +09:00
4d9fa2d6a5 좌표계 5179 추가 2026-03-03 15:03:44 +09:00
e28e5ba371 국유인 반영 파일 경로 수정 2026-03-03 14:23:24 +09:00
22cc996399 Merge pull request 'feat/infer_dev_260211' (#126) from feat/infer_dev_260211 into develop
Reviewed-on: #126
2026-02-28 00:34:35 +09:00
e32f5e33d9 추론 기본 정보에 상태 추가 2026-02-28 00:34:03 +09:00
f95b82bf7f 스케줄링에 log 추가 완료 2026-02-28 00:23:48 +09:00
060a0db126 Merge pull request 'feat/infer_dev_260211' (#125) from feat/infer_dev_260211 into develop
Reviewed-on: #125
2026-02-27 22:53:07 +09:00
0692456b5b 학습데이터제작 > 작업현황 > 폴리곤 수 추가 2026-02-27 22:52:45 +09:00
8fb9f89c8f 국유인 실태조사 적합여부 임의로 업데이트 API 2026-02-27 22:15:31 +09:00
1cfe6e923f Merge remote-tracking branch 'origin/feat/infer_dev_260211' into feat/infer_dev_260211 2026-02-27 21:04:24 +09:00
af2721949c shp 생성 컨트롤러 이동, 주석추가 2026-02-27 21:04:18 +09:00
e9b9075c72 국유인 실태조사 job 에 log 찍기 추가 2026-02-27 20:37:56 +09:00
2b28241110 Merge pull request '국유인 연동 전 파일 경로 확인 log 추가' (#124) from feat/infer_dev_260211 into develop
Reviewed-on: #124
2026-02-27 19:41:26 +09:00
8e002f5673 국유인 연동 전 파일 경로 확인 log 추가 2026-02-27 19:40:10 +09:00
972cf808a9 Merge pull request '학습데이터 관리 목록 순서 변경, 주석추가' (#123) from feat/infer_dev_260211 into develop
Reviewed-on: #123
2026-02-27 18:54:29 +09:00
086eb20e8d 학습데이터 관리 목록 순서 변경, 주석추가 2026-02-27 18:54:03 +09:00
c3c194c067 Merge pull request '국유인 실태조사 bulk 확인' (#122) from feat/infer_dev_260211 into develop
Reviewed-on: #122
2026-02-27 17:02:22 +09:00
0acaeaac09 국유인 실태조사 bulk 확인 2026-02-27 17:01:54 +09:00
53ce735bca Merge pull request '국유인 체크 url 수정' (#121) from feat/infer_dev_260211 into develop
Reviewed-on: #121
2026-02-27 16:24:11 +09:00
a341be7ed6 국유인 체크 url 수정 2026-02-27 16:23:50 +09:00
9f6dc2b3c6 Merge pull request '국유인 연동 url 수정' (#120) from feat/infer_dev_260211 into develop
Reviewed-on: #120
2026-02-27 16:01:32 +09:00
cc46315e3a 국유인 연동 url 수정 2026-02-27 16:01:05 +09:00
ca631d5d58 Merge pull request '변화지도 도엽 목록에 도엽별bbox 추가' (#119) from feat/infer_dev_260211 into develop
Reviewed-on: #119
2026-02-27 15:31:52 +09:00
5d0590bd3c 변화지도 도엽 목록에 도엽별bbox 추가 2026-02-27 15:31:33 +09:00
3aed3cf1ec Merge pull request 'feat/infer_dev_260211' (#118) from feat/infer_dev_260211 into develop
Reviewed-on: #118
2026-02-27 15:14:37 +09:00
396e76c362 국유인 연동 outputdir 경로 변경 2026-02-27 15:14:18 +09:00
9f379c6dc3 영상관리, 도엽관리 API만 우선 정리함 2026-02-27 14:07:44 +09:00
d069981c8f 패스워드 변경 권한 수정 2026-02-27 14:01:54 +09:00
ece70f1d68 Merge remote-tracking branch 'origin/feat/infer_dev_260211' into feat/infer_dev_260211 2026-02-27 14:01:21 +09:00
b7470d11d4 추론 주석 추가 2026-02-27 14:01:14 +09:00
a7108c44f4 Merge pull request '비밀번호 변경 security 설정 수정' (#117) from feat/infer_dev_260211 into develop
Reviewed-on: #117
2026-02-27 14:00:11 +09:00
5039dd0f51 비밀번호 변경 security 설정 수정 2026-02-27 13:59:47 +09:00
9cb3a100aa Merge pull request '주석 추가, 패스워드변경 권한 수정' (#116) from feat/infer_dev_260211 into develop
Reviewed-on: #116
2026-02-27 13:41:32 +09:00
dc42baf91a 주석 추가, 패스워드변경 권한 수정 2026-02-27 13:41:17 +09:00
9d36208845 Merge pull request '국유인 반영 부분 막기 해제' (#115) from feat/infer_dev_260211 into develop
Reviewed-on: #115
2026-02-27 11:41:57 +09:00
ae4c1c61e8 국유인 반영 부분 막기 해제 2026-02-27 11:40:59 +09:00
369f303f6c Merge pull request 'feat/infer_dev_260211' (#114) from feat/infer_dev_260211 into develop
Reviewed-on: #114
2026-02-27 10:09:04 +09:00
96d4bb4af3 주석 제거 2026-02-27 10:08:39 +09:00
7b55204ae1 추론 api 호출시 대용량이면 3초 timeout 문제
60초 long restTemplate 추가
2026-02-27 10:06:39 +09:00
f54655c191 추론 수정 2026-02-27 09:25:53 +09:00
f4cbb48aa2 Merge pull request '추론 수정' (#113) from feat/infer_dev_260211 into develop
Reviewed-on: #113
2026-02-27 09:15:56 +09:00
fbad8d1cd3 추론 수정 2026-02-27 09:15:38 +09:00
6e4682dad6 Merge pull request '추론 수정' (#112) from feat/infer_dev_260211 into develop
Reviewed-on: #112
2026-02-27 08:29:22 +09:00
4629715443 추론 수정 2026-02-27 08:28:59 +09:00
5ba7f9ccfc Merge pull request '추론 수정' (#111) from feat/infer_dev_260211 into develop
Reviewed-on: #111
2026-02-27 00:03:26 +09:00
264dae3ba9 추론 수정 2026-02-27 00:03:03 +09:00
fab5b02211 Merge pull request '추론 수정' (#110) from feat/infer_dev_260211 into develop
Reviewed-on: #110
2026-02-26 23:55:53 +09:00
38f70b933f 추론 수정 2026-02-26 23:55:29 +09:00
a4b1db462b Merge pull request '추론 수정' (#109) from feat/infer_dev_260211 into develop
Reviewed-on: #109
2026-02-26 23:42:09 +09:00
2bf7c42a3f 추론 수정 2026-02-26 23:41:49 +09:00
a1be4e9faf Merge pull request '추론 수정' (#108) from feat/infer_dev_260211 into develop
Reviewed-on: #108
2026-02-26 23:18:50 +09:00
8904de0e3d 추론 수정 2026-02-26 23:18:31 +09:00
a21df9d018 중복 경고 2026-02-26 18:38:56 +09:00
85cad2dd28 중복 경고 2026-02-26 18:34:29 +09:00
7b8bf8726b 중복 경고 2026-02-26 18:29:03 +09:00
c841d460aa 중복 경고 2026-02-26 18:26:40 +09:00
3a8ac3a24f Merge pull request 'ensureAccepted 함수 InferenceCommonService 생성후 공통으로 생성' (#107) from feat/infer_dev_260211 into develop
Reviewed-on: #107
2026-02-26 16:56:04 +09:00
046f4f06d3 기준년도 없을때 throw 추가 2026-02-26 16:53:29 +09:00
5c9f33d210 Merge branch 'develop' of https://kamco.git.gs.dabeeo.com/MVPTeam/kamco-cd-api into feat/infer_dev_260211 2026-02-26 16:31:33 +09:00
ea7e98d28e ensureAccepted 함수 InferenceCommonService 생성후 공통으로 생성 2026-02-26 16:30:52 +09:00
3e78f744a4 Merge pull request 'shp파일 a 링크로 다운로드할때 이력 저장 변경' (#106) from feat/infer_dev_260211 into develop
Reviewed-on: #106
2026-02-26 15:27:17 +09:00
cea1f01ed9 shp파일 a 링크로 다운로드할때 이력 저장 변경 2026-02-26 15:26:21 +09:00
d7f2d22b93 Merge branch 'develop' of https://kamco.git.gs.dabeeo.com/MVPTeam/kamco-cd-api into feat/infer_dev_260211 2026-02-26 15:25:21 +09:00
eccdfb17e6 shp파일 a 링크로 다운로드할때 이력 저장 변경 2026-02-26 15:25:10 +09:00
d2fa86a89f Merge pull request 'merge develop_add_log' (#105) from feat/dean/add_log into develop
Reviewed-on: #105
2026-02-26 13:22:05 +09:00
76 changed files with 2374 additions and 1106 deletions

47
inference-table-index.sh Normal file
View File

@@ -0,0 +1,47 @@
#!/bin/bash
#############################################
# PostgreSQL INDEX CREATE SCRIPT
# 에러 발생해도 계속 진행
#############################################
# set -e 제거 (중단 안함)
# ===== 환경변수 체크 =====
if [ -z "$DB_HOST" ] || [ -z "$DB_PORT" ] || [ -z "$DB_NAME" ] || [ -z "$DB_USER" ]; then
echo "DB 환경변수가 설정되지 않았습니다."
exit 1
fi
echo "========================================"
echo "START INDEX CREATE"
echo "TIME: $(date)"
echo "========================================"
run_index() {
echo "----------------------------------------"
echo "Running: $1"
psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -c "$1"
echo "----------------------------------------"
}
run_index "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_files_uid_ext_state
ON tb_map_sheet_mng_files (hst_uid, file_ext, file_state);"
run_index "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_mng_files_hstuid_ext
ON tb_map_sheet_mng_files (hst_uid, file_ext);"
run_index "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_inkx_no_use
ON tb_map_inkx_5k (mapidcd_no, use_inference);"
run_index "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_inkx5k_mapidcd
ON tb_map_inkx_5k (mapidcd_no);"
run_index "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_hst_exec_sheetnum_yyyy_desc
ON tb_map_sheet_mng_hst (map_sheet_num, mng_yyyy DESC);"
echo "========================================"
echo "END INDEX CREATE"
echo "TIME: $(date)"
echo "========================================"
echo "모든 인덱스 시도 완료"

View File

@@ -19,9 +19,9 @@ import org.springframework.web.filter.OncePerRequestFilter;
public class JwtAuthenticationFilter extends OncePerRequestFilter { public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
// JWT 필터를 타지 않게 할 URL 패턴들
private static final String[] EXCLUDE_PATHS = { private static final String[] EXCLUDE_PATHS = {
// "/api/auth/signin", "/api/auth/refresh", "/api/auth/logout", "/api/members/*/password" "/api/auth/signin", "/api/auth/refresh", "/api/auth/logout", "/api/members/*/password"
"/api/auth/signin", "/api/auth/refresh", "/api/auth/logout"
}; };
private final JwtTokenProvider jwtTokenProvider; private final JwtTokenProvider jwtTokenProvider;
private final UserDetailsService userDetailsService; private final UserDetailsService userDetailsService;
@@ -31,8 +31,10 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException { throws ServletException, IOException {
// HTTP 요청 헤더에서 JWT 토큰 꺼내기
String token = resolveToken(request); String token = resolveToken(request);
// JWT 토큰을 검증하고, 인증된 사용자로 SecurityContext에 등록
if (token != null && jwtTokenProvider.isValidToken(token)) { if (token != null && jwtTokenProvider.isValidToken(token)) {
String username = jwtTokenProvider.getSubject(token); String username = jwtTokenProvider.getSubject(token);
@@ -58,7 +60,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
return false; return false;
} }
// /api/members/{memberId}/password // HTTP 요청 헤더에서 JWT 토큰 꺼내기
private String resolveToken(HttpServletRequest request) { private String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization"); String bearer = request.getHeader("Authorization");
if (bearer != null && bearer.startsWith("Bearer ")) { if (bearer != null && bearer.startsWith("Bearer ")) {

View File

@@ -11,6 +11,7 @@ import javax.crypto.SecretKey;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** 토큰 생성 */
@Component @Component
public class JwtTokenProvider { public class JwtTokenProvider {
@@ -31,10 +32,12 @@ public class JwtTokenProvider {
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
} }
// Access Token 생성
public String createAccessToken(String subject) { public String createAccessToken(String subject) {
return createToken(subject, accessTokenValidityInMs); return createToken(subject, accessTokenValidityInMs);
} }
// Refresh Token 생성
public String createRefreshToken(String subject) { public String createRefreshToken(String subject) {
return createToken(subject, refreshTokenValidityInMs); return createToken(subject, refreshTokenValidityInMs);
} }

View File

@@ -6,6 +6,7 @@ import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations; import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** redis token handler */
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class RefreshTokenService { public class RefreshTokenService {
@@ -13,16 +14,33 @@ public class RefreshTokenService {
private final StringRedisTemplate redisTemplate; private final StringRedisTemplate redisTemplate;
private static final String PREFIX = "RT:"; private static final String PREFIX = "RT:";
/**
* Refresh Token 저장
*
* @param username 사용자 식별값 (보통 username or userId)
* @param refreshToken 발급된 Refresh Token
* @param ttlMillis 토큰 만료 시간 (밀리초 단위)
*/
public void save(String username, String refreshToken, long ttlMillis) { public void save(String username, String refreshToken, long ttlMillis) {
ValueOperations<String, String> ops = redisTemplate.opsForValue(); ValueOperations<String, String> ops = redisTemplate.opsForValue();
ops.set(PREFIX + username, refreshToken, Duration.ofMillis(ttlMillis)); ops.set(PREFIX + username, refreshToken, Duration.ofMillis(ttlMillis));
} }
/**
* Refresh Token 검증
*
* <p>1. Redis에 저장된 값 조회 2. 클라이언트가 보낸 refreshToken과 비교 3. 동일하면 true
*/
public boolean validate(String username, String refreshToken) { public boolean validate(String username, String refreshToken) {
String stored = redisTemplate.opsForValue().get(PREFIX + username); String stored = redisTemplate.opsForValue().get(PREFIX + username);
return stored != null && stored.equals(refreshToken); return stored != null && stored.equals(refreshToken);
} }
/**
* Refresh Token 삭제
*
* <p>로그아웃 시 호출 Redis에서 해당 사용자 토큰 제거
*/
public void delete(String username) { public void delete(String username) {
redisTemplate.delete(PREFIX + username); redisTemplate.delete(PREFIX + username);
} }

View File

@@ -163,13 +163,13 @@ public class ChangeDetectionApiController {
@RequestParam @RequestParam
String chnDtctId, String chnDtctId,
@Parameter(description = "polygon 32자 uid", example = "3B1A7E5F895A4D9698489540EE1BBE1E") @Parameter(description = "polygon 32자 uid", example = "3B1A7E5F895A4D9698489540EE1BBE1E")
@RequestParam @RequestParam(required = false)
String cdObjectId, String cdObjectId,
@Parameter( @Parameter(
description = "polygon 32자 uids", description = "polygon 32자 uids",
example = example =
"3B1A7E5F895A4D9698489540EE1BBE1E,3B221A2AF9614647A0903A972D56C574,3B22686A7ACE44FC9CB20F1B4FA6DEFD,3B376D94A183479BB5FBE3D7166E6E1A") "3B1A7E5F895A4D9698489540EE1BBE1E,3B221A2AF9614647A0903A972D56C574,3B22686A7ACE44FC9CB20F1B4FA6DEFD,3B376D94A183479BB5FBE3D7166E6E1A")
@RequestParam @RequestParam(required = false)
List<String> cdObjectIds, List<String> cdObjectIds,
@Parameter(description = "pnu") @RequestParam(required = false) String pnu) { @Parameter(description = "pnu") @RequestParam(required = false) String pnu) {
return ApiResponseDto.ok( return ApiResponseDto.ok(
@@ -183,13 +183,13 @@ public class ChangeDetectionApiController {
@RequestParam @RequestParam
String chnDtctId, String chnDtctId,
@Parameter(description = "polygon 32자 uid", example = "3B1A7E5F895A4D9698489540EE1BBE1E") @Parameter(description = "polygon 32자 uid", example = "3B1A7E5F895A4D9698489540EE1BBE1E")
@RequestParam @RequestParam(required = false)
String cdObjectId, String cdObjectId,
@Parameter( @Parameter(
description = "polygon 32자 uids", description = "polygon 32자 uids",
example = example =
"3B1A7E5F895A4D9698489540EE1BBE1E,3B221A2AF9614647A0903A972D56C574,3B22686A7ACE44FC9CB20F1B4FA6DEFD,3B376D94A183479BB5FBE3D7166E6E1A") "3B1A7E5F895A4D9698489540EE1BBE1E,3B221A2AF9614647A0903A972D56C574,3B22686A7ACE44FC9CB20F1B4FA6DEFD,3B376D94A183479BB5FBE3D7166E6E1A")
@RequestParam @RequestParam(required = false)
List<String> cdObjectIds, List<String> cdObjectIds,
@Parameter(description = "pnu") @RequestParam(required = false) String pnu) { @Parameter(description = "pnu") @RequestParam(required = false) String pnu) {
return ApiResponseDto.ok( return ApiResponseDto.ok(

View File

@@ -1,6 +1,9 @@
package com.kamco.cd.kamcoback.changedetection.dto; package com.kamco.cd.kamcoback.changedetection.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose; import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
import com.kamco.cd.kamcoback.common.utils.enums.EnumType; import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
@@ -143,6 +146,23 @@ public class ChangeDetectionDto {
private String mapSheetNum; private String mapSheetNum;
private String mapSheetName; private String mapSheetName;
private String alias; private String alias;
@JsonIgnore private String bboxStr;
private JsonNode bbox;
public MapSheetList(String mapSheetNum, String mapSheetName, String alias, String bboxStr) {
this.mapSheetNum = mapSheetNum;
this.mapSheetName = mapSheetName;
this.alias = alias;
if (bboxStr != null) {
ObjectMapper mapper = new ObjectMapper();
try {
this.bbox = mapper.readTree(bboxStr);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
} }
@Schema(name = "PolygonFeatureList", description = "Geometry 리턴 객체") @Schema(name = "PolygonFeatureList", description = "Geometry 리턴 객체")
@@ -262,6 +282,7 @@ public class ChangeDetectionDto {
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class ChangeDetectionMapDto { public static class ChangeDetectionMapDto {
private Integer compareYyyy; private Integer compareYyyy;
private Integer targetYyyy; private Integer targetYyyy;
private String cdObjectId; private String cdObjectId;

View File

@@ -101,7 +101,7 @@ public class ChangeDetectionService {
*/ */
public ChangeDetectionDto.PolygonFeatureList getPolygonListByCd( public ChangeDetectionDto.PolygonFeatureList getPolygonListByCd(
String chnDtctId, String cdObjectId, List<String> cdObjectIds, String pnu) { String chnDtctId, String cdObjectId, List<String> cdObjectIds, String pnu) {
return changeDetectionCoreService.getPolygonListByCd(chnDtctId, cdObjectId, cdObjectIds); return changeDetectionCoreService.getPolygonListByCd(chnDtctId, cdObjectId, cdObjectIds, pnu);
} }
/** /**
@@ -115,7 +115,7 @@ public class ChangeDetectionService {
*/ */
public ChangeDetectionDto.PointFeatureList getPointListByCd( public ChangeDetectionDto.PointFeatureList getPointListByCd(
String chnDtctId, String cdObjectId, List<String> cdObjectIds, String pnu) { String chnDtctId, String cdObjectId, List<String> cdObjectIds, String pnu) {
return changeDetectionCoreService.getPointListByCd(chnDtctId, cdObjectId, cdObjectIds); return changeDetectionCoreService.getPointListByCd(chnDtctId, cdObjectId, cdObjectIds, pnu);
} }
/** /**

View File

@@ -5,7 +5,9 @@ import com.kamco.cd.kamcoback.common.download.dto.DownloadAuditEvent;
import com.kamco.cd.kamcoback.menu.dto.MenuDto; import com.kamco.cd.kamcoback.menu.dto.MenuDto;
import com.kamco.cd.kamcoback.menu.service.MenuService; import com.kamco.cd.kamcoback.menu.service.MenuService;
import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity; import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity;
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
import com.kamco.cd.kamcoback.postgres.repository.log.AuditLogRepository; import com.kamco.cd.kamcoback.postgres.repository.log.AuditLogRepository;
import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository;
import java.util.Comparator; import java.util.Comparator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@@ -24,6 +26,7 @@ import org.springframework.transaction.annotation.Transactional;
public class DownloadAuditEventListener { public class DownloadAuditEventListener {
private final AuditLogRepository auditLogRepository; private final AuditLogRepository auditLogRepository;
private final MembersRepository membersRepository;
private final MenuService menuService; private final MenuService menuService;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
@@ -42,9 +45,23 @@ public class DownloadAuditEventListener {
return; return;
} }
Long userId = ev.userId();
if (userId == null) {
// a 링크로 들어온 download는 사번으로 파라미터가 전달 되므로 사번으로 user id 조회 하기
MemberEntity memberEntity =
membersRepository.findByEmployeeNo(ev.employeeNo()).orElse(null);
if (memberEntity == null) {
return; // 매핑 실패 시 로그 저장 안 함
}
userId = memberEntity.getId();
}
AuditLogEntity logEntity = AuditLogEntity logEntity =
AuditLogEntity.forFileDownload( AuditLogEntity.forFileDownload(
ev.userId(), ev.requestUri(), menuUid, ev.ip(), ev.status(), ev.downloadUuid()); userId, ev.requestUri(), menuUid, ev.ip(), ev.status(), ev.downloadUuid());
auditLogRepository.save(logEntity); auditLogRepository.save(logEntity);

View File

@@ -14,23 +14,56 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/**
* Range(부분 다운로드) 지원 파일 다운로드 응답 생성기.
*
* <p>브라우저/다운로드 매니저가 Range 헤더를 보내면 206 Partial Content로 일부 구간만 내려주고, Range 헤더가 없으면 200 OK로 전체 파일을
* 내려준다.
*
* <p>대용량 ZIP(또는 바이너리) 파일 다운로드 시: - 메모리에 파일 전체를 올리지 않고(Resource/FileSystemResource 스트리밍) -
* 이어받기(Resume) 및 병렬 다운로드(일부 클라이언트) 지원 - 잘못된 Range에 대해 416 Range Not Satisfiable 처리
*/
@Component @Component
public class RangeDownloadResponder { public class RangeDownloadResponder {
/**
* ZIP(또는 바이너리) 파일 다운로드 응답을 생성한다.
*
* @param filePath 실제 서버 파일 경로
* @param downloadFileName 사용자에게 노출될 다운로드 파일명
* @param request Range 헤더 확인용 HttpServletRequest
* @return Range 유무에 따라 200(전체) 또는 206(부분) ResponseEntity 반환
* @throws IOException 파일 접근/조회 실패 시
*/
public ResponseEntity<?> buildZipResponse( public ResponseEntity<?> buildZipResponse(
Path filePath, String downloadFileName, HttpServletRequest request) throws IOException { Path filePath, String downloadFileName, HttpServletRequest request) throws IOException {
// 1) 파일 존재/정상 파일 여부 확인
// - 일반 파일(regular file)이 아니면 404 반환 (디렉토리/없는 파일/특수 파일 등 방지)
if (!Files.isRegularFile(filePath)) { if (!Files.isRegularFile(filePath)) {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} }
// 2) 파일 전체 크기 및 Spring Resource 래핑
// - Files.size: 전체 파일 크기(Content-Range 계산/검증에 필요)
// - FileSystemResource: 스프링이 스트리밍 형태로 파일을 응답 바디로 내려줄 수 있게 함
long totalSize = Files.size(filePath); long totalSize = Files.size(filePath);
Resource resource = new FileSystemResource(filePath); Resource resource = new FileSystemResource(filePath);
// 3) 다운로드 강제(Content-Disposition)
// - attachment; filename="xxx.zip" 형태로 브라우저가 저장 대화상자/다운로드로 처리
String disposition = "attachment; filename=\"" + downloadFileName + "\""; String disposition = "attachment; filename=\"" + downloadFileName + "\"";
// 4) Range 헤더 조회
// - Range: bytes=0-1023 (일부 구간 요청)
// - Range가 없으면 전체 다운로드로 처리
String rangeHeader = request.getHeader(HttpHeaders.RANGE); String rangeHeader = request.getHeader(HttpHeaders.RANGE);
// 🔥 공통 헤더 (여기 고정) // 5) 공통 헤더(전체/부분 다운로드 공통으로 넣을 것)
// - Content-Type: 바이너리(필요시 application/zip 으로 바꿔도 됨)
// - Content-Disposition: 다운로드 강제
// - Accept-Ranges: bytes -> 서버가 Range(이어받기/부분요청) 지원함을 알림
// - X-Accel-Buffering: no -> Nginx 사용 시 버퍼링 비활성화(스트리밍/대용량에 유리)
ResponseEntity.BodyBuilder base = ResponseEntity.BodyBuilder base =
ResponseEntity.ok() ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM) .contentType(MediaType.APPLICATION_OCTET_STREAM)
@@ -38,10 +71,15 @@ public class RangeDownloadResponder {
.header(HttpHeaders.ACCEPT_RANGES, "bytes") .header(HttpHeaders.ACCEPT_RANGES, "bytes")
.header("X-Accel-Buffering", "no"); .header("X-Accel-Buffering", "no");
// 6) Range 헤더가 없으면 전체 파일 반환 (200 OK)
if (rangeHeader == null || rangeHeader.isBlank()) { if (rangeHeader == null || rangeHeader.isBlank()) {
// Content-Length를 전체 크기로 설정하고 Resource를 그대로 바디에 담아 스트리밍
return base.contentLength(totalSize).body(resource); return base.contentLength(totalSize).body(resource);
} }
// 7) Range 헤더 파싱
// - 잘못된 Range 헤더 형식이면 parseRanges에서 IllegalArgumentException 발생 가능
// - RFC에 따라 416 응답 + Content-Range: bytes */{total} 형태로 알려줌
List<HttpRange> ranges; List<HttpRange> ranges;
try { try {
ranges = HttpRange.parseRanges(rangeHeader); ranges = HttpRange.parseRanges(rangeHeader);
@@ -52,11 +90,18 @@ public class RangeDownloadResponder {
.build(); .build();
} }
// 8) 다중 Range 요청이 와도(예: bytes=0-99,200-299) 여기서는 첫 번째 Range만 처리
// - 대부분 브라우저는 단일 Range를 사용
// - 멀티파트/byteranges 처리를 하려면 별도 구현 필요
HttpRange range = ranges.get(0); HttpRange range = ranges.get(0);
// 9) 실제 시작/끝 범위 계산
// - bytes=500- : end를 파일 끝으로 해석
// - bytes=-500 : 마지막 500바이트로 해석
long start = range.getRangeStart(totalSize); long start = range.getRangeStart(totalSize);
long end = range.getRangeEnd(totalSize); long end = range.getRangeEnd(totalSize);
// 10) 시작점이 파일 크기 이상이면 만족 불가 -> 416
if (start >= totalSize) { if (start >= totalSize) {
return ResponseEntity.status(416) return ResponseEntity.status(416)
.header(HttpHeaders.CONTENT_RANGE, "bytes */" + totalSize) .header(HttpHeaders.CONTENT_RANGE, "bytes */" + totalSize)
@@ -64,9 +109,18 @@ public class RangeDownloadResponder {
.build(); .build();
} }
// 11) 요청 구간 길이 계산
// - end/start는 inclusive이므로 +1 필요
long regionLength = end - start + 1; long regionLength = end - start + 1;
// 12) ResourceRegion 생성
// - resource의 start부터 regionLength 만큼만 응답으로 내려줄 수 있게 함
// - 파일 전체를 메모리에 올리지 않고 필요한 부분만 스트리밍
ResourceRegion region = new ResourceRegion(resource, start, regionLength); ResourceRegion region = new ResourceRegion(resource, start, regionLength);
// 13) 206 Partial Content로 응답 구성
// - Content-Range: bytes start-end/total
// - Content-Length: regionLength(부분 크기)
return ResponseEntity.status(206) return ResponseEntity.status(206)
.contentType(MediaType.APPLICATION_OCTET_STREAM) .contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, disposition) .header(HttpHeaders.CONTENT_DISPOSITION, disposition)

View File

@@ -4,6 +4,7 @@ import java.util.UUID;
public record DownloadAuditEvent( public record DownloadAuditEvent(
Long userId, Long userId,
String employeeNo,
String requestUri, String requestUri,
String normalizedUri, String normalizedUri,
String ip, String ip,

View File

@@ -11,7 +11,8 @@ import lombok.Getter;
public enum CrsType implements EnumType { public enum CrsType implements EnumType {
EPSG_3857("Web Mercator, 웹지도 미터(EPSG:900913 동일)"), EPSG_3857("Web Mercator, 웹지도 미터(EPSG:900913 동일)"),
EPSG_4326("WGS84 위경도, GeoJSON/OSM 기본"), EPSG_4326("WGS84 위경도, GeoJSON/OSM 기본"),
EPSG_5186("Korea 2000 중부 TM, 한국 SHP"); EPSG_5186("5186::Korea 2000 중부 TM, 한국 SHP"),
EPSG_5179("5179::Korea 2000 중부 TM, 한국 SHP");
private final String desc; private final String desc;

View File

@@ -0,0 +1,111 @@
package com.kamco.cd.kamcoback.common.inference.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient;
import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient.ExternalCallResult;
import com.kamco.cd.kamcoback.inference.dto.InferenceSendDto;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Log4j2
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class InferenceCommonService {
@Value("${spring.profiles.active}")
private String profile;
@Value("${inference.url}")
private String inferenceUrl;
private final ObjectMapper objectMapper;
private final ExternalHttpClient externalHttpClient;
/**
* 추론 AI API 호출 batch id를 리턴
*
* @param dto
* @return
*/
public Long ensureAccepted(InferenceSendDto dto) {
if (dto == null) {
log.warn("not InferenceSendDto dto");
throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST);
}
log.info("");
log.info("========================================================");
log.info("[SEND INFERENCE] Inference request dto= {}", dto);
log.info("========================================================");
log.info("");
// 1) 요청 로그
try {
log.debug("Inference request dto={}", objectMapper.writeValueAsString(dto));
} catch (JsonProcessingException e) {
log.warn("Failed to serialize inference dto", e);
}
// 2) local 환경 임시 처리
// if ("local".equals(profile)) {
// if (dto.getPred_requests_areas() == null) {
// throw new IllegalStateException("pred_requests_areas is null");
// }
//
// dto.getPred_requests_areas().setInput1_scene_path("/kamco-nfs/requests/2023_local.geojson");
//
// dto.getPred_requests_areas().setInput2_scene_path("/kamco-nfs/requests/2024_local.geojson");
// }
// 3) HTTP 호출
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
// 4) 추론 실행 API 호출
ExternalCallResult<String> result =
externalHttpClient.callLong(inferenceUrl, HttpMethod.POST, dto, headers, String.class);
if (result.statusCode() < 200 || result.statusCode() >= 300) {
log.error("Inference API failed. status={}, body={}", result.statusCode(), result.body());
throw new CustomApiException("BAD_GATEWAY", HttpStatus.BAD_GATEWAY);
}
// 5) 응답 파싱
try {
List<Map<String, Object>> list =
objectMapper.readValue(result.body(), new TypeReference<>() {});
if (list.isEmpty()) {
throw new CustomApiException(
"NOT_FOUND", HttpStatus.NOT_FOUND, "Inference response is empty");
}
Object batchIdObj = list.get(0).get("batch_id");
if (batchIdObj == null) {
throw new CustomApiException(
"NOT_FOUND", HttpStatus.NOT_FOUND, "batch_id not found in response");
}
return Long.valueOf(batchIdObj.toString());
} catch (Exception e) {
log.error("Failed to parse inference response. body={}", result.body(), e);
throw new CustomApiException("INVALID_INFERENCE_RESPONSE", HttpStatus.BAD_GATEWAY);
}
}
}

View File

@@ -0,0 +1,264 @@
package com.kamco.cd.kamcoback.common.inference.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
/**
* GeoJSON 파일의 "features[].properties.scene_id" 값들이 "요청한 도엽번호 목록(requestedMapSheetNums)"과 정확히 일치하는지
* 검증하는 유틸.
*
* <p>핵심 목적: - 요청한 도엽번호를 기반으로 GeoJSON을 생성했는데, 실제 결과 파일에 누락/추가/중복/빈값(scene_id 없음) 등이 발생했는지 빠르게 잡아내기.
*
* <p>검증 실패 시: - 404: 파일 자체가 없음 - 400: 파일이 비어있거나(0 byte), features 구조가 이상하거나, 요청 목록이 비어있음 - 500: 파일
* IO/파싱 자체가 실패(읽기 실패 등) - 422: 정합성(요청 vs 결과)이 맞지 않음 (누락/추가/중복/빈 scene_id 존재)
*/
public class GeoJsonValidator {
/** GeoJSON 파싱용 ObjectMapper (정적 1개로 재사용) */
private static final ObjectMapper om = new ObjectMapper();
/** 로그 출력용 */
private static final Logger log = LogManager.getLogger(GeoJsonValidator.class);
/**
* @param geojsonPath GeoJSON 파일 경로(문자열)
* @param requestedMapSheetNums "요청한 도엽번호" 리스트 (중복/공백/NULL 포함 가능)
* <p>동작 개요: 1) 파일 존재/크기 검증 2) 요청 도엽번호 목록 정리(Trim + 공백 제거 + 중복 제거) 3) GeoJSON 파싱 후 features 배열
* 확보 4) features에서 scene_id 추출하여 유니크 set 구성 5) requested vs found 비교: - missing: requested -
* found - extra : found - requested - duplicates: GeoJSON 내부에서 scene_id 중복 등장 - nullIdCount:
* scene_id가 null/blank 인 feature 개수 6) 이상 있으면 422로 실패 처리
*/
public static void validateWithRequested(String geojsonPath, List<String> requestedMapSheetNums) {
// 문자열 경로를 Path로 변환 (Files API 사용 목적)
Path path = Path.of(geojsonPath);
// =========================================================
// 1) 파일 기본 검증
// - 파일이 존재하는지
// - 파일 크기가 0인지(비어있으면 생성 실패/오류 가능성)
// =========================================================
try {
// 파일 존재 여부 체크 (없으면 404)
if (!Files.exists(path)) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "GeoJSON 파일이 존재하지 않습니다: " + geojsonPath);
}
// 파일 사이즈 체크 (0 byte면 400)
if (Files.size(path) == 0) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "GeoJSON 파일이 비어있습니다: " + geojsonPath);
}
} catch (IOException e) {
// 파일 사이즈/상태 확인 중 IO 오류면 서버오류로 처리
log.error("GeoJSON 파일 상태 확인 실패: path={}", path, e);
throw new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR, "GeoJSON 파일 상태 확인 실패: " + geojsonPath, e);
}
// =========================================================
// 2) 요청 도엽 리스트 유효성 검증
// - 요청 목록 자체가 null/empty면 검증할 기준이 없으므로 400
// =========================================================
if (requestedMapSheetNums == null || requestedMapSheetNums.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "requestedMapSheetNums 가 비어있습니다.");
}
// =========================================================
// 2-1) 요청 도엽 Set 정리 (중복/공백/NULL 제거)
// - null 제거
// - trim 적용
// - 빈 문자열 제거
// - LinkedHashSet 사용: "중복 제거 + 원래 입력 순서 유지"
// =========================================================
Set<String> requested =
requestedMapSheetNums.stream()
.filter(Objects::nonNull) // null 제거
.map(String::trim) // 앞뒤 공백 제거
.filter(s -> !s.isEmpty()) // "" 제거
.collect(Collectors.toCollection(LinkedHashSet::new)); // 중복 제거 + 순서 유지
// 정리 결과가 비어있으면(전부 null/공백)이므로 400
if (requested.isEmpty()) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "requestedMapSheetNums 가 공백/NULL만 포함합니다.");
}
// =========================================================
// 3) GeoJSON 파싱
// 기대 구조:
// {
// "type": "FeatureCollection",
// "features": [ ... ]
// }
//
// - features가 없거나 배열이 아니면 "유효하지 않은 GeoJSON" (400)
// - 파일 읽기/파싱 IO 문제는 500
// - JSON 자체가 깨진 경우는 400
// =========================================================
final JsonNode features;
try {
// JSON 파일을 트리 형태로 파싱
JsonNode root = om.readTree(path.toFile());
// GeoJSON FeatureCollection의 핵심은 features 배열
features = root.get("features");
// features가 없거나 배열이 아니면 GeoJSON 구조가 이상한 것
if (features == null || !features.isArray()) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "유효하지 않은 GeoJSON: features가 없거나 배열이 아닙니다.");
}
} catch (ResponseStatusException e) {
// 위에서 직접 던진 에러는 그대로 전달
throw e;
} catch (IOException e) {
// 읽기/파싱 과정에서 IO 문제가 터지면 서버오류
log.error("GeoJSON 파일 읽기/파싱 실패: path={}", path, e);
throw new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR, "GeoJSON 파일 읽기/파싱 실패: " + geojsonPath, e);
} catch (Exception e) {
// JSON 문법 오류/예상치 못한 파싱 오류는 클라이언트 입력/파일 자체 문제로 400 처리
log.error("GeoJSON 파싱 오류(비정상 JSON): path={}", path, e);
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "GeoJSON 파싱 오류(비정상 JSON): " + geojsonPath, e);
}
// =========================================================
// 4) 검증 로직
// - featureCount: 전체 feature 수 (중복 포함)
// - foundUnique: GeoJSON에 등장한 유니크 scene_id 집합
// - duplicates: GeoJSON 내부에서 scene_id가 중복된 목록(샘플 출력용)
// - nullIdCount: scene_id가 없거나 빈 값인 feature 개수
// =========================================================
int featureCount = features.size();
// 유니크 scene_id를 담는 Set (중복 판단을 위해 add 결과를 사용)
Set<String> foundUnique = new HashSet<>();
// 중복된 scene_id 목록 (샘플 로그 출력용이라 순서 유지 가능한 LinkedHashSet 사용)
Set<String> duplicates = new LinkedHashSet<>();
// scene_id가 null 또는 blank인 feature의 개수 (데이터 이상)
int nullIdCount = 0;
// ---------------------------------------------------------
// features를 돌면서 feature.properties.scene_id를 추출한다.
//
// 기대 구조(일반적):
// features[i] = {
// "type": "Feature",
// "properties": {
// "scene_id": "도엽번호"
// },
// "geometry": {...}
// }
// ---------------------------------------------------------
for (JsonNode feature : features) {
JsonNode props = feature.get("properties");
// properties가 있고 scene_id가 null이 아니면 텍스트로 읽음
// 없으면 null 처리
String sceneId =
(props != null && props.hasNonNull("scene_id")) ? props.get("scene_id").asText() : null;
// scene_id가 없거나 빈값이면 "정상적으로 도엽번호가 들어오지 않은 feature"로 카운트
if (sceneId == null || sceneId.isBlank()) {
nullIdCount++; // 도엽번호가 없으면 증가
continue;
}
// foundUnique.add(sceneId)가 false면 "이미 같은 값이 있었다"는 뜻 => 중복
if (!foundUnique.add(sceneId)) {
duplicates.add(sceneId);
}
}
// =========================================================
// 4-1) requested vs found 비교(set 차집합)
//
// missing = requested - found
// : 요청은 했는데 결과 GeoJSON에 없는 도엽번호
//
// extra = found - requested
// : 요청하지 않았는데 결과 GeoJSON에 들어간 도엽번호
// =========================================================
// missing: requested를 복사한 뒤(foundUnique에 있는 값들을 제거) => 남은 것이 누락분
Set<String> missing = new LinkedHashSet<>(requested);
missing.removeAll(foundUnique);
// extra: foundUnique를 복사한 뒤(requested에 있는 값들을 제거) => 남은 것이 추가분
Set<String> extra = new LinkedHashSet<>(foundUnique);
extra.removeAll(requested);
// =========================================================
// 5) 로그 출력
// - 운영에서 문제 생겼을 때 "요청 vs 생성 결과"를 한 눈에 보게
// - sample 로그는 너무 길어질 수 있으므로 limit 걸어줌
// =========================================================
log.info(
"""
===== GeoJSON Validation =====
file: {}
features(total): {}
requested(unique): {}
found(unique scene_id): {}
scene_id null/blank: {}
duplicates(scene_id): {}
missing(requested - found): {}
extra(found - requested): {}
==============================
""",
geojsonPath,
featureCount, // 중복 포함한 전체 feature 수
requested.size(), // 요청 도엽 유니크 수
foundUnique.size(), // GeoJSON에서 발견된 scene_id 유니크 수
nullIdCount, // scene_id가 비어있는 feature 수
duplicates.size(), // 중복 scene_id 종류 수
missing.size(), // 요청했지만 빠진 도엽 수
extra.size()); // 요청하지 않았는데 들어온 도엽 수
// 중복/누락/추가 항목은 전체를 다 찍으면 로그 폭발하므로 샘플만
if (!duplicates.isEmpty())
log.warn("duplicates sample: {}", duplicates.stream().limit(20).toList());
if (!missing.isEmpty()) log.warn("missing sample: {}", missing.stream().limit(50).toList());
if (!extra.isEmpty()) log.warn("extra sample: {}", extra.stream().limit(50).toList());
// =========================================================
// 6) 실패 조건 판정
//
// 아래 중 하나라도 있으면 "요청 대비 결과 정합성이 깨졌다"로 보고 실패 처리(422):
// - missing 존재: 요청했는데 결과에 없음
// - extra 존재 : 요청 안했는데 결과에 있음
// - duplicates 존재: 동일 도엽이 중복 생성됨
// - nullIdCount > 0: scene_id가 비어있는 feature가 있음(데이터 이상)
//
// 422(Unprocessable Entity):
// - 요청 문법은 맞지만(파일은 있고 JSON도 읽힘),
// 내용(정합성)이 요구사항을 만족하지 못하는 경우에 적합.
// =========================================================
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(), duplicates.size(), nullIdCount));
}
// 모든 조건을 통과하면 정상
log.info("GeoJSON validation OK");
}
}

View File

@@ -13,6 +13,7 @@ import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
/** 파일 다운로드 log 저장 */
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -30,9 +31,17 @@ public class FileDownloadInteceptor implements HandlerInterceptor {
if (request.getDispatcherType() != DispatcherType.REQUEST) return; if (request.getDispatcherType() != DispatcherType.REQUEST) return;
Long userId; Long userId;
String employeeNo = "";
try { try {
// a 링크 다운로드일경우 userId가 없으므로 전달받은 사번을 넣는다
userId = userUtil.getId(); userId = userUtil.getId();
if (userId == null) return; // userId null 불가면 스킵 if (userId == null) {
employeeNo = request.getParameter("employeeNo");
if (employeeNo == null) {
return;
}
}
} catch (Exception e) { } catch (Exception e) {
log.warn("Download audit userId resolve failed. uri={}, err={}", uri, e.toString()); log.warn("Download audit userId resolve failed. uri={}, err={}", uri, e.toString());
return; return;
@@ -48,8 +57,9 @@ public class FileDownloadInteceptor implements HandlerInterceptor {
return; // downloadUuid null 불가 -> 스킵 return; // downloadUuid null 불가 -> 스킵
} }
// log저장 DownloadAuditEventListener 클래스 호출
publisher.publishEvent( publisher.publishEvent(
new DownloadAuditEvent(userId, uri, normalizedUri, ip, status, downloadUuid)); new DownloadAuditEvent(userId, employeeNo, uri, normalizedUri, ip, status, downloadUuid));
} }
private UUID extractUuidFromUri(String uri) { private UUID extractUuidFromUri(String uri) {

View File

@@ -24,10 +24,12 @@ public class FileProperties {
private String ptPath; private String ptPath;
private String datasetResponse; private String datasetResponse;
private TrainingData trainingData; private TrainingData trainingData;
private String outputDir;
@Getter @Getter
@Setter @Setter
public static class TrainingData { public static class TrainingData {
private String geojsonDir; private String geojsonDir;
} }
} }

View File

@@ -77,7 +77,7 @@ public class SecurityConfig {
// 다운로드는 인증 필요 // 다운로드는 인증 필요
.requestMatchers(HttpMethod.GET, DownloadPaths.PATTERNS) .requestMatchers(HttpMethod.GET, DownloadPaths.PATTERNS)
.authenticated() .permitAll()
// 메뉴 등록 ADMIN만 가능 // 메뉴 등록 ADMIN만 가능
.requestMatchers(HttpMethod.POST, "/api/menu/auth") .requestMatchers(HttpMethod.POST, "/api/menu/auth")
@@ -102,17 +102,16 @@ public class SecurityConfig {
"/api/upload/file-chunk-upload", "/api/upload/file-chunk-upload",
"/api/upload/chunk-upload-complete", "/api/upload/chunk-upload-complete",
"/api/change-detection/**", "/api/change-detection/**",
"/api/members/*/password",
"/api/layer/map/**", "/api/layer/map/**",
"/api/layer/tile-url", "/api/layer/tile-url",
"/api/layer/tile-url-year", "/api/layer/tile-url-year",
"/api/common-code/clazz", "/api/common-code/clazz")
"/api/inference/download/**")
.permitAll() .permitAll()
// 로그인한 사용자만 가능 IAM // 로그인한 사용자만 가능 IAM
.requestMatchers( .requestMatchers(
"/api/user/**", "/api/user/**",
"/api/my/menus", "/api/my/menus",
"/api/members/*/password",
"/api/training-data/label/**", "/api/training-data/label/**",
"/api/training-data/review/**") "/api/training-data/review/**")
.authenticated() .authenticated()

View File

@@ -91,6 +91,7 @@ public class StartupLogger {
│ Dataset Dir : %s │ Dataset Dir : %s
│ Model Dir : %s │ Model Dir : %s
│ PT Path : %s │ PT Path : %s
│ Output Dir : %s
╠════════════════════════════════════════════════════════════════════════════════╣ ╠════════════════════════════════════════════════════════════════════════════════╣
║ INFERENCE CONFIGURATION ║ ║ INFERENCE CONFIGURATION ║
╠────────────────────────────────────────────────────────────────────────────────╣ ╠────────────────────────────────────────────────────────────────────────────────╣
@@ -118,6 +119,7 @@ public class StartupLogger {
fileProperties.getDatasetDir() != null ? fileProperties.getDatasetDir() : "N/A", fileProperties.getDatasetDir() != null ? fileProperties.getDatasetDir() : "N/A",
fileProperties.getModelDir() != null ? fileProperties.getModelDir() : "N/A", fileProperties.getModelDir() != null ? fileProperties.getModelDir() : "N/A",
fileProperties.getPtPath() != null ? fileProperties.getPtPath() : "N/A", fileProperties.getPtPath() != null ? fileProperties.getPtPath() : "N/A",
fileProperties.getOutputDir() != null ? fileProperties.getOutputDir() : "N/A",
inferenceProperties.getNfs() != null ? inferenceProperties.getNfs() : "N/A", inferenceProperties.getNfs() != null ? inferenceProperties.getNfs() : "N/A",
inferenceProperties.getUrl() != null ? inferenceProperties.getUrl() : "N/A", inferenceProperties.getUrl() != null ? inferenceProperties.getUrl() : "N/A",
inferenceProperties.getBatchUrl() != null ? inferenceProperties.getBatchUrl() : "N/A", inferenceProperties.getBatchUrl() != null ? inferenceProperties.getBatchUrl() : "N/A",

View File

@@ -173,8 +173,9 @@ public class ApiResponseDto<T> {
+ "To reset your password again, please submit a new request through \"Forgot" + "To reset your password again, please submit a new request through \"Forgot"
+ " Password.\""), + " Password.\""),
PAYLOAD_TOO_LARGE("업로드 용량 제한을 초과했습니다."), PAYLOAD_TOO_LARGE("업로드 용량 제한을 초과했습니다."),
NOT_FOUND_TARGET_YEAR("기준도 도엽을 찾을 수 없습니다."), NOT_FOUND_TARGET_YEAR("기준도 도엽을 찾을 수 없습니다."),
NOT_FOUND_COMPARE_YEAR("비교도 도엽을 찾을 수 없습니다."), NOT_FOUND_COMPARE_YEAR("비교도 도엽을 찾을 수 없습니다."),
NOT_FOUND_MAP_SHEET_NUM("추론 가능한 도엽이 없습니다."),
FAIL_SAVE_MAP_SHEET("도엽 저장 중 오류가 발생했습니다."), FAIL_SAVE_MAP_SHEET("도엽 저장 중 오류가 발생했습니다."),
FAIL_CREATE_MAP_SHEET_FILE("도엽 설정파일 생성 중 오류가 발생했습니다."), FAIL_CREATE_MAP_SHEET_FILE("도엽 설정파일 생성 중 오류가 발생했습니다."),
; ;

View File

@@ -3,8 +3,8 @@ package com.kamco.cd.kamcoback.config.resttemplate;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -14,54 +14,86 @@ import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@RequiredArgsConstructor
@Component @Component
@Log4j2 @Log4j2
public class ExternalHttpClient { public class ExternalHttpClient {
private final RestTemplate restTemplate; private final RestTemplate restTemplate; // short (@Primary)
private final RestTemplate restTemplateLong; // long
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
public ExternalHttpClient(
RestTemplate restTemplate,
@Qualifier("restTemplateLong") RestTemplate restTemplateLong,
ObjectMapper objectMapper) {
this.restTemplate = restTemplate;
this.restTemplateLong = restTemplateLong;
this.objectMapper = objectMapper;
}
/** 기본(짧은 timeout) 호출 */
public <T> ExternalCallResult<T> call( public <T> ExternalCallResult<T> call(
String url, HttpMethod method, Object body, HttpHeaders headers, Class<T> responseType) { String url, HttpMethod method, Object body, HttpHeaders headers, Class<T> responseType) {
// responseType 기반으로 Accept 동적 세팅 return doCall(restTemplate, url, method, body, headers, responseType);
}
/** 추론/대용량 전용 (긴 timeout) */
public <T> ExternalCallResult<T> callLong(
String url, HttpMethod method, Object body, HttpHeaders headers, Class<T> responseType) {
return doCall(restTemplateLong, url, method, body, headers, responseType);
}
private <T> ExternalCallResult<T> doCall(
RestTemplate rt,
String url,
HttpMethod method,
Object body,
HttpHeaders headers,
Class<T> responseType) {
HttpHeaders resolvedHeaders = resolveHeaders(headers, responseType); HttpHeaders resolvedHeaders = resolveHeaders(headers, responseType);
logRequestBody(body); logRequestBody(body);
HttpEntity<Object> entity = new HttpEntity<>(body, resolvedHeaders); HttpEntity<Object> entity = new HttpEntity<>(body, resolvedHeaders);
try { try {
// String: raw bytes -> UTF-8 string
// String 응답은 raw byte로 받아 UTF-8 변환
if (responseType == String.class) { if (responseType == String.class) {
ResponseEntity<byte[]> res = restTemplate.exchange(url, method, entity, byte[].class); ResponseEntity<byte[]> res = rt.exchange(url, method, entity, byte[].class);
String raw = String raw =
(res.getBody() == null) ? null : new String(res.getBody(), StandardCharsets.UTF_8); (res.getBody() == null) ? null : new String(res.getBody(), StandardCharsets.UTF_8);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
T casted = (T) raw; T casted = (T) raw;
return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null); return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null);
} }
// byte[]: raw bytes로 받고, JSON이면 에러로 처리 // byte[] 응답 처리
if (responseType == byte[].class) { if (responseType == byte[].class) {
ResponseEntity<byte[]> res = restTemplate.exchange(url, method, entity, byte[].class); ResponseEntity<byte[]> res = rt.exchange(url, method, entity, byte[].class);
MediaType ct = res.getHeaders().getContentType(); MediaType ct = res.getHeaders().getContentType();
byte[] bytes = res.getBody(); byte[] bytes = res.getBody();
// JSON이면 에러로 간주
if (isJsonLike(ct)) { if (isJsonLike(ct)) {
String err = (bytes == null) ? null : new String(bytes, StandardCharsets.UTF_8); String err = (bytes == null) ? null : new String(bytes, StandardCharsets.UTF_8);
return new ExternalCallResult<>(res.getStatusCodeValue(), false, null, err); return new ExternalCallResult<>(res.getStatusCodeValue(), false, null, err);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
T casted = (T) bytes; T casted = (T) bytes;
return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null); return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null);
} }
// DTO 등: 일반 역직렬화 // DTO 응답
ResponseEntity<T> res = restTemplate.exchange(url, method, entity, responseType); ResponseEntity<T> res = rt.exchange(url, method, entity, responseType);
return new ExternalCallResult<>(res.getStatusCodeValue(), true, res.getBody(), null); return new ExternalCallResult<>(res.getStatusCodeValue(), true, res.getBody(), null);
} catch (HttpStatusCodeException e) { } catch (HttpStatusCodeException e) {
@@ -70,29 +102,28 @@ public class ExternalHttpClient {
} }
} }
// 기존 resolveJsonHeaders를 "동적"으로 교체 /** Accept / Content-Type 자동 처리 */
private HttpHeaders resolveHeaders(HttpHeaders headers, Class<?> responseType) { private HttpHeaders resolveHeaders(HttpHeaders headers, Class<?> responseType) {
// 원본 headers를 그대로 쓰면 외부에서 재사용할 때 사이드이펙트 날 수 있어서 복사 권장
HttpHeaders h = (headers == null) ? new HttpHeaders() : new HttpHeaders(headers); HttpHeaders h = (headers == null) ? new HttpHeaders() : new HttpHeaders(headers);
// 요청 바디 기본은 JSON이라고 가정 (필요하면 호출부에서 덮어쓰기) // 기본 Content-Type
if (h.getContentType() == null) { if (h.getContentType() == null) {
h.setContentType(MediaType.APPLICATION_JSON); h.setContentType(MediaType.APPLICATION_JSON);
} }
// 호출부에서 Accept를 명시했으면 존중 // Accept 이미 있으면 존중
if (h.getAccept() != null && !h.getAccept().isEmpty()) { if (h.getAccept() != null && !h.getAccept().isEmpty()) {
return h; return h;
} }
// responseType Accept 자동 지정 // 응답 타입 Accept 자동 지정
if (responseType == byte[].class) { if (responseType == byte[].class) {
h.setAccept( h.setAccept(
List.of( List.of(
MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_OCTET_STREAM,
MediaType.valueOf("application/zip"), MediaType.valueOf("application/zip"),
MediaType.APPLICATION_JSON // 실패(JSON 에러 바디) 대비 MediaType.APPLICATION_JSON));
));
} else { } else {
h.setAccept(List.of(MediaType.APPLICATION_JSON)); h.setAccept(List.of(MediaType.APPLICATION_JSON));
} }
@@ -100,12 +131,15 @@ public class ExternalHttpClient {
return h; return h;
} }
/** JSON 응답 여부 체크 */
private boolean isJsonLike(MediaType ct) { private boolean isJsonLike(MediaType ct) {
if (ct == null) return false; if (ct == null) return false;
return ct.includes(MediaType.APPLICATION_JSON) return ct.includes(MediaType.APPLICATION_JSON)
|| "application/problem+json".equalsIgnoreCase(ct.toString()); || "application/problem+json".equalsIgnoreCase(ct.toString());
} }
/** 요청 바디 로그 */
private void logRequestBody(Object body) { private void logRequestBody(Object body) {
try { try {
if (body != null) { if (body != null) {

View File

@@ -4,6 +4,7 @@ import lombok.extern.log4j.Log4j2;
import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.client.BufferingClientHttpRequestFactory; import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@@ -13,10 +14,20 @@ import org.springframework.web.client.RestTemplate;
public class RestTemplateConfig { public class RestTemplateConfig {
@Bean @Bean
@Primary
public RestTemplate restTemplate(RestTemplateBuilder builder) { public RestTemplate restTemplate(RestTemplateBuilder builder) {
return build(builder, 20000, 60000);
}
@Bean("restTemplateLong")
public RestTemplate restTemplateLong(RestTemplateBuilder builder) {
return build(builder, 20000, 60000);
}
private RestTemplate build(RestTemplateBuilder builder, int connectTimeoutMs, int readTimeoutMs) {
SimpleClientHttpRequestFactory baseFactory = new SimpleClientHttpRequestFactory(); SimpleClientHttpRequestFactory baseFactory = new SimpleClientHttpRequestFactory();
baseFactory.setConnectTimeout(2000); baseFactory.setConnectTimeout(connectTimeoutMs);
baseFactory.setReadTimeout(3000); baseFactory.setReadTimeout(readTimeoutMs);
RestTemplate rt = RestTemplate rt =
builder builder
@@ -24,10 +35,8 @@ public class RestTemplateConfig {
.additionalInterceptors(new RetryInterceptor()) .additionalInterceptors(new RetryInterceptor())
.build(); .build();
// byte[] 응답은 무조건 raw로 읽게 강제 (Jackson이 끼어들 여지 제거)
rt.getMessageConverters() rt.getMessageConverters()
.add(0, new org.springframework.http.converter.ByteArrayHttpMessageConverter()); .add(0, new org.springframework.http.converter.ByteArrayHttpMessageConverter());
return rt; return rt;
} }
} }

View File

@@ -322,4 +322,23 @@ public class GukYuinApiController {
@PathVariable String chnDtctObjtId) { @PathVariable String chnDtctObjtId) {
return ApiResponseDto.ok(gukYuinApiService.findRlbDtctObject(chnDtctObjtId)); return ApiResponseDto.ok(gukYuinApiService.findRlbDtctObject(chnDtctObjtId));
} }
@Operation(summary = "실태조사 적합여부 업데이트", description = "실태조사 적합여부 업데이트")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "등록 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = DetectMastReq.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/rlb/objt/{chnDtctObjtId}/stblt/{stbltYn}")
public ApiResponseDto<ChngDetectContDto.ResultLabelDto> updateStbltObjtYn(
@PathVariable String chnDtctObjtId, @PathVariable String stbltYn) {
return ApiResponseDto.ok(gukYuinApiService.updateStbltObjtYn(chnDtctObjtId, stbltYn, ""));
}
} }

View File

@@ -69,8 +69,11 @@ public class GukYuinApiService {
@Value("${file.nfs}") @Value("${file.nfs}")
private String nfs; private String nfs;
// @Value("${file.dataset-dir}") @Value("${file.output-dir}") // 국유인 반영 파일 경로
// private String datasetDir; private String outputDir;
@Value("${file.dataset-dir}")
private String datasetDir;
@Transactional @Transactional
public ChngDetectMastDto.RegistResDto regist( public ChngDetectMastDto.RegistResDto regist(
@@ -246,9 +249,9 @@ public class GukYuinApiService {
return GukYuinLinkFailCode.NOT_FOUND; return GukYuinLinkFailCode.NOT_FOUND;
} }
if (f.isPartScope()) { // if (f.isPartScope()) {
return GukYuinLinkFailCode.SCOPE_PART_NOT_ALLOWED; // return GukYuinLinkFailCode.SCOPE_PART_NOT_ALLOWED;
} // }
if (f.hasRunningInference()) { if (f.hasRunningInference()) {
return GukYuinLinkFailCode.HAS_RUNNING_INFERENCE; return GukYuinLinkFailCode.HAS_RUNNING_INFERENCE;
@@ -278,6 +281,7 @@ public class GukYuinApiService {
+ "&reqEpno=" + "&reqEpno="
+ ("Y".equals(batchYn) ? "BATCH" : userUtil.getEmployeeNo()); + ("Y".equals(batchYn) ? "BATCH" : userUtil.getEmployeeNo());
log.info("##### API 호출 URL : {}", url);
ExternalCallResult<ChngDetectContDto.ResultContDto> result = ExternalCallResult<ChngDetectContDto.ResultContDto> result =
externalHttpClient.call( externalHttpClient.call(
url, url,
@@ -286,6 +290,7 @@ public class GukYuinApiService {
netUtils.jsonHeaders(), netUtils.jsonHeaders(),
ChngDetectContDto.ResultContDto.class); ChngDetectContDto.ResultContDto.class);
log.info("##### API 호출 완료 : {}", result.toString());
List<ContBasic> contList = result.body().getResult(); List<ContBasic> contList = result.body().getResult();
if (contList == null || contList.isEmpty()) { if (contList == null || contList.isEmpty()) {
return new ResultContDto( return new ResultContDto(
@@ -345,6 +350,7 @@ public class GukYuinApiService {
info.setReqIp(myip); info.setReqIp(myip);
info.setReqEpno("Y".equals(batchYn) ? "BATCH" : userUtil.getEmployeeNo()); info.setReqEpno("Y".equals(batchYn) ? "BATCH" : userUtil.getEmployeeNo());
log.info("##### API 호출 URL : {}", url);
ExternalCallResult<ChngDetectContDto.ResultLabelDto> result = ExternalCallResult<ChngDetectContDto.ResultLabelDto> result =
externalHttpClient.call( externalHttpClient.call(
url, url,
@@ -352,6 +358,7 @@ public class GukYuinApiService {
info, info,
netUtils.jsonHeaders(), netUtils.jsonHeaders(),
ChngDetectContDto.ResultLabelDto.class); ChngDetectContDto.ResultLabelDto.class);
log.info("##### API 호출 완료 : {}", result.toString());
this.insertGukyuinAuditLog( this.insertGukyuinAuditLog(
EventType.MODIFIED.getId(), EventType.MODIFIED.getId(),
@@ -405,10 +412,12 @@ public class GukYuinApiService {
+ "&reqEpno=" + "&reqEpno="
+ ("Y".equals(batchYn) ? "BATCH" : userUtil.getEmployeeNo()); + ("Y".equals(batchYn) ? "BATCH" : userUtil.getEmployeeNo());
log.info("##### API 호출 URL : {}", url);
ExternalCallResult<ChngDetectMastDto.ResultDto> result = ExternalCallResult<ChngDetectMastDto.ResultDto> result =
externalHttpClient.call( externalHttpClient.call(
url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.ResultDto.class); url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.ResultDto.class);
log.info("##### API 호출 완료 : {}", result.toString());
this.insertGukyuinAuditLog( this.insertGukyuinAuditLog(
EventType.DETAIL.getId(), EventType.DETAIL.getId(),
netUtils.getLocalIP(), netUtils.getLocalIP(),
@@ -456,10 +465,13 @@ public class GukYuinApiService {
return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 국유인 연동을 한 회차입니다."); return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 국유인 연동을 한 회차입니다.");
} }
// String kamconfsDatasetExportPathfsDatasetExportPath = "/kamco-nfs/dataset/export/"; // 추론 shp 파일 생성되는 위치
String kamconfsDatasetExportPathfsDatasetExportPath = log.info(
String.format("%s%s", nfs, "/dataset/export/"); "═════════════════════════════════[s] datasetDir : 추론 shp파일 생성되는 위치 ══════════════════════════════════");
if (!Files.isDirectory(Path.of(kamconfsDatasetExportPathfsDatasetExportPath + info.getUid()))) { log.info("datasetDir path : " + datasetDir + info.getUid());
log.info(
"═════════════════════════════════[e] datasetDir : 추론 shp파일 생성되는 위치 ════════════════════════════");
if (!Files.isDirectory(Path.of(datasetDir + info.getUid()))) {
return new ResponseObj( return new ResponseObj(
ApiResponseCode.NOT_FOUND_DATA, "파일 경로에 회차 실행 파일이 생성되지 않았습니다. 확인 부탁드립니다."); ApiResponseCode.NOT_FOUND_DATA, "파일 경로에 회차 실행 파일이 생성되지 않았습니다. 확인 부탁드립니다.");
} }
@@ -469,6 +481,13 @@ public class GukYuinApiService {
gukyuinCoreService.findMapSheetLearnYearStage(info.getCompareYyyy(), info.getTargetYyyy()); gukyuinCoreService.findMapSheetLearnYearStage(info.getCompareYyyy(), info.getTargetYyyy());
// reqDto 셋팅 // reqDto 셋팅
// 마운트된 추론 shp 파일 생성되는 위치
log.info(
"═════════════════════════════════[s] outputDir : 마운트된 추론 shp 파일 생성되는 위치 ══════════════════════════════════");
log.info("outputDir path : " + outputDir + info.getUid());
log.info(
"═════════════════════════════════[e] outputDir : 마운트된 추론 shp 파일 생성되는 위치 ════════════════════════════");
String kamconfsDatasetExportPathfsDatasetExportPath = outputDir;
ChnDetectMastReqDto reqDto = new ChnDetectMastReqDto(); ChnDetectMastReqDto reqDto = new ChnDetectMastReqDto();
reqDto.setCprsYr(String.valueOf(info.getCompareYyyy())); reqDto.setCprsYr(String.valueOf(info.getCompareYyyy()));
reqDto.setCrtrYr(String.valueOf(info.getTargetYyyy())); reqDto.setCrtrYr(String.valueOf(info.getTargetYyyy()));
@@ -549,10 +568,12 @@ public class GukYuinApiService {
+ "&yyyymmdd=" + "&yyyymmdd="
+ yyyymmdd; + yyyymmdd;
log.info("##### API 호출 URL : {}", url);
ExternalCallResult<ChngDetectMastDto.RlbDtctDto> result = ExternalCallResult<ChngDetectMastDto.RlbDtctDto> result =
externalHttpClient.call( externalHttpClient.call(
url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.RlbDtctDto.class); url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.RlbDtctDto.class);
log.info("##### API 호출 완료 : {}", result.toString());
this.insertGukyuinAuditLog( this.insertGukyuinAuditLog(
EventType.LIST.getId(), EventType.LIST.getId(),
netUtils.getLocalIP(), netUtils.getLocalIP(),
@@ -586,4 +607,45 @@ public class GukYuinApiService {
result.body() != null && result.body().getSuccess()); result.body() != null && result.body().getSuccess());
return result.body(); return result.body();
} }
public ChngDetectContDto.ResultLabelDto updateStbltObjtYn(
String chnDtctObjtId, String stbltYn, String batchYn) {
String url = gukyuinCdiUrl + "/rlb/objt/" + chnDtctObjtId + "/stblt/" + stbltYn;
ReqInfo info = new ReqInfo();
info.setReqIp(myip);
info.setReqEpno("Y".equals(batchYn) ? "BATCH" : userUtil.getEmployeeNo());
ExternalCallResult<ChngDetectContDto.ResultLabelDto> result =
externalHttpClient.call(
url,
HttpMethod.POST,
info,
netUtils.jsonHeaders(),
ChngDetectContDto.ResultLabelDto.class);
this.insertGukyuinAuditLog(
EventType.MODIFIED.getId(),
netUtils.getLocalIP(),
userUtil.getId(),
url.replace(gukyuinUrl, ""),
null,
result.body().getSuccess());
return result.body();
}
public void stbltBulkUpdate(List<String> objectIds) {
for (String objectId : objectIds) {
this.updateStbltObjtYn(objectId, "Y", "");
}
}
public List<String> findStbltObjectIds(String uid, String mapSheetNum) {
return gukyuinCoreService.findStbltObjectIds(uid, mapSheetNum);
}
public Integer updateStbltRandomData(String uid, int updateCnt) {
return gukyuinCoreService.updateStbltRandomData(uid, updateCnt);
}
} }

View File

@@ -0,0 +1,43 @@
package com.kamco.cd.kamcoback.inference;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
import com.kamco.cd.kamcoback.inference.service.InferenceManualService;
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.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "추론결과 데이터 생성", description = "추론결과 데이터 생성 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/inference/manual")
public class InferenceManualApiController {
private final InferenceManualService inferenceManualService;
@Operation(summary = "추론 결과로 추론 목록 및 shp 생성", description = "추론 결과로 추론 목록 및 shp 생성")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "데이터 저장 성공",
content =
@Content(
mediaType = "application/json",
schema =
@Schema(implementation = InferenceResultShpDto.InferenceCntDto.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/save")
public void saveTesting(List<Long> batchIds) {
inferenceManualService.saveResultsTesting(batchIds);
}
}

View File

@@ -18,7 +18,6 @@ import com.kamco.cd.kamcoback.model.dto.ModelMngDto;
import com.kamco.cd.kamcoback.model.service.ModelMngService; import com.kamco.cd.kamcoback.model.service.ModelMngService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; 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.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
@@ -28,6 +27,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
@@ -36,6 +36,7 @@ import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -58,7 +59,8 @@ public class InferenceResultApiController {
private final ModelMngService modelMngService; private final ModelMngService modelMngService;
private final RangeDownloadResponder rangeDownloadResponder; private final RangeDownloadResponder rangeDownloadResponder;
@Operation(summary = "추론관리 목록", description = "어드민 홈 > 추론관리 > 추론관리 > 추론관리 목록") /** 추론관리 목록 화면에서 호출 */
@Operation(summary = "추론관리 목록", description = "추론관리 > 추론관리 목록 ")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -90,7 +92,8 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(analResList); return ApiResponseDto.ok(analResList);
} }
@Operation(summary = "추론 진행 여부 확인", description = "어드민 홈 > 추론관리 > 추론관리 > 추론관리 목록") /** 추론관리 목록 화면에서 호출 */
@Operation(summary = "추론 진행 여부 확인", description = "추론관리 > 추론관리 목록")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -112,7 +115,8 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(inferenceResultService.getProcessing()); return ApiResponseDto.ok(inferenceResultService.getProcessing());
} }
@Operation(summary = "년도 목록 조회", description = "어드민 홈 > 추론관리 > 추론목록 > 변화탐지 실행 정보 입력 > 년도 목록 조회") /** 추론관리 목록 화면에서 호출 */
@Operation(summary = "년도 목록 조회", description = "추론관리 > 추론목록 > 변화탐지 실행 정보 입력 > 년도 목록 조회")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -130,7 +134,8 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(mapSheetMngService.findMapSheetMngDoneYyyyList()); return ApiResponseDto.ok(mapSheetMngService.findMapSheetMngDoneYyyyList());
} }
@Operation(summary = "변화탐지 실행 정보 입력", description = "어드민 홈 > 추론관리 > 추론목록 > 변화탐지 실행 정보 입력") /** 변화탐지 실행 정보 입력화면에서 호출 */
@Operation(summary = "변화탐지 실행 정보 입력, 추론실행", description = "추론관리 > 추론목록 > 변화탐지 실행 정보 입력")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -155,7 +160,8 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(uuid); return ApiResponseDto.ok(uuid);
} }
@Operation(summary = "추론 종료", description = "추론 종료") /** 추론진행 현황 화면에서 호출 */
@Operation(summary = "추론 종료", description = "추론관리 > 추론목록 > 추론진행 현황")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -174,7 +180,8 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(uuid); return ApiResponseDto.ok(uuid);
} }
@Operation(summary = "분석 모델 선택 조회", description = "변화탐지 실행 정보 입력 모델선택 팝업 ") /** 변화탐지 실행 정보 입력화면에서 호출 */
@Operation(summary = "분석 모델 선택 조회", description = "추론관리 > 추론목록 > 변화탐지 실행 정보 입력 > 모델선택 팝업 ")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -205,7 +212,8 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(result); return ApiResponseDto.ok(result);
} }
@Operation(summary = "추론관리 추론진행 서버 현황", description = "추론관리 추론진행 서버 현황") /** 추론진행 현황 화면에서 호출 */
@Operation(summary = "추론관리 추론진행 서버 현황", description = "추론관리 > 추론목록 > 추론진행 현황")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -224,7 +232,8 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(inferenceResultService.getInferenceServerStatusList()); return ApiResponseDto.ok(inferenceResultService.getInferenceServerStatusList());
} }
@Operation(summary = "추론관리 진행현황 상세", description = "어드민 홈 > 추론관리 > 추론관리 > 진행현황 상세") /** 추론진행 현황 화면에서 호출 */
@Operation(summary = "추론관리 진행현황 상세", description = "추론관리 > 추론진행 현황")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -248,7 +257,8 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(inferenceResultService.getInferenceStatus(uuid)); return ApiResponseDto.ok(inferenceResultService.getInferenceStatus(uuid));
} }
@Operation(summary = "추론결과 기본정보", description = "추론결과 기본정보") /** 추론결과 화면에서 호출 */
@Operation(summary = "추론결과 기본정보", description = "추론관리 > 추론결과")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -269,7 +279,8 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(inferenceResultService.getInferenceResultInfo(uuid)); return ApiResponseDto.ok(inferenceResultService.getInferenceResultInfo(uuid));
} }
@Operation(summary = "추론결과 분류별 탐지 건수", description = "추론결과 분류별 탐지 건수") /** 추론결과 화면에서 호출 */
@Operation(summary = "추론결과 분류별 탐지 건수", description = "추론관리 > 추론결과")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -290,6 +301,7 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(inferenceResultService.getInferenceClassCountList(uuid)); return ApiResponseDto.ok(inferenceResultService.getInferenceClassCountList(uuid));
} }
/** 추론결과 화면에서 호출 */
@Operation(summary = "추론관리 분석결과 상세 목록", description = "추론관리 분석결과 상세 목록 geojson 데이터 조회") @Operation(summary = "추론관리 분석결과 상세 목록", description = "추론관리 분석결과 상세 목록 geojson 데이터 조회")
@ApiResponses( @ApiResponses(
value = { value = {
@@ -329,26 +341,14 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(geomList); return ApiResponseDto.ok(geomList);
} }
@Operation( /** 추론결과 화면에서 호출 */
summary = "shp 파일 다운로드", /** 다운로드는 a 링크로 받는걸로 변경되어 사번을 파라미터로 받아서 로그에 저장하는걸로 변경함 */
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( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "shp zip파일 다운로드", description = "shp 파일 다운로드",
content = content =
@Content( @Content(
mediaType = "application/octet-stream", mediaType = "application/octet-stream",
@@ -357,13 +357,16 @@ public class InferenceResultApiController {
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/download/{uuid}") @GetMapping("/download/{uuid}")
public ResponseEntity<?> download(@PathVariable UUID uuid, HttpServletRequest request) public ResponseEntity<?> download(
@PathVariable UUID uuid,
@Parameter(description = "사번", example = "123456") @RequestParam String employeeNo,
HttpServletRequest request)
throws IOException { throws IOException {
String path; String path;
String uid; String uid;
try { try {
// 추론결과 shp zip 파일 확인하여 다운로드 경로 생성
Map<String, Object> map = inferenceResultService.shpDownloadPath(uuid); Map<String, Object> map = inferenceResultService.shpDownloadPath(uuid);
path = String.valueOf(map.get("path")); path = String.valueOf(map.get("path"));
uid = String.valueOf(map.get("uid")); uid = String.valueOf(map.get("uid"));
@@ -372,11 +375,14 @@ public class InferenceResultApiController {
} }
Path zipPath = Path.of(path); Path zipPath = Path.of(path);
if (!Files.isRegularFile(zipPath)) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("추론이 완료되지 않아 파일이 생성되지 않았습니다.");
}
// Range + 200/206/416 공통 처리 (추가 헤더 포함)
return rangeDownloadResponder.buildZipResponse(zipPath, uid + ".zip", request); return rangeDownloadResponder.buildZipResponse(zipPath, uid + ".zip", request);
} }
/** 추론결과 화면에서 호출 */
@Operation(summary = "shp 파일 다운로드 이력 조회", description = "추론관리 분석결과 shp 파일 다운로드 이력 조회") @Operation(summary = "shp 파일 다운로드 이력 조회", description = "추론관리 분석결과 shp 파일 다운로드 이력 조회")
@GetMapping(value = "/download-audit/{uuid}") @GetMapping(value = "/download-audit/{uuid}")
@ApiResponses( @ApiResponses(
@@ -419,7 +425,8 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(inferenceResultService.getDownloadAudit(searchReq, downloadReq)); return ApiResponseDto.ok(inferenceResultService.getDownloadAudit(searchReq, downloadReq));
} }
@Operation(summary = "추론 실행중인 도엽 목록", description = "추론관리 실행중인 도엽명 5k 목록") /** 추론진행 현황 화면에서 호출, 분석도엽 부분 옵션일때 분석중인 도엽 확인용 */
@Operation(summary = "추론관리 분석중인 도엽명 5k 목록", description = "추론관리 분석중인 도엽명 50k 목록")
@ApiResponses({ @ApiResponses({
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",

View File

@@ -1,90 +0,0 @@
package com.kamco.cd.kamcoback.inference;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.Scene;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
import com.kamco.cd.kamcoback.inference.service.InferenceResultShpService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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 java.util.Map;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
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.RestController;
@Tag(name = "추론결과 데이터 생성", description = "추론결과 데이터 생성 API")
@Log4j2
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/inference/shp")
public class InferenceResultShpApiController {
private final InferenceResultShpService inferenceResultShpService;
public static final String MAP_ID =
"{ \"mapIds\": [\"37716096\",\"37716095\",\"37716094\",\"37716091\",\"37716086\",\"37716085\",\"37716084\",\"37716083\",\"37716076\",\"37716066\",\"37716065\",\"37716064\",\"37716063\",\"37716061\",\"37716051\",\"37716011\"] }";
@Operation(summary = "추론결과 데이터 저장", description = "추론결과 데이터 저장")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "데이터 저장 성공",
content =
@Content(
mediaType = "application/json",
schema =
@Schema(implementation = InferenceResultShpDto.InferenceCntDto.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/save/{learnId}")
public ApiResponseDto<InferenceResultShpDto.InferenceCntDto> saveInferenceData(
@PathVariable Long learnId) {
return ApiResponseDto.createOK(inferenceResultShpService.saveInferenceResultData(learnId));
}
@Operation(summary = "추론결과 shp 생성", description = "추론결과 shp 생성")
@PostMapping("/shp/{uuid}")
public ApiResponseDto<Void> createShp(
@Parameter(example = "feb2ec0b-a0f7-49ca-95e4-98b2231bdaae") @PathVariable UUID uuid) {
inferenceResultShpService.createShp(uuid);
return ApiResponseDto.createOK(null);
}
@Operation(summary = "추론실행에 필요한 geojson 파일 생성", description = "추론실행에 필요한 geojson 파일 생성")
@PostMapping("/geojson/{yyyy}/{mapSheetScope}/{detectOption}")
public ApiResponseDto<Scene> createGeojson(
@Schema(description = "년도") @PathVariable String yyyy,
@Schema(description = "전체(ALL),부분(PART)", example = "PART") @PathVariable
String mapSheetScope,
@Schema(description = "추론제외(EXCL),이전 년도 도엽 사용(PREV)", example = "EXCL") @PathVariable
String detectOption,
@Schema(description = "5k도엽번호", example = MAP_ID) @RequestBody Map<String, Object> body) {
Object raw = body.get("mapIds");
if (raw == null) {
throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST);
}
@SuppressWarnings("unchecked")
List<String> mapIds = (List<String>) raw;
Scene scene =
inferenceResultShpService.createGeojson(yyyy, mapSheetScope, detectOption, mapIds);
return ApiResponseDto.createOK(scene);
}
}

View File

@@ -458,6 +458,7 @@ public class InferenceDetailDto {
private String bboxGeom; private String bboxGeom;
private String bboxCenterPoint; private String bboxCenterPoint;
private UUID inferenceUuid; private UUID inferenceUuid;
private String status;
public AnalResultInfo( public AnalResultInfo(
String analTitle, String analTitle,
@@ -474,7 +475,8 @@ public class InferenceDetailDto {
String subUid, String subUid,
Boolean applyYn, Boolean applyYn,
ZonedDateTime applyDttm, ZonedDateTime applyDttm,
UUID inferenceUuid) { UUID inferenceUuid,
String status) {
this.analTitle = analTitle; this.analTitle = analTitle;
this.modelVer1 = modelVer1; this.modelVer1 = modelVer1;
this.modelVer2 = modelVer2; this.modelVer2 = modelVer2;
@@ -489,6 +491,7 @@ public class InferenceDetailDto {
this.subUid = subUid; this.subUid = subUid;
this.applyYn = applyYn; this.applyYn = applyYn;
this.applyDttm = applyDttm; this.applyDttm = applyDttm;
this.status = status;
Duration elapsed = Duration elapsed =
(inferStartDttm != null && inferEndDttm != null) (inferStartDttm != null && inferEndDttm != null)
? Duration.between(inferStartDttm, inferEndDttm) ? Duration.between(inferStartDttm, inferEndDttm)
@@ -538,6 +541,10 @@ public class InferenceDetailDto {
public Boolean getApplyYn() { public Boolean getApplyYn() {
return this.applyYn != null && this.applyYn; return this.applyYn != null && this.applyYn;
} }
public String getStatusNm() {
return InferenceResultDto.Status.getDescByCode(this.status);
}
} }
@Getter @Getter

View File

@@ -71,14 +71,16 @@ public class InferenceResultShpDto {
@NoArgsConstructor @NoArgsConstructor
public static class InferenceCntDto { public static class InferenceCntDto {
@Schema(description = "추론 결과(inference_results)를 기준으로 신규 목록 저장 터이터 건수", example = "120") @Schema(
description = "추론 결과(inference_results_testing)를 기준으로 데이터 목록 저장 터이터 건수",
example = "120")
int sheetAnalDataCnt; int sheetAnalDataCnt;
@Schema(description = "추론 결과(inference_results)를 기준으로 신규 저장 데이터 건수", example = "120") @Schema(description = "추론 결과(inference_results_testing)를 기준으로 저장 Geom 데이터 건수", example = "120")
int inferenceCnt;
@Schema(description = "추론 결과(inference_results)를 기준으로 신규 저장 Geom 데이터 건수", example = "120")
int inferenceGeomCnt; int inferenceGeomCnt;
@Schema(description = "추론 결과(inference_results_testing)를 기준으로 저장 집계 데이터 건수", example = "120")
int inferenceSttcnt;
} }
@Setter @Setter

View File

@@ -1,10 +0,0 @@
package com.kamco.cd.kamcoback.inference.service;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/inference/manual")
public class InferenceManualApiController {}

View File

@@ -1,24 +1,61 @@
package com.kamco.cd.kamcoback.inference.service; package com.kamco.cd.kamcoback.inference.service;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultsTestingDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultsTestingDto;
import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService; import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService;
import com.kamco.cd.kamcoback.postgres.core.ModelMngCoreService;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class InferenceManualService { public class InferenceManualService {
private final InferenceResultCoreService inferenceResultCoreService; private final InferenceResultCoreService inferenceResultCoreService;
private final ModelMngCoreService modelMngCoreService;
public void getResultsTesting(List<Long> batchIds) { public void saveResultsTesting(List<Long> batchIds) {
List<InferenceResultsTestingDto.Basic> resultList = // 배치 id로 추론 결과 testing 테이블에서 조회
inferenceResultCoreService.getInferenceResults(batchIds); List<InferenceResultsTestingDto.Basic> resultInfoList =
inferenceResultCoreService.getInferenceResultGroupList(batchIds);
if (resultList.isEmpty()) {} if (resultInfoList.isEmpty()) {
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND);
}
// Long compareYear = resultInfoList.getFirst().getBeforeYear();
// Long targetYear = resultInfoList.getFirst().getAfterYear();
// String title = compareYear + "-" + targetYear + "변화탐지";
//
// InferenceResultDto.RegReq inferenceDto = new InferenceResultDto.RegReq();
// inferenceDto.setTitle(title);
// inferenceDto.setCompareYyyy(Integer.valueOf(compareYear));
// inferenceDto.setTargetYyyy(targetYear);
// // 추론 기본정보 저장
// for (InferenceResultsTestingDto.Basic result : resultInfoList) {
//
// if (result.getModelVersion().startsWith(ModelType.G1.getId()) ||
// result.getModelVersion().startsWith("M1")) {
// ModelMngDto.Basic model =
// modelMngCoreService.findByModelVer(result.getModelVersion());
// inferenceDto.setModel1Uuid(model.getUuid());
//
// } else if (result.getModelVersion().startsWith("G2") ||
// result.getModelVersion().startsWith("M2")) {
// ModelMngDto.Basic model =
// modelMngCoreService.findByModelVer(result.getModelVersion());
// inferenceDto.setModel2Uuid(model.getUuid());
//
// } else if (result.getModelVersion().startsWith("G3") ||
// result.getModelVersion().startsWith("M3")) {
// ModelMngDto.Basic model =
// modelMngCoreService.findByModelVer(result.getModelVersion());
// inferenceDto.setModel3Uuid(model.getUuid());
// }
//
// System.out.println(result);
// }
for (InferenceResultsTestingDto.Basic result : resultList) {
System.out.println(result);
}
} }
} }

View File

@@ -1,11 +1,11 @@
package com.kamco.cd.kamcoback.inference.service; package com.kamco.cd.kamcoback.inference.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.common.exception.CustomApiException; import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature; import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.Scene; import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.Scene;
import com.kamco.cd.kamcoback.common.inference.service.InferenceCommonService;
import com.kamco.cd.kamcoback.common.inference.utils.GeoJsonValidator;
import com.kamco.cd.kamcoback.common.utils.UserUtil; import com.kamco.cd.kamcoback.common.utils.UserUtil;
import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient; import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient;
import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient.ExternalCallResult; import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient.ExternalCallResult;
@@ -21,8 +21,7 @@ import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.DetectOption;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceLearnDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceLearnDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceServerStatusDto; 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.InferenceStatusDetailDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetFallbackYearDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetScope;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetNumDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.ResultList; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.ResultList;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SaveInferenceAiDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SaveInferenceAiDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Status; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Status;
@@ -40,6 +39,8 @@ import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService;
import com.kamco.cd.kamcoback.postgres.core.MapSheetMngCoreService; import com.kamco.cd.kamcoback.postgres.core.MapSheetMngCoreService;
import com.kamco.cd.kamcoback.postgres.core.ModelMngCoreService; import com.kamco.cd.kamcoback.postgres.core.ModelMngCoreService;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@@ -76,14 +77,11 @@ public class InferenceResultService {
private final MapSheetMngCoreService mapSheetMngCoreService; private final MapSheetMngCoreService mapSheetMngCoreService;
private final ModelMngCoreService modelMngCoreService; private final ModelMngCoreService modelMngCoreService;
private final AuditLogCoreService auditLogCoreService; private final AuditLogCoreService auditLogCoreService;
private final InferenceCommonService inferenceCommonService;
private final ExternalHttpClient externalHttpClient; private final ExternalHttpClient externalHttpClient;
private final ObjectMapper objectMapper;
private final UserUtil userUtil; private final UserUtil userUtil;
@Value("${inference.url}")
private String inferenceUrl;
@Value("${inference.batch-url}") @Value("${inference.batch-url}")
private String batchUrl; private String batchUrl;
@@ -94,7 +92,10 @@ public class InferenceResultService {
private String datasetDir; private String datasetDir;
@Value("${spring.profiles.active}") @Value("${spring.profiles.active}")
private String profile; private String activeEnv;
@Value("${inference.geojson-dir}")
private String inferenceDir;
/** /**
* 추론관리 목록 * 추론관리 목록
@@ -107,7 +108,7 @@ public class InferenceResultService {
} }
/** /**
* 추론 진행중인지 확인 * 추론 진행중인지 확인, 변화탐지 설정 등록 버튼 활성화 여부에 필요함
* *
* @return * @return
*/ */
@@ -120,7 +121,7 @@ public class InferenceResultService {
} }
/** /**
* 추론 실행 - 추론제외, 이전도 도엽 사용 분기 * 추론 실행 - 추론제외, 이전도 도엽 사용 분기
* *
* @param req * @param req
* @return * @return
@@ -128,8 +129,11 @@ public class InferenceResultService {
@Transactional @Transactional
public UUID run(InferenceResultDto.RegReq req) { public UUID run(InferenceResultDto.RegReq req) {
if (req.getDetectOption().equals(DetectOption.EXCL.getId())) { if (req.getDetectOption().equals(DetectOption.EXCL.getId())) {
// 추론 제외 일때 EXCL
return runExcl(req); return runExcl(req);
} }
// 이전연도 도엽 사용 일때 PREV
return runPrev(req); return runPrev(req);
} }
@@ -140,38 +144,96 @@ public class InferenceResultService {
* @return * @return
*/ */
public UUID runExcl(InferenceResultDto.RegReq req) { public UUID runExcl(InferenceResultDto.RegReq req) {
// target 도엽 조회 // TODO 쿼리로 한번에 할수 있게 수정해야하나..
List<MngListDto> targetDtoList = mapSheetMngCoreService.getHstMapSheetList(req); // 기준연도 실행가능 도엽 조회
List<MngListDto> targetMngList =
mapSheetMngCoreService.getMapSheetMngHst(
req.getTargetYyyy(), req.getMapSheetScope(), req.getMapSheetNum());
// target 리스트 추출 (null 제거 + 중복 제거) // List<MngListDto> mngList =
List<String> targetList = // mapSheetMngCoreService.findExecutableSheets(
targetDtoList.stream() // req.getCompareYyyy(),
// req.getTargetYyyy(),
// req.getMapSheetScope(),
// req.getMapSheetNum());
if (targetMngList == null || targetMngList.isEmpty()) {
throw new CustomApiException("NOT_FOUND_MAP_SHEET_NUM", HttpStatus.NOT_FOUND);
}
// 비교연도 실행가능 도엽 조회
List<MngListDto> compareMngList =
mapSheetMngCoreService.getMapSheetMngHst(
req.getCompareYyyy(), req.getMapSheetScope(), req.getMapSheetNum());
if (compareMngList == null || compareMngList.isEmpty()) {
throw new CustomApiException("NOT_FOUND_COMPARE_YEAR", HttpStatus.NOT_FOUND);
}
// compare 도엽번호 Set 구성
Set<String> compareSet =
compareMngList.stream()
.map(MngListDto::getMapSheetNum)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 기준년도 비교년도 동일한 도엽번호만 담기
List<MngListDto> intersectionList =
targetMngList.stream()
.filter(dto -> dto.getMapSheetNum() != null)
.filter(dto -> compareSet.contains(dto.getMapSheetNum()))
.toList();
log.info("target size = {}", targetMngList.size());
log.info("compare size = {}", compareMngList.size());
log.info("intersection size = {}", intersectionList.size());
// 비교 연도 도엽번호를 꺼내와서 최종 추론 대상 도엽번호를 담기
List<String> mapSheetNums =
intersectionList.stream()
.map(MngListDto::getMapSheetNum) .map(MngListDto::getMapSheetNum)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.distinct() .distinct()
.toList(); .toList();
// compare 도엽번호 리스트 조회 (null 제거 + 중복 제거) int targetTotal = targetMngList.size();
List<String> compareList = int compareTotal = compareMngList.size();
mapSheetMngCoreService.getMapSheetNumByHst(req.getCompareYyyy()).stream() int intersection = intersectionList.size();
.filter(Objects::nonNull)
.distinct()
.toList();
// compare 기준 // ===== MapSheet Year Comparison =====
Set<String> compareSet = new HashSet<>(compareList); // target Total : 기준연도 실행가능 전체 도엽 수
// compare Total : 비교연도 실행가능 전체 도엽 수
// Intersection : 양 연도에 모두 존재하는 도엽 수 (최종 추론 대상)
// target Only (Excluded) : 기준연도에만 존재하고 비교연도에는 없는 도엽 수
// compare Only : 비교연도에만 존재하고 기준연도에는 없는 도엽 수
// ====================================
// target 기준으로 compare에 존재하는 도엽만 필터링 log.info(
List<String> filteredTargetList = targetList.stream().filter(compareSet::contains).toList(); """
===== MapSheet Year Comparison =====
target Total: {}
compare Total: {}
Intersection: {}
target Only (Excluded): {}
compare Only: {}
====================================
""",
targetTotal,
compareTotal,
intersection,
targetTotal - intersection,
compareTotal - intersection);
// 도엽 비교 로그 출력 if (mapSheetNums.isEmpty()) {
logYearComparison(targetList, compareList, filteredTargetList); // 추론 가능한 도엽이 없습니다.
throw new CustomApiException("NOT_FOUND_MAP_SHEET_NUM", HttpStatus.NOT_FOUND);
}
// compare geojson 파일 생성 // compare geojson 파일 생성
Scene compareScene = Scene compareScene =
getSceneInference( getSceneInference(
req.getCompareYyyy().toString(), // 기준년도 req.getCompareYyyy().toString(), // 기준년도
filteredTargetList, // 교집합 도엽 mapSheetNums, // 최종 추론 대상
req.getMapSheetScope(), // ALL / 부분 req.getMapSheetScope(), // ALL / 부분
req.getDetectOption()); // EXCL / PREV req.getDetectOption()); // EXCL / PREV
@@ -179,15 +241,20 @@ public class InferenceResultService {
Scene targetScene = Scene targetScene =
getSceneInference( getSceneInference(
req.getTargetYyyy().toString(), // 대상년도 req.getTargetYyyy().toString(), // 대상년도
filteredTargetList, // 교집합 도엽 mapSheetNums, // 최종 추론 대상
req.getMapSheetScope(), req.getMapSheetScope(),
req.getDetectOption()); req.getDetectOption());
log.info("비교년도 geojson 파일 validation ===== {}", compareScene.getFilePath());
GeoJsonValidator.validateWithRequested(compareScene.getFilePath(), mapSheetNums);
log.info("기준년도 geojson 파일 validation ===== {}", targetScene.getFilePath());
GeoJsonValidator.validateWithRequested(targetScene.getFilePath(), mapSheetNums);
// 추론 실행 // 추론 실행
return executeInference( return executeInference(
req, req,
targetDtoList, // 전체 target 목록 intersectionList, // 전체 target 목록
filteredTargetList, // 최종 추론 대상 mapSheetNums, // 최종 추론 대상
compareScene, // compare geojson compareScene, // compare geojson
targetScene // target geojson targetScene // target geojson
); );
@@ -201,91 +268,123 @@ public class InferenceResultService {
*/ */
@Transactional @Transactional
public UUID runPrev(InferenceResultDto.RegReq req) { public UUID runPrev(InferenceResultDto.RegReq req) {
// target 목록 조회 // TODO 쿼리로 한번에 할수 있게 수정해야하나..
List<MngListDto> targetDtoList = mapSheetMngCoreService.getHstMapSheetList(req); // 기준연도 실행가능 도엽 조회
List<MngListDto> targetMngList =
mapSheetMngCoreService.getMapSheetMngHst(
req.getTargetYyyy(), req.getMapSheetScope(), req.getMapSheetNum());
// target 도엽번호 리스트 추출 중복 제거 if (targetMngList == null || targetMngList.isEmpty()) {
List<String> targetList = throw new CustomApiException("NOT_FOUND_TARGET_YEAR", HttpStatus.NOT_FOUND);
targetDtoList.stream() }
// 비교연도 실행가능 도엽 조회
List<MngListDto> compareMngList =
mapSheetMngCoreService.getMapSheetMngHst(
req.getCompareYyyy(), req.getMapSheetScope(), req.getMapSheetNum());
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());
// 로그용 원본 카운트 (이전도엽 추가 전)
int targetTotal = targetMngList.size();
int compareTotalBeforeFallback = compareMngList.size();
// 기준연도 기준 비교연도 구해서 이전년도로 compare 보완 하기위해서 도엽번호만 정리
Set<String> compareSet0 =
compareMngList.stream()
.map(MngListDto::getMapSheetNum) .map(MngListDto::getMapSheetNum)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.distinct()
.toList();
// compare 목록 조회
List<MapSheetFallbackYearDto> compareDtoList =
new ArrayList<>(mapSheetMngCoreService.getMapSheetNumDtoByHst(req.getCompareYyyy()));
// compare 도엽번호 Set 구성
Set<String> compareSet =
compareDtoList.stream()
.map(MapSheetFallbackYearDto::getMapSheetNum)
.filter(Objects::nonNull)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// target에는 있으나 compare에는 없는 도엽 추출 // 기준연도 기준 비교연도에 도협번호가 없으면 이전연도 조회해서 compare 보완, 없는거 담기
List<String> remainingTargetList = List<String> targetOnlyMapSheetNums =
targetList.stream().filter(s -> !compareSet.contains(s)).toList(); targetMngList.stream()
.map(MngListDto::getMapSheetNum)
.filter(Objects::nonNull)
.filter(num -> !compareSet0.contains(num))
.toList();
// compare에 없을때 이전 년도 사용 가능여부 조회 log.info("targetOnlyMapSheetNums in count = {}", targetOnlyMapSheetNums.size());
List<MapSheetFallbackYearDto> fallbackYearDtoList =
// 이전연도 초회 추가
compareMngList.addAll(
mapSheetMngCoreService.findFallbackCompareYearByMapSheets( mapSheetMngCoreService.findFallbackCompareYearByMapSheets(
req.getTargetYyyy(), // 대상년도 기준 req.getCompareYyyy(), targetOnlyMapSheetNums));
remainingTargetList // compare에 없는 도엽들
);
// 기존 compare , 사용가능 이전년도 정보 합치기 log.info("fallback compare size= {}", compareMngList.size());
compareDtoList.addAll(fallbackYearDtoList);
// 중복제거하여 사용할 compare 도엽 목록 // 이전연도 추가 후 compare 총 개수
Set<String> availableCompareSheets = int compareTotalAfterFallback = compareMngList.size();
compareDtoList.stream()
.map(MapSheetFallbackYearDto::getMapSheetNum) // 이전연도 추가한 기준연도 값 도협번호만 담기
Set<String> compareSet1 =
compareMngList.stream()
.map(MngListDto::getMapSheetNum)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// 최종 추론 대상 도엽 // 기준연도 기준으로 비교연도에 있는것만 담기 (도협번호) 결국 비교년도와 개수가 같아짐
// target 기준으로 compare 에 존재하는 도엽만 추출 List<String> mapSheetNums =
List<String> filteredTargetList = targetMngList.stream()
targetList.stream().filter(availableCompareSheets::contains).toList(); .map(MngListDto::getMapSheetNum)
.filter(Objects::nonNull)
// compareDtoList도 최종 기준으로 필터 .filter(compareSet1::contains)
Set<String> filteredTargetSet = new HashSet<>(filteredTargetList);
List<MapSheetFallbackYearDto> filteredCompareDtoList =
compareDtoList.stream()
.filter(d -> d.getMapSheetNum() != null)
.filter(d -> filteredTargetSet.contains(d.getMapSheetNum()))
.toList(); .toList();
// compare only 계산 (target에는 없는 compare 도엽 수) log 용 int intersection = mapSheetNums.size();
long compareOnlyCount =
compareDtoList.stream() Set<String> intersectionSet = new HashSet<>(mapSheetNums);
.map(MapSheetFallbackYearDto::getMapSheetNum)
.filter(s -> s != null && !targetList.contains(s)) // 비교연도 같은거 담기(dto list)
.count(); compareMngList =
compareMngList.stream()
.filter(c -> c.getMapSheetNum() != null)
.filter(c -> intersectionSet.contains(c.getMapSheetNum()))
.toList();
// 기준연도 같은거 담기(dto list)
List<MngListDto> filteredTargetMngList =
targetMngList.stream()
.filter(t -> t.getMapSheetNum() != null)
.filter(t -> intersectionSet.contains(t.getMapSheetNum()))
.toList();
// 로그
int targetOnlyExcluded = targetTotal - intersection;
int compareOnly = compareTotalAfterFallback - intersection;
// 연도별 도엽 비교 로그 출력
log.info( log.info(
""" """
===== MapSheet Year Comparison ===== ===== MapSheet Year Comparison =====
target Total: {} target Total: {}
compare Total: {} compare Total(before fallback): {}
compare Total(after fallback): {}
Intersection: {} Intersection: {}
target Only (Excluded): {} target Only (Excluded): {}
compare Only: {} compare Only: {}
==================================== ====================================
""", """,
targetList.size(), // target count targetTotal,
compareDtoList.size(), // compare count compareTotalBeforeFallback,
filteredTargetList.size(), // target 기준으로 compare 비교하여 최종 추론할 도엽 count compareTotalAfterFallback,
targetList.size() - filteredTargetList.size(), // compare에 존재하지 않는 target 도엽 수 intersection,
compareOnlyCount); // target 에 존재하지 않는 compare 도엽수 targetOnlyExcluded,
compareOnly);
// compare 기준 geojson 생성 (년도 fallback 반영) if (mapSheetNums.isEmpty()) {
throw new CustomApiException("NOT_FOUND_MAP_SHEET_NUM", HttpStatus.NOT_FOUND);
}
// compare 기준 geojson 생성
Scene compareScene = Scene compareScene =
getSceneInference( getSceneInference(
filteredCompareDtoList, compareMngList,
req.getCompareYyyy().toString(), req.getCompareYyyy().toString(),
req.getMapSheetScope(), req.getMapSheetScope(),
req.getDetectOption()); req.getDetectOption());
@@ -294,12 +393,18 @@ public class InferenceResultService {
Scene targetScene = Scene targetScene =
getSceneInference( getSceneInference(
req.getTargetYyyy().toString(), req.getTargetYyyy().toString(),
filteredTargetList, mapSheetNums,
req.getMapSheetScope(), req.getMapSheetScope(),
req.getDetectOption()); req.getDetectOption());
// AI 추론 실행 log.info("비교년도 geojson 파일 validation ===== {}", compareScene.getFilePath());
return executeInference(req, targetDtoList, filteredTargetList, compareScene, targetScene); GeoJsonValidator.validateWithRequested(compareScene.getFilePath(), mapSheetNums);
log.info("기준년도 geojson 파일 validation ===== {}", targetScene.getFilePath());
GeoJsonValidator.validateWithRequested(targetScene.getFilePath(), mapSheetNums);
// 추론 실행
return executeInference(req, filteredTargetMngList, mapSheetNums, compareScene, targetScene);
} }
/** /**
@@ -326,20 +431,24 @@ public class InferenceResultService {
.filter(m -> filteredSet.contains(m.getMapSheetNum())) .filter(m -> filteredSet.contains(m.getMapSheetNum()))
.toList(); .toList();
// 추론 실행 목록 테이블 저장, 도엽목록별 상태 체크 테이블 저장
UUID uuid = inferenceResultCoreService.saveInferenceInfo(req, newTargetList); UUID uuid = inferenceResultCoreService.saveInferenceInfo(req, newTargetList);
// 추론 AI 전달 파라미터 생성
pred_requests_areas predRequestsAreas = new pred_requests_areas(); pred_requests_areas predRequestsAreas = new pred_requests_areas();
predRequestsAreas.setInput1_year(req.getCompareYyyy()); predRequestsAreas.setInput1_year(req.getCompareYyyy());
predRequestsAreas.setInput2_year(req.getTargetYyyy()); predRequestsAreas.setInput2_year(req.getTargetYyyy());
predRequestsAreas.setInput1_scene_path(modelComparePath.getFilePath()); predRequestsAreas.setInput1_scene_path(modelComparePath.getFilePath());
predRequestsAreas.setInput2_scene_path(modelTargetPath.getFilePath()); predRequestsAreas.setInput2_scene_path(modelTargetPath.getFilePath());
// 모델정보 조회 dto 생성 후 반환
InferenceSendDto m1 = this.getModelInfo(req.getModel1Uuid()); InferenceSendDto m1 = this.getModelInfo(req.getModel1Uuid());
m1.setPred_requests_areas(predRequestsAreas); m1.setPred_requests_areas(predRequestsAreas);
log.info("[INFERENCE] Start m1 = {}", m1); log.info("[INFERENCE] Start m1 = {}", m1);
Long batchId = ensureAccepted(m1); // AI 호출
Long batchId = inferenceCommonService.ensureAccepted(m1);
SaveInferenceAiDto saveInferenceAiDto = new SaveInferenceAiDto(); SaveInferenceAiDto saveInferenceAiDto = new SaveInferenceAiDto();
saveInferenceAiDto.setUuid(uuid); saveInferenceAiDto.setUuid(uuid);
@@ -351,43 +460,14 @@ public class InferenceResultService {
saveInferenceAiDto.setModelTargetPath(modelTargetPath.getFilePath()); saveInferenceAiDto.setModelTargetPath(modelTargetPath.getFilePath());
saveInferenceAiDto.setModelStartDttm(ZonedDateTime.now()); saveInferenceAiDto.setModelStartDttm(ZonedDateTime.now());
// AI 호출 하고 리턴 받은 정보 추론 실행 목록 테이블에 업데이트
inferenceResultCoreService.update(saveInferenceAiDto); inferenceResultCoreService.update(saveInferenceAiDto);
return uuid; return uuid;
} }
/** /**
* EXCL 로그 * 변화탐지 실행 정보 생성 TODO 미사용, 새로운 추론실행 로직 테스트후 삭제 해야합니다.
*
* @param targetList
* @param compareList
* @param filteredTargetList
*/
private void logYearComparison(
List<String> targetList, List<String> compareList, List<String> filteredTargetList) {
Set<String> targetSet = new HashSet<>(targetList);
long compareOnlyCount = compareList.stream().filter(s -> !targetSet.contains(s)).count();
log.info(
"""
===== MapSheet Year Comparison =====
target Total: {}
compare Total: {}
Intersection: {}
target Only (Excluded): {}
compare Only: {}
====================================
""",
targetList.size(), // target count
compareList.size(), // compare count
filteredTargetList.size(), // target 기준으로 compare 비교하여 최종 추론할 도엽 count
targetList.size() - filteredTargetList.size(), // compare에 존재하지 않는 target 도엽 수
compareOnlyCount); // target 에 존재하지 않는 compare 도엽수
}
/**
* 변화탐지 실행 정보 생성
* *
* @param req * @param req
*/ */
@@ -395,7 +475,7 @@ public class InferenceResultService {
public UUID saveInferenceInfo(InferenceResultDto.RegReq req) { public UUID saveInferenceInfo(InferenceResultDto.RegReq req) {
// 변화탐지 실행 가능 기준 년도 조회 // 변화탐지 실행 가능 기준 년도 조회
List<MngListDto> targetList = mapSheetMngCoreService.getHstMapSheetList(req); List<MngListDto> targetList = null; // mapSheetMngCoreService.getHstMapSheetList(req);
if (targetList.isEmpty()) { if (targetList.isEmpty()) {
throw new CustomApiException("NOT_FOUND_TARGET_YEAR", HttpStatus.NOT_FOUND); throw new CustomApiException("NOT_FOUND_TARGET_YEAR", HttpStatus.NOT_FOUND);
@@ -513,7 +593,7 @@ public class InferenceResultService {
m1.setPred_requests_areas(predRequestsAreas); m1.setPred_requests_areas(predRequestsAreas);
// ai 추론 실행 api 호출 // ai 추론 실행 api 호출
Long batchId = ensureAccepted(m1); Long batchId = inferenceCommonService.ensureAccepted(m1);
// ai 추론 실행후 응답값 update // ai 추론 실행후 응답값 update
SaveInferenceAiDto saveInferenceAiDto = new SaveInferenceAiDto(); SaveInferenceAiDto saveInferenceAiDto = new SaveInferenceAiDto();
@@ -530,145 +610,6 @@ public class InferenceResultService {
return uuid; return uuid;
} }
// 비교년도 탐지 제이터 옵션 별로 조회하여 req에 적용
private List<MapSheetNumDto> createdMngDto(
InferenceResultDto.RegReq req, List<MngListDto> targetList) {
List<String> mapTargetIds = new ArrayList<>();
targetList.forEach(
hstMapSheet -> {
// 비교년도는 target 년도 기준으로 가져옴 파라미터 만들기
mapTargetIds.add(hstMapSheet.getMapSheetNum());
});
// 비교년도 조회
List<String> mapCompareIds = new ArrayList<>();
List<MngListCompareDto> compareList =
mapSheetMngCoreService.getByHstMapSheetCompareList(req.getCompareYyyy(), mapTargetIds);
for (MngListCompareDto dto : compareList) {
// 추론 제외일때 이전년도 파일이 없으면 제외
if (req.getDetectOption().equals(DetectOption.EXCL.getId())) {
int targetYear = req.getTargetYyyy() - 1;
if (dto.getBeforeYear() != targetYear) {
continue;
}
}
// 비교년도는 target 년도 기준으로 가져옴
mapCompareIds.add(dto.getMapSheetNum());
}
Set<String> compareSet =
mapCompareIds.stream()
.filter(Objects::nonNull)
.map(String::trim) // 공백/개행 방지
.collect(Collectors.toSet());
// target 기준 compare 비교하여 서로 있는것만 저장
List<String> commonIds =
mapTargetIds.stream()
.filter(Objects::nonNull)
.map(String::trim)
.filter(compareSet::contains)
.toList();
Set<String> commonIdSet =
commonIds.stream().filter(Objects::nonNull).map(String::trim).collect(Collectors.toSet());
// 저장하기위해 파라미터 다시 구성
List<MapSheetNumDto> mapSheetNum =
targetList.stream()
.filter(dto -> dto.getMapSheetNum() != null)
.filter(dto -> commonIdSet.contains(dto.getMapSheetNum().trim()))
.map(
dto -> {
MapSheetNumDto mapSheetNumDto = new MapSheetNumDto();
mapSheetNumDto.setMapSheetNum(dto.getMapSheetNum());
mapSheetNumDto.setMapSheetName(dto.getMapSheetName());
return mapSheetNumDto;
})
.toList();
return mapSheetNum;
}
/**
* 추론 AI API 호출 batch id를 리턴
*
* @param dto
*/
// 같은함수가 왜 두개지
private Long ensureAccepted(InferenceSendDto dto) {
if (dto == null) {
log.warn("not InferenceSendDto dto");
throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST);
}
// [중복]운영환경일때 경로수정 dean 260226
if (profile != null && profile.equals("prod")) {
log.info("========================================================");
log.info("[CHANGE INFERENCE] profile = {} Inforence req", profile);
log.info("========================================================");
log.info("");
}
// 1) 요청 로그
try {
log.debug("Inference request dto={}", objectMapper.writeValueAsString(dto));
} catch (JsonProcessingException e) {
log.warn("Failed to serialize inference dto", e);
}
// 2) local 환경 임시 처리
// if ("local".equals(profile)) {
// if (dto.getPred_requests_areas() == null) {
// throw new IllegalStateException("pred_requests_areas is null");
// }
//
// dto.getPred_requests_areas().setInput1_scene_path("/kamco-nfs/requests/2023_local.geojson");
//
// dto.getPred_requests_areas().setInput2_scene_path("/kamco-nfs/requests/2024_local.geojson");
// }
// 3) HTTP 호출
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
// 추론 실행 API 호출
ExternalCallResult<String> result =
externalHttpClient.call(inferenceUrl, HttpMethod.POST, dto, headers, String.class);
if (result.statusCode() < 200 || result.statusCode() >= 300) {
log.error("Inference API failed. status={}, body={}", result.statusCode(), result.body());
throw new CustomApiException("BAD_GATEWAY", HttpStatus.BAD_GATEWAY);
}
// 4) 응답 파싱
try {
List<Map<String, Object>> list =
objectMapper.readValue(result.body(), new TypeReference<>() {});
if (list.isEmpty()) {
throw new CustomApiException(
"NOT_FOUND", HttpStatus.NOT_FOUND, "Inference response is empty");
}
Object batchIdObj = list.get(0).get("batch_id");
if (batchIdObj == null) {
throw new CustomApiException(
"NOT_FOUND", HttpStatus.NOT_FOUND, "batch_id not found in response");
}
return Long.valueOf(batchIdObj.toString());
} catch (Exception e) {
log.error("Failed to parse inference response. body={}", result.body(), e);
throw new CustomApiException("INVALID_INFERENCE_RESPONSE", HttpStatus.BAD_GATEWAY);
}
}
/** /**
* 모델정보 조회 dto 생성 후 반환 * 모델정보 조회 dto 생성 후 반환
* *
@@ -731,25 +672,36 @@ public class InferenceResultService {
*/ */
private Scene getSceneInference( private Scene getSceneInference(
String yyyy, List<String> mapSheetNums, String mapSheetScope, String detectOption) { String yyyy, List<String> mapSheetNums, String mapSheetScope, String detectOption) {
return mapSheetMngCoreService.getSceneInference(
yyyy, mapSheetNums, mapSheetScope, detectOption); // geojson 생성시 필요한 영상파일 정보 조회
List<ImageFeature> features =
mapSheetMngCoreService.loadSceneInferenceBySheets(yyyy, mapSheetNums);
if (features == null || features.isEmpty()) {
log.warn(
"NOT_FOUND_MAP_SHEET_NUM : yyyy={}, scenesSize={}",
yyyy,
mapSheetNums == null ? 0 : mapSheetNums.size());
throw new CustomApiException("NOT_FOUND_MAP_SHEET_NUM", HttpStatus.NOT_FOUND);
}
return writeSceneGeoJson(yyyy, mapSheetScope, detectOption, features);
} }
/** /**
* 년도 별로 조회하여 geojson 파일 생성 * 년도 별로 조회하여 geojson 파일 생성
* *
* @param yearDtos * @param yearDtos
* @param year * @param yyyy
* @param mapSheetScope * @param mapSheetScope
* @param detectOption * @param detectOption
* @return * @return
*/ */
private Scene getSceneInference( private Scene getSceneInference(
List<MapSheetFallbackYearDto> yearDtos, List<MngListDto> yearDtos, String yyyy, String mapSheetScope, String detectOption) {
String year,
String mapSheetScope, List<ImageFeature> features =
String detectOption) { mapSheetMngCoreService.loadSceneInferenceByFallbackYears(yearDtos);
return mapSheetMngCoreService.getSceneInference(yearDtos, year, mapSheetScope, detectOption); return writeSceneGeoJson(yyyy, mapSheetScope, detectOption, features);
} }
/** /**
@@ -828,11 +780,17 @@ public class InferenceResultService {
return inferenceResultCoreService.listGetScenes5k(id); return inferenceResultCoreService.listGetScenes5k(id);
} }
/**
* 추론 서버 현황 cpu, gpu 확인
*
* @return 서버 정보
*/
public List<InferenceServerStatusDto> getInferenceServerStatusList() { public List<InferenceServerStatusDto> getInferenceServerStatusList() {
String[] serverNames = inferenceServerName.split(","); String[] serverNames = inferenceServerName.split(",");
int serveCnt = serverNames.length; int serveCnt = serverNames.length;
// 서버정보 조회
List<InferenceServerStatusDto> dtoList = List<InferenceServerStatusDto> dtoList =
inferenceResultCoreService.getInferenceServerStatusList(); inferenceResultCoreService.getInferenceServerStatusList();
int size = dtoList.size(); int size = dtoList.size();
@@ -842,6 +800,7 @@ public class InferenceResultService {
System.out.println("size =" + size); System.out.println("size =" + size);
if (size == 0) { if (size == 0) {
// 서버 정보가 없을때
for (int k = 0; k < serveCnt; k++) { for (int k = 0; k < serveCnt; k++) {
InferenceServerStatusDto dto = new InferenceServerStatusDto(); InferenceServerStatusDto dto = new InferenceServerStatusDto();
dto.setServerName(serverNames[k]); dto.setServerName(serverNames[k]);
@@ -909,17 +868,35 @@ public class InferenceResultService {
return inferenceResultCoreService.getInferenceResultInfo(uuid); return inferenceResultCoreService.getInferenceResultInfo(uuid);
} }
/**
* 분류별 탐지건수 조회
*
* @param uuid 추론 uuid
* @return 분류별 탐지건수 정보
*/
public List<Dashboard> getInferenceClassCountList(UUID uuid) { public List<Dashboard> getInferenceClassCountList(UUID uuid) {
return inferenceResultCoreService.getInferenceClassCountList(uuid); return inferenceResultCoreService.getInferenceClassCountList(uuid);
} }
/**
* 추론결과 geom 목록 조회
*
* @param uuid 추론 uuid
* @param searchGeoReq 추론 결과 상세화면 geom 조회 조건
* @return geom 목록 정보
*/
public Page<Geom> getInferenceGeomList(UUID uuid, SearchGeoReq searchGeoReq) { public Page<Geom> getInferenceGeomList(UUID uuid, SearchGeoReq searchGeoReq) {
return inferenceResultCoreService.getInferenceGeomList(uuid, searchGeoReq); return inferenceResultCoreService.getInferenceGeomList(uuid, searchGeoReq);
} }
/** 추론 종료 */ /**
* 추론 종료
*
* @return 호출한 uuid
*/
@Transactional @Transactional
public UUID deleteInferenceEnd() { public UUID deleteInferenceEnd() {
// 추론 진행중인지 확인
SaveInferenceAiDto dto = inferenceResultCoreService.getProcessing(); SaveInferenceAiDto dto = inferenceResultCoreService.getProcessing();
if (dto == null) { if (dto == null) {
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND); throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND);
@@ -931,13 +908,15 @@ public class InferenceResultService {
headers.setContentType(MediaType.APPLICATION_JSON); headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON)); headers.setAccept(List.of(MediaType.APPLICATION_JSON));
// 종료 api 호출
ExternalCallResult<String> result = ExternalCallResult<String> result =
externalHttpClient.call(url, HttpMethod.DELETE, dto, headers, String.class); externalHttpClient.callLong(url, HttpMethod.DELETE, dto, headers, String.class);
if (!result.success()) { if (!result.success()) {
throw new CustomApiException("BAD_GATEWAY", HttpStatus.BAD_GATEWAY); throw new CustomApiException("BAD_GATEWAY", HttpStatus.BAD_GATEWAY);
} }
// 추론 정보 테이블 update
SaveInferenceAiDto request = new SaveInferenceAiDto(); SaveInferenceAiDto request = new SaveInferenceAiDto();
request.setStatus(Status.FORCED_END.getId()); request.setStatus(Status.FORCED_END.getId());
request.setUuid(dto.getUuid()); request.setUuid(dto.getUuid());
@@ -952,14 +931,17 @@ public class InferenceResultService {
} }
/** /**
* 추론결과 shp zip 파일 다운로드 경로 생성 * 추론결과 shp zip 파일 확인하여 다운로드 경로 생성
* *
* @param uuid * @param uuid 추론 uuid
* @return * @return 32자 추론 uid, shp 파일 경로
*/ */
public Map<String, Object> shpDownloadPath(UUID uuid) { public Map<String, Object> shpDownloadPath(UUID uuid) {
// 추론정보 조회
InferenceLearnDto dto = inferenceResultCoreService.getInferenceUid(uuid); InferenceLearnDto dto = inferenceResultCoreService.getInferenceUid(uuid);
String uid = dto.getUid(); String uid = dto.getUid();
// 파일 경로 생성
Path path = Path.of(datasetDir).resolve(uid).resolve("merge").resolve(uid + ".zip"); Path path = Path.of(datasetDir).resolve(uid).resolve("merge").resolve(uid + ".zip");
Map<String, Object> downloadMap = new HashMap<>(); Map<String, Object> downloadMap = new HashMap<>();
@@ -981,7 +963,7 @@ public class InferenceResultService {
} }
/** /**
* 실행중인 추론 도엽명 목록 * 분석중인 추론 도엽명 목록
* *
* @param uuid uuid * @param uuid uuid
* @return * @return
@@ -989,4 +971,69 @@ public class InferenceResultService {
public List<String> getInferenceRunMapId(UUID uuid) { public List<String> getInferenceRunMapId(UUID uuid) {
return inferenceResultCoreService.getInferenceRunMapId(uuid); return inferenceResultCoreService.getInferenceRunMapId(uuid);
} }
/**
* 파일 경로/이름 , 파일 생성 , 도엽번호 반환
*
* @param yyyy
* @param mapSheetScope
* @param detectOption
* @param sceneInference
* @return Scene
*/
private Scene writeSceneGeoJson(
String yyyy, String mapSheetScope, String detectOption, List<ImageFeature> sceneInference) {
boolean isAll = MapSheetScope.ALL.getId().equals(mapSheetScope);
String optionSuffix = buildOptionSuffix(detectOption);
String targetDir =
"local".equals(activeEnv) ? System.getProperty("user.home") + "/geojson" : inferenceDir;
// 파일명 생성
String filename =
isAll
? String.format("%s_%s_ALL%s.geojson", yyyy, activeEnv, optionSuffix)
: String.format("%s_%s%s.geojson", yyyy, activeEnv, optionSuffix);
Path outputPath = Paths.get(targetDir, filename);
try {
log.info("create Directories outputPath: {}", outputPath);
log.info(
"activeEnv={}, inferenceDir={}, targetDir={}, filename={}",
activeEnv,
inferenceDir,
targetDir,
filename);
log.info("outputPath={}, parent={}", outputPath.toAbsolutePath(), outputPath.getParent());
Files.createDirectories(outputPath.getParent());
new GeoJsonFileWriter()
.exportToFile(sceneInference, "scene_inference_" + yyyy, 5186, outputPath.toString());
Scene scene = new Scene();
scene.setFeatures(sceneInference);
scene.setFilePath(outputPath.toString());
return scene;
} catch (IOException e) {
log.error(
"FAIL_CREATE_MAP_SHEET_FILE: yyyy={}, isAll={}, path={}", yyyy, isAll, outputPath, e);
throw new CustomApiException("INTERNAL_SERVER_ERROR", HttpStatus.INTERNAL_SERVER_ERROR, e);
}
}
/**
* geojson 파일명 Suffix
*
* @param detectOption
* @return
*/
private String buildOptionSuffix(String detectOption) {
if (DetectOption.EXCL.getId().equals(detectOption)) return "_EXCL";
if (DetectOption.PREV.getId().equals(detectOption)) return "_PREV";
return "";
}
} }

View File

@@ -1,13 +1,11 @@
package com.kamco.cd.kamcoback.inference.service; package com.kamco.cd.kamcoback.inference.service;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.Scene;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceLearnDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceLearnDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService; import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService;
import com.kamco.cd.kamcoback.postgres.core.InferenceResultShpCoreService; import com.kamco.cd.kamcoback.postgres.core.InferenceResultShpCoreService;
import com.kamco.cd.kamcoback.postgres.core.MapSheetMngCoreService; import com.kamco.cd.kamcoback.postgres.core.MapSheetMngCoreService;
import com.kamco.cd.kamcoback.scheduler.service.ShpPipelineService; import com.kamco.cd.kamcoback.scheduler.service.ShpPipelineService;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -39,10 +37,16 @@ public class InferenceResultShpService {
@Value("${file.dataset-dir}") @Value("${file.dataset-dir}")
private String datasetDir; private String datasetDir;
/** inference_results 테이블을 기준으로 분석 결과 테이블과 도형 테이블을 최신 상태로 반영한다. */ /**
* 추론 결과 inference 테이블 upsert
*
* @param uuid learn uuid
* @return
*/
@Transactional @Transactional
public InferenceResultShpDto.InferenceCntDto saveInferenceResultData(Long id) { public InferenceResultShpDto.InferenceCntDto saveInferenceResultData(UUID uuid) {
return coreService.buildInferenceData(id); Long learnId = inferenceResultCoreService.getInferenceLearnIdByUuid(uuid);
return coreService.buildInferenceData(learnId);
} }
/** /**
@@ -57,30 +61,13 @@ public class InferenceResultShpService {
return; return;
} }
String batchId = String batchIds =
Stream.of(dto.getM1ModelBatchId(), dto.getM2ModelBatchId(), dto.getM3ModelBatchId()) Stream.of(dto.getM1ModelBatchId(), dto.getM2ModelBatchId(), dto.getM3ModelBatchId())
.filter(Objects::nonNull) .filter(Objects::nonNull)
.map(String::valueOf) .map(String::valueOf)
.collect(Collectors.joining(",")); .collect(Collectors.joining(","));
// shp 파일 비동기 생성 // shp 파일 비동기 생성
shpPipelineService.runPipeline(jarPath, datasetDir, batchId, dto.getUid()); shpPipelineService.runPipeline(jarPath, datasetDir, batchIds, dto.getUid());
}
/**
* 추론 실행전 geojson 파일 생성
*
* @param yyyy
* @param mapSheetScope
* @param detectOption
* @param mapIds
* @return
*/
public Scene createGeojson(
String yyyy, String mapSheetScope, String detectOption, List<String> mapIds) {
Scene getSceneInference =
mapSheetMngCoreService.getSceneInference(yyyy, mapIds, mapSheetScope, detectOption);
log.info("getSceneInference: {}", getSceneInference);
return getSceneInference;
} }
} }

View File

@@ -219,6 +219,9 @@ public class WorkerStatsDto {
@Deprecated @Deprecated
@Schema(description = "[Deprecated] inspectionRemainingCount 사용 권장") @Schema(description = "[Deprecated] inspectionRemainingCount 사용 권장")
private Long remainingInspectCount; private Long remainingInspectCount;
@Schema(description = "파일 다운로드 가능한 폴리곤 수")
private Long downloadPolygonCnt;
} }
@Getter @Getter

View File

@@ -1,14 +1,10 @@
package com.kamco.cd.kamcoback.mapsheet; package com.kamco.cd.kamcoback.mapsheet;
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
import com.kamco.cd.kamcoback.code.service.CommonCodeService;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FilesDto;
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FoldersDto; import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.FoldersDto;
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFilesDto;
import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFoldersDto; import com.kamco.cd.kamcoback.mapsheet.dto.FileDto.SrchFoldersDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngYyyyDto;
import com.kamco.cd.kamcoback.mapsheet.service.MapSheetMngService; import com.kamco.cd.kamcoback.mapsheet.service.MapSheetMngService;
import com.kamco.cd.kamcoback.model.dto.ModelMngDto.ModelUploadResDto; import com.kamco.cd.kamcoback.model.dto.ModelMngDto.ModelUploadResDto;
import com.kamco.cd.kamcoback.upload.dto.UploadDto; import com.kamco.cd.kamcoback.upload.dto.UploadDto;
@@ -42,7 +38,6 @@ import org.springframework.web.multipart.MultipartFile;
@RequestMapping({"/api/imagery/dataset"}) @RequestMapping({"/api/imagery/dataset"})
public class MapSheetMngApiController { public class MapSheetMngApiController {
private final CommonCodeService commonCodeService;
private final MapSheetMngService mapSheetMngService; private final MapSheetMngService mapSheetMngService;
@Value("${file.sync-root-dir}") @Value("${file.sync-root-dir}")
@@ -51,7 +46,7 @@ public class MapSheetMngApiController {
@Value("${file.sync-tmp-dir}") @Value("${file.sync-tmp-dir}")
private String syncRootTmpDir; private String syncRootTmpDir;
@Operation(summary = "영상 데이터 관리 목록 조회", description = "영상 데이터 관리 목록 조회") @Operation(summary = "영상데이터관리 > 목록 조회", description = "영상데이터관리 > 목록 조회")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -70,7 +65,7 @@ public class MapSheetMngApiController {
return ApiResponseDto.ok(mapSheetMngService.findMapSheetMngList()); return ApiResponseDto.ok(mapSheetMngService.findMapSheetMngList());
} }
@Operation(summary = "영상 데이터 관리 상세", description = "영상 데이터 관리 상세") @Operation(summary = "영상데이터관리 > 상세 조회", description = "영상데이터관리 > 상세 조회")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -89,7 +84,7 @@ public class MapSheetMngApiController {
return ApiResponseDto.ok(mapSheetMngService.findMapSheetMng(mngYyyy)); return ApiResponseDto.ok(mapSheetMngService.findMapSheetMng(mngYyyy));
} }
@Operation(summary = "영상관리 > 데이터 등록", description = "영상관리 > 데이터 등록") @Operation(summary = "영상데이터관리 > 데이터 등록", description = "영상데이터관리 > 데이터 등록")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -109,26 +104,7 @@ public class MapSheetMngApiController {
return ApiResponseDto.ok(mapSheetMngService.mngDataSave(AddReq)); return ApiResponseDto.ok(mapSheetMngService.mngDataSave(AddReq));
} }
@Operation(summary = "영상관리 > 작업완료", description = "영상관리 > 작업완료") @Operation(summary = "영상데이터관리 > 데이터 등록 > 연도 선택 목록", description = "영상데이터관리 > 데이터 등록 > 연도 선택 목록")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "작업완료 처리 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PutMapping("/mng-complete")
public ApiResponseDto<MapSheetMngDto.DmlReturn> mngComplete(@RequestParam @Valid int mngYyyy) {
return ApiResponseDto.ok(mapSheetMngService.mngComplete(mngYyyy));
}
@Operation(summary = "영상 데이터 관리 년도 목록", description = "영상 데이터 관리 년도 목록")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -147,7 +123,7 @@ public class MapSheetMngApiController {
return ApiResponseDto.ok(mapSheetMngService.findMapSheetMngYyyyList()); return ApiResponseDto.ok(mapSheetMngService.findMapSheetMngYyyyList());
} }
@Operation(summary = "영상 데이터 관리 오류 목록", description = "영상 데이터 관리 오류 목록") @Operation(summary = "영상데이터관리 > 상세 > 오류 처리 내역", description = "영상데이터관리 > 상세 > 오류 처리 내역")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -166,42 +142,9 @@ public class MapSheetMngApiController {
return ApiResponseDto.ok(mapSheetMngService.findMapSheetErrorList(searchReq)); return ApiResponseDto.ok(mapSheetMngService.findMapSheetErrorList(searchReq));
} }
/* @Operation(
@Operation(summary = "오류데이터 팝업 > 업로드 처리", description = "오류데이터 팝업 > 업로드 처리") summary = "영상데이터관리 > 상세 > 오류 처리 내역 > 업로드 (페어 파일 저장)",
@ApiResponses( description = "영상데이터관리 > 상세 > 오류 처리 내역 > 업로드 (페어 파일 저장)")
value = {
@ApiResponse(
responseCode = "201",
description = "업로드 처리 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PutMapping("/upload-process")
public ApiResponseDto<MapSheetMngDto.DmlReturn> uploadProcess(
@RequestBody @Valid List<Long> hstUidList) {
return ApiResponseDto.ok(mapSheetMngService.uploadProcess(hstUidList));
}
*/
/*
@Operation(summary = "오류데이터 팝업 > 추론 제외", description = "오류데이터 팝업 > 추론 제외")
@PutMapping("/except-inference")
public ApiResponseDto<MapSheetMngDto.DmlReturn> updateExceptUseInference(
@RequestBody @Valid List<Long> hstUidList) {
return ApiResponseDto.ok(mapSheetMngService.updateExceptUseInference(hstUidList));
}
*/
@Operation(summary = "페어 파일 업로드", description = "TFW/TIF 두 파일을 쌍으로 업로드 및 검증")
@PostMapping(value = "/upload-pair", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PostMapping(value = "/upload-pair", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponseDto<MapSheetMngDto.DmlReturn> uploadPair( public ApiResponseDto<MapSheetMngDto.DmlReturn> uploadPair(
@RequestPart("tfw") MultipartFile tfwFile, @RequestPart("tfw") MultipartFile tfwFile,
@@ -213,7 +156,9 @@ public class MapSheetMngApiController {
mapSheetMngService.uploadPair(tfwFile, tifFile, hstUid, tifFileSize)); mapSheetMngService.uploadPair(tfwFile, tifFile, hstUid, tifFileSize));
} }
@Operation(summary = "영상관리 > 파일조회", description = "영상관리 > 파일조회") @Operation(
summary = "영상데이터관리 > 상세 > 오류 처리 내역 > 중복제거 > 팝업 내 해당 파일조회",
description = "영상데이터관리 > 상세 > 오류 처리 내역 > 중복제거 > 팝업 내 해당 파일조회")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -234,13 +179,13 @@ public class MapSheetMngApiController {
} }
@Operation( @Operation(
summary = "영상관리 > 파일사용설정 및 중복제거", summary = "영상데이터관리 > 상세 > 오류 처리 내역 > 중복제거 업데이트",
description = "영상관리 >파일사용설정 및 중복제거(중복파일제거 및 선택파일사용설정)") description = "영상데이터관리 > 상세 > 오류 처리 내역 > 중복제거 업데이트")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "201", responseCode = "201",
description = "파일사용설정 처리 성공", description = "중복제거 업데이트 처리 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
@@ -255,7 +200,7 @@ public class MapSheetMngApiController {
return ApiResponseDto.ok(mapSheetMngService.setUseByFileUidMngFile(fileUids)); return ApiResponseDto.ok(mapSheetMngService.setUseByFileUidMngFile(fileUids));
} }
@Operation(summary = "폴더 조회", description = "폴더 조회 (ROOT:/app/original-images 이하로 경로입력)") @Operation(summary = "영상데이터관리 > 데이터 등록 > NAS 폴더 선택", description = "영상데이터관리 > 데이터 등록 > NAS 폴더 선택")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -274,45 +219,9 @@ public class MapSheetMngApiController {
return ApiResponseDto.createOK(mapSheetMngService.getFolderAll(srchDto)); return ApiResponseDto.createOK(mapSheetMngService.getFolderAll(srchDto));
} }
@Operation(summary = "지정폴더내 파일목록 조회", description = "지정폴더내 파일목록 조회") @Operation(
@ApiResponses( summary = "영상데이터관리 > 상세 > 오류 처리 내역 > 업로드 (영상 tif 대용량 파일 분할 전송)",
value = { description = "영상데이터관리 > 상세 > 오류 처리 내역 > 업로드 (영상 tif 대용량 파일 분할 전송)")
@ApiResponse(
responseCode = "200",
description = "조회 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/file-list")
public ApiResponseDto<FilesDto> getFiles(@RequestBody SrchFilesDto srchDto) {
return ApiResponseDto.createOK(mapSheetMngService.getFilesAll(srchDto));
}
@Operation(summary = "영상 데이터 관리 완료 년도 목록 조회", description = "영상 데이터 관리 완료 년도 목록 조회")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "조회 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/mng-done-yyyy-list")
public ApiResponseDto<List<MngYyyyDto>> findMapSheetMngDoneYyyyList() {
return ApiResponseDto.ok(mapSheetMngService.findMapSheetMngDoneYyyyList());
}
@Operation(summary = "영상 tif 대용량 파일 분할 전송", description = "영상 tif 파일 대용량 파일을 청크 단위로 전송합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse(responseCode = "200", description = "청크 업로드 성공", content = @Content), @ApiResponse(responseCode = "200", description = "청크 업로드 성공", content = @Content),

View File

@@ -59,10 +59,20 @@ public class MapSheetMngService {
@Value("${file.sync-file-extention}") @Value("${file.sync-file-extention}")
private String syncFileExtention; private String syncFileExtention;
/**
* 영상데이터관리 > 목록 조회
*
* @return
*/
public List<MngDto> findMapSheetMngList() { public List<MngDto> findMapSheetMngList() {
return mapSheetMngCoreService.findMapSheetMngList(); return mapSheetMngCoreService.findMapSheetMngList();
} }
/**
* 영상데이터관리 > 데이터 등록 > 연도 선택 목록
*
* @return
*/
public List<Integer> findMapSheetMngYyyyList() { public List<Integer> findMapSheetMngYyyyList() {
return mapSheetMngCoreService.findMapSheetMngYyyyList(); return mapSheetMngCoreService.findMapSheetMngYyyyList();
} }
@@ -77,6 +87,12 @@ public class MapSheetMngService {
.toList(); .toList();
} }
/**
* 영상데이터관리 > 상세 조회
*
* @param mngYyyy
* @return
*/
public MngDto findMapSheetMng(int mngYyyy) { public MngDto findMapSheetMng(int mngYyyy) {
return mapSheetMngCoreService.findMapSheetMng(mngYyyy); return mapSheetMngCoreService.findMapSheetMng(mngYyyy);
} }
@@ -89,6 +105,12 @@ public class MapSheetMngService {
return new DmlReturn("success", "작업완료 처리되었습니다."); return new DmlReturn("success", "작업완료 처리되었습니다.");
} }
/**
* 영상데이터관리 > 상세 > 오류 처리 내역
*
* @param searchReq
* @return
*/
public Page<ErrorDataDto> findMapSheetErrorList(@Valid ErrorSearchReq searchReq) { public Page<ErrorDataDto> findMapSheetErrorList(@Valid ErrorSearchReq searchReq) {
return mapSheetMngCoreService.findMapSheetErrorList(searchReq); return mapSheetMngCoreService.findMapSheetErrorList(searchReq);
} }
@@ -97,12 +119,20 @@ public class MapSheetMngService {
return mapSheetMngCoreService.findMapSheetError(hstUid); return mapSheetMngCoreService.findMapSheetError(hstUid);
} }
/**
* 영상데이터관리 > 데이터 등록
*
* @param addReq
* @return
*/
@Transactional @Transactional
public DmlReturn mngDataSave(AddReq addReq) { public DmlReturn mngDataSave(AddReq addReq) {
addReq.setCreatedUid(userUtil.getId()); addReq.setCreatedUid(userUtil.getId());
int execCnt = mapSheetMngCoreService.mngDataSave(addReq); int execCnt =
mapSheetMngCoreService.mngDataSave(addReq); // tb_map_sheet_mng_hst 테이블에 insert된 개수 리턴됨
return new MapSheetMngDto.DmlReturn("success", addReq.getMngYyyy() + "년, " + execCnt + "건 생성"); return new MapSheetMngDto.DmlReturn("success", addReq.getMngYyyy() + "년, " + execCnt + "건 생성");
} }
@@ -110,6 +140,15 @@ public class MapSheetMngService {
return mapSheetMngCoreService.updateExceptUseInference(hstUidList); return mapSheetMngCoreService.updateExceptUseInference(hstUidList);
} }
/**
* 영상데이터관리 > 상세 > 오류 처리 내역 > 업로드 (페어 파일 저장)
*
* @param tfwFile
* @param tifFile
* @param hstUid
* @param tifFileSize
* @return
*/
@Transactional @Transactional
public DmlReturn uploadPair( public DmlReturn uploadPair(
MultipartFile tfwFile, String tifFile, Long hstUid, Long tifFileSize) { MultipartFile tfwFile, String tifFile, Long hstUid, Long tifFileSize) {
@@ -118,6 +157,7 @@ public class MapSheetMngService {
DmlReturn dmlReturn = new DmlReturn("success", "UPLOAD COMPLETE"); DmlReturn dmlReturn = new DmlReturn("success", "UPLOAD COMPLETE");
// hst 테이블에 선택한 hstUid row 정보 조회
ErrorDataDto errDto = mapSheetMngCoreService.findMapSheetError(hstUid); ErrorDataDto errDto = mapSheetMngCoreService.findMapSheetError(hstUid);
if (errDto == null) { if (errDto == null) {
@@ -135,33 +175,14 @@ public class MapSheetMngService {
return dmlReturn; return dmlReturn;
} }
// TODO 삭제? // ★ tif 파일은 대용량 업로드에서 아래 정보들을 체크하기 때문에 체크 로직에서 제외됨
MngDto mngDto = mapSheetMngCoreService.findMapSheetMng(errDto.getMngYyyy());
// 중복체크 -> 도엽50k/uuid 경로에 업로드 할 거라 overwrite 되지 않음
// if (!overwrite) {
// dmlReturn =
// this.duplicateFile(
// errDto.getMngYyyy(), tfwFile.getOriginalFilename(), tifFile.getOriginalFilename());
// if (dmlReturn.getFlag().equals("duplicate")) {
// return dmlReturn;
// }
// }
// 멀티파트 파일 tmp폴더 저장(파일형식 체크를 위해) // 멀티파트 파일 tmp폴더 저장(파일형식 체크를 위해)
String tfwTmpPath = tmpPath + tfwFile.getOriginalFilename(); String tfwTmpPath = tmpPath + tfwFile.getOriginalFilename();
// String tifTmpPath = tmpPath + tifFile;
if (!FIleChecker.multipartSaveTo(tfwFile, tfwTmpPath)) { if (!FIleChecker.multipartSaveTo(tfwFile, tfwTmpPath)) {
return new DmlReturn("fail", "UPLOAD ERROR"); return new DmlReturn("fail", "UPLOAD ERROR");
} }
// if (!FIleChecker.multipartSaveTo(tifFile, tifTmpPath)) {
// return new DmlReturn("fail", "UPLOAD ERROR");
// }
// if (!FIleChecker.cmmndGdalInfo(tifTmpPath)) {
// return new DmlReturn("fail", "TIF TYPE ERROR");
// }
if (!FIleChecker.checkTfw(tfwTmpPath)) { if (!FIleChecker.checkTfw(tfwTmpPath)) {
return new DmlReturn("fail", "TFW TYPE ERROR"); return new DmlReturn("fail", "TFW TYPE ERROR");
} }
@@ -175,7 +196,6 @@ public class MapSheetMngService {
} }
Path tfwTmpSavePath = Paths.get(tfwTmpPath); Path tfwTmpSavePath = Paths.get(tfwTmpPath);
// Path tifTmpSavePath = Paths.get(tifTmpPath);
Path tfwTargetPath = null; Path tfwTargetPath = null;
Path tifTargetPath = null; Path tifTargetPath = null;
@@ -222,7 +242,7 @@ public class MapSheetMngService {
try { try {
Files.move(tfwTmpSavePath, tfwTargetPath, StandardCopyOption.REPLACE_EXISTING); Files.move(tfwTmpSavePath, tfwTargetPath, StandardCopyOption.REPLACE_EXISTING);
// Files.move(tifTmpSavePath, tifTargetPath, StandardCopyOption.REPLACE_EXISTING); // tif파일은 대용량 업로드에서 이미 업로드 했기 때문에 완료된 상태
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -234,7 +254,7 @@ public class MapSheetMngService {
updReqSyncCheckState.setFilePath(uploadPath); updReqSyncCheckState.setFilePath(uploadPath);
updReqSyncCheckState.setSyncCheckTfwFileName(tfwFile.getOriginalFilename()); updReqSyncCheckState.setSyncCheckTfwFileName(tfwFile.getOriginalFilename());
updReqSyncCheckState.setSyncCheckTifFileName(tifFile); updReqSyncCheckState.setSyncCheckTifFileName(tifFile);
updReqSyncCheckState.setSyncCheckState("DONE"); updReqSyncCheckState.setSyncCheckState("DONE"); // 오류처리 완료
mapSheetMngCoreService.updateMapSheetMngHstSyncCheckState(updReqSyncCheckState); mapSheetMngCoreService.updateMapSheetMngHstSyncCheckState(updReqSyncCheckState);
// 파일정보 업데이트 // 파일정보 업데이트
mapSheetMngCoreService.deleteByHstUidMngFile(hstUid); mapSheetMngCoreService.deleteByHstUidMngFile(hstUid);
@@ -339,7 +359,12 @@ public class MapSheetMngService {
public FoldersDto getFolderAll(SrchFoldersDto srchDto) { public FoldersDto getFolderAll(SrchFoldersDto srchDto) {
String dirPath = syncRootDir + srchDto.getDirPath(); // "경로중복"
String dirPath =
(srchDto.getDirPath() == null || srchDto.getDirPath().isEmpty())
? syncRootDir
: srchDto.getDirPath();
// String dirPath = syncRootDir + srchDto.getDirPath();
log.info("[FIND_FOLDER] DIR : {}", dirPath); log.info("[FIND_FOLDER] DIR : {}", dirPath);
List<FIleChecker.Folder> folderList = List<FIleChecker.Folder> folderList =
@@ -381,6 +406,11 @@ public class MapSheetMngService {
mapSheetMngCoreService.getSceneInference(yyyy); mapSheetMngCoreService.getSceneInference(yyyy);
} }
/**
* 연도 목록 조회
*
* @return
*/
public List<MngYyyyDto> findMapSheetMngDoneYyyyList() { public List<MngYyyyDto> findMapSheetMngDoneYyyyList() {
List<MngDto> mngList = mapSheetMngCoreService.findMapSheetMngList(); List<MngDto> mngList = mapSheetMngCoreService.findMapSheetMngList();

View File

@@ -67,6 +67,7 @@ public class ModelMngDto {
private String clsModelVersion; private String clsModelVersion;
private Double priority; private Double priority;
private String memo; private String memo;
private UUID uuid;
public Basic( public Basic(
Long modelUid, Long modelUid,
@@ -89,7 +90,8 @@ public class ModelMngDto {
String clsModelFileName, String clsModelFileName,
String clsModelVersion, String clsModelVersion,
double priority, double priority,
String memo) { String memo,
UUID uuid) {
this.modelUid = modelUid; this.modelUid = modelUid;
this.modelVer = modelVer; this.modelVer = modelVer;
this.createCompleteDttm = createCompleteDttm; this.createCompleteDttm = createCompleteDttm;
@@ -111,6 +113,7 @@ public class ModelMngDto {
this.clsModelVersion = clsModelVersion; this.clsModelVersion = clsModelVersion;
this.priority = priority; this.priority = priority;
this.memo = memo; this.memo = memo;
this.uuid = uuid;
} }
} }

View File

@@ -41,6 +41,16 @@ public class ModelMngService {
@Value("${file.pt-FileName}") @Value("${file.pt-FileName}")
private String ptFileName; private String ptFileName;
/**
* 모델조회
*
* @param searchReq 페이징
* @param startDate 시작날짜
* @param endDate 종료날짜
* @param modelType 모델 타입 G1, G2, G3
* @param searchVal 모델 ver
* @return 모델 목록
*/
public Page<ModelMngDto.ModelList> findModelMgmtList( public Page<ModelMngDto.ModelList> findModelMgmtList(
ModelMngDto.searchReq searchReq, ModelMngDto.searchReq searchReq,
LocalDate startDate, LocalDate startDate,

View File

@@ -46,6 +46,13 @@ public class AuditLogCoreService
return auditLogRepository.findLogByAccount(searchRange, searchValue); return auditLogRepository.findLogByAccount(searchRange, searchValue);
} }
/**
* 다운로드 이력 조회
*
* @param searchReq 페이징 파라미터
* @param downloadReq 다운로드 이력 팝업 검색 조건
* @return 다운로드 이력 정보 목록
*/
public Page<AuditLogDto.DownloadRes> findLogByAccount( public Page<AuditLogDto.DownloadRes> findLogByAccount(
AuditLogDto.searchReq searchReq, DownloadReq downloadReq) { AuditLogDto.searchReq searchReq, DownloadReq downloadReq) {
return auditLogRepository.findDownloadLog(searchReq, downloadReq); return auditLogRepository.findDownloadLog(searchReq, downloadReq);

View File

@@ -108,8 +108,8 @@ public class ChangeDetectionCoreService {
* @return * @return
*/ */
public ChangeDetectionDto.PolygonFeatureList getPolygonListByCd( public ChangeDetectionDto.PolygonFeatureList getPolygonListByCd(
String chnDtctId, String cdObjectId, List<String> cdObjectIds) { String chnDtctId, String cdObjectId, List<String> cdObjectIds, String pnu) {
return changeDetectionRepository.getPolygonListByCd(chnDtctId, cdObjectId, cdObjectIds); return changeDetectionRepository.getPolygonListByCd(chnDtctId, cdObjectId, cdObjectIds, pnu);
} }
/** /**
@@ -121,8 +121,8 @@ public class ChangeDetectionCoreService {
* @return * @return
*/ */
public ChangeDetectionDto.PointFeatureList getPointListByCd( public ChangeDetectionDto.PointFeatureList getPointListByCd(
String chnDtctId, String cdObjectId, List<String> cdObjectIds) { String chnDtctId, String cdObjectId, List<String> cdObjectIds, String pnu) {
return changeDetectionRepository.getPointListByCd(chnDtctId, cdObjectId, cdObjectIds); return changeDetectionRepository.getPointListByCd(chnDtctId, cdObjectId, cdObjectIds, pnu);
} }
/** /**

View File

@@ -77,4 +77,12 @@ public class GukYuinCoreService {
public void updateMapSheetInferenceLabelEndStatus(Long learnId) { public void updateMapSheetInferenceLabelEndStatus(Long learnId) {
gukYuinRepository.updateMapSheetInferenceLabelEndStatus(learnId); gukYuinRepository.updateMapSheetInferenceLabelEndStatus(learnId);
} }
public List<String> findStbltObjectIds(String uid, String mapSheetNum) {
return gukYuinRepository.findStbltObjectIds(uid, mapSheetNum);
}
public Integer updateStbltRandomData(String uid, int updateCnt) {
return gukYuinRepository.updateStbltRandomData(uid, updateCnt);
}
} }

View File

@@ -79,12 +79,13 @@ public class InferenceResultCoreService {
} }
/** /**
* 변화탐지 실행 정보 생성 * 변화탐지 실행 정보 생성 - 추론 실행 목록 테이블 저장, 도엽목록별 상태 체크 테이블 저장
* *
* @param req * @param req 추론 실행 목록 uuid
*/ */
public UUID saveInferenceInfo(InferenceResultDto.RegReq req, List<MngListDto> targetList) { public UUID saveInferenceInfo(InferenceResultDto.RegReq req, List<MngListDto> targetList) {
// 대표 도엽명 외 N 건 실행 문구 만들기 위해 Null, 중복 제거
List<MngListDto> distinctList = List<MngListDto> distinctList =
targetList.stream() targetList.stream()
.filter(dto -> dto.getMapSheetName() != null && !dto.getMapSheetName().isBlank()) .filter(dto -> dto.getMapSheetName() != null && !dto.getMapSheetName().isBlank())
@@ -124,17 +125,13 @@ public class InferenceResultCoreService {
mapSheetLearnEntity.setDetectingCnt(0L); mapSheetLearnEntity.setDetectingCnt(0L);
mapSheetLearnEntity.setTotalJobs((long) targetList.size()); mapSheetLearnEntity.setTotalJobs((long) targetList.size());
// 회차는 국유인 반영할때 update로 변경됨
// mapSheetLearnEntity.setStage(
// mapSheetLearnRepository.getLearnStage(req.getCompareYyyy(), req.getTargetYyyy()));
// learn 테이블 저장 // learn 테이블 저장
MapSheetLearnEntity savedLearn = mapSheetLearnRepository.save(mapSheetLearnEntity); MapSheetLearnEntity savedLearn = mapSheetLearnRepository.save(mapSheetLearnEntity);
final int CHUNK = 1000; final int CHUNK = 1000;
List<MapSheetLearn5kEntity> buffer = new ArrayList<>(CHUNK); List<MapSheetLearn5kEntity> buffer = new ArrayList<>(CHUNK);
// learn 도엽별 저장 // learn 도엽별 저장, 도엽수가 많으므로 1000개 씩 저장함
for (MngListDto mngDto : targetList) { for (MngListDto mngDto : targetList) {
MapSheetLearn5kEntity entity = new MapSheetLearn5kEntity(); MapSheetLearn5kEntity entity = new MapSheetLearn5kEntity();
entity.setLearn(savedLearn); entity.setLearn(savedLearn);
@@ -145,12 +142,15 @@ public class InferenceResultCoreService {
buffer.add(entity); buffer.add(entity);
if (buffer.size() == CHUNK) { if (buffer.size() == CHUNK) {
// 도엽별 저장 learn 5k 테이블
flushChunk(buffer); flushChunk(buffer);
buffer.clear(); buffer.clear();
} }
} }
// chunk 남은거 처리
if (!buffer.isEmpty()) { if (!buffer.isEmpty()) {
// 도엽별 저장 learn 5k 테이블
flushChunk(buffer); flushChunk(buffer);
buffer.clear(); buffer.clear();
} }
@@ -159,9 +159,9 @@ public class InferenceResultCoreService {
} }
/** /**
* 도엽별 저장 * 도엽별 저장 learn 5k 테이블
* *
* @param buffer * @param buffer 저장 정보
*/ */
private void flushChunk(List<MapSheetLearn5kEntity> buffer) { private void flushChunk(List<MapSheetLearn5kEntity> buffer) {
@@ -271,7 +271,7 @@ public class InferenceResultCoreService {
.getInferenceResultByUuid(request.getUuid()) .getInferenceResultByUuid(request.getUuid())
.orElseThrow(EntityNotFoundException::new); .orElseThrow(EntityNotFoundException::new);
// M1/M2/M3 영역 업데이트 // G1/G2/G3 영역 업데이트
if (request.getType() != null) { if (request.getType() != null) {
applyModelUpdate(entity, request); applyModelUpdate(entity, request);
} }
@@ -283,7 +283,7 @@ public class InferenceResultCoreService {
.distinct() // 중복 방지 (선택) .distinct() // 중복 방지 (선택)
.toList(); .toList();
// testing 추론결과 테이블 조회하여 탐지 개수 업데이트 // testing 테이블 추론결과 테이블 조회하여 탐지 개수 업데이트
Long testing = getInferenceResultCnt(batchIds); Long testing = getInferenceResultCnt(batchIds);
// 공통 영역 업데이트 // 공통 영역 업데이트
@@ -422,7 +422,7 @@ public class InferenceResultCoreService {
/** /**
* 추론 진행중인지 확인 * 추론 진행중인지 확인
* *
* @return * @return 추론 실행중인 추론 uuid, batch id
*/ */
public SaveInferenceAiDto getProcessing() { public SaveInferenceAiDto getProcessing() {
MapSheetLearnEntity entity = mapSheetLearnRepository.getProcessing(); MapSheetLearnEntity entity = mapSheetLearnRepository.getProcessing();
@@ -453,17 +453,30 @@ public class InferenceResultCoreService {
* @return * @return
*/ */
public AnalResultInfo getInferenceResultInfo(UUID uuid) { public AnalResultInfo getInferenceResultInfo(UUID uuid) {
// 추론 결과 정보조회
AnalResultInfo resultInfo = mapSheetLearnRepository.getInferenceResultInfo(uuid); AnalResultInfo resultInfo = mapSheetLearnRepository.getInferenceResultInfo(uuid);
// bbox, point 조회
BboxPointDto bboxPointDto = mapSheetLearnRepository.getBboxPoint(uuid); BboxPointDto bboxPointDto = mapSheetLearnRepository.getBboxPoint(uuid);
resultInfo.setBboxGeom(bboxPointDto.getBboxGeom()); resultInfo.setBboxGeom(bboxPointDto.getBboxGeom());
resultInfo.setBboxCenterPoint(bboxPointDto.getBboxCenterPoint()); resultInfo.setBboxCenterPoint(bboxPointDto.getBboxCenterPoint());
return resultInfo; return resultInfo;
} }
/**
* 분류별 탐지건수 조회
*
* @param uuid 추론 uuid
* @return 분류별 탐지건수 정보
*/
public List<Dashboard> getInferenceClassCountList(UUID uuid) { public List<Dashboard> getInferenceClassCountList(UUID uuid) {
return mapSheetLearnRepository.getInferenceClassCountList(uuid); return mapSheetLearnRepository.getInferenceClassCountList(uuid);
} }
/**
* @param uuid 추론 uuid
* @param searchGeoReq 추론 결과 상세화면 geom 조회 조건
* @return geom 목록 정보
*/
public Page<Geom> getInferenceGeomList(UUID uuid, SearchGeoReq searchGeoReq) { public Page<Geom> getInferenceGeomList(UUID uuid, SearchGeoReq searchGeoReq) {
return mapSheetLearnRepository.getInferenceGeomList(uuid, searchGeoReq); return mapSheetLearnRepository.getInferenceGeomList(uuid, searchGeoReq);
} }
@@ -522,15 +535,21 @@ public class InferenceResultCoreService {
return list.stream().map(InferenceResultsTestingEntity::toDto).toList(); return list.stream().map(InferenceResultsTestingEntity::toDto).toList();
} }
/**
* 테스팅 테이블 조회하여 탐지건수 조회
*
* @param batchIds batchIds
* @return batchIds 조회 count 수
*/
public Long getInferenceResultCnt(List<Long> batchIds) { public Long getInferenceResultCnt(List<Long> batchIds) {
return inferenceResultsTestingRepository.getInferenceResultCnt(batchIds); return inferenceResultsTestingRepository.getInferenceResultCnt(batchIds);
} }
/** /**
* uid 조회 * 추론 정보 조회 하여 batch id, 32자 uid 리턴
* *
* @param uuid * @param uuid 추론 uuid
* @return * @return 추론정보
*/ */
public InferenceLearnDto getInferenceUid(UUID uuid) { public InferenceLearnDto getInferenceUid(UUID uuid) {
MapSheetLearnEntity entity = inferenceResultRepository.getInferenceUid(uuid).orElse(null); MapSheetLearnEntity entity = inferenceResultRepository.getInferenceUid(uuid).orElse(null);
@@ -547,7 +566,7 @@ public class InferenceResultCoreService {
} }
/** /**
* 실행중인 추론 도엽명 목록 * 분석중인 추론 도엽명 목록
* *
* @param uuid 추론 실행중인 uuid * @param uuid 추론 실행중인 uuid
* @return * @return
@@ -556,11 +575,37 @@ public class InferenceResultCoreService {
return mapSheetLearn5kRepository.getInferenceRunMapId(uuid); return mapSheetLearn5kRepository.getInferenceRunMapId(uuid);
} }
/**
* 실패 처리되어야 할 목록 중에 아직 실패로 표시되지 않은 ID 조회
*
* @param uuid 추론 uuid
* @param failMapIds AI API 연결하여 조회한 실패 job id
* @param type 모델 타입
* @return job id
*/
public List<Long> findFail5kList(UUID uuid, List<Long> failMapIds, String type) { public List<Long> findFail5kList(UUID uuid, List<Long> failMapIds, String type) {
return mapSheetLearn5kRepository.findFail5kList(uuid, failMapIds, type); return mapSheetLearn5kRepository.findFail5kList(uuid, failMapIds, type);
} }
/**
* 완료된 것으로 들어온 목록 중 실제로 존재하는 5k jobId 조회
*
* @param uuid 추론 uuid
* @param completedIds AI API 연결하여 조회한 성공 job id
* @param type 모델 타입
* @return job id
*/
public List<Long> findCompleted5kList(UUID uuid, List<Long> completedIds, String type) { public List<Long> findCompleted5kList(UUID uuid, List<Long> completedIds, String type) {
return mapSheetLearn5kRepository.findCompleted5kList(uuid, completedIds, type); return mapSheetLearn5kRepository.findCompleted5kList(uuid, completedIds, type);
} }
/**
* testing 테이블 결과로 기본정보 조회
*
* @param batchIds batch id
* @return batch id, model ver, year 정보
*/
public List<InferenceResultsTestingDto.Basic> getInferenceResultGroupList(List<Long> batchIds) {
return inferenceResultsTestingRepository.getInferenceResultGroupList(batchIds);
}
} }

View File

@@ -3,10 +3,12 @@ package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
import com.kamco.cd.kamcoback.postgres.repository.Inference.InferenceResultRepository; import com.kamco.cd.kamcoback.postgres.repository.Inference.InferenceResultRepository;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@Log4j2
@RequiredArgsConstructor @RequiredArgsConstructor
public class InferenceResultShpCoreService { public class InferenceResultShpCoreService {
@@ -15,15 +17,27 @@ public class InferenceResultShpCoreService {
/** /**
* inference_results 기준으로 - tb_map_sheet_anal_data_inference - * inference_results 기준으로 - tb_map_sheet_anal_data_inference -
* tb_map_sheet_anal_data_inference_geom 테이블을 최신 상태로 구성한다. * tb_map_sheet_anal_data_inference_geom 테이블을 최신 상태로 구성한다.
*
* @param id learn id
* @return
*/ */
@Transactional @Transactional
public InferenceResultShpDto.InferenceCntDto buildInferenceData(Long id) { public InferenceResultShpDto.InferenceCntDto buildInferenceData(Long id) {
Long analId = repo.upsertGroupsFromMapSheetAnal(id);
repo.upsertGroupsFromInferenceResults(analId);
repo.upsertGeomsFromInferenceResults(analId);
repo.upsertSttcFromInferenceResults(analId);
InferenceResultShpDto.InferenceCntDto cntDto = new InferenceResultShpDto.InferenceCntDto();
Long analId = repo.upsertGroupsFromMapSheetAnal(id);
int analDataCnt = repo.upsertGroupsFromInferenceResults(analId);
int geomCnt = repo.upsertGeomsFromInferenceResults(analId);
int sttcCnt = repo.upsertSttcFromInferenceResults(analId);
InferenceResultShpDto.InferenceCntDto cntDto = new InferenceResultShpDto.InferenceCntDto();
cntDto.setSheetAnalDataCnt(analDataCnt);
cntDto.setInferenceGeomCnt(geomCnt);
cntDto.setInferenceSttcnt(sttcCnt);
log.info(
"[ANAL SAVE] analId={}, tb_map_sheet_anal_data_inference={}, tb_map_sheet_anal_data_inference_geom={}, tb_map_sheet_anal_sttc={}",
analId,
analDataCnt,
geomCnt,
sttcCnt);
return cntDto; return cntDto;
} }
} }

View File

@@ -1,14 +1,8 @@
package com.kamco.cd.kamcoback.postgres.core; package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.common.enums.MngStateType; import com.kamco.cd.kamcoback.common.enums.MngStateType;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter; import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature; import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.Scene;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.DetectOption;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetFallbackYearDto;
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;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListCompareDto; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListCompareDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto;
@@ -22,11 +16,11 @@ import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -37,7 +31,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -55,13 +48,20 @@ public class MapSheetMngCoreService {
@Value("${file.sync-root-dir}") @Value("${file.sync-root-dir}")
private String syncRootDir; private String syncRootDir;
@Value("${inference.geojson-dir}") /**
private String inferenceDir; * 영상데이터관리 > 목록 조회
*
* @return
*/
public List<MapSheetMngDto.MngDto> findMapSheetMngList() { public List<MapSheetMngDto.MngDto> findMapSheetMngList() {
return mapSheetMngRepository.findMapSheetMngList(); return mapSheetMngRepository.findMapSheetMngList();
} }
/**
* 영상데이터관리 > 데이터 등록 > 연도 선택 목록
*
* @return
*/
public List<Integer> findMapSheetMngYyyyList() { public List<Integer> findMapSheetMngYyyyList() {
return mapSheetMngRepository.findMapSheetMngYyyyList(); return mapSheetMngRepository.findMapSheetMngYyyyList();
} }
@@ -78,6 +78,12 @@ public class MapSheetMngCoreService {
return mapSheetMngRepository.getYears(req); return mapSheetMngRepository.getYears(req);
} }
/**
* 영상데이터관리 > 상세 조회
*
* @param mngYyyy
* @return
*/
public MapSheetMngDto.MngDto findMapSheetMng(int mngYyyy) { public MapSheetMngDto.MngDto findMapSheetMng(int mngYyyy) {
return mapSheetMngRepository.findMapSheetMng(mngYyyy); return mapSheetMngRepository.findMapSheetMng(mngYyyy);
} }
@@ -105,15 +111,33 @@ public class MapSheetMngCoreService {
mapSheetMngRepository.updateMapSheetMngHstSyncCheckState(reqDto); mapSheetMngRepository.updateMapSheetMngHstSyncCheckState(reqDto);
} }
/**
* 영상데이터관리 > 상세 > 오류 처리 내역
*
* @param searchReq 오류 검색 조건
* @return
*/
public Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList( public Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList(
MapSheetMngDto.@Valid ErrorSearchReq searchReq) { MapSheetMngDto.@Valid ErrorSearchReq searchReq) {
return mapSheetMngRepository.findMapSheetErrorList(searchReq); return mapSheetMngRepository.findMapSheetErrorList(searchReq);
} }
/**
* hst 테이블에 선택한 hstUid row 정보 조회
*
* @param hstUid
* @return
*/
public MapSheetMngDto.ErrorDataDto findMapSheetError(Long hstUid) { public MapSheetMngDto.ErrorDataDto findMapSheetError(Long hstUid) {
return mapSheetMngRepository.findMapSheetError(hstUid); return mapSheetMngRepository.findMapSheetError(hstUid);
} }
/**
* 파일 목록으로 업로드 경로 확인
*
* @param hstUid
* @return
*/
public List<MapSheetMngDto.MngFilesDto> findByHstUidMapSheetFileList(Long hstUid) { public List<MapSheetMngDto.MngFilesDto> findByHstUidMapSheetFileList(Long hstUid) {
return mapSheetMngRepository.findByHstUidMapSheetFileList(hstUid); return mapSheetMngRepository.findByHstUidMapSheetFileList(hstUid);
} }
@@ -156,12 +180,19 @@ public class MapSheetMngCoreService {
entity.setCreatedUid(addReq.getCreatedUid()); entity.setCreatedUid(addReq.getCreatedUid());
entity.setUpdatedUid(addReq.getCreatedUid()); entity.setUpdatedUid(addReq.getCreatedUid());
// 같은 년도로 저장된 데이터가 있다면 삭제
// tb_map_sheet_mng, tb_map_sheet_hst, tb_map_sheet_mng_files, tb_map_sheet_mng_tile
mapSheetMngRepository.deleteByMngYyyyMngAll(addReq.getMngYyyy()); mapSheetMngRepository.deleteByMngYyyyMngAll(addReq.getMngYyyy());
// tb_map_sheet_mng 엔티티 저장
MapSheetMngEntity saved = mapSheetMngRepository.save(entity); MapSheetMngEntity saved = mapSheetMngRepository.save(entity);
// 5k 도엽 기준으로 tb_map_sheet_hst 테이블에 먼저 insert 하기
int hstCnt = int hstCnt =
mapSheetMngRepository.insertMapSheetOrgDataToMapSheetMngHst( mapSheetMngRepository.insertMapSheetOrgDataToMapSheetMngHst(
saved.getMngYyyy(), saved.getMngPath()); saved.getMngYyyy(), saved.getMngPath());
// tb_year 에 해당 년도 완료로 업데이트
mapSheetMngRepository.updateYearState(saved.getMngYyyy(), "DONE"); mapSheetMngRepository.updateYearState(saved.getMngYyyy(), "DONE");
// 년도별 Tile 정보 등록 // 년도별 Tile 정보 등록
@@ -235,124 +266,29 @@ public class MapSheetMngCoreService {
} }
/** /**
* geojson 생성 * geojson 생성시 필요한 영상파일 정보 조회
* *
* @param yyyy * @param yyyy
* @param scenes * @param mapSheetNums
* @param mapSheetScope
* @param detectOption
* @return
*/
public Scene getSceneInference(
String yyyy, List<String> scenes, String mapSheetScope, String detectOption) {
List<ImageFeature> features = loadSceneInferenceBySheets(yyyy, scenes);
return writeSceneGeoJson(yyyy, mapSheetScope, detectOption, features);
}
/**
* geojson 생성
*
* @param yearDtos
* @param yyyy
* @param mapSheetScope
* @param detectOption
* @return
*/
public Scene getSceneInference(
List<MapSheetFallbackYearDto> yearDtos,
String yyyy,
String mapSheetScope,
String detectOption) {
List<ImageFeature> features = loadSceneInferenceByFallbackYears(yearDtos);
return writeSceneGeoJson(yyyy, mapSheetScope, detectOption, features);
}
/**
* 파일 경로/이름 , 파일 생성 , 도엽번호 반환
*
* @param yyyy
* @param mapSheetScope
* @param detectOption
* @param sceneInference
* @return Scene
*/
private Scene writeSceneGeoJson(
String yyyy, String mapSheetScope, String detectOption, List<ImageFeature> sceneInference) {
boolean isAll = MapSheetScope.ALL.getId().equals(mapSheetScope);
String optionSuffix = buildOptionSuffix(detectOption);
String targetDir =
"local".equals(activeEnv) ? System.getProperty("user.home") + "/geojson" : inferenceDir;
String filename =
isAll
? String.format("%s_%s_ALL%s.geojson", yyyy, activeEnv, optionSuffix)
: String.format("%s_%s%s.geojson", yyyy, activeEnv, optionSuffix);
Path outputPath = Paths.get(targetDir, filename);
if (sceneInference == null || sceneInference.isEmpty()) {
log.warn("NOT_FOUND_TARGET_YEAR: yyyy={}, isAll={}, featuresSize={}", yyyy, isAll, 0);
throw new CustomApiException("NOT_FOUND_TARGET_YEAR", HttpStatus.NOT_FOUND);
}
try {
log.info("create Directories outputPath: {}", outputPath);
log.info(
"activeEnv={}, inferenceDir={}, targetDir={}, filename={}",
activeEnv,
inferenceDir,
targetDir,
filename);
log.info("outputPath={}, parent={}", outputPath.toAbsolutePath(), outputPath.getParent());
Files.createDirectories(outputPath.getParent());
new GeoJsonFileWriter()
.exportToFile(sceneInference, "scene_inference_" + yyyy, 5186, outputPath.toString());
Scene scene = new Scene();
scene.setFeatures(sceneInference);
scene.setFilePath(outputPath.toString());
return scene;
} catch (IOException e) {
log.error(
"FAIL_CREATE_MAP_SHEET_FILE: yyyy={}, isAll={}, path={}", yyyy, isAll, outputPath, e);
throw new CustomApiException("INTERNAL_SERVER_ERROR", HttpStatus.INTERNAL_SERVER_ERROR, e);
}
}
/**
* geojson 파일명 Suffix
*
* @param detectOption
* @return
*/
private String buildOptionSuffix(String detectOption) {
if (DetectOption.EXCL.getId().equals(detectOption)) return "_EXCL";
if (DetectOption.PREV.getId().equals(detectOption)) return "_PREV";
return "";
}
/**
* 년도, 도엽번호로 조회
*
* @param yyyy
* @param scenes
* @return ImageFeature * @return ImageFeature
*/ */
private List<ImageFeature> loadSceneInferenceBySheets(String yyyy, List<String> scenes) { public List<ImageFeature> loadSceneInferenceBySheets(String yyyy, List<String> mapSheetNums) {
List<ImageFeature> sceneInference = mapSheetMngRepository.getSceneInference(yyyy, scenes);
if (sceneInference == null || sceneInference.isEmpty()) { if (mapSheetNums == null || mapSheetNums.isEmpty()) {
log.warn( return List.of();
"NOT_FOUND_TARGET_YEAR: yyyy={}, scenesSize={}",
yyyy,
scenes == null ? 0 : scenes.size());
throw new CustomApiException("NOT_FOUND_TARGET_YEAR", HttpStatus.NOT_FOUND);
} }
return sceneInference;
// CHUNK_SIZE 단위로 나누어 여러 번 조회한다.
final int CHUNK_SIZE = 1000;
List<ImageFeature> features = new ArrayList<>();
// i부터 CHUNK_SIZE만큼 잘라서 조회
// 마지막 구간은 남은 개수만큼만 처리하기 위해 Math.min 사용
for (int i = 0; i < mapSheetNums.size(); i += CHUNK_SIZE) {
List<String> chunk = mapSheetNums.subList(i, Math.min(i + CHUNK_SIZE, mapSheetNums.size()));
features.addAll(mapSheetMngRepository.getSceneInference(yyyy, chunk));
}
return features;
} }
/** /**
@@ -361,33 +297,32 @@ public class MapSheetMngCoreService {
* @param yearDtos * @param yearDtos
* @return ImageFeature * @return ImageFeature
*/ */
private List<ImageFeature> loadSceneInferenceByFallbackYears( public List<ImageFeature> loadSceneInferenceByFallbackYears(List<MngListDto> yearDtos) {
List<MapSheetFallbackYearDto> yearDtos) {
if (yearDtos == null || yearDtos.isEmpty()) { if (yearDtos == null || yearDtos.isEmpty()) {
return List.of(); return List.of();
} }
// 년도 별로 루프를 돌리기위해 년도별 정리 // 년도 별로 루프를 돌리기위해 년도별 정리
Map<Integer, List<MapSheetFallbackYearDto>> groupedByYear = Map<Integer, List<MngListDto>> groupedByYear =
yearDtos.stream() yearDtos.stream()
.filter(d -> d.getMngYyyy() != null && d.getMapSheetNum() != null) .filter(d -> d.getMngYyyy() != 0 && d.getMapSheetNum() != null)
.collect(Collectors.groupingBy(MapSheetFallbackYearDto::getMngYyyy)); .collect(Collectors.groupingBy(MngListDto::getMngYyyy));
List<ImageFeature> sceneInference = new ArrayList<>(); List<ImageFeature> sceneInference = new ArrayList<>();
for (Map.Entry<Integer, List<MapSheetFallbackYearDto>> entry : groupedByYear.entrySet()) { for (Map.Entry<Integer, List<MngListDto>> entry : groupedByYear.entrySet()) {
Integer year = entry.getKey(); Integer year = entry.getKey();
// 년도별 mapSheetNum 만들기 // 년도별 mapSheetNum 만들기
List<String> sheetNums = List<String> sheetNums =
entry.getValue().stream() entry.getValue().stream()
.map(MapSheetFallbackYearDto::getMapSheetNum) .map(MngListDto::getMapSheetNum)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.distinct() .distinct()
.toList(); .toList();
// tif파일 정보 조회 // tif파일 정보 조회
List<ImageFeature> temp = mapSheetMngRepository.getSceneInference(year.toString(), sheetNums); List<ImageFeature> temp = loadSceneInferenceBySheets(year.toString(), sheetNums);
if (temp != null && !temp.isEmpty()) { if (temp != null && !temp.isEmpty()) {
sceneInference.addAll(temp); sceneInference.addAll(temp);
@@ -397,20 +332,6 @@ public class MapSheetMngCoreService {
return sceneInference; return sceneInference;
} }
/**
* 변화탐지 실행 가능 기준 년도 조회
*
* @param req
* @return
*/
public List<MngListDto> getHstMapSheetList(InferenceResultDto.RegReq req) {
return mapSheetMngRepository.findByHstMapSheetTargetList(req);
}
public List<MngListDto> getHstMapSheetList(int mngYyyy, List<String> mapIds) {
return mapSheetMngRepository.findByHstMapSheetTargetList(mngYyyy, mapIds);
}
public void updateMapSheetMngHstUploadId(Long hstUid, UUID uuid, String uploadId) { public void updateMapSheetMngHstUploadId(Long hstUid, UUID uuid, String uploadId) {
mapSheetMngRepository.updateMapSheetMngHstUploadId(hstUid, uuid, uploadId); mapSheetMngRepository.updateMapSheetMngHstUploadId(hstUid, uuid, uploadId);
} }
@@ -431,30 +352,32 @@ public class MapSheetMngCoreService {
return mapSheetMngYearRepository.findByHstMapSheetCompareList(mngYyyy, mapId); return mapSheetMngYearRepository.findByHstMapSheetCompareList(mngYyyy, mapId);
} }
public List<String> getMapSheetNumByHst(Integer year) { public List<MngListDto> getMapSheetMngHst(
List<MapSheetMngHstEntity> entity = mapSheetMngRepository.getMapSheetMngHst(year); Integer year, String mapSheetScope, List<String> mapSheetNum) {
return entity.stream().map(MapSheetMngHstEntity::getMapSheetNum).toList(); return mapSheetMngRepository.getMapSheetMngHst(year, mapSheetScope, mapSheetNum);
} }
/** /**
* 특정 연도의 도엽 이력 데이터를 조회 * 이전 년도 도엽 조회 조건이 많을 수 있으므로 chunk 줘서 끊어서 조회
* *
* @param year * @param year
* @param mapIds
* @return * @return
*/ */
public List<MapSheetFallbackYearDto> getMapSheetNumDtoByHst(Integer year) { public List<MngListDto> findFallbackCompareYearByMapSheets(Integer year, List<String> mapIds) {
List<MapSheetMngHstEntity> entity = mapSheetMngRepository.getMapSheetMngHst(year); if (mapIds == null || mapIds.isEmpty()) {
return entity.stream() return Collections.emptyList();
.map(
e ->
new MapSheetFallbackYearDto(
e.getMapSheetNum(), e.getMngYyyy() // 조회 기준 연도
))
.toList();
} }
public List<MapSheetFallbackYearDto> findFallbackCompareYearByMapSheets( int chunkSize = 1000;
Integer year, List<String> mapIds) { List<MngListDto> result = new ArrayList<>();
return mapSheetMngRepository.findFallbackCompareYearByMapSheets(year, mapIds);
for (int i = 0; i < mapIds.size(); i += chunkSize) {
List<String> chunk = mapIds.subList(i, Math.min(i + chunkSize, mapIds.size()));
result.addAll(mapSheetMngRepository.findFallbackCompareYearByMapSheets(year, chunk));
}
return result;
} }
} }

View File

@@ -17,6 +17,16 @@ public class ModelMngCoreService {
private final ModelMngRepository modelMngRepository; private final ModelMngRepository modelMngRepository;
/**
* 모델조회
*
* @param searchReq 페이징
* @param startDate 시작날짜
* @param endDate 종료날짜
* @param modelType 모델 타입 G1, G2, G3
* @param searchVal 모델 ver
* @return 모델 목록
*/
public Page<ModelMngDto.ModelList> findModelMgmtList( public Page<ModelMngDto.ModelList> findModelMgmtList(
ModelMngDto.searchReq searchReq, ModelMngDto.searchReq searchReq,
LocalDate startDate, LocalDate startDate,
@@ -99,4 +109,18 @@ public class ModelMngCoreService {
.orElseThrow(() -> new EntityNotFoundException("모델 정보가 없습니다.")); .orElseThrow(() -> new EntityNotFoundException("모델 정보가 없습니다."));
return entity.toDto(); return entity.toDto();
} }
/**
* 모델 버전명으로 조회
*
* @param ver 모델버전
* @return 모델정보
*/
public ModelMngDto.Basic findByModelVer(String ver) {
ModelMngEntity entity =
modelMngRepository
.findByModelVer(ver)
.orElseThrow(() -> new EntityNotFoundException("모델 정보가 없습니다."));
return entity.toDto();
}
} }

View File

@@ -202,6 +202,33 @@ public class MapSheetLearnEntity {
@Column(name = "chn_dtct_mst_id") @Column(name = "chn_dtct_mst_id")
private String chnDtctMstId; private String chnDtctMstId;
@Column(name = "shp_create_status")
private String shp_create_status;
@Column(name = "shp_create_message")
private String shp_create_message;
@Column(name = "shp_create_status_dttm")
private ZonedDateTime shp_create_status_dttm;
@Column(name = "shp_status")
private String shp_status;
@Column(name = "shp_stage")
private String shp_stage;
@Column(name = "shp_started_dttm")
private ZonedDateTime shp_started_dttm;
@Column(name = "shp_ended_dttm")
private ZonedDateTime shp_ended_dttm;
@Column(name = "shp_last_message")
private String shp_last_message;
@Column(name = "shp_error_message")
private String shp_error_message;
public InferenceResultDto.ResultList toDto() { public InferenceResultDto.ResultList toDto() {
return new InferenceResultDto.ResultList( return new InferenceResultDto.ResultList(
this.uuid, this.uuid,

View File

@@ -117,6 +117,7 @@ public class ModelMngEntity extends CommonDateEntity {
this.clsModelFileName, this.clsModelFileName,
this.clsModelVersion, this.clsModelVersion,
this.priority, this.priority,
this.memo); this.memo,
this.uuid);
} }
} }

View File

@@ -8,13 +8,19 @@ public interface InferenceResultRepositoryCustom {
Long upsertGroupsFromMapSheetAnal(Long id); Long upsertGroupsFromMapSheetAnal(Long id);
void upsertGroupsFromInferenceResults(Long analId); int upsertGroupsFromInferenceResults(Long analId);
void upsertGeomsFromInferenceResults(Long analId); int upsertGeomsFromInferenceResults(Long analId);
void upsertSttcFromInferenceResults(Long analId); int upsertSttcFromInferenceResults(Long analId);
Long getInferenceLearnIdByUuid(UUID uuid); Long getInferenceLearnIdByUuid(UUID uuid);
/**
* 추론 정보 조회
*
* @param uuid 추론 uuid
* @return 추론 정보
*/
Optional<MapSheetLearnEntity> getInferenceUid(UUID uuid); Optional<MapSheetLearnEntity> getInferenceUid(UUID uuid);
} }

View File

@@ -80,7 +80,7 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
* @return 반영된 행 수 * @return 반영된 행 수
*/ */
@Override @Override
public void upsertGroupsFromInferenceResults(Long analId) { public int upsertGroupsFromInferenceResults(Long analId) {
String sql = String sql =
""" """
@@ -124,7 +124,7 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
updated_dttm = now() updated_dttm = now()
"""; """;
em.createNativeQuery(sql).setParameter("analId", analId).executeUpdate(); return em.createNativeQuery(sql).setParameter("analId", analId).executeUpdate();
} }
/** /**
@@ -136,7 +136,7 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
* @return 반영된 행 수 * @return 반영된 행 수
*/ */
@Override @Override
public void upsertGeomsFromInferenceResults(Long analUid) { public int upsertGeomsFromInferenceResults(Long analUid) {
String sql = String sql =
""" """
@@ -229,11 +229,11 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
ref_map_sheet_num = EXCLUDED.ref_map_sheet_num ref_map_sheet_num = EXCLUDED.ref_map_sheet_num
"""; """;
em.createNativeQuery(sql).setParameter("analUid", analUid).executeUpdate(); return em.createNativeQuery(sql).setParameter("analUid", analUid).executeUpdate();
} }
@Override @Override
public void upsertSttcFromInferenceResults(Long analUid) { public int upsertSttcFromInferenceResults(Long analUid) {
String sql = String sql =
""" """
@@ -306,7 +306,7 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
,updated_uid = EXCLUDED.updated_uid ,updated_uid = EXCLUDED.updated_uid
"""; """;
em.createNativeQuery(sql).setParameter("analUid", analUid).executeUpdate(); return em.createNativeQuery(sql).setParameter("analUid", analUid).executeUpdate();
} }
// =============================== // ===============================

View File

@@ -1,11 +1,32 @@
package com.kamco.cd.kamcoback.postgres.repository.Inference; package com.kamco.cd.kamcoback.postgres.repository.Inference;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultsTestingDto;
import com.kamco.cd.kamcoback.postgres.entity.InferenceResultsTestingEntity; import com.kamco.cd.kamcoback.postgres.entity.InferenceResultsTestingEntity;
import java.util.List; import java.util.List;
public interface InferenceResultsTestingRepositoryCustom { public interface InferenceResultsTestingRepositoryCustom {
/**
* 추론 결과 조회
*
* @param batchIds batch id
* @return 추론 결과 목록
*/
List<InferenceResultsTestingEntity> getInferenceResultList(List<Long> batchIds); List<InferenceResultsTestingEntity> getInferenceResultList(List<Long> batchIds);
/**
* 테스팅 테이블 조회하여 탐지건수 조회
*
* @param batchIds batchIds
* @return batchIds 조회 count 수
*/
Long getInferenceResultCnt(List<Long> batchIds); Long getInferenceResultCnt(List<Long> batchIds);
/**
* testing 테이블 결과로 기본정보 조회
*
* @param batchIds batch id
* @return batch id, model ver, year 정보
*/
List<InferenceResultsTestingDto.Basic> getInferenceResultGroupList(List<Long> batchIds);
} }

View File

@@ -2,7 +2,9 @@ package com.kamco.cd.kamcoback.postgres.repository.Inference;
import static com.kamco.cd.kamcoback.postgres.entity.QInferenceResultsTestingEntity.inferenceResultsTestingEntity; import static com.kamco.cd.kamcoback.postgres.entity.QInferenceResultsTestingEntity.inferenceResultsTestingEntity;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultsTestingDto;
import com.kamco.cd.kamcoback.postgres.entity.InferenceResultsTestingEntity; import com.kamco.cd.kamcoback.postgres.entity.InferenceResultsTestingEntity;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory; import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -47,4 +49,29 @@ public class InferenceResultsTestingRepositoryImpl
return cnt == null ? 0L : cnt; return cnt == null ? 0L : cnt;
} }
@Override
public List<InferenceResultsTestingDto.Basic> getInferenceResultGroupList(List<Long> batchIds) {
return queryFactory
.select(
Projections.constructor(
InferenceResultsTestingDto.Basic.class,
inferenceResultsTestingEntity.batchId,
inferenceResultsTestingEntity.modelVersion.max(),
inferenceResultsTestingEntity.beforeYear.max(),
inferenceResultsTestingEntity.afterYear.max()))
.from(inferenceResultsTestingEntity)
.where(
inferenceResultsTestingEntity
.batchId
.in(batchIds)
.and(inferenceResultsTestingEntity.afterC.isNotNull())
.and(inferenceResultsTestingEntity.afterP.isNotNull()))
.groupBy(
inferenceResultsTestingEntity.batchId,
inferenceResultsTestingEntity.modelVersion,
inferenceResultsTestingEntity.beforeYear,
inferenceResultsTestingEntity.afterYear)
.fetch();
}
} }

View File

@@ -6,13 +6,49 @@ import java.util.UUID;
public interface MapSheetLearn5kRepositoryCustom { public interface MapSheetLearn5kRepositoryCustom {
/**
* 추론 실행 실패 정보 저장
*
* @param uuid 추론 uuid
* @param jobDto AI API에서 조회한 Job 정보
* @param type 모델 타입
*/
void saveFail5k(UUID uuid, JobStatusDto jobDto, String type); void saveFail5k(UUID uuid, JobStatusDto jobDto, String type);
/**
* 추론 실행중인 Job id 저장
*
* @param uuid 추론 uuid
* @param jobDto AI API에서 조회한 Job 정보
* @param type 모델 타입
*/
void saveJobId(UUID uuid, JobStatusDto jobDto, String type); void saveJobId(UUID uuid, JobStatusDto jobDto, String type);
/**
* 실패 처리되어야 할 목록 중에 아직 실패로 표시되지 않은 ID 조회
*
* @param uuid 추론 uuid
* @param failIds AI API 연결하여 조회한 실패 job id
* @param type 모델 타입
* @return 실패로 표시되지 않은 ID
*/
List<Long> findFail5kList(UUID uuid, List<Long> failIds, String type); List<Long> findFail5kList(UUID uuid, List<Long> failIds, String type);
/**
* 완료된 것으로 들어온 목록 중 실제로 존재하는 5k jobId 조회
*
* @param uuid 추론 uuid
* @param completedIds AI API 연결하여 조회한 실패 job id
* @param type 모델 타입
* @return 성공한 job id
*/
List<Long> findCompleted5kList(UUID uuid, List<Long> completedIds, String type); List<Long> findCompleted5kList(UUID uuid, List<Long> completedIds, String type);
/**
* 추론 실행중일때 분석중인 도엽명 목록 조회
*
* @param uuid 추론 uuid
* @return 도엽명+50K 도엽번호
*/
List<String> getInferenceRunMapId(UUID uuid); List<String> getInferenceRunMapId(UUID uuid);
} }

View File

@@ -315,7 +315,8 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
Expressions.stringTemplate("substring({0} from 1 for 8)", mapSheetLearnEntity.uid), Expressions.stringTemplate("substring({0} from 1 for 8)", mapSheetLearnEntity.uid),
mapSheetLearnEntity.applyYn, mapSheetLearnEntity.applyYn,
mapSheetLearnEntity.applyDttm, mapSheetLearnEntity.applyDttm,
mapSheetAnalInferenceEntity.uuid)) mapSheetAnalInferenceEntity.uuid,
mapSheetLearnEntity.status))
.from(mapSheetLearnEntity) .from(mapSheetLearnEntity)
.leftJoin(m1) .leftJoin(m1)
.on(mapSheetLearnEntity.m1ModelUuid.eq(m1.uuid)) .on(mapSheetLearnEntity.m1ModelUuid.eq(m1.uuid))

View File

@@ -31,10 +31,10 @@ public interface ChangeDetectionRepositoryCustom {
List<MapSheetList> getChangeDetectionMapSheet50kList(UUID uuid); List<MapSheetList> getChangeDetectionMapSheet50kList(UUID uuid);
ChangeDetectionDto.PolygonFeatureList getPolygonListByCd( ChangeDetectionDto.PolygonFeatureList getPolygonListByCd(
String chnDtctId, String cdObjectId, List<String> cdObjectIds); String chnDtctId, String cdObjectId, List<String> cdObjectIds, String pnu);
ChangeDetectionDto.PointFeatureList getPointListByCd( ChangeDetectionDto.PointFeatureList getPointListByCd(
String chnDtctId, String cdObjectId, List<String> cdObjectIds); String chnDtctId, String cdObjectId, List<String> cdObjectIds, String pnu);
ChangeDetectionDto.PolygonFeatureList getSelectedChangeDetectionPolygonListByPnu( ChangeDetectionDto.PolygonFeatureList getSelectedChangeDetectionPolygonListByPnu(
String chnDtctId, String pnu); String chnDtctId, String pnu);

View File

@@ -18,7 +18,9 @@ import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto.DetectSearc
import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto.MapScaleType; import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto.MapScaleType;
import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto.MapSheetList; import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto.MapSheetList;
import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto.PointFeatureList; import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto.PointFeatureList;
import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto.PointQueryData;
import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto.PolygonFeatureList; import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto.PolygonFeatureList;
import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto.PolygonQueryData;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Status; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Status;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity;
@@ -31,6 +33,7 @@ import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringExpression; import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory; import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@@ -341,7 +344,10 @@ public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
mapInkx5kEntity.mapidcdNo, mapInkx5kEntity.mapidcdNo,
mapInkx5kEntity.mapidNm, mapInkx5kEntity.mapidNm,
Expressions.stringTemplate( Expressions.stringTemplate(
"concat({0}, ' ', {1})", mapInkx5kEntity.mapidNm, mapInkx5kEntity.mapidcdNo))) "concat({0}, ' ', {1})", mapInkx5kEntity.mapidNm, mapInkx5kEntity.mapidcdNo),
Expressions.stringTemplate(
"ST_AsGeoJSON(ST_Transform({0}, 5186))", mapInkx5kEntity.geom)
.as("bbox")))
.from(mapSheetAnalInferenceEntity) .from(mapSheetAnalInferenceEntity)
.innerJoin(mapSheetAnalDataInferenceEntity) .innerJoin(mapSheetAnalDataInferenceEntity)
.on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid)) .on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid))
@@ -361,7 +367,10 @@ public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
mapInkx50kEntity.mapidcdNo, mapInkx50kEntity.mapidcdNo,
mapInkx50kEntity.mapidNm, mapInkx50kEntity.mapidNm,
Expressions.stringTemplate( Expressions.stringTemplate(
"concat({0}, ' ', {1})", mapInkx50kEntity.mapidNm, mapInkx50kEntity.mapidcdNo))) "concat({0}, ' ', {1})", mapInkx50kEntity.mapidNm, mapInkx50kEntity.mapidcdNo),
Expressions.stringTemplate(
"ST_AsGeoJSON(ST_Transform({0}, 5186))", mapInkx50kEntity.geom)
.as("bbox")))
.from(mapSheetAnalInferenceEntity) .from(mapSheetAnalInferenceEntity)
.innerJoin(mapSheetAnalDataInferenceEntity) .innerJoin(mapSheetAnalDataInferenceEntity)
.on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid)) .on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid))
@@ -369,7 +378,7 @@ public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
.on(mapSheetAnalDataInferenceEntity.mapSheetNum.stringValue().eq(mapInkx5kEntity.mapidcdNo)) .on(mapSheetAnalDataInferenceEntity.mapSheetNum.stringValue().eq(mapInkx5kEntity.mapidcdNo))
.innerJoin(mapInkx5kEntity.mapInkx50k, mapInkx50kEntity) .innerJoin(mapInkx5kEntity.mapInkx50k, mapInkx50kEntity)
.where(mapSheetAnalInferenceEntity.uuid.eq(uuid)) .where(mapSheetAnalInferenceEntity.uuid.eq(uuid))
.groupBy(mapInkx50kEntity.mapidcdNo, mapInkx50kEntity.mapidNm) .groupBy(mapInkx50kEntity.mapidcdNo, mapInkx50kEntity.mapidNm, mapInkx50kEntity.geom)
.orderBy(mapInkx50kEntity.mapidcdNo.asc()) .orderBy(mapInkx50kEntity.mapidcdNo.asc())
.fetch(); .fetch();
} }
@@ -385,16 +394,23 @@ public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
@Override @Override
public PolygonFeatureList getPolygonListByCd( public PolygonFeatureList getPolygonListByCd(
String chnDtctId, String cdObjectId, List<String> cdObjectIds) { String chnDtctId, String cdObjectId, List<String> cdObjectIds, String pnu) {
BooleanBuilder builder = new BooleanBuilder(); BooleanBuilder builder = new BooleanBuilder();
builder.and(mapSheetLearnEntity.uid.eq(chnDtctId)); builder.and(mapSheetLearnEntity.uid.eq(chnDtctId));
// pnu가 들어온 경우
boolean hasPnu = pnu != null && !pnu.isBlank();
if (!hasPnu) {
builder.and( builder.and(
mapSheetAnalDataInferenceGeomEntity mapSheetAnalDataInferenceGeomEntity
.resultUid .resultUid
.eq(chnDtctId) .eq(chnDtctId)
.or(mapSheetAnalDataInferenceGeomEntity.resultUid.in(cdObjectIds))); .or(mapSheetAnalDataInferenceGeomEntity.resultUid.in(cdObjectIds)));
}
List<ChangeDetectionDto.PolygonQueryData> list = // List<ChangeDetectionDto.PolygonQueryData> list =
JPAQuery<PolygonQueryData> query =
queryFactory queryFactory
.select( .select(
Projections.constructor( Projections.constructor(
@@ -419,10 +435,22 @@ public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
.innerJoin(mapSheetAnalInferenceEntity) .innerJoin(mapSheetAnalInferenceEntity)
.on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid)) .on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid))
.innerJoin(mapSheetLearnEntity) .innerJoin(mapSheetLearnEntity)
.on(mapSheetLearnEntity.id.eq(mapSheetAnalInferenceEntity.learnId)) .on(mapSheetLearnEntity.id.eq(mapSheetAnalInferenceEntity.learnId));
.where(builder) // .where(builder)
.orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) // .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc());
.fetch(); // .fetch();
// 🔥 pnu 조건이면 tb_pnu join 추가
if (hasPnu) {
query
.innerJoin(pnuEntity)
.on(pnuEntity.geo.geoUid.eq(mapSheetAnalDataInferenceGeomEntity.geoUid));
builder.and(pnuEntity.pnu.eq(pnu));
}
List<ChangeDetectionDto.PolygonQueryData> list =
query.where(builder).orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()).fetch();
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
List<ChangeDetectionDto.PolygonFeature> result = List<ChangeDetectionDto.PolygonFeature> result =
@@ -464,16 +492,23 @@ public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
@Override @Override
public PointFeatureList getPointListByCd( public PointFeatureList getPointListByCd(
String chnDtctId, String cdObjectId, List<String> cdObjectIds) { String chnDtctId, String cdObjectId, List<String> cdObjectIds, String pnu) {
BooleanBuilder builder = new BooleanBuilder(); BooleanBuilder builder = new BooleanBuilder();
builder.and(mapSheetLearnEntity.uid.eq(chnDtctId)); builder.and(mapSheetLearnEntity.uid.eq(chnDtctId));
// pnu가 들어온 경우
boolean hasPnu = pnu != null && !pnu.isBlank();
if (!hasPnu) {
builder.and( builder.and(
mapSheetAnalDataInferenceGeomEntity mapSheetAnalDataInferenceGeomEntity
.resultUid .resultUid
.eq(chnDtctId) .eq(chnDtctId)
.or(mapSheetAnalDataInferenceGeomEntity.resultUid.in(cdObjectIds))); .or(mapSheetAnalDataInferenceGeomEntity.resultUid.in(cdObjectIds)));
}
List<ChangeDetectionDto.PointQueryData> list = // List<ChangeDetectionDto.PointQueryData> list =
JPAQuery<PointQueryData> query =
queryFactory queryFactory
.select( .select(
Projections.constructor( Projections.constructor(
@@ -492,9 +527,21 @@ public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
.innerJoin(mapSheetAnalInferenceEntity) .innerJoin(mapSheetAnalInferenceEntity)
.on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid)) .on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid))
.innerJoin(mapSheetLearnEntity) .innerJoin(mapSheetLearnEntity)
.on(mapSheetLearnEntity.id.eq(mapSheetAnalInferenceEntity.learnId)) .on(mapSheetLearnEntity.id.eq(mapSheetAnalInferenceEntity.learnId));
.where(builder) // .where(builder)
.fetch(); // .fetch();
// 🔥 pnu 조건이면 tb_pnu join 추가
if (hasPnu) {
query
.innerJoin(pnuEntity)
.on(pnuEntity.geo.geoUid.eq(mapSheetAnalDataInferenceGeomEntity.geoUid));
builder.and(pnuEntity.pnu.eq(pnu));
}
List<ChangeDetectionDto.PointQueryData> list =
query.where(builder).orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()).fetch();
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
List<ChangeDetectionDto.PointFeature> result = List<ChangeDetectionDto.PointFeature> result =

View File

@@ -45,4 +45,8 @@ public interface GukYuinRepositoryCustom {
void updateMapSheetLearnGukyuinEndStatus(Long learnId); void updateMapSheetLearnGukyuinEndStatus(Long learnId);
void updateMapSheetInferenceLabelEndStatus(Long learnId); void updateMapSheetInferenceLabelEndStatus(Long learnId);
List<String> findStbltObjectIds(String uid, String mapSheetNum);
Integer updateStbltRandomData(String uid, int updateCnt);
} }

View File

@@ -1,11 +1,13 @@
package com.kamco.cd.kamcoback.postgres.repository.gukyuin; package com.kamco.cd.kamcoback.postgres.repository.gukyuin;
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity; import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity;
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.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity; 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.QMapSheetLearnEntity.mapSheetLearnEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QPnuEntity.pnuEntity; import static com.kamco.cd.kamcoback.postgres.entity.QPnuEntity.pnuEntity;
import com.kamco.cd.kamcoback.common.enums.ImageryFitStatus;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.Basic; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.Basic;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LabelSendDto; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LabelSendDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto; import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
@@ -318,6 +320,61 @@ public class GukYuinRepositoryImpl implements GukYuinRepositoryCustom {
.execute(); .execute();
} }
@Override
public List<String> findStbltObjectIds(String uid, String mapSheetNum) {
return queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.resultUid)
.from(mapSheetLearnEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(mapSheetLearnEntity.id.eq(mapSheetAnalInferenceEntity.learnId))
.innerJoin(mapSheetAnalDataInferenceEntity)
.on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid))
.innerJoin(mapSheetAnalDataInferenceGeomEntity)
.on(mapSheetAnalDataInferenceEntity.id.eq(mapSheetAnalDataInferenceGeomEntity.dataUid))
.where(
mapSheetLearnEntity.uid.eq(uid),
mapSheetAnalDataInferenceGeomEntity.mapSheetNum.eq(Long.valueOf(mapSheetNum)))
.fetch();
}
/**
* mapSheetAnalDataInferenceGeomEntity 데이터 에 실태조사 값 들어온 것으로 간주하고 update 랜덤으로 하기
*
* @param uid
* @param updateCnt
* @return
*/
@Override
public Integer updateStbltRandomData(String uid, int updateCnt) {
List<Long> geoUids =
queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.geoUid)
.from(mapSheetLearnEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(mapSheetLearnEntity.id.eq(mapSheetAnalInferenceEntity.learnId))
.innerJoin(mapSheetAnalDataInferenceEntity)
.on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid))
.innerJoin(mapSheetAnalDataInferenceGeomEntity)
.on(
mapSheetAnalDataInferenceEntity.id.eq(mapSheetAnalDataInferenceGeomEntity.dataUid),
mapSheetAnalDataInferenceGeomEntity.fitState.isNull())
.where(mapSheetLearnEntity.uid.eq(uid))
.orderBy(mapSheetAnalDataInferenceGeomEntity.geoUid.asc())
.limit(updateCnt)
.fetch();
for (Long geoUid : geoUids) {
queryFactory
.update(mapSheetAnalDataInferenceGeomEntity)
.set(mapSheetAnalDataInferenceGeomEntity.pnu, 1L)
.set(mapSheetAnalDataInferenceGeomEntity.fitState, ImageryFitStatus.UNFIT.getId())
.set(mapSheetAnalDataInferenceGeomEntity.fitStateDttm, ZonedDateTime.now())
.where(mapSheetAnalDataInferenceGeomEntity.geoUid.eq(geoUid))
.execute();
}
return updateCnt;
}
@Override @Override
@Transactional @Transactional
public void updateGukYuinApplyStateComplete(Long id, GukYuinStatus status) { public void updateGukYuinApplyStateComplete(Long id, GukYuinStatus status) {

View File

@@ -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.QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; 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.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnDataGeomEntity.mapSheetLearnDataGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity;
@@ -388,12 +389,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(analEntity.getCompareYyyy()), mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(analEntity.getCompareYyyy()),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(analEntity.getTargetYyyy()), mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(analEntity.getTargetYyyy()),
mapSheetAnalDataInferenceGeomEntity.stage.eq(analEntity.getStage()), mapSheetAnalDataInferenceGeomEntity.stage.eq(analEntity.getStage()),
// mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull()
mapSheetAnalDataInferenceGeomEntity.pnu.gt(0L), mapSheetAnalDataInferenceGeomEntity.pnu.gt(0L),
mapSheetAnalDataInferenceGeomEntity.fitState.eq( mapSheetAnalDataInferenceGeomEntity.fitState.eq(ImageryFitStatus.UNFIT.getId()))
ImageryFitStatus.UNFIT.getId()) // TODO:
// 추후 라벨링 대상 조건 수정하기
)
.fetchOne(); .fetchOne();
} }
@@ -493,6 +490,19 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
inspectionStatus = inspectionRemaining > 0 ? "진행중" : "완료"; inspectionStatus = inspectionRemaining > 0 ? "진행중" : "완료";
} }
Long downloadPolygonCnt =
queryFactory
.select(mapSheetLearnDataGeomEntity.geoUid.count())
.from(mapSheetLearnDataGeomEntity)
.innerJoin(labelingAssignmentEntity)
.on(labelingAssignmentEntity.inferenceGeomUid.eq(mapSheetLearnDataGeomEntity.geoUid))
.innerJoin(mapSheetAnalInferenceEntity)
.on(
labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id),
mapSheetAnalInferenceEntity.id.eq(analUid))
.where(mapSheetLearnDataGeomEntity.fileCreateYn.isTrue())
.fetchOne();
return WorkProgressInfo.builder() return WorkProgressInfo.builder()
// 라벨링 (pass_yn = false인 부적합 데이터 기준) // 라벨링 (pass_yn = false인 부적합 데이터 기준)
.labelingProgressRate(labelingRate) .labelingProgressRate(labelingRate)
@@ -516,6 +526,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.completedCount(labelCompleted) .completedCount(labelCompleted)
.remainingLabelCount(labelingRemaining) .remainingLabelCount(labelingRemaining)
.remainingInspectCount(inspectionRemaining) .remainingInspectCount(inspectionRemaining)
.downloadPolygonCnt(downloadPolygonCnt)
.build(); .build();
} }
@@ -659,6 +670,19 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
inspectionStatus = inspectionRemaining > 0 ? "진행중" : "완료"; inspectionStatus = inspectionRemaining > 0 ? "진행중" : "완료";
} }
Long downloadPolygonCnt =
queryFactory
.select(mapSheetLearnDataGeomEntity.geoUid.count())
.from(mapSheetLearnDataGeomEntity)
.innerJoin(labelingAssignmentEntity)
.on(labelingAssignmentEntity.inferenceGeomUid.eq(mapSheetLearnDataGeomEntity.geoUid))
.innerJoin(mapSheetAnalInferenceEntity)
.on(
labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id),
mapSheetAnalInferenceEntity.uuid.eq(targetUuid))
.where(mapSheetLearnDataGeomEntity.fileCreateYn.isTrue())
.fetchOne();
return WorkProgressInfo.builder() return WorkProgressInfo.builder()
.labelingProgressRate(labelingRate) .labelingProgressRate(labelingRate)
.labelingStatus(labelingStatus) .labelingStatus(labelingStatus)
@@ -679,6 +703,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.completedCount(labelCompleted) .completedCount(labelCompleted)
.remainingLabelCount(labelingRemaining) .remainingLabelCount(labelingRemaining)
.remainingInspectCount(inspectionRemaining) .remainingInspectCount(inspectionRemaining)
.downloadPolygonCnt(downloadPolygonCnt)
.build(); .build();
} }

View File

@@ -4,6 +4,7 @@ import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapShe
import com.kamco.cd.kamcoback.common.enums.ImageryFitStatus; import com.kamco.cd.kamcoback.common.enums.ImageryFitStatus;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState; 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; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState;
import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto;
import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng;
@@ -166,6 +167,19 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
.from(labelingAssignmentEntity) .from(labelingAssignmentEntity)
.where(labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id)); .where(labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id));
/** 순서 ING 진행중 ASSIGNED 작업할당 PENDING 작업대기 FINISH 종료 */
NumberExpression<Integer> stateOrder =
new CaseBuilder()
.when(mapSheetAnalInferenceEntity.analState.eq(LabelMngState.ING.getId()))
.then(0)
.when(mapSheetAnalInferenceEntity.analState.eq(LabelMngState.ASSIGNED.getId()))
.then(1)
.when(mapSheetAnalInferenceEntity.analState.eq(LabelMngState.PENDING.getId()))
.then(2)
.when(mapSheetAnalInferenceEntity.analState.eq(LabelMngState.FINISH.getId()))
.then(3)
.otherwise(99);
List<LabelWorkMng> foundContent = List<LabelWorkMng> foundContent =
queryFactory queryFactory
.select( .select(
@@ -251,6 +265,7 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
mapSheetLearnEntity.uid, mapSheetLearnEntity.uid,
mapSheetLearnEntity.uuid) mapSheetLearnEntity.uuid)
.orderBy( .orderBy(
stateOrder.asc(),
mapSheetAnalInferenceEntity.targetYyyy.desc(), mapSheetAnalInferenceEntity.targetYyyy.desc(),
mapSheetAnalInferenceEntity.compareYyyy.desc(), mapSheetAnalInferenceEntity.compareYyyy.desc(),
mapSheetAnalInferenceEntity.stage.desc()) mapSheetAnalInferenceEntity.stage.desc())

View File

@@ -1,8 +1,6 @@
package com.kamco.cd.kamcoback.postgres.repository.mapsheet; package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature; import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetFallbackYearDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto; 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.AddReq;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto;
@@ -17,16 +15,39 @@ import org.springframework.data.domain.Page;
public interface MapSheetMngRepositoryCustom { public interface MapSheetMngRepositoryCustom {
/**
* 영상데이터관리 > 목록 조회
*
* @return
*/
List<MapSheetMngDto.MngDto> findMapSheetMngList(); List<MapSheetMngDto.MngDto> findMapSheetMngList();
/**
* 영상데이터관리 > 데이터 등록 > 연도 선택 목록
*
* @return
*/
List<Integer> findMapSheetMngYyyyList(); List<Integer> findMapSheetMngYyyyList();
/**
* 영상데이터관리 > 상세 조회
*
* @param mngYyyy
* @return
*/
MapSheetMngDto.MngDto findMapSheetMng(int mngYyyy); MapSheetMngDto.MngDto findMapSheetMng(int mngYyyy);
void MapSheetMngComplete(int mngYyyy); void MapSheetMngComplete(int mngYyyy);
Optional<MapSheetMngHstEntity> findMapSheetMngHstInfo(Long hstUid); Optional<MapSheetMngHstEntity> findMapSheetMngHstInfo(Long hstUid);
/**
* 5k 도엽 기준으로 tb_map_sheet_hst 테이블에 먼저 insert 하기
*
* @param mngYyyy
* @param mngPath
* @return
*/
int insertMapSheetOrgDataToMapSheetMngHst(int mngYyyy, String mngPath); int insertMapSheetOrgDataToMapSheetMngHst(int mngYyyy, String mngPath);
List<MapSheetMngDto.MngFilesDto> findHstUidToMapSheetFileList(Long hstUid); List<MapSheetMngDto.MngFilesDto> findHstUidToMapSheetFileList(Long hstUid);
@@ -35,6 +56,11 @@ public interface MapSheetMngRepositoryCustom {
MapSheetMngDto.MngFilesDto findYyyyToMapSheetFilePathRefer(int mngYyyy); MapSheetMngDto.MngFilesDto findYyyyToMapSheetFilePathRefer(int mngYyyy);
/**
* 같은 년도로 저장된 데이터가 있다면 삭제
*
* @param mngYyyy
*/
void deleteByMngYyyyMngAll(int mngYyyy); void deleteByMngYyyyMngAll(int mngYyyy);
void deleteByMngYyyyMng(int mngYyyy); void deleteByMngYyyyMng(int mngYyyy);
@@ -55,24 +81,38 @@ public interface MapSheetMngRepositoryCustom {
void deleteByNotInFileUidMngFile(Long hstUid, List<Long> fileUids); void deleteByNotInFileUidMngFile(Long hstUid, List<Long> fileUids);
/**
* tb_year 에 해당 년도 완료로 업데이트
*
* @param yyyy
* @param status
*/
void updateYearState(int yyyy, String status); void updateYearState(int yyyy, String status);
/**
* 영상데이터관리 > 상세 > 오류 처리 내역
*
* @param searchReq 오류 검색 조건
* @return
*/
Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList( Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList(
MapSheetMngDto.@Valid ErrorSearchReq searchReq); MapSheetMngDto.@Valid ErrorSearchReq searchReq);
MapSheetMngDto.ErrorDataDto findMapSheetError(Long hstUid);
List<MapSheetMngDto.MngFilesDto> findByHstUidMapSheetFileList(Long hstUid);
/** /**
* 변화탐지 실행 가능 기준 연도 조회 * hst 테이블에 선택한 hstUid row 정보 조회
* *
* @param req 조회 연도, 도엽번호 목록, * @param hstUid
* @return * @return
*/ */
List<MngListDto> findByHstMapSheetTargetList(InferenceResultDto.RegReq req); MapSheetMngDto.ErrorDataDto findMapSheetError(Long hstUid);
List<MngListDto> findByHstMapSheetTargetList(int mngYyyy, List<String> mapIds); /**
* 파일 목록으로 업로드 경로 확인
*
* @param hstUid
* @return
*/
List<MapSheetMngDto.MngFilesDto> findByHstUidMapSheetFileList(Long hstUid);
MapSheetMngDto.MngFilesDto findByFileUidMapSheetFile(Long fileUid); MapSheetMngDto.MngFilesDto findByFileUidMapSheetFile(Long fileUid);
@@ -82,14 +122,38 @@ public interface MapSheetMngRepositoryCustom {
Page<YearEntity> getYears(YearSearchReq req); Page<YearEntity> getYears(YearSearchReq req);
/**
* 도엽 영상파일 정보 조회
*
* @param yyyy 연도
* @param mapSheetNums 도엽목록
* @return 도엽 영상파일 정보
*/
List<ImageFeature> getSceneInference(String yyyy, List<String> mapSheetNums); List<ImageFeature> getSceneInference(String yyyy, List<String> mapSheetNums);
void updateMapSheetMngHstUploadId(Long hstUid, UUID uuid, String uploadId); void updateMapSheetMngHstUploadId(Long hstUid, UUID uuid, String uploadId);
/**
* 년도별 Tile 정보 등록
*
* @param addReq
*/
void insertMapSheetMngTile(@Valid AddReq addReq); void insertMapSheetMngTile(@Valid AddReq addReq);
List<MapSheetMngHstEntity> getMapSheetMngHst(Integer year); /**
* 연도 조건으로 도엽번호 조회
*
* @param year 연도
* @return 추론 가능한 도엽 정보
*/
List<MngListDto> getMapSheetMngHst(Integer year, String mapSheetScope, List<String> mapSheetNum);
List<MapSheetFallbackYearDto> findFallbackCompareYearByMapSheets( /**
Integer year, List<String> mapIds); * 비교연도 사용 가능한 이전도엽을 조회한다.
*
* @param year 연도
* @param mapIds 도엽목록
* @return 사용 가능한 이전도엽목록
*/
List<MngListDto> findFallbackCompareYearByMapSheets(Integer year, List<String> mapIds);
} }

View File

@@ -5,20 +5,20 @@ import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngEntity.mapSheet
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngFileEntity.mapSheetMngFileEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngFileEntity.mapSheetMngFileEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity.mapSheetMngHstEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity.mapSheetMngHstEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngTileEntity.mapSheetMngTileEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngTileEntity.mapSheetMngTileEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngYearYnEntity.mapSheetMngYearYnEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QYearEntity.yearEntity; import static com.kamco.cd.kamcoback.postgres.entity.QYearEntity.yearEntity;
import static com.querydsl.core.types.dsl.Expressions.nullExpression; import static com.querydsl.core.types.dsl.Expressions.nullExpression;
import com.kamco.cd.kamcoback.common.enums.CommonUseStatus; import com.kamco.cd.kamcoback.common.enums.CommonUseStatus;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature; import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetFallbackYearDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetScope; 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;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.AddReq; 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.MngListDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.YearSearchReq; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.YearSearchReq;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngFileEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity;
import com.kamco.cd.kamcoback.postgres.entity.QYearEntity; import com.kamco.cd.kamcoback.postgres.entity.QYearEntity;
import com.kamco.cd.kamcoback.postgres.entity.YearEntity; import com.kamco.cd.kamcoback.postgres.entity.YearEntity;
import com.querydsl.core.BooleanBuilder; import com.querydsl.core.BooleanBuilder;
@@ -28,6 +28,7 @@ import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberExpression; import com.querydsl.core.types.dsl.NumberExpression;
import com.querydsl.core.types.dsl.StringExpression; import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQueryFactory; import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
@@ -58,16 +59,17 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
this.queryFactory = queryFactory; this.queryFactory = queryFactory;
} }
/**
* 영상데이터관리 > 목록 조회
*
* @return
*/
@Override @Override
public List<MapSheetMngDto.MngDto> findMapSheetMngList() { public List<MapSheetMngDto.MngDto> findMapSheetMngList() {
// Pageable pageable = searchReq.toPageable(); // Pageable pageable = searchReq.toPageable();
BooleanBuilder whereBuilder = new BooleanBuilder(); BooleanBuilder whereBuilder = new BooleanBuilder();
// if (searchReq.getMngYyyy() != null) {
// whereBuilder.and(mapSheetMngEntity.mngYyyy.eq(searchReq.getMngYyyy()));
// }
NumberExpression<Long> totalCount = mapSheetMngHstEntity.count().as("syncTotCnt"); NumberExpression<Long> totalCount = mapSheetMngHstEntity.count().as("syncTotCnt");
NumberExpression<Long> doneCount = NumberExpression<Long> doneCount =
@@ -226,6 +228,11 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
return foundContent; return foundContent;
} }
/**
* 영상데이터관리 > 데이터 등록 > 연도 선택 목록
*
* @return List<Integer>
*/
public List<Integer> findMapSheetMngYyyyList() { public List<Integer> findMapSheetMngYyyyList() {
List<Integer> foundContent = List<Integer> foundContent =
@@ -244,6 +251,12 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
return foundContent; return foundContent;
} }
/**
* tb_year 에 해당 년도 완료로 업데이트
*
* @param yyyy
* @param status
*/
public void updateYearState(int yyyy, String status) { public void updateYearState(int yyyy, String status) {
long execCount = long execCount =
queryFactory queryFactory
@@ -253,6 +266,12 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.execute(); .execute();
} }
/**
* 영상데이터관리 > 상세 조회
*
* @param mngYyyy
* @return
*/
public MapSheetMngDto.MngDto findMapSheetMng(int mngYyyy) { public MapSheetMngDto.MngDto findMapSheetMng(int mngYyyy) {
BooleanBuilder whereBuilder = new BooleanBuilder(); BooleanBuilder whereBuilder = new BooleanBuilder();
@@ -398,6 +417,12 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
return foundContent; return foundContent;
} }
/**
* 영상데이터관리 > 상세 > 오류 처리 내역
*
* @param searchReq 오류 검색 조건
* @return
*/
@Override @Override
public Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList( public Page<MapSheetMngDto.ErrorDataDto> findMapSheetErrorList(
MapSheetMngDto.@Valid ErrorSearchReq searchReq) { MapSheetMngDto.@Valid ErrorSearchReq searchReq) {
@@ -406,6 +431,10 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
BooleanBuilder whereBuilder = new BooleanBuilder(); BooleanBuilder whereBuilder = new BooleanBuilder();
whereBuilder.and(mapSheetMngHstEntity.mngYyyy.eq(searchReq.getMngYyyy())); whereBuilder.and(mapSheetMngHstEntity.mngYyyy.eq(searchReq.getMngYyyy()));
// syncState : 동기화 상태
// hst 테이블의 syncState 가 DONE, NOTYET 이 아닌 것 -> 오류
// (오류인 상태 SyncStateType : DUPLICATE, NOFILE, NOTPAIR, TYPEERROR)
whereBuilder.and( whereBuilder.and(
mapSheetMngHstEntity.syncState.ne("DONE").and(mapSheetMngHstEntity.syncState.ne("NOTYET"))); mapSheetMngHstEntity.syncState.ne("DONE").and(mapSheetMngHstEntity.syncState.ne("NOTYET")));
@@ -413,10 +442,12 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
whereBuilder.and(mapSheetMngHstEntity.syncState.eq(searchReq.getSyncState())); whereBuilder.and(mapSheetMngHstEntity.syncState.eq(searchReq.getSyncState()));
} }
// syncCheckState : 동기화 이후, 에러 처리 상태 - 처리 DONE / 미처리 NOTYET
if (searchReq.getSyncCheckState() != null && !searchReq.getSyncCheckState().isEmpty()) { if (searchReq.getSyncCheckState() != null && !searchReq.getSyncCheckState().isEmpty()) {
whereBuilder.and(mapSheetMngHstEntity.syncCheckState.eq(searchReq.getSyncCheckState())); whereBuilder.and(mapSheetMngHstEntity.syncCheckState.eq(searchReq.getSyncCheckState()));
} }
// 도엽 검색 조건 있을 때 whereBuilder에 추가
if (searchReq.getSearchValue() != null && !searchReq.getSearchValue().isEmpty()) { if (searchReq.getSearchValue() != null && !searchReq.getSearchValue().isEmpty()) {
whereBuilder.and( whereBuilder.and(
mapSheetMngHstEntity mapSheetMngHstEntity
@@ -474,7 +505,8 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
mapSheetMngHstEntity mapSheetMngHstEntity
.mapSheetNum .mapSheetNum
.eq(mapInkx5kEntity.mapidcdNo) .eq(mapInkx5kEntity.mapidcdNo)
.and(mapInkx5kEntity.useInference.eq(CommonUseStatus.USE))) .and(
mapInkx5kEntity.useInference.eq(CommonUseStatus.USE))) // 도엽 사용인 것만 오류 내역 표기
.where(whereBuilder) .where(whereBuilder)
.orderBy(mapSheetMngHstEntity.createdDate.desc()) .orderBy(mapSheetMngHstEntity.createdDate.desc())
.offset(pageable.getOffset()) .offset(pageable.getOffset())
@@ -490,13 +522,20 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
mapSheetMngHstEntity mapSheetMngHstEntity
.mapSheetNum .mapSheetNum
.eq(mapInkx5kEntity.mapidcdNo) .eq(mapInkx5kEntity.mapidcdNo)
.and(mapInkx5kEntity.useInference.eq(CommonUseStatus.USE))) .and(
mapInkx5kEntity.useInference.eq(CommonUseStatus.USE))) // 도엽 사용인 것만 오류 내역 표기
.where(whereBuilder) .where(whereBuilder)
.fetchOne(); .fetchOne();
return new PageImpl<>(foundContent, pageable, countQuery); return new PageImpl<>(foundContent, pageable, countQuery);
} }
/**
* hst 테이블에 선택한 hstUid row 정보 조회
*
* @param hstUid
* @return
*/
@Override @Override
public MapSheetMngDto.ErrorDataDto findMapSheetError(Long hstUid) { public MapSheetMngDto.ErrorDataDto findMapSheetError(Long hstUid) {
@@ -572,87 +611,6 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
return foundContent; return foundContent;
} }
/**
* 기준년도 추론 실행 가능 도엽 조회
*
* @param req
* @return
*/
@Override
public List<MngListDto> findByHstMapSheetTargetList(InferenceResultDto.RegReq req) {
BooleanBuilder whereBuilder = new BooleanBuilder();
whereBuilder.and(mapSheetMngHstEntity.dataState.eq("DONE"));
whereBuilder.and(
mapSheetMngHstEntity
.syncState
.eq("DONE")
.or(mapSheetMngHstEntity.syncCheckState.eq("DONE")));
whereBuilder.and(mapSheetMngHstEntity.useInference.eq("USE"));
whereBuilder.and(mapSheetMngHstEntity.mngYyyy.eq(req.getTargetYyyy()));
BooleanBuilder likeBuilder = new BooleanBuilder();
if (MapSheetScope.PART.getId().equals(req.getMapSheetScope())) {
List<String> list = req.getMapSheetNum();
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(
MngListDto.class,
mapSheetMngHstEntity.mngYyyy,
mapSheetMngHstEntity.mapSheetNum,
mapSheetMngHstEntity.mapSheetName,
nullExpression(Integer.class),
nullExpression(Boolean.class)))
.from(mapSheetMngHstEntity)
.innerJoin(mapInkx5kEntity)
.on(
mapInkx5kEntity
.mapidcdNo
.eq(mapSheetMngHstEntity.mapSheetNum)
.and(mapInkx5kEntity.useInference.eq(CommonUseStatus.USE)))
.where(whereBuilder)
.fetch();
}
@Override
public List<MngListDto> findByHstMapSheetTargetList(int mngYyyy, List<String> mapIds) {
BooleanBuilder whereBuilder = new BooleanBuilder();
whereBuilder.and(mapSheetMngHstEntity.mngYyyy.eq(mngYyyy));
whereBuilder.and(mapSheetMngHstEntity.mapSheetNum.in(mapIds));
whereBuilder.and(mapSheetMngHstEntity.dataState.eq("DONE"));
whereBuilder.and(mapSheetMngHstEntity.syncState.eq("DONE")); // TODO 싱크체크 or조건 추가
whereBuilder.and(mapSheetMngHstEntity.useInference.eq("USE"));
return queryFactory
.select(
Projections.constructor(
MngListDto.class,
mapSheetMngHstEntity.mngYyyy,
mapSheetMngHstEntity.mapSheetNum,
mapSheetMngHstEntity.mapSheetName))
.from(mapSheetMngHstEntity)
.where(whereBuilder)
.fetch();
}
@Override @Override
public List<MapSheetMngDto.MngFilesDto> findHstUidToMapSheetFileList(Long hstUid) { public List<MapSheetMngDto.MngFilesDto> findHstUidToMapSheetFileList(Long hstUid) {
BooleanBuilder whereBuilder = new BooleanBuilder(); BooleanBuilder whereBuilder = new BooleanBuilder();
@@ -760,6 +718,11 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.execute(); .execute();
} }
/**
* 같은 년도로 저장된 데이터가 있다면 삭제
*
* @param mngYyyy
*/
@Override @Override
public void deleteByMngYyyyMngAll(int mngYyyy) { public void deleteByMngYyyyMngAll(int mngYyyy) {
@@ -780,6 +743,12 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.delete(mapSheetMngEntity) .delete(mapSheetMngEntity)
.where(mapSheetMngEntity.mngYyyy.eq(mngYyyy)) .where(mapSheetMngEntity.mngYyyy.eq(mngYyyy))
.execute(); .execute();
long deletedTileCount =
queryFactory
.delete(mapSheetMngTileEntity)
.where(mapSheetMngTileEntity.mngYyyy.eq(mngYyyy))
.execute();
} }
@Override @Override
@@ -910,6 +879,13 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.execute(); .execute();
} }
/**
* 5k 도엽 기준으로 tb_map_sheet_hst 테이블에 먼저 insert 하기
*
* @param mngYyyy
* @param mngPath
* @return
*/
@Override @Override
public int insertMapSheetOrgDataToMapSheetMngHst(int mngYyyy, String mngPath) { public int insertMapSheetOrgDataToMapSheetMngHst(int mngYyyy, String mngPath) {
@@ -1046,6 +1022,11 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.execute(); .execute();
} }
/**
* 년도별 Tile 정보 등록
*
* @param addReq
*/
@Override @Override
public void insertMapSheetMngTile(AddReq addReq) { public void insertMapSheetMngTile(AddReq addReq) {
long execute = long execute =
@@ -1103,42 +1084,119 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
} }
@Override @Override
public List<MapSheetMngHstEntity> getMapSheetMngHst(Integer year) { public List<MngListDto> getMapSheetMngHst(
return queryFactory Integer year, String mapSheetScope, List<String> mapSheetNum) {
.select(mapSheetMngHstEntity) BooleanBuilder whereBuilder = new BooleanBuilder();
.from(mapSheetMngHstEntity)
.innerJoin(mapSheetMngFileEntity) whereBuilder.and(mapSheetMngHstEntity.dataState.eq("DONE"));
.on(mapSheetMngFileEntity.hstUid.eq(mapSheetMngHstEntity.hstUid)) whereBuilder.and(
.where(
mapSheetMngHstEntity
.mngYyyy
.eq(year)
.and(
mapSheetMngHstEntity mapSheetMngHstEntity
.syncState .syncState
.eq("DONE") .eq("DONE")
.or(mapSheetMngHstEntity.syncCheckState.eq("DONE"))) .or(mapSheetMngHstEntity.syncCheckState.eq("DONE")));
.and(mapSheetMngFileEntity.fileExt.eq("tif"))) whereBuilder.and(mapSheetMngHstEntity.useInference.eq("USE"));
.fetch();
whereBuilder.and(mapSheetMngHstEntity.mngYyyy.eq(year));
whereBuilder.and(
JPAExpressions.selectOne()
.from(mapSheetMngFileEntity)
.where(
mapSheetMngFileEntity
.hstUid
.eq(mapSheetMngHstEntity.hstUid) // FK 관계에 맞게 유지
.and(mapSheetMngFileEntity.fileExt.eq("tif"))
.and(mapSheetMngFileEntity.fileState.eq("DONE")))
.exists());
BooleanBuilder likeBuilder = new BooleanBuilder();
if (MapSheetScope.PART.getId().equals(mapSheetScope)) {
List<String> list = mapSheetNum;
if (list == null || list.isEmpty()) {
return List.of();
} }
@Override for (String prefix : list) {
public List<MapSheetFallbackYearDto> findFallbackCompareYearByMapSheets( if (prefix == null || prefix.isBlank()) {
Integer year, List<String> mapIds) { continue;
BooleanBuilder builder = new BooleanBuilder(); }
builder.and(mapSheetMngYearYnEntity.id.mapSheetNum.in(mapIds)); likeBuilder.or(mapSheetMngHstEntity.mapSheetNum.like(prefix.trim() + "%"));
builder.and(mapSheetMngYearYnEntity.id.mngYyyy.lt(year)); }
builder.and(mapSheetMngYearYnEntity.yn.eq("Y")); }
if (likeBuilder.hasValue()) {
whereBuilder.and(likeBuilder);
}
return queryFactory return queryFactory
.select( .select(
Projections.constructor( Projections.constructor(
MapSheetFallbackYearDto.class, MngListDto.class,
mapSheetMngYearYnEntity.id.mapSheetNum, mapSheetMngHstEntity.mngYyyy,
mapSheetMngYearYnEntity.id.mngYyyy.max())) mapSheetMngHstEntity.mapSheetNum,
.from(mapSheetMngYearYnEntity) mapSheetMngHstEntity.mapSheetName,
.where(builder) nullExpression(Integer.class),
.groupBy(mapSheetMngYearYnEntity.id.mapSheetNum) nullExpression(Boolean.class)))
.from(mapSheetMngHstEntity)
.innerJoin(mapInkx5kEntity)
.on(
mapInkx5kEntity
.mapidcdNo
.eq(mapSheetMngHstEntity.mapSheetNum)
.and(mapInkx5kEntity.useInference.eq(CommonUseStatus.USE)))
.where(whereBuilder)
.fetch();
}
@Override
public List<MngListDto> findFallbackCompareYearByMapSheets(Integer year, List<String> mapIds) {
QMapSheetMngHstEntity h = QMapSheetMngHstEntity.mapSheetMngHstEntity;
QMapSheetMngHstEntity h2 = new QMapSheetMngHstEntity("h2");
QMapSheetMngFileEntity f = QMapSheetMngFileEntity.mapSheetMngFileEntity;
QMapSheetMngFileEntity f2 = new QMapSheetMngFileEntity("f2");
QMapInkx5kEntity inkk = QMapInkx5kEntity.mapInkx5kEntity;
return queryFactory
.select(
Projections.constructor(
MngListDto.class,
h.mngYyyy,
h.mapSheetNum,
h.mapSheetName,
nullExpression(Integer.class),
nullExpression(Boolean.class)))
.from(h)
.innerJoin(inkk)
.on(inkk.mapidcdNo.eq(h.mapSheetNum).and(inkk.useInference.eq(CommonUseStatus.USE)))
.where(
h.mapSheetNum.in(mapIds),
h.mngYyyy.lt(year),
h.dataState.eq("DONE"),
h.useInference.eq("USE"),
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"))
.exists(),
// mapSheetNum별 최대 mngYyyy인 행만 남김
h.mngYyyy.eq(
JPAExpressions.select(h2.mngYyyy.max())
.from(h2)
.where(
h2.mapSheetNum.eq(h.mapSheetNum),
h2.mngYyyy.lt(year),
h2.dataState.eq("DONE"),
h2.useInference.eq("USE"),
h2.syncState.eq("DONE").or(h2.syncCheckState.eq("DONE")),
JPAExpressions.selectOne()
.from(f2)
.where(
f2.hstUid.eq(h2.hstUid),
f2.fileExt.eq("tif"),
f2.fileState.eq("DONE"))
.exists())))
.fetch(); .fetch();
} }
} }

View File

@@ -13,6 +13,12 @@ public interface MembersRepositoryCustom {
boolean existsByEmployeeNo(String employeeNo); boolean existsByEmployeeNo(String employeeNo);
/**
* 사번으로 사용자 조회
*
* @param employeeNo 사번
* @return 사용자 정보 조회
*/
Optional<MemberEntity> findByEmployeeNo(String employeeNo); Optional<MemberEntity> findByEmployeeNo(String employeeNo);
Optional<MemberEntity> findByUserId(String userId); Optional<MemberEntity> findByUserId(String userId);

View File

@@ -9,6 +9,16 @@ import org.springframework.data.domain.Page;
public interface ModelMngRepositoryCustom { public interface ModelMngRepositoryCustom {
/**
* 모델조회
*
* @param searchReq 페이징
* @param startDate 시작날짜
* @param endDate 종료날짜
* @param modelType 모델 타입 G1, G2, G3
* @param searchVal 모델 ver
* @return 모델 목록
*/
Page<ModelMngDto.ModelList> findModelMgmtList( Page<ModelMngDto.ModelList> findModelMgmtList(
ModelMngDto.searchReq searchReq, ModelMngDto.searchReq searchReq,
LocalDate startDate, LocalDate startDate,
@@ -29,4 +39,12 @@ public interface ModelMngRepositoryCustom {
Optional<ModelMngEntity> findByModelId(Long id); Optional<ModelMngEntity> findByModelId(Long id);
Optional<ModelMngEntity> findByModelId(UUID id); Optional<ModelMngEntity> findByModelId(UUID id);
/**
* 버전명으로 모델 조회
*
* @param ver 모델버전
* @return 모델정보
*/
Optional<ModelMngEntity> findByModelVer(String ver);
} }

View File

@@ -246,4 +246,14 @@ public class ModelMngRepositoryImpl extends QuerydslRepositorySupport
return Optional.ofNullable( return Optional.ofNullable(
queryFactory.selectFrom(modelMngEntity).where(modelMngEntity.uuid.eq(uuid)).fetchOne()); queryFactory.selectFrom(modelMngEntity).where(modelMngEntity.uuid.eq(uuid)).fetchOne());
} }
@Override
public Optional<ModelMngEntity> findByModelVer(String ver) {
return Optional.ofNullable(
queryFactory
.selectFrom(modelMngEntity)
.where(modelMngEntity.modelVer.eq(ver))
.limit(1)
.fetchOne());
}
} }

View File

@@ -59,6 +59,8 @@ public class MapInkxMngApiController {
mapInkxMngService.findMapInkxMngList(searchReq, useInference, searchVal)); mapInkxMngService.findMapInkxMngList(searchReq, useInference, searchVal));
} }
// 미사용 : 기획에서 도엽정보 등록 로직 제거됨
@Hidden
@Operation(summary = "저장", description = "도엽정보를 저장 합니다.") @Operation(summary = "저장", description = "도엽정보를 저장 합니다.")
@ApiResponses( @ApiResponses(
value = { value = {

View File

@@ -2,6 +2,9 @@ package com.kamco.cd.kamcoback.scheduler;
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.gukyuin.service.GukYuinApiService;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
import com.kamco.cd.kamcoback.inference.service.InferenceResultShpService;
import com.kamco.cd.kamcoback.scheduler.service.GukYuinApiLabelJobService; import com.kamco.cd.kamcoback.scheduler.service.GukYuinApiLabelJobService;
import com.kamco.cd.kamcoback.scheduler.service.GukYuinApiPnuJobService; import com.kamco.cd.kamcoback.scheduler.service.GukYuinApiPnuJobService;
import com.kamco.cd.kamcoback.scheduler.service.GukYuinApiStatusJobService; import com.kamco.cd.kamcoback.scheduler.service.GukYuinApiStatusJobService;
@@ -10,20 +13,26 @@ import com.kamco.cd.kamcoback.scheduler.service.MemberInactiveJobService;
import com.kamco.cd.kamcoback.scheduler.service.TrainingDataLabelJobService; import com.kamco.cd.kamcoback.scheduler.service.TrainingDataLabelJobService;
import com.kamco.cd.kamcoback.scheduler.service.TrainingDataReviewJobService; import com.kamco.cd.kamcoback.scheduler.service.TrainingDataReviewJobService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping; 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.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@Tag(name = "스케줄링 수동 호출 테스트", description = "스케줄링 수동 호출 테스트 API") @Tag(name = "스케줄링 및 jar 수동 호출 테스트", description = "스케줄링 및 jar 수동 호출 테스트 API")
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("/api/schedule") @RequestMapping("/api/schedule")
@@ -37,6 +46,8 @@ public class SchedulerApiController {
private final TrainingDataReviewJobService trainingDataReviewJobService; private final TrainingDataReviewJobService trainingDataReviewJobService;
private final MemberInactiveJobService memberInactiveJobService; private final MemberInactiveJobService memberInactiveJobService;
private final MapSheetMngFileJobController mapSheetMngFileJobController; private final MapSheetMngFileJobController mapSheetMngFileJobController;
private final InferenceResultShpService inferenceResultShpService;
private final GukYuinApiService gukYuinApiService;
@Operation(summary = "국유인 탐지객체 조회 PNU 업데이트 스케줄링", description = "국유인 탐지객체 조회 PNU 업데이트 스케줄링") @Operation(summary = "국유인 탐지객체 조회 PNU 업데이트 스케줄링", description = "국유인 탐지객체 조회 PNU 업데이트 스케줄링")
@GetMapping("/gukyuin/pnu") @GetMapping("/gukyuin/pnu")
@@ -60,7 +71,21 @@ public class SchedulerApiController {
return ApiResponseDto.ok(null); return ApiResponseDto.ok(null);
} }
@Operation(summary = "국유인 실태조사 적합여부 업데이트 스케줄링", description = "국유인 실태조사 적합여부 업데이트 스케줄링") @Operation(summary = "국유인 실태조사 적합여부 전송가능한 객체 ids", description = "국유인 실태조사 적합여부 전송가능한 객체 ids")
@GetMapping("/gukyuin/stblt-object-ids/{uid}/{mapSheetNum}")
public ApiResponseDto<List<String>> findStbltObjectIds(
@PathVariable String uid, @PathVariable String mapSheetNum) {
return ApiResponseDto.ok(gukYuinApiService.findStbltObjectIds(uid, mapSheetNum));
}
@Operation(summary = "국유인 라벨 완료 전송 스케줄링", description = "국유인 라벨 완료 전송 스케줄링")
@PostMapping("/gukyuin/stblt-update")
public ApiResponseDto<Void> stbltBulkUpdate(@RequestBody List<String> objectIds) {
gukYuinApiService.stbltBulkUpdate(objectIds);
return ApiResponseDto.ok(null);
}
@Operation(summary = "국유인 실태조사 적합여부 조회 스케줄링", description = "국유인 실태조사 적합여부 조회 스케줄링")
@GetMapping("/gukyuin/stblt") @GetMapping("/gukyuin/stblt")
public ApiResponseDto<Void> findGukYuinEligibleForSurvey( public ApiResponseDto<Void> findGukYuinEligibleForSurvey(
@RequestParam(required = false) LocalDate baseDate) { @RequestParam(required = false) LocalDate baseDate) {
@@ -117,4 +142,44 @@ public class SchedulerApiController {
return ApiResponseDto.createOK("OK"); return ApiResponseDto.createOK("OK");
} }
@Operation(summary = "추론결과 데이터 저장", description = "추론결과 데이터 저장")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "데이터 저장 성공",
content =
@Content(
mediaType = "application/json",
schema =
@Schema(implementation = InferenceResultShpDto.InferenceCntDto.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/save/inference/{uuid}")
public ApiResponseDto<InferenceResultShpDto.InferenceCntDto> saveInferenceData(
@Parameter(example = "feb2ec0b-a0f7-49ca-95e4-98b2231bdaae", description = "추론 uuid")
@PathVariable
UUID uuid) {
return ApiResponseDto.createOK(inferenceResultShpService.saveInferenceResultData(uuid));
}
@Operation(summary = "추론결과 shp 생성", description = "추론결과 shp 생성")
@PostMapping("/shp/inference/{uuid}")
public ApiResponseDto<Void> createShp(
@Parameter(example = "feb2ec0b-a0f7-49ca-95e4-98b2231bdaae", description = "추론 uuid")
@PathVariable
UUID uuid) {
// shp 파일 수동생성
inferenceResultShpService.createShp(uuid);
return ApiResponseDto.createOK(null);
}
@Operation(summary = "국유인 실태조사 적합여부 랜덤 업데이트", description = "국유인 실태조사 적합여부 랜덤 업데이트")
@PutMapping("/gukyuin/random-stblt-update/{uid}/{updateCnt}")
public ApiResponseDto<Integer> updateStbltRandomData(
@PathVariable String uid, @PathVariable int updateCnt) {
return ApiResponseDto.ok(gukYuinApiService.updateStbltRandomData(uid, updateCnt));
}
} }

View File

@@ -7,19 +7,76 @@ import org.springframework.stereotype.Component;
@Component @Component
public class ShpKeyLock { public class ShpKeyLock {
/**
* key별 Lock 객체를 저장하는 맵
*
* <p>- key: 예) shp 파일 경로, uuid, 도엽번호 등 - value: 해당 key 전용 ReentrantLock
*
* <p>ConcurrentHashMap을 사용하여 멀티스레드 환경에서도 안전하게 접근 가능
*/
private final ConcurrentHashMap<String, ReentrantLock> locks = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, ReentrantLock> locks = new ConcurrentHashMap<>();
/**
* 특정 key에 대한 Lock 시도
*
* @param key 동시성 제어 대상 식별자
* @return true → 락 획득 성공 false → 이미 다른 스레드가 사용 중
*/
public boolean tryLock(String key) { public boolean tryLock(String key) {
/**
* computeIfAbsent 설명:
*
* <p>- 해당 key가 이미 존재하면 기존 Lock 반환 - 없으면 새 ReentrantLock 생성 후 저장
*
* <p>동시성 환경에서도 원자적으로 실행됨
*/
ReentrantLock lock = locks.computeIfAbsent(key, k -> new ReentrantLock()); ReentrantLock lock = locks.computeIfAbsent(key, k -> new ReentrantLock());
/**
* tryLock():
*
* <p>- 대기하지 않고 즉시 시도 - 이미 다른 스레드가 점유 중이면 false 반환
*
* <p>→ 다운로드 중복 방지에 적합
*/
return lock.tryLock(); return lock.tryLock();
} }
/**
* 특정 key에 대한 Lock 해제
*
* <p>반드시 tryLock 성공한 동일 스레드에서 호출해야 함
*/
public void unlock(String key) { public void unlock(String key) {
// 해당 key에 등록된 Lock 조회
ReentrantLock lock = locks.get(key); ReentrantLock lock = locks.get(key);
/**
* isHeldByCurrentThread():
*
* <p>- 현재 스레드가 해당 락을 보유 중인지 확인 - 다른 스레드가 unlock 호출하는 것 방지
*/
if (lock != null && lock.isHeldByCurrentThread()) { if (lock != null && lock.isHeldByCurrentThread()) {
// 실제 락 해제
lock.unlock(); lock.unlock();
// 메모리 누수 방지(락이 비어있으면 제거)
/**
* 메모리 누수 방지 처리
*
* <p>hasQueuedThreads(): - 현재 이 락을 기다리는 스레드가 있는지 확인
*
* <p>대기 스레드가 없다면 locks 맵에서 제거하여 불필요한 Lock 객체 정리
*/
if (!lock.hasQueuedThreads()) { if (!lock.hasQueuedThreads()) {
/**
* remove(key, lock):
*
* <p>- 현재 맵에 등록된 값이 'lock'일 경우에만 제거 - 동시성 안전 (CAS 방식)
*/
locks.remove(key, lock); locks.remove(key, lock);
} }
} }

View File

@@ -31,26 +31,33 @@ public class GukYuinApiLabelJobService {
return "local".equalsIgnoreCase(profile); return "local".equalsIgnoreCase(profile);
} }
// @Scheduled(cron = "0 0 2 * * *")
public void runTask() { public void runTask() {
findLabelingCompleteSend(null); findLabelingCompleteSend(null);
} }
/** 어제 라벨링 검수 완료된 것 -> 국유인에 전송 */ /** 어제 라벨링 검수 완료된 것 -> 국유인에 전송 */
public void findLabelingCompleteSend(LocalDate baseDate) { public void findLabelingCompleteSend(LocalDate baseDate) {
// if (isLocalProfile()) {
// return; log.info("[Step 1-1] 어제 검수완료된 라벨링을 검색한다.");
// } log.info("=== baseDate : {}", baseDate);
log.info("=== baseDate 있으면 해당 일자, 없으면 어제일자로 조회");
List<GeomUidDto> list = gukYuinLabelJobCoreService.findYesterdayLabelingCompleteList(baseDate); List<GeomUidDto> list = gukYuinLabelJobCoreService.findYesterdayLabelingCompleteList(baseDate);
log.info("[Step 1-2] 검수완료된 폴리곤 객체 수 : {}", list == null ? 0 : list.size());
if (list.isEmpty()) { if (list.isEmpty()) {
log.info("[Step 1-3] 객체 없어서 return : 스케줄링 종료");
return; return;
} }
log.info("[Step 2-1] 객체 목록으로 라벨링 전송 API 호출 시작");
for (GeomUidDto gto : list) { for (GeomUidDto gto : list) {
log.info("[Step 2-2] 객체ID 확인 gto.getResultUid(): {}", gto.getResultUid());
ChngDetectContDto.ResultLabelDto dto = ChngDetectContDto.ResultLabelDto dto =
gukYuinApiService.updateChnDtctObjtLabelingYn(gto.getResultUid(), "Y", "Y"); gukYuinApiService.updateChnDtctObjtLabelingYn(gto.getResultUid(), "Y", "Y");
log.info("[Step 2-3] 결과 dto.getSuccess(): {}", dto.getSuccess());
if (dto.getSuccess()) { if (dto.getSuccess()) {
log.info("[Step 2-4] 결과가 성공일 때 inference_geom에 label_send_dttm 업데이트 하기");
log.info("==== 업데이트 하는 객체 gto.getGeoUid(): {}", gto.getGeoUid());
// inference_geom 에 label_send_dttm 업데이트 하기 // inference_geom 에 label_send_dttm 업데이트 하기
gukYuinLabelJobCoreService.updateAnalDataInferenceGeomSendDttm(gto.getGeoUid()); gukYuinLabelJobCoreService.updateAnalDataInferenceGeomSendDttm(gto.getGeoUid());
} }

View File

@@ -41,16 +41,24 @@ public class GukYuinApiPnuJobService {
// return; // return;
// } // }
log.info("[Step 1-1] 국유인 연동까지 완료된 추론 목록 가져오기");
log.info("=== apply_status -> 100% 다운 완료: GUK_COMPLETED, PNU매핑 실패: PNU_FAILED");
List<LearnKeyDto> list = List<LearnKeyDto> list =
gukYuinPnuJobCoreService.findGukyuinApplyStatusUidList( gukYuinPnuJobCoreService.findGukyuinApplyStatusUidList(
List.of(GukYuinStatus.GUK_COMPLETED.getId(), GukYuinStatus.PNU_FAILED.getId())); List.of(GukYuinStatus.GUK_COMPLETED.getId(), GukYuinStatus.PNU_FAILED.getId()));
log.info("[Step 1-2] 매핑할 추론 회차 갯수 : {}", list == null ? 0 : list.size());
if (list.isEmpty()) { if (list.isEmpty()) {
log.info("[Step 1-3] 매핑할 추론 회차 갯수 없어서 리턴하고 끝남");
return; return;
} }
log.info("[Step 2-1] 추론 회차별 pnu 매핑 for문 시작 ");
for (LearnKeyDto dto : list) { for (LearnKeyDto dto : list) {
try { try {
processUid(dto.getUid(), dto.getUid()); log.info("[Step 2-2] 진행하는 추론 Uid: {}", dto.getUid());
processUid(dto.getUid());
gukYuinPnuJobCoreService.updateGukYuinApplyStateComplete( gukYuinPnuJobCoreService.updateGukYuinApplyStateComplete(
dto.getId(), GukYuinStatus.PNU_COMPLETED); dto.getId(), GukYuinStatus.PNU_COMPLETED);
} catch (Exception e) { } catch (Exception e) {
@@ -61,15 +69,21 @@ public class GukYuinApiPnuJobService {
} }
} }
private void processUid(String chnDtctId, String uid) { private void processUid(String uid) {
ResultDto result = gukYuinApiService.listChnDtctId(chnDtctId, "Y"); log.info("[Step 2-4] 탐지 등록목록 상세 API 호출 시작");
ResultDto result = gukYuinApiService.listChnDtctId(uid, "Y");
if (result == null || result.getResult() == null || result.getResult().isEmpty()) { if (result == null || result.getResult() == null || result.getResult().isEmpty()) {
log.info("[Step 2-5] 결과값 없어서 return");
return; return;
} }
log.info("[Step 2-5] 결과값 첫번째 값 가져오기");
ChngDetectMastDto.Basic basic = result.getResult().get(0); ChngDetectMastDto.Basic basic = result.getResult().get(0);
String chnDtctCnt = basic.getChnDtctCnt(); String chnDtctCnt = basic.getChnDtctCnt();
log.info("[Step 2-6] 탐지 객체 전체 갯수 chnDtctCnt = {}", chnDtctCnt);
if (chnDtctCnt == null || chnDtctCnt.isEmpty()) { if (chnDtctCnt == null || chnDtctCnt.isEmpty()) {
log.info("[Step 2-76] 탐지 객체 전체 갯수 없어서 return");
return; return;
} }
@@ -78,29 +92,44 @@ public class GukYuinApiPnuJobService {
int totalCount = Integer.parseInt(chnDtctCnt); int totalCount = Integer.parseInt(chnDtctCnt);
int totalPages = (totalCount + pageSize - 1) / pageSize; int totalPages = (totalCount + pageSize - 1) / pageSize;
log.info("[Step 3-1] 탐지 객체 전체 수로 페이지 계산 : {}", totalPages);
for (int page = 0; page < totalPages; page++) { for (int page = 0; page < totalPages; page++) {
log.info("[Step 3-2] 페이지 별 호출 : {}", page);
processPage(uid, page, pageSize); processPage(uid, page, pageSize);
} }
} }
private void processPage(String uid, int page, int pageSize) { private void processPage(String uid, int page, int pageSize) {
log.info("[Step 4-1] 탐지객체 목록 API 호출 시작");
ResultContDto resContList = gukYuinApiService.findChnContList(uid, page, pageSize, "Y"); ResultContDto resContList = gukYuinApiService.findChnContList(uid, page, pageSize, "Y");
if (resContList.getResult() == null || resContList.getResult().isEmpty()) { if (resContList.getResult() == null || resContList.getResult().isEmpty()) {
log.info("[Step 4-2] 탐지객체 목록 결과 없어서 return");
return; // 외부 API 이상 방어 return; // 외부 API 이상 방어
} }
List<ContBasic> contList = resContList.getResult(); List<ContBasic> contList = resContList.getResult();
log.info("[Step 4-3] 탐지객체 목록 결과 contList.size : {}", contList == null ? 0 : contList.size());
for (ContBasic cont : contList) { for (ContBasic cont : contList) {
String[] pnuList = cont.getPnuList(); String[] pnuList = cont.getPnuList();
long pnuCnt = pnuList == null ? 0 : pnuList.length; long pnuCnt = pnuList == null ? 0 : pnuList.length;
log.info("[Step 4-4] 객체에 연결된 pnuCnt : {}", pnuCnt);
if (cont.getChnDtctObjtId() != null) { if (cont.getChnDtctObjtId() != null) {
log.info(
"[Step 4-5] inference_geom 에 pnu 갯수 update : cont.getChnDtctObjtId = {}",
cont.getChnDtctObjtId());
log.info(" === cont.getChnDtctObjtId : {}", cont.getChnDtctObjtId());
log.info(" === pnuCnt : {}", pnuCnt);
gukYuinPnuJobCoreService.updateInferenceGeomDataPnuCnt(cont.getChnDtctObjtId(), pnuCnt); gukYuinPnuJobCoreService.updateInferenceGeomDataPnuCnt(cont.getChnDtctObjtId(), pnuCnt);
if (pnuCnt > 0) { if (pnuCnt > 0) {
log.info("[Step 4-6] 객체 ID로 geoUid 검색 = {}", cont.getChnDtctObjtId());
Long geoUid = Long geoUid =
gukYuinPnuJobCoreService.findMapSheetAnalDataInferenceGeomUid( gukYuinPnuJobCoreService.findMapSheetAnalDataInferenceGeomUid(
cont.getChnDtctObjtId()); cont.getChnDtctObjtId());
log.info("[Step 4-7] tb_pnu 에 데이터 upsert 수행");
log.info("===== geoUid = {}", geoUid);
gukYuinPnuJobCoreService.insertGeoUidPnuData(geoUid, pnuList, cont.getChnDtctObjtId()); gukYuinPnuJobCoreService.insertGeoUidPnuData(geoUid, pnuList, cont.getChnDtctObjtId());
} }
} }

View File

@@ -38,24 +38,26 @@ public class GukYuinApiStbltJobService {
return "local".equalsIgnoreCase(profile); return "local".equalsIgnoreCase(profile);
} }
// @Scheduled(cron = "0 0 3 * * *")
public void runTask() { public void runTask() {
findGukYuinEligibleForSurvey(null); findGukYuinEligibleForSurvey(null);
} }
/** 국유인 연동 후, 실태조사 적합여부 확인하여 update */ /** 국유인 연동 후, 실태조사 적합여부 확인하여 update */
public void findGukYuinEligibleForSurvey(LocalDate baseDate) { public void findGukYuinEligibleForSurvey(LocalDate baseDate) {
// if (isLocalProfile()) {
// return;
// }
log.info("[Step 1-1] 국유인 연동 PNU 완료된 추론 회차 정보 가져오기 ");
log.info(" learn 테이블의 apply_status : {}", GukYuinStatus.PNU_COMPLETED.getId());
List<LearnKeyDto> list = List<LearnKeyDto> list =
gukYuinStbltJobCoreService.findGukYuinEligibleForSurveyList( gukYuinStbltJobCoreService.findGukYuinEligibleForSurveyList(
GukYuinStatus.PNU_COMPLETED.getId()); GukYuinStatus.PNU_COMPLETED.getId());
log.info("[Step 1-2] 국유인 연동 PNU 완료된 추론 회차 갯수 : {}", list == null ? 0 : list.size());
if (list.isEmpty()) { if (list.isEmpty()) {
log.info("[Step 1-3] 국유인 연동 PNU 완료된 추론 회차 갯수 없어서 return");
return; return;
} }
log.info("[Step 2-1] 추론 회차 list 로 for문 실행하기 ");
for (LearnKeyDto dto : list) { for (LearnKeyDto dto : list) {
try { try {
String targetDate = String targetDate =
@@ -63,19 +65,30 @@ public class GukYuinApiStbltJobService {
.minusDays(1) .minusDays(1)
.format(DateTimeFormatter.ofPattern("yyyyMMdd")); .format(DateTimeFormatter.ofPattern("yyyyMMdd"));
log.info("[Step 2-2] 실태조사 적합여부 조회 날짜 확인 : {}", targetDate);
if (baseDate != null) { // 파라미터가 있으면 if (baseDate != null) { // 파라미터가 있으면
targetDate = baseDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")); targetDate = baseDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
log.info("[Step 2-3] 수동호출 baseDate 가 있을 경우, 실태조사 적합여부 조회 날짜 확인 : {}", targetDate);
} }
log.info("[Step 3-1] 국유인 실태조사 적합여부 API 호출 시작 ");
log.info(" === 값 확인 - uid : {}", dto.getUid());
log.info(" === 값 확인 - targetDate : {}", targetDate);
RlbDtctDto result = gukYuinApiService.findRlbDtctList(dto.getUid(), targetDate, "Y"); RlbDtctDto result = gukYuinApiService.findRlbDtctList(dto.getUid(), targetDate, "Y");
if (result == null || result.getResult() == null || result.getResult().isEmpty()) { if (result == null || result.getResult() == null || result.getResult().isEmpty()) {
log.warn("[GUKYUIN] empty result chnDtctMstId={}", dto.getChnDtctMstId()); log.info("[GUKYUIN] empty result chnDtctId={}", dto.getUid());
log.info("=== 국유인 API 조회 결과 없어서 continue");
continue; continue;
} }
log.info("[Step 4-1] 국유인 실태조사 적합여부 result 값으로 데이터 업데이트");
log.info(" === 데이터 갯수 : {}", result.getResult() == null ? 0 : result.getResult().size());
for (RlbDtctMastDto stbltDto : result.getResult()) { for (RlbDtctMastDto stbltDto : result.getResult()) {
log.info("[Step 4-2] 국유인 실태조사 적합여부 결과 가져오기");
String resultUid = stbltDto.getChnDtctObjtId(); String resultUid = stbltDto.getChnDtctObjtId();
log.info(" == 테이블 tb_pnu 에 적합여부 리턴 결과를 upsert 진행, 객체 uid : {}", resultUid);
gukYuinStbltJobCoreService.updateGukYuinEligibleForSurvey(resultUid, stbltDto); gukYuinStbltJobCoreService.updateGukYuinEligibleForSurvey(resultUid, stbltDto);
} }
@@ -90,6 +103,7 @@ public class GukYuinApiStbltJobService {
e -> { e -> {
List<RlbDtctMastDto> pnuList = e.getValue(); List<RlbDtctMastDto> pnuList = e.getValue();
log.info("[Step 4-3] 국유인 실태조사 적합여부 업데이트 값을 객체 uid 기준으로 DTO 생성");
boolean hasY = pnuList.stream().anyMatch(v -> "Y".equals(v.getStbltYn())); boolean hasY = pnuList.stream().anyMatch(v -> "Y".equals(v.getStbltYn()));
String fitYn = hasY ? "Y" : "N"; String fitYn = hasY ? "Y" : "N";
@@ -105,7 +119,9 @@ public class GukYuinApiStbltJobService {
.findFirst() .findFirst()
.orElse(null); .orElse(null);
log.info(" === selected DTO : {}", selected);
if (selected == null) { if (selected == null) {
log.info(" === selected NULL");
return null; // 방어 코드 return null; // 방어 코드
} }
@@ -113,10 +129,11 @@ public class GukYuinApiStbltJobService {
fitYn, selected.getIncyCd(), selected.getIncyRsnCont()); fitYn, selected.getIncyCd(), selected.getIncyRsnCont());
})); }));
log.info("[Step 4-4] 국유인 실태조사 적합여부, 사유, 내용을 inference_geom 테이블에 update");
resultMap.forEach(gukYuinStbltJobCoreService::updateGukYuinObjectStbltYn); resultMap.forEach(gukYuinStbltJobCoreService::updateGukYuinObjectStbltYn);
} catch (Exception e) { } catch (Exception e) {
log.error("[GUKYUIN] failed uid={}", dto.getChnDtctMstId(), e); log.error("[GUKYUIN] failed uid={}", dto.getUid(), e);
} }
} }
} }

View File

@@ -1,9 +1,8 @@
package com.kamco.cd.kamcoback.scheduler.service; package com.kamco.cd.kamcoback.scheduler.service;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.common.exception.CustomApiException; import com.kamco.cd.kamcoback.common.inference.service.InferenceCommonService;
import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient; import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient;
import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient.ExternalCallResult; import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient.ExternalCallResult;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.InferenceBatchSheet; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.InferenceBatchSheet;
@@ -19,7 +18,6 @@ import java.nio.file.Paths;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@@ -31,7 +29,6 @@ import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -43,6 +40,7 @@ public class MapSheetInferenceJobService {
private final InferenceResultCoreService inferenceResultCoreService; private final InferenceResultCoreService inferenceResultCoreService;
private final ShpPipelineService shpPipelineService; private final ShpPipelineService shpPipelineService;
private final InferenceCommonService inferenceCommonService;
private final ExternalHttpClient externalHttpClient; private final ExternalHttpClient externalHttpClient;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
@@ -216,7 +214,7 @@ public class MapSheetInferenceJobService {
// 현재 모델 종료 업데이트 // 현재 모델 종료 업데이트
updateProcessingEndTimeByModel(job, sheet.getUuid(), now, currentType); updateProcessingEndTimeByModel(job, sheet.getUuid(), now, currentType);
// M3이면 전체 종료 // G3이면 전체 종료
if (ModelType.G3.getId().equals(currentType)) { if (ModelType.G3.getId().equals(currentType)) {
endAll(sheet, now); endAll(sheet, now);
return; return;
@@ -237,6 +235,13 @@ public class MapSheetInferenceJobService {
* @param now * @param now
*/ */
private void endAll(InferenceBatchSheet sheet, ZonedDateTime now) { private void endAll(InferenceBatchSheet sheet, ZonedDateTime now) {
List<Long> batchIds =
Stream.of(sheet.getM1BatchId(), sheet.getM2BatchId(), sheet.getM3BatchId())
.filter(Objects::nonNull)
.distinct()
.toList();
SaveInferenceAiDto save = new SaveInferenceAiDto(); SaveInferenceAiDto save = new SaveInferenceAiDto();
save.setUuid(sheet.getUuid()); save.setUuid(sheet.getUuid());
save.setStatus(Status.END.getId()); save.setStatus(Status.END.getId());
@@ -248,12 +253,6 @@ public class MapSheetInferenceJobService {
inferenceResultCoreService.upsertGeomData(sheet.getId()); inferenceResultCoreService.upsertGeomData(sheet.getId());
// 추론 종료일때 shp 파일 생성 // 추론 종료일때 shp 파일 생성
List<Long> batchIds =
Stream.of(sheet.getM1BatchId(), sheet.getM2BatchId(), sheet.getM3BatchId())
.filter(Objects::nonNull)
.distinct()
.toList();
String batchIdStr = batchIds.stream().map(String::valueOf).collect(Collectors.joining(",")); String batchIdStr = batchIds.stream().map(String::valueOf).collect(Collectors.joining(","));
// shp 파일 비동기 생성 // shp 파일 비동기 생성
@@ -357,7 +356,7 @@ public class MapSheetInferenceJobService {
m.setPriority(5d); m.setPriority(5d);
log.info("[BEFORE INFERENCE] BEFORE SendDto={}", m); log.info("[BEFORE INFERENCE] BEFORE SendDto={}", m);
// 추론 실행 api 호출 // 추론 실행 api 호출
Long batchId = ensureAccepted(m); Long batchId = inferenceCommonService.ensureAccepted(m);
SaveInferenceAiDto saveInferenceAiDto = new SaveInferenceAiDto(); SaveInferenceAiDto saveInferenceAiDto = new SaveInferenceAiDto();
saveInferenceAiDto.setUuid(uuid); saveInferenceAiDto.setUuid(uuid);
@@ -369,73 +368,6 @@ public class MapSheetInferenceJobService {
inferenceResultCoreService.update(saveInferenceAiDto); inferenceResultCoreService.update(saveInferenceAiDto);
} }
/**
* api 호출
*
* @param dto
* @return
*/
// 같은함수가 왜 두개지
private Long ensureAccepted(InferenceSendDto dto) {
if (dto == null) {
log.warn("not InferenceSendDto dto");
throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST);
}
// 1) 요청 로그
log.info("");
log.info("========================================================");
log.info("[SEND INFERENCE] Inference request dto= {}", dto);
log.info("========================================================");
log.info("");
// 2) local 환경 임시 처리
// if ("local".equals(profile)) {
// if (dto.getPred_requests_areas() == null) {
// throw new IllegalStateException("pred_requests_areas is null");
// }
//
// dto.getPred_requests_areas().setInput1_scene_path("/kamco-nfs/requests/2023_local.geojson");
//
// dto.getPred_requests_areas().setInput2_scene_path("/kamco-nfs/requests/2024_local.geojson");
// }
// 3) HTTP 호출
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
// TODO 어떤 URL로 어떤파리티러로 요청한 로딩해야지
ExternalCallResult<String> result =
externalHttpClient.call(inferenceUrl, HttpMethod.POST, dto, headers, String.class);
if (result.statusCode() < 200 || result.statusCode() >= 300) {
log.error("Inference API failed. status={}, body={}", result.statusCode(), result.body());
throw new CustomApiException("BAD_GATEWAY", HttpStatus.BAD_GATEWAY);
}
// 4) 응답 파싱
try {
List<Map<String, Object>> list =
objectMapper.readValue(result.body(), new TypeReference<>() {});
if (list.isEmpty()) {
// 어떤 URL로 어떤파리티러로 요청한 정보를 봐야 재현을 할듯하지요
throw new IllegalStateException("Inference response is empty");
}
Object batchIdObj = list.get(0).get("batch_id");
if (batchIdObj == null) {
throw new IllegalStateException("batch_id not found in response");
}
return Long.valueOf(batchIdObj.toString());
} catch (Exception e) {
log.error("Failed to parse inference response. body={}", result.body(), e);
throw new CustomApiException("INVALID_INFERENCE_RESPONSE", HttpStatus.BAD_GATEWAY);
}
}
/** /**
* 실행중인 profile * 실행중인 profile
* *
@@ -492,6 +424,7 @@ public class MapSheetInferenceJobService {
.map(Long::valueOf) .map(Long::valueOf)
.toList(); .toList();
// 추론 실행 도엽별 정보 조회
List<Long> jobIds = inferenceResultCoreService.findFail5kList(uuid, failedIds, type); List<Long> jobIds = inferenceResultCoreService.findFail5kList(uuid, failedIds, type);
Set<Long> jobIdSet = new HashSet<>(jobIds); Set<Long> jobIdSet = new HashSet<>(jobIds);

View File

@@ -1,6 +1,7 @@
package com.kamco.cd.kamcoback.scheduler.service; package com.kamco.cd.kamcoback.scheduler.service;
import com.kamco.cd.kamcoback.common.service.ExternalJarRunner; 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 com.kamco.cd.kamcoback.scheduler.config.ShpKeyLock;
import java.nio.file.Paths; import java.nio.file.Paths;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -13,11 +14,15 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor @RequiredArgsConstructor
public class ShpPipelineService { public class ShpPipelineService {
private final InferenceResultCoreService inferenceResultCoreService;
private final ExternalJarRunner externalJarRunner; private final ExternalJarRunner externalJarRunner;
private final ShpKeyLock shpKeyLock; private final ShpKeyLock shpKeyLock;
@Async("shpExecutor") @Async("shpExecutor")
public void runPipeline(String jarPath, String datasetDir, String batchId, String inferenceId) { public void runPipeline(String jarPath, String datasetDir, String batchIds, String inferenceId) {
//
// batchIds.split(",")
// inferenceResultCoreService.getInferenceResultCnt();
// inferenceId 기준 동시 실행 제한 // inferenceId 기준 동시 실행 제한
if (!shpKeyLock.tryLock(inferenceId)) { if (!shpKeyLock.tryLock(inferenceId)) {
@@ -27,7 +32,7 @@ public class ShpPipelineService {
try { try {
// uid 기준 merge shp, geojson 파일 생성 // uid 기준 merge shp, geojson 파일 생성
externalJarRunner.run(jarPath, batchId, inferenceId, "", "MERGED"); externalJarRunner.run(jarPath, batchIds, inferenceId, "", "MERGED");
// uid 기준 shp 파일 geoserver 등록 // uid 기준 shp 파일 geoserver 등록
String register = String register =
@@ -36,7 +41,7 @@ public class ShpPipelineService {
externalJarRunner.run(jarPath, register, inferenceId); externalJarRunner.run(jarPath, register, inferenceId);
// uid 기준 도엽별 shp, geojson 파일 생성 // uid 기준 도엽별 shp, geojson 파일 생성
externalJarRunner.run(jarPath, batchId, inferenceId, "", "RESOLVE"); externalJarRunner.run(jarPath, batchIds, inferenceId, "", "RESOLVE");
log.info("SHP pipeline finished. inferenceId={}", inferenceId); log.info("SHP pipeline finished. inferenceId={}", inferenceId);

View File

@@ -32,7 +32,6 @@ public class TrainingDataLabelJobService {
return "local".equalsIgnoreCase(profile); return "local".equalsIgnoreCase(profile);
} }
// @Scheduled(cron = "0 0 0 * * *")
public void runTask() { public void runTask() {
// 프록시를 통해 호출해야 @Transactional이 적용됨 // 프록시를 통해 호출해야 @Transactional이 적용됨
applicationContext applicationContext
@@ -43,55 +42,72 @@ public class TrainingDataLabelJobService {
@Transactional @Transactional
public void assignReviewerYesterdayLabelComplete(LocalDate baseDate) { public void assignReviewerYesterdayLabelComplete(LocalDate baseDate) {
// if (isLocalProfile()) {
// return;
// }
try { try {
log.info("[Step 1-1] 라벨링 완료된 데이터 목록 조회한다.");
log.info("=== baseDate : {}", baseDate);
log.info("=== baseDate 있으면 해당 일자, 없으면 어제일자로 조회");
List<Tasks> tasks = List<Tasks> tasks =
trainingDataLabelJobCoreService.findCompletedYesterdayUnassigned(baseDate); trainingDataLabelJobCoreService.findCompletedYesterdayUnassigned(baseDate);
log.info("[Step 1-2] 목록 객체 건수 count : {}", tasks == null ? 0 : tasks.size());
if (tasks.isEmpty()) { if (tasks.isEmpty()) {
log.info("[Step 1-3] 조회된 것 없어 return");
return; return;
} }
// 회차별로 그룹핑 // 회차별로 그룹핑
log.info("[Step 2-1] 회차별로 그룹핑 시작");
Map<Long, List<Tasks>> taskByRound = Map<Long, List<Tasks>> taskByRound =
tasks.stream().collect(Collectors.groupingBy(Tasks::getAnalUid)); tasks.stream().collect(Collectors.groupingBy(Tasks::getAnalUid));
// 회차별 분배 // 회차별 분배
log.info("[Step 3-1] 회차별로 분배 시작");
for (Map.Entry<Long, List<Tasks>> entry : taskByRound.entrySet()) { for (Map.Entry<Long, List<Tasks>> entry : taskByRound.entrySet()) {
Long analUid = entry.getKey(); Long analUid = entry.getKey();
List<Tasks> analTasks = entry.getValue(); List<Tasks> analTasks = entry.getValue();
// pending 계산 // pending 계산
log.info("[Step 3-2] 수행하는 회차 analUid: {}", analUid);
log.info("해당 회차에 라벨링 할당받은 검수자별 완료 건수 count(), 완료한 게 적은 순으로 해야 일이 한 사람에게 몰리지 않음");
List<InspectorPendingDto> pendings = List<InspectorPendingDto> pendings =
trainingDataLabelJobCoreService.findInspectorPendingByRound(analUid); trainingDataLabelJobCoreService.findInspectorPendingByRound(analUid);
log.info("검수자 수: {}", pendings == null ? 0 : pendings.size());
if (pendings.isEmpty()) { if (pendings.isEmpty()) {
log.info("[Step 3-3] 할당된 검수자가 없으면 return");
continue; continue;
} }
log.info("[Step 4-1] 검수자 사번 List 생성");
List<String> reviewerIds = List<String> reviewerIds =
pendings.stream().map(InspectorPendingDto::getInspectorUid).toList(); pendings.stream().map(InspectorPendingDto::getInspectorUid).toList();
// Lock 걸릴 수 있기 때문에 엔티티 조회하는 Repository 에서 구현 // Lock 걸릴 수 있기 때문에 엔티티 조회하는 Repository 에서 구현
log.info("[Step 4-2] 검수자 테이블 lock 걸리지 않게 처리");
trainingDataLabelJobCoreService.lockInspectors(analUid, reviewerIds); trainingDataLabelJobCoreService.lockInspectors(analUid, reviewerIds);
// 균등 분배 // 균등 분배
log.info("[Step 5-1] 검수자에게 라벨 작업 균등분배 시작");
Map<String, List<Tasks>> assignMap = distributeByLeastPending(analTasks, reviewerIds); Map<String, List<Tasks>> assignMap = distributeByLeastPending(analTasks, reviewerIds);
log.info("[Step 5-2] 검수자에게 라벨 작업 균등분배 완료");
// reviewer별 batch update // reviewer별 batch update
log.info("[Step 5-3] 검수자별 할당 데이터를 batch update 시작");
assignMap.forEach( assignMap.forEach(
(reviewerId, assignedTasks) -> { (reviewerId, assignedTasks) -> {
if (assignedTasks.isEmpty()) { if (assignedTasks.isEmpty()) {
log.info("[Step 5-4] 할당된 데이터 없으면 return");
return; return;
} }
List<UUID> assignmentUids = List<UUID> assignmentUids =
assignedTasks.stream().map(Tasks::getAssignmentUid).toList(); assignedTasks.stream().map(Tasks::getAssignmentUid).toList();
log.info("[Step 6-1] 할당 작업에 검수자 아이디 update");
log.info("==== 검수자 사번: {}", reviewerId);
log.info("==== 할당 갯수: {}", assignmentUids == null ? 0 : assignmentUids.size());
trainingDataLabelJobCoreService.assignReviewerBatch(assignmentUids, reviewerId); trainingDataLabelJobCoreService.assignReviewerBatch(assignmentUids, reviewerId);
log.info("[Step 7-1] geom 테이블에 검수 상태 update");
List<Long> geomUids = assignedTasks.stream().map(Tasks::getInferenceUid).toList(); List<Long> geomUids = assignedTasks.stream().map(Tasks::getInferenceUid).toList();
trainingDataLabelJobCoreService.updateGeomUidTestState(geomUids); trainingDataLabelJobCoreService.updateGeomUidTestState(geomUids);
}); });

View File

@@ -39,52 +39,68 @@ public class TrainingDataReviewJobService {
} }
@Transactional @Transactional
// @Scheduled(cron = "0 0 2 * * *")
public void runTask() { public void runTask() {
exportGeojsonLabelingGeom(null); exportGeojsonLabelingGeom(null);
} }
public void exportGeojsonLabelingGeom(LocalDate baseDate) { public void exportGeojsonLabelingGeom(LocalDate baseDate) {
// if (isLocalProfile()) {
// return;
// }
// 1) 경로/파일명 결정 // 1) 경로/파일명 결정
String targetDir = String targetDir =
"local".equals(profile) ? System.getProperty("user.home") + "/geojson" : trainingDataDir; "local".equals(profile) ? System.getProperty("user.home") + "/geojson" : trainingDataDir;
log.info("[Step 1-1] geojson 파일 생성할 경로: {}", targetDir);
// 2) 진행중인 회차 중, complete_cnt 가 존재하는 회차 목록 가져오기 // 2) 진행중인 회차 중, complete_cnt 가 존재하는 회차 목록 가져오기
log.info("[Step 1-2] 진행중인 회차 중, complete_cnt 가 존재하는 회차 목록 가져오기");
List<AnalCntInfo> analList = trainingDataReviewJobCoreService.findAnalCntInfoList(); List<AnalCntInfo> analList = trainingDataReviewJobCoreService.findAnalCntInfoList();
log.info("[Step 1-3] 회차 리스트 건수: {}", analList == null ? 0 : analList.size());
if (analList.isEmpty()) {
log.info("[Step 1-4] 회차 리스트 없어 return 하고 종료");
return;
}
for (AnalCntInfo info : analList) { for (AnalCntInfo info : analList) {
log.info("[Step 2-1] 회차 폴리곤 전체 건수 == 파일 생성 건수 같은지 확인");
log.info("=== info.getAllCnt(): {}", info.getAllCnt());
log.info("=== info.getFileCnt(): {}", info.getFileCnt());
if (Objects.equals(info.getAllCnt(), info.getFileCnt())) { if (Objects.equals(info.getAllCnt(), info.getFileCnt())) {
log.info("[Step 2-2] 회차 폴리곤 전체 건수 == 파일 생성 건수 같아서 파일 생성 진행하지 않음 continue");
continue; continue;
} }
String resultUid = info.getResultUid(); // 회차의 대문자 uid (폴더명으로 사용) String resultUid = info.getResultUid(); // 회차의 대문자 uid (폴더명으로 사용)
// 3) 회차 + 어제까지 검수 완료된 총 데이터의 도엽별 목록 가져오기 // 3) 회차 + 어제까지 검수 완료된 총 데이터의 도엽별 목록 가져오기
log.info("[Step 3-1] 회차 + 어제까지 검수 완료된 총 데이터의 도엽별 목록 가져오기");
List<AnalMapSheetList> analMapList = List<AnalMapSheetList> analMapList =
trainingDataReviewJobCoreService.findCompletedAnalMapSheetList( trainingDataReviewJobCoreService.findCompletedAnalMapSheetList(
info.getAnalUid(), baseDate); info.getAnalUid(), baseDate);
log.info("=== analMapList cnt: {}", analMapList == null ? 0 : analMapList.size());
if (analMapList.isEmpty()) { if (analMapList.isEmpty()) {
log.info("[Step 3-2] 도엽 목록 조회되지 않아 continue");
continue; continue;
} }
log.info("[Step 4-1] 도엽별 geom 데이터 가지고 와서 geojson 만들기 시작");
for (AnalMapSheetList mapSheet : analMapList) { for (AnalMapSheetList mapSheet : analMapList) {
// 4) 도엽별 geom 데이터 가지고 와서 geojson 만들기 // 4) 도엽별 geom 데이터 가지고 와서 geojson 만들기
log.info("[Step 4-2] 도엽별 검수완료된 폴리곤 데이터 목록 조회");
List<CompleteLabelData> completeList = List<CompleteLabelData> completeList =
trainingDataReviewJobCoreService.findCompletedYesterdayLabelingList( trainingDataReviewJobCoreService.findCompletedYesterdayLabelingList(
info.getAnalUid(), mapSheet.getMapSheetNum(), baseDate); info.getAnalUid(), mapSheet.getMapSheetNum(), baseDate);
log.info("=== completeList size: {}", completeList == null ? 0 : completeList.size());
if (!completeList.isEmpty()) { if (!completeList.isEmpty()) {
log.info("[Step 4-3] 목록에서 filter로 geoUid List 생성, 폴리곤 feature별 리스트 생성");
List<Long> geoUids = completeList.stream().map(CompleteLabelData::getGeoUid).toList(); List<Long> geoUids = completeList.stream().map(CompleteLabelData::getGeoUid).toList();
List<GeoJsonFeature> features = completeList.stream().map(GeoJsonFeature::from).toList(); List<GeoJsonFeature> features = completeList.stream().map(GeoJsonFeature::from).toList();
// 5) 파일서버에 uid 폴더 생성 후 업로드 하기 // 5) 파일서버에 uid 폴더 생성 후 업로드 하기
log.info("[Step 5-1] 파일서버에 uid 폴더 생성 후 업로드 하기 시작");
FeatureCollection collection = new FeatureCollection(features); FeatureCollection collection = new FeatureCollection(features);
String filename = String filename =
String.format( String.format(
@@ -94,15 +110,21 @@ public class TrainingDataReviewJobService {
mapSheet.getTargetYyyy(), mapSheet.getTargetYyyy(),
mapSheet.getMapSheetNum()); mapSheet.getMapSheetNum());
log.info("=== filename: {}", filename);
log.info("=== 회차의 uid: {}", resultUid);
Path outputPath = Paths.get(targetDir + "/" + resultUid, filename); Path outputPath = Paths.get(targetDir + "/" + resultUid, filename);
log.info("=== outputPath: {}", outputPath);
try { try {
log.info("[Step 6-1] Uid로 폴더 생성");
Files.createDirectories(outputPath.getParent()); Files.createDirectories(outputPath.getParent());
log.info("[Step 6-2] geoJson 파일 생성");
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT); objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.writeValue(outputPath.toFile(), collection); objectMapper.writeValue(outputPath.toFile(), collection);
// geoUids : file_create_yn = true 로 업데이트 // geoUids : file_create_yn = true 로 업데이트
log.info("[Step 6-3] learn_data_geom 에 file_create_yn = true 로 업데이트");
trainingDataReviewJobCoreService.updateLearnDataGeomFileCreateYn(geoUids); trainingDataReviewJobCoreService.updateLearnDataGeomFileCreateYn(geoUids);
} catch (IOException e) { } catch (IOException e) {

View File

@@ -101,6 +101,7 @@ inference:
batch-url: http://192.168.2.183:8000/batches batch-url: http://192.168.2.183:8000/batches
jar-path: ${inference.nfs}/repo/jar/shp-exporter.jar jar-path: ${inference.nfs}/repo/jar/shp-exporter.jar
inference-server-name: server1,server2,server3,server4 inference-server-name: server1,server2,server3,server4
output-dir: ${inference.nfs}/model_output/export
gukyuin: gukyuin:
#url: http://localhost:8080 #url: http://localhost:8080

View File

@@ -74,11 +74,12 @@ file:
pt-FileName: v5-best.pt pt-FileName: v5-best.pt
inference: inference:
nfs: C:/Users/gyparkkamco-nfs nfs: C:/Users/gypark/kamco-nfs
url: http://10.100.0.11:8000/jobs url: http://10.100.0.11:8000/jobs
batch-url: http://10.100.0.11:8000/batches batch-url: http://10.100.0.11:8000/batches
jar-path: jar/shp-exporter.jar jar-path: jar/shp-exporter.jar
inference-server-name: server1,server2,server3,server4 inference-server-name: server1,server2,server3,server4
output-dir: ${inference.nfs}/model_output/export
gukyuin: gukyuin:
#url: http://localhost:8080 #url: http://localhost:8080
@@ -90,3 +91,6 @@ layer:
wms-path: geoserver/cd wms-path: geoserver/cd
wmts-path: geoserver/cd/gwc/service wmts-path: geoserver/cd/gwc/service
workspace: cd workspace: cd
training-data:
geojson-dir: /kamco-nfs/dataset/request/

View File

@@ -88,6 +88,7 @@ file:
nfs: /data nfs: /data
pt-path: ${file.nfs}/ckpt/model/v6-cls-checkpoints/ pt-path: ${file.nfs}/ckpt/model/v6-cls-checkpoints/
pt-FileName: yolov8_6th-6m.pt pt-FileName: yolov8_6th-6m.pt
output-dir: /kamco-nfs/dataset/export/
inference: inference:
nfs: /data nfs: /data
@@ -95,6 +96,7 @@ inference:
batch-url: http://172.16.4.56:8000/batches batch-url: http://172.16.4.56:8000/batches
jar-path: ${inference.nfs}/repo/jar/shp-exporter.jar jar-path: ${inference.nfs}/repo/jar/shp-exporter.jar
inference-server-name: server1,server2,server3,server4 inference-server-name: server1,server2,server3,server4
output-dir: ${inference.nfs}/model_output/export
gukyuin: gukyuin:
url: http://127.0.0.1:5301 url: http://127.0.0.1:5301

View File

@@ -82,6 +82,8 @@ file:
dataset-response: ${file.nfs}/dataset/response/ dataset-response: ${file.nfs}/dataset/response/
training-data: training-data:
geojson-dir: ${file.nfs}/dataset/request/ geojson-dir: ${file.nfs}/dataset/request/
output-dir: ${file.nfs}/dataset/export/ # 마운트 경로 : 국유인 연계 등록할 추론 shp 파일
inference: inference:
nfs: /kamco-nfs nfs: /kamco-nfs
geojson-dir: ${inference.nfs}/requests/ # 추론실행을 위한 파일생성경로 geojson-dir: ${inference.nfs}/requests/ # 추론실행을 위한 파일생성경로