forked from datawhale/whale-town-end
Merge pull request 'refactor(auth): 优化注册流程中Zulip账号创建为异步处理' (#53) from refactor/register-zulip-async-20260309 into main
Reviewed-on: datawhale/whale-town-end#53
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -46,6 +46,10 @@ coverage/
|
|||||||
redis-data/
|
redis-data/
|
||||||
|
|
||||||
.kiro/
|
.kiro/
|
||||||
|
.claude/
|
||||||
|
|
||||||
|
# 本地测试脚本
|
||||||
|
test-*.ps1
|
||||||
|
|
||||||
config/
|
config/
|
||||||
docs/merge-requests
|
docs/merge-requests
|
||||||
|
|||||||
220
REGISTER_ZULIP_CHANGES.md
Normal file
220
REGISTER_ZULIP_CHANGES.md
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# 注册流程 Zulip 错误处理优化
|
||||||
|
|
||||||
|
## 修改概述
|
||||||
|
|
||||||
|
优化了用户注册流程中的 Zulip 账号创建逻辑,确保 Zulip 服务的问题不会影响用户注册体验。
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
|
||||||
|
### 1. 异步处理 Zulip 账号创建
|
||||||
|
|
||||||
|
**修改前:**
|
||||||
|
- Zulip 账号创建是同步的,阻塞注册流程
|
||||||
|
- 如果 Zulip 创建失败,会回滚游戏账号
|
||||||
|
- 错误信息直接返回给前端用户
|
||||||
|
|
||||||
|
**修改后:**
|
||||||
|
- Zulip 账号创建改为异步处理,不阻塞注册流程
|
||||||
|
- 游戏账号创建成功后立即返回,Zulip 在后台处理
|
||||||
|
- 用户无感知,注册体验流畅
|
||||||
|
|
||||||
|
### 2. 自动重试机制
|
||||||
|
|
||||||
|
新增 `createZulipAccountWithRetry()` 方法:
|
||||||
|
- 最多重试 3 次
|
||||||
|
- 递增延迟:1秒、2秒、3秒
|
||||||
|
- 每次尝试都记录详细日志
|
||||||
|
- 所有重试失败后记录最终错误
|
||||||
|
|
||||||
|
### 3. 错误信息隔离
|
||||||
|
|
||||||
|
**修改前:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "注册失败:Zulip账号创建失败 - [具体错误]",
|
||||||
|
"error_code": "REGISTER_FAILED"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改后:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"user": {...},
|
||||||
|
"access_token": "...",
|
||||||
|
"message": "注册成功"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
用户永远不会看到 Zulip 相关的错误信息。
|
||||||
|
|
||||||
|
### 4. 日志记录增强
|
||||||
|
|
||||||
|
所有 Zulip 相关操作都有详细日志:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 开始尝试
|
||||||
|
LOG: 尝试创建Zulip账号 (第1/3次)
|
||||||
|
- operationId: 用于追踪
|
||||||
|
- gameUserId: 游戏用户ID
|
||||||
|
- email: 用户邮箱
|
||||||
|
|
||||||
|
// 失败重试
|
||||||
|
WARN: Zulip账号创建失败 (第1/3次尝试)
|
||||||
|
- error: 具体错误信息
|
||||||
|
- 等待1000ms后重试
|
||||||
|
|
||||||
|
// 最终失败
|
||||||
|
ERROR: Zulip账号创建最终失败,已尝试3次
|
||||||
|
- finalError: 最后一次错误
|
||||||
|
- note: 用户注册已成功,但Zulip账号创建失败
|
||||||
|
```
|
||||||
|
|
||||||
|
## 代码变更
|
||||||
|
|
||||||
|
### 文件:`src/business/auth/register.service.ts`
|
||||||
|
|
||||||
|
#### 1. 移除同步 Zulip 创建和回滚逻辑
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 删除的代码
|
||||||
|
try {
|
||||||
|
await this.createZulipAccountForUser(...);
|
||||||
|
} catch (zulipError) {
|
||||||
|
// 回滚游戏用户注册
|
||||||
|
await this.loginCoreService.deleteUser(...);
|
||||||
|
throw new Error(`注册失败:Zulip账号创建失败 - ${err.message}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 新增异步处理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 新增的代码
|
||||||
|
if (registerRequest.email && registerRequest.password) {
|
||||||
|
this.createZulipAccountWithRetry(
|
||||||
|
authResult.user,
|
||||||
|
registerRequest.password,
|
||||||
|
operationId
|
||||||
|
).then(success => {
|
||||||
|
// 成功日志
|
||||||
|
}).catch(err => {
|
||||||
|
// 失败日志(不影响用户)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 新增重试方法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
private async createZulipAccountWithRetry(
|
||||||
|
gameUser: Users,
|
||||||
|
password: string,
|
||||||
|
operationId: string,
|
||||||
|
maxRetries: number = 3
|
||||||
|
): Promise<boolean> {
|
||||||
|
// 重试逻辑
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
|
try {
|
||||||
|
await this.createZulipAccountForUser(gameUser, password);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
// 记录日志并重试
|
||||||
|
if (attempt < maxRetries) {
|
||||||
|
await this.delay(attempt * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试验证
|
||||||
|
|
||||||
|
### 测试场景 1:正常注册(无邮箱)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
POST /auth/register
|
||||||
|
{
|
||||||
|
"username": "testuser",
|
||||||
|
"password": "Test123456",
|
||||||
|
"nickname": "测试用户"
|
||||||
|
}
|
||||||
|
|
||||||
|
响应:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"user": {...},
|
||||||
|
"access_token": "...",
|
||||||
|
"message": "注册成功" // ✓ 不包含 Zulip 信息
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试场景 2:Zulip 服务不可用
|
||||||
|
|
||||||
|
**行为:**
|
||||||
|
1. 用户注册成功
|
||||||
|
2. 后台尝试创建 Zulip 账号
|
||||||
|
3. 失败后自动重试 3 次
|
||||||
|
4. 所有失败都记录在服务器日志
|
||||||
|
5. 用户收到成功响应
|
||||||
|
|
||||||
|
**日志示例:**
|
||||||
|
```
|
||||||
|
LOG: 尝试创建Zulip账号 (第1/3次)
|
||||||
|
WARN: Zulip账号创建失败 (第1/3次尝试)
|
||||||
|
LOG: 等待1000ms后重试
|
||||||
|
LOG: 尝试创建Zulip账号 (第2/3次)
|
||||||
|
WARN: Zulip账号创建失败 (第2/3次尝试)
|
||||||
|
LOG: 等待2000ms后重试
|
||||||
|
LOG: 尝试创建Zulip账号 (第3/3次)
|
||||||
|
WARN: Zulip账号创建失败 (第3/3次尝试)
|
||||||
|
ERROR: Zulip账号创建最终失败,已尝试3次
|
||||||
|
```
|
||||||
|
|
||||||
|
## 优势
|
||||||
|
|
||||||
|
### 1. 用户体验
|
||||||
|
- ✅ 注册流程不受 Zulip 服务影响
|
||||||
|
- ✅ 响应速度快(不等待 Zulip)
|
||||||
|
- ✅ 错误信息友好(不暴露技术细节)
|
||||||
|
|
||||||
|
### 2. 系统稳定性
|
||||||
|
- ✅ Zulip 故障不影响核心注册功能
|
||||||
|
- ✅ 自动重试提高成功率
|
||||||
|
- ✅ 降级处理保证服务可用性
|
||||||
|
|
||||||
|
### 3. 可维护性
|
||||||
|
- ✅ 详细的日志便于问题排查
|
||||||
|
- ✅ 清晰的错误追踪(operationId)
|
||||||
|
- ✅ 独立的重试逻辑易于调整
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **Zulip 账号创建失败的用户**
|
||||||
|
- 可以正常使用游戏
|
||||||
|
- 无法使用聊天功能
|
||||||
|
- 需要开发者手动处理或实现补偿机制
|
||||||
|
|
||||||
|
2. **监控建议**
|
||||||
|
- 定期检查 Zulip 创建失败的日志
|
||||||
|
- 统计失败率,及时发现 Zulip 服务问题
|
||||||
|
- 考虑实现告警机制
|
||||||
|
|
||||||
|
3. **后续优化方向**
|
||||||
|
- 实现定时任务补偿创建失败的 Zulip 账号
|
||||||
|
- 添加管理员界面查看 Zulip 创建状态
|
||||||
|
- 提供手动重试接口
|
||||||
|
|
||||||
|
## 修改日期
|
||||||
|
|
||||||
|
2026-02-08
|
||||||
|
|
||||||
|
## 修改者
|
||||||
|
|
||||||
|
AI Assistant (Kiro)
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
# API接口文档
|
# API接口文档
|
||||||
|
|
||||||
本目录包含了 Whale Town 像素游戏服务器的完整API文档,采用业务功能模块化设计,提供17个接口覆盖所有核心功能。
|
本目录包含了 Whale Town 像素游戏服务器的完整API文档,采用业务功能模块化设计,提供80+个接口覆盖所有核心功能。
|
||||||
|
|
||||||
## 📋 文档文件说明
|
## 📋 文档文件说明
|
||||||
|
|
||||||
### 1. api-documentation.md
|
### 1. api-documentation.md
|
||||||
详细的API接口文档,包含:
|
详细的API接口文档,包含:
|
||||||
- **17个API接口** - 用户认证、用户管理、管理员功能、安全防护
|
- **80+个API接口** - 用户认证、用户管理、管理员功能、聊天系统、位置广播、通知系统、Zulip集成、数据库管理、操作日志
|
||||||
- 接口概述和通用响应格式
|
- 接口概述和通用响应格式
|
||||||
- 每个接口的详细说明、参数、响应示例
|
- 每个接口的详细说明、参数、响应示例
|
||||||
- 错误代码说明和状态码映射
|
- 错误代码说明和状态码映射
|
||||||
@@ -65,18 +65,21 @@ openapi-generator generate -i docs/api/openapi.yaml -g typescript-axios -o ./cli
|
|||||||
|
|
||||||
## 📊 API接口概览
|
## 📊 API接口概览
|
||||||
|
|
||||||
### 🔐 用户认证模块 (9个接口)
|
### 🔐 用户认证模块 (12个接口)
|
||||||
| 接口 | 方法 | 路径 | 描述 |
|
| 接口 | 方法 | 路径 | 描述 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| 用户登录 | POST | /auth/login | 支持用户名、邮箱或手机号登录 |
|
| 用户登录 | POST | /auth/login | 支持用户名、邮箱或手机号登录 |
|
||||||
| 用户注册 | POST | /auth/register | 创建新用户账户 |
|
| 用户注册 | POST | /auth/register | 创建新用户账户 |
|
||||||
| GitHub OAuth | POST | /auth/github | 使用GitHub账户登录或注册 |
|
| GitHub OAuth | POST | /auth/github | 使用GitHub账户登录或注册 |
|
||||||
| 发送重置验证码 | POST | /auth/forgot-password | 发送密码重置验证码 |
|
| 验证码登录 | POST | /auth/verification-code-login | 使用验证码登录 |
|
||||||
| 重置密码 | POST | /auth/reset-password | 使用验证码重置密码 |
|
| 发送登录验证码 | POST | /auth/send-login-verification-code | 发送登录验证码 |
|
||||||
| 修改密码 | PUT | /auth/change-password | 修改用户密码 |
|
|
||||||
| 发送邮箱验证码 | POST | /auth/send-email-verification | 发送邮箱验证码 |
|
| 发送邮箱验证码 | POST | /auth/send-email-verification | 发送邮箱验证码 |
|
||||||
| 验证邮箱 | POST | /auth/verify-email | 验证邮箱验证码 |
|
| 验证邮箱 | POST | /auth/verify-email | 验证邮箱验证码 |
|
||||||
| 重发邮箱验证码 | POST | /auth/resend-email-verification | 重新发送邮箱验证码 |
|
| 重发邮箱验证码 | POST | /auth/resend-email-verification | 重新发送邮箱验证码 |
|
||||||
|
| 发送重置验证码 | POST | /auth/forgot-password | 发送密码重置验证码 |
|
||||||
|
| 重置密码 | POST | /auth/reset-password | 使用验证码重置密码 |
|
||||||
|
| 修改密码 | PUT | /auth/change-password | 修改用户密码 |
|
||||||
|
| 刷新令牌 | POST | /auth/refresh-token | 刷新访问令牌 |
|
||||||
|
|
||||||
### 👥 用户管理模块 (3个接口)
|
### 👥 用户管理模块 (3个接口)
|
||||||
| 接口 | 方法 | 路径 | 描述 |
|
| 接口 | 方法 | 路径 | 描述 |
|
||||||
@@ -85,13 +88,83 @@ openapi-generator generate -i docs/api/openapi.yaml -g typescript-axios -o ./cli
|
|||||||
| 批量修改状态 | POST | /admin/users/batch-status | 批量修改用户状态 |
|
| 批量修改状态 | POST | /admin/users/batch-status | 批量修改用户状态 |
|
||||||
| 用户状态统计 | GET | /admin/users/status-stats | 获取各状态用户统计 |
|
| 用户状态统计 | GET | /admin/users/status-stats | 获取各状态用户统计 |
|
||||||
|
|
||||||
### 🛡️ 管理员模块 (4个接口)
|
### 🛡️ 管理员模块 (6个接口)
|
||||||
| 接口 | 方法 | 路径 | 描述 |
|
| 接口 | 方法 | 路径 | 描述 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| 管理员登录 | POST | /admin/auth/login | 管理员身份认证 |
|
| 管理员登录 | POST | /admin/auth/login | 管理员身份认证 |
|
||||||
| 获取用户列表 | GET | /admin/users | 分页获取用户列表 |
|
| 获取用户列表 | GET | /admin/users | 分页获取用户列表 |
|
||||||
| 获取用户详情 | GET | /admin/users/:id | 获取指定用户信息 |
|
| 获取用户详情 | GET | /admin/users/:id | 获取指定用户信息 |
|
||||||
| 重置用户密码 | POST | /admin/users/:id/reset-password | 管理员重置用户密码 |
|
| 重置用户密码 | POST | /admin/users/:id/reset-password | 管理员重置用户密码 |
|
||||||
|
| 获取运行时日志 | GET | /admin/logs/runtime | 获取运行日志 |
|
||||||
|
| 下载日志归档 | GET | /admin/logs/archive | 下载全部日志 |
|
||||||
|
|
||||||
|
### 💬 聊天系统 (3个接口)
|
||||||
|
| 接口 | 方法 | 路径 | 描述 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 获取聊天历史 | GET | /chat/history | 获取聊天历史记录 |
|
||||||
|
| 获取系统状态 | GET | /chat/status | 获取聊天系统状态 |
|
||||||
|
| WebSocket信息 | GET | /chat/websocket/info | 获取WebSocket连接信息 |
|
||||||
|
|
||||||
|
### 📍 位置广播 (9个接口)
|
||||||
|
| 接口 | 方法 | 路径 | 描述 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 创建会话 | POST | /location-broadcast/sessions | 创建新的游戏会话 |
|
||||||
|
| 查询会话列表 | GET | /location-broadcast/sessions | 查询游戏会话列表 |
|
||||||
|
| 获取会话详情 | GET | /location-broadcast/sessions/:sessionId | 获取会话详细信息 |
|
||||||
|
| 更新会话配置 | PUT | /location-broadcast/sessions/:sessionId/config | 更新会话配置 |
|
||||||
|
| 结束会话 | DELETE | /location-broadcast/sessions/:sessionId | 结束游戏会话 |
|
||||||
|
| 查询位置信息 | GET | /location-broadcast/positions | 查询用户位置 |
|
||||||
|
| 位置统计 | GET | /location-broadcast/positions/stats | 获取位置统计 |
|
||||||
|
| 位置历史 | GET | /location-broadcast/users/:userId/position-history | 获取位置历史 |
|
||||||
|
| 清理用户数据 | DELETE | /location-broadcast/users/:userId/data | 清理用户数据 |
|
||||||
|
|
||||||
|
### 🔔 通知系统 (7个接口)
|
||||||
|
| 接口 | 方法 | 路径 | 描述 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 创建通知 | POST | /api/notices | 创建新通知 |
|
||||||
|
| 获取通知列表 | GET | /api/notices | 获取通知列表 |
|
||||||
|
| 未读通知数量 | GET | /api/notices/unread-count | 获取未读数量 |
|
||||||
|
| 获取通知详情 | GET | /api/notices/:id | 获取通知详情 |
|
||||||
|
| 标记已读 | PATCH | /api/notices/:id/read | 标记为已读 |
|
||||||
|
| 发送系统通知 | POST | /api/notices/system | 发送系统通知 |
|
||||||
|
| 发送广播通知 | POST | /api/notices/broadcast | 发送广播通知 |
|
||||||
|
|
||||||
|
### 🔗 Zulip集成 (17个接口)
|
||||||
|
| 接口 | 方法 | 路径 | 描述 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 创建关联 | POST | /zulip-accounts | 创建Zulip账号关联 |
|
||||||
|
| 查询关联列表 | GET | /zulip-accounts | 查询关联列表 |
|
||||||
|
| 获取关联详情 | GET | /zulip-accounts/:id | 根据ID获取关联 |
|
||||||
|
| 根据游戏用户ID获取 | GET | /zulip-accounts/game-user/:gameUserId | 根据游戏用户ID获取 |
|
||||||
|
| 根据Zulip用户ID获取 | GET | /zulip-accounts/zulip-user/:zulipUserId | 根据Zulip用户ID获取 |
|
||||||
|
| 根据邮箱获取 | GET | /zulip-accounts/zulip-email/:zulipEmail | 根据邮箱获取 |
|
||||||
|
| 更新关联 | PUT | /zulip-accounts/:id | 更新关联信息 |
|
||||||
|
| 根据游戏用户ID更新 | PUT | /zulip-accounts/game-user/:gameUserId | 根据游戏用户ID更新 |
|
||||||
|
| 删除关联 | DELETE | /zulip-accounts/:id | 删除关联 |
|
||||||
|
| 根据游戏用户ID删除 | DELETE | /zulip-accounts/game-user/:gameUserId | 根据游戏用户ID删除 |
|
||||||
|
| 需要验证的账号 | GET | /zulip-accounts/management/verification-needed | 获取需要验证的账号 |
|
||||||
|
| 错误状态账号 | GET | /zulip-accounts/management/error-accounts | 获取错误状态账号 |
|
||||||
|
| 批量更新状态 | PUT | /zulip-accounts/management/batch-status | 批量更新状态 |
|
||||||
|
| 账号统计 | GET | /zulip-accounts/management/statistics | 获取统计信息 |
|
||||||
|
| 验证账号 | POST | /zulip-accounts/management/verify | 验证账号有效性 |
|
||||||
|
| 检查邮箱存在 | GET | /zulip-accounts/validation/email-exists/:email | 检查邮箱是否存在 |
|
||||||
|
| 检查用户ID存在 | GET | /zulip-accounts/validation/zulip-user-exists/:zulipUserId | 检查用户ID是否存在 |
|
||||||
|
|
||||||
|
### 🗄️ 数据库管理 (6个接口)
|
||||||
|
| 接口 | 方法 | 路径 | 描述 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 获取用户列表 | GET | /admin/database/users | 获取用户列表 |
|
||||||
|
| 获取用户详情 | GET | /admin/database/users/:id | 获取用户详情 |
|
||||||
|
| 搜索用户 | GET | /admin/database/users/search | 搜索用户 |
|
||||||
|
| 创建用户 | POST | /admin/database/users | 创建新用户 |
|
||||||
|
| 更新用户 | PUT | /admin/database/users/:id | 更新用户信息 |
|
||||||
|
| 删除用户 | DELETE | /admin/database/users/:id | 删除用户 |
|
||||||
|
|
||||||
|
### 📝 操作日志 (2个接口)
|
||||||
|
| 接口 | 方法 | 路径 | 描述 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 获取日志列表 | GET | /admin/operation-logs | 获取操作日志列表 |
|
||||||
|
| 获取日志详情 | GET | /admin/operation-logs/:id | 获取日志详情 |
|
||||||
|
|
||||||
### 📊 系统状态 (1个接口)
|
### 📊 系统状态 (1个接口)
|
||||||
| 接口 | 方法 | 路径 | 描述 |
|
| 接口 | 方法 | 路径 | 描述 |
|
||||||
@@ -188,6 +261,9 @@ console.log('登录结果:', loginData);
|
|||||||
5. **测试模式**: 邮件服务支持测试模式,验证码会在控制台输出
|
5. **测试模式**: 邮件服务支持测试模式,验证码会在控制台输出
|
||||||
6. **存储模式**: 支持Redis文件存储和内存数据库,便于无依赖测试
|
6. **存储模式**: 支持Redis文件存储和内存数据库,便于无依赖测试
|
||||||
7. **安全防护**: 实现了维护模式、内容类型检查、超时控制等安全机制
|
7. **安全防护**: 实现了维护模式、内容类型检查、超时控制等安全机制
|
||||||
|
8. **WebSocket**: 实时功能(聊天、位置广播)推荐使用WebSocket接口
|
||||||
|
9. **Zulip集成**: 支持与Zulip聊天系统的完整集成
|
||||||
|
10. **操作审计**: 管理员操作自动记录审计日志
|
||||||
|
|
||||||
## 🔄 更新文档
|
## 🔄 更新文档
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -48,7 +48,6 @@ const ERROR_CODES = {
|
|||||||
|
|
||||||
const MESSAGES = {
|
const MESSAGES = {
|
||||||
REGISTER_SUCCESS: '注册成功',
|
REGISTER_SUCCESS: '注册成功',
|
||||||
REGISTER_SUCCESS_WITH_ZULIP: '注册成功,Zulip账号已同步创建',
|
|
||||||
EMAIL_VERIFICATION_SUCCESS: '邮箱验证成功',
|
EMAIL_VERIFICATION_SUCCESS: '邮箱验证成功',
|
||||||
CODE_SENT: '验证码已发送,请查收',
|
CODE_SENT: '验证码已发送,请查收',
|
||||||
EMAIL_CODE_SENT: '验证码已发送,请查收邮件',
|
EMAIL_CODE_SENT: '验证码已发送,请查收邮件',
|
||||||
@@ -136,53 +135,38 @@ export class RegisterService {
|
|||||||
// 2. 调用核心服务进行注册
|
// 2. 调用核心服务进行注册
|
||||||
const authResult = await this.loginCoreService.register(registerRequest);
|
const authResult = await this.loginCoreService.register(registerRequest);
|
||||||
|
|
||||||
// 3. 创建Zulip账号(使用相同的邮箱和密码)
|
// 3. 创建Zulip账号(使用相同的邮箱和密码)- 异步处理,不影响注册流程
|
||||||
let zulipAccountCreated = false;
|
|
||||||
|
|
||||||
if (registerRequest.email && registerRequest.password) {
|
if (registerRequest.email && registerRequest.password) {
|
||||||
try {
|
// 异步创建Zulip账号,不阻塞注册流程
|
||||||
await this.createZulipAccountForUser(authResult.user, registerRequest.password);
|
this.createZulipAccountWithRetry(
|
||||||
zulipAccountCreated = true;
|
authResult.user,
|
||||||
|
registerRequest.password,
|
||||||
this.logger.log(`Zulip账号创建成功`, {
|
operationId
|
||||||
|
).then(success => {
|
||||||
|
if (success) {
|
||||||
|
this.logger.log(`Zulip账号异步创建成功`, {
|
||||||
operation: 'register',
|
operation: 'register',
|
||||||
operationId,
|
operationId,
|
||||||
gameUserId: authResult.user.id.toString(),
|
gameUserId: authResult.user.id.toString(),
|
||||||
email: registerRequest.email,
|
email: registerRequest.email,
|
||||||
});
|
});
|
||||||
} catch (zulipError) {
|
}
|
||||||
const err = zulipError as Error;
|
}).catch(err => {
|
||||||
this.logger.error(`Zulip账号创建失败,开始回滚`, {
|
// 错误已在重试方法中记录,这里只是确保Promise不会未处理
|
||||||
|
this.logger.warn(`Zulip账号异步创建最终失败`, {
|
||||||
operation: 'register',
|
operation: 'register',
|
||||||
operationId,
|
operationId,
|
||||||
username: registerRequest.username,
|
|
||||||
gameUserId: authResult.user.id.toString(),
|
|
||||||
zulipError: err.message,
|
|
||||||
}, err.stack);
|
|
||||||
|
|
||||||
// 回滚游戏用户注册
|
|
||||||
try {
|
|
||||||
await this.loginCoreService.deleteUser(authResult.user.id);
|
|
||||||
this.logger.log(`游戏用户注册回滚成功`, {
|
|
||||||
operation: 'register',
|
|
||||||
operationId,
|
|
||||||
username: registerRequest.username,
|
|
||||||
gameUserId: authResult.user.id.toString(),
|
gameUserId: authResult.user.id.toString(),
|
||||||
|
email: registerRequest.email,
|
||||||
});
|
});
|
||||||
} catch (rollbackError) {
|
});
|
||||||
const rollbackErr = rollbackError as Error;
|
|
||||||
this.logger.error(`游戏用户注册回滚失败`, {
|
this.logger.log(`Zulip账号创建已提交到后台异步处理`, {
|
||||||
operation: 'register',
|
operation: 'register',
|
||||||
operationId,
|
operationId,
|
||||||
username: registerRequest.username,
|
|
||||||
gameUserId: authResult.user.id.toString(),
|
gameUserId: authResult.user.id.toString(),
|
||||||
rollbackError: rollbackErr.message,
|
email: registerRequest.email,
|
||||||
}, rollbackErr.stack);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// 抛出原始错误
|
|
||||||
throw new Error(`注册失败:Zulip账号创建失败 - ${err.message}`);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.logger.log(`跳过Zulip账号创建:缺少邮箱或密码`, {
|
this.logger.log(`跳过Zulip账号创建:缺少邮箱或密码`, {
|
||||||
operation: 'register',
|
operation: 'register',
|
||||||
@@ -203,7 +187,7 @@ export class RegisterService {
|
|||||||
expires_in: tokenPair.expires_in,
|
expires_in: tokenPair.expires_in,
|
||||||
token_type: tokenPair.token_type,
|
token_type: tokenPair.token_type,
|
||||||
is_new_user: true,
|
is_new_user: true,
|
||||||
message: zulipAccountCreated ? MESSAGES.REGISTER_SUCCESS_WITH_ZULIP : MESSAGES.REGISTER_SUCCESS
|
message: MESSAGES.REGISTER_SUCCESS
|
||||||
};
|
};
|
||||||
|
|
||||||
const duration = Date.now() - startTime;
|
const duration = Date.now() - startTime;
|
||||||
@@ -214,7 +198,6 @@ export class RegisterService {
|
|||||||
gameUserId: authResult.user.id.toString(),
|
gameUserId: authResult.user.id.toString(),
|
||||||
username: authResult.user.username,
|
username: authResult.user.username,
|
||||||
email: authResult.user.email,
|
email: authResult.user.email,
|
||||||
zulipAccountCreated,
|
|
||||||
duration,
|
duration,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
@@ -437,6 +420,103 @@ export class RegisterService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带重试机制的异步创建Zulip账号
|
||||||
|
*
|
||||||
|
* 功能描述:
|
||||||
|
* 异步创建Zulip账号,失败时自动重试最多3次
|
||||||
|
* 所有错误只记录日志,不影响用户注册流程
|
||||||
|
*
|
||||||
|
* @param gameUser 游戏用户信息
|
||||||
|
* @param password 用户密码
|
||||||
|
* @param operationId 操作ID(用于日志追踪)
|
||||||
|
* @param maxRetries 最大重试次数,默认3次
|
||||||
|
* @returns Promise<boolean> 是否创建成功
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private async createZulipAccountWithRetry(
|
||||||
|
gameUser: Users,
|
||||||
|
password: string,
|
||||||
|
operationId: string,
|
||||||
|
maxRetries: number = 3
|
||||||
|
): Promise<boolean> {
|
||||||
|
let lastError: Error | null = null;
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
|
try {
|
||||||
|
this.logger.log(`尝试创建Zulip账号 (第${attempt}/${maxRetries}次)`, {
|
||||||
|
operation: 'createZulipAccountWithRetry',
|
||||||
|
operationId,
|
||||||
|
attempt,
|
||||||
|
maxRetries,
|
||||||
|
gameUserId: gameUser.id.toString(),
|
||||||
|
email: gameUser.email,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.createZulipAccountForUser(gameUser, password);
|
||||||
|
|
||||||
|
this.logger.log(`Zulip账号创建成功 (第${attempt}次尝试)`, {
|
||||||
|
operation: 'createZulipAccountWithRetry',
|
||||||
|
operationId,
|
||||||
|
attempt,
|
||||||
|
gameUserId: gameUser.id.toString(),
|
||||||
|
email: gameUser.email,
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error as Error;
|
||||||
|
|
||||||
|
this.logger.warn(`Zulip账号创建失败 (第${attempt}/${maxRetries}次尝试)`, {
|
||||||
|
operation: 'createZulipAccountWithRetry',
|
||||||
|
operationId,
|
||||||
|
attempt,
|
||||||
|
maxRetries,
|
||||||
|
gameUserId: gameUser.id.toString(),
|
||||||
|
email: gameUser.email,
|
||||||
|
error: lastError.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果不是最后一次尝试,等待后重试
|
||||||
|
if (attempt < maxRetries) {
|
||||||
|
const delayMs = attempt * 1000; // 递增延迟:1秒、2秒、3秒
|
||||||
|
this.logger.log(`等待${delayMs}ms后重试`, {
|
||||||
|
operation: 'createZulipAccountWithRetry',
|
||||||
|
operationId,
|
||||||
|
attempt,
|
||||||
|
delayMs,
|
||||||
|
});
|
||||||
|
await this.delay(delayMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有重试都失败
|
||||||
|
this.logger.error(`Zulip账号创建最终失败,已尝试${maxRetries}次`, {
|
||||||
|
operation: 'createZulipAccountWithRetry',
|
||||||
|
operationId,
|
||||||
|
maxRetries,
|
||||||
|
gameUserId: gameUser.id.toString(),
|
||||||
|
email: gameUser.email,
|
||||||
|
finalError: lastError?.message,
|
||||||
|
note: '用户注册已成功,但Zulip账号创建失败。用户可以正常使用游戏,但无法使用聊天功能。',
|
||||||
|
}, lastError?.stack);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延迟工具方法
|
||||||
|
*
|
||||||
|
* @param ms 延迟毫秒数
|
||||||
|
* @returns Promise<void>
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private delay(ms: number): Promise<void> {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 为用户创建或绑定Zulip账号
|
* 为用户创建或绑定Zulip账号
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user