范围:src/business/zulip/ - 统一命名规范和注释格式 - 完善JSDoc注释和参数说明 - 优化代码结构和缩进 - 清理未使用的导入和变量 - 更新修改记录和版本信息
679 lines
18 KiB
TypeScript
679 lines
18 KiB
TypeScript
/**
|
||
* Zulip账号关联管理控制器
|
||
*
|
||
* 功能描述:
|
||
* - 提供Zulip账号关联管理的REST API接口
|
||
* - 支持CRUD操作和批量管理
|
||
* - 提供账号验证和统计功能
|
||
* - 集成性能监控和结构化日志记录
|
||
* - 实现统一的错误处理和响应格式
|
||
*
|
||
* 职责分离:
|
||
* - API接口:提供RESTful风格的HTTP接口
|
||
* - 参数验证:使用DTO进行请求参数验证
|
||
* - 业务调用:调用Service层处理业务逻辑
|
||
* - 响应格式:统一API响应格式和错误处理
|
||
* - 性能监控:记录接口调用耗时和性能指标
|
||
* - 日志记录:使用AppLoggerService记录结构化日志
|
||
*
|
||
* 最近修改:
|
||
* - 2026-01-12: 性能优化 - 集成AppLoggerService和性能监控,优化错误处理
|
||
* - 2025-01-07: 初始创建 - 实现基础的CRUD和管理接口
|
||
*
|
||
* @author angjustinl
|
||
* @version 1.1.0
|
||
* @since 2025-01-07
|
||
* @lastModified 2026-01-12
|
||
*/
|
||
|
||
import {
|
||
Controller,
|
||
Get,
|
||
Post,
|
||
Put,
|
||
Delete,
|
||
Body,
|
||
Param,
|
||
Query,
|
||
UseGuards,
|
||
HttpStatus,
|
||
HttpCode,
|
||
Inject,
|
||
Req,
|
||
} from '@nestjs/common';
|
||
import {
|
||
ApiTags,
|
||
ApiOperation,
|
||
ApiResponse,
|
||
ApiBearerAuth,
|
||
ApiParam,
|
||
ApiQuery,
|
||
} from '@nestjs/swagger';
|
||
import { Request } from 'express';
|
||
import { JwtAuthGuard } from '../auth/jwt_auth.guard';
|
||
import { ZulipAccountsService } from '../../core/db/zulip_accounts/zulip_accounts.service';
|
||
import { ZulipAccountsMemoryService } from '../../core/db/zulip_accounts/zulip_accounts_memory.service';
|
||
import { AppLoggerService } from '../../core/utils/logger/logger.service';
|
||
import {
|
||
CreateZulipAccountDto,
|
||
UpdateZulipAccountDto,
|
||
QueryZulipAccountDto,
|
||
ZulipAccountResponseDto,
|
||
ZulipAccountListResponseDto,
|
||
ZulipAccountStatsResponseDto,
|
||
BatchUpdateStatusDto,
|
||
BatchUpdateResponseDto,
|
||
VerifyAccountDto,
|
||
VerifyAccountResponseDto,
|
||
} from '../../core/db/zulip_accounts/zulip_accounts.dto';
|
||
|
||
@ApiTags('zulip-accounts')
|
||
@Controller('zulip-accounts')
|
||
@UseGuards(JwtAuthGuard)
|
||
@ApiBearerAuth('JWT-auth')
|
||
export class ZulipAccountsController {
|
||
private readonly requestLogger: any;
|
||
|
||
constructor(
|
||
@Inject('ZulipAccountsService') private readonly zulipAccountsService: any,
|
||
@Inject(AppLoggerService) private readonly logger: AppLoggerService,
|
||
) {
|
||
this.logger.info('ZulipAccountsController初始化完成', {
|
||
module: 'ZulipAccountsController',
|
||
operation: 'constructor'
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 创建性能监控器
|
||
*
|
||
* @param req HTTP请求对象
|
||
* @param operation 操作名称
|
||
* @param context 上下文信息
|
||
* @returns 性能监控器
|
||
* @private
|
||
*/
|
||
private createPerformanceMonitor(req: Request, operation: string, context?: Record<string, any>) {
|
||
const startTime = Date.now();
|
||
const requestLogger = this.logger.bindRequest(req, 'ZulipAccountsController');
|
||
|
||
requestLogger.info(`开始${operation}`, context);
|
||
|
||
return {
|
||
success: (additionalContext?: Record<string, any>) => {
|
||
const duration = Date.now() - startTime;
|
||
requestLogger.info(`${operation}成功`, {
|
||
...context,
|
||
...additionalContext,
|
||
duration
|
||
});
|
||
},
|
||
error: (error: unknown, additionalContext?: Record<string, any>) => {
|
||
const duration = Date.now() - startTime;
|
||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||
requestLogger.error(
|
||
`${operation}失败`,
|
||
error instanceof Error ? error.stack : undefined,
|
||
{
|
||
...context,
|
||
...additionalContext,
|
||
error: errorMessage,
|
||
duration
|
||
}
|
||
);
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 创建Zulip账号关联
|
||
*/
|
||
@Post()
|
||
@ApiOperation({
|
||
summary: '创建Zulip账号关联',
|
||
description: '为游戏用户创建与Zulip账号的关联关系'
|
||
})
|
||
@ApiResponse({
|
||
status: 201,
|
||
description: '创建成功',
|
||
type: ZulipAccountResponseDto,
|
||
})
|
||
@ApiResponse({
|
||
status: 400,
|
||
description: '请求参数错误',
|
||
})
|
||
@ApiResponse({
|
||
status: 409,
|
||
description: '关联已存在',
|
||
})
|
||
@HttpCode(HttpStatus.CREATED)
|
||
async create(
|
||
@Req() req: Request,
|
||
@Body() createDto: CreateZulipAccountDto
|
||
): Promise<ZulipAccountResponseDto> {
|
||
const monitor = this.createPerformanceMonitor(req, '创建Zulip账号关联', {
|
||
gameUserId: createDto.gameUserId,
|
||
zulipUserId: createDto.zulipUserId,
|
||
zulipEmail: createDto.zulipEmail
|
||
});
|
||
|
||
try {
|
||
const result = await this.zulipAccountsService.create(createDto);
|
||
monitor.success({
|
||
accountId: result.id,
|
||
status: result.status
|
||
});
|
||
return result;
|
||
} catch (error) {
|
||
monitor.error(error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取所有Zulip账号关联
|
||
*/
|
||
@Get()
|
||
@ApiOperation({
|
||
summary: '查询Zulip账号关联列表',
|
||
description: '根据条件查询Zulip账号关联列表'
|
||
})
|
||
@ApiQuery({
|
||
name: 'gameUserId',
|
||
required: false,
|
||
description: '游戏用户ID',
|
||
example: '12345'
|
||
})
|
||
@ApiQuery({
|
||
name: 'zulipUserId',
|
||
required: false,
|
||
description: 'Zulip用户ID',
|
||
example: 67890
|
||
})
|
||
@ApiQuery({
|
||
name: 'zulipEmail',
|
||
required: false,
|
||
description: 'Zulip邮箱地址',
|
||
example: 'user@example.com'
|
||
})
|
||
@ApiQuery({
|
||
name: 'status',
|
||
required: false,
|
||
description: '账号状态',
|
||
enum: ['active', 'inactive', 'suspended', 'error']
|
||
})
|
||
@ApiQuery({
|
||
name: 'includeGameUser',
|
||
required: false,
|
||
description: '是否包含游戏用户信息',
|
||
type: Boolean,
|
||
example: false
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '查询成功',
|
||
type: ZulipAccountListResponseDto,
|
||
})
|
||
async findMany(@Query() queryDto: QueryZulipAccountDto): Promise<ZulipAccountListResponseDto> {
|
||
return this.zulipAccountsService.findMany(queryDto);
|
||
}
|
||
|
||
/**
|
||
* 根据ID获取Zulip账号关联
|
||
*/
|
||
@Get(':id')
|
||
@ApiOperation({
|
||
summary: '根据ID获取Zulip账号关联',
|
||
description: '根据关联记录ID获取详细信息'
|
||
})
|
||
@ApiParam({
|
||
name: 'id',
|
||
description: '关联记录ID',
|
||
example: '1'
|
||
})
|
||
@ApiQuery({
|
||
name: 'includeGameUser',
|
||
required: false,
|
||
description: '是否包含游戏用户信息',
|
||
type: Boolean,
|
||
example: false
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '获取成功',
|
||
type: ZulipAccountResponseDto,
|
||
})
|
||
@ApiResponse({
|
||
status: 404,
|
||
description: '记录不存在',
|
||
})
|
||
async findById(
|
||
@Param('id') id: string,
|
||
@Query('includeGameUser') includeGameUser?: boolean,
|
||
): Promise<ZulipAccountResponseDto> {
|
||
return this.zulipAccountsService.findById(id, includeGameUser);
|
||
}
|
||
|
||
/**
|
||
* 根据游戏用户ID获取Zulip账号关联
|
||
*/
|
||
@Get('game-user/:gameUserId')
|
||
@ApiOperation({
|
||
summary: '根据游戏用户ID获取Zulip账号关联',
|
||
description: '根据游戏用户ID获取关联的Zulip账号信息'
|
||
})
|
||
@ApiParam({
|
||
name: 'gameUserId',
|
||
description: '游戏用户ID',
|
||
example: '12345'
|
||
})
|
||
@ApiQuery({
|
||
name: 'includeGameUser',
|
||
required: false,
|
||
description: '是否包含游戏用户信息',
|
||
type: Boolean,
|
||
example: false
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '获取成功',
|
||
type: ZulipAccountResponseDto,
|
||
})
|
||
@ApiResponse({
|
||
status: 404,
|
||
description: '关联不存在',
|
||
})
|
||
async findByGameUserId(
|
||
@Param('gameUserId') gameUserId: string,
|
||
@Query('includeGameUser') includeGameUser?: boolean,
|
||
): Promise<ZulipAccountResponseDto | null> {
|
||
return this.zulipAccountsService.findByGameUserId(gameUserId, includeGameUser);
|
||
}
|
||
|
||
/**
|
||
* 根据Zulip用户ID获取账号关联
|
||
*/
|
||
@Get('zulip-user/:zulipUserId')
|
||
@ApiOperation({
|
||
summary: '根据Zulip用户ID获取账号关联',
|
||
description: '根据Zulip用户ID获取关联的游戏账号信息'
|
||
})
|
||
@ApiParam({
|
||
name: 'zulipUserId',
|
||
description: 'Zulip用户ID',
|
||
example: '67890'
|
||
})
|
||
@ApiQuery({
|
||
name: 'includeGameUser',
|
||
required: false,
|
||
description: '是否包含游戏用户信息',
|
||
type: Boolean,
|
||
example: false
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '获取成功',
|
||
type: ZulipAccountResponseDto,
|
||
})
|
||
@ApiResponse({
|
||
status: 404,
|
||
description: '关联不存在',
|
||
})
|
||
async findByZulipUserId(
|
||
@Param('zulipUserId') zulipUserId: string,
|
||
@Query('includeGameUser') includeGameUser?: boolean,
|
||
): Promise<ZulipAccountResponseDto | null> {
|
||
return this.zulipAccountsService.findByZulipUserId(parseInt(zulipUserId), includeGameUser);
|
||
}
|
||
|
||
/**
|
||
* 根据Zulip邮箱获取账号关联
|
||
*/
|
||
@Get('zulip-email/:zulipEmail')
|
||
@ApiOperation({
|
||
summary: '根据Zulip邮箱获取账号关联',
|
||
description: '根据Zulip邮箱地址获取关联的游戏账号信息'
|
||
})
|
||
@ApiParam({
|
||
name: 'zulipEmail',
|
||
description: 'Zulip邮箱地址',
|
||
example: 'user@example.com'
|
||
})
|
||
@ApiQuery({
|
||
name: 'includeGameUser',
|
||
required: false,
|
||
description: '是否包含游戏用户信息',
|
||
type: Boolean,
|
||
example: false
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '获取成功',
|
||
type: ZulipAccountResponseDto,
|
||
})
|
||
@ApiResponse({
|
||
status: 404,
|
||
description: '关联不存在',
|
||
})
|
||
async findByZulipEmail(
|
||
@Param('zulipEmail') zulipEmail: string,
|
||
@Query('includeGameUser') includeGameUser?: boolean,
|
||
): Promise<ZulipAccountResponseDto | null> {
|
||
return this.zulipAccountsService.findByZulipEmail(zulipEmail, includeGameUser);
|
||
}
|
||
|
||
/**
|
||
* 更新Zulip账号关联
|
||
*/
|
||
@Put(':id')
|
||
@ApiOperation({
|
||
summary: '更新Zulip账号关联',
|
||
description: '根据ID更新Zulip账号关联信息'
|
||
})
|
||
@ApiParam({
|
||
name: 'id',
|
||
description: '关联记录ID',
|
||
example: '1'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '更新成功',
|
||
type: ZulipAccountResponseDto,
|
||
})
|
||
@ApiResponse({
|
||
status: 404,
|
||
description: '记录不存在',
|
||
})
|
||
async update(
|
||
@Param('id') id: string,
|
||
@Body() updateDto: UpdateZulipAccountDto,
|
||
): Promise<ZulipAccountResponseDto> {
|
||
return this.zulipAccountsService.update(id, updateDto);
|
||
}
|
||
|
||
/**
|
||
* 根据游戏用户ID更新关联
|
||
*/
|
||
@Put('game-user/:gameUserId')
|
||
@ApiOperation({
|
||
summary: '根据游戏用户ID更新关联',
|
||
description: '根据游戏用户ID更新Zulip账号关联信息'
|
||
})
|
||
@ApiParam({
|
||
name: 'gameUserId',
|
||
description: '游戏用户ID',
|
||
example: '12345'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '更新成功',
|
||
type: ZulipAccountResponseDto,
|
||
})
|
||
@ApiResponse({
|
||
status: 404,
|
||
description: '关联不存在',
|
||
})
|
||
async updateByGameUserId(
|
||
@Param('gameUserId') gameUserId: string,
|
||
@Body() updateDto: UpdateZulipAccountDto,
|
||
): Promise<ZulipAccountResponseDto> {
|
||
return this.zulipAccountsService.updateByGameUserId(gameUserId, updateDto);
|
||
}
|
||
|
||
/**
|
||
* 删除Zulip账号关联
|
||
*/
|
||
@Delete(':id')
|
||
@ApiOperation({
|
||
summary: '删除Zulip账号关联',
|
||
description: '根据ID删除Zulip账号关联记录'
|
||
})
|
||
@ApiParam({
|
||
name: 'id',
|
||
description: '关联记录ID',
|
||
example: '1'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '删除成功',
|
||
schema: {
|
||
type: 'object',
|
||
properties: {
|
||
success: { type: 'boolean', example: true },
|
||
message: { type: 'string', example: '删除成功' }
|
||
}
|
||
}
|
||
})
|
||
@ApiResponse({
|
||
status: 404,
|
||
description: '记录不存在',
|
||
})
|
||
async delete(@Param('id') id: string): Promise<{ success: boolean; message: string }> {
|
||
await this.zulipAccountsService.delete(id);
|
||
return { success: true, message: '删除成功' };
|
||
}
|
||
|
||
/**
|
||
* 根据游戏用户ID删除关联
|
||
*/
|
||
@Delete('game-user/:gameUserId')
|
||
@ApiOperation({
|
||
summary: '根据游戏用户ID删除关联',
|
||
description: '根据游戏用户ID删除Zulip账号关联记录'
|
||
})
|
||
@ApiParam({
|
||
name: 'gameUserId',
|
||
description: '游戏用户ID',
|
||
example: '12345'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '删除成功',
|
||
schema: {
|
||
type: 'object',
|
||
properties: {
|
||
success: { type: 'boolean', example: true },
|
||
message: { type: 'string', example: '删除成功' }
|
||
}
|
||
}
|
||
})
|
||
@ApiResponse({
|
||
status: 404,
|
||
description: '关联不存在',
|
||
})
|
||
async deleteByGameUserId(@Param('gameUserId') gameUserId: string): Promise<{ success: boolean; message: string }> {
|
||
await this.zulipAccountsService.deleteByGameUserId(gameUserId);
|
||
return { success: true, message: '删除成功' };
|
||
}
|
||
|
||
/**
|
||
* 获取需要验证的账号列表
|
||
*/
|
||
@Get('management/verification-needed')
|
||
@ApiOperation({
|
||
summary: '获取需要验证的账号列表',
|
||
description: '获取超过指定时间未验证的账号列表'
|
||
})
|
||
@ApiQuery({
|
||
name: 'maxAge',
|
||
required: false,
|
||
description: '最大验证间隔(毫秒),默认24小时',
|
||
example: 86400000
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '获取成功',
|
||
type: ZulipAccountListResponseDto,
|
||
})
|
||
async findAccountsNeedingVerification(
|
||
@Query('maxAge') maxAge?: number,
|
||
): Promise<ZulipAccountListResponseDto> {
|
||
return this.zulipAccountsService.findAccountsNeedingVerification(maxAge);
|
||
}
|
||
|
||
/**
|
||
* 获取错误状态的账号列表
|
||
*/
|
||
@Get('management/error-accounts')
|
||
@ApiOperation({
|
||
summary: '获取错误状态的账号列表',
|
||
description: '获取处于错误状态的账号列表'
|
||
})
|
||
@ApiQuery({
|
||
name: 'maxRetryCount',
|
||
required: false,
|
||
description: '最大重试次数,默认3次',
|
||
example: 3
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '获取成功',
|
||
type: ZulipAccountListResponseDto,
|
||
})
|
||
async findErrorAccounts(
|
||
@Query('maxRetryCount') maxRetryCount?: number,
|
||
): Promise<ZulipAccountListResponseDto> {
|
||
return this.zulipAccountsService.findErrorAccounts(maxRetryCount);
|
||
}
|
||
|
||
/**
|
||
* 批量更新账号状态
|
||
*/
|
||
@Put('management/batch-status')
|
||
@ApiOperation({
|
||
summary: '批量更新账号状态',
|
||
description: '批量更新多个账号的状态'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '更新成功',
|
||
type: BatchUpdateResponseDto,
|
||
})
|
||
async batchUpdateStatus(@Body() batchDto: BatchUpdateStatusDto): Promise<BatchUpdateResponseDto> {
|
||
return this.zulipAccountsService.batchUpdateStatus(batchDto.ids, batchDto.status);
|
||
}
|
||
|
||
/**
|
||
* 获取账号状态统计
|
||
*/
|
||
@Get('management/statistics')
|
||
@ApiOperation({
|
||
summary: '获取账号状态统计',
|
||
description: '获取各种状态的账号数量统计'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '获取成功',
|
||
type: ZulipAccountStatsResponseDto,
|
||
})
|
||
async getStatusStatistics(@Req() req: Request): Promise<ZulipAccountStatsResponseDto> {
|
||
const monitor = this.createPerformanceMonitor(req, '获取账号状态统计');
|
||
|
||
try {
|
||
const result = await this.zulipAccountsService.getStatusStatistics();
|
||
monitor.success({
|
||
total: result.total,
|
||
active: result.active,
|
||
error: result.error
|
||
});
|
||
return result;
|
||
} catch (error) {
|
||
monitor.error(error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 验证账号有效性
|
||
*/
|
||
@Post('management/verify')
|
||
@ApiOperation({
|
||
summary: '验证账号有效性',
|
||
description: '验证指定游戏用户的Zulip账号关联是否有效'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '验证完成',
|
||
type: VerifyAccountResponseDto,
|
||
})
|
||
async verifyAccount(@Body() verifyDto: VerifyAccountDto): Promise<VerifyAccountResponseDto> {
|
||
return this.zulipAccountsService.verifyAccount(verifyDto.gameUserId);
|
||
}
|
||
|
||
/**
|
||
* 检查邮箱是否已存在
|
||
*/
|
||
@Get('validation/email-exists/:email')
|
||
@ApiOperation({
|
||
summary: '检查邮箱是否已存在',
|
||
description: '检查指定的Zulip邮箱是否已被其他账号使用'
|
||
})
|
||
@ApiParam({
|
||
name: 'email',
|
||
description: 'Zulip邮箱地址',
|
||
example: 'user@example.com'
|
||
})
|
||
@ApiQuery({
|
||
name: 'excludeId',
|
||
required: false,
|
||
description: '排除的记录ID(用于更新时检查)',
|
||
example: '1'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '检查完成',
|
||
schema: {
|
||
type: 'object',
|
||
properties: {
|
||
exists: { type: 'boolean', example: false },
|
||
email: { type: 'string', example: 'user@example.com' }
|
||
}
|
||
}
|
||
})
|
||
async checkEmailExists(
|
||
@Param('email') email: string,
|
||
@Query('excludeId') excludeId?: string,
|
||
): Promise<{ exists: boolean; email: string }> {
|
||
const exists = await this.zulipAccountsService.existsByEmail(email, excludeId);
|
||
return { exists, email };
|
||
}
|
||
|
||
/**
|
||
* 检查Zulip用户ID是否已存在
|
||
*/
|
||
@Get('validation/zulip-user-exists/:zulipUserId')
|
||
@ApiOperation({
|
||
summary: '检查Zulip用户ID是否已存在',
|
||
description: '检查指定的Zulip用户ID是否已被其他账号使用'
|
||
})
|
||
@ApiParam({
|
||
name: 'zulipUserId',
|
||
description: 'Zulip用户ID',
|
||
example: '67890'
|
||
})
|
||
@ApiQuery({
|
||
name: 'excludeId',
|
||
required: false,
|
||
description: '排除的记录ID(用于更新时检查)',
|
||
example: '1'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '检查完成',
|
||
schema: {
|
||
type: 'object',
|
||
properties: {
|
||
exists: { type: 'boolean', example: false },
|
||
zulipUserId: { type: 'number', example: 67890 }
|
||
}
|
||
}
|
||
})
|
||
async checkZulipUserIdExists(
|
||
@Param('zulipUserId') zulipUserId: string,
|
||
@Query('excludeId') excludeId?: string,
|
||
): Promise<{ exists: boolean; zulipUserId: number }> {
|
||
const zulipUserIdNum = parseInt(zulipUserId);
|
||
const exists = await this.zulipAccountsService.existsByZulipUserId(zulipUserIdNum, excludeId);
|
||
return { exists, zulipUserId: zulipUserIdNum };
|
||
}
|
||
} |