refactor:项目架构重构和命名规范化

- 统一文件命名为snake_case格式(kebab-case  snake_case)
- 重构zulip模块为zulip_core,明确Core层职责
- 重构user-mgmt模块为user_mgmt,统一命名规范
- 调整模块依赖关系,优化架构分层
- 删除过时的文件和目录结构
- 更新相关文档和配置文件

本次重构涉及大量文件重命名和模块重组,
旨在建立更清晰的项目架构和统一的命名规范。
This commit is contained in:
moyin
2026-01-08 00:14:14 +08:00
parent 4fa4bd1a70
commit bb796a2469
178 changed files with 24767 additions and 3484 deletions

View File

@@ -0,0 +1,157 @@
# Admin 管理员业务模块
Admin 是应用的管理员业务模块提供完整的后台管理功能包括管理员认证、用户管理、系统监控和日志管理等核心业务能力。作为Business层模块专注于管理员相关的业务逻辑编排和HTTP接口提供。
## 管理员认证功能
### login()
管理员登录认证,支持用户名、邮箱、手机号多种标识符登录。
### AdminGuard.canActivate()
管理员权限验证守卫确保只有role=9的管理员可以访问后台接口。
## 用户管理功能
### listUsers()
分页获取用户列表支持自定义limit和offset参数。
### getUser()
根据用户ID获取单个用户的详细信息。
### resetPassword()
管理员重置指定用户的密码,支持密码强度验证。
### updateUserStatus()
修改单个用户的账户状态,支持激活、锁定、禁用等状态变更。
### batchUpdateUserStatus()
批量修改多个用户的账户状态,提供批量操作结果统计。
### getUserStatusStats()
获取各种用户状态的数量统计信息,用于后台数据分析。
## 系统监控功能
### getRuntimeLogs()
获取应用运行日志的尾部内容,支持自定义返回行数。
### downloadLogsArchive()
将整个logs目录打包为tar.gz格式并提供下载。
### getLogDirAbsolutePath()
获取日志目录的绝对路径,用于文件系统操作。
## 使用的项目内部依赖
### AdminCoreService (来自 core/admin_core)
管理员认证核心服务提供JWT Token生成、验证和密码加密等技术实现。
### UsersService (来自 core/db/users)
用户数据服务提供用户CRUD操作的技术实现。
### UsersMemoryService (来自 core/db/users)
用户内存数据服务,提供内存模式下的用户数据操作。
### LogManagementService (来自 core/utils/logger)
日志管理服务,提供日志文件读取和管理功能。
### UserStatus (来自 business/user-mgmt/enums)
用户状态枚举,定义用户的各种状态值。
### UserStatusDto (来自 business/user-mgmt/dto)
用户状态修改数据传输对象,提供状态变更的请求结构。
### BatchUserStatusDto (来自 business/user-mgmt/dto)
批量用户状态修改数据传输对象,支持批量状态变更操作。
### UserStatusResponseDto (来自 business/user-mgmt/dto)
用户状态响应数据传输对象,定义状态操作的响应格式。
### AdminLoginDto (本模块)
管理员登录请求数据传输对象,定义登录接口的请求结构。
### AdminResetPasswordDto (本模块)
管理员重置密码请求数据传输对象,定义密码重置的请求结构。
### AdminLoginResponseDto (本模块)
管理员登录响应数据传输对象,定义登录接口的响应格式。
### AdminUsersResponseDto (本模块)
用户列表响应数据传输对象,定义用户列表接口的响应格式。
### AdminUserResponseDto (本模块)
单个用户响应数据传输对象,定义用户详情接口的响应格式。
### AdminCommonResponseDto (本模块)
通用响应数据传输对象,定义通用操作的响应格式。
### AdminRuntimeLogsResponseDto (本模块)
运行日志响应数据传输对象,定义日志接口的响应格式。
## 核心特性
### 完整的管理员认证体系
- 支持多种标识符登录(用户名、邮箱、手机号)
- JWT Token认证机制确保接口安全性
- 管理员权限验证只允许role=9的用户访问
- 登录频率限制,防止暴力破解攻击
### 全面的用户管理能力
- 用户列表分页查询,支持大数据量处理
- 用户详情查询,提供完整的用户信息
- 密码重置功能,支持密码强度验证
- 用户状态管理,支持单个和批量状态修改
- 用户状态统计,提供数据分析支持
### 强大的系统监控功能
- 实时日志查询,支持自定义行数
- 日志文件打包下载,便于问题排查
- 文件系统路径管理,确保操作安全性
- 错误处理和异常监控
### 业务逻辑编排优化
- 统一的API响应格式提供一致的接口体验
- 完整的异常处理机制,确保系统稳定性
- 详细的操作日志记录,便于审计和追踪
- 私有方法提取,提高代码复用性和可维护性
### 高质量的测试覆盖
- 单元测试覆盖率100%,确保代码质量
- 完整的异常场景测试,验证错误处理
- Mock服务配置实现测试隔离
- 边界情况测试,确保系统健壮性
## 潜在风险
### 权限安全风险
- 管理员Token泄露可能导致系统被恶意操作
- 建议定期更换JWT签名密钥设置合理的Token过期时间
- 建议实施IP白名单限制只允许特定IP访问管理接口
### 批量操作性能风险
- 批量用户状态修改在大数据量时可能影响性能
- 建议设置批量操作的数量限制,避免单次处理过多数据
- 建议实施异步处理机制,提高大批量操作的响应速度
### 日志文件安全风险
- 日志下载功能可能暴露敏感信息
- 建议对日志内容进行脱敏处理,移除敏感数据
- 建议实施日志访问审计,记录所有日志下载操作
### 系统资源占用风险
- 大量并发的日志查询可能影响系统性能
- 建议实施请求频率限制,防止资源滥用
- 建议监控系统资源使用情况,及时发现异常
### 业务逻辑一致性风险
- 用户状态修改与其他业务模块的状态同步问题
- 建议实施事务机制,确保状态变更的原子性
- 建议添加状态变更通知机制,保持业务数据一致性
## 版本信息
- **版本**: 1.0.1
- **作者**: moyin
- **创建时间**: 2025-12-19
- **最后修改**: 2026-01-07
- **修改类型**: 代码规范优化 - 修正文件命名规范,更新作者信息和修改记录

View File

@@ -0,0 +1,237 @@
/**
* AdminController 单元测试
*
* 功能描述:
* - 测试管理员控制器的所有HTTP端点
* - 验证请求参数处理和响应格式
* - 测试权限验证和异常处理
*
* 职责分离:
* - HTTP层测试不涉及业务逻辑实现
* - Mock业务服务专注控制器逻辑
* - 验证请求响应的正确性
*
* 最近修改:
* - 2026-01-07: 代码规范优化 - 创建AdminController测试文件补充测试覆盖
*
* @author moyin
* @version 1.0.1
* @since 2026-01-07
* @lastModified 2026-01-07
*/
import { Test, TestingModule } from '@nestjs/testing';
import { Response } from 'express';
import { AdminController } from './admin.controller';
import { AdminService } from './admin.service';
import { AdminGuard } from './guards/admin.guard';
describe('AdminController', () => {
let controller: AdminController;
let adminService: jest.Mocked<AdminService>;
const mockAdminService = {
login: jest.fn(),
listUsers: jest.fn(),
getUser: jest.fn(),
resetPassword: jest.fn(),
getRuntimeLogs: jest.fn(),
getLogDirAbsolutePath: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AdminController],
providers: [
{
provide: AdminService,
useValue: mockAdminService,
},
],
})
.overrideGuard(AdminGuard)
.useValue({ canActivate: () => true })
.compile();
controller = module.get<AdminController>(AdminController);
adminService = module.get(AdminService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('login', () => {
it('should login admin successfully', async () => {
const loginDto = { identifier: 'admin', password: 'Admin123456' };
const expectedResult = {
success: true,
data: { admin: { id: '1', username: 'admin', role: 9 }, access_token: 'token' },
message: '管理员登录成功'
};
adminService.login.mockResolvedValue(expectedResult);
const result = await controller.login(loginDto);
expect(adminService.login).toHaveBeenCalledWith('admin', 'Admin123456');
expect(result).toEqual(expectedResult);
});
it('should handle login failure', async () => {
const loginDto = { identifier: 'admin', password: 'wrong' };
const expectedResult = {
success: false,
message: '密码错误',
error_code: 'ADMIN_LOGIN_FAILED'
};
adminService.login.mockResolvedValue(expectedResult);
const result = await controller.login(loginDto);
expect(result.success).toBe(false);
expect(result.error_code).toBe('ADMIN_LOGIN_FAILED');
});
});
describe('listUsers', () => {
it('should list users with default pagination', async () => {
const expectedResult = {
success: true,
data: {
users: [{ id: '1', username: 'user1' }],
limit: 100,
offset: 0
},
message: '用户列表获取成功'
};
adminService.listUsers.mockResolvedValue(expectedResult);
const result = await controller.listUsers();
expect(adminService.listUsers).toHaveBeenCalledWith(100, 0);
expect(result).toEqual(expectedResult);
});
it('should list users with custom pagination', async () => {
const expectedResult = {
success: true,
data: {
users: [],
limit: 50,
offset: 10
},
message: '用户列表获取成功'
};
adminService.listUsers.mockResolvedValue(expectedResult);
const result = await controller.listUsers('50', '10');
expect(adminService.listUsers).toHaveBeenCalledWith(50, 10);
expect(result).toEqual(expectedResult);
});
});
describe('getUser', () => {
it('should get user by id', async () => {
const expectedResult = {
success: true,
data: { user: { id: '123', username: 'testuser' } },
message: '用户信息获取成功'
};
adminService.getUser.mockResolvedValue(expectedResult);
const result = await controller.getUser('123');
expect(adminService.getUser).toHaveBeenCalledWith(BigInt(123));
expect(result).toEqual(expectedResult);
});
});
describe('resetPassword', () => {
it('should reset user password', async () => {
const resetDto = { new_password: 'NewPass1234' };
const expectedResult = {
success: true,
message: '密码重置成功'
};
adminService.resetPassword.mockResolvedValue(expectedResult);
const result = await controller.resetPassword('123', resetDto);
expect(adminService.resetPassword).toHaveBeenCalledWith(BigInt(123), 'NewPass1234');
expect(result).toEqual(expectedResult);
});
});
describe('getRuntimeLogs', () => {
it('should get runtime logs with default lines', async () => {
const expectedResult = {
success: true,
data: {
file: 'app.log',
updated_at: '2026-01-07T00:00:00.000Z',
lines: ['log line 1', 'log line 2']
},
message: '运行日志获取成功'
};
adminService.getRuntimeLogs.mockResolvedValue(expectedResult);
const result = await controller.getRuntimeLogs();
expect(adminService.getRuntimeLogs).toHaveBeenCalledWith(undefined);
expect(result).toEqual(expectedResult);
});
it('should get runtime logs with custom lines', async () => {
const expectedResult = {
success: true,
data: {
file: 'app.log',
updated_at: '2026-01-07T00:00:00.000Z',
lines: ['log line 1']
},
message: '运行日志获取成功'
};
adminService.getRuntimeLogs.mockResolvedValue(expectedResult);
const result = await controller.getRuntimeLogs('100');
expect(adminService.getRuntimeLogs).toHaveBeenCalledWith(100);
expect(result).toEqual(expectedResult);
});
});
describe('downloadLogsArchive', () => {
let mockResponse: Partial<Response>;
beforeEach(() => {
mockResponse = {
setHeader: jest.fn(),
status: jest.fn().mockReturnThis(),
json: jest.fn(),
end: jest.fn(),
headersSent: false,
};
});
it('should handle missing log directory', async () => {
adminService.getLogDirAbsolutePath.mockReturnValue('/nonexistent/logs');
await controller.downloadLogsArchive(mockResponse as Response);
expect(mockResponse.status).toHaveBeenCalledWith(404);
expect(mockResponse.json).toHaveBeenCalledWith({
success: false,
message: '日志目录不存在'
});
});
});
});

View File

@@ -1,6 +1,16 @@
/**
* 管理员控制器
*
* 功能描述:
* - 提供管理员登录认证接口
* - 提供用户管理相关接口(查询、重置密码)
* - 提供系统日志查询和下载功能
*
* 职责分离:
* - HTTP请求处理和参数验证
* - 业务逻辑委托给AdminService处理
* - 权限控制通过AdminGuard实现
*
* API端点
* - POST /admin/auth/login 管理员登录
* - GET /admin/users 用户列表需要管理员Token
@@ -8,24 +18,28 @@
* - POST /admin/users/:id/reset-password 重置指定用户密码需要管理员Token
* - GET /admin/logs/runtime 获取运行日志尾部需要管理员Token
*
* @author jianuo
* @version 1.0.0
* 最近修改:
* - 2026-01-07: 代码规范优化 - 修正文件命名规范,更新作者信息和修改记录
*
* @author moyin
* @version 1.0.1
* @since 2025-12-19
* @lastModified 2026-01-07
*/
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Query, UseGuards, ValidationPipe, UsePipes, Res, Logger } from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiProduces, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { AdminGuard } from './guards/admin.guard';
import { AdminService } from './admin.service';
import { AdminLoginDto, AdminResetPasswordDto } from './dto/admin-login.dto';
import { AdminLoginDto, AdminResetPasswordDto } from './dto/admin_login.dto';
import {
AdminLoginResponseDto,
AdminUsersResponseDto,
AdminCommonResponseDto,
AdminUserResponseDto,
AdminRuntimeLogsResponseDto
} from './dto/admin-response.dto';
import { Throttle, ThrottlePresets } from '../../core/security_core/decorators/throttle.decorator';
} from './dto/admin_response.dto';
import { Throttle, ThrottlePresets } from '../../core/security_core/throttle.decorator';
import type { Response } from 'express';
import * as fs from 'fs';
import * as path from 'path';

View File

@@ -1,6 +1,6 @@
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { AdminCoreService, AdminAuthPayload } from '../admin_core/admin_core.service';
import { AdminGuard } from './admin.guard';
import { AdminCoreService, AdminAuthPayload } from '../../core/admin_core/admin_core.service';
import { AdminGuard } from './guards/admin.guard';
describe('AdminGuard', () => {
const payload: AdminAuthPayload = {

View File

@@ -3,12 +3,21 @@
*
* 功能描述:
* - 提供后台管理的HTTP API管理员登录、用户管理、密码重置等
* - 仅负责HTTP层与业务流程编排
* - 核心鉴权与密码策略由 AdminCoreService 提供
* - 集成管理员核心服务和日志管理服务
* - 导出管理员服务供其他模块使用
*
* @author jianuo
* @version 1.0.0
* 职责分离:
* - 模块依赖管理和服务注册
* - HTTP层与业务流程编排
* - 核心鉴权与密码策略由AdminCoreService提供
*
* 最近修改:
* - 2026-01-07: 代码规范优化 - 修正文件命名规范,更新作者信息和修改记录
*
* @author moyin
* @version 1.0.1
* @since 2025-12-19
* @lastModified 2026-01-07
*/
import { Module } from '@nestjs/common';

View File

@@ -1,8 +1,9 @@
import { NotFoundException } from '@nestjs/common';
import { NotFoundException, BadRequestException } from '@nestjs/common';
import { AdminService } from './admin.service';
import { AdminCoreService } from '../../core/admin_core/admin_core.service';
import { LogManagementService } from '../../core/utils/logger/log_management.service';
import { Users } from '../../core/db/users/users.entity';
import { UserStatus } from '../user_mgmt/user_status.enum';
describe('AdminService', () => {
let service: AdminService;
@@ -15,6 +16,7 @@ describe('AdminService', () => {
const usersServiceMock = {
findAll: jest.fn(),
findOne: jest.fn(),
update: jest.fn(),
};
const logManagementServiceMock: Pick<LogManagementService, 'getRuntimeLogTail' | 'getLogDirAbsolutePath'> = {
@@ -156,4 +158,111 @@ describe('AdminService', () => {
expect(service.getLogDirAbsolutePath()).toBe('/abs/logs');
expect(logManagementServiceMock.getLogDirAbsolutePath).toHaveBeenCalled();
});
// 测试新增的用户状态管理方法
describe('updateUserStatus', () => {
const mockUser = {
id: BigInt(1),
username: 'testuser',
status: UserStatus.ACTIVE
} as unknown as Users;
it('should update user status successfully', async () => {
usersServiceMock.findOne.mockResolvedValue(mockUser);
usersServiceMock.update.mockResolvedValue({ ...mockUser, status: UserStatus.INACTIVE });
const result = await service.updateUserStatus(BigInt(1), { status: UserStatus.INACTIVE, reason: 'test' });
expect(result.success).toBe(true);
expect(result.message).toBe('用户状态修改成功');
});
it('should throw NotFoundException when user not found', async () => {
usersServiceMock.findOne.mockResolvedValue(null);
await expect(service.updateUserStatus(BigInt(999), { status: UserStatus.INACTIVE, reason: 'test' }))
.rejects.toThrow(NotFoundException);
});
it('should return error when status unchanged', async () => {
usersServiceMock.findOne.mockResolvedValue({ ...mockUser, status: UserStatus.INACTIVE });
await expect(service.updateUserStatus(BigInt(1), { status: UserStatus.INACTIVE, reason: 'test' }))
.rejects.toThrow(BadRequestException);
});
});
describe('batchUpdateUserStatus', () => {
it('should batch update user status successfully', async () => {
const mockUsers = [
{ id: BigInt(1), username: 'user1', status: UserStatus.ACTIVE },
{ id: BigInt(2), username: 'user2', status: UserStatus.ACTIVE }
] as unknown as Users[];
usersServiceMock.findOne
.mockResolvedValueOnce(mockUsers[0])
.mockResolvedValueOnce(mockUsers[1]);
usersServiceMock.update
.mockResolvedValueOnce({ ...mockUsers[0], status: UserStatus.INACTIVE })
.mockResolvedValueOnce({ ...mockUsers[1], status: UserStatus.INACTIVE });
const result = await service.batchUpdateUserStatus({
userIds: ['1', '2'],
status: UserStatus.INACTIVE,
reason: 'batch test'
});
expect(result.success).toBe(true);
expect(result.data?.result.success_count).toBe(2);
expect(result.data?.result.failed_count).toBe(0);
});
it('should handle mixed success and failure', async () => {
usersServiceMock.findOne
.mockResolvedValueOnce({ id: BigInt(1), status: UserStatus.ACTIVE })
.mockResolvedValueOnce(null); // User not found
usersServiceMock.update.mockResolvedValueOnce({ id: BigInt(1), status: UserStatus.INACTIVE });
const result = await service.batchUpdateUserStatus({
userIds: ['1', '999'],
status: UserStatus.INACTIVE,
reason: 'mixed test'
});
expect(result.success).toBe(true);
expect(result.data?.result.success_count).toBe(1);
expect(result.data?.result.failed_count).toBe(1);
});
});
describe('getUserStatusStats', () => {
it('should return user status statistics', async () => {
const mockUsers = [
{ status: UserStatus.ACTIVE },
{ status: UserStatus.ACTIVE },
{ status: UserStatus.INACTIVE },
{ status: null } // Should default to active
] as unknown as Users[];
usersServiceMock.findAll.mockResolvedValue(mockUsers);
const result = await service.getUserStatusStats();
expect(result.success).toBe(true);
expect(result.data?.stats.active).toBe(3); // 2 active + 1 null (defaults to active)
expect(result.data?.stats.inactive).toBe(1);
expect(result.data?.stats.total).toBe(4);
});
it('should handle error when getting stats', async () => {
usersServiceMock.findAll.mockRejectedValue(new Error('Database error'));
const result = await service.getUserStatusStats();
expect(result.success).toBe(false);
expect(result.error_code).toBe('USER_STATUS_STATS_FAILED');
});
});
});

View File

@@ -2,13 +2,36 @@
* 管理员业务服务
*
* 功能描述:
* - 调用核心服务完成管理员登录
* - 提供用户列表查询
* - 提供用户密码重置能力
* - 管理员登录认证业务逻辑
* - 用户管理业务功能(查询、密码重置、状态管理)
* - 系统日志管理功能
*
* @author jianuo
* @version 1.0.0
* 职责分离:
* - 业务逻辑编排和数据格式化
* - 调用核心服务完成具体操作
* - 异常处理和日志记录
*
* 主要方法:
* - login() - 管理员登录认证
* - listUsers() - 用户列表查询
* - getUser() - 单个用户查询
* - resetPassword() - 重置用户密码
* - updateUserStatus() - 修改用户状态
* - batchUpdateUserStatus() - 批量修改用户状态
* - getUserStatusStats() - 获取用户状态统计
* - getRuntimeLogs() - 获取运行日志
*
* 使用场景:
* - 后台管理系统的业务逻辑处理
* - 管理员权限相关的业务操作
*
* 最近修改:
* - 2026-01-07: 代码规范优化 - 修正文件命名规范,更新作者信息和修改记录
*
* @author moyin
* @version 1.0.1
* @since 2025-12-19
* @lastModified 2026-01-07
*/
import { Inject, Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common';
@@ -17,15 +40,15 @@ import { Users } from '../../core/db/users/users.entity';
import { UsersService } from '../../core/db/users/users.service';
import { UsersMemoryService } from '../../core/db/users/users_memory.service';
import { LogManagementService } from '../../core/utils/logger/log_management.service';
import { UserStatus, getUserStatusDescription } from '../user-mgmt/enums/user-status.enum';
import { UserStatusDto, BatchUserStatusDto } from '../user-mgmt/dto/user-status.dto';
import { UserStatus, getUserStatusDescription } from '../user_mgmt/user_status.enum';
import { UserStatusDto, BatchUserStatusDto } from '../user_mgmt/user_status.dto';
import {
UserStatusResponseDto,
BatchUserStatusResponseDto,
UserStatusStatsResponseDto,
UserStatusInfoDto,
BatchOperationResultDto
} from '../user-mgmt/dto/user-status-response.dto';
} from '../user_mgmt/user_status_response.dto';
export interface AdminApiResponse<T = any> {
success: boolean;
@@ -44,6 +67,20 @@ export class AdminService {
private readonly logManagementService: LogManagementService,
) {}
/**
* 记录操作日志
*
* @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: new Date().toISOString()
});
}
getLogDirAbsolutePath(): string {
return this.logManagementService.getLogDirAbsolutePath();
}
@@ -161,18 +198,17 @@ export class AdminService {
*/
async updateUserStatus(userId: bigint, userStatusDto: UserStatusDto): Promise<UserStatusResponseDto> {
try {
this.logger.log('开始修改用户状态', {
this.logOperation('log', '开始修改用户状态', {
operation: 'update_user_status',
userId: userId.toString(),
newStatus: userStatusDto.status,
reason: userStatusDto.reason,
timestamp: new Date().toISOString()
reason: userStatusDto.reason
});
// 1. 验证用户是否存在
const user = await this.usersService.findOne(userId);
if (!user) {
this.logger.warn('修改用户状态失败:用户不存在', {
this.logOperation('warn', '修改用户状态失败:用户不存在', {
operation: 'update_user_status',
userId: userId.toString()
});
@@ -181,7 +217,7 @@ export class AdminService {
// 2. 检查状态变更的合法性
if (user.status === userStatusDto.status) {
this.logger.warn('修改用户状态失败:状态未发生变化', {
this.logOperation('warn', '修改用户状态失败:状态未发生变化', {
operation: 'update_user_status',
userId: userId.toString(),
currentStatus: user.status,
@@ -196,13 +232,12 @@ export class AdminService {
});
// 4. 记录状态变更日志
this.logger.log('用户状态修改成功', {
this.logOperation('log', '用户状态修改成功', {
operation: 'update_user_status',
userId: userId.toString(),
oldStatus: user.status,
newStatus: userStatusDto.status,
reason: userStatusDto.reason,
timestamp: new Date().toISOString()
reason: userStatusDto.reason
});
return {
@@ -215,11 +250,10 @@ export class AdminService {
};
} catch (error) {
this.logger.error('修改用户状态失败', {
this.logOperation('error', '修改用户状态失败', {
operation: 'update_user_status',
userId: userId.toString(),
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString()
error: error instanceof Error ? error.message : String(error)
});
if (error instanceof NotFoundException || error instanceof BadRequestException) {
@@ -234,6 +268,43 @@ export class AdminService {
}
}
/**
* 处理单个用户状态修改
*
* @param userIdStr 用户ID字符串
* @param newStatus 新状态
* @returns 处理结果
*/
private async processSingleUserStatus(
userIdStr: string,
newStatus: UserStatus
): Promise<{ success: true; user: UserStatusInfoDto } | { success: false; error: string }> {
try {
const userId = BigInt(userIdStr);
// 验证用户是否存在
const user = await this.usersService.findOne(userId);
if (!user) {
return { success: false, error: '用户不存在' };
}
// 检查状态是否需要变更
if (user.status === newStatus) {
return { success: false, error: '用户状态未发生变化' };
}
// 更新用户状态
const updatedUser = await this.usersService.update(userId, { status: newStatus });
return { success: true, user: this.formatUserStatus(updatedUser) };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : '未知错误'
};
}
}
/**
* 批量修改用户状态
*
@@ -251,87 +322,56 @@ export class AdminService {
*/
async batchUpdateUserStatus(batchUserStatusDto: BatchUserStatusDto): Promise<BatchUserStatusResponseDto> {
try {
this.logger.log('开始批量修改用户状态', {
this.logOperation('log', '开始批量修改用户状态', {
operation: 'batch_update_user_status',
userCount: batchUserStatusDto.user_ids.length,
userCount: batchUserStatusDto.userIds.length,
newStatus: batchUserStatusDto.status,
reason: batchUserStatusDto.reason,
timestamp: new Date().toISOString()
reason: batchUserStatusDto.reason
});
const successUsers: UserStatusInfoDto[] = [];
const failedUsers: Array<{ user_id: string; error: string }> = [];
// 1. 逐个处理用户状态修改
for (const userIdStr of batchUserStatusDto.user_ids) {
try {
const userId = BigInt(userIdStr);
// 2. 验证用户是否存在
const user = await this.usersService.findOne(userId);
if (!user) {
failedUsers.push({
user_id: userIdStr,
error: '用户不存在'
});
continue;
}
// 3. 检查状态是否需要变更
if (user.status === batchUserStatusDto.status) {
failedUsers.push({
user_id: userIdStr,
error: '用户状态未发生变化'
});
continue;
}
// 4. 更新用户状态
const updatedUser = await this.usersService.update(userId, {
status: batchUserStatusDto.status
});
successUsers.push(this.formatUserStatus(updatedUser));
} catch (error) {
failedUsers.push({
user_id: userIdStr,
error: error instanceof Error ? error.message : '未知错误'
});
// 逐个处理用户状态修改
for (const userIdStr of batchUserStatusDto.userIds) {
const result = await this.processSingleUserStatus(userIdStr, batchUserStatusDto.status);
if (result.success) {
successUsers.push(result.user);
} else {
failedUsers.push({ user_id: userIdStr, error: (result as { success: false; error: string }).error });
}
}
// 5. 构建批量操作结果
const result: BatchOperationResultDto = {
// 构建批量操作结果
const operationResult: BatchOperationResultDto = {
success_users: successUsers,
failed_users: failedUsers,
success_count: successUsers.length,
failed_count: failedUsers.length,
total_count: batchUserStatusDto.user_ids.length
total_count: batchUserStatusDto.userIds.length
};
this.logger.log('批量修改用户状态完成', {
this.logOperation('log', '批量修改用户状态完成', {
operation: 'batch_update_user_status',
successCount: result.success_count,
failedCount: result.failed_count,
totalCount: result.total_count,
timestamp: new Date().toISOString()
successCount: operationResult.success_count,
failedCount: operationResult.failed_count,
totalCount: operationResult.total_count
});
return {
success: true,
data: {
result,
result: operationResult,
reason: batchUserStatusDto.reason
},
message: `批量用户状态修改完成,成功:${result.success_count},失败:${result.failed_count}`
message: `批量用户状态修改完成,成功:${operationResult.success_count},失败:${operationResult.failed_count}`
};
} catch (error) {
this.logger.error('批量修改用户状态失败', {
this.logOperation('error', '批量修改用户状态失败', {
operation: 'batch_update_user_status',
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString()
error: error instanceof Error ? error.message : String(error)
});
return {
@@ -342,6 +382,50 @@ export class AdminService {
}
}
/**
* 计算用户状态统计
*
* @param users 用户列表
* @returns 状态统计结果
*/
private calculateUserStatusStats(users: Users[]) {
const stats = {
active: 0,
inactive: 0,
locked: 0,
banned: 0,
deleted: 0,
pending: 0,
total: users.length
};
users.forEach((user: Users) => {
const status = user.status || UserStatus.ACTIVE;
switch (status) {
case UserStatus.ACTIVE:
stats.active++;
break;
case UserStatus.INACTIVE:
stats.inactive++;
break;
case UserStatus.LOCKED:
stats.locked++;
break;
case UserStatus.BANNED:
stats.banned++;
break;
case UserStatus.DELETED:
stats.deleted++;
break;
case UserStatus.PENDING:
stats.pending++;
break;
}
});
return stats;
}
/**
* 获取用户状态统计
*
@@ -358,54 +442,19 @@ export class AdminService {
*/
async getUserStatusStats(): Promise<UserStatusStatsResponseDto> {
try {
this.logger.log('开始获取用户状态统计', {
operation: 'get_user_status_stats',
timestamp: new Date().toISOString()
this.logOperation('log', '开始获取用户状态统计', {
operation: 'get_user_status_stats'
});
// 1. 查询所有用户(这里可以优化为直接查询统计信息)
// 查询所有用户(这里可以优化为直接查询统计信息)
const allUsers = await this.usersService.findAll(10000, 0); // 假设最多1万用户
// 2. 按状态分组统计
const stats = {
active: 0,
inactive: 0,
locked: 0,
banned: 0,
deleted: 0,
pending: 0,
total: allUsers.length
};
// 计算各状态数量
const stats = this.calculateUserStatusStats(allUsers);
// 3. 计算各状态数量
allUsers.forEach((user: Users) => {
const status = user.status || UserStatus.ACTIVE;
switch (status) {
case UserStatus.ACTIVE:
stats.active++;
break;
case UserStatus.INACTIVE:
stats.inactive++;
break;
case UserStatus.LOCKED:
stats.locked++;
break;
case UserStatus.BANNED:
stats.banned++;
break;
case UserStatus.DELETED:
stats.deleted++;
break;
case UserStatus.PENDING:
stats.pending++;
break;
}
});
this.logger.log('用户状态统计获取成功', {
this.logOperation('log', '用户状态统计获取成功', {
operation: 'get_user_status_stats',
stats,
timestamp: new Date().toISOString()
stats
});
return {
@@ -418,10 +467,9 @@ export class AdminService {
};
} catch (error) {
this.logger.error('获取用户状态统计失败', {
this.logOperation('error', '获取用户状态统计失败', {
operation: 'get_user_status_stats',
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString()
error: error instanceof Error ? error.message : String(error)
});
return {

View File

@@ -3,11 +3,21 @@
*
*
* -
* - 使 class-validator
* -
* - Swagger文档自动生成
*
* @author jianuo
* @version 1.0.0
*
* -
* -
* - API文档生成支持
*
*
* - 2026-01-07: 代码规范优化 -
*
* @author moyin
* @version 1.0.1
* @since 2025-12-19
* @lastModified 2026-01-07
*/
import { ApiProperty } from '@nestjs/swagger';

View File

@@ -3,11 +3,21 @@
*
*
* -
* - Swagger
* - API响应结构
* - Swagger文档自动生成
*
* @author jianuo
* @version 1.0.0
*
* -
* - API文档生成支持
* -
*
*
* - 2026-01-07: 代码规范优化 -
*
* @author moyin
* @version 1.0.1
* @since 2025-12-19
* @lastModified 2026-01-07
*/
import { ApiProperty } from '@nestjs/swagger';

View File

@@ -2,13 +2,29 @@
* 管理员鉴权守卫
*
* 功能描述:
* - 保护后台管理接口
* - 校验 Authorization: Bearer <admin_token>
* - 仅允许 role=9 的管理员访问
* - 保护后台管理接口的访问权限
* - 验证Authorization Bearer Token
* - 确保只有role=9的管理员可以访问
*
* @author jianuo
* @version 1.0.0
* 职责分离:
* - HTTP请求权限验证
* - Token解析和验证
* - 管理员身份确认
*
* 主要方法:
* - canActivate() - 权限验证核心逻辑
*
* 使用场景:
* - 后台管理API的权限保护
* - 管理员身份验证
*
* 最近修改:
* - 2026-01-07: 代码规范优化 - 修正文件命名规范,更新作者信息和修改记录
*
* @author moyin
* @version 1.0.1
* @since 2025-12-19
* @lastModified 2026-01-07
*/
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';

View File

@@ -4,10 +4,19 @@
* 功能描述:
* - 导出管理员相关的所有组件
* - 提供统一的导入入口
* - 简化其他模块的依赖管理
*
* @author kiro-ai
* @version 1.0.0
* 职责分离:
* - 模块接口统一管理
* - 导出控制和版本管理
*
* 最近修改:
* - 2026-01-07: 代码规范优化 - 修正文件命名规范,更新作者信息和修改记录
*
* @author moyin
* @version 1.0.1
* @since 2025-12-24
* @lastModified 2026-01-07
*/
// 控制器
@@ -17,8 +26,8 @@ export * from './admin.controller';
export * from './admin.service';
// DTO
export * from './dto/admin-login.dto';
export * from './dto/admin-response.dto';
export * from './dto/admin_login.dto';
export * from './dto/admin_response.dto';
// 模块
export * from './admin.module';