diff --git a/docs/engineering/FRONTEND_UI_SPECIFICATION.md b/docs/engineering/FRONTEND_UI_SPECIFICATION.md new file mode 100644 index 0000000..ac6d79d --- /dev/null +++ b/docs/engineering/FRONTEND_UI_SPECIFICATION.md @@ -0,0 +1,307 @@ +# 交付平台前端 UI 需求规格说明(走查稿) + +> 依据 `web/delivery-platform-ui` 源码整理,供 Figma Make / 设计迭代与产品跟进维护。 +> 侧栏实现见 `src/layout/MainLayout.vue`;路由与角色见 `src/router/index.js`。 + +--- + +## 1. 全局壳层(需登录页共用) + +| 区域 | 内容 | +|------|------| +| 侧栏品牌 | 文案:`创飞 · 交付平台` | +| 侧栏菜单 | 见 §2(按角色 `v-if` 显示) | +| 顶栏右侧 | 当前用户展示名(`displayName`)、链接按钮「退出」 | +| 主内容区 | 路由出口,背景为控制台风格浅灰 | + +### 1.1 角色与菜单可见性 + +与路由 `meta.roles` 一致: + +- **SYS_ADMIN**:可见下文全部侧栏项(无单独剔除)。 +- **DEVELOPER**:客户管理、项目管理、合同管理、交付管理、许可 SN、集成环境、产品线;**无** Callback 收件箱。 +- **OPS**:Callback 收件箱、集成环境、产品线;**无** 客户 / 项目 / 合同 / 交付 / 许可 SN。 + +### 1.2 信息架构说明 + +当前侧栏为 **单层 `el-menu`**,无折叠分组、无物理「二级子菜单」。本文 **一级 = 产品模块**,**二级 = 侧栏入口 + 由其进入的子页面**(详情、向导、弹窗表单)。 + +--- + +## 2. 按模块:菜单项 → 页面 → 数据与操作 + +### 2.1 首页 + +| 项 | 说明 | +|----|------| +| 路由 | `/`,`HomeView.vue` | +| 侧栏 | 「首页」 | +| 内容 | 信息 Alert(I7:按角色展示入口;Callback 仅 OPS / SYS_ADMIN);当前用户与角色;**与侧栏一致的模块快捷链接**(随角色过滤) | +| 附加 | 「调试」:`GET /api/v1/ping` 按钮 + JSON 文本结果区 | + +--- + +### 2.2 客户管理 + +| 项 | 说明 | +|----|------| +| 路由 | `/customers`,`CustomersView.vue` | +| 侧栏 | 「客户管理」 | +| 列表筛选 | 关键词(名称或统一社会信用代码)、「查询」 | +| 表格列 | 客户名称、统一社会信用代码 | +| 行操作 | 编辑、删除(确认框) | +| 工具栏 | 「新建客户」 | +| 分页 | 10 / 20 / 50;total、sizes、prev、pager、next、jumper | + +#### 无独立菜单:新建 / 编辑客户(`el-dialog`,宽约 480px) + +| 字段 | 校验 / 约束 | API 语义 | +|------|----------------|----------| +| 客户名称 | 必填,`maxlength=200`,字数统计 | `POST` / `PUT` body | +| 统一社会信用代码 | 选填,`maxlength=32` | 空则不发该字段 | +| 底部 | 取消、保存(loading) | `createCustomer` / `updateCustomer` | + +--- + +### 2.3 项目管理 + +| 项 | 说明 | +|----|------| +| 路由 | `/projects`,`ProjectsView.vue` | +| 侧栏 | 「项目管理」 | +| 列表筛选 | 客户下拉(可清空、可搜索)、「查询」 | +| 表格列 | 客户(名)、项目名称、阶段(字典中文) | +| 行操作 | 编辑、删除(确认框) | +| 工具栏 | 「新建项目」 | +| 分页 | 同客户管理 | + +#### 无独立菜单:新建 / 编辑项目(`el-dialog`,宽约 520px) + +| 字段 | 校验 / 约束 | 说明 | +|------|----------------|------| +| 客户 | 必填 | 选项来自客户列表(最多 500 条) | +| 项目名称 | 必填,`maxlength=200` | | +| 阶段 | 必填 | `GET /api/v1/dictionaries/PROJECT_PHASE`,前端兼容多种响应包裹形态 | + +--- + +### 2.4 合同管理 + +| 项 | 说明 | +|----|------| +| 列表路由 | `/contracts`,`ContractsView.vue` | +| 侧栏 | 「合同管理」 | +| 列表筛选 | 关键词(合同标题)、「查询」 | +| 表格列 | 合同标题/编号、客户、项目、状态(Tag)、创建时间 | +| 行操作 | 「详情」→ `/contracts/:id` | +| 工具栏 | 「新建合同」→ `/contracts/new` | + +#### 无菜单:新建合同向导(`/contracts/new`,`ContractWizardView.vue`) + +三步 `el-steps`: + +**步骤 0 — 客户与项目** + +- 客户必选;项目必选;项目下拉随所选客户过滤;更换客户时清空项目。 + +**步骤 1 — 合同基本信息** + +- 合同标题/编号:必填,`maxlength=256`,字数统计。 +- 备注:选填,`textarea`,`maxlength=4000`。 + +**步骤 2 — 明细行** + +- 表内编辑:标的/行项名称(`itemName`)每行必填,`maxlength=256`。 +- 数量:`el-input-number`,`precision=4`,`min=0.0001`,须大于 0。 +- 单位:选填,`maxlength=32`。 +- 至少保留一行;「添加明细」;「删除」(仅一行时删除禁用)。 + +**提交**:先 `createContract`,再对每行 `addLine`;成功跳转合同详情。 + +#### 无菜单:合同详情(`/contracts/:id`,`ContractDetailView.vue`) + +| 区块 | 行为 | +|------|------| +| 头部 | ← 合同列表;标题「合同详情」;状态 Tag;**草稿** 显示「保存」 | +| 描述区 | 标题/备注:草稿可编辑;客户/项目只读(优先接口 name,否则 ID 映射) | +| 状态操作条 | 依状态显示按钮(均需确认):草稿→提交待生效;待生效→确认生效;生效→发起变更 / 终止合同;变更中→完成变更 | +| 合同明细 | 草稿可「添加明细」、行内编辑/删除;非草稿只读 | +| 明细弹窗 | 添加/编辑:行项名称必填;数量必填;单位选填 | +| 最近审计 | 列:时间、操作者、动作、摘要(`listAuditEvents` 归一化展示) | + +**合同状态枚举(Tag)** + +| 值 | 中文 | +|----|------| +| DRAFT | 草稿 | +| PENDING_EFFECTIVE | 待生效 | +| EFFECTIVE | 生效 | +| CHANGING | 变更中 | +| TERMINATED | 已终止 | + +--- + +### 2.5 交付管理 + +| 项 | 说明 | +|----|------| +| 列表路由 | `/deliveries`,`DeliveriesView.vue` | +| 侧栏 | 「交付管理」 | +| 列表筛选 | 项目下拉、批次编码关键词、「查询」 | +| 表格列 | 批次编码、项目、合同 ID、状态 Tag、计划交付日、创建时间 | +| 行操作 | 「详情」→ `/deliveries/:id` | +| 工具栏 | 「新建交付批次」→ `/deliveries/new` | + +#### 无菜单:新建交付批次(`/deliveries/new`,`DeliveryBatchWizardView.vue`) + +| 字段 | 校验 | 说明 | +|------|------|------| +| 项目 | 必填 | 可搜索 | +| 合同 | 选填 | 依赖项目;按 `projectId` 过滤合同 | +| 批次编码 | 必填,`maxlength=64` | | +| 计划交付日 | 选填 | 日期,`YYYY-MM-DD` | +| 备注 | 选填,`maxlength=4000` | | +| 底部 | 「创建并返回列表」/「创建并进入详情」 | | + +#### 无菜单:交付批次详情(`/deliveries/:id`,`DeliveryBatchDetailView.vue`) + +| 区块 | 行为 | +|------|------| +| 头部 | ← 交付列表;状态 Tag;**PENDING** 时:「保存抬头」「标记已交付」(确认) | +| 抬头 | 批次编码 / 项目 / 合同 ID / 完成时间只读;**PENDING** 可编辑计划交付日、备注 | +| 交付明细 | **PENDING** 可增删改;列:排序、说明、数量、合同行 ID | +| 明细弹窗 | 排序 number 0–999999;说明必填,`maxlength=512`;数量必填;合同行 ID 选填 | + +**交付状态枚举** + +| 值 | 中文 | +|----|------| +| PENDING | 待交付 | +| DELIVERED | 已交付 | +| CANCELLED | 已取消 | + +--- + +### 2.6 许可 SN + +| 项 | 说明 | +|----|------| +| 列表路由 | `/licenses/sn`,`LicenseSnListView.vue` | +| 侧栏 | 「许可 SN」 | +| 列表筛选 | 项目、SN 关键词、「查询」 | +| 表格列 | SN 编码、项目、合同行 ID、状态 Tag、创建时间 | +| 行操作 | 「详情」→ `/licenses/sn/:id` | +| 工具栏 | 「新建许可 SN」→ `/licenses/sn/new` | + +#### 无菜单:新建许可 SN(`/licenses/sn/new`,`LicenseSnWizardView.vue`) + +| 字段 | 校验 | 说明 | +|------|------|------| +| SN 编码 | 必填,`maxlength=128` | | +| 项目 | 选填 | 可搜索 | +| 合同行 ID | 选填,number ≥1 | MVP 手工录入提示 | +| 激活备注 | 选填,`textarea`,`maxlength=512` | | +| 底部 | 创建并返回列表 / 创建并进入详情 | | + +#### 无菜单:许可 SN 详情(`/licenses/sn/:id`,`LicenseSnDetailView.vue`) + +| 区块 | 内容 | +|------|------| +| 基础 | SN 编码、创建时间 | +| 绑定与备注 | 表单:项目、合同行 ID、激活备注;「保存绑定」 | +| 状态 | 下拉:REGISTERED / ISSUED / ACTIVATED / SUSPENDED / REVOKED(含中英文标签);「更新状态」 | + +**许可 SN 状态枚举** + +| 值 | 中文 | +|----|------| +| REGISTERED | 已登记 | +| ISSUED | 已发放 | +| ACTIVATED | 已激活 | +| SUSPENDED | 已暂停 | +| REVOKED | 已吊销 | + +--- + +### 2.7 Callback 收件箱(SYS_ADMIN、OPS) + +| 项 | 说明 | +|----|------| +| 列表路由 | `/callbacks`,`CallbackInboxView.vue` | +| 侧栏 | 「Callback 收件箱」 | +| 列表筛选 | 状态(PENDING / PROCESSED / FAILED / IGNORED)、事件类型、SN、项目 ID(文本)、「查询」 | +| 表格列 | 来源、外部消息 ID、事件类型、SN、状态 Tag、收件时间 | +| 行操作 | 「详情」→ `/callbacks/:id` | + +> **扩展说明**:`listCallbackInbox` API 另支持 `productLineId`、`environmentId`、`from`、`to` 等;当前 UI 未暴露,可在后续版本增加筛选。 + +#### 无菜单:Callback 详情(`/callbacks/:id`,`CallbackInboxDetailView.vue`) + +| 区块 | 规格 | +|------|------| +| 主信息 | ID、来源系统、外部消息 ID、事件类型、Schema 版本、幂等键、Webhook 收据 ID、SN、项目/合同/许可 SN/产品线/环境 ID、收件/处理时间、失败原因、备注 | +| Payload | 标题「Payload(脱敏预览)」;深色只读代码块,`pre`,约 `max-height: 420px` 可滚动 | +| I9 Webhook 出库状态 | **仅当**存在 `webhookReceiptId`:加载骨架 / 错误或描述列表:出库状态、尝试次数、上次错误、下次重试、出库更新时间 | +| I8 重新入队 | **同上条件**:配置说明(`LICENSE_WEBHOOK_BASE_URL`、`LICENSE_WEBHOOK_OPS_TOKEN`);按钮「重新入队出库(DEAD→待投递)」+ 确认 | +| 状态处置 | **仅 PENDING**:标已处理 / 标失败 / 忽略(均确认) | +| 人工挂接 | 许可 SN ID、项目 ID、合同 ID(文本输入);「保存挂接」需至少填一项 | + +**Callback 状态枚举** + +| 值 | 中文 | +|----|------| +| PENDING | 待处理 | +| PROCESSED | 已处理 | +| FAILED | 失败 | +| IGNORED | 忽略 | + +--- + +### 2.8 集成环境 + +| 项 | 说明 | +|----|------| +| 路由 | `/integration/environments`,`IntegrationEnvironmentsView.vue` | +| 侧栏 | 「集成环境」 | +| 页面 | 只读表 + 「刷新」 | +| 列 | 编码、名称、类型、比特 URL(`bitanswerBaseUrl` 等兼容字段)、产品线 ID | +| 分页 | 默认 `pageSize=20`,可选 10/20/50 | + +--- + +### 2.9 产品线 + +| 项 | 说明 | +|----|------| +| 路由 | `/integration/product-lines`,`IntegrationProductLinesView.vue` | +| 侧栏 | 「产品线」 | +| 页面 | 只读表 + 「刷新」 | +| 列 | 编码、名称、描述、启用(`enabled` / `active` 为 false 显示「否」) | +| 分页 | 同集成环境 | + +--- + +## 3. 无侧栏:认证与异常页 + +| 路由 | 页面 | 内容 | +|------|------|------| +| `/login` | `LoginView.vue` | 标题「客户商务与交付管理平台(I1)」;用户名、密码;登录 loading;演示账号提示 | +| `/403` | `ForbiddenView.vue` | Result:无权限;「返回首页」 | +| `/*` | `NotFoundView.vue` | Result:404;「返回首页」 | + +--- + +## 4. Figma / 画板拆分建议 + +1. **App Shell**:侧栏 + 顶栏 + 内容槽(与各列表/详情组合)。 +2. **一级画板**:首页、各侧栏列表页、登录、403、404。 +3. **二级画板**(无侧栏直达):合同向导(3 步)、合同详情、交付新建、交付详情、许可新建、许可详情、Callback 详情。 +4. **模态层**:客户表单、项目表单、合同行、交付行;各类二次确认可在设计注释中说明。 + +--- + +## 5. 修订记录 + +| 日期 | 说明 | +|------|------| +| 2026-04-07 | 初版:按 `web/delivery-platform-ui` 源码走查整理,供设计跟进。 | diff --git a/docs/engineering/PARALLEL_ITERATION_INDEX.md b/docs/engineering/PARALLEL_ITERATION_INDEX.md index ca05e75..4ff8488 100644 --- a/docs/engineering/PARALLEL_ITERATION_INDEX.md +++ b/docs/engineering/PARALLEL_ITERATION_INDEX.md @@ -9,11 +9,13 @@ ## 1. 三条轨道与文档 -| 轨道 | 仓库/工作区 | 执行包文档 | -|------|-------------|------------| -| **A. 平台后端** | `craftlabs-delivery-platform` + `craftlabs-license-webhook`(规划) | [tracks/01-backend-platform-webhook.md](./tracks/01-backend-platform-webhook.md) | -| **B. 平台前端** | `craftlabs-delivery-platform-ui`(规划) | [tracks/02-frontend-platform-ui.md](./tracks/02-frontend-platform-ui.md) | -| **C. 客户端 SDK** | 本工作区 `craftlabs-authorization-sdk` | [tracks/03-client-sdk.md](./tracks/03-client-sdk.md) | + +| 轨道 | 仓库/工作区 | 执行包文档 | +| -------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| **A. 平台后端** | `craftlabs-delivery-platform` + `craftlabs-license-webhook`(规划) | [tracks/01-backend-platform-webhook.md](./tracks/01-backend-platform-webhook.md) | +| **B. 平台前端** | `craftlabs-delivery-platform-ui`(规划) | [tracks/02-frontend-platform-ui.md](./tracks/02-frontend-platform-ui.md) | +| **C. 客户端 SDK** | 本工作区 `craftlabs-authorization-sdk` | [tracks/03-client-sdk.md](./tracks/03-client-sdk.md) | + --- @@ -21,32 +23,38 @@ 三轨 **共用** Roadmap 的 **I1~I6**(各约 **2 周**),同一迭代内并行开工;**硬耦合**集中在 **I5(Callback + Schema)** 与 **I6(UAT)**。 -| 迭代 | 后端(双 JAR) | 前端(Vue) | 客户端 SDK(本仓) | -|------|----------------|-------------|---------------------| -| I1 | 身份 + Webhook 脚手架 | 登录 + 布局壳 + RBAC 路由 | 文档/边界说明,无阻塞发布 | -| I2 | M1 主数据 + 字典 | 客户/项目页 | 同上 | -| I3 | M2 合同 + M10-F01 | 合同向导与行项 | 同上 | -| I4 | M3 交付 + M4 SN | 交付 + SN 页 | 文档与示例与 BP-10 口径一致 | -| I5 | M5 Inbox + M6 + Webhook 生产链 | Callback + 集成只读页 | **Schema + AuthConfigs + examples 对齐 BP-10** | -| I6 | 加固 + UAT | E2E + 缺陷 | **冻结 SDK 版本 + CHANGELOG + 兼容矩阵** | -| I7 | Webhook **异步投递** + **OPS** 运营权限(Callback) | 路由/菜单与 `@PreAuthorize` 对齐 | 若涉及 Schema 仍走轨道 C | -设计/复盘:[I7_DESIGN.md](./iterations/I7_DESIGN.md)、[I7_IMPLEMENTATION_REVIEW.md](./iterations/I7_IMPLEMENTATION_REVIEW.md)。 +| 迭代 | 后端(双 JAR) | 前端(Vue) | 客户端 SDK(本仓) | +| --- | ---------------------------------------------------- | ------------------------- | -------------------------------------------- | +| I1 | 身份 + Webhook 脚手架 | 登录 + 布局壳 + RBAC 路由 | 文档/边界说明,无阻塞发布 | +| I2 | M1 主数据 + 字典 | 客户/项目页 | 同上 | +| I3 | M2 合同 + M10-F01 | 合同向导与行项 | 同上 | +| I4 | M3 交付 + M4 SN | 交付 + SN 页 | 文档与示例与 BP-10 口径一致 | +| I5 | M5 Inbox + M6 + Webhook 生产链 | Callback + 集成只读页 | **Schema + AuthConfigs + examples 对齐 BP-10** | +| I6 | 加固 + UAT | E2E + 缺陷 | **冻结 SDK 版本 + CHANGELOG + 兼容矩阵** | +| I7 | Webhook **异步投递** + **OPS** 运营权限(Callback) | 路由/菜单与 `@PreAuthorize` 对齐 | 若涉及 Schema 仍走轨道 C | +| I8 | `**DEAD` 出库重放**:Webhook 内部 API + 平台代理 + UI / Runbook | Callback 详情「重新入队」 | 不涉及 SDK Schema | +| I9 | **出库状态只读**:Webhook `GET by-receipt` + 平台代理 + 详情展示 | Callback 详情「出库状态」 | 不涉及 SDK Schema | + + +设计/复盘:[I7_DESIGN.md](./iterations/I7_DESIGN.md)、[I7_IMPLEMENTATION_REVIEW.md](./iterations/I7_IMPLEMENTATION_REVIEW.md);I8:[I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md](./iterations/I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md)、[I8_IMPLEMENTATION_REVIEW.md](./iterations/I8_IMPLEMENTATION_REVIEW.md);I9:[I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md](./iterations/I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md)、[I9_IMPLEMENTATION_REVIEW.md](./iterations/I9_IMPLEMENTATION_REVIEW.md)。 --- ## 3. 跨轨同步点(必须对齐) -| 周次/迭代 | 同步内容 | 参与方 | -|-----------|----------|--------| -| **I1 末** | 认证方式(JWT vs Session)、OpenAPI `auth` tag、错误码与 401 行为 | 后端 + 前端 | -| **I2 末** | 客户/项目 DTO 冻结;字典编码 | 后端 + 前端 | -| **I3 末** | 合同状态枚举与非法迁移码;M10-F01 字段 | 后端 + 前端 | -| **I4 末** | SN 绑定与「孤儿 SN」规则;交付门禁参数(M11-F20) | 后端 + 前端 + SDK(文档) | -| **I5 起** | Callback payload 与 inbox DTO;**Idempotency-Key**;Webhook→平台投递方式(HTTP/MQ) | 后端 Webhook + 后端平台 + 前端 + **SDK(Schema/示例)** | -| **I6** | UAT 场景、冻结 **SDK 版本**、两枚 Fat JAR 与前端 `VITE_API_BASE` | 全员 | -**契约 Owner(建议角色)**:架构或 Tech Lead 兼任;**OpenAPI 单一事实来源** 为本仓库 [`contracts/openapi/delivery-platform-api.json`](../../contracts/openapi/delivery-platform-api.json)(与运行时 `/v3/api-docs` 由 `OpenApiContractSnapshotTest` 对齐),说明见 [`contracts/README.md`](../../contracts/README.md)。 +| 周次/迭代 | 同步内容 | 参与方 | +| -------- | ------------------------------------------------------------------------ | ------------------------------------------- | +| **I1 末** | 认证方式(JWT vs Session)、OpenAPI `auth` tag、错误码与 401 行为 | 后端 + 前端 | +| **I2 末** | 客户/项目 DTO 冻结;字典编码 | 后端 + 前端 | +| **I3 末** | 合同状态枚举与非法迁移码;M10-F01 字段 | 后端 + 前端 | +| **I4 末** | SN 绑定与「孤儿 SN」规则;交付门禁参数(M11-F20) | 后端 + 前端 + SDK(文档) | +| **I5 起** | Callback payload 与 inbox DTO;**Idempotency-Key**;Webhook→平台投递方式(HTTP/MQ) | 后端 Webhook + 后端平台 + 前端 + **SDK(Schema/示例)** | +| **I6** | UAT 场景、冻结 **SDK 版本**、两枚 Fat JAR 与前端 `VITE_API_BASE` | 全员 | + + +**契约 Owner(建议角色)**:架构或 Tech Lead 兼任;**OpenAPI 单一事实来源** 为本仓库 `[contracts/openapi/delivery-platform-api.json](../../contracts/openapi/delivery-platform-api.json)`(与运行时 `/v3/api-docs` 由 `OpenApiContractSnapshotTest` 对齐),说明见 `[contracts/README.md](../../contracts/README.md)`。 --- @@ -74,10 +82,16 @@ flowchart LR JAR --> NAT ``` + + --- ## 5. 修订记录 -| 日期 | 说明 | -|------|------| -| 2026-04-06 | 初版:三轨并行索引 + 同步点 + Gantt 示意。 | + +| 日期 | 说明 | +| ---------- | ----------------------------------------- | +| 2026-04-06 | 初版:三轨并行索引 + 同步点 + Gantt 示意。 | +| 2026-04-07 | 增加 **I9**:Webhook 出库状态只读(Callback 详情可观测)。 | + + diff --git a/docs/engineering/iterations/I7_IMPLEMENTATION_REVIEW.md b/docs/engineering/iterations/I7_IMPLEMENTATION_REVIEW.md index 6f434d3..71cf71d 100644 --- a/docs/engineering/iterations/I7_IMPLEMENTATION_REVIEW.md +++ b/docs/engineering/iterations/I7_IMPLEMENTATION_REVIEW.md @@ -20,7 +20,7 @@ | 项 | 说明 | |----|------| -| **DEAD 行运维** | 仅 DB 字段 `last_error`/`status`;无 UI 重放按钮(I7.1 可选)。 | +| **DEAD 行运维** | **I8** 已提供平台代理重放 + 详情页按钮;**I9** 补充出库 **只读状态**(`PENDING`/`SENT`/`DEAD` 等)见 [I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md](./I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md)。 | | **`v-permission` 指令** | 设计可选组件级指令;当前以 **路由 + 菜单 `hasAnyRole`** 为主,足够覆盖 I7 DoD。 | | **Playwright** | 仍未进 CI;与 [I6_CLOSEOUT](./I6_CLOSEOUT.md) 一致列为后续。 | | **内部 mTLS** | 未在本次范围;仍共享 `X-Platform-Internal-Token`。 | diff --git a/docs/engineering/iterations/I8_IMPLEMENTATION_REVIEW.md b/docs/engineering/iterations/I8_IMPLEMENTATION_REVIEW.md new file mode 100644 index 0000000..418a340 --- /dev/null +++ b/docs/engineering/iterations/I8_IMPLEMENTATION_REVIEW.md @@ -0,0 +1,40 @@ +# I8 实现审核 — 对照 [I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md](./I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md) + +> **日期**:2026-04-06。 + +--- + +## 1. 总评 + +| 设计条款 | 结论 | +|----------|------| +| 浏览器只调平台、平台代调 Webhook | **已落地**:`POST /api/v1/callback-inbox/{id}/replay-webhook-delivery` + `WebhookDeliveryReplayClient`。 | +| Webhook `/internal/**` 独立 Ops Token | **已落地**:`WebhookOpsTokenFilter`、`X-Webhook-Ops-Token`;空配置 **503**。 | +| 仅 `DEAD` 可重放 | **已落地**:`PlatformDeliveryService.replayDeadDeliveryByReceiptId`。 | +| 关联 `webhookReceiptId` | **已落地**:平台从 `PlatformCallbackInbox` 解析 `Long` 调 Webhook。 | +| OpenAPI / Runbook | **已更新**:`CallbackWebhookReplayResponse`、RUNBOOK §10.5 I8 段。 | + +--- + +## 2. 偏差与后续 + +| 项 | 说明 | +|----|------| +| **UI 不展示出库状态** | 仍为「成功调用平台接口」提示;是否在详情旁拉取 Webhook 库属 **Mid**(需只读 API 或观测面)。 | +| **Playwright** | 未进 CI;与 I6/I7 一致可后续补一条 OPS 点击重放(需 mock Webhook 或 test 栈)。 | + +--- + +## 3. 验证命令(提交前) + +- `mvn -f services/pom.xml verify` +- `npm run build`(`web/delivery-platform-ui`) +- `UPDATE_OPENAPI=0 mvn -f services/pom.xml test -Dtest=OpenApiContractSnapshotTest`(契约已与 `contracts/openapi/delivery-platform-api.json` 手工对齐时亦应通过) + +--- + +## 4. 修订记录 + +| 日期 | 说明 | +|------|------| +| 2026-04-06 | 初版:I8 实现对照设计审核。 | diff --git a/docs/engineering/iterations/I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md b/docs/engineering/iterations/I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md new file mode 100644 index 0000000..d9d0061 --- /dev/null +++ b/docs/engineering/iterations/I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md @@ -0,0 +1,88 @@ +# I8 设计 — Webhook 平台投递 DEAD 重放(MVP) + +> **角色**:解决方案架构;指导前后端实现与 Runbook。 +> **前置**:I7 已落地 `webhook_platform_delivery`(`PENDING`/`SENT`/`DEAD`)与调度器;平台 `platform_callback_inbox.webhook_receipt_id` 与 Webhook 收据主键对齐(`PlatformCallbackRequestPlanner` 写入 `webhookReceiptId`)。 + +--- + +## 1. 问题与目标 + +| 维度 | 说明 | +|------|------| +| **问题** | 出库 `DEAD` 后仅能通过查库/SQL 手工改状态,无受控运维入口,易误操作。 | +| **目标** | Ops 在 **Callback 详情** 一键将对应 **`DEAD`** 投递重新入队(`PENDING`),由现有调度器按 `max-attempts` 再次尝试 `POST /internal/v1/callback-events`。 | +| **非目标** | 修改比特 Callback 契约;不实现全量 DLQ 控制台;不在浏览器直连 Webhook 服务。 | + +--- + +## 2. 信任边界与 API 分层 + +```mermaid +flowchart LR + UI[delivery-platform-ui OPS JWT] + API[delivery-platform-api] + WH[license-webhook-ingress] + + UI -->|Bearer JWT| API + API -->|X-Webhook-Ops-Token + receiptId| WH + WH -->|调度| API +``` + +1. **浏览器只调平台** `POST /api/v1/callback-inbox/{id}/replay-webhook-delivery`,沿用 `CallbackInboxController` 的 `@PreAuthorize("hasAnyRole('OPS','SYS_ADMIN')")`。 +2. **平台服务器到 Webhook** 使用 **独立共享密钥** `X-Webhook-Ops-Token`(与 `X-Platform-Internal-Token` 分离:前者保护 Webhook 运维面,后者保护平台内部 ingest)。 +3. **Webhook** 暴露 `POST /internal/v1/platform-deliveries/by-receipt/{receiptId}/replay`,由 **Servlet Filter** 校验 Ops Token(模块无 Spring Security 依赖,与现有 `/webhook/bitanswer/callback` 并存)。 + +--- + +## 3. 业务规则 + +| 规则 | 说明 | +|------|------| +| **关联键** | 使用平台收件箱的 `webhook_receipt_id`(字符串数字)对应 `webhook_platform_delivery.receipt_id`(唯一)。缺失则 **400**,提示未关联 Webhook 出库记录。 | +| **仅 DEAD 可重放** | Webhook 侧若行不存在 → **404**;若状态为 `PENDING`/`SENT` → **409**,避免误重置进行中或已成功任务。 | +| **重放语义** | `status=PENDING`,`attempts=0`,`last_error=NULL`,`next_retry_at=NULL`,`updated_at=now`(重新给满 `max-attempts` 次数)。 | +| **平台幂等** | 重放仅重新 HTTP 投递;若平台 Inbox 已存在同 `(sourceSystem, externalMessageId)`,内部 ingest 返回 duplicate,**不新建 Inbox**(与现有幂等一致)。 | + +--- + +## 4. 配置项 + +| 组件 | 属性 / 环境变量 | 说明 | +|------|-----------------|------| +| Webhook | `craftlabs.webhook.ops-token` / `LICENSE_WEBHOOK_OPS_TOKEN` | 非空才启用 `/internal/**` 鉴权;空则 **503** 所有内部运维路径(避免误暴露)。 | +| Platform | `craftlabs.webhook.base-url` / `LICENSE_WEBHOOK_BASE_URL` | Webhook 根协议+主机+端口。 | +| Platform | `craftlabs.webhook.ops-token` | 与 Webhook 相同密钥,仅驻内存。 | + +**Runbook**:在 [RUNBOOK.md](../../../services/RUNBOOK.md) §10.5 旁补充:重放前确认平台已恢复;同 receipt **不要并发多次重放**(单机调度即可)。 + +--- + +## 5. 前端 + +- **Callback 详情**:展示 `webhookReceiptId`;若存在则显示 **「重新入队出库(DEAD→待投递)」**,调平台 POST;成功/失败用现有 `apiErrorMessage`。 +- **可见性**:路由已为 OPS/SYS_ADMIN,与 I7 一致。 + +--- + +## 6. 契约 + +- 更新 `contracts/openapi/delivery-platform-api.json`:`POST /api/v1/callback-inbox/{id}/replay-webhook-delivery` 与响应 DTO(如 `status`、`receiptId`)。 +- Webhook 内部路由 **不入** 对外 OpenAPI。 + +--- + +## 7. 测试与验收 + +| 层级 | 验收 | +|------|------| +| Webhook | 单测:`replayDeadByReceiptId` 对 DEAD/非 DEAD/缺行行为;集成或 MockMvc:401/503 无 token。 | +| Platform | 单测或 Mock:`CallbackInboxService` 在缺 receipt、Webhook 409/404 时映射 HTTP。 | +| 手工 | DEAD 一行 → 详情点重放 → 行变 `PENDING` → 调度成功后 `SENT`。 | + +--- + +## 8. 修订记录 + +| 日期 | 说明 | +|------|------| +| 2026-04-06 | 初版:I8 DEAD 重放架构(平台代理 + Webhook 内部 API)。 | diff --git a/docs/engineering/iterations/I9_IMPLEMENTATION_REVIEW.md b/docs/engineering/iterations/I9_IMPLEMENTATION_REVIEW.md new file mode 100644 index 0000000..6a101fd --- /dev/null +++ b/docs/engineering/iterations/I9_IMPLEMENTATION_REVIEW.md @@ -0,0 +1,44 @@ +# I9 实现审核 — 对照 [I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md](./I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md) + +> **日期**:2026-04-07。 + +--- + +## 1. 总评 + + +| 设计条款 | 结论 | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| Webhook 只读 `GET by-receipt` | **已落地**:`PlatformDeliveryService#getStatusByReceiptId` + `WebhookPlatformDeliveryOpsController`。 | +| 平台代理 + JWT | **已落地**:`GET /api/v1/callback-inbox/{id}/webhook-delivery`,`WebhookDeliveryReplayClient#fetchDeliveryStatus`。 | +| 前端不直连 Webhook | **已落地**:`getCallbackWebhookDelivery` + 详情 `el-descriptions`;失败仅提示文案不遮断主详情。 | +| OpenAPI | **已导出**:`UPDATE_OPENAPI=1` 更新 `contracts/openapi/delivery-platform-api.json`。 | +| 文档索引 / I7 复盘 | **已修订**:[PARALLEL_ITERATION_INDEX](../PARALLEL_ITERATION_INDEX.md)、[I7_IMPLEMENTATION_REVIEW](./I7_IMPLEMENTATION_REVIEW.md) §2。 | + + +--- + +## 2. 后续(未在本迭代范围) + + +| 项 | 说明 | +| -------------- | ----------------------------------------------- | +| **Playwright** | 可对「有 webhookReceiptId 的详情」断言状态区块或 503 提示。 | +| **指标** | M5-F08:积压 `PENDING`/`DEAD` 仍以观测平台为准,本迭代仅 UI 只读。 | + + +--- + +## 3. 验证 + +- `JAVA_HOME`=JDK **17**:`mvn -f services/pom.xml verify` +- `npm run build`(`web/delivery-platform-ui`) + +--- + +## 4. 修订记录 + + +| 日期 | 说明 | +| ---------- | --- | +| 2026-04-07 | 初版。 | diff --git a/docs/engineering/iterations/I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md b/docs/engineering/iterations/I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md new file mode 100644 index 0000000..4117bcf --- /dev/null +++ b/docs/engineering/iterations/I9_WEBHOOK_DELIVERY_VISIBILITY_DESIGN.md @@ -0,0 +1,68 @@ +# I9 设计 — Callback 详情可观测 Webhook 平台投递状态 + +> **角色**:架构审查与实现规划;承接 [I8](I8_WEBHOOK_DELIVERY_REPLAY_DESIGN.md)(重放入队)之后的 **只读观测** 缺口。 +> **日期**:2026-04-07。 + +--- + +## 1. 走查结论(相对 I6/I7/I8) + +| 主题 | 状态 | 说明 | +|------|------|------| +| I7 异步出库 | 已落地 | `PENDING` / `SENT` / `DEAD` 在 Webhook 库表。 | +| I8 重放 | 已落地 | 平台 POST 代理 + Webhook `replay`;**详情页仍看不到实时出库状态**。 | +| I7 复盘 §2 DEAD | **文档过时** | 已具备 UI 重放(I8);本迭代补 **状态只读** 并修订 I7 复盘表述。 | +| OpenAPI / JDK | 门禁 | 变更契约后须 **JDK 17** + `OpenApiContractSnapshotTest`;见 [contracts/README](../../../contracts/README.md)。 | + +--- + +## 2. 目标与非目标 + +| **目标** | Ops 在 **Callback 详情** 查看与该收件箱 `webhookReceiptId` 对应的 **`webhook_platform_delivery` 行摘要**(`status`、`attempts`、`lastError`、`nextRetryAt`、`updatedAt`),无需直连 Webhook 库。 | +| **非目标** | 浏览器调用 Webhook;改动比特契约;全量 M5-F08 仪表盘;Playwright 进 CI(列为 I9 后可选)。 | + +--- + +## 3. API 与信任边界 + +与 I8 相同:**仅** `delivery-platform-api` 凭 **`LICENSE_WEBHOOK_BASE_URL` + `LICENSE_WEBHOOK_OPS_TOKEN`** 访问 Webhook **`/internal/**`**;前端只打平台 JWT。 + +| 组件 | 方法 | 路径 | 说明 | +|------|------|------|------| +| Webhook | `GET` | `/internal/v1/platform-deliveries/by-receipt/{receiptId}` | 同 `WebhookOpsTokenFilter`;无行则 **404**。 | +| Platform | `GET` | `/api/v1/callback-inbox/{id}/webhook-delivery` | `@PreAuthorize` 与 Inbox 一致(`OPS`/`SYS_ADMIN`);无 `webhookReceiptId` 则 **400**;未配 Webhook 则 **503**。 | + +**响应体(双方 JSON 字段一致,便于直连 RestClient 反序列化)**: + +- `receiptId` (long) +- `status` (string) +- `attempts` (int) +- `lastError` (string, 可 null) +- `nextRetryAt` (date-time, 可 null) +- `updatedAt` (date-time) + +--- + +## 4. 实现要点 + +1. **Webhook**:`PlatformDeliveryService#getStatusByReceiptId` 查表组装 Map/DTO;Controller 增加 `GET`。 +2. **平台**:`WebhookDeliveryReplayClient#fetchDeliveryStatus`(保留 Bean 名以减重构面);`CallbackInboxService#getWebhookDeliveryStatus` → `CallbackInboxController`。 +3. **前端**:详情页在存在 `webhookReceiptId` 时请求上述 GET,展示 `el-descriptions`;失败时 **不阻断** 主详情(提示或短文案)。 +4. **契约**:`UPDATE_OPENAPI=1` 导出快照并提交。 +5. **文档**:更新 [PARALLEL_ITERATION_INDEX](../PARALLEL_ITERATION_INDEX.md) I9 行;修正 [I7_IMPLEMENTATION_REVIEW](./I7_IMPLEMENTATION_REVIEW.md) §2 DEAD 表述。 + +--- + +## 5. 验证 + +- `mvn -f services/pom.xml verify`(Java 17) +- `npm run build`(`web/delivery-platform-ui`) +- Webhook / 平台单测各 ≥1 条覆盖 GET 与平台代理 + +--- + +## 6. 修订记录 + +| 日期 | 说明 | +|------|------| +| 2026-04-07 | 初版:I9 出库可见性架构与 API。 |