docs: 清掉三处"会静默过期"的小账 + check-test-profile 加固(F-98)

一、附录 A 从"手抄表"改成"可再生成的命令"
   check-api-parity.py 加 --extra,现算"代码有、契约 §4 没有"的接口 = 41 个
   (旧手抄表写 83/62/21,已差近一倍,而且没人会记得回来改它)。
   并按"谁去改什么"分六类 —— 只有 E 类(8 个)要客户定口径,其余是文档侧补记。

二、F-6 已修:frontend-pc 的 Mock 说明
   AGENTS.md 那句改正之外,连带修了 .agents/skills/project-api/SKILL.md ——
   它整篇仍以 vite-plugin-mock / mock/** 为主线,而该项目里 mock/ 目录与插件都已删除。
   只改指路牌、不改路的尽头,等于没改。

三、docs/03 两处文档内部口径冲突加 ⚠️ 注(不改口径,只把冲突摆明)
   §2 notifyBizType 的 5/6 与 §13.5、落库值不一致 → 实现以 §13.5 为准(催报去重按 5);
   §3.6/§9.5 的 collect_file.source 字段表(入口来源)与建表注释(谁传的)冲突 →
   实现以建表口径为准。两处都写明"待客户确认",见 F-30 / F-38 / F-39。

四、check-test-profile.py 加固(新增 F-98)
   · 子进程输出落盘(不再用没人读的 PIPE):失败时能看见"停在哪一行",
     成功时打出启动用时(基线 ~10 秒 / 72 行 / 9.5KB);
   · 开跑前按 jar + profile 清残留 JVM,且保留 dev 后端(E2E 正打着它);
   · 等待期间每 15 秒报一次进度,卡住时不留沉默。
   教训:随机端口这个防撞设计把残留 JVM 藏起来了 —— 撞不到端口就永远发现不了,
   于是表现成"时好时坏",而失败时连日志都读不到。

验证:python scripts/verify-all.py --with-e2e → 通过 8 / 失败 0 / 跳过 0
      (E2E 36 脚本 / 1548 断言 / 278s;prodguard 9/9;test profile 能起)
      python scripts/verify-docs.py → 22 项全部通过
This commit is contained in:
2026-09-30 09:10:34 +08:00
parent bfd7676974
commit db6a51d617
8 changed files with 258 additions and 58 deletions
+25 -3
View File
@@ -256,11 +256,22 @@
| `detectSource` | `1` 文件名 / `2` 形态 / `3` OCR 内容 / `4` VLM / `5` 用户指定 |
| `compressProfile` | `P1` 证件照 / `P2` 证件翻拍 / `P3` A4 扫描件 / `P4` 截图 / `P5` 兜底 |
| `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` 已过期 |
| `cleanupSourceType` | `1` 压缩包 / `2` 文件夹 / `3` 云盘链接 |
| `cleanupStatus` | `0` 排队 / `1` 解析中 / `2` 处理中 / `3` 已完成 / `4` 失败 |
> ⚠️ **`notifyBizType` 的 `5` / `6` 在本文件内部是不一致的**:本表写「`5` 导出完成 / `6` 催报」,
> 而 §13.5 消息中心的示例(`2` 临期提醒 / `3` 驳回)与落库值 `collect_notify_record.biz_type`
> 用的是「**`5` 催报 / `6` 导出完成**」。
>
> **实现以 §13.5 为准**(代码里是 `CollectMessage.BIZ_URGE = 5` / `BIZ_EXPORT = 6`,
> 催报去重按 `5` 走)。这不是文案问题:若按本表实现,催报会被写成"导出完成",
> **去重会静默失效**(老师点两下学生收到两条),而排查时从数据上完全看不出是码对不上。
>
> 需要客户确认以哪一边为准;若确认以本表为准,要一起改的是两个常量 + 已落库的历史数据。
> 记录见《08-完成度盘点与问题清单》**F-30**。
---
## §3 通用数据结构
@@ -461,9 +472,19 @@
| `vlmCalled` | bool | `vlm_called` | 是否调用过 VLM(**成本监控用**) |
| `validateStatus` | int | `validate_status` | 见 §2.7 `fileValidateStatus` |
| `issues` | array | — | 该文件的 `ValidationIssueVO[]` |
| `source` | int | `source` | `1` 聊天记录 / `2` 相册 / `3` 手机文件 / `4` 代传 |
| `source` | int | `source` | `1` 聊天记录 / `2` 相册 / `3` 手机文件 / `4` 代传 ⚠️ 见下方注 |
| `rawExpireTime` | string | `raw_expire_time` | **原图过期时间(默认 30 天)** |
> ⚠️ **`collect_file.source` 有两套互相冲突的口径**:本表写的是"从哪个入口选的"
> (`1` 聊天记录 / `2` 相册 / `3` 手机文件 / `4` 代传),而建表注释(`V1__business_tables.sql`)
> 与实现(`CollectFile.SOURCE_*`)写的是"**谁传的**":
> `1` 本人上传 / `2` 班主任代传 / `3` 管理员代传 / `4` 整理器导入。
>
> **实现以建表注释为准**:§10.6 要求代传必须可追溯,要回答的是"**谁**替他传的、
> **为什么**",而不是"他从哪个入口选的"—— 后者在同一个学生身上还会随操作路径变化。
> 两个口径都是 4 个值、含义完全不同,所以"照字段表写"会得到一份**看起来有数据、
> 但答案是错的**统计。记录见《08-完成度盘点与问题清单》**F-38 / F-39**。
**`ownership.verdict` 取值**(对应 PRD §6.1 归属裁决矩阵)
| verdict | 含义 | blockSubmit | 学生端表现 |
@@ -1784,7 +1805,7 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手
| `fileName` | string | ✅ | 原始文件名(**可能是乱的,正常**) |
| `fileSize` | int64 | ✅ | 字节 |
| `contentType` | string | ✅ | MIME |
| `source` | int | — | `1` 聊天记录 / `2` 相册 / `3` 手机文件 |
| `source` | int | — | `1` 聊天记录 / `2` 相册 / `3` 手机文件(⚠️ 落库口径见 §3.6 注:实际存的是「谁传的」= 1 本人上传 / 2 班主任代传 / 3 管理员代传 / 4 整理器导入) |
| `fileHash` | string | — | 客户端 SHA-256(**用于秒传与去重**) |
**响应**
@@ -2862,6 +2883,7 @@ X-Sign: HMAC-SHA256(appSecret, appKey + timestamp + nonce + body)
| 版本 | 日期 | 变更 |
|---|---|---|
| **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`)而非密文,宽度足够,无需改动。 |
| **V1.2** | **2026-09-28** | **错误码规范化**:`40007b` / `40007c` 带字母后缀,与 §1.3「`code` 为数字」的约定冲突(JSON 里会变成字符串,前端 `code === 0` 判断失效)。改为纯数字:**`40007b` → `40071`**(门禁限流)、**`40007c` → `40072`**(名单项已被绑定)。总码数不变,仍 **58 个**。 |
| V1.0 | 2026-09-28 | 首版。PRD §8 的 96 个接口全部展开字段级定义;**补齐追溯矩阵审计发现的 10 个缺口(新增 21 个接口,含 1 个登记遗漏)**;错误码由 20 个扩至 **50 个**;新增 §2 枚举全集、§3 通用数据结构、§15 错误码全集 |
+47 -32
View File
@@ -163,10 +163,15 @@
2. §9.9 OCR 标注「一期缓做(等 D1 选型)」,不要留着像已实现的样子;
3. 若后续要恢复直传(大文件/弱网),再单独立任务,不要在这轮混做。
### F-4【P1 · 文档与代码不一致】契约里没有的 19 个接口
### F-4【P1 · 文档与代码不一致】契约里没有的接口(首次盘点 19 个,现在 41 个)
- **现象**(代码有、03 文档 §4 清单没有):`GET /mp/scene-context`、`GET /auth/captcha`、`GET /auth/public-key`、`GET /mp/h5/authorize|callback|stub-login`(代替 `/mp/login`)、`PUT /users/{id}/status`、`GET /mp/files/submissions/{submissionId}`、`GET /mp/files/{id}/url|content`、`GET /audit/files/{id}/url|content`、`POST /tasks/{id}/materials/apply-template`、`GET /material-catalog`、`PUT /tasks/{id}/plan-scope`(清单里只列了 GET)。
- **现象**(代码有、03 文档 §4 清单没有):最典型的是 `GET /mp/scene-context`、
`GET /auth/captcha`、`GET /mp/h5/authorize|callback|stub-login`(代替 `/mp/login`)、
`POST /mp/files/upload`(代替 `upload-token` + 直传)、`GET /material-catalog`……
完整清单**现跑**:`python scripts/check-api-parity.py --extra`(当前 **41 个**)。
- **影响**:契约不是"当前系统长什么样",导致每次核对都要重新考古;前端/测试无法以文档为准。
⚠️ 更要紧的是:**这个数会继续长**(每加一处界面必需接口就 +1),
所以它必须以命令的形式存在,不能以表格的形式存在 —— 见 §九。
- **建议**:在 03 文档**新增一节「§17 实现补充与偏差记录」**,逐条列出这些接口(路径 / 权限 / 是否要补进 §4 清单),并在 §4 的合计口径里说明"121 是设计口径,另有 N 个实现补充"。改动后必须重跑 `python scripts/verify-docs.py`(它逐节核对接口数,加节时注意别改动 §4 的声称数)。
### F-5【P2 · 数据卫生】E2E 租户残留到"每个请求多一次解析"
@@ -175,11 +180,19 @@
- **影响**:本地演示/截图时列表里全是 `e2e-*` 名字;租户域名解析与登录"跨单位重名"分支被无意义数据搅扰。
- **建议**:`scripts/dev-clean-e2e.py` 增加一节"按名字前缀删测试租户与账号"(默认 dry-run,`--yes` 才真删),并在 `AGENTS.md` 里把"跑完 E2E 顺手清一次"写成固定动作。
### F-6【P2 · 文档误导】`frontend-pc/AGENTS.md` 里的 Mock 说明已过时
### F-6【P2 · 文档误导】`frontend-pc/AGENTS.md` 里的 Mock 说明已过时 —— **已修(2026-09-30)**
- **现象**:该文件写「开发默认 Mock(`/mock` 前缀)」,而 `AGENTS.md` 已注明"已过时,没有 Mock,dev 走 vite proxy 到 8080"。
- **影响**:AI/新人按旧说明写 Mock 代码或排查"为什么没有 `/mock`"。
- **建议**:直接改 `frontend-pc/AGENTS.md` 那段为"联调真实后端:`pnpm dev` + vite proxy → `http://localhost:8080`;无 Mock"。
- **已修**(按下面的建议做的,但**多修了一处**,见"连带"):
`frontend-pc/AGENTS.md` 的 `pnpm dev` 注释与"开发默认 Mock"那句,
改为"`/api` 代理到 `http://localhost:8080`,没有 Mock → **后端不起着就没有数据**",
并写明我们的接口模块在 `src/apis/xt/`。
- **连带(这才是根子)**:`frontend-pc/.agents/skills/project-api/SKILL.md` 整篇仍以
`vite-plugin-mock` / `mock/**` / `defineMock` 为主线 —— 而**该项目里 `mock/` 目录与插件都已删除**,
照它做会去建一个没人加载的 `mock/` 目录。只改 `AGENTS.md` 那句等于"把指路牌改了、
但路的尽头还是错的",所以在那份 skill 开头加了 ⚠️ 说明(保留原文,供 Gi 模板升级时做差异比对)。
核实方式:`frontend-pc/` 下无 `mock/`,`package.json` 与 `vite.config.ts` 里都没有 mock 关键字。
### F-7【P2 · 一致性】撤回的动作码与适用状态
@@ -397,6 +410,7 @@
| F-95 | ⚠️ **手册初稿里有两处"按设想的系统写",而不是"按真的系统写"**:① 我写了「同一张图重复传会被自动识别(秒传)」—— 而 `/mp/files/upload` **只计算 `file_hash`、没有去重查询**(秒传逻辑在 `/mp/files/upload-token` 里,而 F-72 已记过:上传接口并不消费它)。手册这么写会让学生以为重复传不占份数,然后撞上份数上限;② 我写了「代传**要**填原因(这是审计要求)」—— 而 `proxyReason` 是**选填**,手册说"必须填"会让老师以为界面坏了(因为不填也能提交) | 两处都改成与实现一致的说法。**并把这件事故化**:新增 `scripts/check-manual.py`,把"手册引用的错误码与提示文案是否真的存在"变成机械检查并进 CI —— 因为**手册是文档、文案在代码里**,改一次文案手册就静默失准,而没有任何东西会红(与 F-82「首页在说一件不真的事」同一类)。⚠️ 但要说清它**查不到什么**:它拦的是"手册写了代码里没有的东西",**拦不住"手册漏了新增的东西"** —— 那仍然要靠人 |
| F-96 | ⚠️ **本轮我连续犯了三个"没断言就往下走"的错**,形态不同、根因相同:① **文本补丁静默没生效**:`str.replace` 没命中时不报错、不返回失败,脚本照样打印"成功",而文件一个字没改 —— 随后的编译错("方法不存在")看起来像另一个问题;② **夹具构造失败却继续往下走**:`with_checksum` 传错位数后,失败发生在 300 行之外的名册导入报告里("当前 19 位");③ **`R.call` 的返回值没查 `code`**:建草稿失败时 `data` 是 `null`,报的是 `TypeError: 'NoneType' object is not subscriptable`,真正原因一个字都没打 | ①改成"每处替换都断言命中"(`must_replace`),并**回读校验**;②把 helper 改成**参数不合法就抛**(治本,前两次我只改调用点);③每个 `R.call` 先查 `code` 并把 `msg` 带进断言信息。**根因是同一条:把"我以为发生了"当成"确实发生了"** —— 而这三处的共同代价是**把排查引向错误的方向**(比直接报错贵得多) |
| F-97 | ⚠️ **我断言错了字段名,于是"缩略图功能"看起来没生效**:`FileUrl` 有 `url`(原图)与 `thumbUrl`(缩略图)两个字段,而我的验证脚本去检查 `d2["url"]` 里有没有 `imageView2` —— 按设计它**永远是原图链接**,所以那个断言必然为 false;更糟的是它连着把后面两条断言一起带错了(下载的是原图,于是"没变小") | 改成检查 `thumbUrl`,并**加一条反向断言**:`url` 不该带处理参数、`thumbUrl` 该带。⚠️ 这与 F-77(用错 CSS 选择器,差点修一个不存在的 bug)**是同一类**:**断言之前先确认"你在量的那个东西是对的"**。这次的隐蔽之处是字段名很像(`url` vs `thumbUrl`),而且接口确实返回 200、数据确实存在 —— 一切看起来都正常,只有断言是错的 |
| F-98 | ⚠️ **"随机端口"这个防撞设计带来一个反直觉的副作用**:`check-test-profile.py` 每次取**随机空闲端口**,于是上一次失败留下的 JVM **永远撞不到**这个端口、也就永远不会被发现 —— 它会一直占着内存与 CPU,直到某一次"怎么都起不来"(实测:连跑几轮后本机攒了 5~7 个 java,启动从 10 秒变成 180 秒超时;而**下一次跑又好了**,这正是"时好时坏"的来源,很容易被归因成"配置有问题")。同时那次失败**没法排查**:子进程输出走 `PIPE` 而没人读,事后才读又已被 kill 截断,最后只剩一句"180 秒内没探活成功" | ①开跑前**按 jar + profile 清残留**(只杀本项目的 jar,且**保留 dev 后端** —— E2E 正打着它,顺手杀掉等于把后面的检查全废掉);②子进程输出**落盘**(`backend/_test-profile.txt`,已被 `.gitignore` 的 `backend/_*.txt` 覆盖),失败时打印"停在哪一行 + 关键字命中 + 尾部 20 行",成功时打印**启动用时**(基线 ~10 秒 / 72 行 / 9.5KB,一旦变成几十秒先怀疑本机负载);③等待期间每 15 秒报一次进度,**卡住时不留沉默**。**教训:给"防撞"加一层机制时,要问一句"那它把什么东西藏起来了"** |
---
@@ -435,36 +449,35 @@ M7 收尾 52 人日、M5 剩余前端约 31 人日。**关键路径仍是"审核
---
## 九、附录 A · 代码有、契约清单没有的接口(F-4 的待办底稿)
## 九、附录 A · 代码有、契约 §4 清单没有的接口(F-4 的待办底稿)
> 用途:修 §17 补充节时**照着抄**,不用再重新统计一遍。
> 生成方式:`python scripts/check-api-parity.py` 打印的"代码注册接口"(当前 **83 个**),
> 减去能与 §4 清单对上的 **62 个**,差集恰好 **21 个**(下方逐条已核对;
> 首次盘点 19 个 + M6 导出新增的 A20/A21)。
> **这份清单是跑出来的,不是抄出来的:**
>
> ```bash
> python scripts/check-api-parity.py --extra
> ```
>
> 当前口径:代码共注册 **161** 个接口,契约 §4 声称 **121** 个(其中 **120** 个已在代码里),
> 差集 **41** 个。
>
> ⚠️ 上一版这里是"人工做一次差集、手抄成表"(写着 83 / 62 / 21)——
> 此后每加一个接口它就悄悄过期一次,最后差了近一倍,而且**没人会记得回来改它**。
> 手抄件会过期,命令不会。所以下面只留"为什么它们不在契约里"的分类结论;
> **要逐条清单时现跑那条命令。**
| # | 接口 | 说明 |
|:--:|---|---|
| A1 | `GET /api/v1/mp/scene-context` | 匿名扫码落地页查询(scene 三段式,只返回最少信息 + 限流) |
| A2 | `GET /api/v1/auth/captcha` | 图形验证码(连续失败 2 次后必填,§5.1 正文提过、清单未列) |
| A3 | `GET /api/v1/auth/public-key` | 密码 RSA 加密传输用的公钥 |
| A4 | `GET /api/v1/mp/h5/authorize` | H5 微信网页授权跳转(代替 `POST /mp/login` 的学生入口) |
| A5 | `GET /api/v1/mp/h5/callback` | 授权回调(token 放 URL fragment) |
| A6 | `GET /api/v1/mp/h5/stub-login` | **仅 dev** 打桩登录(`stub-enabled` + profile 双重保护) |
| A7 | `PUT /api/v1/users/{id}/status` | 启用/停用(清单里只有 `DELETE` 停用) |
| A8 | `GET /api/v1/tenants/{id}` | 租户详情(清单只列了列表/开通/修改/用量) |
| A9 | `POST /api/v1/mp/files/upload` | 实际的上传入口(替代 `upload-token` + OSS 直传) |
| A10 | `GET /api/v1/mp/files/{id}/url` | 取签名预览链接(学生端) |
| A11 | `GET /api/v1/mp/files/{id}/content` | 直出内容(学生端预览) |
| A12 | `GET /api/v1/mp/files/submissions/{submissionId}` | 按提交单列出材料 |
| A13 | `GET /api/v1/audit/files/{id}/url` | 审核端签名链接(单独通道,不复用学生端) |
| A14 | `GET /api/v1/audit/files/{id}/content` | 审核端直出内容 |
| A15 | `POST /api/v1/audit/submissions/batch-approve` | 批量通过(§11.3 标题里带过、§4.7 清单未单列) |
| A16 | `POST /api/v1/tasks/{id}/materials/apply-template` | 应用内置材料模板(幂等) |
| A17 | `GET /api/v1/material-catalog` | 材料目录(码/档位/格式/模板/体积范围) |
| A18 | `GET /api/v1/qrcodes/{scene}` | 匿名出图(清单只列了 `GET /tasks/{id}/qrcode`) |
| A19 | `GET /api/v1/mp/teacher/audit/reason-codes` | 教师端驳回原因字典(§11.7 有 PC 版,移动端未列) |
| A20 | `GET /api/v1/exports/precheck` | **导出前置检查(只检查不创建)** —— 界面靠它决定"开始导出"按钮是否可点(决定 D4) |
| A21 | `GET /api/v1/exports/{id}/file` | 导出包的**流式下载**(§12.6 只定义了取下载信息;真正下文件走这条) |
### 9.1 这 41 个按其「为什么不在契约里」分成六类
| 类 | 个数 | 为什么契约里没有 | 该怎么办 |
|:--|:--:|---|---|
| **A 材料通道:一步 multipart / 审核端独立一条** | 7 | 契约写的是"先传文件拿 `fileId` 再挂载"与 `upload-token` + OSS 直传;实现是**一步 multipart**,且审核端与学生端**各一条通道**(审核端要看防伪纹,学生端只看自己的) | §17 注明"实现按此写法"。**不要**改实现去迁就文档:直传要把密钥交给前端,反而更不安全 |
| **B 微信授权链路代替 `POST /mp/login`** | 4 | H5 走网页授权跳转(`authorize` → `callback`,token 放 URL fragment)、匿名落地页要 `scene-context`、dev 打桩登录只用于联调 | §17 注明;`stub-login` 要写明**仅非生产可用**(代码里另有一道 profile 判断) |
| **C 签名 URL:契约只定义了"取下载信息"** | 1 | `GET /exports/{id}/file` 才是真正下文件那条;§12.6 只定义了取下载信息 | 补进 §17,并写明 **URL 有有效期** —— 不能入库、不能发出去让人明天再打开 |
| **D 界面与接口必需、清单漏列** | 17 | 按钮要先知道"能不能点"(`exports/precheck`、`exports/preview`、`material-catalog`)、匿名出图、批量通过、教师端原因字典、名册项"标记不参与"这个可逆替代(`roster/{rid}/deactivate`)…… | **直接补进 §17 清单**,这类没有口径争议 |
| **E 契约链条本身断在第一步(要客户确认)** | 8 | `§12.7` 模板:`GET /notify/templates` 返回的内置模板**没有 id**,而 `PUT …/{id}` 要 id → 用户点"编辑"时根本拿不出 id(F-52);`§14` 的**接入方管理整块没写**(F-67) | 这 8 条**不是补文档能解决的**,要客户定口径(见 F-51 / F-52 / F-67) |
| **F 运维与调试动作** | 4 | 定时任务在测试里"等不起",所以留了手工入口(F-37):`schedules/run`、`exports/cleanup-run`、`cleanup/jobs/json`、`cleanup/jobs/parse-preview` | 补进 §17,标注"运维/调试用"(权限走运营侧的 `log:view:all`) |
> 合计 7 + 4 + 1 + 17 + 8 + 4 = **41**。
> 分类是按"谁去改什么"分的 —— 需要客户回答的只有 **E** 那一栏,其余三类是文档侧的补记。
---
@@ -499,6 +512,8 @@ M7 收尾 52 人日、M5 剩余前端约 31 人日。**关键路径仍是"审核
| 2026-09-30 | **压测(P2-2)**:新增 `scripts/load-test.py`(四场景、按端点统计、可调规模);**修掉 F-92 —— 门禁 IP 限流曾把整间教室挡住(60 人同 IP 只过 20 个)**,改成"只对失败计数";新增 F-93(压测会打到真实七牛空间)/F-94(**我自己的三个度量错误**:吞吐公式、混合 p95、用并发数字推断每行成本);`docs/09-部署与运维.md` 加 §9 压测记录 |
| 2026-09-30 | **用户手册与客服话术(P2-4)**:新增 `docs/10-用户手册与客服话术.md`(三角色手册 + 标准回答 + 培训提纲 + 上线检查单);新增 `scripts/check-manual.py`(手册引用的错误码/文案 ↔ 代码,已进 CI);新增 F-95(**手册初稿两处"按设想的系统写"**:秒传不存在、代传原因其实选填);docs 一致性检查从 21 项增至 22 项 |
| 2026-09-30 | **缩略图(P2-7)+ H5 独立 dev server(P2-9)**:`StorageService.thumbUrl` / `FileUrl.thumbUrl` / 审核端 `?thumb=true`;`frontend-mp` 加 `/api` 代理与 `VITE_API_BASE`;新增 `scripts/e2e-m9-thumb.py`(12 条,打到真实七牛);**同步 AGENTS.md 里"H5 没有独立 dev server"那句过期说明**;新增 F-96(**三个"没断言就往下走"**:补丁静默无效 / 夹具静默失败 /不查 code)/F-97(**断言错了字段名**,与 F-77 同类) |
| 2026-09-30 | **对外待决事项归拢 + 一键交付自检**:`docs/01` 新增 **E 组·第二轮待确认**(E1 `union_id` 是"H5 与小程序不能同时上线"的阻断项,E2 D3 限流口径更新、E3 二期时点,D3 那行划掉);§四 的每条卡点补上"**找谁 / 编号**";交接摘要新增 §一·二十八(坐标刷新 + 这 20 多轮最该记住的 7 条);新增 `scripts/verify-all.py`(一条命令跑完交付自检,**跳过项如实列出、不算失败**)。全量回归 **36 脚本 / 1548 断言 / 0 失败** |
| 2026-09-30 | **文档小账清理 + `check-test-profile` 加固**:① **附录 A 改为"可再生成"** —— `check-api-parity.py` 加 `--extra` 现算"代码有、契约 §4 没有"的接口(**41 个**;旧手抄表写 83/62/21,已差近一倍),并按"谁去改什么"分六类(只有 E 类要客户定口径);② **F-6 已修**:`frontend-pc/AGENTS.md` 的 Mock 说明改正,**并连带修 `.agents/skills/project-api/SKILL.md`**(整篇仍以 `vite-plugin-mock` 为主线,而 `mock/` 目录与插件都已删除 —— 只改指路牌不改路的尽头等于没改);③ `docs/03` 给 **§2 `notifyBizType` 的 5/6** 与 **§3.6/§9.5 的 `source`** 加 ⚠️ 注(**不改口径**,只把文档内部冲突摆明 + 指出实现以哪边为准,待客户确认);④ `check-test-profile.py` 改为**日志落盘 + 按 jar/profile 清残留 JVM** —— 随机端口让残留**永远撞不到端口**,于是静静攒着,直到某次"怎么都起不来"(新增 F-98) |
+43 -1
View File
@@ -2286,7 +2286,7 @@ M1 规则模板与任务克隆 → M2 报考范围与名册改删 → M3 提交
**§14 开放接口(认定机构对接,HMAC)** → 小程序登录与组织导入 → 工作台数据看板 →
CI/CD 与 test/prod 环境 → 压测 → 用户手册与客服话术 → 缩略图与 H5 dev server。
**每一轮的"做了什么 / 发现了什么"都在 docs/08 §六**,包含 97 条编号问题(F-1~F-97)。
**每一轮的"做了什么 / 发现了什么"都在 docs/08 §六**,包含 98 条编号问题(F-1~F-98)。
### 这 20 多轮里最该记住的几条
@@ -2337,3 +2337,45 @@ CI/CD 与 test/prod 环境 → 压测 → 用户手册与客服话术 → 缩略
⚠️ **不要在没有这些输入的情况下去"顺手实现"**:M4 的识别链路一旦按某家供应商写死,
换供应商要重写;`union_id` 没拿到之前,"按 union_id 认人"也无从下手
(字段与方案都已就位,见 `MpH5AuthService` 类头注释与 `sys_user.union_id`)。
---
## §一·二十九 提交入库 + 文档小账清理(2026-09-30 上午)
### 一、昨晚的改动已入库(18 个提交,**未推送**)
按"一轮一个 commit"的仓库惯例分组提交,`main` 上多了 18 个提交。
⚠️ **没有推送**:`git status -sb` 显示 `main...origin/main [gone]`(跟踪分支已失效),
要推之前先确认远端分支还在不在。
| 提交组 | 内容 |
|---|---|
| `chore(git)` | `.gitignore` 补 `backend/_*.txt` / `scripts/_*` / `shots/` —— 调试期临时产物,之前每次都要靠人记着别 add |
| `feat(export)` / `feat(audit)` / `feat(mp)` / `feat(join)` / `feat(notify)` / `feat(ops)` | M6 导出交付 / 审核台 / 学生端与班主任端 / B1 加入申请 / 通知与校验 / 日志与字典 |
| `feat(cleanup)` / `feat(open)` / `feat(task)` / `feat(schedule)` / `feat(file)` / `feat(pc)` | 历史整理器 / §14 开放接口 / 任务配置 / 定时任务 / 文件与缩略图 / 工作台看板 |
| `chore(ci)` / `test(scripts)` / `docs`×3 | CI 三阶段 6 job / 36 个 E2E 与 6 个机械校验 / 部署手册 + 用户手册 / 盘点与交接 |
### 二、这一轮清掉的文档小账(都是"会静默过期"的那类)
1. **附录 A 从"手抄表"改成"可再生成的命令"**:`check-api-parity.py` 加 `--extra`,
现算"代码有、契约 §4 没有"的接口 = **41 个**(旧手抄表写 83/62/21,已差近一倍),
并按"谁去改什么"分六类 —— **只有 E 类(8 个)要客户定口径**,其余都是文档侧补记。
教训:**手抄件会过期,命令不会**。
2. **F-6 已修**:`frontend-pc/AGENTS.md` 的 Mock 说明改正;**并连带修了
`.agents/skills/project-api/SKILL.md`** —— 它整篇仍以 `vite-plugin-mock` 为主线,
而 `mock/` 目录与插件都已删除。只改指路牌、不改路的尽头,等于没改。
3. **`docs/03` 两处内部口径冲突加了 ⚠️ 注(不改口径)**:
§2 `notifyBizType` 的 `5`/`6` 与 §13.5、落库值不一致 → **实现以 §13.5 为准**(催报去重按 5);
§3.6/§9.5 的 `collect_file.source` 字段表(入口来源)与建表注释(**谁传的**)冲突 →
**实现以建表口径为准**。两处都写明"待客户确认",见 F-30 / F-38 / F-39。
4. **`check-test-profile.py` 加固**(新增 **F-98**):日志落盘 + 按 jar/profile 清残留 JVM。
⚠️ 这条的教训值得单独记:**"随机端口"这个防撞设计把残留 JVM 藏起来了** ——
撞不到端口就永远发现不了,于是表现成"时好时坏",而失败时连日志都读不到
(走 `PIPE` 没人读、事后读已被 kill 截断)。现在:基线 **~10 秒**起,
失败时能看到"停在哪一行",等待期间每 15 秒报一次进度。
### 三、还没做的(顺序未变)
`docs/08` §四 剩下的**全部是外部依赖**(OCR 选型 / 小程序类目 / `union_id` / 真机 / 二期),
另有 4 项"**没测过**":导出大包打包、上传并发、5000 行名册导入、长跑稳定性
(`docs/09` §9.5 逐条写明"没测就是没测")。