# 小树三端上线前审查(2026-10-13) ## 结论与范围 **当前版本不建议直接作为已验收版本发布。** 主体教学功能已经形成闭环,但账号安全、移动端登录、密码规则、工资结算入口与现有同步方案之间仍有明确问题。先处理 A01—A06,再进行发布验收。 依用户要求,本次只审查当前代码、本地运行与本机已有运行记录;未部署、未操作生产会员/工资/预约。学生端和老师端共用移动应用,按两个身份分别检查。代码基线为 `9224f7a` 加工作区原有两项未提交修改:`learning-page.component.ts` 和 `home-learning-modules.integration.spec.ts`;报告不是仅对 Git HEAD 的审查。 证据分为:**本地复现**、**源码确认**、**本机历史报告**、**发布前待验证**。浏览器使用隔离模拟账号与拦截接口;模拟页面通过不等于真实线上接口通过。审查覆盖关键业务和横切能力,未声称逐一人工点击全部历史迁移路由。 ## 优先处理的问题 ### A01 · P1 · 旧系统密码可以覆盖新系统已修改密码 - 证据:`scripts/deploy-admin-functions.mjs` 的 `user_login_passwd`(约 2990 行)在新密码登录失败后调用 `migrateLegacyLogin`(2881 行)。后者只要旧系统验证成功,就执行 `target.setPassword(password)`,并清除 `passwordResetRequired`,没有“仅首次迁移”条件。 - 本地复现:隔离内存中的账号已有新密码,旧密码验证成功后,新密码被替换,强制改密标记被清除;没有操作真实账号。见 `auth-reproduction.json`。 - 影响:新系统改密不能可靠切断旧密码;学生、教师与复用该入口的后台账号均需核查。前提是旧系统仍接受该账号旧密码,不代表任何人无需密码即可登录。 - 修复:只允许尚未完成密码迁移的账号进入旧验证;在一次性迁移、主动改密或管理员重置后永久关闭旧密码回退。显式维护迁移状态,并撤销应失效会话。 - 验收:首次迁移成功;迁移后新密码成功、旧密码失败;网络故障不能触发改密;停用、跨帐套与重置密码场景均拒绝不合规回退。 ### A02 · P1 · 移动端旧请求失败会清除新会话 - 证据:`projects/xiaoshu-mobile/src/app/core/api.service.ts` 的 `callCloud` 只把“是否需登录”的布尔值交给错误处理;`handleError` 收到 401 后调用 `expireSession`,未核对失败请求所属 token。 - 本地复现:A 会话发起请求 → 换成 B 会话 → A 的迟到 401 返回 → B 会话被清空,跳到 `wxauth?expired=1`。 - 影响:学生、老师操作中可能突然退出并清理本地学习草稿。后台已有 token 比较和会话复核,本问题主要在共用移动端服务。 - 修复:传递实际请求 token;仅处理与当前会话匹配的失效响应;区分网络故障与真实失效;复用后台已实现的复核策略。 - 验收:迟到 401 不影响新登录;当前会话确实失效才退出;503、断网不退出;并发失效只跳转一次。 ### A03 · P1 · 后台接受的密码,移动端可能无法登录 - 证据:后台创建/导入/重置密码支持 8—72 位;移动端 `login-page.component.ts` 的 `submitClassic` 限制 6—18 位。会员导入模板也明示 8—72 位。 - 本地复现:20 位密码在移动端被客户端直接拒绝,请求数为 0。 - 修复:统一三端与服务端密码规则,登录端不要用注册旧规则拒绝现有合法密码;调整提示与导入模板。 - 验收:后台创建的 8、18、19、72 位合法密码均能在移动端按统一规则登录,超界输入有一致提示。 ### A04 · P1 · 临时密码强制修改流程没有在移动端闭环 - 证据:后台重置写入 `passwordResetRequired=true`,移动端没有消费此标记;`pages/member/passwd` 被 `feature-policy.ts` 的表单策略阻止。 - 本地页面:进入“修改登录密码”显示“暂未开放,请联系管理员处理”。 - 影响:管理员交付的临时密码无法由学生/老师自主修改,也没有强制改密约束。与 A01 组合后风险更高。 - 修复:服务端返回并执行强制改密状态;实现当前密码验证、修改、撤销旧会话与重新登录;允许访问必要的改密/退出/帮助页面。 - 验收:临时账号先改密再使用;旧密码和旧会话失效;失败保留输入并可重试。 ### A05 · P1 · 工资批次仍被旧同步桥验收条件阻挡 - 证据:`scripts/cloud/payroll-integrity.js` 的 `payrollSettlementSyncStatus` 仍以同步桥配置、manifest、检查点及两分钟延迟为条件;`payrollMutation` 对 create/refresh/review 强制检查 `settlementReady`。`directPayrollSyncStatus` 的直接接口凭据固定 `settlementReady:false`;`scripts/cloud/legacy-direct-control.js:87` 的完成报告同样固定为 false。 - 本机最新完整同步报告:旧接口已完成、9 个数据集业务编号对账通过、失败 0,但 `settlementReady:false`。见 `sync-readonly.json`。这是已发生的历史报告,不是本次生产实时查询。 - 影响:每天 23 点同步成功并不等于工资可以生成、刷新或审核;页面仍可能提示未配置持续同步。现有提示还写着“手动同步”“每日自动同步尚未验收”,与已安装任务容易混淆。 - 修复:接入直接接口的**工资截止范围核验**:按结算月份核对预约、完课和复习凭据;分类处理已保留的新系统修改与未解决差异,保存对账版本和确认结果;基于这些证据开放结算。不能把 false 改成 true,也不能将所有 1,330 条保留差异一概当作错误或一概忽略。 - 验收:真实缺失/未解决工资冲突仍拦截;已核验范围可生成;核验后源数据变更会使确认失效;复习奖励、跨月补结和已发放金额不重复。 ### A06 · P1 · 依赖存在已知安全公告,上线前需要分级处理 - 官方 npm registry 的本次完整扫描:85 个受影响依赖条目,其中 critical 6、high 49、moderate 22、low 8。**这是依赖树条目数,含构建/测试工具和传递依赖,不是 85 个已经证实可利用的产品漏洞。** 默认 npmmirror 不支持 audit,本次已改用官方 registry;未执行 `audit fix --force`。 - 直接依赖涉及 Angular 18、`xlsx`、`html2pdf.js`、`jspdf` 等;详见 `dependency-summary.json` 的安装版本及公告链接。 - 后台会员、词库 Excel 导入确实调用 `xlsx.read`,应优先处理 SheetJS 解析链;PDF 导出库也进入实际功能。Angular 的 i18n、SSR 等公告各有触发条件,不能仅凭版本认定当前 SPA 都可利用。 - 修复:先梳理浏览器可达依赖,升级/替换已知风险导入导出库,再安排 Angular 支持版本迁移;单独处理开发工具。每组升级做对应功能回归,不自动强升整树。 - 验收:产物依赖风险有处理结果或有依据的不可达说明;Excel 导入、PDF 多页导出、路由、表单与移动端全部回归。 - 一级来源:[Angular 官方公告](https://github.com/angular/angular/security/advisories/GHSA-g93w-mfhg-p222)、[html2pdf 官方公告](https://github.com/eKoopmans/html2pdf.js/security/advisories/GHSA-w8x4-x68c-m6fc)、[SheetJS 公告](https://github.com/advisories/GHSA-4r6h-8v6p-xvw6)。扫描结果只代表审查时点。 ## 使用体验、稳定性与维护问题 | 编号 | 级别 | 问题与证据 | 建议与验收 | |---|---|---|---| | B01 | P2 | 学生首页 `home-page.component.html:97–101` 将缺失统计/余额回退为 0,统计失败只给教师设置错误状态;模拟 503 时学生仍看到一排 0。部分余额来自登录快照,未标出更新时间。 | 未取得值显示“—/暂不可用”,已有值标为缓存并可刷新;真实 0 才显示 0。模拟失败、充值后刷新、跨端变化分别验收。 | | B02 | P2 | 学生/老师课表失败时同时显示“暂无排课”和错误提示;后台排课错误状态也仍显示 0 与空泳道。见 `local-page-probes.json`。 | 首次失败、真实为空、保留缓存三种状态分开;未成功查询前不下“没有课”的结论。 | | B03 | P2 | 隐私注销入口 `pages/member/acc_destory` 被功能策略阻断;本地显示暂未开放。登录协议走站内文章,用户中心另有旧域名文档来源。 | 提供可完成的申请/管理员处理入口,明确处理状态;核对并统一协议内容及版本。本项是功能与内容一致性问题,不代替法律审查。 | | B04 | P2 | `CloudFunctionsService` 的后台 admin/operations/app 请求没有统一超时;移动端也只有特定 action 设置 20 秒超时。 | 给读写分别设有限等待与取消/核对路径;写入超时显示“结果待核对”,重试必须沿用幂等键,避免误导重复操作。用悬挂响应测试释放 loading。 | | B05 | P2 | `operations-vocabulary.component.ts:138–141` 在解析 Excel 前没有文件大小/行数限制,直接主线程读取全表;会员导入已有 5 MB/200 行限制,可作参考。 | 解析前限制大小,限制解压/行数并考虑 Worker;大文件应快速拒绝且页面仍可操作。 | | B06 | P2 | `webview-page.component.ts` 接受任意 http/https 参数并绕过资源 URL 安全检查,iframe 允许脚本、同源、弹窗及下载。没有主机允许名单。未复现跨域脚本读取,也不能据此声称存在完整 XSS。 | 收敛到 HTTPS 允许域名,按来源最小化 sandbox 权限;明确外部站点提示;测试非法协议、恶意主机、嵌套编码与新窗口。 | | B07 | P2 | 移动端 SessionService 没有后台已有的跨标签 storage 监听;退出仅清理本地会话,未见此路径调用服务端撤销会话。 | 明确多端退出策略;同浏览器标签及时更新身份,退出后旧 token 按产品约定失效;不能只隐藏界面。当前为源码判断,未做生产 token 重放。 | | B08 | P2 | 全量前端测试有 7 个失败项,旧流程/日期/缓存命名的测试未同步维护。 | 修复测试契约并稳定日期时钟;建立完整构建与测试门禁。不要删掉断言或修改业务去迎合旧测试。详见下一节。 | | B09 | P2 | 夜间任务具备失败重试和私有报告,但执行器未见主动通知实现;只有日志不足以让人及时知道凭据过期或持续失败。 | 给最终失败、连续未完成、冲突显著增长设去重告警;成功保留安静。验证人为失败可被发现、恢复后告警消除。 | | B10 | P3 | 前端未找到全局异常采集与关联请求编号链路;现有多处错误只弹短暂 toast。 | 增加脱敏错误记录、版本号、请求编号和可重试提示,避免上报密码/token/学员完整信息。仓库未发现不等于部署平台没有监控。 | | B11 | P3 | 移动登录文本输入主要用 placeholder,缺少持久 label;部分后台图标按钮/自制 drawer 缺少可访问名称与焦点管理。 | 登录表单补 label,主要触控目标约 44px;对话框聚焦、Escape、关闭后回焦;键盘与读屏抽验。这里是源码抽查,未做全站无障碍认证。 | ## 已执行的验证与通过部分 | 检查 | 本次结果 | 证明范围 | |---|---|---| | `npm run build` | 移动端、后台均成功 | 当前工作区可生成生产包;已有样式预算/CommonJS 警告未阻断构建 | | 移动端全量测试 | 197 项,192 成功、5 失败 | 未全绿 | | 后台全量测试 | 256 项,254 成功、2 失败 | 未全绿;移动端失败使组合命令中断,所以另行运行后台 | | `node --test --test-concurrency=2 scripts/tests/*.test.mjs` | 163 成功、0 失败 | 本地 SQL/网关/业务策略测试,不是生产数据库测试 | | 云 action 清单核对 | requested 116、implemented 99、blocked 21、missing 0 | 没有未分类 action;三组非互斥,不能相加成覆盖率,也不代表 21 个关闭功能已实现 | | 报告与预约详情浏览器 | 375/812 宽度及横屏编辑;保存一次、无页面异常、无横向溢出 | 模拟接口;取消、报告跳转编辑、保存路径通过 | | 三端故障状态抽查 | 375/1366 宽度;首页、课表、用户中心、后台工作台/排课/工资;无页面异常/文档横向溢出 | 模拟 503,与健康真实数据 UI 分开记录 | | 未登录路由 | 移动预约跳登录、后台工资跳登录 | 前端 guard;服务端权限另由源码/SQL测试支撑 | | 身份与越权 | 本地测试覆盖普通会员无后台权限、运营不能提权、跨帐套拒绝、停用撤销、版本冲突 | 生产角色/ACL 是否与代码一致仍需发布者确认 | | 业务事务 | 结课幂等、写入失败回滚、课时预留、重复退款、并发抢最后课时、报告文字修改、奖励防重复均有通过用例 | PGlite/隔离测试,不能替代生产恢复演练 | ### 7 个失败项的归因 1. 移动端 3 个学习分组/跳过拼写用例使用旧缓存键 `...901.`;当前代码按 `...901.unbound.` 或 bindingId 隔离。**只在临时测试副本更新前缀后,3 项全通过**,没有修改业务逻辑。 2. 移动端 2 个预约课程/单元用例仍模拟旧 `content_list`,没有提供新的 `app_lesson_courses` 契约,其中还断言“不应有课程选择框”,与用户要求显示学员全部已开通课程相反。源码证明测试过期;这两项尚未重写完整测试并复跑,不把它们认定为线上课程加载故障,也不声称已修好。 3. 后台 2 个陪练月份用例硬编码 2026-09/9月;当前默认月份为 10 月。临时副本改为 10 月后该文件 4 项全通过。最终应使用稳定时钟/依赖注入而不是继续改硬编码月份。组件模块级 `CURRENT_MONTH` 还应在长期开页跨月场景重新计算。 临时副本均已删除,原有未提交代码保持原样。全量基线仍为 7 个失败,不能用诊断副本成功代替门禁通过。 ## 分端覆盖与剩余验收 | 范围 | 审查内容 | 结论 / 尚缺证据 | |---|---|---| | 学生 | 登录、个人资料入口、课程/排课、学习分组、抗遗忘、结课报告、评价、密码/注销 | 核心本地测试可用;A01—A04、B01/B03/B07 待修。手机真实音频、断网回前台与跨设备会话待验收。 | | 老师 | 学员绑定、预约当天开课、全部已开通词库选择、多词库学习、结课反馈与修改、复习奖励 | 本地权限/课时/报告事务用例通过;A02/A04 影响会话。长课中断、锁屏恢复和真实媒体待验收。 | | 后台 | 会员导入/开课、余额划拨、排课/取消、老师可用时间、工资、报表导出、词库、管理员角色 | 本地业务事务保护较完整;A01/A03/A05/A06 是重点。大表导入、真实打印文件与生产角色映射需后续验证。 | | 同步与数据 | 旧接口执行器、依赖顺序、原子主附表、差异保留、断点、业务编号对账、删除标记与阅读身份冲突 | 直接接口本地测试通过;已有完成报告。保留差异不是失败,但需按工资/课时影响分类处理。未重新同步真实数据。 | | 性能 | 生产初始包、懒加载、PDF/Excel按需导入、故障渲染 | 移动初始约 410.17 kB/估算传输112.89 kB;后台367.21/102.76 kB。仅体积,未得出端到端速度结论。需真数据/网络条件下测可交互 p75。 | | 安全与隐私 | 密码迁移、会话、权限、导入库、外链、隐私入口、日志与凭据 | 已确认 A01—A04;其余区分源码风险与实际利用。未输出执行凭据,审查文件仅模拟账号和汇总。 | | 发布与维护 | 构建、测试、依赖、打包、同步任务、回滚和备份证据 | 后台有 release prepare/verify;未找到对应移动发布打包和仓库CI流程。数据库备份/恢复、TLS/CORS/安全头属于发布环境待核验,不认定线上缺失。 | ## 本机 23 点同步现状(只读) - LaunchAgent `cn.xiaoshu.legacy-direct-sync-nightly` 已加载;北京时间 23:00,RunAtLoad 开启,失败后 KeepAlive 重试、ThrottleInterval 300 秒。 - 运行器及两个依赖文件与当前仓库内容一致;凭据文件权限 0600;最近记录 exit 0、runs 2。白天 state=not running 是正常的,不表示任务未安装。 - 2026-10-12 完整报告:新增业务记录 53、更新 181、保留冲突 1,330、失败 0,9 类编号对账完成;次日 00:22 的 `already_completed` 是防重复,不是新一轮同步。 - 本次读取的另一个 `ready` 是就绪检查,不是已同步。不能把它当当天同步成功。 - Mac 作为运行主机已获用户确认。仍需开机并登录该用户;关机、用户退出等条件下不能保证 23 点执行。安装记录/一次完成不等于以后每晚定时触发已验证,下一次自然触发应核对 businessDate 和 finish 回执。 - `sync-worker:nightly` 已走直接接口;package.json 中 `sync-worker:once/start` 仍指旧桥 worker,容易误用,应明确标记历史用途或收敛命令。 ## 修复顺序与发布交接 1. **先修账号链路**:A01—A04 同一组处理,覆盖旧密码关闭、新密码统一、首次改密、迟到响应与跨标签登录。用隔离账号测试,保留迁移兼容但禁止覆盖已更新密码。 2. **闭合工资核验**:A05,使用现有直接接口成功凭据与月份截止核验,不回到同步桥,不绕过差异与财务事务。 3. **处理依赖与可靠性**:A06、B01—B09;先真实可达导入库、错误零值、超时、告警,再扩展到统一体验。 4. **稳定验收门禁**:更新过期测试并使全量通过;固定时间、明确 fixture 契约;在隔离数据上完成学生—老师—后台全链路。 5. **用户自行发布前核对**:前端 commit/hash、后端版本、SQL迁移版本一致;配置学生/老师端及后台域名、HTTPS、API CORS、SPA回退、缓存策略;备份并实际恢复一次;准备前端回滚包与后端兼容回滚方案。 6. **发布后验收由发布者执行**:用隔离学生/老师/管理员验证登录→开课→排课→开始→多词库学习→结课→报告修改→学生评价→工资核对;再验 23 点自然触发、断网、iOS Safari/微信/Android、音频播放、PDF下载。不得用真实客户记录制造验收扣费或发薪。 ### 可用的发布验收清单 - [ ] A01—A06 已关闭或有明确范围的风险处置结论。 - [ ] 两端生产构建、前端全量、服务端本地测试全通过。 - [ ] 真实环境的角色/帐套、禁用用户、错误身份、重复提交、并发版本冲突已验证。 - [ ] 工资核验与旧接口同步凭据连通,保留差异已按影响处理。 - [ ] 发布产物与源码版本可追溯,移动端与后台不会误用旧 dist 目录。 - [ ] 备份、恢复、回滚、安全头、上传限制、登录限流和告警由发布者确认。 - [ ] 真机核心教学流程与下一次 23 点自然触发通过。 ## 证据与复现 - `verification-summary.json`:构建、测试、清单、报告UI摘要。 - `auth-reproduction.json`:三项登录问题及注销入口的隔离复现。 - `dependency-summary.json`:官方 registry 扫描摘要,含直接依赖公告。 - `local-page-probes.json`:各身份/视口的模拟故障页面文本与溢出/错误检查。 - `sync-readonly.json`:任务配置、脚本一致性与已有对账报告的脱敏汇总。 - 两张 `*-home-unavailable.png`:本地故障态截图(模拟账号)。 - `reproduce-auth.cjs`、`probe-local-pages.cjs`:从仓库根目录运行,需 Chrome 和 Playwright;可通过 `PLAYWRIGHT_MODULE` 指向本机模块位置。它们只针对本地4200/4300,不应改成生产地址运行。 本次交付为审查报告和本地证据;未修改上述业务缺陷,未部署。原有测试未全通过,因此不把报告描述为“上线验收通过”。