docs: 同步交接摘要 / PRD / 原型,并记录品牌改名「深学在线」

- 交接摘要补 §一·三十五~§一·三十八:角色与授权、需求收敛(4 角色 / 10 菜单 / 取消二期)、
  PC 端重搭后的 UI 修复与卡片式基线、品牌改名与新标识
- PRD 与功能逻辑卷只改标题行与产品代号线;**文件名不动** —— verify-docs.py 按路径读,
  全库还有 20+ 处按文件名引用,改名会连带炸脚本
- 原型 index / admin-web / teacher-app / student-app 的品牌区改「深学在线」,
  admin-web 的 .mark(10 处)换成内嵌的新标 data URI,保持原型"单文件自包含"的约定
- 根 AGENTS.md 与 frontend-pc 的规范同步:接线检查补第 ⑨ 项(SVG 资产是不是合法 XML ——
  SVG 注释里出现连续两个减号就 not well-formed,图标裂开而控制台一句话都不报)、
  「最容易写错的约定」6 -> 7 条(新增品牌标)
- deploy/docker-compose.yml 只改头注释里的项目名(compose 的 name 与七牛对象前缀都没动)
This commit is contained in:
2026-10-10 16:49:04 +08:00
parent 1f3c485ceb
commit 7df078e0ea
22 changed files with 1158 additions and 1093 deletions
+44 -16
View File
@@ -4,6 +4,32 @@
> 口径:以**代码与实测**为准(文档是需求源,但与代码冲突处以代码为准,冲突项在 §五 单列)。
> 盘点时间:2026-09-29 · 基线提交 `5213bc2`
> ## ⚠️ 2026-10 需求收敛(**本文件以下数字与条目的最新口径**)
>
> 客户要求收敛范围后,**代码 / 库 / E2E / 原型 / 文档已同步**,本文件的盘点结论随之变化:
>
> | 项 | 本文件写的(当时) | **现在** |
> |---|---:|---:|
> | 契约接口 | 129 | **119** |
> | 错误码 | 59 | **54** |
> | 权限码 | 30 | **28** |
> | 内置角色 | 6 | **4** |
> | PC 业务菜单 | 12 | **10** |
> | 小程序页面 | 10 | **9** |
> | 原型屏 | 32 | **29** |
> | E2E 脚本 | 39 | **37**(断言 1698 → **1595 条**,0 失败 0 跳过) |
>
> **删除的三块**:① 内置角色「副班主任 `DEPUTY_TEACHER`」「审核员 `AUDITOR`」;
> ② **历史整理器**(`module/cleanup`,5 接口 + `cleanup:run` + `collect_cleanup_job` 表 + PC 页 + 原型屏);
> ③ **加入申请**(`module/join`,5 接口 + `join:audit` + `collect_join_request` 表 + PC 页 + 学生端 `pages/join`
> + 错误码 `40037/40038/40039/40040/40042` + 两个站内信模板)。
> 另有 `collect_task_org.deputy_user_ids` **整列删除**(审核范围三级回退 → 两级),
> 以及 **二期整体取消**(社会收集 / 社会评价 / 专项职业能力 / 数据范围可配 …一律不做)。
>
> ⇒ **本文件里凡提到这三块的条目(施工记录 §六、复盘、F-102 / F-107 等)都是历史记录**,
> 记录的是"当时做过/当时的问题";**结论以本节与《交接摘要》§一·三十六 为准**。
> 相关条目已在原位加注(未删行,便于对账)。
---
## 一、盘点方式(复现命令)
@@ -11,7 +37,7 @@
| # | 盘点项 | 命令 / 依据 | 本次结果 |
|:--:|---|---|---|
| 1 | 后端回归 | 依次跑 `scripts/e2e-*.py`(18 个) | **753 条断言、0 失败** |
| 2 | 接口实现度 | `python scripts/check-api-parity.py`(解析 §4 的 121 个接口 ↔ 全部 controller 的 `@*Mapping`) | 首次盘点 **58 / 121**;完成 M6 导出后为 **62 / 121(51%)**;另有 21 个接口在代码里但契约清单没有 |
| 2 | 接口实现度 | `python scripts/check-api-parity.py`(解析 §4 的**清单行** ↔ 全部 controller 的 `@*Mapping`;**当前口径 119**) | 首次盘点 **58 / 121**;M6 导出后 **62 / 121**;**2026-10 收敛后:契约 119 / 已实现 118 / 缺口 1**(唯一缺口 `/mp/ocr/idcard`) |
| 3 | 管理端 | `pnpm typecheck` / `pnpm build` | 0 错 / 构建通过 |
| 4 | 库表 | `docker exec xt-pg psql -U fill -d filldb` | 26 张表(20 业务 + `sys_*` 业务 5 + flyway 1),其中 4 张业务表**恒为 0 行** |
| 5 | 文档一致性 | `python scripts/verify-docs.py` | **20 项全通过**(含本文件) |
@@ -24,7 +50,7 @@
> 「脚本审计前先验证脚本本身」是同一类错误。
>
> 现在这件事固化成 `scripts/check-api-parity.py`(**可复现**):
> 它按 §4 总览逐条比对代码,结束时**自检"契约必须恰好 121 个"**,不等就退出码 1 ——
> 它按 §4 总览逐条比对代码,结束时**自检"契约必须恰好 119 个"**(2026-10 由 129 改准)
> 这样"文档改了表格格式导致解析漏项"也会当场暴露,而不是给出一份看着对的错报告。
---
@@ -33,12 +59,12 @@
| 层 | 已完成 | 未完成 | 完成度 |
|---|---|---:|---:|
| 后端接口 | 120 个(M0/M1/M2/M3 主链路 + M5 审核闭环与班主任看板 + M6 导出、分卷与过期清理 + B1 加入申请 + 学生端我的任务/撤回/进度 + 通知消息与催报 + 名册改删 + 操作/登录日志 + 字典管理 + 代传与教师端文件访问 + 历史数据整理器 + 校验问题清单 / 通知模板与手工发送 / 微信回调 + 任务克隆 / 任务统计 / 规则模板 / 名册导入模板 / 审核统计 / PC 端代传 + 开放接口(认定机构对接) + **小程序登录与绑手机号 / 组织导入 / 租户用量 / 文件视图·直传凭证·批量处理**) | 1 个 | 99% |
| 后端里程碑 | **契约 §4 的 121 个接口里 120 个已实现**(M0~M3 主链路、M5 审核闭环、M6 导出与交付、M7 整理器与运维接口、开放接口、小程序登录与文件接口) | **只剩 M4 的 `/mp/ocr/idcard`**(等 OCR 选型)+ 前端页面与 CI/CD | 约 7.5/8 |
| 管理端(PC) | 登录/工作台/组织/账号/租户/收集任务 + 3 个配置抽屉 + 名册与报考范围 + 审核台 + 导出中心 / 加入申请 / 通知记录 / 操作日志 / 字典管理 | 角色、**历史整理器页面**(后端已就绪)、表单设计器 | 约 92% |
| 学生端(H5/小程序) | 落地页、门禁核验、填报、**我的任务 / 撤回 / 进度 / 消息中心 / 加入申请** | 临期提醒页、催报 | 约 85% |
| 后端接口 | 118 个(M0/M1/M2/M3 主链路 + M5 审核闭环与班主任看板 + M6 导出、分卷与过期清理 + 学生端我的任务/撤回/进度 + 通知消息与催报 + 名册改删 + 操作/登录日志 + 字典管理 + 代传与教师端文件访问 + 校验问题清单 / 通知模板与手工发送 / 微信回调 + 任务克隆 / 任务统计 / 规则模板 / 名册导入模板 / 审核统计 / PC 端代传 + 开放接口(认定机构对接) + 小程序登录与绑手机号 / 组织导入 / 租户用量 / 文件视图·直传凭证·批量处理);**已删除**:B1 加入申请、历史数据整理器(2026-10) | 1 个 | 99% |
| 后端里程碑 | **契约 §4 的 119 个接口里 118 个已实现**(2026-10 收敛后的口径;原为 129 里 128) | **只剩 M4 的 `/mp/ocr/idcard`**(等 OCR 供应商选型)+ 前端页面与 CI/CD | 约 7.5/8 |
| 管理端(PC) | 登录/工作台/组织/账号/租户/收集任务 + 3 个配置抽屉 + 名册与报考范围 + 审核台 + 导出中心 / 通知记录 / 操作日志 / 字典管理 / **角色与授权**(**纯 Vue 3 + Naive UI**,2026-10 重搭);**已删除**:加入申请页、历史整理器页(2026-10) | 表单设计器已上线 | 约 100% |
| 学生端(H5/小程序) | 落地页、门禁核验、填报、**我的任务 / 撤回 / 进度 / 消息中心**(**加入申请页已删除**,门禁失败只提示联系老师) | 临期提醒页、催报 | 约 90% |
| 班主任端(小程序) | 看板 / 审核卡片流 / 详情与驳回(右滑通过、左滑不绑驳回)+ **代传材料 / 催报 / 学生材料预览**(后端与 MP 页面均已就绪) | 临期提醒入口 | 约 95% |
| 基础设施 | PG 16 + Redis 7 + 七牛(`provider=qiniu` 已验)、Flyway **V1~V16**、本地存储兜底、导出产物落盘 + 流式下载 + 分卷与过期清理、**33 个 E2E 脚本(1489 条断言)+ 3 个机械校验脚本** | CI/CD、test/prod 环境、压测、灰度 | 约 58% |
| 基础设施 | PG 16 + Redis 7 + 七牛(`provider=qiniu` 已验)、Flyway **V1~V19**、本地存储兜底、导出产物落盘 + 流式下载 + 分卷与过期清理、**37 个 E2E 脚本(断言数以 `verify-all` 为准)+ 4 个机械校验脚本** | CI/CD、test/prod 环境、压测、灰度 | 约 58% |
**契约接口实现度(按 §4 分类)**
@@ -74,7 +100,8 @@
## 四、未完成(按"能不能上线"排序)
> ⚠️ **截至 2026-09-30,剩下的全部是外部依赖或二期范围** —— 没有一条是"我们还没写"。
> ⚠️ **截至 2026-09-30,剩下的全部是外部依赖或(当时的)二期范围** —— 没有一条是"我们还没写"。
> **2026-10 更新**:其中的"二期范围"已**整体取消**(不再排期),只剩**外部依赖**(OCR 供应商 / 小程序类目 / `union_id` / 真机验证)。
> 所以每条都指向 `docs/01-客户与机构确认函.md` 的具体编号:**卡在谁那里、要谁做什么**。
>
> | 剩项 | 卡点 | 找谁 | 编号 |
@@ -107,10 +134,10 @@
| **P1-1** | **M4 识别与校验**(形态 / OCR / VLM / 串档)—— **现在唯一的大块**(§4.8 的 7 个缺口全在这里) | M4-01~07、M4-09(→ `docs/01` **D1**;识别失败一律放行) |
| ~~P1-2~~ | ~~校验问题接口(重跑/清单/导出)~~ → ✅ **已完成**(§六 6.1 第 6i 项)。**剩余**:M4 的**识别类**校验本身(等 D1 选型) | §12.1~12.3 |
| ~~P1-3~~ | ~~通知与消息中心、订阅额度、催报~~ | ✅ **已完成**(§13.5 三个 + §13.6 一个 + §8.6/§10.7 催报四个 + §13.3 通知记录一个)。**剩余**:§4.6 的教师端文件访问(`/mp/teacher/files/{fileId}`)—— 见 F-28:钉住降级链后两级的不是代码量,而是"短信/公众号通道还没有商务结论" |
| P1-4 | ~~B1 加入申请(客户唯一功能增量)~~ → **已完成**(§六 6.1 第 3 项) | §8.7 的 5 个接口 + 学生 STEP 03B + 管理端审批页 **全部落地** |
| ~~P1-4~~ | ~~B1 加入申请(客户唯一功能增量)~~ → 曾完成,**2026-10 需求回退已整体删除** | — |
| ~~P1-5~~ | ~~学生端「我的任务列表 / 撤回 / 进度与问题清单」~~ → ✅ **已完成**:`pages/tasks`(含「撤回修改」,`canWithdraw` 由服务端算)+ `pages/progress`;`scripts/e2e-m3-mytask.py` 42 条 | §9.1、§9.11、§9.12 |
| ~~P1-6~~ | ~~班主任端看板与文件访问~~ → ✅ **已完成**(§六 6.1 第 6g 项:看板 / §10.6 代传 / §10.7 催报 / §4.6 学生材料预览) | §10.1、§10.6、§10.7 |
| ~~P1-7~~ | ~~历史数据整理器(差异化获客功能)~~ → ✅ **后端已完成**(§六 6.1 第 6h 项,含串档检测与体检报告)。**剩余**:PC 端页面 | M7-06/07、§13.2 的 5 个接口 |
| ~~P1-7~~ | ~~历史数据整理器(差异化获客功能)~~ → 曾完成,**2026-10 已整体删除** | — |
| ~~P1-8~~ | ~~操作日志与登录日志查询、字典管理~~ → ✅ **已完成**:操作日志**真的在写**(`sys_operation_log` 实测 523 条,最近一条是几分钟前;F-1 已在前面几轮修掉),写入口统一走 `OperationLogService`;`views/log` 与 `views/dict` 均已可用 | M7-03/04/05、§13.3、§13.4 |
| ~~P1-9~~ | ~~任务统计与数据看板~~ → ✅ **已完成**(§六 6.1 第 6j 项 + 本轮 6o 的工作台看板页) | §7.11、M7-01/02 |
| ~~P1-10~~ | ~~名册改/删、导入模板下载~~ → ✅ **已完成**:`RosterEditService`(改姓名/证件号/删除,都写操作日志)、名册导入模板下载(表头固定、必填列标红、带 9 条规则说明页);`scripts/e2e-m2-roster.py` 67 条 | §8.3、§8.4、§8.5 |
@@ -119,17 +146,17 @@
| ~~P1-13~~ | ~~开放接口(认定机构对接)~~ → ✅ **已完成**(§六 6.1 第 6k 项;另补了契约漏掉的接入方管理,见 F-67) | §14 的 4 个接口(HMAC 加签) |
| ~~P1-14~~ | ~~学生端 `/mp/login`(小程序端登录)+ 手机号绑定~~ → ✅ **已完成**(§六 6.1 第 6l 项) | §5.3、§4.1 |
### 4.3 P2 · 上线收尾与二期
### 4.3 P2 · 上线收尾(原"与二期"已取消)
| # | 未完成项 | 说明 |
|:--:|---|---|
| ~~P2-1~~ | ~~CI/CD + test/prod 环境 + 部署 + 数据迁移预案~~ → ✅ **本轮完成**:`.gitlab-ci.yml`(6 个 job,三阶段;**e2e 阶段跑全套 1516 条断言**,每次随机生成密钥)+ `application-test.yml` / `application-prod.yml`(**零密钥**)+ `ProdEnvCheck`(启动自检,6 条"配错了也能启动"的配置)+ `docs/09-部署与运维.md`(发布 / 密钥轮换 / 迁移与回滚 / 灰度 / 排障 / 首次接入 CI 的检查单) | M0-04、M7-10 |
| ~~P2-2~~ | ~~压测与调优~~ → ✅ **本轮完成**:新增 `scripts/load-test.py`(四个场景、按端点分别统计、可调规模);**压测发现并修掉一个会让整间教室被挡住的限流缺陷**(F-92)。结论:**后端没有 N+1、DB 在 2ms 以内**,并发下的延迟是排队效应。尚未测:导出大包、上传并发、5000 人名册、长稳(见 docs/09 §9.5) | M7-09 |
| ~~P2-3~~ | ~~定时任务(PRD 附录 E)~~ → ✅ **已完成**:**8 项已实现**(任务自动截止 / 自动催报 / 审核超时提醒 / 原始件清理 / **用量快照** / **压缩失败重试** / **租户到期提醒** / 导出包清理 + 开放接口推送重试),`POST /schedules/run` 可手工触发任意一项。⚠️ 第 9 项「待认领名册清理」**不做** —— 它对应的"自助认领"机制已在 PRD V1.5 废除(见 F-91) | M7-12 |
| ~~P2-3~~ | ~~定时任务(PRD 附录 E)~~ → ✅ **已完成**:**8 项已实现**(任务自动截止 / 自动催报 / 审核超时提醒 / 原始件清理 / **用量快照** / **压缩失败重试** / **租户到期提醒** / 导出包清理 + 开放接口推送重试),`POST /schedules/run` 可手工触发任意一项。⚠️ 第 9 项「待认领名册清理」**不做** —— 它对应的"自助认领"机制已在 PRD V1.5 废除(见 F-91)。**二期另加第 9 个任务**「内置角色授权同步」(每日 03:20,`runNow` 名 `role`,见 6v) | M7-12 |
| ~~P2-4~~ | ~~用户手册 / 培训材料 / 客服话术~~ → ✅ **本轮完成**:`docs/10-用户手册与客服话术.md`(三角色操作手册 + 高频问题标准回答 + 30 分钟培训提纲 + 上线首日检查单);并新增 `scripts/check-manual.py`(**机械核对手册引用的错误码与提示文案是否真的存在**,已进 CI) | M7-11 |
| ~~P2-5~~ | ~~导出包过期清理~~ → ✅ **已完成**:`ExportCleanupService.cleanupExpired()`(`@Scheduled` 每小时第 17 分跑) | M6-07 |
| ~~P2-10~~ | ~~导出分卷(`splitSizeMb > 0`)~~ → ✅ **已完成**:`SplitZipWriter` 会产出 `{base}-part1.zip`、`{base}-part2.zip`…;`scripts/e2e-m6-export-split.py` 35 条 | M6-07 |
| P2-6 | 二期:社会收集 / 社会评价 / 专项职业能力 | 一期范围外(`collectMode=2`、`certType=2,3` 返回 `40044`)→ 启动时点见 `docs/01` **E3** |
| ~~P2-6~~ | ~~二期:社会收集 / 社会评价 / 专项职业能力~~ | **已取消**(2026-10 不再设计二期;传 `collectMode=2`、`certType=2,3` 返回 `40044`) |
| ~~P2-7~~ | ~~七牛实时样式缩略图~~ → ✅ **本轮完成**:`StorageService.thumbUrl()`(**参数拼在签名之前**,拼错顺序会 401 且看起来像网络问题);`FileUrl` 同时给 `url` 与 `thumbUrl`;审核端 `?thumb=true`。**实测 1454KB → 36KB(2.4%)**,1600×1200 → 400×300 等比不变形 | M7-09 附带 |
| P2-8 | 小程序端 `compressImage` 真机验证;D6 三通道体积上限实测 | 需真机(→ `docs/01` D6) |
| ~~P2-9~~ | ~~学生端 H5 没有独立 dev server~~ → ✅ **本轮完成**:`frontend-mp` 加 `/api` 代理 + `.env.development` 把 `VITE_API_BASE` 设空串,`pnpm dev:h5` → http://localhost:5174 可直接联调;三端行为(dev server / 构建产物 / 小程序)都验过,且 **AGENTS.md 里那句过期的说明已同步** | 开发体验 |
@@ -174,7 +201,7 @@
- **影响**:契约不是"当前系统长什么样",导致每次核对都要重新考古;前端/测试无法以文档为准。
⚠️ 更要紧的是:**这个数会继续长**(每加一处界面必需接口就 +1),
所以它必须以命令的形式存在,不能以表格的形式存在 —— 见 §九。
- **建议**:在 03 文档**新增一节「§17 实现补充与偏差记录」**,逐条列出这些接口(路径 / 权限 / 是否要补进 §4 清单),并在 §4 的合计口径里说明"121 是设计口径,另有 N 个实现补充"。改动后必须重跑 `python scripts/verify-docs.py`(它逐节核对接口数,加节时注意别改动 §4 的声称数)。
- **建议**(已落地):在 03 文档新增「§17 实现补充与偏差记录」,逐条列出这些接口,并在 §4 的合计口径里说明"119 是设计口径,另有 38 个实现补充"(2026-10 复核数)。
- ✅ **已闭环(2026-09-30)**:契约新增 **§17 实现补充与偏差记录** —— 41 个接口按六类分组(清单由 `check-api-parity.py --extra` 生成,写明「手抄件会过期,以命令输出为准」),外加 12 条已确认偏离 D-1~D-12(含租户域名一行、`biz_type` 落库字符串、§14 路径前缀)。
@@ -255,7 +282,7 @@
| 3 | **P1-5 学生端「我的任务 / 撤回 / 进度」** | 后端 `StudentTaskMapper` + `StudentTaskService` + `StudentTaskController`(§9.1 `/mp/tasks`、§9.11 `/mp/submissions/{id}/withdraw`、§9.12 `/mp/submissions/{id}/progress`);学生端新页 `pages/tasks/tasks.vue`(我的填报:待办数/状态/驳回原因/截止提醒/撤回)与 `pages/progress/progress.vue`(材料清单 + 问题 + 时间轴);落地页在"有登录态但没 scene"时改为进「我的填报」 | **`scripts/e2e-m3-mytask.py` → 42/42**;H5 `build:h5` 通过且产物含 `pages-tasks-tasks` / `pages-progress-progress` |
| 3b | **P1-4 B1 名单加入申请** | 见 §6.1 上一轮记录(后端 + 学生 H5 + 管理端审批 + V7 迁移) | `scripts/e2e-m2-join.py` → 50/50 |
| 4 | **P0-3 班主任端(小程序)看板 + 审核卡片流** | 后端 `TeacherDashboardMapper` + `TeacherDashboardService` + `MpTeacherDashboardController`(§10.1 看板);小程序新页 `pages/dashboard/dashboard.vue`(已交率与已通过**分开呈现**、四个待办角标、班级卡片、截止提醒)、`pages/audit/audit.vue`(卡片流:**右滑通过 / 左滑不绑驳回**、一键通过只带无问题项)、`pages/audit-detail/audit-detail.vue`(材料预览 + 问题清单 + 改动明细 + 驳回弹层:原因快选 + 二次确认);「我的填报」按 `/auth/me` 的角色给出老师入口 | **`scripts/e2e-m5-teacher-dashboard.py` → 24/24**;H5 `build:h5` 通过且产物含 `pages-dashboard-dashboard` / `pages-audit-audit` / `pages-audit-detail-audit-detail` |
| 5 | **可复现的接口计数** | `scripts/check-api-parity.py`(契约 §4 的 121 个接口 ↔ 全部 controller,自带"必须恰好 121"的自检) | `python scripts/check-api-parity.py` 退出码 0 |
| 5 | **可复现的接口计数** | `scripts/check-api-parity.py`(契约 §4 的 129 个接口 ↔ 全部 controller,自带"必须恰好 129"的自检) | `python scripts/check-api-parity.py` 退出码 0 |
| 6 | **界面验收用的演示数据** | `scripts/dev-seed-audit.py`(走真实接口摆出:任务 + 班级 + 材料 + 名册 + 门禁绑定 + 3 份已提交填报单 + 1 份已初审,含一个**演示班主任**账号) | 脚本自身对每步 `code` 断言;审核接口回读确认(见 6.3) |
| 6b | **P1-3 通知与消息中心** | 后端 `module/notify/`:`CollectMessage` 实体 + `CollectMessageMapper`(批量已读)+ `SubscribeQuotaMapper`(额度 UPSERT / 原子扣减)+ `NotifyRecordMapper` + `NotificationService`(降级链)+ `NotifyController`(§13.5 消息中心 3 个 + §13.6 订阅额度 1 个);**V8 迁移**新建 `collect_message`;`AuditService` 的通过/驳回真的会通知学生;小程序新增 `pages/messages/messages.vue` + `utils/subscribe.ts`(提交后索取授权) | **`scripts/e2e-m5-notify.py` → 48/48**;H5 `build:h5` 通过且产物含 `pages-messages-messages` |
| 6c | **P1-3 剩余:催报与通知记录** | 后端 `UrgeService` + `UrgeMapper` + `UrgeDtos` + 两个 Controller(§8.6 定向/一键催报、§10.7 教师端催报与历史、§13.3 管理端通知记录);`NotificationService` 的发送记录补上 `task_id`(催报去重要用它);PC 新增 `views/notify/index.vue`(通知记录:状态 3 与 2 分开显示)+ [notify.ts](frontend-pc/src/apis/xt/notify.ts) | **`scripts/e2e-m5-urge.py` → 40/40**;`vue-tsc` 0 错、`eslint` 0 错 |
@@ -270,6 +297,7 @@
| 6j | **P1-11 / P1-9 任务克隆、任务统计、规则模板、名册导入模板、审核统计、PC 端代传** | **V15 迁移**给 `collect_rule_template` 补审计列(该表 V1 就建好、列与契约 §7.13 的 FORM/MATERIALS/NAMING 三部分逐项对应,见 F-50)并随迁移带两条**公共模板**;`TaskCloneService`(克隆**配置**、**刻意不克隆业务数据**)、`TaskStatisticsService`(补上 `abnormalCount` 与 `avgAuditHours` —— 这两个字段以前恒为 null,因为问题表与审核记录还不存在)、`RuleTemplateService`(可按 parts 只应用一部分、材料清单**覆盖式合并**)、`RosterTemplateService`(表头固定第 1 行 + 必填列标红 + 9 条填表规则页)、`AuditStatisticsService`、`TeacherProxyService.proxyUploadAsAdmin`;并修掉 F-61(初审待办会列出**批不了**的单子) | **`scripts/e2e-m7-taskconfig.py` → 69/69**(含待办范围判据的回归防线) |
| 7 | **全量回归** | — | **33 个脚本 / 1489 条断言 / 0 失败**(753 + 导出 60 + 加入申请 50 + 学生端 42 + 看板 24 + 通知 48 + 催报 40 + 日志 44 + 字典 53 + 分卷 35 + 代传 33 + 整理器 54 + 校验 70 + 任务配置 69 + 开放接口 60 + 组织与文件 54) |
| 6v | **二期「角色与授权」(FR-205)** | **V18 迁移**:`sys_role` 加 `user_type`(账号的 `user_type` 一直是从角色反推的,自定义角色不在代码映射里 → 没有这列就"角色建得出来、发不出去")、新建 `sys_role_menu`(菜单不做权限的附属,与 `sys_role_permission` 同构)。**权限判定**:`PermissionChecker` 合并两个来源 —— 内置角色按代码、自定义角色按库(`DbRoleGrantSource`,Redis 缓存 + 写后失效;**空结果也缓存**,否则已删角色的 token 会把库打穿;Redis 挂了退化成直读库,两个方向都安全)。**内置角色授权同步**:`RoleProvisioner` 从"已存在就跳过"改成幂等回填 + `ScheduleJobs` 每日 03:20 同步(修掉"代码改了权限、老租户永远停在旧的那套"这种**不会报错的**漂移)。**8 个接口**(`docs/03` §4.12/§5.10)+ `40045`;学校租户授予平台级权限码 / 配置平台专用菜单 / 建平台运营类角色 → `40301`;**防自锁**(不许去掉自己最后的 `role:manage` 权限或 `role` 菜单、不许停用/删除它、改账号角色时同理保护 `user:manage`)。**PC 页** `views/role/`(内置只读 + 权限矩阵 + 菜单勾选 + 保存即生效)+ 原型 ⑪;账号页的可选角色 / 用户类型 / 角色名全部改读库(删掉前端三份写死的镜像表)。⚠️ **`dataScope` 本期只展示**:范围仍由"权限码 + 任务里被指派了哪些班"决定,收口成 resolver 是下一批 | **新增 `scripts/e2e-m10-role.py` → 77/77**(含"改授权后**同一个 token** 立刻生效""停用角色立刻失效""内置角色只读""越权与自锁""删除守卫 40045""内置角色权限以代码为准 + 同步回填幂等");**浏览器实测**(新建自定义角色 → 账号页出现该角色 → 改权限落库 → 删除);`pnpm typecheck` 0 错、`eslint` 0 错;`verify-docs` / `check-wiring` / `check-api-parity`(契约 129)全绿 |
| 6u | **P2-7 缩略图 + P2-9 H5 独立 dev server** | **缩略图**:`StorageService.thumbUrl(path, expire, maxLongSide)`(七牛在**签名之前**拼 `imageView2/2/w/h`;本地存储回退原图,让调用方不必判断"当前是什么存储");`FileUrl` 加 `thumbUrl`/`thumbMaxLongSide`;审核端 `?thumb=true`(**默认 false** —— 详情页要看防伪纹)。**H5 dev server**:`vite.config.ts` 加 `/api` 代理 + `.env.development` 把 `VITE_API_BASE` 设空串(`uni.request` 不能传相对路径,所以空串时仍补 `location.origin`,由代理兜住) | **缩略图实测 1454KB → 36KB(2.4%)**、400×300 等比;新增 `scripts/e2e-m9-thumb.py` → **12/12**(打到真实七牛,含"参数若拼在签名之后会 401"这一条);H5 dev server 实测 5174 可访问且 `/api` 代理到 8080;**H5/小程序两端构建都验过**(小程序产物仍是写死地址) |
| 6t | **P2-4 用户手册 / 培训材料 / 客服话术(M7-11)** | 新增 `docs/10-用户手册与客服话术.md`:学生/班主任/管理员三份操作手册 + 「高频问题 → 标准回答」+ 30 分钟培训提纲 + 上线首日检查单。⚠️ 文案与错误码**逐条取自代码**,并在写完后**对着代码核了一遍** —— 抓到两处与实现不符的说法(见 F-95);另把这项核对固化成 `scripts/check-manual.py` 并进 CI | **9 个错误码 + 14 句提示文案全部与代码一致** |
| 6s | **P2-2 压测(M7-09)** | 新增 `scripts/load-test.py`(门禁 / 读接口 / 上传 / 导出四场景;**按端点分别统计**;`--rows` 可调规模)。**压测抓到一个真缺陷并修掉**:门禁 IP 限流计的是"所有请求",于是 **60 个不同学生同一出口 IP 同时核验只过了 20 个** —— 而"一个班连同一个 WiFi"正是首次落地第一分钟就会发生的事(见 F-92)。**调优结论(三条独立证据)**:SQL 条数恒为 3(与 pageSize 无关)→ 无 N+1;单请求延迟与页大小无关(20/50/200 → 47/36/44ms);`EXPLAIN ANALYZE` 显示 DB 侧 ≤2ms。所以并发下的 p50 156~375ms 是**排队效应**,要提吞吐得加机器而不是改 SQL | **修前 20/60 → 修后 60/60**;门禁 E2E 仍 29/29 绿 |
@@ -277,7 +305,7 @@
| 6q | **P2-1 CI/CD + test/prod 环境(M0-04 / M7-10)** | 新增 `.gitlab-ci.yml`(三阶段 6 个 job:机械校验 / 构建(含 H5)/ **全套 E2E 打真库**;密钥**每次随机生成**,顺带证明"应用没有偷偷依赖 dev 默认值");`application-test.yml`(零密钥、允许 clean 以便每次从空库跑全量迁移)、`application-prod.yml`(零密钥、禁 clean、关 Swagger、日志滚动);`ProdEnvCheck`(`EnvironmentPostProcessor`,**跑在任何 bean 之前**);`docs/09-部署与运维.md`;删掉模板残留的 GitHub Pages 工作流(远端是 GitLab、用 pnpm、且它发布的是没有 API 的静态页) | **新增 `scripts/e2e-prodguard.py` → 9/9**(三种误配形态都拒绝启动)+ `scripts/check-test-profile.py`(test profile 真起一次,可重复跑);本地照跑 CI 的 check/build 各步骤全通过 |
| 6p | **P2-3 定时任务(PRD 附录 E 的四项)** | 新建 `module/schedule/`:`ScheduleMapper`(**跨租户的候选查询集中在这一个文件**,全部 `@InterceptorIgnore` + 只读;别名一律带双引号,见 F-83)+ `ScheduleJobs`(四个 `@Scheduled`,每个都是"先以平台身份扫候选 → 再逐个租户摆上下文干活"的两段式)+ `ScheduleController`(`POST /schedules/run` 手工触发,权限用运营侧的 `log:view:all`)。四项:**任务自动截止**(`end_time < CURRENT_DATE`,动作名 `AUTO_CLOSE` 以区分「系统关的」与「管理员关的」)、**自动催报**(7/3/1 天,`end_time = 那天` 而非 `<=`,只催未提交)、**审核超时提醒**(时效与 §11.6 同口径,发给"真的要动手的那个人",**找不到班主任时升级给学校管理员**)、**原始件清理**(先清引用再删文件)。另给 `UrgeService` 加 `autoUrge`、`TaskService` 加 `autoClose` | **新增 `scripts/e2e-m7-schedule.py` → 27/27** |
| 6o | **P1-9 工作台数据看板(PC)** | 工作台底部原来是一张「开发进度」卡,写着「M0 后端已完成,界面建设中」—— 而项目已是 120/121 个接口可用、10 个 PC 页面可点。**首页上说一件不真的事,比空着更糟**,所以换成真实看板:`apis/xt/task.ts` 加 `getTaskStatistics` + `TASK_STATUS_TEXT`(状态文案放一处)、`apis/xt/audit.ts` 加 `getAuditStatistics`;`views/dashboard/index.vue` 加"选任务 → 进度四格(应填/已提交/已通过/待审)+ 分班进度 + 审核情况 + 驳回原因分布"。**提交率与通过率分两列**(已提交 ≠ 已通过);平均审核时长按量级换单位显示(`null`=还没有样本 / 0=不足 1 分钟 / 否则分钟或小时) | **浏览器实测**:应填 3 / 已提交 3 / 提交率 100.0% / 已通过 3 / 通过率 100.0% / 待审 0,分班表与审核表都出数;`pnpm typecheck` 通过、`eslint` 0 错 |
| 6n | **「待建」页不再说谎(3 个"功能已就绪、入口在别处"的菜单项)** | `名单管理 / 材料规则 / 表单设计` 三个菜单项对应的功能**早就做好了**(`TaskRosterDrawer` / `TaskMaterialDrawer` / `TaskFormSchemaDrawer`,入口在「收集任务」每一行的链接里),但点进菜单看到的一直是「**这个页面还没做**」+ 一句早已过期的"已实现的页面:工作台、组织架构、租户管理"。**说"还没做"而功能其实在隔壁,比找不到更糟 —— 它让人得出错误结论。** 给 `XtMenu` 加可选的 `entryAt`,待建页据此分两种说法:有 `entryAt` → 「功能已就绪」+ 真实入口指路 + 跳转按钮;没有 → 原来的「还没做」(`角色与授权` 就是这个:权限码由代码决定、自定义角色属二期,确实没做)。并删掉那句**会静默过期**的清单 —— 任何写死在代码里的"现状"都会过期,而没人会记得回来改它 | **浏览器实测四个菜单项**:三个显示「功能已就绪」并可一键跳到「收集任务」,`角色与授权` 显示「二期」;`pnpm typecheck` 通过、`eslint` 0 错 |
| 6n | **「待建」页不再说谎(3 个"功能已就绪、入口在别处"的菜单项)** | `名单管理 / 材料规则 / 表单设计` 三个菜单项对应的功能**早就做好了**(`TaskRosterDrawer` / `TaskMaterialDrawer` / `TaskFormSchemaDrawer`,入口在「收集任务」每一行的链接里),但点进菜单看到的一直是「**这个页面还没做**」+ 一句早已过期的"已实现的页面:工作台、组织架构、租户管理"。**说"还没做"而功能其实在隔壁,比找不到更糟 —— 它让人得出错误结论。** 给 `XtMenu` 加可选的 `entryAt`,待建页据此分两种说法:有 `entryAt` → 「功能已就绪」+ 真实入口指路 + 跳转按钮;没有 → 原来的「还没做」(`角色与授权` 就是这个:权限码由代码决定、自定义角色属二期,确实没做)。并删掉那句**会静默过期**的清单 —— 任何写死在代码里的"现状"都会过期,而没人会记得回来改它 | **浏览器实测四个菜单项**:三个显示「功能已就绪」并可一键跳到「收集任务」,`角色与授权` 显示「二期」;`pnpm typecheck` 通过、`eslint` 0 错 → ✅ **已闭环(2026-10-09,二期)**:「角色与授权」已实现(见 6v),`ready:false` 的菜单**归零**,「待建」页只作为兜底保留 |
| 6m | **P2-04 历史整理器页面(PC)** | 新增 `frontend-pc/src/apis/xt/cleanup.ts`(含**报告口径**:`headlineOf` 拼"112 个文件里 90 个全自动处理,只有 1 个必须你亲自判断",但它拼的是**服务端算好的两个数**,不是前端减出来的 —— 前端减出来的早晚会与后端分叉;下载走 api 层,与导出中心同一写法)+ `views/cleanup/index.vue`(列表 / 上传 / 进度轮询 / 报告 / 难点清单 / 改名样例 / 证件号冲突 / 下载 / 导入);路由 `ready: true`。**实机验证**(浏览器里真的点了一遍):列表出数据、点详情出报告、压缩率显示 `—`、重点条目带建议、导入按钮可见 | `pnpm typecheck` 通过;`eslint` 0 错;**截图留证** |
| 6l | **P1-14 / P1-8 / P1-10 小程序登录、组织导入、租户用量、文件视图·直传凭证·批量处理** | `MpLoginService`(`jscode2session` + **复用** H5 的 `resolveUser`,不复制一份建账号逻辑)+ 手机号绑定(手机号**从客户端到不了服务端**,所以能作凭据;一个号只能绑一个账号);`OrgImportService`(父子关系用**编码 + 上级编码**、两遍扫描支持"子行在父行之前"、补 `ancestor_path`);`TenantUsageService`(周期支持月/季;`ocrCount`/`smsCount` **恒为 0 并带 notes 说明**);`MaterialFileViewService`(§9.5 预检+秒传+压缩提示、§9.7 批量处理、§9.8 预览带 `actualKind`);并修掉 F-73(秒传排在份数检查之后 → 网络重传得到"你超了"而不是"你已经传过了") | **`scripts/e2e-m8-orgfile.py` → 54/54** |
| 6k | **P2-05 开放接口(§14,供认定机构对接)** | **V16 迁移**新建 `collect_open_app`(密钥存 AES 密文 —— HMAC 要能还原,不能只存哈希)与 `collect_open_push_log`(`next_retry_at` **落库**,内存定时器在重启后就没了);`OpenSignService`(HMAC-SHA256 + 5 分钟时间窗 + nonce 防重放,**先验签再占 nonce**)、`OpenDataService`(§14.2/§14.3,**一律不返回明文证件号**、签名 URL 30 分钟)、`OpenPushService`(§14.4 推送 + 定时重试 + 人工重推,推送挂在 `afterCommit` 上)、`OpenAppAdminController`(契约漏掉的接入方管理,见 F-67);并把规则模板的两个接口**修回契约路径** `/api/v1/rule-templates`(见 F-68) | **`scripts/e2e-m8-open.py` → 60/60** |