203 Commits

Author SHA1 Message Date
d240e4349c 영상관리 스케줄러 true로 변경, 영상삭제 시 year도 업데이트 되게 추가 2026-05-18 15:29:44 +09:00
4801ae8b7d 라벨링 툴 영문명칭 수정 추가 2026-05-15 16:41:20 +09:00
8c33439bcd 작업현황 > 작업자목록 RoleType 영문명 추가 2026-05-15 11:13:25 +09:00
ec4fb46cea 적합여부 상태명 영문 추가 2026-05-15 10:57:06 +09:00
63b3b3c09d 변화지도 년도 목록 id desc 로 수정 2026-05-15 09:58:46 +09:00
cfd5963a15 추론 삭제여부 로직 추가 2026-05-15 09:55:31 +09:00
9116bf7b89 선택 모델목록 생성완료일 desc 로 수정 2026-05-12 16:58:29 +09:00
2d7413c8e1 영문버전 변환 누락된 부분 추가 2026-05-12 11:16:19 +09:00
df7a67ed41 추론 목록,class-count, geom-list 에 한글로 나오는 코드 영문버전 로직 추가 2026-05-12 10:49:39 +09:00
176d27c229 로그인 시, 접속 IP 대역 확인 2026-05-12 09:50:26 +09:00
8c70ec345c 추론 상세 geom-list 오래 걸리는 현상 수정 2026-05-11 17:52:37 +09:00
8fd92f5691 lang check 로직 수정 2026-05-11 16:24:11 +09:00
b69d09176e lang check 2026-05-11 16:14:25 +09:00
04af23b814 영문 Enum 항목값 내릴 수 있도록 수정 Accept-Language: en 으로 추가하여 호출해야 함. 2026-05-11 15:58:03 +09:00
a1fc62b3eb 메뉴 목록, 영상관리 목록 API 영문 헤더 추가, 추론 삭제기능 추가 2026-05-11 14:15:45 +09:00
ed3c901143 메뉴 API 영문명 로직 추가 2026-05-11 10:36:45 +09:00
b58b859470 주석 추가 2026-05-11 10:08:18 +09:00
b78fda151a 영상관리 삭제 API 추가, shp 생성 기존로직으로 수정 2026-05-11 10:02:48 +09:00
435a48afb9 test 2026-05-07 12:53:01 +09:00
af0866ff5b Merge branch 'develop' of https://kamco.git.gs.dabeeo.com/MVPTeam/kamco-cd-api into develop 2026-05-06 17:46:56 +09:00
d0132ac697 local 에서도 redis 포트 수정 2026-05-06 17:46:32 +09:00
dean
4272558f32 add log 2026-04-22 09:41:23 +09:00
d83fb01cbe 학습데이터 목록 쿼리 수정 2026-04-20 16:37:22 +09:00
0425a6486d Merge pull request 'shp 테스트' (#160) from feat/infer_dev_260211 into develop
Reviewed-on: #160
2026-03-25 14:07:21 +09:00
241c7222d1 shp 테스트 2026-03-25 14:07:06 +09:00
71e4ab14bd Merge pull request 'shp 테스트' (#159) from feat/infer_dev_260211 into develop
Reviewed-on: #159
2026-03-25 14:01:26 +09:00
52da4dafc3 shp 테스트 2026-03-25 14:01:02 +09:00
24d615174d Merge pull request '토근 정보 변경' (#158) from feat/infer_dev_260211 into develop
Reviewed-on: #158
2026-03-19 13:48:56 +09:00
12b0f0867d 토근 정보 변경 2026-03-19 13:48:14 +09:00
283d906da6 Merge pull request '토큰시간 변경' (#157) from feat/infer_dev_260211 into develop
Reviewed-on: #157
2026-03-19 10:31:05 +09:00
360b451c38 토큰시간 변경 2026-03-19 10:30:42 +09:00
80e281cb99 Merge pull request '개발서버 토큰 시간 변경' (#156) from feat/infer_dev_260211 into develop
Reviewed-on: #156
2026-03-18 14:02:29 +09:00
b07bc38ee8 개발서버 토큰 시간 변경 2026-03-18 14:01:56 +09:00
e4c1c76b2b Merge pull request '재할당 transactional 추가' (#155) from feat/infer_dev_260211 into develop
Reviewed-on: #155
2026-03-17 17:47:43 +09:00
01b64eeca7 재할당 transactional 추가 2026-03-17 17:47:15 +09:00
516f949a37 Merge pull request 'spotless 적용' (#154) from feat/infer_dev_260211 into develop
Reviewed-on: #154
2026-03-17 14:00:22 +09:00
b6ed3b57ef spotless 적용 2026-03-17 14:00:04 +09:00
9eebf23774 Merge pull request 'dev 토큰 유효시간 변경, 인증 Log 추가한거 삭제' (#153) from feat/infer_dev_260211 into develop
Reviewed-on: #153
2026-03-17 13:55:53 +09:00
4f742edf8b dev 토큰 유효시간 변경, 인증 Log 추가한거 삭제 2026-03-17 13:55:30 +09:00
0aa415cf3a Merge pull request '토큰 생성 로그 추가' (#152) from feat/infer_dev_260211 into develop
Reviewed-on: #152
2026-03-17 13:33:34 +09:00
884b635585 토큰 생성 로그 추가 2026-03-17 13:33:17 +09:00
6861f6b8b6 Merge pull request '메뉴 권한 Log 추가' (#151) from feat/infer_dev_260211 into develop
Reviewed-on: #151
2026-03-17 13:22:06 +09:00
a97af0d4dd 메뉴 권한 Log 추가 2026-03-17 13:21:46 +09:00
9297d19e24 Merge pull request 'refresh 토큰 인증 로그 추가' (#150) from feat/infer_dev_260211 into develop
Reviewed-on: #150
2026-03-17 12:46:12 +09:00
65c38b3083 refresh 토큰 인증 로그 추가 2026-03-17 12:45:56 +09:00
24dca652f0 Merge pull request 'refresh 토큰 재발급 로그 추가' (#149) from feat/infer_dev_260211 into develop
Reviewed-on: #149
2026-03-17 12:26:31 +09:00
193cd449a8 refresh 토큰 재발급 로그 추가 2026-03-17 12:23:45 +09:00
dean
0efde4e5bb access-token 2026-03-17 07:49:13 +09:00
dean
548d82da12 access-token 2026-03-17 07:20:08 +09:00
536ff8fc65 추론 종료 async 수정 2026-03-13 15:52:20 +09:00
1dc1ce741e 추론 종료 비동기 호출 2026-03-13 15:38:11 +09:00
d21ed61666 hello 2026-03-12 08:22:39 +09:00
c9a1007c21 hello 2026-03-12 08:08:59 +09:00
413afb0b7c shp파일 바꾸는로직정리 2026-03-12 08:08:11 +09:00
e69eccc82b shp파일 바꾸는로직정리 2026-03-12 07:57:14 +09:00
828a4c5dca shp파일 바꾸는로직정리 2026-03-12 07:31:54 +09:00
5d417d85ff Merge pull request '국유인연동 알럿 문구 변경' (#148) from feat/infer_dev_260211 into develop
Reviewed-on: #148
2026-03-09 10:52:25 +09:00
614d6da695 국유인연동 알럿 문구 변경 2026-03-09 10:51:57 +09:00
8d45e91982 Merge pull request '국유인연동 : 라벨링 진행중 회차 uuid 추가' (#147) from feat/infer_dev_260211 into develop
Reviewed-on: #147
2026-03-09 10:10:52 +09:00
e1febf5863 국유인연동 : 라벨링 진행중 회차 uuid 추가 2026-03-09 10:10:08 +09:00
97fb659f15 enum @notBlank제거 2026-03-07 01:43:07 +09:00
ebb48c3f57 enum @notBlank제거 2026-03-07 01:41:34 +09:00
a35b4b8f59 enum @notBlank제거 2026-03-07 01:24:55 +09:00
0a53e186dc enum @notBlank제거 2026-03-07 01:21:30 +09:00
f3c7c5e8e8 enum @notBlank제거 2026-03-07 01:13:28 +09:00
c505e9b740 @enumvalid제거 2026-03-07 01:11:56 +09:00
8c2f2eff1b enum @notBlank제거 2026-03-07 01:08:21 +09:00
ade8bfa76a Merge pull request 'feat/dean/polishing_2' (#146) from feat/dean/polishing_2 into develop
Reviewed-on: #146
2026-03-07 01:02:58 +09:00
3752b83292 인퍼런스실행 정리 2026-03-07 00:56:51 +09:00
9f31f661fc 원복 2026-03-06 22:12:54 +09:00
b477928261 Merge pull request 'testing 테이블 조회 조건 추가' (#145) from feat/infer_dev_260211 into develop
Reviewed-on: #145
2026-03-06 19:51:41 +09:00
f4f75f353c testing 테이블 조회 조건 추가 2026-03-06 19:51:08 +09:00
f977e4be7c Merge pull request 'shp 파일 생성중 오류 발생시 중지되게 수정' (#144) from feat/infer_dev_260211 into develop
Reviewed-on: #144
2026-03-06 19:29:27 +09:00
573da5b53a shp 파일 생성중 오류 발생시 중지되게 수정 2026-03-06 19:21:08 +09:00
bd72852556 Merge pull request '추론실행 오류 수정' (#143) from feat/infer_dev_260211 into develop
Reviewed-on: #143
2026-03-06 18:54:16 +09:00
e4b904606f 추론실행 오류 수정 2026-03-06 18:53:38 +09:00
0d14dafecc Merge pull request 'feat/infer_dev_260211' (#142) from feat/infer_dev_260211 into develop
Reviewed-on: #142
2026-03-06 18:05:00 +09:00
37f534abff spotless 2026-03-06 18:04:38 +09:00
3521a5fd3d trim, foundUnique.add 추가 2026-03-06 18:04:23 +09:00
cbae052338 Merge branch 'develop' into feat/infer_dev_260211
# Conflicts:
#	src/main/java/com/kamco/cd/kamcoback/common/inference/utils/GeoJsonValidator.java
2026-03-06 18:02:44 +09:00
b2c9c36d4c trim, log 추가 2026-03-06 17:58:23 +09:00
114088469e 추론실행 테스트 2026-03-06 17:53:01 +09:00
7d6dca8b24 Merge pull request '추론실행 테스트' (#138) from feat/infer_dev_260211 into develop
Reviewed-on: #138
2026-03-06 17:42:58 +09:00
2e7ad26528 추론실행 테스트 2026-03-06 17:42:40 +09:00
0353e172ed Merge pull request 'feat/infer_dev_260211' (#137) from feat/infer_dev_260211 into develop
Reviewed-on: #137
2026-03-06 17:15:20 +09:00
1d5b1343a9 file del 추가 2026-03-06 17:14:44 +09:00
65f9026922 학습서버배포정보 수정 2026-03-06 16:02:18 +09:00
9b79f31d7b 줄맞춤 2026-03-06 16:01:28 +09:00
de45bf47c5 review-to-geojson 로직 수정, 미사용 주석 추가 2026-03-06 13:16:08 +09:00
a413de4b93 학습데이터 상세 > 폴리곤 수 로직 수정 2026-03-06 13:12:33 +09:00
815675f112 Merge pull request 'inference_results 결과 저장 중복방지' (#136) from feat/infer_dev_260211 into develop
Reviewed-on: #136
2026-03-06 12:43:56 +09:00
b9f7e36175 inference_results 결과 저장 중복방지 2026-03-06 12:43:36 +09:00
855aca6e5a Merge pull request 'feat/infer_dev_260211' (#135) from feat/infer_dev_260211 into develop
Reviewed-on: #135
2026-03-06 12:25:04 +09:00
206dba6ff9 추론결과 저장 result 조회 조건 수정 2026-03-06 12:24:52 +09:00
5db9127f0c 추론결과 저장 result 조회 저장 테스트 2026-03-06 12:23:16 +09:00
132bad8c33 Merge pull request '추론결과 저장 result 조회 저장 테스트' (#134) from feat/infer_dev_260211 into develop
Reviewed-on: #134
2026-03-06 12:00:01 +09:00
6dde4cd891 추론결과 저장 result 조회 저장 테스트 2026-03-06 11:59:38 +09:00
ac248c2f30 Merge pull request '추론결과 저장 result 조회 조건 변경' (#133) from feat/infer_dev_260211 into develop
Reviewed-on: #133
2026-03-06 11:10:04 +09:00
15d082af0e 추론결과 저장 result 조회 조건 변경 2026-03-06 11:09:34 +09:00
3be536424a Merge pull request 'feat/infer_dev_260211' (#132) from feat/infer_dev_260211 into develop
Reviewed-on: #132
2026-03-05 11:33:24 +09:00
a3b2fd0c73 status 체크 후 pnu cnt update 로 수정 2026-03-05 11:32:43 +09:00
9b504396bc status 체크 후 pnu cnt update 추가 2026-03-05 09:39:00 +09:00
2cc490012e Merge pull request 'feat/infer_dev_260211' (#131) from feat/infer_dev_260211 into develop
Reviewed-on: #131
2026-03-04 23:01:14 +09:00
f99144eccc pnu cnt update 수동호출 추가 2026-03-04 23:00:46 +09:00
66b78022a9 변화탐지 selected polygon, point 조건 수정 2026-03-04 22:59:59 +09:00
199e302068 Merge pull request '실태조사 적합여부 값 수정' (#130) from feat/infer_dev_260211 into develop
Reviewed-on: #130
2026-03-04 21:01:52 +09:00
5a3370d5fb 실태조사 적합여부 값 수정 2026-03-04 21:01:21 +09:00
5d7cb18fb8 줄맞춤 2026-03-04 20:05:00 +09:00
57baf4d911 국유인의타입추가 2026-03-04 19:58:02 +09:00
2fee2143ba Merge pull request 'pnu 로 polygon, point 조회 API 수정' (#129) from feat/infer_dev_260211 into develop
Reviewed-on: #129
2026-03-04 17:34:46 +09:00
f4a410ba49 pnu 로 polygon, point 조회 API 수정 2026-03-04 17:34:12 +09:00
deba2b84f0 Merge pull request '추론결과 상세 조회 api 시간 log 추가' (#128) from feat/infer_dev_260211 into develop
Reviewed-on: #128
2026-03-04 17:04:25 +09:00
85d8efc493 추론결과 상세 조회 api 시간 log 추가
- 추론결과 기본정보
- 분류별 탐지건수 조회
- 추론관리 분석결과 geom 상세 목록
2026-03-04 17:03:50 +09:00
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
32d56cf8fe merge develop_add_log 2026-02-26 13:20:11 +09:00
c3b7daebb7 Merge pull request '미사용 쿼리 삭제, 추론 주석 추가, api/inference/download/ 권한 제거 수정' (#104) from feat/infer_dev_260211 into develop
Reviewed-on: #104
2026-02-26 12:36:40 +09:00
2188d426d4 미사용 쿼리 삭제, 추론 주석 추가, api/inference/download/ 권한 제거 수정 2026-02-26 12:36:02 +09:00
5c2ee0974b Merge pull request '추론실행 수정, develop pull 반영, 국유인 파일경로 dir 하드코딩 수정' (#103) from feat/infer_dev_260211 into develop
Reviewed-on: #103
2026-02-26 12:21:06 +09:00
7980fe1d42 InferenceRunService 미사용 삭제 및 추론 실행
추론제외, 이전연도 사용 별로 분기처리
2026-02-26 12:16:48 +09:00
c10141e915 Merge branch 'develop' of https://kamco.git.gs.dabeeo.com/MVPTeam/kamco-cd-api into feat/infer_dev_260211
# Conflicts:
#	src/main/java/com/kamco/cd/kamcoback/gukyuin/service/GukYuinApiService.java
2026-02-26 12:12:50 +09:00
97565c5369 Merge pull request 'feat/dean/test2_temp' (#102) from feat/dean/test2_temp into develop
Reviewed-on: #102
2026-02-26 11:58:48 +09:00
30f0e1a885 merge develop 2026-02-26 11:57:52 +09:00
ba562261c3 /api/inference/download 로그인 없이 접근 가능하도록 수정 2026-02-26 11:56:21 +09:00
a084c80715 Merge remote-tracking branch 'origin/feat/infer_dev_260211' into feat/infer_dev_260211 2026-02-26 11:53:01 +09:00
a44e93c234 추론 실행 수정 및 추석 추가 작업중 2026-02-26 11:52:56 +09:00
a63b81008a inference_hard_coding 2026-02-26 11:52:51 +09:00
2309357c0d 파일경로를 application.yml에서 가져올수있게 동적으로 처리 (#100)
Reviewed-on: #100
Co-authored-by: dean[백병남] <byungnam.baek@dabeeo.com>
Co-committed-by: dean[백병남] <byungnam.baek@dabeeo.com>
2026-02-26 11:49:49 +09:00
7b15e5bb8c Merge branch 'develop' into feat/infer_dev_260211
# Conflicts:
#	src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceRunService.java
2026-02-26 11:36:40 +09:00
001ad73de7 국유인 파일경로 dir 하드코딩 수정 2026-02-26 11:34:02 +09:00
cefacb291b develop pull 2026-02-26 09:54:43 +09:00
744cbb55a9 Merge branch 'develop' of https://kamco.git.gs.dabeeo.com/MVPTeam/kamco-cd-api into feat/infer_dev_260211
# Conflicts:
#	src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceSendDto.java
#	src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceRunService.java
2026-02-26 09:53:11 +09:00
4863091406 추론 run 수정 2026-02-25 22:50:38 +09:00
70c28e0b54 추론 run 수정 2026-02-25 22:40:30 +09:00
9197819340 추론 run 수정 2026-02-25 22:39:49 +09:00
f2500c33e6 Merge remote-tracking branch 'origin/feat/infer_dev_260211' into feat/infer_dev_260211 2026-02-25 22:30:46 +09:00
18dc831b05 추론 run 수정 2026-02-25 22:30:36 +09:00
135 changed files with 4583 additions and 1912 deletions

6
.gitignore vendored
View File

@@ -60,6 +60,7 @@ Thumbs.db
.env.*.local .env.*.local
application-local.yml application-local.yml
application-secret.yml application-secret.yml
metrics-collector/.env
### Docker (local testing) ### ### Docker (local testing) ###
.dockerignore .dockerignore
@@ -72,3 +73,8 @@ docker-compose.override.yml
*.swo *.swo
*~ *~
!/CLAUDE.md !/CLAUDE.md
### Metrics Collector ###
metrics-collector/venv/
metrics-collector/*.pid
metrics-collector/wheels/

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,11 +31,12 @@ 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);
UserDetails userDetails = userDetailsService.loadUserByUsername(username); UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
@@ -58,7 +59,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

@@ -8,10 +8,13 @@ import jakarta.annotation.PostConstruct;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Date; import java.util.Date;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import lombok.extern.log4j.Log4j2;
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
@Log4j2
public class JwtTokenProvider { public class JwtTokenProvider {
@Value("${jwt.secret}") @Value("${jwt.secret}")
@@ -31,11 +34,15 @@ 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) {
log.info("TOKEN VALIDITY = {}", accessTokenValidityInMs);
return createToken(subject, accessTokenValidityInMs); return createToken(subject, accessTokenValidityInMs);
} }
// Refresh Token 생성
public String createRefreshToken(String subject) { public String createRefreshToken(String subject) {
log.info("REFRESH TOKEN VALIDITY = {}", refreshTokenValidityInMs);
return createToken(subject, refreshTokenValidityInMs); return createToken(subject, refreshTokenValidityInMs);
} }

View File

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

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

@@ -117,16 +117,18 @@ public class ChangeDetectionApiController {
@Parameter(description = "도곽/일반(MAPSHEET/ADDRESS) 검색 타입", required = true) @Parameter(description = "도곽/일반(MAPSHEET/ADDRESS) 검색 타입", required = true)
@RequestParam(defaultValue = "MAPSHEET") @RequestParam(defaultValue = "MAPSHEET")
DetectSearchType type, DetectSearchType type,
@Parameter(description = "5k/50k 구분(SCALE_5K/SCALE_50K))", required = true) @Parameter(description = "5k/50k 구분(SCALE_5K/SCALE_50K))")
@RequestParam(defaultValue = "SCALE_50K") @RequestParam(defaultValue = "SCALE_50K", required = false)
MapScaleType scale, MapScaleType scale,
@Parameter( @Parameter(
description = "변화탐지 년도(차수) /year-list 의 uuid", description = "변화탐지 년도(차수) /year-list 의 uuid",
example = "8584e8d4-53b3-4582-bde2-28a81495a626") example = "8584e8d4-53b3-4582-bde2-28a81495a626")
UUID uuid, UUID uuid,
@Parameter(description = "도엽번호", example = "34607") @RequestParam String mapSheetNum) { @Parameter(description = "도엽번호", example = "34607") @RequestParam(required = false)
String mapSheetNum,
@Parameter(description = "pnu") @RequestParam(required = false) String pnu) {
return ApiResponseDto.ok( return ApiResponseDto.ok(
changeDetectionService.getChangeDetectionPolygonList(type, scale, uuid, mapSheetNum)); changeDetectionService.getChangeDetectionPolygonList(type, scale, uuid, mapSheetNum, pnu));
} }
@Operation(summary = "변화탐지 결과 Point", description = "변화탐지 결과 Point") @Operation(summary = "변화탐지 결과 Point", description = "변화탐지 결과 Point")
@@ -135,16 +137,18 @@ public class ChangeDetectionApiController {
@Parameter(description = "도곽/일반(MAPSHEET/ADDRESS) 검색 타입", required = true) @Parameter(description = "도곽/일반(MAPSHEET/ADDRESS) 검색 타입", required = true)
@RequestParam(defaultValue = "MAPSHEET") @RequestParam(defaultValue = "MAPSHEET")
DetectSearchType type, DetectSearchType type,
@Parameter(description = "5k/50k 구분(SCALE_5K/SCALE_50K))", required = true) @Parameter(description = "5k/50k 구분(SCALE_5K/SCALE_50K))")
@RequestParam(defaultValue = "SCALE_50K") @RequestParam(defaultValue = "SCALE_50K", required = false)
MapScaleType scale, MapScaleType scale,
@Parameter( @Parameter(
description = "변화탐지 년도(차수) /year-list 의 uuid", description = "변화탐지 년도(차수) /year-list 의 uuid",
example = "8584e8d4-53b3-4582-bde2-28a81495a626") example = "8584e8d4-53b3-4582-bde2-28a81495a626")
UUID uuid, UUID uuid,
@Parameter(description = "도엽번호", example = "34607") @RequestParam String mapSheetNum) { @Parameter(description = "도엽번호", example = "34607") @RequestParam(required = false)
String mapSheetNum,
@Parameter(description = "pnu") @RequestParam(required = false) String pnu) {
return ApiResponseDto.ok( return ApiResponseDto.ok(
changeDetectionService.getChangeDetectionPointList(type, scale, uuid, mapSheetNum)); changeDetectionService.getChangeDetectionPointList(type, scale, uuid, mapSheetNum, pnu));
} }
@Operation(summary = "선택 변화탐지 결과 uuid 조회", description = "선택 변화탐지 결과 uuid 조회") @Operation(summary = "선택 변화탐지 결과 uuid 조회", description = "선택 변화탐지 결과 uuid 조회")
@@ -163,13 +167,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 +187,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;
@@ -32,6 +35,11 @@ public class ChangeDetectionDto {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return name();
}
} }
@CodeExpose @CodeExpose
@@ -52,6 +60,11 @@ public class ChangeDetectionDto {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return desc;
}
} }
@Schema(name = "TestDto", description = "테스트용") @Schema(name = "TestDto", description = "테스트용")
@@ -143,6 +156,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 +292,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

@@ -46,13 +46,13 @@ public class ChangeDetectionService {
} }
public ChangeDetectionDto.PolygonFeatureList getChangeDetectionPolygonList( public ChangeDetectionDto.PolygonFeatureList getChangeDetectionPolygonList(
DetectSearchType type, MapScaleType scale, UUID uuid, String mapSheetNum) { DetectSearchType type, MapScaleType scale, UUID uuid, String mapSheetNum, String pnu) {
switch (type) { switch (type) {
case MAPSHEET -> { case MAPSHEET -> {
return changeDetectionCoreService.getChangeDetectionPolygonList(scale, uuid, mapSheetNum); return changeDetectionCoreService.getChangeDetectionPolygonList(scale, uuid, mapSheetNum);
} }
case ADDRESS -> { case ADDRESS -> {
return new ChangeDetectionDto.PolygonFeatureList(); // TODO: 일반 주소 검색 로직 확인 후 작업 필요 return changeDetectionCoreService.getChangeDetectionPnuPolygonList(uuid, pnu);
} }
default -> throw new IllegalArgumentException("Unsupported type: " + type); default -> throw new IllegalArgumentException("Unsupported type: " + type);
} }
@@ -60,14 +60,14 @@ public class ChangeDetectionService {
// Geometry 객체 순환 참조 문제로 캐싱 불가 // Geometry 객체 순환 참조 문제로 캐싱 불가
public ChangeDetectionDto.PointFeatureList getChangeDetectionPointList( public ChangeDetectionDto.PointFeatureList getChangeDetectionPointList(
DetectSearchType type, MapScaleType scale, UUID uuid, String mapSheetNum) { DetectSearchType type, MapScaleType scale, UUID uuid, String mapSheetNum, String pnu) {
switch (type) { switch (type) {
case MAPSHEET -> { case MAPSHEET -> {
return changeDetectionCoreService.getChangeDetectionPointList(scale, uuid, mapSheetNum); return changeDetectionCoreService.getChangeDetectionPointList(scale, uuid, mapSheetNum);
} }
case ADDRESS -> { case ADDRESS -> {
return new ChangeDetectionDto.PointFeatureList(); // TODO: 일반 주소 검색 로직 확인 후 작업 필요 return changeDetectionCoreService.getChangeDetectionPnuPointList(uuid, pnu);
} }
default -> throw new IllegalArgumentException("Unsupported type: " + type); default -> throw new IllegalArgumentException("Unsupported type: " + type);
} }
@@ -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

@@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.code.dto;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
import com.kamco.cd.kamcoback.common.utils.html.HtmlEscapeDeserializer; import com.kamco.cd.kamcoback.common.utils.html.HtmlEscapeDeserializer;
import com.kamco.cd.kamcoback.common.utils.html.HtmlUnescapeSerializer; import com.kamco.cd.kamcoback.common.utils.html.HtmlUnescapeSerializer;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
@@ -119,10 +120,11 @@ public class CommonCodeDto {
String props2, String props2,
String props3, String props3,
ZonedDateTime deletedDttm) { ZonedDateTime deletedDttm) {
boolean english = HeaderUtil.isEnglishRequest();
this.id = id; this.id = id;
this.code = code; this.code = code;
this.description = description; this.description = description;
this.name = name; this.name = english ? code : name;
this.order = order; this.order = order;
this.used = used; this.used = used;
this.deleted = deleted; this.deleted = deleted;

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

@@ -45,4 +45,9 @@ public enum CommonUseStatus implements EnumType {
public EnumDto<CommonUseStatus> getEnumDto() { public EnumDto<CommonUseStatus> getEnumDto() {
return new EnumDto<>(this, this.id, this.text); return new EnumDto<>(this, this.id, this.text);
} }
@Override
public String getTextEn() {
return id;
}
} }

View File

@@ -9,11 +9,13 @@ import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum CrsType implements EnumType { public enum CrsType implements EnumType {
EPSG_3857("Web Mercator, 웹지도 미터(EPSG:900913 동일)"), EPSG_3857("Web Mercator, 웹지도 미터(EPSG:900913 동일)", "Web Mercator"),
EPSG_4326("WGS84 위경도, GeoJSON/OSM 기본"), EPSG_4326("WGS84 위경도, GeoJSON/OSM 기본", "GeoJSON/OSM"),
EPSG_5186("Korea 2000 중부 TM, 한국 SHP"); EPSG_5186("5186::Korea 2000 중부 TM, 한국 SHP", "5186::Korea 2000"),
EPSG_5179("5179::Korea 2000 중부 TM, 한국 SHP", "5179::Korea 2000");
private final String desc; private final String desc;
private final String descEn;
@Override @Override
public String getId() { public String getId() {
@@ -24,4 +26,9 @@ public enum CrsType implements EnumType {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return descEn;
}
} }

View File

@@ -1,5 +1,6 @@
package com.kamco.cd.kamcoback.common.enums; package com.kamco.cd.kamcoback.common.enums;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@@ -51,6 +52,6 @@ public enum DetectionClassification {
*/ */
public static String fromStrDesc(String text) { public static String fromStrDesc(String text) {
DetectionClassification dtf = fromString(text); DetectionClassification dtf = fromString(text);
return dtf.getDesc(); return HeaderUtil.isEnglishRequest() ? dtf.getId() : dtf.getDesc();
} }
} }

View File

@@ -9,13 +9,14 @@ import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum FileUploadStatus implements EnumType { public enum FileUploadStatus implements EnumType {
INIT("초기화"), INIT("초기화", "Init"),
UPLOADING("업로드중"), UPLOADING("업로드중", "Uploading"),
DONE("업로드완료"), DONE("업로드완료", "Upload Done"),
MERGED("병합완료"), MERGED("병합완료", "Merged Done"),
MERGE_FAIL("병합 실패"); MERGE_FAIL("병합 실패", "Merge Failed");
private final String desc; private final String desc;
private final String descEn;
@Override @Override
public String getId() { public String getId() {
@@ -26,4 +27,9 @@ public enum FileUploadStatus implements EnumType {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return descEn;
}
} }

View File

@@ -1,5 +1,6 @@
package com.kamco.cd.kamcoback.common.enums; package com.kamco.cd.kamcoback.common.enums;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
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 java.util.Arrays; import java.util.Arrays;
@@ -25,6 +26,11 @@ public enum ImageryFitStatus implements EnumType {
return desc; return desc;
} }
@Override
public String getTextEn() {
return name();
}
public static ImageryFitStatus fromCode(String code) { public static ImageryFitStatus fromCode(String code) {
if (code == null) { if (code == null) {
return null; return null;
@@ -38,6 +44,8 @@ public enum ImageryFitStatus implements EnumType {
public static String getDescByCode(String code) { public static String getDescByCode(String code) {
ImageryFitStatus status = fromCode(code); ImageryFitStatus status = fromCode(code);
return status != null ? status.getDesc() : null; return status != null
? (HeaderUtil.isEnglishRequest() ? status.getTextEn() : status.getDesc())
: null;
} }
} }

View File

@@ -10,12 +10,15 @@ import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum LayerType implements EnumType { public enum LayerType implements EnumType {
TILE("배경지도"), TILE("배경지도", "Tile"),
GEOJSON("객체데이터"), GEOJSON("객체데이터", "GeoJSON"),
WMTS("타일레이어"), WMTS("타일레이어", "WMTS"),
WMS("지적도"); WMS("지적도", "WMS"),
KAMCO_WMS("국유인WMS", "KAMCO_WMS"),
KAMCO_WMTS("국유인WMTS", "KAMCO_WMTS");
private final String desc; private final String desc;
private final String descEn;
@Override @Override
public String getId() { public String getId() {
@@ -27,6 +30,11 @@ public enum LayerType implements EnumType {
return desc; return desc;
} }
@Override
public String getTextEn() {
return descEn;
}
public static Optional<LayerType> from(String type) { public static Optional<LayerType> from(String type) {
try { try {
return Optional.of(LayerType.valueOf(type)); return Optional.of(LayerType.valueOf(type));

View File

@@ -9,12 +9,13 @@ import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum MngStateType implements EnumType { public enum MngStateType implements EnumType {
NOTYET("동기화 시작"), NOTYET("동기화 시작", "Sync Started"),
PROCESSING("데이터 체크"), PROCESSING("데이터 체크", "Data Check"),
DONE("동기화 작업 종료"), DONE("동기화 작업 종료", "Sync Completed"),
TAKINGERROR("오류 데이터 처리중"); TAKINGERROR("오류 데이터 처리중", "Processing Error Data");
private final String desc; private final String desc;
private final String descEn;
@Override @Override
public String getId() { public String getId() {
@@ -25,4 +26,8 @@ public enum MngStateType implements EnumType {
public String getText() { public String getText() {
return desc; return desc;
} }
public String getTextEn() {
return descEn;
}
} }

View File

@@ -24,4 +24,9 @@ public enum RoleType implements EnumType {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return name();
}
} }

View File

@@ -24,4 +24,9 @@ public enum StatusType implements EnumType {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return name();
}
} }

View File

@@ -23,4 +23,9 @@ public enum SyncCheckStateType implements EnumType {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return name();
}
} }

View File

@@ -30,4 +30,9 @@ public enum SyncStateType implements EnumType {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return name();
}
} }

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,274 @@
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.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
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().trim()
: 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

@@ -1,23 +1,45 @@
package com.kamco.cd.kamcoback.common.service; package com.kamco.cd.kamcoback.common.service;
import com.kamco.cd.kamcoback.config.InferenceProperties;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Log4j2 @Log4j2
// 0312
@RequiredArgsConstructor
@Component @Component
public class ExternalJarRunner { public class ExternalJarRunner {
@Value("${spring.profiles.active}") @Value("${spring.profiles.active}")
private String profile; private String profile;
// 0312
private final InferenceProperties inferenceProperties;
private static final long TIMEOUT_MINUTES = TimeUnit.DAYS.toMinutes(3); private static final long TIMEOUT_MINUTES = TimeUnit.DAYS.toMinutes(3);
// java -jar build/libs/shp-exporter.jar --batch --geoserver.enabled=true
// --converter.inference-id=qq99999 --converter.batch-ids[0]=111
// 0312 shp 파일 배치를 통해 생성
public void run(String inferenceLearningId, List<Long> batchIds) {
// JAR 경로 (shape파일 생성용)
String jarPathV2 = inferenceProperties.getJarPathV2();
List<String> args = new ArrayList<>();
args.add(" --spring.profiles.active=" + profile);
args.add(" --batch");
args.add(" --geoserver.enabled=true");
args.add(" --converter.inference-id=" + inferenceLearningId);
batchIds.forEach(batchId -> args.add(" --converter.batch-ids[" + args.size() + "]=" + batchId));
execJar(jarPathV2, args);
}
/** /**
* shp 파일 생성 * shp 파일 생성
* *
@@ -28,7 +50,8 @@ public class ExternalJarRunner {
* @param mode * @param mode
* <p>MERGED - batch-ids 에 해당하는 **모든 데이터를 하나의 Shapefile로 병합 생성, * <p>MERGED - batch-ids 에 해당하는 **모든 데이터를 하나의 Shapefile로 병합 생성,
* <p>MAP_IDS - 명시적으로 전달한 map-ids만 대상으로 Shapefile 생성, * <p>MAP_IDS - 명시적으로 전달한 map-ids만 대상으로 Shapefile 생성,
* <p>RESOLVE - batch-ids 기준으로 **JAR 내부에서 map_ids를 조회**한 뒤 Shapefile 생성 * <p>RESOLVE - batch-ids 기준으로 **JAR 내부에서 map_ids를 조회**한 뒤 Shapefile 생성 java -jar
* build/libs/shp-exporter.jar --spring.profiles.active=prod
*/ */
public void run(String jarPath, String batchIds, String inferenceId, String mapIds, String mode) { public void run(String jarPath, String batchIds, String inferenceId, String mapIds, String mode) {
List<String> args = new ArrayList<>(); List<String> args = new ArrayList<>();
@@ -73,7 +96,8 @@ public class ExternalJarRunner {
cmd.add("-jar"); cmd.add("-jar");
cmd.add(jarPath); cmd.add(jarPath);
cmd.addAll(args); cmd.addAll(args);
// 0312
log.info("exec jar command: {}", cmd);
ProcessBuilder pb = new ProcessBuilder(cmd); ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(true); pb.redirectErrorStream(true);
@@ -103,6 +127,7 @@ public class ExternalJarRunner {
} catch (Exception e) { } catch (Exception e) {
log.error("jar execution error. output=\n{}", out, e); log.error("jar execution error. output=\n{}", out, e);
throw new RuntimeException("jar execution error\n" + out, e);
} }
} }

View File

@@ -279,8 +279,9 @@ public class FIleChecker {
return true; return true;
} }
//kamco-nfs를 확인하는곳이 있어서 파라미터 추가 사용용도확인후 처리 // kamco-nfs를 확인하는곳이 있어서 파라미터 추가 사용용도확인후 처리
public static List<Folder> getFolderAll(String dirPath, String sortType, int maxDepth,String nfsRootDir) { public static List<Folder> getFolderAll(
String dirPath, String sortType, int maxDepth, String nfsRootDir) {
Path startPath = Paths.get(dirPath); Path startPath = Paths.get(dirPath);
@@ -309,9 +310,10 @@ public class FIleChecker {
String parentPath = path.getParent().toString(); String parentPath = path.getParent().toString();
String fullPath = path.toAbsolutePath().toString(); String fullPath = path.toAbsolutePath().toString();
//이것이 필요한건가? // 이것이 필요한건가?
// boolean isShowHide = // boolean isShowHide =
// !parentFolderNm.equals("kamco-nfs"); // 폴더 리스트에 kamco-nfs 하위만 나오도록 처리 // !parentFolderNm.equals("kamco-nfs"); // 폴더 리스트에
// kamco-nfs 하위만 나오도록 처리
boolean isShowHide = boolean isShowHide =
!parentFolderNm.equals(nfsRootDir); // 폴더 리스트에 nfsRootDir 하위만 나오도록 처리 !parentFolderNm.equals(nfsRootDir); // 폴더 리스트에 nfsRootDir 하위만 나오도록 처리
File file = new File(fullPath); File file = new File(fullPath);
@@ -361,8 +363,8 @@ public class FIleChecker {
return folderList; return folderList;
} }
public static List<Folder> getFolderAll(String dirPath,String nfsRootDir) { public static List<Folder> getFolderAll(String dirPath, String nfsRootDir) {
return getFolderAll(dirPath, "name", 1,nfsRootDir); return getFolderAll(dirPath, "name", 1, nfsRootDir);
} }
public static int getChildFolderCount(File directory) { public static int getChildFolderCount(File directory) {

View File

@@ -1,6 +1,9 @@
package com.kamco.cd.kamcoback.common.utils; package com.kamco.cd.kamcoback.common.utils;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
public final class HeaderUtil { public final class HeaderUtil {
@@ -20,4 +23,20 @@ public final class HeaderUtil {
public static String getRequired(HttpServletRequest request, String headerName) { public static String getRequired(HttpServletRequest request, String headerName) {
return get(request, headerName); return get(request, headerName);
} }
public static boolean isEnglishRequest() {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
if (!(attrs instanceof ServletRequestAttributes servletAttrs)) {
return false;
}
String acceptLanguage = servletAttrs.getRequest().getHeader("Accept-Language");
if (acceptLanguage == null || acceptLanguage.isBlank()) {
return false;
}
return acceptLanguage.toLowerCase().startsWith("en");
}
} }

View File

@@ -5,4 +5,6 @@ public interface EnumType {
String getId(); String getId();
String getText(); String getText();
String getTextEn();
} }

View File

@@ -1,6 +1,7 @@
package com.kamco.cd.kamcoback.common.utils.enums; package com.kamco.cd.kamcoback.common.utils.enums;
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.CodeDto; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.CodeDto;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -32,11 +33,12 @@ public class Enums {
// enum -> CodeDto list // enum -> CodeDto list
public static List<CodeDto> toList(Class<? extends Enum<?>> enumClass) { public static List<CodeDto> toList(Class<? extends Enum<?>> enumClass) {
Object[] enums = enumClass.getEnumConstants(); Object[] enums = enumClass.getEnumConstants();
boolean english = HeaderUtil.isEnglishRequest();
return Arrays.stream(enums) return Arrays.stream(enums)
.map(e -> (EnumType) e) .map(e -> (EnumType) e)
.filter(e -> !isHidden(enumClass, (Enum<?>) e)) .filter(e -> !isHidden(enumClass, (Enum<?>) e))
.map(e -> new CodeDto(e.getId(), e.getText())) .map(e -> new CodeDto(e.getId(), english ? e.getTextEn() : e.getText()))
.toList(); .toList();
} }

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

@@ -0,0 +1,35 @@
package com.kamco.cd.kamcoback.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "file")
public class FileProperties {
private String root;
private String nfs;
private String syncRootDir;
private String syncTmpDir;
private String syncFileExtention;
private String datasetDir;
private String datasetTmpDir;
private String modelDir;
private String modelTmpDir;
private String modelFileExtention;
private String ptPath;
private String datasetResponse;
private TrainingData trainingData;
private String outputDir;
@Getter
@Setter
public static class TrainingData {
private String geojsonDir;
}
}

View File

@@ -0,0 +1,22 @@
package com.kamco.cd.kamcoback.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "inference")
public class InferenceProperties {
private String nfs;
private String url;
private String batchUrl;
private String geojsonDir;
private String jarPath;
// 0312
private String jarPathV2;
private String inferenceServerName;
}

View File

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

@@ -16,6 +16,8 @@ public class StartupLogger {
private final Environment environment; private final Environment environment;
private final DataSource dataSource; private final DataSource dataSource;
private final FileProperties fileProperties;
private final InferenceProperties inferenceProperties;
@EventListener(ApplicationReadyEvent.class) @EventListener(ApplicationReadyEvent.class)
public void logStartupInfo() { public void logStartupInfo() {
@@ -79,6 +81,26 @@ public class StartupLogger {
│ DDL Auto : %s │ DDL Auto : %s
│ JDBC Batch Size : %s │ JDBC Batch Size : %s
│ Fetch Batch Size : %s │ Fetch Batch Size : %s
╠════════════════════════════════════════════════════════════════════════════════╣
║ FILE CONFIGURATION ║
╠────────────────────────────────────────────────────────────────────────────────╣
│ Root Directory : %s
│ NFS Mount Path : %s
│ Sync Root Dir : %s
│ Sync Tmp Dir : %s
│ Dataset Dir : %s
│ Model Dir : %s
│ PT Path : %s
│ Output Dir : %s
╠════════════════════════════════════════════════════════════════════════════════╣
║ INFERENCE CONFIGURATION ║
╠────────────────────────────────────────────────────────────────────────────────╣
│ NFS Mount Path : %s
│ Inference URL : %s
│ Batch URL : %s
│ GeoJSON Dir : %s
│ JAR Path : %s
│ Server Names : %s
╚════════════════════════════════════════════════════════════════════════════════╝ ╚════════════════════════════════════════════════════════════════════════════════╝
""", """,
profileInfo, profileInfo,
@@ -89,7 +111,25 @@ public class StartupLogger {
showSql, showSql,
ddlAuto, ddlAuto,
batchSize, batchSize,
batchFetchSize); batchFetchSize,
fileProperties.getRoot() != null ? fileProperties.getRoot() : "N/A",
fileProperties.getNfs() != null ? fileProperties.getNfs() : "N/A",
fileProperties.getSyncRootDir() != null ? fileProperties.getSyncRootDir() : "N/A",
fileProperties.getSyncTmpDir() != null ? fileProperties.getSyncTmpDir() : "N/A",
fileProperties.getDatasetDir() != null ? fileProperties.getDatasetDir() : "N/A",
fileProperties.getModelDir() != null ? fileProperties.getModelDir() : "N/A",
fileProperties.getPtPath() != null ? fileProperties.getPtPath() : "N/A",
fileProperties.getOutputDir() != null ? fileProperties.getOutputDir() : "N/A",
inferenceProperties.getNfs() != null ? inferenceProperties.getNfs() : "N/A",
inferenceProperties.getUrl() != null ? inferenceProperties.getUrl() : "N/A",
inferenceProperties.getBatchUrl() != null ? inferenceProperties.getBatchUrl() : "N/A",
inferenceProperties.getGeojsonDir() != null
? inferenceProperties.getGeojsonDir()
: "N/A",
inferenceProperties.getJarPath() != null ? inferenceProperties.getJarPath() : "N/A",
inferenceProperties.getInferenceServerName() != null
? inferenceProperties.getInferenceServerName()
: "N/A");
log.info(startupMessage); log.info(startupMessage);
} }

View File

@@ -39,10 +39,10 @@ public class ApiLogFunction {
public static String getXFowardedForIp(HttpServletRequest request) { public static String getXFowardedForIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For"); String ip = request.getHeader("X-Forwarded-For");
if (ip != null) { if (ip != null && !ip.isBlank()) {
ip = ip.split(",")[0].trim(); return ip.split(",")[0].trim();
} }
return ip; return request.getRemoteAddr();
} }
// 사용자 ID 추출 예시 (Spring Security 기준) // 사용자 ID 추출 예시 (Spring Security 기준)

View File

@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.config.api;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.kamco.cd.kamcoback.common.utils.enums.EnumType; import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
import java.util.Arrays;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.ToString; import lombok.ToString;
@@ -173,8 +174,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("도엽 설정파일 생성 중 오류가 발생했습니다."),
; ;
@@ -191,6 +193,11 @@ public class ApiResponseDto<T> {
return message; return message;
} }
@Override
public String getTextEn() {
return message;
}
public static ApiResponseCode getCode(String name) { public static ApiResponseCode getCode(String name) {
return ApiResponseCode.valueOf(name.toUpperCase()); return ApiResponseCode.valueOf(name.toUpperCase());
} }
@@ -219,5 +226,12 @@ public class ApiResponseDto<T> {
return INTERNAL_SERVER_ERROR; return INTERNAL_SERVER_ERROR;
} }
public static ApiResponseCode fromMessage(String message) {
return Arrays.stream(values())
.filter(code -> code.getMessage().equals(message))
.findFirst()
.orElse(null);
}
} }
} }

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

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

View File

@@ -26,4 +26,9 @@ public enum GukYuinStatus implements EnumType {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return desc;
}
} }

View File

@@ -69,9 +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(
@@ -235,9 +237,12 @@ public class GukYuinApiService {
GukYuinLinkFailCode code = decideCode(f); GukYuinLinkFailCode code = decideCode(f);
GukYuinLinkableRes res = new GukYuinLinkableRes(); GukYuinLinkableRes res = new GukYuinLinkableRes();
// res.setCode(code); res.setCode(code);
res.setLinkable(code == GukYuinLinkFailCode.OK); res.setLinkable(code == GukYuinLinkFailCode.OK);
res.setMessage(code.getDesc()); res.setMessage(code.getDesc());
if (code == GukYuinLinkFailCode.HAS_RUNNING_INFERENCE) {
res.setInferenceUuid(f.inferenceUuid());
}
return res; return res;
} }
@@ -247,9 +252,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;
@@ -279,6 +284,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,
@@ -287,6 +293,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(
@@ -346,6 +353,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,
@@ -353,6 +361,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(),
@@ -406,10 +415,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(),
@@ -457,9 +468,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 = String.format("%s%s", nfs, "/dataset/export/"); log.info(
if (!Files.isDirectory(Path.of(kamconfsDatasetExportPathfsDatasetExportPath + info.getUid()))) { "═════════════════════════════════[s] datasetDir : 추론 shp파일 생성되는 위치 ══════════════════════════════════");
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 +484,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 +571,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 +610,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

@@ -8,6 +8,7 @@ import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceServerStatusDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.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.ResultList; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.ResultList;
import com.kamco.cd.kamcoback.inference.service.InferenceAsyncService;
import com.kamco.cd.kamcoback.inference.service.InferenceResultService; import com.kamco.cd.kamcoback.inference.service.InferenceResultService;
import com.kamco.cd.kamcoback.log.dto.AuditLogDto; import com.kamco.cd.kamcoback.log.dto.AuditLogDto;
import com.kamco.cd.kamcoback.log.dto.AuditLogDto.DownloadReq; import com.kamco.cd.kamcoback.log.dto.AuditLogDto.DownloadReq;
@@ -18,7 +19,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 +28,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 +37,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;
@@ -54,11 +56,13 @@ import org.springframework.web.bind.annotation.RestController;
public class InferenceResultApiController { public class InferenceResultApiController {
private final InferenceResultService inferenceResultService; private final InferenceResultService inferenceResultService;
private final InferenceAsyncService inferenceAsyncService;
private final MapSheetMngService mapSheetMngService; private final MapSheetMngService mapSheetMngService;
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 +94,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 +117,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 +136,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 +162,8 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(uuid); return ApiResponseDto.ok(uuid);
} }
@Operation(summary = "추론 종료", description = "추론 종료") /** 추론진행 현황 화면에서 호출 */
@Operation(summary = "추론 종료", description = "추론관리 > 추론목록 > 추론진행 현황")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -170,11 +178,13 @@ public class InferenceResultApiController {
}) })
@DeleteMapping("/end") @DeleteMapping("/end")
public ApiResponseDto<UUID> getInferenceGeomList() { public ApiResponseDto<UUID> getInferenceGeomList() {
UUID uuid = inferenceResultService.deleteInferenceEnd(); // UUID uuid = inferenceResultService.deleteInferenceEnd();
UUID uuid = inferenceAsyncService.asyncInferenceEnd();
return ApiResponseDto.ok(uuid); return ApiResponseDto.ok(uuid);
} }
@Operation(summary = "분석 모델 선택 조회", description = "변화탐지 실행 정보 입력 모델선택 팝업 ") /** 변화탐지 실행 정보 입력화면에서 호출 */
@Operation(summary = "분석 모델 선택 조회", description = "추론관리 > 추론목록 > 변화탐지 실행 정보 입력 > 모델선택 팝업 ")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
@@ -205,7 +215,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 +235,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 +260,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 +282,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 +304,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 +344,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 +360,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 +378,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 +428,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",
@@ -438,4 +448,22 @@ public class InferenceResultApiController {
UUID uuid) { UUID uuid) {
return ApiResponseDto.ok(inferenceResultService.getInferenceRunMapId(uuid)); return ApiResponseDto.ok(inferenceResultService.getInferenceRunMapId(uuid));
} }
@Operation(summary = "추론 삭제", description = "추론관리 > 추론목록 > 추론 상세 > 추론 삭제")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "종료 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Page.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@DeleteMapping("/delete-inference/{uuid}")
public ApiResponseDto<ApiResponseDto.ResponseObj> deleteInference(@PathVariable UUID uuid) {
return ApiResponseDto.ok(inferenceResultService.deleteInference(uuid));
}
} }

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

@@ -1,57 +0,0 @@
package com.kamco.cd.kamcoback.inference;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.inference.service.InferenceRunService;
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.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "추론 실행", description = "추론 실행")
@Log4j2
@RequestMapping("/api/inference/run")
@RequiredArgsConstructor
@RestController
public class InferenceRunController {
private final InferenceRunService inferenceRunService;
@Operation(summary = "추론 진행 여부 확인", description = "어드민 홈 > 추론관리 > 추론관리 > 추론관리 목록")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
schema =
@Schema(
description = "진행 여부 (UUID 있으면 진행중)",
type = "UUID",
example = "44709877-2e27-4fc5-bacb-8e0328c69b64"))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping
public ApiResponseDto<Void> getProcessing(
@Parameter(description = "비교년도", example = "2021") @RequestParam(required = false)
Integer compareYear,
@Parameter(description = "기준년도", example = "2022") @RequestParam(required = false)
Integer targetYear,
@Parameter(description = "모델 uuid") @RequestParam(required = false) UUID modelUuid) {
inferenceRunService.run(compareYear, targetYear, modelUuid);
return ApiResponseDto.ok(null);
}
}

View File

@@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.common.enums.DetectionClassification; import com.kamco.cd.kamcoback.common.enums.DetectionClassification;
import com.kamco.cd.kamcoback.common.enums.ImageryFitStatus; import com.kamco.cd.kamcoback.common.enums.ImageryFitStatus;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.DetectOption; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.DetectOption;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetScope; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetScope;
@@ -156,9 +157,14 @@ public class InferenceDetailDto {
String classAfterName; String classAfterName;
Long classAfterCnt; Long classAfterCnt;
boolean english = HeaderUtil.isEnglishRequest();
public Dashboard(String classAfterCd, Long classAfterCnt) { public Dashboard(String classAfterCd, Long classAfterCnt) {
this.classAfterCd = classAfterCd; this.classAfterCd = classAfterCd;
this.classAfterName = DetectionClassification.fromString(classAfterCd).getDesc(); this.classAfterName =
english
? DetectionClassification.fromString(classAfterCd).getId()
: DetectionClassification.fromString(classAfterCd).getDesc();
this.classAfterCnt = classAfterCnt; this.classAfterCnt = classAfterCnt;
} }
} }
@@ -294,8 +300,10 @@ public class InferenceDetailDto {
} }
@Getter @Getter
@Setter
public static class Geom { public static class Geom {
Long geoUid;
UUID uuid; UUID uuid;
String uid; String uid;
Integer compareYyyy; Integer compareYyyy;
@@ -313,7 +321,10 @@ public class InferenceDetailDto {
String pnu; String pnu;
String fitState; String fitState;
boolean english = HeaderUtil.isEnglishRequest();
public Geom( public Geom(
Long geoUid,
UUID uuid, UUID uuid,
String uid, String uid,
Integer compareYyyy, Integer compareYyyy,
@@ -328,16 +339,23 @@ public class InferenceDetailDto {
String subUid, String subUid,
String pnu, String pnu,
String fitState) { String fitState) {
this.geoUid = geoUid;
this.uuid = uuid; this.uuid = uuid;
this.uid = uid; this.uid = uid;
this.compareYyyy = compareYyyy; this.compareYyyy = compareYyyy;
this.targetYyyy = targetYyyy; this.targetYyyy = targetYyyy;
this.cdProb = cdProb; this.cdProb = cdProb;
this.classBeforeCd = classBeforeCd; this.classBeforeCd = classBeforeCd;
this.classBeforeName = DetectionClassification.fromString(classBeforeCd).getDesc(); this.classBeforeName =
english
? DetectionClassification.fromString(classBeforeCd).getId()
: DetectionClassification.fromString(classBeforeCd).getDesc();
this.classBeforeProb = classBeforeProb; this.classBeforeProb = classBeforeProb;
this.classAfterCd = classAfterCd; this.classAfterCd = classAfterCd;
this.classAfterName = DetectionClassification.fromString(classAfterCd).getDesc(); this.classAfterName =
english
? DetectionClassification.fromString(classAfterCd).getId()
: DetectionClassification.fromString(classAfterCd).getDesc();
this.classAfterProb = classAfterProb; this.classAfterProb = classAfterProb;
this.mapSheetNum = mapSheetNum; this.mapSheetNum = mapSheetNum;
this.mapSheetName = mapSheetName; this.mapSheetName = mapSheetName;
@@ -458,6 +476,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 +493,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 +509,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 +559,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

@@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.inference.dto;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
import com.kamco.cd.kamcoback.common.utils.enums.EnumType; import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
import com.kamco.cd.kamcoback.common.utils.interfaces.EnumValid; import com.kamco.cd.kamcoback.common.utils.interfaces.EnumValid;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
@@ -14,10 +15,13 @@ import java.time.ZonedDateTime;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.ToString;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -50,6 +54,11 @@ public class InferenceResultDto {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return desc;
}
} }
/** 탐지 데이터 옵션 dto */ /** 탐지 데이터 옵션 dto */
@@ -78,6 +87,11 @@ public class InferenceResultDto {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return desc;
}
} }
@Getter @Getter
@@ -86,6 +100,7 @@ public class InferenceResultDto {
READY("대기"), READY("대기"),
IN_PROGRESS("진행중"), IN_PROGRESS("진행중"),
END("완료"), END("완료"),
END_FAIL("종료실패"),
FORCED_END("강제종료"); FORCED_END("강제종료");
private final String desc; private final String desc;
@@ -94,8 +109,13 @@ public class InferenceResultDto {
} }
public static String getDescByCode(String code) { public static String getDescByCode(String code) {
boolean english = HeaderUtil.isEnglishRequest();
if (english) {
return fromCode(code).getTextEn();
} else {
return fromCode(code).getDesc(); return fromCode(code).getDesc();
} }
}
@Override @Override
public String getId() { public String getId() {
@@ -106,6 +126,11 @@ public class InferenceResultDto {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return name();
}
} }
@Getter @Getter
@@ -127,6 +152,11 @@ public class InferenceResultDto {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return desc;
}
} }
/** 목록조회 dto */ /** 목록조회 dto */
@@ -209,10 +239,49 @@ public class InferenceResultDto {
long m = (s % 3600) / 60; long m = (s % 3600) / 60;
long sec = s % 60; long sec = s % 60;
boolean english = HeaderUtil.isEnglishRequest();
if (english) {
return String.format("%dh %dm %ds", h, m, sec);
} else {
return String.format("%d시간 %d분 %d초", h, m, sec); return String.format("%d시간 %d분 %d초", h, m, sec);
} }
} }
@JsonProperty("mapSheetCnt")
public String getMapSheetCnt() {
boolean english = HeaderUtil.isEnglishRequest();
String text = this.mapSheetCnt;
if (!english) {
return text;
}
// "창원 외 34건"
Pattern otherPattern = Pattern.compile("^(.+) 외 (\\d+)건$");
Matcher otherMatcher = otherPattern.matcher(text);
if (otherMatcher.find()) {
String name = otherMatcher.group(1);
int count = Integer.parseInt(otherMatcher.group(2));
return name + " and " + count + (count == 1 ? " other" : " others");
}
// "창원 1건"
Pattern itemPattern = Pattern.compile("^(.+) (\\d+)건$");
Matcher itemMatcher = itemPattern.matcher(text);
if (itemMatcher.find()) {
String name = itemMatcher.group(1);
int count = Integer.parseInt(itemMatcher.group(2));
return name + " (" + count + (count == 1 ? " item" : " items") + ")";
}
return text;
}
}
/** 목록조회 검색 조건 dto */ /** 목록조회 검색 조건 dto */
@Getter @Getter
@Setter @Setter
@@ -240,21 +309,22 @@ public class InferenceResultDto {
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@ToString
public static class RegReq { public static class RegReq {
@Schema(description = "제목", example = "2023-2024 변화탐지 테스트") @Schema(description = "제목", example = "2023-2024 변화탐지 테스트")
@NotBlank @NotBlank
private String title; private String title;
@Schema(description = "G1", example = "b40e0f68-c1d8-49fc-93f9-a36270093861") @Schema(description = "G1", example = "643adead-f3d2-4f10-9037-862bee919399")
@NotNull @NotNull
private UUID model1Uuid; private UUID model1Uuid;
@Schema(description = "G2", example = "ec92b7d2-b5a3-4915-9bdf-35fb3ca8ad27") @Schema(description = "G2", example = "dd86b4ef-28e3-4e3d-9ee4-f60d9cb54e13")
@NotNull @NotNull
private UUID model2Uuid; private UUID model2Uuid;
@Schema(description = "G3", example = "37f45782-8ccf-4cf6-911c-a055a1510d39") @Schema(description = "G3", example = "58c1153e-dec6-4424-82a1-189083a9d9dc")
@NotNull @NotNull
private UUID model3Uuid; private UUID model3Uuid;
@@ -272,11 +342,10 @@ public class InferenceResultDto {
private String mapSheetScope; private String mapSheetScope;
@Schema(description = "탐지 데이터 옵션 - 추론제외(EXCL), 이전 년도 도엽 사용(PREV)", example = "EXCL") @Schema(description = "탐지 데이터 옵션 - 추론제외(EXCL), 이전 년도 도엽 사용(PREV)", example = "EXCL")
@NotBlank // @EnumValid(
@EnumValid( // enumClass = DetectOption.class,
enumClass = DetectOption.class, // message = "탐지 데이터 옵션은 '추론제외', '이전 년도 도엽 사용' 만 사용 가능합니다.")
message = "탐지 데이터 옵션은 '추론제외', '이전 년도 도엽 사용' 만 사용 가능합니다.") private DetectOption detectOption;
private String detectOption;
@Schema(description = "5k 도협 번호 목록", example = "[33605,33606, 33610, 34802, 35603, 35611]") @Schema(description = "5k 도협 번호 목록", example = "[33605,33606, 33610, 34802, 35603, 35611]")
@NotNull @NotNull
@@ -676,4 +745,14 @@ public class InferenceResultDto {
private Long m2ModelBatchId; private Long m2ModelBatchId;
private Long m3ModelBatchId; private Long m3ModelBatchId;
} }
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MapSheetFallbackYearDto {
private String mapSheetNum;
private Integer mngYyyy;
}
} }

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

@@ -7,9 +7,7 @@ import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /** AI API 추론 실행 DTO */
* AI API 추론 실행 DTO
*/
@Slf4j @Slf4j
@Getter @Getter
@Setter @Setter
@@ -27,7 +25,6 @@ public class InferenceSendDto {
private String cd_model_type; private String cd_model_type;
private Double priority; private Double priority;
@Getter @Getter
@Setter @Setter
@AllArgsConstructor @AllArgsConstructor
@@ -39,7 +36,5 @@ public class InferenceSendDto {
private Integer input2_year; private Integer input2_year;
private String input1_scene_path; private String input1_scene_path;
private String input2_scene_path; private String input2_scene_path;
} }
} }

View File

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

View File

@@ -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,22 +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) {}
} }
} }

View File

@@ -1,30 +1,19 @@
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.api.ApiResponseDto;
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; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.AnalResultInfo; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.*;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Dashboard;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Detail;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Geom;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.MapSheet;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.SearchGeoReq;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; 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.*;
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.InferenceStatusDetailDto;
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.SaveInferenceAiDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Status;
import com.kamco.cd.kamcoback.inference.dto.InferenceSendDto; import com.kamco.cd.kamcoback.inference.dto.InferenceSendDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceSendDto.pred_requests_areas; import com.kamco.cd.kamcoback.inference.dto.InferenceSendDto.pred_requests_areas;
import com.kamco.cd.kamcoback.log.dto.AuditLogDto; import com.kamco.cd.kamcoback.log.dto.AuditLogDto;
@@ -39,22 +28,15 @@ 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;
import java.util.ArrayList; import java.util.*;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; 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.HttpHeaders; import org.springframework.http.HttpHeaders;
@@ -64,24 +46,22 @@ import org.springframework.http.MediaType;
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 @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional
public class InferenceResultService { public class InferenceResultService {
private final InferenceResultCoreService inferenceResultCoreService; private final InferenceResultCoreService inferenceResultCoreService;
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;
@@ -92,7 +72,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;
/** /**
* 추론관리 목록 * 추론관리 목록
@@ -105,7 +88,7 @@ public class InferenceResultService {
} }
/** /**
* 추론 진행중인지 확인 * 추론 진행중인지 확인, 변화탐지 설정 등록 버튼 활성화 여부에 필요함
* *
* @return * @return
*/ */
@@ -117,40 +100,86 @@ public class InferenceResultService {
return dto.getUuid(); return dto.getUuid();
} }
/**
* 추론 실행 - 추론제외, 이전연도 도엽 사용 분기
*
* @param req
* @return
*/
@Transactional @Transactional
public UUID run(InferenceResultDto.RegReq req) { public UUID run(InferenceResultDto.RegReq req) {
List<MngListDto> targetDtoList = mapSheetMngCoreService.getHstMapSheetList(req); log.info("inference start request = {}", req);
List<String> compareList = mapSheetMngCoreService.getMapSheetMngHst(req.getCompareYyyy()); DetectOption detectOption = req.getDetectOption();
List<String> targetList = if (detectOption == DetectOption.EXCL) {
mapSheetMngCoreService.getHstMapSheetList(req).stream() // 추론 제외 일때 EXCL
return runExcl(req);
}
// 이전연도 도엽 사용 일때 PREV
return runPrev(req);
}
/**
* 변화탐지 [옵션 추론제외 실행]
*
* @param req
* @return
*/
public UUID runExcl(InferenceResultDto.RegReq req) {
// 기준연도 실행가능 도엽 조회
List<MngListDto> targetMngList =
mapSheetMngCoreService.getMapSheetMngHst(req.getTargetYyyy(), req.getMapSheetNum());
if (targetMngList == null || targetMngList.isEmpty()) {
throw new CustomApiException("NOT_FOUND_MAP_SHEET_NUM", HttpStatus.NOT_FOUND);
}
log.info("targetMngList size = {}", targetMngList.size());
// 비교연도 실행가능 도엽 조회
List<MngListDto> compareMngList =
mapSheetMngCoreService.getMapSheetMngHst(req.getCompareYyyy(), req.getMapSheetNum());
if (compareMngList == null || compareMngList.isEmpty()) {
throw new CustomApiException("NOT_FOUND_COMPARE_YEAR", HttpStatus.NOT_FOUND);
}
log.info("compareMngList size = {}", compareMngList.size());
// compare 도엽번호 Set 구성
Set<String> compareSet =
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();
log.info( int targetTotal = targetMngList.size();
"hst list count compareList = {}, targetList = {}", compareList.size(), targetList.size()); int compareTotal = compareMngList.size();
int intersection = intersectionList.size();
Set<String> compareSet = new HashSet<>(compareList); // ===== MapSheet Year Comparison =====
Set<String> targetSet = new HashSet<>(targetList); // target Total : 기준연도 실행가능 전체 도엽 수
// compare Total : 비교연도 실행가능 전체 도엽 수
long intersectionCount = // Intersection : 양 연도에 모두 존재하는 도엽 수 (최종 추론 대상)
targetSet.stream() // target Only (Excluded) : 기준연도에만 존재하고 비교연도에는 없는 도엽 수
.distinct() // compare Only : 비교연도에만 존재하고 기준연도에는 없는 도엽 수
.filter(compareSet::contains) // ====================================
.count(); // compare와 target에 공통으로 존재하는 도협 수
long excludedTargetCount =
targetSet.stream()
.distinct()
.filter(s -> !compareSet.contains(s))
.count(); // target 에만 존재하는 도협 수 (compare 에는 없음)
long onlyCompareCount =
compareSet.stream()
.distinct()
.filter(s -> !targetSet.contains(s))
.count(); // compare 에만 존재하는 도협 수 (target 에는 없음)
log.info( log.info(
""" """
@@ -158,69 +187,239 @@ public class InferenceResultService {
target Total: {} target Total: {}
compare Total: {} compare Total: {}
Intersection: {} Intersection: {}
target Only (Excluded from compare): {} target Only (Excluded): {}
compare Only: {} compare Only: {}
==================================== ====================================
""", """,
targetSet.size(), targetTotal,
compareSet.size(), compareTotal,
intersectionCount, intersection,
excludedTargetCount, targetTotal - intersection,
onlyCompareCount); compareTotal - intersection);
List<String> filteredTargetList = if (mapSheetNums.isEmpty()) {
targetSet.stream() // target 기준으로 // 추론 가능한 도엽이 없습니다.
.filter(compareSet::contains) // compare에 있는 도협만 남김 throw new CustomApiException("NOT_FOUND_MAP_SHEET_NUM", HttpStatus.NOT_FOUND);
.toList();
Scene modelComparePath =
getSceneInference(req.getCompareYyyy().toString(), filteredTargetList, "", "");
Scene modelTargetPath =
getSceneInference(req.getTargetYyyy().toString(), filteredTargetList, "", "");
// 작은 쪽 기준으로 탐지건수/파일생성 리스트 결정
List<ImageFeature> imageFeatureList;
if (modelComparePath.getFeatures().size() <= modelTargetPath.getFeatures().size()) {
imageFeatureList = modelComparePath.getFeatures();
} else {
imageFeatureList = modelTargetPath.getFeatures();
} }
// imageFeatureList 기준 sceneId Set // compare geojson 파일 생성
Set<String> sceneIdSet = Scene compareScene =
imageFeatureList.stream() getSceneInference(
.map(ImageFeature::getSceneId) req.getCompareYyyy().toString(), // 기준년도
mapSheetNums, // 최종 추론 대상
req.getMapSheetScope(), // ALL / 부분
req.getDetectOption()); // EXCL / PREV
// target geojson 파일 생성
Scene targetScene =
getSceneInference(
req.getTargetYyyy().toString(), // 대상년도
mapSheetNums, // 최종 추론 대상
req.getMapSheetScope(),
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(
req,
intersectionList, // 전체 target 목록
mapSheetNums, // 최종 추론 대상
compareScene, // compare geojson
targetScene // target geojson
);
}
/**
* 변화탐지 옵션 이전 년도 도엽 사용 실행
*
* @param req
* @return
*/
@Transactional
public UUID runPrev(InferenceResultDto.RegReq req) {
Integer targetYyyy = req.getTargetYyyy();
Integer compareYyyy = req.getCompareYyyy();
String mapSheetScope = req.getMapSheetScope();
log.info("[{}|{}}] ,{}", compareYyyy, targetYyyy, mapSheetScope);
// 기준연도 실행가능 도엽 조회[AFTER]
List<MngListDto> targetMngList =
mapSheetMngCoreService.getMapSheetMngHst(targetYyyy, req.getMapSheetNum());
log.info("[runPrev] targetMngList size = {}", targetMngList.size());
if (targetMngList == null || targetMngList.isEmpty()) {
throw new CustomApiException("NOT_FOUND_TARGET_YEAR", HttpStatus.NOT_FOUND);
}
// 비교연도 실행가능 도엽 조회
List<MngListDto> compareMngList =
mapSheetMngCoreService.getMapSheetMngHst(compareYyyy, req.getMapSheetNum());
log.info("[runPrev] compareMngList size = {}", compareMngList.size());
if (compareMngList == null || compareMngList.isEmpty()) {
throw new CustomApiException("NOT_FOUND_COMPARE_YEAR", HttpStatus.NOT_FOUND);
}
log.info("[runPrev] Difference in count = {}", targetMngList.size() - compareMngList.size());
// 로그용 원본 카운트 (이전도엽 추가 전)
int targetTotal = targetMngList.size();
int compareTotalBeforeFallback = compareMngList.size();
// 기준연도 기준 비교연도 구해서 이전년도로 compare 보완 하기위해서 도엽번호만 정리
Set<String> compareSet0 =
compareMngList.stream()
.map(MngListDto::getMapSheetNum)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// targetList(List<MngListDto>) 리턴용으로 필터링 // 기준연도 기준 비교연도에 도협번호가 없으면 이전연도 조회해서 compare 보완, 없는거 담기
List<String> targetOnlyMapSheetNums =
targetMngList.stream()
.map(MngListDto::getMapSheetNum)
.filter(Objects::nonNull)
.filter(num -> !compareSet0.contains(num))
.toList();
log.info("[runPrev] targetOnlyMapSheetNums in count = {}", targetOnlyMapSheetNums.size());
// 이전연도 초회 추가
compareMngList.addAll(
mapSheetMngCoreService.findFallbackCompareYearByMapSheets(
compareYyyy, targetOnlyMapSheetNums));
log.info("[runPrev] fallback compare size= {}", compareMngList.size());
// 이전연도 추가 후 compare 총 개수
int compareTotalAfterFallback = compareMngList.size();
// 이전연도 추가한 기준연도 값 도협번호만 담기
Set<String> compareSet1 =
compareMngList.stream()
.map(MngListDto::getMapSheetNum)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 기준연도 기준으로 비교연도에 있는것만 담기 (도협번호) 결국 비교년도와 개수가 같아짐
List<String> mapSheetNums =
targetMngList.stream()
.map(MngListDto::getMapSheetNum)
.filter(Objects::nonNull)
.filter(compareSet1::contains)
.toList();
int intersection = mapSheetNums.size();
Set<String> intersectionSet = new HashSet<>(mapSheetNums);
// 비교연도 같은거 담기(dto list)
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(
"""
===== MapSheet Year Comparison =====
target Total: {}
compare Total(before fallback): {}
compare Total(after fallback): {}
Intersection: {}
target Only (Excluded): {}
compare Only: {}
====================================
""",
targetTotal,
compareTotalBeforeFallback,
compareTotalAfterFallback,
intersection,
targetOnlyExcluded,
compareOnly);
if (mapSheetNums.isEmpty()) {
throw new CustomApiException("NOT_FOUND_MAP_SHEET_NUM", HttpStatus.NOT_FOUND);
}
// compare 기준 geojson 생성
Scene compareScene =
getSceneInference(
compareMngList, compareYyyy.toString(), mapSheetScope, req.getDetectOption());
// target 기준 geojson 생성
Scene targetScene =
getSceneInference(
targetYyyy.toString(), mapSheetNums, mapSheetScope, 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(req, filteredTargetMngList, mapSheetNums, compareScene, targetScene);
}
/**
* learn 테이블 저장 및 AI 추론 API 호출
*
* @param req
* @param targetDtoList
* @param filteredTargetList
* @param modelComparePath
* @param modelTargetPath
* @return
*/
private UUID executeInference(
InferenceResultDto.RegReq req,
List<MngListDto> targetDtoList,
List<String> filteredTargetList,
Scene modelComparePath,
Scene modelTargetPath) {
Set<String> filteredSet = new HashSet<>(filteredTargetList);
List<MngListDto> newTargetList = List<MngListDto> newTargetList =
targetDtoList.stream() targetDtoList.stream()
.filter(m -> m.getMapSheetNum() != null) .filter(m -> m.getMapSheetNum() != null)
.filter(m -> sceneIdSet.contains(m.getMapSheetNum())) .filter(m -> filteredSet.contains(m.getMapSheetNum()))
.toList(); .toList();
// 목록 및 추론 대상 도엽정보 저장 // 추론 실행 목록 테이블 저장, 도엽목록별 상태 체크 테이블 저장
UUID uuid = inferenceResultCoreService.saveInferenceInfo(req, newTargetList); UUID uuid = inferenceResultCoreService.saveInferenceInfo(req, newTargetList);
// ai 서버에 전달 파라미터 생성 // 추론 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);
m1.setPred_requests_areas(predRequestsAreas);
// ai 추론 실행 api 호출 // AI 호출
Long batchId = ensureAccepted(m1); Long batchId = inferenceCommonService.ensureAccepted(m1);
// ai 추론 실행후 응답값 update
SaveInferenceAiDto saveInferenceAiDto = new SaveInferenceAiDto(); SaveInferenceAiDto saveInferenceAiDto = new SaveInferenceAiDto();
saveInferenceAiDto.setUuid(uuid); saveInferenceAiDto.setUuid(uuid);
saveInferenceAiDto.setBatchId(batchId); saveInferenceAiDto.setBatchId(batchId);
@@ -230,13 +429,15 @@ public class InferenceResultService {
saveInferenceAiDto.setModelComparePath(modelComparePath.getFilePath()); saveInferenceAiDto.setModelComparePath(modelComparePath.getFilePath());
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;
} }
/** /**
* 변화탐지 실행 정보 생성 * 변화탐지 실행 정보 생성 TODO 미사용, 새로운 추론실행 로직 테스트후 삭제 해야합니다.
* *
* @param req * @param req
*/ */
@@ -244,7 +445,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);
@@ -265,31 +466,31 @@ public class InferenceResultService {
List<TotalListDto> totalNumList = new ArrayList<>(); List<TotalListDto> totalNumList = new ArrayList<>();
// if (DetectOption.EXCL.getId().equals(req.getDetectOption())) { if (DetectOption.EXCL.getId().equals(req.getDetectOption())) {
// // "추론제외" 일때 전년도 이전 값이 있어도 전년도 도엽이 없으면 비교 안함 // "추론제외" 일때 전년도 이전 값이 있어도 전년도 도엽이 없으면 비교 안함
// for (MngListCompareDto dto : compareList) { for (MngListCompareDto dto : compareList) {
// if (Objects.equals(dto.getBeforeYear(), req.getCompareYyyy())) { if (Objects.equals(dto.getBeforeYear(), req.getCompareYyyy())) {
// TotalListDto totalDto = new TotalListDto(); TotalListDto totalDto = new TotalListDto();
// totalDto.setBeforeYear(dto.getBeforeYear() == null ? 0 : dto.getBeforeYear()); totalDto.setBeforeYear(dto.getBeforeYear() == null ? 0 : dto.getBeforeYear());
// totalDto.setMapSheetNum(dto.getMapSheetNum()); totalDto.setMapSheetNum(dto.getMapSheetNum());
// totalNumList.add(totalDto); totalNumList.add(totalDto);
// } }
// } }
// } else if (DetectOption.PREV.getId().equals(req.getDetectOption())) { } else if (DetectOption.PREV.getId().equals(req.getDetectOption())) {
// // "이전 년도 도엽 사용" 이면 전년도 이전 도엽도 사용 // "이전 년도 도엽 사용" 이면 전년도 이전 도엽도 사용
// for (MngListCompareDto dto : compareList) { for (MngListCompareDto dto : compareList) {
// if (dto.getBeforeYear() != 0) { if (dto.getBeforeYear() != 0) {
// TotalListDto totalDto = new TotalListDto(); TotalListDto totalDto = new TotalListDto();
// totalDto.setBeforeYear(dto.getBeforeYear() == null ? 0 : dto.getBeforeYear()); totalDto.setBeforeYear(dto.getBeforeYear() == null ? 0 : dto.getBeforeYear());
// totalDto.setMapSheetNum(dto.getMapSheetNum()); totalDto.setMapSheetNum(dto.getMapSheetNum());
// totalNumList.add(totalDto); totalNumList.add(totalDto);
// } }
// } }
// } }
//
// if (totalNumList.isEmpty()) { if (totalNumList.isEmpty()) {
// throw new CustomApiException("NOT_FOUND_COMPARE_YEAR", HttpStatus.NOT_FOUND); throw new CustomApiException("NOT_FOUND_COMPARE_YEAR", HttpStatus.NOT_FOUND);
// } }
// 사용할 영상파일 년도 기록 및 추론에 포함되는지 설정 // 사용할 영상파일 년도 기록 및 추론에 포함되는지 설정
for (MngListDto target : targetList) { for (MngListDto target : targetList) {
@@ -362,7 +563,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();
@@ -379,140 +580,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 호출
*
* @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));
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 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);
}
}
/** /**
* 모델정보 조회 dto 생성 후 반환 * 모델정보 조회 dto 생성 후 반환
* *
@@ -521,6 +588,7 @@ public class InferenceResultService {
*/ */
private InferenceSendDto getModelInfo(UUID uuid) { private InferenceSendDto getModelInfo(UUID uuid) {
// 모델정보 조회
Basic modelInfo = modelMngCoreService.findByModelUuid(uuid); Basic modelInfo = modelMngCoreService.findByModelUuid(uuid);
String cdModelPath = ""; String cdModelPath = "";
@@ -573,9 +641,37 @@ public class InferenceResultService {
* @return * @return
*/ */
private Scene getSceneInference( private Scene getSceneInference(
String yyyy, List<String> mapSheetNums, String mapSheetScope, String detectOption) { String yyyy, List<String> mapSheetNums, String mapSheetScope, DetectOption 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 파일 생성
*
* @param yearDtos
* @param yyyy
* @param mapSheetScope
* @param detectOption
* @return
*/
private Scene getSceneInference(
List<MngListDto> yearDtos, String yyyy, String mapSheetScope, DetectOption detectOption) {
List<ImageFeature> features =
mapSheetMngCoreService.loadSceneInferenceByFallbackYears(yearDtos);
return writeSceneGeoJson(yyyy, mapSheetScope, detectOption, features);
} }
/** /**
@@ -654,11 +750,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();
@@ -668,6 +770,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]);
@@ -735,17 +838,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);
@@ -757,13 +878,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());
@@ -778,14 +901,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<>();
@@ -807,7 +933,7 @@ public class InferenceResultService {
} }
/** /**
* 실행중인 추론 도엽명 목록 * 분석중인 추론 도엽명 목록
* *
* @param uuid uuid * @param uuid uuid
* @return * @return
@@ -815,4 +941,86 @@ 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,
DetectOption 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(DetectOption detectOption) {
if (DetectOption.EXCL == detectOption) {
return "_EXCL";
}
if (DetectOption.PREV == detectOption) {
return "_PREV";
}
return "";
}
public ApiResponseDto.ResponseObj deleteInference(UUID uuid) {
long result = inferenceResultCoreService.deleteInference(uuid);
if (result > 0) {
return new ApiResponseDto.ResponseObj(ApiResponseDto.ApiResponseCode.OK, "삭제되었습니다.");
} else {
return new ApiResponseDto.ResponseObj(
ApiResponseDto.ApiResponseCode.INTERNAL_SERVER_ERROR, "삭제 실패하였습니다. 관리자에게 문의해주세요.");
}
}
} }

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

@@ -1,224 +0,0 @@
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.geometry.GeoJsonFileWriter.Scene;
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 com.kamco.cd.kamcoback.inference.dto.InferenceSendDto.pred_requests_areas;
import com.kamco.cd.kamcoback.model.dto.ModelMngDto.Basic;
import com.kamco.cd.kamcoback.model.dto.ModelMngDto.ModelType;
import com.kamco.cd.kamcoback.postgres.core.MapSheetMngCoreService;
import com.kamco.cd.kamcoback.postgres.core.ModelMngCoreService;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
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;
@Service
@Log4j2
@RequiredArgsConstructor
public class InferenceRunService {
private final ExternalHttpClient externalHttpClient;
private final MapSheetMngCoreService mapSheetMngCoreService;
private final ModelMngCoreService modelMngCoreService;
private final ObjectMapper objectMapper;
@Value("${spring.profiles.active}")
private String profile;
@Value("${inference.url}")
private String inferenceUrl;
// TODO 이거 쓰는건가?
public void run(Integer compareYear, Integer targetYear, UUID modelUuid) {
List<String> compareList = mapSheetMngCoreService.getMapSheetMngHst(compareYear);
List<String> targetList = mapSheetMngCoreService.getMapSheetMngHst(targetYear);
log.info(
"hst list count compareList = {}, targetList = {}", compareList.size(), targetList.size());
Set<String> compareSet = new HashSet<>(compareList);
Set<String> targetSet = new HashSet<>(targetList);
long intersectionCount =
targetSet.stream()
.distinct()
.filter(compareSet::contains)
.count(); // compare와 target에 공통으로 존재하는 도협 수
long excludedTargetCount =
targetSet.stream()
.distinct()
.filter(s -> !compareSet.contains(s))
.count(); // target 에만 존재하는 도협 수 (compare 에는 없음)
long onlyCompareCount =
compareSet.stream()
.distinct()
.filter(s -> !targetSet.contains(s))
.count(); // compare 에만 존재하는 도협 수 (target 에는 없음)
log.info(
"""
===== MapSheet Year Comparison =====
target Total: {}
compare Total: {}
Intersection: {}
target Only (Excluded from compare): {}
compare Only: {}
====================================
""",
targetSet.size(),
compareSet.size(),
intersectionCount,
excludedTargetCount,
onlyCompareCount);
List<String> filteredTargetList =
targetSet.stream() // target 기준으로
.filter(compareSet::contains) // compare에 있는 도협만 남김
.toList();
Scene modelComparePath = getSceneInference(compareYear.toString(), filteredTargetList, "", "");
Scene modelTargetPath = getSceneInference(targetYear.toString(), filteredTargetList, "", "");
// ai 서버에 전달할 파라미터 생성
pred_requests_areas predRequestsAreas = new pred_requests_areas();
predRequestsAreas.setInput1_year(compareYear);
predRequestsAreas.setInput2_year(targetYear);
predRequestsAreas.setInput1_scene_path(modelComparePath.getFilePath());
predRequestsAreas.setInput2_scene_path(modelTargetPath.getFilePath());
InferenceSendDto m1 = this.getModelInfo(modelUuid);
m1.setPred_requests_areas(predRequestsAreas);
// ai 추론 실행 api 호출
Long batchId = ensureAccepted(m1);
}
private Scene getSceneInference(
String yyyy, List<String> mapSheetNums, String mapSheetScope, String detectOption) {
return mapSheetMngCoreService.getSceneInference(
yyyy, mapSheetNums, mapSheetScope, detectOption);
}
/**
* 추론 AI API 호출
*
* @param dto
*/
private Long ensureAccepted(InferenceSendDto dto) {
if (dto == null) {
log.warn("not InferenceSendDto dto");
throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST);
}
// 1) 요청 로그
try {
log.info("Inference request dto={}", objectMapper.writeValueAsString(dto));
} catch (JsonProcessingException e) {
log.warn("Failed to serialize inference dto", e);
}
// 3) HTTP 호출
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
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 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);
}
}
/**
* 모델정보 조회 dto 생성 후 반환
*
* @param uuid
* @return
*/
private InferenceSendDto getModelInfo(UUID uuid) {
Basic modelInfo = modelMngCoreService.findByModelUuid(uuid);
String cdModelPath = "";
String cdModelConfigPath = "";
String cdClsModelPath = "";
if (modelInfo.getCdModelPath() != null && modelInfo.getCdModelFileName() != null) {
cdModelPath =
Paths.get(modelInfo.getCdModelPath(), modelInfo.getCdModelFileName()).toString();
}
if (modelInfo.getCdModelConfig() != null && modelInfo.getCdModelConfigFileName() != null) {
cdModelConfigPath =
Paths.get(modelInfo.getCdModelConfig(), modelInfo.getCdModelConfigFileName()).toString();
}
if (modelInfo.getClsModelPath() != null && modelInfo.getClsModelFileName() != null) {
cdClsModelPath =
Paths.get(modelInfo.getClsModelPath(), modelInfo.getClsModelFileName()).toString();
}
String modelType = "";
if (modelInfo.getModelType().equals(ModelType.G1.getId())) {
modelType = ModelType.G1.getId();
} else if (modelInfo.getModelType().equals(ModelType.G2.getId())) {
modelType = ModelType.G2.getId();
} else {
modelType = ModelType.G3.getId();
}
InferenceSendDto sendDto = new InferenceSendDto();
sendDto.setModel_version(modelInfo.getModelVer());
sendDto.setCd_model_path(cdModelPath);
sendDto.setCd_model_config(cdModelConfigPath);
sendDto.setCls_model_path(cdClsModelPath);
sendDto.setCls_model_version(modelInfo.getModelVer());
sendDto.setCd_model_type(modelType);
sendDto.setPriority(5.0);
return sendDto;
}
}

View File

@@ -37,6 +37,11 @@ public class LabelAllocateDto {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return name();
}
} }
@CodeExpose @CodeExpose
@@ -59,6 +64,11 @@ public class LabelAllocateDto {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return name();
}
} }
@CodeExpose @CodeExpose
@@ -80,6 +90,11 @@ public class LabelAllocateDto {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return name();
}
} }
@Getter @Getter

View File

@@ -1,6 +1,8 @@
package com.kamco.cd.kamcoback.label.dto; package com.kamco.cd.kamcoback.label.dto;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.kamco.cd.kamcoback.common.enums.RoleType;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
import com.kamco.cd.kamcoback.common.utils.enums.Enums; import com.kamco.cd.kamcoback.common.utils.enums.Enums;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
@@ -34,6 +36,7 @@ public class LabelWorkDto {
@AllArgsConstructor @AllArgsConstructor
public static class LabelWorkMng { public static class LabelWorkMng {
private Long analUid;
private UUID uuid; private UUID uuid;
private Integer compareYyyy; private Integer compareYyyy;
private Integer targetYyyy; private Integer targetYyyy;
@@ -108,9 +111,15 @@ public class LabelWorkDto {
if (type == null) { if (type == null) {
return enumId; return enumId;
} }
boolean english = HeaderUtil.isEnglishRequest();
if (!english) {
return type.getText(); return type.getText();
} }
return type.getTextEn();
}
/** /**
* 작업 진행률 반환 (tb_labeling_assignment.stagnation_yn = 'N'인 정상 진행율 기준) 계산식: (정상 진행 건수 / 총 배정 건수) * * 작업 진행률 반환 (tb_labeling_assignment.stagnation_yn = 'N'인 정상 진행율 기준) 계산식: (정상 진행 건수 / 총 배정 건수) *
* 100 * 100
@@ -222,10 +231,8 @@ public class LabelWorkDto {
} }
public String getUserRoleName() { public String getUserRoleName() {
if (this.userRole.equals("LABELER")) { RoleType type = Enums.fromId(RoleType.class, this.userRole);
return "라벨러"; return HeaderUtil.isEnglishRequest() ? type.getTextEn() : type.getText();
}
return "검수자";
} }
} }
@@ -260,4 +267,18 @@ public class LabelWorkDto {
return PageRequest.of(page, size); return PageRequest.of(page, size);
} }
} }
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class LabelCntInfo {
private Long assignedCnt;
private Long skipCnt;
private Long doneCnt;
private Long unconfirmCnt;
private Long exceptCnt;
private Long completeCnt;
}
} }

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

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

View File

@@ -62,7 +62,7 @@ public class LayerService {
.orElseThrow(() -> new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST)); .orElseThrow(() -> new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST));
switch (layerType) { switch (layerType) {
case TILE -> { case TILE, KAMCO_WMS, KAMCO_WMTS -> {
return mapLayerCoreService.saveTile(dto); return mapLayerCoreService.saveTile(dto);
} }

View File

@@ -1,7 +1,10 @@
package com.kamco.cd.kamcoback.log.dto; package com.kamco.cd.kamcoback.log.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
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 com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate; import java.time.LocalDate;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -36,6 +39,11 @@ public class ErrorLogDto {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return desc;
}
} }
@Schema(name = "ErrorLogBasic", description = "에러로그 기본 정보") @Schema(name = "ErrorLogBasic", description = "에러로그 기본 정보")
@@ -56,6 +64,26 @@ public class ErrorLogDto {
private final String errorMessage; private final String errorMessage;
private final String errorDetail; private final String errorDetail;
private final String createDate; // to_char해서 가져옴 private final String createDate; // to_char해서 가져옴
@JsonProperty("errorMessage")
public String getErrorMessage() {
boolean english = HeaderUtil.isEnglishRequest();
if (!english) {
return this.errorMessage;
}
return ApiResponseDto.ApiResponseCode.fromMessage(this.errorMessage).toString();
}
@JsonProperty("errorName")
public String getErrorName() {
boolean english = HeaderUtil.isEnglishRequest();
if (!english) {
return this.errorName;
}
return ApiResponseDto.ApiResponseCode.fromMessage(this.errorName).toString();
}
} }
@Schema(name = "ErrorSearchReq", description = "에러로그 검색 요청") @Schema(name = "ErrorSearchReq", description = "에러로그 검색 요청")

View File

@@ -21,4 +21,9 @@ public enum EventStatus implements EnumType {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return desc;
}
} }

View File

@@ -39,4 +39,9 @@ public enum EventType implements EnumType {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return name();
}
} }

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;
@@ -26,14 +22,7 @@ import lombok.RequiredArgsConstructor;
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.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@Tag(name = "영상 관리", description = "영상 관리 API") @Tag(name = "영상 관리", description = "영상 관리 API")
@@ -42,7 +31,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 +39,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 +58,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 +77,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 +97,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 +116,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 +135,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 +149,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 +172,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 +193,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 +212,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),
@@ -347,4 +249,11 @@ public class MapSheetMngApiController {
return ApiResponseDto.ok( return ApiResponseDto.ok(
mapSheetMngService.uploadChunkMapSheetFile(hstUid, upAddReqDto, chunkFile)); mapSheetMngService.uploadChunkMapSheetFile(hstUid, upAddReqDto, chunkFile));
} }
@Operation(summary = "영상데이터관리 > 등록된 년도 데이터 전체 삭제", description = "영상데이터관리 > 등록된 년도 데이터 전체 삭제")
@DeleteMapping
public ApiResponseDto<Void> deleteByMngYyyyMngAll(@RequestParam Integer mngYyyy) {
mapSheetMngService.deleteByMngYyyyMngAll(mngYyyy);
return ApiResponseDto.ok(null);
}
} }

View File

@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.mapsheet.dto;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kamco.cd.kamcoback.common.enums.MngStateType; import com.kamco.cd.kamcoback.common.enums.MngStateType;
import com.kamco.cd.kamcoback.common.enums.SyncStateType; import com.kamco.cd.kamcoback.common.enums.SyncStateType;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
import com.kamco.cd.kamcoback.common.utils.enums.EnumType; import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
import com.kamco.cd.kamcoback.common.utils.enums.Enums; import com.kamco.cd.kamcoback.common.utils.enums.Enums;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
@@ -41,6 +42,11 @@ public class MapSheetMngDto {
public String getText() { public String getText() {
return message; return message;
} }
@Override
public String getTextEn() {
return message;
}
} }
@Schema(name = "MngSearchReq", description = "영상관리 검색 요청") @Schema(name = "MngSearchReq", description = "영상관리 검색 요청")
@@ -217,7 +223,11 @@ public class MapSheetMngDto {
enumId = "NOTYET"; enumId = "NOTYET";
} }
boolean english = HeaderUtil.isEnglishRequest();
MngStateType type = Enums.fromId(MngStateType.class, enumId); MngStateType type = Enums.fromId(MngStateType.class, enumId);
if (english) {
return type.getTextEn();
}
return type.getText(); return type.getText();
} }
} }
@@ -341,6 +351,10 @@ public class MapSheetMngDto {
} }
SyncStateType type = Enums.fromId(SyncStateType.class, enumId); SyncStateType type = Enums.fromId(SyncStateType.class, enumId);
boolean english = HeaderUtil.isEnglishRequest();
if (english) {
return type.getTextEn();
}
return type.getText(); return type.getText();
} }
} }

View File

@@ -40,14 +40,13 @@ import org.springframework.web.multipart.MultipartFile;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional
public class MapSheetMngService { public class MapSheetMngService {
private final MapSheetMngCoreService mapSheetMngCoreService; private final MapSheetMngCoreService mapSheetMngCoreService;
private final UploadService uploadService; private final UploadService uploadService;
private final UserUtil userUtil = new UserUtil(); private final UserUtil userUtil = new UserUtil();
@Value("${file.root}") @Value("${file.root}")
private String nfsRootDir; private String nfsRootDir;
@@ -60,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();
} }
@@ -78,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);
} }
@@ -90,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);
} }
@@ -98,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 + "건 생성");
} }
@@ -111,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) {
@@ -119,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) {
@@ -136,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");
} }
@@ -176,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;
@@ -223,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);
} }
@@ -235,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);
@@ -340,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 =
@@ -382,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();
@@ -460,4 +489,8 @@ public class MapSheetMngService {
return modelUploadResDto; return modelUploadResDto;
} }
public void deleteByMngYyyyMngAll(Integer mngYyyy) {
mapSheetMngCoreService.deleteByMngYyyyMngAll(mngYyyy);
}
} }

View File

@@ -4,6 +4,7 @@ import com.kamco.cd.kamcoback.auth.CustomUserDetails;
import com.kamco.cd.kamcoback.auth.JwtTokenProvider; import com.kamco.cd.kamcoback.auth.JwtTokenProvider;
import com.kamco.cd.kamcoback.auth.RefreshTokenService; import com.kamco.cd.kamcoback.auth.RefreshTokenService;
import com.kamco.cd.kamcoback.common.enums.StatusType; import com.kamco.cd.kamcoback.common.enums.StatusType;
import com.kamco.cd.kamcoback.config.api.ApiLogFunction;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.members.dto.MembersDto; import com.kamco.cd.kamcoback.members.dto.MembersDto;
import com.kamco.cd.kamcoback.members.dto.SignInRequest; import com.kamco.cd.kamcoback.members.dto.SignInRequest;
@@ -16,11 +17,13 @@ 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 jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.nio.file.AccessDeniedException; import java.nio.file.AccessDeniedException;
import java.time.Duration; import java.time.Duration;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.ResponseCookie; import org.springframework.http.ResponseCookie;
@@ -34,6 +37,7 @@ 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.RestController; import org.springframework.web.bind.annotation.RestController;
@Slf4j
@Tag(name = "인증(Auth)", description = "로그인, 토큰 재발급, 로그아웃 API") @Tag(name = "인증(Auth)", description = "로그인, 토큰 재발급, 로그아웃 API")
@RestController @RestController
@RequestMapping("/api/auth") @RequestMapping("/api/auth")
@@ -103,8 +107,13 @@ public class AuthController {
required = true) required = true)
@RequestBody @RequestBody
SignInRequest request, SignInRequest request,
HttpServletRequest servletRequest,
HttpServletResponse response) { HttpServletResponse response) {
// TODO: 접속 가능한 IP 대역 조회
String clientIp = ApiLogFunction.getXFowardedForIp(servletRequest);
log.info("####### clientIp: {}", clientIp);
// 사용자 상태 조회 // 사용자 상태 조회
String status = authService.getUserStatus(request); String status = authService.getUserStatus(request);
Authentication authentication = null; Authentication authentication = null;
@@ -169,6 +178,7 @@ public class AuthController {
if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) { if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) {
throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다."); throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다.");
} }
String username = jwtTokenProvider.getSubject(refreshToken); String username = jwtTokenProvider.getSubject(refreshToken);
// Redis에 저장된 RefreshToken과 일치하는지 확인 // Redis에 저장된 RefreshToken과 일치하는지 확인

View File

@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.members.dto;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kamco.cd.kamcoback.common.enums.RoleType; import com.kamco.cd.kamcoback.common.enums.RoleType;
import com.kamco.cd.kamcoback.common.enums.StatusType; import com.kamco.cd.kamcoback.common.enums.StatusType;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
import com.kamco.cd.kamcoback.common.utils.enums.Enums; import com.kamco.cd.kamcoback.common.utils.enums.Enums;
import com.kamco.cd.kamcoback.common.utils.interfaces.EnumValid; import com.kamco.cd.kamcoback.common.utils.interfaces.EnumValid;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
@@ -65,16 +66,18 @@ public class MembersDto {
private String getUserRoleName(String roleId) { private String getUserRoleName(String roleId) {
RoleType type = Enums.fromId(RoleType.class, roleId); RoleType type = Enums.fromId(RoleType.class, roleId);
return type.getText(); boolean english = HeaderUtil.isEnglishRequest();
return english ? type.getTextEn() : type.getText();
} }
private String getStatusName(String status, Boolean pwdResetYn) { private String getStatusName(String status, Boolean pwdResetYn) {
StatusType type = Enums.fromId(StatusType.class, status); StatusType type = Enums.fromId(StatusType.class, status);
boolean english = HeaderUtil.isEnglishRequest();
pwdResetYn = pwdResetYn != null && pwdResetYn; pwdResetYn = pwdResetYn != null && pwdResetYn;
if (type.equals(StatusType.PENDING) && pwdResetYn) { if (type.equals(StatusType.PENDING) && pwdResetYn) {
type = StatusType.ACTIVE; type = StatusType.ACTIVE;
} }
return type.getText(); return english ? type.getTextEn() : type.getText();
} }
} }

View File

@@ -31,7 +31,7 @@ public class MenuDto {
@JsonFormatDttm private ZonedDateTime updatedDttm; @JsonFormatDttm private ZonedDateTime updatedDttm;
private String menuApiUrl; private String menuNmEn;
public Basic( public Basic(
String menuUid, String menuUid,
@@ -45,7 +45,8 @@ public class MenuDto {
Long updatedUid, Long updatedUid,
List<MenuDto.Basic> children, List<MenuDto.Basic> children,
ZonedDateTime createdDttm, ZonedDateTime createdDttm,
ZonedDateTime updatedDttm) { ZonedDateTime updatedDttm,
String menuNmEn) {
this.menuUid = menuUid; this.menuUid = menuUid;
this.menuNm = menuNm; this.menuNm = menuNm;
this.menuUrl = menuUrl; this.menuUrl = menuUrl;
@@ -58,6 +59,7 @@ public class MenuDto {
this.children = children; this.children = children;
this.createdDttm = createdDttm; this.createdDttm = createdDttm;
this.updatedDttm = updatedDttm; this.updatedDttm = updatedDttm;
this.menuNmEn = menuNmEn;
} }
} }

View File

@@ -36,6 +36,11 @@ public class ModelMngDto {
public String getText() { public String getText() {
return desc; return desc;
} }
@Override
public String getTextEn() {
return desc;
}
} }
@Schema(name = "ModelMgmtDto Basic", description = "모델관리 엔티티 기본 정보") @Schema(name = "ModelMgmtDto Basic", description = "모델관리 엔티티 기본 정보")
@@ -67,6 +72,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 +95,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 +118,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

@@ -6,6 +6,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto; import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto;
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.PolygonFeatureList;
import com.kamco.cd.kamcoback.common.enums.DetectionClassification; import com.kamco.cd.kamcoback.common.enums.DetectionClassification;
import com.kamco.cd.kamcoback.common.exception.CustomApiException; import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
@@ -108,8 +110,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 +123,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);
} }
/** /**
@@ -136,4 +138,12 @@ public class ChangeDetectionCoreService {
.getLearnUuid(chnDtctId) .getLearnUuid(chnDtctId)
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND)); .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
} }
public PolygonFeatureList getChangeDetectionPnuPolygonList(UUID uuid, String pnu) {
return changeDetectionRepository.getChangeDetectionPnuPolygonList(uuid, pnu);
}
public PointFeatureList getChangeDetectionPnuPointList(UUID uuid, String pnu) {
return changeDetectionRepository.getChangeDetectionPnuPointList(uuid, 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

@@ -0,0 +1,26 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.postgres.repository.gukyuin.GukYuinPnuCntUpdateJobRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class GukYuinPnuCntUpdateJobCoreService {
private final GukYuinPnuCntUpdateJobRepository gukYuinPnuCntUpdateRepository;
public GukYuinPnuCntUpdateJobCoreService(
GukYuinPnuCntUpdateJobRepository gukYuinPnuCntUpdateRepository) {
this.gukYuinPnuCntUpdateRepository = gukYuinPnuCntUpdateRepository;
}
@Transactional
public void updateGukYuinContListPnuUpdateCnt() {
gukYuinPnuCntUpdateRepository.updateGukYuinContListPnuUpdateCnt();
}
@Transactional
public void updateGukYuinApplyStatus(String uid, String status) {
gukYuinPnuCntUpdateRepository.updateGukYuinApplyStatus(uid, status);
}
}

View File

@@ -36,6 +36,7 @@ import com.kamco.cd.kamcoback.scheduler.dto.JobStatusDto;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -46,7 +47,7 @@ import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -54,7 +55,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@Log4j2 @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class InferenceResultCoreService { public class InferenceResultCoreService {
@@ -79,12 +80,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())
@@ -118,23 +120,19 @@ public class InferenceResultCoreService {
mapSheetLearnEntity.setCompareYyyy(req.getCompareYyyy()); mapSheetLearnEntity.setCompareYyyy(req.getCompareYyyy());
mapSheetLearnEntity.setTargetYyyy(req.getTargetYyyy()); mapSheetLearnEntity.setTargetYyyy(req.getTargetYyyy());
mapSheetLearnEntity.setMapSheetScope(req.getMapSheetScope()); mapSheetLearnEntity.setMapSheetScope(req.getMapSheetScope());
mapSheetLearnEntity.setDetectOption(req.getDetectOption()); mapSheetLearnEntity.setDetectOption(req.getDetectOption().getId());
mapSheetLearnEntity.setCreatedUid(userUtil.getId()); mapSheetLearnEntity.setCreatedUid(userUtil.getId());
mapSheetLearnEntity.setMapSheetCnt(mapSheetName); mapSheetLearnEntity.setMapSheetCnt(mapSheetName);
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 +143,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 +160,9 @@ public class InferenceResultCoreService {
} }
/** /**
* 도엽별 저장 * 도엽별 저장 learn 5k 테이블
* *
* @param buffer * @param buffer 저장 정보
*/ */
private void flushChunk(List<MapSheetLearn5kEntity> buffer) { private void flushChunk(List<MapSheetLearn5kEntity> buffer) {
@@ -271,7 +272,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 +284,7 @@ public class InferenceResultCoreService {
.distinct() // 중복 방지 (선택) .distinct() // 중복 방지 (선택)
.toList(); .toList();
// testing 추론결과 테이블 조회하여 탐지 개수 업데이트 // testing 테이블 추론결과 테이블 조회하여 탐지 개수 업데이트
Long testing = getInferenceResultCnt(batchIds); Long testing = getInferenceResultCnt(batchIds);
// 공통 영역 업데이트 // 공통 영역 업데이트
@@ -369,6 +370,12 @@ public class InferenceResultCoreService {
return mapSheetLearnRepository.getInferenceServerStatusList(); return mapSheetLearnRepository.getInferenceServerStatusList();
} }
/**
* 진행중 배치 조회
*
* @param status
* @return
*/
public InferenceBatchSheet getInferenceResultByStatus(String status) { public InferenceBatchSheet getInferenceResultByStatus(String status) {
MapSheetLearnEntity entity = MapSheetLearnEntity entity =
mapSheetLearnRepository.getInferenceResultByStatus(status).orElse(null); mapSheetLearnRepository.getInferenceResultByStatus(status).orElse(null);
@@ -403,6 +410,12 @@ public class InferenceResultCoreService {
return mapSheetLearnRepository.getInferenceAiResultById(id, modelUuid); return mapSheetLearnRepository.getInferenceAiResultById(id, modelUuid);
} }
/**
* 추론 진행 현황 상세
*
* @param uuid
* @return
*/
public InferenceStatusDetailDto getInferenceStatus(UUID uuid) { public InferenceStatusDetailDto getInferenceStatus(UUID uuid) {
return mapSheetLearnRepository.getInferenceStatus(uuid); return mapSheetLearnRepository.getInferenceStatus(uuid);
} }
@@ -410,7 +423,7 @@ public class InferenceResultCoreService {
/** /**
* 추론 진행중인지 확인 * 추론 진행중인지 확인
* *
* @return * @return 추론 실행중인 추론 uuid, batch id
*/ */
public SaveInferenceAiDto getProcessing() { public SaveInferenceAiDto getProcessing() {
MapSheetLearnEntity entity = mapSheetLearnRepository.getProcessing(); MapSheetLearnEntity entity = mapSheetLearnRepository.getProcessing();
@@ -441,19 +454,45 @@ public class InferenceResultCoreService {
* @return * @return
*/ */
public AnalResultInfo getInferenceResultInfo(UUID uuid) { public AnalResultInfo getInferenceResultInfo(UUID uuid) {
// 추론 결과 정보조회
log.info("get inference result info start time = {}", LocalDateTime.now());
AnalResultInfo resultInfo = mapSheetLearnRepository.getInferenceResultInfo(uuid); AnalResultInfo resultInfo = mapSheetLearnRepository.getInferenceResultInfo(uuid);
log.info("get inference result info end time = {}", LocalDateTime.now());
// bbox, point 조회
log.info("get inference result info bbox start time = {}", LocalDateTime.now());
BboxPointDto bboxPointDto = mapSheetLearnRepository.getBboxPoint(uuid); BboxPointDto bboxPointDto = mapSheetLearnRepository.getBboxPoint(uuid);
log.info("get inference result info bbox end time = {}", LocalDateTime.now());
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); log.info("get inference class count list start time = {}", LocalDateTime.now());
List<Dashboard> classCountList = mapSheetLearnRepository.getInferenceClassCountList(uuid);
log.info("get inference class count list end time = {}", LocalDateTime.now());
return classCountList;
} }
/**
* @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); log.info("get Inference Geom List start time = {}", LocalDateTime.now());
Page<Geom> geom = mapSheetLearnRepository.getInferenceGeomList(uuid, searchGeoReq);
log.info("get Inference Geom List end time = {}", LocalDateTime.now());
return geom;
} }
/** /**
@@ -463,9 +502,13 @@ public class InferenceResultCoreService {
*/ */
@Transactional @Transactional
public void upsertGeomData(Long id) { public void upsertGeomData(Long id) {
// 추론 결과 목록 저장
Long analId = inferenceResultRepository.upsertGroupsFromMapSheetAnal(id); Long analId = inferenceResultRepository.upsertGroupsFromMapSheetAnal(id);
// 추론 결과 상세 저장
inferenceResultRepository.upsertGroupsFromInferenceResults(analId); inferenceResultRepository.upsertGroupsFromInferenceResults(analId);
// geom 목록 추론 결과 저장
inferenceResultRepository.upsertGeomsFromInferenceResults(analId); inferenceResultRepository.upsertGeomsFromInferenceResults(analId);
// 집계 추론 결과 저장
inferenceResultRepository.upsertSttcFromInferenceResults(analId); inferenceResultRepository.upsertSttcFromInferenceResults(analId);
} }
@@ -510,15 +553,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);
@@ -535,7 +584,7 @@ public class InferenceResultCoreService {
} }
/** /**
* 실행중인 추론 도엽명 목록 * 분석중인 추론 도엽명 목록
* *
* @param uuid 추론 실행중인 uuid * @param uuid 추론 실행중인 uuid
* @return * @return
@@ -544,11 +593,41 @@ 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);
}
public long deleteInference(UUID uuid) {
return inferenceResultRepository.deleteInference(uuid);
}
} }

View File

@@ -1,12 +1,17 @@
package com.kamco.cd.kamcoback.postgres.core; package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultShpDto;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity;
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.http.HttpStatus;
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 +20,33 @@ 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();
MapSheetAnalInferenceEntity analInferenceEntity =
repo.getAnalInferenceDataByLearnId(id).orElse(null);
if (analInferenceEntity != null) {
throw new CustomApiException("CONFLICT", HttpStatus.CONFLICT);
}
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,13 +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.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;
@@ -21,21 +16,21 @@ 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.Arrays; import java.util.Arrays;
import java.util.HashMap; 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;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; 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;
@@ -53,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();
} }
@@ -76,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);
} }
@@ -103,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);
} }
@@ -154,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 정보 등록
@@ -233,94 +266,70 @@ public class MapSheetMngCoreService {
} }
/** /**
* 추론 실행에 필요한 geojson 파일 생성 * geojson 생성시 필요한 영상파일 정보 조회
* *
* @param yyyy 영상관리 파일별 년도 * @param yyyy
* @param scenes 5k 도엽 번호 리스트 * @param mapSheetNums
* @param mapSheetScope EXCL : 추론제외, PREV 이전 년도 도엽 사용 * @return ImageFeature
* @return
*/ */
public Scene getSceneInference( public List<ImageFeature> loadSceneInferenceBySheets(String yyyy, List<String> mapSheetNums) {
String yyyy, List<String> scenes, String mapSheetScope, String detectOption) {
Map<String, Object> result = new HashMap<>(); if (mapSheetNums == null || mapSheetNums.isEmpty()) {
boolean isAll = MapSheetScope.ALL.getId().equals(mapSheetScope); return List.of();
String optionSuffix = "";
if (DetectOption.EXCL.getId().equals(detectOption)) {
optionSuffix = "_EXCL";
} else if (DetectOption.PREV.getId().equals(detectOption)) {
optionSuffix = "_PREV";
} }
// 1) 경로/파일명 결정 // CHUNK_SIZE 단위로 나누어 여러 번 조회한다.
String targetDir = final int CHUNK_SIZE = 1000;
"local".equals(activeEnv) ? System.getProperty("user.home") + "/geojson" : inferenceDir; List<ImageFeature> features = new ArrayList<>();
String filename = // i부터 CHUNK_SIZE만큼 잘라서 조회
isAll // 마지막 구간은 남은 개수만큼만 처리하기 위해 Math.min 사용
? String.format("%s_%s_ALL%s.geojson", yyyy, activeEnv, optionSuffix) for (int i = 0; i < mapSheetNums.size(); i += CHUNK_SIZE) {
: String.format("%s_%s%s.geojson", yyyy, activeEnv, optionSuffix); List<String> chunk = mapSheetNums.subList(i, Math.min(i + CHUNK_SIZE, mapSheetNums.size()));
features.addAll(mapSheetMngRepository.getSceneInference(yyyy, chunk));
Path outputPath = Paths.get(targetDir, filename);
// 2) ALL일 때만 재사용
// if (isAll && Files.exists(outputPath)) {
// return outputPath.toString();
// }
// 3) 데이터 조회
List<ImageFeature> sceneInference = mapSheetMngRepository.getSceneInference(yyyy, scenes);
if (sceneInference == null || sceneInference.isEmpty()) {
log.warn(
"NOT_FOUND_TARGET_YEAR: yyyy={}, isAll={}, scenesSize={}",
yyyy,
isAll,
scenes == null ? 0 : scenes.size());
throw new CustomApiException("NOT_FOUND_TARGET_YEAR", HttpStatus.NOT_FOUND);
}
// 4) 파일 생성
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());
log.info("GeoJsonFileWriter: {}", "scene_inference_" + yyyy);
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);
} }
return features;
} }
/** /**
* 변화탐지 실행 가능 기준 년도 조회 * 년도별로 나눠 조회
* *
* @param req * @param yearDtos
* @return * @return ImageFeature
*/ */
public List<MngListDto> getHstMapSheetList(InferenceResultDto.RegReq req) { public List<ImageFeature> loadSceneInferenceByFallbackYears(List<MngListDto> yearDtos) {
return mapSheetMngRepository.findByHstMapSheetTargetList(req); if (yearDtos == null || yearDtos.isEmpty()) {
return List.of();
} }
public List<MngListDto> getHstMapSheetList(int mngYyyy, List<String> mapIds) { // 년도 별로 루프를 돌리기위해 년도별 정리
return mapSheetMngRepository.findByHstMapSheetTargetList(mngYyyy, mapIds); Map<Integer, List<MngListDto>> groupedByYear =
yearDtos.stream()
.filter(d -> d.getMngYyyy() != 0 && d.getMapSheetNum() != null)
.collect(Collectors.groupingBy(MngListDto::getMngYyyy));
List<ImageFeature> sceneInference = new ArrayList<>();
for (Map.Entry<Integer, List<MngListDto>> entry : groupedByYear.entrySet()) {
Integer year = entry.getKey();
// 년도별 mapSheetNum 만들기
List<String> sheetNums =
entry.getValue().stream()
.map(MngListDto::getMapSheetNum)
.filter(Objects::nonNull)
.distinct()
.toList();
// tif파일 정보 조회
List<ImageFeature> temp = loadSceneInferenceBySheets(year.toString(), sheetNums);
if (temp != null && !temp.isEmpty()) {
sceneInference.addAll(temp);
}
}
return sceneInference;
} }
public void updateMapSheetMngHstUploadId(Long hstUid, UUID uuid, String uploadId) { public void updateMapSheetMngHstUploadId(Long hstUid, UUID uuid, String uploadId) {
@@ -336,15 +345,42 @@ public class MapSheetMngCoreService {
* 변화탐지 실행 가능 비교년도 조회 * 변화탐지 실행 가능 비교년도 조회
* *
* @param mngYyyy 비교년도 * @param mngYyyy 비교년도
* @param mapId 5k 도엽번호 * @param mapIds 5k 도엽번호
* @return List<MngListCompareDto> * @return List<MngListCompareDto>
*/ */
public List<MngListCompareDto> getByHstMapSheetCompareList(int mngYyyy, List<String> mapId) { public List<MngListCompareDto> getByHstMapSheetCompareList(int mngYyyy, List<String> mapIds) {
return mapSheetMngYearRepository.findByHstMapSheetCompareList(mngYyyy, mapId); return mapSheetMngYearRepository.findByHstMapSheetCompareList(mngYyyy, mapIds);
} }
public List<String> getMapSheetMngHst(Integer year) { public List<MngListDto> getMapSheetMngHst(Integer year, List<String> mapSheetNums50k) {
List<MapSheetMngHstEntity> entity = mapSheetMngRepository.getMapSheetMngHst(year); return mapSheetMngRepository.getMapSheetMngHst(year, mapSheetNums50k);
return entity.stream().map(MapSheetMngHstEntity::getMapSheetNum).toList(); }
/**
* 이전 년도 도엽 조회 조건이 많을 수 있으므로 chunk 줘서 끊어서 조회
*
* @param year
* @param mapIds
* @return
*/
public List<MngListDto> findFallbackCompareYearByMapSheets(Integer year, List<String> mapIds) {
if (mapIds == null || mapIds.isEmpty()) {
return Collections.emptyList();
}
int chunkSize = 1000;
List<MngListDto> result = new ArrayList<>();
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;
}
public void deleteByMngYyyyMngAll(Integer mngYyyy) {
mapSheetMngRepository.deleteByMngYyyyMngAll(mngYyyy);
} }
} }

View File

@@ -1,5 +1,6 @@
package com.kamco.cd.kamcoback.postgres.core; package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
import com.kamco.cd.kamcoback.menu.dto.MenuDto; import com.kamco.cd.kamcoback.menu.dto.MenuDto;
import com.kamco.cd.kamcoback.menu.dto.MyMenuDto; import com.kamco.cd.kamcoback.menu.dto.MyMenuDto;
import com.kamco.cd.kamcoback.postgres.entity.MenuEntity; import com.kamco.cd.kamcoback.postgres.entity.MenuEntity;
@@ -7,8 +8,10 @@ import com.kamco.cd.kamcoback.postgres.repository.menu.MenuRepository;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class MenuCoreService { public class MenuCoreService {
@@ -27,6 +30,8 @@ public class MenuCoreService {
*/ */
public List<MyMenuDto.Basic> getFindByRole(String role) { public List<MyMenuDto.Basic> getFindByRole(String role) {
List<MenuEntity> entities = menuRepository.getFindByRole(role); List<MenuEntity> entities = menuRepository.getFindByRole(role);
boolean english = HeaderUtil.isEnglishRequest();
log.info("[LANG CHECK] english={}", english);
return entities.stream() return entities.stream()
.map( .map(
@@ -34,7 +39,7 @@ public class MenuCoreService {
MyMenuDto.Basic p = MyMenuDto.Basic p =
new MyMenuDto.Basic( new MyMenuDto.Basic(
parent.getMenuUid(), parent.getMenuUid(),
parent.getMenuNm(), english ? parent.getMenuNmEn() : parent.getMenuNm(),
parent.getMenuUrl(), parent.getMenuUrl(),
parent.getMenuOrder()); parent.getMenuOrder());
@@ -48,7 +53,10 @@ public class MenuCoreService {
.map( .map(
c -> c ->
new MyMenuDto.Basic( new MyMenuDto.Basic(
c.getMenuUid(), c.getMenuNm(), c.getMenuUrl(), c.getMenuOrder())) c.getMenuUid(),
english ? c.getMenuNmEn() : c.getMenuNm(),
c.getMenuUrl(),
c.getMenuOrder()))
.forEach(childDto -> p.getChildren().add(childDto)); .forEach(childDto -> p.getChildren().add(childDto));
return p; return p;

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

@@ -11,37 +11,94 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.ColumnDefault;
/**
* GPU 메트릭 엔티티
*
* <p>서버의 GPU 성능 및 자원 사용량 메트릭 데이터를 저장하는 JPA 엔티티입니다. GPU 연산 사용률 및 메모리 사용량 등 GPU 리소스 모니터링 데이터를 관리합니다.
*
* <p>데이터 소스: nvidia-smi 명령어 또는 NVML (NVIDIA Management Library)
*
* <p>활용 사례:
*
* <ul>
* <li>AI/ML 학습 모니터링: 딥러닝 작업 중 GPU 활용도 추적
* <li>리소스 최적화: GPU 메모리 부족 또는 유휴 상태 감지
* <li>용량 계획: GPU 추가 필요 시점 예측
* <li>알림 설정: gpuUtil > 95% 또는 gpuMemUsed/gpuMemTotal > 90% 시 경고
* </ul>
*/
@Getter @Getter
@Setter @Setter
@Entity @Entity
@Table(name = "gpu_metrics") @Table(name = "gpu_metrics")
public class GpuMetricEntity { public class GpuMetricEntity {
/** 기본 키 (UUID, 자동 생성) */
@Id @Id
@ColumnDefault("gen_random_uuid()") @ColumnDefault("gen_random_uuid()")
@Column(name = "uuid", nullable = false) @Column(name = "uuid", nullable = false)
private UUID id; private UUID id;
/** 시퀀스 기반 보조 ID */
@NotNull @NotNull
@ColumnDefault("nextval('gpu_metrics_id_seq')") @ColumnDefault("nextval('gpu_metrics_id_seq')")
@Column(name = "id", nullable = false) @Column(name = "id", nullable = false)
private Integer id1; private Integer id1;
/** 메트릭 수집 시각 (시간대 포함, 기본값: 현재 시각) */
@NotNull @NotNull
@ColumnDefault("now()") @ColumnDefault("now()")
@Column(name = "\"timestamp\"", nullable = false) @Column(name = "\"timestamp\"", nullable = false)
private OffsetDateTime timestamp; private OffsetDateTime timestamp;
/** 모니터링 대상 서버 이름 */
@NotNull @NotNull
@Column(name = "server_name", nullable = false, length = Integer.MAX_VALUE) @Column(name = "server_name", nullable = false, length = Integer.MAX_VALUE)
private String serverName; private String serverName;
/**
* GPU 연산 사용률 (백분율)
*
* <p>GPU 코어의 연산 처리 활용도를 나타냅니다.
*
* <p>범위: 0.0 ~ 100.0
*
* <p>예시: 85.5 = GPU가 85.5% 활용되어 연산 중
*
* <p>데이터 소스: nvidia-smi의 'utilization.gpu' 또는 NVML의 nvmlDeviceGetUtilizationRates
*
* <p>참고: 높은 사용률(>90%)은 GPU가 충분히 활용되고 있음을 의미하며, 낮은 사용률은 병목 지점이 다른 곳(CPU, I/O)에 있을 수 있음
*/
@Column(name = "gpu_util") @Column(name = "gpu_util")
private Float gpuUtil; private Float gpuUtil;
/**
* GPU 메모리 사용량 (MB 단위)
*
* <p>현재 GPU에 할당되어 사용 중인 메모리 양
*
* <p>예시: 10240.0 = 약 10GB의 GPU 메모리 사용 중
*
* <p>데이터 소스: nvidia-smi의 'memory.used' 또는 NVML의 nvmlDeviceGetMemoryInfo
*
* <p>용도: 딥러닝 모델 크기, 배치 사이즈 최적화, OOM(Out Of Memory) 에러 예측
*/
@Column(name = "gpu_mem_used") @Column(name = "gpu_mem_used")
private Float gpuMemUsed; private Float gpuMemUsed;
/**
* GPU 총 메모리 용량 (MB 단위)
*
* <p>GPU에 장착된 전체 메모리 용량
*
* <p>예시: 16384.0 = 16GB VRAM 장착
*
* <p>데이터 소스: nvidia-smi의 'memory.total' 또는 NVML의 nvmlDeviceGetMemoryInfo
*
* <p>계산식: 메모리 사용률(%) = (gpuMemUsed / gpuMemTotal) × 100
*
* <p>활용: 여유 메모리 = gpuMemTotal - gpuMemUsed
*/
@Column(name = "gpu_mem_total") @Column(name = "gpu_mem_total")
private Float gpuMemTotal; private Float gpuMemTotal;
} }

View File

@@ -202,6 +202,36 @@ 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;
@Column(name = "is_deleted")
private Boolean isDeleted;
public InferenceResultDto.ResultList toDto() { public InferenceResultDto.ResultList toDto() {
return new InferenceResultDto.ResultList( return new InferenceResultDto.ResultList(
this.uuid, this.uuid,

View File

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

View File

@@ -58,6 +58,9 @@ public class MenuEntity extends CommonDateEntity {
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL) @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<MenuEntity> children = new ArrayList<>(); private List<MenuEntity> children = new ArrayList<>();
@Column(name = "menu_nm_en")
private String menuNmEn; // 영문 메뉴명
public MenuDto.Basic toDto() { public MenuDto.Basic toDto() {
return new MenuDto.Basic( return new MenuDto.Basic(
this.menuUid, this.menuUid,
@@ -71,6 +74,7 @@ public class MenuEntity extends CommonDateEntity {
this.updatedUid, this.updatedUid,
this.children.stream().map(MenuEntity::toDto).toList(), this.children.stream().map(MenuEntity::toDto).toList(),
this.getCreatedDate(), this.getCreatedDate(),
this.getModifiedDate()); this.getModifiedDate(),
this.menuNmEn);
} }
} }

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

@@ -11,48 +11,101 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.ColumnDefault;
/**
* 시스템 메트릭 엔티티
*
* <p>서버 시스템의 성능 메트릭 데이터를 저장하는 JPA 엔티티입니다. CPU 및 메모리 사용량 등 시스템 리소스 모니터링 데이터를 관리합니다.
*
* <p>데이터 소스: Linux sar 명령어 또는 /proc/meminfo 파일
*
* <p>활용 사례:
*
* <ul>
* <li>용량 계획: 메모리 추가 필요 시점 예측
* <li>성능 모니터링: 메모리 부족 상황 감지
* <li>트렌드 분석: 시간대별 메모리 사용 패턴 파악
* <li>알림 설정: memused > 90% 시 경고
* </ul>
*/
@Getter @Getter
@Setter @Setter
@Entity @Entity
@Table(name = "system_metrics") @Table(name = "system_metrics")
public class SystemMetricEntity { public class SystemMetricEntity {
/** 기본 키 (UUID, 자동 생성) */
@Id @Id
@ColumnDefault("gen_random_uuid()") @ColumnDefault("gen_random_uuid()")
@Column(name = "uuid", nullable = false) @Column(name = "uuid", nullable = false)
private UUID id; private UUID id;
/** 시퀀스 기반 보조 ID */
@NotNull @NotNull
@ColumnDefault("nextval('system_metrics_id_seq')") @ColumnDefault("nextval('system_metrics_id_seq')")
@Column(name = "id", nullable = false) @Column(name = "id", nullable = false)
private Integer id1; private Integer id1;
/** 메트릭 수집 시각 (시간대 포함) */
@NotNull @NotNull
@Column(name = "\"timestamp\"", nullable = false) @Column(name = "\"timestamp\"", nullable = false)
private OffsetDateTime timestamp; private OffsetDateTime timestamp;
/** 모니터링 대상 서버 이름 */
@NotNull @NotNull
@Column(name = "server_name", nullable = false, length = Integer.MAX_VALUE) @Column(name = "server_name", nullable = false, length = Integer.MAX_VALUE)
private String serverName; private String serverName;
/** 사용자 프로세스가 사용한 CPU 사용률 (%) - 응용 프로그램 실행 */
@Column(name = "cpu_user") @Column(name = "cpu_user")
private Float cpuUser; private Float cpuUser;
/** 시스템 프로세스가 사용한 CPU 사용률 (%) - 커널 작업 */
@Column(name = "cpu_system") @Column(name = "cpu_system")
private Float cpuSystem; private Float cpuSystem;
/** I/O 대기로 소모된 CPU 사용률 (%) - 디스크/네트워크 대기 */
@Column(name = "cpu_iowait") @Column(name = "cpu_iowait")
private Float cpuIowait; private Float cpuIowait;
/** 유휴 상태 CPU 사용률 (%) - 사용 가능한 여유 CPU */
@Column(name = "cpu_idle") @Column(name = "cpu_idle")
private Float cpuIdle; private Float cpuIdle;
/**
* 사용 가능한 여유 메모리 (KB 단위)
*
* <p>시스템에서 즉시 사용 가능한 물리 메모리 양
*
* <p>예시: 4194304 = 약 4GB의 여유 메모리
*
* <p>데이터 소스: /proc/meminfo의 MemFree
*/
@Column(name = "kbmemfree") @Column(name = "kbmemfree")
private Long kbmemfree; private Long kbmemfree;
/**
* 현재 사용 중인 메모리 (KB 단위)
*
* <p>시스템이 현재 할당하여 사용 중인 물리 메모리 양
*
* <p>예시: 8388608 = 약 8GB의 사용 중인 메모리
*
* <p>계산: MemTotal - MemFree
*/
@Column(name = "kbmemused") @Column(name = "kbmemused")
private Long kbmemused; private Long kbmemused;
/**
* 메모리 사용률 (백분율)
*
* <p>전체 메모리 대비 사용 중인 메모리 비율
*
* <p>계산식: (kbmemused / (kbmemused + kbmemfree)) × 100
*
* <p>예시: 66.7 = 전체 메모리의 66.7% 사용 중
*
* <p>관계식: 총 메모리 = kbmemused + kbmemfree
*/
@Column(name = "memused") @Column(name = "memused")
private Float memused; private Float memused;
} }

View File

@@ -1,20 +1,72 @@
package com.kamco.cd.kamcoback.postgres.repository.Inference; package com.kamco.cd.kamcoback.postgres.repository.Inference;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public interface InferenceResultRepositoryCustom { public interface InferenceResultRepositoryCustom {
/**
* tb_map_sheet_anal_inference 추론 결과 목록 저장
*
* @param id learn 테이블 id
* @return
*/
Long upsertGroupsFromMapSheetAnal(Long id); Long upsertGroupsFromMapSheetAnal(Long id);
void upsertGroupsFromInferenceResults(Long analId); /**
* tb_map_sheet_anal_data_inference 추론 결과 상세 저장
*
* @param analId
* @return
*/
int upsertGroupsFromInferenceResults(Long analId);
void upsertGeomsFromInferenceResults(Long analId); /**
* tb_map_sheet_anal_data_inference_geom geom 목록 추론 결과 저장
*
* @param analId
* @return
*/
int upsertGeomsFromInferenceResults(Long analId);
void upsertSttcFromInferenceResults(Long analId); /**
* tb_map_sheet_anal_sttc 집계 추론 결과 저장
*
* @param analId
* @return
*/
int upsertSttcFromInferenceResults(Long analId);
/**
* 추론실행 목록 uuid 조회
*
* @param uuid 추론 uuid
* @return 추론 실행 정보
*/
Long getInferenceLearnIdByUuid(UUID uuid); Long getInferenceLearnIdByUuid(UUID uuid);
/**
* 추론 정보 조회
*
* @param uuid 추론 uuid
* @return 추론 정보
*/
Optional<MapSheetLearnEntity> getInferenceUid(UUID uuid); Optional<MapSheetLearnEntity> getInferenceUid(UUID uuid);
/**
* learn id 로 analInference 값 조회
*
* @param id 추론 id
* @return
*/
Optional<MapSheetAnalInferenceEntity> getAnalInferenceDataByLearnId(Long id);
/**
* 추론 삭제
*
* @param uuid
*/
long deleteInference(UUID uuid);
} }

View File

@@ -1,8 +1,10 @@
package com.kamco.cd.kamcoback.postgres.repository.Inference; package com.kamco.cd.kamcoback.postgres.repository.Inference;
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 com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity;
import com.querydsl.jpa.impl.JPAQueryFactory; import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
@@ -80,7 +82,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 +126,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 +138,7 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
* @return 반영된 행 수 * @return 반영된 행 수
*/ */
@Override @Override
public void upsertGeomsFromInferenceResults(Long analUid) { public int upsertGeomsFromInferenceResults(Long analUid) {
String sql = String sql =
""" """
@@ -214,6 +216,9 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
WHERE msl.anal_uid = :analUid WHERE msl.anal_uid = :analUid
AND r.after_c is not null AND r.after_c is not null
AND r.after_p is not null AND r.after_p is not null
AND r.probability is not null
AND r.before_c is not null
AND r.before_p is not null
ORDER BY r.uid, r.created_date DESC NULLS LAST ORDER BY r.uid, r.created_date DESC NULLS LAST
) x ) x
ON CONFLICT (result_uid) ON CONFLICT (result_uid)
@@ -229,11 +234,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 +311,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();
} }
// =============================== // ===============================
@@ -331,4 +336,23 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
.where(mapSheetLearnEntity.uuid.eq(uuid)) .where(mapSheetLearnEntity.uuid.eq(uuid))
.fetchOne()); .fetchOne());
} }
@Override
public Optional<MapSheetAnalInferenceEntity> getAnalInferenceDataByLearnId(Long id) {
return Optional.ofNullable(
queryFactory
.select(mapSheetAnalInferenceEntity)
.from(mapSheetAnalInferenceEntity)
.where(mapSheetAnalInferenceEntity.learnId.eq(id))
.fetchOne());
}
@Override
public long deleteInference(UUID uuid) {
return queryFactory
.update(mapSheetLearnEntity)
.set(mapSheetLearnEntity.isDeleted, true)
.where(mapSheetLearnEntity.uuid.eq(uuid))
.execute();
}
} }

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;
@@ -21,11 +23,12 @@ public class InferenceResultsTestingRepositoryImpl
.select(inferenceResultsTestingEntity) .select(inferenceResultsTestingEntity)
.from(inferenceResultsTestingEntity) .from(inferenceResultsTestingEntity)
.where( .where(
inferenceResultsTestingEntity inferenceResultsTestingEntity.batchId.in(batchIds),
.batchId inferenceResultsTestingEntity.afterC.isNotNull(),
.in(batchIds) inferenceResultsTestingEntity.afterP.isNotNull(),
.and(inferenceResultsTestingEntity.afterC.isNotNull()) inferenceResultsTestingEntity.beforeC.isNotNull(),
.and(inferenceResultsTestingEntity.afterP.isNotNull())) inferenceResultsTestingEntity.beforeP.isNotNull(),
inferenceResultsTestingEntity.probability.isNotNull())
.fetch(); .fetch();
} }
@@ -42,9 +45,38 @@ public class InferenceResultsTestingRepositoryImpl
.where( .where(
inferenceResultsTestingEntity.batchId.in(batchIds), inferenceResultsTestingEntity.batchId.in(batchIds),
inferenceResultsTestingEntity.afterC.isNotNull(), inferenceResultsTestingEntity.afterC.isNotNull(),
inferenceResultsTestingEntity.afterP.isNotNull()) inferenceResultsTestingEntity.afterP.isNotNull(),
inferenceResultsTestingEntity.beforeC.isNotNull(),
inferenceResultsTestingEntity.beforeP.isNotNull(),
inferenceResultsTestingEntity.probability.isNotNull())
.fetchOne(); .fetchOne();
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),
inferenceResultsTestingEntity.afterC.isNotNull(),
inferenceResultsTestingEntity.afterP.isNotNull(),
inferenceResultsTestingEntity.beforeC.isNotNull(),
inferenceResultsTestingEntity.beforeP.isNotNull(),
inferenceResultsTestingEntity.probability.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

@@ -18,29 +18,99 @@ import org.springframework.data.domain.Page;
public interface MapSheetLearnRepositoryCustom { public interface MapSheetLearnRepositoryCustom {
/**
* 추론 관리 목록 조회
*
* @param req 추론관리 목록 화면 조회 조건
* @return 추론 관리 목록
*/
Page<MapSheetLearnEntity> getInferenceMgnResultList(InferenceResultDto.SearchListReq req); Page<MapSheetLearnEntity> getInferenceMgnResultList(InferenceResultDto.SearchListReq req);
/**
* uuid 조건으로 추론 실행 정보 조회
*
* @param uuid uuid
* @return 추론 실행 정보
*/
Optional<MapSheetLearnEntity> getInferenceResultByUuid(UUID uuid); Optional<MapSheetLearnEntity> getInferenceResultByUuid(UUID uuid);
/**
* 추론 실행중 서버정보 조회 cpu, gpu
*
* @return cpu, gpu 정보
*/
List<InferenceServerStatusDto> getInferenceServerStatusList(); List<InferenceServerStatusDto> getInferenceServerStatusList();
/**
* 추론 실행 목록 진행 상태별 조회
*
* @param status 추론 진행 상태
* @return 추론 실행 정보
*/
Optional<MapSheetLearnEntity> getInferenceResultByStatus(String status); Optional<MapSheetLearnEntity> getInferenceResultByStatus(String status);
/**
* 등록된 추론 실행목록 및 등록된 모델 정보 조회
*
* @param id 추론 실행 테이블 id
* @param modelUuid 모델 uuid
* @return 모델 정보
*/
InferenceProgressDto getInferenceAiResultById(Long id, UUID modelUuid); InferenceProgressDto getInferenceAiResultById(Long id, UUID modelUuid);
/**
* 진행중인 추론 정보 상세 조회
*
* @param uuid 추론진행 uuid
* @return 진행중인 추론정보 상세 정보
*/
InferenceStatusDetailDto getInferenceStatus(UUID uuid); InferenceStatusDetailDto getInferenceStatus(UUID uuid);
/**
* 진행중인 추론이 있는지 조회
*
* @return 진행중인 추론 정보
*/
MapSheetLearnEntity getProcessing(); MapSheetLearnEntity getProcessing();
Integer getLearnStage(Integer compareYear, Integer targetYear); /**
* 추론 결과 정보 조회
*
* @param uuid 추론 uuid
* @return 추론 결과 및 사용 모델 정보
*/
AnalResultInfo getInferenceResultInfo(UUID uuid); AnalResultInfo getInferenceResultInfo(UUID uuid);
/**
* 추론 결과 bbox, point 조회
*
* @param uuid 추론 uuid
* @return bbox, pont 정보
*/
BboxPointDto getBboxPoint(UUID uuid); BboxPointDto getBboxPoint(UUID uuid);
/**
* 분류별 탐지건수 조회
*
* @param uuid 추론 uuid
* @return 분류별 탐지건수 정보
*/
List<Dashboard> getInferenceClassCountList(UUID uuid); List<Dashboard> getInferenceClassCountList(UUID uuid);
/**
* 추론 결과 상세 geom 목록 조회
*
* @param uuid 추론 uuid
* @param searchGeoReq 추론 결과 상세화면 geom 조회 조건
* @return geom 목록 정보
*/
Page<Geom> getInferenceGeomList(UUID uuid, SearchGeoReq searchGeoReq); Page<Geom> getInferenceGeomList(UUID uuid, SearchGeoReq searchGeoReq);
/**
* 국유in연동 가능여부 확인 조회
*
* @param uuid 추론 uuid
* @return 추론 존재여부, 부분도엽 여부, 추론 진행중 여부, 국유인 작업 진행중 여부
*/
GukYuinLinkFacts findLinkFacts(UUID uuid); GukYuinLinkFacts findLinkFacts(UUID uuid);
} }

View File

@@ -30,6 +30,7 @@ import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity;
import com.kamco.cd.kamcoback.postgres.entity.QModelMngEntity; import com.kamco.cd.kamcoback.postgres.entity.QModelMngEntity;
import com.querydsl.core.BooleanBuilder; import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Expression; import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Projections; import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.BooleanExpression;
@@ -37,13 +38,10 @@ 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 java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.List; import java.util.*;
import java.util.Objects; import java.util.stream.Collectors;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -90,6 +88,9 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
builder.and(mapSheetLearnEntity.title.containsIgnoreCase(req.getTitle())); builder.and(mapSheetLearnEntity.title.containsIgnoreCase(req.getTitle()));
} }
// 삭제여부 값 추가
builder.and(mapSheetLearnEntity.isDeleted.eq(false).or(mapSheetLearnEntity.isDeleted.isNull()));
List<MapSheetLearnEntity> content = List<MapSheetLearnEntity> content =
queryFactory queryFactory
.select(mapSheetLearnEntity) .select(mapSheetLearnEntity)
@@ -291,24 +292,6 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
.fetchOne(); .fetchOne();
} }
@Override
public Integer getLearnStage(Integer compareYear, Integer targetYear) {
Integer stage =
queryFactory
.select(mapSheetLearnEntity.stage)
.from(mapSheetLearnEntity)
.where(
mapSheetLearnEntity
.compareYyyy
.eq(compareYear)
.and(mapSheetLearnEntity.targetYyyy.eq(targetYear)))
.orderBy(mapSheetLearnEntity.id.desc())
.limit(1)
.fetchOne();
return stage == null ? 1 : stage + 1;
}
@Override @Override
public AnalResultInfo getInferenceResultInfo(UUID uuid) { public AnalResultInfo getInferenceResultInfo(UUID uuid) {
QModelMngEntity m1 = new QModelMngEntity("m1"); QModelMngEntity m1 = new QModelMngEntity("m1");
@@ -333,7 +316,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))
@@ -462,12 +446,14 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
NumberExpression<Long> inkxNoAsLong = NumberExpression<Long> inkxNoAsLong =
Expressions.numberTemplate(Long.class, "cast({0} as long)", mapInkx5kEntity.mapidcdNo); Expressions.numberTemplate(Long.class, "cast({0} as long)", mapInkx5kEntity.mapidcdNo);
/* 미사용
StringExpression pnu = StringExpression pnu =
Expressions.stringTemplate( Expressions.stringTemplate(
"nullif(({0}), '')", "nullif(({0}), '')",
JPAExpressions.select(Expressions.stringTemplate("string_agg({0}, ',')", pnuEntity.pnu)) JPAExpressions.select(Expressions.stringTemplate("string_agg({0}, ',')", pnuEntity.pnu))
.from(pnuEntity) .from(pnuEntity)
.where(pnuEntity.geo.geoUid.eq(mapSheetAnalDataInferenceGeomEntity.geoUid))); .where(pnuEntity.geo.geoUid.eq(mapSheetAnalDataInferenceGeomEntity.geoUid)));
*/
// 4) content // 4) content
List<Geom> content = List<Geom> content =
@@ -475,6 +461,7 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
.select( .select(
Projections.constructor( Projections.constructor(
Geom.class, Geom.class,
mapSheetAnalDataInferenceGeomEntity.geoUid,
mapSheetAnalDataInferenceGeomEntity.uuid, mapSheetAnalDataInferenceGeomEntity.uuid,
mapSheetAnalDataInferenceGeomEntity.resultUid, mapSheetAnalDataInferenceGeomEntity.resultUid,
mapSheetAnalDataInferenceGeomEntity.compareYyyy, mapSheetAnalDataInferenceGeomEntity.compareYyyy,
@@ -489,7 +476,7 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
Expressions.stringTemplate( Expressions.stringTemplate(
"substring({0} from 1 for 8)", "substring({0} from 1 for 8)",
mapSheetAnalDataInferenceGeomEntity.resultUid), mapSheetAnalDataInferenceGeomEntity.resultUid),
pnu, Expressions.constant(""),
mapSheetAnalDataInferenceGeomEntity.fitState)) mapSheetAnalDataInferenceGeomEntity.fitState))
.from(mapSheetAnalInferenceEntity) .from(mapSheetAnalInferenceEntity)
.join(mapSheetAnalDataInferenceEntity) .join(mapSheetAnalDataInferenceEntity)
@@ -504,6 +491,33 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
.limit(pageable.getPageSize()) .limit(pageable.getPageSize())
.fetch(); .fetch();
// 4-1) pnu 를 geoUid 로 검색하기 -> AS-IS 에서는 전체 tb_pnu 를 조회해서 오래 걸렸음
List<Long> geoUids = content.stream().map(Geom::getGeoUid).filter(Objects::nonNull).toList();
Map<Long, String> pnuMap = new HashMap<>();
if (!geoUids.isEmpty()) {
StringExpression pnuAgg = Expressions.stringTemplate("string_agg({0}, ',')", pnuEntity.pnu);
List<Tuple> pnuRows =
queryFactory
.select(
pnuEntity.geo.geoUid,
Expressions.stringTemplate("string_agg({0}, ',')", pnuEntity.pnu))
.from(pnuEntity)
.where(pnuEntity.geo.geoUid.in(geoUids))
.groupBy(pnuEntity.geo.geoUid)
.fetch();
pnuMap =
pnuRows.stream()
.collect(
Collectors.toMap(row -> row.get(pnuEntity.geo.geoUid), row -> row.get(pnuAgg)));
}
for (Geom geom : content) {
geom.setPnu(pnuMap.get(geom.getGeoUid()));
}
// 5) total (조인 최소화 유지) // 5) total (조인 최소화 유지)
Long total = Long total =
queryFactory queryFactory
@@ -528,6 +542,7 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
@Override @Override
public GukYuinLinkFacts findLinkFacts(UUID uuid) { public GukYuinLinkFacts findLinkFacts(UUID uuid) {
// 해당 추론 있는지 확인
MapSheetLearnEntity learn = MapSheetLearnEntity learn =
queryFactory queryFactory
.selectFrom(QMapSheetLearnEntity.mapSheetLearnEntity) .selectFrom(QMapSheetLearnEntity.mapSheetLearnEntity)
@@ -535,18 +550,21 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
.fetchOne(); .fetchOne();
if (learn == null) { if (learn == null) {
return new GukYuinLinkFacts(false, false, false, false); return new GukYuinLinkFacts(false, false, false, false, null);
} }
// 부분 도엽 실행인지 확인
boolean isPartScope = MapSheetScope.PART.getId().equals(learn.getMapSheetScope()); boolean isPartScope = MapSheetScope.PART.getId().equals(learn.getMapSheetScope());
QMapSheetAnalInferenceEntity inf = QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity; QMapSheetAnalInferenceEntity inf = QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity;
QMapSheetLearnEntity learn2 = new QMapSheetLearnEntity("learn2"); QMapSheetLearnEntity learn2 = new QMapSheetLearnEntity("learn2");
QMapSheetLearnEntity learnQ = QMapSheetLearnEntity.mapSheetLearnEntity; QMapSheetLearnEntity learnQ = QMapSheetLearnEntity.mapSheetLearnEntity;
boolean hasRunningInference = // 현재 국유인 연동하려는 추론의 비교년도,기준년도와 같은 회차 중, 할당되거나 진행중인 학습데이터 uuid 조회
// ex. 2022-2023년도 9회차 학습데이터 제작 진행중 -> 10회차 연동하려고 할 시, 먼저 9회차를 종료해야 함
UUID runningInferenceUuid =
queryFactory queryFactory
.selectOne() .select(inf.uuid)
.from(inf) .from(inf)
.join(learn2) .join(learn2)
.on(inf.learnId.eq(learn2.id)) .on(inf.learnId.eq(learn2.id))
@@ -554,9 +572,11 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
learn2.compareYyyy.eq(learn.getCompareYyyy()), learn2.compareYyyy.eq(learn.getCompareYyyy()),
learn2.targetYyyy.eq(learn.getTargetYyyy()), learn2.targetYyyy.eq(learn.getTargetYyyy()),
inf.analState.in("ASSIGNED", "ING")) inf.analState.in("ASSIGNED", "ING"))
.fetchFirst() .fetchFirst();
!= null;
boolean hasRunningInference = runningInferenceUuid != null;
// 국유인 작업 진행중 있는지 확인
boolean hasOtherUnfinishedGukYuin = boolean hasOtherUnfinishedGukYuin =
queryFactory queryFactory
.selectOne() .selectOne()
@@ -569,6 +589,7 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
.fetchFirst() .fetchFirst()
!= null; != null;
return new GukYuinLinkFacts(true, isPartScope, hasRunningInference, hasOtherUnfinishedGukYuin); return new GukYuinLinkFacts(
true, isPartScope, hasRunningInference, hasOtherUnfinishedGukYuin, runningInferenceUuid);
} }
} }

View File

@@ -3,6 +3,8 @@ package com.kamco.cd.kamcoback.postgres.repository.changedetection;
import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto; import com.kamco.cd.kamcoback.changedetection.dto.ChangeDetectionDto;
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.PolygonFeatureList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@@ -31,10 +33,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);
@@ -43,4 +45,8 @@ public interface ChangeDetectionRepositoryCustom {
String chnDtctId, String pnu); String chnDtctId, String pnu);
Optional<UUID> getLearnUuid(String chnDtctId); Optional<UUID> getLearnUuid(String chnDtctId);
PolygonFeatureList getChangeDetectionPnuPolygonList(UUID uuid, String pnu);
PointFeatureList getChangeDetectionPnuPointList(UUID uuid, 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;
@@ -208,8 +211,9 @@ public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
.where( .where(
l.status l.status
.eq(Status.END.getId()) .eq(Status.END.getId())
.and(JPAExpressions.selectOne().from(d).where(d.analUid.eq(a.id)).exists())) .and(JPAExpressions.selectOne().from(d).where(d.analUid.eq(a.id)).exists()),
.orderBy(l.id.asc()) l.isDeleted.eq(false).or(l.isDeleted.isNull()))
.orderBy(l.id.desc())
.fetch(); .fetch();
} }
@@ -341,7 +345,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 +368,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 +379,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 +395,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();
// pnu 가 없을 때
if (!hasPnu) {
builder.and( builder.and(
mapSheetAnalDataInferenceGeomEntity mapSheetAnalDataInferenceGeomEntity
.resultUid .resultUid
.eq(chnDtctId) .eq(cdObjectId)
.or(mapSheetAnalDataInferenceGeomEntity.resultUid.in(cdObjectIds))); .or(mapSheetAnalDataInferenceGeomEntity.resultUid.in(cdObjectIds)));
}
List<ChangeDetectionDto.PolygonQueryData> list = JPAQuery<PolygonQueryData> query =
queryFactory queryFactory
.select( .select(
Projections.constructor( Projections.constructor(
@@ -419,10 +436,19 @@ 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)
.orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) // pnu 조건이면 tb_pnu join 추가
.fetch(); 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 +490,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();
// pnu 가 없을 때
if (!hasPnu) {
builder.and( builder.and(
mapSheetAnalDataInferenceGeomEntity mapSheetAnalDataInferenceGeomEntity
.resultUid .resultUid
.eq(chnDtctId) .eq(cdObjectId)
.or(mapSheetAnalDataInferenceGeomEntity.resultUid.in(cdObjectIds))); .or(mapSheetAnalDataInferenceGeomEntity.resultUid.in(cdObjectIds)));
}
List<ChangeDetectionDto.PointQueryData> list = JPAQuery<PointQueryData> query =
queryFactory queryFactory
.select( .select(
Projections.constructor( Projections.constructor(
@@ -492,9 +525,19 @@ 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)
.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 =
@@ -653,4 +696,120 @@ public class ChangeDetectionRepositoryImpl extends QuerydslRepositorySupport
.where(mapSheetLearnEntity.uid.eq(chnDtctId)) .where(mapSheetLearnEntity.uid.eq(chnDtctId))
.fetchOne()); .fetchOne());
} }
@Override
public PolygonFeatureList getChangeDetectionPnuPolygonList(UUID uuid, String pnu) {
List<ChangeDetectionDto.PolygonQueryData> list =
queryFactory
.select(
Projections.constructor(
ChangeDetectionDto.PolygonQueryData.class,
Expressions.stringTemplate("{0}", "Feature"),
Expressions.stringTemplate(
"ST_AsGeoJSON({0})", mapSheetAnalDataInferenceGeomEntity.geom),
mapSheetAnalDataInferenceGeomEntity.geoUid,
mapSheetAnalDataInferenceGeomEntity.area,
mapSheetAnalDataInferenceGeomEntity.compareYyyy,
mapSheetAnalDataInferenceGeomEntity.classBeforeProb,
mapSheetAnalDataInferenceGeomEntity.classBeforeCd.toUpperCase(),
mapSheetAnalDataInferenceGeomEntity.targetYyyy,
mapSheetAnalDataInferenceGeomEntity.classAfterProb,
mapSheetAnalDataInferenceGeomEntity.classAfterCd.toUpperCase(),
mapSheetAnalDataInferenceGeomEntity.cdProb,
mapSheetAnalDataInferenceGeomEntity.uuid,
mapSheetAnalDataInferenceGeomEntity.resultUid))
.from(mapSheetAnalDataInferenceGeomEntity)
.innerJoin(mapSheetAnalDataInferenceEntity)
.on(mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id))
.innerJoin(mapSheetAnalInferenceEntity)
.on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid))
.innerJoin(pnuEntity)
.on(mapSheetAnalDataInferenceGeomEntity.geoUid.eq(pnuEntity.geo.geoUid))
.where(mapSheetAnalInferenceEntity.uuid.eq(uuid), pnuEntity.pnu.eq(pnu))
.orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc())
.fetch();
ObjectMapper mapper = new ObjectMapper();
List<ChangeDetectionDto.PolygonFeature> result =
list.stream()
.map(
data -> {
String geoJson = data.getGeometry();
JsonNode jsonNode;
try {
jsonNode = mapper.readTree(geoJson);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
ChangeDetectionDto.PolygonProperties properties =
new ChangeDetectionDto.PolygonProperties(
data.getGeoUid(),
data.getArea(),
data.getBeforeYear(),
data.getBeforeConfidence(),
data.getBeforeClass(),
data.getAfterYear(),
data.getAfterConfidence(),
data.getAfterClass(),
data.getCdProb(),
data.getUuid(),
data.getResultUid());
return new ChangeDetectionDto.PolygonFeature(
data.getType(), jsonNode, properties);
})
.collect(Collectors.toList());
ChangeDetectionDto.PolygonFeatureList polygonList = new ChangeDetectionDto.PolygonFeatureList();
polygonList.setType("FeatureCollection");
polygonList.setFeatures(result);
return polygonList;
}
@Override
public PointFeatureList getChangeDetectionPnuPointList(UUID uuid, String pnu) {
List<ChangeDetectionDto.PointQueryData> list =
queryFactory
.select(
Projections.constructor(
ChangeDetectionDto.PointQueryData.class,
Expressions.stringTemplate("{0}", "Feature"),
Expressions.stringTemplate(
"ST_AsGeoJSON({0})",
mapSheetAnalDataInferenceGeomEntity.geomCenter), // point
Projections.constructor(
ChangeDetectionDto.PointProperties.class,
mapSheetAnalDataInferenceGeomEntity.geoUid,
mapSheetAnalDataInferenceGeomEntity.classAfterCd.toUpperCase())))
.from(mapSheetAnalDataInferenceGeomEntity)
.innerJoin(mapSheetAnalDataInferenceEntity)
.on(mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id))
.innerJoin(mapSheetAnalInferenceEntity)
.on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid))
.innerJoin(pnuEntity)
.on(mapSheetAnalDataInferenceGeomEntity.geoUid.eq(pnuEntity.geo.geoUid))
.where(mapSheetAnalInferenceEntity.uuid.eq(uuid), pnuEntity.pnu.eq(pnu))
.fetch();
ObjectMapper mapper = new ObjectMapper();
List<ChangeDetectionDto.PointFeature> result =
list.stream()
.map(
data -> {
String geoJson = data.getGeometry();
JsonNode jsonNode;
try {
jsonNode = mapper.readTree(geoJson);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return new ChangeDetectionDto.PointFeature(
data.getType(), jsonNode, data.getProperties());
})
.collect(Collectors.toList());
return new ChangeDetectionDto.PointFeatureList("FeatureCollection", result);
}
} }

View File

@@ -0,0 +1,7 @@
package com.kamco.cd.kamcoback.postgres.repository.gukyuin;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface GukYuinPnuCntUpdateJobRepository
extends JpaRepository<MapSheetLearnEntity, Long>, GukYuinPnuCntUpdateJobRepositoryCustom {}

View File

@@ -0,0 +1,8 @@
package com.kamco.cd.kamcoback.postgres.repository.gukyuin;
public interface GukYuinPnuCntUpdateJobRepositoryCustom {
void updateGukYuinContListPnuUpdateCnt();
void updateGukYuinApplyStatus(String uid, String status);
}

Some files were not shown because too many files have changed in this diff Show More