🔧 트러블슈팅 & 이슈 히스토리
3D 종·횡단면 분석 마이크로프론트엔드
| 프로젝트 | ***-svc3d-front-mf-crossanalysis |
| 범위 | 운영·개발 중 발생한 주요 이슈 · 원인 · 해결 · 재발 방지 |
| 작성일 | 2026-07-09 |
코드베이스, Git 커밋 이력, 주석·워크어라운드 패턴을 기반으로 정리했습니다.
IP · 내부 서비스명 등 민감 정보는 ████ 처리되었습니다.
📑 목차
#이슈심각도
| 01 | CORS · Nginx API Gateway 라우팅 불일치 | 🔴 Critical |
| 02 | Qiankun CORS · Webpack Externals 의존성 | 🔴 Critical |
| 03 | 공유 Cesium Viewer Entity 오염 | 🔴 Critical |
| 04 | 3D Tileset 하이라이트 누적 버그 | 🟠 Major |
| 05 | SHP MultiLineString 인덱스 클로저 오류 | 🟠 Major |
| 06 | 좌표계 불일치 (EPSG:5186 ↔ 4326) | 🔴 Critical |
| 07 | 모듈 스코프 변수 상태 꼬임 | 🟠 Major |
| 08 | 보고서 생성 비동기 Race Condition | 🟠 Major |
| 09 | request 인터셉터 조건식 버그 | 🟡 Minor |
| 10 | 지하 시각화 Globe 투명도 · Depth Test | 🟠 Major |
| 11 | 레이어 WFS · Capabilities 파싱 실패 | 🟡 Minor |
| 12 | Docker · K8s 배포 경로 불일치 | 🟠 Major |
이슈 요약 매트릭스
발생 빈도
낮음 ────────────────── 높음
┌──────────────────────────────┐
Critical │ #06 좌표계 #01 CORS │
│ #02 Externals #03 Entity │
├──────────────────────────────┤
Major │ #12 Docker #04 Tileset │
│ #07 Module #05 SHP index │
│ #08 Report #10 Globe │
├──────────────────────────────┤
Minor │ #09 Request #11 Layer WFS │
└──────────────────────────────┘
Issue #01: CORS · Nginx API Gateway 라우팅 불일치
| 심각도 | 🔴 Critical |
| 증상 | 브라우저에서 API 호출 시 CORS policy 차단, 502/404, 프론트는 정상 로드되나 분석·레이어·보고서 API 전부 실패 |
| 영향 범위 | 전체 기능 (분석 · 레이어 · 보고서 · 파일 업로드) |
| 관련 파일 | default.conf, dsDockerfile |
원인
- Cross-Origin 요청 — 마이크로앱(8099)과 API 서버(다른 Origin)가 분리되어 있어 브라우저 CORS 정책에 걸림
- Nginx root 경로 오류 — 정적 파일 root가 /usr/share/nginx/html/dtmap으로 설정되어 빌드 산출물 경로와 불일치
- API Upstream 직접 연결 — /api-gateway/ location이 내부 IP(10.**.***.***:8887)에 직접 프록시되어 게이트웨이 통합 전환 후 라우팅 실패
해결
① dsDockerfile — CORS 사전 대응
# CORS 에러 발생을 차단하기 위한 사전작성 설정 정보 적용
COPY default.conf /etc/nginx/conf.d/
② default.conf — 정적 root 수정 (commit: 886218a)
- root /usr/share/nginx/html/dtmap;
+ root /usr/share/nginx/html;
③ API Gateway 통합 라우팅 (commit: d9f0b7a)
location /api-gateway/ {
- proxy_pass http://10.**.***.***:8887/;
+ proxy_pass http://10.**.***.***:8000;
}
아키텍처 (해결 후)
flowchart LR
Browser["Browser\n(same origin)"] --> Nginx["Nginx :8080"]
Nginx --> Static["/crossSectionalAnalysis\n(static)"]
Nginx --> GW["/server · /dtapi\n/api-gateway · /geosvr"]
GW --> Internal["Internal Services\n(10.**.***.***:8000)"]
재발 방지
- 프론트 코드에 절대 URL(내부 IP) 하드코딩 금지 — 상대 경로(/server, /dtapi)만 사용
- Nginx location 추가·변경 시 정적 root와 dist COPY 경로 동시 확인
- 배포 후 Network 탭에서 Origin 일치 여부 smoke test
Issue #02: Qiankun 서브앱 CORS · Webpack Externals 의존성
| 심각도 | 🔴 Critical |
| 증상 | (개발) 메인 포털에서 서브앱 JS 로드 실패 / (운영) Cesium is not defined, React is not defined |
| 영향 범위 | 마이크로앱 로드 · Production 빌드 |
| 관련 파일 | webpack.config.js, index.tsx |
원인
환경원인
| 개발 | Webpack Dev Server(8099)와 메인 포털(다른 포트) 간 Cross-Origin 스크립트 로드 |
| 운영 | Production 빌드에서 React · Cesium · Turf 등을 externals로 분리 → 메인 포털 CDN/global 변수에 의존 |
해결
개발 — CORS 헤더
// webpack.config.js
devServer: {
port: 8099,
headers: {
'Access-Control-Allow-Origin': '*'
},
}
운영 — Externals + UMD
externals: {
"@cesiumgs/cesium-analytics": "Cesium",
react: 'React',
'react-dom': 'ReactDOM',
'@turf/turf': 'turf',
proj4: 'proj4',
wellknown: 'wellknown',
},
output: {
libraryTarget: 'umd' // window.LongitudinalAnalysis
}
체크리스트
□ 메인 포털 HTML에 Cesium · React · Turf CDN/global 선로드 확인
□ build:prod 산출물을 포털 없이 단독 열면 externals 에러 발생 (정상)
□ Qiankun mount 시 container DOM id 일치 확인
→ #microapp-crossSectionalAnalysis-LongitudinalAnalysis
Issue #03: 공유 Cesium Viewer Entity 오염
| 심각도 | 🔴 Critical |
| 증상 | 마이크로앱 종료 후에도 분석선·포인트·레이어가 지도에 잔존, 다른 마이크로앱과 Entity ID 충돌 |
| 영향 범위 | 전체 포털 UX, 다른 분석 도구 |
| 관련 파일 | app.tsx, LongitudinalAnalysis.tsx, ChangeInfo.tsx |
원인
초기 구현에서 Entity를 **view.entities(Viewer 기본 컬렉션)**에 직접 추가했습니다. Qiankun 아키텍처에서 Viewer는 여러 마이크로앱이 공유하므로, 한 앱의 Entity가 다른 앱에 영향을 줍니다.
// ❌ 초기 패턴 (주석으로 잔존)
// view.entities.removeById(`${prefix}-line`);
// view.entities.removeAll();
해결
① CustomDataSource 격리
// LongitudinalAnalysis.tsx
dataSource = new CustomDataSource('crossDrawCreat');
view.dataSources.add(dataSource);
// Entity 추가/제거는 dataSource 기준
dataSource.entities.add({ ... });
dataSource.entities.removeById(`${prefix}-line`);
② 언마운트 cleanup
// useEffect return (LongitudinalAnalysis.tsx)
if (dataSource) {
dataSource.entities.removeAll();
view.dataSources.remove(dataSource);
}
view.dataSources.getByName('crossMemoCreate').forEach(item => {
view.dataSources.remove(item);
});
③ 앱 종료 시 Viewer 정리 (app.tsx)
useEffect(() => {
return () => {
refreshCoverageCheck();
onCloseMicroApp();
}
}, [])
// 패널 닫기 (handleChange case 1)
window.cviewer.entities.removeAll();
layer.forEach(item => window.cviewer.imageryLayers.remove(item.inst));
DataSource 네이밍 규칙
DataSource Name용도
| crossDrawCreat | 분석선 · 포인트 · 하이라이트 |
| crossMemoCreate | 지점 메모 Entity |
| polygon / lodPolygon | WFS · LOD1 폴리곤 |
Issue #04: 3D Tileset 하이라이트 누적 버그
| 심각도 | 🟠 Major |
| 증상 | 분석 반복 시 LOD1 건물 타일 색상이 점점 진해지거나, 이전 분석 객체 색상이 남음 |
| 영향 범위 | 지상 객체 시각화 · 3D Tileset Coverage |
| 관련 파일 | app.tsx — refreshCoverageCheck() |
원인
분석 중 Coverage 3D Tileset Feature에 색상을 직접 변경하지만, 타일이 unload/reload될 때 이전 색상 상태가 GPU 버퍼에 누적됩니다. tileVisible 이벤트 리스너도 분석마다 중복 등록될 수 있습니다.
해결
// app.tsx — refreshCoverageCheck()
primitive.tileVisible.addEventListener(async (tile) => {
try {
if (!tile.contentReady) {
await tile.content.readyPromise;
}
const content = tile.content;
for (let j = 0; j < content.featuresLength; j++) {
const feature = content.getFeature(j);
// ★ 이전 상태와 무관하게 강제 WHITE 리셋
feature.color = Cesium.Color.WHITE;
}
} catch (error) {
console.error(`Error processing tile at index ${i}:`, error);
}
});
언마운트 시 refreshCoverageCheck() 호출로 타일 색상을 초기화합니다.
주의
⚠ tileVisible 리스너는 타일셋마다 등록됨 → 앱 재진입 시 중복 등록 가능
⚠ 분석 시작(buttonClick) 시 tileset = [] 로 배열 초기화 병행
Issue #05: SHP MultiLineString 인덱스 클로저 오류
| 심각度 | 🟠 Major |
| 증상 | MultiLineString SHP 업로드 시 모든 라인이 동일 label/value를 갖거나, 잘못된 라인이 선택됨 |
| 영향 범위 | SHP 기반 분석선 업로드 |
| 관련 커밋 | 0fa21b4 — SHP(line) 분석 시 index 수정 |
| 관련 파일 | LongitudinalAnalysis.tsx — handleUpload() |
원인
forEach 중첩 시 **외부 루프의 index**를 내부 MultiLineString 항목 label에 재사용했습니다. Feature가 여러 개일 때 마지막 index 값만 반영되는 클로저/스코프 버그입니다.
// ❌ Before
allFeatures.forEach((feature, index) => {
featuresArr.forEach((featureItem) => {
label: `line${index + 1}`, // 외부 index 재사용
value: index,
});
});
// ✅ After
featuresArr.forEach((featureItem, lineIndex) => {
label: `line${lineIndex + 1}`,
value: lineIndex,
});
재발 방지
- 중첩 forEach/map에서 각 레벨 고유 index 변수명 사용 (lineIndex, featureIndex)
- MultiLineString SHP 테스트 케이스: 2개 이상 LineString 포함 파일로 회귀 테스트
Issue #06: 좌표계 불일치 (EPSG:5186 ↔ 4326)
| 심각도 | 🔴 Critical |
| 증상 | 지하시설물 Scatter가 종단면 축에서 수 km 어긋남, 교차점 0건, 차트에 지하 데이터 미표시 |
| 영향 범위 | 지하 객체 분석 · SHP 업로드 |
| 관련 파일 | LongitudinalAnalysis.tsx, LayerData.tsx, sample.ts |
원인
시스템 내 데이터 소스별 기본 좌표계가 다릅니다.
데이터좌표계API/소스
| Cesium 지형 · 분석선 | EPSG:4326 (WGS84) | sampleTerrainMostDetailed |
| LOD1 건물 | EPSG:4326 | getBuildgeom2 |
| 지하시설물 | EPSG:5186 (Korea 2000) | queryCrossList |
| SHP 업로드 | 사용자 선택 (4326~5186) | uploadShpZipForEPSG |
지하 geometry WKT는 5186인데, 종단면 축(lineString)은 4326으로 계산 → Turf lineIntersect 결과 무효.
해결
// 지하 MULTILINESTRING → WGS84 변환 후 교차
json.forEach((item) => {
let obj4326 = convertCoordinates(
'EPSG:5186', 'EPSG:4326',
[Number(item[0]), Number(item[1]), Number(item[2])]
);
newList.push([obj4326.x, obj4326.y]);
});
const intersection = turf.lineIntersect(turf.lineString(newList), lineString);
SHP 업로드는 sourceEpsg → targetEpsg: 4326 서버 변환 후 사용합니다.
디버깅 팁
콘솔 필터: [종/횡단 분석]
확인 항목:
① queryCrossList 요청 WKT 좌표 범위 (5186: x~200000, y~500000)
② lineString 좌표 범위 (4326: lng~126-132, lat~33-39)
③ 변환 후 intersection.features.length > 0 ?
Issue #07: 모듈 스코프 변수 상태 꼬임
| 심각도 | 🟠 Major |
| 증상 | HMR(Hot Reload) 후 이전 분석 데이터 잔존, 분석 버튼 무반응, allOption 플래그 오동작 |
| 영향 범위 | 개발 환경 · React Strict Mode |
| 관련 파일 | LongitudinalAnalysis.tsx, LongDataDownload.tsx, ChangeInfo.tsx |
원인
Cesium Entity 수명주기와 연동된 상태를 컴포넌트 외부 let 변수로 관리합니다.
// LongitudinalAnalysis.tsx — 모듈 스코프
let drawPoints: Cartesian3[] = [];
let splinePositions: Cartesian3[] = [];
let dataSource: CustomDataSource;
let allOption = 0;
let lineChange: any[] = [];
// LongDataDownload.tsx
let newGroundData: any = [];
let newUnderGroundData: any = [];
React 컴포넌트 remount/HMR 시 useState는 초기화되지만 모듈 변수는 유지됩니다.
완화 조치 (현재 코드)
시점조치
| useEffect cleanup | dataSource.entities.removeAll(), Handler removeInputAction |
| buttonClick(1) 초기화 | addedPoints = [], lineChange = [], dataSource.entities.removeAll() |
| citySpaceData | allOption = 0 분석 완료 후 리셋 |
권장 개선 (향후)
useRef 또는 Redux로 마이그레이션
→ HMR/Strict Mode double-mount에도 안전
→ 디버깅 시 React DevTools로 상태 추적 가능
Issue #08: 보고서 생성 비동기 Race Condition
| 심각도 | 🟠 Major |
| 증상 | DOCX 다운로드 시 지상 객체 목록이 비어 있거나 일부만 포함, upperList 누락 |
| 영향 범위 | 보고서 생성 (LongDataDownload) |
| 관련 파일 | LongDataDownload.tsx |
원인
useEffect에서 groundData.map() 내부 newquery3DtailInfonext1 Promise가 비동기인데, setGroundDataSource(newGroundData)를 Promise 완료 전에 호출합니다.
// ❌ 문제 패턴
groundData.map((data) => {
newquery3DtailInfonext1({ nfId }).then((res) => {
newGroundData.push(obj); // 나중에 완료
});
});
setGroundDataSource(newGroundData); // 빈 배열 또는 일부만
handleDownload 시점에 groundDataSource가 아직 채워지지 않았을 수 있습니다.
해결 방향
// ✅ Promise.all 패턴 (권장)
const results = await Promise.all(
groundData.map(data =>
newquery3DtailInfonext1({ nfId: data.newPolygon.properties.item.nfId })
)
);
setGroundDataSource(results.map(/* transform */));
// handleDownload에서 groundDataSource 대신
// await로 fresh fetch 후 reportDownLoad 호출
임시 우회
보고서 다운로드 전 속성 패널에서 데이터 로드 완료 확인 후 버튼 클릭 (UX 개선 필요).
Issue #09: request 인터셉터 조건식 버그
| 심각도 | 🟡 Minor (잠재적) |
| 증상 | Portal Embed가 아닌 환경에서도 URL에 . prefix가 붙거나, 반대로 prefix가 누락 |
| 관련 파일 | utils/request.ts |
원인
// ❌ JavaScript truthy 함정 — "op-portal" 문자열은 항상 true
if (process.env.appConfigGbn === "portal-embed" || "op-portal") {
if (url.startsWith('/')) {
url = '.' + url;
}
}
|| "op-portal"은 두 번째 operand가 non-empty string이므로 조건식 전체가 항상 true입니다.
수정 (권장)
// ✅
if (process.env.appConfigGbn === "portal-embed" ||
process.env.appConfigGbn === "op-portal") {
if (url.startsWith('/')) {
url = '.' + url;
}
}
Issue #10: 지하 시각화 Globe 투명도 · Depth Test
| 심각度 | 🟠 Major |
| 증상 | 지하시설물 Scatter는 보이나 지형 아래 매설 깊이가 가려짐, 또는 지형이 불투명하게 덮음 |
| 영향 범위 | 종단면 차트 + 3D 지도 동시 분석 UX |
| 관련 파일 | LongitudinalAnalysis.tsx — mount/unmount useEffect |
원인
Cesium Globe 기본 설정은 지형이 항상 불투명하고 depthTestAgainstTerrain = false입니다. 지하 매설 깊이를 3D+차트로 동시에 보여주려면 Globe translucency 조정이 필요합니다.
해결 (마운트 시)
// 분석 시작 — 지하 관찰 모드
globe.depthTestAgainstTerrain = true;
globe.translucency.enabled = true;
globe.translucency.frontFaceAlphaByDistance = new Cesium.NearFarScalar(
10.0, 0.0, // 가까울수록 투명
5000, 1.0 // 멀수록 불투명
);
scene.screenSpaceCameraController.enableCollisionDetection = false;
cleanup (언마운트 시)
// 원래 Globe 설정 복원 필수 — 다른 마이크로앱 영향 방지
globe.baseColor = oldBaseColor;
globe.undergroundColor = oldUndergroundColor;
globe.depthTestAgainstTerrain = oldDepthTestAgainstTerrain;
globe.translucency.enabled = oldTransEnable;
scene.screenSpaceCameraController.enableCollisionDetection = oldEnableCollisionDetection;
Issue #11: 레이어 WFS · Capabilities 파싱 실패
| 심각도 | 🟡 Minor |
| 증상 | 레이어 트리 체크 시 리턴값 없음 Ant Design error toast, WFS 폴리곤 미표시 |
| 영향 범위 | LayerData 패널 · 2D 레이어 오버레이 |
| 관련 파일 | LayerData.tsx, sample.ts |
원인
- GeoServer WFS GetFeature 응답 empty 또는 CQL Filter 좌표 오류
- Portal Embed 환경에서 레이어 URL이 /로 시작 → 상대 경로 prefix 누락 시 404
- WMS Capabilities XML 파싱 시 cntntsId 매칭 실패
해결
Portal Embed URL prefix
// LayerData.tsx — addLayer()
if (process.env.appConfigGbn === "portal-embed") {
if (node.url.startsWith('/')) {
node.url = '.' + node.url;
}
}
에러 핸들링
try {
// WFS GetFeature + INTERSECTS(the_geom, ...)
} catch (error) {
message.error('리턴값 없음');
}
디버깅
콘솔 필터: [종/횡단 분석] 레이어
확인: capabilitiesJson, matchingLayer, minX/minY/maxX/maxY
Issue #12: Docker · K8s 배포 경로 불일치
| 심각도 | 🟠 Major |
| 증상 | 컨테이너 기동 후 404, 마이크로앱 HTML/JS 로드 실패, 구버전 캐시 서빙 |
| 영향 범위 | CI/CD · 운영 배포 |
| 관련 커밋 | d133510, 4e227fe, 886218a 등 Dockerfile/default.conf 연속 수정 |
| 관련 파일 | dsDockerfile, default.conf, deploy/all-in-one/devops-web-master.yaml |
원인
문제상세
| dist COPY 경로 | projects/crossSectionalAnalysis/dist → /usr/share/nginx/html/crossSectionalAnalysis |
| Nginx root 불일치 | root가 dtmap 서브디렉터리를 가리켜 index.html 404 |
| Private Registry | FROM ████.██.██.███:5000/nginx_unprivileged:1.0 — 레지스트리 접근 필요 |
| K8s Image placeholder | __IMAGE__ CI 치환 누락 시 ImagePullBackOff |
해결된 Dockerfile
FROM ████.██.██.███:5000/nginx_unprivileged:1.0
USER root
RUN rm /usr/share/nginx/html/index.html
COPY projects/crossSectionalAnalysis/dist /usr/share/nginx/html/crossSectionalAnalysis
COPY default.conf /etc/nginx/conf.d/
EXPOSE 80
배포 검증 체크리스트
□ curl http://<host>/crossSectionalAnalysis/LongitudinalAnalysis.html → 200
□ curl http://<host>/crossSectionalAnalysis/static/*.js → 200
□ /server/api/health (또는 동등 엔드포인트) → 200
□ K8s Pod log에 nginx error 없음
□ 브라우저 Hard Refresh (chunkhash 변경 반영)
부록 A: 건물 Annotation startIndex/endIndex 교정
종단면 차트에 LOD1 건물 Bar를 그릴 때, 교차 구간의 start/end index가 역전되는 경우가 있습니다.
// LongitudinalAnalysis.tsx
if (startIndex > endIndex) {
[startIndex, endIndex] = [endIndex, startIndex];
}
const unifiedValue = data[startIndex].value;
for (let i = startIndex; i <= endIndex; i++) {
data[i].value = unifiedValue; // Bar 높이 통일
}
증상 (수정 전): 건물 Bar가 차트 범위 밖에 그려지거나, Recharts annotation 위치 깨짐.
부록 B: 분석선 10개 제한
// LongitudinalAnalysis.tsx
message.warning('분석선은 최대 10개까지 그릴 수 있습니다.');
Cesium Entity · API 부하 · 차트 가독성을 위해 동시 분석선 수를 10개로 제한합니다. 초과 시 사용자에게 warning toast 표시.
부록 C: Git 이슈 타임라인
timeline
title 주요 Fix 커밋 흐름
section 2025-02
init : 프로젝트 초기화
Dockerfile 연속 수정 : 배포 경로 튜닝
default.conf root 수정 : 정적 404 해결
API Gateway 라우팅 : /api-gateway upstream 변경
SHP index fix : MultiLineString label 버그
section 2025+
UI/UX Source Merge : 포털 UI 통합
config 수정 : AppConfig 환경 분기
부록 D: 운영 디버깅 Quick Reference
증상1차 확인2차 확인
| API 전부 실패 | Nginx /server proxy | Network 탭 CORS |
| Cesium is not defined | 포털 CDN 로드 | webpack externals |
| Entity 잔존 | CustomDataSource cleanup | onCloseMicroApp() |
| 지하 데이터 없음 | EPSG:5186→4326 변환 | queryCrossList 응답 |
| SHP 라인 선택 오류 | MultiLineString lineIndex | features.length |
| 보고서 upperList 빈값 | Promise.all 미적용 | API 응답 timing |
| 레이어 404 | portal . URL prefix | Capabilities XML |
| 404 static | nginx root path | dist COPY 경로 |
콘솔 필터
[종/횡단 분석]
Tags: #Troubleshooting #Cesium #MicroFrontend #CORS #GIS #DevOps
'TroubleShooting' 카테고리의 다른 글
| [TroubleShooting] 레이스 컨디션 버그를 해결한 경험 (0) | 2026.07.07 |
|---|---|
| [TroubleShooting] 번들 사이즈 30% 줄인 과정 (0) | 2026.07.07 |
| [TroubleShooting] 백엔드 트러블슈팅 (1) | 2026.07.05 |
| [TroubleShooting] Gaussian Splatting 이슈 & 트러블슈팅 (0) | 2026.07.03 |
| [TroubleShooting] ml-sharp(feat. Apple) (0) | 2026.07.03 |