Files
whale-town-front/_Core/systems/SocketIOClient.gd
王浩 fb7cba4088 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>
2026-01-07 17:42:31 +08:00

305 lines
9.1 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends Node
# ============================================================================
# SocketIOClient.gd - Socket.IO 协议封装
# ============================================================================
# 封装 Godot 的 WebSocketPeer实现简化的 Socket.IO 协议
#
# 核心职责:
# - WebSocket 连接管理
# - Socket.IO 消息协议(简化版 JSON 格式)
# - 事件监听器管理
# - 消息发送/接收
#
# 注意事项:
# - 后端使用简化版 Socket.IO纯 JSON无二进制协议
# - 发送消息使用 "t" 字段标识事件类型
# - 所有消息通过 JSON 序列化
# ============================================================================
class_name SocketIOClient
# ============================================================================
# 信号定义
# ============================================================================
# 连接成功信号
signal connected()
# 连接断开信号
# 参数:
# clean_close: bool - 是否为正常关闭
signal disconnected(clean_close: bool)
# 事件接收信号
# 参数:
# event_name: String - 事件名称(从 "t" 字段提取)
# data: Dictionary - 事件数据
signal event_received(event_name: String, data: Dictionary)
# 错误发生信号
# 参数:
# error: String - 错误信息
signal error_occurred(error: String)
# ============================================================================
# 常量定义
# ============================================================================
# 连接状态枚举
enum ConnectionState {
DISCONNECTED, # 未连接
CONNECTING, # 连接中
CONNECTED # 已连接
}
# ============================================================================
# 成员变量
# ============================================================================
# WebSocket 客户端
var _websocket_peer: WebSocketPeer = WebSocketPeer.new()
# 连接状态
var _connection_state: ConnectionState = ConnectionState.DISCONNECTED
# 服务器 URL
var _server_url: String = ""
# 事件监听器: {event_name: [Callable, ...]}
var _event_listeners: Dictionary = {}
# ============================================================================
# 生命周期方法
# ============================================================================
# 初始化
func _ready() -> void:
print("SocketIOClient 初始化完成")
# 处理进程 - 轮询 WebSocket 消息
func _process(_delta: float) -> void:
# 轮询 WebSocket 状态
_websocket_peer.poll()
# 检查连接状态变化
var new_state: ConnectionState = _get_connection_state()
if new_state != _connection_state:
_connection_state = new_state
_on_state_changed(_connection_state)
# 处理接收到的消息
_process_incoming_messages()
# ============================================================================
# 公共 API - 连接管理
# ============================================================================
# 连接到服务器
#
# 参数:
# url: String - WebSocket 服务器 URL (ws:// 或 wss://)
#
# 使用示例:
# socket_client.connect_to_server("wss://example.com/game")
func connect_to_server(url: String) -> void:
if _connection_state == ConnectionState.CONNECTED:
push_warning("已经连接到服务器,无需重复连接")
return
_server_url = url
print("=== SocketIOClient 开始连接 ===")
print("服务器 URL: ", _server_url)
# 创建 WebSocket 客户端
_websocket_peer = WebSocketPeer.new()
# 发起连接
var error := _websocket_peer.connect_to_url(url)
if error != OK:
push_error("WebSocket 连接失败: %s" % error)
error_occurred.emit("WebSocket 连接失败")
return
_connection_state = ConnectionState.CONNECTING
print("WebSocket 连接中...")
# 断开连接
func disconnect_from_server() -> void:
if _connection_state == ConnectionState.DISCONNECTED:
return
print("=== SocketIOClient 断开连接 ===")
_websocket_peer.close()
_connection_state = ConnectionState.DISCONNECTED
disconnected.emit(true)
# 检查是否已连接
#
# 返回值:
# bool - 是否已连接
func is_connected() -> bool:
return _connection_state == ConnectionState.CONNECTED
# ============================================================================
# 公共 API - 事件发送
# ============================================================================
# 发送事件(对应 socket.emit
#
# 参数:
# event_name: String - 事件名称(如 "login", "chat"
# data: Dictionary - 事件数据
#
# 使用示例:
# socket_client.emit("login", {"type": "login", "token": "abc123"})
# socket_client.emit("chat", {"t": "chat", "content": "Hello", "scope": "local"})
func emit(event_name: String, data: Dictionary) -> void:
if not is_connected():
push_error("无法发送事件: 未连接到服务器")
error_occurred.emit("未连接到服务器")
return
# 序列化为 JSON
var json_string := JSON.stringify(data)
if json_string.is_empty():
push_error("JSON 序列化失败")
error_occurred.emit("JSON 序列化失败")
return
# 发送数据包
var packet := json_string.to_utf8_buffer()
var error := _websocket_peer.send(packet)
if error != OK:
push_error("发送数据包失败: %s" % error)
error_occurred.emit("发送数据包失败")
return
print("📤 发送事件: ", event_name)
print(" 数据: ", json_string if json_string.length() < 200 else json_string.substr(0, 200) + "...")
# ============================================================================
# 公共 API - 事件监听
# ============================================================================
# 添加事件监听器(对应 socket.on
#
# 参数:
# event_name: String - 事件名称
# callback: Callable - 回调函数,接收 data: Dictionary 参数
#
# 使用示例:
# socket_client.add_event_listener("chat_render", func(data):
# print("收到消息: ", data.txt)
# )
func add_event_listener(event_name: String, callback: Callable) -> void:
if not _event_listeners.has(event_name):
_event_listeners[event_name] = []
_event_listeners[event_name].append(callback)
print("注册事件监听器: ", event_name, " -> ", callback)
# 移除事件监听器
#
# 参数:
# event_name: String - 事件名称
# callback: Callable - 要移除的回调函数
func remove_event_listener(event_name: String, callback: Callable) -> void:
if not _event_listeners.has(event_name):
return
var listeners: Array = _event_listeners[event_name]
listeners.erase(callback)
if listeners.is_empty():
_event_listeners.erase(event_name)
print("移除事件监听器: ", event_name, " -> ", callback)
# ============================================================================
# 内部方法 - 消息处理
# ============================================================================
# 处理接收到的消息
func _process_incoming_messages() -> void:
# 检查是否有可用数据包
while _websocket_peer.get_available_packet_count() > 0:
# 接收数据包
var packet: PackedByteArray = _websocket_peer.get_packet()
# 解析为字符串
var json_string: String = packet.get_string_from_utf8()
# 解析 JSON
var json := JSON.new()
var parse_result := json.parse(json_string)
if parse_result != OK:
push_error("JSON 解析失败: " + json_string)
error_occurred.emit("JSON 解析失败")
continue
var data: Dictionary = json.data
# 提取事件类型(从 "t" 字段)
var event_name: String = data.get("t", "")
if event_name.is_empty():
# 如果没有 "t" 字段,尝试其他方式识别
if data.has("type"):
event_name = data["type"]
elif data.has("code"):
event_name = "error"
else:
push_warning("收到未知格式消息: " + json_string)
continue
print("📨 收到事件: ", event_name)
print(" 数据: ", json_string if json_string.length() < 200 else json_string.substr(0, 200) + "...")
# 调用事件监听器
_notify_event_listeners(event_name, data)
# 发射通用信号
event_received.emit(event_name, data)
# 通知事件监听器
func _notify_event_listeners(event_name: String, data: Dictionary) -> void:
if not _event_listeners.has(event_name):
return
var listeners: Array = _event_listeners[event_name]
for callback in listeners:
if callback.is_valid():
callback.call(data)
# ============================================================================
# 内部方法 - 状态管理
# ============================================================================
# 获取当前连接状态
func _get_connection_state() -> ConnectionState:
match _websocket_peer.get_ready_state():
WebSocketPeer.STATE_CONNECTING:
return ConnectionState.CONNECTING
WebSocketPeer.STATE_OPEN:
return ConnectionState.CONNECTED
WebSocketPeer.STATE_CLOSING:
return ConnectionState.CONNECTING
WebSocketPeer.STATE_CLOSED:
return ConnectionState.DISCONNECTED
_:
return ConnectionState.DISCONNECTED
# 连接状态变化处理
func _on_state_changed(new_state: ConnectionState) -> void:
match new_state:
ConnectionState.CONNECTED:
print("✅ WebSocket 连接成功")
connected.emit()
ConnectionState.DISCONNECTED:
print("🔌 WebSocket 连接断开")
disconnected.emit(false)
ConnectionState.CONNECTING:
print("⏳ WebSocket 连接中...")