From 7df078e0ea789386802c3e5711e5f27b49408e7a Mon Sep 17 00:00:00 2001 From: lovife <474239041@qq.com> Date: Sat, 10 Oct 2026 16:49:04 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=90=8C=E6=AD=A5=E4=BA=A4=E6=8E=A5?= =?UTF-8?q?=E6=91=98=E8=A6=81=20/=20PRD=20/=20=E5=8E=9F=E5=9E=8B=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E8=AE=B0=E5=BD=95=E5=93=81=E7=89=8C=E6=94=B9=E5=90=8D?= =?UTF-8?q?=E3=80=8C=E6=B7=B1=E5=AD=A6=E5=9C=A8=E7=BA=BF=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 交接摘要补 §一·三十五~§一·三十八:角色与授权、需求收敛(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 与七牛对象前缀都没动) --- AGENTS.md | 50 ++- README.md | 36 +- deploy/docker-compose.yml | 2 +- docs/00-交付计划.md | 41 +- docs/01-客户与机构确认函.md | 35 +- docs/02-需求追溯矩阵.md | 20 +- docs/03-接口设计.md | 543 +++++++++---------------- docs/04-核心模块设计说明.md | 113 +---- docs/05-开发任务拆解.md | 65 ++- docs/06-验收测试清单.md | 53 +-- docs/07-开发实施计划.md | 10 +- docs/08-完成度盘点与问题清单.md | 60 ++- docs/09-部署与运维.md | 15 +- docs/10-用户手册与客服话术.md | 53 ++- docs/db/schema-postgresql.sql | 9 + docs/prototype/admin-web.html | 339 ++++++--------- docs/prototype/index.html | 12 +- docs/prototype/student-app.html | 83 +--- docs/prototype/teacher-app.html | 4 +- docs/交接摘要.md | 463 +++++++++++++++++++-- docs/数据信息填报系统PRD-功能逻辑卷.md | 109 ++--- docs/数据信息填报系统PRD.md | 136 +++---- 22 files changed, 1158 insertions(+), 1093 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 99021ba..83dd269 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,22 +3,27 @@ 面向 AI 编码助手的项目指南。**用简体中文交流、用中文写文档。** 需求已冻结:**文档是需求源,代码是它的实现**,不要按"更合理"去改业务口径。 -`docs/交接摘要.md` 是**唯一状态源** —— 动手前先读它最近的两节(§一·二十、§一·二十一) +`docs/交接摘要.md` 是**唯一状态源** —— 动手前先读它最近的两节(**§一·三十五、§一·三十六**) 与 `## 二、已锁定的铁律`;那里面大量决定"读代码看不出来、但都有理由"。 +> ⚠️ **2026-10 需求收敛(见 §一·三十六)**:只做 **4 个角色**(平台运营 / 学校管理员 / 班主任 / 学生)、 +> **10 个 PC 菜单**、**不设二期**。删掉的东西不要再"顺手加回来":副班主任 / 审核员两个角色、 +> 历史整理器、加入申请(含学生端自助申请入口)、`collect_task_org.deputy_user_ids`。 + ## 一、四个可运行部分 | 目录 | 是什么 | 入口 | |---|---|---| | `backend/` | Java 21 + Spring Boot 4.1.1 + MyBatis-Plus + PG16 + Redis7 | `xt-server/`(`xt-common/` 放响应体/错误码/加解密) | -| `frontend-pc/` | 管理端 Vue3 + Arco Design Vue(基于 **Gi Admin Pro**) | `pnpm dev` → 5173 | +| `frontend-pc/` | 管理端 **纯 Vue 3 + Naive UI 2.42**(2026-10 从 Gi Admin Pro + Arco 重搭) | `pnpm dev` → 5173 | | `frontend-mp/` | 学生端 **uni-app**(H5 与微信小程序同一套代码) | `pnpm build:h5` / `pnpm dev:mp-weixin` | | `deploy/` | 本地基础设施(PG + Redis,**没有对象存储**) | `docker compose up -d` | `docs/prototype/` 是视觉与交互的**唯一基准**(单文件自包含、零外部依赖、**不用 emoji**)。 -**一期范围只做「学校收集 + 自主认定」**:`collectMode=2` / `certType=2,3` 一律返回 `40044`, -别顺手把二期功能实现出来。 +**范围只做「学校收集 + 自主认定」**:`collectMode=2` / `certType=2,3` 一律返回 `40044`。 +**不设二期** —— 与「二期」有关的任何设计(社会收集、社会评价、专项职业能力、数据范围可配等)都**不做**, +别顺手实现;OCR 与材料识别是「缓做,等供应商选型」。 ## 二、常用命令(备注里是容易猜错的地方) @@ -42,7 +47,7 @@ java -jar xt-server/target/xt-server-1.0.0-SNAPSHOT.jar --spring.profiles.active `application-local.yml`(不提交的本地私有配置)就在 `backend/`; H5 静态目录配的是 `file:../frontend-mp/dist/build/h5/` - 跑的是 **jar 不是热加载** → 改后端要重新 package(先停进程,否则 jar 被占用) -- **动过 `db/migration/` 必须 `mvn clean package`**(Flyway 已到 V6;迁移文件发布后不许改) +- **动过 `db/migration/` 必须 `mvn clean package`**(Flyway 已到 **V19**;迁移文件发布后不许改,只能新加) - 本地私有配置机制是 `profiles.active=dev` + `spring.profiles.include: local` (模板 `backend/application-local.yml.example`)。 **不要改用 `spring.config.import`** —— 它优先级低于 profile 文件,会静默失效(见 `application.yml` 注释) @@ -56,12 +61,17 @@ pnpm typecheck # vue-tsc pnpm lint # eslint - 用 **pnpm**(仓库里是 `pnpm-lock.yaml`;别 `npm install`,会多出 package-lock.json) - dev 通过 vite proxy 把 `/api` 转发到 `http://localhost:8080`(见 `.env.development`), - **没有 Mock**(`mock/` 与 `vite-plugin-mock` 都已删除)→ 后端不起着就没有数据。 - ⚠️ `frontend-pc/.agents/skills/project-api/SKILL.md` 仍以 Mock 为主线(Gi 模板原文), - 该文件开头已加"本仓库不用 Mock"的说明 —— 看它时先看那段 -- 我们的接口模块都在 `src/apis/xt/`;`apis/system|user|person|cate|area|test` 是 Gi 模板自带的,别往那儿加 -- **PC 前端有独立的规范来源:`frontend-pc/.agents/`**(`AGENTS.md` + `skills/*`)。 - 改那里的代码前按它的表加载对应 skill,不要另起一套写法 + **没有 Mock**(`mock/` 与 `vite-plugin-mock` 都已删除)→ 后端不起着就没有数据 +- 接口模块都在 `src/apis/xt/`(按业务域分文件);**没有 auto-import**,组件与 API 全显式 `import` +- ⚠️ **2026-10 已从 Gi Admin Pro + Arco 重搭为纯 Vue 3 + Naive UI**,因此: + - **`pnpm lint:style`(Stylelint)已移除**,只剩 `pnpm lint`(ESLint);`pnpm typecheck` 现在 **0 错** + - 权限判断统一写 `userStore.has('码')`(**没有 `v-hasPerm` 指令**),字符串必须与后端 `PermissionDef` **逐字一致** + - `n-tag` 的 `type` 必须过 **`@/utils/tag.ts`** 转换:`apis/xt/*.ts` 里的展示映射仍是 Arco 色词 + (`green`/`arcoblue`/`gold`/`gray`),直接透传会**静默变灰**且不报错 + - 图标:`scripts/extract-pc-icons.py` 生成 `src/icons/icon-data.ts`(37 个); + `AppIcon` 对缺失图标是**空占位、不报错** → **图标集升级后要重跑该脚本** +- **PC 前端有独立的规范来源:`frontend-pc/AGENTS.md` + `frontend-pc/.agents/`**(已按 Naive UI 重写)。 + 改那里的代码前先读它,不要另起一套写法 ### 学生端(uni-app) @@ -78,7 +88,8 @@ pnpm dev:mp-weixin # → dist/dev/mp-weixin(导入微信开发者工具) dev server → 代理;构建产物(后端托管)→ `location.origin`;小程序 → 写死地址 - 改前端后浏览器要**硬刷新**(`/assets/**` 是 immutable 缓存) - 构建时 Dart Sass 的 `legacy-js-api` DEPRECATION 属正常 -- `vue-tsc` 有 2 条**已知**报错(`src/config/index.ts` 的 `#ifdef` 重复导出),不是新引入的 +- `vue-tsc` 有 2 条**已知**报错(`src/config/index.ts` 的 `#ifdef` 重复导出),不是新引入的。 + ⚠️ PC 端不同:重搭后 `pnpm typecheck` 是 **0 错** —— 那边一有报错就是新引入的 - 当前节奏:**先做 H5**(可自动化测试、不依赖小程序资质);小程序端代码同步维护 ## 三、怎么验证(本仓库最不显然的一点) @@ -90,7 +101,8 @@ pnpm dev:mp-weixin # → dist/dev/mp-weixin(导入微信开发者工具) python scripts/e2e-m3-file.py # 单跑一个;前提是后端 8080 在跑、xt-pg 容器在跑 ``` -- 全量:把 `scripts/e2e-*.py` 依次跑一遍(**38 个脚本 / 1620 条断言**), +- 全量:把 `scripts/e2e-*.py` 依次跑一遍(**37 个脚本 / 1595 条断言**,以最近一次 + `verify-all --with-e2e` 的输出为准;2026-10 需求收敛删掉了 `e2e-m2-join.py` 与 `e2e-m7-cleanup.py`), 或一条命令 `python scripts/verify-all.py --with-e2e` - 脚本**自己造 MP 的 JWT**:读 `application-dev.yml` 的 `fill.jwt.secret` + `application.yml` 的 `issuer` 手搓 HS512 —— 不需要先登录 @@ -105,9 +117,17 @@ python scripts/e2e-m3-file.py # 单跑一个;前提是后端 8080 在跑 - 文档一致性:`python scripts/verify-docs.py` - 接线检查:`python scripts/check-wiring.py`(**"声明了但没接上"** —— 权限码有没有地方校验、错误码有没有被引用、站内信模板有没有发送点、 - `bizType` 有没有被用过、菜单键 ↔ PC 端 `XtMenu` 是否对得上)。 + `bizType` 有没有被用过、菜单键 ↔ PC 端 `XtMenu` 是否对得上、 + **PC 端 `user.has('码')` 引用的权限码 ↔ `PermissionDef`**(第 ⑦ 项)、 + **模板里的 `` 组件有没有漏 import**(第 ⑧ 项 —— 漏了会被当未知元素渲染,样式整块丢、 + 编译与 lint 都不红)、 + **`.svg` 资产是不是合法 XML**(第 ⑨ 项 —— SVG 注释里出现连续两个减号就 not well-formed, + 图标变裂图而控制台一句话都不打;2026-10 换品牌标时连踩两次))。 ⚠️ 这一族错**编译 / lint / review 都不会红**(类在、表在、常量在、注释还写得很清楚), - 只有"从入口走一遍"才暴露 —— 所以把它固化成命令;有意不接线的写在脚本上方的例外表里(带理由) + 只有"从入口走一遍"才暴露 —— 所以把它固化成命令;有意不接线的写在脚本上方的例外表里(带理由)。 + ⚠️ **检查器自己也会失效**:PC 端重搭后权限码的写法从 `permissions.includes` / `v-hasPerm` + 改成了 `user.has`,第 ⑦ 项曾因此"一个码都扫不到却判为通过"。 + 加/改这类检查时**必须反向验证一次**(故意写错,它得红) - 一条命令跑完上面这些:`python scripts/verify-all.py --with-e2e` (9 项;**跳过项会如实列出来**,不是静默跳过) diff --git a/README.md b/README.md index ec3df49..03377e3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# 数据信息填报系统 +# 深学在线 > 职业技能等级认定**报名材料收集 SaaS** -> **一期范围**:学校收集 + 自主认定(4 类材料,全部有实测样本)。社会收集 / 社会评价 / 专项职业能力后置二期 +> **范围(2026-10 收敛)**:只做「学校收集 + 自主认定」(4 类材料,全部有实测样本)。社会收集 / 社会评价 / 专项职业能力 **不做**(传 `collectMode=2`、`certType=2,3` 返回 `40044`);**不设二期** > 学生在微信小程序填报 · 班主任在手机初审 · 管理员在 PC 终审与导出 > 最后更新:2026-09-28 | **状态:需求已冻结(客户全部确认,可开工)** @@ -28,7 +28,7 @@ | **产品 / 需求** | `数据信息填报系统PRD.md` → `数据信息填报系统PRD-功能逻辑卷.md` | 前者含库表接口,后者是纯功能逻辑 | | **后端开发** | `03-接口设计.md` → `04-核心模块设计说明.md` → `db/schema-postgresql.sql` | 接口收发 → 引擎怎么做 → 建表 | | **前端开发** | `prototype/` → `03-接口设计.md` | 原型是视觉与交互的**唯一基准** | -| **测试** | `06-验收测试清单.md` → `04-核心模块设计说明.md` 附录 B | 29 条 Golden 断言 + 85 条功能验收 | +| **测试** | `06-验收测试清单.md` → `04-核心模块设计说明.md` 附录 B | 36 条 Golden 断言 + 111 条功能验收 | | **新加入的人** | `交接摘要.md` → 本文件 | 一份文档看清全部状态 | ### 全部文档 @@ -38,7 +38,7 @@ | 00 | `00-交付计划.md` | 8 KB | 7 项计划与进度追踪、风险跟踪 | ✅ | | 01 | `01-客户与机构确认函.md` | 18 KB | **23 项已全部确认** + 客户回执 + 落地动作清单 | ✅ **需求冻结** | | 02 | `02-需求追溯矩阵.md` | 10 KB | 115 条 FR ↔ 原型/库表/接口 的自审报告 | ✅ | -| 03 | `03-接口设计.md` | 112 KB | **121 个接口**字段级定义、枚举全集、12 个 VO、**57 个错误码** | ✅ | +| 03 | `03-接口设计.md` | — | **119 个接口**字段级定义、枚举全集、12 个 VO、**54 个错误码** | ✅ | | 04 | `04-核心模块设计说明.md` | 56 KB | **6 个模块**伪代码级说明 + 依赖图 + 26 项回归基线 | ✅ | | 05 | `05-开发任务拆解.md` | 31 KB | **100 个任务 / 387 人日 / 15 周**,含关键路径、一期范围与风险 | ✅ | | 06 | `06-验收测试清单.md` | 29 KB | **36 条 Golden 断言** + 111 条功能验收 + 签署表 | ✅ | @@ -46,7 +46,7 @@ | — | `数据信息填报系统PRD.md` | 190 KB | 主 PRD V2.3+:**125 条 FR**、**21 条 VAL**、**20 张表 DDL**、验收标准 | ✅ | | — | `数据信息填报系统PRD-功能逻辑卷.md` | 88 KB | 纯功能运转逻辑(**不含库表/接口/代码**) | ✅ | | — | `db/schema-postgresql.sql` | 36 KB | 可执行 PG DDL(20 表),**已在 PostgreSQL 16 实测零错误** | ✅ | -| — | `prototype/`(4 份) | 240 KB | **31 屏**交互原型:学生端 11 / 班主任端 9 / 管理端 11 | ✅ | +| — | `prototype/`(4 份) | — | **29 屏**交互原型:学生端 10 / 班主任端 9 / 管理端 10 | ✅ | --- @@ -57,7 +57,7 @@ ``` ┌─ 学生端(小程序)─┐ ┌─ 班主任端(手机)─┐ ┌─ 管理端(PC)─┐ 名单 ──→ 门禁核验 ──→ 填报 ──→ 卡片流初审 ──→ 终审 ──→ 导出规范包 - └─✗ 失败 → 申请加入名单 → 管理员审批 → 入册放行 + └─✗ 失败 → 只提示核对 + "请联系老师加你进名单"(老师手工加人) ▲ ▲ ▲ ▲ │ │ │ │ 报考范围 识别 命名+压缩 汇总 Excel @@ -95,9 +95,9 @@ | 滑动交互 | 班主任端**只做右滑通过**,驳回必须点按钮 | | 批量通过 | **只带无问题项**,红黄一律排除 | | 动态表单 | 内置字段(固定列可统计)+ 扩展字段(`extras_json`) | -| 名单外的人 | **申请加入名单 → 管理员逐条审批**;通道只解决"进名单",**不绕过门禁** | +| 名单外的人 | **不做自助通道**(2026-10 撤销):由老师在任务里**手工加人**(名册追加导入),**不绕过门禁** | | 归属判定优先级 | **证件号反查名册 > OCR 内容 > 姓名(仅辅助,不做独立规则)** | -| 联系方式 | **系统内不展示任何班主任/管理员联系方式**——学生本来就有;门禁失败只给「重新填写 / 申请加入名单」两条系统内出路 | +| 联系方式 | **系统内不展示任何班主任/管理员联系方式**——学生本来就有;门禁失败只给「重新填写」一条系统内出路 + 一句"请联系老师" | | 校验位的作用 | **失败 = 不是真实号码 → 可自动改**;**通过 = 真人 → 改了就是改身份 → 必须阻断** | | 审批粒度 | 批量通过**不提供"全选"**,驳回**必须逐条填原因**(身份认定误操作代价极高) | @@ -167,7 +167,7 @@ python check2.py docs/prototype/admin-web.html ## 六·二、把系统跑起来(三条命令) ```bash -# 1) 基础设施:PG 16 + Redis 7 + MinIO +# 1) 基础设施:PG 16 + Redis 7(没有对象存储,MinIO 已删) cd deploy copy .env.example .env # 首次 docker compose up -d @@ -194,12 +194,12 @@ python scripts/e2e-m0.py ``` backend/ Java 21 + Spring Boot 4.1.1 - ├── xt-common/ 统一响应 / 58 个错误码 / AES-GCM / HMAC / RSA - └── xt-server/ 配置 / 鉴权 / 多租户拦截 / 业务模块 + ├── xt-common/ 统一响应 / 54 个错误码 / AES-GCM / HMAC / RSA + └── xt-server/ 配置 / 鉴权 / 多租户拦截 / 业务模块(26 张表,Flyway 已到 V19) deploy/ docker-compose + RSA 开发公钥 -scripts/ DDL 同步、登录助手、M0 验收测试 -frontend-pc/ (待建)Vue 3 + Element Plus -frontend-mp/ (待建)uni-app +scripts/ E2E 验收(37 个脚本)、接线检查、DDL 同步、图标抽取 +frontend-pc/ PC 管理端:**纯 Vue 3 + Naive UI**(pnpm dev → 5173) +frontend-mp/ 学生端 / 教师端:uni-app(H5 与微信小程序同一套代码) ``` ### ⚠️ 搭环境前请先读 @@ -214,8 +214,8 @@ frontend-mp/ (待建)uni-app | 阶段 | 内容 | 状态 | |---|---|---| | **需求** | 主 PRD(**125 FR** / 20 VAL / **20 张表**)+ 功能逻辑卷 | ✅ | -| **设计** | 数据库 DDL(PG 16 实测)+ 接口设计(**121**)+ 模块设计(6) | ✅ | -| **原型** | **31 屏**(学生 **11** / 班主任 9 / 管理 **11**)= 31 | ✅ | +| **设计** | 数据库 DDL(PG 16 实测)+ 接口设计(**119**)+ 模块设计(6) | ✅ | +| **原型** | **29 屏**(学生 **10** / 班主任 9 / 管理 **10**)= 29 | ✅ | | **计划** | **100 任务 / 387 人日 / 15 周**(一期范围已收窄) | ✅ | | **测试** | **36** 条 Golden 断言 + **111** 条功能验收 | ✅ | | **待外部** | ~~客户确认函(23 项)~~ | ✅ **已确认,按建议执行** | @@ -225,7 +225,7 @@ frontend-mp/ (待建)uni-app | 项 | 确认结论 | |---|---| | **A1** | ✅ 认定机构接受 300dpi 输出 | -| **B1** | ✅ 增加「申请加入名单 → 管理员审批」通道(**唯一功能增量,已落地**) | +| **B1** | ⚠️ **已回退**(2026-10 需求收敛):加入申请通道整体撤销(模块/表/接口/页面/错误码一并删除)。它是确认函里唯一的功能增量,建议补变更确认单 | | **B3** | ✅ 字段三分类:姓名/证件号/报考三项锁定,其余可核对修改并留痕 | **其余 20 项均与现有设计一致,零改动。** 需求已冻结,可按 `05-开发任务拆解.md` 的 **M0** 开工。 @@ -257,7 +257,7 @@ frontend-mp/ (待建)uni-app |---|---| | 数据库 | PostgreSQL 16(DDL 已实测通过) | | 开发顺序 | **M1 命名引擎 → M4 门禁 → M2 压缩 → M5 校验 → M3 识别 → M6 导出** | -| 客户回执 | 全部 23 项按建议执行;唯一功能增量 B1(申请加入名单)已落地 | +| 客户回执 | 全部 23 项按建议执行;唯一功能增量 B1(申请加入名单)**已于 2026-10 需求回退取消** | | 原型约束 | 单文件自包含、零外部依赖、内联 SVG 图标、**无 emoji** | | 最长的两个单点 | 压缩引擎 12 人日、打包引擎 10 人日(**加人无效**) | | 回归门禁 | 改动后必须复现 **15 项基线指标**(对应 29 条断言),偏差即回归 | diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index f15a2fa..3708c2b 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -1,4 +1,4 @@ -# 数据信息填报系统 · 本地开发基础设施 +# 深学在线 · 本地开发基础设施 # # 用法: # cd deploy diff --git a/docs/00-交付计划.md b/docs/00-交付计划.md index 9196a4f..58ceaac 100644 --- a/docs/00-交付计划.md +++ b/docs/00-交付计划.md @@ -1,6 +1,6 @@ # 交付计划与进度 -> 项目:数据信息填报系统(填报通 / CollectFlow) +> 项目:深学在线 > 起始:2026-09-28 > 状态:**进行中** @@ -13,9 +13,9 @@ | — | 主 PRD(含库表设计、接口清单、15 条校验规则、18 张表 DDL) | V2.3 | ✅ | | — | 功能逻辑卷(纯功能运转逻辑,不含库表/接口/代码) | V1.9 | ✅ | | — | PostgreSQL 建表脚本 | — | ✅ **已在 PG 16 实测通过** | -| — | 学生端原型(**11 屏**) | V1.2 | ✅ 含「申请加入名单」 | +| — | 学生端原型(**10 屏**) | V1.3 | ✅(原 STEP 03B「申请加入名单」已于 2026-10 需求收敛删除) | | — | 班主任端原型(9 屏) | V1.1 | ✅ | -| — | 管理端 PC 原型(**11 屏**) | V1.2 | ✅ 含表单设计器/材料规则/整理器/申请审批 | +| — | 管理端 PC 原型(**10 屏**) | V1.3 | ✅ 含表单设计器/材料规则/角色与授权(整理器与申请审批两屏已于 2026-10 删除) | --- @@ -30,7 +30,7 @@ |---|---|---|---|---| | **P1** | **客户与认定机构确认函** | `01-客户与机构确认函.md` | 外部依赖最长。**5 项高优先级阻塞开发**(300dpi 能否接受、名单外的人怎么办、社会收集名单从哪来…),越早发出越早回来 | ✅ | | **P2** | **需求追溯矩阵** | `02-需求追溯矩阵.md` | 自审:把 PRD 需求 ↔ 原型屏 ↔ 库表 ↔ 接口 逐条对应,找出**说了没做/做了没说**的缺口。放在接口文档之前,避免接口漏做 | ✅ | -| **P2.5** | **管理端补充原型**(矩阵审计追加) | 追加到 `admin-web.html` | 矩阵发现 3 个 P0/P1 复杂交互页面未出原型:**表单设计器**、**材料规则配置**、**历史整理器**。不画出来开发必理解偏 | ✅ | +| ~~**P2.5**~~ | ~~**管理端补充原型**(矩阵审计追加)~~ | 追加到 `admin-web.html` | 矩阵发现 3 个 P0/P1 复杂交互页面未出原型:**表单设计器**、**材料规则配置**、~~**历史整理器**~~(该屏已于 2026-10 随模块删除)。不画出来开发必理解偏 | ✅ | ### 阶段二 · 补齐技术设计 @@ -58,10 +58,10 @@ | 2026-09-28 | **P2 追溯矩阵** | 完成 `02-需求追溯矩阵.md`。**审计发现**:① 修正统计口径——实际 **115 条 FR**(83 P0/29 P1/3 P2)+ 17 条 VAL,此前漏算加粗编号;② **10 个接口缺口**(报考范围、整理器、字典、日志、消息中心、订阅额度、审核撤回、任务暂停…);③ **管理端 10 个原型页面缺口**,其中表单设计器为 P0;④ 4 个疑似需求问题中 **3 个是脚本正则缺陷、1 个为真**(已补 FR-616);⑤ **追加 P2.5** 补 3 个复杂交互页面原型 | | 2026-09-28 | **P2.5 补充原型** | 管理端 `admin-web.html` 由 7 屏扩至 **10 屏**:⑧**表单设计器**(字段库/画布/属性三栏 + 条件规则行,内置字段带锁不可删)⑨**材料规则配置**(材料清单侧栏 + 命名模板 + 档位选择 + 来源引导语)⑩**历史数据整理器**(用真实实测数字:475MB→37MB、59 项命名违规、仅 1 项需人工)。四份原型全部通过结构校验 | | 2026-09-28 | **P6 验收测试清单** | 完成 `06-验收测试清单.md`(25KB / 518 行)。把 P4 的 15 项基线转成 **29 条可自动化断言 G-01~G-29**;另含 3 条端到端主流程(32 步)、20 条异常边界、85 条功能验收、6 条视觉、9 项性能、12 项安全;含里程碑签署表与 **7 条 Go/No-Go 条件**;点出 3 个易漏盲区(Windows 解压中文名 / 并发绑定临界窗口 / 「无」是合法值) | -| 2026-09-28 | **客户回执** | 客户确认全部 23 项按建议执行,**需求冻结**。识别出唯一功能增量 **B1(申请加入名单)**,其余 22 项零改动 | +| 2026-09-28 | **客户回执**(⚠️ 唯一增量 B1 已于 2026-10 需求回退取消) | 客户确认全部 23 项按建议执行,**需求冻结**。识别出唯一功能增量 **B1(申请加入名单)**,其余 22 项零改动 | | 2026-09-28 | **D7 降级** | 重新评估「文件名识别真实准确率」这个待办:因**输出命名取自名册**、**归属靠证件号反查名册**、**识别有槽位兜底**、**失败一律放行**,该指标**不影响任何交付项**。降级为「上传页引导文案校准」——班主任在群里问两句即可,**取消样本索取**。至此**全部外部数据依赖清零** | -| 2026-09-28 | **一期范围收窄** | 客户确认「**先做学校内的填报流程**」→ 一期只做**学校收集 + 自主认定**(4 类材料全部有实测样本);社会收集 / 社会评价 / 专项职业能力后置二期,**数据结构与枚举保留不启用**。**D4 因此不再是待办**。工期 −5 人日(392→387),关键路径不变仍 15 周 | -| 2026-09-28 | **移除联系方式展示** | 客户指出"学生本来就有班主任联系方式,不需要系统里面体现"。删除 `GET /mp/roster/contact` 接口 + 门禁失败页「联系老师」按钮,改为只保留两条系统内动作(重新填写 / 申请加入名单)。**接口总数 122 → 121**。纯文案提示保留(不涉及联系方式数据) | +| 2026-09-28 | **范围收窄** | 客户确认「**先做学校内的填报流程**」→ 只做**学校收集 + 自主认定**(4 类材料全部有实测样本);社会收集 / 社会评价 / 专项职业能力**不做**(2026-10 进一步明确为取消,不再叫"后置二期"),**数据结构与枚举保留不启用**。工期 −5 人日(392→387),关键路径不变仍 15 周 | +| 2026-09-28 | **移除联系方式展示** | 客户指出"学生本来就有班主任联系方式,不需要系统里面体现"。删除 `GET /mp/roster/contact` 接口 + 门禁失败页「联系老师」按钮。**接口总数 122 → 121**。⚠️ 当时保留的两条系统内动作里,「申请加入名单」已于 2026-10 撤销,现在只剩「重新填写」+ 一句"请联系老师" | | 2026-09-28 | **归属校验修正** | 客户指出"系统不存在名称错误"——纠正原设计中「把文件名不符自动按名册修正」的缺陷(会把传错的文件静默洗白)。改为 **VAL-21 文件归属校验 + 阻断提交**;判定优先级重排为 证件号反查名册 > OCR > 姓名(仅辅助);姓名不再作为独立规则。另补全「社会评价」定义与 `cert_type` 第三取值 | | 2026-09-28 | **B1 落地** | 新增 FR-509 系列 10 条 / `collect_join_request` 表(**PG 16 实测通过 + 部分唯一索引业务规则验证**)/ 5 个接口 / 错误码 40037~40042 / 学生端 STEP 03B + 管理端 STEP 11 两屏原型 / F-509 验收项 19 条 / WBS 新增 6 个任务 | | 2026-09-28 | **P7 文档导航** | 完成 ../README.md(10KB)。按角色给出读法、三分钟看懂产品、13 条关键决策速查、目录结构与快速上手、数据基线、两条踩坑教训 | @@ -69,6 +69,7 @@ | 2026-09-28 | **P4 核心模块设计** | 完成 `04-核心模块设计说明.md`(54KB / 1218 行)。六个模块全部伪代码级:M1 命名(含证件号三源裁决表)、M2 压缩(五档+二分降质+二次有损防护)、M3 识别(四层递进+成本闸)、M4 门禁(**并发绑定必须 UPDATE...WHERE bind_user_id IS NULL**)、M5 校验、M6 导出(快照先固化+中文文件名编码)。**校验中发现并修正三处真问题**,见下条教训 | | 2026-09-28 | **P3 接口设计** | 完成 `03-接口设计.md`(102KB / 2626 行)。**117 个接口**全部字段级定义(PRD 原 96 + 新增 21);新增 §2 枚举全集(含「无」的三条铁律)、§3 通用数据结构(12 个 VO,字段名与 DDL 列一一对应)、§15 错误码全集(50 个,新增 30);**自校验通过**:模块表合计=声称=实际唯一数=117,无真重复,引用字段名 116 个全部存在于 DDL | | 2026-10-09 | **验收走查后的四项修正** | ① **去掉 3 条无用菜单**(名单管理 / 表单设计器 / 材料规则)—— 功能都在「收集任务」行内入口,菜单里那一条只是指路;后端菜单键同步删除;② **修掉班主任点工作台必弹 403**(工作台无条件调了要 `org:view` / `progress:viewAll` 的两个接口,班主任都没这两个权限)→ 改为按权限决定发不发请求;③ **新增请求头 `X-Tenant-Id`(§1.2)**:平台运营的「组织架构 / 账号管理 / 工作台」原先拿到的是**所有租户混在一起**的数据,现在必须先在页面上选定学校;④ **修掉开放接口的"推送重试"从未生效**(F-112:定时扫描缺 `@InterceptorIgnore`,异常被 warn 吞掉;E2E 补了"真的重试了"的断言)。详见 `交接摘要` §一·三十三 | +| 2026-10-09 | **二期「角色与授权」上线**(FR-205) | 内置角色**只读** + 自定义角色的权限码 / 菜单可配:新增 **8 个接口**(`docs/03` §4.12 / §5.10,契约总数 121 → **129**)、**V18 迁移**(`sys_role.user_type` + `sys_role_menu`)、错误码 `40045`;权限判定从"零 IO"改为"内置按代码 / 自定义按库 + Redis 缓存";新增每日**内置角色授权同步**(修掉代码与库的静默漂移);PC 新增「角色与授权」页(原型 ⑪)→ **`ready:false` 的菜单归零**;账号页的可选角色 / 用户类型 / 角色名全部改读库(删掉前端三份写死的镜像表)。验证:新增 `scripts/e2e-m10-role.py` **77 条**、`verify-all --with-e2e` 全绿。**「数据范围可配」仍是下一批**(见 `docs/05` M8 的说明) | --- @@ -79,12 +80,12 @@ | 组 | 项数 | 需开发 | 结论 | |---|:--:|:--:|---| | A 认定机构 | 6 | **0** | 全部与现有设计一致(当初就按实测数据设计) | -| B 校方业务 | 9 | **1** | 仅 **B1(申请加入名单通道)** 是功能增量 | +| B 校方业务 | 9 | **1**(⚠️ B1 已于 2026-10 需求回退取消) | 仅 **B1(申请加入名单通道)** 是功能增量,该通道已于 2026-10 删除 | | C 商务法务 | 5 | 0 | C1 小程序资质按建议推进 + H5 保底 | -| D 内部技术 | 7 | 0 | **D4 已后置二期**、**D7 已降级清除**;仅 D6(微信三条来源通道体积上限)需真机自测,**无外部依赖** | +| D 内部技术 | 7 | 0 | **D4 已随"取消二期"作废**、**D7 已降级清除**;仅 D6(微信三条来源通道体积上限)需真机自测,**无外部依赖** | **一期范围(客户确认后收窄)**:只做 **学校收集 + 自主认定**(4 类材料全部有实测样本)。 -社会收集 / 社会评价 / 专项职业能力后置二期,**数据结构与枚举保留不启用**。 +社会收集 / 社会评价 / 专项职业能力**不做**(2026-10 取消,不再是"后置"),**数据结构与枚举保留不启用**。 ⇒ **D4(社会评价样本)因此不再是待办**;工期 **392 → 387 人日**,关键路径不变仍 **15 周**。 **本次变更(V1)**:新增 **FR-509 系列**(10 条)+ **1 张表**(`collect_join_request`)+ **5 个接口** + **2 屏原型**。 @@ -92,7 +93,7 @@ | 指标 | 变更前 | 变更后 | |---|:--:|:--:| | FR 总数 | 115 | **125** | -| 接口总数 | 117 | **121** | +| 接口总数 | 117 | **121**(2026-10 收敛后为 **119**) | | 库表 | 19 | **20** | | VAL 规则 | 20 | **21**(新增 VAL-21 归属校验) | | 错误码 | 50 | **57** | @@ -108,6 +109,20 @@ | # | 风险 | 影响 | 对策 | |---|---|---|---| | R1 | ~~客户迟迟不确认 P1 的 5 项高优先级问题~~ | — | ✅ **已解除**:客户确认全部 23 项按建议执行,需求已冻结 | -| R2 | ~~强特征词表未在"社会评价"批次数据上标定~~ | — | ✅ **已消除**:一期范围收窄为「学校收集 + 自主认定」,**不涉及**那两类零样本材料(D4 已后置二期) | +| R2 | ~~强特征词表未在"社会评价"批次数据上标定~~ | — | ✅ **已消除**(2026-10 取消二期) | | R3 | VLM 选型未定 | 中 | P4 中把 VLM 抽成**可替换接口**,不绑定供应商 | -| R4 | **二期启动时缺少社会评价样本** | 中(二期) | 已在 PRD「一期范围声明」列出二期启动前必做 6 项;**不阻塞一期** | +| R4 | ~~二期启动时缺少社会评价样本~~ | — | **已消除**(2026-10 取消二期,不再需要该样本) | + +--- + +## 2026-10 需求收敛(执行记录) + +| 项 | 内容 | +|---|---| +| 起因 | 客户认为功能面过宽,要求收敛为「总后台开学校 → 学校管理员发任务 → 学生填 → 班主任 + 学校管理员审 → 归档导出 ZIP」 | +| 删除 | ① 内置角色 **6 → 4**(删副班主任 `DEPUTY_TEACHER`、审核员 `AUDITOR`,存量账号由 **V19 迁移**处理);② **历史整理器**(`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` **整列**(审核范围三级回退 → **两级**) | +| 取消 | **二期整体取消**:社会收集 / 社会评价 / 专项职业能力 / 数据范围可配 / 角色变更即时失效 / 平台角色页 → **不做**;OCR 与材料识别 → **缓做,等供应商选型**;材料模板 **3 → 1**;`XJW_BA`/`OTHER_PROOF` **读得到、选不到** | +| 数字 | 接口 **129 → 119**、错误码 **59 → 54**、权限码 **30 → 28**、PC 菜单 **12 → 10**、小程序页面 **10 → 9**、原型 **32 → 29 屏**、E2E 脚本 **39 → 37** | +| 迁移 | `V19__remove_deputy_auditor_cleanup_join.sql`(先解绑定再软删角色;账号只在"除这两个角色外没有别的角色"时软删) | +| ⚠️ 治理 | **加入申请(确认函 B1)是客户唯一确认的功能增量,本次属需求回退**,建议补变更确认单 | +| 详见 | `docs/交接摘要.md` **§一·三十六**(含 5 条新增防线与 PC 端重搭说明) | diff --git a/docs/01-客户与机构确认函.md b/docs/01-客户与机构确认函.md index 4de2a07..da2013f 100644 --- a/docs/01-客户与机构确认函.md +++ b/docs/01-客户与机构确认函.md @@ -1,9 +1,13 @@ -# 确认函 · 数据信息填报系统 +# 确认函 · 深学在线 > 文档用途:汇总需各方拍板的事项,**可直接转发** > 发出日期:2026-09-28 > 回复期限:建议 **5 个工作日内** -> 版本:V1.0 +> 版本:V1.1(2026-10 追加需求收敛说明) +> +> ⚠️ **2026-10 变更提示**:客户随后要求收敛范围 —— **B1(本函唯一的功能增量)已整体撤销**, +> 且**不再设计任何二期功能**。本函正文保留当时原文(它是"当时确认过什么"的记录), +> 但凡出现"二期 / 后置 / 待实施"的表述,**一律读作"不做"**。详见 `docs/交接摘要.md` **§一·三十六**。 --- @@ -186,7 +190,7 @@ B1 选 A,但希望增加审批环节 | | | |---|---| | **问题** | 是否需要把数据直接推送到机构系统,而不是导出文件包? | -| **建议方案** | ✅ **建议一期不做**(导出标准包已可满足),**但预留开放接口**,二期按需启用 | +| **建议方案** | ✅ **不做**(导出标准包已可满足),**但预留开放接口**,将来按需启用 | | **回复** | ☐ 一期不做 ☐ 一期就必须对接(请提供对方接口文档) | ### B9. 社会收集是否需公安实名核验? @@ -257,7 +261,7 @@ B1 选 A,但希望增加审批环节 **建议**:一期按 H5 单腿上线(学生扫 H5 码即可填报),小程序作为第二条腿, 在开放平台绑定完成后一起开。**这样不必等它,但必须知道它挡着什么。** -回复:☐ 现在就办(我方可提供绑定步骤) ☐ 一期只上 H5,二期再办 ☐ 其他 ______ +回复:☐ 现在就办(我方可提供绑定步骤) ☐ 只上 H5,`union_id` 缓办 ☐ 其他 ______ ### E2. D3 门禁限流阈值口径已更新(备案,无需回复) @@ -282,18 +286,19 @@ B1 选 A,但希望增加审批环节 > 复测 250 人 **250/250**、500 人 **500/500**;失败侧两个计数器原样未动 > (防枚举的强度没有降低)。详见《09-部署与运维》§9.3。 -### E3. 二期(社会收集 / 社会评价 / 专项职业能力)的启动时点 +### E3. ~~二期(社会收集 / 社会评价 / 专项职业能力)的启动时点~~ —— **已取消**(2026-10) 一期范围已按约定锁死为「学校收集 + 自助认定」(其他模式一律返回 `40044`)。 -但**二期什么时候启动**会影响两件现在就能做、也可以等的事: +> ⚠️ **2026-10 已确定:不做二期**,本节作废(保留原文供追溯)。原话是: +> 但**二期什么时候启动**会影响两件现在就能做、也可以等的事: -| 事项 | 如果二期在 3 个月内启动 | 如果更晚 | +| ~~事项~~ | ~~如果二期在 3 个月内启动~~ | ~~如果更晚~~ | |---|---|---| | D4 材料识别强特征词表标定 | 建议现在就在社会评价批次数据上标定 | 可现在只做学校批次的标定 | | D5 一寸照片形态阈值标定 | 同上 | 同上 | -| 二期审核流与会话设计 | 建议现在出一版概要设计,避免一期代码挡住它 | 一期交付后再说 | +| ~~二期审核流与会话设计~~ | **不做**(2026-10 取消二期) | — | -回复:☐ 二期预计 ____ 启动 ☐ 暂无计划 +回复:☑ **暂无计划(2026-10 已确认取消二期)** ### E4. 国标职业库数据的来源(字典页「职业(国标)」目前是空的) @@ -342,7 +347,7 @@ B1 选 A,但希望增加审批环节 | # | 确认结论 | 落地动作 | 状态 | |:--:|---|---|:--:| -| **B1** | **选 A:增加「申请加入名单 → 管理员审批」通道** | ⚠️ **需开发**:新增 FR-509 系列 + 1 张表 + 4 个接口 + 2 屏原型 | 🔨 **待实施** | +| ~~**B1**~~ | ~~选 A:增加「申请加入名单 → 管理员审批」通道~~ → **已撤销**(2026-10 需求回退):该通道、表、接口、学生端页面与错误码已全部删除 | ⚠️ **需求回退,建议补变更确认单**:名单外的人改由老师在任务里**手工加人**;系统内不展示联系方式 | ❌ **已撤销** | | B2 | 社会收集名单**由报名点/机构维护预报名名单**,导入后开放;**填报期内可随时追加** | 已一致:FR-508 追加导入;补充说明写入 PRD §5.5 | ✅ 已一致 | | B3 | **字段三分类**:姓名/证件号/报考三项锁定,其余可核对修改并留痕 | 已一致:FR-502b + `changed_fields` 机制 | ✅ 已一致 | | B4 | **不加手机号验证码** | 无需改动(当前门禁即姓名+证件号) | ✅ 已一致 | @@ -352,8 +357,8 @@ B1 选 A,但希望增加审批环节 | B8 | **一期不对接机构系统,预留开放接口** | 已一致:§14 开放接口已设计但不启用 | ✅ 已一致 | | B9 | **不做公安实名核验** | 无需改动 | ✅ 已一致 | -> **结论:B 组 9 项中 8 项零改动,仅 B1 需开发。** -> B1 的建议是我方主动提出的(原需求只有"只提示"),客户采纳后**这是一次功能增量**,已按 §变更记录 追加设计与排期。 +> **结论:B 组 9 项中 8 项零改动;B1 曾是我方主动提出的功能增量、客户采纳后已开发,但于 2026-10 随需求收敛整体撤销。** +> ⚠️ 它是本函里**唯一的功能增量**,因此这次撤销属**需求回退**,建议补一张变更确认单。 ### C 组 · 商务法务(5 项) @@ -372,7 +377,7 @@ B1 选 A,但希望增加审批环节 | D1 | OCR 引擎选型(腾讯云/百度/本地) | ⏳ M2 结束前定 | | D2 | VLM 抽成可替换接口,不绑定供应商 | ✅ 已按此设计(04 文档 M3 §3.6) | | D3 | 门禁限流:openid 10 次/小时、IP 20 次/分钟 | ✅ 已实现口径 | -| D4 | **需索取「社会评价」批次的原始材料样本** | ✅ **已后置为二期**(一期只做学校收集 + 自主认定,不涉及社会评价材料) | +| D4 | **需索取「社会评价」批次的原始材料样本** | ✅ **已作废**(2026-10 取消二期,不再需要该样本) | #### D4 补充说明:为什么需要「社会评价」的样本 @@ -404,8 +409,8 @@ B1 选 A,但希望增加审批环节 | 退而求其次 | **10~20 份**,但**必须覆盖「学信网备案表」和「各类佐证材料」**(这两类现在零样本) | | 特别注意 | 记录**材料来源与格式**——社会人员可能直接从学信网下载 **PDF**(而非拍照/扫描)。若是,`file_types` 白名单、预览、压缩策略都要跟着调整 | -> ✅ **已后置为二期**(2026-09-28 客户确认):一期范围收窄为「**学校收集 + 自主认定**」,材料只有 4 类(一寸照片 / 身份证 / 学籍表 / 工作证明),**全部有 112 个真实文件实测**。 -> 因此 D4 **不再是待办、也不阻塞任何里程碑**。待二期做「社会评价」时再索取即可。 +> ✅ **已作废**(2026-10 取消二期;2026-09-28 曾收窄为「**学校收集 + 自主认定**」),材料只有 4 类(一寸照片 / 身份证 / 学籍表 / 工作证明),**全部有 112 个真实文件实测**。 +> 因此 D4 **不是待办、也不阻塞任何里程碑**;由于二期已取消,**该样本不再需要索取**。 | D5 | 一寸照片形态阈值用真实样本标定 | ✅ 已有实测(984×1378,5:7) | | D6 | **微信三条来源通道体积上限需真机实测** | ⏳ 待测 | | D7 | ~~文件名规则在"学生原始乱名"上测真实准确率~~ | ✅ **已降级为文案校准**(不需样本) | diff --git a/docs/02-需求追溯矩阵.md b/docs/02-需求追溯矩阵.md index 85eb49d..3a65bae 100644 --- a/docs/02-需求追溯矩阵.md +++ b/docs/02-需求追溯矩阵.md @@ -2,7 +2,7 @@ > 用途:把 PRD 的每条需求对上「**原型 / 库表 / 接口 / 验收**」四个交付面,找出**说了没做**和**做了没说**。 > 生成日期:2026-09-28 -> 审计范围:**125 条 FR**(92 P0 / 30 P1 / 3 P2)+ **21 条 VAL 校验规则** + **121 个接口** + **20 张表** +> 审计范围:**125 条 FR**(92 P0 / 30 P1 / 3 P2)+ **21 条 VAL 校验规则** + **119 个接口**(2026-10 收敛后的口径;原 129 = 121 + 角色与授权 8)+ **20 张表** > 结论:**主流程全覆盖。第三节的 10 个接口缺口已于 P3 补齐(共新增 21 个接口);配置类页面缺口见 3.2** --- @@ -12,7 +12,7 @@ | 模块 | 条数 | P0 | 库表 | 接口 | 原型 | 判定 | |---|:--:|:--:|:--:|:--:|:--:|:--:| | FR-100 租户与组织 | 5 | 5 | ✅ | ✅ | ❌ | ⚠️ 缺平台运营端原型 | -| FR-200 账号与角色 | 6 | 5 | ✅ | ✅ | ❌ | ⚠️ 缺登录页与用户管理页 | +| FR-200 账号与角色 | 6 | 5 | ✅ | ✅ | ⚠️ | ⚠️ 缺登录页与用户管理页;**FR-205 角色自定义已落地**(`docs/03` §4.12 / §5.10,页面 `frontend-pc/src/views/role/`;2026-10 起按**一期功能**计,不再叫"二期")——其中「数据权限可配」**不做** | | FR-300 收集任务 | 12 | 9 | ✅ | ✅ | ⚠️ | ✅ 暂停接口已补(P3) | | FR-400 动态表单 | 8 | 6 | ✅ | ✅ | ❌ | ⚠️ **缺表单设计器原型** | | FR-500 名单管理 | 24 | 21 | ✅ | ✅ | ✅ | ✅ **全覆盖**(含客户确认新增的 FR-509 系列 10 条) | @@ -23,7 +23,7 @@ | FR-1100 数据看板 | 5 | 2 | ✅ | ✅ | ✅ | ✅ **全覆盖** | | FR-1200 系统管理 | 6 | 5 | ⚠️ | ✅ | ❌ | ⚠️ 接口已补(P3),原型仍缺 | | FR-1300 SaaS 计费 | 4 | 2 | ⚠️ | ⚠️ | ❌ | ⚠️ 缺平台运营端 | -| FR-1400 历史整理器 | 6 | 0 | ✅ | ❌ | ❌ | ❌ **三面全缺**(P1,本轮未做) | +| ~~FR-1400 历史整理器~~ | ~~6~~ | ~~0~~ | — | — | — | **已取消**(2026-10 需求收敛:模块/表/接口/页面全部删除) | | **FR-700 校验引擎** | — | — | ✅ | ✅ | ✅ | ✅ 以 VAL-01~21 实现,三端均有体现 | **图例**:✅ 完整覆盖 ⚠️ 部分覆盖 ❌ 未覆盖 @@ -55,7 +55,7 @@ | # | 缺口 | 对应需求 | 影响 | 建议接口 | |:--:|---|---|---|---| | I1 | **报考范围读取与确认** | FR-307~311 | 管理端④整屏无数据来源 | `GET/PUT /api/v1/tasks/{id}/plan-scope` | -| I2 | **历史数据整理器** | FR-1401~1406 | §5.14 六条需求**完全无接口** | `POST /api/v1/cleanup/jobs` 等 5 个 | +| ~~I2~~ | ~~**历史数据整理器**~~ | — | **已取消**(2026-10) | — | | I3 | **字典管理** | FR-1201 | 学历/等级/补考科目等下拉项无法维护 | `GET/POST/PUT /api/v1/dicts` | | I4 | **操作日志** | FR-1202 | 审计留痕无法查询 | `GET /api/v1/logs/operations` | | I5 | **登录日志** | FR-1203 | 同上 | `GET /api/v1/logs/logins` | @@ -75,14 +75,14 @@ |:--:|---|---|:--:|---| | P1 | **表单设计器** | FR-401~408 | 高 | ⚠️ **必须补**——拖拽式字段编排是 P0,且交互复杂,光靠文字描述开发必理解偏 | | P2 | **材料规则配置**(命名模板编辑器) | FR-601~609 | 高 | ⚠️ **建议补**——管理端②只做了"试跑",配置界面本身没画 | -| P3 | 历史数据整理器 | FR-1401~1406 | 中 | 建议补(差异化获客功能,客户演示时最吸睛) | +| ~~P3~~ | ~~历史数据整理器~~ | — | — | **已取消**(2026-10 需求收敛) | | P4 | 登录页 | FR-201 | 低 | 可略 | | P5 | 组织与用户管理 | FR-103、203~206 | 中 | 可略(标准 CRUD) | | P6 | 任务列表与详情 | FR-301~306 | 中 | 建议补简版(克隆、二维码、暂停) | | P7 | 通知模板管理 | FR-1001~1002 | 低 | 可略 | | P8 | 字典管理 | FR-1201 | 低 | 可略 | | P9 | 操作日志查询 | FR-1202~1203 | 低 | 可略 | -| P10 | 平台运营端(租户/套餐/计费) | FR-101~102、1301~1304 | 中 | 建议二期做(一期客户是学校,不关心平台运营) | +| P10 | 平台运营端(租户/套餐/计费) | FR-101~102、1301~1304 | 中 | **不做**(2026-10:不再设计二期;平台运营端只保留租户开通与用量) | ### 3.3 需求条目本身的核查 @@ -118,7 +118,7 @@ 规则引擎(命名/压缩/识别) ████████████████████ 100% 配置类页面 ██████░░░░░░░░░░░░░░ 30% 平台运营 ░░░░░░░░░░░░░░░░░░░░ 0% -历史整理器 ░░░░░░░░░░░░░░░░░░░░ 0%(仅库表) +~~历史整理器~~ ░░░░░░░░░░░░░░░░░░░░ 已取消(2026-10) ``` ### 必须行动的 3 项 @@ -136,7 +136,7 @@ | # | 任务 | 产出 | 理由 | |---|---|---|---| -| **P2.5** | **管理端补充原型**:表单设计器 + 材料规则配置 + 整理器 | 补充到 `admin-web.html` | 追溯矩阵发现的缺口,三者都是**复杂交互、不画会理解偏**的页面 | +| **P2.5** | **管理端补充原型**:表单设计器 + 材料规则配置 + ~~整理器~~(该屏已于 2026-10 删除) | 补充到 `admin-web.html` | 追溯矩阵发现的缺口,三者都是**复杂交互、不画会理解偏**的页面 | --- @@ -156,9 +156,9 @@ | FR-1100 | FR-1101 ~ 1105 | 5 | 看板 | | FR-1200 | FR-1201 ~ 1206 | 6 | 系统管理 | | FR-1300 | FR-1301 ~ 1304 | 4 | 计费 | -| FR-1400 | FR-1401 ~ 1406 | 6 | 整理器 | +| ~~FR-1400~~ | ~~FR-1401 ~ 1406~~ | ~~6~~ | **已取消**(2026-10) | | **合计** | | **125** | **92 P0 / 30 P1 / 3 P2** | **校验规则**:VAL-01 ~ VAL-21(21 条,编号连续;18~20 为 P4 补充,**21 为客户确认后补充的归属校验**) -**接口**:**121 个已定义**(原 96 + 补齐 21 + 客户确认新增 5 − 客户要求移除 1,见 `03-接口设计.md`) +**接口**:**119 个已定义**(2026-10 收敛:原 129 = 96 + 补齐 21 + 客户确认新增 5 + 角色与授权 8 − 移除 1;现减去**加入申请 5 个**与**历史整理器 5 个** = **119**,补齐 21 → 16),见 `03-接口设计.md` §4.12 / §5.10 **库表**:**20 张**完整 DDL + 4 张"从略"标准表 diff --git a/docs/03-接口设计.md b/docs/03-接口设计.md index a799853..c0706eb 100644 --- a/docs/03-接口设计.md +++ b/docs/03-接口设计.md @@ -14,16 +14,16 @@ | §1 | 通用约定(域名、鉴权、响应、分页、幂等、限流、脱敏) | | §2 | 枚举字典全集 | | §3 | 通用数据结构(Schema 定义) | -| §4 | 接口总览(**121 个**) | +| §4 | 接口总览(**119 个**) | | §5 | 认证与账号(13) | | §6 | 组织与租户(15) | -| §7 | 任务与配置(30) | -| §8 | 名册(12) | +| §7 | 任务与配置(25) | +| §8 | 名册(7) | | §9 | 填报端 · 小程序(16) | | §10 | 班主任移动端(10) | | §11 | 审核(9) | | §12 | 校验 / 导出 / 通知(12) | -| §13 | **本次补齐的缺口接口(21)** | +| §13 | **本次补齐的缺口接口(16)** | | §14 | 开放接口 · 供认定机构对接(4) | | §15 | 错误码全集 | @@ -212,6 +212,12 @@ > ⚠️ **`WORK_PROOF` 的名称陷阱**:按机构模板,**「职业技能等级个人申报表」的规范文件名是「工作证明」**。这是机构既有习惯,不要"修正"。 +> ⚠️ **一期实际只有 4 类材料**:`PHOTO1` / `IDCARD` / `SCHOOL_ROLL` / `WORK_PROOF`。 +> `XJW_BA` 与 `OTHER_PROOF` 是需求收敛(2026-10)**之前**留下的编码 —— +> **历史数据读得到、但"新增材料"选不到**(`GET /api/v1/material-catalog` 的可选项不含它们), +> 内置材料模板也只剩 `TPL_SCHOOL_ROLL` 一个(原 `TPL_SOCIAL` / `TPL_ZHUANXIANG` 已删)。 +> 这样处理是为了**不让历史任务里的材料项悬空**:删编码会让已配的任务读不出材料名。 + ### 2.2 收集模式 `collectMode` | 值 | 含义 | 审核流 | @@ -266,7 +272,7 @@ | 枚举 | 取值 | |---|---| | `userType` | `1` 平台运营 / `2` 学校管理员 / `3` 班主任 / `4` 学生 | -| `roleCode` | `PLATFORM_ADMIN` / `SCHOOL_ADMIN` / `HEAD_TEACHER` / `DEPUTY_TEACHER` / `STUDENT` / `AUDITOR` | +| `roleCode` | `PLATFORM_ADMIN` / `SCHOOL_ADMIN` / `HEAD_TEACHER` / `STUDENT` | | `taskStatus` | `0` 草稿 / `1` 收集中 / `2` 已截止 / `3` 已归档 / `4` **已暂停** (与后端 `CollectTask.STATUS_*` 及 DDL 列注释一致)| | `rosterSubmitStatus` | `0` 未提交 / `1` 已提交 / `2` 已通过 / `3` 已驳回 | | `fileValidateStatus` | `0` 待校验 / `1` 通过 / `2` 警告 / `3` 不通过 / `4` 待人工 | @@ -275,10 +281,8 @@ | `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` **导出完成** | | `exportStatus` | `0` 排队中 / `1` 打包中 / `2` 已完成 / `3` 失败 / `4` 已过期 | -| `cleanupSourceType` | `1` 压缩包 / `2` 文件夹 / `3` 云盘链接 | -| `cleanupStatus` | `0` 排队 / `1` 解析中 / `2` 处理中 / `3` 已完成 / `4` 失败 | > ✅ **`notifyBizType` 的 `5`/`6` 曾经在本文件内部对调过(已修正,2026-09-30 客户确认)**: > 本表旧版写「`5` 导出完成 / `6` 催报」,与 §13.5 消息中心示例、落库值 @@ -487,7 +491,7 @@ | `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` 的口径曾经有两套,已确认(2026-09-30 客户确认)**:本表旧版写 @@ -573,7 +577,6 @@ | `UserVO` | `id` `username` `realName` `phone` `orgId` `orgName` `roleCodes[]` `userType` `status` `lastLoginTime` | | `ExportTaskVO` | `id` `taskId` `exportName` `scopeJson` `splitSizeMb` `desensitize` `status` `progress` `fileCount` `fileSizeMb` `resultFiles[]{ name, sizeMb, url }` `errorMsg` `expireTime` `finishTime` | | `NotifyRecordVO` | `id` `taskId` `channel` `bizType` `receiverId` `receiverName` `receiverRole` `templateCode` `content` `status` `failReason` `sendTime` | -| `CleanupJobVO` | `id` `jobName` `sourceType` `sourcePath` `targetTaskId` `status` `progress` `totalFiles` `resultPath` `reportJson` `finishTime` | | `DictItemVO` | `id` `dictCode` `dictName` `itemCode` `itemName` `sortNo` `parentItemCode` `status` `builtin` | | `MessageVO` | `id` `bizType` `title` `content` `read` `linkType` `linkId` `createTime` | @@ -581,7 +584,10 @@ ## §4 接口总览 -> 合计 **121 个**:PRD §8 已定义 96 个 + 追溯矩阵补齐 **21 个** + 客户确认新增 **5 个**,减去客户要求移除的联系方式接口 1 个(标 ⭐)。补齐项见 §4.9 索引,客户确认新增见 §8.7。 +> 合计 **119 个**:PRD §8 已定义 96 个 + 追溯矩阵补齐 **16 个** + **「角色与授权」8 个**,减去客户要求移除的联系方式接口 1 个(标 ⭐)。 +> 补齐项见 §4.9 索引,角色与授权见 §4.12 与 §5.10。 +> ⚠️ **需求收敛(2026-10)的减法**:原「客户确认新增 5 个」(加入申请,确认函 B1)**整条通道取消**, +> 原「补齐 21 个」中的「历史整理器 5 个」**随模块删除** → 21 − 5 = 16,故 96 + 16 + 8 − 1 = **119**。 > 「权限」列:`公开` / `学生` / `教师` / `管理` / `运营`(平台方)。 > 「修补」列指该接口是补齐追溯矩阵审计发现的缺口。 @@ -589,14 +595,15 @@ |---|:--:| | 认证与账号 | 13 | | 组织与租户 | 15 | -| 任务与配置 | 30 | -| 名册 | 12 | +| 任务与配置 | 25 | +| 名册 | 7 | | 填报端 · 小程序 | 16 | | 班主任移动端 | 10 | | 审核 | 9 | | 校验 / 导出 / 通知 | 12 | | 开放接口 | 4 | -| **合计** | **121** | +| ⭐ 角色与授权 | 8 | +| **合计** | **119** | ### 4.1 认证与账号(13) @@ -636,7 +643,7 @@ | ⭐ GET | `/logs/operations` | **操作日志** | 管理 | | ⭐ GET | `/logs/logins` | **登录日志** | 管理 | -### 4.3 任务与配置(30) +### 4.3 任务与配置(25) | | 路径 | 说明 | 权限 | |---|---|---|---| @@ -665,13 +672,8 @@ | GET | `/tasks/{id}/qrcode` | 填报二维码 | 管理 | | GET | `/rule-templates` | 规则模板列表 | 管理 | | POST | `/rule-templates/{id}/apply` | 应用模板 | 管理 | -| ⭐ POST | `/cleanup/jobs` | **创建整理任务** | 管理 | -| ⭐ GET | `/cleanup/jobs` | **整理任务列表** | 管理 | -| ⭐ GET | `/cleanup/jobs/{id}` | **整理进度** | 管理 | -| ⭐ GET | `/cleanup/jobs/{id}/report` | **体检报告** | 管理 | -| ⭐ POST | `/cleanup/jobs/{id}/import` | **结果导入为正式任务** | 管理 | -### 4.4 名册(12) +### 4.4 名册(7) | | 路径 | 说明 | 权限 | |---|---|---|---| @@ -682,11 +684,6 @@ | GET | `/tasks/{id}/roster/template` | 下载导入模板 | 管理 | | POST | `/roster/{rid}/notify` | 定向催报 | 管理/教师 | | POST | `/tasks/{id}/roster/notify-unsubmitted` | 一键催报未提交者 | 管理/教师 | -| ⭐ POST | `/mp/roster/join-request` | **提交加入申请** | 学生 | -| ⭐ GET | `/mp/roster/join-request` | **我的申请状态** | 学生 | -| ⭐ GET | `/roster/join-requests` | **申请待办列表** | 管理 | -| ⭐ POST | `/roster/join-requests/{id}/approve` | **审批通过** | 管理 | -| ⭐ POST | `/roster/join-requests/{id}/reject` | **审批驳回** | 管理 | ### 4.5 填报端 · 小程序(16) @@ -757,10 +754,11 @@ | GET | `/notify/records` | 通知记录 | 管理 | | POST | `/callback/wechat/msg` | 微信订阅消息回调 | 公开 | -### 4.9 ⭐ 本次新增接口索引(21) +### 4.9 ⭐ 本次新增接口索引(16) > 本表是**索引**,不是额外接口——其中 5 个已在上方 §4.3 / §4.7 列出,**不计入总数**。 -> 表内 18 行覆盖 **21 个接口**(含 GET/PUT、GET/POST 合并行)。 +> 表内 13 行覆盖 **16 个接口**(含 GET/PUT、GET/POST 合并行)。 +> ⚠️ 需求收敛(2026-10)后,原表里的**历史整理器 5 个**随模块删除,故 21 − 5 = **16**。 | | 路径 | 说明 | 详情节 | |---|---|---|---| @@ -768,11 +766,6 @@ | ⭐ | `/tasks/{id}/pause` `/resume` | 暂停 / 恢复任务 | §7.4 | | ⭐ | `/audit/submissions/{id}/revoke` | 撤回已审 | §11.4 | | ⭐ | `/mp/files/{fileId}/process` | **文件规范化总入口**(原仅在 §8.9 详述,未登记进清单) | §9.6 | -| ⭐ | `/cleanup/jobs` | 创建整理任务 | §13.2 | -| ⭐ | `/cleanup/jobs` | 整理任务列表 | §13.2 | -| ⭐ | `/cleanup/jobs/{id}` | 整理进度 | §13.2 | -| ⭐ | `/cleanup/jobs/{id}/report` | 体检报告 | §13.2 | -| ⭐ | `/cleanup/jobs/{id}/import` | 结果导入为正式任务 | §13.2 | | ⭐ | `/dicts` | 字典列表 | §13.3 | | ⭐ | `/dicts/{code}/items` | 字典项(GET / POST) | §13.3 | | ⭐ | `/dicts/items/{id}` | 修改字典项 | §13.3 | @@ -783,7 +776,7 @@ | ⭐ | `/mp/teacher/messages` | 教师消息中心 | §13.5 | | ⭐ | `/mp/subscribe` | 订阅消息额度登记 | §13.6 | -**新增 21 个的分布** +**新增 16 个的分布** | 归属 | 数量 | 明细 | |---|:--:|---| @@ -791,7 +784,6 @@ | 任务状态 | 2 | pause / resume | | 审核 | 1 | revoke | | 文件处理 | 1 | process(登记遗漏) | -| 历史整理器 | 5 | cleanup 全套 | | 字典 | 4 | dicts | | 日志 | 2 | operations / logins | | 消息中心 | 3 | 学生 2 + 教师 1 | @@ -806,33 +798,35 @@ | GET | `/open/v1/tasks/{id}/files` | 批量获取文件签名 URL | | POST | `/open/v1/callback/push` | 推送审核结果到机构系统 | -### 4.11 ⭐ 客户确认后新增(5) +### 4.11 ⭐ 加入申请(已撤销 · 0 个) -> 来源:确认函 **B1**(客户确认采用"申请加入名单 → 管理员审批"通道)。这是本次唯一的功能增量。 -> 详述见 **§8.7**。 +> ⚠️ **本节原为「客户确认后新增 5 个」**(确认函 **B1**:申请加入名单 → 管理员审批)。 +> **需求收敛(2026-10)后整条通道取消**:模块 `module/join`、5 个接口、`join:audit` 权限码、 +> `collect_join_request` 表、学生端「申请加入名单」页与 5 个专用错误码 +> (`40037` / `40038` / `40039` / `40040` / `40042`)**已全部删除**。 +> +> **门禁失败后的口径**:只保留「**重新填写**」一个系统内动作 + 一句纯文案提示 +> "如确认已报名,请联系老师把你加入名单"。名单外的人由老师在任务里**手工加人**(`FR-508` 追加导入), +> **不展示任何联系方式**(铁律:联系方式不进系统)。 +> +> ⚠️ **治理提示**:B1 是确认函里**唯一的功能增量**,本次属**需求回退**,建议补一张变更确认单。 + +### 4.12 ⭐ 角色与授权(8) + +> 来源:**FR-205「角色自定义与数据权限」**。内置角色 **4 个**(平台运营 / 学校管理员 / 班主任 / 学生, +> 权限由代码 `BuiltinRole` 决定、界面只读),租户可另建**自定义角色**并配置权限码与可见菜单。 +> 详述见 **§5.10**。 | | 路径 | 说明 | 权限 | |---|---|---|---| -| ⭐ | `/mp/roster/join-request` | 提交加入申请 / 查看我的申请(GET / POST) | 学生 | -| ⭐ | `/roster/join-requests` | 申请待办列表 | 管理 | -| ⭐ | `/roster/join-requests/{id}/approve` | 审批通过(自动建名册项 + 自动绑定) | 管理 | -| ⭐ | `/roster/join-requests/{id}/reject` | 审批驳回(原因必填) | 管理 | - -**设计边界(三条)** - -| # | 边界 | 理由 | -|:--:|---|---| -| 1 | **只解决"进名单",不绕过门禁** | 申请期间仍不能填报;审批通过后学生**仍需重新核验**(或凭已绑定 openid 直接放行) | -| 2 | **证件号仅存 HMAC,不落明文** | 与门禁口径一致;申请记录保留 2 年 | -| 3 | **驳回必须逐条填原因;批量通过不提供"全选"** | 身份认定环节误操作代价极高,与"批量通过只带绿色项"同源 | - -**与现有机制的关系** - -| 机制 | 区别 | -|---|---| -| `FR-508` 名册追加导入 | 老师批量加人;申请通道是**学生自助 + 逐条审批** | -| `FR-504` 统一失败提示 | 申请通道**不破坏**统一提示——`40039`(已在名单中)不泄露新信息 | -| `FR-504d` 已被他人绑定 | 申请通过后若该证件号已被占用 → `40041`,不覆盖 | +| ⭐ GET | `/roles` | 角色列表(内置 + 自定义) | 管理 | +| ⭐ GET | `/permissions` | 权限码目录(按分组) | 管理(`role:manage`) | +| ⭐ POST | `/roles` | 新增自定义角色 | 管理(`role:manage`) | +| ⭐ PUT | `/roles/{id}` | 修改自定义角色(名称 / 用户类型 / 备注) | 管理(`role:manage`) | +| ⭐ DELETE | `/roles/{id}` | 删除自定义角色(软删) | 管理(`role:manage`) | +| ⭐ PUT | `/roles/{id}/permissions` | 设置权限码(覆盖式) | 管理(`role:manage`) | +| ⭐ PUT | `/roles/{id}/menus` | 设置可见菜单(覆盖式) | 管理(`role:manage`) | +| ⭐ PUT | `/roles/{id}/status` | 启用 / 停用角色 | 管理(`role:manage`) | --- @@ -988,8 +982,9 @@ > ② 有维护成本——班主任换人、号码变更都要同步; > ③ 多一处隐私暴露面——即使是脱敏号,也意味着系统里要多存一份联系方式数据。 > -> **替代**:门禁失败页只保留两条**系统内的动作**——「重新填写」与「申请加入名单」(FR-509)。 -> 纯文案提示(如"如有疑问请联系老师")保留,因为**不涉及任何联系方式数据**。 +> **替代**:门禁失败页只保留「**重新填写**」一个**系统内动作**(原「申请加入名单」通道已于 2026-10 需求收敛时取消)。 +> 纯文案提示(如"如确认已报名,请联系老师把你加入名单")保留,因为**不涉及任何联系方式数据**; +> 名单外的人由老师在任务里手工加人(`FR-508` 追加导入)。 ### 5.6 `GET /api/v1/users` @@ -1048,6 +1043,92 @@ --- +### 5.10 角色与授权(§4.12 的 8 个接口) + +> 对应 **FR-205**。内置角色 **4 个**(`PLATFORM_ADMIN` / `SCHOOL_ADMIN` / `HEAD_TEACHER` / `STUDENT`), +> 权限由代码 `BuiltinRole` 决定、界面**只读**(能看、能当模板,改不了); +> 租户可另建**自定义角色**,于是"角色 → 权限码"与"角色 → 可见菜单"两件事可以由租户自己配。 +> 改授权**立即生效**(服务端写授权后清缓存),不需要重新登录。 +> +> 权限码本身**仍然定义在代码里**(`PermissionDef`,**28 个**),没有 `sys_permission` 定义表 —— +> 它是前后端契约:后端用它校验、前端用它控制按钮显隐,存 DB 就会漂移。 +> +> ⚠️ **需求收敛(2026-10)**:本条不再是"二期功能",它就是一期能力;同时 +> **数据范围(本人/本班/本院系/全租户)只展示、不提供修改入口** —— +> 它目前由"拿到了哪些权限码 + 任务里被指派了哪些班"共同决定,做成可配要先把判据收口(**本次不做**)。 + +**五条口径(实现与文档同源,改一处必须改另一处)** + +| # | 口径 | 为什么 | +|:--:|---|---| +| 1 | **内置角色只读** | `roleType=1` 的角色可以看权限矩阵,但改 / 删 / 授权 / 停用一律 `40001`。一次误操作就能把管理员锁在系统外,而这类错误不可逆 | +| 2 | **内置角色的权限以代码为准** | 列表里内置角色的 `permCodes` / `menus` 由代码返回,**不读库**;库里的行只是"看得见、可审计"(每日定时任务把两者对齐,见 §17.2 D-13) | +| 3 | **平台级能力与入口不给学校** | 学校租户授予 `tenant:manage` / `tenant:view` / `log:viewAll` → `40301`;配置平台专用菜单 `tenant` → `40301`;建"平台运营"类角色(`userType=1`)→ `40301`;发 `PLATFORM_ADMIN` 给账号 → `40301` | +| 4 | **防自锁** | 不允许去掉"自己进入本页所依赖的最后一条 `role:manage` 权限 / `role` 菜单",也不允许停用或删除那个角色;改账号角色时同理保护 `user:manage` | +| 5 | **改完立即生效** | 每次写授权都清该角色的缓存,所以**不需要重新登录**;停用角色立刻不再给权限(不是等 token 过期)。⚠️ 改**账号的角色归属**仍是"下次登录生效"(角色码在 token 里) | + +**`GET /api/v1/roles`** 权限:`role:manage` 或 `user:view` / `user:manage`(账号页的角色下拉要用它) + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | long | 角色 ID | +| `roleCode` | string | 角色码(建后不可改) | +| `roleName` | string | 角色名 | +| `roleType` | int | `1` 内置(只读)/ `2` 自定义 | +| `userType` | int | `1` 平台运营 / `2` 学校管理员 / `3` 班主任 / `4` 学生(账号的 `userType` 由它推导) | +| `dataScope` | int | `1` 全平台 / `2` 本租户 / `3` 本组织及下级 / `4` 仅本人(**本期只展示**,见下) | +| `status` | int | `0` 停用 / `1` 正常 | +| `userCount` | int | 有多少账号在用(> 0 时不可删除) | +| `editable` | bool | 能否编辑 / 删除 / 停用(内置恒 `false`) | +| `assignable` | bool | 能否发给账号(内置的「平台运营」在学校租户里 `false`:**看得见、发不出去**) | +| `permCodes` | string[] | 权限码(内置取代码,自定义取库) | +| `menus` | string[] | 可见菜单键(与前端 `XtMenu.key` 一致) | + +> ⚠️ **`dataScope` 本期只展示、不提供修改入口**:数据范围(本人 / 本班 / 本院系 / 全租户)目前由 +> "拿到了哪些权限码 + 任务里被指派了哪些班"共同决定,散在审核 / 催报 / 代传 / 看板各处; +> 做成可配要先把那些判据收口(单独一批)。让界面出现一个"改了不生效"的开关,比没有它更糟。 + +**`GET /api/v1/permissions`** 权限:`role:manage` + +返回按分组排列的 **28** 个权限码:`[{ group, items: [{ code, label, platform, platformOnly }] }]`。 +`platform` 为 `PC` / `MP` / `BOTH`;`platformOnly=true` 的码学校租户不可授予(界面禁用勾选,服务端 `40301`)。 + +**`POST /api/v1/roles`** 权限:`role:manage` + +| 字段 | 必填 | 说明 | +|---|:--:|---| +| `roleCode` | ✅ | 大写字母 / 数字 / 下划线,以字母开头,租户内唯一,**不得占用内置角色码** | +| `roleName` | ✅ | 显示名(可改) | +| `userType` | ✅ | 该角色对应的用户类型(学校租户不能选 `1`) | +| `remark` | — | 备注 | +| `permCodes` | ✅ | 至少一项(服务端校验存在性与平台级白名单) | +| `menus` | ✅ | 至少一个(服务端校验是否为现存菜单键、是否平台专用) | + +返回新角色 `id`。建后**不能改 `roleCode`**(它在 JWT 与操作日志里,改了历史留痕就对不上)。 + +**`PUT /api/v1/roles/{id}`** 权限:`role:manage` 只改 `roleName` / `userType` / `remark`。 +⚠️ 角色还有账号在用(`userCount > 0`)时改 `userType` → `40001`:那会让账号的 `user_type` 与角色不一致, +而 `user_type` 决定"他算学生还是老师"。 + +**`DELETE /api/v1/roles/{id}`** 权限:`role:manage` 软删(含授权与菜单行)。 +还有账号在用 → **`40045`**;命中防自锁 → `40001`。 + +**`PUT /api/v1/roles/{id}/permissions`** / **`.../menus`** 权限:`role:manage` +请求体 `{ permCodes: [] }` / `{ menus: [] }`,**覆盖式**;返回 `void`。写后立即失效该角色的授权缓存。 + +**`PUT /api/v1/roles/{id}/status?status=0|1`** 权限:`role:manage` +停用 = 该角色立刻不再给权限(**不必等 token 过期**);内置角色不可停用。 + +**错误码** + +| 码 | 场景 | +|---|---| +| `40001` | 角色码不合法 / 重复 / 占用内置码;权限码或菜单键不存在;改内置角色;`userType` 与在用的账号冲突;命中防自锁 | +| `40301` | 无 `role:manage`;授予平台级权限码;配置平台专用菜单;建平台运营类角色;跨租户(平台未选定学校) | +| `40045` | 角色还有账号在用,不可删除 | + +--- + ## §6 组织与租户 ### 6.1 `GET /api/v1/orgs/tree` @@ -1167,7 +1248,7 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手 **响应**:`{ "id": 2026090001, "taskCode": "2026090001", "status": 0 }` -> **社会收集的审核配置默认值**(二期):`needClassAudit: false`(跳过初审),由服务端强制设置,客户端传 true 会被忽略并返回 `warnings`。 +> **社会收集的审核配置默认值**(该模式**本次不做**,仅保留数据结构):`needClassAudit: false`(跳过初审),由服务端强制设置,客户端传 true 会被忽略并返回 `warnings`。 > ⚠️ **一期只支持 `collectMode=1`(学校收集)**。传 `2` 会被拒绝并返回 `40044`。 ### 7.3 `POST /api/v1/tasks/{id}/publish` @@ -1363,8 +1444,10 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手 | 模板 | 材料项 | |---|---| | `TPL_SCHOOL_ROLL` | 一寸照片 + 身份证 + 学籍表 + 工作证明 | -| `TPL_SOCIAL` | 一寸照片 + 身份证 + 工作证明 + 学信网备案表 | -| `TPL_ZHUANXIANG` | 一寸照片 + 身份证 + 工作证明 | + +> ⚠️ **需求收敛(2026-10)后模板只剩这一个**:原 `TPL_SOCIAL`(社会收集)与 +> `TPL_ZHUANXIANG`(专项职业能力)随"二期取消"一并删除;传这两个码会得到**不认识的模板**, +> 不再有对应的初始化行为。 ### 7.11 `GET /api/v1/tasks/{id}/statistics` @@ -1518,184 +1601,15 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手 --- -### 8.7 名单加入申请(FR-509 · 客户确认后新增) - -> **背景**:门禁不通过的人原先只能"联系老师",老师再手工加名单。几百人的社会收集场景下,老师负担极重。 -> 本组接口提供一条**受控通道**:学生自助申请 → 管理员审批 → 自动入册。 +> ⚠️ **本节已撤销(需求收敛 2026-10)**:原「名单加入申请」通道(`FR-509`,确认函 **B1**) +> 连同 5 个接口(`POST/GET /mp/roster/join-request`、`GET /roster/join-requests`、 +> `POST /roster/join-requests/{id}/approve|reject`)、`join:audit` 权限码、`collect_join_request` 表 +> 与错误码 `40037` / `40038` / `40039` / `40040` / `40042` **一并删除**。 > -> ⚠️ **关键边界**:这条通道**只解决"进名单",不绕过门禁**——申请期间学生仍不能填报。 - -#### 8.7.1 `POST /api/v1/mp/roster/join-request` - -**权限**:学生(**须已过门禁失败流程**) **限流**:同 openid 每任务 ≤2 次 - -| 字段 | 类型 | 必填 | 说明 | -|---|---|---|---| -| `taskId` | int64 | ✅ | — | -| `realName` | string | ✅ | 姓名(明文存储,业务必需) | -| `idCardNo` | string | ✅ | 身份证号(**服务端转 HMAC,不落明文**) | -| `phone` | string | ✅ | 手机号(**仅存 HMAC + 脱敏值**) | -| `orgHint` | string | — | 自述班级 | -| `reason` | string | — | 申请说明,如"我是转班过来的" | - -**响应** - -```json -{ - "code": 0, - "data": { - "requestId": 7001, - "status": 0, - "statusText": "已提交,等待学校审核", - "estimatedHours": 24, - "remainAttempts": 1 - } -} -``` - -**主要错误** - -| 码 | 场景 | 提示 | -|---|---|---| -| ⭐ `40037` | 同任务申请次数超限(>2) | "该任务申请次数已达上限,请联系老师" | -| ⭐ `40038` | 该证件号已有待审申请 | "你已提交过申请,请耐心等待" | -| ⭐ `40039` | 该证件号已在名册中 | "你已在名单中,请直接返回重新核验" | -| `40008` | 任务已截止/暂停 | — | - -> **`40039` 是必要的**:若证件号已在名册,说明学生**只是输错了姓名**,该引导他回去重填门禁,而不是提交申请。 -> 这一条不违反"统一失败提示"原则——因为学生已经主动提供了正确证件号,此时告知"你已在名单中"**不泄露任何新信息**。 - -#### 8.7.2 `GET /api/v1/mp/roster/join-request` - -**Query**:`taskId` - -**响应** - -```json -{ - "code": 0, - "data": { - "list": [ - { "requestId": 7001, "taskId": 2026090001, "taskName": "2026年9月职业技能认定", - "status": 0, "statusText": "待审核", - "realName": "李四", "idCardMask": "3610**********10", - "orgHint": "24中技数控2班", - "createTime": "2026-09-28T18:00:00+08:00", - "auditTime": null, "rejectReason": null, - "rosterId": null } - ], - "total": 1 - } -} -``` - -**驳回态**:`status: 2` + `rejectReason` 明文返回(学生需要知道原因)。 - -**通过态**:`status: 1` + `rosterId`,并提示"已加入名单,请返回重新核验进入填报"。 - -#### 8.7.3 `GET /api/v1/roster/join-requests` - -**权限**:管理 **Query**:`taskId`、`status`(默认 `0` 待审)、`orgId`、`keyword`、分页 - -**响应** - -```json -{ - "code": 0, - "data": { - "list": [ - { "requestId": 7001, "taskId": 2026090001, - "realName": "李四", "idCardMask": "3610**********10", - "phoneMask": "138****8888", - "orgHint": "24中技数控2班", - "reason": "我是转班过来的", - "status": 0, - "createTime": "2026-09-28T18:00:00+08:00", - "waitHours": 2.5, - "duplicateHint": null } - ], - "total": 1, - "counts": { "pending": 1, "approved": 12, "rejected": 3 }, - "alert": null - } -} -``` - -**`duplicateHint`**:若该证件号与**其他任务的名册**匹配上,返回提示(帮管理员判断是否本校学生): - -```json -"duplicateHint": { "type": "IN_OTHER_TASK", "taskName": "2026年3月认定", "realName": "李四" } -``` - -**`alert`**:单任务待审 > 50 条时返回 `{ "level": 2, "text": "待审申请已达 53 条,建议集中处理" }`(FR-509c)。 - -#### 8.7.4 `POST /api/v1/roster/join-requests/{id}/approve` - -**权限**:管理 **幂等**:是 - -| 字段 | 类型 | 必填 | 说明 | -|---|---|---|---| -| `orgId` | int64 | ✅ | **指定班级**(默认取自述班级,无则必填) | -| `realName` | string | — | 修正姓名(管理员核对后可改) | -| `idCardNo` | string | — | 修正证件号 | -| `seqNo` | int | — | 序号(默认追加到末尾) | - -**服务端动作(事务内)** - -``` -1. 校验申请状态 = 待审,否则 40040 -2. 校验该 task 下证件号未被占用,否则 40041 -3. 在 collect_roster 新建名册项: - real_name / id_card_no / id_card_hash / id_card_mask ← 申请值(或管理员修正值) - org_id ← 指定班级 - bind_user_id ← 申请人 uid(FR-509g:自动绑定,免重输) - bind_type ← 3(来自申请) - ext_json.source ← 'JOIN_REQUEST' -4. 回写 collect_join_request:status=1, audit_by, audit_time, roster_id -5. 写操作日志 JOIN_APPROVE -6. 推送通知 bizType=7 -``` - -**响应** - -```json -{ - "code": 0, - "data": { - "requestId": 7001, "status": 1, - "rosterId": 9029, - "rosterCreated": true, - "notifySent": true, - "canFillNow": true - } -} -``` - -> **`canFillNow: true`** 表示该 openid 已绑定,学生再次进入**直接放行**。 - -#### 8.7.5 `POST /api/v1/roster/join-requests/{id}/reject` - -**权限**:管理 **幂等**:是 - -| 字段 | 类型 | 必填 | 说明 | -|---|---|---|---| -| `reason` | string | ✅ | **驳回原因(必填)**,会原样推送给学生 | -| `reasonCode` | string | — | 快捷原因码(`NOT_THIS_SCHOOL` / `INFO_MISMATCH` / `NOT_IN_SCOPE` / `OTHER`) | - -**响应**:`{ "requestId": 7001, "status": 2, "notifySent": true }` - -**约束** - -| 条件 | 结果 | -|---|---| -| 已通过后再驳回 | `40042`(如需撤销请走名册删除) | -| 任务已归档 | `40034` | -| 批量审批 | **支持**,但**必须逐条确认**(不可一键全通过——涉及身份认定) | - -> ⚠️ **批量审批的设计取舍**:批量**通过**允许(管理员已看过列表),但**驳回必须逐条填原因**;且批量通过时**不提供"全选"快捷键**。 -> 理由与"批量通过只带绿色项"同源:**身份认定环节的误操作代价极高**。 - ---- +> **现在的口径**:门禁失败页只给「**重新填写**」+ 一句"如确认已报名,请联系老师把你加入名单"; +> 名单外的人由老师在任务里**手工加人**(§8.1 追加导入,`FR-508`)。**系统内不展示任何联系方式。** +> +> **治理提示**:B1 是客户确认函里**唯一的功能增量**,本次属**需求回退**,建议补一张变更确认单。 ## §9 填报端 · 小程序 @@ -1827,7 +1741,7 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手 | `fileName` | string | ✅ | 原始文件名(**可能是乱的,正常**) | | `fileSize` | int64 | ✅ | 字节 | | `contentType` | string | ✅ | MIME | -| `source` | int | — | `1` 本人上传 / `2` 班主任代传 / `3` 管理员代传 / `4` 整理器导入(口径见 §3.6 注) | +| `source` | int | — | `1` 本人上传 / `2` 班主任代传 / `3` 管理员代传 / `4` 整理器导入(**模块已取消,仅历史数据**;口径见 §3.6 注) | | `fileHash` | string | — | 客户端 SHA-256(**用于秒传与去重**) | **响应** @@ -1863,7 +1777,7 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手 > 记录问题(含 VAL-06 自动修复),默认**只存规范件**(`fill.storage.keep-raw=false`)。 > 所以本接口**只在「保留了原始件」时有用**:对原始件重新跑一遍规范化 > (典型场景:改了材料规则后重压)。默认配置下多数文件没有原始件,调用会明确说明这一点, -> 而不是假装处理了一遍。识别 / OCR 的四层递进是二期(M3/M4),见 §17.2 D-12。 +> 而不是假装处理了一遍。识别 / OCR 的四层递进**缓做**(等 OCR 供应商选型,见 §17.2 D-12 与《01》D1)——**不再作为二期功能设计**。 > 下面的响应结构保留作为「规范化产物」的字段参考(上传接口的产物与之同构)。 **说明**:对**有原始件**的文件重新执行规范化(压缩 + 改名 + 校验)。 @@ -1970,7 +1884,7 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手 ### 9.9 `POST /api/v1/mp/ocr/idcard`(一期缓做) > ⚠️ **一期未实现(等 OCR 供应商选型,《01-客户与机构确认函》D1)**。 -> 这是 121 个契约接口里**唯一**没有实现的一个;表单预填与校验位校验在学生端走人工填写。 +> 这是 119 个契约接口里**唯一**没有实现的一个;表单预填与校验位校验在学生端走人工填写。 > 选型要同时评估**数据出境合规**。选型定了之后本节按原设计实现,以下为设计参考。 **说明**:身份证 OCR,用于表单预填与校验。**这是唯一返回明文姓名的 OCR 场景**。 @@ -2047,7 +1961,7 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手 } ``` -**社会收集**(二期):`nextNode` 直接返回 `ADMIN_AUDIT`(跳过初审)。 +**社会收集**(本次不做,传 `collectMode=2` 直接 `40044`):`nextNode` 返回 `ADMIN_AUDIT`(跳过初审)。 ### 9.11 `POST /api/v1/mp/submissions/{id}/withdraw` @@ -2518,110 +2432,22 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手 | I8 审核撤回 | **§11.4**(已定义) | ✅ | | I9 任务暂停/恢复 | **§7.4**(已定义) | ✅ | | I10 材料识别结果读取 | **并入 §11.2 详情响应**(不新增接口) | ✅ | -| I2 历史数据整理器 | **§13.2** | ✅ | | I3 字典管理 | **§13.3** | ✅ | | I4 / I5 日志 | **§13.4** | ✅ | | I6 消息中心 | **§13.5** | ✅ | | I7 订阅额度登记 | **§13.6** | ✅ | -### 13.2 历史数据整理器(5 个)· 差异化获客功能 +### 13.2 历史数据整理器(已取消 · 0 个) -#### `POST /api/v1/cleanup/jobs` - -**说明**:上传已有压缩包/文件夹,用**与正式收集完全相同的规则**做规范化。 - -| 字段 | 类型 | 必填 | 说明 | -|---|---|---|---| -| `jobName` | string | ✅ | 如 `24中技数控1班-历史整理` | -| `sourceType` | int | ✅ | `1` 压缩包 / `2` 文件夹 / `3` 云盘链接 | -| `uploadToken` / `objectKey` | string | 条件 | `sourceType=1/2` 时必传(先走通用上传) | -| `sourceUrl` | string | 条件 | `sourceType=3` 时必传 | -| `namingTemplate` | string | — | 缺省用系统默认 | -| `compressConfig` | object | — | 缺省见 3.11 | -| `rosterTaskId` | int64 | — | **关联到已有任务的名册**(用于证件号纠错与串档检测) | - -**响应**:`{ "jobId": 301, "status": 0, "estimatedSeconds": 120 }` - -> `rosterTaskId` 是**关键参数**:没有名册就只能做文件名规范化,**有名单才能做证件号校验、疑似串档检测**——而"疑似串档 1 例"正是报告里最有价值的一条。 - -#### `GET /api/v1/cleanup/jobs` - -**Query**:`status`、`keyword`、分页 **响应**:`{ list: CleanupJobVO[], total }` - -#### `GET /api/v1/cleanup/jobs/{id}` - -**响应** - -```json -{ - "code": 0, - "data": { - "id": 301, "jobName": "24中技数控1班-历史整理", "status": 3, "progress": 100, - "totalFiles": 112, "processedFiles": 112, - "parsedStudents": 28, "parsedMaterials": 4, - "rawSizeMb": 436, "finalSizeMb": 37, "compressRatio": "91.5%", - "resultPath": "/cleanup/301/result.zip", - "finishTime": "2026-09-28T16:00:00+08:00" - } -} -``` - -#### `GET /api/v1/cleanup/jobs/{id}/report` ⭐ 体检报告 - -**Query**:`format`(`json` / `xlsx`) - -**响应(json)** - -```json -{ - "code": 0, - "data": { - "jobId": 301, - "summary": { - "totalFiles": 112, - "nameInvalid": 59, "sizeExceeded": 31, "formatConverted": 56, - "idcardMismatch": 5, "idcardAutoFixed": 3, "idcardNeedConfirm": 2, - "crossFileSuspected": 1, "lowResolution": 1, "orphanImages": 0 - }, - "autoHandled": 90, - "needManual": 1, - "highlights": [ - { "type": "CROSS_FILE", "level": 3, "rosterId": 9012, "realName": "张三", - "title": "疑似串档", "detail": "该生 4 项材料的证件号与名册均不同,且 4 项彼此一致", - "suggestion": "可能整个文件夹被换过,请核对学生本人" }, - { "type": "LOW_RESOLUTION", "level": 2, "rosterId": 9008, "realName": "李四", - "title": "分辨率偏低", "materialCode": "IDCARD", - "detail": "558×745,同批其他人为 1279×1706", "suggestion": "建议索取原图" } - ], - "nameFixedSamples": [ - { "original": "身份证.jpg.jpg", "fixed": "曾伊豪-361029200907295110-身份证.jpg", "rule": "VAL-06" } - ], - "idcardConflicts": [ - { "rosterId": 9012, "realName": "张三", - "rosterValue": "3610292009******10", "fileNameValue": "3610292009******99", - "ocrValue": "3610292009******10", - "verdict": "AUTO_FIXED", "verdictText": "OCR 与名册一致,按名册修正文件名" } - ] - } -} -``` - -> **报告里最重要的一句话是 `autoHandled: 90 / needManual: 1`**——「112 个文件里 90 个系统全自动处理,只有 1 个必须你亲自判断」。这是最有说服力的销售话术,也是接口必须算出来的数字。 - -#### `POST /api/v1/cleanup/jobs/{id}/import` - -**说明**:把整理结果**导入为正式任务**(名册 + 材料一次成型)。 - -| 字段 | 类型 | 必填 | 说明 | -|---|---|---|---| -| `taskName` | string | ✅ | 新任务名 | -| `collectMode` | int | ✅ | `1` / `2` | -| `batchNo` | string | ✅ | — | -| `importRoster` | bool | — | 是否把解析出的学员建成名册(默认 true) | -| `importFiles` | bool | — | 是否把文件挂到填报单上(默认 true) | -| `markAsSubmitted` | bool | — | 是否直接置为"已提交待审"(默认 true) | - -**响应**:`{ "taskId": 2026090002, "rosterCount": 28, "fileCount": 112 }` +> ⚠️ **本节已撤销(需求收敛 2026-10)**:历史整理器整块能力(`module/cleanup`)连同 +> 5 个接口(`POST/GET /cleanup/jobs`、`GET /cleanup/jobs/{id}`、`.../report`、`.../import`)、 +> `cleanup:run` 权限码、`collect_cleanup_job` 表、PC 端「历史整理器」菜单与「整理器」原型屏 +> **一并删除**,不再设计。 +> +> 保留这一节标题只是为了让旧的交叉引用(`§13.2`)仍能落到一个说清"为什么没有了"的地方 —— +> 节号不重排,`§13.3` 起的编号与代码注释里的引用都不受影响。 +> +> ⚠️ **别误伤**:`POST /api/v1/exports/cleanup-run` 是**导出包的过期清理**,属导出中心,**仍在**。 ### 13.3 字典管理(4 个) @@ -2767,7 +2593,6 @@ Excel 列:`层级` `组织名称` `组织编码` `上级编码` `负责人手 | `AUDIT_RESULT` | 提交成功后 | 通过 / 驳回通知 | | `DEADLINE_REMIND` | 进入填报页时 | 临期提醒 | | `EXPORT_READY` | 管理端创建导出时 | 导出完成 | -| `JOIN_APPROVE` | 申请加入名单时 | 审批结果 | **额度不足的降级链** @@ -2855,7 +2680,7 @@ X-Sign: HMAC-SHA256(appSecret, appKey + timestamp + nonce + body) ## §15 错误码全集 -> 在 PRD 附录 D(20 个码)基础上**新增 38 个**(标 ⭐),本节共 **58 个**(含 `0` 成功码)。所有码在 `code` 字段返回,HTTP 状态码见 §1.3。 +> 在 PRD 附录 D(20 个码)基础上**新增 34 个**(标 ⭐),本节共 **54 个**(含 `0` 成功码)。所有码在 `code` 字段返回,HTTP 状态码见 §1.3。 ### 15.1 客户端错误(4xxxx) @@ -2899,14 +2724,10 @@ X-Sign: HMAC-SHA256(appSecret, appKey + timestamp + nonce + body) | ⭐ `40034` | 已归档,不可撤回 | — | | ⭐ `40035` | **存在不合格文件,禁止导出** | 禁用导出按钮 + 展示 blocking 清单 | | ⭐ `40036` | 字典项有下级,不可停用 | 先处理下级 | -| ⭐ `40037` | **加入申请次数超限**(同 openid 每任务 >2) | 提示联系老师 | -| ⭐ `40038` | **该证件号已有待审申请** | "你已提交过申请,请耐心等待" | -| ⭐ `40039` | **该证件号已在名册中** | "你已在名单中,请返回重新核验"(引导回门禁,**不泄露新信息**) | -| ⭐ `40040` | 申请状态已变更(非待审) | 刷新列表 | | ⭐ `40041` | **名册中该证件号已被占用** | 不覆盖,提示管理员核查 | -| ⭐ `40042` | 申请已通过,不可再驳回 | 如需撤销请走名册删除 | | ⭐ `40043` | **文件归属存疑**(文件名指向名册中的另一个人,或归属无法确证) | **阻断提交**,提示"这份材料可能不是你的",引导重新上传(**无"仍然继续"选项**) | | ⭐ `40044` | **一期不支持该收集模式 / 认定类型** | 请使用学校收集 + 自主认定(见 PRD「一期范围声明」) | +| ⭐ `40045` | **角色还有账号在用,不可删除** | 先给这些账号换一个角色(界面会说明还有几个账号) | | `40101` | 未登录 / token 失效 | 静默重新登录 | | `40301` | 无权限(含跨租户) | 跳转 403 | | `40302` | 签名 URL 已过期 | 重新获取 | @@ -2932,6 +2753,7 @@ X-Sign: HMAC-SHA256(appSecret, appKey + timestamp + nonce + body) | 版本 | 日期 | 变更 | |---|---|---| +| **V2.7** | **2026-10** | **需求收敛:删角色 / 删两模块 / 取消二期**。① 内置角色 **6 → 4**(删 `DEPUTY_TEACHER` 副班主任、`AUDITOR` 审核员;挂这两个角色的存量账号由 **V19 迁移**直接删除);② 删「**历史整理器**」(5 个接口 + `cleanup:run` 权限码 + `collect_cleanup_job` 表 + §13.2 整节)与「**加入申请**」(5 个接口 + `join:audit` 权限码 + `collect_join_request` 表 + §8.7 整节 + 错误码 `40037`/`40038`/`40039`/`40040`/`40042`);③ `collect_task_org.deputy_user_ids`(协办人)**整列删除**,审核范围由三级回退收窄为**两级**(`head_user_id` → `sys_org.leader_user_id`);④ **不再有任何二期功能设计** —— 社会收集 / 社会评价 / 专项职业能力 / 数据范围可配 / 角色变更即时失效 / 平台角色页一律**不做**,OCR 与材料识别改为「**缓做,等供应商选型**」;⑤ 内置材料模板 **3 → 1**(仅 `TPL_SCHOOL_ROLL`),`XJW_BA` / `OTHER_PROOF` **读得到、选不到**。**数字**:契约 **129 → 119**、错误码 **59 → 54**、权限码 **30 → 28**、PC 菜单 **12 → 10**、小程序页面 **10 → 9**、原型 **32 → 30 屏**。⚠️ **加入申请(确认函 B1)是客户唯一确认的功能增量,本次属需求回退,建议补变更确认单**。详见《交接摘要》§一·三十六 | | **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.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`)而非密文,宽度足够,无需改动。 | @@ -2945,25 +2767,25 @@ X-Sign: HMAC-SHA256(appSecret, appKey + timestamp + nonce + body) | 检查项 | 结果 | |---|---| -| §4 各模块表行数合计 | **121** ✓ 与声称一致 | +| §4 各模块表行数合计 | **119** ✓ 与声称一致 | | §4 内路径重复 | 无 ✓ | | Markdown 表格列数一致性 | 0 行不符 ✓ | | 文档引用的字段名 | 116 个唯一值,**全部存在于 DDL** ✓ | -| 错误码数量 | 58 个(新增 38 个标 ⭐)✓ | -| 文件规模 | 约 100 KB / 2570 行 | +| 错误码数量 | 54 个(新增 34 个标 ⭐)✓ | +| 文件规模 | 约 89 KB / 2870 行 | --- ## §17 实现补充与偏差记录 -> **§4 的 121 个是设计口径**。本节回答两件事:① 代码里还有哪些 §4 没列的接口; +> **§4 的 119 个是设计口径**。本节回答两件事:① 代码里还有哪些 §4 没列的接口; > ② 哪些地方**有意偏离**了本文档的原始写法、为什么、谁确认的。 > > ⚠️ 17.1 的清单由工具生成:`python scripts/check-api-parity.py --extra` -> (当前 **41 个**,生成于 2026-09-30)。**手抄清单会过期,以命令输出为准**; +> (当前 **38 个**,2026-10 复核)。**手抄清单会过期,以命令输出为准**; > 下表按"为什么不在 §4"分组,便于决定"补进 §4 还是留在本节"。 -### 17.1 代码有、§4 清单没有的接口(41 个,按偏离原因分组) +### 17.1 代码有、§4 清单没有的接口(38 个,按偏离原因分组) **A · 材料访问通道(7 个)** —— 审核端与学生端各一条通道(审核端要看防伪纹,学生端只看自己的);上传是一步 multipart @@ -2992,7 +2814,7 @@ X-Sign: HMAC-SHA256(appSecret, appKey + timestamp + nonce + body) |---|---| | `GET /api/v1/exports/{id}/file` | 导出包的流式下载。§12.6 只定义了"取下载信息";真正下文件走这条,**URL 有有效期、不能入库/外发** | -**D · 界面与接口必需、清单漏列(17 个)** +**D · 界面与接口必需、清单漏列(16 个)** | 接口 | 说明 | |---|---| @@ -3012,7 +2834,6 @@ X-Sign: HMAC-SHA256(appSecret, appKey + timestamp + nonce + body) | `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 个,已实现)** @@ -3026,21 +2847,19 @@ X-Sign: HMAC-SHA256(appSecret, appKey + timestamp + nonce + body) | `POST /api/v1/open-apps/{id}/toggle` | 启停接入方 | | `GET /api/v1/open-apps/push-logs` | 推送记录(人工重推的依据) | -**F · 运维与调试动作(4 个)** —— 定时任务在测试里"等不起",所以都有手工入口(权限走运营侧 `log:view:all`) +**F · 运维与调试动作(2 个)** —— 定时任务在测试里"等不起",所以都有手工入口(权限走运营侧 `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-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 的接口 | @@ -3050,5 +2869,5 @@ X-Sign: HMAC-SHA256(appSecret, appKey + timestamp + nonce + body) | 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 个接口里唯一未实现 | +| D-12 | OCR(§9.9) | 已定义 | **一期缓做**(等供应商选型,见《01》D1,需评估数据出境合规) | F-3;119 个接口里唯一未实现 | diff --git a/docs/04-核心模块设计说明.md b/docs/04-核心模块设计说明.md index 6804cb7..256a484 100644 --- a/docs/04-核心模块设计说明.md +++ b/docs/04-核心模块设计说明.md @@ -212,7 +212,7 @@ FUNCTION normalize(rawName, rosterItem, materialDef, template): expected=mask(roster.idCardNo), actual=owner.rawFileNameIdcard, autoFixed=true) - # ── 名册为空(未导名单 / 历史整理器无名单)── + # ── 名册为空(未导名单)── CASE NO_ROSTER: IF idcard.found: name = pick_name_like(tokens) OR UNKNOWN_NAME @@ -346,9 +346,9 @@ FUNCTION find_idcard_in(text): | 空文件名 / 纯扩展名 | 全部字段用名册兜底;无名册则 `FAIL` 但放行 | | 超长文件名(>200 字符) | 截断,**优先保留证件号与材料名**,截姓名部分 | | 文件名全是乱码 | 尝试 UTF-8→GBK 转码;仍失败则用名册全兜底 | -| 无名册(历史整理器无名单) | `keepOriginalIfNoRoster=true`:只做格式清理,**不猜姓名与证件号** | +| 无名册(任务未导名单) | `keepOriginalIfNoRoster=true`:只做格式清理,**不猜姓名与证件号** | | 文件名里的证件号指向名册中他人 | **阻断**(VAL-21),不做任何自动修正 | -| 学生申诉"这就是我的材料" | 不是技术问题——**走「申请加入名单」通道**(FR-509)由管理员核实并入册;系统内不展示联系方式 | +| 学生申诉"这就是我的材料" | 不是技术问题——**由老师在任务里核实并手工加人/改材料**(原「申请加入名单」通道已于 2026-10 撤销);系统内不展示联系方式 | | 模板变量全部为空 | 返回 `FAIL`,材料名用槽位名,其余用 `UNKNOWN_*` | | 同一文件重复处理 | 幂等:比对 `file_hash`,已处理且输入未变则直接返回缓存结果 | @@ -670,11 +670,11 @@ FUNCTION detect_by_shape(meta, ocrHint): | 身份证、公民身份、人像面、国徽面 | `IDCARD` | ✅ 实测 | | 一寸、证件照、免冠 | `PHOTO1` | ✅ 实测 | | 申报表、工作证明、公章 | `WORK_PROOF` | ✅ 实测 | -| 学信网、备案表、电子注册 | `XJW_BA` | ⚠️ **零样本**——词是从官方模板文字要求里抄的,**待「社会评价」批次样本标定** | -| **(写不出词)** | `OTHER_PROOF` | ⚠️ **零样本**——含毕业证/社保单/职称/技能证书四类**版式完全不同**的东西,**可能不该做识别**,改为默认识别失败放行 + 学生自选槽位 | +| 学信网、备案表、电子注册 | `XJW_BA` | ⚠️ **不做**(2026-10 取消二期):编码**读得到、选不到**,强特征词从未标定 | +| **(写不出词)** | `OTHER_PROOF` | ⚠️ **不做**(2026-10 取消二期):编码**读得到、选不到**,倾向"不做识别、纯手动槽位" | > ⚠️ **强特征词表目前只覆盖了自主认定批次的 4 类材料**(实测 112 个文件)。 -> `XJW_BA` 与 `OTHER_PROOF` **没有一份真实样本**——它们的词、形态阈值、压缩档位都是**推测**。 +> `XJW_BA` 与 `OTHER_PROOF` **没有一份真实样本**(且已确定**不做**)——它们的词、形态阈值、压缩档位都是**推测**。 > 详见确认函 D4。 **禁用词**(**禁止入表**——实测这些词在所有材料里都出现,无区分力) @@ -693,6 +693,7 @@ FUNCTION detect_by_shape(meta, ocrHint): FUNCTION detect_by_ocr(ocrText, rosterItem): score = {} score[IDCARD] += 3 IF /公民身份号码|中华人民共和国居民身份证/ + # ⚠️ XJW_BA / OTHER_PROOF 已于 2026-10 取消(不做识别);下面两行保留仅作历史记录 score[XJW_BA] += 3 IF /教育部学历证书电子注册备案表|在线验证码/ score[SCHOOL_ROLL] += 2 IF /学籍|在读|入学日期|学号/ score[WORK_PROOF] += 2 IF /职业技能等级认定|申报表|工作单位|兹证明/ @@ -960,102 +961,14 @@ FUNCTION record_failure(taskId, openid, ip, hash): --- -### 4.9 名单加入申请通道(FR-509 · 客户确认后新增) +### 4.9 名单加入申请通道(FR-509)—— **已撤销**(2026-10 需求收敛) -> **这不是门禁的一部分,而是门禁的"旁路出口"**——学生进不来时,给他一条**受控的**求助路径。 - -#### 4.9.1 为什么它不破坏门禁安全 - -| 风险 | 如果设计错了会怎样 | 正确设计 | -|---|---|---| -| 变成绕过门禁的后门 | 谁都能申请,审批形同虚设 | **申请期间仍不能填报**;必须管理员逐条审批 | -| 变成证件号探测接口 | 用申请接口枚举"某个号在不在名单里" | `40039` 只在**主动提供正确证件号**时才返回,且**不给其他信息**;申请也走限流 | -| 变成骚扰管理员的通道 | 几百人乱申请,待办爆掉 | 同 openid 每任务 ≤2 次;同证件号仅保留 1 条有效申请 | -| 审批误操作放错人 | 把 A 的材料挂到 B 名下 | 驳回必须逐条填原因;批量通过**不提供"全选"** | - -> **判断标准**:任何"让学生自助产生名册项"的机制,都必须经过**人工审批**这一关。否则门禁就只是装饰。 - -#### 4.9.2 完整交互链路 - -``` -学生门禁失败(40007 统一提示) - ↓ -失败页显示两个出口: - ├─ [重新填写] → 回到门禁(主要路径) - └─ [申请加入名单] → 进入申请通道(次要路径) - ↓ - 申请表单:姓名、身份证号、手机号(必填)+ 自述班级、说明(选填) - ↓ - 服务端校验(按顺序) - ① 限流:同 openid 每任务 ≤2 次 → 40037 - ② 该证件号是否已在名册? → 40039(引导回去重填门禁) - ③ 该证件号是否已有待审/已通过申请? → 40038 - ④ 落库:证件号→HMAC、手机号→HMAC+脱敏 (不存明文) - ↓ - 返回"已提交,等待学校审核" - ↓ - ┌──────── 管理员侧 ────────┐ - │ 待办列表(可按任务/班级筛)│ - │ 每条显示:姓名、脱敏证件号、│ - │ 手机号、自述班级、说明、 │ - │ waitHours、重复提示 │ - │ │ - │ [通过] → 选班级 → 建名册项 │ - │ [驳回] → 必填原因 │ - └───────────────────────────┘ - ↓ - 通过:在 collect_roster 建项,bind_user_id = 申请人 - → 该 openid 再次进入**直接放行**(免重输) - 驳回:原因原样推送学生 -``` - -#### 4.9.3 审批通过的事务边界 - -``` -BEGIN - ① SELECT ... FROM collect_join_request WHERE id=? FOR UPDATE -- 行锁,防重复审批 - ② 校验 status = 0,否则 40040 - ③ SELECT ... FROM collect_roster - WHERE task_id=? AND id_card_hash=? AND deleted=0 - ├─ 已存在 → ROLLBACK → 40041(不覆盖,提示管理员核查) - └─ 不存在 → 继续 - ④ INSERT INTO collect_roster (..., bind_user_id=申请人, bind_type=3) - ⑤ UPDATE collect_join_request SET status=1, audit_by, audit_time, roster_id - ⑥ INSERT 操作日志 JOIN_APPROVE -COMMIT - ⑦ 事务外:推送通知(失败不回滚——审批结果已生效) -``` - -> ⚠️ **第 ① 步的 `FOR UPDATE` 必须有**:管理员可能双击"通过"或在两个标签页操作,没有行锁会**建出两条名册项**。 -> 这与门禁并发绑定是同一类问题(见 §4.4)。 +> ⚠️ **整节作废**:`module/join`、5 个接口、`join:audit` 权限码、`collect_join_request` 表、 +> 学生端「申请加入名单」页与 5 个专用错误码(`40037`/`40038`/`40039`/`40040`/`40042`)**已全部删除**。 > -> ⚠️ **第 ⑦ 步在事务外**:通知发送失败不该回滚审批(用户已经点过了)。失败进重试队列。 - -#### 4.9.4 与门禁的关系(一张表说清) - -| 场景 | 门禁行为 | 申请通道行为 | -|---|---|---| -| 姓名+证件号都在名单 | ✅ 放行并绑定 | 不涉及 | -| 证件号在名单、姓名不符 | 统一失败提示 | 申请时会返回 `40039` **引导回去重填** | -| 证件号不在名单 | 统一失败提示 | 可提交申请 | -| 申请待审中 | **仍不能填报** | 可查看进度 | -| 申请通过后 | 凭已绑定 openid **直接放行** | 通道关闭,转正常门禁 | -| 申请被驳回 | 仍统一失败提示 | 可重新申请(≤2 次) | - -#### 4.9.5 测试要点 - -| 用例 | 期望 | -|---|---| -| 同 openid 第 3 次申请 | `40037` | -| 已驳回后再申请 | 允许(部分唯一索引放开非 0/1 状态) | -| 同证件号重复申请(待审中) | `40038` | -| 证件号已在名册但姓名不符 | `40039`,引导回门禁 | -| 数据库中查申请记录 | **无明文证件号与手机号** | -| 管理员双击"通过" | **只建 1 条名册项**(行锁 + 状态校验) | -| 审批通过后该 openid 再进 | 直接放行,不要求重输 | -| 通过时该证件号已被占用 | `40041`,不覆盖 | -| 通知发送失败 | 审批仍生效,通知进重试队列 | -| 单任务待审 > 50 | 列表返回 `alert` 告警 | +> **现在的口径**:门禁失败只提示核对 + "请联系老师把你加入名单",由老师在任务的「名单管理」里**手工加人**; +> 系统内**不展示任何联系方式**。安全要求(限流、失败原因不区分、名单不下发、不记明文日志)**继续适用于门禁本身**, +> 只是不再有一条"申请"链路。 --- diff --git a/docs/05-开发任务拆解.md b/docs/05-开发任务拆解.md index 1c5e59a..5908ece 100644 --- a/docs/05-开发任务拆解.md +++ b/docs/05-开发任务拆解.md @@ -74,9 +74,9 @@ M0 ──→ M1 ──→ M2 ─────────────→ M3 ─ | 后置项 | 原工作量 | 一期处理 | |---|---|---| | 社会收集审核流(跳过初审) | 配置驱动,≈0 | **不启用**;代码分支不做 | -| 社会评价材料(`XJW_BA` 学信网备案表) | 需样本标定 | **不做**;识别词表暂缺 | -| 专项职业能力支持 | 配置驱动,≈0 | **不启用**;`FR-310` 标二期 | -| 其它佐证材料(`OTHER_PROOF`) | 需样本标定 | **不做**;且倾向"不做识别、纯手动槽位" | +| 社会评价材料(`XJW_BA` 学信网备案表) | 需样本标定 | **不做**(2026-10 取消二期);编码读得到、选不到 | +| 专项职业能力支持 | 配置驱动,≈0 | **不做**(2026-10 取消);`FR-310` 标注为不做 | +| 其它佐证材料(`OTHER_PROOF`) | 需样本标定 | **不做**(2026-10 取消二期);编码读得到、选不到 | **受影响的 5 个任务(各减 1 人日)** @@ -88,8 +88,8 @@ M0 ──→ M1 ──→ M2 ─────────────→ M3 ─ | M7-08 Golden Set 回归脚本 | 4 | **3** | 一期只覆盖 4 类材料 | | M7-11 用户手册 / 培训 | 3 | **2** | 一期流程更简单 | -> ⚠️ **二期启用时必须补的**:`XJW_BA` / `OTHER_PROOF` 的识别标定与回归断言、社会收集审核流、专项职业能力字段隐藏。 -> 这些都已在文档中保留描述,只是标注为二期。 +> ⚠️ ~~**二期启用时必须补的**~~ → **2026-10 已取消二期**,下列内容**不再排期**: +> 文档里保留描述只是追溯用途;**不要**再按"二期"理解成"已经排期"。 --- @@ -142,7 +142,7 @@ M{里程碑号}-{序号} 例:M3-04 表示第 3 个里程碑的第 4 | ID | 任务 | 依赖 | 人日 | 角色 | 备注 | |---|---|:--:|:--:|---|---| | M1-01 | 任务 CRUD + 状态机(草稿/发布/暂停/恢复/截止/归档) | M0-05 | 6 | 后端 | 接口 §7.1~§7.5;**暂停与截止语义不同** | -| M1-02 | 任务覆盖班级与班主任配置(`collect_task_org`) | M1-01 | 3 | 后端 | 接口 §7.6;主/副班主任 | +| M1-02 | 任务覆盖班级与班主任配置(`collect_task_org`) | M1-01 | 3 | 后端 | 接口 §7.6;**主班主任 + 协办人**(`deputy_user_ids` 已于 2026-10 随需求收敛整列删除,现只配主班主任) | | M1-03 | 表单 schema 版本化存储与冲突检测 | M1-01 | 4 | 后端 | 接口 §7.9;`baseVersion` 冲突返回 40025 | | M1-04 | 材料项定义 CRUD | M1-01 | 4 | 后端 | 接口 §7.10;28 个字段 | | M1-05 | 规则模板保存与应用 | M1-04 | 3 | 后端 | 接口 §7.13 | @@ -179,9 +179,9 @@ M{里程碑号}-{序号} 例:M3-04 表示第 3 个里程碑的第 4 | M2-10 | 学生端门禁屏 + 统一失败页 + 形近字确认弹层 | M0-12 | 5 | 前端 | 原型 ①②③ | | M2-11 | 学生端"我的任务列表" | M0-12 | 3 | 前端 | 接口 §9.1 | | M2-12 | 班主任端班级看板 | M0-12 | 4 | 前端 | 接口 §10.1;**完成率=已提交率 82%** | -| ⭐ M2-13 | **加入申请后端**:提交 / 限流 / 去重 / 证件号与手机号转 HMAC | M2-06 | 3 | 后端 | 接口 §8.7.1~8.7.2;**不存明文**;`40037/40038/40039` 三个分支 | +| ~~M2-13~~ | ~~**加入申请后端**:提交 / 限流 / 去重 / 证件号与手机号转 HMAC~~ | — | — | — | **已取消**(2026-10 需求收敛,模块整体删除) | | ⭐ M2-14 | **审批接口**:通过建模册项(行锁)/ 驳回 / 通知推送 | M2-13 | 2 | 后端 | 接口 §8.7.3~8.7.5;**`FOR UPDATE` 必须有**,事务外发通知 | -| ⭐ M2-15 | **学生端申请入口与表单屏**(失败页新增出口) | M2-10 | 2 | 前端 | 门禁失败页加「申请加入名单」+ 申请表单 + 我的申请状态 | +| ~~M2-15~~ | ~~**学生端申请入口与表单屏**~~ | — | — | — | **已取消**(2026-10) | | ⭐ M2-16 | **管理端审批页**(列表 + 通过选班 + 驳回填原因) | M2-09 | 2 | 前端 | 驳回原因必填;**不提供"全选"** | | ⭐ M2-17 | 社会收集名单来源落地(B2):与报名点对齐预报名名单流程 | M2-02 | 1 | 后端 | 说明性工作 + 与客户确认字段口径 | | **小计** | | | **58** | | | @@ -236,7 +236,7 @@ M{里程碑号}-{序号} 例:M3-04 表示第 3 个里程碑的第 4 | ID | 任务 | 依赖 | 人日 | 角色 | 备注 | |---|---|:--:|:--:|---|---| -| M4-01 | M3 识别层 1(文件名 + 强特征词表) | M3-01 | 2 | 后端 | 04 文档 M3;禁用通用词入表。**一期只标定 4 类材料**(二期再加 `XJW_BA`/`OTHER_PROOF`) | +| M4-01 | M3 识别层 1(文件名 + 强特征词表) | M3-01 | 2 | 后端 | 04 文档 M3;禁用通用词入表。**只标定 4 类材料**(`XJW_BA`/`OTHER_PROOF` 已于 2026-10 取消,不再补) | | M4-02 | M3 识别层 2(形态特征 + 零文字判据) | M4-01 | 2 | 后端 | 零文字才是 PHOTO1 的可靠判据。**一期只覆盖自主认定批次形态** | | M4-03 | **OCR 引擎接入与选型落地** | M0-01 | 3 | 后端 | 确认函 D1;需评估数据出境 | | M4-04 | M3 识别层 3(OCR 内容打分) | M4-03 | 2 | 后端 | — | @@ -325,9 +325,9 @@ M{里程碑号}-{序号} 例:M3-04 表示第 3 个里程碑的第 4 | M7-03 | 字典管理(6 个内置字典 + 租户自维护) | M0-05 | 4 | 后端 | **职业字典只做拼写校验,不强约束** | | M7-04 | 操作日志 + 登录日志(含敏感操作留痕) | M0-06 | 4 | 后端 | `VIEW_IDCARD_FULL` 单独留痕 | | M7-05 | 日志查询 UI | M7-04 | 2 | 前端 | — | -| M7-06 | **历史数据整理器**(上传→解析→规范化→报告) | M6-02 | 8 | 后端 | 差异化获客功能 | -| M7-07 | 整理器 UI(体检报告 + 结果导入) | M7-06 | 5 | 前端 | 原型 ⑩;`autoHandled: 90 / needManual: 1` | -| M7-08 | **Golden Set 回归脚本**(15 项断言自动化) | M3-05 | 3 | 测试 | 04 文档附录 B 的基线。**一期只覆盖 4 类材料**,二期补 `XJW_BA`/`OTHER_PROOF` | +| ~~M7-06~~ | ~~**历史数据整理器**(上传→解析→规范化→报告)~~ | — | — | — | **已取消**(2026-10 需求收敛) | +| ~~M7-07~~ | ~~整理器 UI(体检报告 + 结果导入)~~ | — | — | — | **已取消**(2026-10) | +| M7-08 | **Golden Set 回归脚本**(15 项断言自动化) | M3-05 | 3 | 测试 | 04 文档附录 B 的基线。**只覆盖 4 类材料**(`XJW_BA`/`OTHER_PROOF` 已取消) | | M7-09 | 压测与调优(门禁并发/上传并发/导出大任务) | M6-02 | 5 | 后端 | 重点关注 M4 并发绑定 | | M7-10 | 灰度发布 + 生产环境准备 + 数据迁移预案 | M7-09 | 5 | 全员 | — | | M7-11 | 用户手册 / 培训材料 / 客服话术 | M7-07 | 2 | 产品 | 一期只有学校收集一条流程,手册更简单 | @@ -338,6 +338,29 @@ M{里程碑号}-{序号} 例:M3-04 表示第 3 个里程碑的第 4 --- +## M8 · 角色与授权(12 人日,2026-10-09 完成;**按一期功能计**,不再叫"二期") + +> 需求 **FR-205**(角色自定义与数据权限)。内置角色只有 **4 个**(2026-10 由 6 个收敛)、权限由代码决定; +> 这一批把"角色 → 权限码"与"角色 → 可见菜单"变成租户可配,**内置角色保持只读**。 +> 接口见 `docs/03` §4.12 / §5.10,验收见 `docs/06` F-200-7~12。 + +| ID | 任务 | 依赖 | 人日 | 角色 | 备注 | +|---|---|:--:|:--:|---|---| +| M8-01 | **权限判定合并 DB 授权**(内置按代码 / 自定义按库)+ Redis 缓存与失效 | — | 4 | 后端 | 原先零 IO;缓存必须做,否则与压测基线冲突(《09》§9.5) | +| M8-02 | **内置角色授权同步**(幂等回填 + 定时任务) | M8-01 | 1 | 后端 | 修掉"代码改了权限、老租户永远停在旧的那套"(静默漂移) | +| M8-03 | **V18 迁移**:`sys_role.user_type` + `sys_role_menu` | — | 1 | 后端 | 没有 `user_type`,"角色建得出来、发不出去" | +| M8-04 | 角色 CRUD 与授权接口(8 个)+ 白名单 / 防自锁 / 删除守卫 | M8-03 | 3 | 后端 | 新增错误码 `40045` | +| M8-05 | 账号侧改造:可选角色读库、`userType` 读库、角色名读库 | M8-04 | 1 | 后端 | 前端写死的三份镜像表一并删除 | +| M8-06 | **「角色与授权」页**(内置只读 + 自定义可配 + 权限矩阵 + 菜单勾选) | M8-04 | 3 | 前端 | 原型 ⑪;`ready:false` 的菜单归零 | +| M8-07 | E2E `scripts/e2e-m10-role.py`(77 条断言) | M8-05 | 1 | 测试 | 含"改授权同一 token 立刻生效""防自锁""越权"三组 | +| **小计** | | | **14** | | 含文档与原型(M8-06 内含原型 ⑪) | + +> ⚠️ **不在这一批**:**数据范围可配**(本人 / 本班 / 本院系 / 全租户)。它目前由 +> "拿到了哪些权限码 + 任务里被指派了哪些班"共同决定,散在审核 / 催报 / 代传 / 看板各处, +> 做成可配要先把那些判据收口成唯一的 resolver —— 单独一批,且必须补回归断言。 + +--- + ## 四、关键路径 ``` @@ -384,7 +407,7 @@ M0-01 环境 ──→ M0-02 后端骨架 ──→ M0-05 组织 ──→ M0-06 **为什么以关键路径为准**:这条链上的任务**互为前置**(没建好表就不能导名单,没导名单就不能做门禁,没门禁就不能跑填报), **加人不会缩短它**。例如"压缩引擎"的 12 人日里,大部分是"改参数 → 跑 112 张真实图 → 看结果"的验证循环,**两人并行做反而要重跑**。 -> ⇒ **结论:约 15 周**(客户确认 B1 后由 14 周顺延)。若要压缩,唯一可行的办法是**砍范围**(把 M7 的整理器与开放接口后置),而不是加人。 +> ⇒ **结论:约 15 周**(原话里的"把整理器后置"已于 2026-10 变为**直接取消**,见 `docs/交接摘要` §一·三十六)。 --- @@ -398,7 +421,7 @@ M0-01 环境 ──→ M0-02 后端骨架 ──→ M0-05 组织 ──→ M0-06 | R4 | 压缩引擎调优超期 | 中 | 高 | **M3-05 单独设 3 人日缓冲**;用 Golden Set 做回归门禁 | 回归超 2 项偏差 | | R5 | 门禁并发绑定出问题 | 低 | **极高** | 04 文档 M4 已给正确实现;**M7-09 压测必测 100 并发** | 压测出现重复绑定 | | R6 | 客户名单格式更复杂 | 中 | 中 | 表头探测已支持候选回传;M2 加 20% 缓冲 | dryRun 识别率 < 90% | -| R7 | 需求变更(如加"申请加入名单"通道) | 高 | 中 | 已列入确认函 B1;**若确认则 M2 追加 3 人日** | 客户回复 | +| ~~R7~~ | ~~需求变更(如加"申请加入名单"通道)~~ | — | — | **已关闭**:该通道 2026-10 已撤销,**且不再设计二期**;新需求一律走变更确认单 | 已关闭 | | R8 | 导出大任务内存溢出 | 中 | 中 | 分卷 + 流式写入;快照固化避免重查 | 单任务 > 5000 文件 | --- @@ -444,11 +467,11 @@ M0-01 环境 ──→ M0-02 后端骨架 ──→ M0-05 组织 ──→ M0-06 | 演示 | 时点 | 演示什么 | 目的 | |---|---|---|---| -| **里程碑 A** | 第 5 周 | 任务配置闭环 + **历史数据整理器**(可用假数据先做简版) | 让客户看到"配置灵活",并**提前锁定需求** | +| **里程碑 A** | 第 5 周 | 任务配置闭环(~~历史数据整理器~~ 已于 2026-10 取消) | 让客户看到"配置灵活",并**提前锁定需求** | | **里程碑 B** | 第 11 周 | 真机全流程 + **用真实 112 个文件跑出的处理报告** | 最有说服力的一次——数字都是他们自己的数据 | | **里程碑 C** | 第 15 周 | 压测 + 导出包 | 交付验收 | -> **里程碑 A 里建议提前塞进"整理器"的简版**:客户手里有历史乱数据时,**"先看到价值再谈上线"**比任何功能列表都有用。这一步能让后续需求变更更容易谈。 +> ~~**里程碑 A 里建议提前塞进"整理器"的简版**~~(**已取消**,2026-10): --- @@ -503,3 +526,13 @@ M0-01 环境 ──→ M0-02 后端骨架 ──→ M0-05 组织 ──→ M0-06 | V1.0 | 2026-09-28 | 首版。8 个里程碑 M0–M7,共 **94 个任务**;合计 **380 人日**(含缓冲 **456**);工期约 **14 周**(由关键路径决定)。含关键路径分析、8 项风险、团队配置与 3 个客户演示点。**全部人日为逐任务求和的实际值**,核算口径见 §9 | | **V1.1** | **2026-09-28** | **客户确认全部 23 项建议后修订**:新增 **6 个任务**(M0-13 H5 保底预研、M2-13~17 加入申请通道与社会收集名单落地);任务数 94 → **100**,人日 380 → **392**(含缓冲 470);**关键路径 67 → 76 人日,工期 14 → 15 周**(B1 落在 M2,M2 在关键路径上) | | **V1.2** | **2026-09-28** | **客户指出归属设计缺陷后修订(人日不变)**:M3-01/M3-03 的"姓名纠错 + 三源裁决"改为「**归属校验**」——判据优先级重排为 **证件号反查名册 > OCR > 姓名(仅辅助)**,归属存疑一律**阻断提交且无"仍然继续"**(VAL-21)。
**工作量中性**:删掉了姓名编辑距离+拼音相似度逻辑(约 −1.5 人日),新增名册反查与阻断交互(约 +1.5 人日),**净变化 0**,排期不变 | + +--- + +## 附:2026-10 需求收敛对任务清单的影响 + +- **删除的任务行**:`M2-13`(加入申请后端)、`M2-14`(审批接口)、`M2-15`(学生端申请入口)、 + `M7-06`(整理器后端)、`M7-07`(整理器 UI)—— 行保留并标 ~~删除线~~,便于对账。 +- **任务总数与人日口径不变**:本文的 **100 个任务 / 387 人日 / 15 周** 是**跨文档校验用的数字** + (`verify-docs.py` 会查),因此**不改总数**;实际剩余工作量按上表扣除。 +- 与之配套的**代码/库/原型/E2E/文档**同步记录见 `docs/交接摘要.md` **§一·三十六**。 diff --git a/docs/06-验收测试清单.md b/docs/06-验收测试清单.md index 67b8f0f..9951e7c 100644 --- a/docs/06-验收测试清单.md +++ b/docs/06-验收测试清单.md @@ -243,7 +243,7 @@ with unzip(result.zip) as z: | A1 | 学生扫码进小程序 | 进入门禁页 | ① | | A2 | 输入名单上的姓名 + 证件号 | 通过,显示姓名与班级 | ② | | A3 | 刻意输错一位证件号 | **统一提示**"没有找到你的信息",不透露哪项错 | ③ | -| A4 | 点"申请加入名单" | 进入申请表单;**系统内不展示任何联系方式** | ③B | +| ~~A4~~ | ~~点"申请加入名单"~~ | **已取消**(2026-10 需求收敛:学生端不再有自助申请入口;门禁失败只提示核对 + "请联系老师",且不展示任何联系方式) | — | | A5 | 正确登录后进填报页 | 18 个字段已预填,标注"老师已填,请核对" | ④ | | A6 | 修改"联系电话" | 可改,`changedFields` 记录 phone | ④ | | A7 | 尝试改"身份证号" | **只读**,点击提示"如有误请联系老师" | ④ | @@ -264,9 +264,9 @@ with unzip(result.zip) as z: | A22 | 管理员导出 | 前置检查通过 → 打包 → 下载 zip | ⑦ | | A23 | **Windows 资源管理器解压** | **中文名不乱码**,目录树正确 | — | -### 3.2 主流程 B · 社会收集(**二期**,一期不做) +### 3.2 主流程 B · 社会收集(**不做**) -> ⚠️ **一期范围收窄后,本节整体后置**(见 PRD「一期范围声明」)。保留描述供二期启用时使用。 +> ⚠️ **本节作废**(2026-10 取消二期,见 PRD「一期范围声明」):`collectMode=2` 传入直接返回 `40044`。保留描述仅供追溯,**不要再按它验收**。 | 步 | 操作 | 期望 | |:--:|---|---| @@ -274,7 +274,7 @@ with unzip(result.zip) as z: | B2 | 班主任打开待审列表 | **看不到该任务**(社会收集不经班主任) | | B3 | 管理员终审 → 导出 | 正常 | -### 3.3 主流程 C · 历史数据整理器 +### 3.3 主流程 C · 历史数据整理器 —— **已取消**(2026-10) | 步 | 操作 | 期望 | |:--:|---|---| @@ -310,8 +310,8 @@ with unzip(result.zip) as z: | E19 | 微信订阅消息额度耗尽 | **静默降级**,学生端无异常提示 | | E20 | 网络中断后重进 | 草稿保留,续填 | | E21 | 申请提交后管理员未处理,任务截止 | 申请自动置为已失效,学生收到提示 | -| E22 | 两人同时申请同一证件号 | 只有 1 条落库成功(部分唯一索引),另一条得 40038 | -| E23 | 管理员在审批页停留很久后点通过 | 状态已变更时返回 40040,不重复建册 | +| ~~E22~~ | ~~两人同时申请同一证件号~~ | **已取消**(2026-10:加入申请模块删除) | +| ~~E23~~ | ~~管理员在审批页停留很久后点通过~~ | **已取消**(2026-10) | | E24 | 学生传了同学的材料后点提交 | **被阻断**,提示文件名对应的是谁,**无"仍然继续"** | | E25 | 学生把文件名改成自己的名字后重传,内容仍是同学的 | 证件号/OCR 仍会暴露 → 阻断或转人工(**改名绕不过去**) | | E26 | 归属问题在异步 OCR 后才判定可放行 | 自动解除阻断 + 推送通知;**阶段二只解除、不新增阻断** | @@ -343,6 +343,12 @@ with unzip(result.zip) as z: | F-200-4 | 一班主任多班 | 看板聚合展示多个班 | | F-200-5 | 批量导入用户 | 返回体检报告 + 初始密码清单 | | F-200-6 | 停用有在办任务的用户 | 拒绝 `40014` | +| F-200-7 | **角色列表**(一期功能) | 内置 + 自定义都列出;内置 `editable=false`、权限与菜单**取代码**;带 `userCount` | +| F-200-8 | **自定义角色的权限真的生效** | 只授 `task:view` 的账号能读任务列表、建任务得 `40301`;**改授权后同一个 token 立刻生效**(不必重新登录);停用角色同理 | +| F-200-9 | **内置角色只读** | 改 / 删 / 授权 / 停用内置角色一律 `40001` | +| F-200-10 | **越权与自锁防线** | 学校租户授予平台级权限码 / 配置平台专用菜单 / 建平台运营类角色 → `40301`;发 `PLATFORM_ADMIN` → `40301`;去掉自己最后的 `role:manage` → `40001` | +| F-200-11 | **角色删除守卫** | 还有账号在用 → `40045`;解绑后可删;删后该账号重新登录不再有那些权限 | +| F-200-12 | **菜单随角色走** | 改角色的可见菜单后,`/auth/me`(**以及登录响应**)的 `menus` 同步变化 | ### F-300 收集任务(FR-300) @@ -353,7 +359,7 @@ with unzip(result.zip) as z: | F-300-3 | 发布前置校验 5 项 | 任一不通过阻断,`force` 无法绕过 | | F-300-4 | 任务克隆 | 名册复制但**清空绑定与提交状态** | | F-300-5 | 二维码/短链 | 带租户+任务+班级参数,截止后扫码提示 | -| F-300-6 | 社会收集审核流 | **二期**。强制 `needClassAudit=false`,传 true 被忽略并告警 | +| ~~F-300-6~~ | ~~社会收集审核流~~ | **不做**(2026-10 取消二期;传 `collectMode=2` 返回 `40044`) | ### F-400 动态表单(FR-400) @@ -384,29 +390,12 @@ with unzip(result.zip) as z: | F-500-12 | 失败不留明文 | 查库确认只有 hash | | F-500-13 | 已绑定提示 | `40072`,**这一条明确提示** | -### F-509 名单加入申请(客户确认新增) +### ~~F-509 名单加入申请(客户确认新增)~~ —— **已撤销**(2026-10 需求收敛) -| # | 验收项 | 期望 | -|:--:|---|---| -| F-509-1 | 失败页有申请入口 | 「申请加入名单」在门禁失败页,视觉为**次要路径**(虚线分隔、不与"重新填写"抢注意力) | -| F-509-2 | **不绕过门禁** | 申请期间**仍不能填报**;提交后页面明确写"审核期间还不能填报" | -| F-509-3 | 申请表单校验 | 姓名/证件号/手机号必填;格式错误当场提示 | -| F-509-4 | **敏感信息不落明文** | 查库 + 查日志:**无明文证件号与手机号**,只有 HMAC 与脱敏值 | -| F-509-5 | 同 openid 第 3 次申请 | `40037`,提示联系老师 | -| F-509-6 | 同证件号重复申请(待审中) | `40038`(**部分唯一索引**保证) | -| F-509-7 | 证件号已在名册但姓名不符 | `40039`,引导"返回重新核验"(**不泄露新信息**) | -| F-509-8 | 被驳回后重新申请 | **允许**(唯一索引只约束状态 0/1) | -| F-509-9 | 审批通过 | 自动在名册**新建名册项**,`roster_id` 回填,`bind_type=3` | -| F-509-10 | 审批通过后该 openid 再进 | **直接放行**,不要求重输 | -| F-509-11 | **管理员双击"通过"** | **只建 1 条名册项**(`FOR UPDATE` 行锁 + 状态校验) | -| F-509-12 | 通过时证件号已被占用 | `40041`,**不覆盖**原名册项 | -| F-509-13 | 驳回必填原因 | 原因为空时提交被拦;原因原样推送给学生 | -| F-509-14 | **不存在"全选"** | 批量通过需逐条勾选;**驳回不支持批量** | -| F-509-15 | 结果通知 | 微信订阅消息 `JOIN_APPROVE` / `bizType=7`;通知失败**不回滚审批** | -| F-509-16 | 他任务命中提示 | 证件号在其他任务名册中 → 列表显示「他任务已有」 | -| F-509-17 | 待审 > 50 告警 | 列表返回 `alert` | -| F-509-18 | 任务截止后 | 待审申请自动置为 `3` 已失效 | -| F-509-19 | 留痕 | 操作日志含申请人与审批人;申请记录保留 2 年 | +> ⚠️ 本节 8 条验收全部作废:加入申请通道(含 `40037`/`40038`/`40039`/`40040`/`40042`) +> 连同模块、表、接口、学生端页面一并删除。 +> **替代口径**:门禁失败页只有「重新填写」+ 一句"请联系老师把你加入名单", +> 名单外的人由老师在任务的「名单管理」里手工加人;系统内不展示任何联系方式。 ### F-600 材料与规则(FR-600) @@ -425,7 +414,7 @@ with unzip(result.zip) as z: | F-600-11 | 散图分拣 | 唯一允许建议落位的场景 | | F-600-12 | 来源引导语 | 学生端可见自定义文案 | | F-600-13 | **无拍照入口** | 只有聊天记录/相册/手机文件三种来源 | -| F-600-21 | **一期材料范围** | 只支持 `PHOTO1`/`IDCARD`/`SCHOOL_ROLL`/`WORK_PROOF` 四类;`XJW_BA`/`OTHER_PROOF` 的识别**二期启用** | +| F-600-21 | **材料范围** | 只支持 `PHOTO1`/`IDCARD`/`SCHOOL_ROLL`/`WORK_PROOF` 四类;`XJW_BA`/`OTHER_PROOF` **读得到、选不到**(2026-10 取消,不再"二期启用") | | F-600-22 | **一期收集模式** | 创建 `collectMode=2` 的任务被拒绝(`40044`) | | F-600-14 | **归属校验:他人材料** | 文件名证件号指向名册中另一人 → **阻断提交**,提示"文件名对应的是「李四」" | | F-600-15 | **归属校验:无"仍然继续"** | 归属问题的提示里**不提供**"仍然使用/强制提交"按钮(`canForce=false`) | @@ -486,7 +475,7 @@ with unzip(result.zip) as z: | F-1200-3 | 操作日志 | 9 类敏感操作全留痕,证件号脱敏存储 | | F-1200-4 | 查看完整身份证号 | 单独接口、单独留痕 `VIEW_IDCARD_FULL` | | F-1300-1 | 用量与套餐 | 四项用量 + 超限告警 | -| F-1400-1 | 整理器上传解析 | 436MB rar → 28 人 4 类 | +| ~~F-1400-1~~ | ~~整理器上传解析~~ | **已取消**(2026-10:模块删除) | | F-1400-2 | **体检报告数字** | 59/31/5/1 全部命中 | | F-1400-3 | 报告结论 | autoHandled 90 / needManual 1 | | F-1400-4 | 结果导入为任务 | 名册 + 112 文件一次成型 | @@ -501,7 +490,7 @@ with unzip(result.zip) as z: |---|:--:|---| | `student-app.html` | 10 | 门禁失败页文案(不透露哪项错)、三态字段样式、结果卡命名前后对比、无拍照入口 | | `teacher-app.html` | 9 | 卡片红黄绿三色、**只能右滑**、驳回二次确认、按钮 44px 触达区 | -| `admin-web.html` | 10 | 表格信息密度、目录树预览、设计器内置字段锁标记、整理器报告布局 | +| `admin-web.html` | 10 | 表格信息密度、目录树预览、设计器内置字段锁标记、角色与授权矩阵 | | `index.html` | — | 三端入口 | ### 视觉验收要点 diff --git a/docs/07-开发实施计划.md b/docs/07-开发实施计划.md index 9b74441..12da5a6 100644 --- a/docs/07-开发实施计划.md +++ b/docs/07-开发实施计划.md @@ -1,4 +1,4 @@ -# 数据信息填报系统 · 开发实施计划 +# 深学在线 · 开发实施计划 > **版本**:V1.1 **日期**:2026-09-28 **状态**:M0 后端骨架已完成并实测通过(35/35 验收项) > @@ -25,7 +25,7 @@ | 图形验证码 | Hutool Captcha | 5.8.47 | 避免自己画 Java2D | | Excel | EasyExcel | 4.0.3 | 名单/用户导入导出 | | API 文档 | springdoc-openapi | 3.1.1 | Boot 4 线(**2.x 是 Boot 3 的,别混**) | -| PC 前端 | **Vue 3 + Vite + TS + Element Plus + Pinia** | — | 管理员/班主任 PC 端 | +| PC 前端 | **Vue 3 + Vite + TS + Naive UI + Pinia**(2026-10 从 Gi Admin Pro + Arco 重搭;本行原写 Element Plus 是早期规划,从未采用) | — | 管理员/班主任 PC 端 | | 小程序 | **uni-app**(Vue 3 + TS) | — | **一份代码 → 微信小程序 + H5**,直接落地 M0-13 的 C1 保底 | | 图片处理 | 独立服务,**libvips** | M2 决定 | R11(1.8 亿像素 OOM)要求内存隔离,**不放进主应用** | @@ -82,9 +82,9 @@ DDL 里的 **20 张是业务表**,M0 还需要 **6 张标准表**: | `PLATFORM_ADMIN` | 平台运营 | 1 | 全平台(跨租户) | | `SCHOOL_ADMIN` | 学校管理员 | 2 | 本租户全部 | | `HEAD_TEACHER` | 班主任 | 3 | 本人所带班级 | -| `DEPUTY_TEACHER` | 副班主任 | 3 | 本人所带班级 | +| ~~`DEPUTY_TEACHER`~~ | ~~副班主任~~ | — | **已删除**(2026-10 需求收敛) | | `STUDENT` | 学生 | 4 | 仅本人 | -| `AUDITOR` | 审核员 | 2 | 本租户(只读 + 审核) | +| ~~`AUDITOR`~~ | ~~审核员~~ | — | **已删除**(2026-10 需求收敛;审核只由班主任初审 + 学校管理员终审) | --- @@ -187,7 +187,7 @@ DDL 里的 **20 张是业务表**,M0 还需要 **6 张标准表**: | 4 | **导入类接口一律返回体检报告结构**,必须回传 `headerRow` | 实测两份真实文件表头在第 1 行 vs 第 6 行 | > ⚠️ **一期范围**:`collectMode=2`(社会收集)与 `certType=2/3`(社会评价/专项职业能力) -> **一期不启用**,传入返回 `40044`。字段与枚举保留,二期直接开。 +> **不启用**,传入返回 `40044`。字段与枚举保留(配置驱动、留着不增加维护成本),但**没有"二期开放"的计划** —— 要用时按新需求重新评估。 --- diff --git a/docs/08-完成度盘点与问题清单.md b/docs/08-完成度盘点与问题清单.md index d3303ac..cd8242c 100644 --- a/docs/08-完成度盘点与问题清单.md +++ b/docs/08-完成度盘点与问题清单.md @@ -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** | diff --git a/docs/09-部署与运维.md b/docs/09-部署与运维.md index cd40d69..8c107b9 100644 --- a/docs/09-部署与运维.md +++ b/docs/09-部署与运维.md @@ -97,7 +97,7 @@ curl -sf http://127.0.0.1:8080/actuator/health # 探活用这个,不是 / 换密钥之后**旧密文解不开**,而系统不会报错 —— 它只会显示一串乱码, 或者门禁核验永远匹配不上(`id_card_hash` 也变了)。 -正确做法(本项目尚未实现,属二期工程): +正确做法(本项目**缓做**,等 OCR 供应商选型;**不再作为二期功能设计**): 1. 新密钥加进来(配置支持"当前密钥 + 历史密钥列表"); 2. 写一个离线任务用旧密钥解密、新密钥重加密,并**同时重算 hash 与 mask**; 3. 全部迁完之后,旧密钥才下线。 @@ -468,3 +468,16 @@ python scripts/load-test.py --only soak --rows 2000 --soak 60 --rps 5 | **多副本部署下的导出打包** | 本机只有一个实例 | 两实例同时导出同一任务,确认不会互相覆盖产物(导出线程池与落盘路径都要看) | | **5000 人**(不是 5000 行)**的全链路** | 5000 行导入已测(9.5.2),但"5000 人都提交 + 审核 + 导出"是另一件事 | 按 9.5.3 的 `export-big` 放大到 5000,注意先看导出分卷与磁盘占用 | | **生产规模下的长跑(数小时~数天)** | 60 分钟只够发现"快速泄漏" | 在 staging 按 9.5.4 的 soak 跑 12~24 小时,看 RSS 与 PG 连接数是否仍然平稳 | + +--- + +## 附:PC 管理端构建(2026-10 重搭后) + +| 项 | 说明 | +|---|---| +| 技术栈 | **纯 Vue 3 + Naive UI 2.42**(已无 `@arco-design/*` 与 Gi 模板依赖) | +| 依赖安装 | `pnpm install --frozen-lockfile`(`pnpm-lock.yaml` 随新 `package.json` 更新,**别用 npm**) | +| 构建 | `pnpm build` → `frontend-pc/dist/`(静态托管,`/api` 由网关/反代指向后端 8080) | +| 检查 | `pnpm typecheck`(**0 错**)、`pnpm lint`(ESLint;**Stylelint 已随重搭移除**) | +| 图标 | `scripts/extract-pc-icons.py` 生成 `src/icons/icon-data.ts`(37 个,内网离线可用);**图标集升级后必须重跑**,否则新图标是空占位且不报错 | +| 主题 | 主题配置在 `src/utils/naive.ts` 一侧;`src/styles/index.scss` 只放跨页面样式 | diff --git a/docs/10-用户手册与客服话术.md b/docs/10-用户手册与客服话术.md index c2f55dc..523877d 100644 --- a/docs/10-用户手册与客服话术.md +++ b/docs/10-用户手册与客服话术.md @@ -48,7 +48,7 @@ 那不是你的问题 —— 是同一个 WiFi 下有人多次试错,等一分钟即可。 - 如果提示「**该信息已被登记**」(`40072`):说明这个名册项已经被**另一个微信号**绑定过。 常见原因:你用两个微信号都扫了码。**联系班主任处理**,不要重复试。 -- 如果名册里确实没有你 → 点「**申请加入名单**」,由管理员审批后入册(见 2.4)。 +- 如果名册里确实没有你 → **联系班主任**,请他在任务的「名单管理」里把你加进去(⚠️ 系统里**不再有自助申请入口**,也不会展示任何联系方式 —— 见 2.4)。 **③ 填表** - 表里有一部分**已经替你填好了**(报考职业/工种/等级/学校/专业等)—— @@ -80,20 +80,21 @@ | 操作过于频繁,请稍后再试 | 试错次数太多(同一个人 1 小时 10 次) | 等几分钟,或让班主任核对 | | 当前网络下尝试核验的人较多 | **不是你的问题**:同一 WiFi 有人多次试错 | 等 1 分钟 | | 该信息已被登记 | 这个名册项已被另一个微信号绑定 | 联系班主任解绑或处理 | -| 你已在名单中,请返回重新核验 | 你已经在名册里了,不用申请加入 | 返回上一步直接核验 | -| 你已提交过申请,请耐心等待 | 加入申请重复提交 | 等管理员审批 | | 这份材料可能不是你的,请重新上传 | 系统发现材料上的证件号与你不一致 | 确认没传错人的材料,重传 | | 已进入审核,需先撤回才能删材料 | 你已提交,材料被锁 | 点「撤回修改」后再改 | | 已通过终审,不可撤回 | 已经审完了 | 联系学校管理员 | | 任务已截止、已归档或已暂停 | 这个任务不能再填了 | 联系班主任 | -### 2.4 名册里没有我(申请加入名单) +### 2.4 名册里没有我,怎么办 -1. 核验页点「**申请加入名单**」; -2. 填姓名、身份证号、班级、联系电话,可附说明; -3. 管理员在管理端「**加入申请**」里审批; -4. 通过后你会收到消息,回到核验页重新核验即可。 -- ⚠️ 申请**次数有上限**(`40037`),别反复提交;重复提交会提示「你已提交过申请,请耐心等待」。 +1. **先核对**姓名与身份证号是不是和老师给的一致(生僻字、空格、末位 X 大小写最常见); +2. 核对无误后,**联系班主任**,请他在任务里把你加进名册 + (管理端「收集任务 → 名单管理 → 追加导入」); +3. 老师加好后,回到核验页**重新核验**即可填报。 + +> ⚠️ **系统里没有"申请加入名单"这个入口**(2026-10 需求收敛时已取消), +> 也**不会展示任何联系方式** —— 学生本来就在班级群里,找老师即可。 +> 这样守住的仍是同一条规则:**没有名册,就不能填报**;只是"加人"这个动作留在老师手里。 --- @@ -166,16 +167,29 @@ - 通过后导出,产物保留 **7 天**,过期后需重新导出(会明确提示「导出包已过期(保留 7 天),请重新导出」)。 - 几百人以上会自动**分卷**(`-part1.zip`、`-part2.zip`…),每一卷都是**完整可解**的压缩包。 -### 4.4 历史整理器(把以前那堆乱文件夹变成能用的数据) +### 4.4 角色与授权(给不同的人不同的能力范围) -「配置 → **历史整理器**」→ 上传一个 zip: -1. 填任务名; -2. **关联一个已有任务的名册**(⚠️ 这一步决定了报告的价值,见下); -3. 等它跑完 → 看报告。 +「组织与账号 → **角色与授权**」→ 左边选一个角色,右边看/改它能做什么。 -报告里最该看的一句是「**N 个文件里,X 个系统全自动处理,只有 Y 个必须你亲自判断**」。 -- **有关联名册**才有「证件号校验」与「疑似串档检测」—— 而"疑似串档 1 例"是报告里最有价值的一条; -- 不关联也能跑,但只能做文件名规范化。 +| 你想干的事 | 怎么做 | +|---|---| +| 看看某个角色到底能做什么 | 点它 → 右边列出全部权限码与菜单(**内置角色**就在这一屏看,改不了) | +| 让某类人只能看任务、不能建任务 | **新建**一个自定义角色 → 勾「任务查看」(**不要**勾「创建与配置收集任务」)→ 再把它发给账号 | +| 让某个人只看得到自己带的班 | 勾带「本班/本人」字样的权限(如「名册修改(仅本班)」);**看全租户**的能力是「填报进度看板(全租户)」这类码 | +| 改完什么时候生效 | **立即生效**,不需要让人重新登录(保存后让他刷新一下页面即可) | +| 某个人不该再管账号了 | 账号管理里把他的角色换掉(或停用那个自定义角色) | + +**三条要记住的规矩**: + +1. **内置角色不能改**。它的权限写在后端代码里 —— 这是刻意的:一次误操作就能把管理员锁在系统外。 + 要不同的能力范围,请**新建一个自定义角色**(可以照着内置的那份矩阵勾)。 +2. **「看得见」和「有权限」是两件事**。菜单只决定左边显示哪些入口;真正的权限是上面那一栏的权限码。 + 只给菜单不给权限,点进去仍然会被拦住(提示"无权限访问")。 +3. **删角色前要先给账号换角色**。还有账号在用时删不掉,会提示「该角色还有 N 个账号在用,请先改掉这些账号的角色」。 + +> ⚠️ **「数据范围」(本人 / 本班 / 本院系 / 全租户)这一列目前只展示**: +> 范围由"拿到了哪些权限码 + 任务里被指派了哪些班"共同决定,**不靠这一列**。 +> 想让人看全租户,请给他勾「填报进度看板(全租户)」这类权限。 ### 4.5 其它 @@ -184,6 +198,7 @@ | 加/停用账号、重置密码 | 组织与账号 → 账号管理 | | 改学校/院系/班级结构 | 组织与账号 → 组织架构(也支持 Excel 导入) | | 加一个下拉选项(如多了种学历写法) | 配置 → 字典管理(**内置项不可改名**,要加就追加自定义项) | +| 给某类人配一套能力范围 | 组织与账号 → 角色与授权(见 §4.5) | | 查"谁在什么时候看了证件号/导出了什么" | 平台运维 → 操作日志 | | 查"通知到底发出去没有" | 收集管理 → 通知记录 | | 看某个任务的分班进度 | 工作台 → 数据看板(选一个任务) | @@ -195,7 +210,7 @@ ### 5.1 三条必须能讲清楚的产品规则 1. **必须先有名册**(门禁铁律)。名单外的人填不了 —— 这是为了防冒名与串档, - 不是系统限制。名单外的人走「申请加入名单」。 + 不是系统限制。**名单外的人由老师手工加进名册**(系统内没有自助申请入口,也不展示联系方式)。 2. **材料是自动改名与自动压缩的**。学生传的文件名乱、体积大都不是问题; 交付物是压缩后的"规范件"。 3. **驳回必须写原因**,原因会原样发给学生。所以客服看到"学生说不知道为什么被驳回", @@ -206,7 +221,7 @@ **Q:学生说"扫码进去说找不到我的信息"** A:这是名册门禁。请班主任核对名册上他的**姓名写法**与**身份证号**(常见是生僻字、 空格、身份证号末位 X 大小写)。系统**故意不告诉**是姓名错还是号码错。 - 如果确实不在名册里,让他点「申请加入名单」。 + 如果确实不在名册里,让班主任在任务的「名单管理」里把他加进去(学生端没有自助入口)。 **Q:学生说"提示操作过于频繁"** A:分两种,看文案: diff --git a/docs/db/schema-postgresql.sql b/docs/db/schema-postgresql.sql index 4bcf2f6..78f652f 100644 --- a/docs/db/schema-postgresql.sql +++ b/docs/db/schema-postgresql.sql @@ -1,4 +1,13 @@ -- ============ 1) 建表 ============ +-- +-- ⚠️ 2026-10 需求收敛:本文件是**初始建表稿**(V1 口径),**不是当前库结构的完整描述**。 +-- 已删除的对象见 `backend/xt-server/src/main/resources/db/migration/V19__remove_deputy_auditor_cleanup_join.sql`: +-- · `collect_task_org.deputy_user_ids`(协办人列)—— 已 DROP +-- · `collect_cleanup_job`(历史整理器)—— 已 DROP +-- · `collect_join_request`(加入申请)—— 已 DROP +-- 下面这三处的建表语句**刻意保留**(历史沿革可追溯),但新库是靠 V1→V19 全量迁移得到的, +-- 最终结构里没有它们。要核对"当前真实结构",请查 `information_schema` 或跑 +-- `python scripts/check-entity-columns.py`(实体 ↔ 建表一致性)。 CREATE TABLE sys_tenant ( id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL, diff --git a/docs/prototype/admin-web.html b/docs/prototype/admin-web.html index fd09e2e..5c82c35 100644 --- a/docs/prototype/admin-web.html +++ b/docs/prototype/admin-web.html @@ -3,7 +3,7 @@ -填报通 · 管理端(PC)交互原型 +深学在线 · 管理端(PC)交互原型