# 留言状态与后续 AI 对话恢复 留言分为待录音意图和已关联录音两个阶段。刷卡或短按先创建待录音意图,不绑定 连接表中的旧 WebSocket;录音开始包再将意图关联到唯一的连接和 session。 新连接接入、尚未录音便断开以及旧连接延迟退出,均不清除待录音意图; 已关联录音的目标仍在其连接断开或被替换时清理。 目标设置后默认空闲有效期为 120 秒,可用 `VOICE_TARGET_TIMEOUT_SECONDS` 调整; 录音开始及有效留言音频会刷新空闲期限,单纯连接或重连不会延长有效期。 每次访问均检查过期状态,后台会定期回收过期目标及缓存。 开始包统一返回一次 `START_ACK`,先登记会话,再根据首个音频包选择流程: 类型 `0` 清除待处理留言目标并完成 AI 初始化,原始首包随后正常送入 ASR; 类型 `4` 写入本次留言的独立缓存。录音开始传输音频后不允许切换类型。 没有有效目标的留言音频不会启动 AI,结束时返回 `END_ACK` 和既有留言保存失败 提示音;旧录音延迟结束时不向正在进行的新会话插入该失败提示。 没有音频的普通 START/END 会话仍走原有静音识别响应。 已关联录音的目标及音频缓存,在结束、取消、异常、空音频、超时或连接断开时释放。 结束时先消费本次目标,再处理上传和通知,避免旧处理完成时清除新设置的目标。 旧连接断开只清理该连接的会话和任务,不影响替换它的新连接。 音频缓存键按发送设备及本次目标独立生成,多个发送方对同一接收方留言时互不覆盖。 本次修复保留既有数据包类型、鉴权及设备协议。家长留言的上传、会话保存和通知流程 沿用原实现。 ## 本地验证(2026-10-28) - 全量后端回归 243/243,包括新增的 28 项留言状态与恢复测试。 - 本机隔离 MySQL、原有 `/ws` 路由和真实 ASR/LLM/TTS:正常对话及静音 10/10, 中断及恢复 4/4;包含两个角色在待处理、过期、取消家长留言后的 AI 对话。 - 实际 WebSocket 家长留言取消 2 次,均收到取消确认及处理完成通知,随后 AI 对话成功。 - 共 52 段音频通过 HTTP 200、Range 206、完整 MP3 解码及非零 PCM 检查。 - 正常留言的上传、保存、通知和失败分支使用本地替身测试,未向真实家长发送通知。 - 没有访问或部署生产服务,未验证实物设备的麦克风、扬声器及播放中打断。 覆盖边界:留言取消、空音频、处理异常、过期及未结束录音后的 AI 恢复已有回归; AI 中断后再次 AI 对话已有真实服务验证。留言取消后再次留言验证了录音隔离, AI 中断后留言验证了启动、收音及取消,尚未串联验证这两种情况下第二条留言完整发送。 正常家长留言处理的成功、空音频和上传异常分支已用模拟外部接口验证。 状态测试:`.venv/bin/python -m pytest tests/test_voice_target_lifecycle.py -q` 完整结果:[本地测试汇总](../output/voice-target-recovery-20261028/summary.json)。 ## 待录音目标重连修复(2026-10-30) 设备先刷卡、后建立录音 WebSocket 时,旧实现将新意图绑定到旧连接,随后以 `connection_changed` 清除有效意图。本次取消创建意图和连接接入时的提前绑定, 保留已关联录音的连接与 session 隔离,并将 AI 初始化推迟到首个普通音频包 (纯静音会话则在结束包)以避免无目标留言误走 AI。 - 新增用例先在旧代码复现:8 种刷卡后换连接/旧连接退出组合失败, 另有 1 项无目标留言误走 AI 的测试失败。 - 修改后留言状态测试 49/49,完整后端回归 264/264。 - 覆盖家长及设备留言、旧连接在新连接前/后及录音开始后退出、重连不刷新 待录音有效期、旧包隔离、静音、无目标留言失败以及取消/断线/超时后的再次留言。 - 两项本地集成用例串联实际 MQTT 005/011 业务处理函数、`/ws` 路由和 留言处理函数,核对完整音频、成功提示、家长会话保存或设备归档/待投递接口参数 及缓存清理;鉴权、MQTT 发布、存储、数据库写入及通知均使用隔离替身。 - 本轮未连接真实 MQTT Broker、未调用真实 ASR/LLM/TTS、未验证物理设备, 未访问或部署生产服务。 测试证据:`output/voice-target-reconnect-20261030/pytest.xml`。