feat:实现管理员系统核心功能

- 添加管理员数据库管理控制器和服务
- 实现管理员操作日志记录系统
- 添加数据库异常处理过滤器
- 完善管理员权限验证和响应格式
- 添加全面的属性测试覆盖
This commit is contained in:
moyin
2026-01-08 23:05:34 +08:00
parent 0f37130832
commit 6924416bbd
34 changed files with 9481 additions and 199 deletions

View File

@@ -0,0 +1,400 @@
/**
* 管理员数据库管理控制器
*
* 功能描述:
* - 提供管理员专用的数据库管理HTTP接口
* - 集成用户、用户档案、Zulip账号关联的CRUD操作
* - 实现统一的权限控制和参数验证
* - 支持分页查询和搜索功能
*
* 职责分离:
* - HTTP请求处理接收和验证HTTP请求参数
* - 权限控制通过AdminGuard确保只有管理员可以访问
* - 业务委托将业务逻辑委托给DatabaseManagementService处理
* - 响应格式化返回统一格式的HTTP响应
*
* API端点分组
* - /admin/database/users/* 用户管理相关接口
* - /admin/database/user-profiles/* 用户档案管理相关接口
* - /admin/database/zulip-accounts/* Zulip账号关联管理相关接口
*
* 最近修改:
* - 2026-01-08: 注释规范优化 - 修正@author字段更新版本号和修改记录 (修改者: moyin)
* - 2026-01-08: 代码质量优化 - 清理未使用的导入 (修改者: moyin)
* - 2026-01-08: 文件夹扁平化 - 从controllers/子文件夹移动到上级目录 (修改者: moyin)
* - 2026-01-08: 功能新增 - 创建管理员数据库管理控制器 (修改者: assistant)
*
* @author moyin
* @version 1.1.0
* @since 2026-01-08
* @lastModified 2026-01-08
*/
import {
Controller,
Get,
Post,
Put,
Delete,
Param,
Query,
Body,
UseGuards,
UseFilters,
UseInterceptors,
ParseIntPipe,
DefaultValuePipe
} from '@nestjs/common';
import {
ApiTags,
ApiBearerAuth,
ApiOperation,
ApiParam,
ApiQuery,
ApiResponse,
ApiBody
} from '@nestjs/swagger';
import { AdminGuard } from './admin.guard';
import { AdminDatabaseExceptionFilter } from './admin_database_exception.filter';
import { AdminOperationLogInterceptor } from './admin_operation_log.interceptor';
import { LogAdminOperation } from './log_admin_operation.decorator';
import { DatabaseManagementService, AdminApiResponse, AdminListResponse } from './database_management.service';
import {
AdminCreateUserDto,
AdminUpdateUserDto,
AdminBatchUpdateStatusDto,
AdminDatabaseResponseDto,
AdminHealthCheckResponseDto
} from './admin_database.dto';
import { PAGINATION_LIMITS, REQUEST_ID_PREFIXES } from './admin_constants';
import { safeLimitValue, createSuccessResponse, getCurrentTimestamp } from './admin_utils';
@ApiTags('admin-database')
@Controller('admin/database')
@UseGuards(AdminGuard)
@UseFilters(AdminDatabaseExceptionFilter)
@UseInterceptors(AdminOperationLogInterceptor)
@ApiBearerAuth('JWT-auth')
export class AdminDatabaseController {
constructor(
private readonly databaseManagementService: DatabaseManagementService
) {}
// ==================== 用户管理接口 ====================
@ApiOperation({
summary: '获取用户列表',
description: '分页获取用户列表,支持管理员查看所有用户信息'
})
@ApiQuery({ name: 'limit', required: false, description: '返回数量默认20最大100', example: 20 })
@ApiQuery({ name: 'offset', required: false, description: '偏移量默认0', example: 0 })
@ApiResponse({ status: 200, description: '获取成功' })
@ApiResponse({ status: 401, description: '未授权访问' })
@ApiResponse({ status: 403, description: '权限不足' })
@LogAdminOperation({
operationType: 'QUERY',
targetType: 'users',
description: '获取用户列表',
isSensitive: false
})
@Get('users')
async getUserList(
@Query('limit', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_LIMIT), ParseIntPipe) limit: number,
@Query('offset', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_OFFSET), ParseIntPipe) offset: number
): Promise<AdminListResponse> {
const safeLimit = safeLimitValue(limit, PAGINATION_LIMITS.USER_LIST_MAX_LIMIT);
return await this.databaseManagementService.getUserList(safeLimit, offset);
}
@ApiOperation({
summary: '获取用户详情',
description: '根据用户ID获取详细的用户信息'
})
@ApiParam({ name: 'id', description: '用户ID', example: '1' })
@ApiResponse({ status: 200, description: '获取成功' })
@ApiResponse({ status: 404, description: '用户不存在' })
@Get('users/:id')
async getUserById(@Param('id') id: string): Promise<AdminApiResponse> {
return await this.databaseManagementService.getUserById(BigInt(id));
}
@ApiOperation({
summary: '搜索用户',
description: '根据关键词搜索用户,支持用户名、邮箱、昵称模糊匹配'
})
@ApiQuery({ name: 'keyword', description: '搜索关键词', example: 'admin' })
@ApiQuery({ name: 'limit', required: false, description: '返回数量默认20最大50', example: 20 })
@ApiResponse({ status: 200, description: '搜索成功' })
@Get('users/search')
async searchUsers(
@Query('keyword') keyword: string,
@Query('limit', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_LIMIT), ParseIntPipe) limit: number
): Promise<AdminListResponse> {
const safeLimit = safeLimitValue(limit, PAGINATION_LIMITS.SEARCH_MAX_LIMIT);
return await this.databaseManagementService.searchUsers(keyword, safeLimit);
}
@ApiOperation({
summary: '创建用户',
description: '创建新用户,需要提供用户名和昵称等基本信息'
})
@ApiBody({ type: AdminCreateUserDto, description: '用户创建数据' })
@ApiResponse({ status: 201, description: '创建成功', type: AdminDatabaseResponseDto })
@ApiResponse({ status: 400, description: '请求参数错误' })
@ApiResponse({ status: 409, description: '用户名或邮箱已存在' })
@LogAdminOperation({
operationType: 'CREATE',
targetType: 'users',
description: '创建用户',
isSensitive: true
})
@Post('users')
async createUser(@Body() createUserDto: AdminCreateUserDto): Promise<AdminApiResponse> {
return await this.databaseManagementService.createUser(createUserDto);
}
@ApiOperation({
summary: '更新用户',
description: '根据用户ID更新用户信息'
})
@ApiParam({ name: 'id', description: '用户ID', example: '1' })
@ApiBody({ type: AdminUpdateUserDto, description: '用户更新数据' })
@ApiResponse({ status: 200, description: '更新成功', type: AdminDatabaseResponseDto })
@ApiResponse({ status: 404, description: '用户不存在' })
@Put('users/:id')
async updateUser(
@Param('id') id: string,
@Body() updateUserDto: AdminUpdateUserDto
): Promise<AdminApiResponse> {
return await this.databaseManagementService.updateUser(BigInt(id), updateUserDto);
}
@ApiOperation({
summary: '删除用户',
description: '根据用户ID删除用户软删除'
})
@ApiParam({ name: 'id', description: '用户ID', example: '1' })
@ApiResponse({ status: 200, description: '删除成功' })
@ApiResponse({ status: 404, description: '用户不存在' })
@LogAdminOperation({
operationType: 'DELETE',
targetType: 'users',
description: '删除用户',
isSensitive: true
})
@Delete('users/:id')
async deleteUser(@Param('id') id: string): Promise<AdminApiResponse> {
return await this.databaseManagementService.deleteUser(BigInt(id));
}
// ==================== 用户档案管理接口 ====================
@ApiOperation({
summary: '获取用户档案列表',
description: '分页获取用户档案列表,包含位置信息和档案数据'
})
@ApiQuery({ name: 'limit', required: false, description: '返回数量默认20最大100', example: 20 })
@ApiQuery({ name: 'offset', required: false, description: '偏移量默认0', example: 0 })
@ApiResponse({ status: 200, description: '获取成功' })
@Get('user-profiles')
async getUserProfileList(
@Query('limit', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_LIMIT), ParseIntPipe) limit: number,
@Query('offset', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_OFFSET), ParseIntPipe) offset: number
): Promise<AdminListResponse> {
const safeLimit = safeLimitValue(limit, PAGINATION_LIMITS.USER_LIST_MAX_LIMIT);
return await this.databaseManagementService.getUserProfileList(safeLimit, offset);
}
@ApiOperation({
summary: '获取用户档案详情',
description: '根据档案ID获取详细的用户档案信息'
})
@ApiParam({ name: 'id', description: '档案ID', example: '1' })
@ApiResponse({ status: 200, description: '获取成功' })
@ApiResponse({ status: 404, description: '档案不存在' })
@Get('user-profiles/:id')
async getUserProfileById(@Param('id') id: string): Promise<AdminApiResponse> {
return await this.databaseManagementService.getUserProfileById(BigInt(id));
}
@ApiOperation({
summary: '根据地图获取用户档案',
description: '获取指定地图中的所有用户档案信息'
})
@ApiParam({ name: 'mapId', description: '地图ID', example: 'plaza' })
@ApiQuery({ name: 'limit', required: false, description: '返回数量默认20最大100', example: 20 })
@ApiQuery({ name: 'offset', required: false, description: '偏移量默认0', example: 0 })
@ApiResponse({ status: 200, description: '获取成功' })
@Get('user-profiles/by-map/:mapId')
async getUserProfilesByMap(
@Param('mapId') mapId: string,
@Query('limit', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_LIMIT), ParseIntPipe) limit: number,
@Query('offset', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_OFFSET), ParseIntPipe) offset: number
): Promise<AdminListResponse> {
const safeLimit = safeLimitValue(limit, PAGINATION_LIMITS.USER_LIST_MAX_LIMIT);
return await this.databaseManagementService.getUserProfilesByMap(mapId, safeLimit, offset);
}
@ApiOperation({
summary: '创建用户档案',
description: '为指定用户创建档案信息'
})
@ApiBody({ type: 'AdminCreateUserProfileDto', description: '用户档案创建数据' })
@ApiResponse({ status: 201, description: '创建成功' })
@ApiResponse({ status: 400, description: '请求参数错误' })
@ApiResponse({ status: 409, description: '用户档案已存在' })
@Post('user-profiles')
async createUserProfile(@Body() createProfileDto: any): Promise<AdminApiResponse> {
return await this.databaseManagementService.createUserProfile(createProfileDto);
}
@ApiOperation({
summary: '更新用户档案',
description: '根据档案ID更新用户档案信息'
})
@ApiParam({ name: 'id', description: '档案ID', example: '1' })
@ApiBody({ type: 'AdminUpdateUserProfileDto', description: '用户档案更新数据' })
@ApiResponse({ status: 200, description: '更新成功' })
@ApiResponse({ status: 404, description: '档案不存在' })
@Put('user-profiles/:id')
async updateUserProfile(
@Param('id') id: string,
@Body() updateProfileDto: any
): Promise<AdminApiResponse> {
return await this.databaseManagementService.updateUserProfile(BigInt(id), updateProfileDto);
}
@ApiOperation({
summary: '删除用户档案',
description: '根据档案ID删除用户档案'
})
@ApiParam({ name: 'id', description: '档案ID', example: '1' })
@ApiResponse({ status: 200, description: '删除成功' })
@ApiResponse({ status: 404, description: '档案不存在' })
@Delete('user-profiles/:id')
async deleteUserProfile(@Param('id') id: string): Promise<AdminApiResponse> {
return await this.databaseManagementService.deleteUserProfile(BigInt(id));
}
// ==================== Zulip账号关联管理接口 ====================
@ApiOperation({
summary: '获取Zulip账号关联列表',
description: '分页获取Zulip账号关联列表包含关联状态和错误信息'
})
@ApiQuery({ name: 'limit', required: false, description: '返回数量默认20最大100', example: 20 })
@ApiQuery({ name: 'offset', required: false, description: '偏移量默认0', example: 0 })
@ApiResponse({ status: 200, description: '获取成功' })
@Get('zulip-accounts')
async getZulipAccountList(
@Query('limit', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_LIMIT), ParseIntPipe) limit: number,
@Query('offset', new DefaultValuePipe(PAGINATION_LIMITS.DEFAULT_OFFSET), ParseIntPipe) offset: number
): Promise<AdminListResponse> {
const safeLimit = safeLimitValue(limit, PAGINATION_LIMITS.USER_LIST_MAX_LIMIT);
return await this.databaseManagementService.getZulipAccountList(safeLimit, offset);
}
@ApiOperation({
summary: '获取Zulip账号关联详情',
description: '根据关联ID获取详细的Zulip账号关联信息'
})
@ApiParam({ name: 'id', description: '关联ID', example: '1' })
@ApiResponse({ status: 200, description: '获取成功' })
@ApiResponse({ status: 404, description: '关联不存在' })
@Get('zulip-accounts/:id')
async getZulipAccountById(@Param('id') id: string): Promise<AdminApiResponse> {
return await this.databaseManagementService.getZulipAccountById(id);
}
@ApiOperation({
summary: '获取Zulip账号关联统计',
description: '获取各种状态的Zulip账号关联数量统计信息'
})
@ApiResponse({ status: 200, description: '获取成功' })
@Get('zulip-accounts/statistics')
async getZulipAccountStatistics(): Promise<AdminApiResponse> {
return await this.databaseManagementService.getZulipAccountStatistics();
}
@ApiOperation({
summary: '创建Zulip账号关联',
description: '创建游戏用户与Zulip账号的关联'
})
@ApiBody({ type: 'AdminCreateZulipAccountDto', description: 'Zulip账号关联创建数据' })
@ApiResponse({ status: 201, description: '创建成功' })
@ApiResponse({ status: 400, description: '请求参数错误' })
@ApiResponse({ status: 409, description: '关联已存在' })
@Post('zulip-accounts')
async createZulipAccount(@Body() createAccountDto: any): Promise<AdminApiResponse> {
return await this.databaseManagementService.createZulipAccount(createAccountDto);
}
@ApiOperation({
summary: '更新Zulip账号关联',
description: '根据关联ID更新Zulip账号关联信息'
})
@ApiParam({ name: 'id', description: '关联ID', example: '1' })
@ApiBody({ type: 'AdminUpdateZulipAccountDto', description: 'Zulip账号关联更新数据' })
@ApiResponse({ status: 200, description: '更新成功' })
@ApiResponse({ status: 404, description: '关联不存在' })
@Put('zulip-accounts/:id')
async updateZulipAccount(
@Param('id') id: string,
@Body() updateAccountDto: any
): Promise<AdminApiResponse> {
return await this.databaseManagementService.updateZulipAccount(id, updateAccountDto);
}
@ApiOperation({
summary: '删除Zulip账号关联',
description: '根据关联ID删除Zulip账号关联'
})
@ApiParam({ name: 'id', description: '关联ID', example: '1' })
@ApiResponse({ status: 200, description: '删除成功' })
@ApiResponse({ status: 404, description: '关联不存在' })
@Delete('zulip-accounts/:id')
async deleteZulipAccount(@Param('id') id: string): Promise<AdminApiResponse> {
return await this.databaseManagementService.deleteZulipAccount(id);
}
@ApiOperation({
summary: '批量更新Zulip账号状态',
description: '批量更新多个Zulip账号关联的状态'
})
@ApiBody({ type: AdminBatchUpdateStatusDto, description: '批量更新数据' })
@ApiResponse({ status: 200, description: '批量更新完成', type: AdminDatabaseResponseDto })
@LogAdminOperation({
operationType: 'BATCH',
targetType: 'zulip_accounts',
description: '批量更新Zulip账号状态',
isSensitive: true
})
@Post('zulip-accounts/batch-update-status')
async batchUpdateZulipAccountStatus(@Body() batchUpdateDto: AdminBatchUpdateStatusDto): Promise<AdminApiResponse> {
return await this.databaseManagementService.batchUpdateZulipAccountStatus(
batchUpdateDto.ids,
batchUpdateDto.status,
batchUpdateDto.reason
);
}
// ==================== 系统健康检查接口 ====================
@ApiOperation({
summary: '数据库管理系统健康检查',
description: '检查数据库管理系统的运行状态和连接情况'
})
@ApiResponse({ status: 200, description: '系统正常', type: AdminHealthCheckResponseDto })
@Get('health')
async healthCheck(): Promise<AdminApiResponse> {
return createSuccessResponse({
status: 'healthy',
timestamp: getCurrentTimestamp(),
services: {
users: 'connected',
user_profiles: 'connected',
zulip_accounts: 'connected'
}
}, '数据库管理系统运行正常', REQUEST_ID_PREFIXES.HEALTH_CHECK);
}
}