Skip to content

2026-09-13 迭代 — 验收用例(TC) ​

依据 PRD 原文 与 技术方案。PRD 定义产品行为,技术方案定义实现契约; 本文只把两者转成可重复执行、可留证据的判定条件,不另行扩大范围。 本文与 PRD、技术方案一同评审,评审通过后方可作为本期验收依据。

任务追踪:GitHub 父 Issue · 组织 Project

2026-09-12 离线预跑已完成,结果见 §8 与预跑报告。 三项硬阈值(N01—N03)已实测取数,采样量与计时口径随报告归档; 控制台(nexo-user-console)尚未部署,涉及其 UI 的用例已降级为 API 级验证并逐条标注。

1. 执行规则 ​

  • 编号含义:P = 端到端主路径,B = 边界与安全,N = 非功能硬阈值,MTG = 上会用例子集。 编号一经发布不得复用或重排,后续任务的「测试追踪」按本编号引用。
  • 会上只执行 §3 的 9 条,每条判定时间不超过 3 分钟;其余用例在 2026-09-12 预跑阶段完成。
  • 一条用例的全部判定条件均满足才算通过;部分满足、无证据或只能口头说明都记为不通过。
  • 因环境、资产或评审结论缺失而无法执行时记为阻塞,不允许把“未测”写成“通过”。
  • 涉及时间的用例以服务端时间为准;毫秒级阈值(N01)用服务端毫秒时间戳差计算,不用人工秒表。
  • 允许在验收库直改 users.created_at 或用缩短 TTL 的验收配置模拟长周期,但必须同时证明生产默认值仍正确。
  • 三项硬阈值必须实测取数,不得估算;数据由预跑阶段采集(已完成,见 §8.4),会上只复核证据,不现场重测。
  • 管理接口按“合法 Bearer JWT 即放行”验收,同时把全仓检索无 ADMIN_USERNAMES、无 requireAdmin(代码审查证据) 作为 P05、P07 的附加判定;本期不做任何伪鉴权。

1.1 本期冻结的验收基线 ​

项目验收值
激活凭据ACTIVATION_TICKET_TTL_HOURS=24,单次核销(used_at),重发即作废旧凭据
重置凭据RESET_TICKET_TTL_MINUTES=60
访问令牌AUTH_ACCESS_TOKEN_TTL_SECONDS=180;SDK 静默刷新阈值 60s,两者同批发布,不得单独上线任一侧
吊销集合保留时长复用 nexo-im-api/src/services/revocations.ts 的 TTL 语义(由 ACCESS_TOKEN_MAX_TTL_MS 派生);本文件不另立常量
停用阻断通知发出到 WebSocket 关闭 ≤150ms;后续接口 401 阻断 ≤1ms;关闭码 4001,reason「账号已停用」
IM 按需拉取本地 LRU 缓存 TTL 5 分钟;GET /internal/users?ids=... 响应 P99 ≤50ms
批量导入单批 ≤500 行、文件 ≤2MB;Dry-Run 校验 ≤1s;入库单事务 ≤1s
内部通道鉴权HMAC-SHA256;头 X-Nexo-Timestamp / X-Nexo-Nonce / X-Nexo-Signature;时间窗 ±60s;nonce 去重防重放
停用通知POST /internal/users/{userId}/disabled,载荷仅 { userId, status: 'DISABLED' }
密码材料浏览器派生 H1(PBKDF2,32 字节 base64url)→ 服务端 H2 scrypt,存储前缀 scrypt-h1-v1;明文口令不出浏览器
账号状态PENDING / ACTIVE / DISABLED 三态,users.status 为唯一事实源,credentials 无 status 列

2. 环境前置 ​

  • nexo-account(由 nexo-auth 原地演进)与 nexo-im-api 已按发布计划部署,反向代理显式拒绝 /internal/ 前缀, 服务间 HMAC 与 JWKS 验签可用;控制台 nexo-user-console 可访问,桌面壳 nexo-app 侧边栏已挂载入口。
  • 数据库:nexo_account(本地验收 localhost:5433)与 nexo_im(本地验收 localhost:5432)可只读查询; 迁移前的 nexo_auth 快照可用于 B07。
  • 账号矩阵:1 个管理员账号(持合法 Bearer JWT)、1 个员工账号(ACTIVE)、若干临时账号(建号/激活/批量导入用例自建)。 员工账号在 IM 桌面端与网页端各保持至少一条在线连接。
  • 工具:psql(只读断言)、服务端结构化日志检索、浏览器 DevTools(网络面板 / HAR / 存储)、 WebSocket 帧查看(关闭码与 reason)、CSV 编辑器、并发与压测脚本(并发核销、批量拉取采样)。
  • 时间与配置:可修改验收库的 created_at、可替换缩短 TTL 的验收配置,并能读取生产默认配置值。
  • 密码派生:浏览器端 H1 由 /activate 页面自行派生,验收只需检查提交载荷;不得在用例中要求员工明文口令离开浏览器。

3. 会上用例子集(固定 9 条) ​

编号覆盖用例操作摘要判定阈值
MTG-01P01控制台录入 username + real_name,复制激活链接单事务写入三表;status=PENDING;链接 24h 有效;界面零明文密码
MTG-02P02打开激活链接设密并登录载荷为 H1;used_at/新哈希/ACTIVE 同事务生效;再访问链接失效
MTG-03P03对超时账号触发停用后重新生成激活链接超时账号单条 SQL 转 DISABLED;重发单事务回 PENDING 且旧链接作废
MTG-04P04冷启动 IM 后打开会话/检索成员首次按需拉取、5 分钟内命中缓存;无全量轮询;返回字段不含凭据(阈值见 N02)
MTG-05P05停用弹窗姓名不全等 → 补全后确认姓名不全等时前端禁用且后端拒绝;确认后单事务停用 + 吊销 Session + 发通知
MTG-06P06停用瞬间观察在线端与后续请求全部连接以 4001/「账号已停用」关闭(阈值见 N01);客户端回登录页;后续 API 401
MTG-07P07对停用账号点击【重新启用】并登录status=ACTIVE;旧 Session 不复活;登录与 IM 立即恢复
MTG-08P08上传错误 CSV 预检 → 修正入库 → 导出链接错误行逐行标红且零入库;合法批次单事务全量写入;导出链接可核销
MTG-09B06对 /internal/* 发无签名 / 错签名 / 重放 / 超窗请求四类请求全部 401;日志不含签名、密钥与报文

N01—N03 三项硬阈值在会上只复核预跑证据,不现场重测;证据缺失时该条记阻塞。

4. 主路径用例(P01—P08) ​

P01 管理员单人建号与生成激活链接 ​

  • 前置条件:管理员已登录控制台并持有合法 Bearer JWT;目标 username 未被占用;可只读查询 nexo_account 三表。
  • 操作步骤:① 在「新建用户」抽屉填写 username、real_name(nickname 留空)并提交;② 记录响应中的 activationUrl; ③ 只读查询 users / credentials / account_tickets;④ 用同一 username 再次提交。
  • 预期结果:响应成功并返回 { id, username, activationUrl },链接形如 https://<auth-domain>/activate?code=<高熵随机码>; 三表记录同时存在且互相外键一致,users.status='PENDING',credentials.password_hash 以 scrypt-h1-v1: 开头, account_tickets.purpose='activation'、used_at IS NULL、expires_at - created_at 为 24 小时(±1s); 管理员界面全程无明文密码字段;重复 username 返回冲突错误且三表零新增(原子回滚)。
  • 证据采集:请求/响应 JSON;三表 psql 断言输出(password_hash 只留前缀、token_hash 只留前 8 位); 激活链接截图(code 打码);重复提交后的三表计数对比。
  • 对应:PRD §10-1 · FR-2 / FR-5 · 上会 MTG-01

P02 员工自助设密激活链路 ​

  • 前置条件:存在 PENDING 账号与 24h 内有效激活链接;/activate 页面可访问;IM 已就绪。
  • 操作步骤:① 浏览器打开激活链接,确认页面展示用户名;② 输入新密码并提交,用 DevTools 检查提交载荷; ③ 跳转登录页后用 username + 新密码登录;④ 再次访问同一激活链接。
  • 预期结果:提交载荷为浏览器派生的 H1(32 字节 base64url),明文口令不出现在请求体、URL 与日志中; account_tickets.used_at 写入、credentials.password_hash 更新为新哈希、users.status='ACTIVE' 三者在同一事务内可见; 登录成功并签发 Token,IM 可正常收发消息;再次访问链接提示已失效/已使用且不产生第二次核销。
  • 证据采集:HAR / 请求响应(脱敏);激活前后三表断言;登录响应;二次访问页面截图。
  • 对应:PRD §10-2 · FR-3 / FR-5 · 上会 MTG-02

P03 超时未激活自动停用与重新生成激活链接 ​

  • 前置条件:存在 PENDING 账号,且该账号从未激活过(用户名下不存在 used_at IS NOT NULL 的 activation ticket, 属超时自动停用,判定口径见 lamolabs/nexo-auth#55);验收库可修改 created_at (或使用缩短 ACTIVATION_TICKET_TTL_HOURS 的验收配置);超时扫描入口可手动触发。
  • 操作步骤:① 触发超时扫描并记录日志;② 只读断言该账号状态与凭据,并确认无 used_at IS NOT NULL 的 activation ticket; ③ 控制台点击【重新生成激活链接】;④ 用新链接完成激活。
  • 预期结果:超时账号由单条原子 SQL 转为 DISABLED(等价于 WHERE status='PENDING' AND created_at < now() - interval '24 hours'), 无中间态;因该账号从未激活过,重新生成被放行:单事务内 users.status 回到 PENDING、签发新的 24h 激活凭据、 旧链接立即失效;新链接可完成激活并转为 ACTIVE。若账号曾经激活过(存在 used_at IS NOT NULL 的 activation ticket), 则属管理员主动停用,必须拒绝重发并提示先【重新启用】,反例见 B04。
  • 证据采集:超时任务日志;扫描前后 SQL 断言(含 used_at 判定查询输出);旧链接与新链接的访问结果截图; 生产默认 ACTIVATION_TICKET_TTL_HOURS=24 的配置读取记录。
  • 对应:PRD §10-3 · FR-5 / FR-9 · 与 B04 互为反例 · 上会 MTG-03

P04 IM 产生关联时按需拉取资料 ​

  • 前置条件:账号 A 为 ACTIVE 且 IM 在线;两端配置同一 AUTH_INTERNAL_SHARED_SECRET;IM 缓存命中情况可观测。
  • 操作步骤:① 冷启动 IM 后,客户端打开与 A 的会话或检索通讯录;② 观察 IM → nexo-account 的内部调用; ③ 5 分钟内再次触发同一批资料读取;④ 检索 nexo-im-api 源码与运行日志中的周期任务。
  • 预期结果:首次未命中缓存时发起 GET /internal/users?ids=... 批量拉取,返回字段仅 id, username, real_name, nickname, avatar_url, status,不含任何凭据字段;5 分钟内重复读取命中本地缓存且不再发起请求; 代码中已无 startDirectorySync() 等 5 分钟轮询与 24 小时刷新逻辑,日志中无周期性全量同步; IM 不接收任何资料推送(唯一入站通知是停用状态通知)。响应耗时判定见 N02。
  • 证据采集:内部调用日志(含批大小与耗时);代理/抓包记录;源码检索结果截图;缓存命中日志。
  • 对应:PRD §10-4 · FR-10 · DEBT-2 · 上会 MTG-04

P05 防误触停用账号 ​

  • 前置条件:账号 B 为 ACTIVE 且至少一端在线;管理员登录控制台;可只读查询 users / sessions。
  • 操作步骤:① 点击【停用账号】弹出高危确认框;② 输入不完整或错误的姓名并尝试确认; ③ 输入真实姓名全称后确认;④ 断言状态、Session 与 IM 收到的通知。
  • 预期结果:姓名不全等时确认按钮保持禁用;确认后单事务内 users.status='DISABLED' 且该用户全部活跃 sessions.revoked_at 非空; 事务提交后向 IM 发送 POST /internal/users/{userId}/disabled,载荷仅 { userId, status: 'DISABLED' },不含姓名、头像等资料; 全仓检索无 ADMIN_USERNAMES、无 requireAdmin(合法 Bearer JWT 即放行)。
  • 证据采集:确认框截图(禁用态与可点态);停用前后 SQL 断言;IM 侧收到的通知报文(脱敏);通知发送日志;免伪鉴权代码检索结果。
  • 对应:PRD §10-5 · FR-4 / FR-8 / FR-11 · 上会 MTG-05

P06 账号停用即时断连 ​

  • 前置条件:B 在 IM 桌面端与网页端各保持至少一条在线连接;可获取服务端毫秒时间戳与 WebSocket 关闭帧。
  • 操作步骤:① 管理员对 B 执行停用;② 记录 IM 收到通知的服务端时间戳与每条连接的关闭帧时间戳; ③ 观察客户端提示与登录态;④ 用 B 的旧 Access Token 调用 IM REST 接口。
  • 预期结果:全部在线连接以关闭码 4001、reason「账号已停用」关闭,通知到关闭的间隔满足 N01; 客户端退回登录页并禁用自动重连;后续 API 请求返回 401;已吊销会话进入资源服务本地吊销集合,未过期 Access Token 最迟 180s 自然失效。
  • 证据采集:服务端毫秒时间戳日志(同一请求串联);WebSocket 关闭帧(code / reason);客户端录屏;401 响应。
  • 对应:PRD §10-6 · FR-4 / FR-10 · BUG-1 · 上会 MTG-06

P07 重新启用账号无冲突 ​

  • 前置条件:账号 B 处于 DISABLED(由 P05 / P06 产生),其原密码与已吊销 Session 均已记录。
  • 操作步骤:① 控制台点击【重新启用】;② B 用原密码登录并收发消息;③ 用停用前签发的 refresh token 尝试续期; ④ 断言状态与 IM 缓存;⑤ 全仓检索确认无 ADMIN_USERNAMES、无 requireAdmin(与 P05 同一附加判定)。
  • 预期结果:单事务将 users.status 置回 ACTIVE,无需重置密码即可登录并正常使用 IM; 停用时吊销的旧 Session 不复活,旧 refresh token 仍被拒绝;IM 侧该用户状态刷新为 ACTIVE,无残留停用缓存; 重新启用接口按“合法 Bearer JWT 即放行”验收,全仓检索无 ADMIN_USERNAMES、无 requireAdmin。
  • 证据采集:SQL 断言;登录与 IM 收发录屏;旧 refresh token 的拒绝响应;IM 缓存状态日志;免伪鉴权代码检索结果。
  • 对应:PRD §10-7 · FR-4 · 上会 MTG-07

P08 批量导入与导出激活链接 ​

  • 前置条件:控制台 CSV 导入入口可用;准备两份文件——含重复 username、缺 real_name 的错误文件,以及修正后的合法文件(≤500 行、≤2MB)。
  • 操作步骤:① 上传错误文件执行 Dry-Run 预检;② 修正后重新上传并确认入库;③ 一键导出全员激活链接 CSV; ④ 只读断言三表计数与逐行内容。
  • 预期结果:预检返回逐行报告(行号 + 原因),错误行可定位,且预检阶段零入库; 合法批次在单个事务内写入,逐行生成 users(PENDING)+ credentials + 24h account_tickets,行数与文件一致; 导出 CSV 含 username / real_name / activationUrl,抽样链接可完成激活;超过 500 行或 2MB 的文件被拒绝。
  • 证据采集:预检报告截图/JSON;导入前后三表计数断言;导出文件(code 打码);抽样激活结果;超限文件被拒响应。
  • 对应:PRD §10-8 · FR-7 · 上会 MTG-08

5. 边界与安全用例(B01—B07) ​

B01 并发核销同一激活码(只允许一次成功) ​

  • 前置条件:一个 PENDING 账号 + 一个 24h 内有效激活链接;可发起并发请求(脚本 / xargs -P / 压测工具)。
  • 操作步骤:① 用同一 code 并发提交 N 次(建议 N≥10)POST /activate,每次携带不同的新密码; ② 统计成功与失败次数;③ 断言 account_tickets 与 users;④ 用唯一成功请求的密码登录。
  • 预期结果:恰好 1 次成功,其余全部返回 4xx(已使用/已失效);account_tickets.used_at 只写入一次; users.status='ACTIVE';生效的密码哈希对应唯一成功请求;不存在两次改密都成功或半更新状态。
  • 证据采集:并发请求结果统计(成功/失败计数与响应码);used_at 与 status 断言;成功请求的密码登录结果。
  • 对应:FR-5 · BUG-2 · 安全准入

B02 超时边界 23h 不停用 / 25h 停用 ​

  • 前置条件:两个 PENDING 账号,created_at 分别置为 now() - 23h 与 now() - 25h;可读取生产默认 TTL 配置。
  • 操作步骤:① 触发超时扫描;② 断言两账号状态;③ 分别用两个账号的激活链接访问 /activate。
  • 预期结果:23h 账号仍为 PENDING 且链接仍可核销;25h 账号转为 DISABLED 且原链接不可用; 生产默认 ACTIVATION_TICKET_TTL_HOURS=24 未被验收配置覆盖或篡改。
  • 证据采集:扫描前后 SQL 断言;配置读取记录;两条链接的访问结果。
  • 对应:FR-5 · 时间边界

B03 停用全名校验绕过尝试(前端禁用绕过后端仍拒) ​

  • 前置条件:ACTIVE 账号;管理员 Bearer JWT;可抓取并改写停用请求。
  • 操作步骤:① 前端不输入或输入部分姓名,记录确认按钮状态;② 绕过前端直接构造停用请求,分别省略姓名、 提交空串、提交部分姓名与提交错字;③ 提交完全一致的全名作为对照;④ 断言账号状态、Session 与 IM 通知计数。
  • 预期结果:所有姓名不匹配的请求均被后端拒绝(4xx),账号保持 ACTIVE、Session 未被吊销、IM 未收到停用通知; 仅全名完全一致时放行;不得只依赖前端禁用。首尾空格与全半角等规范化处理以实际实现为准,实测结论记入预跑报告。
  • 证据采集:四类绕过请求的响应;状态未变的 SQL 断言;IM 通知计数为 0;确认按钮禁用态截图。
  • 对应:FR-8 · 安全准入

B04 管理员主动停用的账号不可通过【重新生成激活链接】复活 ​

  • 前置条件:账号 C 曾经激活过(用户名下存在 used_at IS NOT NULL 的 activation ticket),处于管理员主动停用后的 DISABLED(判定口径见 lamolabs/nexo-auth#55;与 P03 的超时自动停用互为反例)。
  • 操作步骤:① 只读确认 C 存在 used_at IS NOT NULL 的 activation ticket;② 控制台对 C 尝试【重新生成激活链接】与 【发起密码重置】;③ 若返回链接,尝试用该链接完成激活或设密;④ 直接调用凭据签发接口绕过前端;⑤ 尝试用 C 登录。
  • 预期结果:DISABLED 账号不提供激活或重置链接,接口拒绝并提示先【重新启用】;即使绕过前端直接调用接口也必须拒绝(fail-closed); 任何路径都不得让 C 的 status 变为 ACTIVE 或登录成功;恢复只能通过显式的【重新启用】。
  • 证据采集:used_at 判定查询输出;接口响应(含拒绝提示文案);SQL 断言(状态仍为 DISABLED);登录尝试结果。
  • 对应:FR-4 / FR-9 · 与 P03 互为反例 · 安全准入

B05 批量导入中途失败整批回滚 ​

  • 前置条件:合法 CSV(例如 50 行)且预检可通过;可构造“预检通过但入库第 N 行失败”的条件 (例如提交前由另一会话插入同 username 账号,触发唯一约束冲突)。
  • 操作步骤:① 构造冲突后提交批量入库;② 观察接口响应;③ 只读断言三表计数与逐行内容。
  • 预期结果:整批事务回滚,除预置冲突账号外 users / credentials / account_tickets 零新增; 接口返回失败并指出失败行;控制台不展示部分成功;不产生半入库脏数据。
  • 证据采集:接口响应;三表前后计数断言;服务端事务错误日志。
  • 对应:FR-7 · BUG-2

B06 /internal/* HMAC 无签名 / 错签名 / 重放 ​

  • 前置条件:AUTH_INTERNAL_SHARED_SECRET 已配置;可用脚本按签名串(method + 含 query 的 path + timestamp + nonce + body)生成签名。
  • 操作步骤:① 对 GET /internal/users?ids=... 与 POST /internal/users/{id}/disabled 分别发送不带 X-Nexo-Timestamp / X-Nexo-Nonce / X-Nexo-Signature 的请求;② 发送签名被篡改一位的请求; ③ 用正确签名在 ±60s 窗口内原样重放同一 nonce;④ 用超出时间窗(如 now - 120s)的时间戳配合正确签名请求。
  • 预期结果:四类请求全部返回 401(内部签名校验失败语义),且不产生任何业务副作用(不泄露资料、不踢线); 仅签名正确且未使用过的请求通过;重放被 nonce 去重拒绝;超窗被拒;拒绝日志只含原因分类,不含签名、密钥与报文。
  • 证据采集:四组请求/响应;服务端拒绝日志(确认无敏感内容);正常签名请求作为对照。
  • 对应:FR-10 · 安全准入 · 上会 MTG-09

B07 迁移回滚后登录仍可用 ​

  • 前置条件:迁移前的 nexo_auth 库快照(含既有账号、devices、clients、活跃 sessions);迁移脚本与配套回滚脚本。
  • 操作步骤:① 在快照上执行迁移(先迁移表结构、再改库名为 nexo_account); ② 断言迁移结果并登录;③ 执行回滚脚本还原;④ 回滚后再次登录。
  • 预期结果:迁移后既有账号 status 全为 ACTIVE、密码哈希完整迁移到 credentials、users 不再有 password_hash 列、 devices / clients / 既有活跃会话未受影响;回滚后 schema 与数据回到迁移前状态(users.password_hash 逐行一致、 三表行数一致),登录仍可用;回滚脚本可重复执行或有明确的一次性约束说明。
  • 证据采集:迁移与回滚执行日志;迁移前后 schema 对比(\d users 等);行数与哈希校验断言;两次登录响应。
  • 对应:FR-1 / FR-2 / FR-3 · BUG-3 · 发布门禁

6. 非功能硬阈值用例(N01—N03,09-12 预跑已实测) ​

本节三条必须实测取数,不得估算;09-12 预跑已完成,实测值、采样量与计时口径见 §8.4 与预跑报告 §3。采集脚本与原始输出随证据归档 (evidence/scripts/、evidence/logs/)。

N01 停用断连 ≤150ms ​

  • 前置条件:目标账号在 IM 保持多条在线连接;服务端日志带毫秒时间戳与可串联的请求标识。
  • 采集方法:停用一次,按同一请求串联 nexo-account 发出停用通知的时间戳与 IM 执行 closeUser / 连接关闭帧的时间戳; 拆成「通知发出 → IM 收到」与「IM 收到 → 连接关闭」两段分别记录;采样 ≥20 次(含单账号多连接场景),报告 P50 / P95 与最大值。
  • 判定:端到端(通知发出 → 连接关闭)每次 ≤150ms,且后续接口 401 阻断 ≤1ms。 实测:端到端上界最大 12ms(段 B 精确值最大 1ms),401 阻断服务端最大 1ms — 达标(21 轮采样,详见 §8.4)。
  • 证据采集:带毫秒时间戳的服务端日志;WebSocket 关闭帧记录;采样明细表。
  • 对应:PRD §9 · FR-10 · BUG-1 · 上会 MTG-06 复核证据

N02 批量拉取 P99 ≤50ms ​

  • 前置条件:GET /internal/users?ids=... 可被合法签名调用;可分别构造缓存未命中(冷)与命中(热)两种场景。
  • 采集方法:对冷、热两组各采样 ≥200 次,分别统计 IM 侧内部调用耗时与 nexo-account 端点耗时,报告 P50 / P95 / P99 与最大值; 同时记录批大小与缓存命中率。
  • 判定:nexo-account 端点 P99 ≤50ms(冷热两组分别报告)。 实测:热组 P99 7.81ms、冷组 P99 11.31ms、满批 200 P99 14.00ms — 达标(各组 ≥200 次采样,详见 §8.4)。
  • 证据采集:压测脚本与原始输出;服务端耗时日志;缓存命中统计。
  • 对应:PRD §9 · FR-10 · 上会 MTG-04 复核证据

N03 500 人批量导入事务 ≤1s ​

  • 前置条件:可生成 500 行合法 CSV;可采集服务端耗时(排除前端渲染与网络)。
  • 采集方法:分别计时 Dry-Run 校验与入库事务的服务端耗时,各重复 ≥5 次,报告中位数与最大值; 并断言 500 行全部写入(PENDING + 各一条 credentials 与 24h account_tickets)。
  • 判定:Dry-Run 校验 ≤1s 且入库单事务 ≤1s。 实测:Dry-Run 最大 47.6ms、入库事务(服务端 txMs)最大 126ms — 达标(5 轮 × 500 行)。 同一批次的请求端到端耗时约 11.5s,差异来自实现在事务外预计算 500 份 scrypt 凭据 — 事务指标达标,但控制台用户实际等待时间应按 11.5s 计。
  • 证据采集:计时脚本与原始输出;导入前后三表计数断言;服务端耗时日志。
  • 对应:PRD §9 · FR-7 · 上会 MTG-08 复核证据

7. 需求追踪矩阵 ​

需求覆盖用例
FR-1 单服务单库真原子架构P01、B07
FR-2 极简单一用户模型(三态单一事实源)P01、P03、B07
FR-3 密码物理分表隔离P01、P02、B07
FR-4 账号停用与重新启用P05、P06、P07、B04
FR-5 轻量开户与 24h 激活设密P01、P02、P03、B01、B02
FR-6 用户管理控制台与用户列表P05、P07、P08
FR-7 批量导入与 Dry-Run 预检P08、B05、N03
FR-8 停用防误触全名核验P05、B03
FR-9 凭据重置与激活链接管理P03、B04
FR-10 IM 按需拉取与停用切断P04、P06、B06、N01、N02
FR-11 管理端点免伪鉴权放行P05、P07(附加判定:无 ADMIN_USERNAMES / requireAdmin)
DEBT-2 移除 5 分钟全量轮询P04
BUG-1 停用未及时掐断 WebSocketP06、N01
BUG-2 跨服务半状态脏数据P01、P02、B01、B05
BUG-3 账号缺少停用状态P03、B07
非功能与安全准入B01、B03、B04、B06、N01—N03

8. 验收记录 ​

预跑状态:2026-09-12 离线预跑已执行完毕,结果如实填在下表。 汇总报告:tc-evidence-preflight.md;逐用例原始证据: evidence/scripts/(采集脚本)、evidence/logs/(原始输出 JSON)。

归档约定(沿用 2026-08-30 做法):

  • 截图、HAR、日志片段、SQL 输出、压测原始数据等非页面资产放 products/nexo-im/iterations/2026-09-13/evidence/<用例编号>/,不进入文档站导航 (本迭代证据为脚本 + JSON,统一放在 evidence/scripts/ 与 evidence/logs/);
  • 汇总报告为同目录 tc-evidence-preflight.md;
  • 本节表格只填真实采集到的结果,缺失即记「阻塞」或「未执行」,无任何估算值。

UI 补跑(2026-09-12 晚):控制台 nexo-user-console 已部署到测试环境 (https://nexo-user-console-test.snailuu.cn),离线预跑中「阻塞于控制台部署」的 UI 判定已在真实浏览器中补跑完毕,截图与夹具归档在 evidence/ui-2026-09-12/。 补跑推翻了预跑对 B04 的结论(详见下表与 nexo-auth#76)。 补跑后测试库已按精确用户名清理夹具,回到 5 账号全 ACTIVE(users=5、credentials=5、account_tickets=0)。

8.1 会上记录 ​

会上 9 条只复核预跑证据,不现场重测;本节在 09-13 会上执行后回填。

编号结果(通过 / 不通过 / 阻塞)实际耗时证据链接备注
MTG-01未执行(会上进行)—预跑 P01对应 P01,09-12 真机复跑通过(含三表断言与重复用户名原子回滚)
MTG-02未执行(会上进行)—预跑 P02对应 P02,预跑通过
MTG-03未执行(会上进行)—预跑 P03对应 P03,09-12 真机复跑通过(超时自动停用 → 重发拉回 PENDING、旧链接作废)
MTG-04未执行(会上进行)—预跑 P04对应 P04;阈值证据见 §8.4 N02
MTG-05未执行(会上进行)—预跑 P05对应 P05,09-12 真机复跑通过(四档防误触 + 单事务停用 + 通知投递)
MTG-06未执行(会上进行)—预跑 P06对应 P06;09-12 真机复跑通过,客户端表现已补齐(见 §8.3.2);阈值证据见 §8.4 N01
MTG-07未执行(会上进行)—预跑 P07对应 P07,09-12 真机复跑通过(旧会话不复活,重新登录后新签发)
MTG-08未执行(会上进行)—预跑 P08对应 P08,09-12 真机复跑通过(逐行标红 → 入库 → 导出);阈值证据见 §8.4 N03
MTG-09未执行(会上进行)—预跑 B06对应 B06,预跑通过

8.2 离线预跑汇总 ​

分组用例范围通过数不通过数阻塞数报告链接
主路径P01—P08800tc-evidence-preflight
边界与安全B01—B07601(B07)tc-evidence-preflight
非功能硬阈值N01—N03300tc-evidence-preflight §3

上表为 09-12 UI 补跑后的最终计数。预跑时降级为 API 级的 4 条(P05、P08、B03、P07) UI 判定已补齐;P08 由「阻塞」转「通过」。

B04 的三次结论:预跑「通过(API 级)」→ 09-12 UI 补跑「不通过」(换成无激活票据历史的 账号后结论反转,属安全准入项失败)→ 修复(nexo-auth#77) 合入 dev 并部署到测试环境后复测通过。当前计数为复测后的口径。

8.3 逐用例记录 ​

主路径 P01—P08 ​

编号结果实测要点证据未验证部分
P01通过201 + {id, username, activationUrl};activate?code=<43 位 base64url>;三表各一行、status=PENDING、password_hash 前缀 scrypt-h1-v1:、expires_at-created_at=86400s;重复 username 409 且三表计数不变logs/p01.json + evidence/ui-2026-09-12/P01-*.png— (09-12 补跑:新建页仅 3 个 text 输入,input[type=password] 计数为 0,"密码"二字只出现在说明文案里;建号后出现激活链接面板 + 一键复制 + 24h 时限提醒)
P02通过提交载荷为 43 位 base64url H1;单次快照 ACTIVE|t|t(同事务可见);新口令登录成功且令牌 TTL=180s;再访问链接 3006logs/p02.json无(/activate 由认证中心托管,含真实页面)
P03通过真实定时任务(容器重启触发首轮 tick,日志 超时未激活账号已停用 count=2);25h 账号转 DISABLED;重发 201 且旧凭据作废(未核销 activation 仅剩 1 枚);新链接激活成功logs/p03-b02-expiry.json + evidence/ui-2026-09-12/P03-01-regenerate-link.png— (09-12 补跑:PENDING 账号点【重新生成激活链接】→ 二次确认 → 新链接展示,票据总数 2、未核销 1,旧链接已作废)
P04通过冷启动首次读取恰好 1 次 GET /internal/users?ids=;5 分钟内重复读取 0 次内部调用;返回字段仅 id/username/realName/nickname/avatarUrl/status;源码无 startDirectorySync、src/routes/users 下无 setIntervallogs/p04.jsonIM 客户端缓存命中日志(服务端侧已用认证中心调用计数证明)
P05通过停用 200 + DISABLED;会话吊销后 0 条活跃;IM 收到通知;无令牌请求 401;全仓检索无 ADMIN_USERNAMES、无 requireAdminlogs/p05-b03.json + evidence/ui-2026-09-12/P05-*.png— (09-12 补跑:空/「用户」/「用户 五」/「用户5」四档确认按钮均 disabled=true,仅「用户五」全等时点亮;确认后 DISABLED 且活跃会话 0)
P06通过21 轮全部以 4001 / 「账号已停用」关闭;单账号 3 连接轮全部关闭;旧令牌调 IM REST 一律 401logs/n01-p06.json客户端"退回登录页并禁用自动重连"录屏(IM 前端未部署)
P07通过启用 200 + ACTIVE;停用前那 1 条会话在启用后仍为吊销态(未复活);旧 refresh token 在停用后与启用后均 401;原密码即可重新登录(TTL 180s);IM /conversations 200logs/p07.json + evidence/ui-2026-09-12/P07-*.png— (09-12 补跑:用户列表操作列出现【重新启用】→ 二次确认弹窗明示「停用期间被吊销的登录态不会恢复」→ 确认后状态回 ACTIVE)
P08通过API 级全部满足:Dry-Run 逐行报告(issues[].line + errors[])、预览零入库、合法批单事务写入 500 行、导出 CSV 头 username,real_name,activation_url(带 BOM 与 CRLF,行数与批次一致,抽样链接可核销)、501 行 3005 拒绝logs/n03-p08-b05.json + evidence/ui-2026-09-12/P08-*.png— (09-12 补跑:错误行红字 rgb(200,58,74) + 浅红底 rgb(252,235,237),逐行给物理行号与原因「真实姓名不能为空 / 用户名与第 3 行重复 / 用户名已被占用」,确认按钮 disabled=true;501 行文件被前端就地拒绝且未上传;合法批 3 行入库后三表 5/5/0 → 8/8/3 全 PENDING、票据 TTL 恰为 86400s;导出 CSV 带 BOM+CRLF、表头 username,real_name,activation_url,抽样链接打开托管设密页正常)
8.3.1 B04 修复复测(2026-09-12) ​

nexo-auth#77 合入 dev(491faad)并部署到测试环境 (镜像 sha-d013a330,迁移 0007 已生效、users.disabled_reason 列存在)后,按同一路径复测:

步骤结果
控制台停用 user5(种子账号,无任何票据历史),输全名确认status='DISABLED'、disabled_reason='ADMIN'、未核销票据 0 条
点【重新生成激活链接】→【确认生成新链接】被拒,页面显示「该账号已被管理员停用,请先重新启用后再重发凭据」;status 仍 DISABLED、disabled_reason 仍 ADMIN、票据仍 0 条
把 disabled_reason 改成 'TIMEOUT' 后重试(验证放行路径未被误伤)放行,拉回 PENDING、签发 24h 新链接、disabled_reason 清为 null
还原为 DISABLED/ADMIN 后走控制台【重新启用】status='ACTIVE'、disabled_reason 清为 null

证据:evidence/ui-2026-09-12/B04-03-fixed-rejected.png、B04-04-enable-clears-reason.png。

复测后测试库回到 5 账号全 ACTIVE、disabled_reason 全 null、account_tickets 0 条。

8.3.2 2026-09-12 真机复跑(网页端 + 桌面端) ​

控制台部署到测试环境、桌面端按 dev HEAD 2988b1c 重新构建安装后,对会上 9 条子集做了一轮真机复跑。 与 §8.3.1 的 B04 复测同一环境(认证中心镜像 sha-d48296c,迁移 0007 已生效)。

网页端(浏览器驱动控制台,5 条全过)

编号实测
MTG-01三表各 +1 且外键一致、PENDING、expires_at-created_at=86400s、password_hash 前缀 scrypt-h1-v1:、激活码 43 位;页面 input[type=password] 计数 0;重复 username 就地报「该用户名已被占用」、未进成功面板、三表零新增
MTG-03改 created_at 至 25h 前 → 定时任务 超时未激活账号已停用 count=1,转 DISABLED/TIMEOUT;重发拉回 PENDING 且 disabled_reason 清空;旧票置为已核销、新票未核销 TTL 86400
MTG-05空 / 「用户」/「用户 五」/「用户5」四档确认按钮全禁用,仅全名全等点亮;确认后 DISABLED/ADMIN、活跃会话 0(sessionsRevoked=1);停用通知投递成功 attempts=1,「账号已停用」→「投递成功」间隔 3ms
MTG-07回 ACTIVE、disabled_reason 清空;旧会话保持吊销未复活
MTG-08错误行红字 rgb(200,58,74) + 浅红底 rgb(252,235,237),逐行给物理行号与原因;未通过时【确认导入】禁用;预检阶段三表零变化;合法批 3 行入库后 6/6/2 → 9/9/5 全 PENDING;导出 CSV 带 BOM+CRLF、表头 username,real_name,activation_url

桌面端(macOS 原生窗口驱动)

MTG-06 补齐了预跑中缺失的客户端表现那一半(预跑只验到服务端侧,见 §9.4):

12:05:55.642  认证中心  账号已停用  sessionsRevoked=61
12:05:55.646  IM 收到   POST /internal/users/{id}/disabled
12:05:55.647  IM 处理   收到账号停用通知 closed=1 → 200 (1ms)
12:05:55.664  WS 关闭   code=4001  lastDevice=true
判定项阈值实测
停用通知 → 连接关闭≤150ms端到端 22ms(段 A 4ms、段 B 1ms)
关闭码4001✅
活跃会话归零—61 → 0
客户端退回登录页—✅ 立即
禁用自动重连—✅ 20 秒内零重连、零 401 重试

重新登录后 12:08:10.464 登录成功 → 12:08:10.605 授权码兑换成功 → 12:08:12.184 IM 连接已建立; user1 活跃会话 1(新签发)、已吊销 91,停用期间吊销的旧会话未随重新登录复活(P07 的附加确认)。

顺带闭合的门禁:访问令牌 TTL 与 SDK 刷新阈值四方一致 —— 认证中心运行时 AUTH_ACCESS_TOKEN_TTL_SECONDS=180,桌面壳 / IM / 控制台的 @lamolabs/nexo-web-sdk 均为 0.5.1(60s 刷新阈值)。

T21 附带验证:新构建的桌面壳侧边栏挂载「消息」+「用户管理」两个子应用, 点击切换成功加载控制台 #/roster,且壳内 iframe 的控制台顶栏同样显示真实姓名 (说明「壳下发令牌 → 子应用调 /api/users/me」这条链路成立,不只独立访问时有效)。

复跑结束后测试库已还原:5 个种子账号全 ACTIVE、disabled_reason 全 null、account_tickets 0 条。

复跑中发现的两处桌面壳缺陷(不影响本迭代判定,已单独跟踪): 账号被停用时下线提示显示「你已退出登录」(manual 文案)而非「被管理员停用」; 壳的账户菜单不显示当前登录账号。两条都是同批优化点在壳侧的遗留。

边界与安全 B01—B07 ​

编号结果实测要点证据未验证部分
B01通过12 并发恰好 1 次成功、11 次 400/3006;used_at 只写入一次;credentials 仅一行;成功者密码可登录、失败者密码一律不可登录logs/b01.json—
B02通过23h 仍 PENDING 且链接可核销;25h 转 DISABLED 且原链接 3006;生产默认 ACTIVATION_TICKET_TTL_HOURS=24 未被覆盖logs/p03-b02-expiry.json—
B03通过省略姓名 / 空串 / 部分姓名 / 错字 四类全部 4xx 且状态保持 ACTIVE;全名全等放行;首尾空格经 trim 后放行(实测结论,tc.md 原要求的规范化口径以此为准);被拒请求零通知,成功次数与通知数增量一致logs/p05-b03.json + evidence/ui-2026-09-12/P05-01/02/03-*.png— (09-12 补跑,见 P05:四档不匹配输入按钮全程禁用,前端未提供绕过入口)
B04通过(修复后复测)预跑(前置账号有 used_at IS NOT NULL 的 activation ticket):重发返回 4xx、提示含「重新启用」、0 条新凭据、状态仍 DISABLED。09-12 UI 补跑改用无票据历史的种子账号 user5:管理员输全名停用 → DISABLED、活跃会话 0,随后点【重新生成激活链接】→【确认生成新链接】返回成功并签发新 24h 链接,users.status 由 DISABLED 变为 PENDING,未核销 activation ticket 1 条(口令哈希未轮换)。判定依据疑似是「是否存在已核销的 activation ticket」,种子/存量账号没有这段历史即被当作「超时停用」放行logs/b04.json + evidence/ui-2026-09-12/B04-01-disabled-profile.png、B04-02-rejected.png— (已修复并复测通过,见 §8.3.1)
B05通过50 行批次、预检通过(49 valid)后在入库时撞唯一约束 → 409;三表计数前后一致(除预置占用账号外零新增);无半入库脏数据logs/n03-p08-b05.json—
B06通过无签名(GET+POST)/ 签名篡改一位 / 同 nonce 重放第二轮 / 超窗(now-120s)四类全部 401;同 nonce 首次 200、第二次 401;对照组合法签名 POST 确实产生 1 次业务效果;拒绝日志仅含 reason=、不含签名与密钥;公网 /internal/ 前缀 404logs/b06.json—
B07阻塞已做:迁移 SQL 7 个、回滚脚本 2 个均存在;users 表确无 password_hash 列;当前(迁移后)库登录仍可用。未做:完整迁移→回滚→再登录链路logs/b07.json缺迁移前 nexo_auth 快照 —— 测试环境的库早在 08-30 之前就已完成结构迁移,服务器上只有已迁移的测试库与正式库,无可作回滚靶子的快照。不记通过

非功能硬阈值 N01—N03 ​

编号结果采样量计时口径实测值判定证据
N01通过21 轮(20 轮单连接 + 1 轮 3 连接,共 23 条连接)段 A = 认证中心日志 POST /api/users/{id}/disable 收到 → IM 日志收到停用通知(上界);段 B = IM 收到通知 → closed=N 日志(精确值,IM 侧三步同步执行);端到端 = A+B段 B 最大 1ms(p50 1ms);端到端上界最大 12ms(p50 8ms);容器内客户端观测最大 14.7ms≤150ms 达标logs/n01-p06.json
N01通过21 次IM 日志 --> GET /conversations 401 Nms(服务端自我计时)最大 1ms(p50 1ms)≤1ms 达标logs/n01-p06.json
N02通过热 210 / 冷 210 / 批 20:200 / 批 100:200 / 批 200:120采样在 IM 容器内发起(同 docker 网络),fetch 前 → 响应体读完;服务端另有日志口径对照热 P50 3.45 / P95 6.26 / P99 7.81 / max 74.25;冷 P50 3.13 / P95 6.28 / P99 11.31 / max 16.11;批 200 P99 14.00;IM 侧 210 次会话列表读取仅产生 1 次内部拉取冷热两组 P99 均 ≤50ms,达标logs/n02.json
N03通过5 轮 × 500 行Dry-Run:客户端 dryRun=true 请求往返;入库事务:认证中心日志 批量导入完成 ... txMs=(服务端计时)Dry-Run 最大 47.6ms(p50 45.2);入库事务 txMs 最大 126ms(p50 119);三表计数 500/500/500均 ≤1s 达标logs/n03-p08-b05.json

N03 需要一并记录的偏差:同一批次请求端到端耗时约 11.5s(p50 11510.78ms), 差异来自实现把 500 次 scrypt 凭据派生刻意放在事务外(batch-import-service.ts, 单次 scrypt 约 24ms)。TC 判定项是"入库单事务",故记达标; 但控制台管理员的实际等待时间是 11.5s,会上应按后者评估交互体验。

另:500 行批次的 expires_at - created_at 实测为 86388 秒(500 张票一致), 比 24h 少约 12 秒 —— 正是上面那段事务外派生耗时。P01 的「24h ±1s」判定在 批量路径上不成立。完整分析见预跑报告 §4.1。

8.4 硬阈值取数汇总(会上复核用) ​

指标阈值实测(服务端口径)采样量结论
停用通知 → WebSocket 关闭≤150ms最大 12ms(精确段 1ms)21 轮 / 23 条连接达标
停用后接口 401 阻断≤1ms最大 1ms21 次达标
GET /internal/users?ids= P99(热)≤50ms7.81ms210 次达标
GET /internal/users?ids= P99(冷)≤50ms11.31ms210 次达标
同一端点批大小 200≤50ms14.00ms120 次达标
批量导入 Dry-Run≤1s最大 47.6ms5 轮 × 500 行达标
批量导入入库单事务≤1s最大 126ms(txMs)5 轮 × 500 行达标(请求端到端 11.5s,见上注)

8.5 最终结论 ​

  • 通过条件:9 条会上用例全部通过;离线用例零 P0/P1 不通过;三项硬阈值(N01—N03)实测达标且证据齐备。
  • 当前结论(含 09-12 UI 补跑):三项硬阈值已实测达标;主路径 P01—P08 全部通过 (预跑中降级为 API 级的 UI 判定已补齐,P08 由阻塞转通过); B01—B03、B05、B06 通过; B04 通过(09-12 补跑发现的复活路径已由 nexo-auth#77 修复并在测试环境复测通过,见 §8.3.1); B07 阻塞(缺迁移前 nexo_auth 快照,属环境前置缺失,不记通过)。
  • 不通过条件:任一安全准入项失败(B01 并发核销出现多次成功、B03 全名校验可绕过、 B04 停用账号被激活链接复活、B06 内部通道鉴权可绕过或重放成功、密码材料或凭据字段泄露), 或任一硬阈值未达成。安全准入项当前全部通过:B04 曾在 09-12 UI 补跑中失败 (管理员停用的存量账号可被激活链接复活),已由 nexo-auth#77 修复并在测试环境复测通过(§8.3.1);B01、B03、B06 与密码材料/凭据字段泄露三项一直通过。
  • 延期条件:外部权限平台(#58)相关结论缺失时,FR-11 只能按"合法 Bearer JWT 放行 + 无伪鉴权代码"验收, 不得引入临时管理员白名单;其余待定项只能由评审明确移出对应 FR,不得用"未测"替代结论。
  • 待评审确认的两项实现/部署偏差(详见预跑报告 §4、§5):
    1. 测试环境 .env 缺 AUTH_USER_DISABLED_WEBHOOK_URL,导致停用通知静默降级为 60s 轮询兜底 (部署清单层面缺口,config-matrix.md 把它标为"可选");
    2. 500 行批次的 activation ticket 实际 TTL 为 86388s(少约 12s),已开 nexo-auth#74; 第 1 项已开 nexo-auth#75。
    3. 09-12 UI 补跑新增:控制台顶栏当前登录人显示的是用户 ID(UUID)而非真实姓名; 且对管理员主动停用的账号,详情页仍提示「该账号正在等待激活」——文案与实际状态不符。