docs(i6): UAT closeout, architecture review, Runbook internal token

Made-with: Cursor
This commit is contained in:
2026-04-06 22:46:31 +08:00
parent 841bd3e0bd
commit 78433faa89
5 changed files with 334 additions and 108 deletions
+138 -107
View File
@@ -2,21 +2,23 @@
> **仓库**`craftlabs-authorization-sdk`(分支 `develop`)。
> **角色**:解决方案架构设计稿;**不**在本任务中落地代码。
> **实现锚点**(与现有模式一致):`delivery-platform-api` 使用 Flyway `V5__…` 起(当前末版为 `V4__delivery_batch_and_license_sn.sql`)、`AuditService` + `AuditEntityTypes` / `AuditActions`、`ApiExceptionHandler`、公开业务 API 经 `SecurityConfig` + `JwtAuthenticationFilter``Authorization: Bearer`);OpenAPI SSOT 为 [`contracts/openapi/delivery-platform-api.json`](../../../contracts/openapi/delivery-platform-api.json) 与 `OpenApiContractSnapshotTest`。
> **Webhook 工程名**:本工作区已实现为 `license-webhook-ingress`[`services/README.md`](../../../services/README.md)`webhook_callback_receipt`、`Idempotency-Key`)。
> **实现锚点**(与现有模式一致):`delivery-platform-api` 使用 Flyway `V5__…` 起(当前末版为 `V4__delivery_batch_and_license_sn.sql`)、`AuditService` + `AuditEntityTypes` / `AuditActions`、`ApiExceptionHandler`、公开业务 API 经 `SecurityConfig` + `JwtAuthenticationFilter``Authorization: Bearer`);OpenAPI SSOT 为 `[contracts/openapi/delivery-platform-api.json](../../../contracts/openapi/delivery-platform-api.json)` 与 `OpenApiContractSnapshotTest`。
> **Webhook 工程名**:本工作区已实现为 `license-webhook-ingress``[services/README.md](../../../services/README.md)``webhook_callback_receipt`、`Idempotency-Key`)。
---
## 0. 上游文档走查(按路径引用,不全文摘录)
| 文档 | 与本设计的关系(摘要) |
|------|------------------------|
| [docs/engineering/PARALLEL_ITERATION_INDEX.md](../PARALLEL_ITERATION_INDEX.md) | 定义三轨并行;**I5** 为 **Callback + Schema** 硬耦合周;**I6** 为 **UAT** 与 SDK 冻结;同步点含 **I5 起**Callback payload / inbox DTO、**Idempotency-Key**、Webhook→平台投递方式;**I6**UAT 场景、`VITE_API_BASE`、两枚 Fat JAR 与 SDK 版本。 |
| 文档 | 与本设计的关系(摘要) |
| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [docs/engineering/PARALLEL_ITERATION_INDEX.md](../PARALLEL_ITERATION_INDEX.md) | 定义三轨并行;**I5** 为 **Callback + Schema** 硬耦合周;**I6** 为 **UAT** 与 SDK 冻结;同步点含 **I5 起**Callback payload / inbox DTO、**Idempotency-Key**、Webhook→平台投递方式;**I6**UAT 场景、`VITE_API_BASE`、两枚 Fat JAR 与 SDK 版本。 |
| [docs/engineering/tracks/01-backend-platform-webhook.md](../tracks/01-backend-platform-webhook.md) | **I5 DoD**E2E「模拟 Callback → 平台 DB 一条 Inbox」;**§3 Webhook↔平台**`schemaVersion` / `X-Event-Schema-Version`、幂等 `(source_system, external_message_id)``POST /internal/v1/callback-events` 或 MQ、对比特 **2xx** 须在持久化或可靠入队**之后**。 |
| [docs/engineering/tracks/02-frontend-platform-ui.md](../tracks/02-frontend-platform-ui.md) | **I5 路由**`/callbacks``/integration/environments``product-lines`(本文统一为 `/integration/product-lines`);组件:`CallbackInboxTable``CallbackPayloadViewer`(脱敏);与 Webhook 联调或 staging。 |
| [docs/engineering/tracks/03-client-sdk.md](../tracks/03-client-sdk.md) | **I5****Schema + `AuthConfigs` + examples** 与 **BP-10** 同步为硬交付;**I6**:冻结 SDK 版本、CHANGELOG、**BitAnswer 兼容矩阵**UAT 周禁止 MAJOR Schema。 |
| [docs/chuangfei-platform-product-modules.md](../../chuangfei-platform-product-modules.md) §67 | **M5 P0**:收件箱列表/详情/处理状态/关联失败兜底/事件类型字典(M5-F01~F05 等);**M6 P0**:产品线(M6-F01)、环境维度与 `bitanswer.url`M6-F02);P1 如比特 ID 映射、JSON 模板、发布记录等 **I5 MVP 可裁减**。 |
| [docs/chuangfei-platform-bpm-and-roadmap.md](../../chuangfei-platform-bpm-and-roadmap.md) | **BP-06**:Webhook 验签 → 落库 → 解析关联 → Ops 处置;重复投递幂等。**BP-10**:M6 与 Schema、客户端配置发布链路;**依赖**本仓 `schemas/craftlabs-auth-config.schema.json`。 |
| [docs/engineering/tracks/02-frontend-platform-ui.md](../tracks/02-frontend-platform-ui.md) | **I5 路由**`/callbacks``/integration/environments``product-lines`(本文统一为 `/integration/product-lines`);组件:`CallbackInboxTable``CallbackPayloadViewer`(脱敏);与 Webhook 联调或 staging。 |
| [docs/engineering/tracks/03-client-sdk.md](../tracks/03-client-sdk.md) | **I5****Schema + `AuthConfigs` + examples** 与 **BP-10** 同步为硬交付;**I6**:冻结 SDK 版本、CHANGELOG、**BitAnswer 兼容矩阵**UAT 周禁止 MAJOR Schema。 |
| [docs/chuangfei-platform-product-modules.md](../../chuangfei-platform-product-modules.md) §67 | **M5 P0**:收件箱列表/详情/处理状态/关联失败兜底/事件类型字典(M5-F01~F05 等);**M6 P0**:产品线(M6-F01)、环境维度与 `bitanswer.url`M6-F02);P1 如比特 ID 映射、JSON 模板、发布记录等 **I5 MVP 可裁减** |
| [docs/chuangfei-platform-bpm-and-roadmap.md](../../chuangfei-platform-bpm-and-roadmap.md) | **BP-06**:Webhook 验签 → 落库 → 解析关联 → Ops 处置;重复投递幂等。**BP-10**:M6 与 Schema、客户端配置发布链路;**依赖**本仓 `schemas/craftlabs-auth-config.schema.json` |
---
@@ -24,79 +26,91 @@
### A.1 问题与目标结果
| 维度 | 说明 |
|------|------|
| **业务问题** | 比特规则 **HTTPS Callback****不断链、可审计、可运营处置**;平台侧需统一收件箱,避免只在 Webhook 边缘落库不可见。 |
| **I5 目标结果(E2E** | **模拟或真实 Callback**`license-webhook-ingress` →(持久化收据后)**投递** → `delivery-platform-api` **Inbox 表出现一行**;运营账号用 JWT 在 UI **列表可见、详情可打开** |
| **幂等** | 与轨道 A 一致:**`Idempotency-Key`**HTTP 头)+ 比特稳定 **`message_id`**(或等价字段);DB **唯一约束 `(source_system, external_message_id)`**;重复请求 **不重复插入**、返回与首次一致的接受语义(HTTP 200 + 相同 `inboxId` 或约定 DTO)。 |
| **schemaVersion** | 事件体或头携带 **`schemaVersion`**(与轨道 A 的 `X-Event-Schema-Version` 二选一或并存,**须写 ADR 定一种主口径**);平台拒绝无法识别的 major 版本时返回 **4xx** 并记录可观测字段,避免静默损坏。 |
| 维度 | 说明 |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **业务问题** | 比特规则 **HTTPS Callback****不断链、可审计、可运营处置**;平台侧需统一收件箱,避免只在 Webhook 边缘落库不可见。 |
| **I5 目标结果(E2E** | **模拟或真实 Callback**`license-webhook-ingress` →(持久化收据后)**投递** → `delivery-platform-api` **Inbox 表出现一行**;运营账号用 JWT 在 UI **列表可见、详情可打开** |
| **幂等** | 与轨道 A 一致:`**Idempotency-Key`**HTTP 头)+ 比特稳定 `**message_id**`(或等价字段);DB **唯一约束 `(source_system, external_message_id)`**;重复请求 **不重复插入**、返回与首次一致的接受语义(HTTP 200 + 相同 `inboxId` 或约定 DTO。 |
| **schemaVersion** | 事件体或头携带 `**schemaVersion`**(与轨道 A 的 `X-Event-Schema-Version` 二选一或并存,**须写 ADR 定一种主口径**);平台拒绝无法识别的 major 版本时返回 **4xx** 并记录可观测字段,避免静默损坏。 |
### A.2 数据模型(`delivery-platform-api`PostgreSQL + Flyway
命名与现有表一致采用 **`platform_*` 前缀**(见 `V1``V4` 迁移)。
命名与现有表一致采用 `**platform_*` 前缀**(见 `V1``V4` 迁移)。
#### A.2.1 `platform_callback_inbox`M5 Inbox P0
| 列(示例) | 类型/说明 |
|------------|-----------|
| `id` | UUID / BIGSERIAL PK |
| `source_system` | VARCHAR,如 `BITANSWER` |
| `external_message_id` | VARCHAR,比特侧稳定消息 ID |
| **唯一约束** | `UNIQUE (source_system, external_message_id)` |
| `schema_version` | VARCHAR,与 payload 解析版本对齐 |
| `event_type` | VARCHAR,与 M5-F05 字典一致(如 `sn:pre_activate` |
| `status` | ENUM/VARCHAR**`PENDING` / `PROCESSED` / `FAILED` / `IGNORED`**(对应产品「待处理、已处理、失败、忽略」) |
| `raw_payload` | **JSONB**(或 TEXT + 大小上限);UI **脱敏展示** |
| `idempotency_key` | VARCHAR NULL,审计与排障 |
| **关联(均可 NULL,支撑 M5-F04** | `license_sn_id``platform_license_sn``contract_id` / `project_id`(若已有表);解析字段如 `sn_code``mid` 等冗余列便于列表筛选 |
| `product_line_id` / `integration_environment_id` | FK → M6 最小表(可选,便于按产品线/环境筛选 |
| `received_at` | 平台收件时间 |
| `processed_at` / `processed_by_user_id` | 运营处置 |
| `failure_reason` / `operator_note` | 文本,P1 可扩展「失败原因分类」字典 |
| 标准审计 | 与现有实体一致可补充 `created_at`/`updated_at`;关键状态迁移建议走 **`AuditService`**(扩展 `AuditEntityTypes` / `AuditActions` |
| 列(示例) | 类型/说明 |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| `id` | UUID / BIGSERIAL PK |
| `source_system` | VARCHAR,如 `BITANSWER` |
| `external_message_id` | VARCHAR,比特侧稳定消息 ID |
| **唯一约束** | `UNIQUE (source_system, external_message_id)` |
| `schema_version` | VARCHAR,与 payload 解析版本对齐 |
| `event_type` | VARCHAR,与 M5-F05 字典一致(如 `sn:pre_activate` |
| `status` | ENUM/VARCHAR`**PENDING` / `PROCESSED` / `FAILED` / `IGNORED`**(对应产品「待处理、已处理、失败、忽略」) |
| `raw_payload` | **JSONB**(或 TEXT + 大小上限);UI **脱敏展示** |
| `idempotency_key` | VARCHAR NULL,审计与排障 |
| **关联(均可 NULL,支撑 M5-F04** | `license_sn_id` `platform_license_sn``contract_id` / `project_id`(若已有表);解析字段如 `sn_code``mid` 等冗余列便于列表筛选 |
| `product_line_id` / `integration_environment_id` | FK → M6 最小表(可选,便于按产品线/环境筛选) |
| `received_at` | 平台收件时间 |
| `processed_at` / `processed_by_user_id` | 运营处置 |
| `failure_reason` / `operator_note` | 文本,P1 可扩展「失败原因分类」字典 |
| 标准审计 | 与现有实体一致可补充 `created_at`/`updated_at`;关键状态迁移建议走 `**AuditService`**(扩展 `AuditEntityTypes` / `AuditActions` |
#### A.2.2 M6 最小只读支撑表
仅包含 **I5 UI 与 Inbox 筛选** 所需字段;**不做** M6-F03F06 全量。
| 表 | P0 字段(示例) |
|----|-----------------|
| **`platform_product_line`** | `id``code`(唯一)、`name``description` NULL、启用标志 |
| **`platform_integration_environment`** | `id``code`(唯一)、`name``bitanswer_base_url`(对应 M6-F02)、`kind`DEV/TEST/STAGING/PROD 等枚举)、可选 `product_line_id` 或后续多对多(MVP 可 **单列 FK** 简化) |
| 表 | P0 字段(示例) |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `**platform_product_line`** | `id``code`(唯一)、`name``description` NULL、启用标志 |
| `**platform_integration_environment**` | `id``code`(唯一)、`name``bitanswer_base_url`(对应 M6-F02)、`kind`DEV/TEST/STAGING/PROD 等枚举)、可选 `product_line_id` 或后续多对多(MVP 可 **单列 FK** 简化) |
**MVP 裁减**:比特产品/模版/业务 ID 映射(M6-F03)、特征映射(M6-F04)、JSON 模板与发布记录(M6-F05/F06**推迟**至 V1.1 或 Mid,除非比特联调硬依赖。
### A.3 公开 REST APIJWT`/api/v1`
与现有 Controller 风格一致;**RBAC**:与当前演示一致 **`SYS_ADMIN` / `DEVELOPER`** 均可访问 MVP 接口(与 [`AuthController`](../../../services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/auth/AuthController.java) 对齐),**I7+** 再收紧为 Ops 专用权限码。
与现有 Controller 风格一致;**RBAC**:与当前演示一致 `**SYS_ADMIN` / `DEVELOPER`** 均可访问 MVP 接口(与 `[AuthController](../../../services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/auth/AuthController.java)` 对齐),**I7+** 再收紧为 Ops 专用权限码。
| 方法 | 路径 | 说明 |
| ------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `GET` | `/api/v1/callback-inbox` | 分页;查询建议:`status``eventType``snCode``projectId``productLineId``environmentId``from`/`to``receivedAt`)、`page``size` |
| `GET` | `/api/v1/callback-inbox/{id}` | 详情;含 `rawPayload`(或单独 `GET .../payload` 若需权限分级) |
| `PATCH` | `/api/v1/callback-inbox/{id}/status` | 运营状态迁移:`**PENDING``PROCESSED` / `FAILED` / `IGNORED`**;非法迁移 **409** + 业务错误码(与合同/交付模式一致,经 `**ApiExceptionHandler`** |
| `PATCH` | `/api/v1/callback-inbox/{id}/link`(可选) | 人工挂接:`licenseSnId` / `projectId` / `contractId` 等,支撑 M5-F04 |
| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/v1/callback-inbox` | 分页;查询建议:`status``eventType``snCode``projectId``productLineId``environmentId``from`/`to``receivedAt`)、`page``size` |
| `GET` | `/api/v1/callback-inbox/{id}` | 详情;含 `rawPayload`(或单独 `GET .../payload` 若需权限分级) |
| `PATCH` | `/api/v1/callback-inbox/{id}/status` | 运营状态迁移:**`PENDING``PROCESSED` / `FAILED` / `IGNORED`**;非法迁移 **409** + 业务错误码(与合同/交付模式一致,经 **`ApiExceptionHandler`** |
| `PATCH` | `/api/v1/callback-inbox/{id}/link`(可选) | 人工挂接:`licenseSnId` / `projectId` / `contractId` 等,支撑 M5-F04 |
**可选**`POST /api/v1/callback-inbox/simulate`**仅非生产**,对应 M5-F10 P2 — **I5 若排期紧可不做**,改用 curl → Webhook → 平台链)。
**M6 只读**
| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/v1/integration/environments` | 列表/分页 |
| `GET` | `/api/v1/integration/product-lines` | 列表/分页 |
| 方法 | 路径 | 说明 |
| ----- | ---------------------------------------------------------------- | ------ |
| `GET` | `/api/v1/integration/environments` | 列表/分页 |
| `GET` | `/api/v1/integration/product-lines` | 列表/分页 |
| `GET` | `/api/v1/integration/environments/{id}``.../product-lines/{id}` | 详情(按需) |
写接口(维护环境/产品线)MVP 可 **仅种子数据 + Flyway****管理员 POST**(若 I5 周可交付则加 `POST/PUT`,否则 **推迟**)。
### A.4 内部 API(平台服务间,`license-webhook-ingress` → `delivery-platform-api`
| 项 | 说明 |
|----|------|
| **路径** | `POST /internal/v1/callback-events` |
| **认证(MVP 推荐)** | 共享密钥:**`X-Platform-Internal-Token`**(或 `Authorization: Bearer <internal>`),配置与 `PLATFORM_JWT_SECRET` 分离;**生产建议路线图**:mTLS 或双向签名(文档中注明 **I6 Runbook:轮换步骤**)。 |
| **幂等** | 请求头 **`Idempotency-Key`** + 体 **`sourceSystem` + `externalMessageId`** 与 Inbox 唯一键一致;重复 POST → **200** 且 body 指向同一 `inboxId`。 |
| **行为** | 校验 `schemaVersion` → 插入或跳过(幂等)→ 尝试解析并 **填充关联列**(失败则 `status=PENDING` 保留人工挂接)。 |
| 项 | 说明 |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **路径** | `POST /internal/v1/callback-events` |
| **认证(MVP 推荐)** | 共享密钥:`**X-Platform-Internal-Token`**(或 `Authorization: Bearer <internal>`),配置与 `PLATFORM_JWT_SECRET` 分离;**生产建议路线图**:mTLS 或双向签名(文档中注明 **I6 Runbook:轮换步骤**。 |
| **幂等** | 请求头 `**Idempotency-Key`** + 体 `**sourceSystem` + `externalMessageId**` 与 Inbox 唯一键一致;重复 POST → **200** 且 body 指向同一 `inboxId` |
| **行为** | 校验 `schemaVersion` → 插入或跳过(幂等)→ 尝试解析并 **填充关联列**(失败则 `status=PENDING` 保留人工挂接)。 |
**请求 / 响应 JSON 示例(示意)**
@@ -128,30 +142,36 @@ Content-Type: application/json
### A.5 `license-webhook-ingress` 链路
| 步骤 | 说明 |
|------|------|
| 1 | **验签 / token**(现有 `x-bitanswer-token``CRAFTLABS_WEBHOOK_EXPECTED_TOKEN` |
| 2 | **持久化收据**(现有 **`webhook_callback_receipt`** + **`Idempotency-Key`** 幂等) |
| 3 | **HTTP 转发** 至平台 `POST /internal/v1/callback-events`:**带重试**(指数退避、最大次数、超时);贯通 **`traceparent` / `X-Request-Id`**(轨道 A §3 |
| 步骤 | 说明 |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
| 1 | **验签 / token**(现有 `x-bitanswer-token``CRAFTLABS_WEBHOOK_EXPECTED_TOKEN` |
| 2 | **持久化收据**(现有 `**webhook_callback_receipt`** + `**Idempotency-Key**` 幂等) |
| 3 | **HTTP 转发** 至平台 `POST /internal/v1/callback-events`:**带重试**(指数退避、最大次数、超时);贯通 `**traceparent` / `X-Request-Id`**(轨道 A §3 |
| **对比特的 HTTP 响应** | 与轨道 A 一致:**2xx 须在收据已持久化(或可靠入队)之后**再返回。**MVP 推荐**:先落库收据即对比特 **2xx**,平台投递 **异步重试**;若 **同步** 转发,须 **短超时** 且平台幂等,避免比特侧超时重放放大。 |
| **平台非 2xx** | Webhook 侧重试;仍失败则记 **DLQ/失败计数**(日志 + DB 字段,**M5-F08 完整监控可推迟**);**不**因平台暂时不可用而对已持久化收据重复向比特报错(若已 2xx)。 |
| **平台非 2xx** | Webhook 侧重试;仍失败则记 **DLQ/失败计数**(日志 + DB 字段,**M5-F08 完整监控可推迟**);**不**因平台暂时不可用而对已持久化收据重复向比特报错(若已 2xx)。 |
### A.6 OpenAPI 与 springdoc
| 项 | 说明 |
|----|------|
| **SSOT** | 更新 [`contracts/openapi/delivery-platform-api.json`](../../../contracts/openapi/delivery-platform-api.json):仅 **公开** `/api/v1/callback-inbox*``/api/v1/integration/*` 路径、`components/schemas`、错误码与 `security`Bearer JWT)。 |
| **内部路由** | **`/internal/**` 建议排除在默认 springdoc 分组之外**,或打上 tag **`internal`** 且 **生产禁用**该分组(与「对外契约」分离,避免集成方误用)。 |
| **校验** | `UPDATE_OPENAPI=1 mvn test -Dtest=OpenApiContractSnapshotTest`(见 [`contracts/README.md`](../../../contracts/README.md))。 |
| 项 | 说明 |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **SSOT** | 更新 `[contracts/openapi/delivery-platform-api.json](../../../contracts/openapi/delivery-platform-api.json)`:仅 **公开** `/api/v1/callback-inbox*``/api/v1/integration/*` 路径、`components/schemas`、错误码与 `security`Bearer JWT)。 |
| **内部路由** | `**/internal/`** 建议排除在默认 springdoc 分组之外**,或打上 tag `**internal`** 且 **生产禁用**该分组(与「对外契约」分离,避免集成方误用)。 |
| **校验** | `UPDATE_OPENAPI=1 mvn test -Dtest=OpenApiContractSnapshotTest`(见 `[contracts/README.md](../../../contracts/README.md)`)。 |
### A.7 前端(`web/delivery-platform-ui`
| 路由 | 页面职责 |
|------|----------|
| `/callbacks` | Inbox 列表、`CallbackInboxTable`;跳转详情 |
| `/callbacks/:id` | 详情 + **`CallbackPayloadViewer`(脱敏)**;状态 PATCH、可选人工挂接 |
| `/integration/environments` | M6 环境只读表 |
| `/integration/product-lines` | 产品线只读表 |
| 路由 | 页面职责 |
| ---------------------------- | ---------------------------------------------------- |
| `/callbacks` | Inbox 列表、`CallbackInboxTable`;跳转详情 |
| `/callbacks/:id` | 详情 + `**CallbackPayloadViewer`(脱敏)**;状态 PATCH、可选人工挂接 |
| `/integration/environments` | M6 环境只读表 |
| `/integration/product-lines` | 产品线只读表 |
路由 meta:**权限码与 I1 壳一致**;菜单对 **SYS_ADMIN / DEVELOPER** 可见(MVP)。
@@ -159,63 +179,74 @@ Content-Type: application/json
- **Schema**`schemas/craftlabs-auth-config.schema.json` 等 — 若 BP-10 变更类型触及「平台导出 → Schema → 客户端」,按 [轨道 C §3](../tracks/03-client-sdk.md) bump 规则执行。
- **Java `AuthConfigs`**:与 Schema 同步(表见轨道 C BP-10)。
- **`examples/`**:与最新字段及环境变量说明一致;CI 与 Schema 校验对齐。
- `**examples/**`:与最新字段及环境变量说明一致;CI 与 Schema 校验对齐。
- **文档**:明确 **SDK 版本线 ≠ 平台 Fat JAR 版本**;引用 BPM **BP-10** 与产品 M6-F01/F02 口径。
- **不做**Native 与 Webhook 运行时耦合;平台不嵌入 JNI。
---
## Part B — I6本文件内仅规划(不展开实现)
## Part B — I6规划与收口文档
| 主题 | 内容要点 | 执行文档 |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| **UAT 门禁** | 跑通 **BP-0106、11** 主链路(与 [轨道 B I6](../tracks/02-frontend-platform-ui.md) E2E 一致);**Callback** 场景含重复投递幂等、关联失败人工挂接。 | [I6_CLOSEOUT.md §2](./I6_CLOSEOUT.md) |
| **冻结清单** | **SDK**:定版 tag、CHANGELOG、**BitAnswer 兼容矩阵**(轨道 C);**OpenAPI** 快照冻结;**两 JAR** 版本与镜像标签可追踪;前端 `**VITE_API_BASE`** 环境矩阵文档化。 | [I6_CLOSEOUT.md §3~§4](./I6_CLOSEOUT.md) |
| **Runbook** | `[services/RUNBOOK.md](../../../services/RUNBOOK.md)`**内部 token 轮换**、Webhook→平台连通性检查、DB 迁移顺序(`flyway_platform_api` / `flyway_webhook`)。 | RUNBOOK **§10** |
| **安全加固** | **安全响应头**、Cookie/Session 策略(若 Mid 前仍为 JWT 则文档化 **仅 Bearer**)、依赖扫描与已知 CVE 处理;**禁止** I6 周排入大块新功能(仅缺陷与加固)。 | 平台 `SecurityConfig` + Runbook |
| **实现审核** | I1~I6 对照设计与三轨道文档 | [I6_IMPLEMENTATION_REVIEW.md](./I6_IMPLEMENTATION_REVIEW.md) |
| 主题 | 内容要点 |
|------|----------|
| **UAT 门禁** | 跑通 **BP-0106、11** 主链路(与 [轨道 B I6](../tracks/02-frontend-platform-ui.md) E2E 一致);**Callback** 场景含重复投递幂等、关联失败人工挂接。 |
| **冻结清单** | **SDK**:定版 tag、CHANGELOG、**BitAnswer 兼容矩阵**(轨道 C);**OpenAPI** 快照冻结;**两 JAR** 版本与镜像标签可追踪;前端 **`VITE_API_BASE`** 环境矩阵文档化。 |
| **Runbook** | 复用并增补 [`services/RUNBOOK.md`](../../../services/RUNBOOK.md)**内部 token 轮换**、Webhook→平台连通性检查、DB 迁移顺序(`flyway_platform_api` / `flyway_webhook`)。 |
| **安全加固** | **安全响应头**、Cookie/Session 策略(若 Mid 前仍为 JWT 则文档化 **仅 Bearer**)、依赖扫描与已知 CVE 处理;**禁止** I6 周排入大块新功能(仅缺陷与加固)。 |
---
## Part C — 实现顺序与 MVP 切割
1. **平台 DBFlyway `V5+`**`platform_callback_inbox` + M6 两表 + 必要索引与外键;种子数据(环境/产品线)可选。
2. **平台内部 API**`POST /internal/v1/callback-events` + 幂等与 `schemaVersion` 校验 + **`AuditService` 钩子**(状态/关联变更)。
3. **平台公开 API**`GET/PATCH` callback-inbox、M6 只读 GET**统一异常**经 `ApiExceptionHandler`
4. **OpenAPI 快照** 与契约测试更新。
5. **Webhook**:在收据落库后增加 **转发客户端**(重试、观测头);配置项:`PLATFORM_INTERNAL_BASE_URL``PLATFORM_INTERNAL_TOKEN`
6. **集成测试**Testcontainers + 双模块或 Compose(与轨道 A **I5+ `cross-service-it`** 建议一致)。
7. **前端**:路由与表格/详情/脱敏展示;联调 staging。
8. **SDK / Schema / examples**:按 BP-10 终版对齐并过 CI。
1. **平台 DBFlyway `V5+`**`platform_callback_inbox` + M6 两表 + 必要索引与外键;种子数据(环境/产品线)可选。
2. **平台内部 API**`POST /internal/v1/callback-events` + 幂等与 `schemaVersion` 校验 + `**AuditService` 钩子**(状态/关联变更)。
3. **平台公开 API**`GET/PATCH` callback-inbox、M6 只读 GET**统一异常**经 `ApiExceptionHandler`
4. **OpenAPI 快照** 与契约测试更新。
5. **Webhook**:在收据落库后增加 **转发客户端**(重试、观测头);配置项:`PLATFORM_INTERNAL_BASE_URL``PLATFORM_INTERNAL_TOKEN`
6. **集成测试**Testcontainers + 双模块或 Compose(与轨道 A **I5+ `cross-service-it`** 建议一致)。
7. **前端**:路由与表格/详情/脱敏展示;联调 staging。
8. **SDK / Schema / examples**:按 BP-10 终版对齐并过 CI。
**MVP 明确推迟(若全量 M5/M6 过大)**
| 推迟项 | 说明 |
|--------|------|
| M6-F03F09 | 比特 ID 映射、特征映射、模板库、发布记录、影响分析 |
| M5-F06F09 | 失败分类字典、批量重试 UI、积压监控、M8 待办联动 |
| M5-F10 | 模拟投递 UI(可用 curl/Postman 代替) |
| MQ 投递 | 保留 HTTP MVPMQ + 消费者为 ADR 备选(轨道 A 已列 B 方案) |
| 推迟项 | 说明 |
| ---------- | ------------------------------------------ |
| M6-F03F09 | 比特 ID 映射、特征映射、模板库、发布记录、影响分析 |
| M5-F06~F09 | 失败分类字典、批量重试 UI、积压监控、M8 待办联动 |
| M5-F10 | 模拟投递 UI(可用 curl/Postman 代替) |
| MQ 投递 | 保留 HTTP MVPMQ + 消费者为 ADR 备选(轨道 A 已列 B 方案) |
---
## Part D — 可追溯性(设计章节 → 产品功能点)
| 设计章节 | 产品模块 / 功能点(适用处) |
|----------|----------------------------|
| A.1 幂等与 schemaVersion | M5 运营基础;BP-06 |
| A.2.1 `platform_callback_inbox` | **M5-F01F04**(列表、详情、状态、关联兜底) |
| A.2.1 `event_type`、字典 | **M5-F05** |
| A.2.2 产品线 / 环境表 | **M6-F01、M6-F02** |
| A.3 公开 REST | **M5-F01F03**;人工挂接 **M5-F04** |
| A.4 内部 API | BP-06 入站;与轨道 A Webhook↔平台契约 |
| A.5 Webhook 转发 | BP-06 步骤①②;**不丢链** |
| A.8 SDK / Schema | **BP-10**;**M6** 配置治理(文档与校验链) |
| Part B I6 | BP-0106、11 UATM11 安全与运维 |
| 设计章节 | 产品模块 / 功能点(适用处) |
| ------------------------------- | ------------------------------ |
| A.1 幂等与 schemaVersion | M5 运营基础;BP-06 |
| A.2.1 `platform_callback_inbox` | **M5-F01F04**(列表、详情、状态、关联兜底) |
| A.2.1 `event_type`、字典 | **M5-F05** |
| A.2.2 产品线 / 环境表 | **M6-F01、M6-F02** |
| A.3 公开 REST | **M5-F01F03**;人工挂接 **M5-F04** |
| A.4 内部 API | BP-06 入站;与轨道 A Webhook↔平台契约 |
| A.5 Webhook 转发 | BP-06 步骤①②;**不丢链** |
| A.8 SDK / Schema | **BP-10**;**M6** 配置治理(文档与校验链) |
| Part B I6 | BP-0106、11 UATM11 安全与运维 |
---
## 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 初版:I5/I6 架构设计,对齐并行索引、三轨道文档与产品 M5/M6 P0。 |
| 日期 | 说明 |
| ---------- | ----------------------------------------------------------------- |
| 2026-04-06 | 初版:I5/I6 架构设计,对齐并行索引、三轨道文档与产品 M5/M6 P0。 |
| 2026-04-06 | Part B 关联 I6 收口文档(CLOSEOUT / IMPLEMENTATION_REVIEW)与 RUNBOOK §10。 |
@@ -0,0 +1,67 @@
# I6 收口执行包(UAT 门禁、冻结清单、运维)
> **定位**:在 [I5_I6_DESIGN.md](./I5_I6_DESIGN.md) **Part B** 规划基础上,把 **I6 周可执行项** 收束为检查表与环境矩阵。
> **前置**I5 代码路径已合入 `develop`Callback Inbox、内部投递、Webhook 转发、集成只读 API、前端路由)。
> **实现对照审核**[I6_IMPLEMENTATION_REVIEW.md](./I6_IMPLEMENTATION_REVIEW.md)。
---
## 1. 架构师任务(I6 周入口)
| 次序 | 产出 | 责任 |
|------|------|------|
| 1 | 本文件 + Runbook §10 运维段落 | 架构 / Tech Lead |
| 2 | 后端:安全响应头、配置与观测无回归 | 后端 |
| 3 | 前端:`VITE_API_BASE`、首页 UAT 导航、构建说明 | 前端 |
| 4 | [I6_IMPLEMENTATION_REVIEW.md](./I6_IMPLEMENTATION_REVIEW.md) 关闭 I1–I6 偏差项 | 架构审核 |
---
## 2. UAT 门禁(P0 场景)
> 与 [轨道 B §2 I6](../tracks/02-frontend-platform-ui.md)「BP-0106+11」一致;以下按 **本工作区已实现能力** 细化。
| # | 场景 | 预期 | 备注 |
|---|------|------|------|
| U1 | 登录 JWT | `admin/admin` 登录后访问受保护路由 | I1 |
| U2 | 客户 → 项目 | CRUD 与列表 | I2 |
| U3 | 合同 | 创建、行项、状态迁移合法/非法 | I3 |
| U4 | 交付批次 → 许可 SN | 创建、详情、状态 | I4 |
| U5 | Callback 全链 | Webhook 收据后转发 → 平台 Inbox 一行;UI 列表/详情、状态 PATCH、可选 link | I5;需配置内部 Token 与 base-url |
| U6 | 集成只读 | 环境/产品线列表与详情 | I5 |
| U7 | 重复幂等 | 同 `Idempotency-Key` / 同 `externalMessageId` 不重复插入;内部 API 返回 `duplicate: true` | I5 |
| U8 | 401 统一 | Token 失效回登录带 redirect | I1 |
**UAT 退出条件**:上表 **无 P0 缺陷**;已知 P1/P2 记入工单或 [I6_IMPLEMENTATION_REVIEW.md](./I6_IMPLEMENTATION_REVIEW.md) 「已知局限」。
---
## 3. 冻结清单(I6 末)
| 项 | 动作 |
|----|------|
| **OpenAPI** | `contracts/openapi/delivery-platform-api.json``OpenApiContractSnapshotTest` 一致;打 tag 可追溯 |
| **两枚 Fat JAR** | `delivery-platform-api``license-webhook-ingress` 版本与镜像标签写入发布说明 |
| **前端** | 生产构建使用明确 `VITE_API_BASE`(见 §4 |
| **SDK(本仓)** | 定版 tag、`CHANGELOG`、[轨道 C](../tracks/03-client-sdk.md) **兼容矩阵** 填齐;**I6 周内禁止 MAJOR Schema**(与设计一致) |
| **内部 Token** | 平台 `PLATFORM_INTERNAL_TOKEN` / `CRAFTLABS_PLATFORM_INTERNAL_TOKEN` 与 Webhook `craftlabs.platform.internal.token` **同值**;轮换走 [RUNBOOK §10](../../../services/RUNBOOK.md) |
---
## 4. 前端 `VITE_API_BASE` 环境矩阵
| 环境 | 示例 `VITE_API_BASE` | 说明 |
|------|----------------------|------|
| 本地开发 | (不设) | Vite 代理 `/api``127.0.0.1:8080` |
| Staging | `https://platform-api.staging.example.com` | 无尾部斜杠;axios 请求 `/api/v1/...` |
| 生产 | `https://platform-api.example.com` | 同源反代时可设为空,由 Nginx 处理 `/api` |
构建:`VITE_API_BASE=https://… npm run build`。详见 `web/delivery-platform-ui/README.md`
---
## 5. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | I6 收口执行包初版:UAT 表、冻结清单、VITE 矩阵。 |
@@ -0,0 +1,90 @@
# 架构师审核:I1~I6 实现对照(本工作区 `develop`)
> **方法**:以 [并行索引](../PARALLEL_ITERATION_INDEX.md)、各迭代设计稿(如 [I5_I6_DESIGN.md](./I5_I6_DESIGN.md))、三轨道文档([01](../tracks/01-backend-platform-webhook.md) / [02](../tracks/02-frontend-platform-ui.md) / [03](../tracks/03-client-sdk.md))为预期,对照仓库 **当前实现** 做收口审计。
> **范围**`services/delivery-platform-api`、`services/license-webhook-ingress`、`web/delivery-platform-ui`、`contracts/openapi`、`schemas/` 及 CI/Enforcer 门禁;**不**评价未在本仓的独立 Fat JAR 发布物。
---
## 1. 总评
| 维度 | 结论 |
|------|------|
| **迭代完整性** | I1I5 主路径已在前后端与 Webhook 贯通;I6 以 **UAT 文档、Runbook、安全头、前端生产基址与导航** 收口,符合 Part B「无大块新功能」原则。 |
| **契约** | 公开 API 以 `contracts/openapi/delivery-platform-api.json` 为 SSOT`OpenApiContractSnapshotTest` 守门;`/internal/**` 排除默认 springdoc 分组,与设计一致。 |
| **风险** | 内部 Token 为 MVP 共享密钥;生产应规划 mTLS/签名(设计已提示)。Webhook 转发失败仅日志重试次数用尽,**无持久化 DLQ**,适合 I6 后需求排期。 |
---
## 2. 分项审核
### 2.1 I1 — 身份、JWT、壳层
| 预期 | 实现 | 偏差 |
|------|------|------|
| Bearer JWT、401 入口、路由 RBAC | `JwtAuthenticationFilter``SecurityConfig` JWT 链、`router` meta.roles | 无 |
| 登录与 ping | `AuthController``/api/v1/ping`、前端 `LoginView` | 无 |
### 2.2 I2 — M1 主数据
| 预期 | 实现 | 偏差 |
|------|------|------|
| 客户/项目 CRUD + 字典 | Controllers + `CustomersView` / `ProjectsView` | 无结构性偏差(字段级以 OpenAPI 为准) |
### 2.3 I3 — M2 合同 + M10-F01
| 预期 | 实现 | 偏差 |
|------|------|------|
| 合同与行项、状态机服务端校验 | Contract API + 前端向导/详情 | 历史曾出现前后端动词不一致,**当前以 PATCH status 等与后端对齐**(需在 UAT 再点检) |
| 审计 | `AuditService``AuditEntityTypes` / `AuditActions` | 已扩展 Callback 相关常量(I5 |
### 2.4 I4 — M3/M4 交付与 SN
| 预期 | 实现 | 偏差 |
|------|------|------|
| 交付批次、行、许可 SN | 对应 API + `DeliveriesView` / SN 向导与列表 | 与设计 P0 一致 |
### 2.5 I5 — M5 Inbox、M6 只读、Webhook 链
| 预期(见 I5_I6_DESIGN Part A | 实现 | 偏差 / 备注 |
|--------------------------------|------|----------------|
| `platform_callback_inbox` + M6 表 + Flyway V5 | 已落地 | 无 |
| 公开 `GET/PATCH` callback-inbox、integration GET | 已落地 | 无 |
| `POST /internal/v1/callback-events` + 内部 Token | `CallbackInternalController``InternalTokenAuthenticationFilter` | 无 |
| 幂等 `(sourceSystem, externalMessageId)` + 重复返回 `duplicate` | `CallbackEventIngestService` | 无 |
| `schemaVersion` major 校验 | `SUPPORTED_SCHEMA_MAJOR = 1` | 与设计「拒绝无法识别的 major」一致 |
| Webhook:收据后转发、trace 头、有限重试 | `PlatformCallbackForwarder` | **MVP**:同步线程内 3 次退避;与设计「异步重试」相比属简化,Runbook 已说明可配置性 |
| 前端 `/callbacks`、脱敏 | `redactPayload.js`、Inbox 视图 | 建议 UAT 确认敏感字段字典与产品一致 |
| OpenAPI 仅公开路由 | 内部 `@Hidden` + `paths-to-exclude` | 无 |
### 2.6 I6 — UAT、冻结、加固
| 预期(Part B + I6_CLOSEOUT | 实现 | 偏差 |
|-------------------------------|------|------|
| UAT 检查表 | [I6_CLOSEOUT.md](./I6_CLOSEOUT.md) | 过程性文档;**不替代**自动化 E2E |
| Runbook:内部 Token、连通性、轮换 | [RUNBOOK.md §10](../../../services/RUNBOOK.md) | 无 |
| 安全响应头 | `SecurityConfig.apiHeaders``X-Content-Type-Options`、frame deny、Referrer-Policy | **HSTS** 依设计交由边缘层 |
| 前端生产 `VITE_API_BASE` | `main.js` + README + CLOSEOUT §4 | 无 |
| 首页全链路导航 | `HomeView` 模块链接 | 满足轨道 B I6「全链路导航」最低要求 |
### 2.7 轨道 CSDK / Schema
| 预期 | 实现 | 偏差 |
|------|------|------|
| I5 硬交付 Schema/示例/AuthConfigs | 以本仓 `schemas/``java/`、CI 为准(参见轨道 C 文档) | **I6 冻结**需在发布周由发布 Owner 执行 tag/CHANGELOG/矩阵,**非本审计单次提交所能证明** |
---
## 3. 建议进入 I7 / V1.1 前跟踪项
1. **Webhook 投递**:评估异步队列或 Outbox,补 **DLQ 指标**(设计 A.5、M5-F08)。
2. **内部认证**:mTLS 或请求签名;Token 多版本滚动。
3. **Playwright/Cypress**:将 I6_CLOSEOUT §2 固化为流水线冒烟。
4. **权限细化**M5 运营接口由 SYS_ADMIN/DEVELOPER 收口为 Ops 角色(设计 A.3 已注明 I7+)。
---
## 4. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 初版:I6 架构师审核,对照 I5_I6_DESIGN 与三轨道文档。 |
@@ -28,7 +28,7 @@
| **I3** | `/contracts`、新建向导、`/contracts/:id` | `ContractWizard``ContractLineEditor``StatusTag` | 状态机由后端校验,前端禁用非法操作 | P0 草稿→生效 | M2 P0M10-F01 入口 |
| **I4** | `/deliveries``/licenses/sn`、导入 | `DeliveryBatchForm``SnBindDialog``SnStatusTimeline` | 交付与合同行;孤儿 SN 警告 | P0 交付→SN→回写 | M3/M4 P0 |
| **I5** | `/callbacks``/integration/environments``product-lines` | `CallbackInboxTable``CallbackPayloadViewer`(脱敏) | Inbox 处置;M6 只读/受限写 | P0 列表→详情→状态 | 与 Webhook 联调或 staging |
| **I6** | 全链路导航与修缺陷 | 可选 `GlobalSearch` | 错误与空态统一 | P0 **BP-0106+11** 全链路 E2E | UAT 无 P0;手册截图一致 |
| **I6** | 全链路导航与修缺陷(参见 [I6_CLOSEOUT.md](../iterations/I6_CLOSEOUT.md) | 可选 `GlobalSearch` | 错误与空态统一;生产 `VITE_API_BASE` | P0 **BP-0106+11** 全链路 E2E | UAT 无 P0;手册截图一致 |
---
+38
View File
@@ -95,3 +95,41 @@ java -jar license-webhook-ingress-0.1.0-SNAPSHOT.jar
- 应用:回退上一版 JAR 并重启。
- 数据库:Flyway **无自动 down**;回滚需 **人工迁移脚本** 或从备份恢复(生产变更前应备份)。
## 10. I5/I6:内部 Token、Webhook → 平台与轮换
### 10.1 变量对照
| 组件 | 变量 / 配置键 | 说明 |
|------|----------------|------|
| **平台** `delivery-platform-api` | `PLATFORM_INTERNAL_TOKEN``CRAFTLABS_PLATFORM_INTERNAL_TOKEN``platform.internal.token` | 校验入站 `X-Platform-Internal-Token` |
| **Webhook** `license-webhook-ingress` | `CRAFTLABS_PLATFORM_INTERNAL_TOKEN``craftlabs.platform.internal.token` | 出站请求头,**须与平台一致** |
| **Webhook** | `PLATFORM_INTERNAL_BASE_URL``craftlabs.platform.internal.base-url` | 平台根 URL,无尾斜杠;转发 `POST …/internal/v1/callback-events` |
未配置 `base-url` 或 token 时,Webhook **仅落库收据**,不向平台投递(本地可只验 bitanswer token)。
### 10.2 连通性自检(平台已启动)
```bash
# 将 <TOKEN> 与平台环境变量一致
curl -sS -o /dev/null -w "%{http_code}\n" \
-X POST "http://127.0.0.1:8080/internal/v1/callback-events" \
-H "Content-Type: application/json" \
-H "X-Platform-Internal-Token: <TOKEN>" \
-H "Idempotency-Key: runbook-probe-$(date +%s)" \
-d '{"schemaVersion":"1.0","sourceSystem":"BITANSWER","externalMessageId":"runbook-probe-msg","eventType":"probe","rawPayload":{}}'
```
期望:**401** 表示路由可达但 Token 错误;**400** 可能为体缺字段;**200** 且 JSON 含 `inboxId` 表示鉴权与入队逻辑正常。
### 10.3 Token 轮换(简要)
1. 在平台与 Webhook **同时**配置新 Token(滚动窗口内可同时接受旧+新需改代码时另议;**MVP 为单值**,应选维护窗**同步切换**)。
2. 重启 **平台** API,再重启 **Webhook**(避免 Webhook 仍用旧值调用已切换的平台)。
3.**§10.2** 探测;再在 UI **Callback 收件箱** 验证新事件可见。
4. 审计:记录轮换时间与操作人(可另走工单)。
### 10.4 Flyway 历史表
- 平台:`flyway_platform_api`(迁移含 `platform_callback_inbox`、M6 表等)。
- Webhook`flyway_webhook`(收据表)。**同一 PostgreSQL 实例** 下两表共存,**勿**手动改名或合并。