selection-save-duplicate-practice-fix-2026-11-03.md 3.2 KB

选词“重试保存”失败:历史重复学习记录(2026-11-03)

诊断

截图入口为预约 881692、单元 158、词库 80、开课 587pMttl5y;预约关联学员业务 ID 1719。仅页面通用错误不能区分失败原因,因此先读取真实保存回执、关联词表及记录,再用回滚事务复现。

该学员的 game(词库 ID 586395)存在两条历史导入 PracticeRecord:

  • dMxDC1ASKU,来源 legacy:model:53:general:764022
  • tTpsjInteC,来源 legacy:model:53:general:764023

两条记录都创建于 2026-09-01T02:40:40.843Z,次数均为 1。网关之前把全部重复行转成 Map,后返回的记录覆盖前一条;事务按创建时间、objectId 取第一条。于是把第二条记录的唯一 sourceKey 拷到第一条记录上,触发 23505 / PracticeRecord_sourceKey_key,整批回滚。前端收到通用“云函数执行失败”,重试同一请求会稳定重现,并非又碰到 100 词限制。

今天同一学员已有 10 词和 6 词的成功回执,但它们并非截图所显示的六个例外词,不能据此认定截图这批已经保存。

修复

  • updateLearningWords 按单词去重,优先选择最高学习次数,再按创建时间、objectId 稳定选择;与课程词表原有 MAX 进度读取逻辑一致。
  • xs_mobile_teaching_write 对 PracticeRecord 使用相同的选择规则,并保留被更新行已有的唯一来源键。其他教学表的选择逻辑保持不变。
  • 保留所有历史记录,不删重复行、不倒退较高进度;保存仍为单事务,原请求标识的重试只增加一次。

验证

  • 新增 4 项回归测试。旧代码已复现来源键唯一约束错误;修复后覆盖整批保存、最大进度、同创建时间/同次数的稳定选择、来源键与历史 ID、生词标记、新词创建及重复请求。
  • 相关教学写入、单元进度、课程选择、同步与历史进度读取测试共 48 项通过。
  • npm run cloud:validate 验证 11 个云函数定义通过。
  • 独立只读审查未发现阻塞问题。
  • 使用真实学员数据,在同一事务内临时安装候选函数,验证包含问题词的 73 个词整批保存,以及同一请求重试不再次增加进度;随后回滚所有写入及候选函数。验证耗时约 7 秒。
  • 回滚后重新从另一请求读取确认:线上网关哈希仍为 a8f27027e749f44b9ba28b9e3b7a4eebbe09026f19a797536562c9b2b97d29a9,教学事务函数体哈希仍为 9a24362b5ee24f7aced29bcf258f3a9391f4a923b43f33f511749d92fa4d6cdd,诊断请求未留下回执。

私有诊断与验证证据:/Users/mac/.local/state/xiaoshu/cloud-releases/2026-11-03-selection-save-fix/。候选函数体哈希:2ef95cc5c2959dc3d10d16a198ed06e551afcaa5289767b305db3c3924d051b6。

交付边界

本次仅提交、推送修复代码。回滚验证不等于正式发布;没有更新长期在线的网关或 SQL 函数,没有删除、归并或永久修改学员历史记录。本次没有前端改动,不需重发前端包。后续正式发布需一并选择性更新网关的 updateLearningWords 和 SQL 的 xs_mobile_teaching_write,再做线上回读与请求验证。