# 服务端取消支持方案 ## 问题分析 当前取消功能的问题: 1. 客户端 `XMLHttpRequest.abort()` 只能终止网络传输,无法停止服务端处理 2. 服务端可能已经生成并保存了会话记录 3. 延迟响应导致"取消"的内容稍后出现 ## 服务端需要支持的功能 ### 1. 请求中断检测 **方案A:连接断开检测(推荐)** ```python # 伪代码示例 async def ai_chat_stream(request): conversation_id = request.json.get('conversationId') messages = request.json.get('messages', []) # 如果是新会话且客户端断开,不创建会话记录 is_new_conversation = not conversation_id temp_conversation_id = None try: # 检查客户端连接状态 if await request.is_disconnected(): print("Client disconnected before processing") return # 只有在开始生成内容时才创建会话 if is_new_conversation: temp_conversation_id = create_conversation() async for chunk in ai_generate_stream(messages): # 每次生成前检查连接状态 if await request.is_disconnected(): print("Client disconnected during generation") # 如果是新会话,删除临时创建的会话 if temp_conversation_id: delete_conversation(temp_conversation_id) return yield chunk # 成功完成,保存会话 if temp_conversation_id: save_conversation(temp_conversation_id, messages + [ai_response]) except ClientDisconnectedError: # 清理未完成的会话 if temp_conversation_id: delete_conversation(temp_conversation_id) ``` **方案B:取消端点(备选)** ```python # 添加专门的取消端点 @app.post("/api/ai-coach/cancel") async def cancel_request(request): request_id = request.json.get('requestId') conversation_id = request.json.get('conversationId') # 标记请求为已取消 cancel_request_processing(request_id) # 如果会话是新创建的,删除它 if is_new_conversation(conversation_id): delete_conversation(conversation_id) return {"status": "cancelled"} # 在主处理函数中检查取消状态 async def ai_chat_stream(request): request_id = request.headers.get('X-Request-Id') while generating: if is_request_cancelled(request_id): cleanup_and_exit() return # 继续处理... ``` ### 2. 会话管理优化 **延迟会话创建:** ```python class ConversationManager: def __init__(self): self.temp_conversations = {} async def start_conversation(self, client_id): """开始新会话,但不立即持久化""" temp_id = f"temp_{uuid4()}" self.temp_conversations[temp_id] = { 'client_id': client_id, 'messages': [], 'created_at': datetime.now(), 'confirmed': False } return temp_id async def confirm_conversation(self, temp_id, final_messages): """确认会话并持久化""" if temp_id in self.temp_conversations: # 保存到数据库 real_id = self.save_to_database(final_messages) del self.temp_conversations[temp_id] return real_id async def cancel_conversation(self, temp_id): """取消临时会话""" if temp_id in self.temp_conversations: del self.temp_conversations[temp_id] return True return False ``` ### 3. 流式响应优化 **分段提交策略:** ```python async def ai_chat_stream(request): try: # 第一阶段:验证请求 if await request.is_disconnected(): return # 第二阶段:开始生成(创建临时会话) temp_conv_id = await start_temp_conversation() yield json.dumps({"temp_conversation_id": temp_conv_id}) # 第三阶段:流式生成内容 full_response = "" for chunk in ai_generate(): if await request.is_disconnected(): await cancel_temp_conversation(temp_conv_id) return full_response += chunk yield chunk # 第四阶段:确认并保存会话 final_conv_id = await confirm_conversation(temp_conv_id, full_response) yield json.dumps({"final_conversation_id": final_conv_id}) except ClientDisconnectedError: await cancel_temp_conversation(temp_conv_id) ``` ## 具体实现建议 ### 1. 服务端 API 修改 **请求头支持:** ```http POST /api/ai-coach/chat X-Request-Id: req_1234567890_abcdef Content-Type: application/json { "conversationId": "existing_conv_id", // 可选,新会话时为空 "messages": [...], "stream": true } ``` **响应头:** ```http HTTP/1.1 200 OK X-Conversation-Id: mobile-1701234567-abcdef123 X-Request-Id: req_1234567890_abcdef Content-Type: text/plain; charset=utf-8 ``` ### 2. 数据库设计优化 **会话状态管理:** ```sql CREATE TABLE conversations ( id VARCHAR(50) PRIMARY KEY, user_id INT, status ENUM('temp', 'active', 'cancelled') DEFAULT 'temp', created_at TIMESTAMP, confirmed_at TIMESTAMP NULL, messages JSON, INDEX idx_status_created (status, created_at) ); -- 定期清理临时会话 DELETE FROM conversations WHERE status = 'temp' AND created_at < NOW() - INTERVAL 10 MINUTE; ``` ### 3. 客户端配合改进 **添加请求ID头:** ```typescript // 在 api.ts 中添加 export function postTextStream(path: string, body: any, callbacks: TextStreamCallbacks, options: TextStreamOptions = {}) { const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; const requestHeaders: Record = { 'Content-Type': 'application/json', 'X-Request-Id': requestId, ...(options.headers || {}), }; // ... 其余代码 } ``` **主动取消通知(可选):** ```typescript async function notifyServerCancel(conversationId?: string, requestId?: string) { try { if (requestId) { await api.post('/api/ai-coach/cancel', { requestId, conversationId }); } } catch (error) { console.warn('Failed to notify server of cancellation:', error); } } ``` ## 完整取消流程 ### 客户端取消时: 1. 增加请求序列号(防止延迟响应) 2. 调用 `XMLHttpRequest.abort()` 3. 清理本地状态 4. (可选)通知服务端取消 ### 服务端检测到断开: 1. 立即停止 AI 内容生成 2. 检查会话状态(临时/已确认) 3. 如果是临时会话,删除记录 4. 清理相关资源 ### 防止延迟响应: 1. 客户端使用请求序列号验证 2. 服务端检查连接状态 3. 分段式会话确认机制 ## 优先级建议 **立即实现(高优先级):** 1. 客户端请求序列号验证 ✅ 已实现 2. 延迟会话ID生成 ✅ 已实现 3. 客户端状态严格管理 ✅ 已实现 **服务端配合(中优先级):** 1. 连接断开检测 2. 临时会话管理 3. 请求ID追踪 **可选优化(低优先级):** 1. 主动取消通知端点 2. 会话状态数据库优化 3. 定期清理机制 通过以上客户端和服务端的协作改进,可以彻底解决取消功能的问题,确保用户取消操作的即时性和有效性。