Files
whale-town-end/docs/systems/zulip/quick_tests/test-get-messages.js
angjustinl 8f9a6e7f9d feat(login, zulip): 引入 JWT 验证并重构 API 密钥管理
### 详细变更描述

* **修复 JWT 签名冲突**:重构 `LoginService.generateTokenPair()`,移除载荷(Payload)中的 `iss` (issuer) 与 `aud` (audience) 字段,解决签名校验失败的问题。
* **统一验证逻辑**:更新 `ZulipService` 以调用 `LoginService.verifyToken()`,消除重复的 JWT 校验代码,确保逻辑单一职责化(Single Responsibility)。
* **修复硬编码 API 密钥问题**:消息发送功能不再依赖静态配置,改为从 Redis 动态读取用户真实的 API 密钥。
* **解耦依赖注入**:在 `ZulipModule` 中注入 `AuthModule` 依赖,以支持标准的 Token 验证流程。
* **完善技术文档**:补充了关于 JWT 验证流程及 API 密钥管理逻辑的详细文档。
* **新增测试工具**:添加 `test-get-messages.js` 脚本,用于验证通过 WebSocket 接收消息的功能。
* **更新自动化脚本**:同步更新了 API 密钥验证及用户注册校验的快速测试脚本。
* **端到端功能验证**:确保消息发送逻辑能够正确映射并调用用户真实的 Zulip API 密钥。
2026-01-06 18:51:37 +08:00

261 lines
8.3 KiB
JavaScript

/**
* 测试通过 WebSocket 接收 Zulip 消息
*
* 设计理念:
* - Zulip API Key 永不下发到客户端
* - 所有 Zulip 交互通过游戏服务器的 WebSocket 进行
* - 客户端只接收 chat_render 消息,不直接调用 Zulip API
*
* 功能:
* 1. 登录游戏服务器获取 JWT Token
* 2. 通过 WebSocket 连接游戏服务器
* 3. 在当前地图 (Whale Port) 接收消息
* 4. 切换到 Pumpkin Valley 接收消息
* 5. 统计接收到的消息数量
*
* 使用方法:
* node docs/systems/zulip/quick_tests/test-get-messages.js
*/
const axios = require('axios');
const io = require('socket.io-client');
// 配置
const GAME_SERVER = 'http://localhost:3000';
const TEST_USER = {
username: 'angtest123',
password: 'angtest123',
email: 'angjustinl@163.com'
};
// 测试配置
const TEST_CONFIG = {
whalePortWaitTime: 10000, // 在 Whale Port 等待 10 秒
pumpkinValleyWaitTime: 10000, // 在 Pumpkin Valley 等待 10 秒
totalTimeout: 30000 // 总超时时间 30 秒
};
/**
* 登录游戏服务器获取用户信息
*/
async function loginToGameServer() {
console.log('📝 步骤 1: 登录游戏服务器');
console.log(` 用户名: ${TEST_USER.username}`);
try {
const response = await axios.post(`${GAME_SERVER}/auth/login`, {
identifier: TEST_USER.username,
password: TEST_USER.password
});
if (response.data.success) {
console.log('✅ 登录成功');
console.log(` 用户ID: ${response.data.data.user.id}`);
console.log(` 邮箱: ${response.data.data.user.email}`);
return {
userId: response.data.data.user.id,
username: response.data.data.user.username,
email: response.data.data.user.email,
token: response.data.data.access_token
};
} else {
throw new Error(response.data.message || '登录失败');
}
} catch (error) {
console.error('❌ 登录失败:', error.response?.data?.message || error.message);
throw error;
}
}
/**
* 通过 WebSocket 接收消息
*/
async function receiveMessagesViaWebSocket(userInfo) {
console.log('\n📡 步骤 2: 通过 WebSocket 连接并接收消息');
console.log(` 连接到: ${GAME_SERVER}/game`);
return new Promise((resolve, reject) => {
const socket = io(`${GAME_SERVER}/game`, {
transports: ['websocket'],
timeout: 20000
});
const receivedMessages = {
whalePort: [],
pumpkinValley: []
};
let currentMap = 'whale_port';
let testPhase = 0; // 0: 连接中, 1: Whale Port, 2: Pumpkin Valley, 3: 完成
// 连接成功
socket.on('connect', () => {
console.log('✅ WebSocket 连接成功');
// 发送登录消息
const loginMessage = {
type: 'login',
token: userInfo.token
};
console.log('📤 发送登录消息...');
socket.emit('login', loginMessage);
});
// 登录成功
socket.on('login_success', (data) => {
console.log('✅ 登录成功');
console.log(` 会话ID: ${data.sessionId}`);
console.log(` 用户ID: ${data.userId}`);
console.log(` 当前地图: ${data.currentMap}`);
testPhase = 1;
currentMap = data.currentMap || 'whale_port';
console.log(`\n📬 步骤 3: 在 Whale Port 接收消息 (等待 ${TEST_CONFIG.whalePortWaitTime / 1000} 秒)`);
console.log(' 💡 提示: 请在 Zulip 的 "Whale Port" Stream 发送测试消息');
// 在 Whale Port 等待一段时间
setTimeout(() => {
console.log(`\n📊 Whale Port 接收到 ${receivedMessages.whalePort.length} 条消息`);
// 切换到 Pumpkin Valley
console.log(`\n📤 步骤 4: 切换到 Pumpkin Valley`);
const positionUpdate = {
t: 'position',
x: 150,
y: 400,
mapId: 'pumpkin_valley'
};
socket.emit('position_update', positionUpdate);
testPhase = 2;
currentMap = 'pumpkin_valley';
console.log(`\n📬 步骤 5: 在 Pumpkin Valley 接收消息 (等待 ${TEST_CONFIG.pumpkinValleyWaitTime / 1000} 秒)`);
console.log(' 💡 提示: 请在 Zulip 的 "Pumpkin Valley" Stream 发送测试消息');
// 在 Pumpkin Valley 等待一段时间
setTimeout(() => {
console.log(`\n📊 Pumpkin Valley 接收到 ${receivedMessages.pumpkinValley.length} 条消息`);
testPhase = 3;
console.log('\n📊 测试完成,断开连接...');
socket.disconnect();
}, TEST_CONFIG.pumpkinValleyWaitTime);
}, TEST_CONFIG.whalePortWaitTime);
});
// 接收到消息 (chat_render)
socket.on('chat_render', (data) => {
const timestamp = new Date().toLocaleTimeString('zh-CN');
console.log(`\n📨 [${timestamp}] 收到消息:`);
console.log(` ├─ 发送者: ${data.from}`);
console.log(` ├─ 内容: ${data.txt}`);
console.log(` ├─ Stream: ${data.stream || '未知'}`);
console.log(` ├─ Topic: ${data.topic || '未知'}`);
console.log(` └─ 当前地图: ${currentMap}`);
// 记录消息
const message = {
from: data.from,
content: data.txt,
stream: data.stream,
topic: data.topic,
timestamp: new Date(),
map: currentMap
};
if (testPhase === 1) {
receivedMessages.whalePort.push(message);
} else if (testPhase === 2) {
receivedMessages.pumpkinValley.push(message);
}
});
// 错误处理
socket.on('error', (error) => {
console.error('❌ 收到错误:', JSON.stringify(error, null, 2));
});
// 连接断开
socket.on('disconnect', () => {
console.log('\n🔌 WebSocket 连接已关闭');
resolve(receivedMessages);
});
// 连接错误
socket.on('connect_error', (error) => {
console.error('❌ 连接错误:', error.message);
reject(error);
});
// 总超时保护
setTimeout(() => {
if (socket.connected) {
console.log('\n⏰ 测试超时,关闭连接');
socket.disconnect();
}
}, TEST_CONFIG.totalTimeout);
});
}
/**
* 主测试流程
*/
async function runTest() {
console.log('🚀 开始测试通过 WebSocket 接收 Zulip 消息');
console.log('='.repeat(60));
console.log('📋 设计理念: Zulip API Key 永不下发到客户端');
console.log('📋 所有消息通过游戏服务器的 WebSocket (chat_render) 接收');
console.log('='.repeat(60));
try {
// 步骤1: 登录游戏服务器
const userInfo = await loginToGameServer();
// 步骤2-5: 通过 WebSocket 接收消息
const receivedMessages = await receiveMessagesViaWebSocket(userInfo);
// 步骤6: 统计信息
console.log('\n' + '='.repeat(60));
console.log('📊 测试结果汇总');
console.log('='.repeat(60));
console.log(`✅ Whale Port: ${receivedMessages.whalePort.length} 条消息`);
console.log(`✅ Pumpkin Valley: ${receivedMessages.pumpkinValley.length} 条消息`);
console.log(`📝 总计: ${receivedMessages.whalePort.length + receivedMessages.pumpkinValley.length} 条消息`);
// 显示详细消息列表
if (receivedMessages.whalePort.length > 0) {
console.log('\n📬 Whale Port 消息列表:');
receivedMessages.whalePort.forEach((msg, index) => {
console.log(` ${index + 1}. [${msg.timestamp.toLocaleTimeString()}] ${msg.from}: ${msg.content.substring(0, 50)}${msg.content.length > 50 ? '...' : ''}`);
});
}
if (receivedMessages.pumpkinValley.length > 0) {
console.log('\n📬 Pumpkin Valley 消息列表:');
receivedMessages.pumpkinValley.forEach((msg, index) => {
console.log(` ${index + 1}. [${msg.timestamp.toLocaleTimeString()}] ${msg.from}: ${msg.content.substring(0, 50)}${msg.content.length > 50 ? '...' : ''}`);
});
}
console.log('='.repeat(60));
console.log('\n🎉 测试完成!');
console.log('💡 提示: 客户端通过 WebSocket 接收消息,无需直接访问 Zulip API');
console.log('💡 提示: 访问 https://zulip.xinghangee.icu 查看完整消息历史');
process.exit(0);
} catch (error) {
console.error('\n❌ 测试失败:', error.message);
process.exit(1);
}
}
// 运行测试
runTest();