From e989b4adf19e051d49f8f8d801096325508c3ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B5=A9?= Date: Wed, 14 Jan 2026 17:11:48 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=88=A0=E9=99=A4=20chat=5Fsystem.md?= =?UTF-8?q?=20=E8=AE=A1=E5=88=92=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/AI_docs/plan/chat_system.md | 865 ------------------------------- 1 file changed, 865 deletions(-) delete mode 100644 docs/AI_docs/plan/chat_system.md diff --git a/docs/AI_docs/plan/chat_system.md b/docs/AI_docs/plan/chat_system.md deleted file mode 100644 index 06de5b3..0000000 --- a/docs/AI_docs/plan/chat_system.md +++ /dev/null @@ -1,865 +0,0 @@ -# WhaleTown 聊天系统实施计划 - -## 📋 项目概述 - -为 WhaleTown 游戏实现基于 Socket.IO 的实时聊天系统,对接现有的 Zulip 集成后端。 - -**后端地址**: `wss://whaletownend.xinghangee.icu/game` - -**技术限制**: Godot 原生支持 WebSocket 但不支持 Socket.IO 协议,需要实现轻量级 Socket.IO 协议封装。 - ---- - -## 🎯 核心架构原则 - -严格遵循项目规范: -- **Signal Up, Call Down** - 高层通过事件通知低层 -- **严格分层** - `_Core`(框架层)、`scenes`(游戏层)、`UI`(界面层) -- **类型安全** - 所有变量使用严格类型标注 -- **命名规范** - `class_name PascalCase`,函数/变量 `snake_case`,常量 `SCREAMING_SNAKE_CASE` - ---- - -## 📁 文件结构 - -### 新建文件 - -``` -_Core/ - systems/ - SocketIOClient.gd # Socket.IO 协议封装(核心) - managers/ - ChatManager.gd # 聊天系统业务逻辑管理器 - WebSocketManager.gd # WebSocket 连接生命周期管理 - -scenes/ - ui/ - ChatUI.tscn # 聊天界面场景 - ChatUI.gd # 聊天界面控制器 - prefabs/ui/ - ChatMessage.tscn # 单条消息气泡预制体 - ChatMessage.gd # 消息气泡组件 - -tests/ - unit/ - test_chat_manager.gd # ChatManager 单元测试 - test_socketio_client.gd # SocketIOClient 单元测试 -``` - -### 修改文件 - -- [_Core/EventNames.gd](_Core/EventNames.gd) - 添加聊天事件常量 -- [project.godot](project.godot) - 添加 ChatManager 到自动加载 - ---- - -## 🔧 核心组件设计 - -### 1. SocketIOClient.gd - 协议封装层 - -**位置**: `_Core/systems/SocketIOClient.gd` - -**职责**: -- 封装 Godot 的 `WebSocketPeer` -- 实现 Socket.IO 消息协议(简化版 JSON 格式) -- 管理事件监听器 - -**核心接口**: -```gdscript -class_name SocketIOClient -extends Node - -# 信号 -signal connected() -signal disconnected() -signal event_received(event_name: String, data: Dictionary) -signal error_occurred(error: String) - -# 连接管理 -func connect_to_server(url: String) -> void -func disconnect_from_server() -> void -func is_connected() -> bool - -# 事件发送(对应 socket.emit) -func emit(event_name: String, data: Dictionary) -> void - -# 事件监听(对应 socket.on) -func add_event_listener(event_name: String, callback: Callable) -> void - -# 内部处理 -func _process(delta: float) -> void # 轮询 WebSocket 消息 -``` - -**协议实现要点**: -- 后端使用简化版 Socket.IO(纯 JSON,无二进制协议) -- 发送消息: `{"type": "login", "token": "..."}` 或 `{"t": "chat", "content": "..."}` -- 接收消息: 通过 `"t"` 字段识别事件类型 -- 所有消息使用 `JSON.stringify()` 序列化 - ---- - -### 2. WebSocketManager.gd - 连接管理 - -**位置**: `_Core/managers/WebSocketManager.gd` - -**职责**: -- 管理连接状态(断开、连接中、已连接、重连中) -- 自动重连(指数退避:3s, 6s, 12s, 24s, 30s) -- 错误恢复 - -**核心接口**: -```gdscript -class_name WebSocketManager -extends Node - -enum ConnectionState { - DISCONNECTED, - CONNECTING, - CONNECTED, - RECONNECTING, - ERROR -} - -# 信号 -signal connection_state_changed(new_state: ConnectionState) - -# 连接管理 -func connect_to_game_server() -> void -func disconnect() -> void -func is_connected() -> bool - -# 自动重连 -func enable_auto_reconnect(enabled: bool, max_attempts: int = 5, base_delay: float = 3.0) - -# 访问 Socket.IO 客户端 -func get_socket_client() -> SocketIOClient -``` - ---- - -### 3. ChatManager.gd - 业务逻辑核心 - -**位置**: `_Core/managers/ChatManager.gd` - -**职责**: -- 聊天消息发送/接收协调 -- 客户端频率限制(10条/分钟) -- 消息历史管理(最多100条) -- **Signal Up**: 通过信号和 EventSystem 向上通知 -- 整合 AuthManager 获取 token - -**核心接口**: -```gdscript -class_name ChatManager -extends Node - -# 信号(Signal Up) -signal chat_message_sent(message_id: String, timestamp: float) -signal chat_message_received(from_user: String, content: String, show_bubble: bool, timestamp: float) -signal chat_error_occurred(error_code: String, message: String) -signal chat_connection_state_changed(state: WebSocketManager.ConnectionState) - -# 聊天操作 -func send_chat_message(content: String, scope: String = "local") -> void -func update_player_position(x: float, y: float, map_id: String) -> void - -# 连接管理 -func connect_to_chat_server() -> void -func disconnect_from_chat_server() -> void - -# 频率限制 -func can_send_message() -> bool -func get_time_until_next_message() -> float - -# 内部事件处理 -func _on_socket_connected() -> void -func _on_socket_event_received(event_name: String, data: Dictionary) -> void -func _handle_login_success(data: Dictionary) -> void -func _handle_chat_render(data: Dictionary) -> void -func _handle_error_response(data: Dictionary) -> void -``` - -**关键实现**: -- **登录流程**: 从 AuthManager 获取 token → 发送 login 消息 → 等待 login_success -- **消息发送**: 检查频率限制 → 通过 SocketIOClient 发送 → 记录历史 -- **消息接收**: 接收 chat_render → 通过 EventSystem 发送事件(Signal Up) - ---- - -### 4. EventNames.gd - 事件注册表 - -**位置**: [_Core/EventNames.gd](_Core/EventNames.gd) - -**添加内容**: -```gdscript -# ============================================================================ -# 聊天事件 -# ============================================================================ -const CHAT_MESSAGE_SENT = "chat_message_sent" -const CHAT_MESSAGE_RECEIVED = "chat_message_received" -const CHAT_ERROR_OCCURRED = "chat_error_occurred" -const CHAT_CONNECTION_STATE_CHANGED = "chat_connection_state_changed" -const CHAT_POSITION_UPDATED = "chat_position_updated" -const CHAT_LOGIN_SUCCESS = "chat_login_success" -const CHAT_LOGIN_FAILED = "chat_login_failed" -``` - ---- - -### 5. ChatUI.tscn & ChatUI.gd - 用户界面 - -**位置**: `scenes/ui/ChatUI.tscn` 和 `scenes/ui/ChatUI.gd` - -**UI 结构**: -``` -ChatUI (Control) -├── ChatPanel (Panel) - 主容器 -│ ├── ChatHistory (ScrollContainer) - 消息历史 -│ │ └── MessageList (VBoxContainer) - 消息列表 -│ ├── InputContainer (HBoxContainer) -│ │ ├── ChatInput (LineEdit) - 输入框 -│ │ └── SendButton (Button) - 发送按钮 -│ └── StatusLabel (Label) - 连接状态 -``` - -**核心接口**: -```gdscript -extends Control - -# 节点引用 -@onready var chat_history: ScrollContainer = %ChatHistory -@onready var message_list: VBoxContainer = %MessageList -@onready var chat_input: LineEdit = %ChatInput -@onready var send_button: Button = %SendButton -@onready var status_label: Label = %StatusLabel - -# 生命周期 -func _ready() -> void: - _subscribe_to_events() # Call Down - 订阅 EventSystem - -# UI 事件 -func _on_send_button_pressed() -> void: - var content: String = chat_input.text - ChatManager.send_chat_message(content, "local") - -# 订阅事件(Call Down) -func _subscribe_to_events() -> void: - EventSystem.connect_event(EventNames.CHAT_MESSAGE_RECEIVED, _on_chat_message_received, self) - EventSystem.connect_event(EventNames.CHAT_ERROR_OCCURRED, _on_chat_error, self) - EventSystem.connect_event(EventNames.CHAT_CONNECTION_STATE_CHANGED, _on_connection_state_changed, self) - -# 事件处理器 -func _on_chat_message_received(data: Dictionary) -> void: - var from_user: String = data["from_user"] - var content: String = data["content"] - add_message_to_history(from_user, content, data["timestamp"], false) - -func add_message_to_history(from_user: String, content: String, timestamp: float, is_self: bool) -> void: - var message_node: ChatMessage = chat_message_scene.instantiate() - message_list.add_child(message_node) - message_node.set_message(from_user, content, timestamp, is_self) -``` - ---- - -### 6. ChatMessage.tscn & ChatMessage.gd - 消息气泡 - -**位置**: `scenes/prefabs/ui/ChatMessage.tscn` - -**UI 结构**: -``` -ChatMessage (Panel) -├── UserInfo (HBoxContainer) -│ ├── UsernameLabel (Label) -│ └── TimestampLabel (Label) -└── ContentLabel (RichTextLabel) -``` - -**核心接口**: -```gdscript -class_name ChatMessage -extends Panel - -@export var max_width: int = 400 - -@onready var username_label: Label = %UsernameLabel -@onready var timestamp_label: Label = %TimestampLabel -@onready var content_label: RichTextLabel = %ContentLabel - -func set_message(from_user: String, content: String, timestamp: float, is_self: bool = false) -> void: - username_label.text = from_user - content_label.text = content - # 格式化时间戳和样式 -``` - ---- - -## 🔄 数据流与事件通信 - -### 发送消息流程 - -``` -用户点击发送按钮 - ↓ -ChatUI._on_send_button_pressed() - ↓ -ChatManager.send_chat_message(content, "local") - ↓ -检查频率限制 - ↓ -SocketIOClient.emit("chat", {t: "chat", content: "...", scope: "local"}) - ↓ -WebSocketPeer.put_packet(json_bytes) - ↓ -服务器响应 chat_sent - ↓ -ChatManager._handle_chat_sent() - ↓ -EventSystem.emit_event(CHAT_MESSAGE_SENT, data) ← Signal Up - ↓ -ChatUI 可以订阅此事件更新 UI -``` - -### 接收消息流程 - -``` -WebSocketPeer 接收数据 - ↓ -SocketIOClient._process() 轮询 - ↓ -解析 JSON,提取 "t" 字段(事件类型) - ↓ -event_received.emit("chat_render", data) - ↓ -ChatManager._on_socket_event_received() - ↓ -_handle_chat_render(data) - ↓ -EventSystem.emit_event(CHAT_MESSAGE_RECEIVED, data) ← Signal Up - ↓ -ChatUI._on_chat_message_received(data) ← Call Down via EventSystem - ↓ -创建 ChatMessage 节点并添加到 UI -``` - ---- - -## 🔐 错误处理策略 - -### 错误码映射(在 ChatManager.gd 中实现) - -```gdscript -const CHAT_ERROR_MESSAGES: Dictionary = { - "AUTH_FAILED": "聊天认证失败,请重新登录", - "RATE_LIMIT": "消息发送过于频繁,请稍后再试", - "CONTENT_FILTERED": "消息内容包含违规内容", - "CONTENT_TOO_LONG": "消息内容过长(最大1000字符)", - "PERMISSION_DENIED": "您没有权限发送消息", - "SESSION_EXPIRED": "会话已过期,请重新连接", - "ZULIP_ERROR": "消息服务暂时不可用", - "INTERNAL_ERROR": "服务器内部错误" -} -``` - -### 错误处理流程 - -``` -服务器返回 error - ↓ -ChatManager._handle_error_response(data) - ↓ -提取 error_code 和 message - ↓ -映射为用户友好的错误消息 - ↓ -EventSystem.emit_event(CHAT_ERROR_OCCURRED, {...}) ← Signal Up - ↓ -ChatUI._on_chat_error(data) ← Call Down - ↓ -显示错误提示(Toast 或 Label) -``` - ---- - -## ⚙️ 配置与常量 - -### ChatManager.gd 常量 - -```gdscript -const WEBSOCKET_URL: String = "wss://whaletownend.xinghangee.icu/game" -const RECONNECT_MAX_ATTEMPTS: int = 5 -const RECONNECT_BASE_DELAY: float = 3.0 -const RATE_LIMIT_MESSAGES: int = 10 -const RATE_LIMIT_WINDOW: float = 60.0 # 秒 -const MAX_MESSAGE_LENGTH: int = 1000 -const MAX_MESSAGE_HISTORY: int = 100 -``` - -### project.godot 自动加载 - -```ini -[autoload] -ChatManager="*res://_Core/managers/ChatManager.gd" -``` - ---- - -## 🔗 集成点 - -### 1. AuthManager 集成 - -**需求**: ChatManager 需要获取 access_token 用于 WebSocket 聊天认证 - -**解决方案**: AuthManager 在登录成功后自动提取并保存 access_token 和 refresh_token - -**Token 管理架构**: -```gdscript -# 内存存储(快速访问) -var _access_token: String = "" # JWT访问令牌(短期,用于API和WebSocket) -var _refresh_token: String = "" # JWT刷新令牌(长期,用于获取新access_token) -var _user_info: Dictionary = {} # 用户信息 -var _token_expiry: float = 0.0 # access_token过期时间(Unix时间戳) - -# 本地存储(ConfigFile持久化) -const AUTH_CONFIG_PATH: String = "user://auth.cfg" -``` - -**登录流程**: -1. 用户登录成功后,服务器返回 `access_token` 和 `refresh_token` -2. AuthManager 调用 `_save_tokens_to_memory(data)` 保存到内存 -3. AuthManager 调用 `_save_tokens_to_local(data)` 保存到本地ConfigFile -4. AuthScene 在登录成功后调用 `ChatManager.set_game_token(token)` 设置token - -**Token 存储内容**: -- **内存**: access_token, refresh_token, user_info, token_expiry -- **本地 (user://auth.cfg)**: refresh_token, user_id, username, saved_at - -**注意事项**: -- `access_token` 仅保存在内存中,不存储到本地(安全考虑) -- `refresh_token` 加密存储到本地,用于下次登录时自动刷新token -- Token 过期后需要使用 refresh_token 刷新 - -### 2. EventSystem 集成 - -**ChatManager 发送事件**(Signal Up): -```gdscript -EventSystem.emit_event(EventNames.CHAT_MESSAGE_RECEIVED, { - "from_user": from_user, - "content": content, - "show_bubble": show_bubble, - "timestamp": timestamp -}) -``` - -**ChatUI 订阅事件**(Call Down): -```gdscript -EventSystem.connect_event(EventNames.CHAT_MESSAGE_RECEIVED, _on_chat_message_received, self) -``` - -### 3. 自动连接时机 - -在游戏进入主场景时自动连接聊天: -```gdscript -# MainScene.gd 或 GameManager.gd -func _ready(): - ChatManager.connect_to_chat_server() -``` - ---- - -## 📝 API 规范(来自 api.md) - -### 消息类型 - -#### 1. 登录 -```json -// 发送 -// token 字段应该使用登录接口返回的 access_token -{"type": "login", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."} - -// 成功响应 -{"t": "login_success", "sessionId": "...", "currentMap": "...", "username": "..."} - -// 失败响应 -{"t": "error", "code": "AUTH_FAILED", "message": "..."} -``` - -**Token 来源**: -- 登录接口 (`/auth/login`) 返回 `access_token` (JWT访问令牌) -- AuthManager 在登录成功后保存 access_token 到内存 -- AuthScene 在登录成功后设置 token 给 ChatManager -- ChatManager 使用该 token 发送 WebSocket 登录消息 - -#### 2. 发送聊天 -```json -// 发送 -{"t": "chat", "content": "Hello", "scope": "local"} - -// 成功响应 -{"t": "chat_sent", "messageId": "...", "timestamp": 1703500800000} -``` - -#### 3. 接收聊天 -```json -// 服务器推送 -{"t": "chat_render", "from": "other_player", "txt": "Hi!", "bubble": true, "timestamp": 1703500800000} -``` - -#### 4. 位置更新 -```json -// 发送 -{"t": "position", "x": 150, "y": 200, "mapId": "novice_village"} - -// 响应 -{"t": "position_updated", "stream": "Novice Village", "topic": "General"} -``` - -#### 5. 登出 -```json -// 发送 -{"type": "logout"} - -// 响应 -{"t": "logout_success"} -``` - ---- - -## 🧪 测试策略 - -### 单元测试 - -**test_socketio_client.gd**: -- 测试消息格式化(JSON 序列化) -- 测试事件监听器注册 -- 测试连接状态管理 - -**test_chat_manager.gd**: -- 测试消息发送流程 -- 测试频率限制(10条/分钟) -- 测试消息历史管理(最多100条) - -### 集成测试 - -**test_chat_integration.gd**: -- 测试完整的连接 → 登录 → 发送消息 → 接收消息流程 -- 测试自动重连机制 -- 测试错误处理流程 - -### 手动测试清单 - -- [ ] 成功连接到游戏服务器 -- [ ] 使用有效 token 登录成功 -- [ ] 发送聊天消息成功 -- [ ] 接收到其他玩家消息 -- [ ] 位置更新发送成功 -- [ ] 频率限制生效(10条/分钟) -- [ ] 连接状态在 UI 正确显示 -- [ ] 断线后自动重连成功 -- [ ] 错误消息正确显示 -- [ ] 消息历史正确显示 - ---- - -## 📅 实施顺序 - -### 阶段 1: 基础设施(第1-2步) -1. 创建 `_Core/systems/SocketIOClient.gd` - WebSocket 协议封装 -2. 创建 `_Core/managers/WebSocketManager.gd` - 连接管理 -3. 测试与后端的 WebSocket 连接 - -### 阶段 2: 业务逻辑(第3-4步) -4. 创建 `_Core/managers/ChatManager.gd` - 聊天管理器 -5. 实现登录流程(从 AuthManager 获取 token) -6. 实现消息发送/接收逻辑 -7. 添加频率限制和错误处理 - -### 阶段 3: 用户界面(第5-6步) -8. 创建 `scenes/prefabs/ui/ChatMessage.tscn` - 消息气泡 -9. 创建 `scenes/ui/ChatUI.tscn` - 聊天界面 -10. 实现 `ChatUI.gd` - 事件订阅和 UI 交互 - -### 阶段 4: 集成(第7步) -11. 更新 `_Core/EventNames.gd` - 添加聊天事件 -12. 更新 `project.godot` - 添加 ChatManager 到自动加载 -13. 集成 AuthManager(添加 get_game_token 方法) -14. 在主场景中初始化聊天连接 - -### 阶段 5: 测试与优化(第8-9步) -15. 编写单元测试 -16. 编写集成测试 -17. 手动测试清单验证 -18. 性能优化(消息历史限制、UI 更新优化) - ---- - -## ⚠️ 关键注意事项 - -1. **Token 获取**: 需要确认 `/auth/login` 返回的 token 是否就是 WebSocket 登录需要的 token -2. **协议简化**: 后端使用简化版 Socket.IO(纯 JSON),不需要实现完整的 Socket.IO 二进制协议 -3. **频率限制**: 客户端和服务器都会限制,客户端检查是为了更好的用户体验 -4. **消息历史**: 限制在内存中保存最多 100 条消息,避免内存泄漏 -5. **UI 更新**: 使用 `@onready` 缓存节点引用,避免在 `_process` 中使用 `get_node()` -6. **类型安全**: 所有变量必须使用严格类型标注(`var name: String = ""`) -7. **Signal Up, Call Down**: ChatManager 通过 EventSystem 发送事件,ChatUI 通过 EventSystem 订阅事件 - ---- - -## 📚 参考资料 - -- [api.md](api.md) - Zulip 集成 API 文档 -- [test_zulip.js](test_zulip.js) - 后端测试客户端(Node.js + Socket.IO) -- [_Core/EventNames.gd](_Core/EventNames.gd) - 事件名称常量 -- [_Core/systems/EventSystem.gd](_Core/systems/EventSystem.gd) - 事件系统实现 -- [_Core/managers/NetworkManager.gd](_Core/managers/NetworkManager.gd) - HTTP 请求管理器(参考模式) -- [scenes/ui/AuthScene.gd](scenes/ui/AuthScene.gd) - UI 控制器参考模式 - ---- - -## ✅ 实施进度(2025-01-06) - -### 已完成 ✅ - -#### 阶段 1: 基础设施 ✅ -- [x] **SocketIOClient.gd** - Socket.IO 协议封装(284 行) - - WebSocket 连接管理 - - 消息发送/接收(JSON 格式) - - 事件监听器系统 - - 连接状态管理 - -- [x] **WebSocketManager.gd** - 连接生命周期管理(329 行) - - 连接状态枚举(DISCONNECTED, CONNECTING, CONNECTED, RECONNECTING, ERROR) - - 自动重连机制(指数退避:3s → 6s → 12s → 24s → 30s) - - 错误恢复逻辑 - -#### 阶段 2: 业务逻辑 ✅ -- [x] **ChatManager.gd** - 聊天业务逻辑核心(641 行) - - Token 管理(set_game_token / get_game_token) - - 消息发送/接收协调 - - 客户端频率限制(10条/分钟) - - **会话与历史分离**: - - 当前会话缓存:最多 100 条消息(内存中,性能优化) - - 历史消息:存储在 Zulip 后端,按需加载(每次 100 条) - - 会话重置:每次登录/重连时清空缓存,重新接收消息 - - 会话管理方法: - - `reset_session()` - 清空当前会话缓存 - - `load_history(count)` - 从 Zulip 加载历史消息 - - `_on_history_loaded(messages)` - 历史消息加载完成回调 - - **Signal Up**: 通过 EventSystem 发送事件 - - 错误处理和映射 - -- [x] **EventNames.gd** - 添加 7 个聊天事件常量 - ```gdscript - const CHAT_MESSAGE_SENT = "chat_message_sent" - const CHAT_MESSAGE_RECEIVED = "chat_message_received" - const CHAT_ERROR_OCCURRED = "chat_error_occurred" - const CHAT_CONNECTION_STATE_CHANGED = "chat_connection_state_changed" - const CHAT_POSITION_UPDATED = "chat_position_updated" - const CHAT_LOGIN_SUCCESS = "chat_login_success" - const CHAT_LOGIN_FAILED = "chat_login_failed" - ``` - -- [x] **project.godot** - 添加 ChatManager 到自动加载 - ```ini - ChatManager="*res://_Core/managers/ChatManager.gd" - ``` - -- [x] **AuthManager.gd** - Token 管理集成 - - 添加 `_game_token: String` 成员变量 - - 添加 `set_game_token()` 方法 - - 添加 `get_game_token()` 方法 - -#### 阶段 3: 用户界面 ✅ -- [x] **ChatMessage.tscn & ChatMessage.gd** - 消息气泡组件(185 行) - - 区分自己/他人消息样式(不同背景色和对齐) - - 自动格式化时间戳(HH:MM) - - 响应式布局(最大宽度 400px) - -- [x] **ChatUI.tscn & ChatUI.gd** - 聊天界面(279 行脚本 + 场景) - - 消息历史显示(ScrollContainer) - - 输入框和发送按钮 - - 连接状态显示 - - 最小化/最大化功能 - - **Call Down**: 通过 EventSystem 订阅事件 - -#### 阶段 4: 测试 ✅ (MANDATORY) -- [x] **test_socketio_client.gd** - SocketIOClient 单元测试(361 行,42 个测试用例) - - 初始化测试 - - 连接状态管理测试 - - 事件监听器管理测试 - - JSON 序列化测试(含 Unicode) - - 信号测试 - - 边界条件测试 - -- [x] **test_websocket_manager.gd** - WebSocketManager 单元测试(331 行,38 个测试用例) - - 初始化测试 - - 连接状态管理测试 - - 自动重连机制测试 - - 重连延迟计算测试(指数退避) - - Socket.IO 客户端访问测试 - - 常量测试 - - 状态转换测试 - -- [x] **test_chat_manager.gd** - ChatManager 单元测试(432 行,48 个测试用例) - - 初始化测试 - - Token 管理测试 - - 频率限制测试(10条/分钟) - - 消息历史管理测试(最多100条) - - 错误处理测试 - - 信号测试 - - 边界条件测试(空消息、超长消息、Unicode) - -**测试覆盖统计**: -- 总测试文件: 3 个 -- 总测试用例: 128 个 -- 测试代码行数: 1,124 行 - ---- - -### 待完成 ⚠️ - -#### 集成工作 -- [ ] **主场景集成** - - 在 MainScene.gd 或 GameManager.gd 中添加 `ChatManager.connect_to_chat_server()` - - 在用户登录成功后设置 token:`ChatManager.set_game_token(token)` - - 添加聊天 UI 到游戏界面 - -- [ ] **Token 获取流程** - - 需要确认 `/auth/login` 返回的数据中是否包含 token - - 如果包含,在登录成功回调中保存并设置到 ChatManager - - 如果不包含,需要单独获取游戏 token 的接口 - -#### 测试与验证 -- [ ] **手动测试** - - [ ] 成功连接到游戏服务器 - - [ ] 使用有效 token 登录成功 - - [ ] 发送聊天消息成功 - - [ ] 接收到其他玩家消息 - - [ ] 位置更新发送成功 - - [ ] 频率限制生效(10条/分钟) - - [ ] 连接状态在 UI 正确显示 - - [ ] 断线后自动重连成功 - - [ ] 错误消息正确显示 - - [ ] 消息历史正确显示 - -- [ ] **运行单元测试** - ```bash - godot --headless -s addons/gut/gut_cmdline.gd -gdir=res://tests/unit -ginclude_subdirs - ``` - -#### 功能增强(可选) -- [ ] **世界内聊天气泡** - - 在玩家头顶显示聊天气泡 - - 根据 `bubble` 字段决定是否显示 - - 自动消失机制(3-5 秒) - -- [ ] **聊天命令系统** - - 支持 `/help`, `/whisper`, `/invite` 等命令 - - 命令解析和执行 - -- [ ] **聊天历史持久化** - - 保存到本地存储 - - 重启后恢复聊天记录 - -- [ ] **消息搜索功能** - - 在聊天历史中搜索关键字 - ---- - -### 使用指南 - -#### 基本使用流程 - -```gdscript -# 1. 用户登录成功后,设置 token -func _on_login_success(token: String, username: String): - # 设置游戏 token - ChatManager.set_game_token(token) - - # 连接到聊天服务器 - ChatManager.connect_to_chat_server() - -# 2. 显示聊天界面 -func _show_chat_ui(): - var chat_ui := preload("res://scenes/ui/ChatUI.tscn").instantiate() - add_child(chat_ui) - -# 3. 订阅聊天事件(可选) -func _subscribe_to_chat_events(): - EventSystem.connect_event(EventNames.CHAT_MESSAGE_RECEIVED, _on_message_received) - EventSystem.connect_event(EventNames.CHAT_ERROR_OCCURRED, _on_chat_error) - -func _on_message_received(data: Dictionary): - print("收到消息: ", data["from_user"], " -> ", data["content"]) - -func _on_chat_error(data: Dictionary): - print("聊天错误: ", data["message"]) - -# 4. 发送消息(通过 UI 或代码) -func send_test_message(): - ChatManager.send_chat_message("Hello, world!", "local") - -# 5. 更新玩家位置(可选,用于切换地图聊天频道) -func _on_player_moved_to_new_map(position: Vector2, map_id: String): - ChatManager.update_player_position(position.x, position.y, map_id) -``` - -#### Token 配置说明 - -根据 [test_zulip.js](test_zulip.js) 的测试代码,游戏 token 就是用户的 Zulip API Key。 - -**测试 token**(来自 test_zulip.js): -``` -Ke8BYpbWBUhRrkCUW8kGlnhAWE3jBauf -``` - -**测试方法**: -```gdscript -# 在开发阶段,可以手动设置测试 token -func _ready(): - if OS.has_feature("editor"): - # 编辑器模式下使用测试 token - ChatManager.set_game_token("Ke8BYpbWBUhRrkCUW8kGlnhAWE3jBauf") - ChatManager.connect_to_chat_server() -``` - ---- - -### 文件清单 - -#### 核心文件(9 个) -- `_Core/systems/SocketIOClient.gd` - 284 行 -- `_Core/managers/WebSocketManager.gd` - 329 行 -- `_Core/managers/ChatManager.gd` - 643 行(含会话/历史分离) -- `_Core/managers/AuthManager.gd` - 修改(添加 token 管理) -- `_Core/EventNames.gd` - 修改(添加 7 个常量) -- `scenes/prefabs/ui/ChatMessage.tscn` - 场景文件 -- `scenes/prefabs/ui/ChatMessage.gd` - 185 行 -- `scenes/ui/ChatUI.tscn` - 场景文件 -- `scenes/ui/ChatUI.gd` - 279 行 - - ---- - -**最后更新**: 2026-01-08 -**实施状态**: ✅ 核心功能完成,所有编译错误已修复 -**测试覆盖**: ✅ 100% (128 个测试用例) -**当前状态**: ⏸️ 等待后端修复 Zulip 集成问题 - -## 🎯 已完成的工作 - -### 核心功能 -- ✅ JWT Token 管理系统(access_token + refresh_token) -- ✅ Socket.IO 协议封装(WebSocket + JSON) -- ✅ 聊天管理器(消息发送/接收、频率限制) -- ✅ 会话与历史分离架构 -- ✅ 用户界面(ChatUI + 消息气泡) -- ✅ 主场景集成(自动连接聊天) - -### 代码质量 -- ✅ 所有编译错误已修复 -- ✅ 符合项目规范(类型安全、命名规范) -- ✅ 单元测试覆盖 100% - -### 待后端解决的问题 -- ⚠️ Zulip 用户创建失败(需要预创建组织或禁用集成)