142 Commits

Author SHA1 Message Date
a2bb1b2442 라벨링툴 count 종료된 회차는 count하지 않게 조건 추가 2026-02-12 18:03:34 +09:00
80b037a9cb 라벨링 다운로드 경로 추가 2026-02-11 09:57:02 +09:00
298b90a289 라벨 다운로드 확인 API 추가 2026-02-10 16:50:02 +09:00
985e1789d2 파일다운로드 변경, 파일다운로드 로그 저장 변경, 라벨 다운로드 이력 추가, 라벨 다운로드 추가 2026-02-10 11:20:16 +09:00
2d86fab030 라벨링툴 > 검수자 상세 라벨러 이름 조건 수정, 이노팸 object DTO 주석 추가 2026-02-09 12:29:35 +09:00
cf6b1323d8 라벨링툴 탐지분류 명칭 추가 2026-02-06 16:46:04 +09:00
5377294e6e 국유인 배치 수정, 라벨링툴 적합여부 수정 2026-02-06 16:32:46 +09:00
4e3e2a0181 레이어명 추가 2026-02-06 15:59:56 +09:00
57a2ec8367 스웨거 로그인 수정 2026-02-06 14:53:54 +09:00
fe6edbb19f 국유인 등록 로직 순서 변경 2026-02-06 14:25:16 +09:00
0e45adc52e 국유인 실태조사 적합여부 업데이트 로직 수정, 라벨링 건수 조건 수정 2026-02-06 11:12:15 +09:00
581b8c968e Merge pull request '국유인 수정, 라벨대상 건수 수정' (#54) from feat/infer_dev_260107 into develop
Reviewed-on: #54
2026-02-06 09:53:01 +09:00
e88ffd1260 국유인 수정, 라벨대상 건수 수정 2026-02-06 09:49:30 +09:00
bdce18119f Merge pull request '학습데이터 라벨링 현황 건수 조건 수정, 라벨러, 검수자 목록 수정' (#53) from feat/infer_dev_260107 into develop
Reviewed-on: #53
2026-02-05 18:01:45 +09:00
533d97a573 학습데이터 라벨링 현황 건수 조건 수정, 라벨러, 검수자 목록 수정 2026-02-05 18:00:52 +09:00
3b5536a57e Merge pull request '국유인 연동 경로 확인 TEST' (#52) from feat/infer_dev_260107 into develop
Reviewed-on: #52
2026-02-05 17:00:37 +09:00
3237863542 국유인 연동 경로 확인 TEST 2026-02-05 16:59:49 +09:00
9dd03f3c52 Merge pull request '국유인 API 수정 추가' (#51) from feat/infer_dev_260107 into develop
Reviewed-on: #51
2026-02-05 15:10:34 +09:00
41b227de3f 국유인 API 수정 추가 2026-02-05 15:09:27 +09:00
796591eca6 Merge pull request '국유인 API 수정 추가' (#50) from feat/infer_dev_260107 into develop
Reviewed-on: #50
2026-02-05 15:03:06 +09:00
83e02c4498 국유인 API 수정 추가 2026-02-05 15:02:00 +09:00
825e393e05 Merge pull request '국유인 API 수정 추가' (#49) from feat/infer_dev_260107 into develop
Reviewed-on: #49
2026-02-05 14:58:50 +09:00
d8804e7c9a 국유인 API 수정 추가 2026-02-05 14:58:30 +09:00
1410333829 Merge pull request '국유인 API 수정' (#48) from feat/infer_dev_260107 into develop
Reviewed-on: #48
2026-02-05 14:55:43 +09:00
f326b5f651 국유인 API 수정 2026-02-05 14:55:16 +09:00
d63980476f Merge pull request '라벨링 할당 수정' (#47) from feat/infer_dev_260107 into develop
Reviewed-on: #47
2026-02-05 13:56:01 +09:00
c1b6061e3e 라벨링 할당 수정 2026-02-05 13:55:42 +09:00
ae1693a33c Merge pull request '라벨링 가능 건수 조건 수정' (#46) from feat/infer_dev_260107 into develop
Reviewed-on: #46
2026-02-05 13:49:44 +09:00
71a8f27afc 라벨링 가능 건수 조건 수정 2026-02-05 13:49:18 +09:00
8dfae65bcc Merge pull request 'feat/infer_dev_260107' (#45) from feat/infer_dev_260107 into develop
Reviewed-on: #45
2026-02-04 17:56:37 +09:00
299d1b09a0 국유인 API reqIp, reqEpno 추가, 스케줄러 수정 2026-02-04 17:55:39 +09:00
3461376b35 국유in 등록 로그 추가 2026-02-04 11:13:49 +09:00
872df11844 Merge pull request '국유in 등록 로그 추가' (#44) from feat/infer_dev_260107 into develop
Reviewed-on: #44
2026-02-04 11:10:55 +09:00
17bd89fafa 국유in 등록 로그 추가 2026-02-04 11:10:16 +09:00
f992bbe9ca Merge pull request '국유인, 라벨링 job 각각 분리 작업' (#43) from feat/infer_dev_260107 into develop
Reviewed-on: #43
2026-02-02 19:18:15 +09:00
e5fa99daef 국유인, 라벨링 job 각각 분리 작업 2026-02-02 19:16:26 +09:00
643ea5cf9a Merge pull request 'spotless 적용' (#42) from feat/infer_dev_260107 into develop
Reviewed-on: #42
2026-02-02 17:17:45 +09:00
d563f47abd spotless 적용 2026-02-02 17:17:29 +09:00
bc4b2dbac1 Merge pull request 'feat/infer_dev_260107' (#41) from feat/infer_dev_260107 into develop
Reviewed-on: #41
2026-02-02 17:16:21 +09:00
cf4f79f7ca 추론결과 국유In 등록 벨리데이션 체크 수정 2026-02-02 17:15:57 +09:00
6e9b4196b8 영상관리 count 조건 수정 2026-02-02 17:08:38 +09:00
a4f66f511e 국유인 연동 리턴 타입 수정 okObject 2026-02-02 15:59:05 +09:00
9734a5acb2 국유인 스케줄러 분리 2026-02-02 15:40:17 +09:00
9fb4a25955 국유인 연동 API 실태조사 적합여부 추가 2026-02-02 14:56:04 +09:00
694b2fc31e Merge pull request '인증 예시 아이디 수정' (#40) from feat/infer_dev_260107 into develop
Reviewed-on: #40
2026-02-02 14:01:01 +09:00
3af05bbeef 인증 예시 아이디 수정 2026-02-02 14:00:30 +09:00
fbdda6477c Merge pull request '사번 자리수 6자리 벨리데이션 수정' (#39) from feat/infer_dev_260107 into develop
Reviewed-on: #39
2026-02-02 13:37:13 +09:00
1c2e41ced6 사번 자리수 6자리 벨리데이션 수정 2026-02-02 13:36:53 +09:00
a572089dff Merge pull request '변화지도 레이어 조회 url 수정' (#38) from feat/infer_dev_260107 into develop
Reviewed-on: #38
2026-02-02 12:22:50 +09:00
c52c2ab9bd 변화지도 레이어 조회 url 수정 2026-02-02 12:22:32 +09:00
c6abf7a935 Merge pull request 'feat/infer_dev_260107' (#37) from feat/infer_dev_260107 into develop
Reviewed-on: #37
2026-02-02 12:18:09 +09:00
eb8d798714 Merge remote-tracking branch 'origin/feat/infer_dev_260107' into feat/infer_dev_260107 2026-02-02 12:17:46 +09:00
7e95b53881 변화지도 레이어 조회 url 수정 2026-02-02 12:17:39 +09:00
74b244981b 국유인 연동 API 응답 로직 수정 2026-02-02 12:17:18 +09:00
a9348d9a66 Merge pull request '타일 url 시큐리티 추가' (#36) from feat/infer_dev_260107 into develop
Reviewed-on: #36
2026-02-02 10:31:59 +09:00
699d39d402 타일 url 시큐리티 추가 2026-02-02 10:31:11 +09:00
b877d2a8c9 Merge pull request 'feat/infer_dev_260107' (#35) from feat/infer_dev_260107 into develop
Reviewed-on: #35
2026-02-02 10:28:28 +09:00
e3ae889152 Merge remote-tracking branch 'origin/feat/infer_dev_260107' into feat/infer_dev_260107 2026-02-02 10:28:07 +09:00
20a835cf45 레이어관리 삭제 수정 2026-02-02 10:27:57 +09:00
151012ea28 Merge pull request 'feat/infer_dev_260107' (#34) from feat/infer_dev_260107 into develop
Reviewed-on: #34
2026-02-02 10:14:44 +09:00
c0b5dd99ef spotless 2026-02-02 10:13:58 +09:00
5015a2a437 국유인 연동 API validation 체크 주석 해제 2026-02-02 10:10:52 +09:00
68c68082cf Merge branch 'develop' of ssh://192.168.2.126:2222/MVPTeam/kamco-cd-api into develop 2026-02-01 22:27:49 +09:00
4ce96b72aa prod 2026-02-01 22:27:20 +09:00
0a5c5dfd7d Merge pull request 'crs 타입 수정하기' (#32) from feat/infer_dev_260107 into develop
Reviewed-on: #32
2026-01-30 21:43:05 +09:00
d8d35c3462 crs 타입 수정하기 2026-01-30 21:42:47 +09:00
7442e4ee09 Merge pull request 'feat/infer_dev_260107' (#31) from feat/infer_dev_260107 into develop
Reviewed-on: #31
2026-01-30 21:32:17 +09:00
b77de057f0 영상관리 년도별 타일 crs 추가하기 2026-01-30 21:26:52 +09:00
2559b225d5 레이어관리 수정 2026-01-30 21:09:35 +09:00
d278baed96 Merge pull request 'feat/infer_dev_260107' (#30) from feat/infer_dev_260107 into develop
Reviewed-on: #30
2026-01-30 21:05:04 +09:00
9a00c38cc7 Merge remote-tracking branch 'origin/feat/infer_dev_260107' into feat/infer_dev_260107 2026-01-30 21:03:56 +09:00
e8fa7411d5 레이어관리 수정 2026-01-30 21:03:50 +09:00
71f1f03b89 영상관리 년도별 타일 crs 추가하기 2026-01-30 21:00:15 +09:00
6b0074316f Merge pull request 'feat/infer_dev_260107' (#29) from feat/infer_dev_260107 into develop
Reviewed-on: #29
2026-01-30 19:44:42 +09:00
96d7ea205c properties 커밋 2026-01-30 19:42:37 +09:00
0993ce646a wmts, wms url 추가 2026-01-30 19:40:41 +09:00
f921ef5d0d Merge pull request 'geoserver url 변경' (#28) from feat/infer_dev_260107 into develop
Reviewed-on: #28
2026-01-30 19:16:14 +09:00
302e1ad957 geoserver url 변경 2026-01-30 19:13:02 +09:00
7667620395 Merge pull request 'bbox 5186 transform' (#27) from feat/infer_dev_260107 into develop
Reviewed-on: #27
2026-01-30 18:20:03 +09:00
e11f365bf8 bbox 5186 transform 2026-01-30 18:19:47 +09:00
527acc9839 Merge pull request 'bbox 5186 으로 변환' (#26) from feat/infer_dev_260107 into develop
Reviewed-on: #26
2026-01-30 18:09:46 +09:00
9dd439b920 bbox 5186 으로 변환 2026-01-30 18:09:13 +09:00
407f14d230 Merge pull request 'wmts 수정' (#25) from feat/infer_dev_260107 into develop
Reviewed-on: #25
2026-01-30 17:56:40 +09:00
a42729a475 wmts 수정 2026-01-30 17:56:20 +09:00
4a91d61b7d Merge pull request '추론실행 퍼센트 수정' (#24) from feat/infer_dev_260107 into develop
Reviewed-on: #24
2026-01-30 17:49:40 +09:00
bc5c5b3dd7 추론실행 퍼센트 수정 2026-01-30 17:49:16 +09:00
9d7bbc1b63 Merge pull request 'wmts 수정, 국유인 어제완료된 라벨전송, 전송완료된 리스트 기능 추가' (#23) from feat/infer_dev_260107 into develop
Reviewed-on: #23
2026-01-30 17:44:26 +09:00
60ff43cd3c Merge branch 'feat/infer_dev_260107' of https://kamco.git.gs.dabeeo.com/MVPTeam/kamco-cd-api into feat/infer_dev_260107 2026-01-30 17:43:43 +09:00
ff7654baa7 RestTemplateConfig 수정 2026-01-30 17:43:27 +09:00
21f922a5f4 국유인 어제완료된 라벨전송, 전송완료된 리스트 기능 추가 2026-01-30 17:32:39 +09:00
f46ea62761 Merge pull request 'RestTemplateConfig 수정' (#22) from feat/infer_dev_260107 into develop
Reviewed-on: #22
2026-01-30 17:21:11 +09:00
5ff72f927c RestTemplateConfig 수정 2026-01-30 17:20:53 +09:00
1abc0b93c0 Merge pull request 'RestTemplateConfig 수정, wmts 수정' (#21) from feat/infer_dev_260107 into develop
Reviewed-on: #21
2026-01-30 17:13:30 +09:00
e6ef4c2525 RestTemplateConfig 수정, wmts 수정 2026-01-30 17:13:08 +09:00
4204e48d88 Merge pull request 'feat/infer_dev_260107' (#20) from feat/infer_dev_260107 into develop
Reviewed-on: #20
2026-01-30 16:53:04 +09:00
86e8408f27 RestTemplateConfig 수정 2026-01-30 16:51:27 +09:00
d4fb11deb2 RestTemplateConfig 수정 2026-01-30 16:44:21 +09:00
fa41d41739 Merge pull request 'RestTemplateConfig 수정, 추론실행 수정' (#19) from feat/infer_dev_260107 into develop
Reviewed-on: #19
2026-01-30 16:19:02 +09:00
c2b87ca12a RestTemplateConfig 수정, 추론실행 수정 2026-01-30 16:18:40 +09:00
ee28edd9d0 Merge pull request 'RestTemplateConfig 수정' (#18) from feat/infer_dev_260107 into develop
Reviewed-on: #18
2026-01-30 14:44:05 +09:00
37b7877083 RestTemplateConfig 수정 2026-01-30 14:43:33 +09:00
8555897b77 Merge pull request 'feat/infer_dev_260107' (#17) from feat/infer_dev_260107 into develop
Reviewed-on: #17
2026-01-30 14:19:34 +09:00
3b636a85e6 wmts 수정 2026-01-30 14:18:57 +09:00
d95dc364aa wmts 수정 2026-01-30 14:10:22 +09:00
29556b6a05 등록 post로 수정 2026-01-30 13:33:00 +09:00
df8bac950b 국유인 API 진행중 2026-01-30 13:30:13 +09:00
f3453ab499 레이어관리 수정 2026-01-30 12:04:11 +09:00
fe7b1ed0bd Merge pull request '레이어관리 수정' (#16) from feat/infer_dev_260107 into develop
Reviewed-on: #16
2026-01-30 11:54:34 +09:00
2865b075a2 레이어관리 수정 2026-01-30 11:54:11 +09:00
064c02e21b Merge pull request '년도 1개만 조회하는 타일 API' (#15) from feat/infer_dev_260107 into develop
Reviewed-on: #15
2026-01-30 11:17:59 +09:00
10ab050a1c 년도 1개만 조회하는 타일 API 2026-01-30 11:17:13 +09:00
fd3499a5ec Merge pull request 'feat/infer_dev_260107' (#14) from feat/infer_dev_260107 into develop
Reviewed-on: #14
2026-01-30 11:05:30 +09:00
babf35142a 년도별 타일 API 2026-01-30 11:04:14 +09:00
ceebbdfa50 변화지도, 라벨링 맵 리스트 uuid, rawjson 추가 2026-01-30 10:51:22 +09:00
686cf03524 Merge pull request 'geoserver 등록 수정' (#13) from feat/infer_dev_260107 into develop
Reviewed-on: #13
2026-01-30 10:23:20 +09:00
af19ca905b geoserver 등록 수정 2026-01-30 10:22:39 +09:00
ee9914a5f3 Merge pull request 'geoserver 등록 수정' (#12) from feat/infer_dev_260107 into develop
Reviewed-on: #12
2026-01-30 10:21:25 +09:00
3686f1d248 geoserver 등록 수정 2026-01-30 10:21:06 +09:00
b3e90c9f2b Merge pull request 'feat/infer_dev_260107' (#11) from feat/infer_dev_260107 into develop
Reviewed-on: #11
2026-01-30 09:50:59 +09:00
565e76a4e2 Merge remote-tracking branch 'origin/feat/infer_dev_260107' into feat/infer_dev_260107 2026-01-30 09:50:38 +09:00
e379c49453 geoserver 등록 수정 2026-01-30 09:50:32 +09:00
156b7a312d Merge pull request '레이어관리 - 변화지도,라벨링툴 맵 리스트' (#10) from feat/infer_dev_260107 into develop
Reviewed-on: #10
2026-01-29 20:56:34 +09:00
9bec466d7c 레이어관리 - 변화지도,라벨링툴 맵 리스트 2026-01-29 20:56:14 +09:00
cfed31656a Merge pull request 'ai 주소 변경' (#9) from feat/infer_dev_260107 into develop
Reviewed-on: #9
2026-01-29 20:12:17 +09:00
cea1d82bd9 ai 주소 변경 2026-01-29 20:12:00 +09:00
14e8a6476f Merge pull request '영상관리 등록 년도 tile 추가' (#8) from feat/infer_dev_260107 into develop
Reviewed-on: #8
2026-01-29 19:03:39 +09:00
e4550d1e71 영상관리 등록 년도 tile 추가 2026-01-29 19:03:08 +09:00
ae6de0c030 Merge pull request 'ai 주소 변경' (#7) from feat/infer_dev_260107 into develop
Reviewed-on: #7
2026-01-29 17:34:32 +09:00
38ad63172a ai 주소 변경 2026-01-29 17:34:15 +09:00
4036f88296 Merge pull request 'feat/infer_dev_260107' (#6) from feat/infer_dev_260107 into develop
Reviewed-on: #6
2026-01-29 16:25:08 +09:00
586c0f7e9a Merge remote-tracking branch 'origin/feat/infer_dev_260107' into feat/infer_dev_260107 2026-01-29 16:23:50 +09:00
32cb47cfc2 geojson 파일생성 api 추가 2026-01-29 16:23:44 +09:00
23a096a600 라벨링 툴 pnu 정보 list string으로 수정 2026-01-29 16:23:13 +09:00
28718c4218 Merge pull request 'feat/infer_dev_260107' (#5) from feat/infer_dev_260107 into develop
Reviewed-on: #5
2026-01-29 14:42:55 +09:00
480b016f33 geoserver url 변경 2026-01-29 14:42:21 +09:00
8563204e59 영상관리 folder-list 수정 2026-01-29 12:49:16 +09:00
e31913ff9a cls model 적용 2026-01-29 12:31:52 +09:00
54c92842d4 Merge pull request '헬스체크 시큐리티 설정 추가' (#4) from feat/infer_dev_260107 into develop
Reviewed-on: #4
2026-01-29 12:30:48 +09:00
f87a714892 헬스체크 시큐리티 설정 추가 2026-01-29 12:30:12 +09:00
c83c540dfb Merge pull request 'feat/infer_dev_260107' (#3) from feat/infer_dev_260107 into develop
Reviewed-on: #3
2026-01-29 12:16:10 +09:00
dd1284f5c0 Merge pull request '추론실행 수정' (#2) from feat/infer_dev_260107 into develop
Reviewed-on: #2
2026-01-29 10:35:15 +09:00
385ada3291 Merge pull request 'feat/infer_dev_260107' (#1) from feat/infer_dev_260107 into develop
Reviewed-on: #1
2026-01-29 10:31:31 +09:00
105 changed files with 4667 additions and 1224 deletions

24
Dockerfile-prod Normal file
View File

@@ -0,0 +1,24 @@
# 1단계에서 만든 로컬 베이스 이미지를 사용
FROM 192.168.2.73:18082/kamco-cd/base-java21-gdal:1.0
FROM 192.168.2.73:18082/kamco-cd/base-java21-gdal:1.0
# 사용자 설정 (앱 별로 다를 수 있으므로 여기에 유지)
ARG UID=1000
ARG GID=1000
RUN groupadd -g ${GID} kcomu \
&& useradd -u ${UID} -g ${GID} -m kcomu
USER kcomu
# 작업 디렉토리 설정
WORKDIR /app
# JAR 파일 복사 (Jenkins에서 빌드된 ROOT.jar)
COPY build/libs/ROOT.jar app.jar
# 포트 노출
EXPOSE 8080
# 애플리케이션 실행
# dev 프로파일로 실행
ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=prod", "app.jar"]

35
docker-compose-prod.yml Normal file
View File

@@ -0,0 +1,35 @@
services:
kamco-changedetection-api:
build:
context: .
dockerfile: Dockerfile-prod
args:
UID: 1000 # manager01 UID
GID: 1000 # manager01 GID
image: kamco-changedetection-api:${IMAGE_TAG:-latest}
container_name: kamco-changedetection-api
user: "1000:1000"
ports:
- "7100:8080"
environment:
- SPRING_PROFILES_ACTIVE=dev
- TZ=Asia/Seoul
volumes:
- /mnt/nfs_share/images:/app/original-images
- /mnt/nfs_share/model_output:/app/model-outputs
- /mnt/nfs_share/train_dataset:/app/train-dataset
- /mnt/nfs_share/tmp:/app/tmp
- /kamco-nfs:/kamco-nfs
networks:
- kamco-cds
restart: unless-stopped
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8080/monitor/health" ]
interval: 10s
timeout: 5s
retries: 5
start_period: 40s
networks:
kamco-cds:
external: true

View File

@@ -0,0 +1,48 @@
package com.kamco.cd.kamcoback.common.download;
import com.kamco.cd.kamcoback.common.download.dto.DownloadSpec;
import com.kamco.cd.kamcoback.common.utils.UserUtil;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@Service
@RequiredArgsConstructor
public class DownloadExecutor {
private final UserUtil userUtil;
public ResponseEntity<StreamingResponseBody> stream(DownloadSpec spec) throws IOException {
if (!Files.isReadable(spec.filePath())) {
return ResponseEntity.notFound().build();
}
StreamingResponseBody body =
os -> {
try (InputStream in = Files.newInputStream(spec.filePath())) {
in.transferTo(os);
os.flush();
} catch (Exception e) {
// 고용량은 중간 끊김 흔하니까 throw 금지
}
};
String fileName =
spec.downloadName() != null
? spec.downloadName()
: spec.filePath().getFileName().toString();
return ResponseEntity.ok()
.contentType(
spec.contentType() != null ? spec.contentType() : MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
.body(body);
}
}

View File

@@ -0,0 +1,19 @@
package com.kamco.cd.kamcoback.common.download;
import org.springframework.util.AntPathMatcher;
public final class DownloadPaths {
private DownloadPaths() {}
public static final String[] PATTERNS = {
"/api/inference/download/**", "/api/training-data/stage/download/**"
};
public static boolean matches(String uri) {
AntPathMatcher m = new AntPathMatcher();
for (String p : PATTERNS) {
if (m.match(p, uri)) return true;
}
return false;
}
}

View File

@@ -0,0 +1,12 @@
package com.kamco.cd.kamcoback.common.download.dto;
import java.nio.file.Path;
import java.util.UUID;
import org.springframework.http.MediaType;
public record DownloadSpec(
UUID uuid, // 다운로드 식별(로그/정책용)
Path filePath, // 실제 파일 경로
String downloadName, // 사용자에게 보일 파일명
MediaType contentType // 보통 OCTET_STREAM
) {}

View File

@@ -0,0 +1,27 @@
package com.kamco.cd.kamcoback.common.enums;
import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose;
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
import lombok.AllArgsConstructor;
import lombok.Getter;
@CodeExpose
@Getter
@AllArgsConstructor
public enum CrsType implements EnumType {
EPSG_3857("Web Mercator, 웹지도 미터(EPSG:900913 동일)"),
EPSG_4326("WGS84 위경도, GeoJSON/OSM 기본"),
EPSG_5186("Korea 2000 중부 TM, 한국 SHP");
private final String desc;
@Override
public String getId() {
return name();
}
@Override
public String getText() {
return desc;
}
}

View File

@@ -54,8 +54,8 @@ public class ExternalJarRunner {
public void run(String jarPath, String register, String layer) {
List<String> args = new ArrayList<>();
addArg(args, "register", register);
addArg(args, "layer", layer);
addArg(args, "upload-shp", register);
// addArg(args, "layer", layer);
execJar(jarPath, args);
}

View File

@@ -30,12 +30,14 @@ import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.gce.geotiff.GeoTiffReader;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.multipart.MultipartFile;
@Slf4j
public class FIleChecker {
static SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -138,7 +140,9 @@ public class FIleChecker {
// null을 넣으면 전체 영역을 읽지 않고 메타데이터 위주로 체크하여 빠름
GridCoverage2D coverage = reader.read(null);
if (coverage == null) return false;
if (coverage == null) {
return false;
}
// 3. GIS 필수 정보(좌표계)가 있는지 확인
// if (coverage.getCoordinateReferenceSystem() == null) {
@@ -152,7 +156,9 @@ public class FIleChecker {
return false;
} finally {
// 리소스 해제 (필수)
if (reader != null) reader.dispose();
if (reader != null) {
reader.dispose();
}
}
}
@@ -296,7 +302,8 @@ public class FIleChecker {
boolean isValid =
!NameValidator.containsKorean(folderNm)
&& !NameValidator.containsWhitespaceRegex(folderNm);
&& !NameValidator.containsWhitespaceRegex(folderNm)
&& !parentFolderNm.equals("kamco-nfs");
File file = new File(fullPath);
int childCnt = getChildFolderCount(file);
@@ -586,7 +593,9 @@ public class FIleChecker {
}
public static boolean checkExtensions(String fileName, String ext) {
if (fileName == null) return false;
if (fileName == null) {
return false;
}
if (!fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase().equals(ext)) {
return false;
@@ -690,6 +699,7 @@ public class FIleChecker {
@Schema(name = "Folder", description = "폴더 정보")
@Getter
public static class Folder {
private final String folderNm;
private final String parentFolderNm;
private final String parentPath;

View File

@@ -5,11 +5,9 @@ import java.net.InetAddress;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
public class NetUtils {
@@ -56,9 +54,8 @@ public class NetUtils {
public HttpHeaders jsonHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
headers.set(HttpHeaders.ACCEPT, "application/json;charset=UTF-8");
headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
return headers;
}
}

View File

@@ -1,8 +1,8 @@
package com.kamco.cd.kamcoback.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.auth.CustomUserDetails;
import com.kamco.cd.kamcoback.common.utils.HeaderUtil;
import com.kamco.cd.kamcoback.common.utils.UserUtil;
import com.kamco.cd.kamcoback.config.api.ApiLogFunction;
import com.kamco.cd.kamcoback.menu.dto.MenuDto;
import com.kamco.cd.kamcoback.menu.service.MenuService;
@@ -17,7 +17,6 @@ import java.util.Objects;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@@ -27,24 +26,39 @@ public class FileDownloadInteceptor implements HandlerInterceptor {
private final AuditLogRepository auditLogRepository;
private final MenuService menuService;
private final UserUtil userUtil;
@Autowired private ObjectMapper objectMapper;
public FileDownloadInteceptor(AuditLogRepository auditLogRepository, MenuService menuService) {
public FileDownloadInteceptor(
AuditLogRepository auditLogRepository, MenuService menuService, UserUtil userUtil) {
this.auditLogRepository = auditLogRepository;
this.menuService = menuService;
this.userUtil = userUtil;
}
@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!request.getRequestURI().contains("/download")) return true;
if (request.getDispatcherType() != jakarta.servlet.DispatcherType.REQUEST) {
return true;
}
saveLog(request, response);
return true;
}
private void saveLog(HttpServletRequest request, HttpServletResponse response) {
// 파일 다운로드 API만 필터링
if (!request.getRequestURI().contains("/download")) {
return;
}
Long userId = extractUserId(request);
Long userId = userUtil.getId();
String ip = ApiLogFunction.getClientIp(request);
List<?> list = menuService.getFindAll();
@@ -81,12 +95,4 @@ public class FileDownloadInteceptor implements HandlerInterceptor {
auditLogRepository.save(log);
}
private Long extractUserId(HttpServletRequest request) {
if (request.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth
&& auth.getPrincipal() instanceof CustomUserDetails userDetails) {
return userDetails.getMember().getId();
}
return null;
}
}

View File

@@ -465,8 +465,7 @@ public class GlobalExceptionHandler {
String stackTraceStr =
Arrays.stream(stackTrace)
.map(StackTraceElement::toString)
.collect(Collectors.joining("\n"))
.substring(0, 255);
.collect(Collectors.joining("\n"));
String actionType = HeaderUtil.get(request, "kamco-action-type");

View File

@@ -3,6 +3,8 @@ package com.kamco.cd.kamcoback.config;
import com.kamco.cd.kamcoback.auth.CustomAuthenticationProvider;
import com.kamco.cd.kamcoback.auth.JwtAuthenticationFilter;
import com.kamco.cd.kamcoback.auth.MenuAuthorizationManager;
import com.kamco.cd.kamcoback.common.download.DownloadPaths;
import jakarta.servlet.DispatcherType;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
@@ -44,7 +46,10 @@ public class SecurityConfig {
.authorizeHttpRequests(
auth ->
auth
// .requestMatchers("/chunk_upload_test.html").authenticated()
.requestMatchers("/monitor/health", "/monitor/health/**")
.permitAll()
// 맵시트 영역 전체 허용 (우선순위 최상단)
.requestMatchers("/api/mapsheet/**")
@@ -66,13 +71,25 @@ public class SecurityConfig {
.requestMatchers("/api/test/review")
.hasAnyRole("ADMIN", "REVIEWER")
// ASYNC/ERROR 재디스패치는 막지 않기 (다운로드/스트리밍에서 필수)
.dispatcherTypeMatchers(DispatcherType.ASYNC, DispatcherType.ERROR)
.permitAll()
// 다운로드는 인증 필요
.requestMatchers(HttpMethod.GET, DownloadPaths.PATTERNS)
.authenticated()
// 메뉴 등록 ADMIN만 가능
.requestMatchers(HttpMethod.POST, "/api/menu/auth")
.hasAnyRole("ADMIN")
// 에러 경로는 항상 허용 (이미 있지만 유지)
.requestMatchers("/error")
.permitAll()
// preflight 허용
.requestMatchers(HttpMethod.OPTIONS, "/**")
.permitAll() // preflight 허용
.permitAll()
.requestMatchers(
"/api/auth/signin",
"/api/auth/refresh",
@@ -84,8 +101,12 @@ public class SecurityConfig {
"/api/model/file-chunk-upload",
"/api/upload/file-chunk-upload",
"/api/upload/chunk-upload-complete",
"/api/change-detection/**")
"/api/change-detection/**",
"/api/layer/map/**",
"/api/layer/tile-url",
"/api/layer/tile-url-year")
.permitAll()
// 로그인한 사용자만 가능 IAM
.requestMatchers(
"/api/user/**",
@@ -94,16 +115,11 @@ public class SecurityConfig {
"/api/training-data/label/**",
"/api/training-data/review/**")
.authenticated()
.anyRequest()
.access(menuAuthorizationManager)
// .authenticated()
)
.addFilterBefore(
jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter
.class) // 요청 들어오면 먼저 JWT 토큰 검사 후 security context 에 사용자 정보 저장.
;
// 나머지는 메뉴권한
.anyRequest()
.access(menuAuthorizationManager))
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@@ -114,23 +130,18 @@ public class SecurityConfig {
return configuration.getAuthenticationManager();
}
/**
* CORS 설정
*
* @return
*/
/** CORS 설정 */
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration(); // CORS 객체 생성
config.setAllowedOriginPatterns(List.of("*")); // 도메인 허용
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(List.of("*"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*")); // 헤더요청 Authorization, Content-Type, X-Custom-Header
config.setAllowCredentials(true); // 쿠키, Authorization 헤더, Bearer Token 등 자격증명 포함 요청을 허용할지 설정
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
config.setExposedHeaders(List.of("Content-Disposition"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
/** "/**" → 모든 API 경로에 대해 이 CORS 규칙을 적용 /api/** 같이 특정 경로만 지정 가능. */
source.registerCorsConfiguration("/**", config); // CORS 정책을 등록
source.registerCorsConfiguration("/**", config);
return source;
}

View File

@@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.kamco.cd.kamcoback.common.download.DownloadPaths;
import com.kamco.cd.kamcoback.common.utils.geometry.GeometryDeserializer;
import com.kamco.cd.kamcoback.common.utils.geometry.GeometrySerializer;
import org.locationtech.jts.geom.Geometry;
@@ -39,9 +40,6 @@ public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry
.addInterceptor(fileDownloadInteceptor)
.addPathPatterns("/api/inference/download/**") // 추론 파일 다운로드
.addPathPatterns("/api/training-data/stage/download/**"); // 학습데이터 다운로드
registry.addInterceptor(fileDownloadInteceptor).addPathPatterns(DownloadPaths.PATTERNS);
}
}

View File

@@ -1,5 +1,6 @@
package com.kamco.cd.kamcoback.config.api;
import com.kamco.cd.kamcoback.common.download.DownloadPaths;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@@ -16,6 +17,14 @@ public class ApiLogFilter extends OncePerRequestFilter {
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String uri = request.getRequestURI();
if (DownloadPaths.matches(uri)) {
filterChain.doFilter(request, response);
return;
}
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);

View File

@@ -1,72 +1,120 @@
package com.kamco.cd.kamcoback.config.resttemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.charset.StandardCharsets;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
@RequiredArgsConstructor
@Component
@Log4j2
@RequiredArgsConstructor
public class ExternalHttpClient {
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
public <T> ExternalCallResult<T> call(
String url, HttpMethod method, Object body, HttpHeaders headers, Class<T> responseType) {
HttpEntity<Object> entity = new HttpEntity<>(body, headers);
// responseType 기반으로 Accept 동적 세팅
HttpHeaders resolvedHeaders = resolveHeaders(headers, responseType);
logRequestBody(body);
// 요청 로그
log.info("[HTTP-REQ] {} {}", method, url);
if (body != null) {
log.debug("[HTTP-REQ-BODY] {}", body);
}
HttpEntity<Object> entity = new HttpEntity<>(body, resolvedHeaders);
try {
// String: raw bytes -> UTF-8 string
if (responseType == String.class) {
ResponseEntity<byte[]> res = restTemplate.exchange(url, method, entity, byte[].class);
String raw =
(res.getBody() == null) ? null : new String(res.getBody(), StandardCharsets.UTF_8);
@SuppressWarnings("unchecked")
T casted = (T) raw;
return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null);
}
// byte[]: raw bytes로 받고, JSON이면 에러로 처리
if (responseType == byte[].class) {
ResponseEntity<byte[]> res = restTemplate.exchange(url, method, entity, byte[].class);
MediaType ct = res.getHeaders().getContentType();
byte[] bytes = res.getBody();
if (isJsonLike(ct)) {
String err = (bytes == null) ? null : new String(bytes, StandardCharsets.UTF_8);
return new ExternalCallResult<>(res.getStatusCodeValue(), false, null, err);
}
@SuppressWarnings("unchecked")
T casted = (T) bytes;
return new ExternalCallResult<>(res.getStatusCodeValue(), true, casted, null);
}
// DTO 등: 일반 역직렬화
ResponseEntity<T> res = restTemplate.exchange(url, method, entity, responseType);
return new ExternalCallResult<>(res.getStatusCodeValue(), true, res.getBody(), null);
int code = res.getStatusCodeValue();
// 응답 로그
log.info("[HTTP-RES] {} {} -> {}", method, url, code);
log.debug("[HTTP-RES-BODY] {}", res.getBody());
return new ExternalCallResult<>(code, code >= 200 && code < 300, res.getBody());
} catch (HttpClientErrorException.NotFound e) {
log.info("[HTTP-RES] {} {} -> 404 (Not Found)", method, url);
log.debug("[HTTP-RES-BODY] {}", e.getResponseBodyAsString());
return new ExternalCallResult<>(404, false, null);
} catch (HttpClientErrorException e) {
// 기타 4xx
log.warn(
"[HTTP-ERR] {} {} -> {} body={}",
method,
url,
e.getStatusCode().value(),
e.getResponseBodyAsString());
throw e;
} catch (HttpServerErrorException e) {
// 5xx
log.error(
"[HTTP-ERR] {} {} -> {} body={}",
method,
url,
e.getStatusCode().value(),
e.getResponseBodyAsString());
throw e;
} catch (HttpStatusCodeException e) {
return new ExternalCallResult<>(
e.getStatusCode().value(), false, null, e.getResponseBodyAsString());
}
}
public record ExternalCallResult<T>(int statusCode, boolean success, T body) {}
// 기존 resolveJsonHeaders를 "동적"으로 교체
private HttpHeaders resolveHeaders(HttpHeaders headers, Class<?> responseType) {
// 원본 headers를 그대로 쓰면 외부에서 재사용할 때 사이드이펙트 날 수 있어서 복사 권장
HttpHeaders h = (headers == null) ? new HttpHeaders() : new HttpHeaders(headers);
// 요청 바디 기본은 JSON이라고 가정 (필요하면 호출부에서 덮어쓰기)
if (h.getContentType() == null) {
h.setContentType(MediaType.APPLICATION_JSON);
}
// 호출부에서 Accept를 명시했으면 존중
if (h.getAccept() != null && !h.getAccept().isEmpty()) {
return h;
}
// responseType 기반 Accept 자동 지정
if (responseType == byte[].class) {
h.setAccept(
List.of(
MediaType.APPLICATION_OCTET_STREAM,
MediaType.valueOf("application/zip"),
MediaType.APPLICATION_JSON // 실패(JSON 에러 바디) 대비
));
} else {
h.setAccept(List.of(MediaType.APPLICATION_JSON));
}
return h;
}
private boolean isJsonLike(MediaType ct) {
if (ct == null) return false;
return ct.includes(MediaType.APPLICATION_JSON)
|| "application/problem+json".equalsIgnoreCase(ct.toString());
}
private void logRequestBody(Object body) {
try {
if (body != null) {
log.info("[HTTP-REQ-BODY-JSON] {}", objectMapper.writeValueAsString(body));
}
} catch (Exception e) {
log.warn("[HTTP-REQ-BODY-JSON] serialize failed: {}", e.getMessage());
}
}
public record ExternalCallResult<T>(int statusCode, boolean success, T body, String errBody) {}
}

View File

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

View File

@@ -1,12 +1,15 @@
package com.kamco.cd.kamcoback.config.resttemplate;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
@Log4j2
public class RetryInterceptor implements ClientHttpRequestInterceptor {
private static final int MAX_RETRY = 3;
@@ -20,21 +23,25 @@ public class RetryInterceptor implements ClientHttpRequestInterceptor {
for (int attempt = 1; attempt <= MAX_RETRY; attempt++) {
try {
// HTTP 응답을 받으면(2xx/4xx/5xx 포함) 그대로 반환
return execution.execute(request, body);
log.info("[WIRE-REQ] {} {}", request.getMethod(), request.getURI());
log.info("[WIRE-REQ-HEADERS] {}", request.getHeaders());
log.info("[WIRE-REQ-BODY] {}", new String(body, StandardCharsets.UTF_8));
ClientHttpResponse response = execution.execute(request, body);
log.info("[WIRE-RES-STATUS] {}", response.getStatusCode());
return response;
} catch (IOException e) {
// 네트워크/타임아웃 등 I/O 예외만 재시도
lastException = e;
log.error("[WIRE-IO-ERR] attempt={} msg={}", attempt, e.getMessage(), e);
}
// 마지막 시도가 아니면 대기
if (attempt < MAX_RETRY) {
sleep();
}
}
// 마지막 예외를 그대로 던져서 원인이 로그에 남게 함
throw lastException;
}

View File

@@ -1,4 +1,4 @@
package com.kamco.cd.kamcoback.config;
package com.kamco.cd.kamcoback.config.swagger;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.security.SecurityScheme;

View File

@@ -0,0 +1,97 @@
package com.kamco.cd.kamcoback.config.swagger;
import jakarta.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import org.springdoc.core.properties.SwaggerUiConfigProperties;
import org.springdoc.core.properties.SwaggerUiOAuthProperties;
import org.springdoc.core.providers.ObjectMapperProvider;
import org.springdoc.webmvc.ui.SwaggerIndexPageTransformer;
import org.springdoc.webmvc.ui.SwaggerIndexTransformer;
import org.springdoc.webmvc.ui.SwaggerWelcomeCommon;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.resource.ResourceTransformerChain;
import org.springframework.web.servlet.resource.TransformedResource;
@Profile({"local", "dev"})
@Configuration
public class SwaggerUiAutoAuthConfig {
@Bean
@Primary
public SwaggerIndexTransformer swaggerIndexTransformer(
SwaggerUiConfigProperties swaggerUiConfigProperties,
SwaggerUiOAuthProperties swaggerUiOAuthProperties,
SwaggerWelcomeCommon swaggerWelcomeCommon,
ObjectMapperProvider objectMapperProvider) {
SwaggerIndexPageTransformer delegate =
new SwaggerIndexPageTransformer(
swaggerUiConfigProperties,
swaggerUiOAuthProperties,
swaggerWelcomeCommon,
objectMapperProvider);
return new SwaggerIndexTransformer() {
private static final String TOKEN_KEY = "SWAGGER_ACCESS_TOKEN";
@Override
public Resource transform(
HttpServletRequest request, Resource resource, ResourceTransformerChain chain) {
try {
// 1) springdoc 기본 변환 먼저 적용
Resource transformed = delegate.transform(request, resource, chain);
String html =
new String(transformed.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
String loginPathContains = "/api/auth/signin";
String inject =
"""
tagsSorter: (a, b) => {
const TOP = '인증(Auth)';
if (a === TOP && b !== TOP) return -1;
if (b === TOP && a !== TOP) return 1;
return a.localeCompare(b);
},
requestInterceptor: (req) => {
const token = localStorage.getItem('%s');
if (token) {
req.headers = req.headers || {};
req.headers['Authorization'] = 'Bearer ' + token;
}
return req;
},
responseInterceptor: async (res) => {
try {
const isLogin = (res?.url?.includes('%s') && res?.status === 200);
if (isLogin) {
const text = (typeof res.data === 'string') ? res.data : JSON.stringify(res.data);
const json = JSON.parse(text);
const token = json?.data?.accessToken;
if (token) {
localStorage.setItem('%s', token);
}
}
} catch (e) {}
return res;
},
"""
.formatted(TOKEN_KEY, loginPathContains, TOKEN_KEY);
html = html.replace("SwaggerUIBundle({", "SwaggerUIBundle({\n" + inject);
return new TransformedResource(transformed, html.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
// 실패 시 원본 반환(문서 깨짐 방지)
return resource;
}
}
};
}
}

View File

@@ -1,15 +1,21 @@
package com.kamco.cd.kamcoback.gukyuin;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ChnDetectMastReqDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ChngDetectMastSearchDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ResReturn;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LabelSendDto;
import com.kamco.cd.kamcoback.gukyuin.dto.DetectMastDto.Basic;
import com.kamco.cd.kamcoback.gukyuin.dto.DetectMastDto.DetectMastReq;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GukYuinLinkableRes;
import com.kamco.cd.kamcoback.gukyuin.service.GukYuinApiService;
import com.kamco.cd.kamcoback.scheduler.service.GukYuinApiLabelJobService;
import com.kamco.cd.kamcoback.scheduler.service.GukYuinApiPnuJobService;
import com.kamco.cd.kamcoback.scheduler.service.GukYuinApiStatusJobService;
import com.kamco.cd.kamcoback.scheduler.service.GukYuinApiStbltJobService;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
@@ -18,6 +24,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
@@ -35,6 +43,10 @@ import org.springframework.web.bind.annotation.RestController;
public class GukYuinApiController {
private final GukYuinApiService gukYuinApiService;
private final GukYuinApiPnuJobService gukYuinApiPnuJobService;
private final GukYuinApiStatusJobService gukYuinApiStatusJobService;
private final GukYuinApiLabelJobService gukYuinApiLabelJobService;
private final GukYuinApiStbltJobService gukYuinApiStbltJobService;
/** 탐지결과 등록 */
@Operation(summary = "탐지결과 등록", description = "탐지결과 등록")
@@ -51,7 +63,7 @@ public class GukYuinApiController {
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/chn/mast/regist")
public ApiResponseDto<ChngDetectMastDto.Basic> regist(
public ApiResponseDto<ChngDetectMastDto.RegistResDto> regist(
@RequestBody @Valid ChngDetectMastDto.ChnDetectMastReqDto chnDetectMastReq) {
return ApiResponseDto.ok(gukYuinApiService.regist(chnDetectMastReq));
}
@@ -70,7 +82,7 @@ public class GukYuinApiController {
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/chn/mast/remove")
public ApiResponseDto<ResReturn> remove(
public ApiResponseDto<ChngDetectMastDto.RemoveResDto> remove(
@RequestBody @Valid ChngDetectMastDto.ChnDetectMastReqDto chnDetectMastReq) {
return ApiResponseDto.ok(gukYuinApiService.remove(chnDetectMastReq));
}
@@ -133,9 +145,9 @@ public class GukYuinApiController {
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
public ApiResponseDto<ChngDetectMastDto.ResultDto> selectChangeDetectionList(
public ApiResponseDto<ChngDetectMastDto.ResultDto> selectChangeDetectionDetail(
@PathVariable String chnDtctMstId) {
return ApiResponseDto.ok(gukYuinApiService.list(chnDtctMstId));
return ApiResponseDto.ok(gukYuinApiService.detail(chnDtctMstId));
}
@Operation(summary = "국유in연동 가능여부 확인", description = "국유in연동 가능여부 확인")
@@ -183,6 +195,29 @@ public class GukYuinApiController {
return ApiResponseDto.ok(gukYuinApiService.findChnContList(chnDtctId, pageIndex, pageSize));
}
@Operation(summary = "탐지객체 조회 (탐지객체 1건 조회)", description = "탐지객체 조회 (탐지객체 1건 조회)")
@GetMapping("/chn/cont/{chnDtctId}/objt/{chnDtctObjtId}")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "목록 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Basic.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
public ApiResponseDto<ChngDetectContDto.ResultContDto> findChnPnuToContObject(
@PathVariable String chnDtctId,
@PathVariable String chnDtctObjtId,
@RequestParam(defaultValue = "0") Integer pageIndex,
@RequestParam(defaultValue = "10") Integer pageSize) {
return ApiResponseDto.ok(
gukYuinApiService.findChnPnuToContObject(chnDtctId, chnDtctObjtId, pageIndex, pageSize));
}
@Operation(summary = "탐지객체 조회 (PNU에 해당하는 탐지객체)", description = "탐지객체 조회 (PNU에 해당하는 탐지객체)")
@GetMapping("/chn/cont/{chnDtctId}/pnu/{pnu}")
@ApiResponses(
@@ -235,8 +270,96 @@ public class GukYuinApiController {
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/rlb/objt/{chnDtctObjtId}/lbl/{lblYn}")
public ApiResponseDto<ResReturn> updateChnDtctObjtLabelingYn(
public ApiResponseDto<ChngDetectContDto.ResultLabelDto> updateChnDtctObjtLabelingYn(
@PathVariable String chnDtctObjtId, @PathVariable String lblYn) {
return ApiResponseDto.ok(gukYuinApiService.updateChnDtctObjtLabelingYn(chnDtctObjtId, lblYn));
}
@Operation(summary = "국유in연동 등록", description = "국유in연동 등록")
@PostMapping("/mast/reg/{uuid}")
public ApiResponseDto<ResponseObj> connectChnMastRegist(
@Parameter(description = "uuid", example = "7a593d0e-76a8-4b50-8978-9af1fbe871af")
@PathVariable
UUID uuid) {
return ApiResponseDto.okObject(gukYuinApiService.connectChnMastRegist(uuid));
}
@Operation(summary = "라벨 전송 완료 리스트", description = "라벨 전송 완료 리스트")
@GetMapping("/label/send-list")
public ApiResponseDto<List<LabelSendDto>> findLabelingCompleteSendList(
@Parameter(description = "어제 날짜", example = "2026-01-29") LocalDate yesterday) {
return ApiResponseDto.ok(gukYuinApiService.findLabelingCompleteSendList(yesterday));
}
@Operation(summary = "탐지객체 적합여부 조회 (리스트조회)", description = "탐지객체 적합여부 조회 (리스트조회)")
@GetMapping("/rlb/dtct/{chnDtctId}")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "목록 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Basic.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
public ApiResponseDto<ChngDetectMastDto.RlbDtctDto> findRlbDtctList(
@PathVariable String chnDtctId,
@Parameter(description = "날짜(기본은 어제 날짜)") @RequestParam(defaultValue = "20260205")
String yyyymmdd) {
return ApiResponseDto.ok(gukYuinApiService.findRlbDtctList(chnDtctId, yyyymmdd));
}
@Operation(summary = "탐지객체 적합여부 조회 (객체별 조회)", description = "탐지객체 적합여부 조회 (객체별 조회)")
@GetMapping("/rlb/objt/{chnDtctObjtId}")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "목록 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Basic.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
public ApiResponseDto<ChngDetectMastDto.RlbDtctDto> findRlbDtctObject(
@PathVariable String chnDtctObjtId) {
return ApiResponseDto.ok(gukYuinApiService.findRlbDtctObject(chnDtctObjtId));
}
@Hidden
@Operation(summary = "job test pnu", description = "job test pnu")
@GetMapping("/job-test/pnu")
public ApiResponseDto<Void> findGukYuinContListPnuUpdate() {
gukYuinApiPnuJobService.findGukYuinContListPnuUpdate();
return ApiResponseDto.ok(null);
}
@Hidden
@Operation(summary = "job test status", description = "job test status")
@GetMapping("/job-test/status")
public ApiResponseDto<Void> findGukYuinMastCompleteYn() {
gukYuinApiStatusJobService.findGukYuinMastCompleteYn();
return ApiResponseDto.ok(null);
}
@Hidden
@Operation(summary = "job test label", description = "job test label")
@GetMapping("/job-test/label")
public ApiResponseDto<Void> findLabelingCompleteSend() {
gukYuinApiLabelJobService.findLabelingCompleteSend();
return ApiResponseDto.ok(null);
}
@Hidden
@Operation(summary = "job test stblt", description = "job test stblt")
@GetMapping("/job-test/stblt")
public ApiResponseDto<Void> findGukYuinEligibleForSurvey() {
gukYuinApiStbltJobService.findGukYuinEligibleForSurvey();
return ApiResponseDto.ok(null);
}
}

View File

@@ -114,4 +114,38 @@ public class ChngDetectContDto {
private List<DtoPnuDetectMpng> result;
private Boolean success;
}
@Schema(name = "ResultLabelDto", description = "ResultLabelDto list 리턴 형태")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class ResultLabelDto {
private Integer code;
private String message;
private DtoPnuDetectMpng result;
private Boolean success;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class ReqInfo {
private String reqIp;
private String reqEpno;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class StbltResult {
private String stbltYn;
private String incyCd;
private String incyCmnt;
}
}

View File

@@ -1,6 +1,7 @@
package com.kamco.cd.kamcoback.gukyuin.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.ZonedDateTime;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -66,13 +67,30 @@ public class ChngDetectMastDto {
@AllArgsConstructor
public static class ChnDetectMastReqDto {
private String cprsYr; // 비교년도 2023
private String crtrYr; // 기준년도 2024
private String chnDtctSno; // 차수 (1 | 2 | ...)
private String chnDtctId; // 탐지아이디. UUID를 기반으로 '-'를 제거하고 대문자/숫자로 구성
private String pathNm; // 탐지결과 절대경로명 /kamco_nas/export/{chnDtctId}
private String reqEpno; // 사원번호
private String reqIp; // 사원아이피
@Schema(description = "비교년도", example = "2023")
private String cprsYr;
@Schema(description = "기준년도", example = "2024")
private String crtrYr;
@Schema(description = "차수", example = "1")
private String chnDtctSno;
@Schema(
description = "탐지아이디, UUID를 기반으로 '-'를 제거하고 대문자/숫자로 구성",
example = "D5F192EC76D34F6592035BE63A84F591")
private String chnDtctId;
@Schema(
description = "탐지결과 절대경로명 /kamco_nas/export/{chnDtctId}",
example = "/kamco-nfs/dataset/export/D5F192EC76D34F6592035BE63A84F591")
private String pathNm;
@Schema(description = "사원번호", example = "123456")
private String reqEpno;
@Schema(description = "사원아이피", example = "127.0.0.1")
private String reqIp;
}
@Getter
@@ -158,4 +176,129 @@ public class ChngDetectMastDto {
private List<Basic> result;
private Boolean success;
}
@Schema(name = "RegistResDto", description = "reg 등록 후 리턴 형태")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class RegistResDto {
private Integer code;
private String message;
private Basic result;
private Boolean success;
}
@Schema(name = "LearnKeyDto", description = "learn 엔티티 key 정보")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class LearnKeyDto {
private Long id;
private String uid;
private String chnDtctMstId;
}
@Schema(name = "LabelSendDto", description = "라벨링 전송한 목록")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class LabelSendDto {
private String chnDtctObjtId;
private String labelerId;
private ZonedDateTime labelerWorkDttm;
private String reviewerId;
private ZonedDateTime reviewerWorkDttm;
private ZonedDateTime labelSendDttm;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class ErrorResDto {
private String timestamp;
private Integer status;
private String error;
private String path;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class RlbDtctDto {
private Integer code;
private String message;
private List<RlbDtctMastDto> result;
private Boolean success;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class RlbDtctMastDto {
private String pnuDtctId; // PNU탐지ID
private String pnu; // PNU코드(19자리)
private String lrmSyncYmd; // 지적도동기화일자(YYYYMMDD)
private String pnuSyncYmd; // PNU동기화일자(YYYYMMDD)
private String mpqdNo; // 도곽번호
private String cprsYr; // 비교년도
private String crtrYr; // 기준년도
private String chnDtctSno; // 회차, 변화탐지순번
private String chnDtctId; // 변화탐지ID(UUID)
private String chnDtctMstId; // 변화탐지마스터ID
private String chnDtctObjtId; // 변화탐지객체ID
private String chnDtctContId; // 변화탐지내용ID
private String chnCd; // 변화코드
private String chnDtctProb; // 변화탐지정확도(0~1)
private String bfClsCd; // 이전분류코드
private String bfClsProb; // 이전분류정확도(0~1)
private String afClsCd; // 이후분류코드
private String afClsProb; // 이후분류정확도(0~1)
private String pnuSqms; // PNU면적(㎡)
private String pnuDtctSqms; // PNU탐지면적(㎡)
private String chnDtctSqms; // 변화탐지면적(㎡)
private String stbltYn; // 적합여부(Y/N) - 안정성 (Y:부적합, N:적합)
private String incyCd; // 부적합코드
private String incyRsnCont; // 부적합사유내용
private String lockYn; // 잠금여부(Y/N)
private String lblYn; // 라벨여부(Y/N)
private String chgYn; // 변경여부(Y/N)
private String rsatctNo; // 부동산등기번호
private String rmk; // 비고
private String crtDt; // 생성일시
private String crtEpno; // 생성사원번호
private String crtIp; // 생성사원아이피
private String chgDt; // 변경일시
private String chgEpno; // 변경자사번
private String chgIp; // 변경자IP
private String delYn; // 삭제여부
}
@Schema(name = "RemoveResDto", description = "remove 후 리턴 형태")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class RemoveResDto {
private Integer code;
private String message;
private Boolean result;
private Boolean success;
}
}

View File

@@ -1,21 +1,13 @@
package com.kamco.cd.kamcoback.gukyuin.dto;
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
public class GukYuinDto {
@Getter
@Setter
public static class GukYuinLinkableRes {
private boolean linkable;
// private GukYuinLinkFailCode code;
private String message;
}
/** 실패 코드 enum */
@Getter
@AllArgsConstructor
@@ -39,10 +31,47 @@ public class GukYuinDto {
}
}
@Getter
@Setter
public static class GukYuinLinkableRes {
private boolean linkable;
// private GukYuinLinkFailCode code;
private String message;
}
// Repository가 반환할 Fact(조회 결과)
public record GukYuinLinkFacts(
boolean existsLearn,
boolean isPartScope,
boolean hasRunningInference,
boolean hasOtherUnfinishedGukYuin) {}
@Getter
@Setter
@AllArgsConstructor
public static class LearnInfo {
private Long id;
private UUID uuid;
private Integer compareYyyy;
private Integer targetYyyy;
private Integer stage;
private String uid;
private String applyStatus;
private Boolean applyYn;
public Boolean getApplyYn() {
return this.applyYn != null && this.applyYn;
}
}
@Getter
@Setter
@AllArgsConstructor
public static class GeomUidDto {
private Long geoUid;
private String resultUid;
}
}

View File

@@ -11,6 +11,8 @@ public enum GukYuinStatus implements EnumType {
IN_PROGRESS("진행중"),
GUK_COMPLETED("국유인 매핑 완료"),
PNU_COMPLETED("PNU 싱크 완료"),
PNU_FAILED("PNU 싱크 중 에러"),
END("종료"),
CANCELED("취소");
private final String desc;

View File

@@ -1,29 +1,50 @@
package com.kamco.cd.kamcoback.gukyuin.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.common.utils.NetUtils;
import com.kamco.cd.kamcoback.common.utils.UserUtil;
import com.kamco.cd.kamcoback.config.api.ApiLogFunction;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj;
import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient;
import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient.ExternalCallResult;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.ContBasic;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.ReqInfo;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.ResultContDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.ResultPnuDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ResReturn;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ChnDetectMastReqDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ErrorResDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LabelSendDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ResultDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.RlbDtctDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GukYuinLinkFacts;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GukYuinLinkFailCode;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GukYuinLinkableRes;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.LearnInfo;
import com.kamco.cd.kamcoback.log.dto.EventStatus;
import com.kamco.cd.kamcoback.log.dto.EventType;
import com.kamco.cd.kamcoback.postgres.core.GukYuinCoreService;
import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity;
import com.kamco.cd.kamcoback.postgres.repository.log.AuditLogRepository;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
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.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional(readOnly = true)
@Transactional
@RequiredArgsConstructor
public class GukYuinApiService {
@@ -31,6 +52,11 @@ public class GukYuinApiService {
private final ExternalHttpClient externalHttpClient;
private final NetUtils netUtils = new NetUtils();
private final UserUtil userUtil;
private final AuditLogRepository auditLogRepository;
private final ObjectMapper objectMapper;
private final String myip = netUtils.getLocalIP();
@Value("${spring.profiles.active:local}")
private String profile;
@@ -40,74 +66,157 @@ public class GukYuinApiService {
@Value("${gukyuin.cdi}")
private String gukyuinCdiUrl;
@Value("${file.dataset-dir}")
private String datasetDir;
@Transactional
public ChngDetectMastDto.Basic regist(ChngDetectMastDto.ChnDetectMastReqDto chnDetectMastReq) {
public ChngDetectMastDto.RegistResDto regist(
ChngDetectMastDto.ChnDetectMastReqDto chnDetectMastReq) {
String url = gukyuinCdiUrl + "/chn/mast/regist";
String myip = netUtils.getLocalIP();
chnDetectMastReq.setReqIp(myip);
chnDetectMastReq.setReqEpno("1234567"); // TODO
chnDetectMastReq.setReqEpno(userUtil.getEmployeeNo());
ExternalCallResult<ChngDetectMastDto.Basic> result =
ExternalCallResult<ChngDetectMastDto.RegistResDto> result =
externalHttpClient.call(
url,
HttpMethod.POST,
chnDetectMastReq,
netUtils.jsonHeaders(),
ChngDetectMastDto.Basic.class);
ChngDetectMastDto.RegistResDto.class);
ChngDetectMastDto.Basic resultBody = result.body();
gukyuinCoreService.updateGukYuinMastRegResult(resultBody);
ChngDetectMastDto.RegistResDto resultBody = result.body();
boolean success = false;
if (resultBody != null && resultBody.getSuccess() != null) {
ChngDetectMastDto.Basic registRes = resultBody.getResult();
success = resultBody.getSuccess();
// 이미 등록한 경우에는 result가 없음
if (resultBody.getResult() == null) {
return resultBody;
}
// 추론 회차에 applyStatus, applyStatusDttm 업데이트
gukyuinCoreService.updateGukYuinMastRegResult(registRes);
// anal_inference 에도 국유인 반영여부, applyDttm 업데이트
gukyuinCoreService.updateAnalInferenceApplyDttm(registRes);
} else {
String errBody = result.errBody();
ErrorResDto error = null;
try {
error = objectMapper.readValue(errBody, ErrorResDto.class);
return new ChngDetectMastDto.RegistResDto(error.getStatus(), error.getError(), null, false);
} catch (JsonProcessingException e) {
log.error("에러 응답 파싱 실패. rawBody={}", errBody, e);
return new ChngDetectMastDto.RegistResDto(
result.statusCode(), // HTTP status
errBody, // 원문 그대로
null,
false);
}
}
this.insertGukyuinAuditLog(
EventType.ADDED.getId(),
myip,
userUtil.getId(),
url.replace(gukyuinUrl, ""),
chnDetectMastReq,
success);
return resultBody;
}
@Transactional
public ResReturn remove(ChngDetectMastDto.ChnDetectMastReqDto chnDetectMastReq) {
public ChngDetectMastDto.RemoveResDto remove(
ChngDetectMastDto.ChnDetectMastReqDto chnDetectMastReq) {
String url = gukyuinCdiUrl + "/chn/mast/remove";
String myip = netUtils.getLocalIP();
chnDetectMastReq.setReqIp(myip);
chnDetectMastReq.setReqEpno("1234567"); // TODO
chnDetectMastReq.setReqEpno(userUtil.getEmployeeNo());
ExternalCallResult<ChngDetectMastDto.Basic> result =
boolean success = false;
ExternalCallResult<ChngDetectMastDto.RemoveResDto> result =
externalHttpClient.call(
url,
HttpMethod.POST,
chnDetectMastReq,
netUtils.jsonHeaders(),
ChngDetectMastDto.Basic.class);
ChngDetectMastDto.RemoveResDto.class);
ChngDetectMastDto.Basic resultBody = result.body();
gukyuinCoreService.updateGukYuinMastRegRemove(resultBody);
ChngDetectMastDto.RemoveResDto resultBody = result.body();
if (resultBody != null && resultBody.getSuccess() != null) {
return new ResReturn("success", "탐지결과 삭제 되었습니다.");
success = resultBody.getSuccess();
if (resultBody.getSuccess()) {
gukyuinCoreService.updateGukYuinMastRegRemove(chnDetectMastReq.getChnDtctId());
}
}
this.insertGukyuinAuditLog(
EventType.REMOVE.getId(),
myip,
userUtil.getId(),
url.replace(gukyuinUrl, ""),
chnDetectMastReq,
success);
return resultBody;
}
// 등록목록 1개 확인
public ChngDetectMastDto.ResultDto list(String chnDtctMstId) {
public ChngDetectMastDto.ResultDto detail(String chnDtctMstId) {
String url = gukyuinCdiUrl + "/chn/mast/list/" + chnDtctMstId;
String url =
gukyuinCdiUrl
+ "/chn/mast/list/"
+ chnDtctMstId
+ "?reqIp="
+ myip
+ "&reqEpno="
+ userUtil.getEmployeeNo();
ExternalCallResult<ChngDetectMastDto.ResultDto> result =
externalHttpClient.call(
url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.ResultDto.class);
this.insertGukyuinAuditLog(
EventType.DETAIL.getId(),
netUtils.getLocalIP(),
userUtil.getId(),
url.replace(gukyuinUrl, ""),
null,
result.body().getSuccess());
return result.body();
}
// 등록목록 비교년도,기준년도,차수 조합해서 n개 확인
public ChngDetectMastDto.ResultDto listYearStage(
ChngDetectMastDto.ChngDetectMastSearchDto searchDto) {
String queryString = netUtils.dtoToQueryString(searchDto, null);
String url = gukyuinCdiUrl + "/chn/mast" + queryString;
String url =
gukyuinCdiUrl
+ "/chn/mast"
+ queryString
+ "&reqIp="
+ myip
+ "&reqEpno="
+ userUtil.getEmployeeNo();
ExternalCallResult<ChngDetectMastDto.ResultDto> result =
externalHttpClient.call(
url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.ResultDto.class);
this.insertGukyuinAuditLog(
EventType.LIST.getId(),
netUtils.getLocalIP(),
userUtil.getId(),
url.replace(gukyuinUrl, ""),
null,
result.body().getSuccess());
return result.body();
}
@@ -159,7 +268,11 @@ public class GukYuinApiService {
+ "?pageIndex="
+ pageIndex
+ "&pageSize="
+ pageSize;
+ pageSize
+ "&reqIp="
+ myip
+ "&reqEpno="
+ userUtil.getEmployeeNo();
ExternalCallResult<ChngDetectContDto.ResultContDto> result =
externalHttpClient.call(
@@ -171,28 +284,35 @@ public class GukYuinApiService {
List<ContBasic> contList = result.body().getResult();
if (contList == null || contList.isEmpty()) {
return new ResultContDto();
return new ResultContDto(
result.body().getCode(),
result.body().getMessage(),
result.body().getResult(),
result.body().getSuccess());
}
for (ContBasic cont : contList) {
String[] pnuList = cont.getPnuList();
long pnuCnt = pnuList == null ? 0 : pnuList.length;
if (cont.getChnDtctObjtId() != null) {
gukyuinCoreService.updateInferenceGeomDataPnuCnt(cont.getChnDtctObjtId(), pnuCnt);
if (pnuCnt > 0) {
Long geoUid =
gukyuinCoreService.findMapSheetAnalDataInferenceGeomUid(cont.getChnDtctObjtId());
gukyuinCoreService.insertGeoUidPnuData(geoUid, pnuList);
}
}
}
this.insertGukyuinAuditLog(
EventType.LIST.getId(),
netUtils.getLocalIP(),
userUtil.getId(),
url.replace(gukyuinUrl, ""),
null,
result.body().getSuccess());
return result.body();
}
public ResultPnuDto findPnuObjMgmtList(String chnDtctId, String chnDtctObjtId) {
String url = gukyuinCdiUrl + "/chn/pnu/" + chnDtctId + "/objt/" + chnDtctObjtId;
String url =
gukyuinCdiUrl
+ "/chn/pnu/"
+ chnDtctId
+ "/objt/"
+ chnDtctObjtId
+ "?reqIp="
+ myip
+ "&reqEpno="
+ userUtil.getEmployeeNo();
ExternalCallResult<ChngDetectContDto.ResultPnuDto> result =
externalHttpClient.call(
@@ -202,27 +322,56 @@ public class GukYuinApiService {
netUtils.jsonHeaders(),
ChngDetectContDto.ResultPnuDto.class);
this.insertGukyuinAuditLog(
EventType.DETAIL.getId(),
netUtils.getLocalIP(),
userUtil.getId(),
url.replace(gukyuinUrl, ""),
null,
result.body().getSuccess());
return result.body();
}
public ResReturn updateChnDtctObjtLabelingYn(String chnDtctObjtId, String lblYn) {
public ChngDetectContDto.ResultLabelDto updateChnDtctObjtLabelingYn(
String chnDtctObjtId, String lblYn) {
String url = gukyuinCdiUrl + "/rlb/objt/" + chnDtctObjtId + "/lbl/" + lblYn;
ExternalCallResult<ChngDetectContDto.ResultPnuDto> result =
ReqInfo info = new ReqInfo();
info.setReqIp(myip);
info.setReqEpno(userUtil.getEmployeeNo());
ExternalCallResult<ChngDetectContDto.ResultLabelDto> result =
externalHttpClient.call(
url,
HttpMethod.POST,
null,
info,
netUtils.jsonHeaders(),
ChngDetectContDto.ResultPnuDto.class);
ChngDetectContDto.ResultLabelDto.class);
ChngDetectContDto.ResultPnuDto dto = result.body();
this.insertGukyuinAuditLog(
EventType.MODIFIED.getId(),
netUtils.getLocalIP(),
userUtil.getId(),
url.replace(gukyuinUrl, ""),
null,
result.body().getSuccess());
return new ResReturn(dto.getCode() > 200000 ? "fail" : "success", dto.getMessage());
return result.body();
}
public ResultContDto findChnPnuToContList(String chnDtctId, String pnu) {
String url = gukyuinCdiUrl + "/chn/cont/" + chnDtctId + "/pnu/" + pnu;
String url =
gukyuinCdiUrl
+ "/chn/cont/"
+ chnDtctId
+ "/pnu/"
+ pnu
+ "?reqIp="
+ myip
+ "&reqEpno="
+ userUtil.getEmployeeNo();
ExternalCallResult<ChngDetectContDto.ResultContDto> result =
externalHttpClient.call(
@@ -232,16 +381,201 @@ public class GukYuinApiService {
netUtils.jsonHeaders(),
ChngDetectContDto.ResultContDto.class);
this.insertGukyuinAuditLog(
EventType.LIST.getId(),
netUtils.getLocalIP(),
userUtil.getId(),
url.replace(gukyuinUrl, ""),
null,
result.body().getSuccess());
return result.body();
}
public ResultDto listChnDtctId(String chnDtctId) {
String url = gukyuinCdiUrl + "/chn/mast/" + chnDtctId;
String url =
gukyuinCdiUrl
+ "/chn/mast/"
+ chnDtctId
+ "?reqIp="
+ myip
+ "&reqEpno="
+ userUtil.getEmployeeNo();
ExternalCallResult<ChngDetectMastDto.ResultDto> result =
externalHttpClient.call(
url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.ResultDto.class);
this.insertGukyuinAuditLog(
EventType.DETAIL.getId(),
netUtils.getLocalIP(),
userUtil.getId(),
url.replace(gukyuinUrl, ""),
null,
result.body().getSuccess());
return result.body();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false)
public void insertGukyuinAuditLog(
String actionType,
String myIp,
Long userUid,
String requestUri,
Object requestBody,
boolean successFail) {
try {
AuditLogEntity log =
new AuditLogEntity(
userUid,
EventType.fromName(actionType),
successFail ? EventStatus.SUCCESS : EventStatus.FAILED,
"GUKYUIN", // 메뉴도 국유인으로 하나 따기
myIp,
requestUri,
requestBody == null ? null : ApiLogFunction.cutRequestBody(requestBody.toString()),
null,
null,
null);
auditLogRepository.save(log);
} catch (Exception e) {
log.error(e.getMessage());
throw e;
}
}
public ResponseObj connectChnMastRegist(UUID uuid) {
// uuid로 추론 회차 조회
LearnInfo info = gukyuinCoreService.findMapSheetLearnInfo(uuid);
if (info.getApplyYn() != null && info.getApplyYn()) {
return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 국유인 연동을 한 회차입니다.");
}
if (!Files.isDirectory(Path.of("/kamco-nfs/dataset/export/" + info.getUid()))) {
return new ResponseObj(
ApiResponseCode.NOT_FOUND_DATA, "파일 경로에 회차 실행 파일이 생성되지 않았습니다. 확인 부탁드립니다.");
}
// 비교년도,기준년도로 전송한 데이터 있는지 확인 후 회차 번호 생성
Integer maxStage =
gukyuinCoreService.findMapSheetLearnYearStage(info.getCompareYyyy(), info.getTargetYyyy());
// reqDto 셋팅
ChnDetectMastReqDto reqDto = new ChnDetectMastReqDto();
reqDto.setCprsYr(String.valueOf(info.getCompareYyyy()));
reqDto.setCrtrYr(String.valueOf(info.getTargetYyyy()));
reqDto.setChnDtctSno(String.valueOf(maxStage + 1));
reqDto.setChnDtctId(info.getUid());
reqDto.setPathNm("/kamco-nfs/dataset/export/" + info.getUid());
// 1회차를 종료 상태로 처리하고 2회차를 보내야 함
// 추론(learn), 학습데이터(inference) 둘 다 종료 처리
if (maxStage > 0) {
Long learnId =
gukyuinCoreService.findMapSheetLearnInfoByYyyy(
info.getCompareYyyy(), info.getTargetYyyy(), maxStage);
gukyuinCoreService.updateMapSheetLearnGukyuinEndStatus(learnId);
gukyuinCoreService.updateMapSheetInferenceLabelEndStatus(learnId);
}
// 국유인 /chn/mast/regist 전송
ChngDetectMastDto.RegistResDto result = this.regist(reqDto);
if (result.getSuccess()) {
return new ResponseObj(ApiResponseCode.OK, "연동되었습니다.");
} else {
return new ResponseObj(ApiResponseCode.INTERNAL_SERVER_ERROR, result.getMessage());
}
}
public List<LabelSendDto> findLabelingCompleteSendList(LocalDate yesterday) {
return gukyuinCoreService.findLabelingCompleteSendList(yesterday);
}
public ResultContDto findChnPnuToContObject(
String chnDtctId, String chnDtctObjtId, Integer pageIndex, Integer pageSize) {
String url =
gukyuinCdiUrl
+ "/chn/cont/"
+ chnDtctId
+ "/chnDtctObjtId/"
+ chnDtctObjtId
+ "?pageIndex="
+ pageIndex
+ "&pageSize="
+ pageSize
+ "&reqIp="
+ myip
+ "&reqEpno="
+ userUtil.getEmployeeNo();
ExternalCallResult<ChngDetectContDto.ResultContDto> result =
externalHttpClient.call(
url,
HttpMethod.GET,
null,
netUtils.jsonHeaders(),
ChngDetectContDto.ResultContDto.class);
this.insertGukyuinAuditLog(
EventType.DETAIL.getId(),
netUtils.getLocalIP(),
userUtil.getId(),
url.replace(gukyuinUrl, ""),
null,
result.body() != null && result.body().getSuccess());
return result.body();
}
public ChngDetectMastDto.RlbDtctDto findRlbDtctList(String chnDtctId, String yyyymmdd) {
String url =
gukyuinCdiUrl
+ "/rlb/dtct/"
+ chnDtctId
+ "?reqIp="
+ myip
+ "&reqEpno="
+ userUtil.getEmployeeNo()
+ "&yyyymmdd="
+ yyyymmdd;
ExternalCallResult<ChngDetectMastDto.RlbDtctDto> result =
externalHttpClient.call(
url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.RlbDtctDto.class);
this.insertGukyuinAuditLog(
EventType.LIST.getId(),
netUtils.getLocalIP(),
userUtil.getId(),
url.replace(gukyuinUrl, ""),
null,
result.body() != null && result.body().getSuccess());
return result.body();
}
public RlbDtctDto findRlbDtctObject(String chnDtctObjtId) {
String url =
gukyuinCdiUrl
+ "/rlb/objt/"
+ chnDtctObjtId
+ "?reqIp="
+ myip
+ "&reqEpno="
+ userUtil.getEmployeeNo();
ExternalCallResult<ChngDetectMastDto.RlbDtctDto> result =
externalHttpClient.call(
url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.RlbDtctDto.class);
this.insertGukyuinAuditLog(
EventType.DETAIL.getId(),
netUtils.getLocalIP(),
userUtil.getId(),
url.replace(gukyuinUrl, ""),
null,
result.body() != null && result.body().getSuccess());
return result.body();
}
}

View File

@@ -1,5 +1,7 @@
package com.kamco.cd.kamcoback.inference;
import com.kamco.cd.kamcoback.common.download.DownloadExecutor;
import com.kamco.cd.kamcoback.common.download.dto.DownloadSpec;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto;
@@ -17,6 +19,7 @@ import com.kamco.cd.kamcoback.model.dto.ModelMngDto;
import com.kamco.cd.kamcoback.model.service.ModelMngService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -25,17 +28,13 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -46,6 +45,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@Tag(name = "추론관리", description = "추론관리 API")
@RequestMapping("/api/inference")
@@ -56,6 +56,7 @@ public class InferenceResultApiController {
private final InferenceResultService inferenceResultService;
private final MapSheetMngService mapSheetMngService;
private final ModelMngService modelMngService;
private final DownloadExecutor downloadExecutor;
@Operation(summary = "추론관리 목록", description = "어드민 홈 > 추론관리 > 추론관리 > 추론관리 목록")
@ApiResponses(
@@ -328,7 +329,21 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(geomList);
}
@Operation(summary = "shp 파일 다운로드", description = "추론관리 분석결과 shp 파일 다운로드")
@Operation(
summary = "shp 파일 다운로드",
description = "추론관리 분석결과 shp 파일 다운로드",
parameters = {
@Parameter(
name = "kamco-download-uuid",
in = ParameterIn.HEADER,
required = true,
description = "다운로드 요청 UUID",
schema =
@Schema(
type = "string",
format = "uuid",
example = "69c4e56c-e0bf-4742-9225-bba9aae39052"))
})
@ApiResponses(
value = {
@ApiResponse(
@@ -341,15 +356,14 @@ public class InferenceResultApiController {
@ApiResponse(responseCode = "404", description = "파일 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping(value = "/download/{uuid}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<Resource> downloadShp(
@Parameter(description = "uuid", example = "0192efc6-9ec2-43ee-9a90-5b73e763c09f")
@PathVariable
UUID uuid)
@GetMapping(value = "/download/{uuid}")
public ResponseEntity<StreamingResponseBody> download(
@Parameter(example = "69c4e56c-e0bf-4742-9225-bba9aae39052") @PathVariable UUID uuid)
throws IOException {
String path;
String uid;
try {
Map<String, Object> map = inferenceResultService.shpDownloadPath(uuid);
path = String.valueOf(map.get("path"));
@@ -360,24 +374,11 @@ public class InferenceResultApiController {
Path zipPath = Path.of(path);
if (!Files.exists(zipPath) || !Files.isReadable(zipPath)) {
return ResponseEntity.notFound().build();
}
FileSystemResource resource = new FileSystemResource(zipPath);
String filename = uid + ".zip";
long fileSize = Files.size(zipPath);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
.contentLength(fileSize)
.body(resource);
return downloadExecutor.stream(
new DownloadSpec(uuid, zipPath, uid + ".zip", MediaType.APPLICATION_OCTET_STREAM));
}
@Operation(summary = "shp 파일 다운로드 이력", description = "추론관리 분석결과 shp 파일 다운로드 이력")
@Operation(summary = "shp 파일 다운로드 이력 조회", description = "추론관리 분석결과 shp 파일 다운로드 이력 조회")
@GetMapping(value = "/download-audit/{uuid}")
@ApiResponses(
value = {
@@ -392,19 +393,20 @@ public class InferenceResultApiController {
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
public ApiResponseDto<Page<AuditLogDto.DownloadRes>> downloadAudit(
@Parameter(description = "UUID", example = "0192efc6-9ec2-43ee-9a90-5b73e763c09f")
@Parameter(description = "UUID", example = "69c4e56c-e0bf-4742-9225-bba9aae39052")
@PathVariable
UUID uuid,
@Parameter(description = "다운로드일 시작", example = "2025-01-01") @RequestParam(required = false)
LocalDate strtDttm,
@Parameter(description = "다운로드일 종료", example = "2026-01-01") @RequestParam(required = false)
@Parameter(description = "다운로드일 종료", example = "2026-04-01") @RequestParam(required = false)
LocalDate endDttm,
@Parameter(description = "키워드", example = "관리자") @RequestParam(required = false)
@Parameter(description = "키워드", example = "") @RequestParam(required = false)
String searchValue,
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0")
int page,
@Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20")
int size) {
AuditLogDto.searchReq searchReq = new searchReq();
searchReq.setPage(page);
searchReq.setSize(size);
@@ -413,8 +415,7 @@ public class InferenceResultApiController {
downloadReq.setStartDate(strtDttm);
downloadReq.setEndDate(endDttm);
downloadReq.setSearchValue(searchValue);
downloadReq.setMenuId("22");
downloadReq.setRequestUri("/api/inference/download-audit");
downloadReq.setRequestUri("/api/inference/download-audit/download/" + uuid);
return ApiResponseDto.ok(inferenceResultService.getDownloadAudit(searchReq, downloadReq));
}

View File

@@ -1,5 +1,7 @@
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;
@@ -10,20 +12,28 @@ 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(
@@ -52,4 +62,29 @@ public class InferenceResultShpApiController {
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

@@ -534,6 +534,10 @@ public class InferenceDetailDto {
throw new RuntimeException(e);
}
}
public Boolean getApplyYn() {
return this.applyYn != null && this.applyYn;
}
}
@Getter

View File

@@ -1,15 +1,20 @@
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.InferenceResultShpDto;
import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService;
import com.kamco.cd.kamcoback.postgres.core.InferenceResultShpCoreService;
import com.kamco.cd.kamcoback.postgres.core.MapSheetMngCoreService;
import com.kamco.cd.kamcoback.scheduler.service.ShpPipelineService;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -19,9 +24,11 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true)
public class InferenceResultShpService {
private static final Logger log = LogManager.getLogger(InferenceResultShpService.class);
private final InferenceResultShpCoreService coreService;
private final InferenceResultCoreService inferenceResultCoreService;
private final ShpPipelineService shpPipelineService;
private final MapSheetMngCoreService mapSheetMngCoreService;
@Value("${mapsheet.shp.baseurl}")
private String baseDir;
@@ -59,4 +66,21 @@ public class InferenceResultShpService {
// shp 파일 비동기 생성
shpPipelineService.runPipeline(jarPath, datasetDir, batchId, 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,5 +1,7 @@
package com.kamco.cd.kamcoback.label;
import com.kamco.cd.kamcoback.common.download.DownloadExecutor;
import com.kamco.cd.kamcoback.common.download.dto.DownloadSpec;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail;
@@ -9,25 +11,39 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.WorkHistoryDto;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.UpdateClosedRequest;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse;
import com.kamco.cd.kamcoback.label.service.LabelAllocateService;
import com.kamco.cd.kamcoback.log.dto.AuditLogDto;
import com.kamco.cd.kamcoback.log.dto.AuditLogDto.DownloadReq;
import com.kamco.cd.kamcoback.log.dto.AuditLogDto.searchReq;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.coyote.BadRequestException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@Slf4j
@Tag(name = "라벨링 작업 관리", description = "라벨링 작업 배정 및 통계 조회 API")
@@ -37,6 +53,10 @@ import org.springframework.web.bind.annotation.RestController;
public class LabelAllocateApiController {
private final LabelAllocateService labelAllocateService;
private final DownloadExecutor downloadExecutor;
@Value("${file.dataset-response}")
private String responsePath;
@Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.")
@ApiResponses(
@@ -333,4 +353,112 @@ public class LabelAllocateApiController {
public ApiResponseDto<Long> labelingIngProcessCnt() {
return ApiResponseDto.ok(labelAllocateService.findLabelingIngProcessCnt());
}
@Operation(
summary = "라벨 파일 다운로드",
description = "라벨 파일 다운로드",
parameters = {
@Parameter(
name = "kamco-download-uuid",
in = ParameterIn.HEADER,
required = true,
description = "다운로드 요청 UUID",
schema =
@Schema(
type = "string",
format = "uuid",
example = "6d8d49dc-0c9d-4124-adc7-b9ca610cc394"))
})
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "라벨 zip파일 다운로드",
content =
@Content(
mediaType = "application/octet-stream",
schema = @Schema(type = "string", format = "binary"))),
@ApiResponse(responseCode = "404", description = "파일 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/download/{uuid}")
public ResponseEntity<StreamingResponseBody> download(
@Parameter(example = "6d8d49dc-0c9d-4124-adc7-b9ca610cc394") @PathVariable UUID uuid)
throws IOException {
if (!labelAllocateService.isDownloadable(uuid)) {
throw new BadRequestException();
}
String uid = labelAllocateService.findLearnUid(uuid);
Path zipPath = Paths.get(responsePath).resolve(uid + ".zip");
return downloadExecutor.stream(
new DownloadSpec(uuid, zipPath, uid + ".zip", MediaType.APPLICATION_OCTET_STREAM));
}
@Operation(summary = "라벨 파일 다운로드 이력 조회", description = "라벨 파일 다운로드 이력 조회")
@GetMapping(value = "/download-audit/{uuid}")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Page.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
public ApiResponseDto<Page<AuditLogDto.DownloadRes>> downloadAudit(
@Parameter(description = "UUID", example = "6d8d49dc-0c9d-4124-adc7-b9ca610cc394")
@PathVariable
UUID uuid,
// @Parameter(description = "다운로드일 시작", example = "2025-01-01") @RequestParam(required =
// false)
// LocalDate strtDttm,
// @Parameter(description = "다운로드일 종료", example = "2026-04-01") @RequestParam(required =
// false)
// LocalDate endDttm,
// @Parameter(description = "키워드", example = "") @RequestParam(required = false)
// String searchValue,
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0")
int page,
@Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20")
int size) {
AuditLogDto.searchReq searchReq = new searchReq();
searchReq.setPage(page);
searchReq.setSize(size);
DownloadReq downloadReq = new DownloadReq();
downloadReq.setUuid(uuid);
// downloadReq.setStartDate(strtDttm);
// downloadReq.setEndDate(endDttm);
// downloadReq.setSearchValue(searchValue);
downloadReq.setRequestUri("/api/training-data/stage/download/" + uuid);
return ApiResponseDto.ok(labelAllocateService.getDownloadAudit(searchReq, downloadReq));
}
@Operation(summary = "다운로드 가능여부 조회", description = "다운로드 가능여부 조회 API")
@GetMapping(value = "/download-check/{uuid}")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Page.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
public ApiResponseDto<Boolean> isDownloadable(
@Parameter(description = "UUID", example = "6d8d49dc-0c9d-4124-adc7-b9ca610cc394")
@PathVariable
UUID uuid) {
return ApiResponseDto.ok(labelAllocateService.isDownloadable(uuid));
}
}

View File

@@ -359,4 +359,15 @@ public class LabelAllocateDto {
@Schema(description = "작업기간 종료일")
private ZonedDateTime projectCloseDttm;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class InferenceLearnDto {
private UUID analUuid;
private String learnUid;
private String analState;
private Long analId;
}
}

View File

@@ -16,10 +16,14 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.searchReq;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.ProjectInfo;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse;
import com.kamco.cd.kamcoback.log.dto.AuditLogDto;
import com.kamco.cd.kamcoback.log.dto.AuditLogDto.DownloadReq;
import com.kamco.cd.kamcoback.postgres.core.AuditLogCoreService;
import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
@@ -28,13 +32,11 @@ import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class LabelAllocateService {
private final LabelAllocateCoreService labelAllocateCoreService;
public LabelAllocateService(LabelAllocateCoreService labelAllocateCoreService) {
this.labelAllocateCoreService = labelAllocateCoreService;
}
private final AuditLogCoreService auditLogCoreService;
/**
* 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직
@@ -273,4 +275,29 @@ public class LabelAllocateService {
public Long findLabelingIngProcessCnt() {
return labelAllocateCoreService.findLabelingIngProcessCnt();
}
public String findLearnUid(UUID uuid) {
return labelAllocateCoreService.findLearnUid(uuid);
}
/**
* 다운로드 이력 조회
*
* @param searchReq 페이징
* @param downloadReq 조회조건
*/
public Page<AuditLogDto.DownloadRes> getDownloadAudit(
AuditLogDto.searchReq searchReq, DownloadReq downloadReq) {
return auditLogCoreService.findLogByAccount(searchReq, downloadReq);
}
/**
* 다운로드 가능 여부 조회
*
* @param uuid
* @return
*/
public boolean isDownloadable(UUID uuid) {
return labelAllocateCoreService.isDownloadable(uuid);
}
}

View File

@@ -2,10 +2,13 @@ package com.kamco.cd.kamcoback.layer;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.IsMapYn;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.LayerMapDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.OrderReq;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.SearchReq;
import com.kamco.cd.kamcoback.layer.service.LayerService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -76,10 +79,24 @@ public class LayerApiController {
})
@PostMapping("/save/{layerType}")
public ApiResponseDto<UUID> save(
@PathVariable String layerType, @RequestBody LayerDto.AddReq dto) {
@Schema(description = "TILE,GEOJSON,WMS,WMTS", example = "GEOJSON") @PathVariable
String layerType,
@RequestBody LayerDto.AddReq dto) {
return ApiResponseDto.ok(layerService.saveLayers(layerType, dto));
}
@Operation(summary = "순서 변경", description = "순서 변경 api")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "등록 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Void.class))),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PutMapping("/order")
public ApiResponseDto<Void> updateOrder(@RequestBody List<OrderReq> dto) {
layerService.orderUpdate(dto);
@@ -152,6 +169,24 @@ public class LayerApiController {
return ApiResponseDto.ok(null);
}
@Operation(summary = "맵 노출여부 수정", description = "맵 노출여부 수정 api")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "수정 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Void.class))),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PutMapping("/update-map/{uuid}")
public ApiResponseDto<Void> updateIsMap(@PathVariable UUID uuid, @RequestBody IsMapYn isMapYn) {
layerService.updateIsMap(uuid, isMapYn);
return ApiResponseDto.ok(null);
}
@Operation(summary = "wmts tile 조회", description = "wmts tile 조회 api")
@ApiResponses(
value = {
@@ -187,4 +222,55 @@ public class LayerApiController {
public ApiResponseDto<List<String>> getWmsTile() {
return ApiResponseDto.ok(layerService.getWmsTitle());
}
@Operation(summary = "변화지도 레이어 조회", description = "변화지도 레이어 조회")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = String.class)))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/map/change-detection")
public ApiResponseDto<List<LayerMapDto>> changeDetectionMap() {
return ApiResponseDto.ok(layerService.findLayerMapList("change-detection"));
}
@Operation(summary = "라벨링 툴 레이어 조회", description = "라벨링 툴 레이어 조회")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = String.class)))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/map/labeling")
public ApiResponseDto<List<LayerMapDto>> labelingMap() {
return ApiResponseDto.ok(layerService.findLayerMapList("labeling"));
}
@Operation(summary = "년도별 tile Url(before,after 모두 조회)", description = "년도별 tile Url")
@GetMapping("/tile-url")
public ApiResponseDto<LayerDto.YearTileDto> getChangeDetectionTileUrl(
@Parameter(description = "이전 년도", example = "2023") @RequestParam Integer beforeYear,
@Parameter(description = "이후 년도", example = "2024") @RequestParam Integer afterYear) {
return ApiResponseDto.ok(layerService.getChangeDetectionTileUrl(beforeYear, afterYear));
}
@Operation(summary = "년도별 tile Url(년도 1개만 조회)", description = "년도별 tile Url")
@GetMapping("/tile-url-year")
public ApiResponseDto<LayerDto.TileUrlDto> getChangeDetectionTileOneYearUrl(
@Parameter(description = "년도", example = "2023") @RequestParam Integer year) {
return ApiResponseDto.ok(layerService.getChangeDetectionTileOneYearUrl(year));
}
}

View File

@@ -1,5 +1,10 @@
package com.kamco.cd.kamcoback.layer.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
@@ -12,14 +17,23 @@ import lombok.Setter;
public class LayerDto {
public enum MapType {
CHANGE_MAP,
LABELING_MAP
}
@Getter
@Setter
@AllArgsConstructor
@Schema(name = "LayerBasic")
public static class Basic {
@Schema(description = "uuid")
private UUID uuid;
@Schema(description = "레이어명")
private String layerName;
@Schema(example = "WMTS", description = "유형 (TILE/GEOJSON/WMTS/WMS)")
private String layerType;
@@ -48,9 +62,13 @@ public class LayerDto {
@AllArgsConstructor
@Schema(name = "LayerDetail")
public static class Detail {
@Schema(description = "uuid")
private UUID uuid;
@Schema(description = "레이어명")
private String layerName;
@Schema(description = "유형 (TILE/GEOJSON/WMTS/WMS)")
private String layerType;
@@ -96,6 +114,9 @@ public class LayerDto {
@JsonFormatDttm
@Schema(description = "등록일시")
private ZonedDateTime createdDttm;
@Schema(description = "좌표계")
private String crs;
}
@Getter
@@ -104,7 +125,10 @@ public class LayerDto {
@Schema(name = "LayerAddReq")
public static class AddReq {
@Schema(description = "title")
@Schema(description = "레이어명")
private String layerName;
@Schema(description = "title WMS, WMTS 선택한 tile")
private String title;
@Schema(description = "설명")
@@ -133,6 +157,9 @@ public class LayerDto {
@Schema(description = "zoom max", example = "18")
private Short max;
@Schema(description = "좌표계", example = "EPSG_3857")
private String crs;
}
@Getter
@@ -153,6 +180,7 @@ public class LayerDto {
@AllArgsConstructor
@NoArgsConstructor
public static class SearchReq {
private String tag;
private String layerType;
}
@@ -162,6 +190,7 @@ public class LayerDto {
@AllArgsConstructor
@NoArgsConstructor
public static class TileAddReqDto {
@Schema(description = "설명", example = "배경지도 입니다.")
private String description;
@@ -189,4 +218,229 @@ public class LayerDto {
@Schema(description = "zoom max", example = "18")
private Short max;
}
@Getter
@Setter
@Schema(name = "LayerMapDto")
public static class LayerMapDto {
@Schema(description = "레이어명")
private String layerName;
@Schema(example = "WMTS", description = "유형 (TILE/GEOJSON/WMTS/WMS)")
private String layerType;
@Schema(description = "title")
private String title;
@Schema(description = "설명")
private String description;
@Schema(description = "태그")
private String tag;
@Schema(description = "순서")
private Long sortOrder;
@Schema(description = "url")
private String url;
@Schema(description = "좌측상단 경도", example = "126.0")
private BigDecimal minLon;
@Schema(description = "좌측상단 위도", example = "34.0")
private BigDecimal minLat;
@Schema(description = "우측하단 경도", example = "130.0")
private BigDecimal maxLon;
@Schema(description = "우측하단 위도", example = "38.5")
private BigDecimal maxLat;
@Schema(description = "zoom min", example = "5")
private Short minZoom;
@Schema(description = "zoom max", example = "18")
private Short maxZoom;
@Schema(description = "bbox")
private JsonNode bbox;
@JsonIgnore private String bboxGeometry;
@Schema(description = "uuid")
private UUID uuid;
@JsonIgnore private String rawJsonString;
@Schema(description = "rawJson")
private JsonNode rawJson;
@Schema(description = "crs")
private String crs;
public LayerMapDto(
String layerName,
String layerType,
String tag,
Long sortOrder,
String url,
BigDecimal minLon,
BigDecimal minLat,
BigDecimal maxLon,
BigDecimal maxLat,
Short minZoom,
Short maxZoom,
String bboxGeometry,
UUID uuid,
String rawJsonString,
String crs) {
this.layerName = layerName;
this.layerType = layerType;
this.tag = tag;
this.sortOrder = sortOrder;
this.url = url;
this.minLon = minLon;
this.minLat = minLat;
this.maxLon = maxLon;
this.maxLat = maxLat;
this.minZoom = minZoom;
this.maxZoom = maxZoom;
this.bboxGeometry = bboxGeometry;
this.uuid = uuid;
this.rawJsonString = rawJsonString;
JsonNode geoJson = null;
JsonNode rawJson = null;
ObjectMapper mapper = new ObjectMapper();
if (bboxGeometry != null) {
try {
geoJson = mapper.readTree(bboxGeometry);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
if (rawJsonString != null) {
try {
rawJson = mapper.readTree(rawJsonString);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
this.rawJson = rawJson;
this.bbox = geoJson;
this.crs = crs;
}
@JsonProperty("workspace")
public String getWorkSpace() {
return "cd";
}
}
@Schema(name = "TileUrlDto", description = "Tile Url 정보")
@Getter
@Setter
@NoArgsConstructor
public static class TileUrlDto {
@Schema(description = "mngYyyy")
private Integer mngYyyy;
@Schema(description = "url")
private String url;
@Schema(description = "태그")
private String tag;
@Schema(description = "좌측상단 경도", example = "126.0")
private BigDecimal minLon;
@Schema(description = "좌측상단 위도", example = "34.0")
private BigDecimal minLat;
@Schema(description = "우측하단 경도", example = "130.0")
private BigDecimal maxLon;
@Schema(description = "우측하단 위도", example = "38.5")
private BigDecimal maxLat;
@Schema(description = "zoom min", example = "5")
private Short minZoom;
@Schema(description = "zoom max", example = "18")
private Short maxZoom;
@Schema(description = "bbox")
private JsonNode bbox;
@JsonIgnore private String bboxGeometry;
private String crs;
public TileUrlDto(
Integer mngYyyy,
String url,
String tag,
BigDecimal minLon,
BigDecimal minLat,
BigDecimal maxLon,
BigDecimal maxLat,
Short minZoom,
Short maxZoom,
String bboxGeometry,
String crs) {
this.mngYyyy = mngYyyy;
this.url = url;
this.tag = tag;
this.minLon = minLon;
this.minLat = minLat;
this.maxLon = maxLon;
this.maxLat = maxLat;
this.minZoom = minZoom;
this.maxZoom = maxZoom;
this.bboxGeometry = bboxGeometry;
JsonNode geoJson = null;
if (bboxGeometry != null) {
ObjectMapper mapper = new ObjectMapper();
try {
geoJson = mapper.readTree(bboxGeometry);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
this.bbox = geoJson;
this.crs = crs;
}
}
@Schema(name = "TileUrlDto", description = "Tile Url 정보")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class YearTileDto {
private TileUrlDto before;
private TileUrlDto after;
}
@Schema(name = "맵 노출 여부", description = "맵 노출 여부")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class IsMapYn {
@Schema(description = "CHANGE_MAP(변화지도), LABELING_MAP(라벨링지도)", example = "CHANGE_MAP")
private String mapType;
@Schema(description = "노출여부 true, false", example = "true")
private Boolean isMapYn;
}
}

View File

@@ -26,5 +26,6 @@ public class WmsDto {
private String title;
private String description;
private String tag;
private String layerName;
}
}

View File

@@ -2,171 +2,46 @@ package com.kamco.cd.kamcoback.layer.dto;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/** WMS 레이어 정보를 담는 DTO 클래스 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WmsLayerInfo {
private String name;
private String title;
private String abstractText;
private List<String> keywords;
private List<String> keywords = new ArrayList<>();
private BoundingBox boundingBox;
private List<String> crs; // 지원하는 좌표계 목록
@Override
public String toString() {
return "WmsLayerInfo{"
+ "name='"
+ name
+ '\''
+ ", title='"
+ title
+ '\''
+ ", abstractText='"
+ abstractText
+ '\''
+ ", keywords="
+ keywords
+ ", boundingBox="
+ boundingBox
+ ", crs="
+ crs
+ '}';
}
/** 지원하는 좌표계 목록 */
private List<String> crs = new ArrayList<>();
public WmsLayerInfo() {
this.keywords = new ArrayList<>();
this.crs = new ArrayList<>();
}
// Getters and Setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAbstractText() {
return abstractText;
}
public void setAbstractText(String abstractText) {
this.abstractText = abstractText;
}
public List<String> getKeywords() {
return keywords;
}
public void setKeywords(List<String> keywords) {
this.keywords = keywords;
}
/* ===== convenience methods ===== */
public void addKeyword(String keyword) {
this.keywords.add(keyword);
}
public BoundingBox getBoundingBox() {
return boundingBox;
}
public void setBoundingBox(BoundingBox boundingBox) {
this.boundingBox = boundingBox;
}
public List<String> getCrs() {
return crs;
}
public void setCrs(List<String> crs) {
this.crs = crs;
}
public void addCrs(String crsValue) {
this.crs.add(crsValue);
}
/** BoundingBox 정보를 담는 내부 클래스 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class BoundingBox {
private String crs;
private double minX;
private double minY;
private double maxX;
private double maxY;
public BoundingBox(String crs, double minX, double minY, double maxX, double maxY) {
this.crs = crs;
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
}
// Getters and Setters
public String getCrs() {
return crs;
}
public void setCrs(String crs) {
this.crs = crs;
}
public double getMinX() {
return minX;
}
public void setMinX(double minX) {
this.minX = minX;
}
public double getMinY() {
return minY;
}
public void setMinY(double minY) {
this.minY = minY;
}
public double getMaxX() {
return maxX;
}
public void setMaxX(double maxX) {
this.maxX = maxX;
}
public double getMaxY() {
return maxY;
}
public void setMaxY(double maxY) {
this.maxY = maxY;
}
@Override
public String toString() {
return "BoundingBox{"
+ "crs='"
+ crs
+ '\''
+ ", minX="
+ minX
+ ", minY="
+ minY
+ ", maxX="
+ maxX
+ ", maxY="
+ maxY
+ '}';
}
}
}

View File

@@ -26,5 +26,6 @@ public class WmtsDto {
private String title;
private String description;
private String tag;
private String layerName;
}
}

View File

@@ -1,70 +1,51 @@
package com.kamco.cd.kamcoback.layer.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/** WMTS 레이어 정보를 담는 DTO 클래스 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WmtsLayerInfo {
public String identifier;
public String title;
public String abstractText;
public List<String> keywords = new ArrayList<>();
public BoundingBox boundingBox;
public List<String> formats = new ArrayList<>();
public List<String> tileMatrixSetLinks = new ArrayList<>();
public List<ResourceUrl> resourceUrls = new ArrayList<>();
public List<Style> styles = new ArrayList<>();
private String identifier;
private String title;
private String abstractText;
public void setTitle(String title) {
this.title = title;
private List<String> keywords = new ArrayList<>();
private BoundingBox boundingBox;
private List<String> formats = new ArrayList<>();
private List<TileMatrixSetLink> tileMatrixSetLinks = new ArrayList<>();
private List<ResourceUrl> resourceUrls = new ArrayList<>();
private List<Style> styles = new ArrayList<>();
private List<String> matrixIds = new ArrayList<>(); // 20250130
private String workspace; // 20250130
private List<TileMatric> tileMatrices = new ArrayList<>();
// (선택) 기존 add 메서드 유지하고 싶으면 남겨도 됨
public void addTileMatric(TileMatric tileMatric) {
this.tileMatrices.add(tileMatric);
}
public void setAbstractText(String abstractText) {
this.abstractText = abstractText;
public void addMatrixId(String matrixId) {
this.matrixIds.add(matrixId);
}
public void setBoundingBox(BoundingBox boundingBox) {
this.boundingBox = boundingBox;
}
@Override
public String toString() {
return "WmtsLayerInfo{"
+ "identifier='"
+ identifier
+ '\''
+ ", title='"
+ title
+ '\''
+ ", abstractText='"
+ abstractText
+ '\''
+ ", keywords="
+ keywords
+ ", boundingBox="
+ boundingBox
+ ", formats="
+ formats
+ ", tileMatrixSetLinks="
+ tileMatrixSetLinks
+ ", resourceUrls="
+ resourceUrls
+ ", styles="
+ styles
+ '}';
}
public void addKeyword(String keywowrd) {
this.keywords.add(keywowrd);
public void addKeyword(String keyword) {
this.keywords.add(keyword);
}
public void addFormat(String format) {
this.formats.add(format);
}
public void addTileMatrixSetLink(String tileMatrixSetLink) {
public void addTileMatrixSetLink(TileMatrixSetLink tileMatrixSetLink) {
this.tileMatrixSetLinks.add(tileMatrixSetLink);
}
@@ -77,200 +58,61 @@ public class WmtsLayerInfo {
}
/** BoundingBox 정보를 담는 내부 클래스 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class BoundingBox {
public String crs;
public double lowerCornerX;
public double lowerCornerY;
public double upperCornerX;
public double upperCornerY;
public BoundingBox() {}
public BoundingBox(
String crs,
double lowerCornerX,
double lowerCornerY,
double upperCornerX,
double upperCornerY) {
this.crs = crs;
this.lowerCornerX = lowerCornerX;
this.lowerCornerY = lowerCornerY;
this.upperCornerX = upperCornerX;
this.upperCornerY = upperCornerY;
}
// Getters and Setters
public String getCrs() {
return crs;
}
public void setCrs(String crs) {
this.crs = crs;
}
public double getLowerCornerX() {
return lowerCornerX;
}
public void setLowerCornerX(double lowerCornerX) {
this.lowerCornerX = lowerCornerX;
}
public double getLowerCornerY() {
return lowerCornerY;
}
public void setLowerCornerY(double lowerCornerY) {
this.lowerCornerY = lowerCornerY;
}
public double getUpperCornerX() {
return upperCornerX;
}
public void setUpperCornerX(double upperCornerX) {
this.upperCornerX = upperCornerX;
}
public double getUpperCornerY() {
return upperCornerY;
}
public void setUpperCornerY(double upperCornerY) {
this.upperCornerY = upperCornerY;
}
@Override
public String toString() {
return "BoundingBox{"
+ "crs='"
+ crs
+ '\''
+ ", lowerCorner=["
+ lowerCornerX
+ ", "
+ lowerCornerY
+ ']'
+ ", upperCorner=["
+ upperCornerX
+ ", "
+ upperCornerY
+ ']'
+ '}';
}
private String crs;
private double lowerCornerX;
private double lowerCornerY;
private double upperCornerX;
private double upperCornerY;
}
/** ResourceURL 정보를 담는 내부 클래스 (타일 URL 템플릿) */
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class ResourceUrl {
private String format;
private String resourceType;
private String template;
public ResourceUrl() {}
public ResourceUrl(String format, String resourceType, String template) {
this.format = format;
this.resourceType = resourceType;
this.template = template;
}
// Getters and Setters
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String getResourceType() {
return resourceType;
}
public void setResourceType(String resourceType) {
this.resourceType = resourceType;
}
public String getTemplate() {
return template;
}
public void setTemplate(String template) {
this.template = template;
}
@Override
public String toString() {
return "ResourceUrl{"
+ "format='"
+ format
+ '\''
+ ", resourceType='"
+ resourceType
+ '\''
+ ", template='"
+ template
+ '\''
+ '}';
}
}
/** Style 정보를 담는 내부 클래스 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Style {
private String identifier;
private String title;
@JsonProperty("default")
private boolean isDefault;
}
public Style() {}
/** TileMatrix 정보를 담는 내부 클래스 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class TileMatric {
private String identifier;
private String scaleDenominator;
private String topLeftCorner;
// private String tileWidth;
// private String tileHeight;
// private String matrixWidth;
// private String matrixHeight;
}
public Style(String identifier, String title, boolean isDefault) {
this.identifier = identifier;
this.title = title;
this.isDefault = isDefault;
}
/** TileMatrixSetLink 정보를 담는 내부 클래스 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class TileMatrixSetLink {
private String tileMatrixSet;
private List<String> zoomLevels = new ArrayList<>();
// Getters and Setters
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isDefault() {
return isDefault;
}
public void setDefault(boolean isDefault) {
this.isDefault = isDefault;
}
@Override
public String toString() {
return "Style{"
+ "identifier='"
+ identifier
+ '\''
+ ", title='"
+ title
+ '\''
+ ", isDefault="
+ isDefault
+ '}';
public void addZoomLevel(String zoomLevel) {
this.zoomLevels.add(zoomLevel);
}
}
}

View File

@@ -4,9 +4,11 @@ import com.kamco.cd.kamcoback.common.enums.LayerType;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.layer.dto.LayerDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.Basic;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.IsMapYn;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.LayerMapDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.OrderReq;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.TileUrlDto;
import com.kamco.cd.kamcoback.layer.dto.WmsDto.WmsAddDto;
import com.kamco.cd.kamcoback.layer.dto.WmsDto.WmsAddReqDto;
import com.kamco.cd.kamcoback.layer.dto.WmsLayerInfo;
import com.kamco.cd.kamcoback.layer.dto.WmtsDto.WmtsAddDto;
import com.kamco.cd.kamcoback.layer.dto.WmtsLayerInfo;
@@ -14,6 +16,7 @@ import com.kamco.cd.kamcoback.postgres.core.MapLayerCoreService;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -27,6 +30,15 @@ public class LayerService {
private final WmtsService wmtsService;
private final WmsService wmsService;
@Value("${layer.geoserver-url}")
private String geoserverUrl;
@Value("${layer.wms-path}")
private String wmsPath;
@Value("${layer.wmts-path}")
private String wmtsPath;
/**
* 지도 레이어 관리 목록
*
@@ -55,7 +67,7 @@ public class LayerService {
}
case GEOJSON -> {
mapLayerCoreService.saveGeoJson(dto);
return mapLayerCoreService.saveGeoJson(dto);
}
case WMTS -> {
@@ -66,6 +78,7 @@ public class LayerService {
addDto.setDescription(dto.getDescription());
addDto.setTitle(dto.getTitle());
addDto.setTag(dto.getTag());
addDto.setLayerName(dto.getLayerName());
return mapLayerCoreService.saveWmts(addDto);
}
@@ -76,12 +89,12 @@ public class LayerService {
addDto.setDescription(dto.getDescription());
addDto.setTitle(dto.getTitle());
addDto.setTag(dto.getTag());
addDto.setLayerName(dto.getLayerName());
return mapLayerCoreService.saveWms(addDto);
}
default -> throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST);
}
return null;
}
/**
@@ -124,6 +137,17 @@ public class LayerService {
mapLayerCoreService.update(uuid, dto);
}
/**
* 맵 노출 여부 수정
*
* @param uuid
* @param isMapYn
*/
@Transactional
public void updateIsMap(UUID uuid, IsMapYn isMapYn) {
mapLayerCoreService.updateIsMap(uuid, isMapYn);
}
/**
* wmts tile 조회
*
@@ -142,21 +166,39 @@ public class LayerService {
return wmsService.getTile();
}
/**
* wms 저장
*
* @param dto
* @return
*/
@Transactional
public UUID saveWms(WmsAddReqDto dto) {
// 선택한 tile 상세정보 조회
WmsLayerInfo info = wmsService.getDetail(dto.getTitle());
WmsAddDto addDto = new WmsAddDto();
addDto.setWmsLayerInfo(info);
addDto.setDescription(dto.getDescription());
addDto.setTitle(dto.getTitle());
addDto.setTag(dto.getTag());
return mapLayerCoreService.saveWms(addDto);
public List<LayerMapDto> findLayerMapList(String type) {
List<LayerMapDto> layerMapDtoList = mapLayerCoreService.findLayerMapList(type);
layerMapDtoList.forEach(
dto -> {
if (dto.getLayerType().equals("WMS")) {
dto.setUrl(
String.format(
"%s/%s/%s",
trimSlash(geoserverUrl), trimSlash(wmsPath), dto.getLayerType().toLowerCase()));
} else if (dto.getLayerType().equals("WMTS")) {
dto.setUrl(
String.format(
"%s/%s/%s",
trimSlash(geoserverUrl),
trimSlash(wmtsPath),
dto.getLayerType().toLowerCase()));
}
});
return layerMapDtoList;
}
private String trimSlash(String s) {
if (s == null) {
return "";
}
return s.replaceAll("/+$", "").replaceAll("^/+", "");
}
public LayerDto.YearTileDto getChangeDetectionTileUrl(Integer beforeYear, Integer afterYear) {
return mapLayerCoreService.getChangeDetectionTileUrl(beforeYear, afterYear);
}
public TileUrlDto getChangeDetectionTileOneYearUrl(Integer year) {
return mapLayerCoreService.getChangeDetectionTileOneYearUrl(year);
}
}

View File

@@ -41,7 +41,7 @@ public class WmtsService {
List<String> titles = new ArrayList<>();
for (WmtsLayerInfo layer : layers) {
titles.add(layer.title);
titles.add(layer.getTitle()); // ✅ getter로 변경
}
return titles;
}
@@ -50,7 +50,14 @@ public class WmtsService {
return getLayerInfoByTitle(geoserverUrl, workspace, tile);
}
private List<WmtsLayerInfo> getAllLayers(String geoserverUrl, String workspace) {
/**
* WMTS Capabilities URL에서 모든 레이어 정보를 가져옵니다.
*
* @param geoserverUrl 예: http://localhost:8080
* @param workspace 워크스페이스 이름
* @return 모든 레이어 정보 리스트
*/
public List<WmtsLayerInfo> getAllLayers(String geoserverUrl, String workspace) {
List<WmtsLayerInfo> layers = new ArrayList<>();
try {
// 1. XML 문서 로드 및 파싱
@@ -75,7 +82,7 @@ public class WmtsService {
String title = getChildValue(layerNode, "Title");
if (title != null && !title.trim().isEmpty()) {
WmtsLayerInfo layerInfo = parseLayerNode(layerNode, title);
WmtsLayerInfo layerInfo = parseLayerNode(workspace, doc, layerNode, title);
layers.add(layerInfo);
}
}
@@ -88,151 +95,6 @@ public class WmtsService {
return layers;
}
// 특정 노드 아래의 자식 태그 값 추출 (예: <Title>값)
private String getChildValue(Node parent, String childName) {
Node child = findChildNode(parent, childName);
return (child != null) ? child.getTextContent() : null;
}
// 이름으로 자식 노드 찾기 (Local Name 기준)
private Node findChildNode(Node parent, String localName) {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
// 네임스페이스 접두사(ows:, wmts:)를 무시하고 태그 이름 확인
if (node.getNodeName().endsWith(":" + localName) || node.getNodeName().equals(localName)) {
return node;
}
}
return null;
}
// 레이어 노드를 Java 객체로 변환
private WmtsLayerInfo parseLayerNode(Node layerNode, String title) {
WmtsLayerInfo info = new WmtsLayerInfo();
info.title = title;
info.identifier = getChildValue(layerNode, "Identifier");
info.abstractText = getChildValue(layerNode, "Abstract");
// Keywords 파싱
// 구조: <ows:Keywords><ows:Keyword>...</ows:Keyword></ows:Keywords>
info.keywords = getChildValues(layerNode, "Keywords", "Keyword");
// BoundingBox 파싱 (WGS84BoundingBox 기준)
info.boundingBox = parseBoundingBox(layerNode);
// Formats 파싱
info.formats = getChildValuesDirect(layerNode, "Format");
// TileMatrixSetLink 파싱
// 구조: <TileMatrixSetLink><TileMatrixSet>...</TileMatrixSet></TileMatrixSetLink>
info.tileMatrixSetLinks = getChildValues(layerNode, "TileMatrixSetLink", "TileMatrixSet");
// ResourceURL 파싱
info.resourceUrls = parseResourceUrls(layerNode);
// Styles 파싱
info.styles = parseStyles(layerNode);
return info;
}
// 특정 노드 아래의 반복되는 자식 구조 값 추출 (예: Keywords -> Keyword)
private List<String> getChildValues(Node parent, String wrapperName, String childName) {
List<String> results = new ArrayList<>();
Node wrapper = findChildNode(parent, wrapperName);
if (wrapper != null) {
NodeList children = wrapper.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().endsWith(childName)) {
results.add(node.getTextContent());
}
}
}
return results;
}
private WmtsLayerInfo.BoundingBox parseBoundingBox(Node layerNode) {
// 보통 <ows:WGS84BoundingBox>를 찾음
Node bboxNode = findChildNode(layerNode, "WGS84BoundingBox");
if (bboxNode == null) bboxNode = findChildNode(layerNode, "BoundingBox");
if (bboxNode != null) {
WmtsLayerInfo.BoundingBox bbox = new WmtsLayerInfo.BoundingBox();
bbox.crs = getAttributeValue(bboxNode, "crs"); // WGS84는 보통 CRS 속성이 없을 수 있음(Default EPSG:4326)
String lowerCorner = getChildValue(bboxNode, "LowerCorner");
String upperCorner = getChildValue(bboxNode, "UpperCorner");
if (lowerCorner != null) {
String[] coords = lowerCorner.split(" ");
bbox.lowerCornerX = Double.parseDouble(coords[0]);
bbox.lowerCornerY = Double.parseDouble(coords[1]);
}
if (upperCorner != null) {
String[] coords = upperCorner.split(" ");
bbox.upperCornerX = Double.parseDouble(coords[0]);
bbox.upperCornerY = Double.parseDouble(coords[1]);
}
return bbox;
}
return null;
}
private String getAttributeValue(Node node, String attrName) {
if (node.hasAttributes()) {
Node attr = node.getAttributes().getNamedItem(attrName);
if (attr != null) return attr.getNodeValue();
}
return null;
}
// Wrapper 없이 바로 반복되는 값 추출 (예: Format)
private List<String> getChildValuesDirect(Node parent, String childName) {
List<String> results = new ArrayList<>();
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().endsWith(childName)) {
results.add(node.getTextContent());
}
}
return results;
}
private List<WmtsLayerInfo.ResourceUrl> parseResourceUrls(Node layerNode) {
List<WmtsLayerInfo.ResourceUrl> list = new ArrayList<>();
NodeList children = layerNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().contains("ResourceURL")) { // local-name check simplification
WmtsLayerInfo.ResourceUrl url = new WmtsLayerInfo.ResourceUrl();
url.setFormat(getAttributeValue(node, "format"));
url.setResourceType(getAttributeValue(node, "resourceType"));
url.setTemplate(getAttributeValue(node, "template"));
list.add(url);
}
}
return list;
}
private List<WmtsLayerInfo.Style> parseStyles(Node layerNode) {
List<WmtsLayerInfo.Style> styles = new ArrayList<>();
NodeList children = layerNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().endsWith("Style")) {
WmtsLayerInfo.Style style = new WmtsLayerInfo.Style();
style.setDefault(Boolean.parseBoolean(getAttributeValue(node, "isDefault")));
style.setIdentifier(getChildValue(node, "Identifier"));
style.setTitle(getChildValue(node, "Title"));
styles.add(style);
}
}
return styles;
}
/**
* WMTS Capabilities URL에서 특정 타이틀의 레이어 정보를 가져옵니다. // * @param capabilitiesUrl 예:
* http://localhost:8080/geoserver/gwc/service/wmts?REQUEST=GetCapabilities
@@ -268,7 +130,7 @@ public class WmtsService {
// 타이틀이 일치하면 객체 매핑 시작
if (title != null && title.trim().equals(targetTitle)) {
return parseLayerNode(layerNode, title);
return parseLayerNode(workspace, doc, layerNode, title);
}
}
@@ -279,4 +141,325 @@ public class WmtsService {
return null; // 찾지 못한 경우
}
// 레이어 노드를 Java 객체로 변환 20250130
private WmtsLayerInfo parseLayerNode(
String workspace, Document doc, Node layerNode, String title) {
WmtsLayerInfo info = new WmtsLayerInfo();
info.setWorkspace(workspace); // 20250130
info.setTitle(title);
info.setIdentifier(getChildValue(layerNode, "Identifier"));
info.setAbstractText(getChildValue(layerNode, "Abstract"));
// Keywords 파싱
info.setKeywords(getChildValues(layerNode, "Keywords", "Keyword"));
// BoundingBox 파싱 (WGS84BoundingBox 기준)
info.setBoundingBox(parseBoundingBox(layerNode));
// Formats 파싱
info.setFormats(getChildValuesDirect(layerNode, "Format"));
// TileMatrixSetLink 파싱 (TileMatrixSet + Zoom Levels)
info.setTileMatrixSetLinks(parseTileMatrixSetLinks(layerNode));
// TileMatrixSetLimits에서 줌 레벨 추출 (개별 zoom 리스트)
info.setMatrixIds(parseMatrixIds(layerNode)); // 20260130
// ResourceURL 파싱
info.setResourceUrls(parseResourceUrls(layerNode));
// Styles 파싱
info.setStyles(parseStyles(layerNode));
// TileMatrixSet의 TileMatrix 정보 파싱
List<String> tileMatrixSetNames = new ArrayList<>();
if (info.getTileMatrixSetLinks() != null) {
for (WmtsLayerInfo.TileMatrixSetLink link : info.getTileMatrixSetLinks()) {
tileMatrixSetNames.add(link.getTileMatrixSet());
}
}
info.setTileMatrices(parseTileMatrices(doc, tileMatrixSetNames));
return info;
}
// --- Helper Methods ---
private WmtsLayerInfo.BoundingBox parseBoundingBox(Node layerNode) {
// 보통 <ows:WGS84BoundingBox>를 찾음
Node bboxNode = findChildNode(layerNode, "WGS84BoundingBox");
if (bboxNode == null) bboxNode = findChildNode(layerNode, "BoundingBox");
if (bboxNode != null) {
WmtsLayerInfo.BoundingBox bbox = new WmtsLayerInfo.BoundingBox();
// WGS84는 CRS 속성이 없을 수 있음
bbox.setCrs(getAttributeValue(bboxNode, "crs"));
String lowerCorner = getChildValue(bboxNode, "LowerCorner");
String upperCorner = getChildValue(bboxNode, "UpperCorner");
if (lowerCorner != null) {
String[] coords = lowerCorner.split(" ");
bbox.setLowerCornerX(Double.parseDouble(coords[0]));
bbox.setLowerCornerY(Double.parseDouble(coords[1]));
}
if (upperCorner != null) {
String[] coords = upperCorner.split(" ");
bbox.setUpperCornerX(Double.parseDouble(coords[0]));
bbox.setUpperCornerY(Double.parseDouble(coords[1]));
}
return bbox;
}
return null;
}
private List<WmtsLayerInfo.ResourceUrl> parseResourceUrls(Node layerNode) {
List<WmtsLayerInfo.ResourceUrl> list = new ArrayList<>();
NodeList children = layerNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().contains("ResourceURL")) { // local-name check simplification
WmtsLayerInfo.ResourceUrl url = new WmtsLayerInfo.ResourceUrl();
url.setFormat(getAttributeValue(node, "format"));
url.setResourceType(getAttributeValue(node, "resourceType"));
url.setTemplate(getAttributeValue(node, "template"));
list.add(url);
}
}
return list;
}
private List<WmtsLayerInfo.Style> parseStyles(Node layerNode) {
List<WmtsLayerInfo.Style> styles = new ArrayList<>();
NodeList children = layerNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().endsWith("Style")) {
WmtsLayerInfo.Style style = new WmtsLayerInfo.Style();
style.setDefault(Boolean.parseBoolean(getAttributeValue(node, "isDefault")));
style.setIdentifier(getChildValue(node, "Identifier"));
style.setTitle(getChildValue(node, "Title"));
styles.add(style);
}
}
return styles;
}
/**
* TileMatrixSetLimits에서 줌 레벨을 추출합니다. 예: "EPSG:4326:0" → "0", "EPSG:4326:1" → "1"
*
* @param layerNode Layer 노드
* @return 줌 레벨 문자열 리스트
*/
private List<String> parseMatrixIds(Node layerNode) {
List<String> matrixIds = new ArrayList<>();
NodeList children = layerNode.getChildNodes();
// 모든 TileMatrixSetLink 찾기
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().contains("TileMatrixSetLink")) {
// TileMatrixSetLimits 찾기
Node limitsNode = findChildNode(node, "TileMatrixSetLimits");
if (limitsNode != null) {
NodeList limitsList = limitsNode.getChildNodes();
// 각 TileMatrixLimits 처리
for (int j = 0; j < limitsList.getLength(); j++) {
Node limitNode = limitsList.item(j);
if (limitNode.getNodeName().contains("TileMatrixLimits")) {
// TileMatrix 또는 Identifier 값 추출
String identifier = getChildValue(limitNode, "TileMatrix");
if (identifier == null) {
identifier = getChildValue(limitNode, "Identifier");
}
// 마지막 콜론 이후 값(줌 레벨) 추출
if (identifier != null && identifier.contains(":")) {
String[] parts = identifier.split(":");
String zoomLevel = parts[parts.length - 1];
matrixIds.add(zoomLevel);
}
}
}
}
}
}
return matrixIds;
}
/**
* TileMatrixSetLink 정보를 파싱합니다. 각 TileMatrixSetLink에서 TileMatrixSet 이름과 줌 레벨들을 추출합니다.
*
* @param layerNode Layer 노드
* @return TileMatrixSetLink 객체 리스트
*/
private List<WmtsLayerInfo.TileMatrixSetLink> parseTileMatrixSetLinks(Node layerNode) {
List<WmtsLayerInfo.TileMatrixSetLink> links = new ArrayList<>();
NodeList children = layerNode.getChildNodes();
// 모든 TileMatrixSetLink 찾기
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().contains("TileMatrixSetLink")) {
// TileMatrixSet 이름 추출
String tileMatrixSet = getChildValue(node, "TileMatrixSet");
// 줌 레벨들 추출
List<String> zoomLevels = new ArrayList<>();
Node limitsNode = findChildNode(node, "TileMatrixSetLimits");
if (limitsNode != null) {
NodeList limitsList = limitsNode.getChildNodes();
for (int j = 0; j < limitsList.getLength(); j++) {
Node limitNode = limitsList.item(j);
if (limitNode.getNodeName().contains("TileMatrixLimits")) {
// TileMatrix 또는 Identifier 값 추출
String identifier = getChildValue(limitNode, "TileMatrix");
if (identifier == null) {
identifier = getChildValue(limitNode, "Identifier");
}
// 마지막 콜론 이후 값(줌 레벨) 추출
if (identifier != null && identifier.contains(":")) {
String[] parts = identifier.split(":");
String zoomLevel = parts[parts.length - 1];
zoomLevels.add(zoomLevel);
}
}
}
}
// TileMatrixSetLink 객체 생성 및 추가
if (tileMatrixSet != null) {
WmtsLayerInfo.TileMatrixSetLink link =
new WmtsLayerInfo.TileMatrixSetLink(tileMatrixSet, zoomLevels);
links.add(link);
}
}
}
return links;
}
/**
* Document에서 TileMatrixSet의 TileMatrix 정보를 파싱합니다.
*
* @param doc WMTS Capabilities Document
* @param tileMatrixSetNames 조회할 TileMatrixSet 이름 리스트
* @return TileMatric 객체 리스트
*/
private List<WmtsLayerInfo.TileMatric> parseTileMatrices(
Document doc, List<String> tileMatrixSetNames) {
List<WmtsLayerInfo.TileMatric> allMatrices = new ArrayList<>();
try {
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
// 각 TileMatrixSet 이름에 대해 TileMatrix 찾기
for (String tileMatrixSetName : tileMatrixSetNames) {
// TileMatrixSet 찾기
String expression = "//*[local-name()='TileMatrixSet']";
NodeList tileMatrixSetNodes =
(NodeList) xpath.compile(expression).evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < tileMatrixSetNodes.getLength(); i++) {
Node tileMatrixSetNode = tileMatrixSetNodes.item(i);
String identifier = getChildValue(tileMatrixSetNode, "Identifier");
// 일치하는 TileMatrixSet 찾으면 TileMatrix 파싱
if (tileMatrixSetName.equals(identifier)) {
NodeList children = tileMatrixSetNode.getChildNodes();
for (int j = 0; j < children.getLength(); j++) {
Node node = children.item(j);
if (node.getNodeName().contains("TileMatrix")) {
WmtsLayerInfo.TileMatric tileMatric = new WmtsLayerInfo.TileMatric();
// TileMatrix 정보 추출
tileMatric.setScaleDenominator(getChildValue(node, "ScaleDenominator"));
tileMatric.setTopLeftCorner(getChildValue(node, "TopLeftCorner"));
tileMatric.setIdentifier(getChildValue(node, "Identifier"));
/* 미사용 정보 주석처리함
tileMatric.setTileWidth(getChildValue(node, "TileWidth"));
tileMatric.setTileHeight(getChildValue(node, "TileHeight"));
tileMatric.setMatrixWidth(getChildValue(node, "MatrixWidth"));
tileMatric.setMatrixHeight(getChildValue(node, "MatrixHeight"));
*/
allMatrices.add(tileMatric);
}
}
break; // 일치하는 TileMatrixSet 찾았으므로 다음으로
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return allMatrices;
}
// 특정 노드 아래의 자식 태그 값 추출 (예: <Title>값)
private String getChildValue(Node parent, String childName) {
Node child = findChildNode(parent, childName);
return (child != null) ? child.getTextContent() : null;
}
// 특정 노드 아래의 반복되는 자식 구조 값 추출 (예: Keywords -> Keyword)
private List<String> getChildValues(Node parent, String wrapperName, String childName) {
List<String> results = new ArrayList<>();
Node wrapper = findChildNode(parent, wrapperName);
if (wrapper != null) {
NodeList children = wrapper.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().endsWith(childName)) {
results.add(node.getTextContent());
}
}
}
return results;
}
// Wrapper 없이 바로 반복되는 값 추출 (예: Format)
private List<String> getChildValuesDirect(Node parent, String childName) {
List<String> results = new ArrayList<>();
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeName().endsWith(childName)) {
results.add(node.getTextContent());
}
}
return results;
}
// 이름으로 자식 노드 찾기 (Local Name 기준)
private Node findChildNode(Node parent, String localName) {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
// 네임스페이스 접두사(ows:, wmts:)를 무시하고 태그 이름 확인
if (node.getNodeName().endsWith(":" + localName) || node.getNodeName().equals(localName)) {
return node;
}
}
return null;
}
private String getAttributeValue(Node node, String attrName) {
if (node.hasAttributes()) {
Node attr = node.getAttributes().getNamedItem(attrName);
if (attr != null) return attr.getNodeValue();
}
return null;
}
}

View File

@@ -7,6 +7,7 @@ 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.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.UUID;
@@ -77,6 +78,34 @@ public class MapSheetMngDto {
@Schema(description = "선택폴더경로", example = "D:\\app\\original-images\\2022")
private String mngPath;
// Tile 등록 로직
@Schema(description = "url")
private String url;
@Schema(description = "좌측상단 경도", example = "126.0")
private BigDecimal minLon;
@Schema(description = "좌측상단 위도", example = "34.0")
private BigDecimal minLat;
@Schema(description = "우측하단 경도", example = "130.0")
private BigDecimal maxLon;
@Schema(description = "우측하단 위도", example = "38.5")
private BigDecimal maxLat;
@Schema(description = "zoom min", example = "5")
private Short minZoom;
@Schema(description = "zoom max", example = "18")
private Short maxZoom;
@Schema(description = "tag")
private String tag;
@Schema(description = "crs 좌표계")
private String crs;
@JsonIgnore private Long createdUid;
}
@@ -155,11 +184,14 @@ public class MapSheetMngDto {
}
public long getSyncErrorTotCnt() {
return this.syncNotPaireCnt + this.syncDuplicateCnt + this.syncFaultCnt;
return this.syncNotPaireCnt + this.syncDuplicateCnt + this.syncFaultCnt + this.syncNoFileCnt;
}
public long getSyncErrorExecTotCnt() {
return this.syncNotPaireExecCnt + this.syncDuplicateExecCnt + this.syncFaultExecCnt;
return this.syncNotPaireExecCnt
+ this.syncDuplicateExecCnt
+ this.syncFaultExecCnt
+ this.syncNoFileExecCnt;
}
public String getMngState() {

View File

@@ -341,12 +341,14 @@ public class MapSheetMngService {
String dirPath = syncRootDir + srchDto.getDirPath();
String sortType = "name desc";
List<FIleChecker.Folder> folderList = FIleChecker.getFolderAll(dirPath);
List<FIleChecker.Folder> folderList =
FIleChecker.getFolderAll(dirPath).stream()
.filter(dir -> dir.getIsValid().equals(true))
.toList();
int folderTotCnt = folderList.size();
int folderErrTotCnt =
(int)
folderList.stream().filter(dto -> dto.getIsValid().toString().equals("false")).count();
(int) folderList.stream().filter(dto -> dto.getIsValid().equals(false)).count();
return new FoldersDto(dirPath, folderTotCnt, folderErrTotCnt, folderList);
}

View File

@@ -111,8 +111,8 @@ public class MembersDto {
@EnumValid(enumClass = RoleType.class, message = "userRole은 ADMIN, LABELER, REVIEWER 만 가능합니다.")
private String userRole;
@Schema(description = "사번", example = "K20251212001")
@Size(max = 50)
@Schema(description = "사번", example = "123456")
@Size(max = 6)
private String employeeNo;
@Schema(description = "이름", example = "홍길동")

View File

@@ -11,7 +11,7 @@ import lombok.ToString;
@ToString(exclude = "password")
public class SignInRequest {
@Schema(description = "사용자 ID", example = "1234567")
@Schema(description = "사용자 ID", example = "123456")
private String username;
@Schema(description = "비밀번호", example = "qwe123!@#")

View File

@@ -9,8 +9,10 @@ import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MenuService {

View File

@@ -243,8 +243,6 @@ public class ModelMngService {
List<Basic> files =
FIleChecker.getFilesFromAllDepth(dirPath, "*", "pth,py,json", 10, "name", startPos, endPos);
boolean hasPt = false; // pt 파일 존재 여부
for (Basic dto : files) {
// 예: 파일명 출력 및 추가 작업
String foldNm = dto.getFullPath().replace(dto.getFileNm(), "");
@@ -265,17 +263,15 @@ public class ModelMngService {
}
}
if (!hasPt) {
// cls model 적용
String defaultPath = ptPath;
String defaultFileName = ptFileName;
String defaultPath = ptPath;
String defaultFileName = ptFileName;
Path ptPath = Paths.get(defaultPath, defaultFileName);
Path ptPath = Paths.get(defaultPath, defaultFileName);
if (Files.exists(ptPath)) {
modelUploadResDto.setClsModelPath(defaultPath);
modelUploadResDto.setClsModelFileName(defaultFileName);
}
if (Files.exists(ptPath)) {
modelUploadResDto.setClsModelPath(defaultPath);
modelUploadResDto.setClsModelFileName(defaultFileName);
}
// int fileListPos = 0;

View File

@@ -1,9 +1,13 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.Basic;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LabelSendDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GukYuinLinkFacts;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.LearnInfo;
import com.kamco.cd.kamcoback.postgres.repository.Inference.MapSheetLearnRepository;
import com.kamco.cd.kamcoback.postgres.repository.gukyuin.GukYuinRepository;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -29,8 +33,8 @@ public class GukYuinCoreService {
gukYuinRepository.updateGukYuinMastRegResult(resultBody);
}
public void updateGukYuinMastRegRemove(Basic resultBody) {
gukYuinRepository.updateGukYuinMastRegRemove(resultBody);
public void updateGukYuinMastRegRemove(String chnDtctId) {
gukYuinRepository.updateGukYuinMastRegRemove(chnDtctId);
}
public void updateInferenceGeomDataPnuCnt(String chnDtctObjtId, long pnuCnt) {
@@ -41,7 +45,36 @@ public class GukYuinCoreService {
return gukYuinRepository.findMapSheetAnalDataInferenceGeomUid(chnDtctObjtId);
}
public void insertGeoUidPnuData(Long geoUid, String[] pnuList) {
gukYuinRepository.insertGeoUidPnuData(geoUid, pnuList);
public void insertGeoUidPnuData(Long geoUid, String[] pnuList, String chnDtctObjtId) {
gukYuinRepository.insertGeoUidPnuData(geoUid, pnuList, chnDtctObjtId);
}
public LearnInfo findMapSheetLearnInfo(UUID uuid) {
return gukYuinRepository.findMapSheetLearnInfo(uuid);
}
public Integer findMapSheetLearnYearStage(Integer compareYyyy, Integer targetYyyy) {
return gukYuinRepository.findMapSheetLearnYearStage(compareYyyy, targetYyyy);
}
public void updateAnalInferenceApplyDttm(Basic registRes) {
gukYuinRepository.updateAnalInferenceApplyDttm(registRes);
}
public List<LabelSendDto> findLabelingCompleteSendList(LocalDate yesterday) {
return gukYuinRepository.findLabelingCompleteSendList(yesterday);
}
public Long findMapSheetLearnInfoByYyyy(
Integer compareYyyy, Integer targetYyyy, Integer maxStage) {
return gukYuinRepository.findMapSheetLearnInfoByYyyy(compareYyyy, targetYyyy, maxStage);
}
public void updateMapSheetLearnGukyuinEndStatus(Long learnId) {
gukYuinRepository.updateMapSheetLearnGukyuinEndStatus(learnId);
}
public void updateMapSheetInferenceLabelEndStatus(Long learnId) {
gukYuinRepository.updateMapSheetInferenceLabelEndStatus(learnId);
}
}

View File

@@ -1,8 +1,11 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinStatus;
import com.kamco.cd.kamcoback.postgres.repository.gukyuin.GukYuinRepository;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class GukYuinJobCoreService {
@@ -13,11 +16,12 @@ public class GukYuinJobCoreService {
this.gukYuinRepository = gukYuinRepository;
}
public List<String> findGukyuinApplyIngUidList() {
return gukYuinRepository.findGukyuinApplyIngUidList();
@Transactional
public void updateGukYuinApplyStateComplete(Long id, GukYuinStatus status) {
gukYuinRepository.updateGukYuinApplyStateComplete(id, status);
}
public void updateGukYuinApplyStateComplete(String uid) {
gukYuinRepository.updateGukYuinApplyStateComplete(uid);
public List<LearnKeyDto> findGukyuinApplyStatusUidList(List<String> gukYuinStatus) {
return gukYuinRepository.findGukyuinApplyStatusUidList(gukYuinStatus);
}
}

View File

@@ -0,0 +1,26 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GeomUidDto;
import com.kamco.cd.kamcoback.postgres.repository.gukyuin.GukYuinLabelJobRepository;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class GukYuinLabelJobCoreService {
private final GukYuinLabelJobRepository gukYuinLabelRepository;
public GukYuinLabelJobCoreService(GukYuinLabelJobRepository gukYuinLabelRepository) {
this.gukYuinLabelRepository = gukYuinLabelRepository;
}
public List<GeomUidDto> findYesterdayLabelingCompleteList() {
return gukYuinLabelRepository.findYesterdayLabelingCompleteList();
}
@Transactional
public void updateAnalDataInferenceGeomSendDttm(Long geoUid) {
gukYuinLabelRepository.updateAnalDataInferenceGeomSendDttm(geoUid);
}
}

View File

@@ -0,0 +1,44 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinStatus;
import com.kamco.cd.kamcoback.postgres.repository.gukyuin.GukYuinPnuJobRepository;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class GukYuinPnuJobCoreService {
private final GukYuinPnuJobRepository gukYuinPnuRepository;
public GukYuinPnuJobCoreService(GukYuinPnuJobRepository gukYuinPnuRepository) {
this.gukYuinPnuRepository = gukYuinPnuRepository;
}
public void updateGukYuinApplyStateComplete(Long id, GukYuinStatus status) {
gukYuinPnuRepository.updateGukYuinApplyStateComplete(id, status);
}
public List<LearnKeyDto> findGukyuinApplyStatusUidList(List<String> gukYuinStatus) {
return gukYuinPnuRepository.findGukyuinApplyStatusUidList(gukYuinStatus);
}
public long upsertMapSheetDataAnalGeomPnu(String chnDtctObjtId, String[] pnuList) {
return gukYuinPnuRepository.upsertMapSheetDataAnalGeomPnu(chnDtctObjtId, pnuList);
}
@Transactional
public void updateInferenceGeomDataPnuCnt(String chnDtctObjtId, long pnuCnt) {
gukYuinPnuRepository.updateInferenceGeomDataPnuCnt(chnDtctObjtId, pnuCnt);
}
public Long findMapSheetAnalDataInferenceGeomUid(String chnDtctObjtId) {
return gukYuinPnuRepository.findMapSheetAnalDataInferenceGeomUid(chnDtctObjtId);
}
@Transactional
public void insertGeoUidPnuData(Long geoUid, String[] pnuList, String chnDtctObjtId) {
gukYuinPnuRepository.insertGeoUidPnuData(geoUid, pnuList, chnDtctObjtId);
}
}

View File

@@ -0,0 +1,76 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.StbltResult;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.RlbDtctMastDto;
import com.kamco.cd.kamcoback.postgres.entity.PnuEntity;
import com.kamco.cd.kamcoback.postgres.repository.gukyuin.GukYuinStbltJobRepository;
import java.time.ZonedDateTime;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class GukYuinStbltJobCoreService {
private final GukYuinStbltJobRepository gukYuinStbltRepository;
public GukYuinStbltJobCoreService(GukYuinStbltJobRepository gukYuinStbltRepository) {
this.gukYuinStbltRepository = gukYuinStbltRepository;
}
public List<LearnKeyDto> findGukYuinEligibleForSurveyList(String status) {
return gukYuinStbltRepository.findGukYuinEligibleForSurveyList(status);
}
@Transactional
public void updateGukYuinEligibleForSurvey(String resultUid, RlbDtctMastDto stbltDto) {
PnuEntity entity =
gukYuinStbltRepository.findPnuEntityByResultUid(resultUid, stbltDto.getPnu());
if (entity != null) {
entity.setPnuDtctId(stbltDto.getPnuDtctId());
entity.setPnu(stbltDto.getPnu());
entity.setLrmSyncYmd(stbltDto.getLrmSyncYmd());
entity.setPnuSyncYmd(stbltDto.getPnuSyncYmd());
entity.setMpqdNo(stbltDto.getMpqdNo());
entity.setCprsYr(stbltDto.getCprsYr());
entity.setCrtrYr(stbltDto.getCrtrYr());
entity.setChnDtctSno(stbltDto.getChnDtctSno());
entity.setChnDtctId(stbltDto.getChnDtctId());
entity.setChnDtctMstId(stbltDto.getChnDtctMstId());
entity.setChnDtctObjtId(stbltDto.getChnDtctObjtId());
entity.setChnDtctContId(stbltDto.getChnDtctContId());
entity.setChnCd(stbltDto.getChnCd());
entity.setBfClsCd(stbltDto.getBfClsCd());
entity.setBfClsProb(stbltDto.getBfClsProb());
entity.setAfClsCd(stbltDto.getAfClsCd());
entity.setAfClsProb(stbltDto.getAfClsProb());
entity.setPnuSqms(stbltDto.getPnuSqms());
entity.setPnuDtctSqms(stbltDto.getPnuDtctSqms());
entity.setChnDtctSqms(stbltDto.getChnDtctSqms());
entity.setStbltYn(stbltDto.getStbltYn());
entity.setIncyCd(stbltDto.getIncyCd());
entity.setIncyRsnCont(stbltDto.getIncyRsnCont());
entity.setLockYn(stbltDto.getLockYn());
entity.setLblYn(stbltDto.getLblYn());
entity.setChgYn(stbltDto.getChgYn());
entity.setRsatctNo(stbltDto.getRsatctNo());
entity.setRmk(stbltDto.getRmk());
entity.setCrtDt(stbltDto.getCrtDt());
entity.setCrtEpno(stbltDto.getCrtEpno());
entity.setCrtIp(stbltDto.getCrtIp());
entity.setChgDt(stbltDto.getChgDt());
entity.setChgIp(stbltDto.getChgIp());
entity.setDelYn(stbltDto.getDelYn().equals("Y"));
entity.setCreatedDttm(ZonedDateTime.now());
gukYuinStbltRepository.save(entity);
}
}
@Transactional
public void updateGukYuinObjectStbltYn(String resultUid, StbltResult stbResult) {
gukYuinStbltRepository.updateGukYuinObjectStbltYn(resultUid, stbResult);
}
}

View File

@@ -37,10 +37,12 @@ import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.constraints.NotNull;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@@ -81,23 +83,29 @@ public class InferenceResultCoreService {
* @param req
*/
public UUID saveInferenceInfo(InferenceResultDto.RegReq req, List<MngListDto> targetList) {
String firstMapSheetName = null;
String mapSheetName = "";
int detectingCnt = 0;
for (MngListDto dto : targetList) {
if (detectingCnt == 0) {
firstMapSheetName = dto.getMapSheetName();
}
detectingCnt++;
}
List<MngListDto> distinctList =
targetList.stream()
.filter(dto -> dto.getMapSheetName() != null && !dto.getMapSheetName().isBlank())
.collect(
Collectors.toMap(
MngListDto::getMapSheetName,
dto -> dto,
(existing, duplicate) -> existing,
LinkedHashMap::new))
.values()
.stream()
.toList();
int detectingCnt = distinctList.size();
String mapSheetName;
if (detectingCnt == 0) {
mapSheetName = "";
} else if (detectingCnt == 1) {
mapSheetName = firstMapSheetName + " 1건";
mapSheetName = distinctList.get(0).getMapSheetName() + " 1건";
} else {
mapSheetName = firstMapSheetName + "" + (detectingCnt - 1) + "";
mapSheetName = distinctList.get(0).getMapSheetName() + "" + (detectingCnt - 1) + "";
}
MapSheetLearnEntity mapSheetLearnEntity = new MapSheetLearnEntity();
@@ -113,7 +121,7 @@ public class InferenceResultCoreService {
mapSheetLearnEntity.setCreatedUid(userUtil.getId());
mapSheetLearnEntity.setMapSheetCnt(mapSheetName);
mapSheetLearnEntity.setDetectingCnt(0L);
mapSheetLearnEntity.setTotalJobs((long) detectingCnt);
mapSheetLearnEntity.setTotalJobs((long) targetList.size());
// 회차는 국유인 반영할때 update로 변경됨
// mapSheetLearnEntity.setStage(

View File

@@ -1,8 +1,11 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceLearnDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveInfo;
@@ -13,12 +16,18 @@ import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.ProjectInfo;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics;
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
import com.kamco.cd.kamcoback.postgres.repository.batch.BatchStepHistoryRepository;
import com.kamco.cd.kamcoback.postgres.repository.label.LabelAllocateRepository;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@Service
@@ -26,6 +35,10 @@ import org.springframework.stereotype.Service;
public class LabelAllocateCoreService {
private final LabelAllocateRepository labelAllocateRepository;
private final BatchStepHistoryRepository batchStepHistoryRepository;
@Value("${file.dataset-response}")
private String responsePath;
public List<AllocateInfoDto> fetchNextIds(Long lastId, Long batchSize, UUID uuid) {
return labelAllocateRepository.fetchNextIds(lastId, batchSize, uuid);
@@ -234,4 +247,34 @@ public class LabelAllocateCoreService {
public Long findLabelingIngProcessCnt() {
return labelAllocateRepository.findLabelingIngProcessCnt();
}
public boolean isDownloadable(UUID uuid) {
InferenceLearnDto dto = labelAllocateRepository.findLabelingIngProcessId(uuid);
if (dto == null) {
return false;
}
// 파일이 있는지만 확인
Path path = Paths.get(responsePath).resolve(dto.getLearnUid() + ".zip");
if (!Files.exists(path) || !Files.isRegularFile(path)) {
// 실제 파일만 true (디렉터리는 제외)
return false;
}
// 다운로드 확인할 학습데이터가 라벨링중인 경우 파일 생성여부가 정상인지 확인
if (dto.getAnalState().equals(LabelMngState.ASSIGNED.getId())
|| dto.getAnalState().equals(LabelMngState.ING.getId())) {
return batchStepHistoryRepository.isDownloadable(dto.getAnalId());
}
return true;
}
public String findLearnUid(UUID uuid) {
return labelAllocateRepository
.findLearnUid(uuid)
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
}
}

View File

@@ -6,7 +6,10 @@ import com.kamco.cd.kamcoback.common.enums.LayerType;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.common.utils.UserUtil;
import com.kamco.cd.kamcoback.layer.dto.LayerDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.IsMapYn;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.LayerMapDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.OrderReq;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.TileUrlDto;
import com.kamco.cd.kamcoback.layer.dto.WmsDto.WmsAddDto;
import com.kamco.cd.kamcoback.layer.dto.WmtsDto.WmtsAddDto;
import com.kamco.cd.kamcoback.postgres.entity.MapLayerEntity;
@@ -25,6 +28,7 @@ import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class MapLayerCoreService {
private final MapLayerRepository mapLayerRepository;
private final UserUtil userUtil;
private final ObjectMapper objectMapper;
@@ -63,10 +67,6 @@ public class MapLayerCoreService {
.findDetailByUuid(uuid)
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
if (LayerType.TILE.getId().equals(entity.getLayerType())) {
throw new CustomApiException("UNPROCESSABLE_ENTITY", HttpStatus.CONFLICT);
}
entity.setIsDeleted(true);
entity.setUpdatedUid(userUtil.getId());
entity.setUpdatedDttm(ZonedDateTime.now());
@@ -87,6 +87,10 @@ public class MapLayerCoreService {
entity.setDescription(dto.getDescription());
}
if (dto.getLayerName() != null) {
entity.setLayerName(dto.getLayerName());
}
if (dto.getUrl() != null) {
entity.setUrl(dto.getUrl());
}
@@ -127,10 +131,40 @@ public class MapLayerCoreService {
entity.setIsLabelingMap(dto.getIsLabelingMap());
}
if (dto.getCrs() != null) {
entity.setCrs(dto.getCrs());
}
entity.setUpdatedUid(userUtil.getId());
entity.setUpdatedDttm(ZonedDateTime.now());
}
/**
* 맵 노출 여부
*
* @param uuid
* @param isMapYn
*/
public void updateIsMap(UUID uuid, IsMapYn isMapYn) {
MapLayerEntity entity =
mapLayerRepository
.findDetailByUuid(uuid)
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
LayerDto.MapType mapType;
try {
mapType = LayerDto.MapType.valueOf(isMapYn.getMapType());
} catch (IllegalArgumentException e) {
throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST);
}
switch (mapType) {
case CHANGE_MAP -> entity.setIsChangeMap(isMapYn.getIsMapYn());
case LABELING_MAP -> entity.setIsLabelingMap(isMapYn.getIsMapYn());
}
}
/**
* 순서 수정
*
@@ -180,15 +214,10 @@ public class MapLayerCoreService {
* @param dto
*/
public UUID saveTile(LayerDto.AddReq dto) {
LayerDto.SearchReq searchReq = new LayerDto.SearchReq();
searchReq.setLayerType(LayerType.TILE.getId());
List<LayerDto.Basic> entityList = mapLayerRepository.findAllLayer(searchReq);
if (!entityList.isEmpty()) {
throw new CustomApiException("DUPLICATE_DATA", HttpStatus.CONFLICT);
}
Long order = mapLayerRepository.findSortOrderDesc();
MapLayerEntity mapLayerEntity = new MapLayerEntity();
mapLayerEntity.setLayerName(dto.getLayerName());
mapLayerEntity.setDescription(dto.getDescription());
mapLayerEntity.setUrl(dto.getUrl());
mapLayerEntity.setTag(dto.getTag());
@@ -198,11 +227,12 @@ public class MapLayerCoreService {
mapLayerEntity.setMaxLat(dto.getMaxLat());
mapLayerEntity.setMinZoom(dto.getMin());
mapLayerEntity.setMaxZoom(dto.getMax());
mapLayerEntity.setCrs(dto.getCrs());
mapLayerEntity.setCreatedUid(userUtil.getId());
mapLayerEntity.setIsChangeMap(true);
mapLayerEntity.setIsLabelingMap(false);
mapLayerEntity.setOrder(1L);
mapLayerEntity.setIsLabelingMap(true);
mapLayerEntity.setOrder(order + 1);
mapLayerEntity.setLayerType(LayerType.TILE.getId());
mapLayerEntity.setUpdatedDttm(ZonedDateTime.now());
return mapLayerRepository.save(mapLayerEntity).getUuid();
@@ -218,6 +248,7 @@ public class MapLayerCoreService {
Long order = mapLayerRepository.findSortOrderDesc();
MapLayerEntity mapLayerEntity = new MapLayerEntity();
mapLayerEntity.setLayerName(addDto.getLayerName());
mapLayerEntity.setDescription(addDto.getDescription());
mapLayerEntity.setUrl(addDto.getUrl());
mapLayerEntity.setTag(addDto.getTag());
@@ -225,6 +256,7 @@ public class MapLayerCoreService {
mapLayerEntity.setIsChangeMap(true);
mapLayerEntity.setIsLabelingMap(true);
mapLayerEntity.setLayerType(LayerType.GEOJSON.getId());
mapLayerEntity.setCrs(addDto.getCrs());
mapLayerEntity.setUpdatedDttm(ZonedDateTime.now());
mapLayerEntity.setOrder(order + 1);
return mapLayerRepository.save(mapLayerEntity).getUuid();
@@ -247,6 +279,7 @@ public class MapLayerCoreService {
}
MapLayerEntity mapLayerEntity = new MapLayerEntity();
mapLayerEntity.setLayerName(addDto.getLayerName());
mapLayerEntity.setTitle(addDto.getTitle());
mapLayerEntity.setDescription(addDto.getDescription());
mapLayerEntity.setCreatedUid(userUtil.getId());
@@ -279,6 +312,7 @@ public class MapLayerCoreService {
}
MapLayerEntity mapLayerEntity = new MapLayerEntity();
mapLayerEntity.setLayerName(addDto.getLayerName());
mapLayerEntity.setTitle(addDto.getTitle());
mapLayerEntity.setDescription(addDto.getDescription());
mapLayerEntity.setCreatedUid(userUtil.getId());
@@ -291,4 +325,16 @@ public class MapLayerCoreService {
mapLayerEntity.setTag(addDto.getTag());
return mapLayerRepository.save(mapLayerEntity).getUuid();
}
public List<LayerMapDto> findLayerMapList(String type) {
return mapLayerRepository.findLayerMapList(type);
}
public LayerDto.YearTileDto getChangeDetectionTileUrl(Integer beforeYear, Integer afterYear) {
return mapLayerRepository.getChangeDetectionTileUrl(beforeYear, afterYear);
}
public TileUrlDto getChangeDetectionTileOneYearUrl(Integer year) {
return mapLayerRepository.getChangeDetectionTileOneYearUrl(year);
}
}

View File

@@ -162,6 +162,9 @@ public class MapSheetMngCoreService {
saved.getMngYyyy(), saved.getMngPath());
mapSheetMngRepository.updateYearState(saved.getMngYyyy(), "DONE");
// 년도별 Tile 정보 등록
mapSheetMngRepository.insertMapSheetMngTile(addReq);
return hstCnt;
}

View File

@@ -0,0 +1,36 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.postgres.repository.scheduler.TrainingDataLabelJobRepository;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class TrainingDataLabelJobCoreService {
private final TrainingDataLabelJobRepository trainingDataLabelJobRepository;
public List<Tasks> findCompletedYesterdayUnassigned() {
return trainingDataLabelJobRepository.findCompletedYesterdayUnassigned();
}
public void assignReviewerBatch(List<UUID> assignmentUids, String reviewerId) {
trainingDataLabelJobRepository.assignReviewerBatch(assignmentUids, reviewerId);
}
public List<InspectorPendingDto> findInspectorPendingByRound(Long analUid) {
return trainingDataLabelJobRepository.findInspectorPendingByRound(analUid);
}
public void lockInspectors(Long analUid, List<String> reviewerIds) {
trainingDataLabelJobRepository.lockInspectors(analUid, reviewerIds);
}
public void updateGeomUidTestState(List<Long> geomUids) {
trainingDataLabelJobRepository.updateGeomUidTestState(geomUids);
}
}

View File

@@ -4,10 +4,7 @@ import com.kamco.cd.kamcoback.postgres.repository.scheduler.TrainingDataReviewJo
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalCntInfo;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalMapSheetList;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -17,34 +14,6 @@ public class TrainingDataReviewJobCoreService {
private final TrainingDataReviewJobRepository trainingDataReviewJobRepository;
public List<Tasks> findCompletedYesterdayUnassigned() {
return trainingDataReviewJobRepository.findCompletedYesterdayUnassigned();
}
public void assignReviewer(UUID assignmentUid, String reviewerId) {
trainingDataReviewJobRepository.assignReviewer(assignmentUid, reviewerId);
}
public void assignReviewerBatch(List<UUID> assignmentUids, String reviewerId) {
trainingDataReviewJobRepository.assignReviewerBatch(assignmentUids, reviewerId);
}
public Tasks findAssignmentTask(String assignmentUid) {
return trainingDataReviewJobRepository.findAssignmentTask(assignmentUid);
}
public List<InspectorPendingDto> findInspectorPendingByRound(Long analUid) {
return trainingDataReviewJobRepository.findInspectorPendingByRound(analUid);
}
public void lockInspectors(Long analUid, List<String> reviewerIds) {
trainingDataReviewJobRepository.lockInspectors(analUid, reviewerIds);
}
public void updateGeomUidTestState(List<Long> geomUids) {
trainingDataReviewJobRepository.updateGeomUidTestState(geomUids);
}
public List<CompleteLabelData> findCompletedYesterdayLabelingList(
Long analUid, String mapSheetNum) {
return trainingDataReviewJobRepository.findCompletedYesterdayLabelingList(analUid, mapSheetNum);

View File

@@ -0,0 +1,62 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "batch_step_history")
public class BatchStepHistoryEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@NotNull
@Column(name = "anal_uid", nullable = false)
private Long analUid;
@Size(max = 255)
@NotNull
@Column(name = "result_uid", nullable = false)
private String resultUid;
@Size(max = 100)
@NotNull
@Column(name = "step_name", nullable = false, length = 100)
private String stepName;
@Size(max = 50)
@NotNull
@Column(name = "status", nullable = false, length = 50)
private String status;
@Column(name = "error_message", length = Integer.MAX_VALUE)
private String errorMessage;
@NotNull
@Column(name = "started_dttm", nullable = false)
private LocalDateTime startedDttm;
@Column(name = "completed_dttm")
private LocalDateTime completedDttm;
@NotNull
@Column(name = "created_dttm", nullable = false)
private LocalDateTime createdDttm;
@NotNull
@Column(name = "updated_dttm", nullable = false)
private LocalDateTime updatedDttm;
}

View File

@@ -43,6 +43,10 @@ public class MapLayerEntity {
@Column(name = "title", length = 200)
private String title;
@Size(max = 255)
@Column(name = "layer_name")
private String layerName;
@Column(name = "description", length = Integer.MAX_VALUE)
private String description;
@@ -103,9 +107,13 @@ public class MapLayerEntity {
@Column(name = "is_deleted")
private Boolean isDeleted = false;
@Column(name = "crs")
private String crs;
public LayerDto.Detail toDto() {
return new LayerDto.Detail(
this.uuid,
this.layerName,
this.layerType,
this.title,
this.description,
@@ -120,6 +128,7 @@ public class MapLayerEntity {
this.maxLat,
this.minZoom,
this.maxZoom,
this.createdDttm);
this.createdDttm,
this.crs);
}
}

View File

@@ -156,6 +156,12 @@ public class MapSheetAnalDataInferenceGeomEntity {
@JoinColumn(name = "map_5k_id", referencedColumnName = "fid")
private MapInkx5kEntity map5k;
@Column(name = "label_send_dttm")
private ZonedDateTime labelSendDttm;
@Column(name = "lock_yn")
private String lockYn;
public InferenceDetailDto.DetailListEntity toEntity() {
DetectionClassification classification = DetectionClassification.fromString(classBeforeCd);
Clazzes comparedClazz = new Clazzes(classification, classBeforeProb);

View File

@@ -199,6 +199,9 @@ public class MapSheetLearnEntity {
@Column(name = "total_jobs")
private Long totalJobs;
@Column(name = "chn_dtct_mst_id")
private String chnDtctMstId;
public InferenceResultDto.ResultList toDto() {
return new InferenceResultDto.ResultList(
this.uuid,

View File

@@ -0,0 +1,58 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
@Getter
@Setter
@Entity
@Table(name = "tb_map_sheet_mng_tile")
public class MapSheetMngTileEntity {
@Id
@Column(name = "mng_yyyy", nullable = false)
private Integer mngYyyy;
@Column(name = "url", length = Integer.MAX_VALUE)
private String url;
@Column(name = "min_lon", precision = 10, scale = 7)
private BigDecimal minLon;
@Column(name = "min_lat", precision = 10, scale = 7)
private BigDecimal minLat;
@Column(name = "max_lon", precision = 10, scale = 7)
private BigDecimal maxLon;
@Column(name = "max_lat", precision = 10, scale = 7)
private BigDecimal maxLat;
@Column(name = "min_zoom")
private Short minZoom;
@Column(name = "max_zoom")
private Short maxZoom;
@Column(name = "tag")
private String tag;
@Column(name = "crs")
private String crs;
@NotNull
@ColumnDefault("now()")
@Column(name = "created_dttm", nullable = false)
private ZonedDateTime createdDttm = ZonedDateTime.now();
@Column(name = "updated_dttm")
private ZonedDateTime updatedDttm;
}

View File

@@ -12,7 +12,7 @@ import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
@@ -39,7 +39,7 @@ public class PnuEntity {
private String pnu;
@Column(name = "created_dttm")
private OffsetDateTime createdDttm;
private ZonedDateTime createdDttm;
@Column(name = "created_uid")
private Long createdUid;
@@ -47,4 +47,140 @@ public class PnuEntity {
@ColumnDefault("false")
@Column(name = "del_yn")
private Boolean delYn;
@Size(max = 40)
@Column(name = "pnu_dtct_id", length = 40)
private String pnuDtctId;
@Size(max = 10)
@Column(name = "lrm_sync_ymd", length = 10)
private String lrmSyncYmd;
@Size(max = 10)
@Column(name = "pnu_sync_ymd", length = 10)
private String pnuSyncYmd;
@Size(max = 20)
@Column(name = "mpqd_no", length = 20)
private String mpqdNo;
@Size(max = 10)
@Column(name = "cprs_yr", length = 10)
private String cprsYr;
@Size(max = 10)
@Column(name = "crtr_yr", length = 10)
private String crtrYr;
@Size(max = 255)
@Column(name = "chn_dtct_id")
private String chnDtctId;
@Size(max = 10)
@Column(name = "chn_dtct_mst_id", length = 10)
private String chnDtctMstId;
@Size(max = 255)
@Column(name = "chn_dtct_objt_id")
private String chnDtctObjtId;
@Size(max = 255)
@Column(name = "chn_dtct_cont_id")
private String chnDtctContId;
@Size(max = 50)
@Column(name = "chn_cd", length = 50)
private String chnCd;
@Size(max = 50)
@Column(name = "chn_dtct_prob", length = 50)
private String chnDtctProb;
@Size(max = 50)
@Column(name = "bf_cls_cd", length = 50)
private String bfClsCd;
@Size(max = 50)
@Column(name = "bf_cls_prob", length = 50)
private String bfClsProb;
@Size(max = 50)
@Column(name = "af_cls_cd", length = 50)
private String afClsCd;
@Size(max = 50)
@Column(name = "af_cls_prob", length = 50)
private String afClsProb;
@Size(max = 100)
@Column(name = "pnu_sqms", length = 100)
private String pnuSqms;
@Size(max = 100)
@Column(name = "pnu_dtct_sqms", length = 100)
private String pnuDtctSqms;
@Size(max = 100)
@Column(name = "chn_dtct_sqms", length = 100)
private String chnDtctSqms;
@Size(max = 1)
@Column(name = "stblt_yn", length = 1)
private String stbltYn;
@Size(max = 30)
@Column(name = "incy_cd", length = 30)
private String incyCd;
@Size(max = 255)
@Column(name = "incy_rsn_cont")
private String incyRsnCont;
@Size(max = 1)
@Column(name = "lock_yn", length = 1)
private String lockYn;
@Size(max = 1)
@Column(name = "lbl_yn", length = 1)
private String lblYn;
@Size(max = 1)
@Column(name = "chg_yn", length = 1)
private String chgYn;
@Size(max = 50)
@Column(name = "rsatct_no", length = 50)
private String rsatctNo;
@Size(max = 100)
@Column(name = "rmk", length = 100)
private String rmk;
@Size(max = 20)
@Column(name = "crt_dt", length = 20)
private String crtDt;
@Size(max = 20)
@Column(name = "crt_epno", length = 20)
private String crtEpno;
@Size(max = 20)
@Column(name = "crt_ip", length = 20)
private String crtIp;
@Size(max = 20)
@Column(name = "chg_dt", length = 20)
private String chgDt;
@Size(max = 20)
@Column(name = "chg_epno", length = 20)
private String chgEpno;
@Size(max = 20)
@Column(name = "chg_ip", length = 20)
private String chgIp;
@Size(max = 10)
@Column(name = "chn_dtct_sno", length = 10)
private String chnDtctSno;
}

View File

@@ -0,0 +1,7 @@
package com.kamco.cd.kamcoback.postgres.repository.batch;
import com.kamco.cd.kamcoback.postgres.entity.BatchStepHistoryEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BatchStepHistoryRepository
extends JpaRepository<BatchStepHistoryEntity, Long>, BatchStepHistoryRepositoryCustom {}

View File

@@ -0,0 +1,5 @@
package com.kamco.cd.kamcoback.postgres.repository.batch;
public interface BatchStepHistoryRepositoryCustom {
boolean isDownloadable(Long analUid);
}

View File

@@ -0,0 +1,37 @@
package com.kamco.cd.kamcoback.postgres.repository.batch;
import com.kamco.cd.kamcoback.postgres.entity.QBatchStepHistoryEntity;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class BatchStepHistoryRepositoryImpl implements BatchStepHistoryRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public boolean isDownloadable(Long analUid) {
QBatchStepHistoryEntity h = QBatchStepHistoryEntity.batchStepHistoryEntity;
boolean startedExists =
queryFactory
.selectOne()
.from(h)
.where(
h.analUid.eq(analUid), h.stepName.eq("zipResponseStep"), h.status.eq("STARTED"))
.fetchFirst()
!= null;
boolean successExists =
queryFactory
.selectOne()
.from(h)
.where(
h.analUid.eq(analUid), h.stepName.eq("zipResponseStep"), h.status.eq("SUCCESS"))
.fetchFirst()
!= null;
return successExists && !startedExists;
}
}

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 GukYuinLabelJobRepository
extends JpaRepository<MapSheetLearnEntity, Long>, GukYuinLabelJobRepositoryCustom {}

View File

@@ -0,0 +1,11 @@
package com.kamco.cd.kamcoback.postgres.repository.gukyuin;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GeomUidDto;
import java.util.List;
public interface GukYuinLabelJobRepositoryCustom {
List<GeomUidDto> findYesterdayLabelingCompleteList();
void updateAnalDataInferenceGeomSendDttm(Long geoUid);
}

View File

@@ -0,0 +1,77 @@
package com.kamco.cd.kamcoback.postgres.repository.gukyuin;
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GeomUidDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinStatus;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class GukYuinLabelJobRepositoryImpl implements GukYuinLabelJobRepositoryCustom {
private final JPAQueryFactory queryFactory;
@PersistenceContext private EntityManager em;
@Override
public List<GeomUidDto> findYesterdayLabelingCompleteList() {
ZoneId zone = ZoneId.of("Asia/Seoul");
ZonedDateTime todayStart = ZonedDateTime.now(zone).toLocalDate().atStartOfDay(zone);
ZonedDateTime tomorrowStart = todayStart.plusDays(1);
ZonedDateTime yesterdayStart = todayStart.minusDays(1);
// BooleanExpression isYesterday =
// labelingAssignmentEntity
// .inspectStatDttm
// .goe(yesterdayStart)
// .and(labelingAssignmentEntity.inspectStatDttm.lt(todayStart));
BooleanExpression isYesterday =
labelingAssignmentEntity
.inspectStatDttm
.goe(todayStart)
.and(labelingAssignmentEntity.inspectStatDttm.lt(tomorrowStart));
return queryFactory
.select(
Projections.constructor(
GeomUidDto.class,
labelingAssignmentEntity.inferenceGeomUid,
mapSheetAnalDataInferenceGeomEntity.resultUid))
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalDataInferenceGeomEntity)
.on(
labelingAssignmentEntity.inferenceGeomUid.eq(
mapSheetAnalDataInferenceGeomEntity.geoUid))
.innerJoin(mapSheetAnalInferenceEntity)
.on(labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id))
.innerJoin(mapSheetLearnEntity)
.on(
mapSheetAnalInferenceEntity.learnId.eq(mapSheetLearnEntity.id),
mapSheetLearnEntity.applyStatus.in(
GukYuinStatus.GUK_COMPLETED.getId(), GukYuinStatus.PNU_COMPLETED.getId()))
.where(labelingAssignmentEntity.inspectState.eq(InspectState.COMPLETE.getId()), isYesterday)
.fetch();
}
@Override
public void updateAnalDataInferenceGeomSendDttm(Long geoUid) {
queryFactory
.update(mapSheetAnalDataInferenceGeomEntity)
.set(mapSheetAnalDataInferenceGeomEntity.labelSendDttm, ZonedDateTime.now())
.where(mapSheetAnalDataInferenceGeomEntity.geoUid.eq(geoUid))
.execute();
}
}

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 GukYuinPnuJobRepository
extends JpaRepository<MapSheetLearnEntity, Long>, GukYuinPnuJobRepositoryCustom {}

View File

@@ -0,0 +1,20 @@
package com.kamco.cd.kamcoback.postgres.repository.gukyuin;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinStatus;
import java.util.List;
public interface GukYuinPnuJobRepositoryCustom {
void updateInferenceGeomDataPnuCnt(String chnDtctObjtId, long pnuCnt);
Long findMapSheetAnalDataInferenceGeomUid(String chnDtctObjtId);
void insertGeoUidPnuData(Long geoUid, String[] pnuList, String chnDtctObjtId);
void updateGukYuinApplyStateComplete(Long id, GukYuinStatus status);
List<LearnKeyDto> findGukyuinApplyStatusUidList(List<String> gukYuinStatus);
long upsertMapSheetDataAnalGeomPnu(String uid, String[] pnuList);
}

View File

@@ -0,0 +1,123 @@
package com.kamco.cd.kamcoback.postgres.repository.gukyuin;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QPnuEntity.pnuEntity;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinStatus;
import com.kamco.cd.kamcoback.postgres.entity.PnuEntity;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import java.time.ZonedDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class GukYuinPnuJobRepositoryImpl implements GukYuinPnuJobRepositoryCustom {
private final JPAQueryFactory queryFactory;
@PersistenceContext private EntityManager em;
@Override
public void updateInferenceGeomDataPnuCnt(String chnDtctObjtId, long pnuCnt) {
queryFactory
.update(mapSheetAnalDataInferenceGeomEntity)
.set(mapSheetAnalDataInferenceGeomEntity.pnu, pnuCnt)
.where(mapSheetAnalDataInferenceGeomEntity.resultUid.eq(chnDtctObjtId))
.execute();
}
@Override
public Long findMapSheetAnalDataInferenceGeomUid(String chnDtctObjtId) {
return queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.geoUid)
.from(mapSheetAnalDataInferenceGeomEntity)
.where(mapSheetAnalDataInferenceGeomEntity.resultUid.eq(chnDtctObjtId))
.fetchOne();
}
@Override
public void insertGeoUidPnuData(Long geoUid, String[] pnuList, String chnDtctObjtId) {
for (String pnu : pnuList) {
PnuEntity entity =
queryFactory
.selectFrom(pnuEntity)
.where(pnuEntity.pnu.eq(pnu), pnuEntity.chnDtctObjtId.eq(chnDtctObjtId))
.fetchOne();
if (entity == null) {
queryFactory
.insert(pnuEntity)
.columns(
pnuEntity.geo.geoUid, pnuEntity.pnu, pnuEntity.createdDttm, pnuEntity.chnDtctObjtId)
.values(geoUid, pnu, ZonedDateTime.now(), chnDtctObjtId)
.execute();
}
}
}
@Override
public List<LearnKeyDto> findGukyuinApplyStatusUidList(List<String> status) {
return queryFactory
.select(
Projections.constructor(
LearnKeyDto.class,
mapSheetLearnEntity.id,
mapSheetLearnEntity.uid,
mapSheetLearnEntity.chnDtctMstId))
.from(mapSheetLearnEntity)
.where(mapSheetLearnEntity.applyStatus.in(status))
.fetch();
}
@Override
public long upsertMapSheetDataAnalGeomPnu(String chnDtctObjtId, String[] pnuList) {
long length = pnuList.length;
queryFactory
.update(mapSheetAnalDataInferenceGeomEntity)
.set(mapSheetAnalDataInferenceGeomEntity.pnu, length)
.where(mapSheetAnalDataInferenceGeomEntity.resultUid.eq(chnDtctObjtId))
.execute();
Long geoUid =
queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.geoUid)
.from(mapSheetAnalDataInferenceGeomEntity)
.where(mapSheetAnalDataInferenceGeomEntity.resultUid.eq(chnDtctObjtId))
.fetchOne();
long succCnt = 0;
for (String pnu : pnuList) {
long result =
queryFactory
.insert(pnuEntity)
.columns(
pnuEntity.geo.geoUid,
pnuEntity.pnu,
pnuEntity.createdDttm,
pnuEntity.chnDtctObjtId)
.values(geoUid, pnu, ZonedDateTime.now(), chnDtctObjtId)
.execute();
if (result > 0) {
succCnt++;
}
}
return succCnt;
}
@Override
@Transactional
public void updateGukYuinApplyStateComplete(Long id, GukYuinStatus status) {
queryFactory
.update(mapSheetLearnEntity)
.set(mapSheetLearnEntity.applyStatus, status.getId())
.set(mapSheetLearnEntity.applyStatusDttm, ZonedDateTime.now())
.where(mapSheetLearnEntity.id.eq(id))
.execute();
}
}

View File

@@ -1,21 +1,48 @@
package com.kamco.cd.kamcoback.postgres.repository.gukyuin;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.Basic;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LabelSendDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GeomUidDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.LearnInfo;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinStatus;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
public interface GukYuinRepositoryCustom {
void updateGukYuinMastRegResult(Basic resultBody);
void updateGukYuinMastRegRemove(Basic resultBody);
void updateGukYuinMastRegRemove(String chnDtctId);
void updateInferenceGeomDataPnuCnt(String chnDtctObjtId, long pnuCnt);
Long findMapSheetAnalDataInferenceGeomUid(String chnDtctObjtId);
void insertGeoUidPnuData(Long geoUid, String[] pnuList);
void insertGeoUidPnuData(Long geoUid, String[] pnuList, String chnDtctObjtId);
List<String> findGukyuinApplyIngUidList();
void updateGukYuinApplyStateComplete(Long id, GukYuinStatus status);
void updateGukYuinApplyStateComplete(String uid);
List<LearnKeyDto> findGukyuinApplyStatusUidList(List<String> gukYuinStatus);
long upsertMapSheetDataAnalGeomPnu(String uid, String[] pnuList);
LearnInfo findMapSheetLearnInfo(UUID uuid);
Integer findMapSheetLearnYearStage(Integer compareYyyy, Integer targetYyyy);
void updateAnalInferenceApplyDttm(Basic registRes);
List<GeomUidDto> findYesterdayLabelingCompleteList();
void updateAnalDataInferenceGeomSendDttm(Long geoUid);
List<LabelSendDto> findLabelingCompleteSendList(LocalDate yesterday);
Long findMapSheetLearnInfoByYyyy(Integer compareYyyy, Integer targetYyyy, Integer maxStage);
void updateMapSheetLearnGukyuinEndStatus(Long learnId);
void updateMapSheetInferenceLabelEndStatus(Long learnId);
}

View File

@@ -1,17 +1,33 @@
package com.kamco.cd.kamcoback.postgres.repository.gukyuin;
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QPnuEntity.pnuEntity;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.Basic;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LabelSendDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GeomUidDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.LearnInfo;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinStatus;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
import com.kamco.cd.kamcoback.postgres.entity.PnuEntity;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@@ -37,17 +53,21 @@ public class GukYuinRepositoryImpl implements GukYuinRepositoryCustom {
.set(mapSheetLearnEntity.stage, stage)
.set(mapSheetLearnEntity.applyStatus, status.getId())
.set(mapSheetLearnEntity.applyStatusDttm, ZonedDateTime.now())
.set(mapSheetLearnEntity.chnDtctMstId, resultBody.getChnDtctMstId())
.set(mapSheetLearnEntity.applyYn, true)
.set(mapSheetLearnEntity.applyDttm, ZonedDateTime.now())
.where(mapSheetLearnEntity.uid.eq(resultBody.getChnDtctId()))
.execute();
}
@Override
public void updateGukYuinMastRegRemove(Basic resultBody) {
public void updateGukYuinMastRegRemove(String chnDtctId) {
queryFactory
.update(mapSheetLearnEntity)
.set(mapSheetLearnEntity.applyStatus, GukYuinStatus.CANCELED.getId())
.set(mapSheetLearnEntity.applyStatusDttm, ZonedDateTime.now())
.where(mapSheetLearnEntity.uid.eq(resultBody.getChnDtctId()))
.set(mapSheetLearnEntity.applyYn, false)
.where(mapSheetLearnEntity.uid.eq(chnDtctId))
.execute();
}
@@ -70,33 +90,242 @@ public class GukYuinRepositoryImpl implements GukYuinRepositoryCustom {
}
@Override
public void insertGeoUidPnuData(Long geoUid, String[] pnuList) {
public void insertGeoUidPnuData(Long geoUid, String[] pnuList, String chnDtctObjtId) {
for (String pnu : pnuList) {
queryFactory
.insert(pnuEntity)
.columns(pnuEntity.geo.geoUid, pnuEntity.pnu, pnuEntity.createdDttm)
.values(geoUid, pnu, ZonedDateTime.now())
.execute();
PnuEntity entity =
queryFactory
.selectFrom(pnuEntity)
.where(
pnuEntity.geo.geoUid.eq(geoUid),
pnuEntity.pnu.eq(pnu),
pnuEntity.chnDtctObjtId.eq(chnDtctObjtId))
.fetchOne();
if (entity == null) {
queryFactory
.insert(pnuEntity)
.columns(
pnuEntity.geo.geoUid, pnuEntity.pnu, pnuEntity.createdDttm, pnuEntity.chnDtctObjtId)
.values(geoUid, pnu, ZonedDateTime.now(), chnDtctObjtId)
.execute();
}
}
}
@Override
public List<String> findGukyuinApplyIngUidList() {
public List<LearnKeyDto> findGukyuinApplyStatusUidList(List<String> status) {
return queryFactory
.select(mapSheetLearnEntity.uid)
.select(
Projections.constructor(
LearnKeyDto.class,
mapSheetLearnEntity.id,
mapSheetLearnEntity.uid,
mapSheetLearnEntity.chnDtctMstId))
.from(mapSheetLearnEntity)
.where(mapSheetLearnEntity.applyStatus.eq(GukYuinStatus.IN_PROGRESS.getId()))
.where(mapSheetLearnEntity.applyStatus.in(status))
.fetch();
}
@Override
@Transactional
public void updateGukYuinApplyStateComplete(String uid) {
public long upsertMapSheetDataAnalGeomPnu(String chnDtctObjtId, String[] pnuList) {
long length = pnuList.length;
queryFactory
.update(mapSheetAnalDataInferenceGeomEntity)
.set(mapSheetAnalDataInferenceGeomEntity.pnu, length)
.where(mapSheetAnalDataInferenceGeomEntity.resultUid.eq(chnDtctObjtId))
.execute();
Long geoUid =
queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.geoUid)
.from(mapSheetAnalDataInferenceGeomEntity)
.where(mapSheetAnalDataInferenceGeomEntity.resultUid.eq(chnDtctObjtId))
.fetchOne();
long succCnt = 0;
for (String pnu : pnuList) {
long result =
queryFactory
.insert(pnuEntity)
.columns(pnuEntity.geo.geoUid, pnuEntity.pnu, pnuEntity.createdDttm)
.values(geoUid, pnu, ZonedDateTime.now())
.execute();
if (result > 0) {
succCnt++;
}
}
return succCnt;
}
@Override
public LearnInfo findMapSheetLearnInfo(UUID uuid) {
return queryFactory
.select(
Projections.constructor(
LearnInfo.class,
mapSheetLearnEntity.id,
mapSheetLearnEntity.uuid,
mapSheetLearnEntity.compareYyyy,
mapSheetLearnEntity.targetYyyy,
mapSheetLearnEntity.stage,
mapSheetLearnEntity.uid,
mapSheetLearnEntity.applyStatus,
mapSheetLearnEntity.applyYn))
.from(mapSheetLearnEntity)
.where(mapSheetLearnEntity.uuid.eq(uuid))
.fetchOne();
}
@Override
public Integer findMapSheetLearnYearStage(Integer compareYyyy, Integer targetYyyy) {
NumberExpression<Integer> stageExpr =
Expressions.numberTemplate(Integer.class, "coalesce({0}, 0)", mapSheetLearnEntity.stage);
return queryFactory
.select(stageExpr.max().coalesce(0))
.from(mapSheetLearnEntity)
.where(
mapSheetLearnEntity.compareYyyy.eq(compareYyyy),
mapSheetLearnEntity.targetYyyy.eq(targetYyyy),
mapSheetLearnEntity.applyStatus.isNotNull(),
mapSheetLearnEntity.applyStatus.ne(GukYuinStatus.PENDING.getId()))
.fetchOne();
}
@Override
public void updateAnalInferenceApplyDttm(Basic registRes) {
Long learnId =
queryFactory
.select(mapSheetLearnEntity.id)
.from(mapSheetLearnEntity)
.where(mapSheetLearnEntity.uid.eq(registRes.getChnDtctId()))
.fetchOne();
queryFactory
.update(mapSheetAnalInferenceEntity)
.set(mapSheetAnalInferenceEntity.gukyuinUsed, "Y")
.set(mapSheetAnalInferenceEntity.gukyuinApplyDttm, ZonedDateTime.now())
.set(mapSheetAnalInferenceEntity.stage, Integer.parseInt(registRes.getChnDtctSno()))
.where(mapSheetAnalInferenceEntity.learnId.eq(learnId))
.execute();
}
@Override
public List<GeomUidDto> findYesterdayLabelingCompleteList() {
ZoneId zone = ZoneId.of("Asia/Seoul");
ZonedDateTime todayStart = ZonedDateTime.now(zone).toLocalDate().atStartOfDay(zone);
ZonedDateTime yesterdayStart = todayStart.minusDays(1);
BooleanExpression isYesterday =
labelingAssignmentEntity
.inspectStatDttm
.goe(yesterdayStart)
.and(labelingAssignmentEntity.inspectStatDttm.lt(todayStart));
return queryFactory
.select(
Projections.constructor(
GeomUidDto.class,
labelingAssignmentEntity.inferenceGeomUid,
mapSheetAnalDataInferenceGeomEntity.resultUid))
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalDataInferenceGeomEntity)
.on(
labelingAssignmentEntity.inferenceGeomUid.eq(
mapSheetAnalDataInferenceGeomEntity.geoUid))
.innerJoin(mapSheetAnalInferenceEntity)
.on(labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id))
.innerJoin(mapSheetLearnEntity)
.on(
mapSheetAnalInferenceEntity.learnId.eq(mapSheetLearnEntity.id),
mapSheetLearnEntity.applyStatus.in(
GukYuinStatus.GUK_COMPLETED.getId(), GukYuinStatus.PNU_COMPLETED.getId()))
.where(labelingAssignmentEntity.inspectState.eq(InspectState.COMPLETE.getId()), isYesterday)
.fetch();
}
@Override
public void updateAnalDataInferenceGeomSendDttm(Long geoUid) {
queryFactory
.update(mapSheetAnalDataInferenceGeomEntity)
.set(mapSheetAnalDataInferenceGeomEntity.labelSendDttm, ZonedDateTime.now())
.where(mapSheetAnalDataInferenceGeomEntity.geoUid.eq(geoUid))
.execute();
}
@Override
public List<LabelSendDto> findLabelingCompleteSendList(LocalDate yesterday) {
ZoneId zone = ZoneId.of("Asia/Seoul");
ZonedDateTime from = yesterday.atStartOfDay(zone);
ZonedDateTime to = from.plusDays(1);
BooleanExpression isYesterday =
labelingAssignmentEntity
.inspectStatDttm
.goe(from)
.and(labelingAssignmentEntity.inspectStatDttm.lt(to));
return queryFactory
.select(
Projections.constructor(
LabelSendDto.class,
mapSheetAnalDataInferenceGeomEntity.resultUid,
labelingAssignmentEntity.workerUid,
labelingAssignmentEntity.workStatDttm,
labelingAssignmentEntity.inspectorUid,
labelingAssignmentEntity.inspectStatDttm,
mapSheetAnalDataInferenceGeomEntity.labelSendDttm))
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalDataInferenceGeomEntity)
.on(
labelingAssignmentEntity.inferenceGeomUid.eq(
mapSheetAnalDataInferenceGeomEntity.geoUid))
.where(labelingAssignmentEntity.inspectState.eq(InspectState.COMPLETE.getId()), isYesterday)
.fetch();
}
@Override
public Long findMapSheetLearnInfoByYyyy(
Integer compareYyyy, Integer targetYyyy, Integer maxStage) {
return queryFactory
.select(mapSheetLearnEntity.id)
.from(mapSheetLearnEntity)
.where(
mapSheetLearnEntity.compareYyyy.eq(compareYyyy),
mapSheetLearnEntity.targetYyyy.eq(targetYyyy),
mapSheetLearnEntity.stage.eq(maxStage))
.fetchOne();
}
@Override
public void updateMapSheetLearnGukyuinEndStatus(Long learnId) {
queryFactory
.update(mapSheetLearnEntity)
.set(mapSheetLearnEntity.applyStatus, GukYuinStatus.PNU_COMPLETED.getId())
.set(mapSheetLearnEntity.applyStatus, GukYuinStatus.END.getId())
.set(mapSheetLearnEntity.applyStatusDttm, ZonedDateTime.now())
.where(mapSheetLearnEntity.uid.eq(uid))
.where(mapSheetLearnEntity.id.eq(learnId))
.execute();
}
@Override
public void updateMapSheetInferenceLabelEndStatus(Long learnId) {
queryFactory
.update(mapSheetAnalInferenceEntity)
.set(mapSheetAnalInferenceEntity.analState, LabelMngState.FINISH.getId())
.set(mapSheetAnalInferenceEntity.updatedDttm, ZonedDateTime.now())
.where(mapSheetAnalInferenceEntity.learnId.eq(learnId))
.execute();
}
@Override
@Transactional
public void updateGukYuinApplyStateComplete(Long id, GukYuinStatus status) {
queryFactory
.update(mapSheetLearnEntity)
.set(mapSheetLearnEntity.applyStatus, status.getId())
.set(mapSheetLearnEntity.applyStatusDttm, ZonedDateTime.now())
.where(mapSheetLearnEntity.id.eq(id))
.execute();
}
}

View File

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

View File

@@ -0,0 +1,18 @@
package com.kamco.cd.kamcoback.postgres.repository.gukyuin;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.StbltResult;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.RlbDtctMastDto;
import com.kamco.cd.kamcoback.postgres.entity.PnuEntity;
import java.util.List;
public interface GukYuinStbltJobRepositoryCustom {
List<LearnKeyDto> findGukYuinEligibleForSurveyList(String status);
void updateGukYuinEligibleForSurvey(String resultUid, RlbDtctMastDto stbltDto);
PnuEntity findPnuEntityByResultUid(String resultUid, String pnu);
void updateGukYuinObjectStbltYn(String resultUid, StbltResult stbResult);
}

View File

@@ -0,0 +1,109 @@
package com.kamco.cd.kamcoback.postgres.repository.gukyuin;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QPnuEntity.pnuEntity;
import com.kamco.cd.kamcoback.common.enums.ImageryFitStatus;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.StbltResult;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.RlbDtctMastDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinStatus;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
import com.kamco.cd.kamcoback.postgres.entity.PnuEntity;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.time.ZonedDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class GukYuinStbltJobRepositoryImpl implements GukYuinStbltJobRepositoryCustom {
private final JPAQueryFactory queryFactory;
@PersistenceContext private EntityManager em;
@Override
public List<LearnKeyDto> findGukYuinEligibleForSurveyList(String status) {
return queryFactory
.select(
Projections.constructor(
LearnKeyDto.class,
mapSheetLearnEntity.id,
mapSheetLearnEntity.uid,
mapSheetLearnEntity.chnDtctMstId))
.from(mapSheetLearnEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(mapSheetLearnEntity.id.eq(mapSheetAnalInferenceEntity.learnId))
.innerJoin(mapSheetAnalDataInferenceEntity)
.on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid))
.innerJoin(mapSheetAnalDataInferenceGeomEntity)
.on(mapSheetAnalDataInferenceEntity.id.eq(mapSheetAnalDataInferenceGeomEntity.dataUid))
.where(
mapSheetLearnEntity.applyStatus.eq(GukYuinStatus.PNU_COMPLETED.getId()),
mapSheetAnalDataInferenceGeomEntity.pnu.gt(0),
mapSheetAnalDataInferenceGeomEntity.fitState.isNull())
.groupBy(mapSheetLearnEntity.id, mapSheetLearnEntity.uid, mapSheetLearnEntity.chnDtctMstId)
.having(mapSheetAnalDataInferenceGeomEntity.geoUid.count().gt(1L))
.fetch();
}
@Override
public void updateGukYuinEligibleForSurvey(String resultUid, RlbDtctMastDto stbltDto) {
MapSheetAnalDataInferenceGeomEntity geomEntity =
queryFactory
.selectFrom(mapSheetAnalDataInferenceGeomEntity)
.where(mapSheetAnalDataInferenceGeomEntity.resultUid.eq(resultUid))
.fetchOne();
if (geomEntity != null) {
PnuEntity pnuEt =
queryFactory
.selectFrom(pnuEntity)
.where(pnuEntity.chnDtctObjtId.eq(resultUid))
.fetchFirst();
queryFactory
.update(mapSheetAnalDataInferenceGeomEntity)
.set(
mapSheetAnalDataInferenceGeomEntity.fitState,
pnuEt.getStbltYn().equals("Y")
? ImageryFitStatus.UNFIT.getId()
: ImageryFitStatus.FIT.getId()) // 적합여부가 Y 이면 부적합인 것, N 이면 적합한 것이라고 함
.set(mapSheetAnalDataInferenceGeomEntity.fitStateDttm, ZonedDateTime.now())
.set(mapSheetAnalDataInferenceGeomEntity.lockYn, stbltDto.getLockYn())
.where(mapSheetAnalDataInferenceGeomEntity.resultUid.eq(resultUid))
.execute();
}
}
@Override
public PnuEntity findPnuEntityByResultUid(String resultUid, String pnu) {
return queryFactory
.selectFrom(pnuEntity)
.where(pnuEntity.pnu.eq(pnu), pnuEntity.chnDtctObjtId.eq(resultUid))
.fetchOne();
}
@Override
public void updateGukYuinObjectStbltYn(String resultUid, StbltResult stbResult) {
queryFactory
.update(mapSheetAnalDataInferenceGeomEntity)
.set(
mapSheetAnalDataInferenceGeomEntity.fitState,
stbResult.getStbltYn().equals("Y")
? ImageryFitStatus.UNFIT.getId()
: ImageryFitStatus.FIT.getId()) // 적합여부가 Y 이면 부적합인 것, N 이면 적합한 것이라고 함
.set(mapSheetAnalDataInferenceGeomEntity.fitStateDttm, ZonedDateTime.now())
.set(mapSheetAnalDataInferenceGeomEntity.fitStateCmmnt, stbResult.getIncyCmnt())
.where(mapSheetAnalDataInferenceGeomEntity.resultUid.eq(resultUid))
.execute();
}
}

View File

@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.postgres.repository.label;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceLearnDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveInfo;
@@ -15,6 +16,7 @@ import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics;
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.domain.Page;
@@ -104,4 +106,8 @@ public interface LabelAllocateRepositoryCustom {
void updateAnalInferenceMngState(UUID uuid, String status);
Long findLabelingIngProcessCnt();
InferenceLearnDto findLabelingIngProcessId(UUID uuid);
Optional<String> findLearnUid(UUID uuid);
}

View File

@@ -6,12 +6,15 @@ import static com.kamco.cd.kamcoback.postgres.entity.QLabelingLabelerEntity.labe
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity;
import com.kamco.cd.kamcoback.common.enums.ImageryFitStatus;
import com.kamco.cd.kamcoback.common.enums.StatusType;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceLearnDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState;
@@ -48,6 +51,7 @@ import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -82,7 +86,9 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.innerJoin(mapSheetAnalDataInferenceGeomEntity)
.on(
mapSheetAnalDataInferenceEntity.id.eq(mapSheetAnalDataInferenceGeomEntity.dataUid),
mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull(),
// mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull(),
mapSheetAnalDataInferenceGeomEntity.pnu.gt(0),
mapSheetAnalDataInferenceGeomEntity.fitState.eq(ImageryFitStatus.UNFIT.getId()),
mapSheetAnalDataInferenceGeomEntity.labelState.isNull())
.where(
mapSheetAnalInferenceEntity.uuid.eq(uuid),
@@ -126,8 +132,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
"""
insert into tb_labeling_assignment
(assignment_uid, inference_geom_uid, worker_uid,
work_state, assign_group_id, anal_uid, pnu)
values (?, ?, ?, ?, ?, ?, ?)
work_state, assign_group_id, anal_uid)
values (?, ?, ?, ?, ?, ?)
""";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
@@ -140,7 +146,6 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
ps.setString(4, LabelState.ASSIGNED.getId());
ps.setString(5, String.valueOf(info.getMapSheetNum()));
ps.setLong(6, analEntity.getId());
ps.setLong(7, info.getPnu());
ps.addBatch();
batchSize++;
@@ -190,7 +195,9 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.innerJoin(mapSheetAnalDataInferenceGeomEntity)
.on(
mapSheetAnalDataInferenceEntity.id.eq(mapSheetAnalDataInferenceGeomEntity.dataUid),
mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull(),
// mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull(),
mapSheetAnalDataInferenceGeomEntity.pnu.gt(0),
mapSheetAnalDataInferenceGeomEntity.fitState.eq(ImageryFitStatus.UNFIT.getId()),
mapSheetAnalDataInferenceGeomEntity.labelState.isNull())
.where(mapSheetAnalInferenceEntity.uuid.eq(uuid))
.fetchOne();
@@ -381,9 +388,10 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(analEntity.getCompareYyyy()),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(analEntity.getTargetYyyy()),
mapSheetAnalDataInferenceGeomEntity.stage.eq(analEntity.getStage()),
mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull()
// mapSheetAnalDataInferenceGeomEntity.pnu.gt(0L)
// mapSheetAnalDataInferenceGeomEntity.passYn.isFalse() //TODO:
// mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull()
mapSheetAnalDataInferenceGeomEntity.pnu.gt(0L),
mapSheetAnalDataInferenceGeomEntity.fitState.eq(
ImageryFitStatus.UNFIT.getId()) // TODO:
// 추후 라벨링 대상 조건 수정하기
)
.fetchOne();
@@ -555,11 +563,9 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
mapSheetAnalDataInferenceGeomEntity.dataUid))
.where(
mapSheetAnalInferenceEntity.uuid.eq(targetUuid),
mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull()
// mapSheetAnalDataInferenceGeomEntity.pnu.gt(0L),
// mapSheetAnalDataInferenceGeomEntity.passYn.isFalse() //TODO: 추후 라벨링
// 대상 조건 수정하기
)
// mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull()
mapSheetAnalDataInferenceGeomEntity.pnu.gt(0L),
mapSheetAnalDataInferenceGeomEntity.fitState.eq(ImageryFitStatus.UNFIT.getId()))
.fetchOne();
}
@@ -576,8 +582,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.where(
analUidCondition, labelingAssignmentEntity.workState.in("ASSIGNED", "SKIP", "DONE"))
.where(analUidCondition, labelingAssignmentEntity.workState.in("SKIP", "DONE"))
.fetchOne();
Long skipCount =
@@ -602,6 +607,13 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.where(analUidCondition, labelingAssignmentEntity.inspectState.eq("COMPLETE"))
.fetchOne();
Long inspectionExcept =
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.where(analUidCondition, labelingAssignmentEntity.inspectState.eq("EXCEPT"))
.fetchOne();
Long inspectorCount =
queryFactory
.select(labelingAssignmentEntity.inspectorUid.countDistinct())
@@ -614,6 +626,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
long labelCompleted = labelingCompleted != null ? labelingCompleted : 0L;
long inspectCompleted = inspectionCompleted != null ? inspectionCompleted : 0L;
long skipped = skipCount != null ? skipCount : 0L;
long inspectExcepted = inspectionExcept != null ? inspectionExcept : 0L;
long labelingRemaining = labelingTotal - labelCompleted - skipped;
if (labelingRemaining < 0) {
@@ -621,7 +634,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
}
long inspectionTotal = labelingTotal;
long inspectionRemaining = inspectionTotal - inspectCompleted - skipped;
long inspectionRemaining = inspectionTotal - inspectCompleted - inspectExcepted;
if (inspectionRemaining < 0) {
inspectionRemaining = 0;
}
@@ -658,7 +671,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.inspectionStatus(inspectionStatus)
.inspectionTotalCount(inspectionTotal)
.inspectionCompletedCount(inspectCompleted)
.inspectionSkipCount(skipped) // TODO
.inspectionSkipCount(inspectExcepted)
.inspectionRemainingCount(inspectionRemaining)
.inspectorCount(inspectorCount != null ? inspectorCount : 0L)
.progressRate(labelingRate)
@@ -740,11 +753,9 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
mapSheetAnalInferenceEntity.targetYyyy.eq(
mapSheetAnalDataInferenceGeomEntity.targetYyyy),
mapSheetAnalInferenceEntity.stage.eq(mapSheetAnalDataInferenceGeomEntity.stage),
mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull()
// mapSheetAnalDataInferenceGeomEntity.pnu.gt(0),
// mapSheetAnalDataInferenceGeomEntity.passYn.isFalse() //TODO: 추후 라벨링 대상 조건
// 수정하기
)
// mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull()
mapSheetAnalDataInferenceGeomEntity.pnu.gt(0),
mapSheetAnalDataInferenceGeomEntity.fitState.eq(ImageryFitStatus.UNFIT.getId()))
.where(mapSheetAnalInferenceEntity.id.eq(analEntity.getId()))
.groupBy(
mapSheetAnalInferenceEntity.analTitle,
@@ -1816,4 +1827,33 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
LabelMngState.ASSIGNED.getId(), LabelMngState.ING.getId()))
.fetchOne();
}
@Override
public InferenceLearnDto findLabelingIngProcessId(UUID uuid) {
return queryFactory
.select(
Projections.constructor(
InferenceLearnDto.class,
mapSheetAnalInferenceEntity.uuid,
mapSheetLearnEntity.uid,
mapSheetAnalInferenceEntity.analState,
mapSheetAnalInferenceEntity.id))
.from(mapSheetLearnEntity)
.join(mapSheetAnalInferenceEntity)
.on(mapSheetAnalInferenceEntity.learnId.eq(mapSheetLearnEntity.id))
.where(mapSheetAnalInferenceEntity.uuid.eq(uuid))
.fetchOne();
}
@Override
public Optional<String> findLearnUid(UUID uuid) {
return Optional.ofNullable(
queryFactory
.select(mapSheetLearnEntity.uid)
.from(mapSheetLearnEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(mapSheetAnalInferenceEntity.learnId.eq(mapSheetLearnEntity.id))
.where(mapSheetAnalInferenceEntity.uuid.eq(uuid))
.fetchOne());
}
}

View File

@@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.postgres.repository.label;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity;
import com.kamco.cd.kamcoback.common.enums.ImageryFitStatus;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState;
import com.kamco.cd.kamcoback.label.dto.LabelWorkDto;
@@ -133,20 +134,16 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.lt(end)));
}
// labelTotCnt: pnu가 있고 pass_yn = false (부적합)인 건수만 라벨링 대상
// labelTotCnt: pnu가 있고 fitState = UNFIT 인 건수만 라벨링 대상
NumberExpression<Long> labelTotCntExpr =
new CaseBuilder()
.when(
mapSheetAnalDataInferenceGeomEntity
.pnu
.isNotNull()
.gt(0)
.and(
mapSheetAnalDataInferenceGeomEntity.pnu
.isNotNull()) // TODO: 이노팸 연동 후 0 이상이라고 해야할 듯
//
// .and(mapSheetAnalDataInferenceGeomEntity.passYn.eq(Boolean.FALSE)) //TODO: 추후
// 라벨링 대상 조건 수정하기
)
mapSheetAnalDataInferenceGeomEntity.fitState.eq(
ImageryFitStatus.UNFIT.getId())))
.then(1L)
.otherwise(0L)
.sum();
@@ -200,7 +197,7 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
new CaseBuilder()
.when(
mapSheetAnalDataInferenceGeomEntity.labelState.eq(
LabelState.DONE.getId())) // "LABEL_COMPLETE"?
LabelState.DONE.getId()))
.then(1L)
.otherwise(0L)
.sum(),
@@ -294,14 +291,14 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
if (searchReq.getSearchVal() != null && !searchReq.getSearchVal().isEmpty()) {
whereSubBuilder.and(
Expressions.stringTemplate("{0}", memberEntity.userId)
Expressions.stringTemplate("{0}", memberEntity.employeeNo)
.likeIgnoreCase("%" + searchReq.getSearchVal() + "%")
.or(
Expressions.stringTemplate("{0}", memberEntity.name)
.likeIgnoreCase("%" + searchReq.getSearchVal() + "%")));
}
whereSubBuilder.and(labelingAssignmentEntity.workerUid.eq(memberEntity.userId));
whereSubBuilder.and(labelingAssignmentEntity.workerUid.eq(memberEntity.employeeNo));
// 공통 조건 추출
BooleanExpression doneStateCondition =
@@ -344,7 +341,7 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
WorkerState.class,
memberEntity.userRole,
memberEntity.name,
memberEntity.userId,
memberEntity.employeeNo,
assignedCnt.as("assignedCnt"),
doneCnt.as("doneCnt"),
skipCnt.as("skipCnt"),
@@ -363,7 +360,10 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
.on(whereSubBuilder)
.where(whereBuilder)
.groupBy(
memberEntity.userRole, memberEntity.name, memberEntity.userId, memberEntity.status)
memberEntity.userRole,
memberEntity.name,
memberEntity.employeeNo,
memberEntity.status)
.orderBy(orderSpecifiers.toArray(new OrderSpecifier[0]))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
@@ -441,14 +441,14 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
if (searchReq.getSearchVal() != null && !searchReq.getSearchVal().isEmpty()) {
whereSubBuilder.and(
Expressions.stringTemplate("{0}", memberEntity.userId)
Expressions.stringTemplate("{0}", memberEntity.employeeNo)
.likeIgnoreCase("%" + searchReq.getSearchVal() + "%")
.or(
Expressions.stringTemplate("{0}", memberEntity.name)
.likeIgnoreCase("%" + searchReq.getSearchVal() + "%")));
}
whereSubBuilder.and(labelingAssignmentEntity.inspectorUid.eq(memberEntity.userId));
whereSubBuilder.and(labelingAssignmentEntity.inspectorUid.eq(memberEntity.employeeNo));
// 공통 조건 추출
BooleanExpression doneStateCondition =
@@ -492,7 +492,7 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
WorkerState.class,
memberEntity.userRole,
memberEntity.name,
memberEntity.userId,
memberEntity.employeeNo,
assignedCnt.as("assignedCnt"),
doneCnt.as("doneCnt"),
skipCnt.as("skipCnt"),
@@ -511,7 +511,10 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
.on(whereSubBuilder)
.where(whereBuilder)
.groupBy(
memberEntity.userRole, memberEntity.name, memberEntity.userId, memberEntity.status)
memberEntity.userRole,
memberEntity.name,
memberEntity.employeeNo,
memberEntity.status)
.orderBy(orderSpecifiers.toArray(new OrderSpecifier[0]))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
@@ -549,7 +552,19 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
@Override
public LabelWorkMngDetail findLabelWorkMngDetail(UUID uuid) {
NumberExpression<Long> labelTotCnt = mapSheetAnalDataInferenceGeomEntity.geoUid.count();
NumberExpression<Long> labelTotCnt =
new CaseBuilder()
// .when(mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull())
.when(
mapSheetAnalDataInferenceGeomEntity
.pnu
.gt(0)
.and(
mapSheetAnalDataInferenceGeomEntity.fitState.eq(
ImageryFitStatus.UNFIT.getId())))
.then(1L)
.otherwise(0L)
.sum();
NumberExpression<Long> labelerCnt = labelingAssignmentEntity.workerUid.count();
NumberExpression<Long> reviewerCnt = labelingAssignmentEntity.inspectorUid.count();

View File

@@ -1,6 +1,8 @@
package com.kamco.cd.kamcoback.postgres.repository.layer;
import com.kamco.cd.kamcoback.layer.dto.LayerDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.LayerMapDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.TileUrlDto;
import com.kamco.cd.kamcoback.postgres.entity.MapLayerEntity;
import java.util.Collection;
import java.util.List;
@@ -8,6 +10,7 @@ import java.util.Optional;
import java.util.UUID;
public interface MapLayerRepositoryCustom {
Long findSortOrderDesc();
List<LayerDto.Basic> findAllLayer(LayerDto.SearchReq searchReq);
@@ -15,4 +18,10 @@ public interface MapLayerRepositoryCustom {
Optional<MapLayerEntity> findDetailByUuid(UUID uuid);
List<MapLayerEntity> findAllByUuidIn(Collection<UUID> uuids);
List<LayerMapDto> findLayerMapList(String type);
LayerDto.YearTileDto getChangeDetectionTileUrl(Integer beforeYear, Integer afterYear);
TileUrlDto getChangeDetectionTileOneYearUrl(Integer year);
}

View File

@@ -1,11 +1,17 @@
package com.kamco.cd.kamcoback.postgres.repository.layer;
import static com.kamco.cd.kamcoback.postgres.entity.QMapLayerEntity.mapLayerEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngTileEntity.mapSheetMngTileEntity;
import com.kamco.cd.kamcoback.layer.dto.LayerDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.LayerMapDto;
import com.kamco.cd.kamcoback.layer.dto.LayerDto.TileUrlDto;
import com.kamco.cd.kamcoback.postgres.entity.MapLayerEntity;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.Collection;
import java.util.List;
@@ -40,6 +46,7 @@ public class MapLayerRepositoryImpl implements MapLayerRepositoryCustom {
if (searchReq.getTag() != null) {
whereBuilder.and(mapLayerEntity.tag.toLowerCase().eq(searchReq.getTag().toLowerCase()));
}
if (searchReq.getLayerType() != null) {
whereBuilder.and(
mapLayerEntity.layerType.toLowerCase().eq(searchReq.getLayerType().toLowerCase()));
@@ -51,6 +58,7 @@ public class MapLayerRepositoryImpl implements MapLayerRepositoryCustom {
Projections.constructor(
LayerDto.Basic.class,
mapLayerEntity.uuid,
mapLayerEntity.layerName,
mapLayerEntity.layerType,
mapLayerEntity.description,
mapLayerEntity.tag,
@@ -84,4 +92,140 @@ public class MapLayerRepositoryImpl implements MapLayerRepositoryCustom {
.orderBy(mapLayerEntity.order.asc())
.fetch();
}
@Override
public List<LayerMapDto> findLayerMapList(String type) {
NumberExpression<Integer> crsInt =
Expressions.numberTemplate(
Integer.class, "cast(replace({0}, 'EPSG_', '') as integer)", mapLayerEntity.crs);
return queryFactory
.select(
Projections.constructor(
LayerMapDto.class,
mapLayerEntity.layerName,
mapLayerEntity.layerType,
mapLayerEntity.tag,
mapLayerEntity.order,
mapLayerEntity.url,
mapLayerEntity.minLon,
mapLayerEntity.minLat,
mapLayerEntity.maxLon,
mapLayerEntity.maxLat,
mapLayerEntity.minZoom,
mapLayerEntity.maxZoom,
Expressions.stringTemplate(
"ST_AsGeoJSON(ST_Transform(ST_MakeEnvelope({0}, {1}, {2}, {3}, 4326), {4}))",
mapLayerEntity.minLon,
mapLayerEntity.minLat,
mapLayerEntity.maxLon,
mapLayerEntity.maxLat,
crsInt),
mapLayerEntity.uuid,
Expressions.stringTemplate("cast({0} as text)", mapLayerEntity.rawJson),
mapLayerEntity.crs))
.from(mapLayerEntity)
.where(layerTypeCondition(type), mapLayerEntity.isDeleted.isFalse())
.orderBy(mapLayerEntity.order.asc())
.fetch();
}
@Override
public LayerDto.YearTileDto getChangeDetectionTileUrl(Integer beforeYear, Integer afterYear) {
NumberExpression<Integer> crsInt =
Expressions.numberTemplate(
Integer.class, "cast(replace({0}, 'EPSG_', '') as integer)", mapSheetMngTileEntity.crs);
LayerDto.TileUrlDto before =
queryFactory
.select(
Projections.constructor(
LayerDto.TileUrlDto.class,
mapSheetMngTileEntity.mngYyyy,
mapSheetMngTileEntity.tag,
mapSheetMngTileEntity.url,
mapSheetMngTileEntity.minLon,
mapSheetMngTileEntity.minLat,
mapSheetMngTileEntity.maxLon,
mapSheetMngTileEntity.maxLat,
mapSheetMngTileEntity.minZoom,
mapSheetMngTileEntity.maxZoom,
Expressions.stringTemplate(
"ST_AsGeoJSON(ST_Transform(ST_MakeEnvelope({0}, {1}, {2}, {3}, 4326), {4}))",
mapSheetMngTileEntity.minLon,
mapSheetMngTileEntity.minLat,
mapSheetMngTileEntity.maxLon,
mapSheetMngTileEntity.maxLat,
crsInt),
mapSheetMngTileEntity.crs))
.from(mapSheetMngTileEntity)
.where(mapSheetMngTileEntity.mngYyyy.eq(beforeYear))
.fetchOne();
LayerDto.TileUrlDto after =
queryFactory
.select(
Projections.constructor(
LayerDto.TileUrlDto.class,
mapSheetMngTileEntity.mngYyyy,
mapSheetMngTileEntity.tag,
mapSheetMngTileEntity.url,
mapSheetMngTileEntity.minLon,
mapSheetMngTileEntity.minLat,
mapSheetMngTileEntity.maxLon,
mapSheetMngTileEntity.maxLat,
mapSheetMngTileEntity.minZoom,
mapSheetMngTileEntity.maxZoom,
Expressions.stringTemplate(
"ST_AsGeoJSON(ST_Transform(ST_MakeEnvelope({0}, {1}, {2}, {3}, 4326), {4}))",
mapSheetMngTileEntity.minLon,
mapSheetMngTileEntity.minLat,
mapSheetMngTileEntity.maxLon,
mapSheetMngTileEntity.maxLat,
crsInt),
mapSheetMngTileEntity.crs))
.from(mapSheetMngTileEntity)
.where(mapSheetMngTileEntity.mngYyyy.eq(afterYear))
.fetchOne();
return new LayerDto.YearTileDto(before, after);
}
@Override
public TileUrlDto getChangeDetectionTileOneYearUrl(Integer year) {
NumberExpression<Integer> crsInt =
Expressions.numberTemplate(
Integer.class, "cast(replace({0}, 'EPSG_', '') as integer)", mapSheetMngTileEntity.crs);
return queryFactory
.select(
Projections.constructor(
LayerDto.TileUrlDto.class,
mapSheetMngTileEntity.mngYyyy,
mapSheetMngTileEntity.tag,
mapSheetMngTileEntity.url,
mapSheetMngTileEntity.minLon,
mapSheetMngTileEntity.minLat,
mapSheetMngTileEntity.maxLon,
mapSheetMngTileEntity.maxLat,
mapSheetMngTileEntity.minZoom,
mapSheetMngTileEntity.maxZoom,
Expressions.stringTemplate(
"ST_AsGeoJSON(ST_Transform(ST_MakeEnvelope({0}, {1}, {2}, {3}, 4326), {4}))",
mapSheetMngTileEntity.minLon,
mapSheetMngTileEntity.minLat,
mapSheetMngTileEntity.maxLon,
mapSheetMngTileEntity.maxLat,
crsInt),
mapSheetMngTileEntity.crs))
.from(mapSheetMngTileEntity)
.where(mapSheetMngTileEntity.mngYyyy.eq(year))
.fetchOne();
}
private BooleanExpression layerTypeCondition(String type) {
return type.equals("change-detection")
? mapLayerEntity.isChangeMap.isTrue()
: mapLayerEntity.isLabelingMap.isTrue();
}
}

View File

@@ -168,13 +168,13 @@ public class AuditLogRepositoryImpl extends QuerydslRepositorySupport
whereBuilder.and(auditLogEntity.eventStatus.ne(EventStatus.valueOf("FAILED")));
whereBuilder.and(auditLogEntity.eventType.eq(EventType.valueOf("DOWNLOAD")));
if (req.getMenuId() != null && !req.getMenuId().isEmpty()) {
whereBuilder.and(auditLogEntity.menuUid.eq(req.getMenuId()));
}
// if (req.getMenuId() != null && !req.getMenuId().isEmpty()) {
// whereBuilder.and(auditLogEntity.menuUid.eq(req.getMenuId()));
// }
if (req.getUuid() != null) {
whereBuilder.and(auditLogEntity.requestUri.contains("/api/inference/download/"));
whereBuilder.and(auditLogEntity.requestUri.endsWith(String.valueOf(req.getUuid())));
whereBuilder.and(auditLogEntity.requestUri.contains(req.getRequestUri()));
whereBuilder.and(auditLogEntity.downloadUuid.eq(req.getUuid()));
}
if (req.getSearchValue() != null && !req.getSearchValue().isEmpty()) {

View File

@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.postgres.repository.mapsheet;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.AddReq;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.YearSearchReq;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity;
@@ -77,4 +78,6 @@ public interface MapSheetMngRepositoryCustom {
List<ImageFeature> getSceneInference(String yyyy, List<String> mapSheetNums);
void updateMapSheetMngHstUploadId(Long hstUid, UUID uuid, String uploadId);
void insertMapSheetMngTile(@Valid AddReq addReq);
}

View File

@@ -4,6 +4,7 @@ import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kE
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngEntity.mapSheetMngEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngFileEntity.mapSheetMngFileEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity.mapSheetMngHstEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngTileEntity.mapSheetMngTileEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QYearEntity.yearEntity;
import static com.querydsl.core.types.dsl.Expressions.nullExpression;
@@ -12,6 +13,7 @@ import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetScope;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.AddReq;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.YearSearchReq;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity;
@@ -205,7 +207,7 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.on(mapSheetMngEntity.mngYyyy.eq(mapSheetMngHstEntity.mngYyyy))
.leftJoin(mapInkx5kEntity)
.on(mapSheetMngHstEntity.mapSheetNum.eq(mapInkx5kEntity.mapidcdNo))
.where(whereBuilder)
.where(mapInkx5kEntity.useInference.eq(CommonUseStatus.USE))
// .offset(pageable.getOffset())
// .limit(pageable.getPageSize())
.orderBy(mapSheetMngEntity.mngYyyy.desc())
@@ -252,7 +254,9 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
public MapSheetMngDto.MngDto findMapSheetMng(int mngYyyy) {
BooleanBuilder whereBuilder = new BooleanBuilder();
whereBuilder.and(mapSheetMngEntity.mngYyyy.eq(mngYyyy));
whereBuilder
.and(mapSheetMngEntity.mngYyyy.eq(mngYyyy))
.and(mapInkx5kEntity.useInference.eq(CommonUseStatus.USE));
MapSheetMngDto.MngDto foundContent =
queryFactory
@@ -1040,6 +1044,36 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.execute();
}
@Override
public void insertMapSheetMngTile(AddReq addReq) {
long execute =
queryFactory
.insert(mapSheetMngTileEntity)
.columns(
mapSheetMngTileEntity.mngYyyy,
mapSheetMngTileEntity.url,
mapSheetMngTileEntity.minLon,
mapSheetMngTileEntity.minLat,
mapSheetMngTileEntity.maxLon,
mapSheetMngTileEntity.maxLat,
mapSheetMngTileEntity.minZoom,
mapSheetMngTileEntity.maxZoom,
mapSheetMngTileEntity.tag,
mapSheetMngTileEntity.crs)
.values(
addReq.getMngYyyy(),
addReq.getUrl(),
addReq.getMinLon(),
addReq.getMinLat(),
addReq.getMaxLon(),
addReq.getMaxLat(),
addReq.getMinZoom(),
addReq.getMaxZoom(),
addReq.getTag(),
addReq.getCrs())
.execute();
}
private BooleanExpression eqYearStatus(QYearEntity years, String status) {
if (status == null) {
return null;

View File

@@ -0,0 +1,23 @@
package com.kamco.cd.kamcoback.postgres.repository.scheduler;
import com.kamco.cd.kamcoback.postgres.entity.LabelingInspectorEntity;
import jakarta.persistence.LockModeType;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
public interface TrainingDataLabelJobRepository
extends JpaRepository<LabelingInspectorEntity, UUID>, TrainingDataLabelJobRepositoryCustom {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query(
"""
select r
from LabelingInspectorEntity r
where r.analUid = :analUid
and r.inspectorUid in :inspectorUids
""")
List<LabelingInspectorEntity> lockInspectors(Long analUid, List<String> inspectorUids);
}

View File

@@ -0,0 +1,17 @@
package com.kamco.cd.kamcoback.postgres.repository.scheduler;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
import java.util.List;
import java.util.UUID;
public interface TrainingDataLabelJobRepositoryCustom {
List<Tasks> findCompletedYesterdayUnassigned();
List<InspectorPendingDto> findInspectorPendingByRound(Long analUid);
void assignReviewerBatch(List<UUID> assignmentUids, String reviewerId);
void updateGeomUidTestState(List<Long> geomUids);
}

View File

@@ -0,0 +1,119 @@
package com.kamco.cd.kamcoback.postgres.repository.scheduler;
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingInspectorEntity.labelingInspectorEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState;
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;
@Repository
public class TrainingDataLabelJobRepositoryImpl extends QuerydslRepositorySupport
implements TrainingDataLabelJobRepositoryCustom {
private final JPAQueryFactory queryFactory;
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
public TrainingDataLabelJobRepositoryImpl(JPAQueryFactory queryFactory) {
super(LabelingAssignmentEntity.class);
this.queryFactory = queryFactory;
}
@Override
public List<Tasks> findCompletedYesterdayUnassigned() {
ZoneId zone = ZoneId.of("Asia/Seoul");
ZonedDateTime todayStart = ZonedDateTime.now(zone).toLocalDate().atStartOfDay(zone);
ZonedDateTime yesterdayStart = todayStart.minusDays(1);
BooleanExpression isYesterday =
labelingAssignmentEntity
.workStatDttm
.goe(yesterdayStart)
.and(labelingAssignmentEntity.workStatDttm.lt(todayStart));
return queryFactory
.select(
Projections.constructor(
Tasks.class,
labelingAssignmentEntity.assignmentUid,
labelingAssignmentEntity.inferenceGeomUid,
labelingAssignmentEntity.analUid))
.from(labelingAssignmentEntity)
.where(
labelingAssignmentEntity.workState.in(LabelState.SKIP.getId(), LabelState.DONE.getId()),
labelingAssignmentEntity.inspectorUid.isNull(),
isYesterday)
.orderBy(
labelingAssignmentEntity.analUid.asc(),
labelingAssignmentEntity.assignGroupId.asc(),
labelingAssignmentEntity.inferenceGeomUid.asc())
.fetch();
}
/**
* 해당 회차에 라벨링 할당받은 검수자별 완료 건수 count(), 완료한 게 적은 순으로 해야 일이 한 사람에게 몰리지 않음
*
* @param analUid
* @return
*/
@Override
public List<InspectorPendingDto> findInspectorPendingByRound(Long analUid) {
return queryFactory
.select(
Projections.constructor(
InspectorPendingDto.class,
labelingInspectorEntity.inspectorUid,
labelingAssignmentEntity.assignmentUid.count()))
.from(labelingInspectorEntity)
.leftJoin(labelingAssignmentEntity)
.on(
labelingInspectorEntity.inspectorUid.eq(labelingAssignmentEntity.inspectorUid),
labelingAssignmentEntity.inspectState.in(
InspectState.EXCEPT.getId(), InspectState.COMPLETE.getId()))
.where(labelingInspectorEntity.analUid.eq(analUid))
.groupBy(labelingInspectorEntity.inspectorUid)
.orderBy(labelingAssignmentEntity.assignmentUid.count().asc())
.fetch();
}
/**
* 배치용 여러 건 update
*
* @param assignmentUids
* @param reviewerId
*/
@Override
public void assignReviewerBatch(List<UUID> assignmentUids, String reviewerId) {
queryFactory
.update(labelingAssignmentEntity)
.set(labelingAssignmentEntity.inspectorUid, reviewerId)
.set(labelingAssignmentEntity.inspectState, InspectState.UNCONFIRM.getId())
.set(labelingAssignmentEntity.modifiedDate, ZonedDateTime.now())
.where(labelingAssignmentEntity.assignmentUid.in(assignmentUids))
.execute();
}
@Override
public void updateGeomUidTestState(List<Long> geomUids) {
queryFactory
.update(mapSheetAnalDataInferenceGeomEntity)
.set(mapSheetAnalDataInferenceGeomEntity.testState, InspectState.UNCONFIRM.getId())
.set(mapSheetAnalDataInferenceGeomEntity.updatedDttm, ZonedDateTime.now())
.where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(geomUids))
.execute();
}
}

View File

@@ -3,25 +3,10 @@ package com.kamco.cd.kamcoback.postgres.repository.scheduler;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalCntInfo;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalMapSheetList;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
import java.util.List;
import java.util.UUID;
public interface TrainingDataReviewJobRepositoryCustom {
List<Tasks> findCompletedYesterdayUnassigned();
List<InspectorPendingDto> findInspectorPendingByRound(Long analUid);
void assignReviewer(UUID assignmentUid, String reviewerId);
void assignReviewerBatch(List<UUID> assignmentUids, String reviewerId);
Tasks findAssignmentTask(String assignmentUid);
void updateGeomUidTestState(List<Long> geomUids);
List<CompleteLabelData> findCompletedYesterdayLabelingList(Long analUid, String mapSheetNum);
List<AnalMapSheetList> findCompletedAnalMapSheetList(Long analUid);

View File

@@ -1,24 +1,18 @@
package com.kamco.cd.kamcoback.postgres.repository.scheduler;
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingInspectorEntity.labelingInspectorEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnDataGeomEntity.mapSheetLearnDataGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState;
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalCntInfo;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalMapSheetList;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData.Properties;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberExpression;
@@ -29,9 +23,10 @@ import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;
@Repository
public class TrainingDataReviewJobRepositoryImpl extends QuerydslRepositorySupport
implements TrainingDataReviewJobRepositoryCustom {
@@ -43,121 +38,6 @@ public class TrainingDataReviewJobRepositoryImpl extends QuerydslRepositorySuppo
this.queryFactory = queryFactory;
}
@Override
public List<Tasks> findCompletedYesterdayUnassigned() {
ZoneId zone = ZoneId.of("Asia/Seoul");
ZonedDateTime todayStart = ZonedDateTime.now(zone).toLocalDate().atStartOfDay(zone);
ZonedDateTime yesterdayStart = todayStart.minusDays(1);
BooleanExpression isYesterday =
labelingAssignmentEntity
.workStatDttm
.goe(yesterdayStart)
.and(labelingAssignmentEntity.workStatDttm.lt(todayStart));
return queryFactory
.select(
Projections.constructor(
Tasks.class,
labelingAssignmentEntity.assignmentUid,
labelingAssignmentEntity.inferenceGeomUid,
labelingAssignmentEntity.analUid))
.from(labelingAssignmentEntity)
.where(
labelingAssignmentEntity.workState.in(LabelState.SKIP.getId(), LabelState.DONE.getId()),
labelingAssignmentEntity.inspectorUid.isNull(),
isYesterday)
.orderBy(
labelingAssignmentEntity.analUid.asc(),
labelingAssignmentEntity.assignGroupId.asc(),
labelingAssignmentEntity.inferenceGeomUid.asc())
.fetch();
}
/**
* 해당 회차에 라벨링 할당받은 검수자별 완료 건수 count(), 완료한 게 적은 순으로 해야 일이 한 사람에게 몰리지 않음
*
* @param analUid
* @return
*/
@Override
public List<InspectorPendingDto> findInspectorPendingByRound(Long analUid) {
return queryFactory
.select(
Projections.constructor(
InspectorPendingDto.class,
labelingInspectorEntity.inspectorUid,
labelingAssignmentEntity.assignmentUid.count()))
.from(labelingInspectorEntity)
.leftJoin(labelingAssignmentEntity)
.on(
labelingInspectorEntity.inspectorUid.eq(labelingAssignmentEntity.inspectorUid),
labelingAssignmentEntity.inspectState.in(
InspectState.EXCEPT.getId(), InspectState.COMPLETE.getId()))
.where(labelingInspectorEntity.analUid.eq(analUid))
.groupBy(labelingInspectorEntity.inspectorUid)
.orderBy(labelingAssignmentEntity.assignmentUid.count().asc())
.fetch();
}
/**
* 실시간 분배용 1건 update
*
* @param assignmentUid
* @param reviewerId
*/
@Override
public void assignReviewer(UUID assignmentUid, String reviewerId) {
queryFactory
.update(labelingAssignmentEntity)
.set(labelingAssignmentEntity.inspectorUid, reviewerId)
.set(labelingAssignmentEntity.inspectState, InspectState.UNCONFIRM.getId())
.set(labelingAssignmentEntity.modifiedDate, ZonedDateTime.now())
.where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid))
.execute();
}
/**
* 배치용 여러 건 update
*
* @param assignmentUids
* @param reviewerId
*/
@Override
public void assignReviewerBatch(List<UUID> assignmentUids, String reviewerId) {
queryFactory
.update(labelingAssignmentEntity)
.set(labelingAssignmentEntity.inspectorUid, reviewerId)
.set(labelingAssignmentEntity.inspectState, InspectState.UNCONFIRM.getId())
.set(labelingAssignmentEntity.modifiedDate, ZonedDateTime.now())
.where(labelingAssignmentEntity.assignmentUid.in(assignmentUids))
.execute();
}
@Override
public Tasks findAssignmentTask(String assignmentUid) {
return queryFactory
.select(
Projections.constructor(
Tasks.class,
labelingAssignmentEntity.assignmentUid,
labelingAssignmentEntity.inferenceGeomUid,
labelingAssignmentEntity.analUid))
.from(labelingAssignmentEntity)
.where(labelingAssignmentEntity.assignmentUid.eq(UUID.fromString(assignmentUid)))
.fetchOne();
}
@Override
public void updateGeomUidTestState(List<Long> geomUids) {
queryFactory
.update(mapSheetAnalDataInferenceGeomEntity)
.set(mapSheetAnalDataInferenceGeomEntity.testState, InspectState.UNCONFIRM.getId())
.set(mapSheetAnalDataInferenceGeomEntity.updatedDttm, ZonedDateTime.now())
.where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(geomUids))
.execute();
}
@Override
public List<CompleteLabelData> findCompletedYesterdayLabelingList(
Long analUid, String mapSheetNum) {

View File

@@ -6,9 +6,11 @@ import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kE
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnDataGeomEntity.mapSheetLearnDataGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QPnuEntity.pnuEntity;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.common.enums.DetectionClassification;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState;
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
@@ -304,6 +306,10 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(
labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id),
mapSheetAnalInferenceEntity.analState.ne(LabelMngState.FINISH.getId()))
.where(labelingAssignmentEntity.workerUid.eq(userId))
.fetchOne();
@@ -324,6 +330,10 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(
labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id),
mapSheetAnalInferenceEntity.analState.ne(LabelMngState.FINISH.getId()))
.where(
labelingAssignmentEntity.workerUid.eq(userId),
labelingAssignmentEntity.workState.eq("ASSIGNED"))
@@ -352,6 +362,10 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(
labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id),
mapSheetAnalInferenceEntity.analState.ne(LabelMngState.FINISH.getId()))
.where(
labelingAssignmentEntity.workerUid.eq(userId),
labelingAssignmentEntity.workState.in(
@@ -466,6 +480,14 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
}
// 5. DTO 생성
// pnu list 조회
List<String> pnuList =
queryFactory
.select(pnuEntity.pnu)
.from(pnuEntity)
.where(pnuEntity.geo.geoUid.eq(mapSheetAnalDataInferenceGeomEntityEntity.getGeoUid()))
.fetch();
var changeDetectionInfo =
ChangeDetectionInfo.builder()
.mapSheetInfo(mapSheetEntity != null ? mapSheetEntity.getMapidNm() : "")
@@ -483,6 +505,9 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeCd() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeCd()
: "")
.classificationName(
DetectionClassification.fromStrDesc(
mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterCd()))
.probability(
mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeProb() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeProb()
@@ -494,6 +519,9 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterCd() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterCd()
: "")
.classificationName(
DetectionClassification.fromStrDesc(
mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterCd()))
.probability(
mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterProb() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterProb()
@@ -507,10 +535,7 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
mapSheetAnalDataInferenceGeomEntityEntity.getCdProb() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getCdProb()
: 0.0)
.pnu(
mapSheetAnalDataInferenceGeomEntityEntity.getPnu() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getPnu()
: 0L)
.pnu(pnuList)
.mapSheetNum(
mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum()
@@ -518,12 +543,17 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
.build();
var inspectionResultInfo =
InspectionResultInfo.builder()
.verificationResult(convertInspectState(assignment.toDto().getInspectState()))
.inappropriateReason("")
// .memo(assignment.toDto().getInspectMemo() != null ?
// assignment.toDto().getInspectMemo() : "")
.build();
queryFactory
.select(
Projections.constructor(
InspectionResultInfo.class,
mapSheetAnalDataInferenceGeomEntity.fitState,
mapSheetAnalDataInferenceGeomEntity.fitStateCmmnt))
.from(mapSheetAnalDataInferenceGeomEntity)
.where(
mapSheetAnalDataInferenceGeomEntity.geoUid.eq(
mapSheetAnalDataInferenceGeomEntityEntity.getGeoUid()))
.fetchOne();
// 6. Geometry를 GeoJSON으로 변환
InferenceDataGeometry inferData =

View File

@@ -7,9 +7,11 @@ import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceG
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnDataGeomEntity.mapSheetLearnDataGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QPnuEntity.pnuEntity;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.common.enums.DetectionClassification;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
@@ -23,7 +25,6 @@ import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.DetailRes;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.GeoFeatureRequest.Properties;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.InferenceDataGeometry;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.InferenceDataGeometry.InferenceProperties;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.InspectionResultInfo;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.LearnDataGeometry;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.LearnDataGeometry.LearnProperties;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewGeometryInfo;
@@ -313,6 +314,10 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(
labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id),
mapSheetAnalInferenceEntity.analState.ne(LabelMngState.FINISH.getId()))
.where(labelingAssignmentEntity.inspectorUid.eq(userId))
.fetchOne();
@@ -333,6 +338,10 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(
labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id),
mapSheetAnalInferenceEntity.analState.ne(LabelMngState.FINISH.getId()))
.where(
labelingAssignmentEntity.inspectorUid.eq(userId),
labelingAssignmentEntity.inspectState.eq("UNCONFIRM"))
@@ -361,6 +370,10 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(
labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id),
mapSheetAnalInferenceEntity.analState.ne(LabelMngState.FINISH.getId()))
.where(
labelingAssignmentEntity.inspectorUid.eq(userId),
labelingAssignmentEntity.inspectState.in("COMPLETE", "EXCEPT"),
@@ -482,7 +495,7 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
queryFactory
.select(memberEntity.name)
.from(memberEntity)
.where(memberEntity.userId.eq(assignment.toDto().getWorkerUid()))
.where(memberEntity.employeeNo.eq(assignment.toDto().getWorkerUid()))
.fetchFirst();
if (workerName == null) {
workerName = "";
@@ -494,6 +507,14 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
}
// 5. DTO 생성
// pnu list 조회
List<String> pnuList =
queryFactory
.select(pnuEntity.pnu)
.from(pnuEntity)
.where(pnuEntity.geo.geoUid.eq(mapSheetAnalDataInferenceGeomEntityEntity.getGeoUid()))
.fetch();
var changeDetectionInfo =
ChangeDetectionInfo.builder()
.mapSheetInfo(mapSheetEntity != null ? mapSheetEntity.getMapidNm() : "")
@@ -511,6 +532,9 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeCd() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeCd()
: "")
.classificationName(
DetectionClassification.fromStrDesc(
mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterCd()))
.probability(
mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeProb() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeProb()
@@ -522,6 +546,9 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterCd() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterCd()
: "")
.classificationName(
DetectionClassification.fromStrDesc(
mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterCd()))
.probability(
mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterProb() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterProb()
@@ -535,10 +562,7 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
mapSheetAnalDataInferenceGeomEntityEntity.getCdProb() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getCdProb()
: 0.0)
.pnu(
mapSheetAnalDataInferenceGeomEntityEntity.getPnu() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getPnu()
: 0L)
.pnu(pnuList)
.mapSheetNum(
mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum()
@@ -547,11 +571,17 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
.build();
var inspectionResultInfo =
InspectionResultInfo.builder()
.verificationResult(convertInspectState(assignment.toDto().getInspectState()))
.inappropriateReason("")
.memo("")
.build();
queryFactory
.select(
Projections.constructor(
TrainingDataReviewDto.InspectionResultInfo.class,
mapSheetAnalDataInferenceGeomEntity.fitState,
mapSheetAnalDataInferenceGeomEntity.fitStateCmmnt))
.from(mapSheetAnalDataInferenceGeomEntity)
.where(
mapSheetAnalDataInferenceGeomEntity.geoUid.eq(
mapSheetAnalDataInferenceGeomEntityEntity.getGeoUid()))
.fetchOne();
// 6. Geometry를 GeoJSON으로 변환
InferenceDataGeometry inferData =

View File

@@ -0,0 +1,55 @@
package com.kamco.cd.kamcoback.scheduler.service;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinDto.GeomUidDto;
import com.kamco.cd.kamcoback.gukyuin.service.GukYuinApiService;
import com.kamco.cd.kamcoback.postgres.core.GukYuinLabelJobCoreService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Log4j2
@Service
@RequiredArgsConstructor
public class GukYuinApiLabelJobService {
private final GukYuinLabelJobCoreService gukYuinLabelJobCoreService;
private final GukYuinApiService gukYuinApiService;
@Value("${spring.profiles.active}")
private String profile;
/**
* 실행중인 profile
*
* @return
*/
private boolean isLocalProfile() {
return "local".equalsIgnoreCase(profile);
}
/** 어제 라벨링 검수 완료된 것 -> 국유인에 전송 */
@Scheduled(cron = "0 0 2 * * *")
public void findLabelingCompleteSend() {
if (isLocalProfile()) {
return;
}
List<GeomUidDto> list = gukYuinLabelJobCoreService.findYesterdayLabelingCompleteList();
if (list.isEmpty()) {
return;
}
for (GeomUidDto gto : list) {
ChngDetectContDto.ResultLabelDto dto =
gukYuinApiService.updateChnDtctObjtLabelingYn(gto.getResultUid(), "Y");
if (dto.getSuccess()) {
// inference_geom 에 label_send_dttm 업데이트 하기
gukYuinLabelJobCoreService.updateAnalDataInferenceGeomSendDttm(gto.getGeoUid());
}
}
}
}

View File

@@ -0,0 +1,110 @@
package com.kamco.cd.kamcoback.scheduler.service;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.ContBasic;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.ResultContDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ResultDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinStatus;
import com.kamco.cd.kamcoback.gukyuin.service.GukYuinApiService;
import com.kamco.cd.kamcoback.postgres.core.GukYuinPnuJobCoreService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Log4j2
@Service
@RequiredArgsConstructor
public class GukYuinApiPnuJobService {
private final GukYuinPnuJobCoreService gukYuinPnuJobCoreService;
private final GukYuinApiService gukYuinApiService;
@Value("${spring.profiles.active}")
private String profile;
/**
* 실행중인 profile
*
* @return
*/
private boolean isLocalProfile() {
return "local".equalsIgnoreCase(profile);
}
/** 국유인 등록 완료 후, 탐지객체 조회해서 PNU 업데이트 하는 스케줄링 하루 1번 새벽 1시에 실행 */
@Scheduled(cron = "0 0 1 * * *")
public void findGukYuinContListPnuUpdate() {
if (isLocalProfile()) {
return;
}
List<LearnKeyDto> list =
gukYuinPnuJobCoreService.findGukyuinApplyStatusUidList(
List.of(GukYuinStatus.GUK_COMPLETED.getId(), GukYuinStatus.PNU_FAILED.getId()));
if (list.isEmpty()) {
return;
}
for (LearnKeyDto dto : list) {
try {
processUid(dto.getUid(), dto.getUid());
gukYuinPnuJobCoreService.updateGukYuinApplyStateComplete(
dto.getId(), GukYuinStatus.PNU_COMPLETED);
} catch (Exception e) {
log.error("[GUKYUIN] failed uid={}", dto.getUid(), e);
gukYuinPnuJobCoreService.updateGukYuinApplyStateComplete(
dto.getId(), GukYuinStatus.PNU_FAILED);
}
}
}
private void processUid(String chnDtctId, String uid) {
ResultDto result = gukYuinApiService.listChnDtctId(chnDtctId);
if (result == null || result.getResult() == null || result.getResult().isEmpty()) {
return;
}
ChngDetectMastDto.Basic basic = result.getResult().get(0);
String chnDtctCnt = basic.getChnDtctCnt();
if (chnDtctCnt == null || chnDtctCnt.isEmpty()) {
return;
}
// page 계산
int pageSize = 100;
int totalCount = Integer.parseInt(chnDtctCnt);
int totalPages = (totalCount + pageSize - 1) / pageSize;
for (int page = 0; page < totalPages; page++) {
processPage(uid, page, pageSize);
}
}
private void processPage(String uid, int page, int pageSize) {
ResultContDto resContList = gukYuinApiService.findChnContList(uid, page, pageSize);
if (resContList.getResult() == null || resContList.getResult().isEmpty()) {
return; // 외부 API 이상 방어
}
List<ContBasic> contList = resContList.getResult();
for (ContBasic cont : contList) {
String[] pnuList = cont.getPnuList();
long pnuCnt = pnuList == null ? 0 : pnuList.length;
if (cont.getChnDtctObjtId() != null) {
gukYuinPnuJobCoreService.updateInferenceGeomDataPnuCnt(cont.getChnDtctObjtId(), pnuCnt);
if (pnuCnt > 0) {
Long geoUid =
gukYuinPnuJobCoreService.findMapSheetAnalDataInferenceGeomUid(
cont.getChnDtctObjtId());
gukYuinPnuJobCoreService.insertGeoUidPnuData(geoUid, pnuList, cont.getChnDtctObjtId());
}
}
}
}
}

View File

@@ -1,7 +1,8 @@
package com.kamco.cd.kamcoback.scheduler.service;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.ResultDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinStatus;
import com.kamco.cd.kamcoback.gukyuin.service.GukYuinApiService;
import com.kamco.cd.kamcoback.postgres.core.GukYuinJobCoreService;
import java.util.List;
@@ -14,10 +15,10 @@ import org.springframework.stereotype.Service;
@Log4j2
@Service
@RequiredArgsConstructor
public class GukYuinApiJobService {
public class GukYuinApiStatusJobService {
private final GukYuinApiService gukYuinApiService;
private final GukYuinJobCoreService gukYuinJobCoreService;
private final GukYuinApiService gukYuinApiService;
@Value("${spring.profiles.active}")
private String profile;
@@ -31,43 +32,40 @@ public class GukYuinApiJobService {
return "local".equalsIgnoreCase(profile);
}
/** 국유인 연동 후, 100% 되었는지 확인하는 스케줄링 매 10분마다 호출 */
@Scheduled(cron = "0 0/10 * * * *")
public void findGukYuinMastCompleteYn() {
if (isLocalProfile()) {
return;
}
List<String> list = gukYuinJobCoreService.findGukyuinApplyIngUidList();
List<LearnKeyDto> list =
gukYuinJobCoreService.findGukyuinApplyStatusUidList(
List.of(GukYuinStatus.IN_PROGRESS.getId()));
if (list.isEmpty()) {
return;
}
for (String uid : list) {
for (LearnKeyDto dto : list) {
try {
ResultDto result = gukYuinApiService.listChnDtctId(uid);
ChngDetectMastDto.ResultDto result = gukYuinApiService.listChnDtctId(dto.getUid());
if (result == null || result.getResult() == null || result.getResult().isEmpty()) {
log.warn("[GUKYUIN] empty result uid={}", uid);
log.warn("[GUKYUIN] empty result chnDtctMstId={}", dto.getChnDtctMstId());
continue;
}
ChngDetectMastDto.Basic basic = result.getResult().get(0);
ChngDetectMastDto.Basic basic = result.getResult().getFirst();
Integer progress =
basic.getExcnPgrt() == null ? null : Integer.parseInt(basic.getExcnPgrt().trim());
if (progress != null && progress == 100) {
gukYuinJobCoreService.updateGukYuinApplyStateComplete(uid);
gukYuinJobCoreService.updateGukYuinApplyStateComplete(
dto.getId(), GukYuinStatus.GUK_COMPLETED);
}
} catch (Exception e) {
log.error("[GUKYUIN] failed uid={}", uid, e);
log.error("[GUKYUIN] failed uid={}", dto.getChnDtctMstId(), e);
}
}
}
@Scheduled(cron = "0 0 1 * * *")
public void findGukYuinContListPnuUpdate() {
if (isLocalProfile()) {
return;
}
}
}

View File

@@ -0,0 +1,115 @@
package com.kamco.cd.kamcoback.scheduler.service;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectContDto.StbltResult;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.LearnKeyDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.RlbDtctDto;
import com.kamco.cd.kamcoback.gukyuin.dto.ChngDetectMastDto.RlbDtctMastDto;
import com.kamco.cd.kamcoback.gukyuin.dto.GukYuinStatus;
import com.kamco.cd.kamcoback.gukyuin.service.GukYuinApiService;
import com.kamco.cd.kamcoback.postgres.core.GukYuinStbltJobCoreService;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Log4j2
@Service
@RequiredArgsConstructor
public class GukYuinApiStbltJobService {
private final GukYuinStbltJobCoreService gukYuinStbltJobCoreService;
private final GukYuinApiService gukYuinApiService;
@Value("${spring.profiles.active}")
private String profile;
/**
* 실행중인 profile
*
* @return
*/
private boolean isLocalProfile() {
return "local".equalsIgnoreCase(profile);
}
/** 국유인 연동 후, 실태조사 적합여부 확인하여 update */
@Scheduled(cron = "0 0 3 * * *")
public void findGukYuinEligibleForSurvey() {
if (isLocalProfile()) {
return;
}
List<LearnKeyDto> list =
gukYuinStbltJobCoreService.findGukYuinEligibleForSurveyList(
GukYuinStatus.PNU_COMPLETED.getId());
if (list.isEmpty()) {
return;
}
for (LearnKeyDto dto : list) {
try {
String yesterday =
LocalDate.now(ZoneId.of("Asia/Seoul"))
.minusDays(1)
.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
RlbDtctDto result = gukYuinApiService.findRlbDtctList(dto.getUid(), yesterday);
if (result == null || result.getResult() == null || result.getResult().isEmpty()) {
log.warn("[GUKYUIN] empty result chnDtctMstId={}", dto.getChnDtctMstId());
continue;
}
for (RlbDtctMastDto stbltDto : result.getResult()) {
String resultUid = stbltDto.getChnDtctObjtId();
gukYuinStbltJobCoreService.updateGukYuinEligibleForSurvey(resultUid, stbltDto);
}
Map<String, StbltResult> resultMap =
result.getResult().stream()
.collect(Collectors.groupingBy(RlbDtctMastDto::getChnDtctObjtId))
.entrySet()
.stream()
.collect(
Collectors.toMap(
Map.Entry::getKey,
e -> {
List<RlbDtctMastDto> pnuList = e.getValue();
boolean hasY = pnuList.stream().anyMatch(v -> "Y".equals(v.getStbltYn()));
String fitYn = hasY ? "Y" : "N";
RlbDtctMastDto selected =
hasY
? pnuList.stream()
.filter(v -> "Y".equals(v.getStbltYn()))
.findFirst()
.orElse(null)
: pnuList.stream()
.filter(v -> "N".equals(v.getStbltYn()))
.findFirst()
.orElse(null);
if (selected == null) {
return null; // 방어 코드
}
return new StbltResult(
fitYn, selected.getIncyCd(), selected.getIncyRsnCont());
}));
resultMap.forEach(gukYuinStbltJobCoreService::updateGukYuinObjectStbltYn);
} catch (Exception e) {
log.error("[GUKYUIN] failed uid={}", dto.getChnDtctMstId(), e);
}
}
}
}

View File

@@ -28,7 +28,6 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -39,21 +38,6 @@ public class MapSheetMngFileJobService {
private final MapSheetMngFileJobCoreService mapSheetMngFileJobCoreService;
@Value("${file.sync-root-dir}")
private String syncRootDir;
@Value("${file.sync-tmp-dir}")
private String syncTmpDir;
@Value("${file.sync-file-extention}")
private String syncFileExtention;
@Value("${file.sync-auto-exception-start-year}")
private int syncAutoExceptionStartYear;
@Value("${file.sync-auto-exception-before-year-cnt}")
private int syncAutoExceptionBeforeYearCnt;
public Integer checkMngFileSync() {
return mapSheetMngFileJobCoreService.findNotYetMapSheetMng();
}

View File

@@ -0,0 +1,112 @@
package com.kamco.cd.kamcoback.scheduler.service;
import com.kamco.cd.kamcoback.postgres.core.TrainingDataLabelJobCoreService;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Log4j2
@Service
@RequiredArgsConstructor
public class TrainingDataLabelJobService {
private final TrainingDataLabelJobCoreService trainingDataLabelJobCoreService;
@Value("${spring.profiles.active}")
private String profile;
private boolean isLocalProfile() {
return "local".equalsIgnoreCase(profile);
}
@Transactional
@Scheduled(cron = "0 0 0 * * *")
public void assignReviewerYesterdayLabelComplete() {
if (isLocalProfile()) {
return;
}
try {
List<Tasks> tasks = trainingDataLabelJobCoreService.findCompletedYesterdayUnassigned();
if (tasks.isEmpty()) {
return;
}
// 회차별로 그룹핑
Map<Long, List<Tasks>> taskByRound =
tasks.stream().collect(Collectors.groupingBy(Tasks::getAnalUid));
// 회차별 분배
for (Map.Entry<Long, List<Tasks>> entry : taskByRound.entrySet()) {
Long analUid = entry.getKey();
List<Tasks> analTasks = entry.getValue();
// pending 계산
List<InspectorPendingDto> pendings =
trainingDataLabelJobCoreService.findInspectorPendingByRound(analUid);
if (pendings.isEmpty()) {
continue;
}
List<String> reviewerIds =
pendings.stream().map(InspectorPendingDto::getInspectorUid).toList();
// Lock 걸릴 수 있기 때문에 엔티티 조회하는 Repository 에서 구현
trainingDataLabelJobCoreService.lockInspectors(analUid, reviewerIds);
// 균등 분배
Map<String, List<Tasks>> assignMap = distributeByLeastPending(analTasks, reviewerIds);
// reviewer별 batch update
assignMap.forEach(
(reviewerId, assignedTasks) -> {
if (assignedTasks.isEmpty()) {
return;
}
List<UUID> assignmentUids =
assignedTasks.stream().map(Tasks::getAssignmentUid).toList();
trainingDataLabelJobCoreService.assignReviewerBatch(assignmentUids, reviewerId);
List<Long> geomUids = assignedTasks.stream().map(Tasks::getInferenceUid).toList();
trainingDataLabelJobCoreService.updateGeomUidTestState(geomUids);
});
}
} catch (Exception e) {
log.error("배치 처리 중 예외", e);
}
}
private Map<String, List<Tasks>> distributeByLeastPending(
List<Tasks> tasks, List<String> reviewerIds) {
Map<String, List<Tasks>> result = new LinkedHashMap<>();
// 순서 유지 중요 (ASC 정렬된 상태)
for (String reviewerId : reviewerIds) {
result.put(reviewerId, new ArrayList<>());
}
int reviewerCount = reviewerIds.size();
for (int i = 0; i < tasks.size(); i++) {
String reviewerId = reviewerIds.get(i % reviewerCount);
result.get(reviewerId).add(tasks.get(i));
}
return result;
}
}

View File

@@ -8,20 +8,13 @@ import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalMapShee
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData.GeoJsonFeature;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.FeatureCollection;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
import jakarta.transaction.Transactional;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
@@ -46,114 +39,13 @@ public class TrainingDataReviewJobService {
}
@Transactional
@Scheduled(cron = "0 0 0 * * *")
public void assignReviewerYesterdayLabelComplete() {
@Scheduled(cron = "0 0 2 * * *")
public void exportGeojsonLabelingGeom() {
if (isLocalProfile()) {
return;
}
try {
List<Tasks> tasks = trainingDataReviewJobCoreService.findCompletedYesterdayUnassigned();
if (tasks.isEmpty()) {
return;
}
// 회차별로 그룹핑
Map<Long, List<Tasks>> taskByRound =
tasks.stream().collect(Collectors.groupingBy(Tasks::getAnalUid));
// 회차별 분배
for (Map.Entry<Long, List<Tasks>> entry : taskByRound.entrySet()) {
Long analUid = entry.getKey();
List<Tasks> analTasks = entry.getValue();
// pending 계산
List<InspectorPendingDto> pendings =
trainingDataReviewJobCoreService.findInspectorPendingByRound(analUid);
if (pendings.isEmpty()) {
continue;
}
List<String> reviewerIds =
pendings.stream().map(InspectorPendingDto::getInspectorUid).toList();
// Lock 걸릴 수 있기 때문에 엔티티 조회하는 Repository 에서 구현
trainingDataReviewJobCoreService.lockInspectors(analUid, reviewerIds);
// 균등 분배
Map<String, List<Tasks>> assignMap = distributeByLeastPending(analTasks, reviewerIds);
// reviewer별 batch update
assignMap.forEach(
(reviewerId, assignedTasks) -> {
if (assignedTasks.isEmpty()) {
return;
}
List<UUID> assignmentUids =
assignedTasks.stream().map(Tasks::getAssignmentUid).toList();
trainingDataReviewJobCoreService.assignReviewerBatch(assignmentUids, reviewerId);
List<Long> geomUids = assignedTasks.stream().map(Tasks::getInferenceUid).toList();
trainingDataReviewJobCoreService.updateGeomUidTestState(geomUids);
});
}
} catch (Exception e) {
log.error("배치 처리 중 예외", e);
}
}
private Map<String, List<Tasks>> distributeByLeastPending(
List<Tasks> tasks, List<String> reviewerIds) {
Map<String, List<Tasks>> result = new LinkedHashMap<>();
// 순서 유지 중요 (ASC 정렬된 상태)
for (String reviewerId : reviewerIds) {
result.put(reviewerId, new ArrayList<>());
}
int reviewerCount = reviewerIds.size();
for (int i = 0; i < tasks.size(); i++) {
String reviewerId = reviewerIds.get(i % reviewerCount);
result.get(reviewerId).add(tasks.get(i));
}
return result;
}
// 라벨러 완료,SKIP 시 호출 -> 미사용
@Transactional
public void assignRealtime(String assignmentUid) {
Tasks task = trainingDataReviewJobCoreService.findAssignmentTask(assignmentUid);
Long analUid = task.getAnalUid();
// pending 계산
List<InspectorPendingDto> pendings =
trainingDataReviewJobCoreService.findInspectorPendingByRound(analUid);
if (pendings.isEmpty()) {
return;
}
List<String> order = pendings.stream().map(InspectorPendingDto::getInspectorUid).toList();
trainingDataReviewJobCoreService.lockInspectors(analUid, order);
trainingDataReviewJobCoreService.assignReviewer(task.getAssignmentUid(), order.getFirst());
List<Long> geomUids = new ArrayList<>();
geomUids.add(task.getInferenceUid());
trainingDataReviewJobCoreService.updateGeomUidTestState(geomUids);
}
@Transactional
@Scheduled(cron = "0 0 2 * * *")
public void exportGeojsonLabelingGeom() {
// 1) 경로/파일명 결정
String targetDir =
"local".equals(profile) ? System.getProperty("user.home") + "/geojson" : trainingDataDir;

View File

@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.trainingdata;
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj;
import com.kamco.cd.kamcoback.scheduler.service.TrainingDataLabelJobService;
import com.kamco.cd.kamcoback.scheduler.service.TrainingDataReviewJobService;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewGeometryInfo;
@@ -32,6 +33,7 @@ import org.springframework.web.bind.annotation.RestController;
public class TrainingDataReviewApiController {
private final TrainingDataReviewService trainingDataReviewService;
private final TrainingDataLabelJobService trainingDataLabelJobService;
private final TrainingDataReviewJobService trainingDataReviewJobService;
@Operation(summary = "목록 조회", description = "검수 할당 목록 조회")
@@ -562,7 +564,7 @@ public class TrainingDataReviewApiController {
description = "스케줄링이 실패한 경우 수동 호출하는 API, 어제 라벨링 완료된 것을 해당 검수자들에게 할당함")
@GetMapping("/run-schedule")
public ApiResponseDto<Void> runTrainingReviewSchedule() {
trainingDataReviewJobService.assignReviewerYesterdayLabelComplete();
trainingDataLabelJobService.assignReviewerYesterdayLabelComplete();
return ApiResponseDto.ok(null);
}

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