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 密钥。
175 lines
5.7 KiB
JavaScript
175 lines
5.7 KiB
JavaScript
const zulip = require('zulip-js');
|
||
const axios = require('axios');
|
||
|
||
// 配置
|
||
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);
|
||
|
||
// 获取用户信息
|
||
console.log('\n👤 获取用户信息...');
|
||
const profile = await client.users.me.getProfile();
|
||
console.log('用户:', profile.full_name, `(${profile.email})`);
|
||
console.log('是否管理员:', profile.is_admin);
|
||
|
||
// 获取用户订阅的 Streams
|
||
console.log('\n📋 获取用户订阅的 Streams...');
|
||
const subscriptions = await client.streams.subscriptions.retrieve();
|
||
|
||
if (subscriptions.result === 'success') {
|
||
console.log(`\n✅ 找到 ${subscriptions.subscriptions.length} 个订阅的 Streams:`);
|
||
subscriptions.subscriptions.forEach(sub => {
|
||
console.log(` - ${sub.name} (ID: ${sub.stream_id})`);
|
||
});
|
||
|
||
// 检查是否有 "Novice Village"
|
||
const noviceVillage = subscriptions.subscriptions.find(s => s.name === 'Pumpkin Valley');
|
||
if (noviceVillage) {
|
||
console.log('\n✅ "Pumpkin Valley" Stream 已存在!');
|
||
|
||
// 测试发送消息
|
||
console.log('\n📤 测试发送消息...');
|
||
const result = await client.messages.send({
|
||
type: 'stream',
|
||
to: 'Pumpkin Valley',
|
||
subject: 'General',
|
||
content: '测试消息:系统集成测试成功 🎮'
|
||
});
|
||
|
||
if (result.result === 'success') {
|
||
console.log('✅ 消息发送成功! Message ID:', result.id);
|
||
} else {
|
||
console.log('❌ 消息发送失败:', result.msg);
|
||
}
|
||
} else {
|
||
console.log('\n⚠️ "Pumpkin Valley" Stream 不存在');
|
||
console.log('💡 请在 Zulip 网页界面手动创建该 Stream,或使用管理员账号创建');
|
||
|
||
// 尝试发送到第一个可用的 Stream
|
||
if (subscriptions.subscriptions.length > 0) {
|
||
const firstStream = subscriptions.subscriptions[0];
|
||
console.log(`\n📤 尝试发送消息到 "${firstStream.name}"...`);
|
||
const result = await client.messages.send({
|
||
type: 'stream',
|
||
to: firstStream.name,
|
||
subject: 'Test',
|
||
content: '测试消息:验证系统可以发送消息 🎮'
|
||
});
|
||
|
||
if (result.result === 'success') {
|
||
console.log('✅ 消息发送成功! Message ID:', result.id);
|
||
console.log(`💡 系统工作正常,只需创建 "Novice Village" Stream 即可`);
|
||
} else {
|
||
console.log('❌ 消息发送失败:', result.msg);
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
console.log('❌ 获取订阅失败:', subscriptions.msg);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('\n❌ 操作失败:', error.message);
|
||
if (error.response) {
|
||
console.error('响应数据:', error.response.data);
|
||
}
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
// 运行测试
|
||
listSubscriptions();
|