/** * 数据库管理服务 * * 功能描述: * - 提供统一的数据库管理接口,集成所有数据库服务的CRUD操作 * - 实现管理员专用的数据库操作功能 * - 提供统一的响应格式和错误处理 * - 支持操作日志记录和审计功能 * * 职责分离: * - 业务逻辑编排:协调各个数据库服务的操作 * - 数据转换:DTO与实体之间的转换 * - 权限控制:确保只有管理员可以执行操作 * - 日志记录:记录所有数据库操作的详细日志 * * 集成的服务: * - UsersService: 用户数据管理 * - UserProfilesService: 用户档案管理 * - ZulipAccountsService: Zulip账号关联管理 * * 最近修改: * - 2026-01-09: Bug修复 - 修复类型错误,正确处理skin_id类型转换和Zulip账号查询参数 (修改者: moyin) * - 2026-01-09: 功能实现 - 实现所有TODO项,完成UserProfiles和ZulipAccounts的CRUD操作 (修改者: moyin) * - 2026-01-09: 代码质量优化 - 替换any类型为具体的DTO类型,提高类型安全性 (修改者: moyin) * - 2026-01-09: 代码质量优化 - 统一使用admin_utils中的响应创建函数,消除重复代码 (修改者: moyin) * - 2026-01-08: 注释规范优化 - 修正@author字段,更新版本号和修改记录 (修改者: moyin) * - 2026-01-08: 注释规范优化 - 完善方法注释,添加@param、@returns、@throws和@example (修改者: moyin) * - 2026-01-08: 代码规范优化 - 将魔法数字20提取为常量DEFAULT_PAGE_SIZE (修改者: moyin) * - 2026-01-08: 代码质量优化 - 提取用户格式化逻辑,补充缺失方法实现,使用操作监控工具 (修改者: moyin) * - 2026-01-08: 功能新增 - 创建数据库管理服务,支持管理员数据库操作 (修改者: assistant) * * @author moyin * @version 1.6.0 * @since 2026-01-08 * @lastModified 2026-01-09 */ import { Injectable, Logger, NotFoundException, BadRequestException, ConflictException, Inject } from '@nestjs/common'; import { UsersService } from '../../core/db/users/users.service'; import { UserProfilesService } from '../../core/db/user_profiles/user_profiles.service'; import { UserProfiles } from '../../core/db/user_profiles/user_profiles.entity'; import { ZulipAccountsService } from '../../core/db/zulip_accounts/zulip_accounts.service'; import { ZulipAccountResponseDto } from '../../core/db/zulip_accounts/zulip_accounts.dto'; import { getCurrentTimestamp, UserFormatter, OperationMonitor, createSuccessResponse, createErrorResponse, createListResponse } from './admin_utils'; import { AdminCreateUserDto, AdminUpdateUserDto, AdminCreateUserProfileDto, AdminUpdateUserProfileDto, AdminCreateZulipAccountDto, AdminUpdateZulipAccountDto } from './admin_database.dto'; /** * 常量定义 */ const DEFAULT_PAGE_SIZE = 20; /** * 管理员API统一响应格式 */ export interface AdminApiResponse { success: boolean; data?: T; message: string; error_code?: string; timestamp?: string; request_id?: string; } /** * 管理员列表响应格式 */ export interface AdminListResponse { success: boolean; data: { items: T[]; total: number; limit: number; offset: number; has_more: boolean; }; message: string; error_code?: string; timestamp?: string; request_id?: string; } @Injectable() export class DatabaseManagementService { private readonly logger = new Logger(DatabaseManagementService.name); constructor( @Inject('UsersService') private readonly usersService: UsersService, @Inject('IUserProfilesService') private readonly userProfilesService: UserProfilesService, @Inject('ZulipAccountsService') private readonly zulipAccountsService: ZulipAccountsService, ) { this.logger.log('DatabaseManagementService初始化完成'); } /** * 记录操作日志 * * @param level 日志级别 * @param message 日志消息 * @param context 日志上下文 */ private logOperation(level: 'log' | 'warn' | 'error', message: string, context: Record): void { this.logger[level](message, { ...context, timestamp: getCurrentTimestamp() }); } /** * 处理服务异常 * * @param error 异常对象 * @param operation 操作名称 * @param context 操作上下文 * @returns 错误响应 */ private handleServiceError(error: any, operation: string, context: Record): AdminApiResponse { this.logOperation('error', `${operation}失败`, { operation, error: error instanceof Error ? error.message : String(error), context }); if (error instanceof NotFoundException) { return createErrorResponse(error.message, 'RESOURCE_NOT_FOUND'); } if (error instanceof ConflictException) { return createErrorResponse(error.message, 'RESOURCE_CONFLICT'); } if (error instanceof BadRequestException) { return createErrorResponse(error.message, 'INVALID_REQUEST'); } return createErrorResponse(`${operation}失败,请稍后重试`, 'INTERNAL_ERROR'); } /** * 处理列表查询异常 * * @param error 异常对象 * @param operation 操作名称 * @param context 操作上下文 * @returns 空列表响应 */ private handleListError(error: any, operation: string, context: Record): AdminListResponse { this.logOperation('error', `${operation}失败`, { operation, error: error instanceof Error ? error.message : String(error), context }); return createListResponse([], 0, context.limit || DEFAULT_PAGE_SIZE, context.offset || 0, `${operation}失败,返回空列表`); } // ==================== 用户管理方法 ==================== /** * 获取用户列表 * * 功能描述: * 分页获取系统中的用户列表,支持限制数量和偏移量参数 * * 业务逻辑: * 1. 记录操作开始时间和参数 * 2. 调用用户服务获取用户数据和总数 * 3. 格式化用户信息,隐藏敏感字段 * 4. 记录操作成功日志和性能数据 * 5. 返回标准化的列表响应 * * @param limit 限制数量,默认20,最大100 * @param offset 偏移量,默认0,用于分页 * @returns 包含用户列表、总数和分页信息的响应对象 * * @throws NotFoundException 当查询条件无效时 * @throws InternalServerErrorException 当数据库操作失败时 * * @example * ```typescript * const result = await service.getUserList(20, 0); * console.log(result.data.items.length); // 用户数量 * console.log(result.data.total); // 总用户数 * ``` */ async getUserList(limit: number = DEFAULT_PAGE_SIZE, offset: number = 0): Promise { return await OperationMonitor.executeWithMonitoring( '获取用户列表', { limit, offset }, async () => { const users = await this.usersService.findAll(limit, offset); const total = await this.usersService.count(); const formattedUsers = users.map(user => UserFormatter.formatBasicUser(user)); return createListResponse(formattedUsers, total, limit, offset, '用户列表获取成功'); }, this.logOperation.bind(this) ).catch(error => this.handleListError(error, '获取用户列表', { limit, offset })); } /** * 根据ID获取用户详情 * * 功能描述: * 根据用户ID获取指定用户的详细信息 * * 业务逻辑: * 1. 记录操作开始时间和用户ID * 2. 调用用户服务查询用户信息 * 3. 格式化用户详细信息 * 4. 记录操作成功日志和性能数据 * 5. 返回标准化的详情响应 * * @param id 用户ID,必须是有效的bigint类型 * @returns 包含用户详细信息的响应对象 * * @throws NotFoundException 当用户不存在时 * @throws BadRequestException 当用户ID格式无效时 * @throws InternalServerErrorException 当数据库操作失败时 * * @example * ```typescript * const result = await service.getUserById(BigInt(123)); * console.log(result.data.username); // 用户名 * console.log(result.data.email); // 邮箱 * ``` */ async getUserById(id: bigint): Promise { return await OperationMonitor.executeWithMonitoring( '获取用户详情', { userId: id.toString() }, async () => { const user = await this.usersService.findOne(id); const formattedUser = UserFormatter.formatDetailedUser(user); return createSuccessResponse(formattedUser, '用户详情获取成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '获取用户详情', { userId: id.toString() })); } /** * 搜索用户 * * 功能描述: * 根据关键词搜索用户,支持用户名、邮箱、昵称等字段的模糊匹配 * * 业务逻辑: * 1. 记录搜索操作开始时间和关键词 * 2. 调用用户服务执行搜索查询 * 3. 格式化搜索结果 * 4. 记录搜索成功日志和性能数据 * 5. 返回标准化的搜索响应 * * @param keyword 搜索关键词,支持用户名、邮箱、昵称的模糊匹配 * @param limit 返回结果数量限制,默认20,最大50 * @returns 包含搜索结果的响应对象 * * @throws BadRequestException 当关键词为空或格式无效时 * @throws InternalServerErrorException 当搜索操作失败时 * * @example * ```typescript * const result = await service.searchUsers('admin', 10); * console.log(result.data.items); // 搜索结果列表 * ``` */ async searchUsers(keyword: string, limit: number = DEFAULT_PAGE_SIZE): Promise { return await OperationMonitor.executeWithMonitoring( '搜索用户', { keyword, limit }, async () => { const users = await this.usersService.search(keyword, limit); const formattedUsers = users.map(user => UserFormatter.formatBasicUser(user)); return createListResponse(formattedUsers, users.length, limit, 0, '用户搜索成功'); }, this.logOperation.bind(this) ).catch(error => this.handleListError(error, '搜索用户', { keyword, limit })); } /** * 创建用户 * * @param userData 用户数据 * @returns 创建结果响应 */ async createUser(userData: AdminCreateUserDto): Promise { return await OperationMonitor.executeWithMonitoring( '创建用户', { username: userData.username }, async () => { const newUser = await this.usersService.create(userData); const formattedUser = UserFormatter.formatBasicUser(newUser); return createSuccessResponse(formattedUser, '用户创建成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '创建用户', { username: userData.username })); } /** * 更新用户 * * @param id 用户ID * @param updateData 更新数据 * @returns 更新结果响应 */ async updateUser(id: bigint, updateData: AdminUpdateUserDto): Promise { return await OperationMonitor.executeWithMonitoring( '更新用户', { userId: id.toString(), updateFields: Object.keys(updateData) }, async () => { const updatedUser = await this.usersService.update(id, updateData); const formattedUser = UserFormatter.formatBasicUser(updatedUser); return createSuccessResponse(formattedUser, '用户更新成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '更新用户', { userId: id.toString(), updateData })); } /** * 删除用户 * * @param id 用户ID * @returns 删除结果响应 */ async deleteUser(id: bigint): Promise { return await OperationMonitor.executeWithMonitoring( '删除用户', { userId: id.toString() }, async () => { await this.usersService.remove(id); return createSuccessResponse({ deleted: true, id: id.toString() }, '用户删除成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '删除用户', { userId: id.toString() })); } // ==================== 用户档案管理方法 ==================== /** * 获取用户档案列表 * * @param limit 限制数量 * @param offset 偏移量 * @returns 用户档案列表响应 */ async getUserProfileList(limit: number = DEFAULT_PAGE_SIZE, offset: number = 0): Promise { return await OperationMonitor.executeWithMonitoring( '获取用户档案列表', { limit, offset }, async () => { const profiles = await this.userProfilesService.findAll({ limit, offset }); const total = await this.userProfilesService.count(); const formattedProfiles = profiles.map(profile => this.formatUserProfile(profile)); return createListResponse(formattedProfiles, total, limit, offset, '用户档案列表获取成功'); }, this.logOperation.bind(this) ).catch(error => this.handleListError(error, '获取用户档案列表', { limit, offset })); } /** * 根据ID获取用户档案详情 * * @param id 档案ID * @returns 用户档案详情响应 */ async getUserProfileById(id: bigint): Promise { return await OperationMonitor.executeWithMonitoring( '获取用户档案详情', { profileId: id.toString() }, async () => { const profile = await this.userProfilesService.findOne(id); const formattedProfile = this.formatUserProfile(profile); return createSuccessResponse(formattedProfile, '用户档案详情获取成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '获取用户档案详情', { profileId: id.toString() })); } /** * 根据地图获取用户档案 * * @param mapId 地图ID * @param limit 限制数量 * @param offset 偏移量 * @returns 用户档案列表响应 */ async getUserProfilesByMap(mapId: string, limit: number = DEFAULT_PAGE_SIZE, offset: number = 0): Promise { return await OperationMonitor.executeWithMonitoring( '根据地图获取用户档案', { mapId, limit, offset }, async () => { const profiles = await this.userProfilesService.findByMap(mapId, undefined, limit, offset); const total = await this.userProfilesService.count(); const formattedProfiles = profiles.map(profile => this.formatUserProfile(profile)); return createListResponse(formattedProfiles, total, limit, offset, `地图 ${mapId} 的用户档案列表获取成功`); }, this.logOperation.bind(this) ).catch(error => this.handleListError(error, '根据地图获取用户档案', { mapId, limit, offset })); } /** * 创建用户档案 * * @param createProfileDto 创建数据 * @returns 创建结果响应 */ async createUserProfile(createProfileDto: AdminCreateUserProfileDto): Promise { return await OperationMonitor.executeWithMonitoring( '创建用户档案', { userId: createProfileDto.user_id }, async () => { const profileData = { user_id: BigInt(createProfileDto.user_id), bio: createProfileDto.bio, resume_content: createProfileDto.resume_content, tags: createProfileDto.tags ? JSON.parse(createProfileDto.tags) : undefined, social_links: createProfileDto.social_links ? JSON.parse(createProfileDto.social_links) : undefined, skin_id: createProfileDto.skin_id ? parseInt(createProfileDto.skin_id) : undefined, current_map: createProfileDto.current_map, pos_x: createProfileDto.pos_x, pos_y: createProfileDto.pos_y, status: createProfileDto.status }; const newProfile = await this.userProfilesService.create(profileData); const formattedProfile = this.formatUserProfile(newProfile); return createSuccessResponse(formattedProfile, '用户档案创建成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '创建用户档案', { userId: createProfileDto.user_id })); } /** * 更新用户档案 * * @param id 档案ID * @param updateProfileDto 更新数据 * @returns 更新结果响应 */ async updateUserProfile(id: bigint, updateProfileDto: AdminUpdateUserProfileDto): Promise { return await OperationMonitor.executeWithMonitoring( '更新用户档案', { profileId: id.toString(), updateFields: Object.keys(updateProfileDto) }, async () => { // 转换AdminUpdateUserProfileDto为UpdateUserProfileDto const updateData: any = {}; if (updateProfileDto.bio !== undefined) { updateData.bio = updateProfileDto.bio; } if (updateProfileDto.resume_content !== undefined) { updateData.resume_content = updateProfileDto.resume_content; } if (updateProfileDto.tags !== undefined) { updateData.tags = JSON.parse(updateProfileDto.tags); } if (updateProfileDto.social_links !== undefined) { updateData.social_links = JSON.parse(updateProfileDto.social_links); } if (updateProfileDto.skin_id !== undefined) { updateData.skin_id = parseInt(updateProfileDto.skin_id); } if (updateProfileDto.current_map !== undefined) { updateData.current_map = updateProfileDto.current_map; } if (updateProfileDto.pos_x !== undefined) { updateData.pos_x = updateProfileDto.pos_x; } if (updateProfileDto.pos_y !== undefined) { updateData.pos_y = updateProfileDto.pos_y; } if (updateProfileDto.status !== undefined) { updateData.status = updateProfileDto.status; } const updatedProfile = await this.userProfilesService.update(id, updateData); const formattedProfile = this.formatUserProfile(updatedProfile); return createSuccessResponse(formattedProfile, '用户档案更新成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '更新用户档案', { profileId: id.toString(), updateData: updateProfileDto })); } /** * 删除用户档案 * * @param id 档案ID * @returns 删除结果响应 */ async deleteUserProfile(id: bigint): Promise { return await OperationMonitor.executeWithMonitoring( '删除用户档案', { profileId: id.toString() }, async () => { const result = await this.userProfilesService.remove(id); return createSuccessResponse({ deleted: true, id: id.toString(), affected: result.affected }, '用户档案删除成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '删除用户档案', { profileId: id.toString() })); } // ==================== Zulip账号关联管理方法 ==================== /** * 获取Zulip账号关联列表 * * @param limit 限制数量 * @param offset 偏移量 * @returns Zulip账号关联列表响应 */ async getZulipAccountList(limit: number = DEFAULT_PAGE_SIZE, offset: number = 0): Promise { return await OperationMonitor.executeWithMonitoring( '获取Zulip账号关联列表', { limit, offset }, async () => { // ZulipAccountsService的findMany方法目前不支持分页参数 // 先获取所有数据,然后手动分页 const result = await this.zulipAccountsService.findMany({}); // 手动实现分页 const startIndex = offset; const endIndex = offset + limit; const paginatedAccounts = result.accounts.slice(startIndex, endIndex); const formattedAccounts = paginatedAccounts.map(account => this.formatZulipAccount(account)); return createListResponse(formattedAccounts, result.total, limit, offset, 'Zulip账号关联列表获取成功'); }, this.logOperation.bind(this) ).catch(error => this.handleListError(error, '获取Zulip账号关联列表', { limit, offset })); } /** * 根据ID获取Zulip账号关联详情 * * @param id 关联ID * @returns Zulip账号关联详情响应 */ async getZulipAccountById(id: string): Promise { return await OperationMonitor.executeWithMonitoring( '获取Zulip账号关联详情', { accountId: id }, async () => { const account = await this.zulipAccountsService.findById(id, true); const formattedAccount = this.formatZulipAccount(account); return createSuccessResponse(formattedAccount, 'Zulip账号关联详情获取成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '获取Zulip账号关联详情', { accountId: id })); } /** * 获取Zulip账号关联统计 * * @returns 统计信息响应 */ async getZulipAccountStatistics(): Promise { return await OperationMonitor.executeWithMonitoring( '获取Zulip账号关联统计', {}, async () => { const stats = await this.zulipAccountsService.getStatusStatistics(); return createSuccessResponse(stats, 'Zulip账号关联统计获取成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '获取Zulip账号关联统计', {})); } /** * 创建Zulip账号关联 * * @param createAccountDto 创建数据 * @returns 创建结果响应 */ async createZulipAccount(createAccountDto: AdminCreateZulipAccountDto): Promise { return await OperationMonitor.executeWithMonitoring( '创建Zulip账号关联', { gameUserId: createAccountDto.gameUserId }, async () => { const newAccount = await this.zulipAccountsService.create(createAccountDto); const formattedAccount = this.formatZulipAccount(newAccount); return createSuccessResponse(formattedAccount, 'Zulip账号关联创建成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '创建Zulip账号关联', { gameUserId: createAccountDto.gameUserId })); } /** * 更新Zulip账号关联 * * @param id 关联ID * @param updateAccountDto 更新数据 * @returns 更新结果响应 */ async updateZulipAccount(id: string, updateAccountDto: AdminUpdateZulipAccountDto): Promise { return await OperationMonitor.executeWithMonitoring( '更新Zulip账号关联', { accountId: id, updateFields: Object.keys(updateAccountDto) }, async () => { const updatedAccount = await this.zulipAccountsService.update(id, updateAccountDto); const formattedAccount = this.formatZulipAccount(updatedAccount); return createSuccessResponse(formattedAccount, 'Zulip账号关联更新成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '更新Zulip账号关联', { accountId: id, updateData: updateAccountDto })); } /** * 删除Zulip账号关联 * * @param id 关联ID * @returns 删除结果响应 */ async deleteZulipAccount(id: string): Promise { return await OperationMonitor.executeWithMonitoring( '删除Zulip账号关联', { accountId: id }, async () => { const result = await this.zulipAccountsService.delete(id); return createSuccessResponse({ deleted: result, id }, 'Zulip账号关联删除成功'); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '删除Zulip账号关联', { accountId: id })); } /** * 批量更新Zulip账号状态 * * @param ids ID列表 * @param status 新状态 * @param reason 操作原因 * @returns 批量更新结果响应 */ async batchUpdateZulipAccountStatus(ids: string[], status: string, reason?: string): Promise { return await OperationMonitor.executeWithMonitoring( '批量更新Zulip账号状态', { count: ids.length, status, reason }, async () => { const result = await this.zulipAccountsService.batchUpdateStatus(ids, status as any); return createSuccessResponse({ success_count: result.updatedCount, failed_count: ids.length - result.updatedCount, total_count: ids.length, reason }, `Zulip账号关联批量状态更新完成,成功:${result.updatedCount},失败:${ids.length - result.updatedCount}`); }, this.logOperation.bind(this) ).catch(error => this.handleServiceError(error, '批量更新Zulip账号状态', { count: ids.length, status, reason })); } /** * 格式化用户档案信息 * * @param profile 用户档案实体 * @returns 格式化的用户档案信息 */ private formatUserProfile(profile: UserProfiles) { return { id: profile.id.toString(), user_id: profile.user_id.toString(), bio: profile.bio, resume_content: profile.resume_content, tags: profile.tags, social_links: profile.social_links, skin_id: profile.skin_id, current_map: profile.current_map, pos_x: profile.pos_x, pos_y: profile.pos_y, status: profile.status, last_login_at: profile.last_login_at, last_position_update: profile.last_position_update }; } /** * 格式化Zulip账号关联信息 * * @param account Zulip账号关联实体 * @returns 格式化的Zulip账号关联信息 */ private formatZulipAccount(account: ZulipAccountResponseDto) { return { id: account.id, gameUserId: account.gameUserId, zulipUserId: account.zulipUserId, zulipEmail: account.zulipEmail, zulipFullName: account.zulipFullName, status: account.status, lastVerifiedAt: account.lastVerifiedAt, lastSyncedAt: account.lastSyncedAt, errorMessage: account.errorMessage, retryCount: account.retryCount, createdAt: account.createdAt, updatedAt: account.updatedAt, gameUser: account.gameUser }; } }