Files
xiaotutongxue/docs/06-验收测试清单.md
T
lovife 7df078e0ea docs: 同步交接摘要 / PRD / 原型,并记录品牌改名「深学在线」
- 交接摘要补 §一·三十五~§一·三十八:角色与授权、需求收敛(4 角色 / 10 菜单 / 取消二期)、
  PC 端重搭后的 UI 修复与卡片式基线、品牌改名与新标识
- PRD 与功能逻辑卷只改标题行与产品代号线;**文件名不动** —— verify-docs.py 按路径读,
  全库还有 20+ 处按文件名引用,改名会连带炸脚本
- 原型 index / admin-web / teacher-app / student-app 的品牌区改「深学在线」,
  admin-web 的 .mark(10 处)换成内嵌的新标 data URI,保持原型"单文件自包含"的约定
- 根 AGENTS.md 与 frontend-pc 的规范同步:接线检查补第 ⑨ 项(SVG 资产是不是合法 XML ——
  SVG 注释里出现连续两个减号就 not well-formed,图标裂开而控制台一句话都不报)、
  「最容易写错的约定」6 -> 7 条(新增品牌标)
- deploy/docker-compose.yml 只改头注释里的项目名(compose 的 name 与七牛对象前缀都没动)
2026-10-10 16:49:04 +08:00

33 KiB
Raw Blame History

验收测试清单

版本:V1.0 | 日期:2026-09-28 读者:测试工程师、开发(自测)、项目经理(验收) 配套:《04-核心模块设计说明.md》附录 B(基线)、《05-开发任务拆解.md》(里程碑验收) 核心原则:能用真实数据断言的不靠人眼;能自动化的一次写、长期跑


一、测试策略

1.1 四层结构

┌─────────────────────────────────────────────┐
│  L4 验收测试(人工)  ← §6 里程碑验收签署表      │
│     客户在场、用真实数据、不可自动化             │
├─────────────────────────────────────────────┤
│  L3 端到端(半自动)  ← §3 主流程用例            │
│     真机 + 真实数据,脚本辅助准备与核对          │
├─────────────────────────────────────────────┤
│  L2 集成测试(自动)  ← §4 功能验收清单          │
│     接口级,含边界与异常路径                    │
├─────────────────────────────────────────────┤
│  L1 单元测试(自动)  ← §2 Golden Assertions    │
│     引擎级纯函数,**用 112 个真实文件做断言**     │
└─────────────────────────────────────────────┘

1.2 各层覆盖目标

层 覆盖对象 目标 执行时机
L1 M1 命名引擎、M2 压缩引擎、M5 校验规则 行覆盖 ≥ 80%,Golden 断言 100% 通过 每次提交(CI 门禁)
L2 全部 117 个接口 接口覆盖 100%,异常路径 ≥ 60% 每日构建
L3 学生填报、班主任审核、管理员导出 3 条主流程 × 平台差异 每周 / 里程碑
L4 里程碑交付物 按 §6 签署表 里程碑节点

1.3 Golden Test Set(唯一权威测试集)

项 内容
路径 D:\项目\202609机电填报系统\24中技数控1班\
规模 28 人 × 4 类材料 = 112 个文件,原始 475 MB
配套名单 考生线下报名信息导入模板(24中技数车1班班).xlsx(表头第 6 行)
参照模板 官方空白模板(表头第 1 行),21 字段
特点 不是构造数据——含真实的乱命名、双扩展名、证件号错误、串档、低分辨率

⚠️ 这个数据集不可替代。所有"实测"结论都来自它;换成构造数据,双扩展名 56/112 这种数字就复现不出来。 必须纳入版本管理(或至少保持只读副本),否则半年后无法回归。


二、Golden Assertions(L1 · 可自动化)

以下断言直接从 P4 附录 B 的基线转写。任何改动后必须全绿。

2.1 命名规范化(M1)

# G-01 命名违规总数
assert count_files(failing_naming_rule) == 59        # 59 / 112 = 52.7%
# G-02 双扩展名
assert count_files(lambda f: re.search(r'\.(jpg|jpeg|png){2,}$', f.name, re.I)) == 56
# G-03 规范化幂等性(关键!)
for f in all_files:
    once  = normalize(f.name, roster, material_def)
    twice = normalize(once.standard_name, roster, material_def)
    assert twice.standard_name == once.standard_name
    assert twice.issues == []                        # 第二次不应产生任何 issue
# G-04 双扩展名修复正确性
sample = "曾伊豪-361029200907295110身份证.jpg.jpg"
assert normalize(sample).standard_name == "曾伊豪-361029200907295110-身份证.jpg"
# G-05 材料别名归一
assert normalize("...个人申报表.jpg", slot="WORK_PROOF").material_name == "工作证明"
# G-06 证件号抽取:不许暴力凑合法号
assert normalize("张三-111111111111111111.jpg").idcard_found == False   # 非法号不得被"修好"
# G-07 无名册时不猜姓名
assert normalize("乱名.jpg", roster=None).standard_name.startswith("未知姓名") is False  # 用占位而非臆测

2.2 压缩引擎(M2)

# G-08 体积上限:全部达标(D1 决策,无例外)
for f in a4_scans:                                   # 28 张工作证明
    out = compress(f, profile="P3")
    assert out.final_size_kb <= 1024, f"{f.name} 超标 {out.final_size_kb}KB"
# G-09 质量未受损:均值与最大值
sizes = [compress(f, profile="P3").final_size_kb for f in a4_scans]
assert 700 <= mean(sizes) <= 900                     # 实测均值 806KB
assert max(sizes) <= 900                             # 实测最大 831KB
# G-10 零强制压缩
assert count(out.forced == True for out in results) == 0
# G-11 证件照规格化
for f in photo_files:                                # 实测 984×1378 的 27 张
    out = compress(f, profile="P1")
    assert (out.width, out.height) == (295, 413)
    assert out.dpi == 300
# G-12 截图不缩放、保持 PNG
out = compress(school_roll_png, profile="P4")        # 实测 1025×590 PNG
assert (out.width, out.height) == (1025, 590)
assert out.format == "PNG"
# G-13 不放大(关键!)
out = compress(small_idcard, profile="P2")           # 实测 558×745
assert out.width <= 558 and out.height <= 745
assert out.issues_contains("VAL-20")                 # 体积下限保护
# G-14 EXIF 方向正确(不拉扁)
out = compress(rotated_photo, profile="P2")
assert out.width > out.height  # 或按原图判断,绝不允许宽高互换

2.2b 文件归属(M1 · 归属校验)

# G-14b 归属:证件号 = 名册值 + 姓名错别字 → 不打扰学生
f = "扬晨-360825200904303215-一寸照片.jpg"       # 名册为「杨晨」
r = normalize(f, roster=yangchen, material_def=photo1)
assert r.standard_name == "杨晨-360825200904303215-一寸照片.jpg"
assert r.student_facing_issues == []            # ★ 不产生面向学生的 issue
assert r.logonly(NAME_DIFFERS)                   # 仅留痕

# G-14c 归属:乱名无号码 → 零提示(正常路径)
r = normalize("微信图片_20260928103015.jpg", roster=yangchen, material_def=photo1)
assert r.standard_name == "杨晨-360825200904303215-一寸照片.jpg"
assert r.issues == []                            # ★ 零提示
assert r.ownership.verdict == "NO_IDCARD_IN_NAME"

# G-14d 归属:指向名册中的另一个人 → 阻断(最关键)
f = "李四-360122200902044232-工作证明.jpg"        # 该号码在名册里能查到「李四」
r = normalize(f, roster=zhangsan, material_def=work_proof)
assert r.ownership.verdict == "BELONGS_TO_OTHER"
assert r.ownership.block_submit == True          # ★ 阻断
assert r.ownership.other_roster_name == "李四"
assert r.ownership.can_force == False            # ★ 没有"仍然继续"

# G-14e 归属:陌生号 + OCR 佐证 → 不阻断
r = normalize("张三-111111111111111111-身份证.jpg", roster=zhangsan,
              ocr_idcard=zhangsan.id_card_no)
assert r.ownership.verdict == "UNKNOWN_IDCARD"
assert r.ownership.block_submit == False         # OCR 佐证后放行
assert r.issues_contains("VAL-02", severity=1)

# G-14f 归属:陌生号 + 无 OCR → 阻断
r = normalize("张三-111111111111111111-身份证.jpg", roster=zhangsan, ocr_idcard=None)
assert r.ownership.block_submit == True

# G-14g 校验位失败 → 自动改,不阻断(实测 2 例:于豪、杨晨)
r = normalize("于豪-360103200805166169-身份证.jpg.jpg", roster=yuhuo, material_def=idcard)
assert r.ownership.verdict == "CHECKSUM_INVALID"
assert r.ownership.block_submit == False
assert r.standard_name == "于豪-360103200805186619-身份证.jpg"

# G-14h ★ 回归:绝不把"别人的材料"静默洗白
#   这是本设计最重要的一条断言——它防止的是数据污染,不是体验问题
for f in all_files:
    r = normalize(f, roster=current_student, material_def=md)
    if r.ownership.verdict == "BELONGS_TO_OTHER":
        assert r.standard_name != "{当前学生名}-{当前学生证件号}-..." or r.block_submit
        # 即:凡判为他人材料,必须阻断,不得生成一份"名字正确"的输出

2.3 材料识别(M3)

# G-15 文件名层命中率
assert detect_by_name_accuracy(all_files) == 1.0     # 112/112
# G-16 形态层分流派准确率
assert detect_by_shape_accuracy(all_files) >= 0.99   # 实测 99.1%
# G-17 零文字判据
assert detect(photo1).material_code == "PHOTO1"
assert detect(photo1).reason == "整图文字量 < 5 字"
# G-18 通用词禁入表(关键!)
assert "身份证号" not in STRONG_FEATURES
for f in all_files:
    # 不得因为文件里出现"身份证号"四字就判成 IDCARD
    assert not (contains(f, "身份证号") and detect(f).material_code == "IDCARD"
                and detect(f).source == "NAME" and detect(f).confidence >= 0.9)
# G-19 识别失败必须放行
assert detect(black_image).material_code is None
assert can_submit(submission_with(black_image)) == True   # 不阻断!
# G-20 VLM 触发率在预算内
assert 0.02 <= vlm_trigger_rate(all_files) <= 0.10

2.4 校验与串档(M5)

# G-21 证件号异常总数
issues = run_validation(all_submissions)
assert count(issues, rule="VAL-02") + count(issues, rule="VAL-01") == 5
# G-22 自动/人工分流
assert count(issues, rule="VAL-02", auto_fixed=True) == 3
assert count(issues, rule="VAL-02", auto_fixed=False) == 2
# G-23 疑似串档(最关键的一条)
assert count(issues, rule="VAL-19") == 1
s = the_cross_file_issue
assert s.need_human == True and s.auto_fixed == False   # 绝不自动改
# G-24 跨文件一致性不误报
assert count(issues, rule="VAL-04") == 0             # 除串档外无其他不一致
# G-25 低分辨率检出
assert count(issues, rule="VAL-08") == 1
assert issues_for_rule("VAL-08")[0].dimension == "558×745"

2.5 导出(M6)

# G-26 导出总量
result = export(task_id, scope={only_approved: False})
assert 30 <= result.size_mb <= 45                    # 实测 ≈37MB
# G-27 目录树结构
assert result.tree["24中技数控1班"]["工作证明"][0].name \
       == "曾伊豪-361029200907295110-工作证明.jpg"
# G-28 汇总 Excel 列对齐
xlsx = result.summary_excel
assert xlsx.header == OFFICIAL_21_COLUMNS            # A–U 严格对齐
# G-29 中文文件名编码
with unzip(result.zip) as z:
    for name in z.namelist():
        assert name.encode('utf-8').decode('utf-8') == name   # 无乱码

2.6 断言的"红灯"处理规则

情况 处理
G-01~G-10 任一失败 阻断发布。这是产品核心承诺
G-11~G-14 失败 阻断发布。直接影响认定机构接收
G-15~G-20 失败 视偏差幅度:>5% 阻断;≤5% 记录并评估
G-21~G-25 失败 阻断发布。漏掉串档会流出错误数据
G-26~G-29 失败 阻断发布。导出包是最终交付物
基线数字本身需调整 必须走变更评审,在 P4 附录 B 留版本记录

三、端到端主流程用例(L3)

3.1 主流程 A · 学校收集(学生 → 班主任 → 管理员)

步 操作 期望 对应原型
A1 学生扫码进小程序 进入门禁页 ①
A2 输入名单上的姓名 + 证件号 通过,显示姓名与班级 ②
A3 刻意输错一位证件号 统一提示"没有找到你的信息",不透露哪项错 ③
A4 点"申请加入名单" 已取消(2026-10 需求收敛:学生端不再有自助申请入口;门禁失败只提示核对 + "请联系老师",且不展示任何联系方式) —
A5 正确登录后进填报页 18 个字段已预填,标注"老师已填,请核对" ④
A6 修改"联系电话" 可改,changedFields 记录 phone ④
A7 尝试改"身份证号" 只读,点击提示"如有误请联系老师" ④
A8 选"微信聊天记录"传工作证明(双扩展名乱名) 上传成功,结果卡显示命名前后对比 ⑤⑦
A9 查看结果卡 显示标准名、体积 17.5MB→512KB、VAL-06 信息级"格式已转换" ⑦
A10 传"学籍表"时误传工作证明 提示"这张图看起来是工作证明,要移过去吗?",默认不移 ⑧
A11 点"移到工作证明" 文件移动,学籍表槽位变空 ⑧
A12 故意不传必传材料直接提交 被拦,列出缺哪项,给出"去上传" —
A13 补齐后提交 成功,状态"等待李老师初审" ⑨
A14 班主任打开待审列表 该生卡片为绿色(无问题) ②
A15 右滑该卡片 通过,卡片移出列表,显示"已通过" ②
A16 另一个学生有红色问题 卡片灰色/不可滑,点击进详情 ②③
A17 尝试对该生执行"一键通过" 被跳过并回报原因"存在红色问题" ④
A18 驳回该生(选"材料不清晰") 需二次确认;提交后学生收到通知 ⑤
A19 学生查看消息 消息中心有"你的材料被退回" ⑩
A20 学生重新上传后提交 状态回到"待初审" —
A21 管理员终审 通过后状态"已通过" ⑥
A22 管理员导出 前置检查通过 → 打包 → 下载 zip ⑦
A23 Windows 资源管理器解压 中文名不乱码,目录树正确 —

3.2 主流程 B · 社会收集(不做)

⚠️ 本节作废(2026-10 取消二期,见 PRD「一期范围声明」):collectMode=2 传入直接返回 40044。保留描述仅供追溯,不要再按它验收。

步 操作 期望
B1 用社会收集任务重跑 A1~A13 提交后状态直接是"待终审"
B2 班主任打开待审列表 看不到该任务(社会收集不经班主任)
B3 管理员终审 → 导出 正常

3.3 主流程 C · 历史数据整理器 —— 已取消(2026-10)

步 操作 期望
C1 上传 24中技数控1班.rar(436MB) 解析出 28 人 4 类
C2 查看体检报告 命名违规 59、体积超标 31、证件号异常 5、疑似串档 1
C3 读报告结论 "90 个系统全自动处理,只有 1 个必须你亲自判断"
C4 下载规范化包 475MB→37MB,目录树正确
C5 点"导入为正式任务" 生成任务 + 名册 + 112 个文件全挂上
C6 进该任务看数据 真实、无占位、可直接导出

3.4 异常与边界用例(23 条)

# 场景 期望
E1 门禁连错 11 次 第 11 次返回 40071,提示稍后再试
E2 同一 IP 20 人同时门禁 全部通过(NAT 不误伤)
E3 并发 100 请求抢同一名册项 恰好 1 个成功,99 个 40072
E4 门禁失败后查库 无明文证件号,只有 hash
E5 学籍表 4 项材料证件号均与名册不符 标 VAL-19 疑似串档,不自动改
E6 学生提交后撤回,再改再提交 正常;驳回次数不因撤回增加
E7 任务暂停中 学生看到"已暂停",已提交的仍可继续审核
E8 任务截止后 学生不能提交,已提交的可审
E9 归档有未审单 拒绝,返回 40023
E10 导出前有 1 个超标文件 导出按钮禁用,指出"张三的工作证明 1.8M"
E11 已导出后撤回审核 拒绝,返回 40033
E12 表单版本冲突(两人同时改) 40025 + 返回最新 schema
E13 删除内置字段 40026,提示可改标签/顺序
E14 报考范围未覆盖全员 40024 + 返回差集
E15 名字含罕见字(生僻字) 不误判、不阻断
E16 上传 8000×10000 超大图 触发熔断转异步,不超时
E17 OCR 服务不可用 降级为人工填写,不阻断
E18 VLM 服务不可用 取前三层结果或放行
E19 微信订阅消息额度耗尽 静默降级,学生端无异常提示
E20 网络中断后重进 草稿保留,续填
E21 申请提交后管理员未处理,任务截止 申请自动置为已失效,学生收到提示
E22 两人同时申请同一证件号 已取消(2026-10:加入申请模块删除)
E23 管理员在审批页停留很久后点通过 已取消(2026-10)
E24 学生传了同学的材料后点提交 被阻断,提示文件名对应的是谁,无"仍然继续"
E25 学生把文件名改成自己的名字后重传,内容仍是同学的 证件号/OCR 仍会暴露 → 阻断或转人工(改名绕不过去)
E26 归属问题在异步 OCR 后才判定可放行 自动解除阻断 + 推送通知;阶段二只解除、不新增阻断
E27 文件名形如 微信图片_2026....jpg(无姓名无号码) 按名册补全,零提示(这是最常见的情况)

四、功能验收清单(L2 · 按模块)

编号规则:F-{模块}-{序号}。每条必须有明确的可观测结果。

F-100 租户与组织(FR-100)

# 验收项 期望
F-100-1 开通租户 生成 tenantCode,同时创建首个管理员,返回初始密码
F-100-2 组织树三层 学校/院系/班级,ancestorPath 自动维护
F-100-3 删除被占用组织 拒绝,返回占用清单
F-100-4 租户隔离 A 租户 token 访问 B 租户数据 → 40301
F-100-5 用量统计 学生数/存储/OCR/短信四项,超限告警

F-200 账号与角色(FR-200)

# 验收项 期望
F-200-1 PC 登录 连续失败 2 次后出现图形验证码
F-200-2 账号锁定 5 次失败锁定,提示剩余时间
F-200-3 小程序登录 code2session 成功,绑定手机号
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)

# 验收项 期望
F-300-1 任务 CRUD 与状态机 草稿→发布→暂停→恢复→截止→归档
F-300-2 暂停 ≠ 截止 暂停可恢复、审核继续;截止不可逆
F-300-3 发布前置校验 5 项 任一不通过阻断,force 无法绕过
F-300-4 任务克隆 名册复制但清空绑定与提交状态
F-300-5 二维码/短链 带租户+任务+班级参数,截止后扫码提示
F-300-6 社会收集审核流 不做(2026-10 取消二期;传 collectMode=2 返回 40044)

F-400 动态表单(FR-400)

# 验收项 期望
F-400-1 21 个内置字段 可改标签、可调顺序、不可删除
F-400-2 扩展字段 存 extras_json,不参与统计
F-400-3 条件显示 三条级联全部生效
F-400-4 表单版本化 改后旧单不受影响,formVersion 各自保存
F-400-5 版本冲突 40025 + 返回最新 schema
F-400-6 学生三态字段 锁定/可改留痕/正常 三种表现正确

F-500 名册(FR-500)

# 验收项 期望
F-500-1 表头自动探测 第 1 行与第 6 行两份真实文件都识别正确
F-500-2 表头候选回传 多候选时返回并让用户确认
F-500-3 无法定位表头 40028 + 返回前 10 行预览
F-500-4 体检报告 证件号校验位/重复/必填缺失三项检出
F-500-5 dryRun 模式 只体检不落库
F-500-6 追加导入去重 按证件号合并,新人立即可填
F-500-7 改名册证件号留痕 操作日志可查改前改后(脱敏)
F-500-8 已绑定名册项不可删 40029
F-500-9 门禁匹配 正确通过 / 错误统一提示 / 形近字确认
F-500-10 门禁限流 openid 10 次/小时;IP 20 次/分钟
F-500-11 并发绑定 100 并发抢同一项,恰好 1 个成功
F-500-12 失败不留明文 查库确认只有 hash
F-500-13 已绑定提示 40072,这一条明确提示

F-509 名单加入申请(客户确认新增) —— 已撤销(2026-10 需求收敛)

⚠️ 本节 8 条验收全部作废:加入申请通道(含 40037/40038/40039/40040/40042) 连同模块、表、接口、学生端页面一并删除。 替代口径:门禁失败页只有「重新填写」+ 一句"请联系老师把你加入名单", 名单外的人由老师在任务的「名单管理」里手工加人;系统内不展示任何联系方式。

F-600 材料与规则(FR-600)

# 验收项 期望
F-600-1 命名模板配置 变量可拖拽,支持自定义连接符与大小写
F-600-2 命名引擎 双扩展名/非法字符/缺分隔符/别名归一 全部修复
F-600-3 幂等性 跑两次结果相同、第二次无 issue
F-600-4 证件号三源裁决 5 种组合按裁决表执行
F-600-5 NEED_HUMAN 不自动改 两票对一票场景保持原值
F-600-6 格式白名单 非白名单自动转换
F-600-7 体积上下限 超限压缩;过小提示"可能不是原图"
F-600-8 五档压缩 P1 固定尺寸 / P4 不缩放 各自正确
F-600-9 材料识别四层 各层命中率达标
F-600-10 冲突提示 只提示不自动移位置
F-600-11 散图分拣 唯一允许建议落位的场景
F-600-12 来源引导语 学生端可见自定义文案
F-600-13 无拍照入口 只有聊天记录/相册/手机文件三种来源
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)
F-600-16 归属校验:乱名无号码 微信图片_xxx.jpg → 按名册补全,零提示
F-600-17 归属校验:姓名错别字 证件号正确 + 姓名形近 → 按名册输出,学生端无任何提示,仅服务端留痕
F-600-18 归属校验:陌生号 + OCR 佐证 OCR = 名册 → 自动放行,信息级
F-600-19 归属校验:校验位失败 校验位失败 → 自动按名册,信息级,不阻断
F-600-20 姓名不做独立规则 构造"证件号正确但姓名完全不同"的文件 → 仍按名册输出,不阻断(归属由证件号定)

F-800 审核(FR-800)

# 验收项 期望
F-800-1 待办分级 红/黄/绿三色,batchApprovable 服务端判定
F-800-2 只右滑通过 左滑不绑定驳回
F-800-3 驳回需二次确认 不能误触
F-800-4 驳回原因快选 10 条字典 + R999 手填
F-800-5 批量通过只带绿项 红黄被跳过并回报原因
F-800-6 红色问题不可通过 40032
F-800-7 审核时间轴 含提交/初审/终审/撤回/代传
F-800-8 撤回 未导出可撤;已导出 40033
F-800-9 代传材料留痕 source=4,学生端可见
F-800-10 驳回超上限升级 达 3 次通知管理员
F-800-11 变更字段高亮 学生改过的预填字段在详情高亮

F-900 导出(FR-900)

# 验收项 期望
F-900-1 前置检查 有不合格文件 → canExport=false → 按钮禁用
F-900-2 blocking 定位 精确到"谁的哪一项"
F-900-3 目录树预览 导出前可见最终形态
F-900-4 打包总量 ≈37MB,压缩率 ≈92%
F-900-5 中文文件名 Windows 解压不乱码
F-900-6 汇总 Excel 严格对齐官方 21 列
F-900-7 分卷 ≤50MB,不切分单个文件
F-900-8 快照一致性 打包中数据变化不影响产物
F-900-9 断点续传 失败后从断点继续
F-900-10 过期清理 7 天自动清理,重导不重复计费

F-1000 通知(FR-1000)

# 验收项 期望
F-1000-1 订阅消息额度登记 各节点索取授权并记录
F-1000-2 降级链 订阅→公众号→短信→站内信,静默
F-1000-3 自动催报 截止前 7/3/1 天 + 当日,仅催未提交者
F-1000-4 催报去重 excludeRecentHours=24 防骚扰
F-1000-5 模板变量 白名单外的变量渲染为空并告警
F-1000-6 消息中心 学生端 + 教师端可读可标记

F-1200 / F-1300 / F-1400

# 验收项 期望
F-1200-1 字典管理 6 个内置字典,有下级项的不可停用
F-1200-2 职业字典 只做拼写校验,不强约束(Excel 模板本就无下拉)
F-1200-3 操作日志 9 类敏感操作全留痕,证件号脱敏存储
F-1200-4 查看完整身份证号 单独接口、单独留痕 VIEW_IDCARD_FULL
F-1300-1 用量与套餐 四项用量 + 超限告警
F-1400-1 整理器上传解析 已取消(2026-10:模块删除)
F-1400-2 体检报告数字 59/31/5/1 全部命中
F-1400-3 报告结论 autoHandled 90 / needManual 1
F-1400-4 结果导入为任务 名册 + 112 文件一次成型

五、交互与视觉验收(对照原型)

原型是视觉与交互的唯一基准。实现与原型不一致时,以原型为准(除非原型本身有错,走变更)。

原型 屏数 必须一致的要素
student-app.html 10 门禁失败页文案(不透露哪项错)、三态字段样式、结果卡命名前后对比、无拍照入口
teacher-app.html 9 卡片红黄绿三色、只能右滑、驳回二次确认、按钮 44px 触达区
admin-web.html 10 表格信息密度、目录树预览、设计器内置字段锁标记、角色与授权矩阵
index.html — 三端入口

视觉验收要点

# 项 标准
V1 移动端触达区 主要按钮高度 ≥ 44px
V2 色彩语义 红=阻断 / 黄=警告 / 绿=通过,全局一致
V3 无 emoji 全部用内联 SVG 图标,1.7px 描边
V4 零外部依赖 断网可打开(原型约束,生产可放宽)
V5 数字口径一致 教师端 28 = 15+4+3+1+5,完成率 82% 是已提交率
V6 体积表述 475MB → 37MB,压缩率 92%(不得出现 22.4MB)

六、性能与安全测试

6.1 性能

# 场景 指标 说明
P1 门禁匹配(5000 人名册) < 50ms 走 id_card_hash 索引
P2 单文件处理(A4 扫描件) < 3s 实测 PIL 约 2s,libvips 更快
P3 批量处理 4 个文件 < 8s 建议走异步
P4 导出 112 文件 < 60s —
P5 导出 5000 文件 < 15min 分卷 + 流式
P6 门禁并发 100 全部响应 < 1s,恰好 1 个绑定成功 必测
P7 上传并发 200 无失败,p99 < 2s —
P8 任务统计查询(10 万行) < 500ms 走正式列索引
P9 页面首屏(小程序) < 1.5s —

6.2 安全

# 项 验证方法
S1 跨租户越权 用 A 租户 token 请求 B 租户资源,全部 40301
S2 门禁防枚举 20 个不同证件号试同一 IP → 触发异常告警并拉黑
S3 失败不泄露 抓包对比"姓名错"与"号码错"的响应,字节级一致
S4 失败不留明文 查库 + 查日志,无明文证件号
S5 脱敏覆盖 遍历所有响应,确认 idCardNo 从不出现,只有 idCardMask
S6 签名 URL 过期 过期后返回 40302
S7 幂等键重放 同 key 重复提交返回首次结果,不产生脏数据
S8 上传文件类型伪造 改扩展名的可执行文件被拒
S9 路径穿越 文件名含 ../ 被清理
S10 开放接口加签 篡改 body / 过期 timestamp / 重放 nonce 全部拒绝
S11 SQL 注入 全参数化,渗透测试
S12 越权访问他人填报单 学生 A 访问学生 B 的 submissionId → 403

七、里程碑验收签署表(L4)

每个里程碑结束时由项目经理 + 技术负责人 + 客户代表三方签署。

里程碑 签署项 交付物 签署
M0 登录/组织/用户可用,跨租户拦截 内网演示 ☐
M1 能配出完整任务;内置字段不可删 演示 + 截图 ☐
M2 真实名单导入正确;门禁并发测试通过 并发压测报告 ☐
M3 Golden Assertions G-01~G-14 全绿 回归报告 ☐
M4 G-15~G-25 全绿;识别报告 112 文件识别报告 ☐
M5 三端审核闭环;一键通过跳过红项 真机录屏 ☐
M6 G-26~G-29 全绿;Windows 解压验证 导出包样本 ☐
M7 性能 P1P9 达标;安全 S1S12 通过 压测 + 渗透报告 ☐

上线前必须全部满足(Go/No-Go)

# 条件 不满足的后果
1 全部 29 条 Golden Assertions 通过 No-Go
2 性能 P1、P2、P6 达标 No-Go(P6 是安全底线)
3 安全 S3、S4、S5、S11 通过 No-Go
4 小程序主体资质获批 No-Go(无法上线)
5 认定机构书面接受 300dpi No-Go(材料可能被拒收)
6 真实数据跑通一次完整导出并被客户确认 No-Go
7 回滚预案就绪(能退回上一版本) 可延期但需记录

八、测试环境要求

项 要求
数据库 PostgreSQL 16(DDL 已实测)
图像库 支持 300dpi DPI 写入、EXIF 方向读取
测试数据 Golden Test Set 只读副本,纳入版本管理
真机 iOS + Android 各 ≥ 1 台;Windows 用于验证解压不乱码
并发工具 支持 100 并发的压测工具
字符集 文件名测试必须覆盖中文 + 双扩展名 + 全角字符

易被忽略的三个测试盲区

# 盲区 为什么容易漏
1 Windows 解压中文名 开发用 Mac/Linux 测试一切正常,交付到客户才炸
2 并发绑定的临界窗口 单机测试永远不复现,只有压测才暴露
3 「无」是合法值 测试数据里没人填"无",上线后真实数据一填就错

变更记录

版本 日期 变更
V1.1 2026-09-28 客户确认后修订:修正「文件归属」设计缺陷。原设计把"文件名姓名/证件号与名册不符"处理为"自动按名册修正"(信息级),会把传错的文件静默洗白。修正为:
① 新增 7 条归属断言 G-14b~G-14h(Golden 29 → 36);② 判定优先级改为 证件号反查名册 > OCR 内容 > 姓名(仅辅助);③ 新增 VAL-21 文件归属校验(阻断,且无"仍然继续");④ 姓名不再作为独立规则(客户确认);⑤ 新增 7 条功能验收 F-600-14~20(104 → 111)与 4 条异常边界 E24~E27(23 → 27)
修订后总计:36 条断言 / 111 条功能验收 / 27 条异常边界 / 6 条视觉 / 9 项性能 / 12 项安全
V1.0 2026-09-28 首版。29 条 Golden Assertions(可直接写成自动化)、3 条端到端主流程(32 步)、23 条异常边界、104 条功能验收项(含 F-509 共 19 条)、6 条视觉验收、9 项性能、12 项安全;含里程碑签署表与 7 条 Go/No-Go 条件

实现说明(2026-09-28):本文件中的 40007b / 40007c 已规范化为 40071 / 40072。 原因:code 按 §1.3 约定是数字,带字母后缀会在 JSON 里变成字符串,破坏前端 code === 0 的判断。