feat(zulip): Add Zulip account management and integrate with auth system

- Add ZulipAccountsEntity, repository, and module for persistent Zulip account storage
- Create ZulipAccountService in core layer for managing Zulip account lifecycle
- Integrate Zulip account creation into login flow via LoginService
- Add comprehensive test suite for Zulip account creation during user registration
- Create quick test script for validating registered user Zulip integration
- Update UsersEntity to support Zulip account associations
- Update auth module to include Zulip and ZulipAccounts dependencies
- Fix WebSocket connection protocol from ws:// to wss:// in API documentation
- Enhance LoginCoreService to coordinate Zulip account provisioning during authentication
This commit is contained in:
angjustinl
2026-01-05 17:41:54 +08:00
parent fcb81f80d9
commit 2b87eac495
14 changed files with 2698 additions and 38 deletions

View File

@@ -5,7 +5,7 @@
### 连接地址
```
ws://localhost:3000/game
wss://localhost:3000/game
```
### 连接参数

View File

@@ -0,0 +1,232 @@
/**
* 测试新注册用户的Zulip账号功能
*
* 功能:
* 1. 验证新注册用户可以通过游戏服务器登录
* 2. 验证Zulip账号已正确创建和关联
* 3. 验证用户可以通过WebSocket发送消息到Zulip
* 4. 验证用户可以接收来自Zulip的消息
*
* 使用方法:
* node docs/systems/zulip/quick_tests/test-registered-user.js
*/
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'
};
/**
* 步骤1: 登录游戏服务器获取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;
}
}
/**
* 步骤2: 通过WebSocket连接并测试Zulip集成
*/
async function testZulipIntegration(userInfo) {
console.log('\n📡 步骤 2: 测试 Zulip 集成');
console.log(` 连接到: ${GAME_SERVER}/game`);
return new Promise((resolve, reject) => {
const socket = io(`${GAME_SERVER}/game`, {
transports: ['websocket'],
timeout: 20000
});
let testStep = 0;
let testResults = {
connected: false,
loggedIn: false,
messageSent: false,
messageReceived: false
};
// 连接成功
socket.on('connect', () => {
console.log('✅ WebSocket 连接成功');
testResults.connected = true;
testStep = 1;
// 发送登录消息
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.username}`);
console.log(` 当前地图: ${data.currentMap}`);
testResults.loggedIn = true;
testStep = 2;
// 等待Zulip客户端初始化
console.log('\n⏳ 等待 3 秒让 Zulip 客户端初始化...');
setTimeout(() => {
const chatMessage = {
t: 'chat',
content: `🎮 【注册用户测试】来自 ${userInfo.username} 的消息!\n` +
`时间: ${new Date().toLocaleString()}\n` +
`这是通过新注册账号发送的测试消息。`,
scope: 'local'
};
console.log('📤 发送测试消息到 Zulip...');
console.log(` 内容: ${chatMessage.content.split('\n')[0]}`);
socket.emit('chat', chatMessage);
}, 3000);
});
// 消息发送成功
socket.on('chat_sent', (data) => {
console.log('✅ 消息发送成功');
console.log(` 消息ID: ${data.id || '未知'}`);
testResults.messageSent = true;
testStep = 3;
// 等待一段时间接收消息
setTimeout(() => {
console.log('\n📊 测试完成,断开连接...');
socket.disconnect();
}, 5000);
});
// 接收到消息
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 || '未知'}`);
testResults.messageReceived = true;
});
// 错误处理
socket.on('error', (error) => {
console.error('❌ 收到错误:', JSON.stringify(error, null, 2));
});
// 连接断开
socket.on('disconnect', () => {
console.log('\n🔌 WebSocket 连接已关闭');
resolve(testResults);
});
// 连接错误
socket.on('connect_error', (error) => {
console.error('❌ 连接错误:', error.message);
reject(error);
});
// 超时保护
setTimeout(() => {
if (socket.connected) {
socket.disconnect();
}
}, 15000);
});
}
/**
* 打印测试结果
*/
function printTestResults(results) {
console.log('\n' + '='.repeat(60));
console.log('📊 测试结果汇总');
console.log('='.repeat(60));
const checks = [
{ name: 'WebSocket 连接', passed: results.connected },
{ name: '游戏服务器登录', passed: results.loggedIn },
{ name: '发送消息到 Zulip', passed: results.messageSent },
{ name: '接收 Zulip 消息', passed: results.messageReceived }
];
checks.forEach(check => {
const icon = check.passed ? '✅' : '❌';
console.log(`${icon} ${check.name}: ${check.passed ? '通过' : '失败'}`);
});
const passedCount = checks.filter(c => c.passed).length;
const totalCount = checks.length;
console.log('='.repeat(60));
console.log(`总计: ${passedCount}/${totalCount} 项测试通过`);
if (passedCount === totalCount) {
console.log('\n🎉 所有测试通过Zulip账号创建和集成功能正常');
console.log('💡 提示: 请访问 https://zulip.xinghangee.icu/ 查看发送的消息');
} else {
console.log('\n⚠ 部分测试失败,请检查日志');
}
console.log('='.repeat(60));
}
/**
* 主测试流程
*/
async function runTest() {
console.log('🚀 开始测试新注册用户的 Zulip 集成功能');
console.log('='.repeat(60));
try {
// 步骤1: 登录
const userInfo = await loginToGameServer();
// 步骤2: 测试Zulip集成
const results = await testZulipIntegration(userInfo);
// 打印结果
printTestResults(results);
process.exit(results.connected && results.loggedIn && results.messageSent ? 0 : 1);
} catch (error) {
console.error('\n❌ 测试失败:', error.message);
process.exit(1);
}
}
// 运行测试
runTest();