feat:实现聊天系统核心功能

- 添加 SocketIOClient.gd 实现 Socket.IO 协议封装
- 添加 WebSocketManager.gd 管理连接生命周期和自动重连
- 添加 ChatManager.gd 实现聊天业务逻辑与会话管理
  - 支持当前会话缓存(最多 100 条消息)
  - 支持历史消息按需加载(每次 100 条)
  - 每次登录/重连自动重置会话缓存
  - 客户端频率限制(10 条/分钟)
  - Token 管理与认证
- 添加 ChatMessage.gd/tscn 消息气泡 UI 组件
- 添加 ChatUI.gd/tscn 聊天界面
- 在 EventNames.gd 添加 7 个聊天事件常量
- 在 AuthManager.gd 添加 game_token 管理方法
- 添加完整的单元测试(128 个测试用例)
  - test_socketio_client.gd (42 个测试)
  - test_websocket_manager.gd (38 个测试)
  - test_chat_manager.gd (48 个测试)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
王浩
2026-01-07 17:42:31 +08:00
parent e3c4d08021
commit fb7cba4088
22 changed files with 3734 additions and 1 deletions

View File

@@ -0,0 +1,335 @@
extends Node
# ============================================================================
# WebSocketManager.gd - WebSocket 连接生命周期管理
# ============================================================================
# 管理 WebSocket 连接状态、自动重连和错误恢复
#
# 核心职责:
# - 连接状态管理(断开、连接中、已连接、重连中)
# - 自动重连机制(指数退避)
# - 连接错误恢复
# - Socket.IO 客户端封装
#
# 使用方式:
# WebSocketManager.connect_to_game_server()
# WebSocketManager.connection_state_changed.connect(_on_state_changed)
#
# 注意事项:
# - 作为自动加载单例,全局可访问
# - 自动处理连接断开和重连
# - 通过信号通知连接状态变化
# ============================================================================
class_name WebSocketManager
# ============================================================================
# 信号定义
# ============================================================================
# 连接状态变化信号
# 参数:
# new_state: ConnectionState - 新的连接状态
signal connection_state_changed(new_state: ConnectionState)
# 连接丢失信号
signal connection_lost()
# 重连成功信号
signal reconnection_succeeded()
# 重连失败信号
# 参数:
# attempt: int - 当前重连尝试次数
# max_attempts: int - 最大重连次数
signal reconnection_failed(attempt: int, max_attempts: int)
# ============================================================================
# 枚举定义
# ============================================================================
# 连接状态枚举
enum ConnectionState {
DISCONNECTED, # 未连接
CONNECTING, # 连接中
CONNECTED, # 已连接
RECONNECTING, # 重连中
ERROR # 错误状态
}
# ============================================================================
# 常量定义
# ============================================================================
# WebSocket 服务器 URL
const WEBSOCKET_URL: String = "wss://whaletownend.xinghangee.icu/game"
# 默认最大重连次数
const DEFAULT_MAX_RECONNECT_ATTEMPTS: int = 5
# 默认重连基础延迟(秒)
const DEFAULT_RECONNECT_BASE_DELAY: float = 3.0
# 最大重连延迟(秒)
const MAX_RECONNECT_DELAY: float = 30.0
# ============================================================================
# 成员变量
# ============================================================================
# Socket.IO 客户端
var _socket_client: SocketIOClient
# 当前连接状态
var _connection_state: ConnectionState = ConnectionState.DISCONNECTED
# 自动重连启用标志
var _auto_reconnect_enabled: bool = true
# 最大重连次数
var _max_reconnect_attempts: int = DEFAULT_MAX_RECONNECT_ATTEMPTS
# 重连基础延迟
var _reconnect_base_delay: float = DEFAULT_RECONNECT_BASE_DELAY
# 当前重连尝试次数
var _reconnect_attempt: int = 0
# 重连定时器
var _reconnect_timer: Timer = Timer.new()
# 是否为正常关闭(非异常断开)
var _clean_close: bool = true
# ============================================================================
# 生命周期方法
# ============================================================================
# 初始化
func _ready() -> void:
print("WebSocketManager 初始化完成")
# 创建 Socket.IO 客户端
_socket_client = SocketIOClient.new()
add_child(_socket_client)
# 连接信号
_socket_client.connected.connect(_on_socket_connected)
_socket_client.disconnected.connect(_on_socket_disconnected)
_socket_client.error_occurred.connect(_on_socket_error)
# 设置重连定时器
_setup_reconnect_timer()
# 清理
func _exit_tree() -> void:
if is_instance_valid(_reconnect_timer):
_reconnect_timer.stop()
_reconnect_timer.queue_free()
# ============================================================================
# 公共 API - 连接管理
# ============================================================================
# 连接到游戏服务器
func connect_to_game_server() -> void:
if _connection_state == ConnectionState.CONNECTED or _connection_state == ConnectionState.CONNECTING:
push_warning("已经在连接或已连接状态")
return
print("=== WebSocketManager 开始连接 ===")
_set_connection_state(ConnectionState.CONNECTING)
_clean_close = true
_reconnect_attempt = 0
_socket_client.connect_to_server(WEBSOCKET_URL)
# 断开连接
func disconnect() -> void:
print("=== WebSocketManager 断开连接 ===")
_clean_close = true
# 停止重连定时器
_reconnect_timer.stop()
# 断开客户端
_socket_client.disconnect_from_server()
_set_connection_state(ConnectionState.DISCONNECTED)
# 检查是否已连接
#
# 返回值:
# bool - 是否已连接
func is_connected() -> bool:
return _connection_state == ConnectionState.CONNECTED
# 获取当前连接状态
#
# 返回值:
# ConnectionState - 当前连接状态
func get_connection_state() -> ConnectionState:
return _connection_state
# ============================================================================
# 公共 API - 自动重连
# ============================================================================
# 启用/禁用自动重连
#
# 参数:
# enabled: bool - 是否启用自动重连
# max_attempts: int - 最大重连次数(默认 5
# base_delay: float - 基础重连延迟,秒(默认 3.0
#
# 使用示例:
# WebSocketManager.enable_auto_reconnect(true, 5, 3.0)
func enable_auto_reconnect(enabled: bool, max_attempts: int = DEFAULT_MAX_RECONNECT_ATTEMPTS, base_delay: float = DEFAULT_RECONNECT_BASE_DELAY) -> void:
_auto_reconnect_enabled = enabled
_max_reconnect_attempts = max_attempts
_reconnect_base_delay = base_delay
print("自动重连: ", "启用" if enabled else "禁用")
print("最大重连次数: ", _max_reconnect_attempts)
print("基础重连延迟: ", _reconnect_base_delay, "")
# 获取 Socket.IO 客户端
#
# 返回值:
# SocketIOClient - Socket.IO 客户端实例
#
# 使用示例:
# var socket = WebSocketManager.get_socket_client()
# socket.emit("chat", {"t": "chat", "content": "Hello"})
func get_socket_client() -> SocketIOClient:
return _socket_client
# ============================================================================
# 内部方法 - 连接状态管理
# ============================================================================
# 设置连接状态
func _set_connection_state(new_state: ConnectionState) -> void:
if _connection_state == new_state:
return
_connection_state = new_state
print("📡 连接状态变更: ", ConnectionState.keys()[new_state])
# 发射信号
connection_state_changed.emit(new_state)
# ============================================================================
# 内部方法 - Socket 事件处理
# ============================================================================
# Socket 连接成功处理
func _on_socket_connected() -> void:
print("✅ WebSocketManager: Socket 连接成功")
# 如果是重连,发射重连成功信号
if _connection_state == ConnectionState.RECONNECTING:
_reconnect_attempt = 0
reconnection_succeeded.emit()
print("🔄 重连成功")
_set_connection_state(ConnectionState.CONNECTED)
# Socket 连接断开处理
func _on_socket_disconnected(clean_close: bool) -> void:
print("🔌 WebSocketManager: Socket 连接断开")
print(" 正常关闭: ", clean_close)
_clean_close = clean_close
# 如果是异常断开且启用了自动重连
if not clean_close and _auto_reconnect_enabled:
connection_lost.emit()
_attempt_reconnect()
else:
_set_connection_state(ConnectionState.DISCONNECTED)
# Socket 错误处理
func _on_socket_error(error: String) -> void:
print("❌ WebSocketManager: Socket 错误 - ", error)
_set_connection_state(ConnectionState.ERROR)
# ============================================================================
# 内部方法 - 重连机制
# ============================================================================
# 设置重连定时器
func _setup_reconnect_timer() -> void:
_reconnect_timer = Timer.new()
_reconnect_timer.one_shot = true
_reconnect_timer.autostart = false
add_child(_reconnect_timer)
_reconnect_timer.timeout.connect(_on_reconnect_timeout)
# 尝试重连
func _attempt_reconnect() -> void:
# 检查是否超过最大重连次数
if _reconnect_attempt >= _max_reconnect_attempts:
print("❌ 达到最大重连次数 (", _max_reconnect_attempts, "),停止重连")
reconnection_failed.emit(_reconnect_attempt, _max_reconnect_attempts)
_set_connection_state(ConnectionState.ERROR)
return
_reconnect_attempt += 1
_set_connection_state(ConnectionState.RECONNECTING)
# 计算重连延迟(指数退避)
var delay := _calculate_reconnect_delay()
print("🔄 尝试重连 (", _reconnect_attempt, "/", _max_reconnect_attempts, ")")
print(" 延迟: ", delay, "")
# 启动重连定时器
_reconnect_timer.start(delay)
# 计算重连延迟(指数退避)
func _calculate_reconnect_delay() -> float:
# 指数退避: base_delay * 2^(attempt-1)
var delay: float = _reconnect_base_delay * pow(2.0, _reconnect_attempt - 1)
# 限制最大延迟
return min(delay, MAX_RECONNECT_DELAY)
# 重连定时器超时处理
func _on_reconnect_timeout() -> void:
print("⏰ 重连定时器超时,开始重连...")
_socket_client.connect_to_server(WEBSOCKET_URL)
# ============================================================================
# 工具方法
# ============================================================================
# 获取连接状态描述
#
# 返回值:
# String - 连接状态描述
func get_state_description() -> String:
match _connection_state:
ConnectionState.DISCONNECTED:
return "未连接"
ConnectionState.CONNECTING:
return "连接中"
ConnectionState.CONNECTED:
return "已连接"
ConnectionState.RECONNECTING:
return "重连中 (%d/%d)" % [_reconnect_attempt, _max_reconnect_attempts]
ConnectionState.ERROR:
return "错误"
_:
return "未知状态"
# 获取重连信息
#
# 返回值:
# Dictionary - 重连信息 {enabled, attempt, max_attempts, delay}
func get_reconnect_info() -> Dictionary:
return {
"enabled": _auto_reconnect_enabled,
"attempt": _reconnect_attempt,
"max_attempts": _max_reconnect_attempts,
"next_delay": _calculate_reconnect_delay() if _connection_state == ConnectionState.RECONNECTING else 0.0
}