Files
whale-town-end/src/business/admin/database_management.service.ts
moyin 6924416bbd feat:实现管理员系统核心功能
- 添加管理员数据库管理控制器和服务
- 实现管理员操作日志记录系统
- 添加数据库异常处理过滤器
- 完善管理员权限验证和响应格式
- 添加全面的属性测试覆盖
2026-01-08 23:05:34 +08:00

564 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 数据库管理服务
*
* 功能描述:
* - 提供统一的数据库管理接口集成所有数据库服务的CRUD操作
* - 实现管理员专用的数据库操作功能
* - 提供统一的响应格式和错误处理
* - 支持操作日志记录和审计功能
*
* 职责分离:
* - 业务逻辑编排:协调各个数据库服务的操作
* - 数据转换DTO与实体之间的转换
* - 权限控制:确保只有管理员可以执行操作
* - 日志记录:记录所有数据库操作的详细日志
*
* 集成的服务:
* - UsersService: 用户数据管理
* - UserProfilesService: 用户档案管理
* - ZulipAccountsService: Zulip账号关联管理
*
* 最近修改:
* - 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.2.0
* @since 2026-01-08
* @lastModified 2026-01-08
* @since 2026-01-08
* @lastModified 2026-01-08
*/
import { Injectable, Logger, NotFoundException, BadRequestException, ConflictException, Inject } from '@nestjs/common';
import { UsersService } from '../../core/db/users/users.service';
import { generateRequestId, getCurrentTimestamp, UserFormatter, OperationMonitor } from './admin_utils';
/**
* 常量定义
*/
const DEFAULT_PAGE_SIZE = 20;
/**
* 管理员API统一响应格式
*/
export interface AdminApiResponse<T = any> {
success: boolean;
data?: T;
message: string;
error_code?: string;
timestamp?: string;
request_id?: string;
}
/**
* 管理员列表响应格式
*/
export interface AdminListResponse<T = any> {
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,
) {
this.logger.log('DatabaseManagementService初始化完成');
}
/**
* 记录操作日志
*
* @param level 日志级别
* @param message 日志消息
* @param context 日志上下文
*/
private logOperation(level: 'log' | 'warn' | 'error', message: string, context: Record<string, any>): void {
this.logger[level](message, {
...context,
timestamp: getCurrentTimestamp()
});
}
/**
* 创建标准的成功响应
*
* 功能描述:
* 创建符合管理员API标准格式的成功响应对象
*
* @param data 响应数据
* @param message 响应消息
* @returns 标准格式的成功响应
*/
private createSuccessResponse<T>(data: T, message: string): AdminApiResponse<T> {
return {
success: true,
data,
message,
timestamp: getCurrentTimestamp(),
request_id: generateRequestId()
};
}
/**
* 创建标准的错误响应
*
* 功能描述:
* 创建符合管理员API标准格式的错误响应对象
*
* @param message 错误消息
* @param errorCode 错误码
* @returns 标准格式的错误响应
*/
private createErrorResponse(message: string, errorCode?: string): AdminApiResponse {
return {
success: false,
message,
error_code: errorCode,
timestamp: getCurrentTimestamp(),
request_id: generateRequestId()
};
}
/**
* 创建标准的列表响应
*
* 功能描述:
* 创建符合管理员API标准格式的列表响应对象包含分页信息
*
* @param items 列表项
* @param total 总数
* @param limit 限制数量
* @param offset 偏移量
* @param message 响应消息
* @returns 标准格式的列表响应
*/
private createListResponse<T>(
items: T[],
total: number,
limit: number,
offset: number,
message: string
): AdminListResponse<T> {
return {
success: true,
data: {
items,
total,
limit,
offset,
has_more: offset + items.length < total
},
message,
timestamp: getCurrentTimestamp(),
request_id: generateRequestId()
};
}
/**
* 处理服务异常
*
* @param error 异常对象
* @param operation 操作名称
* @param context 操作上下文
* @returns 错误响应
*/
private handleServiceError(error: any, operation: string, context: Record<string, any>): AdminApiResponse {
this.logOperation('error', `${operation}失败`, {
operation,
error: error instanceof Error ? error.message : String(error),
context
});
if (error instanceof NotFoundException) {
return this.createErrorResponse(error.message, 'RESOURCE_NOT_FOUND');
}
if (error instanceof ConflictException) {
return this.createErrorResponse(error.message, 'RESOURCE_CONFLICT');
}
if (error instanceof BadRequestException) {
return this.createErrorResponse(error.message, 'INVALID_REQUEST');
}
return this.createErrorResponse(`${operation}失败,请稍后重试`, 'INTERNAL_ERROR');
}
/**
* 处理列表查询异常
*
* @param error 异常对象
* @param operation 操作名称
* @param context 操作上下文
* @returns 空列表响应
*/
private handleListError(error: any, operation: string, context: Record<string, any>): AdminListResponse {
this.logOperation('error', `${operation}失败`, {
operation,
error: error instanceof Error ? error.message : String(error),
context
});
return this.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<AdminListResponse> {
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 this.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<AdminApiResponse> {
return await OperationMonitor.executeWithMonitoring(
'获取用户详情',
{ userId: id.toString() },
async () => {
const user = await this.usersService.findOne(id);
const formattedUser = UserFormatter.formatDetailedUser(user);
return this.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<AdminListResponse> {
return await OperationMonitor.executeWithMonitoring(
'搜索用户',
{ keyword, limit },
async () => {
const users = await this.usersService.search(keyword, limit);
const formattedUsers = users.map(user => UserFormatter.formatBasicUser(user));
return this.createListResponse(formattedUsers, users.length, limit, 0, '用户搜索成功');
},
this.logOperation.bind(this)
).catch(error => this.handleListError(error, '搜索用户', { keyword, limit }));
}
/**
* 创建用户
*
* @param userData 用户数据
* @returns 创建结果响应
*/
async createUser(userData: any): Promise<AdminApiResponse> {
return await OperationMonitor.executeWithMonitoring(
'创建用户',
{ username: userData.username },
async () => {
const newUser = await this.usersService.create(userData);
const formattedUser = UserFormatter.formatBasicUser(newUser);
return this.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: any): Promise<AdminApiResponse> {
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 this.createSuccessResponse(formattedUser, '用户更新成功');
},
this.logOperation.bind(this)
).catch(error => this.handleServiceError(error, '更新用户', { userId: id.toString(), updateData }));
}
/**
* 删除用户
*
* @param id 用户ID
* @returns 删除结果响应
*/
async deleteUser(id: bigint): Promise<AdminApiResponse> {
return await OperationMonitor.executeWithMonitoring(
'删除用户',
{ userId: id.toString() },
async () => {
await this.usersService.remove(id);
return this.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<AdminListResponse> {
// TODO: 实现用户档案列表查询
return this.createListResponse([], 0, limit, offset, '用户档案列表获取成功(暂未实现)');
}
/**
* 根据ID获取用户档案详情
*
* @param id 档案ID
* @returns 用户档案详情响应
*/
async getUserProfileById(id: bigint): Promise<AdminApiResponse> {
// TODO: 实现用户档案详情查询
return this.createErrorResponse('用户档案详情查询暂未实现', 'NOT_IMPLEMENTED');
}
/**
* 根据地图获取用户档案
*
* @param mapId 地图ID
* @param limit 限制数量
* @param offset 偏移量
* @returns 用户档案列表响应
*/
async getUserProfilesByMap(mapId: string, limit: number = DEFAULT_PAGE_SIZE, offset: number = 0): Promise<AdminListResponse> {
// TODO: 实现按地图查询用户档案
return this.createListResponse([], 0, limit, offset, `地图 ${mapId} 的用户档案列表获取成功(暂未实现)`);
}
/**
* 创建用户档案
*
* @param createProfileDto 创建数据
* @returns 创建结果响应
*/
async createUserProfile(createProfileDto: any): Promise<AdminApiResponse> {
// TODO: 实现用户档案创建
return this.createErrorResponse('用户档案创建暂未实现', 'NOT_IMPLEMENTED');
}
/**
* 更新用户档案
*
* @param id 档案ID
* @param updateProfileDto 更新数据
* @returns 更新结果响应
*/
async updateUserProfile(id: bigint, updateProfileDto: any): Promise<AdminApiResponse> {
// TODO: 实现用户档案更新
return this.createErrorResponse('用户档案更新暂未实现', 'NOT_IMPLEMENTED');
}
/**
* 删除用户档案
*
* @param id 档案ID
* @returns 删除结果响应
*/
async deleteUserProfile(id: bigint): Promise<AdminApiResponse> {
// TODO: 实现用户档案删除
return this.createErrorResponse('用户档案删除暂未实现', 'NOT_IMPLEMENTED');
}
// ==================== Zulip账号关联管理方法 ====================
/**
* 获取Zulip账号关联列表
*
* @param limit 限制数量
* @param offset 偏移量
* @returns Zulip账号关联列表响应
*/
async getZulipAccountList(limit: number = DEFAULT_PAGE_SIZE, offset: number = 0): Promise<AdminListResponse> {
// TODO: 实现Zulip账号关联列表查询
return this.createListResponse([], 0, limit, offset, 'Zulip账号关联列表获取成功暂未实现');
}
/**
* 根据ID获取Zulip账号关联详情
*
* @param id 关联ID
* @returns Zulip账号关联详情响应
*/
async getZulipAccountById(id: string): Promise<AdminApiResponse> {
// TODO: 实现Zulip账号关联详情查询
return this.createErrorResponse('Zulip账号关联详情查询暂未实现', 'NOT_IMPLEMENTED');
}
/**
* 获取Zulip账号关联统计
*
* @returns 统计信息响应
*/
async getZulipAccountStatistics(): Promise<AdminApiResponse> {
// TODO: 实现Zulip账号关联统计
return this.createSuccessResponse({
total: 0,
active: 0,
inactive: 0,
error: 0
}, 'Zulip账号关联统计获取成功暂未实现');
}
/**
* 创建Zulip账号关联
*
* @param createAccountDto 创建数据
* @returns 创建结果响应
*/
async createZulipAccount(createAccountDto: any): Promise<AdminApiResponse> {
// TODO: 实现Zulip账号关联创建
return this.createErrorResponse('Zulip账号关联创建暂未实现', 'NOT_IMPLEMENTED');
}
/**
* 更新Zulip账号关联
*
* @param id 关联ID
* @param updateAccountDto 更新数据
* @returns 更新结果响应
*/
async updateZulipAccount(id: string, updateAccountDto: any): Promise<AdminApiResponse> {
// TODO: 实现Zulip账号关联更新
return this.createErrorResponse('Zulip账号关联更新暂未实现', 'NOT_IMPLEMENTED');
}
/**
* 删除Zulip账号关联
*
* @param id 关联ID
* @returns 删除结果响应
*/
async deleteZulipAccount(id: string): Promise<AdminApiResponse> {
// TODO: 实现Zulip账号关联删除
return this.createErrorResponse('Zulip账号关联删除暂未实现', 'NOT_IMPLEMENTED');
}
/**
* 批量更新Zulip账号状态
*
* @param ids ID列表
* @param status 新状态
* @param reason 操作原因
* @returns 批量更新结果响应
*/
async batchUpdateZulipAccountStatus(ids: string[], status: string, reason?: string): Promise<AdminApiResponse> {
// TODO: 实现Zulip账号关联批量状态更新
return this.createSuccessResponse({
success_count: 0,
failed_count: ids.length,
total_count: ids.length,
errors: ids.map(id => ({ id, error: '批量更新暂未实现' }))
}, 'Zulip账号关联批量状态更新完成暂未实现');
}
}