PLANNING v2.0: 자동전시시스템 재정의, 6역할 웹/모바일 분리, 도메인 M10~M18(옥션 M15·관람객 M10·마케팅/공개사이트 M12·BI M16(운영사 ROI 포함)·CMS M17·관리자 M18), WISE/UIWS 공통·시스템관리 레이어 §5B(2FA OTP), 옥션/견적서/역경매, M2/M3 3안 생성→선택/병합. design.md v1.1(Stitch 정합·SCR 매핑). 하네스 재구성: 전문 에이전트 17종(아키텍트 AA·SA·TA·DA·NA / 공통 common-dev / 코어 backend·frontend·db / 도메인 bidding·visitor·cms·bi·admin / AI ai-dev·visualizer / QA·devops) + kintex-impl-orchestrator v2.0(Phase A~E) + IMPLEMENTATION_BACKLOG v2.0 + CLAUDE.md. 백엔드 스캐폴드: Spring Boot 3.2.5(com.zioinfo.kintex) + JWT/RBAC + WebSocket + 버전형 룰엔진 + M2~M5 컨트롤러(M3 사전검증·M4 견적·M5 RenderJob 실구현, 공간경로 501 대기). compileJava SUCCESS. 자산: 평면도 JPG 15장 + 매니페스트(R4 트렌치·CAD 해소). CAD 93MB·build는 gitignore. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
17 KiB
킨텍스 AI 전시관리 — P0 백엔드 API 계약 (D-3)
작성: kintex-backend-dev · 근거:
docs/PLANNING.mdv1.2(§5 M2~M5·§7 ERD·§8 아키텍처)·docs/design.mdv1.0(SCR-01~12)·docs/IMPLEMENTATION_BACKLOG.md(S-1·D-3·C-1) 스택: Spring Boot 3.x(Java 17) + MyBatis + PostgreSQL(PostGIS) + Redis + WebSocket(STOMP). 패키지com.zioinfo.kintex. 이 문서는 frontend·db-engineer·qa 대조용 단일 계약이다. 스키마 필요 매퍼는 §8에 목록화(db-engineer 인수).
0. 공통 규약
0-1. 응답 봉투 (ApiResponse<T>)
{ "success": true, "data": { ... }, "error": null }
{ "success": false, "data": null, "error": { "code": "FORBIDDEN", "message": "이 행사/부스에 대한 권한이 없습니다." } }
- 목록은
PageResponse<T>={ "items": [...], "page": 0, "size": 20, "total": 123 }(현재 P0에서 목록은 배열 직접 반환도 허용 — 갤러리/워크스페이스).
0-2. 오류 코드 → HTTP
| code | HTTP | 의미 |
|---|---|---|
VALIDATION |
400 | 요청 값 오류(필드 메시지 포함) |
UNAUTHORIZED |
401 | 미인증/토큰 만료 |
FORBIDDEN |
403 | 행사/부스 권한 없음 |
NOT_FOUND |
404 | 대상 없음 |
CONFLICT |
409 | 상태 충돌(낙관적 잠금 등) |
COMPLIANCE_BLOCKED |
422 | 규정 위반(차단) |
RENDER_QUOTA_EXCEEDED |
429 | 행사 이미지 생성 쿼터 소진 |
NOT_REGISTERED_COMPANY |
403 | 미등록 장치업체 초대 차단 |
NOT_IMPLEMENTED |
501 | 매퍼/엔진 구현 대기(스켈레톤) |
INTERNAL |
500 | 서버 오류(요약만) |
0-3. 보안 불변 (계약 강제)
- 스택트레이스·내부 세부 미노출 — 응답
error.message는 사람이 읽을 요약만. 상세는 서버 로그. - ServerOut류 민감정보(IP·SSH·비밀번호·os_pw_enc·비밀번호 해시·내부 식별자) 응답 완전 제외. 사용자/업체 표시는 이름·역할·번호 등 비민감 필드만.
GEMINI_API_KEY는 백엔드에서 다루지 않는다(나노바나나 Python 워커 전용). M5는 큐 발행까지만.- AI 생성 이미지는 응답에
watermarkRequired:true+watermarkText+notice(계약·심사 서류 사용 금지)를 항상 포함(제거 불가 — PLANNING §6-5).
0-4. 인증 헤더
Authorization: Bearer <JWT>(HS256). JWT는sub(userId)·name·roles(eventId→역할)·hm(홀매니저) 클레임.- 공개 경로(인증 불필요):
GET /health,POST /api/auth/login,/ws/**,POST /api/internal/render/callback(워커 토큰 인증).
0-5. 행사 단위 RBAC (역할)
ORGANIZER(주최자·owner) · EXHIBITOR(참가업체·부스 멤버) · CONTRACTOR(장치업체) · HALL_MANAGER(킨텍스 내부·전 행사 열람+승인).
- 모든 도메인 경로는
{eventId}스코프. 가드: 열람=행사 멤버 or 홀매니저 / 편집·액션=역할별(각 엔드포인트 명시).
1. 헬스체크
GET /health — 공개
{ "success": true, "data": { "status": "UP", "service": "kintex-backend", "time": "2026-07-11T…" }, "error": null }
2. 인증·워크스페이스 (C-1 / SCR-01)
POST /api/auth/login — 공개
요청:
{ "email": "pm@expo.co.kr", "password": "••••••" }
응답 LoginResponse:
{
"accessToken": "<jwt>",
"expiresInSeconds": 3600,
"user": { "userId": "u-1", "displayName": "김주최", "hallManager": false },
"workspaces": [
{ "eventId": "e-2026-smf", "eventName": "2026 스마트팩토리 코리아",
"startDate": "2026-08-11", "endDate": "2026-08-14",
"hallLabel": "제2전시장 홀7", "myRole": "ORGANIZER", "dday": 31 }
]
}
- 실패:
UNAUTHORIZED(이메일/비밀번호 불일치, 메시지 일반화). 비밀번호 해시 등 미노출.
GET /api/auth/workspaces — 인증
응답: WorkspaceDto[] (위 workspaces 배열과 동일 shape).
GET /api/auth/me — 인증
응답 KintexPrincipal: { "userId","displayName","eventRoles":{"e-…":"ORGANIZER"},"hallManager":false }.
POST /api/auth/accept-invite — 인증
요청:
{ "inviteCode": "INV-8F2K", "companyRegistrationNo": "123-45-67890" }
- 장치업체(CONTRACTOR) 초대 수락 시
companyRegistrationNo로 킨텍스 등록업체 검증 — 미등록이면NOT_REGISTERED_COMPANY(403). - 성공: 역할이 추가된 새
LoginResponse(신규 토큰 포함) 반환.
스켈레톤 현황:
login·workspaces·accept-invite는UserMapper(§8) 대기로 501. JWT 발급/검증·RBAC 가드·/me는 완성.
3. M2 플로어플랜 스튜디오 (SCR-03/04) — P0
베이스: /api/events/{eventId}/halls/{hallId}/layout
GET …/layout?version={n} — 열람(멤버/홀매니저)
응답 LayoutDto:
{
"layoutId": "lay-1", "eventId": "e-…", "hallId": "H7", "version": 3,
"name": "배치안 B", "status": "draft",
"booths": [
{ "boothId": "b-102", "boothNo": "A-102", "type": "independent",
"polygon": [[0,0],[6,0],[6,3],[0,3],[0,0]], "sizeM": [6,3],
"heightM": 4.2, "floorLoadTPerM2": 3.0, "assignedCompanyName": "(주)한빛로보틱스", "premium": false }
],
"summary": { "boothCount": 486, "targetBoothCount": 510, "salesAreaM2": 4374.0,
"minAisleWidthM": 3.2, "violationBlock": 0, "violationWarn": 3 },
"updatedAt": "2026-07-11T…"
}
polygon: 홀 로컬 좌표계(미터), 닫힌 링. 서버가 PostGIS polygon으로 저장/검증.
PUT …/layout — 편집(ORGANIZER)
요청 LayoutSaveRequest: { "name":"배치안 B", "version":3, "booths":[ BoothDto… ] } (booths 비어있지 않음).
응답: 저장된 LayoutDto(저장 시 규정 검증이 summary에 반영).
POST …/layout/validate?version={n} — 규정검증(ORGANIZER/HALL_MANAGER)
응답 ComplianceReport:
{
"rulesetVersion": "compliance-v1.0",
"disclaimer": "본 룰셋은 사전 필터이며 최종 승인은 킨텍스 및 구조기술사의 판단에 따른다.",
"blockCount": 2, "warnCount": 3, "passCount": 6, "submittable": false,
"violations": [
{ "pin": 1, "code": "AISLE_WIDTH_MIN", "group": "egress",
"label": "피난 통로 폭 최소 3m", "severity": "block", "measured": "측정 2.8m" }
]
}
submittable=false(차단 존재) → 프론트 제출 비활성(SCR-03/09).pin은 도면 하이라이트 번호와 1:1.- 검증 항목(M2):
AISLE_WIDTH_MIN(통로 폭·block)·EXIT_ACCESS(비상구·block)·FLOOR_LOAD(홀별 하중·block)·HEIGHT_MAX(block)·MEZZANINE_RATIO(block)·CLEARANCE_WALL(warn).
POST …/layout/auto-generate — AI 자동배치(ORGANIZER)
요청 AutoLayoutRequest:
{ "targetBoothCount":510, "premiumRatio":0.15, "stageCount":1, "loungeCount":1, "mainEntranceCount":2, "optionCount":3 }
응답 AutoLayoutOption[]:
[ { "optionId":"opt-A", "label":"배치안 A",
"summary": { "boothCount":510, "targetBoothCount":510, "salesAreaM2":4420.0, "minAisleWidthM":3.2, "violationBlock":0, "violationWarn":0 },
"s7RenderJobId":"job-…" } ]
s7RenderJobId: S7 홀 전경(조감) 생성 잡 — 완료 시 WebSocket/topic/render/{jobId}푸시로 SCR-04 카드 이미지 교체.
스켈레톤 현황:
getLayout·saveLayout·validate·autoGenerate는BoothMapper(§8, PostGIS) 대기로 501.ComplianceRuleEngine(규정 평가기)·룰셋 로딩은 완성.
4. M3 부스 설계 스튜디오 (SCR-06/09) — P0
베이스: /api/events/{eventId}/booths/{boothId}/design
GET …/design?version={n} — 열람(멤버/홀매니저)
응답 DesignPlanDto:
{ "designId":"d-1","boothId":"b-102","version":2,"status":"draft",
"spec": DesignSpec, "updatedAt":"2026-07-11T…" }
PUT …/design — 편집(EXHIBITOR/CONTRACTOR)
요청 DesignSaveRequest: { "version":2, "spec": DesignSpec }. 응답: DesignPlanDto.
POST …/design/precheck — 규정 사전검증(EXHIBITOR/CONTRACTOR/HALL_MANAGER) ✅완성
요청 DesignSpec:
{
"boothType": "independent", "industry": "로봇/제조", "budget": 30000000,
"zones": [ { "type":"demo","ratioPercent":40 }, { "type":"consult","ratioPercent":30 } ],
"wallHeightM": 4.2, "signageText": "한빛로보틱스",
"rigging": { "use": true, "heightM": 7.0 },
"mezzanineAreaRatio": 0.3,
"materials": [ { "part":"wall","finish":"matte_white","fireRetardant": true } ],
"lightingMode": "night", "usesDesignatedLightingOnly": true
}
응답: ComplianceReport(§3-validate와 동일 shape). M3 평가 항목: HEIGHT_MAX(5m·block)·RIGGING_RANGE(6.5~8.5m·warn·requiresDocument:"STRUCTURAL_CALC_D7")·MEZZANINE_RATIO(block)·FIRE_RETARDANT(block)·CLEARANCE_CEILING(warn)·LIGHTING_BRING_IN(warn).
wallHeightM > 5→HEIGHT_MAX차단,measured:"측정 5.4m".- 리깅 사용 시 D-7 구조계산서 부속 플래그(
requiresDocument)를 SCR-09 칩으로 표시.
스켈레톤 현황:
precheck완성(스펙→룰 엔진 즉시 평가).getDesign·saveDesign은DesignMapper(§8) 대기로 501.
5. M4 유틸리티 설계 (SCR-07/08) — P0
베이스: /api/events/{eventId}/booths/{boothId}/utility
POST …/utility/quote — 자동 견적(멤버) ✅완성
요청 UtilityQuoteRequest:
{
"devices": [ { "name":"로봇 시연장비","count":2,"powerKw":1.5 },
{ "name":"LED 스포트","count":6,"powerKw":0.3 } ],
"networkWiredLines": 1, "plumbingOutlets": 1, "compressedAirOutlets": 0
}
응답 UtilityQuote:
{
"totalPowerKw": 4.8, "requestedKw": 5, "distributionBox50A": 1,
"lines": [
{ "label":"전기 220V 단상 5kW","qty":5,"unitPrice":55000,"amount":275000 },
{ "label":"분전반 50A 추가","qty":1,"unitPrice":100000,"amount":100000 },
{ "label":"인터넷 유선 회선","qty":1,"unitPrice":150000,"amount":150000 },
{ "label":"급배수 구","qty":1,"unitPrice":150000,"amount":150000 }
],
"total": 675000, "currency":"KRW", "rulesetVersion":"rates-v1.0",
"disclaimer":"공시가 기준이며 최종 금액은 킨텍스 확정 시 안내됩니다."
}
- 산식:
requestedKw = ceil(Σ count×powerKw),distributionBox50A = ceil(requestedKw / 11). 단가는 요율 룰셋(rates-v1.0).
POST …/utility/wiring?hallId={h} — 배선 산출(멤버)
요청 WiringRequest:
{ "terminals": [ { "kind":"power","position":[3.0,1.5],"kw":5 },
{ "kind":"network","position":[1.0,2.0] } ] }
응답 WiringResult:
{ "assumedTrench": true,
"paths": [ { "kind":"power","color":"red","coords":[[1,1],[3,1.5]],"lengthM":2.06,"label":"5kW" },
{ "kind":"network","color":"blue","coords":[[0,2],[1,2]],"lengthM":1.0,"label":"유선 1회선" } ] }
assumedTrench=true→ SCR-07 "가정 트렌치 좌표(실측 대기)" 배지(PLANNING R4). 색상 규약: power=red·network=blue·plumbing/air=green(S6 동일).
POST …/utility/order — 신청 제출(EXHIBITOR/CONTRACTOR)
요청 UtilityQuoteRequest(견적과 동일). 응답 UtilityOrderDto:
{ "orderId":"uo-…","status":"submitted","quote": UtilityQuote,
"locationDiagramUrl":"/files/…/location-diagram.pdf",
"supplyTiming":"장치 마지막 날 오후",
"deadlineNotice":"유틸리티 신청 마감 D-25 · 인터넷은 현장 추가신청 불가",
"relayNotice":"주최자 사무국/킨텍스에 제출 파일이 릴레이됩니다." }
locationDiagramUrl: 자동 생성 위치표시도(수기 작도 대체 서식, M4-3). 마감·릴레이 고지 포함(PLANNING R3).
GET …/utility — 신청 조회(멤버). 응답: UtilityOrderDto.
스켈레톤 현황:
quote완성(요율 룰셋 산술).computeWiring(WiringMapperPostGIS)·submitOrder(위치표시도 PDF·영속)·getOrder는 501.
6. M5 나노바나나 시각화 — RenderJob (SCR-06/12) — P0 · G1 게이트
★ Gemini 외부 호출은 소유자 승인(G1) 대상. 백엔드는 큐 발행·상태·콜백까지만(실호출은 Python 워커). 미승인 시에도 큐잉/상태는 동작.
POST /api/events/{eventId}/booths/{boothId}/render — 발행(멤버) ✅완성(큐잉)
요청 RenderJobRequest:
{ "shotPreset":"S1",
"scene": { "hall":{"id":"H7","dims_m":[126,90],"ceiling_m":12},
"booth":{"id":"A-102","size_m":[6,3],"type":"independent"},
"design":{"signage":{"text":"한빛로보틱스"}},
"lighting":{"mode":"day"} },
"referenceImageUrl":"/files/empty_booth.jpg" }
응답 RenderJobDto(QUEUED):
{ "jobId":"<uuid>","boothId":"b-102","shotPreset":"S1","status":"QUEUED",
"imageUrl":null,"schemaHash":null,"modelVersion":null,
"watermarkRequired": true,
"watermarkText":"AI 생성 예상 이미지 — 실제 시공 결과와 다를 수 있습니다",
"notice":"AI 생성 이미지는 계약·심사 서류에 사용할 수 없습니다 — 시공 기준은 도면입니다",
"errorMessage":null,"createdAt":"2026-07-11T…" }
- 쿼터 초과 시
RENDER_QUOTA_EXCEEDED(429).scene은 PLANNING §6-2 스키마 그대로 Redis 큐(kintex:renderjob:queue)에 적재 → 워커 소비. - S6(배선 오버레이)는 워커의 백엔드 래스터 합성 경로(생성형 아님).
GET /api/events/{eventId}/render-jobs/{jobId} — 상태(멤버) ✅완성
응답: RenderJobDto(status QUEUED|RUNNING|DONE|FAILED). DONE 시 imageUrl 채워짐. 없으면 NOT_FOUND.
GET /api/events/{eventId}/booths/{boothId}/render-jobs?shot={S1} — 갤러리 목록(멤버)
응답: RenderJobDto[](SCR-12). > 스켈레톤: RenderJobMapper(내구 이력) 대기로 501.
POST /api/internal/render/callback — 워커 콜백(내부·공유 시크릿) ✅완성
헤더: X-Worker-Token: <RENDER_WORKER_TOKEN> (env). 요청 WorkerCallbackRequest:
{ "jobId":"<uuid>","status":"DONE","imageUrl":"/files/…/S1.png",
"schemaHash":"…","modelVersion":"gemini-3.1-flash-image-preview","errorMessage":null }
- 처리: 상태 갱신 → WebSocket
/topic/render/{jobId}로RenderJobDto푸시. 성공 시에만 쿼터 차감(내구화는 매퍼). - 실패(
status:"FAILED") 시errorMessage는 요약만 통과(스택트레이스 유입 차단).
WebSocket (STOMP)
- 핸드셰이크:
GET /ws(SockJS). 브로드캐스트 prefix/topic, 클라→서버/app. - 구독:
/topic/render/{jobId}→ RenderJob 완료·실패 이벤트(design.md §1-4). (승인 이벤트 토픽은 M6/C-4에서 확장.)
7. 룰셋(버전 관리 데이터) 계약
- 규정
rulesets/compliance-v1.json(compliance-v1.0): 코드가 아닌 데이터. 개정 시 파일 교체. 리포트에rulesetVersion·disclaimer항상 기록(감사·면책, PLANNING R2). - 요율
rulesets/rates-v1.json(rates-v1.0): 임대·유틸리티 단가.quote응답의rulesetVersion근거. - 연산자:
lte·gte·between·isTrue·eq·lteHall(홀별 상한)·excludesAll(금지목록). 측정값 없는 규칙은 미평가(리포트 카운트 제외).
8. DB 매퍼 인수 목록 (→ kintex-db-engineer)
아래 매퍼 인터페이스는 정의·주입 완료, XML(PostGIS ST_*) 미구현 → 해당 API 501.
resources/mybatis/mapper/에 구현.
| 매퍼 | 메서드 | 공간/쿼리 요지 |
|---|---|---|
auth.mapper.UserMapper |
findAuthByEmail, findEventRoles |
사용자 인증행(해시 응답 제외)·행사별 역할. login/workspaces/accept-invite |
module.m2.mapper.BoothMapper |
findLayout, findBooths, upsertLayout, replaceBooths, sumSalesArea, minAisleWidth, countExitsBlocked |
부스 POLYGON(ST_MakePolygon/ST_AsGeoJSON), 판매면적 ST_Area, 통로폭 ST_Distance/ST_Buffer, 비상구 ST_Intersects |
module.m3.mapper.DesignMapper |
findDesign, upsertDesign, findEventIdByBooth |
DesignPlan 버전(spec jsonb), 부스→행사 역참조(RBAC) |
module.m4.mapper.WiringMapper |
findNearestTrenches, shortestPath, isAssumedTrench |
트렌치 POINT KNN(<->), 배선 LINESTRING 최단(ST_Length), 가정 그리드 플래그 |
module.m5.mapper.RenderJobMapper |
insertJob, updateStatus, findByBooth, countSucceededByEvent |
RenderJob 내구 이력·쿼터 정본(Redis는 큐/실시간) |
필요 스키마(D-2 참조): Event · User · EventMember · Hall · Trench · Booth(polygon) · Layout · DesignPlan · UtilityOrder(wiring LineString) · RenderJob · Company(등록업체). 홀 마스터·요율·규정 룰셋 시드는 D-2에서.
9. 변경 이력
| 버전 | 일자 | 내용 |
|---|---|---|
| v0.1 | 2026-07-11 | 최초 — 스캐폴드(S-1)와 함께 P0(M2·M3·M4·M5)+인증(C-1) 계약 정의. precheck·quote·render 큐잉·룰엔진 완성, 공간·영속 경로는 매퍼 대기(501). |