* **新增 Zulip 模块**:包含完整的集成服务,涵盖客户端池(client pool)、会话管理及事件处理。 * **新增 WebSocket 网关**:用于处理 Zulip 的实时事件监听与双向通信。 * **新增安全服务**:支持 API 密钥加密存储及凭据的安全管理。 * **新增配置管理服务**:支持配置热加载(hot-reload),实现动态配置更新。 * **新增错误处理与监控服务**:提升系统的可靠性与可观测性。 * **新增消息过滤服务**:用于内容校验及速率限制(流控)。 * **新增流初始化与会话清理服务**:优化资源管理与回收。 * **完善测试覆盖**:包含单元测试及端到端(e2e)集成测试。 * **完善详细文档**:包括 API 参考手册、配置指南及集成概述。 * **新增地图配置系统**:实现游戏地点与 Zulip Stream(频道)及 Topic(话题)的逻辑映射。 * **新增环境变量配置**:涵盖 Zulip 服务器地址、身份验证及监控相关设置。 * **更新 App 模块**:注册并启用新的 Zulip 集成模块。 * **更新 Redis 接口**:以支持增强型的会话管理功能。 * **实现 WebSocket 协议支持**:确保与 Zulip 之间的实时双向通信。
9.2 KiB
9.2 KiB
WebSocket 协议详解
协议概述
Zulip 集成系统使用 WebSocket 协议实现游戏客户端与服务器之间的实时双向通信。所有消息采用 JSON 格式编码。
连接生命周期
1. 建立连接
Client Server
| |
|-------- WebSocket Connect --------->|
| |
|<------- Connection Accepted --------|
| |
2. 认证握手
Client Server
| |
|-------- login message ------------->|
| |
| [验证 Token] |
| [创建 Zulip Client] |
| [注册 Event Queue] |
| [创建 Session] |
| |
|<------- login_success --------------|
| |
3. 消息交换
Client Server Zulip
| | |
|-------- chat message -------------->| |
| |-------- POST /messages ---------->|
| |<------- 200 OK -------------------|
|<------- chat_sent ------------------| |
| | |
| |<------- Event Queue Message ------|
|<------- chat_render ----------------| |
| | |
4. 断开连接
Client Server
| |
|-------- logout message ------------>|
| |
| [清理 Session] |
| [注销 Event Queue] |
| [销毁 Zulip Client] |
| |
|<------- logout_success -------------|
| |
|-------- WebSocket Close ----------->|
| |
消息格式规范
消息结构
所有消息都是 JSON 对象,包含以下基本字段:
| 字段 | 类型 | 说明 |
|---|---|---|
type 或 t |
string | 消息类型标识 |
| 其他字段 | any | 根据消息类型不同而变化 |
消息类型标识
- 客户端发送的消息使用
type或t字段 - 服务器响应的消息统一使用
t字段
客户端消息
LOGIN - 登录认证
{
"type": "login",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | string | 是 | 固定值 "login" |
| token | string | 是 | 游戏认证 Token |
CHAT - 发送聊天消息
{
"t": "chat",
"content": "Hello, everyone!",
"scope": "local"
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| t | string | 是 | 固定值 "chat" |
| content | string | 是 | 消息内容 (1-1000 字符) |
| scope | string | 是 | 消息范围 |
scope 取值:
"local": 当前地图的默认 Topic"topic_name": 指定的 Topic 名称
POSITION - 位置更新
{
"t": "position",
"x": 150.5,
"y": 200.3,
"mapId": "novice_village"
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| t | string | 是 | 固定值 "position" |
| x | number | 是 | X 坐标 |
| y | number | 是 | Y 坐标 |
| mapId | string | 是 | 地图 ID |
LOGOUT - 登出
{
"type": "logout"
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | string | 是 | 固定值 "logout" |
服务器消息
LOGIN_SUCCESS - 登录成功
{
"t": "login_success",
"sessionId": "sess_abc123def456",
"currentMap": "novice_village",
"username": "player_name",
"stream": "Novice Village"
}
| 字段 | 类型 | 说明 |
|---|---|---|
| t | string | 固定值 "login_success" |
| sessionId | string | 会话 ID |
| currentMap | string | 当前地图 ID |
| username | string | 用户名 |
| stream | string | 当前 Zulip Stream |
CHAT_SENT - 消息发送确认
{
"t": "chat_sent",
"messageId": "msg_789xyz",
"timestamp": 1703500800000
}
| 字段 | 类型 | 说明 |
|---|---|---|
| t | string | 固定值 "chat_sent" |
| messageId | string | Zulip 消息 ID |
| timestamp | number | 发送时间戳 (毫秒) |
CHAT_RENDER - 接收聊天消息
{
"t": "chat_render",
"from": "other_player",
"txt": "Hi there!",
"bubble": true,
"timestamp": 1703500800000,
"stream": "Novice Village",
"topic": "General"
}
| 字段 | 类型 | 说明 |
|---|---|---|
| t | string | 固定值 "chat_render" |
| from | string | 发送者名称 |
| txt | string | 消息内容 |
| bubble | boolean | 是否显示气泡 |
| timestamp | number | 消息时间戳 |
| stream | string | 来源 Stream |
| topic | string | 来源 Topic |
POSITION_UPDATED - 位置更新确认
{
"t": "position_updated",
"stream": "Novice Village",
"topic": "General"
}
| 字段 | 类型 | 说明 |
|---|---|---|
| t | string | 固定值 "position_updated" |
| stream | string | 新的 Zulip Stream |
| topic | string | 新的 Zulip Topic |
LOGOUT_SUCCESS - 登出成功
{
"t": "logout_success"
}
ERROR - 错误消息
{
"t": "error",
"code": "RATE_LIMIT",
"message": "消息发送过于频繁,请稍后再试",
"details": {
"retryAfter": 60
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
| t | string | 固定值 "error" |
| code | string | 错误码 |
| message | string | 错误描述 |
| details | object | 可选,额外错误信息 |
心跳机制
客户端心跳
客户端应每 30 秒发送一次心跳消息:
{
"t": "ping"
}
服务器响应
{
"t": "pong",
"timestamp": 1703500800000
}
超时处理
- 服务器在 60 秒内未收到任何消息将断开连接
- 客户端应在连接断开后自动重连
重连策略
指数退避算法
重试间隔 = min(baseDelay * 2^attempt, maxDelay)
baseDelay = 1000ms
maxDelay = 30000ms
重连流程
- 检测到连接断开
- 等待重试间隔
- 尝试重新连接
- 连接成功后重新发送 login 消息
- 恢复会话状态
示例代码
class ReconnectingWebSocket {
private baseDelay = 1000;
private maxDelay = 30000;
private attempt = 0;
private getDelay(): number {
const delay = Math.min(
this.baseDelay * Math.pow(2, this.attempt),
this.maxDelay
);
this.attempt++;
return delay;
}
private resetDelay(): void {
this.attempt = 0;
}
async reconnect(): Promise<void> {
const delay = this.getDelay();
console.log(`等待 ${delay}ms 后重连...`);
await new Promise(resolve => setTimeout(resolve, delay));
try {
await this.connect();
this.resetDelay();
} catch (error) {
await this.reconnect();
}
}
}
消息序列化
发送消息
function sendMessage(socket: WebSocket, message: object): void {
const json = JSON.stringify(message);
socket.send(json);
}
接收消息
socket.onmessage = (event: MessageEvent) => {
try {
const message = JSON.parse(event.data);
handleMessage(message);
} catch (error) {
console.error('消息解析失败:', error);
}
};
并发处理
消息顺序
- 同一客户端的消息按发送顺序处理
- 不同客户端的消息可能并发处理
- 服务器响应顺序可能与请求顺序不同
消息确认
对于需要确认的操作(如发送聊天消息),客户端应:
- 生成唯一的请求 ID
- 等待对应的响应
- 设置超时处理
async function sendChatWithConfirmation(
socket: WebSocket,
content: string,
timeout: number = 5000
): Promise<void> {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error('发送超时'));
}, timeout);
const handler = (event: MessageEvent) => {
const message = JSON.parse(event.data);
if (message.t === 'chat_sent') {
clearTimeout(timer);
socket.removeEventListener('message', handler);
resolve();
} else if (message.t === 'error') {
clearTimeout(timer);
socket.removeEventListener('message', handler);
reject(new Error(message.message));
}
};
socket.addEventListener('message', handler);
socket.send(JSON.stringify({
t: 'chat',
content: content,
scope: 'local'
}));
});
}
安全考虑
Token 安全
- Token 仅在 login 消息中传输一次
- 服务器验证后不再需要 Token
- Token 应有合理的过期时间
消息验证
- 服务器验证所有消息格式
- 拒绝格式错误的消息
- 记录异常消息日志
防重放攻击
- 使用时间戳验证消息新鲜度
- 拒绝过期的消息
- 检测重复的消息 ID