forked from datawhale/whale-town-end
### 详细变更描述 * **修复 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 密钥。
261 lines
8.3 KiB
JavaScript
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();
|