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:
+47
-32
@@ -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) |
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user