feat: 完善管理员系统和用户管理模块

- 更新管理员控制器和数据库管理功能
- 完善管理员操作日志系统
- 添加全面的属性测试覆盖
- 优化用户管理和用户档案服务
- 更新代码检查规范文档

功能改进:
- 增强管理员权限验证
- 完善操作日志记录
- 优化数据库管理接口
- 提升系统安全性和可维护性
This commit is contained in:
moyin
2026-01-09 17:05:08 +08:00
parent 8816b29b0a
commit 5f662ef091
30 changed files with 3881 additions and 599 deletions

View File

@@ -19,6 +19,10 @@
* - 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)
@@ -26,16 +30,26 @@
* - 2026-01-08: 功能新增 - 创建数据库管理服务,支持管理员数据库操作 (修改者: assistant)
*
* @author moyin
* @version 1.2.0
* @version 1.6.0
* @since 2026-01-08
* @lastModified 2026-01-08
* @since 2026-01-08
* @lastModified 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 { generateRequestId, getCurrentTimestamp, UserFormatter, OperationMonitor } from './admin_utils';
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';
/**
* 常量定义
@@ -78,6 +92,8 @@ export class DatabaseManagementService {
constructor(
@Inject('UsersService') private readonly usersService: UsersService,
@Inject('IUserProfilesService') private readonly userProfilesService: UserProfilesService,
@Inject('ZulipAccountsService') private readonly zulipAccountsService: ZulipAccountsService,
) {
this.logger.log('DatabaseManagementService初始化完成');
}
@@ -96,81 +112,6 @@ export class DatabaseManagementService {
});
}
/**
* 创建标准的成功响应
*
* 功能描述:
* 创建符合管理员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()
};
}
/**
* 处理服务异常
*
@@ -187,18 +128,18 @@ export class DatabaseManagementService {
});
if (error instanceof NotFoundException) {
return this.createErrorResponse(error.message, 'RESOURCE_NOT_FOUND');
return createErrorResponse(error.message, 'RESOURCE_NOT_FOUND');
}
if (error instanceof ConflictException) {
return this.createErrorResponse(error.message, 'RESOURCE_CONFLICT');
return createErrorResponse(error.message, 'RESOURCE_CONFLICT');
}
if (error instanceof BadRequestException) {
return this.createErrorResponse(error.message, 'INVALID_REQUEST');
return createErrorResponse(error.message, 'INVALID_REQUEST');
}
return this.createErrorResponse(`${operation}失败,请稍后重试`, 'INTERNAL_ERROR');
return createErrorResponse(`${operation}失败,请稍后重试`, 'INTERNAL_ERROR');
}
/**
@@ -216,7 +157,7 @@ export class DatabaseManagementService {
context
});
return this.createListResponse([], 0, context.limit || DEFAULT_PAGE_SIZE, context.offset || 0, `${operation}失败,返回空列表`);
return createListResponse([], 0, context.limit || DEFAULT_PAGE_SIZE, context.offset || 0, `${operation}失败,返回空列表`);
}
// ==================== 用户管理方法 ====================
@@ -256,7 +197,7 @@ export class DatabaseManagementService {
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, '用户列表获取成功');
return createListResponse(formattedUsers, total, limit, offset, '用户列表获取成功');
},
this.logOperation.bind(this)
).catch(error => this.handleListError(error, '获取用户列表', { limit, offset }));
@@ -296,7 +237,7 @@ export class DatabaseManagementService {
async () => {
const user = await this.usersService.findOne(id);
const formattedUser = UserFormatter.formatDetailedUser(user);
return this.createSuccessResponse(formattedUser, '用户详情获取成功');
return createSuccessResponse(formattedUser, '用户详情获取成功');
},
this.logOperation.bind(this)
).catch(error => this.handleServiceError(error, '获取用户详情', { userId: id.toString() }));
@@ -335,7 +276,7 @@ export class DatabaseManagementService {
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, '用户搜索成功');
return createListResponse(formattedUsers, users.length, limit, 0, '用户搜索成功');
},
this.logOperation.bind(this)
).catch(error => this.handleListError(error, '搜索用户', { keyword, limit }));
@@ -347,14 +288,14 @@ export class DatabaseManagementService {
* @param userData 用户数据
* @returns 创建结果响应
*/
async createUser(userData: any): Promise<AdminApiResponse> {
async createUser(userData: AdminCreateUserDto): 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, '用户创建成功');
return createSuccessResponse(formattedUser, '用户创建成功');
},
this.logOperation.bind(this)
).catch(error => this.handleServiceError(error, '创建用户', { username: userData.username }));
@@ -367,14 +308,14 @@ export class DatabaseManagementService {
* @param updateData 更新数据
* @returns 更新结果响应
*/
async updateUser(id: bigint, updateData: any): Promise<AdminApiResponse> {
async updateUser(id: bigint, updateData: AdminUpdateUserDto): 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, '用户更新成功');
return createSuccessResponse(formattedUser, '用户更新成功');
},
this.logOperation.bind(this)
).catch(error => this.handleServiceError(error, '更新用户', { userId: id.toString(), updateData }));
@@ -392,7 +333,7 @@ export class DatabaseManagementService {
{ userId: id.toString() },
async () => {
await this.usersService.remove(id);
return this.createSuccessResponse({ deleted: true, id: id.toString() }, '用户删除成功');
return createSuccessResponse({ deleted: true, id: id.toString() }, '用户删除成功');
},
this.logOperation.bind(this)
).catch(error => this.handleServiceError(error, '删除用户', { userId: id.toString() }));
@@ -408,8 +349,17 @@ export class DatabaseManagementService {
* @returns 用户档案列表响应
*/
async getUserProfileList(limit: number = DEFAULT_PAGE_SIZE, offset: number = 0): Promise<AdminListResponse> {
// TODO: 实现用户档案列表查询
return this.createListResponse([], 0, limit, offset, '用户档案列表获取成功(暂未实现)');
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 }));
}
/**
@@ -419,8 +369,16 @@ export class DatabaseManagementService {
* @returns 用户档案详情响应
*/
async getUserProfileById(id: bigint): Promise<AdminApiResponse> {
// TODO: 实现用户档案详情查询
return this.createErrorResponse('用户档案详情查询暂未实现', 'NOT_IMPLEMENTED');
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() }));
}
/**
@@ -432,8 +390,17 @@ export class DatabaseManagementService {
* @returns 用户档案列表响应
*/
async getUserProfilesByMap(mapId: string, limit: number = DEFAULT_PAGE_SIZE, offset: number = 0): Promise<AdminListResponse> {
// TODO: 实现按地图查询用户档案
return this.createListResponse([], 0, limit, offset, `地图 ${mapId} 的用户档案列表获取成功(暂未实现)`);
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 }));
}
/**
@@ -442,9 +409,30 @@ export class DatabaseManagementService {
* @param createProfileDto 创建数据
* @returns 创建结果响应
*/
async createUserProfile(createProfileDto: any): Promise<AdminApiResponse> {
// TODO: 实现用户档案创建
return this.createErrorResponse('用户档案创建暂未实现', 'NOT_IMPLEMENTED');
async createUserProfile(createProfileDto: AdminCreateUserProfileDto): Promise<AdminApiResponse> {
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 }));
}
/**
@@ -454,9 +442,48 @@ export class DatabaseManagementService {
* @param updateProfileDto 更新数据
* @returns 更新结果响应
*/
async updateUserProfile(id: bigint, updateProfileDto: any): Promise<AdminApiResponse> {
// TODO: 实现用户档案更新
return this.createErrorResponse('用户档案更新暂未实现', 'NOT_IMPLEMENTED');
async updateUserProfile(id: bigint, updateProfileDto: AdminUpdateUserProfileDto): Promise<AdminApiResponse> {
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 }));
}
/**
@@ -466,8 +493,15 @@ export class DatabaseManagementService {
* @returns 删除结果响应
*/
async deleteUserProfile(id: bigint): Promise<AdminApiResponse> {
// TODO: 实现用户档案删除
return this.createErrorResponse('用户档案删除暂未实现', 'NOT_IMPLEMENTED');
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账号关联管理方法 ====================
@@ -480,8 +514,24 @@ export class DatabaseManagementService {
* @returns Zulip账号关联列表响应
*/
async getZulipAccountList(limit: number = DEFAULT_PAGE_SIZE, offset: number = 0): Promise<AdminListResponse> {
// TODO: 实现Zulip账号关联列表查询
return this.createListResponse([], 0, limit, offset, 'Zulip账号关联列表获取成功(暂未实现)');
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 }));
}
/**
@@ -491,8 +541,16 @@ export class DatabaseManagementService {
* @returns Zulip账号关联详情响应
*/
async getZulipAccountById(id: string): Promise<AdminApiResponse> {
// TODO: 实现Zulip账号关联详情查询
return this.createErrorResponse('Zulip账号关联详情查询暂未实现', 'NOT_IMPLEMENTED');
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 }));
}
/**
@@ -501,13 +559,15 @@ export class DatabaseManagementService {
* @returns 统计信息响应
*/
async getZulipAccountStatistics(): Promise<AdminApiResponse> {
// TODO: 实现Zulip账号关联统计
return this.createSuccessResponse({
total: 0,
active: 0,
inactive: 0,
error: 0
}, 'Zulip账号关联统计获取成功暂未实现');
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账号关联统计', {}));
}
/**
@@ -516,9 +576,17 @@ export class DatabaseManagementService {
* @param createAccountDto 创建数据
* @returns 创建结果响应
*/
async createZulipAccount(createAccountDto: any): Promise<AdminApiResponse> {
// TODO: 实现Zulip账号关联创建
return this.createErrorResponse('Zulip账号关联创建暂未实现', 'NOT_IMPLEMENTED');
async createZulipAccount(createAccountDto: AdminCreateZulipAccountDto): Promise<AdminApiResponse> {
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 }));
}
/**
@@ -528,9 +596,17 @@ export class DatabaseManagementService {
* @param updateAccountDto 更新数据
* @returns 更新结果响应
*/
async updateZulipAccount(id: string, updateAccountDto: any): Promise<AdminApiResponse> {
// TODO: 实现Zulip账号关联更新
return this.createErrorResponse('Zulip账号关联更新暂未实现', 'NOT_IMPLEMENTED');
async updateZulipAccount(id: string, updateAccountDto: AdminUpdateZulipAccountDto): Promise<AdminApiResponse> {
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 }));
}
/**
@@ -540,8 +616,15 @@ export class DatabaseManagementService {
* @returns 删除结果响应
*/
async deleteZulipAccount(id: string): Promise<AdminApiResponse> {
// TODO: 实现Zulip账号关联删除
return this.createErrorResponse('Zulip账号关联删除暂未实现', 'NOT_IMPLEMENTED');
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 }));
}
/**
@@ -553,12 +636,67 @@ export class DatabaseManagementService {
* @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账号关联批量状态更新完成暂未实现');
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
};
}
}