# 选词“重试保存”失败:历史重复学习记录(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`,再做线上回读与请求验证。