legacy-enrollment-schedule-2026-11-07.md 4.5 KB

历史单词开课在排课中缺失的修复与发布

北京时间 2026-11-07 00:06,生产应用 7pIbDBJmKx_main 已应用独立数据库补丁 scripts/sql/legacy-enrollment-reconciliation.sql。

原因与范围

课程管理的 businessRows('course-bindings') 展示历史开课记录,而排课的 enrollmentBindings 要求 enrollmentStatus='active'、有效主记录与有效内容。历史迁移和课程映射确认只在执行当时补齐分类/状态:学生后续重新启用不会重新激活待确认开课,直接旧库同步新增的开课也没有补齐新系统分类/状态。删除再开课会新建 active 记录,因此暂时绕过问题,同时存在重置原开课进度的风险。

生产只读核对 1577 条开课,1574 条主记录与内容有效。其中 586 条 active、949 条 pending、39 条状态为空;不能将这些 pending/null 全量改为 active,大部分属于停用学生,另有缺失用户、重复开课和非单词分类。

本次准确恢复 74 条单词开课,涉及 46 位正常学生:43 条 pending、31 条分类/状态缺失。保留原开课 objectId、业务编号、来源、词数和学习进度;仅补齐 word/单词课/active 并更新时间。没有新建或删除开课,没有修改学生状态、余额、预约、课时预留或工资。

持续修复

xs_reconcile_legacy_word_enrollments 只处理来源可确认的历史 pending/null 开课,并同时要求正常 member、有效主记录和内容、唯一已确认 word 映射、没有其他非回收的同分类开课。保留新系统开课、回收记录、重复历史记录、未确认/冲突分类及停用/删除用户。现有唯一索引继续保护同一学生、词库、分类的有效开课;并发显式开课胜出时保留其结果。

五个字段范围受限的触发器覆盖 CourseBinding 与 CommonModel 的两种到达顺序、学生资料补入/重新启用、映射确认和内容恢复。激活操作不更新学习进度,active 行不再递归处理,重复迁移不产生额外变更。

这是独立补丁,应在事务中应用并设置锁等待/执行超时;不通过重新运行 member-enrollment.sql 或整个基础开课迁移来修复线上数据,因为旧回填有更宽的行为边界。本次没有重新部署任何网关或前端,也没有覆盖同工作区其他任务的同步/财务修改。

验证与发布

  • 先运行新增回归用例,确认原逻辑会在历史 pending/null、后续同步和学生重新启用场景失败;修复后新增 11 项 PGlite 用例通过。
  • 58 项相关测试全部通过:历史开课补齐、开课与课时业务、运营开课/排课网关、教学映射、教师选课和学习记录。补齐教师选课测试夹具缺失的 CourseAppointment.sourceKey 字段,使其符合现有生产例程。
  • 独立代码审查通过。审查发现的旧迁移拼接风险已通过保留独立补丁解决。
  • 真实 PostgreSQL 回滚事务验证:准确恢复 74 条,全部其他开课字段不变,所有用户的四类课时余额哈希不变;再次调用返回 0;事务内模拟学生停用、开课元数据缺失、重新启用后自动恢复,最终回滚。
  • 正式应用前备份开课数据及现有触发器,检查新例程不存在;在 repeatable-read 事务中设置 3 秒 lock_timeout 和 45 秒 statement_timeout 后应用。实际只更新相同 74 条开课,重复调用仍返回 0,余额哈希不变。
  • 发布后回读两个例程的正文并与源文件逐一匹配,确认五个触发器存在;74 条开课状态和原编号/学习进度逐一核对通过。
  • xiaoshu/ops/gateway 与 xiaoshu/ops/gateway-v3 的真实 ops/schedule/calendar 响应均包含全部 74 条恢复开课,归属 46 位学生,分类为 word、映射有效,均支持 30/60 分钟。各接口返回 658 条单词开课、788 条课程选项。
  • 没有通过浏览器手动新增预约,也没有代学生/老师实际开课、扣费或完课;本次验收覆盖数据库及线上排课接口。

私有备份、候选回滚、正式发布和接口核对凭据保存在 /Users/mac/.local/state/xiaoshu/cloud-releases/2026-11-06-enrollment-schedule/(目录按排查开始日期命名)。临时管理函数在各次查询/发布后均已删除并回读确认。

回滚时先移除本补丁的五个触发器及两个例程,再按私有发布凭据核对开课版本后恢复本次改动的分类/状态字段;发生后续学习或业务更新的开课必须单独核对,不能整行覆盖历史备份。