# 交接摘要 > 用途:**压缩后的唯一状态源**。新会话读这一份即可接续,不必回看历史对话。 > 更新:2026-09-28 --- ## 一、项目一句话 职业技能等级认定**报名材料收集 SaaS**。学生在微信小程序填报、班主任在手机初审、管理员在 PC 终审与导出。核心价值:**动态表单 + 规则引擎(命名/压缩/识别)+ 门禁核验 + 一键规范导出**。 工作目录 `D:\IDEA_Project\xiaotutongxue`(**空 git 仓库,无 commit,全部改动未提交**),交付物在 `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
PC: Vue3 + Element Plus;小程序: uni-app | | M0 后端骨架 | ✅ 统一响应/异常/58 错误码/多租户拦截/软删除/字段填充/AES-GCM/HMAC/RSA/JWT/权限模型 | | M0 数据库 | ✅ Flyway 2 个迁移;26 张表(20 业务 + 5 标准 + 1 迁移记录) | | M0 后端接口 | ✅ 租户开通与列表、组织 CRUD 与树、PC 登录/登出/当前用户 | | M0 验收 | ✅ **`python scripts/e2e-m0.py` → 35/35 通过** | | 待做 | 用户 CRUD 与 Excel 导入;PC/小程序前端;CI/CD;H5 预研 | **关键实现决策(不要在后继开发中改回去)** | 决策 | 理由 | |---|---| | **权限码定义在代码里**(`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 ` | | DDL 同步方式 | 从主 PRD 的 ` ```sql ` 代码块自动抽取生成,改文档后重跑抽取脚本 | | 原型约束 | 单文件自包含、零外部依赖、内联 SVG(``+``,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** 是否允许学生修改老师预填字段(建议:姓名/证件号/报考三项锁定,其余可改留痕) 未回复则按文档内建议方案推进。