7523ebe789
补上发布前置校验的最后一环(40019),同时确立任务的数据隔离边界 ——
班主任能看到哪些任务、能审哪些单子,全由 collect_task_org 决定。
后端 GET/PUT /tasks/{id}/orgs(整体替换语义 + 软移除 + 历史行复用);
前端「配置班级」抽屉:组织树只允许勾选班级,主班主任留空时显式提示
「将由该班负责人初审」而非留白。发布被拦时给出「去哪里补」的指引
(BLOCKING_HINTS),而不是只说"未设置覆盖班级"。
三条守住的规则:
1) 班级与初审人都必须在本租户内。跨租户的 id 一律当作"不存在或已删除",
不透露"它存在但不属于你" —— 漏了的后果是 A 校能把 B 校的班挂到自己
的任务上,B 校的班主任就出现在 A 校的审批流里。
2) 初审人必须持有班主任/副班主任角色。指定初审人本质上是一次授权,
那个人将能看到该班学生的身份证号与学籍材料;不校验角色就等于把
"谁能看学生隐私"变成任意授权。报错要给出下一步,不能只说"校验失败"。
3) 移除是软移除(status=0),且「移除再加回」必须复用原行。
后者不是优化而是必须:uk_task_org UNIQUE (task_id, org_id) 不带 status
条件,一个班在同一任务下只能有一行,直接 insert 会违反唯一约束。
本轮修掉的 3 个真问题:
1) 我此前误判 uk_task_org 不存在 —— grep 只匹配含表名的行,而
CONSTRAINT uk_task_org UNIQUE (task_id, org_id) 那行没有表名。
基于这个错误前提,我写了一个多余的部分唯一索引 uk_task_org_active
和一段基于错误前提的代码,已全部撤掉并更正注释。
教训:查约束要看 CREATE TABLE 整块,不能只 grep 表名。
2) collect_task_org.expected_count 是 NOT NULL DEFAULT 0,与接口契约矛盾:
DDL 注释说它是"由名册汇总"(系统算的),但《03-接口设计》§7.6 把它放在
PUT 请求体里、说"用于与名册实际人数比对" —— 只有"管理员填的期望值 vs
名册实际值"才需要比对。而 0 是有意义的业务值("预期 0 人"),拿它兼职
表示"没填"会让每个没填的班都触发"应填 0 人、实际 31 人"的假告警;告警
变噪音就没人看了,真正"应填 28、名册却有 31"(名册导错)反而被漏掉,
而这条告警的全部价值就在那一次。已由 V4 迁移改为可空 + 去掉默认值,
并同步更正 docs/db/schema-postgresql.sql 与 PRD 里的内联 DDL。
3) 删掉迁移文件后必须 mvn clean,否则它会"复活":我只跑 mvn package,
target/classes 里的旧副本被打进 jar,Flyway 发现历史里没有 v4 就又执行
了一遍 —— 索引回来了、历史行填回去了。之后又建了同名版本号的新 V4,
于是"两个 v4"→"checksum 冲突",排查绕了好几圈。
顺带修掉:
· 任务列表操作列从 3 个链接涨到 4 个,160px 会挤成竖排 —— 把「进度」并进
「状态」列腾出 40px,操作列给到 200px,合计 820px(可用约 824px)。
· defineEmits 必须用函数调用签名(frontend-pc/.agents/skills/code-vue)。
我此前写的 5 个组件都是被禁的 tuple 形式,一并改正:
OrgFormModal / TaskFormModal / TaskOrgDrawer / UserFormModal / UserImportModal。
· e2e-m1-orgs.py 的清理抽成幂等的 cleanup_all(),并在 __main__ 的 finally
里兜一次 —— 该脚本首次运行时崩在准备阶段、清理段没执行,把两个测试账号
留在了库里(是在浏览器里看到下拉框里有个「E2E班主任」才发现的)。
其余三条 E2E 脚本仍是老样子,下一轮一并改。
验收:e2e-m1-orgs.py 48/48(含跨租户隔离);e2e-m1-task.py 71/71、
e2e-m0-user.py 55/55、e2e-m0.py 36/36 回归通过,共 210 项断言;
前端 typecheck / eslint / lint:style 0 错、build 通过;
浏览器实测抽屉的开合、树的勾选规则与保存落库。
492 lines
38 KiB
Markdown
492 lines
38 KiB
Markdown
# 交接摘要
|
||
|
||
> 用途:**压缩后的唯一状态源**。新会话读这一份即可接续,不必回看历史对话。
|
||
> 更新:2026-09-28
|
||
|
||
---
|
||
|
||
## 一、项目一句话
|
||
|
||
职业技能等级认定**报名材料收集 SaaS**。学生在微信小程序填报、班主任在手机初审、管理员在 PC 终审与导出。核心价值:**动态表单 + 规则引擎(命名/压缩/识别)+ 门禁核验 + 一键规范导出**。
|
||
|
||
工作目录 `D:\IDEA_Project\xiaotutongxue`(git 仓库,**已有 3 次提交**:`63aec55` 文档 / `9d7a8ca` M0 后端 / `87ece5a` 前端换框架;**其后又有一批未提交改动**,见 §一·四),交付物在 `docs/`。
|
||
|
||
---
|
||
|
||
## 一·二、一期范围(2026-09-28 客户确认收窄)
|
||
|
||
> **一期只做「学校收集 + 自主认定」一条流程。** 详见 PRD「一期范围声明」。
|
||
|
||
| 维度 | 字段 | 一期 | 二期后置 |
|
||
|---|---|---|---|
|
||
| 收集模式 | `collect_mode` | `1` 学校收集 | `2` 社会收集 |
|
||
| 认定类型 | `cert_type` | `1` 自主认定 | `2` 社会评价 / `3` 专项职业能力 |
|
||
|
||
**一期材料只有 4 类,全部有实测样本**:一寸照片 / 身份证 / 学籍表 / 工作证明
|
||
**二期才做的 2 类材料零样本**:`XJW_BA` 学信网备案表 / `OTHER_PROOF` 其它佐证材料
|
||
|
||
**数据结构保留不启用**(二期直接开,不需 DB 迁移)——因为这三项本来就是**配置驱动**的
|
||
(`needClassAudit` 是开关、字段隐藏走 `visibleWhen`、材料差异是材料项配置),
|
||
所以**省下的主要是测试成本与一个外部依赖,而不是代码量**。
|
||
|
||
**工期**:392 → **387 人日**(−5);**关键路径不变,仍 15 周**。
|
||
|
||
> ⚠️ **二期启动前必做**:索取社会评价样本(D4)→ 标定 `XJW_BA`/`OTHER_PROOF` → 补社会收集审核流与专项职业能力字段隐藏。
|
||
|
||
---
|
||
|
||
|
||
## 一·三、开发进展(2026-09-28 起)
|
||
|
||
> **代码位置**:`backend/`(Java 21 + Spring Boot 4.1.1)、`deploy/`(基础设施)、`scripts/`(工具)
|
||
|
||
| 项 | 状态 |
|
||
|---|---|
|
||
| 技术栈定版 | ✅ Java 21 + **Spring Boot 4.1.1** + MyBatis-Plus 3.5.17(boot4) + PG16 + Redis7<br>PC: Vue3 + **Gi Admin Pro(Arco Design Vue 2.58)**;小程序: uni-app |
|
||
| M0 后端骨架 | ✅ 统一响应/异常/58 错误码/多租户拦截/软删除/字段填充/AES-GCM/HMAC/RSA/JWT/权限模型 |
|
||
| M0 数据库 | ✅ Flyway 2 个迁移;26 张表(20 业务 + 5 标准 + 1 迁移记录) |
|
||
| M0 后端接口 | ✅ 租户开通与列表、组织 CRUD 与树、PC 登录/登出/当前用户、**账号管理全套(含 Excel 批量导入)** |
|
||
| M0 验收 | ✅ **`e2e-m0.py` → 36/36**;**`e2e-m0-user.py` → 55/55** |
|
||
| **M1-01 任务 CRUD 与状态机** | ✅ 建/改/删/发布/暂停/恢复/截止/归档 + 可见范围(班主任只看自己带的班);**`e2e-m1-task.py` → 71/71** |
|
||
| **M1-02 覆盖班级与班主任** | ✅ `GET/PUT /tasks/{id}/orgs`(整体替换 + 软移除 + 历史行复用)+ 前端「配置班级」抽屉(勾选班级、主/副班主任、应填人数);**`e2e-m1-orgs.py` → 48/48** |
|
||
| PC 前端 | ✅ 已换 Gi Admin Pro + 腾讯云主题;已实现:登录 / 工作台 / 组织架构 / 租户管理 / 账号管理 / **收集任务(含配置班级)** |
|
||
| 待做 | M1 剩余(M1-03 表单 schema、M1-04 材料项、M1-06 二维码、M1-07~M1-10 前端);小程序(uni-app)未创建;CI/CD;H5 保底预研 |
|
||
|
||
---
|
||
|
||
## 一·四、账号管理竖切(2026-09-28 晚,未提交)
|
||
|
||
> 范围:`GET/POST/PUT/DELETE /users`、`PUT /users/{id}/status`、`POST /users/{id}/reset-password`、
|
||
> `POST /users/import`、`PUT /auth/password`;前端「组织与账号 → 账号管理」一屏。
|
||
|
||
**验收**:`python scripts/e2e-m0-user.py` → **55/55**;`python scripts/e2e-m0.py` 回归 **36/36**。
|
||
前端 `pnpm typecheck` **0 错**、`pnpm build` 通过、新增文件 eslint/stylelint 干净。
|
||
账号管理页**已在浏览器肉眼确认**(首屏与切页均可见、8→7 列、数据与分页正确)。
|
||
|
||
### 本轮修掉的 5 个真问题(都不是"功能没做完",是"做完了但错的")
|
||
|
||
| # | 问题 | 后果 | 修法 |
|
||
|:--:|---|---|---|
|
||
| 1 | **学校租户也发放 `PLATFORM_ADMIN` 角色行** | 学校管理员在新增账号时传 `roleCodes:["PLATFORM_ADMIN"]` 即可造出带 `tenant:manage` 的账号;而 `/tenants` 只校验权限码、不校验"是否真在平台租户下" → **任何学校管理员都能开通租户、拉全平台租户列表**(租户级越权到平台级) | ① `RoleProvisioner` 不再给非平台租户发该角色行;② `UserService.resolveAssignableRoles` 按租户白名单校验,越界返回 `40301`。两道都留 |
|
||
| 2 | `UserUpsertReq.username` 上的 `@NotBlank` 对**修改**也生效 | `PUT /users/{id}` 永远返回 `40001`,而错误明细只说"存在阻断性校验问题",看不出是登录名 —— 改账号功能整体不可用 | 去掉该注解(`@Pattern` 对 null 是通过的),"新增时必填"改由 `UserService.create` 判定 |
|
||
| 3 | `Main.vue` 的 `<transition appear>` | 首次渲染被当成一次过渡,元素被加上 `zoom-fade-enter-from/-active`(`opacity:0`)后**永不清理** → **DOM 内容齐全、接口数据正常、文字可选中,但整块内容区肉眼不可见**。极易误判成"页面没渲染"而往路由/接口方向排查 | 去掉 `appear`(首次加载本就不需要入场动画)。**已浏览器复验通过** |
|
||
| 4 | 我写的 `assertAnotherAdminExists` **在停用/删除路径上不可达** | 能调这两个接口的人自己必然是活跃管理员,所以"除目标外还有管理员"恒成立。而真正会锁死租户的路径——**把管理员改成普通角色**(目标可以是操作者自己)——当时没有拦 | 新增 `losingAdminRole()` + `UserMapper.countAdminRoles()`,在改角色时判定;方法注释里写明"哪条路径真生效、哪条是防御性的" |
|
||
| 5 | ⭐ **`sys_user.user_type` 有两套互相冲突的枚举** | DDL/实体注释写「1教师/管理员 2填报人(学生) 3社会人员」,而**权威契约**(《03-接口设计》§2.7)与 `BuiltinRole.UserType` 是「1平台运营 2学校管理员 3班主任 4学生」。两套的 **2 和 3 完全错位**。我照错的注释写了新代码,结果是:**`userType=3`(班主任,最高频的账号)被当成"社会人员(二期)"直接拒掉** —— 建班主任账号会失败;前端「类型」列还把学校管理员显示成"填报人(学生)" | ① 后端改成**从角色反推用户类型**再比对(`ROLE_TO_USER_TYPE`),不再逐条 `if`;② 新增 `V3__fix_user_type_comment.sql` 更正库注释(**不能改 V1,Flyway 会校验 checksum**);③ 更正 PRD 与 `schema-postgresql.sql` 的同一行注释;④ 前端「用户类型」从手选改为**由角色推导**,从根上消除矛盾组合;⑤ E2E 加 3 条回归断言锁住这个语义 |
|
||
|
||
> ⚠️ **第 5 条是最值得记住的一条**:不是"漏了个校验",而是**同一份资料里有两套定义,且都对不上**。
|
||
> 教训:看到"枚举注释"时,要去**权威契约**(这里是《03-接口设计》)核对,不能就近抄实体或 DDL 的注释。
|
||
> 这条也解释了为什么 `V1` 迁移里的错误注释只能靠 `V3` 覆盖 —— 已应用的迁移**一个字都不能改**。
|
||
|
||
### 已知缺口(有意留着,别当成漏做)
|
||
|
||
| 项 | 说明 |
|
||
|---|---|
|
||
| **改密后旧 token 仍有效** | 直到自然过期。要立刻踢人需要 JWT 带签发时间 + 与"最近改密时间"比较,涉及 `JwtService`/`AuthInterceptor`,不适合混在这一刀里。已在 `AuthService.changePassword` 注释里写明 |
|
||
| `hasActiveTask()` 恒为 `false` | `collect_task` 是 M1 的表且当前无"任务负责人"字段,`40014` 这条约束**目前不生效**。方法注释已标红,M1 必须补 |
|
||
| 手机号**模糊**搜不了 | AES-GCM 每次密文不同,`LIKE` 在原理上不可行。完整号码走 `phone_hash` 等值匹配;代价是"只记得中间几位"搜不到,属加密的必然取舍。E2E 里有一条专门断言这个行为(避免后人当 bug 改掉) |
|
||
| **浏览器端验证** | ✅ 已完成。账号管理页首屏与切页均 `opacity: 1`(`appear` 修复生效)、7 列、数据与分页正确、组织树正常;demo 租户已清理干净(1 个账号 `admin`、6 个组织节点)。⚠️ 下一个新页面上线时容易忽略的一点:**新增 `views/` 目录后必须重启 dev server**,见下方环境坑 3 |
|
||
|
||
### 本轮踩到的三个环境坑
|
||
|
||
1. **Maven 的本地仓库不在 `C:\Users\<u>\.m2\repository`**,而在 `D:\maven_repositiory`。在那里查依赖才准。
|
||
2. **`easyexcel-4.0.3.jar` 只有 3KB** —— 它是聚合包,真类在 `easyexcel-core`(528KB)+ `poi 5.2.5`。只看聚合包的大小会误判成"依赖是空的"。这正是本项目"依赖在、功能不在"那条教训的又一例。
|
||
|
||
3. **`import.meta.glob('@/views/**/*.vue')` 在 dev server 里对"新增目录"不总失效**:`views/user/` 建好并 HMR 重载后,路由仍拿不到组件(component 为 `undefined` → 内容区空白)。**新增页面目录后要重启 dev server**,不能只靠 HMR。
|
||
|
||
---
|
||
|
||
## 一·五、M1-01 任务 CRUD 与状态机(2026-09-28 夜,未提交)
|
||
|
||
> 范围:`GET/POST /tasks`、`GET/PUT/DELETE /tasks/{id}`、
|
||
> `POST /tasks/{id}/{publish|pause|resume|close|archive}`;前端「收集管理 → 收集任务」一屏。
|
||
|
||
**验收**:`python scripts/e2e-m1-task.py` → **71/71**;
|
||
`e2e-m0.py` 36/36、`e2e-m0-user.py` 55/55 回归通过;
|
||
前端 `pnpm typecheck` 0 错、`pnpm build` 通过。
|
||
|
||
### 三条硬约束(改动时当成规则,不是"可以商量的校验")
|
||
|
||
| # | 约束 | 为什么 |
|
||
|:--:|---|---|
|
||
| 1 | **「暂停」与「截止」是两件事** | 暂停:学生不能交,**已提交的照常审**,且**可恢复**(名单要改、规则要调时用)。截止:不可交、仍可审,**不可逆**。做成一个就等于把"临时冻一下"变成不可逆操作,现场没人敢点 |
|
||
| 2 | **发布前置 5 项全是阻断级**,`force` 绕不过 | 名册非空(40016)、名册含姓名与证件号(40017)、≥1 项必传材料(40018)、已设置覆盖班级(40019)、命名模板可用(40020)。可绕过就会出现"学生填了却没人审"或"文件名根本生成不出来"。只有 P1 级提醒(某班未指定初审人)可用 `force` 跳过 |
|
||
| 3 | **`collectMode` / `certType` 发布后不可改** | 它们决定审核流与材料清单;改了会让已经填过的学生面对另一份表单。归档后仍可改名称,只锁这两个 |
|
||
|
||
### 本轮修掉的 3 个真问题
|
||
|
||
| # | 问题 | 后果 | 修法 |
|
||
|:--:|---|---|---|
|
||
| 1 | **`collect_task_org` 没有 `deleted` 列** | 我起初让它继承 `TenantEntity`(基类带 `@TableLogic deleted`),MyBatis-Plus 会给**每条**查询自动加 `deleted = 0` → 运行时报「列不存在」。而这**编译期完全看不出来** | 该实体改成不继承基类、显式声明字段;"移除"语义走 `status = 0`(DDL 注释即如此),**不要对它调 `deleteById`**(会变成物理删除) |
|
||
| 2 | **任务编码查重被逻辑删除遮蔽** | 用 MP 的 `selectCount` 查重会自动过滤 `deleted = 0`,但**数据库的唯一约束不看软删标记** —— 软删掉的任务仍占着那个编码。表现为 `duplicate key value violates unique constraint "uk_task_tenant_code"`(500)。另外原用 `COUNT+1` 也错:删掉中间一个任务后计数会退回去,算出已占用的号 | 改为 `MAX(序号) + 1`,且**查重语句刻意不加 `deleted = 0`**(与唯一约束同口径);编码一次用过就不复用(它同时是**文件目录名**,复用会把两批任务的文件混进同一目录)。并发撞号由唯一约束兜底并返回可读提示,不再 500 |
|
||
| 3 | **Boot 4 的 Jackson 自动配置在 `spring-boot-starter-jackson`,项目没引** | 两个症状:① 容器里**没有 `ObjectMapper` bean**(`@Autowired` 直接启动失败);② `application.yml` 里 `spring.jackson.*` **静默失效** —— 其中 `time-zone: Asia/Shanghai` 不生效最危险:本机时区恰好是上海时看不出来,部署到 UTC 服务器后所有时间会整体偏 8 小时,且只表现为"时间看着不对"。**注意 `spring-boot-starter-json` 在 Boot 4 已无 4.x 版本**(仓库里最高 3.5.6),照老习惯找它是找不到的 | 在 `xt-common` 补 `spring-boot-starter-jackson`。但**任务配置的 JSON 读写仍不用 Jackson** —— Boot 4 用 Jackson 3(`tools.jackson`)而 `jjwt-jackson` 带来 Jackson 2,打包后 jar 里两个 `jackson-databind` 并存(2.21.5 + 3.1.5),import 哪个都是赌;改用项目已直接声明的 `hutool-all` |
|
||
|
||
### 已知缺口(有意留着)
|
||
|
||
| 项 | 说明 |
|
||
|---|---|
|
||
| **发布在前置数据齐备前不可达** | 发布要求名册(M2)、材料项(M1-04)、覆盖班级(M1-02)。所以 E2E 用 **SQL 夹具**造这三样,才能走到"已发布→暂停⇄恢复→截止→归档"并验证状态机。这是测试夹具,不是产品捷径 —— **M1-02/M1-04/M2 落地后要回来把夹具换成真实接口** |
|
||
| 命名规则试跑目前是**静态校验** | 只查"变量名是否合法"与"是否含姓名/证件号/材料名"。完整形态(拿真实名册前若干行跑一遍,检查生成的名字非空且两两不重复)需要名册数据,属 M1-09 / M3-01。当前这条已能拦住绝大多数错误(变量名写错、漏必备变量),且名册为空时发布本就会被 `40016` 先拦 |
|
||
| `stat` 恒为 `null` | 依赖名册(M2)与提交单(M3)。**故意不返回全 0 的对象** —— 那会让界面显示"完成率 0%",而真相是"还没有分母"。界面显式显示「待导入名册」 |
|
||
| 任务编码并发撞号 | 两个请求同时到达可能算出同一个号。唯一约束兜底 + 可读提示(不再 500)。一学期几十个任务,撞号概率极低,为此引独立序列表或分布式锁不划算 —— 但**别当成已解决** |
|
||
| `taskOrgMapper` 尚未用到 | M1-01 只用到 `TaskReadinessMapper` 里的计数;覆盖班级的增删改属 M1-02 |
|
||
|
||
### 前端踩到的坑
|
||
|
||
- **不要在校验/修复工具会碰的模板里拼花括号**。我写 `{{ '{' + v + '}' }}` 渲染命名变量按钮,`eslint --fix` 把它"帮忙"改成模板字符串 `` `{${v}}` `` —— 而里面的 `}}` 会截断 Vue 的插值解析器,报
|
||
`Error parsing JavaScript expression: Unexpected token, expected "}"`。
|
||
**这个错只在 `pnpm build` / 编译时出现,编辑器里看不出来。**
|
||
正确做法:在 `<script>` 里算好 `{ value, label }`,模板只做纯取值。
|
||
|
||
- **列宽要先量可用宽度,不能按像素凑总宽**(实测数据):
|
||
| 页面 | 可用宽度 | 原因 |
|
||
|---|---|---|
|
||
| 收集任务 | 约 **824px** | 只有 200 左侧栏 |
|
||
| 账号管理 | 约 **574px** | 200 左侧栏 + 约 250 左侧组织树卡 |
|
||
|
||
Arco 会按容器**重新分配**列宽:声明 120 的列被拉到 188,而 `fixed="right"` 的操作列被压到 105 —— 105px 装不下「编辑 / 重置密码 / 停用」,**竖排成了「编 辑 / 重 置 密 码 / 停 用」**。
|
||
更关键的是:列宽合计超过可用宽度时必然横向滚动,而**钉住的操作列会盖住紧邻它的那一列**(实测把「状态」整个遮住了,表头挤成「进度操作」)—— 这是固定列的固有行为,不是配置错误。
|
||
**踩过两次的教训:去掉 `fixed` 看似不压盖了,实际是把操作列滚出视野,用户根本看不到操作入口 —— 比压盖更糟。**
|
||
真正的解法只有一条:把列数减到能在可用宽度内放下(账号管理目前 5 列 800px,还需要再减一列)。
|
||
|
||
- ⚠️ **改中文内容一律用 Python 脚本文件,不要用 PowerShell 内联命令**(本项目已写进规则,我又犯了一次):
|
||
用 `python -c @"..."@` 内联改这两个 vue 文件时,PowerShell 把 `fixed="right"` 的 `f` 吃成了一个**不可见字符**(变成 `ixed="right"`),同时引入"不规则空白"。
|
||
肉眼完全看不出来,只有 eslint 的 `no-irregular-whitespace` 能发现。
|
||
|
||
---
|
||
|
||
## 一·六、M1-02 覆盖班级与班主任(2026-09-29 上午,未提交)
|
||
|
||
> 范围:`GET/PUT /api/v1/tasks/{id}/orgs`(《03-接口设计》§7.6);
|
||
> 前端「收集任务 → 操作 → 配置班级」抽屉(勾选班级 + 主/副班主任 + 应填人数)。
|
||
|
||
**验收**:`python scripts/e2e-m1-orgs.py` → **48/48**;
|
||
`e2e-m1-task.py` 71/71、`e2e-m0.py` 36/36、`e2e-m0-user.py` 55/55 回归通过;
|
||
前端 `typecheck` / `eslint` / `lint:style` 0 错、`build` 通过。
|
||
|
||
### 三条守住的规则(改动时当成规则)
|
||
|
||
| # | 规则 | 为什么 |
|
||
|:--:|---|---|
|
||
| 1 | **班级与初审人都必须在本租户内** | 跨租户的 id 一律当作"不存在或已删除",不透露"它存在但不属于你"。漏了的后果是 A 校可以把 B 校的班挂到自己的任务上,B 校的班主任就出现在 A 校的审批流里 |
|
||
| 2 | **初审人必须持有班主任/副班主任角色** | 「指定初审人」本质上是一次**授权** —— 那个人将能看到该班学生的身份证号与学籍材料。不做角色校验就等于把"谁能看学生隐私"变成任意授权。报错要给出下一步(去账号管理分配角色),不能只说"校验失败" |
|
||
| 3 | **移除是软移除**(`status = 0`),且**"移除再加回"必须复用原行** | 名册与提交单挂在覆盖关系下,物理删会让历史数据失去归属。而复用是**必须**的(不是优化):`uk_task_org UNIQUE (task_id, org_id)` 不带 status 条件,一个班在同一任务下只能有一行,直接 insert 会违反唯一约束 |
|
||
|
||
### 本轮修掉的 3 个真问题
|
||
|
||
| # | 问题 | 后果与修法 |
|
||
|:--:|---|---|
|
||
| 1 | **我误判 `uk_task_org` 不存在** | 先前的 grep 只匹配含 `collect_task_org` 字样的行,而 `CONSTRAINT uk_task_org UNIQUE (task_id, org_id)` 那行**没有表名** —— 于是漏掉了,还基于"没有唯一约束"的错误前提写了一个多余的部分唯一索引 `uk_task_org_active`。已撤掉(DROP INDEX + 删 flyway 历史行 + 删文件),并更正了注释:**"复用已移除的行"不是优化而是必须**。教训:**查约束要查 `CREATE TABLE` 整块,不能只 grep 表名** |
|
||
| 2 | **`collect_task_org.expected_count` 是 `NOT NULL DEFAULT 0`,与接口契约矛盾** | DDL 注释写的是"冗余,由名册汇总"(系统算的),但《03-接口设计》§7.6 把它放在 **PUT 请求体**里,且说"用于**与名册实际人数比对**,不一致时告警" —— 只有"管理员填的期望值 vs 名册实际值"才需要比对。而 `0` 是有意义的业务值("预期 0 人"),拿它兼职表示"没填"会让**每个没填的班**都触发"应填 0 人、实际 31 人"的假告警;告警变噪音就没人看了,真正"应填 28、名册却有 31"(名册导错)反而被漏掉 —— 而这条告警的全部价值就在那一次。已由 `V4__task_org_expected_count_nullable.sql` 改为**可空 + 去掉默认值**,并同步更正了 `docs/db/schema-postgresql.sql` 与 PRD 里的内联 DDL。**NULL ≠ 0 这条规则适用于所有"可选数字"字段** |
|
||
| 3 | **删掉迁移文件后必须 `mvn clean`,否则它会"复活"** | 我删掉 `V4__task_org_active_unique.sql` 后只跑了 `mvn package`(没有 clean):`target/classes/db/migration/` 里**还留着旧副本**,被打进 jar,Flyway 发现历史里没有 v4 就**又执行了一遍** —— 索引回来了、历史行填回去了。之后 I 又建了同名版本号的新 V4,启动就报 `Found more than one migration with version 4`;清了旧副本后又变成 `Migration checksum mismatch for migration version 4`。**排查绕了好几圈,根源就是一行 `mvn package` 少了 `clean`。** 规则:**动过 `db/migration/` 里的文件(新增、删除、改名),一律 `mvn clean package`** |
|
||
|
||
顺带修掉:`views/task/index.vue` 的操作列从 3 个链接涨到 4 个(编辑/配置班级/发布/删除),
|
||
160px 会挤成竖排 —— 把「进度」并进「状态」列腾出 40px,操作列给到 200px,合计 820px
|
||
(可用约 824px)。发布被拦时现在会**告诉用户去哪里补**(`BLOCKING_HINTS`),
|
||
而不是只说"未设置覆盖班级"。
|
||
|
||
### E2E 脚本必须"崩了也清理"
|
||
|
||
`e2e-m1-orgs.py` 第一次运行时在准备阶段抛了 `TypeError`(我自己写错了参数),
|
||
**脚本直接退出、清理段没执行**,把两个测试账号留在了库里 —— 直到我在浏览器里
|
||
看到「班主任」下拉里有个 `E2E班主任688ed2` 才发现。
|
||
|
||
已改成:清理逻辑抽成模块级 `cleanup_all()`(幂等),既在 `main()` 末尾调用,
|
||
也在 `__main__` 的 `try/except BaseException` 里兜一次。
|
||
**其余三条 E2E 脚本(`e2e-m0.py` / `e2e-m0-user.py` / `e2e-m1-task.py`)还是老样子,
|
||
崩了会漏数据,下一轮一并按这个模式改。**
|
||
|
||
> 另外记一笔:清理残留账号时我写的用户名前缀过滤(`adm`)**把 `admin` 也匹配进去了**,
|
||
> 是服务端的「不能停用当前登录的账号,请换一个管理员账号操作」把它挡下来的。
|
||
> 那条护栏当初加的时候是为了防自锁,这次意外挡住了一次误删 —— 护栏的价值往往
|
||
> 不是在被设计的场景里体现的。
|
||
|
||
### ⚠️ 新增两条前端硬规则(来自 `frontend-pc/AGENTS.md` 与 `.agents/skills/`)
|
||
|
||
1. **未经用户明确要求,不要 `git commit` / `push`**(AGENTS.md 常驻规则)。
|
||
本轮改动**未提交**,工作区里留着。
|
||
2. **`defineEmits` 必须用函数调用签名**,禁止 tuple/对象形式:
|
||
```ts
|
||
// ✅
|
||
const emit = defineEmits<{ (e: 'save-success'): void }>()
|
||
// ❌ const emit = defineEmits<{ 'save-success': [] }>()
|
||
```
|
||
框架自带组件(GiForm / GiButton / GiTable)用的都是前者 —— 这是本项目已确立的写法。
|
||
我此前写的 5 个组件(`OrgFormModal` / `TaskFormModal` / `TaskOrgDrawer` /
|
||
`UserFormModal` / `UserImportModal`)都是被禁的 tuple 形式,**本轮已全部改正**。
|
||
|
||
改文件前先按 `frontend-pc/AGENTS.md` 的表格加载对应 skill(`.vue` → `code-vue`,样式 → `code-css`,
|
||
`.ts` → `code-ts`,CRUD → `project-crud`)。
|
||
|
||
---
|
||
|
||
**关键实现决策(不要在后继开发中改回去)**
|
||
|
||
| 决策 | 理由 |
|
||
|---|---|
|
||
| **权限码定义在代码里**(`PermissionDef` / `BuiltinRole`),不建 `sys_permission` 表 | 权限码是前后端契约,放 DB 必然与代码漂移;一期无"权限配置界面"需求 |
|
||
| **租户拦截器默认拒绝**:匿名请求触及租户表直接抛 40101 | 漏加租户条件 = 越租户泄露全校身份证号,不可接受的失败模式;宁可让开发者立刻撞墙 |
|
||
| 匿名接口只靠 `@PublicApi` 放行,**不做路径白名单** | 两套机制会让"哪些接口公开"出现两个来源,改一处漏一处 |
|
||
| **登录时账号不存在也跑一次 BCrypt**(假哈希) | 否则响应时间差会泄露"账号是否存在",直接把密码喷洒成功率提高几个数量级 |
|
||
| 密码 RSA 加密传输 | 不是替代 HTTPS,而是防止网关/APM/链路追踪把明文密码记进日志 |
|
||
| 登出黑名单**存 token 的 SHA-256**,TTL 到自然过期 | token 本身是凭证,明文写进 Redis 等于复制了一份到别处 |
|
||
|
||
**四个必须实测的坑**(详见 `07-开发实施计划.md` §9,10 条实录)
|
||
|
||
Boot 4 / Flyway 10 / MP 3.5.9 这一批版本都在"把单体拆成模块",
|
||
导致**「依赖在、功能不在」的静默失败变多**。典型:只加 `flyway-core` 时迁移**不跑也不报错**。
|
||
**规则:每加一个中间件,必须实测它的核心动作真的发生了,不能只看依赖解析成功。**
|
||
|
||
---
|
||
|
||
## 一·五、客户回执(2026-09-28 · 需求已冻结)
|
||
|
||
> **客户全部 23 项按建议方案确认。** 唯一功能增量是 **B1:名单加入申请通道**。
|
||
|
||
| 组 | 需开发 | 说明 |
|
||
|---|:--:|---|
|
||
| A 认定机构 6 项 | **0** | 与现有设计完全一致(`min_long_side=1400`、`min_quality=70`、PNG 保留等都是当初按实测设计的) |
|
||
| B 校方业务 9 项 | **1** | **B1** 已落地:FR-509 系列 + `collect_join_request` 表 + 5 接口 + 2 屏原型 |
|
||
| C 商务法务 5 项 | 0 | C1 小程序资质推进中,H5 保底已列入 M0-13 预研 |
|
||
| D 内部技术 7 项 | 0 | **D4 已后置为二期**(一期不涉及零样本材料);**D7 已降级**(文件名识别准确率不影响任何交付项,残留只是上传页文案校准,班主任群里问两句即可)。**均已清除,无外部依赖** |
|
||
|
||
**规模变化**:FR 115 → **125** / 接口 117 → **121** / 库表 19 → **20** / VAL 20 → **21** / 错误码 50 → **57** / Golden 断言 29 → **36** / 功能验收 85 → **111** / 原型 29 → **31** 屏 / 任务 94 → **100** / 人日 380 → **392** → **387**(一期收窄 −5)/ 工期 14 → **15 周**。
|
||
|
||
**B1 的设计要点(三条边界,不要在后继开发中放水)**
|
||
|
||
| # | 边界 | 理由 |
|
||
|:--:|---|---|
|
||
| 1 | **只解决"进名单",不绕过门禁** | 申请期间仍不能填报;审批通过后才自动绑定放行 |
|
||
| 2 | **证件号与手机号仅存 HMAC + 脱敏值** | 与门禁口径一致 |
|
||
| 3 | **驳回必须逐条填原因;批量通过不提供"全选"** | 身份认定环节误操作代价极高 |
|
||
|
||
---
|
||
|
||
## 二、已锁定的铁律(不要重开讨论)
|
||
|
||
| # | 决策 | 关键理由 |
|
||
|---|---|---|
|
||
| 1 | **扫描件强制压到 ≤1M,无例外** | 认定机构硬要求 |
|
||
| 2 | 默认参数 **300dpi / q85** | 实测 28/28 达标,平均 806KB,清晰度零损失 |
|
||
| 3 | 材料来源只有 **聊天记录 / 相册 / 手机文件**,「直接拍照」默认关闭 | 实测 4 类材料无一来自现场拍照 |
|
||
| 4 | **必须先导名单**,姓名+身份证匹配不通过只提示、不放行(学校与社会收集皆然) | 门禁是产品根基 |
|
||
| 5 | 报考范围**从名单聚合**,不存在独立"计划清单"文件 | 真实名单验证 |
|
||
| 6 | 动态表单 = **内置字段(固定列,可统计)+ 扩展字段(extras_json,仅展示导出)** | 兼顾统计能力与灵活性 |
|
||
| 7 | 数据库 **PostgreSQL**(纯 PG);软删除字段名为 **`deleted`** | 客户已确认 |
|
||
| 8 | 班主任端**只做右滑通过**,驳回必须点按钮 | 误触后果不对称 |
|
||
| 9 | 批量通过**只带无问题项**,红黄一律排除 | 防"手快全选" |
|
||
| 10 | 有不合格文件时**直接禁用导出按钮**,不允许带问题打包 | 铁律 |
|
||
| 11 | 识别策略 **规则为主 + VLM 兜底**(触发率 2%~10%) | 成本可控 |
|
||
| 12 | 识别失败**一律放行**,不得阻断提交 | 可用性优先 |
|
||
| 13 | 原型**不用 emoji**(`→` `↔` `·` 等排版字符保留) | 设计约束 |
|
||
|
||
---
|
||
|
||
## 三、已完成交付物
|
||
|
||
| 文件 | 版本/大小 | 状态 |
|
||
|---|---|---|
|
||
| `docs/数据信息填报系统PRD.md` | V2.4 / 189KB / 3019 行 | ✅ 含 **125 条 FR**、**21 条 VAL**、**121 个接口**、**20 表 DDL**、实测附录 |
|
||
| `docs/数据信息填报系统PRD-功能逻辑卷.md` | V1.9 / 88KB | ✅ 纯功能逻辑,**不含库表/接口/代码** |
|
||
| `docs/db/schema-postgresql.sql` | 37KB | ✅ **PG 16.15 实测零错误**(20 表 / 177 列注释 / 65 索引 / 20 表注释) |
|
||
| `docs/prototype/student-app.html` | **11 屏** / 57KB | ✅ 含 STEP 03B「申请加入名单」 |
|
||
| `docs/prototype/teacher-app.html` | 9 屏 / 63KB | ✅ |
|
||
| `docs/prototype/admin-web.html` | **11 屏** / 114KB | ✅ 含表单设计器、材料规则配置、历史整理器、**申请审批** |
|
||
| `docs/prototype/index.html` | 7KB | ✅ 三端入口 |
|
||
| `docs/00-交付计划.md` | — | ✅ 7 项计划与进度记录 |
|
||
| `docs/01-客户与机构确认函.md` | 23KB | ✅ **P1** 23 项**已全部确认**(客户按建议执行,需求冻结) |
|
||
| `docs/02-需求追溯矩阵.md` | 10KB | ✅ **P2** 审计报告,含 10 个接口缺口 |
|
||
| `docs/03-接口设计.md` | **115KB / 2883 行** | ✅ **P3 121 个接口**字段级定义 + 枚举全集 + 12 个 VO + **58 个错误码**;§4 逐节声称数与表行数一致 |
|
||
| `docs/06-验收测试清单.md` | **33KB** | ✅ **P6 36 条 Golden 断言** + **111 条功能验收** + **27 条异常边界** |
|
||
| `../README.md` | **10KB** | ✅ **P7 文档导航**,按角色读法 + 快速上手 |
|
||
| `docs/04-核心模块设计说明.md` | **69KB / 1453 行** | ✅ **P4 六个模块**伪代码级说明 + 依赖图 + Golden 基线 |
|
||
| `docs/05-开发任务拆解.md` | **31KB / 506 行** | ✅ **P5 100 个任务 / 387 人日 / 15 周**,含关键路径、一期范围与核算口径 |
|
||
|
||
---
|
||
|
||
## 四、待办计划
|
||
|
||
| # | 任务 | 产出 | 状态 |
|
||
|---|---|---|---|
|
||
| ~~P1~~ | 客户与认定机构确认函 | `01-客户与机构确认函.md` | ✅ |
|
||
| ~~P2~~ | 需求追溯矩阵 | `02-需求追溯矩阵.md` | ✅ |
|
||
| ~~P2.5~~ | 管理端补 3 屏原型 | 并入 `admin-web.html` | ✅ |
|
||
| **P3** | **接口设计(OpenAPI)** | `03-接口设计.md` | ✅ **已完成**(117 个接口) |
|
||
| P4 | 核心模块设计说明(伪代码级) | `04-核心模块设计说明.md` | ✅ **已完成**(6 个模块) |
|
||
| P5 | 开发任务拆解 WBS(M0–M7) | `05-开发任务拆解.md` | ✅ **已完成**(94 任务 / 380 人日 / 14 周) |
|
||
| P6 | 验收测试清单(Golden Test Set) | `06-验收测试清单.md` | ✅ **已完成**(29 条 Golden 断言 + 85 条功能验收) |
|
||
| P7 | 文档导航 README | `../README.md` | ✅ **已完成** |
|
||
|
||
### P3 已补齐的缺口(21 个接口,原审计发现 10 项)
|
||
|
||
```
|
||
✅ I1 报考范围读取/确认 GET/PUT /api/v1/tasks/{id}/plan-scope
|
||
✅ I2 历史数据整理器 POST /api/v1/cleanup/jobs(共 5 个)
|
||
✅ I3 字典管理 /api/v1/dicts(4 个)
|
||
✅ I4 操作日志 GET /api/v1/logs/operations
|
||
✅ I5 登录日志 GET /api/v1/logs/logins
|
||
✅ I6 消息中心 /mp/messages、/mp/messages/read、/mp/teacher/messages
|
||
✅ I7 订阅额度登记 POST /api/v1/mp/subscribe
|
||
✅ I8 审核撤回 POST /api/v1/audit/submissions/{id}/revoke
|
||
✅ I9 任务暂停/恢复 POST /api/v1/tasks/{id}/pause、/resume
|
||
✅ I10 材料识别结果读取 已并入 GET /api/v1/audit/submissions/{id} 响应
|
||
➕ 文件规范化 process 原仅在 PRD §8.9 详述、未登记进清单,已补登
|
||
```
|
||
|
||
**接口总数:96(PRD 原有)+ 21 = 117**
|
||
|
||
---
|
||
|
||
## 五、关键事实速查
|
||
|
||
### 真实基准数据集(Golden Test Set)
|
||
|
||
`D:\项目\202609机电填报系统\24中技数控1班\` —— **28 人 × 4 类材料 = 112 文件**
|
||
|
||
| 实测项 | 结果 |
|
||
|---|---|
|
||
| 原始总量 | 475 MB |
|
||
| 命名违规 | 59 / 112(52.7%) |
|
||
| 双扩展名 | 56 |
|
||
| 体积超标(>1M) | 31 |
|
||
| 证件号异常 | 5 例(3 可自动、2 待人工) |
|
||
| 疑似串档 | 1(**唯一必须人工判断的**) |
|
||
| 压缩后 | 300dpi/q85 → 平均 806KB、最大 831KB、**0 张超标** |
|
||
| 材料识别 | 文件名层 112/112;形态层 99.1%;OCR 内容层 32/32 |
|
||
|
||
### 官方模板要点
|
||
|
||
- 21 列(A–U):**红=必填 11 项、绿=级联 6 项、黑=选填 4 项**
|
||
- **「报考职业」「报考工种」在 Excel 里没有下拉,是自由填写** ← 我方产品的核心增量
|
||
- 「职业技能等级个人申报表」的规范文件名却是 **「工作证明」**(机构既有习惯)
|
||
- **表头行与工作表名都不固定**(实测一份在第 1 行、一份在第 6 行)
|
||
|
||
### 三个易错实现陷阱
|
||
|
||
1. **「无」是合法业务值,不是空值**
|
||
2. **专项职业能力:无工种 / 无等级 / 无补考科目(0 科)**
|
||
3. 补考约束:三/四/五级最多 1 科,一/二级最多 2 科,专项 0 科
|
||
|
||
### 压缩五档(PRD §6.3)
|
||
|
||
A4 扫描件 300dpi(2480×3508)/q85 · 一寸照片固定 295×413 · 学籍表为 16:9 截图**保持 PNG 不缩放**
|
||
|
||
### 教师端数字口径(原型必须自洽)
|
||
|
||
28 人 = 已通过 15 + 待审 4 + 待核对 3 + 有问题 1 + 未提交 5;**完成率 82% 是已提交率(23/28)**
|
||
|
||
---
|
||
|
||
## 六、环境与工具
|
||
|
||
| 项 | 内容 |
|
||
|---|---|
|
||
| PG 验证容器 | `docker exec -it pg-verify psql -U postgres -d filldb`;清理 `docker rm -f pg-verify` |
|
||
| 原型校验脚本 | `python C:\Users\93292\AppData\Local\Temp\opencode\check2.py <html路径>` |
|
||
| DDL 同步方式 | 从主 PRD 的 ` ```sql ` 代码块自动抽取生成,改文档后重跑抽取脚本 |
|
||
| 原型约束 | 单文件自包含、零外部依赖、内联 SVG(`<symbol>`+`<use>`,1.7px 描边 currentColor)、无 emoji |
|
||
| 原型校验基线 | 四份全通过:标签配对 ✓ / CSS 变量 ✓ / SVG 无冗余 ✓ / emoji 0 ✓ |
|
||
|
||
---
|
||
|
||
## 七、重要教训
|
||
|
||
### 教训一:脚本审计前必须先验证脚本本身
|
||
P2 审计时脚本因未匹配加粗编号 ``| **FR-614** |``,误报 3 个"需求条目问题"(实为假警报);若直接采信并"修复",会引入 3 处新错误。同时 FR 总数由误算的 102 修正为 **115**。
|
||
|
||
> **⚠️ 这条教训在 2026-09-28 写开发实施计划时又复现了一次。**
|
||
> 我为交接摘要重算交付物体量,用了三个正则去数"接口数",分别得到 **74 / 95 / 121**——
|
||
> 三个都"看起来对",但没有一个经过验证。
|
||
>
|
||
> 定位过程:**逐节对比「标题声称数」与「实际表行数」**,发现
|
||
> ① 我漏了 `| ⭐ GET | ... |` 这种 ⭐ 在方法**前面**的行;
|
||
> ② §4.9 / §4.11 是**索引节**(交叉引用别处已计数的接口),本就不该计入。
|
||
> 修正后逐节声称数与表行数**全部为 0 差**,合计 **121**。
|
||
>
|
||
> **加固**:`scripts/verify-docs.py` 现在会做「**逐节声称数 vs 实际行数**」的一致性检查,
|
||
> 而不是只看总数对不对——总数相等也可能是个别节多算、个别节少算恰好抵消。
|
||
>
|
||
> **可复用的规则**:
|
||
> 1. 数字类断言必须**交叉核对至少两种独立方法**;
|
||
> 2. 核对要**分块**(逐节/逐表),不能只看总和;
|
||
> 3. 相互矛盾的中间值**一个都不要先写进文档**——写错再改,比一开始就空着更容易误导人。
|
||
|
||
### 教训二:写新文档必须回查已有编号体系,不能凭记忆
|
||
P4 写 M5 校验引擎时,我凭记忆用了一套 VAL 编号,**与 PRD 权威清单整体错位**:
|
||
|
||
| P4 原写法 | 我以为的含义 | PRD 实际定义 |
|
||
|---|---|---|
|
||
| VAL-01 | 姓名与名册一致 | **证件号格式与校验位** |
|
||
| VAL-03 | 证件号格式 | **姓名与名册一致** |
|
||
| VAL-06 | 双扩展名 | **文件格式白名单** |
|
||
| VAL-07 | 多扩展名冲突 | **文件体积上限** |
|
||
|
||
**后果**:若不修正,开发按 P4 实现会把规则完全接错,且两份文档互相矛盾。
|
||
**修正**:已全部重映射到 PRD 体系(单次扫描替换避免 01↔03 碰撞),并给 PRD 补了 P4 需要的三条新规则 **VAL-18 文件名规范化 / VAL-19 疑似串档 / VAL-20 体积下限保护**。
|
||
**规则**:新增文档引用任何已有编号(FR/VAL/错误码/接口)前,**先脚本抽取权威清单核对**,不凭记忆。
|
||
|
||
### 教训三:系统里的数据不会错,错的一定是「归属」
|
||
客户指出:**"系统应该不存在名称错误的情况。是先录入了有这个姓名和身份证,然后才能填写的。"**
|
||
|
||
这指出一个真实的设计缺陷:
|
||
|
||
| 位置 | 会不会错 | 说明 |
|
||
|---|---|---|
|
||
| **填报单里的姓名/证件号** | **永远不会错** | 门禁已核验 + 字段锁定。客户说得对 |
|
||
| 文件名里的姓名/证件号 | 可能不同 | **但这不是"名称错误",而是"这份文件可能不属于这个人"** |
|
||
|
||
**原设计的错误**:把"文件名姓证件号与名册不符"处理为「自动按名册修正」(信息级),
|
||
这会把"A 传了 B 的材料"**静默洗白**成 A 的材料——审核时完全看不出来,事后无从追溯。
|
||
|
||
```
|
||
学生 A 在微信里误选了同学 B 的材料
|
||
↓ 文件名:李四-3610...(B 的号码,校验位通过)
|
||
错误做法:判"与名册不符" → 改成 张三-3610... → 信息级,A 看不到
|
||
↓ 审核:班主任看到一份名字正确的材料 → 通过
|
||
↓ 最终:B 的工作证明被认定成 A 的 ← 灾难
|
||
```
|
||
|
||
**修正(客户已确认)**:
|
||
1. **判定优先级重排**为 `证件号反查名册 > OCR 内容 > 姓名(仅辅助)`
|
||
2. 新增 **VAL-21 文件归属校验**:归属存疑一律**阻断提交**,且**不提供"仍然继续"**
|
||
3. **姓名不再作为独立规则**——只在证件号佐证时按名册取值,仅留痕不打扰学生
|
||
4. 核心判据:**文件名证件号能否在名册里反查到"另一个人"**(一次查询,走唯一索引,< 1ms)
|
||
|
||
**精髓在于两条的区分**:
|
||
- **校验位失败** = 不是一个真实号码 → 只是写错了 → **可以自动改**
|
||
- **校验位通过** = 一个真实存在的自然人 → 改了就是改了身份 → **必须阻断**
|
||
|
||
### 教训五:联系方式不进系统
|
||
客户指出:**"学生本来就有班主任联系方式,不需要系统里面体现。"**
|
||
|
||
原设计在门禁失败页放了一个「联系老师」按钮(脱敏手机号 + 一键拨打),并为此专门开了一个接口 `GET /mp/roster/contact`。
|
||
已移除:
|
||
|
||
| 移除项 | 原因 |
|
||
|---|---|
|
||
| `GET /mp/roster/contact` 接口 | 不必要(用户已知道怎么找人)+ 有维护成本(换人/换号要同步)+ 多一处隐私暴露面 |
|
||
| 门禁失败页「联系老师」按钮 | 同上。改为只保留两条**系统内动作**:重新填写 / 申请加入名单 |
|
||
| 功能逻辑卷中的联系方式展示描述 | 同步 |
|
||
|
||
**保留**:纯文案提示(如"如有疑问请联系老师")——**不涉及任何联系方式数据**。
|
||
**接口总数**:122 → **121**。
|
||
|
||
### 教训四:「社会评价」与「社会收集」只差一个字
|
||
`cert_type`(认定类型:自主认定 / **社会评价** / 专项职业能力)决定**填什么材料**;
|
||
`collect_mode`(收集模式:学校收集 / **社会收集**)决定**谁审核**。二者可任意组合,已在 PRD §5.3 与接口 §2.3 分别写明。
|
||
另外补全了 `cert_type` 的第三个取值(专项职业能力)在 DDL 注释与附录中的遗漏。
|
||
|
||
### 附带修正
|
||
- **体积口径**:原型导出中心曾写"475MB → 22.4MB(95%)",与 PRD 及历史整理器屏(37MB/92%)矛盾。实为混淆——**444→22MB 是"工作证明"单项,475→37MB 是全班 112 文件合计**。已统一为 37MB / 92%。
|
||
|
||
---
|
||
|
||
## 八、外部依赖(未回)
|
||
|
||
`01-客户与机构确认函.md` 已发出待回,其中**最高优先级 3 项**:
|
||
|
||
- **A1** 认定机构是否书面接受 300dpi 扫描件
|
||
- **B1** 名单外的人怎么处理(建议加"申请加入名单→管理员审批"通道)
|
||
- **B3** 是否允许学生修改老师预填字段(建议:姓名/证件号/报考三项锁定,其余可改留痕)
|
||
|
||
未回复则按文档内建议方案推进。
|