docs(contract): V2.6 六处口径修正 + 新增 §17 实现补充与偏差记录(客户确认)
17 项决策里的 A 组:契约按已确认的口径改写,不动代码。
一、六处口径修正
· §2.7 notifyBizType:5/6 对调修正为「5 催报 / 6 导出完成」——
旧表若按写实现,催报去重会静默失效(F-30)
· §2.7 auditAction:补全 7 个动作,撤回=7(3 是批量通过;F-7);
§11.4 约束补「状态 2 可撤(撤的是初审)」、留痕改 action=7
· §3.6/§9.5 collect_file.source:改为建表口径「谁传的」
(1 本人/2 班主任代传/3 管理员代传/4 整理器;F-38/39 —— 代传可追溯)
· §9.5 重写为「上传前置检查(预检/秒传/压缩建议)」,direct:false,
明写「客户端可跳过;服务端上传本身做全部校验」(F-3/71/72);
§9.6 process 改为「有原始件时的重新规范化」;§9.9 OCR 标注一期缓做
· §12.1 validation/run 改同步并返回统计
(checkedSubmissions/issuesFound/unresolvedLeft/elapsedMs;F-51 ——
全契约本就没有查 jobId 的接口,返回一个查不到的作业号没有意义)
二、新增 §17 实现补充与偏差记录
· 17.1:41 个「代码有、§4 清单没有」的接口按六类分组
(材料通道/微信授权/签名URL下载/界面必需/契约链条断在第一步/运维动作);
清单声明由 check-api-parity.py --extra 生成 —— 手抄件会过期,命令不会
· 17.2:12 条已确认偏离 D-1~D-12(notifyBizType、source、上传链路、process、
validation 同步、撤回动作码、§14 路径前缀、biz_type 落库字符串、归档可删、
租户域名、通知两级、OCR 缓做)—— 每条带契约原口径/实现口径/依据编号
验证:python scripts/verify-docs.py → 22 项全部通过(含表格列数一致性);
check-api-parity.py --quiet → 契约 121 / 已实现 120(§17 不影响 §4 计数)
This commit is contained in:
+155
-33
@@ -252,24 +252,20 @@
|
|||||||
| `rosterSubmitStatus` | `0` 未提交 / `1` 已提交 / `2` 已通过 / `3` 已驳回 |
|
| `rosterSubmitStatus` | `0` 未提交 / `1` 已提交 / `2` 已通过 / `3` 已驳回 |
|
||||||
| `fileValidateStatus` | `0` 待校验 / `1` 通过 / `2` 警告 / `3` 不通过 / `4` 待人工 |
|
| `fileValidateStatus` | `0` 待校验 / `1` 通过 / `2` 警告 / `3` 不通过 / `4` 待人工 |
|
||||||
| `auditNode` | `1` 初审 / `2` 终审 |
|
| `auditNode` | `1` 初审 / `2` 终审 |
|
||||||
| `auditAction` | `1` 通过 / `2` 驳回 / `3` **撤回** / `4` 代传 |
|
| `auditAction` | `1` 通过 / `2` 驳回 / `3` 批量通过 / `4` 批量驳回 / `5` 转交 / `6` 代传 / `7` **撤回** |
|
||||||
| `detectSource` | `1` 文件名 / `2` 形态 / `3` OCR 内容 / `4` VLM / `5` 用户指定 |
|
| `detectSource` | `1` 文件名 / `2` 形态 / `3` OCR 内容 / `4` VLM / `5` 用户指定 |
|
||||||
| `compressProfile` | `P1` 证件照 / `P2` 证件翻拍 / `P3` A4 扫描件 / `P4` 截图 / `P5` 兜底 |
|
| `compressProfile` | `P1` 证件照 / `P2` 证件翻拍 / `P3` A4 扫描件 / `P4` 截图 / `P5` 兜底 |
|
||||||
| `notifyChannel` | `1` 微信订阅消息 / `2` 短信 / `3` 公众号模板 |
|
| `notifyChannel` | `1` 微信订阅消息 / `2` 短信 / `3` 公众号模板 |
|
||||||
| `notifyBizType` | `1` 任务发布 / `2` 临期提醒 / `3` 驳回通知 / `4` 审核通过 / `5` 导出完成 / `6` 催报 / `7` 加入名单审批 ⚠️ 见下方注 |
|
| `notifyBizType` | `1` 任务发布 / `2` 临期提醒 / `3` 驳回通知 / `4` 审核通过 / `5` **催报** / `6` **导出完成** / `7` 加入名单审批 |
|
||||||
| `exportStatus` | `0` 排队中 / `1` 打包中 / `2` 已完成 / `3` 失败 / `4` 已过期 |
|
| `exportStatus` | `0` 排队中 / `1` 打包中 / `2` 已完成 / `3` 失败 / `4` 已过期 |
|
||||||
| `cleanupSourceType` | `1` 压缩包 / `2` 文件夹 / `3` 云盘链接 |
|
| `cleanupSourceType` | `1` 压缩包 / `2` 文件夹 / `3` 云盘链接 |
|
||||||
| `cleanupStatus` | `0` 排队 / `1` 解析中 / `2` 处理中 / `3` 已完成 / `4` 失败 |
|
| `cleanupStatus` | `0` 排队 / `1` 解析中 / `2` 处理中 / `3` 已完成 / `4` 失败 |
|
||||||
|
|
||||||
> ⚠️ **`notifyBizType` 的 `5` / `6` 在本文件内部是不一致的**:本表写「`5` 导出完成 / `6` 催报」,
|
> ✅ **`notifyBizType` 的 `5`/`6` 曾经在本文件内部对调过(已修正,2026-09-30 客户确认)**:
|
||||||
> 而 §13.5 消息中心的示例(`2` 临期提醒 / `3` 驳回)与落库值 `collect_notify_record.biz_type`
|
> 本表旧版写「`5` 导出完成 / `6` 催报」,与 §13.5 消息中心示例、落库值
|
||||||
> 用的是「**`5` 催报 / `6` 导出完成**」。
|
> `collect_notify_record.biz_type`(`5` 催报 / `6` 导出完成)不一致 ——
|
||||||
>
|
> 若按旧表实现,催报会被写成"导出完成",**催报去重会静默失效**(老师点两下学生收到两条),
|
||||||
> **实现以 §13.5 为准**(代码里是 `CollectMessage.BIZ_URGE = 5` / `BIZ_EXPORT = 6`,
|
> 而排查时从数据上完全看不出是码对不上。现全文档统一为「`5` 催报 / `6` 导出完成」。
|
||||||
> 催报去重按 `5` 走)。这不是文案问题:若按本表实现,催报会被写成"导出完成",
|
|
||||||
> **去重会静默失效**(老师点两下学生收到两条),而排查时从数据上完全看不出是码对不上。
|
|
||||||
>
|
|
||||||
> 需要客户确认以哪一边为准;若确认以本表为准,要一起改的是两个常量 + 已落库的历史数据。
|
|
||||||
> 记录见《08-完成度盘点与问题清单》**F-30**。
|
> 记录见《08-完成度盘点与问题清单》**F-30**。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -472,18 +468,14 @@
|
|||||||
| `vlmCalled` | bool | `vlm_called` | 是否调用过 VLM(**成本监控用**) |
|
| `vlmCalled` | bool | `vlm_called` | 是否调用过 VLM(**成本监控用**) |
|
||||||
| `validateStatus` | int | `validate_status` | 见 §2.7 `fileValidateStatus` |
|
| `validateStatus` | int | `validate_status` | 见 §2.7 `fileValidateStatus` |
|
||||||
| `issues` | array | — | 该文件的 `ValidationIssueVO[]` |
|
| `issues` | array | — | 该文件的 `ValidationIssueVO[]` |
|
||||||
| `source` | int | `source` | `1` 聊天记录 / `2` 相册 / `3` 手机文件 / `4` 代传 ⚠️ 见下方注 |
|
| `source` | int | `source` | `1` 本人上传 / `2` 班主任代传 / `3` 管理员代传 / `4` 整理器导入 |
|
||||||
| `rawExpireTime` | string | `raw_expire_time` | **原图过期时间(默认 30 天)** |
|
| `rawExpireTime` | string | `raw_expire_time` | **原图过期时间(默认 30 天)** |
|
||||||
|
|
||||||
> ⚠️ **`collect_file.source` 有两套互相冲突的口径**:本表写的是"从哪个入口选的"
|
> ✅ **`collect_file.source` 的口径曾经有两套,已确认(2026-09-30 客户确认)**:本表旧版写
|
||||||
> (`1` 聊天记录 / `2` 相册 / `3` 手机文件 / `4` 代传),而建表注释(`V1__business_tables.sql`)
|
> "从哪个入口选的"(`1` 聊天记录 / `2` 相册 / `3` 手机文件 / `4` 代传),与建表注释
|
||||||
> 与实现(`CollectFile.SOURCE_*`)写的是"**谁传的**":
|
> (`V1__business_tables.sql`)冲突。**以建表注释为准("谁传的")**:§10.6 要求代传可追溯,
|
||||||
> `1` 本人上传 / `2` 班主任代传 / `3` 管理员代传 / `4` 整理器导入。
|
> 要回答的是"**谁**替他传的、**为什么**",而"从哪个入口选的"会随同一个学生的操作路径变化,
|
||||||
>
|
> 统计上没有意义。实现见 `CollectFile.SOURCE_*` 常量。记录见《08》**F-38 / F-39**。
|
||||||
> **实现以建表注释为准**:§10.6 要求代传必须可追溯,要回答的是"**谁**替他传的、
|
|
||||||
> **为什么**",而不是"他从哪个入口选的"—— 后者在同一个学生身上还会随操作路径变化。
|
|
||||||
> 两个口径都是 4 个值、含义完全不同,所以"照字段表写"会得到一份**看起来有数据、
|
|
||||||
> 但答案是错的**统计。记录见《08-完成度盘点与问题清单》**F-38 / F-39**。
|
|
||||||
|
|
||||||
**`ownership.verdict` 取值**(对应 PRD §6.1 归属裁决矩阵)
|
**`ownership.verdict` 取值**(对应 PRD §6.1 归属裁决矩阵)
|
||||||
|
|
||||||
@@ -533,7 +525,7 @@
|
|||||||
| `id` | int64 | — |
|
| `id` | int64 | — |
|
||||||
| `auditNode` | int | `1` 初审 / `2` 终审 |
|
| `auditNode` | int | `1` 初审 / `2` 终审 |
|
||||||
| `auditorId` / `auditorName` | int64 / string | 审核人 |
|
| `auditorId` / `auditorName` | int64 / string | 审核人 |
|
||||||
| `action` | int | 见 §2.7 `auditAction`(含 `3` **撤回**) |
|
| `action` | int | 见 §2.7 `auditAction`(**撤回是 `7`**;`3` 是批量通过 —— 旧版此表把两者写混过) |
|
||||||
| `reasonCode` | string | 驳回原因码(见 PRD 附录 C) |
|
| `reasonCode` | string | 驳回原因码(见 PRD 附录 C) |
|
||||||
| `reasonText` | string | 手填原因 |
|
| `reasonText` | string | 手填原因 |
|
||||||
| `materialCodes` | string[] | 关联材料 |
|
| `materialCodes` | string[] | 关联材料 |
|
||||||
@@ -1792,7 +1784,18 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手
|
|||||||
|
|
||||||
**自动保存**:前端每 15 秒或失焦时调一次;服务端**不限流**(草稿合并是轻量操作)。
|
**自动保存**:前端每 15 秒或失焦时调一次;服务端**不限流**(草稿合并是轻量操作)。
|
||||||
|
|
||||||
### 9.5 ⭐ `POST /api/v1/mp/files/upload-token` 获取直传凭证
|
### 9.5 ⭐ `POST /api/v1/mp/files/upload-token` 上传前置检查(预检 / 秒传 / 压缩建议)
|
||||||
|
|
||||||
|
> ✅ **定位已确认(2026-09-30 客户确认,原「OSS 直传凭证」改为此口径)**:
|
||||||
|
> 一期实现是**服务端一步上传**(`POST /api/v1/mp/files/upload`,multipart,
|
||||||
|
> 收上来即压缩 + 按模板改名,见 §17.2 D-3),**不走对象存储直传** ——
|
||||||
|
> 直传要把上传凭证交给前端、且七牛直传必须配回调才能让服务端知道"传完了"。
|
||||||
|
> 本接口因此**不是安全边界**(客户端可以跳过它直接上传,服务端上传本身做全部校验),
|
||||||
|
> 它的价值是**在上传一个字节之前**完成三件事:
|
||||||
|
> ① 预检(格式 / 体积 / 份数,传 8MB 才发现格式不对就晚了);
|
||||||
|
> ② **秒传**(同 `fileHash` 已存在 → `existed: true`,网络断开重试时不用重传);
|
||||||
|
> ③ 端侧压缩建议(能大幅降低上传流量)。
|
||||||
|
> 大文件 / 弱网场景要恢复直传时再单独立项(含回调闭环),见《08》**F-3 / F-71 / F-72**。
|
||||||
|
|
||||||
**幂等**:是
|
**幂等**:是
|
||||||
|
|
||||||
@@ -1805,7 +1808,7 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手
|
|||||||
| `fileName` | string | ✅ | 原始文件名(**可能是乱的,正常**) |
|
| `fileName` | string | ✅ | 原始文件名(**可能是乱的,正常**) |
|
||||||
| `fileSize` | int64 | ✅ | 字节 |
|
| `fileSize` | int64 | ✅ | 字节 |
|
||||||
| `contentType` | string | ✅ | MIME |
|
| `contentType` | string | ✅ | MIME |
|
||||||
| `source` | int | — | `1` 聊天记录 / `2` 相册 / `3` 手机文件(⚠️ 落库口径见 §3.6 注:实际存的是「谁传的」= 1 本人上传 / 2 班主任代传 / 3 管理员代传 / 4 整理器导入) |
|
| `source` | int | — | `1` 本人上传 / `2` 班主任代传 / `3` 管理员代传 / `4` 整理器导入(口径见 §3.6 注) |
|
||||||
| `fileHash` | string | — | 客户端 SHA-256(**用于秒传与去重**) |
|
| `fileHash` | string | — | 客户端 SHA-256(**用于秒传与去重**) |
|
||||||
|
|
||||||
**响应**
|
**响应**
|
||||||
@@ -1815,10 +1818,9 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手
|
|||||||
"code": 0,
|
"code": 0,
|
||||||
"data": {
|
"data": {
|
||||||
"fileId": 987654321,
|
"fileId": 987654321,
|
||||||
"uploadUrl": "https://oss.xxx.com/raw/1001/2026090001/202609/9f3a....jpg?Signature=...",
|
"direct": false,
|
||||||
"objectKey": "raw/1001/2026090001/202609/9f3a....jpg",
|
"uploadUrl": "/api/v1/mp/files/upload",
|
||||||
"expireSeconds": 900,
|
"expireSeconds": 900,
|
||||||
"headers": { "Content-Type": "image/jpeg" },
|
|
||||||
"existed": false,
|
"existed": false,
|
||||||
"clientCompressHint": {
|
"clientCompressHint": {
|
||||||
"enabled": true, "maxLongSide": 1600, "quality": 80, "targetSizeKb": 1024
|
"enabled": true, "maxLongSide": 1600, "quality": 80, "targetSizeKb": 1024
|
||||||
@@ -1829,14 +1831,23 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手
|
|||||||
|
|
||||||
| 字段 | 说明 |
|
| 字段 | 说明 |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
| `direct` | 恒为 `false`(一期不做对象存储直传)。`uploadUrl` 就是本项目自己的接收地址,学生端 `wx.uploadFile` 直接 POST 它,与 H5 路径完全一样 |
|
||||||
| `existed` | `true` = **秒传命中**(同 hash 已存在),前端跳过上传直接进入处理 |
|
| `existed` | `true` = **秒传命中**(同 hash 已存在),前端跳过上传直接进入处理 |
|
||||||
| `clientCompressHint` | **端侧预压缩建议**。这是第一道防线,能大幅降低上传流量与处理时长;**服务端仍会按规则重压**,端侧压缩只是加速 |
|
| `clientCompressHint` | **端侧预压缩建议**。这是第一道防线,能大幅降低上传流量与处理时长;**服务端仍会按规则重压**,端侧压缩只是加速 |
|
||||||
|
|
||||||
> **端侧压缩与「一次编码到位」的关系**:实测 300dpi/q85 即可达标,**端侧压缩只是减少上传量的优化,不是质量决定环节**。服务端始终以原图(或端侧压缩后的图)为输入重新规范,保证输出一致。
|
> **端侧压缩与「一次编码到位」的关系**:实测 300dpi/q85 即可达标,**端侧压缩只是减少上传量的优化,不是质量决定环节**。服务端始终以原图(或端侧压缩后的图)为输入重新规范,保证输出一致。
|
||||||
|
|
||||||
### 9.6 ⭐ `POST /api/v1/mp/files/{fileId}/process` 文件规范化(核心)
|
### 9.6 ⭐ `POST /api/v1/mp/files/{fileId}/process` 文件规范化(有原始件时的重新处理)
|
||||||
|
|
||||||
**说明**:这是**命名 + 压缩 + 识别 + 校验**的总入口。
|
> ✅ **语义已确认(2026-09-30,原「命名+压缩+识别+校验总入口」改为此口径)**:
|
||||||
|
> 一期上传是**一步**的 —— `POST /mp/files/upload` 收上来**当场**完成压缩、按模板改名、校验、
|
||||||
|
> 记录问题(含 VAL-06 自动修复),默认**只存规范件**(`fill.storage.keep-raw=false`)。
|
||||||
|
> 所以本接口**只在「保留了原始件」时有用**:对原始件重新跑一遍规范化
|
||||||
|
> (典型场景:改了材料规则后重压)。默认配置下多数文件没有原始件,调用会明确说明这一点,
|
||||||
|
> 而不是假装处理了一遍。识别 / OCR 的四层递进是二期(M3/M4),见 §17.2 D-12。
|
||||||
|
> 下面的响应结构保留作为「规范化产物」的字段参考(上传接口的产物与之同构)。
|
||||||
|
|
||||||
|
**说明**:对**有原始件**的文件重新执行规范化(压缩 + 改名 + 校验)。
|
||||||
|
|
||||||
**请求**
|
**请求**
|
||||||
|
|
||||||
@@ -1937,7 +1948,11 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手
|
|||||||
|
|
||||||
**删除约束**:已提交并进入审核的文件删除需先**撤回**(`40030`)。
|
**删除约束**:已提交并进入审核的文件删除需先**撤回**(`40030`)。
|
||||||
|
|
||||||
### 9.9 `POST /api/v1/mp/ocr/idcard`
|
### 9.9 `POST /api/v1/mp/ocr/idcard`(一期缓做)
|
||||||
|
|
||||||
|
> ⚠️ **一期未实现(等 OCR 供应商选型,《01-客户与机构确认函》D1)**。
|
||||||
|
> 这是 121 个契约接口里**唯一**没有实现的一个;表单预填与校验位校验在学生端走人工填写。
|
||||||
|
> 选型要同时评估**数据出境合规**。选型定了之后本节按原设计实现,以下为设计参考。
|
||||||
|
|
||||||
**说明**:身份证 OCR,用于表单预填与校验。**这是唯一返回明文姓名的 OCR 场景**。
|
**说明**:身份证 OCR,用于表单预填与校验。**这是唯一返回明文姓名的 OCR 场景**。
|
||||||
|
|
||||||
@@ -2287,11 +2302,13 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手
|
|||||||
| 条件 | 结果 |
|
| 条件 | 结果 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 当前状态 `3`(已通过)且未被导出 | ✅ 允许,回退到 `2` 或 `1` |
|
| 当前状态 `3`(已通过)且未被导出 | ✅ 允许,回退到 `2` 或 `1` |
|
||||||
|
| 当前状态 `2`(待终审) | ✅ 允许,回退到 `1`(撤回的是**初审**结论 —— 初审通过后发现填错,正是最需要撤的时刻) |
|
||||||
| **已被导出打包** | ❌ `40033`——**已交付的数据不可追溯修改** |
|
| **已被导出打包** | ❌ `40033`——**已交付的数据不可追溯修改** |
|
||||||
| 已归档 | ❌ `40034` |
|
| 已归档 | ❌ `40034` |
|
||||||
| 撤回者非原审核人且非管理员 | ❌ `40301` |
|
| 撤回者非原审核人且非管理员 | ❌ `40301` |
|
||||||
|
|
||||||
**留痕**:写入一条 `action = 3`(撤回)的 `collect_audit_record`,时间轴显示"李老师撤回了通过结论"。
|
**留痕**:写入一条 `action = 7`(撤回)的 `collect_audit_record`,时间轴显示"李老师撤回了通过结论"。
|
||||||
|
(旧版写 `action = 3` —— `3` 实际是**批量通过**,撤回恒为 `7`,与代码常量 `ACTION_REVOKE` 一致。)
|
||||||
|
|
||||||
### 11.5 `GET /api/v1/audit/submissions/{id}/timeline`
|
### 11.5 `GET /api/v1/audit/submissions/{id}/timeline`
|
||||||
|
|
||||||
@@ -2320,15 +2337,19 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手
|
|||||||
|
|
||||||
**说明**:任务级全量校验,产出问题清单。
|
**说明**:任务级全量校验,产出问题清单。
|
||||||
|
|
||||||
|
> ✅ **改为同步(2026-09-30 客户确认)**:旧版声明 `async` 默认 `true` 并返回 `jobId`,
|
||||||
|
> 但全契约**没有"查 jobId"的接口** —— 那个 jobId 查不到任何东西。
|
||||||
|
> 实测一份填报单的校验 <100ms、一个任务的量级秒级,**同步跑完直接回结果**
|
||||||
|
> 比返回一个查不到状态的作业号有用。保留 `jobId` 仅作本次运行的标识(幂等对账用)。
|
||||||
|
|
||||||
| 字段 | 类型 | 默认 | 说明 |
|
| 字段 | 类型 | 默认 | 说明 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `taskId` | int64 | ✅ | — |
|
| `taskId` | int64 | ✅ | — |
|
||||||
| `scope` | string | `ALL` | `ALL` / `ISSUES_ONLY` / `APPOINTED` |
|
| `scope` | string | `ALL` | `ALL` / `ISSUES_ONLY` / `APPOINTED` |
|
||||||
| `submissionIds` | int64[] | — | `scope=APPOINTED` 时必填 |
|
| `submissionIds` | int64[] | — | `scope=APPOINTED` 时必填 |
|
||||||
| `rules` | string[] | 全部 | 只跑指定规则(如 `["VAL-02","VAL-04"]`) |
|
| `rules` | string[] | 全部 | 只跑指定规则(如 `["VAL-02","VAL-04"]`) |
|
||||||
| `async` | bool | `true` | — |
|
|
||||||
|
|
||||||
**响应**:`{ "jobId": "v_3f2a", "estimatedSeconds": 30 }`
|
**响应**:`{ "jobId": "v_3f2a", "checkedSubmissions": 320, "issuesFound": 47, "unresolvedLeft": 31, "elapsedMs": 812 }`
|
||||||
|
|
||||||
### 12.2 `GET /api/v1/validation/issues`
|
### 12.2 `GET /api/v1/validation/issues`
|
||||||
|
|
||||||
@@ -2883,6 +2904,7 @@ X-Sign: HMAC-SHA256(appSecret, appKey + timestamp + nonce + body)
|
|||||||
|
|
||||||
| 版本 | 日期 | 变更 |
|
| 版本 | 日期 | 变更 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
| **V2.6** | **2026-09-30** | **六处口径修正 + 新增 §17(全部经客户确认)**:① §2.7 `notifyBizType` 5/6 对调修正(5 催报 / 6 导出完成,F-30);② §2.7 `auditAction` 补全为 7 个动作、撤回=7(F-7);③ §3.6/§9.5 `source` 改为建表口径「谁传的」(F-38/39);④ §9.5 改为「上传前置检查」定位 + §9.6 `process` 语义改为「有原始件时的重新规范化」(F-3/71/72);⑤ §9.9 OCR 标注一期缓做;⑥ §11.4 撤回约束补「状态 2 可撤(撤初审)」、留痕 `action=7`;⑦ §12.1 `validation/run` 改同步并返回统计(F-51);⑧ **新增 §17 实现补充与偏差记录**(41 个 §4 未列接口按六类分组 + 12 条已确认偏离 D-1~D-12)。同步记录见《08-完成度盘点与问题清单》 |
|
||||||
| **V2.5** | **2026-09-30** | **两处"文档内部口径冲突"加注(不改口径,只把冲突摆到明处)**:① §2 枚举 `notifyBizType` 的 `5`/`6`(本文件写「5 导出完成 / 6 催报」)与 §13.5 消息中心示例、落库值 `collect_notify_record.biz_type`(「**5 催报 / 6 导出完成**」)不一致 —— **实现以 §13.5 为准**,需要客户确认以哪边为准(若以 §2 为准,要同时改两个常量与已落库数据);② §3.6 与 §9.5 的 `collect_file.source` 字段表写的是"入口来源"(1 聊天记录 / 2 相册 / 3 手机文件 / 4 代传),而建表注释与实现(`CollectFile.SOURCE_*`)是"**谁传的**"(1 本人上传 / 2 班主任代传 / 3 管理员代传 / 4 整理器导入)—— **实现以建表口径为准**(§10.6 要求代传可追溯,要回答的是"谁替他传的")。两处均已在原文加 ⚠️ 注。记录见《08-完成度盘点与问题清单》**F-30 / F-38 / F-39**。 |
|
| **V2.5** | **2026-09-30** | **两处"文档内部口径冲突"加注(不改口径,只把冲突摆到明处)**:① §2 枚举 `notifyBizType` 的 `5`/`6`(本文件写「5 导出完成 / 6 催报」)与 §13.5 消息中心示例、落库值 `collect_notify_record.biz_type`(「**5 催报 / 6 导出完成**」)不一致 —— **实现以 §13.5 为准**,需要客户确认以哪边为准(若以 §2 为准,要同时改两个常量与已落库数据);② §3.6 与 §9.5 的 `collect_file.source` 字段表写的是"入口来源"(1 聊天记录 / 2 相册 / 3 手机文件 / 4 代传),而建表注释与实现(`CollectFile.SOURCE_*`)是"**谁传的**"(1 本人上传 / 2 班主任代传 / 3 管理员代传 / 4 整理器导入)—— **实现以建表口径为准**(§10.6 要求代传可追溯,要回答的是"谁替他传的")。两处均已在原文加 ⚠️ 注。记录见《08-完成度盘点与问题清单》**F-30 / F-38 / F-39**。 |
|
||||||
| **V2.4** | **2026-09-28** | **修正 3 处加密列宽不足(DDL 缺陷)**:`sys_tenant.contact_phone`、`sys_user.phone`、`collect_roster.phone` 原为 `varchar(20)`,是按**明文手机号**的长度建的;但 PRD §10.3 要求手机号 AES-256 加密存储,AES-GCM 密文约 **52 字符**,插入会直接报 `value too long for type character varying(20)`。(`id_card_no` 当初给了 `varchar(128)` 就是考虑到了加密,手机号三列漏了——属设计不一致。)三列统一改为 **`varchar(128)`**,与 `id_card_no` 对齐。**发现方式**:M0 开通租户实测时数据库报错暴露。**审计结论**:其余 `*_mask varchar(32)` 列存的是脱敏值(如 `138****8888`)而非密文,宽度足够,无需改动。 |
|
| **V2.4** | **2026-09-28** | **修正 3 处加密列宽不足(DDL 缺陷)**:`sys_tenant.contact_phone`、`sys_user.phone`、`collect_roster.phone` 原为 `varchar(20)`,是按**明文手机号**的长度建的;但 PRD §10.3 要求手机号 AES-256 加密存储,AES-GCM 密文约 **52 字符**,插入会直接报 `value too long for type character varying(20)`。(`id_card_no` 当初给了 `varchar(128)` 就是考虑到了加密,手机号三列漏了——属设计不一致。)三列统一改为 **`varchar(128)`**,与 `id_card_no` 对齐。**发现方式**:M0 开通租户实测时数据库报错暴露。**审计结论**:其余 `*_mask varchar(32)` 列存的是脱敏值(如 `138****8888`)而非密文,宽度足够,无需改动。 |
|
||||||
| **V1.2** | **2026-09-28** | **错误码规范化**:`40007b` / `40007c` 带字母后缀,与 §1.3「`code` 为数字」的约定冲突(JSON 里会变成字符串,前端 `code === 0` 判断失效)。改为纯数字:**`40007b` → `40071`**(门禁限流)、**`40007c` → `40072`**(名单项已被绑定)。总码数不变,仍 **58 个**。 |
|
| **V1.2** | **2026-09-28** | **错误码规范化**:`40007b` / `40007c` 带字母后缀,与 §1.3「`code` 为数字」的约定冲突(JSON 里会变成字符串,前端 `code === 0` 判断失效)。改为纯数字:**`40007b` → `40071`**(门禁限流)、**`40007c` → `40072`**(名单项已被绑定)。总码数不变,仍 **58 个**。 |
|
||||||
@@ -2902,3 +2924,103 @@ X-Sign: HMAC-SHA256(appSecret, appKey + timestamp + nonce + body)
|
|||||||
| 错误码数量 | 58 个(新增 38 个标 ⭐)✓ |
|
| 错误码数量 | 58 个(新增 38 个标 ⭐)✓ |
|
||||||
| 文件规模 | 约 100 KB / 2570 行 |
|
| 文件规模 | 约 100 KB / 2570 行 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §17 实现补充与偏差记录
|
||||||
|
|
||||||
|
> **§4 的 121 个是设计口径**。本节回答两件事:① 代码里还有哪些 §4 没列的接口;
|
||||||
|
> ② 哪些地方**有意偏离**了本文档的原始写法、为什么、谁确认的。
|
||||||
|
>
|
||||||
|
> ⚠️ 17.1 的清单由工具生成:`python scripts/check-api-parity.py --extra`
|
||||||
|
> (当前 **41 个**,生成于 2026-09-30)。**手抄清单会过期,以命令输出为准**;
|
||||||
|
> 下表按"为什么不在 §4"分组,便于决定"补进 §4 还是留在本节"。
|
||||||
|
|
||||||
|
### 17.1 代码有、§4 清单没有的接口(41 个,按偏离原因分组)
|
||||||
|
|
||||||
|
**A · 材料访问通道(7 个)** —— 审核端与学生端各一条通道(审核端要看防伪纹,学生端只看自己的);上传是一步 multipart
|
||||||
|
|
||||||
|
| 接口 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `POST /api/v1/mp/files/upload` | 实际的上传入口(一步 multipart,收上来即压缩+改名) |
|
||||||
|
| `GET /api/v1/mp/files/{id}/url` | 学生端取签名预览链接 |
|
||||||
|
| `GET /api/v1/mp/files/{id}/content` | 学生端直出内容 |
|
||||||
|
| `GET /api/v1/mp/files/submissions/{submissionId}` | 按提交单列出材料 |
|
||||||
|
| `GET /api/v1/audit/files/{id}/url` | 审核端签名链接(独立通道;支持 `?thumb=true` 取缩略图) |
|
||||||
|
| `GET /api/v1/audit/files/{id}/content` | 审核端直出内容 |
|
||||||
|
| `GET /api/v1/mp/teacher/files/{fileId}/preview` | 教师端(代传场景)预览学生材料 |
|
||||||
|
|
||||||
|
**B · 微信授权链路代替 `POST /mp/login`(4 个)**
|
||||||
|
|
||||||
|
| 接口 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `GET /api/v1/mp/h5/authorize` | H5 微信网页授权跳转 |
|
||||||
|
| `GET /api/v1/mp/h5/callback` | 授权回调(token 放 URL fragment) |
|
||||||
|
| `GET /api/v1/mp/h5/stub-login` | **仅非生产**打桩登录(`stub-enabled` + profile 双重保护) |
|
||||||
|
| `GET /api/v1/mp/scene-context` | 匿名扫码落地页查询(scene 三段式,只返回最少信息 + 限流) |
|
||||||
|
|
||||||
|
**C · 签名 URL 下载(1 个)**
|
||||||
|
|
||||||
|
| 接口 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `GET /api/v1/exports/{id}/file` | 导出包的流式下载。§12.6 只定义了"取下载信息";真正下文件走这条,**URL 有有效期、不能入库/外发** |
|
||||||
|
|
||||||
|
**D · 界面与接口必需、清单漏列(17 个)**
|
||||||
|
|
||||||
|
| 接口 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `GET /api/v1/auth/captcha` | 图形验证码(连续失败 2 次后必填) |
|
||||||
|
| `GET /api/v1/auth/public-key` | 密码 RSA 加密传输用的公钥 |
|
||||||
|
| `GET /api/v1/material-catalog` | 材料目录(码/档位/格式/模板/体积范围) |
|
||||||
|
| `POST /api/v1/tasks/{id}/materials/apply-template` | 应用内置材料模板(幂等) |
|
||||||
|
| `POST /api/v1/tasks/{id}/save-as-template` | 保存为模板(§7.13 的缺口,F-62) |
|
||||||
|
| `POST /api/v1/audit/submissions/batch-approve` | 批量通过(§11.3 标题带过、清单未单列) |
|
||||||
|
| `GET /api/v1/mp/teacher/audit/reason-codes` | 教师端驳回原因字典(§11.7 有 PC 版) |
|
||||||
|
| `GET /api/v1/mp/teacher/submissions/{id}/proxy-context` | 代传前取上下文(哪个班、哪份草稿) |
|
||||||
|
| `GET /api/v1/qrcodes/{scene}` | 匿名出图 |
|
||||||
|
| `POST /api/v1/roster/{rid}/deactivate` | 名册项"标记不参与"(删除的可逆替代,§8.3/8.4) |
|
||||||
|
| `GET /api/v1/tenants/{id}` | 租户详情 |
|
||||||
|
| `PUT /api/v1/users/{id}/status` | 启用/停用(清单只有 DELETE 停用) |
|
||||||
|
| `DELETE /api/v1/dicts/items/{id}` | 删除自定义字典项(§13.3) |
|
||||||
|
| `POST /api/v1/dicts/{code}/items/{itemCode}/toggle` | 内置字典项停用/恢复(§13.3) |
|
||||||
|
| `GET /api/v1/exports/precheck` | 导出前置检查(只检查不创建;界面靠它禁用按钮) |
|
||||||
|
| `GET /api/v1/exports/preview` | 导出目录树预览(与真正打包共用同一份构建代码) |
|
||||||
|
| `GET /api/v1/cleanup/jobs/{id}/result` | 整理器报告(§13.2 界面轮询用) |
|
||||||
|
|
||||||
|
**E · 契约链条原本断在第一步(8 个,已实现)**
|
||||||
|
|
||||||
|
| 接口 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `POST /api/v1/notify/templates/{code}/customize` | 把内置模板复制成可编辑的库行(F-52:内置模板没有 id,`PUT /{id}` 拿不出 id) |
|
||||||
|
| `DELETE /api/v1/notify/templates/{code}` | 恢复默认模板 |
|
||||||
|
| `GET /api/v1/notify/templates/variables` | 变量白名单(前端抄一份迟早不一致) |
|
||||||
|
| `POST /api/v1/open-apps` · `GET /api/v1/open-apps` | §14 的接入方管理(F-67:没有它,appSecret 只能改库产生) |
|
||||||
|
| `POST /api/v1/open-apps/{id}/reset-secret` | 重置密钥(显式动作,会让对方现有签名立即失效) |
|
||||||
|
| `POST /api/v1/open-apps/{id}/toggle` | 启停接入方 |
|
||||||
|
| `GET /api/v1/open-apps/push-logs` | 推送记录(人工重推的依据) |
|
||||||
|
|
||||||
|
**F · 运维与调试动作(4 个)** —— 定时任务在测试里"等不起",所以都有手工入口(权限走运营侧 `log:view:all`)
|
||||||
|
|
||||||
|
| 接口 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| `POST /api/v1/schedules/run` | 手工触发定时任务(`job` 取值 close/urge/timeout/raw/usage/retry/expire) |
|
||||||
|
| `POST /api/v1/exports/cleanup-run` | 手工触发导出包过期清理(可重复执行) |
|
||||||
|
| `POST /api/v1/cleanup/jobs/json` | 整理器:直接提交 JSON(不走上传) |
|
||||||
|
| `POST /api/v1/cleanup/jobs/parse-preview` | 整理器:解析预览(不落库) |
|
||||||
|
|
||||||
|
### 17.2 有意偏离契约的地方(均已确认,2026-09-30)
|
||||||
|
|
||||||
|
| # | 偏离点 | 契约原口径 | 实现口径 | 依据 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| D-1 | `notifyBizType` 5/6 | §2.7 旧表「5 导出完成 / 6 催报」 | 「**5 催报 / 6 导出完成**」(与 §13.5、落库值一致) | F-30;按旧表实现催报去重会静默失效 |
|
||||||
|
| D-2 | `collect_file.source` | §3.6 旧表「入口来源」(聊天记录/相册/手机文件/代传) | 「**谁传的**」:1 本人 / 2 班主任代传 / 3 管理员代传 / 4 整理器(按建表注释) | F-38/F-39;§10.6 代传可追溯 |
|
||||||
|
| D-3 | 上传链路 | OSS 直传 + `upload-token` 凭证 | **服务端一步上传**;`upload-token` 定位为**预检+秒传+压缩建议**(非安全边界) | F-3/F-71/F-72;直传需配回调且把凭证交前端 |
|
||||||
|
| D-4 | `process` 语义 | 「命名+压缩+识别+校验总入口」 | 上传当场完成这些;`process` = **有原始件时的重新规范化** | F-3;默认 `keep-raw=false` |
|
||||||
|
| D-5 | `POST /validation/run` | `async=true` + jobId | **同步跑完**并返回 `checkedSubmissions/issuesFound/unresolvedLeft/elapsedMs` | F-51;全契约没有查 jobId 的接口 |
|
||||||
|
| D-6 | 撤回动作码 | §11.4 旧文 `action=3` | **`action=7`(撤回)**;`3` 是批量通过;状态 2/3 均可撤(2 撤的是初审) | F-7;时间轴不能混淆 |
|
||||||
|
| D-7 | §14 路径前缀 | §14 详情写 `POST /open/v1/...`(无 `/api/v1`) | 统一 `/api/v1/open/v1/...`(与 §1 及其余接口一致,网关一条规则) | F-65 |
|
||||||
|
| D-8 | `biz_type` 落库类型 | §6 字段表按数字码定义 | `collect_notify_record.biz_type` 是 `varchar(32)`,存**数字码的字符串形式**("3");接口层仍是数字 | F-26;DDL 不可改 |
|
||||||
|
| D-9 | 任务删除 | 已发布任务不可删(`40001`) | **已归档(status=3)的任务可删**:管理员显式动作 + 操作留痕(`DELETE_TASK`,记行数/文件数);**子表级联物理删**(名册/材料/审核记录等 14 张表)、**任务行本身软删**(占住 `task_code` 不复用 —— 编码是文件目录名) | F-14;否则任务列表只增不减 |
|
||||||
|
| D-10 | 租户域名 | 未写 | 能力已实现(Host 定位 + token 授权),**默认关闭**;启用四步见《交接摘要》§一·二十六 | F-10;客户决定先不做 |
|
||||||
|
| D-11 | 通知降级链 | 「订阅消息→公众号模板→短信→站内信」四级 | 一期**两级**(订阅消息→站内信);公众号模板需已认证服务号、短信需采购通道(商务动作),不假装可发 | F-28 |
|
||||||
|
| D-12 | OCR(§9.9) | 已定义 | **一期缓做**(等供应商选型,见《01》D1,需评估数据出境合规) | F-3;121 个接口里唯一未实现 |
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user