chore:清理不需要的测试文件和临时文档

- 删除 test_zulip.js、test_zulip_registration.js、test_zulip_user_management.js
- 删除 full_diagnosis.js 诊断脚本
- 删除 docs/development/backend_development_guide.md 重复文档
- 保持代码库整洁,移除临时测试文件
This commit is contained in:
moyin
2026-01-07 15:07:44 +08:00
parent dd91264d0c
commit 2bcbaeb030
5 changed files with 0 additions and 1769 deletions

View File

@@ -1,856 +0,0 @@
# 后端开发规范指南
## 一、文档概述
### 1.1 文档目的
本文档定义了 Datawhale Town 后端开发的编码规范、注释标准、业务逻辑设计原则和日志记录要求,确保代码质量、可维护性和系统稳定性。
### 1.2 适用范围
- 所有后端开发人员
- 代码审查人员
- 系统维护人员
---
## 二、注释规范
### 2.1 模块注释
每个功能模块文件必须包含模块级注释,说明模块用途、主要功能和依赖关系。
**格式要求:**
```typescript
/**
* 玩家管理模块
*
* 功能描述:
* - 处理玩家注册、登录、信息更新等核心功能
* - 管理玩家角色皮肤和个人资料
* - 提供玩家数据的 CRUD 操作
*
* 依赖模块:
* - AuthService: 身份验证服务
* - DatabaseService: 数据库操作服务
* - LoggerService: 日志记录服务
*
* @author 开发者姓名
* @version 1.0.0
* @since 2025-12-13
*/
```
### 2.2 类注释
每个类必须包含类级注释,说明类的职责、主要方法和使用场景。
**格式要求:**
```typescript
/**
* 玩家服务类
*
* 职责:
* - 处理玩家相关的业务逻辑
* - 管理玩家状态和数据
* - 提供玩家操作的统一接口
*
* 主要方法:
* - createPlayer(): 创建新玩家
* - updatePlayerInfo(): 更新玩家信息
* - getPlayerById(): 根据ID获取玩家信息
*
* 使用场景:
* - 玩家注册登录流程
* - 个人陈列室数据管理
* - 广场玩家状态同步
*/
@Injectable()
export class PlayerService {
// 类实现
}
```
### 2.3 方法注释
每个方法必须包含详细的方法注释,说明功能、参数、返回值、异常和业务逻辑。
**格式要求:**
```typescript
/**
* 创建新玩家
*
* 功能描述:
* 根据邮箱创建新的玩家账户,包含基础信息初始化和默认配置设置
*
* 业务逻辑:
* 1. 验证邮箱格式和白名单
* 2. 检查邮箱是否已存在
* 3. 生成唯一玩家ID
* 4. 初始化默认角色皮肤和个人信息
* 5. 创建对应的个人陈列室
* 6. 记录创建日志
*
* @param email 玩家邮箱地址,必须符合邮箱格式且在白名单中
* @param nickname 玩家昵称长度3-20字符不能包含特殊字符
* @param avatarSkin 角色皮肤ID必须是1-8之间的有效值
* @returns Promise<Player> 创建成功的玩家对象
*
* @throws BadRequestException 当邮箱格式错误或不在白名单中
* @throws ConflictException 当邮箱已存在时
* @throws InternalServerErrorException 当数据库操作失败时
*
* @example
* ```typescript
* const player = await playerService.createPlayer(
* 'user@datawhale.club',
* '数据鲸鱼',
* '1'
* );
* ```
*/
async createPlayer(
email: string,
nickname: string,
avatarSkin: string
): Promise<Player> {
// 方法实现
}
```
### 2.4 复杂业务逻辑注释
对于复杂的业务逻辑,必须添加行内注释说明每个步骤的目的和处理逻辑。
**示例:**
```typescript
async joinRoom(roomId: string, playerId: string): Promise<Room> {
// 1. 参数验证 - 确保房间ID和玩家ID格式正确
if (!roomId || !playerId) {
this.logger.warn(`房间加入失败:参数无效`, { roomId, playerId });
throw new BadRequestException('房间ID和玩家ID不能为空');
}
// 2. 获取房间信息 - 检查房间是否存在
const room = await this.roomRepository.findById(roomId);
if (!room) {
this.logger.warn(`房间加入失败:房间不存在`, { roomId, playerId });
throw new NotFoundException('房间不存在');
}
// 3. 检查房间状态 - 只有等待中的房间才能加入
if (room.status !== RoomStatus.WAITING) {
this.logger.warn(`房间加入失败:房间状态不允许加入`, {
roomId,
playerId,
currentStatus: room.status
});
throw new BadRequestException('游戏已开始,无法加入房间');
}
// 4. 检查房间容量 - 防止超过最大人数限制
if (room.players.length >= room.maxPlayers) {
this.logger.warn(`房间加入失败:房间已满`, {
roomId,
playerId,
currentPlayers: room.players.length,
maxPlayers: room.maxPlayers
});
throw new BadRequestException('房间已满');
}
// 5. 检查玩家是否已在房间中 - 防止重复加入
if (room.players.includes(playerId)) {
this.logger.info(`玩家已在房间中,跳过加入操作`, { roomId, playerId });
return room;
}
// 6. 执行加入操作 - 更新房间玩家列表
try {
room.players.push(playerId);
const updatedRoom = await this.roomRepository.save(room);
// 7. 记录成功日志
this.logger.info(`玩家成功加入房间`, {
roomId,
playerId,
currentPlayers: updatedRoom.players.length,
maxPlayers: updatedRoom.maxPlayers
});
return updatedRoom;
} catch (error) {
// 8. 异常处理 - 记录错误并抛出
this.logger.error(`房间加入操作数据库错误`, {
roomId,
playerId,
error: error.message,
stack: error.stack
});
throw new InternalServerErrorException('房间加入失败,请稍后重试');
}
}
```
---
## 三、业务逻辑设计原则
### 3.1 全面性原则
每个业务方法必须考虑所有可能的情况,包括正常流程、异常情况和边界条件。
**必须考虑的情况:**
| 类别 | 具体情况 | 处理方式 |
|------|---------|---------|
| **输入验证** | 参数为空、格式错误、超出范围 | 参数校验 + 异常抛出 |
| **权限检查** | 未登录、权限不足、令牌过期 | 身份验证 + 权限验证 |
| **资源状态** | 资源不存在、状态不正确、已被占用 | 状态检查 + 业务规则验证 |
| **并发控制** | 同时操作、数据竞争、锁冲突 | 事务处理 + 乐观锁/悲观锁 |
| **系统异常** | 数据库连接失败、网络超时、内存不足 | 异常捕获 + 降级处理 |
| **业务规则** | 违反业务约束、超出限制、状态冲突 | 业务规则验证 + 友好提示 |
### 3.2 防御性编程
采用防御性编程思想,对所有外部输入和依赖进行验证和保护。
**实现要求:**
```typescript
/**
* 更新玩家信息 - 防御性编程示例
*/
async updatePlayerInfo(
playerId: string,
updateData: UpdatePlayerDto
): Promise<Player> {
// 1. 输入参数防御性检查
if (!playerId) {
this.logger.warn('更新玩家信息失败玩家ID为空');
throw new BadRequestException('玩家ID不能为空');
}
if (!updateData || Object.keys(updateData).length === 0) {
this.logger.warn('更新玩家信息失败:更新数据为空', { playerId });
throw new BadRequestException('更新数据不能为空');
}
// 2. 数据格式验证
if (updateData.nickname) {
if (updateData.nickname.length < 3 || updateData.nickname.length > 20) {
this.logger.warn('更新玩家信息失败:昵称长度不符合要求', {
playerId,
nicknameLength: updateData.nickname.length
});
throw new BadRequestException('昵称长度必须在3-20字符之间');
}
}
if (updateData.avatarSkin) {
const validSkins = ['1', '2', '3', '4', '5', '6', '7', '8'];
if (!validSkins.includes(updateData.avatarSkin)) {
this.logger.warn('更新玩家信息失败角色皮肤ID无效', {
playerId,
avatarSkin: updateData.avatarSkin
});
throw new BadRequestException('角色皮肤ID必须在1-8之间');
}
}
// 3. 玩家存在性检查
const existingPlayer = await this.playerRepository.findById(playerId);
if (!existingPlayer) {
this.logger.warn('更新玩家信息失败:玩家不存在', { playerId });
throw new NotFoundException('玩家不存在');
}
// 4. 昵称唯一性检查(如果更新昵称)
if (updateData.nickname && updateData.nickname !== existingPlayer.nickname) {
const nicknameExists = await this.playerRepository.findByNickname(updateData.nickname);
if (nicknameExists) {
this.logger.warn('更新玩家信息失败:昵称已存在', {
playerId,
nickname: updateData.nickname
});
throw new ConflictException('昵称已被使用');
}
}
// 5. 执行更新操作(使用事务保证数据一致性)
try {
const updatedPlayer = await this.playerRepository.update(playerId, updateData);
this.logger.info('玩家信息更新成功', {
playerId,
updatedFields: Object.keys(updateData),
timestamp: new Date().toISOString()
});
return updatedPlayer;
} catch (error) {
this.logger.error('更新玩家信息数据库操作失败', {
playerId,
updateData,
error: error.message,
stack: error.stack
});
throw new InternalServerErrorException('更新失败,请稍后重试');
}
}
```
### 3.3 异常处理策略
建立统一的异常处理策略,确保所有异常都能被正确捕获和处理。
**异常分类和处理:**
| 异常类型 | HTTP状态码 | 处理策略 | 日志级别 |
|---------|-----------|---------|---------|
| **BadRequestException** | 400 | 参数验证失败,返回具体错误信息 | WARN |
| **UnauthorizedException** | 401 | 身份验证失败,要求重新登录 | WARN |
| **ForbiddenException** | 403 | 权限不足,拒绝访问 | WARN |
| **NotFoundException** | 404 | 资源不存在,返回友好提示 | WARN |
| **ConflictException** | 409 | 资源冲突,如重复创建 | WARN |
| **InternalServerErrorException** | 500 | 系统内部错误,记录详细日志 | ERROR |
---
## 四、日志系统使用指南
### 4.1 日志服务简介
项目使用统一的 `AppLoggerService` 提供日志记录功能,集成了 Pino 高性能日志库,支持自动敏感信息过滤和请求上下文绑定。
### 4.2 在服务中使用日志
**依赖注入:**
```typescript
import { Injectable } from '@nestjs/common';
import { AppLoggerService } from '../core/utils/logger/logger.service';
@Injectable()
export class UserService {
constructor(
private readonly logger: AppLoggerService
) {}
}
```
### 4.3 日志级别和使用场景
| 级别 | 使用场景 | 示例 |
|------|---------|------|
| **ERROR** | 系统错误、异常情况、数据库连接失败 | 数据库连接超时、第三方服务调用失败 |
| **WARN** | 业务警告、参数错误、权限不足 | 用户输入无效参数、尝试访问不存在的资源 |
| **INFO** | 重要业务操作、状态变更、关键流程 | 用户登录成功、房间创建、玩家加入广场 |
| **DEBUG** | 调试信息、详细执行流程 | 方法调用参数、中间计算结果 |
| **FATAL** | 致命错误、系统不可用 | 数据库完全不可用、关键服务宕机 |
| **TRACE** | 极细粒度调试信息 | 循环内的变量状态、算法执行步骤 |
### 4.4 标准日志格式
**推荐的日志上下文格式:**
```typescript
// 成功操作日志
this.logger.info('操作描述', {
operation: '操作类型',
userId: '用户ID',
resourceId: '资源ID',
params: '关键参数',
result: '操作结果',
duration: '执行时间(ms)',
timestamp: new Date().toISOString()
});
// 警告日志
this.logger.warn('警告描述', {
operation: '操作类型',
userId: '用户ID',
reason: '警告原因',
params: '相关参数',
timestamp: new Date().toISOString()
});
// 错误日志
this.logger.error('错误描述', {
operation: '操作类型',
userId: '用户ID',
error: error.message,
params: '相关参数',
timestamp: new Date().toISOString()
}, error.stack);
```
### 4.5 请求上下文绑定
**在 Controller 中使用:**
```typescript
@Controller('users')
export class UserController {
constructor(private readonly logger: AppLoggerService) {}
@Get(':id')
async getUser(@Param('id') id: string, @Req() req: Request) {
// 绑定请求上下文
const requestLogger = this.logger.bindRequest(req, 'UserController');
requestLogger.info('开始获取用户信息', { userId: id });
try {
const user = await this.userService.findById(id);
requestLogger.info('用户信息获取成功', { userId: id });
return user;
} catch (error) {
requestLogger.error('用户信息获取失败', error.stack, {
userId: id,
reason: error.message
});
throw error;
}
}
}
```
### 4.6 业务方法日志记录最佳实践
**完整的业务方法日志记录示例:**
```typescript
async createPlayer(email: string, nickname: string): Promise<Player> {
const startTime = Date.now();
this.logger.info('开始创建玩家', {
operation: 'createPlayer',
email,
nickname,
timestamp: new Date().toISOString()
});
try {
// 1. 参数验证
if (!email || !nickname) {
this.logger.warn('创建玩家失败:参数无效', {
operation: 'createPlayer',
email,
nickname,
reason: 'invalid_parameters'
});
throw new BadRequestException('邮箱和昵称不能为空');
}
// 2. 邮箱格式验证
if (!this.isValidEmail(email)) {
this.logger.warn('创建玩家失败:邮箱格式无效', {
operation: 'createPlayer',
email,
nickname
});
throw new BadRequestException('邮箱格式不正确');
}
// 3. 检查邮箱是否已存在
const existingPlayer = await this.playerRepository.findByEmail(email);
if (existingPlayer) {
this.logger.warn('创建玩家失败:邮箱已存在', {
operation: 'createPlayer',
email,
nickname,
existingPlayerId: existingPlayer.id
});
throw new ConflictException('邮箱已被使用');
}
// 4. 创建玩家
const player = await this.playerRepository.create({
email,
nickname,
avatarSkin: '1', // 默认皮肤
createTime: new Date()
});
const duration = Date.now() - startTime;
this.logger.info('玩家创建成功', {
operation: 'createPlayer',
playerId: player.id,
email,
nickname,
duration,
timestamp: new Date().toISOString()
});
return player;
} catch (error) {
const duration = Date.now() - startTime;
if (error instanceof BadRequestException ||
error instanceof ConflictException) {
// 业务异常,重新抛出
throw error;
}
// 系统异常,记录详细日志
this.logger.error('创建玩家系统异常', {
operation: 'createPlayer',
email,
nickname,
error: error.message,
duration,
timestamp: new Date().toISOString()
}, error.stack);
throw new InternalServerErrorException('创建玩家失败,请稍后重试');
}
}
```
### 4.7 必须记录日志的操作
| 操作类型 | 日志级别 | 记录内容 |
|---------|---------|---------|
| **用户认证** | INFO/WARN | 登录成功/失败、令牌生成/验证 |
| **数据变更** | INFO | 创建、更新、删除操作 |
| **权限检查** | WARN | 权限验证失败、非法访问尝试 |
| **系统异常** | ERROR | 异常堆栈、错误上下文、影响范围 |
| **性能监控** | INFO | 慢查询、高并发操作、资源使用 |
| **安全事件** | WARN/ERROR | 恶意请求、频繁操作、异常行为 |
### 4.8 敏感信息保护
日志系统会自动过滤以下敏感字段:
- `password` - 密码
- `token` - 令牌
- `secret` - 密钥
- `authorization` - 授权信息
- `cardNo` - 卡号
**注意:** 包含这些关键词的字段会被自动替换为 `[REDACTED]`
---
## 五、代码审查检查清单
### 5.1 注释检查
- [ ] 模块文件包含完整的模块级注释
- [ ] 每个类都有详细的类级注释
- [ ] 每个公共方法都有完整的方法注释
- [ ] 复杂业务逻辑有行内注释说明
- [ ] 注释内容准确,与代码实现一致
### 5.2 业务逻辑检查
- [ ] 考虑了所有可能的输入情况
- [ ] 包含完整的参数验证
- [ ] 处理了所有可能的异常情况
- [ ] 实现了适当的权限检查
- [ ] 考虑了并发和竞态条件
### 5.3 日志记录检查
- [ ] 关键业务操作都有日志记录
- [ ] 日志级别使用正确
- [ ] 日志格式符合规范
- [ ] 包含足够的上下文信息
- [ ] 敏感信息已脱敏处理
### 5.4 异常处理检查
- [ ] 所有异常都被正确捕获
- [ ] 异常类型选择合适
- [ ] 异常信息对用户友好
- [ ] 系统异常有详细的错误日志
- [ ] 不会泄露敏感的系统信息
---
## 六、最佳实践示例
### 6.1 完整的服务类示例
```typescript
/**
* 广场管理服务
*
* 功能描述:
* - 管理中央广场的玩家状态和位置同步
* - 处理玩家进入和离开广场的逻辑
* - 维护广场在线玩家列表最多50人
*
* 依赖模块:
* - PlayerService: 玩家信息服务
* - WebSocketGateway: WebSocket通信网关
* - RedisService: 缓存服务
* - LoggerService: 日志记录服务
*
* @author 开发团队
* @version 1.0.0
* @since 2025-12-13
*/
@Injectable()
export class PlazaService {
private readonly logger = new Logger(PlazaService.name);
private readonly MAX_PLAYERS = 50;
constructor(
private readonly playerService: PlayerService,
private readonly redisService: RedisService,
private readonly webSocketGateway: WebSocketGateway
) {}
/**
* 玩家进入广场
*
* 功能描述:
* 处理玩家进入中央广场的逻辑,包括人数限制检查、位置分配和状态同步
*
* 业务逻辑:
* 1. 验证玩家身份和权限
* 2. 检查广场当前人数是否超限
* 3. 为玩家分配初始位置
* 4. 更新Redis中的在线玩家列表
* 5. 向其他玩家广播新玩家进入消息
* 6. 向新玩家发送当前广场状态
*
* @param playerId 玩家ID必须是有效的已注册玩家
* @param socketId WebSocket连接ID用于消息推送
* @returns Promise<PlazaPlayerInfo> 玩家在广场的信息
*
* @throws UnauthorizedException 当玩家身份验证失败时
* @throws BadRequestException 当广场人数已满时
* @throws InternalServerErrorException 当系统操作失败时
*/
async enterPlaza(playerId: string, socketId: string): Promise<PlazaPlayerInfo> {
const startTime = Date.now();
this.logger.info('玩家尝试进入广场', {
operation: 'enterPlaza',
playerId,
socketId,
timestamp: new Date().toISOString()
});
try {
// 1. 验证玩家身份
const player = await this.playerService.getPlayerById(playerId);
if (!player) {
this.logger.warn('进入广场失败:玩家不存在', {
operation: 'enterPlaza',
playerId,
socketId
});
throw new UnauthorizedException('玩家身份验证失败');
}
// 2. 检查广场人数限制
const currentPlayers = await this.redisService.scard('plaza:online_players');
if (currentPlayers >= this.MAX_PLAYERS) {
this.logger.warn('进入广场失败:人数已满', {
operation: 'enterPlaza',
playerId,
currentPlayers,
maxPlayers: this.MAX_PLAYERS
});
throw new BadRequestException('广场人数已满,请稍后再试');
}
// 3. 检查玩家是否已在广场中
const isAlreadyInPlaza = await this.redisService.sismember('plaza:online_players', playerId);
if (isAlreadyInPlaza) {
this.logger.info('玩家已在广场中,更新连接信息', {
operation: 'enterPlaza',
playerId,
socketId
});
// 更新Socket连接映射
await this.redisService.hset('plaza:player_sockets', playerId, socketId);
// 获取当前位置信息
const existingInfo = await this.redisService.hget('plaza:player_positions', playerId);
return JSON.parse(existingInfo);
}
// 4. 为玩家分配初始位置(广场中心附近随机位置)
const initialPosition = this.generateInitialPosition();
const playerInfo: PlazaPlayerInfo = {
playerId: player.id,
nickname: player.nickname,
avatarSkin: player.avatarSkin,
position: initialPosition,
lastUpdate: new Date(),
socketId
};
// 5. 更新Redis中的玩家状态
await Promise.all([
this.redisService.sadd('plaza:online_players', playerId),
this.redisService.hset('plaza:player_positions', playerId, JSON.stringify(playerInfo)),
this.redisService.hset('plaza:player_sockets', playerId, socketId),
this.redisService.expire('plaza:player_positions', 3600), // 1小时过期
this.redisService.expire('plaza:player_sockets', 3600)
]);
// 6. 向其他玩家广播新玩家进入消息
this.webSocketGateway.broadcastToPlaza('player_entered', {
playerId: player.id,
nickname: player.nickname,
avatarSkin: player.avatarSkin,
position: initialPosition
}, socketId); // 排除新进入的玩家
// 7. 向新玩家发送当前广场状态
const allPlayers = await this.getAllPlazaPlayers();
this.webSocketGateway.sendToPlayer(socketId, 'plaza_state', {
players: allPlayers.filter(p => p.playerId !== playerId),
totalPlayers: allPlayers.length
});
const duration = Date.now() - startTime;
this.logger.info('玩家成功进入广场', {
operation: 'enterPlaza',
playerId,
socketId,
position: initialPosition,
totalPlayers: currentPlayers + 1,
duration,
timestamp: new Date().toISOString()
});
return playerInfo;
} catch (error) {
const duration = Date.now() - startTime;
if (error instanceof UnauthorizedException ||
error instanceof BadRequestException) {
throw error;
}
this.logger.error('玩家进入广场系统异常', {
operation: 'enterPlaza',
playerId,
socketId,
error: error.message,
stack: error.stack,
duration,
timestamp: new Date().toISOString()
});
throw new InternalServerErrorException('进入广场失败,请稍后重试');
}
}
/**
* 生成初始位置
*
* 功能描述:
* 在广场中心附近生成随机的初始位置,避免玩家重叠
*
* @returns Position 包含x、y坐标的位置对象
* @private
*/
private generateInitialPosition(): Position {
// 广场中心坐标 (400, 300)在半径100像素范围内随机分配
const centerX = 400;
const centerY = 300;
const radius = 100;
const angle = Math.random() * 2 * Math.PI;
const distance = Math.random() * radius;
const x = Math.round(centerX + distance * Math.cos(angle));
const y = Math.round(centerY + distance * Math.sin(angle));
return { x, y };
}
/**
* 获取所有广场玩家信息
*
* @returns Promise<PlazaPlayerInfo[]> 广场中所有玩家的信息列表
* @private
*/
private async getAllPlazaPlayers(): Promise<PlazaPlayerInfo[]> {
try {
const playerIds = await this.redisService.smembers('plaza:online_players');
const playerInfos = await Promise.all(
playerIds.map(async (playerId) => {
const info = await this.redisService.hget('plaza:player_positions', playerId);
return info ? JSON.parse(info) : null;
})
);
return playerInfos.filter(info => info !== null);
} catch (error) {
this.logger.error('获取广场玩家列表失败', {
operation: 'getAllPlazaPlayers',
error: error.message
});
return [];
}
}
}
```
---
## 七、工具和配置
### 7.1 推荐的开发工具
| 工具 | 用途 | 配置说明 |
|------|------|---------|
| **ESLint** | 代码规范检查 | 配置注释规范检查规则 |
| **Prettier** | 代码格式化 | 统一代码格式 |
| **TSDoc** | 文档生成 | 从注释生成API文档 |
| **SonarQube** | 代码质量分析 | 检查代码覆盖率和复杂度 |
### 7.2 日志配置示例
```typescript
// logger.config.ts
export const loggerConfig = {
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.Console(),
new winston.transports.File({
filename: 'logs/error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'logs/combined.log'
})
]
};
```
---
## 八、总结
本规范文档定义了后端开发的核心要求:
1. **完整的注释体系**:模块、类、方法三级注释,确保代码可读性
2. **全面的业务逻辑**:考虑所有可能情况,实现防御性编程
3. **规范的日志记录**:关键操作必须记录,便于问题排查和系统监控
4. **统一的异常处理**:分类处理不同类型异常,提供友好的用户体验
遵循本规范将显著提高代码质量、系统稳定性和团队协作效率。所有开发人员必须严格按照本规范进行开发,代码审查时将重点检查这些方面的实现。

View File

@@ -1,311 +0,0 @@
const io = require('socket.io-client');
const https = require('https');
const http = require('http');
console.log('🔍 全面WebSocket连接诊断');
console.log('='.repeat(60));
// 1. 测试基础网络连接
async function testBasicConnection() {
console.log('\n1⃣ 测试基础HTTPS连接...');
return new Promise((resolve) => {
const options = {
hostname: 'whaletownend.xinghangee.icu',
port: 443,
path: '/',
method: 'GET',
timeout: 10000
};
const req = https.request(options, (res) => {
console.log(`✅ HTTPS连接成功 - 状态码: ${res.statusCode}`);
console.log(`📋 服务器: ${res.headers.server || '未知'}`);
resolve({ success: true, statusCode: res.statusCode });
});
req.on('error', (error) => {
console.log(`❌ HTTPS连接失败: ${error.message}`);
resolve({ success: false, error: error.message });
});
req.on('timeout', () => {
console.log('❌ HTTPS连接超时');
req.destroy();
resolve({ success: false, error: 'timeout' });
});
req.end();
});
}
// 2. 测试本地服务器
async function testLocalServer() {
console.log('\n2⃣ 测试本地服务器...');
const testPaths = [
'http://localhost:3000/',
'http://localhost:3000/socket.io/?EIO=4&transport=polling'
];
for (const url of testPaths) {
console.log(`🧪 测试: ${url}`);
await new Promise((resolve) => {
const urlObj = new URL(url);
const options = {
hostname: urlObj.hostname,
port: urlObj.port,
path: urlObj.pathname + urlObj.search,
method: 'GET',
timeout: 5000
};
const req = http.request(options, (res) => {
console.log(` 状态码: ${res.statusCode}`);
if (res.statusCode === 200) {
console.log(' ✅ 本地服务器正常');
} else {
console.log(` ⚠️ 本地服务器响应: ${res.statusCode}`);
}
resolve();
});
req.on('error', (error) => {
console.log(` ❌ 本地服务器连接失败: ${error.message}`);
resolve();
});
req.on('timeout', () => {
console.log(' ❌ 本地服务器超时');
req.destroy();
resolve();
});
req.end();
});
}
}
// 3. 测试远程Socket.IO路径
async function testRemoteSocketIO() {
console.log('\n3⃣ 测试远程Socket.IO路径...');
const testPaths = [
'/socket.io/?EIO=4&transport=polling',
'/game/socket.io/?EIO=4&transport=polling',
'/socket.io/?transport=polling',
'/api/socket.io/?EIO=4&transport=polling'
];
const results = [];
for (const path of testPaths) {
console.log(`🧪 测试路径: ${path}`);
const result = await new Promise((resolve) => {
const options = {
hostname: 'whaletownend.xinghangee.icu',
port: 443,
path: path,
method: 'GET',
timeout: 8000,
headers: {
'User-Agent': 'socket.io-diagnosis'
}
};
const req = https.request(options, (res) => {
console.log(` 状态码: ${res.statusCode}`);
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
if (res.statusCode === 200) {
console.log(' ✅ 路径可用');
console.log(` 📄 响应: ${data.substring(0, 50)}...`);
} else {
console.log(` ❌ 路径不可用: ${res.statusCode}`);
}
resolve({ path, statusCode: res.statusCode, success: res.statusCode === 200 });
});
});
req.on('error', (error) => {
console.log(` ❌ 请求失败: ${error.message}`);
resolve({ path, error: error.message, success: false });
});
req.on('timeout', () => {
console.log(' ❌ 请求超时');
req.destroy();
resolve({ path, error: 'timeout', success: false });
});
req.end();
});
results.push(result);
}
return results;
}
// 4. 测试Socket.IO客户端连接
async function testSocketIOClient() {
console.log('\n4⃣ 测试Socket.IO客户端连接...');
const configs = [
{
name: 'HTTPS + 所有传输方式',
url: 'https://whaletownend.xinghangee.icu',
options: { transports: ['websocket', 'polling'], timeout: 10000 }
},
{
name: 'HTTPS + 仅Polling',
url: 'https://whaletownend.xinghangee.icu',
options: { transports: ['polling'], timeout: 10000 }
},
{
name: 'HTTPS + /game namespace',
url: 'https://whaletownend.xinghangee.icu/game',
options: { transports: ['polling'], timeout: 10000 }
}
];
const results = [];
for (const config of configs) {
console.log(`🧪 测试: ${config.name}`);
console.log(` URL: ${config.url}`);
const result = await new Promise((resolve) => {
const socket = io(config.url, config.options);
let resolved = false;
const timeout = setTimeout(() => {
if (!resolved) {
resolved = true;
socket.disconnect();
console.log(' ❌ 连接超时');
resolve({ success: false, error: 'timeout' });
}
}, config.options.timeout);
socket.on('connect', () => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
console.log(' ✅ 连接成功');
console.log(` 📡 Socket ID: ${socket.id}`);
console.log(` 🚀 传输方式: ${socket.io.engine.transport.name}`);
socket.disconnect();
resolve({ success: true, transport: socket.io.engine.transport.name });
}
});
socket.on('connect_error', (error) => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
console.log(` ❌ 连接失败: ${error.message}`);
resolve({ success: false, error: error.message });
}
});
});
results.push({ config: config.name, ...result });
// 等待1秒再测试下一个
await new Promise(resolve => setTimeout(resolve, 1000));
}
return results;
}
// 5. 检查DNS解析
async function testDNS() {
console.log('\n5⃣ 检查DNS解析...');
const dns = require('dns');
return new Promise((resolve) => {
dns.lookup('whaletownend.xinghangee.icu', (err, address, family) => {
if (err) {
console.log(`❌ DNS解析失败: ${err.message}`);
resolve({ success: false, error: err.message });
} else {
console.log(`✅ DNS解析成功: ${address} (IPv${family})`);
resolve({ success: true, address, family });
}
});
});
}
// 主诊断函数
async function runFullDiagnosis() {
console.log('开始全面诊断...\n');
try {
const dnsResult = await testDNS();
const basicResult = await testBasicConnection();
await testLocalServer();
const socketIOPaths = await testRemoteSocketIO();
const clientResults = await testSocketIOClient();
console.log('\n' + '='.repeat(60));
console.log('📊 诊断结果汇总');
console.log('='.repeat(60));
console.log(`1. DNS解析: ${dnsResult.success ? '✅ 正常' : '❌ 失败'}`);
if (dnsResult.address) {
console.log(` IP地址: ${dnsResult.address}`);
}
console.log(`2. HTTPS连接: ${basicResult.success ? '✅ 正常' : '❌ 失败'}`);
if (basicResult.error) {
console.log(` 错误: ${basicResult.error}`);
}
const workingPaths = socketIOPaths.filter(r => r.success);
console.log(`3. Socket.IO路径: ${workingPaths.length}/${socketIOPaths.length} 个可用`);
workingPaths.forEach(p => {
console.log(`${p.path}`);
});
const workingClients = clientResults.filter(r => r.success);
console.log(`4. Socket.IO客户端: ${workingClients.length}/${clientResults.length} 个成功`);
workingClients.forEach(c => {
console.log(`${c.config} (${c.transport})`);
});
console.log('\n💡 建议:');
if (!dnsResult.success) {
console.log('❌ DNS解析失败 - 检查域名配置');
} else if (!basicResult.success) {
console.log('❌ 基础HTTPS连接失败 - 检查服务器状态和防火墙');
} else if (workingPaths.length === 0) {
console.log('❌ 所有Socket.IO路径都不可用 - 检查nginx配置和后端服务');
} else if (workingClients.length === 0) {
console.log('❌ Socket.IO客户端无法连接 - 可能是CORS或协议问题');
} else {
console.log('✅ 部分功能正常 - 使用可用的配置继续开发');
if (workingClients.length > 0) {
const bestConfig = workingClients.find(c => c.transport === 'websocket') || workingClients[0];
console.log(`💡 推荐使用: ${bestConfig.config}`);
}
}
} catch (error) {
console.error('诊断过程中发生错误:', error);
}
process.exit(0);
}
runFullDiagnosis();

View File

@@ -1,131 +0,0 @@
const io = require('socket.io-client');
// 使用用户 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('📡 游戏服务器: https://whaletownend.xinghangee.icu/game');
const socket = io('wss://whaletownend.xinghangee.icu/game', {
transports: ['websocket', 'polling'], // WebSocket优先polling备用
timeout: 20000,
forceNew: true,
reconnection: true,
reconnectionAttempts: 3,
reconnectionDelay: 1000
});
let testStep = 0;
socket.on('connect', () => {
console.log('✅ WebSocket 连接成功');
testStep = 1;
// 使用包含用户 API Key 的 token
const loginMessage = {
type: 'login',
token: 'lCPWCPfGh7...fGF8_user_token'
};
console.log('📤 步骤 1: 发送登录消息(使用用户 API Key');
socket.emit('login', loginMessage);
});
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;
// 等待 Zulip 客户端初始化
console.log('⏳ 等待 3 秒让 Zulip 客户端初始化...');
setTimeout(() => {
const chatMessage = {
t: 'chat',
content: '🎮 【用户API Key测试】来自游戏的消息\\n' +
'时间: ' + new Date().toLocaleString() + '\\n' +
'使用用户 API Key 发送此消息。',
scope: 'local'
};
console.log('📤 步骤 2: 发送消息到 Zulip使用用户 API Key');
console.log(' 目标 Stream: Whale Port');
socket.emit('chat', chatMessage);
}, 3000);
});
socket.on('chat_sent', (data) => {
console.log('✅ 步骤 2 完成: 消息发送成功');
console.log(' 响应:', JSON.stringify(data, null, 2));
// 只在第一次收到 chat_sent 时发送第二条消息
if (testStep === 2) {
testStep = 3;
setTimeout(() => {
// 先切换到 Pumpkin Valley 地图
console.log('📤 步骤 3: 切换到 Pumpkin Valley 地图');
const positionUpdate = {
t: 'position',
x: 150,
y: 400,
mapId: 'pumpkin_valley'
};
socket.emit('position_update', positionUpdate);
// 等待位置更新后发送消息
setTimeout(() => {
const chatMessage2 = {
t: 'chat',
content: '🎃 在南瓜谷发送的测试消息!',
scope: 'local'
};
console.log('📤 步骤 4: 在 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);
process.exit(1);
});
// 20秒后自动关闭给足够时间完成测试
setTimeout(() => {
console.log('⏰ 测试时间到,关闭连接');
socket.disconnect();
}, 20000);
}
console.log('🔧 准备测试环境...');
testWithUserApiKey().catch(console.error);

View File

@@ -1,196 +0,0 @@
/**
* Zulip用户注册真实环境测试脚本
*
* 功能描述:
* - 测试Zulip用户注册功能在真实环境下的表现
* - 验证API调用是否正常工作
* - 检查配置是否正确
*
* 使用方法:
* node test_zulip_registration.js
*
* @author angjustinl
* @version 1.0.0
* @since 2025-01-06
*/
const https = require('https');
const { URLSearchParams } = require('url');
// 配置信息
const config = {
zulipServerUrl: 'https://zulip.xinghangee.icu',
zulipBotEmail: 'angjustinl@mail.angforever.top',
zulipBotApiKey: 'lCPWCPfGh7WUHxwN56GF8oYXOpqNfGF8',
};
/**
* 检查用户是否存在
*/
async function checkUserExists(email) {
console.log(`🔍 检查用户是否存在: ${email}`);
try {
const url = `${config.zulipServerUrl}/api/v1/users`;
const auth = Buffer.from(`${config.zulipBotEmail}:${config.zulipBotApiKey}`).toString('base64');
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
console.log(`❌ 获取用户列表失败: ${response.status} ${response.statusText}`);
return false;
}
const data = await response.json();
console.log(`📊 获取到 ${data.members?.length || 0} 个用户`);
if (data.members && Array.isArray(data.members)) {
const userExists = data.members.some(user =>
user.email && user.email.toLowerCase() === email.toLowerCase()
);
console.log(`✅ 用户存在性检查完成: ${userExists ? '存在' : '不存在'}`);
return userExists;
}
return false;
} catch (error) {
console.error(`❌ 检查用户存在性失败:`, error.message);
return false;
}
}
/**
* 创建测试用户
*/
async function createTestUser(email, fullName, password) {
console.log(`🚀 开始创建用户: ${email}`);
try {
const url = `${config.zulipServerUrl}/api/v1/users`;
const auth = Buffer.from(`${config.zulipBotEmail}:${config.zulipBotApiKey}`).toString('base64');
const requestBody = new URLSearchParams();
requestBody.append('email', email);
requestBody.append('full_name', fullName);
if (password) {
requestBody.append('password', password);
}
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: requestBody.toString(),
});
const data = await response.json();
if (!response.ok) {
console.log(`❌ 用户创建失败: ${response.status} ${response.statusText}`);
console.log(`📝 错误信息: ${data.msg || data.message || '未知错误'}`);
return { success: false, error: data.msg || data.message };
}
console.log(`✅ 用户创建成功! 用户ID: ${data.user_id}`);
return { success: true, userId: data.user_id };
} catch (error) {
console.error(`❌ 创建用户异常:`, error.message);
return { success: false, error: error.message };
}
}
/**
* 测试连接
*/
async function testConnection() {
console.log('🔗 测试Zulip服务器连接...');
try {
const url = `${config.zulipServerUrl}/api/v1/server_settings`;
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
console.log(`✅ 连接成功! 服务器版本: ${data.zulip_version || '未知'}`);
return true;
} else {
console.log(`❌ 连接失败: ${response.status} ${response.statusText}`);
return false;
}
} catch (error) {
console.error(`❌ 连接异常:`, error.message);
return false;
}
}
/**
* 主测试函数
*/
async function main() {
console.log('🎯 开始Zulip用户注册测试');
console.log('=' * 50);
// 1. 测试连接
const connected = await testConnection();
if (!connected) {
console.log('❌ 无法连接到Zulip服务器测试终止');
return;
}
console.log('');
// 2. 生成测试用户信息
const timestamp = Date.now();
const testEmail = `test_user_${timestamp}@example.com`;
const testFullName = `Test User ${timestamp}`;
const testPassword = 'test123456';
console.log(`📋 测试用户信息:`);
console.log(` 邮箱: ${testEmail}`);
console.log(` 姓名: ${testFullName}`);
console.log(` 密码: ${testPassword}`);
console.log('');
// 3. 检查用户是否已存在
const userExists = await checkUserExists(testEmail);
if (userExists) {
console.log('⚠️ 用户已存在,跳过创建测试');
return;
}
console.log('');
// 4. 创建用户
const createResult = await createTestUser(testEmail, testFullName, testPassword);
console.log('');
console.log('📊 测试结果:');
if (createResult.success) {
console.log('✅ 用户注册功能正常工作');
console.log(` 新用户ID: ${createResult.userId}`);
} else {
console.log('❌ 用户注册功能存在问题');
console.log(` 错误信息: ${createResult.error}`);
}
console.log('');
console.log('🎉 测试完成');
}
// 运行测试
main().catch(error => {
console.error('💥 测试过程中发生未处理的错误:', error);
process.exit(1);
});

View File

@@ -1,275 +0,0 @@
/**
* Zulip用户管理真实环境测试脚本
*
* 功能描述:
* - 测试Zulip用户管理功能在真实环境下的表现
* - 验证用户查询、验证等API调用是否正常工作
* - 检查配置是否正确
*
* 使用方法:
* node test_zulip_user_management.js
*
* @author angjustinl
* @version 1.0.0
* @since 2025-01-06
*/
const https = require('https');
// 配置信息
const config = {
zulipServerUrl: 'https://zulip.xinghangee.icu',
zulipBotEmail: 'angjustinl@mail.angforever.top',
zulipBotApiKey: 'lCPWCPfGh7WUHxwN56GF8oYXOpqNfGF8',
};
/**
* 获取所有用户列表
*/
async function getAllUsers() {
console.log('📋 获取所有用户列表...');
try {
const url = `${config.zulipServerUrl}/api/v1/users`;
const auth = Buffer.from(`${config.zulipBotEmail}:${config.zulipBotApiKey}`).toString('base64');
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
console.log(`❌ 获取用户列表失败: ${response.status} ${response.statusText}`);
return { success: false, error: `${response.status} ${response.statusText}` };
}
const data = await response.json();
const users = data.members?.map(user => ({
userId: user.user_id,
email: user.email,
fullName: user.full_name,
isActive: user.is_active,
isAdmin: user.is_admin,
isBot: user.is_bot,
})) || [];
console.log(`✅ 成功获取 ${users.length} 个用户`);
// 显示前几个用户信息
console.log('👥 用户列表预览:');
users.slice(0, 5).forEach((user, index) => {
console.log(` ${index + 1}. ${user.fullName} (${user.email})`);
console.log(` ID: ${user.userId}, 活跃: ${user.isActive}, 管理员: ${user.isAdmin}, 机器人: ${user.isBot}`);
});
if (users.length > 5) {
console.log(` ... 还有 ${users.length - 5} 个用户`);
}
return { success: true, users, totalCount: users.length };
} catch (error) {
console.error(`❌ 获取用户列表异常:`, error.message);
return { success: false, error: error.message };
}
}
/**
* 检查指定用户是否存在
*/
async function checkUserExists(email) {
console.log(`🔍 检查用户是否存在: ${email}`);
try {
const usersResult = await getAllUsers();
if (!usersResult.success) {
console.log(`❌ 无法获取用户列表: ${usersResult.error}`);
return false;
}
const userExists = usersResult.users.some(user =>
user.email.toLowerCase() === email.toLowerCase()
);
console.log(`✅ 用户存在性检查完成: ${userExists ? '存在' : '不存在'}`);
return userExists;
} catch (error) {
console.error(`❌ 检查用户存在性失败:`, error.message);
return false;
}
}
/**
* 获取用户详细信息
*/
async function getUserInfo(email) {
console.log(`📝 获取用户信息: ${email}`);
try {
const usersResult = await getAllUsers();
if (!usersResult.success) {
console.log(`❌ 无法获取用户列表: ${usersResult.error}`);
return { success: false, error: usersResult.error };
}
const user = usersResult.users.find(u =>
u.email.toLowerCase() === email.toLowerCase()
);
if (!user) {
console.log(`❌ 用户不存在: ${email}`);
return { success: false, error: '用户不存在' };
}
console.log(`✅ 用户信息获取成功:`);
console.log(` 用户ID: ${user.userId}`);
console.log(` 邮箱: ${user.email}`);
console.log(` 姓名: ${user.fullName}`);
console.log(` 状态: ${user.isActive ? '活跃' : '非活跃'}`);
console.log(` 权限: ${user.isAdmin ? '管理员' : '普通用户'}`);
console.log(` 类型: ${user.isBot ? '机器人' : '真实用户'}`);
return { success: true, user };
} catch (error) {
console.error(`❌ 获取用户信息失败:`, error.message);
return { success: false, error: error.message };
}
}
/**
* 测试用户API Key
*/
async function testUserApiKey(email, apiKey) {
console.log(`🔑 测试用户API Key: ${email}`);
try {
const url = `${config.zulipServerUrl}/api/v1/users/me`;
const auth = Buffer.from(`${email}:${apiKey}`).toString('base64');
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
},
});
const isValid = response.ok;
if (isValid) {
const data = await response.json();
console.log(`✅ API Key有效! 用户信息:`);
console.log(` 用户ID: ${data.user_id}`);
console.log(` 邮箱: ${data.email}`);
console.log(` 姓名: ${data.full_name}`);
} else {
console.log(`❌ API Key无效: ${response.status} ${response.statusText}`);
}
return isValid;
} catch (error) {
console.error(`❌ 测试API Key异常:`, error.message);
return false;
}
}
/**
* 测试连接
*/
async function testConnection() {
console.log('🔗 测试Zulip服务器连接...');
try {
const url = `${config.zulipServerUrl}/api/v1/server_settings`;
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
console.log(`✅ 连接成功! 服务器信息:`);
console.log(` 版本: ${data.zulip_version || '未知'}`);
console.log(` 服务器: ${data.realm_name || '未知'}`);
return true;
} else {
console.log(`❌ 连接失败: ${response.status} ${response.statusText}`);
return false;
}
} catch (error) {
console.error(`❌ 连接异常:`, error.message);
return false;
}
}
/**
* 主测试函数
*/
async function main() {
console.log('🎯 开始Zulip用户管理测试');
console.log('='.repeat(50));
// 1. 测试连接
const connected = await testConnection();
if (!connected) {
console.log('❌ 无法连接到Zulip服务器测试终止');
return;
}
console.log('');
// 2. 获取所有用户列表
const usersResult = await getAllUsers();
if (!usersResult.success) {
console.log('❌ 无法获取用户列表,测试终止');
return;
}
console.log('');
// 3. 测试用户存在性检查
const testEmails = [
'angjustinl@mail.angforever.top', // 应该存在
'nonexistent@example.com', // 应该不存在
];
console.log('🔍 测试用户存在性检查:');
for (const email of testEmails) {
const exists = await checkUserExists(email);
console.log(` ${email}: ${exists ? '✅ 存在' : '❌ 不存在'}`);
}
console.log('');
// 4. 测试获取用户信息
console.log('📝 测试获取用户信息:');
const existingEmail = 'angjustinl@mail.angforever.top';
const userInfoResult = await getUserInfo(existingEmail);
console.log('');
// 5. 测试API Key验证如果有的话
console.log('🔑 测试API Key验证:');
const testApiKey = 'lCPWCPfGh7WUHxwN56GF8oYXOpqNfGF8'; // 这是我们的测试API Key
const apiKeyValid = await testUserApiKey(existingEmail, testApiKey);
console.log('');
console.log('📊 测试结果总结:');
console.log(`✅ 服务器连接: 正常`);
console.log(`✅ 用户列表获取: 正常 (${usersResult.totalCount} 个用户)`);
console.log(`✅ 用户存在性检查: 正常`);
console.log(`✅ 用户信息获取: ${userInfoResult.success ? '正常' : '异常'}`);
console.log(`✅ API Key验证: ${apiKeyValid ? '正常' : '异常'}`);
console.log('');
console.log('🎉 用户管理功能测试完成');
}
// 运行测试
main().catch(error => {
console.error('💥 测试过程中发生未处理的错误:', error);
process.exit(1);
});