Flutter应用连接OpenAI时WebSocket连接关闭,求解决方案
问题根源分析
你的代码存在几个核心问题导致WebSocket连接被关闭:
- 错误的WebSocket端点:
wss://chat.openai.com/api/chat是ChatGPT网页端的内部私有接口,不对外开放,且需要复杂的会话认证,直接连接会被服务器拒绝并关闭连接。 - 缺少必要的认证信息:OpenAI的所有API都需要身份验证(API密钥或会话令牌),你的代码未携带任何认证信息,服务器会直接断开连接。
- 消息格式不符合要求:即使连接成功,你发送的纯文本消息不符合OpenAI API的格式规范,服务器无法处理并会关闭连接。
- 状态管理与错误处理不完善:
errorMessage未初始化会导致运行时错误;isConnected在连接发起后立即标记为已连接,未等待实际连接成功;没有连接断开后的重连机制。
修复方案
1. 替换为OpenAI官方流式HTTP接口(推荐)
OpenAI官方提供的Chat Completions流式HTTP接口更稳定,无需WebSocket。首先添加依赖:
dependencies: flutter: sdk: flutter http: ^1.1.0
2. 修改核心代码
import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'My Chat App', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: const MyHomePage(title: 'My Chat App'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({required this.title, super.key}); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { final TextEditingController messageController = TextEditingController(); List<ChatMessage> messages = []; bool isLoading = false; String? errorMessage; // 替换为你的OpenAI API密钥 final String openAiApiKey = 'YOUR_OPENAI_API_KEY'; Future<void> sendMessage() async { if (messageController.text.isEmpty || isLoading) return; final userMessage = ChatMessage(sender: 'You', text: messageController.text); setState(() { messages.add(userMessage); isLoading = true; errorMessage = null; messageController.clear(); }); try { final url = Uri.parse('https://api.openai.com/v1/chat/completions'); final request = http.Request('POST', url) ..headers.addAll({ 'Authorization': 'Bearer $openAiApiKey', 'Content-Type': 'application/json', }) ..body = jsonEncode({ 'model': 'gpt-3.5-turbo', 'messages': messages.map((msg) => { 'role': msg.sender == 'You' ? 'user' : 'assistant', 'content': msg.text, }).toList(), 'stream': true, }); final response = await request.send(); if (response.statusCode != HttpStatus.ok) { final errorBody = await response.stream.bytesToString(); throw Exception('请求失败: ${response.statusCode}, $errorBody'); } String assistantReply = ''; ChatMessage? assistantMessage; await for (final chunk in response.stream.transform(utf8.decoder)) { for (final line in chunk.split('\n')) { if (line.startsWith('data: ')) { final data = line.substring(6); if (data == '[DONE]') break; try { final jsonData = jsonDecode(data); final content = jsonData['choices'][0]['delta']['content'] as String?; if (content != null) { assistantReply += content; setState(() { assistantMessage = ChatMessage(sender: 'AI', text: assistantReply); if (messages.last.sender == 'AI') { messages.removeLast(); } messages.add(assistantMessage!); }); } } catch (e) { continue; } } } } } catch (e) { setState(() { errorMessage = '请求错误: $e'; }); } finally { setState(() { isLoading = false; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Padding( padding: const EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: ListView.builder( itemCount: messages.length, itemBuilder: (context, index) { final message = messages[index]; return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Column( crossAxisAlignment: message.sender == 'You' ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ Text( message.sender, style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 4), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8), decoration: BoxDecoration( color: message.sender == 'You' ? Colors.blue[100] : Colors.grey[100], borderRadius: BorderRadius.circular(12), ), child: Text(message.text), ), ], ), ); }, ), ), if (errorMessage != null) Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text(errorMessage!, style: const TextStyle(color: Colors.red)), ), Row( children: [ Expanded( child: TextField( controller: messageController, enabled: !isLoading, decoration: const InputDecoration( hintText: '输入你的问题...', border: OutlineInputBorder(), ), onSubmitted: (_) => sendMessage(), ), ), const SizedBox(width: 8), ElevatedButton( onPressed: isLoading ? null : sendMessage, child: isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('发送'), ), ], ), ], ), ), ); } } class ChatMessage { final String sender; final String text; ChatMessage({required this.sender, required this.text}); }
3. 若坚持使用WebSocket(OpenAI Real-Time API)
需先申请OpenAI Real-Time API访问权限,示例连接代码:
import 'package:web_socket_channel/web_socket_channel.dart' as ws; void connectToWebSocket() async { try { final uri = Uri.parse('wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01'); final channel = ws.WebSocketChannel.connect(uri, headers: { 'Authorization': 'Bearer $openAiApiKey', 'OpenAI-Beta': 'realtime=v1', }); channel.stream.listen((data) { final jsonData = jsonDecode(data); setState(() { messages.add(ChatMessage(sender: 'AI', text: jsonData['message']['content'][0]['text'])); }); }, onError: (error) { setState(() { errorMessage = 'WebSocket错误: $error'; }); }, onDone: () { setState(() { errorMessage = 'WebSocket连接关闭'; // 添加重连逻辑 // Future.delayed(const Duration(seconds: 5), connectToWebSocket); }); }); } catch (error) { setState(() { errorMessage = '连接失败: $error'; }); } }
4. 其他优化点
- 将
errorMessage初始化为null,避免空指针错误。 - 添加加载状态,防止重复发送请求。
- 优化消息UI,区分用户与AI消息的展示位置。
- WebSocket场景下可添加自动重连逻辑。
注意事项
- 不要硬编码API密钥,建议使用环境变量或安全存储方式。
- 确保你的OpenAI API密钥有足够的权限和额度。
- 遵守OpenAI的API使用规范和速率限制。
内容的提问来源于stack exchange,提问作者Android_devNL
相关产品推荐
相关产品推荐

