kintex/_workspace/01_backend_contracts.md
zio 9987d958db feat(v2.0): 자동전시시스템 하네스 재구성 + PLANNING v2.0 + 백엔드 스캐폴드 + 평면도 자산
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>
2026-07-11 17:32:40 +09:00

17 KiB
Raw Blame History

킨텍스 AI 전시관리 — P0 백엔드 API 계약 (D-3)

작성: kintex-backend-dev · 근거: docs/PLANNING.md v1.2(§5 M2~M5·§7 ERD·§8 아키텍처)·docs/design.md v1.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-inviteUserMapper(§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·autoGenerateBoothMapper(§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 > 5HEIGHT_MAX 차단, measured:"측정 5.4m".
  • 리깅 사용 시 D-7 구조계산서 부속 플래그(requiresDocument)를 SCR-09 칩으로 표시.

스켈레톤 현황: precheck 완성(스펙→룰 엔진 즉시 평가). getDesign·saveDesignDesignMapper(§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(WiringMapper PostGIS)·submitOrder(위치표시도 PDF·영속)·getOrder501.


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).