Files
whale-town-end/docs/systems/zulip/quick_tests/test-list-subscriptions.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

175 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();