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 密钥。
This commit is contained in:
260
docs/systems/zulip/quick_tests/test-get-messages.js
Normal file
260
docs/systems/zulip/quick_tests/test-get-messages.js
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* 测试通过 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();
|
||||
@@ -1,15 +1,102 @@
|
||||
const zulip = require('zulip-js');
|
||||
const axios = require('axios');
|
||||
|
||||
async function listSubscriptions() {
|
||||
console.log('🔧 检查用户订阅的 Streams...');
|
||||
|
||||
const config = {
|
||||
username: 'angjustinl@mail.angforever.top',
|
||||
apiKey: 'lCPWC...pqNfGF8',
|
||||
realm: 'https://zulip.xinghangee.icu/'
|
||||
};
|
||||
// 配置
|
||||
const GAME_SERVER = 'http://localhost:3000';
|
||||
const TEST_USER = {
|
||||
username: 'angtest123',
|
||||
password: 'angtest123',
|
||||
email: 'angjustinl@163.com'
|
||||
};
|
||||
|
||||
/**
|
||||
* 登录游戏服务器获取用户信息
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用密码获取 Zulip API Key
|
||||
*/
|
||||
async function getZulipApiKey(email, password) {
|
||||
console.log('\n📝 步骤 2: 获取 Zulip API Key');
|
||||
console.log(` 邮箱: ${email}`);
|
||||
|
||||
try {
|
||||
// Zulip API 使用 Basic Auth 和 form data
|
||||
const response = await axios.post(
|
||||
'https://zulip.xinghangee.icu/api/v1/fetch_api_key',
|
||||
`username=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (response.data.result === 'success') {
|
||||
console.log('✅ 成功获取 API Key');
|
||||
console.log(` API Key: ${response.data.api_key.substring(0, 10)}...`);
|
||||
console.log(` 用户ID: ${response.data.user_id}`);
|
||||
return {
|
||||
apiKey: response.data.api_key,
|
||||
email: response.data.email,
|
||||
userId: response.data.user_id
|
||||
};
|
||||
} else {
|
||||
throw new Error(response.data.msg || '获取 API Key 失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 获取 API Key 失败:', error.response?.data?.msg || error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function listSubscriptions() {
|
||||
console.log('🚀 开始测试用户订阅的 Streams');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
try {
|
||||
// 步骤1: 登录游戏服务器
|
||||
const userInfo = await loginToGameServer();
|
||||
|
||||
// 步骤2: 获取 Zulip API Key
|
||||
const zulipAuth = await getZulipApiKey(userInfo.email, TEST_USER.password);
|
||||
|
||||
console.log('\n📝 步骤 3: 检查用户订阅的 Streams');
|
||||
|
||||
const config = {
|
||||
username: zulipAuth.email,
|
||||
apiKey: zulipAuth.apiKey,
|
||||
realm: 'https://zulip.xinghangee.icu/'
|
||||
};
|
||||
|
||||
const client = await zulip(config);
|
||||
|
||||
// 获取用户信息
|
||||
@@ -29,15 +116,15 @@ async function listSubscriptions() {
|
||||
});
|
||||
|
||||
// 检查是否有 "Novice Village"
|
||||
const noviceVillage = subscriptions.subscriptions.find(s => s.name === 'Novice Village');
|
||||
const noviceVillage = subscriptions.subscriptions.find(s => s.name === 'Pumpkin Valley');
|
||||
if (noviceVillage) {
|
||||
console.log('\n✅ "Novice Village" Stream 已存在!');
|
||||
console.log('\n✅ "Pumpkin Valley" Stream 已存在!');
|
||||
|
||||
// 测试发送消息
|
||||
console.log('\n📤 测试发送消息...');
|
||||
const result = await client.messages.send({
|
||||
type: 'stream',
|
||||
to: 'Novice Village',
|
||||
to: 'Pumpkin Valley',
|
||||
subject: 'General',
|
||||
content: '测试消息:系统集成测试成功 🎮'
|
||||
});
|
||||
@@ -48,7 +135,7 @@ async function listSubscriptions() {
|
||||
console.log('❌ 消息发送失败:', result.msg);
|
||||
}
|
||||
} else {
|
||||
console.log('\n⚠️ "Novice Village" Stream 不存在');
|
||||
console.log('\n⚠️ "Pumpkin Valley" Stream 不存在');
|
||||
console.log('💡 请在 Zulip 网页界面手动创建该 Stream,或使用管理员账号创建');
|
||||
|
||||
// 尝试发送到第一个可用的 Stream
|
||||
@@ -79,7 +166,9 @@ async function listSubscriptions() {
|
||||
if (error.response) {
|
||||
console.error('响应数据:', error.response.data);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
listSubscriptions();
|
||||
|
||||
@@ -1,127 +1,183 @@
|
||||
const io = require('socket.io-client');
|
||||
const axios = require('axios');
|
||||
|
||||
// 配置
|
||||
const GAME_SERVER = 'http://localhost:3000';
|
||||
const TEST_USER = {
|
||||
username: 'angtest123',
|
||||
password: 'angtest123',
|
||||
email: 'angjustinl@163.com'
|
||||
};
|
||||
|
||||
/**
|
||||
* 登录游戏服务器获取token
|
||||
*/
|
||||
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.nickname}`);
|
||||
console.log(` 邮箱: ${response.data.data.user.email}`);
|
||||
console.log(` Token: ${response.data.data.access_token.substring(0, 20)}...`);
|
||||
return {
|
||||
userId: response.data.data.user.id,
|
||||
username: response.data.data.user.username,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用用户 API Key 测试 Zulip 集成
|
||||
async function testWithUserApiKey() {
|
||||
console.log('🚀 使用用户 API Key 测试 Zulip 集成...');
|
||||
console.log('📡 用户 API Key: lCPWCPfGh7WU...pqNfGF8');
|
||||
console.log('📡 Zulip 服务器: https://zulip.xinghangee.icu/');
|
||||
console.log('📡 游戏服务器: http://localhost:3000/game');
|
||||
console.log('🚀 开始测试用户 API Key 的 Zulip 集成');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
const socket = io('http://localhost:3000/game', {
|
||||
transports: ['websocket'],
|
||||
timeout: 20000
|
||||
});
|
||||
|
||||
let testStep = 0;
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('✅ WebSocket 连接成功');
|
||||
testStep = 1;
|
||||
try {
|
||||
// 登录获取 token
|
||||
const userInfo = await loginToGameServer();
|
||||
|
||||
// 使用包含用户 API Key 的 token
|
||||
const loginMessage = {
|
||||
type: 'login',
|
||||
token: 'lCPWCPfGh7...fGF8_user_token'
|
||||
};
|
||||
|
||||
console.log('📤 步骤 1: 发送登录消息(使用用户 API Key)');
|
||||
socket.emit('login', loginMessage);
|
||||
});
|
||||
console.log('\n📡 步骤 2: 连接 WebSocket 并测试 Zulip 集成');
|
||||
console.log(` 连接到: ${GAME_SERVER}/game`);
|
||||
|
||||
socket.on('login_success', (data) => {
|
||||
console.log('✅ 步骤 1 完成: 登录成功');
|
||||
console.log(' 会话ID:', data.sessionId);
|
||||
console.log(' 用户ID:', data.userId);
|
||||
console.log(' 用户名:', data.username);
|
||||
console.log(' 当前地图:', data.currentMap);
|
||||
testStep = 2;
|
||||
const socket = io(`${GAME_SERVER}/game`, {
|
||||
transports: ['websocket'],
|
||||
timeout: 20000
|
||||
});
|
||||
|
||||
// 等待 Zulip 客户端初始化
|
||||
console.log('⏳ 等待 3 秒让 Zulip 客户端初始化...');
|
||||
setTimeout(() => {
|
||||
const chatMessage = {
|
||||
t: 'chat',
|
||||
content: '🎮 【用户API Key测试】来自游戏的消息!\\n' +
|
||||
'时间: ' + new Date().toLocaleString() + '\\n' +
|
||||
'使用用户 API Key 发送此消息。',
|
||||
scope: 'local'
|
||||
let testStep = 0;
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('✅ WebSocket 连接成功');
|
||||
testStep = 1;
|
||||
|
||||
// 使用真实的 JWT token
|
||||
const loginMessage = {
|
||||
type: 'login',
|
||||
token: userInfo.token
|
||||
};
|
||||
|
||||
console.log('📤 步骤 2: 发送消息到 Zulip(使用用户 API Key)');
|
||||
console.log(' 目标 Stream: Whale Port');
|
||||
socket.emit('chat', chatMessage);
|
||||
}, 3000);
|
||||
});
|
||||
console.log('📤 步骤 3: 发送登录消息(使用 JWT Token)');
|
||||
socket.emit('login', loginMessage);
|
||||
});
|
||||
|
||||
socket.on('chat_sent', (data) => {
|
||||
console.log('✅ 步骤 2 完成: 消息发送成功');
|
||||
console.log(' 响应:', JSON.stringify(data, null, 2));
|
||||
|
||||
// 只在第一次收到 chat_sent 时发送第二条消息
|
||||
if (testStep === 2) {
|
||||
testStep = 3;
|
||||
socket.on('login_success', (data) => {
|
||||
console.log('✅ 步骤 3 完成: 登录成功');
|
||||
console.log(' 会话ID:', data.sessionId);
|
||||
console.log(' 用户ID:', data.userId);
|
||||
console.log(' 用户名:', data.username);
|
||||
console.log(' 当前地图:', data.currentMap);
|
||||
testStep = 2;
|
||||
|
||||
// 等待 Zulip 客户端初始化
|
||||
console.log('\n⏳ 等待 3 秒让 Zulip 客户端初始化...');
|
||||
setTimeout(() => {
|
||||
// 先切换到 Pumpkin Valley 地图
|
||||
console.log('📤 步骤 3: 切换到 Pumpkin Valley 地图');
|
||||
const positionUpdate = {
|
||||
t: 'position',
|
||||
x: 150,
|
||||
y: 400,
|
||||
mapId: 'pumpkin_valley'
|
||||
const chatMessage = {
|
||||
t: 'chat',
|
||||
content: `🎮 【用户API Key测试】来自 ${userInfo.username} 的消息!\n` +
|
||||
`时间: ${new Date().toLocaleString()}\n` +
|
||||
`使用真实 API Key 发送此消息。`,
|
||||
scope: 'local'
|
||||
};
|
||||
socket.emit('position_update', positionUpdate);
|
||||
|
||||
// 等待位置更新后发送消息
|
||||
console.log('📤 步骤 4: 发送消息到 Zulip(使用真实 API Key)');
|
||||
console.log(' 目标 Stream: Whale Port');
|
||||
socket.emit('chat', chatMessage);
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
socket.on('chat_sent', (data) => {
|
||||
console.log('✅ 步骤 4 完成: 消息发送成功');
|
||||
console.log(' 响应:', JSON.stringify(data, null, 2));
|
||||
|
||||
// 只在第一次收到 chat_sent 时发送第二条消息
|
||||
if (testStep === 2) {
|
||||
testStep = 3;
|
||||
|
||||
setTimeout(() => {
|
||||
const chatMessage2 = {
|
||||
t: 'chat',
|
||||
content: '🎃 在南瓜谷发送的测试消息!',
|
||||
scope: 'local'
|
||||
// 先切换到 Pumpkin Valley 地图
|
||||
console.log('\n📤 步骤 5: 切换到 Pumpkin Valley 地图');
|
||||
const positionUpdate = {
|
||||
t: 'position',
|
||||
x: 150,
|
||||
y: 400,
|
||||
mapId: 'pumpkin_valley'
|
||||
};
|
||||
socket.emit('position_update', positionUpdate);
|
||||
|
||||
console.log('📤 步骤 4: 在 Pumpkin Valley 发送消息');
|
||||
socket.emit('chat', chatMessage2);
|
||||
}, 1000);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
// 等待位置更新后发送消息
|
||||
setTimeout(() => {
|
||||
const chatMessage2 = {
|
||||
t: 'chat',
|
||||
content: '🎃 在南瓜谷发送的测试消息!',
|
||||
scope: 'local'
|
||||
};
|
||||
|
||||
console.log('📤 步骤 6: 在 Pumpkin Valley 发送消息');
|
||||
socket.emit('chat', chatMessage2);
|
||||
}, 1000);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('chat_render', (data) => {
|
||||
console.log('📨 收到来自 Zulip 的消息:');
|
||||
console.log(' 发送者:', data.from);
|
||||
console.log(' 内容:', data.txt);
|
||||
console.log(' Stream:', data.stream || '未知');
|
||||
console.log(' Topic:', data.topic || '未知');
|
||||
});
|
||||
|
||||
socket.on('error', (error) => {
|
||||
console.log('❌ 收到错误:', JSON.stringify(error, null, 2));
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('🔌 WebSocket 连接已关闭');
|
||||
console.log('');
|
||||
console.log('📊 测试结果:');
|
||||
console.log(' 完成步骤:', testStep, '/ 4');
|
||||
if (testStep >= 3) {
|
||||
console.log(' ✅ 核心功能正常!');
|
||||
console.log(' 💡 请检查 Zulip 中的 "Whale Port" 和 "Pumpkin Valley" Streams 查看消息');
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
socket.on('connect_error', (error) => {
|
||||
console.error('❌ 连接错误:', error.message);
|
||||
socket.on('chat_render', (data) => {
|
||||
console.log('\n📨 收到来自 Zulip 的消息:');
|
||||
console.log(' 发送者:', data.from);
|
||||
console.log(' 内容:', data.txt);
|
||||
console.log(' Stream:', data.stream || '未知');
|
||||
console.log(' Topic:', data.topic || '未知');
|
||||
});
|
||||
|
||||
socket.on('error', (error) => {
|
||||
console.log('❌ 收到错误:', JSON.stringify(error, null, 2));
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('\n🔌 WebSocket 连接已关闭');
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('📊 测试结果汇总');
|
||||
console.log('='.repeat(60));
|
||||
console.log(' 完成步骤:', testStep, '/ 3');
|
||||
if (testStep >= 3) {
|
||||
console.log(' ✅ 核心功能正常!');
|
||||
console.log(' 💡 请检查 Zulip 中的 "Whale Port" 和 "Pumpkin Valley" Streams 查看消息');
|
||||
} else {
|
||||
console.log(' ⚠️ 部分测试未完成');
|
||||
}
|
||||
console.log('='.repeat(60));
|
||||
process.exit(testStep >= 3 ? 0 : 1);
|
||||
});
|
||||
|
||||
socket.on('connect_error', (error) => {
|
||||
console.error('❌ 连接错误:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// 20秒后自动关闭(给足够时间完成测试)
|
||||
setTimeout(() => {
|
||||
console.log('\n⏰ 测试时间到,关闭连接');
|
||||
socket.disconnect();
|
||||
}, 20000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ 测试失败:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// 20秒后自动关闭(给足够时间完成测试)
|
||||
setTimeout(() => {
|
||||
console.log('⏰ 测试时间到,关闭连接');
|
||||
socket.disconnect();
|
||||
}, 20000);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('🔧 准备测试环境...');
|
||||
testWithUserApiKey().catch(console.error);
|
||||
// 运行测试
|
||||
testWithUserApiKey();
|
||||
Reference in New Issue
Block a user