lovife 6e7597531a feat(mp): M3-02 H5 微信网页授权(公众号 OAuth)
方向决定了 H5 优先:资质门槛低得多、且前端可自动化测试。
小程序端代码不浪费 —— uni-app 一套代码多端,build:h5 直接出 H5。

交付:
· GET /mp/h5/authorize —— 生成微信授权地址(公众号 appid,snsapi_base 静默授权)
· GET /mp/h5/callback —— code 换 openid → 找/建学生 → 下发会话 → 302 回 H5
· GET /mp/h5/stub-login —— 【仅开发/测试】跳过微信授权的登录,供自动化测试
· fill.mp-h5 配置段(公众号凭据)

关键事实:H5 必须用**公众号**的 appid —— 网页授权与小程序码是两套凭据、两套接口,
不能混用。所以配置分成 fill.mini-program(小程序码用)与 fill.mp-h5(网页授权用)。

授权流程的三处设计:
1) token 放在 URL 的 fragment(#)里,不放 query。fragment 不发给服务器 ——
   不会进 Nginx access log、不会进 Referer、不会进浏览器历史。
   放 query 等于把 token 写进四五处日志。
2) scene 不裸放在 state 里,而是存 Redis 用一次性随机串指代 ——
   state 会进 URL、浏览器历史与日志,放业务参数容易被人拿来构造请求。
3) 回调必须"永远重定向回应用",绝不抛业务错误。那是浏览器直接跳转的地址,
   抛错用户看到的是一屏 {"code":40001,...} JSON —— 一个学生完全无法理解、
   也不知道该怎么办的画面。

打桩(为了自动化)与四道约束:
   微信授权那一步必须真人在微信里点,脚本永远做不了,所以要有打桩;
   而打桩是危险动作 —— 一个"跳过微信授权"的开关漏进生产就是
   "任何人可登录任何学生"的后门。约束:
   ① 配置开关默认 false,只有 dev 打开;
   ② 代码里再判一次 profile 不含 prod(配置写错也进不来,双保险);
   ③ 接口路径独立(/mp/h5/stub-login),不是"真实回调里加个 if"——
      那种写法一旦判断写反就是灾难;
   ④ 每次调用都 WARN 日志,不留静默通道。

本轮修掉 2 个真问题:
1) RoleProvisioner.bindRole 在匿名请求下不可用:它内部走 MP 的 BaseMapper,
   插入时租户拦截器拿不到 tenantId、直接抛「未登录」。症状是 500 且角色没建上
   (用户建了却没角色,后面每个接口都会以"无权限"拒绝他,排查毫无头绪)。
   已改为自己写:@InterceptorIgnore + 显式 tenant_id。
2) 授权回调抛业务错误 → 用户看到一屏 JSON。已改成 try/catch 后重定向回应用,
   把 e.getMessage() 作为 authError 带上(那句话本来就是写给人看的)。
   E2E 实测:用户看到的是「网页授权换 openid失败:code 无效或已使用过
   (code 只能用一次,且 5 分钟过期)(微信原文:40029 invalid code...)」。

⚠️ 上线前必须解决:H5 与小程序是**两个 openid**
   同一个人的小程序 openid 与公众号 openid 是不同的(不同 appid 各自签发),
   只有 union_id 一致 —— 而它需要两个账号都绑到同一个微信开放平台账号。
   后果:学生先用 H5 填、后来又用小程序填,系统当成两个用户,
   第二个渠道撞 40072(已被登记)。一期只走 H5 不会碰到,
   但两条腿都上线前必须解决(按 union_id 认人)。

已知缺口:
· H5 核验页未做(落地页的「开始填报」仍是"开发中"提示)。
· H5 桌面全宽铺开,该加 max-width 居中。
· 真实微信授权未走通:需要公众号后台填「授权回调页面域名」+ 域名解析到能跑服务的
  机器(或内网穿透)。打桩跳过的是微信那一步,后面全走真逻辑。
· /mp/login(小程序登录)仍未做,可与 H5 授权共用 resolveUser。

顺带确认:frontend-mp 的 build:h5 已能出 H5 产物,
已在无头浏览器里跑通「URL 带 scene → 读 scene → 跨域调后端 → 显示学校/任务/状态」,
CORS(后端早已配 localhost:*)也通。这条是选 H5 的核心收益:前端交互层可自动化验证。

验收:e2e-m3-h5-auth.py 22/22;受影响的四条回归全绿
(落地页 31 / 门禁 29 / M0 账号 55 / 二维码 32)。
2026-09-29 11:00:18 +08:00

数据信息填报系统

职业技能等级认定报名材料收集 SaaS 一期范围:学校收集 + 自主认定(4 类材料,全部有实测样本)。社会收集 / 社会评价 / 专项职业能力后置二期 学生在微信小程序填报 · 班主任在手机初审 · 管理员在 PC 终审与导出 最后更新:2026-09-28 | 状态:需求已冻结(客户全部确认,可开工)


一、这是什么

客户(职业院校 / 认定机构)每年要收集几百名学生的认定报名材料(一寸照片、身份证、学籍表、工作证明)。现状是靠班主任在微信群里收、手工改名、手工压缩、手工核对——一个班 28 人就有 112 个文件、59 个命名违规、31 个超体积,全靠人眼。

本系统把这件事做成流程:门禁核验 → 按规则自动命名与压缩 → 材料识别 → 校验 → 分级审核 → 一键导出规范包。

核心增量:官方 Excel 模板里「报考职业」「报考工种」是自由填写、没有下拉——我们把它变成从名单聚合 + 一致性体检。


二、文档地图

按角色读

你是 从这几份开始 顺序
客户 / 业务方 01-客户与机构确认函.md → prototype/index.html 先看确认函(有 23 项待拍板),再点开原型
项目经理 00-交付计划.md → 05-开发任务拆解.md → 交接摘要.md 先看进度,再看 94 个任务与 14 周排期
产品 / 需求 数据信息填报系统PRD.md → 数据信息填报系统PRD-功能逻辑卷.md 前者含库表接口,后者是纯功能逻辑
后端开发 03-接口设计.md → 04-核心模块设计说明.md → db/schema-postgresql.sql 接口收发 → 引擎怎么做 → 建表
前端开发 prototype/ → 03-接口设计.md 原型是视觉与交互的唯一基准
测试 06-验收测试清单.md → 04-核心模块设计说明.md 附录 B 29 条 Golden 断言 + 85 条功能验收
新加入的人 交接摘要.md → 本文件 一份文档看清全部状态

全部文档

# 文档 规模 内容 状态
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 个错误码 ✅
04 04-核心模块设计说明.md 56 KB 6 个模块伪代码级说明 + 依赖图 + 26 项回归基线 ✅
05 05-开发任务拆解.md 31 KB 100 个任务 / 387 人日 / 15 周,含关键路径、一期范围与风险 ✅
06 06-验收测试清单.md 29 KB 36 条 Golden 断言 + 111 条功能验收 + 签署表 ✅
— 交接摘要.md 11 KB 状态源:客户回执、13 条铁律、已完成、待办、环境、教训 ✅
— 数据信息填报系统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 ✅

三、三分钟看懂产品

一条主线

          ┌─ 学生端(小程序)─┐   ┌─ 班主任端(手机)─┐   ┌─ 管理端(PC)─┐
名单 ──→  门禁核验 ──→ 填报 ──→   卡片流初审 ──→ 终审 ──→ 导出规范包
          └─✗ 失败 → 申请加入名单 → 管理员审批 → 入册放行
 ▲                  ▲   ▲                                        ▲
 │                  │   │                                        │
 报考范围           识别 命名+压缩                              汇总 Excel
 从名单聚合         校验  (≤1M 无例外)                          + 目录树

两个必须知道的数字

数字 含义
59 / 112 真实批次里的命名违规率(52.7%)。系统自动修
475 MB → 37 MB 原始总量 → 导出包,压缩率 92%。300dpi 清晰度零损失

三条铁律(不要试图绕过)

# 铁律 理由
1 必须先导名单,姓名+身份证匹配不通过不放行 门禁是产品根基
2 有不合格文件时直接禁用导出按钮 带问题打包出去,返工成本远高于当场修
3 识别失败一律放行 不阻断提交是完成率的底线
4 归属存疑一律阻断,且无"仍然继续" 文件名的姓/证号指向别人 = 传错材料。自动改名会把错传洗白,审核看不出来、事后无从追溯

四、关键决策速查

完整 13 条见 交接摘要.md 第二节。这里只列最容易被反复讨论的。

决策 内容
压缩 强制 ≤1M,无例外;默认 300dpi/q85(实测 28/28 达标,均值 806KB)
材料来源 只有聊天记录 / 相册 / 手机文件,「直接拍照」默认关闭
报考范围 从名单聚合,不存在独立的"计划清单"文件
数据库 PostgreSQL(纯 PG);软删除字段名 deleted
滑动交互 班主任端只做右滑通过,驳回必须点按钮
批量通过 只带无问题项,红黄一律排除
动态表单 内置字段(固定列可统计)+ 扩展字段(extras_json)
名单外的人 申请加入名单 → 管理员逐条审批;通道只解决"进名单",不绕过门禁
归属判定优先级 证件号反查名册 > OCR 内容 > 姓名(仅辅助,不做独立规则)
联系方式 系统内不展示任何班主任/管理员联系方式——学生本来就有;门禁失败只给「重新填写 / 申请加入名单」两条系统内出路
校验位的作用 失败 = 不是真实号码 → 可自动改;通过 = 真人 → 改了就是改身份 → 必须阻断
审批粒度 批量通过不提供"全选",驳回必须逐条填原因(身份认定误操作代价极高)

五、目录结构

xiaotutongxue/
├── README.md                      ← 本文件
└── docs/
    ├── 00-交付计划.md
    ├── 01-客户与机构确认函.md
    ├── 02-需求追溯矩阵.md
    ├── 03-接口设计.md
    ├── 04-核心模块设计说明.md
    ├── 05-开发任务拆解.md
    ├── 06-验收测试清单.md
    ├── 交接摘要.md
    ├── 数据信息填报系统PRD.md
    ├── 数据信息填报系统PRD-功能逻辑卷.md
    ├── db/
    │   └── schema-postgresql.sql
    └── prototype/
        ├── index.html              ← 三端入口,从这里打开
        ├── student-app.html
        ├── teacher-app.html
        └── admin-web.html

六、快速上手

看原型

直接双击 docs/prototype/index.html。单文件自包含、零外部依赖,不需要服务器。 管理端原型支持 100% / 82% / 66% 缩放查看。

验证数据库

# 起一个 PG 容器
docker run -d --name pg-verify -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=filldb -p 5432:5432 postgres:16

# 执行 DDL
docker exec -i pg-verify psql -U postgres -d filldb < docs/db/schema-postgresql.sql

# 验证结果:20 张表 / 177 列注释 / 65 索引,零错误
docker exec -it pg-verify psql -U postgres -d filldb

容器仍在本机运行时可 docker exec -it pg-verify psql -U postgres -d filldb 直接进入; 不需要了用 docker rm -f pg-verify 清理。

跑原型结构校验

python check2.py docs/prototype/admin-web.html
# 输出:标签配对 / CSS 变量 / SVG 图标 / emoji / 外部依赖 五项检查


六·二、把系统跑起来(三条命令)

# 1) 基础设施:PG 16 + Redis 7 + MinIO
cd deploy
copy .env.example .env        # 首次
docker compose up -d

# 2) 后端:Flyway 自动建库 + 自动初始化账号
cd ../backend
mvn spring-boot:run

# 3) 验收自测(35 项)
cd ..
python scripts/e2e-m0.py

开发环境初始账号(仅本地,由 fill.seed.enabled 控制):

账号 密码 角色
admin Admin@123 学校管理员(租户 demo)
platform Platform@123 平台运营

接口文档:http://localhost:8080/doc.html

代码结构

backend/          Java 21 + Spring Boot 4.1.1
  ├── xt-common/  统一响应 / 58 个错误码 / AES-GCM / HMAC / RSA
  └── xt-server/  配置 / 鉴权 / 多租户拦截 / 业务模块
deploy/           docker-compose + RSA 开发公钥
scripts/          DDL 同步、登录助手、M0 验收测试
frontend-pc/      (待建)Vue 3 + Element Plus
frontend-mp/      (待建)uni-app

⚠️ 搭环境前请先读

docs/07-开发实施计划.md §9「版本兼容踩坑记录」—— 那里记了 10 条 Boot 4 / Flyway 10 / MyBatis-Plus 3.5.9 的真实坑, 其中多数是「依赖在、功能不在,且不报错」的类型,靠读文档和凭经验都躲不掉。

七、交付状态

阶段 内容 状态
需求 主 PRD(125 FR / 20 VAL / 20 张表)+ 功能逻辑卷 ✅
设计 数据库 DDL(PG 16 实测)+ 接口设计(121)+ 模块设计(6) ✅
原型 31 屏(学生 11 / 班主任 9 / 管理 11)= 31 ✅
计划 100 任务 / 387 人日 / 15 周(一期范围已收窄) ✅
测试 36 条 Golden 断言 + 111 条功能验收 ✅
待外部 客户确认函(23 项) ✅ 已确认,按建议执行

客户已确认(2026-09-28 · 可开工)

项 确认结论
A1 ✅ 认定机构接受 300dpi 输出
B1 ✅ 增加「申请加入名单 → 管理员审批」通道(唯一功能增量,已落地)
B3 ✅ 字段三分类:姓名/证件号/报考三项锁定,其余可核对修改并留痕

其余 20 项均与现有设计一致,零改动。 需求已冻结,可按 05-开发任务拆解.md 的 M0 开工。


八、数据与测试基线

项 值
Golden Test Set D:\项目\202609机电填报系统\24中技数控1班\
规模 28 人 × 4 类材料 = 112 个文件,原始 475 MB
命名违规 59(52.7%)
双扩展名 56
体积超标 31
证件号异常 5(自动修 3 + 待人工 2)
疑似串档 1(唯一必须人工判断的)
压缩后 均值 806 KB,最大 831 KB,0 张超标
导出包 ≈37 MB,压缩率 92%

⚠️ 这个数据集不可替代。所有"实测"结论都来自它;换成构造数据,双扩展名 56/112 这种数字就复现不出来。 必须纳入版本管理或保留只读副本。


九、开发环境

项 说明
数据库 PostgreSQL 16(DDL 已实测通过)
开发顺序 M1 命名引擎 → M4 门禁 → M2 压缩 → M5 校验 → M3 识别 → M6 导出
客户回执 全部 23 项按建议执行;唯一功能增量 B1(申请加入名单)已落地
原型约束 单文件自包含、零外部依赖、内联 SVG 图标、无 emoji
最长的两个单点 压缩引擎 12 人日、打包引擎 10 人日(加人无效)
回归门禁 改动后必须复现 15 项基线指标(对应 29 条断言),偏差即回归

十、两条踩过的坑

# 教训
1 脚本审计前先验证脚本本身。追溯矩阵审计时脚本漏匹配加粗编号,误报 3 个"需求问题",若直接采信会引入 3 处错误
2 引用已有编号前先脚本核对,不凭记忆。模块设计里把 VAL 编号整体写错位(VAL-01 是姓名 vs 证件号格式),若不查会与 PRD 互相矛盾

详见 交接摘要.md 第七节。


文档中心维护约定:任何改动后,请更新 00-交付计划.md 的执行记录与 交接摘要.md 的状态;改动接口/库表/FR 编号时,必须重跑跨文档一致性核对。

S
Description
小图同学
Readme 5.4 MiB
Languages
Java 49.8%
Python 28.2%
Vue 16.7%
TypeScript 4.9%
SCSS 0.3%