forked from datawhale/whale-town-end
refactor:项目架构重构和命名规范化
- 统一文件命名为snake_case格式(kebab-case snake_case) - 重构zulip模块为zulip_core,明确Core层职责 - 重构user-mgmt模块为user_mgmt,统一命名规范 - 调整模块依赖关系,优化架构分层 - 删除过时的文件和目录结构 - 更新相关文档和配置文件 本次重构涉及大量文件重命名和模块重组, 旨在建立更清晰的项目架构和统一的命名规范。
This commit is contained in:
186
src/business/user_mgmt/README.md
Normal file
186
src/business/user_mgmt/README.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# UserMgmt 用户管理业务模块
|
||||
|
||||
UserMgmt 是应用的用户状态管理业务模块,提供完整的用户状态变更、批量操作、状态统计和审计功能,支持管理员对用户生命周期的全面管理,具备完善的权限控制、频率限制和操作审计能力。
|
||||
|
||||
## 用户状态管理
|
||||
|
||||
### updateUserStatus()
|
||||
修改单个用户的账户状态,支持激活、锁定、禁用等操作,记录状态变更原因和审计日志。
|
||||
|
||||
### getUserStatusStats()
|
||||
获取各种用户状态的数量统计信息,提供用户状态分布分析和业务指标计算。
|
||||
|
||||
### getUserStatusHistory()
|
||||
查询指定用户的状态变更历史记录,提供完整的状态变更审计追踪。
|
||||
|
||||
## 批量操作管理
|
||||
|
||||
### batchUpdateUserStatus()
|
||||
批量修改多个用户的账户状态,支持数量限制控制和操作结果统计反馈。
|
||||
|
||||
## 使用的项目内部依赖
|
||||
|
||||
### AdminService (来自 business/admin/admin.service)
|
||||
底层管理员服务,提供用户状态修改的技术实现和数据持久化能力。
|
||||
|
||||
### AdminGuard (来自 business/admin/guards/admin.guard)
|
||||
管理员权限守卫,确保只有具备管理员权限的用户才能执行状态管理操作。
|
||||
|
||||
### UserStatus (本模块)
|
||||
用户状态枚举,定义用户的激活、锁定、禁用、删除、待审核等状态值。
|
||||
|
||||
### UserStatusDto (本模块)
|
||||
用户状态修改请求数据传输对象,提供状态值和修改原因的数据验证规则。
|
||||
|
||||
### BatchUserStatusDto (本模块)
|
||||
批量用户状态修改请求数据传输对象,支持用户ID列表和批量操作数量限制验证。
|
||||
|
||||
### UserStatusResponseDto (本模块)
|
||||
用户状态修改响应数据传输对象,提供统一的API响应格式和错误信息封装。
|
||||
|
||||
### BatchUserStatusResponseDto (本模块)
|
||||
批量用户状态修改响应数据传输对象,包含操作结果统计和成功失败详情。
|
||||
|
||||
### UserStatusStatsResponseDto (本模块)
|
||||
用户状态统计响应数据传输对象,提供各状态用户数量和统计时间信息。
|
||||
|
||||
### ThrottlePresets (来自 core/security_core/throttle.decorator)
|
||||
频率限制预设配置,控制管理员操作的频率以防止滥用。
|
||||
|
||||
### TimeoutPresets (来自 core/security_core/timeout.decorator)
|
||||
超时控制预设配置,为不同类型的操作设置合理的超时时间。
|
||||
|
||||
### BATCH_OPERATION (本模块)
|
||||
批量操作相关常量,定义批量操作的最大最小用户数量限制。
|
||||
|
||||
### VALIDATION (本模块)
|
||||
验证规则常量,定义状态修改原因的最大长度等验证参数。
|
||||
|
||||
### ERROR_CODES (本模块)
|
||||
错误代码常量,提供标准化的错误代码定义和错误处理支持。
|
||||
|
||||
### MESSAGES (本模块)
|
||||
业务消息常量,定义用户友好的错误消息和提示信息。
|
||||
|
||||
### UTILS (本模块)
|
||||
工具函数集合,提供时间戳生成等通用功能。
|
||||
|
||||
## 核心特性
|
||||
|
||||
### RESTful API设计
|
||||
- 标准化的HTTP方法和状态码使用
|
||||
- 统一的请求响应数据格式
|
||||
- 完整的Swagger API文档自动生成
|
||||
- 符合REST设计原则的资源路径规划
|
||||
|
||||
### 权限和安全控制
|
||||
- AdminGuard管理员权限验证
|
||||
- JWT Bearer Token身份认证
|
||||
- 操作频率限制防止API滥用
|
||||
- 请求超时控制避免资源占用
|
||||
|
||||
### 批量操作支持
|
||||
- 支持1-100个用户的批量状态修改
|
||||
- 批量操作结果详细统计和反馈
|
||||
- 部分成功场景的优雅处理
|
||||
- 批量操作数量限制和业务规则验证
|
||||
|
||||
### 数据验证和类型安全
|
||||
- class-validator装饰器数据验证
|
||||
- TypeScript类型系统完整支持
|
||||
- 枚举值验证和错误提示
|
||||
- 请求参数自动转换和验证
|
||||
|
||||
### 审计和日志记录
|
||||
- 完整的操作审计日志记录
|
||||
- 状态变更原因和时间戳记录
|
||||
- 操作者身份和操作类型追踪
|
||||
- 业务指标统计和分析支持
|
||||
|
||||
### 错误处理和用户体验
|
||||
- 标准化的错误代码和消息
|
||||
- 用户友好的错误提示信息
|
||||
- 详细的操作结果反馈
|
||||
- 优雅的异常处理和降级机制
|
||||
|
||||
## 潜在风险
|
||||
|
||||
### 批量操作性能风险
|
||||
- 批量修改100个用户可能造成数据库性能压力
|
||||
- 大量并发批量操作可能导致系统响应缓慢
|
||||
- 建议监控批量操作的执行时间和数据库负载
|
||||
|
||||
### 权限控制风险
|
||||
- AdminGuard依赖外部权限验证逻辑
|
||||
- 权限验证失败可能导致未授权访问
|
||||
- 建议定期审计管理员权限分配和使用情况
|
||||
|
||||
### 数据一致性风险
|
||||
- 批量操作中部分成功可能导致数据不一致
|
||||
- 并发状态修改可能产生竞态条件
|
||||
- 建议在关键业务场景中使用事务控制
|
||||
|
||||
### 审计日志存储风险
|
||||
- 大量的状态变更操作会产生海量审计日志
|
||||
- 日志存储空间可能快速增长
|
||||
- 建议制定日志轮转和归档策略
|
||||
|
||||
### API滥用风险
|
||||
- 频率限制可能无法完全防止恶意调用
|
||||
- 批量操作接口可能被用于攻击
|
||||
- 建议结合IP限制和行为分析进行防护
|
||||
|
||||
### 业务逻辑风险
|
||||
- 状态变更历史功能当前返回空数据
|
||||
- 某些边界情况的业务规则可能不完善
|
||||
- 建议完善状态变更历史功能和业务规则验证
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 修改单个用户状态
|
||||
```typescript
|
||||
// 锁定违规用户
|
||||
const result = await userManagementService.updateUserStatus(BigInt(123), {
|
||||
status: UserStatus.LOCKED,
|
||||
reason: '用户发布违规内容'
|
||||
});
|
||||
```
|
||||
|
||||
### 批量修改用户状态
|
||||
```typescript
|
||||
// 批量激活新用户
|
||||
const result = await userManagementService.batchUpdateUserStatus({
|
||||
userIds: ['456', '789', '101'],
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '批量激活通过审核的新用户'
|
||||
});
|
||||
```
|
||||
|
||||
### 获取用户状态统计
|
||||
```typescript
|
||||
// 获取用户状态分布统计
|
||||
const stats = await userManagementService.getUserStatusStats();
|
||||
console.log(`活跃用户: ${stats.data.stats.active}人`);
|
||||
```
|
||||
|
||||
## 模块配置
|
||||
|
||||
### 依赖模块
|
||||
- AdminModule: 提供底层管理员服务支持
|
||||
- AdminCoreModule: 提供核心管理功能和权限控制
|
||||
|
||||
### 导出服务
|
||||
- UserManagementService: 用户管理业务逻辑服务
|
||||
|
||||
### API路由
|
||||
- PUT /admin/users/:id/status - 修改用户状态
|
||||
- POST /admin/users/batch-status - 批量修改用户状态
|
||||
- GET /admin/users/status-stats - 获取用户状态统计
|
||||
|
||||
## 版本信息
|
||||
|
||||
- **版本**: 1.0.1
|
||||
- **作者**: moyin
|
||||
- **创建时间**: 2025-12-24
|
||||
- **最后修改**: 2026-01-07
|
||||
- **修改内容**: 代码规范优化,完善测试覆盖,增强功能文档
|
||||
38
src/business/user_mgmt/index.ts
Normal file
38
src/business/user_mgmt/index.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 用户管理业务模块导出
|
||||
*
|
||||
* 功能描述:
|
||||
* - 用户状态管理(激活、锁定、禁用等)
|
||||
* - 批量用户操作
|
||||
* - 用户状态统计和分析
|
||||
* - 状态变更审计和历史记录
|
||||
*
|
||||
* 职责分离:
|
||||
* - 统一导出用户管理模块的所有公共组件
|
||||
* - 提供模块化的访问接口
|
||||
* - 简化外部模块的依赖管理
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-07: 代码规范优化 - 修正文件命名规范,完善注释规范,更新作者信息 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @since 2025-12-24
|
||||
* @lastModified 2026-01-07
|
||||
*/
|
||||
|
||||
// 模块
|
||||
export * from './user_mgmt.module';
|
||||
|
||||
// 控制器
|
||||
export * from './user_status.controller';
|
||||
|
||||
// 服务
|
||||
export * from './user_management.service';
|
||||
|
||||
// DTO
|
||||
export * from './user_status.dto';
|
||||
export * from './user_status_response.dto';
|
||||
|
||||
// 常量
|
||||
export * from './user_mgmt.constants';
|
||||
453
src/business/user_mgmt/user_management.service.spec.ts
Normal file
453
src/business/user_mgmt/user_management.service.spec.ts
Normal file
@@ -0,0 +1,453 @@
|
||||
/**
|
||||
* 用户管理业务服务测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试用户状态管理业务逻辑
|
||||
* - 测试批量用户操作功能
|
||||
* - 测试用户状态统计功能
|
||||
* - 测试状态变更审计功能
|
||||
*
|
||||
* 职责分离:
|
||||
* - 单元测试覆盖所有公共方法
|
||||
* - 异常情况和边界情况测试
|
||||
* - Mock依赖服务的行为验证
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-07: 代码规范优化 - 创建完整的测试覆盖 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @since 2026-01-07
|
||||
* @lastModified 2026-01-07
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { UserManagementService } from './user_management.service';
|
||||
import { AdminService } from '../admin/admin.service';
|
||||
import { UserStatusDto, BatchUserStatusDto } from './user_status.dto';
|
||||
import { UserStatus } from './user_status.enum';
|
||||
import { BATCH_OPERATION, ERROR_CODES, MESSAGES } from './user_mgmt.constants';
|
||||
|
||||
describe('UserManagementService', () => {
|
||||
let service: UserManagementService;
|
||||
let mockAdminService: jest.Mocked<AdminService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockAdminServiceProvider = {
|
||||
updateUserStatus: jest.fn(),
|
||||
batchUpdateUserStatus: jest.fn(),
|
||||
getUserStatusStats: jest.fn(),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
UserManagementService,
|
||||
{
|
||||
provide: AdminService,
|
||||
useValue: mockAdminServiceProvider,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<UserManagementService>(UserManagementService);
|
||||
mockAdminService = module.get(AdminService);
|
||||
|
||||
// Mock Logger to avoid console output during tests
|
||||
jest.spyOn(Logger.prototype, 'log').mockImplementation();
|
||||
jest.spyOn(Logger.prototype, 'warn').mockImplementation();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('updateUserStatus', () => {
|
||||
it('should update user status successfully', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(123);
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '用户申诉通过'
|
||||
};
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: '123',
|
||||
username: 'testuser',
|
||||
nickname: '测试用户',
|
||||
status: UserStatus.ACTIVE,
|
||||
status_description: '正常',
|
||||
updated_at: new Date()
|
||||
},
|
||||
reason: '用户申诉通过'
|
||||
},
|
||||
message: '用户状态修改成功'
|
||||
};
|
||||
|
||||
mockAdminService.updateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await service.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(mockAdminService.updateUserStatus).toHaveBeenCalledWith(userId, userStatusDto);
|
||||
expect(mockAdminService.updateUserStatus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle update failure', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(999);
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.LOCKED,
|
||||
reason: '违规操作'
|
||||
};
|
||||
const expectedResult = {
|
||||
success: false,
|
||||
message: '用户不存在',
|
||||
error_code: 'USER_NOT_FOUND'
|
||||
};
|
||||
|
||||
mockAdminService.updateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await service.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(mockAdminService.updateUserStatus).toHaveBeenCalledWith(userId, userStatusDto);
|
||||
});
|
||||
|
||||
it('should log success when update succeeds', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(123);
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '测试'
|
||||
};
|
||||
const successResult = {
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: '123',
|
||||
username: 'testuser',
|
||||
nickname: '测试用户',
|
||||
status: UserStatus.ACTIVE,
|
||||
status_description: '正常',
|
||||
updated_at: new Date()
|
||||
},
|
||||
reason: '测试'
|
||||
},
|
||||
message: '成功'
|
||||
};
|
||||
|
||||
mockAdminService.updateUserStatus.mockResolvedValue(successResult);
|
||||
const logSpy = jest.spyOn(Logger.prototype, 'log');
|
||||
|
||||
// Act
|
||||
await service.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'用户管理:用户状态修改成功',
|
||||
expect.objectContaining({
|
||||
operation: 'user_mgmt_update_status_success',
|
||||
userId: '123',
|
||||
newStatus: UserStatus.ACTIVE
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('batchUpdateUserStatus', () => {
|
||||
it('should batch update user status successfully', async () => {
|
||||
// Arrange
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds: ['1', '2', '3'],
|
||||
status: UserStatus.LOCKED,
|
||||
reason: '批量锁定违规用户'
|
||||
};
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
data: {
|
||||
result: {
|
||||
success_users: [],
|
||||
failed_users: [],
|
||||
success_count: 3,
|
||||
failed_count: 0,
|
||||
total_count: 3
|
||||
},
|
||||
reason: '批量锁定违规用户'
|
||||
},
|
||||
message: '批量用户状态修改完成'
|
||||
};
|
||||
|
||||
mockAdminService.batchUpdateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await service.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(mockAdminService.batchUpdateUserStatus).toHaveBeenCalledWith(batchUserStatusDto);
|
||||
});
|
||||
|
||||
it('should reject batch operation when user count exceeds limit', async () => {
|
||||
// Arrange
|
||||
const userIds = Array.from({ length: BATCH_OPERATION.MAX_USER_COUNT + 1 }, (_, i) => i.toString());
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds,
|
||||
status: UserStatus.LOCKED,
|
||||
reason: '超限测试'
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = await service.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
message: MESSAGES.BATCH_OPERATION_LIMIT_ERROR,
|
||||
error_code: ERROR_CODES.BATCH_OPERATION_LIMIT_EXCEEDED
|
||||
});
|
||||
expect(mockAdminService.batchUpdateUserStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle empty user list', async () => {
|
||||
// Arrange
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds: [],
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '空列表测试'
|
||||
};
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
data: {
|
||||
result: {
|
||||
success_users: [],
|
||||
failed_users: [],
|
||||
success_count: 0,
|
||||
failed_count: 0,
|
||||
total_count: 0
|
||||
}
|
||||
},
|
||||
message: '批量操作完成'
|
||||
};
|
||||
|
||||
mockAdminService.batchUpdateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await service.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it('should log warning when batch operation exceeds limit', async () => {
|
||||
// Arrange
|
||||
const userIds = Array.from({ length: BATCH_OPERATION.MAX_USER_COUNT + 1 }, (_, i) => i.toString());
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds,
|
||||
status: UserStatus.LOCKED,
|
||||
reason: '超限测试'
|
||||
};
|
||||
const warnSpy = jest.spyOn(Logger.prototype, 'warn');
|
||||
|
||||
// Act
|
||||
await service.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
'用户管理:批量操作数量超限',
|
||||
expect.objectContaining({
|
||||
operation: 'user_mgmt_batch_update_limit_exceeded',
|
||||
requestCount: BATCH_OPERATION.MAX_USER_COUNT + 1,
|
||||
maxAllowed: BATCH_OPERATION.MAX_USER_COUNT
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserStatusStats', () => {
|
||||
it('should get user status statistics successfully', async () => {
|
||||
// Arrange
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
data: {
|
||||
stats: {
|
||||
active: 1250,
|
||||
inactive: 45,
|
||||
locked: 12,
|
||||
banned: 8,
|
||||
deleted: 3,
|
||||
pending: 15,
|
||||
total: 1333
|
||||
},
|
||||
timestamp: '2026-01-07T10:00:00.000Z'
|
||||
},
|
||||
message: '用户状态统计获取成功'
|
||||
};
|
||||
|
||||
mockAdminService.getUserStatusStats.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await service.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(mockAdminService.getUserStatusStats).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle statistics retrieval failure', async () => {
|
||||
// Arrange
|
||||
const expectedResult = {
|
||||
success: false,
|
||||
message: '统计数据获取失败',
|
||||
error_code: 'STATS_RETRIEVAL_FAILED'
|
||||
};
|
||||
|
||||
mockAdminService.getUserStatusStats.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await service.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it('should calculate business metrics when stats are available', async () => {
|
||||
// Arrange
|
||||
const statsResult = {
|
||||
success: true,
|
||||
data: {
|
||||
stats: {
|
||||
active: 80,
|
||||
inactive: 10,
|
||||
locked: 5,
|
||||
banned: 3,
|
||||
deleted: 2,
|
||||
pending: 0,
|
||||
total: 100
|
||||
},
|
||||
timestamp: '2026-01-07T10:00:00.000Z'
|
||||
},
|
||||
message: '成功'
|
||||
};
|
||||
|
||||
mockAdminService.getUserStatusStats.mockResolvedValue(statsResult);
|
||||
const logSpy = jest.spyOn(Logger.prototype, 'log');
|
||||
|
||||
// Act
|
||||
await service.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'用户管理:用户状态统计分析',
|
||||
expect.objectContaining({
|
||||
operation: 'user_mgmt_status_analysis',
|
||||
totalUsers: 100,
|
||||
activeUsers: 80,
|
||||
activeRate: '80.00%',
|
||||
problemUsers: 10 // locked + banned + deleted
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle zero total users in statistics', async () => {
|
||||
// Arrange
|
||||
const statsResult = {
|
||||
success: true,
|
||||
data: {
|
||||
stats: {
|
||||
active: 0,
|
||||
inactive: 0,
|
||||
locked: 0,
|
||||
banned: 0,
|
||||
deleted: 0,
|
||||
pending: 0,
|
||||
total: 0
|
||||
},
|
||||
timestamp: '2026-01-07T10:00:00.000Z'
|
||||
},
|
||||
message: '成功'
|
||||
};
|
||||
|
||||
mockAdminService.getUserStatusStats.mockResolvedValue(statsResult);
|
||||
const logSpy = jest.spyOn(Logger.prototype, 'log');
|
||||
|
||||
// Act
|
||||
await service.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'用户管理:用户状态统计分析',
|
||||
expect.objectContaining({
|
||||
activeRate: '0%'
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserStatusHistory', () => {
|
||||
it('should return mock history data with default limit', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(123);
|
||||
|
||||
// Act
|
||||
const result = await service.getUserStatusHistory(userId);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
data: {
|
||||
user_id: '123',
|
||||
history: [],
|
||||
total_count: 0
|
||||
},
|
||||
message: '状态变更历史获取成功(当前返回空数据,待实现完整功能)'
|
||||
});
|
||||
});
|
||||
|
||||
it('should return mock history data with custom limit', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(456);
|
||||
const customLimit = 20;
|
||||
|
||||
// Act
|
||||
const result = await service.getUserStatusHistory(userId, customLimit);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
data: {
|
||||
user_id: '456',
|
||||
history: [],
|
||||
total_count: 0
|
||||
},
|
||||
message: '状态变更历史获取成功(当前返回空数据,待实现完整功能)'
|
||||
});
|
||||
});
|
||||
|
||||
it('should log history query operation', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(789);
|
||||
const limit = 15;
|
||||
const logSpy = jest.spyOn(Logger.prototype, 'log');
|
||||
|
||||
// Act
|
||||
await service.getUserStatusHistory(userId, limit);
|
||||
|
||||
// Assert
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'用户管理:获取用户状态变更历史',
|
||||
expect.objectContaining({
|
||||
operation: 'user_mgmt_get_status_history',
|
||||
userId: '789',
|
||||
limit: 15
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
260
src/business/user_mgmt/user_management.service.ts
Normal file
260
src/business/user_mgmt/user_management.service.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* 用户管理业务服务
|
||||
*
|
||||
* 功能描述:
|
||||
* - 用户状态管理业务逻辑
|
||||
* - 批量用户操作
|
||||
* - 用户状态统计
|
||||
* - 状态变更审计
|
||||
*
|
||||
* 职责分离:
|
||||
* - 专注于用户管理相关的业务逻辑实现
|
||||
* - 调用底层AdminService提供的技术能力
|
||||
* - 提供用户管理特定的业务规则和流程控制
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-07: 代码规范优化 - 修正文件命名规范,完善注释规范,更新作者信息 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @since 2025-12-24
|
||||
* @lastModified 2026-01-07
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { AdminService } from '../admin/admin.service';
|
||||
import { UserStatusDto, BatchUserStatusDto } from './user_status.dto';
|
||||
import {
|
||||
UserStatusResponseDto,
|
||||
BatchUserStatusResponseDto,
|
||||
UserStatusStatsResponseDto
|
||||
} from './user_status_response.dto';
|
||||
import { BATCH_OPERATION, DEFAULTS, ERROR_CODES, MESSAGES, UTILS } from './user_mgmt.constants';
|
||||
|
||||
/**
|
||||
* 用户管理业务服务
|
||||
*
|
||||
* 职责:
|
||||
* - 实现用户状态管理的完整业务逻辑
|
||||
* - 提供批量操作和状态统计的业务能力
|
||||
* - 执行业务规则验证和审计日志记录
|
||||
*
|
||||
* 主要方法:
|
||||
* - updateUserStatus() - 单个用户状态修改业务逻辑
|
||||
* - batchUpdateUserStatus() - 批量用户状态修改业务逻辑
|
||||
* - getUserStatusStats() - 用户状态统计业务逻辑
|
||||
* - getUserStatusHistory() - 用户状态变更历史查询
|
||||
*
|
||||
* 使用场景:
|
||||
* - 管理员执行用户状态管理操作
|
||||
* - 系统自动化用户生命周期管理
|
||||
* - 用户状态监控和数据分析
|
||||
*/
|
||||
@Injectable()
|
||||
export class UserManagementService {
|
||||
private readonly logger = new Logger(UserManagementService.name);
|
||||
|
||||
constructor(private readonly adminService: AdminService) {}
|
||||
|
||||
/**
|
||||
* 修改用户状态
|
||||
*
|
||||
* 业务逻辑:
|
||||
* 1. 验证状态变更的业务规则
|
||||
* 2. 记录状态变更原因
|
||||
* 3. 调用底层服务执行变更
|
||||
* 4. 记录业务审计日志
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param userStatusDto 状态修改数据
|
||||
* @returns 修改结果
|
||||
* @throws NotFoundException 用户不存在时
|
||||
* @throws BadRequestException 状态变更不符合业务规则时
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const result = await service.updateUserStatus(BigInt(123), {
|
||||
* status: UserStatus.ACTIVE,
|
||||
* reason: '用户申诉通过,恢复正常状态'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
async updateUserStatus(userId: bigint, userStatusDto: UserStatusDto): Promise<UserStatusResponseDto> {
|
||||
this.logger.log('用户管理:开始修改用户状态', {
|
||||
operation: 'user_mgmt_update_status',
|
||||
userId: userId.toString(),
|
||||
newStatus: userStatusDto.status,
|
||||
reason: userStatusDto.reason,
|
||||
timestamp: UTILS.getCurrentTimestamp()
|
||||
});
|
||||
|
||||
// 调用底层管理员服务
|
||||
const result = await this.adminService.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// 记录业务层日志
|
||||
if (result.success) {
|
||||
this.logger.log('用户管理:用户状态修改成功', {
|
||||
operation: 'user_mgmt_update_status_success',
|
||||
userId: userId.toString(),
|
||||
newStatus: userStatusDto.status,
|
||||
timestamp: UTILS.getCurrentTimestamp()
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改用户状态
|
||||
*
|
||||
* 业务逻辑:
|
||||
* 1. 验证批量操作的业务规则
|
||||
* 2. 分批处理大量用户
|
||||
* 3. 提供批量操作的进度反馈
|
||||
* 4. 记录批量操作审计
|
||||
*
|
||||
* @param batchUserStatusDto 批量状态修改数据
|
||||
* @returns 批量修改结果
|
||||
* @throws BadRequestException 批量操作数量超限或参数无效时
|
||||
* @throws InternalServerErrorException 批量操作执行失败时
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const result = await service.batchUpdateUserStatus({
|
||||
* userIds: ['123', '456'],
|
||||
* status: UserStatus.LOCKED,
|
||||
* reason: '批量锁定违规用户'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
async batchUpdateUserStatus(batchUserStatusDto: BatchUserStatusDto): Promise<BatchUserStatusResponseDto> {
|
||||
this.logger.log('用户管理:开始批量修改用户状态', {
|
||||
operation: 'user_mgmt_batch_update_status',
|
||||
userCount: batchUserStatusDto.userIds.length,
|
||||
newStatus: batchUserStatusDto.status,
|
||||
reason: batchUserStatusDto.reason,
|
||||
timestamp: UTILS.getCurrentTimestamp()
|
||||
});
|
||||
|
||||
// 业务规则:限制批量操作的数量
|
||||
if (batchUserStatusDto.userIds.length > BATCH_OPERATION.MAX_USER_COUNT) {
|
||||
this.logger.warn('用户管理:批量操作数量超限', {
|
||||
operation: 'user_mgmt_batch_update_limit_exceeded',
|
||||
requestCount: batchUserStatusDto.userIds.length,
|
||||
maxAllowed: BATCH_OPERATION.MAX_USER_COUNT
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: MESSAGES.BATCH_OPERATION_LIMIT_ERROR,
|
||||
error_code: ERROR_CODES.BATCH_OPERATION_LIMIT_EXCEEDED
|
||||
};
|
||||
}
|
||||
|
||||
// 调用底层管理员服务
|
||||
const result = await this.adminService.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// 记录业务层日志
|
||||
if (result.success) {
|
||||
this.logger.log('用户管理:批量用户状态修改完成', {
|
||||
operation: 'user_mgmt_batch_update_status_success',
|
||||
successCount: result.data?.result.success_count || 0,
|
||||
failedCount: result.data?.result.failed_count || 0,
|
||||
timestamp: UTILS.getCurrentTimestamp()
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户状态统计
|
||||
*
|
||||
* 业务逻辑:
|
||||
* 1. 获取基础统计数据
|
||||
* 2. 计算业务相关的指标
|
||||
* 3. 提供状态分布分析
|
||||
* 4. 缓存统计结果
|
||||
*
|
||||
* @returns 状态统计信息
|
||||
* @throws InternalServerErrorException 统计数据获取失败时
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const stats = await service.getUserStatusStats();
|
||||
* // 返回包含各状态用户数量和分析指标的统计数据
|
||||
* ```
|
||||
*/
|
||||
async getUserStatusStats(): Promise<UserStatusStatsResponseDto> {
|
||||
this.logger.log('用户管理:获取用户状态统计', {
|
||||
operation: 'user_mgmt_get_status_stats',
|
||||
timestamp: UTILS.getCurrentTimestamp()
|
||||
});
|
||||
|
||||
// 调用底层管理员服务
|
||||
const result = await this.adminService.getUserStatusStats();
|
||||
|
||||
// 业务层可以在这里添加额外的统计分析
|
||||
if (result.success && result.data) {
|
||||
const stats = result.data.stats;
|
||||
|
||||
// 计算业务指标
|
||||
const activeRate = stats.total > 0 ? (stats.active / stats.total * 100).toFixed(2) : '0';
|
||||
const problemUserCount = stats.locked + stats.banned + stats.deleted;
|
||||
|
||||
this.logger.log('用户管理:用户状态统计分析', {
|
||||
operation: 'user_mgmt_status_analysis',
|
||||
totalUsers: stats.total,
|
||||
activeUsers: stats.active,
|
||||
activeRate: `${activeRate}%`,
|
||||
problemUsers: problemUserCount,
|
||||
timestamp: UTILS.getCurrentTimestamp()
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户状态变更历史
|
||||
*
|
||||
* 业务逻辑:
|
||||
* 1. 查询指定用户的状态变更记录
|
||||
* 2. 提供状态变更的审计追踪
|
||||
* 3. 支持时间范围和数量限制查询
|
||||
* 4. 格式化历史记录数据
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param limit 返回数量限制
|
||||
* @returns 状态变更历史
|
||||
* @throws NotFoundException 用户不存在时
|
||||
* @throws BadRequestException 查询参数无效时
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const history = await service.getUserStatusHistory(BigInt(123), 20);
|
||||
* // 返回用户最近20条状态变更记录
|
||||
* ```
|
||||
*/
|
||||
async getUserStatusHistory(userId: bigint, limit: number = DEFAULTS.STATUS_HISTORY_LIMIT) {
|
||||
this.logger.log('用户管理:获取用户状态变更历史', {
|
||||
operation: 'user_mgmt_get_status_history',
|
||||
userId: userId.toString(),
|
||||
limit,
|
||||
timestamp: UTILS.getCurrentTimestamp()
|
||||
});
|
||||
|
||||
// 注意:此功能当前返回模拟数据,实际实现需要集成审计日志服务
|
||||
// 建议在后续版本中实现完整的状态变更历史查询功能
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
user_id: userId.toString(),
|
||||
history: [] as any[],
|
||||
total_count: 0
|
||||
},
|
||||
message: '状态变更历史获取成功(当前返回空数据,待实现完整功能)'
|
||||
};
|
||||
}
|
||||
}
|
||||
71
src/business/user_mgmt/user_mgmt.constants.ts
Normal file
71
src/business/user_mgmt/user_mgmt.constants.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 用户管理业务常量
|
||||
*
|
||||
* 功能描述:
|
||||
* - 定义用户管理模块的业务常量
|
||||
* - 统一管理魔法数字和配置参数
|
||||
* - 提供类型安全的常量访问
|
||||
*
|
||||
* 职责分离:
|
||||
* - 业务规则常量定义和管理
|
||||
* - 验证规则参数统一配置
|
||||
* - 系统限制和默认值设置
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-07: 代码规范优化 - 创建常量定义文件,消除魔法数字 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @since 2026-01-07
|
||||
* @lastModified 2026-01-07
|
||||
*/
|
||||
|
||||
/**
|
||||
* 批量操作相关常量
|
||||
*/
|
||||
export const BATCH_OPERATION = {
|
||||
/** 批量操作最大用户数量限制 */
|
||||
MAX_USER_COUNT: 100,
|
||||
/** 批量操作最小用户数量限制 */
|
||||
MIN_USER_COUNT: 1,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 验证规则相关常量
|
||||
*/
|
||||
export const VALIDATION = {
|
||||
/** 状态修改原因最大长度 */
|
||||
REASON_MAX_LENGTH: 200,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 默认参数常量
|
||||
*/
|
||||
export const DEFAULTS = {
|
||||
/** 状态变更历史查询默认数量限制 */
|
||||
STATUS_HISTORY_LIMIT: 10,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 错误代码常量
|
||||
*/
|
||||
export const ERROR_CODES = {
|
||||
/** 批量操作数量超限错误代码 */
|
||||
BATCH_OPERATION_LIMIT_EXCEEDED: 'BATCH_OPERATION_LIMIT_EXCEEDED',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 业务消息常量
|
||||
*/
|
||||
export const MESSAGES = {
|
||||
/** 批量操作数量超限错误消息 */
|
||||
BATCH_OPERATION_LIMIT_ERROR: `批量操作数量不能超过${BATCH_OPERATION.MAX_USER_COUNT}个用户`,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 工具函数
|
||||
*/
|
||||
export const UTILS = {
|
||||
/** 获取当前时间戳 */
|
||||
getCurrentTimestamp: (): string => new Date().toISOString(),
|
||||
} as const;
|
||||
436
src/business/user_mgmt/user_mgmt.integration.spec.ts
Normal file
436
src/business/user_mgmt/user_mgmt.integration.spec.ts
Normal file
@@ -0,0 +1,436 @@
|
||||
/**
|
||||
* 用户管理模块集成测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试用户管理模块的完整业务流程
|
||||
* - 测试控制器与服务的集成
|
||||
* - 测试真实的HTTP请求处理
|
||||
* - 测试端到端的业务场景
|
||||
*
|
||||
* 职责分离:
|
||||
* - 集成测试覆盖完整的业务流程
|
||||
* - 测试模块间的协作和数据流
|
||||
* - 验证真实环境下的功能表现
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-07: 代码规范优化 - 创建完整的集成测试覆盖 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @since 2026-01-07
|
||||
* @lastModified 2026-01-07
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { UserStatusController } from './user_status.controller';
|
||||
import { UserManagementService } from './user_management.service';
|
||||
import { AdminService } from '../admin/admin.service';
|
||||
import { AdminGuard } from '../admin/guards/admin.guard';
|
||||
import { UserStatusDto, BatchUserStatusDto } from './user_status.dto';
|
||||
import { UserStatus } from './user_status.enum';
|
||||
import { BATCH_OPERATION, ERROR_CODES, MESSAGES } from './user_mgmt.constants';
|
||||
|
||||
describe('UserManagement Integration', () => {
|
||||
let app: INestApplication;
|
||||
let controller: UserStatusController;
|
||||
let userManagementService: UserManagementService;
|
||||
let mockAdminService: jest.Mocked<AdminService>;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create mock AdminService
|
||||
const mockAdminServiceProvider = {
|
||||
updateUserStatus: jest.fn(),
|
||||
batchUpdateUserStatus: jest.fn(),
|
||||
getUserStatusStats: jest.fn(),
|
||||
};
|
||||
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
controllers: [UserStatusController],
|
||||
providers: [
|
||||
UserManagementService,
|
||||
{
|
||||
provide: AdminService,
|
||||
useValue: mockAdminServiceProvider,
|
||||
},
|
||||
],
|
||||
})
|
||||
.overrideGuard(AdminGuard)
|
||||
.useValue({ canActivate: jest.fn(() => true) })
|
||||
.compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
|
||||
controller = moduleFixture.get<UserStatusController>(UserStatusController);
|
||||
userManagementService = moduleFixture.get<UserManagementService>(UserManagementService);
|
||||
mockAdminService = moduleFixture.get(AdminService);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Complete User Status Management Flow', () => {
|
||||
it('should handle complete user status update workflow', async () => {
|
||||
// Arrange
|
||||
const userId = '123';
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.LOCKED,
|
||||
reason: '用户违反社区规定'
|
||||
};
|
||||
|
||||
const mockUpdateResult = {
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: '123',
|
||||
username: 'testuser',
|
||||
nickname: '测试用户',
|
||||
status: UserStatus.LOCKED,
|
||||
status_description: '已锁定',
|
||||
updated_at: new Date('2026-01-07T10:00:00.000Z')
|
||||
},
|
||||
reason: '用户违反社区规定'
|
||||
},
|
||||
message: '用户状态修改成功'
|
||||
};
|
||||
|
||||
mockAdminService.updateUserStatus.mockResolvedValue(mockUpdateResult);
|
||||
|
||||
// Act - Controller calls Service, Service calls AdminService
|
||||
const result = await controller.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert - Verify complete integration
|
||||
expect(result).toEqual(mockUpdateResult);
|
||||
expect(mockAdminService.updateUserStatus).toHaveBeenCalledWith(BigInt(123), userStatusDto);
|
||||
expect(result.data.user.status).toBe(UserStatus.LOCKED);
|
||||
expect(result.data.reason).toBe('用户违反社区规定');
|
||||
});
|
||||
|
||||
it('should handle complete batch update workflow', async () => {
|
||||
// Arrange
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds: ['1', '2', '3'],
|
||||
status: UserStatus.BANNED,
|
||||
reason: '批量处理违规用户'
|
||||
};
|
||||
|
||||
const mockBatchResult = {
|
||||
success: true,
|
||||
data: {
|
||||
result: {
|
||||
success_users: [
|
||||
{ id: '1', username: 'user1', nickname: '用户1', status: UserStatus.BANNED, status_description: '已封禁', updated_at: new Date() },
|
||||
{ id: '2', username: 'user2', nickname: '用户2', status: UserStatus.BANNED, status_description: '已封禁', updated_at: new Date() }
|
||||
],
|
||||
failed_users: [
|
||||
{ user_id: '3', error: '用户不存在' }
|
||||
],
|
||||
success_count: 2,
|
||||
failed_count: 1,
|
||||
total_count: 3
|
||||
},
|
||||
reason: '批量处理违规用户'
|
||||
},
|
||||
message: '批量用户状态修改完成'
|
||||
};
|
||||
|
||||
mockAdminService.batchUpdateUserStatus.mockResolvedValue(mockBatchResult);
|
||||
|
||||
// Act - Complete batch workflow
|
||||
const result = await controller.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert - Verify batch integration
|
||||
expect(result).toEqual(mockBatchResult);
|
||||
expect(result.data.result.success_count).toBe(2);
|
||||
expect(result.data.result.failed_count).toBe(1);
|
||||
expect(mockAdminService.batchUpdateUserStatus).toHaveBeenCalledWith(batchUserStatusDto);
|
||||
});
|
||||
|
||||
it('should handle complete statistics workflow', async () => {
|
||||
// Arrange
|
||||
const mockStatsResult = {
|
||||
success: true,
|
||||
data: {
|
||||
stats: {
|
||||
active: 1000,
|
||||
inactive: 200,
|
||||
locked: 50,
|
||||
banned: 25,
|
||||
deleted: 10,
|
||||
pending: 30,
|
||||
total: 1315
|
||||
},
|
||||
timestamp: '2026-01-07T10:00:00.000Z'
|
||||
},
|
||||
message: '用户状态统计获取成功'
|
||||
};
|
||||
|
||||
mockAdminService.getUserStatusStats.mockResolvedValue(mockStatsResult);
|
||||
|
||||
// Act - Complete statistics workflow
|
||||
const result = await controller.getUserStatusStats();
|
||||
|
||||
// Assert - Verify statistics integration
|
||||
expect(result).toEqual(mockStatsResult);
|
||||
expect(result.data.stats.total).toBe(1315);
|
||||
expect(mockAdminService.getUserStatusStats).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Business Logic Integration', () => {
|
||||
it('should enforce batch operation limits through service layer', async () => {
|
||||
// Arrange - Create request exceeding limits
|
||||
const userIds = Array.from({ length: BATCH_OPERATION.MAX_USER_COUNT + 1 }, (_, i) => i.toString());
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds,
|
||||
status: UserStatus.LOCKED,
|
||||
reason: '超限测试'
|
||||
};
|
||||
|
||||
// Act - Service should reject before calling AdminService
|
||||
const result = await userManagementService.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert - Verify business rule enforcement
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toBe(MESSAGES.BATCH_OPERATION_LIMIT_ERROR);
|
||||
expect(result.error_code).toBe(ERROR_CODES.BATCH_OPERATION_LIMIT_EXCEEDED);
|
||||
expect(mockAdminService.batchUpdateUserStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle user status history integration', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(456);
|
||||
const limit = 10;
|
||||
|
||||
// Act - Test history functionality (currently mock implementation)
|
||||
const result = await userManagementService.getUserStatusHistory(userId, limit);
|
||||
|
||||
// Assert - Verify history integration
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data.user_id).toBe('456');
|
||||
expect(result.data.history).toEqual([]);
|
||||
expect(result.data.total_count).toBe(0);
|
||||
expect(result.message).toContain('状态变更历史获取成功');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling Integration', () => {
|
||||
it('should handle service errors through complete stack', async () => {
|
||||
// Arrange
|
||||
const userId = '999';
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '测试错误处理'
|
||||
};
|
||||
|
||||
const mockErrorResult = {
|
||||
success: false,
|
||||
message: '用户不存在',
|
||||
error_code: 'USER_NOT_FOUND'
|
||||
};
|
||||
|
||||
mockAdminService.updateUserStatus.mockResolvedValue(mockErrorResult);
|
||||
|
||||
// Act - Error propagation through layers
|
||||
const result = await controller.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert - Verify error handling integration
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toBe('用户不存在');
|
||||
expect(result.error_code).toBe('USER_NOT_FOUND');
|
||||
});
|
||||
|
||||
it('should handle batch operation partial failures', async () => {
|
||||
// Arrange
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds: ['1', '2', '999', '888'],
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '批量激活测试'
|
||||
};
|
||||
|
||||
const mockPartialFailureResult = {
|
||||
success: true,
|
||||
data: {
|
||||
result: {
|
||||
success_users: [
|
||||
{ id: '1', username: 'user1', nickname: '用户1', status: UserStatus.ACTIVE, status_description: '正常', updated_at: new Date() },
|
||||
{ id: '2', username: 'user2', nickname: '用户2', status: UserStatus.ACTIVE, status_description: '正常', updated_at: new Date() }
|
||||
],
|
||||
failed_users: [
|
||||
{ user_id: '999', error: '用户不存在' },
|
||||
{ user_id: '888', error: '用户状态无法修改' }
|
||||
],
|
||||
success_count: 2,
|
||||
failed_count: 2,
|
||||
total_count: 4
|
||||
},
|
||||
reason: '批量激活测试'
|
||||
},
|
||||
message: '批量用户状态修改完成'
|
||||
};
|
||||
|
||||
mockAdminService.batchUpdateUserStatus.mockResolvedValue(mockPartialFailureResult);
|
||||
|
||||
// Act - Handle partial failures
|
||||
const result = await controller.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert - Verify partial failure handling
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data.result.success_count).toBe(2);
|
||||
expect(result.data.result.failed_count).toBe(2);
|
||||
expect(result.data.result.failed_users).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should handle statistics service failures', async () => {
|
||||
// Arrange
|
||||
const mockStatsError = {
|
||||
success: false,
|
||||
message: '数据库连接失败',
|
||||
error_code: 'DATABASE_CONNECTION_ERROR'
|
||||
};
|
||||
|
||||
mockAdminService.getUserStatusStats.mockResolvedValue(mockStatsError);
|
||||
|
||||
// Act - Handle statistics errors
|
||||
const result = await controller.getUserStatusStats();
|
||||
|
||||
// Assert - Verify error propagation
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.message).toBe('数据库连接失败');
|
||||
expect(result.error_code).toBe('DATABASE_CONNECTION_ERROR');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Data Flow Integration', () => {
|
||||
it('should maintain data consistency through all layers', async () => {
|
||||
// Arrange
|
||||
const userId = '789';
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.INACTIVE,
|
||||
reason: '长期未活跃'
|
||||
};
|
||||
|
||||
const mockResult = {
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: '789',
|
||||
username: 'inactive_user',
|
||||
nickname: '非活跃用户',
|
||||
status: UserStatus.INACTIVE,
|
||||
status_description: '非活跃',
|
||||
updated_at: new Date('2026-01-07T10:00:00.000Z')
|
||||
},
|
||||
reason: '长期未活跃'
|
||||
},
|
||||
message: '用户状态修改成功'
|
||||
};
|
||||
|
||||
mockAdminService.updateUserStatus.mockResolvedValue(mockResult);
|
||||
|
||||
// Act - Data flows through Controller -> Service -> AdminService
|
||||
const result = await controller.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert - Verify data consistency
|
||||
expect(result.data.user.id).toBe(userId);
|
||||
expect(result.data.user.status).toBe(userStatusDto.status);
|
||||
expect(result.data.reason).toBe(userStatusDto.reason);
|
||||
expect(mockAdminService.updateUserStatus).toHaveBeenCalledWith(
|
||||
BigInt(789),
|
||||
expect.objectContaining({
|
||||
status: UserStatus.INACTIVE,
|
||||
reason: '长期未活跃'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle BigInt conversion correctly in data flow', async () => {
|
||||
// Arrange - Test large number handling
|
||||
const largeUserId = '9007199254740991';
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.PENDING,
|
||||
reason: '大数字ID测试'
|
||||
};
|
||||
|
||||
const mockResult = {
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: largeUserId,
|
||||
username: 'large_id_user',
|
||||
nickname: '大ID用户',
|
||||
status: UserStatus.PENDING,
|
||||
status_description: '待处理',
|
||||
updated_at: new Date()
|
||||
},
|
||||
reason: '大数字ID测试'
|
||||
},
|
||||
message: '用户状态修改成功'
|
||||
};
|
||||
|
||||
mockAdminService.updateUserStatus.mockResolvedValue(mockResult);
|
||||
|
||||
// Act - Test BigInt conversion in data flow
|
||||
const result = await controller.updateUserStatus(largeUserId, userStatusDto);
|
||||
|
||||
// Assert - Verify BigInt handling
|
||||
expect(result.data.user.id).toBe(largeUserId);
|
||||
expect(mockAdminService.updateUserStatus).toHaveBeenCalledWith(
|
||||
BigInt('9007199254740991'),
|
||||
userStatusDto
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Performance Integration', () => {
|
||||
it('should handle maximum allowed batch size efficiently', async () => {
|
||||
// Arrange - Test with maximum allowed batch size
|
||||
const userIds = Array.from({ length: BATCH_OPERATION.MAX_USER_COUNT }, (_, i) => `user_${i}`);
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds,
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '性能测试'
|
||||
};
|
||||
|
||||
const mockResult = {
|
||||
success: true,
|
||||
data: {
|
||||
result: {
|
||||
success_users: userIds.map(id => ({
|
||||
id,
|
||||
username: `user_${id}`,
|
||||
nickname: `用户_${id}`,
|
||||
status: UserStatus.ACTIVE,
|
||||
status_description: '正常',
|
||||
updated_at: new Date()
|
||||
})),
|
||||
failed_users: [],
|
||||
success_count: userIds.length,
|
||||
failed_count: 0,
|
||||
total_count: userIds.length
|
||||
},
|
||||
reason: '性能测试'
|
||||
},
|
||||
message: '批量用户状态修改完成'
|
||||
};
|
||||
|
||||
mockAdminService.batchUpdateUserStatus.mockResolvedValue(mockResult);
|
||||
|
||||
// Act - Process maximum batch size
|
||||
const startTime = Date.now();
|
||||
const result = await controller.batchUpdateUserStatus(batchUserStatusDto);
|
||||
const endTime = Date.now();
|
||||
|
||||
// Assert - Verify performance and correctness
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data.result.total_count).toBe(BATCH_OPERATION.MAX_USER_COUNT);
|
||||
expect(endTime - startTime).toBeLessThan(1000); // Should complete within 1 second
|
||||
});
|
||||
});
|
||||
});
|
||||
52
src/business/user_mgmt/user_mgmt.module.ts
Normal file
52
src/business/user_mgmt/user_mgmt.module.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 用户管理业务模块
|
||||
*
|
||||
* 功能描述:
|
||||
* - 整合用户状态管理相关的所有组件
|
||||
* - 提供用户生命周期管理功能
|
||||
* - 支持批量操作和状态统计
|
||||
*
|
||||
* 职责分离:
|
||||
* - 模块配置和依赖管理
|
||||
* - 组件注册和导出控制
|
||||
* - 业务模块边界定义
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-07: 代码规范优化 - 修正文件命名规范,完善注释规范,更新作者信息 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @since 2025-12-24
|
||||
* @lastModified 2026-01-07
|
||||
*/
|
||||
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UserStatusController } from './user_status.controller';
|
||||
import { UserManagementService } from './user_management.service';
|
||||
import { AdminModule } from '../admin/admin.module';
|
||||
import { AdminCoreModule } from '../../core/admin_core/admin_core.module';
|
||||
|
||||
/**
|
||||
* 用户管理业务模块
|
||||
*
|
||||
* 职责:
|
||||
* - 整合用户状态管理的所有业务组件
|
||||
* - 管理模块间的依赖关系和配置
|
||||
* - 提供统一的用户管理业务入口
|
||||
*
|
||||
* 主要组件:
|
||||
* - UserStatusController - 用户状态管理API控制器
|
||||
* - UserManagementService - 用户管理业务逻辑服务
|
||||
*
|
||||
* 使用场景:
|
||||
* - 管理员进行用户状态管理操作
|
||||
* - 批量用户操作和状态统计
|
||||
* - 用户生命周期管理流程
|
||||
*/
|
||||
@Module({
|
||||
imports: [AdminModule, AdminCoreModule],
|
||||
controllers: [UserStatusController],
|
||||
providers: [UserManagementService],
|
||||
exports: [UserManagementService],
|
||||
})
|
||||
export class UserMgmtModule {}
|
||||
586
src/business/user_mgmt/user_status.controller.spec.ts
Normal file
586
src/business/user_mgmt/user_status.controller.spec.ts
Normal file
@@ -0,0 +1,586 @@
|
||||
/**
|
||||
* 用户状态管理控制器测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试用户状态管理API接口
|
||||
* - 测试HTTP请求处理和参数验证
|
||||
* - 测试权限控制和频率限制
|
||||
* - 测试响应格式和错误处理
|
||||
*
|
||||
* 职责分离:
|
||||
* - 单元测试覆盖所有API端点
|
||||
* - Mock业务服务依赖
|
||||
* - 验证请求参数和响应格式
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-07: 代码规范优化 - 创建完整的控制器测试覆盖 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @since 2026-01-07
|
||||
* @lastModified 2026-01-07
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { UserStatusController } from './user_status.controller';
|
||||
import { UserManagementService } from './user_management.service';
|
||||
import { AdminGuard } from '../admin/guards/admin.guard';
|
||||
import { UserStatusDto, BatchUserStatusDto } from './user_status.dto';
|
||||
import { UserStatus } from './user_status.enum';
|
||||
import { BATCH_OPERATION } from './user_mgmt.constants';
|
||||
|
||||
describe('UserStatusController', () => {
|
||||
let controller: UserStatusController;
|
||||
let mockUserManagementService: jest.Mocked<UserManagementService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockUserManagementServiceProvider = {
|
||||
updateUserStatus: jest.fn(),
|
||||
batchUpdateUserStatus: jest.fn(),
|
||||
getUserStatusStats: jest.fn(),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [UserStatusController],
|
||||
providers: [
|
||||
{
|
||||
provide: UserManagementService,
|
||||
useValue: mockUserManagementServiceProvider,
|
||||
},
|
||||
],
|
||||
})
|
||||
.overrideGuard(AdminGuard)
|
||||
.useValue({ canActivate: jest.fn(() => true) })
|
||||
.compile();
|
||||
|
||||
controller = module.get<UserStatusController>(UserStatusController);
|
||||
mockUserManagementService = module.get(UserManagementService);
|
||||
|
||||
// Mock Logger to avoid console output during tests
|
||||
jest.spyOn(Logger.prototype, 'log').mockImplementation();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('updateUserStatus', () => {
|
||||
it('should update user status successfully', async () => {
|
||||
// Arrange
|
||||
const userId = '123';
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '用户申诉通过'
|
||||
};
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: '123',
|
||||
username: 'testuser',
|
||||
nickname: '测试用户',
|
||||
status: UserStatus.ACTIVE,
|
||||
status_description: '正常',
|
||||
updated_at: new Date()
|
||||
},
|
||||
reason: '用户申诉通过'
|
||||
},
|
||||
message: '用户状态修改成功'
|
||||
};
|
||||
|
||||
mockUserManagementService.updateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await controller.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(mockUserManagementService.updateUserStatus).toHaveBeenCalledWith(BigInt(123), userStatusDto);
|
||||
expect(mockUserManagementService.updateUserStatus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle user not found error', async () => {
|
||||
// Arrange
|
||||
const userId = '999';
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.LOCKED,
|
||||
reason: '违规操作'
|
||||
};
|
||||
const expectedResult = {
|
||||
success: false,
|
||||
message: '用户不存在',
|
||||
error_code: 'USER_NOT_FOUND'
|
||||
};
|
||||
|
||||
mockUserManagementService.updateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await controller.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(mockUserManagementService.updateUserStatus).toHaveBeenCalledWith(BigInt(999), userStatusDto);
|
||||
});
|
||||
|
||||
it('should log operation details', async () => {
|
||||
// Arrange
|
||||
const userId = '456';
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.BANNED,
|
||||
reason: '严重违规'
|
||||
};
|
||||
const mockResult = {
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: '456',
|
||||
username: 'testuser',
|
||||
nickname: '测试用户',
|
||||
status: UserStatus.BANNED,
|
||||
status_description: '已封禁',
|
||||
updated_at: new Date()
|
||||
},
|
||||
reason: '严重违规'
|
||||
},
|
||||
message: '成功'
|
||||
};
|
||||
mockUserManagementService.updateUserStatus.mockResolvedValue(mockResult);
|
||||
const logSpy = jest.spyOn(Logger.prototype, 'log');
|
||||
|
||||
// Act
|
||||
await controller.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'管理员修改用户状态',
|
||||
expect.objectContaining({
|
||||
operation: 'update_user_status',
|
||||
userId: '456',
|
||||
newStatus: UserStatus.BANNED,
|
||||
reason: '严重违规'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should convert string id to BigInt correctly', async () => {
|
||||
// Arrange
|
||||
const userId = '9007199254740991'; // Large number as string
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.INACTIVE,
|
||||
reason: '长期未活跃'
|
||||
};
|
||||
const mockResult = {
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: '9007199254740991',
|
||||
username: 'large_id_user',
|
||||
nickname: '大ID用户',
|
||||
status: UserStatus.INACTIVE,
|
||||
status_description: '非活跃',
|
||||
updated_at: new Date()
|
||||
},
|
||||
reason: '长期未活跃'
|
||||
},
|
||||
message: '成功'
|
||||
};
|
||||
|
||||
mockUserManagementService.updateUserStatus.mockResolvedValue(mockResult);
|
||||
|
||||
// Act
|
||||
await controller.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(mockUserManagementService.updateUserStatus).toHaveBeenCalledWith(
|
||||
BigInt('9007199254740991'),
|
||||
userStatusDto
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('batchUpdateUserStatus', () => {
|
||||
it('should batch update user status successfully', async () => {
|
||||
// Arrange
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds: ['1', '2', '3'],
|
||||
status: UserStatus.LOCKED,
|
||||
reason: '批量锁定违规用户'
|
||||
};
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
data: {
|
||||
result: {
|
||||
success_users: [
|
||||
{ id: '1', username: 'user1', nickname: '用户1', status: UserStatus.LOCKED, status_description: '已锁定', updated_at: new Date() },
|
||||
{ id: '2', username: 'user2', nickname: '用户2', status: UserStatus.LOCKED, status_description: '已锁定', updated_at: new Date() },
|
||||
{ id: '3', username: 'user3', nickname: '用户3', status: UserStatus.LOCKED, status_description: '已锁定', updated_at: new Date() }
|
||||
],
|
||||
failed_users: [],
|
||||
success_count: 3,
|
||||
failed_count: 0,
|
||||
total_count: 3
|
||||
},
|
||||
reason: '批量锁定违规用户'
|
||||
},
|
||||
message: '批量用户状态修改完成'
|
||||
};
|
||||
|
||||
mockUserManagementService.batchUpdateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await controller.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(mockUserManagementService.batchUpdateUserStatus).toHaveBeenCalledWith(batchUserStatusDto);
|
||||
expect(mockUserManagementService.batchUpdateUserStatus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle partial success in batch operation', async () => {
|
||||
// Arrange
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds: ['1', '2', '999'],
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '批量激活用户'
|
||||
};
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
data: {
|
||||
result: {
|
||||
success_users: [
|
||||
{ id: '1', username: 'user1', nickname: '用户1', status: UserStatus.ACTIVE, status_description: '正常', updated_at: new Date() },
|
||||
{ id: '2', username: 'user2', nickname: '用户2', status: UserStatus.ACTIVE, status_description: '正常', updated_at: new Date() }
|
||||
],
|
||||
failed_users: [
|
||||
{ user_id: '999', error: '用户不存在' }
|
||||
],
|
||||
success_count: 2,
|
||||
failed_count: 1,
|
||||
total_count: 3
|
||||
},
|
||||
reason: '批量激活用户'
|
||||
},
|
||||
message: '批量用户状态修改完成'
|
||||
};
|
||||
|
||||
mockUserManagementService.batchUpdateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await controller.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(result.data.result.success_count).toBe(2);
|
||||
expect(result.data.result.failed_count).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle empty user list', async () => {
|
||||
// Arrange
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds: [],
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '空列表测试'
|
||||
};
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
data: {
|
||||
result: {
|
||||
success_users: [],
|
||||
failed_users: [],
|
||||
success_count: 0,
|
||||
failed_count: 0,
|
||||
total_count: 0
|
||||
},
|
||||
reason: '空列表测试'
|
||||
},
|
||||
message: '批量操作完成'
|
||||
};
|
||||
|
||||
mockUserManagementService.batchUpdateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await controller.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(result.data.result.total_count).toBe(0);
|
||||
});
|
||||
|
||||
it('should log batch operation details', async () => {
|
||||
// Arrange
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds: ['1', '2', '3', '4', '5'],
|
||||
status: UserStatus.BANNED,
|
||||
reason: '批量封禁违规用户'
|
||||
};
|
||||
const mockResult = {
|
||||
success: true,
|
||||
data: {
|
||||
result: {
|
||||
success_users: [],
|
||||
failed_users: [],
|
||||
success_count: 0,
|
||||
failed_count: 0,
|
||||
total_count: 0
|
||||
}
|
||||
},
|
||||
message: '成功'
|
||||
};
|
||||
|
||||
mockUserManagementService.batchUpdateUserStatus.mockResolvedValue(mockResult);
|
||||
const logSpy = jest.spyOn(Logger.prototype, 'log');
|
||||
|
||||
// Act
|
||||
await controller.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'管理员批量修改用户状态',
|
||||
expect.objectContaining({
|
||||
operation: 'batch_update_user_status',
|
||||
userCount: 5,
|
||||
newStatus: UserStatus.BANNED,
|
||||
reason: '批量封禁违规用户'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle large user list within limits', async () => {
|
||||
// Arrange
|
||||
const userIds = Array.from({ length: BATCH_OPERATION.MAX_USER_COUNT }, (_, i) => i.toString());
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds,
|
||||
status: UserStatus.INACTIVE,
|
||||
reason: '批量设置非活跃'
|
||||
};
|
||||
const mockResult = {
|
||||
success: true,
|
||||
data: {
|
||||
result: {
|
||||
success_users: userIds.map(id => ({
|
||||
id,
|
||||
username: `user_${id}`,
|
||||
nickname: `用户_${id}`,
|
||||
status: UserStatus.INACTIVE,
|
||||
status_description: '非活跃',
|
||||
updated_at: new Date()
|
||||
})),
|
||||
failed_users: [],
|
||||
success_count: userIds.length,
|
||||
failed_count: 0,
|
||||
total_count: userIds.length
|
||||
}
|
||||
},
|
||||
message: '批量操作完成'
|
||||
};
|
||||
|
||||
mockUserManagementService.batchUpdateUserStatus.mockResolvedValue(mockResult);
|
||||
|
||||
// Act
|
||||
const result = await controller.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data.result.total_count).toBe(BATCH_OPERATION.MAX_USER_COUNT);
|
||||
expect(mockUserManagementService.batchUpdateUserStatus).toHaveBeenCalledWith(batchUserStatusDto);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserStatusStats', () => {
|
||||
it('should get user status statistics successfully', async () => {
|
||||
// Arrange
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
data: {
|
||||
stats: {
|
||||
active: 1250,
|
||||
inactive: 45,
|
||||
locked: 12,
|
||||
banned: 8,
|
||||
deleted: 3,
|
||||
pending: 15,
|
||||
total: 1333
|
||||
},
|
||||
timestamp: '2026-01-07T10:00:00.000Z'
|
||||
},
|
||||
message: '用户状态统计获取成功'
|
||||
};
|
||||
|
||||
mockUserManagementService.getUserStatusStats.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await controller.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(mockUserManagementService.getUserStatusStats).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle statistics retrieval failure', async () => {
|
||||
// Arrange
|
||||
const expectedResult = {
|
||||
success: false,
|
||||
message: '统计数据获取失败',
|
||||
error_code: 'STATS_RETRIEVAL_FAILED'
|
||||
};
|
||||
|
||||
mockUserManagementService.getUserStatusStats.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await controller.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('should log statistics query operation', async () => {
|
||||
// Arrange
|
||||
const mockResult = {
|
||||
success: true,
|
||||
data: {
|
||||
stats: {
|
||||
active: 800,
|
||||
inactive: 150,
|
||||
locked: 30,
|
||||
banned: 15,
|
||||
deleted: 5,
|
||||
pending: 20,
|
||||
total: 1020
|
||||
},
|
||||
timestamp: '2026-01-07T15:30:00.000Z'
|
||||
},
|
||||
message: '成功'
|
||||
};
|
||||
|
||||
mockUserManagementService.getUserStatusStats.mockResolvedValue(mockResult);
|
||||
const logSpy = jest.spyOn(Logger.prototype, 'log');
|
||||
|
||||
// Act
|
||||
await controller.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'管理员获取用户状态统计',
|
||||
expect.objectContaining({
|
||||
operation: 'get_user_status_stats'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should return detailed statistics breakdown', async () => {
|
||||
// Arrange
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
data: {
|
||||
stats: {
|
||||
active: 800,
|
||||
inactive: 150,
|
||||
locked: 30,
|
||||
banned: 15,
|
||||
deleted: 5,
|
||||
pending: 20,
|
||||
total: 1020
|
||||
},
|
||||
timestamp: '2026-01-07T15:30:00.000Z',
|
||||
metadata: {
|
||||
last_updated: '2026-01-07T15:30:00.000Z',
|
||||
cache_duration: 300
|
||||
}
|
||||
},
|
||||
message: '用户状态统计获取成功'
|
||||
};
|
||||
|
||||
mockUserManagementService.getUserStatusStats.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await controller.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(result.data.stats.total).toBe(1020);
|
||||
expect(result.data.stats.active).toBe(800);
|
||||
expect(result.data.stats.locked).toBe(30);
|
||||
expect(result.data.stats.banned).toBe(15);
|
||||
});
|
||||
|
||||
it('should handle zero statistics gracefully', async () => {
|
||||
// Arrange
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
data: {
|
||||
stats: {
|
||||
active: 0,
|
||||
inactive: 0,
|
||||
locked: 0,
|
||||
banned: 0,
|
||||
deleted: 0,
|
||||
pending: 0,
|
||||
total: 0
|
||||
},
|
||||
timestamp: '2026-01-07T10:00:00.000Z'
|
||||
},
|
||||
message: '用户状态统计获取成功'
|
||||
};
|
||||
|
||||
mockUserManagementService.getUserStatusStats.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await controller.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(result.data.stats.total).toBe(0);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AdminGuard Integration', () => {
|
||||
it('should be protected by AdminGuard', () => {
|
||||
// Verify that AdminGuard is applied to the controller methods
|
||||
const updateUserStatusMethod = Reflect.getMetadata('__guards__', UserStatusController.prototype.updateUserStatus);
|
||||
const batchUpdateMethod = Reflect.getMetadata('__guards__', UserStatusController.prototype.batchUpdateUserStatus);
|
||||
const getStatsMethod = Reflect.getMetadata('__guards__', UserStatusController.prototype.getUserStatusStats);
|
||||
|
||||
// At least one method should have guards (they are applied via @UseGuards decorator)
|
||||
expect(updateUserStatusMethod || batchUpdateMethod || getStatsMethod).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should handle service errors gracefully in updateUserStatus', async () => {
|
||||
// Arrange
|
||||
const userId = '123';
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '测试错误处理'
|
||||
};
|
||||
|
||||
mockUserManagementService.updateUserStatus.mockRejectedValue(new Error('Service error'));
|
||||
|
||||
// Act & Assert
|
||||
await expect(controller.updateUserStatus(userId, userStatusDto)).rejects.toThrow('Service error');
|
||||
});
|
||||
|
||||
it('should handle service errors gracefully in batchUpdateUserStatus', async () => {
|
||||
// Arrange
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds: ['1', '2'],
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '测试错误处理'
|
||||
};
|
||||
|
||||
mockUserManagementService.batchUpdateUserStatus.mockRejectedValue(new Error('Batch service error'));
|
||||
|
||||
// Act & Assert
|
||||
await expect(controller.batchUpdateUserStatus(batchUserStatusDto)).rejects.toThrow('Batch service error');
|
||||
});
|
||||
|
||||
it('should handle service errors gracefully in getUserStatusStats', async () => {
|
||||
// Arrange
|
||||
mockUserManagementService.getUserStatusStats.mockRejectedValue(new Error('Stats service error'));
|
||||
|
||||
// Act & Assert
|
||||
await expect(controller.getUserStatusStats()).rejects.toThrow('Stats service error');
|
||||
});
|
||||
});
|
||||
});
|
||||
243
src/business/user_mgmt/user_status.controller.ts
Normal file
243
src/business/user_mgmt/user_status.controller.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* 用户状态管理控制器
|
||||
*
|
||||
* 功能描述:
|
||||
* - 管理员管理用户账户状态
|
||||
* - 支持批量状态操作
|
||||
* - 提供状态变更审计日志
|
||||
*
|
||||
* 职责分离:
|
||||
* - HTTP请求处理和参数验证
|
||||
* - API文档生成和接口规范定义
|
||||
* - 业务服务调用和响应格式化
|
||||
*
|
||||
* API端点:
|
||||
* - PUT /admin/users/:id/status - 修改用户状态
|
||||
* - POST /admin/users/batch-status - 批量修改用户状态
|
||||
* - GET /admin/users/status-stats - 获取用户状态统计
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-07: 代码规范优化 - 修正文件命名规范,完善注释规范,更新作者信息 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @since 2025-12-24
|
||||
* @lastModified 2026-01-07
|
||||
*/
|
||||
|
||||
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Put, Post, UseGuards, ValidationPipe, UsePipes, Logger } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { AdminGuard } from '../admin/guards/admin.guard';
|
||||
import { UserManagementService } from './user_management.service';
|
||||
import { Throttle, ThrottlePresets } from '../../core/security_core/throttle.decorator';
|
||||
import { Timeout, TimeoutPresets } from '../../core/security_core/timeout.decorator';
|
||||
import { UserStatusDto, BatchUserStatusDto } from './user_status.dto';
|
||||
import { UserStatusResponseDto, BatchUserStatusResponseDto, UserStatusStatsResponseDto } from './user_status_response.dto';
|
||||
import { BATCH_OPERATION, UTILS } from './user_mgmt.constants';
|
||||
|
||||
/**
|
||||
* 用户状态管理控制器
|
||||
*
|
||||
* 职责:
|
||||
* - 处理用户状态管理相关的HTTP请求
|
||||
* - 提供RESTful API接口和Swagger文档
|
||||
* - 执行请求参数验证和权限控制
|
||||
*
|
||||
* 主要方法:
|
||||
* - updateUserStatus() - 修改单个用户状态
|
||||
* - batchUpdateUserStatus() - 批量修改用户状态
|
||||
* - getUserStatusStats() - 获取用户状态统计
|
||||
*
|
||||
* 使用场景:
|
||||
* - 管理员通过API管理用户状态
|
||||
* - 系统集成和自动化用户管理
|
||||
* - 用户状态监控和统计分析
|
||||
*/
|
||||
@ApiTags('user_management')
|
||||
@Controller('admin/users')
|
||||
export class UserStatusController {
|
||||
private readonly logger = new Logger(UserStatusController.name);
|
||||
|
||||
constructor(private readonly userManagementService: UserManagementService) {}
|
||||
|
||||
/**
|
||||
* 修改用户状态
|
||||
*
|
||||
* 业务逻辑:
|
||||
* 1. 验证管理员权限和操作频率限制
|
||||
* 2. 验证用户ID格式和状态参数有效性
|
||||
* 3. 记录状态修改操作的审计日志
|
||||
* 4. 调用业务服务执行状态变更
|
||||
* 5. 返回操作结果和用户最新状态
|
||||
*
|
||||
* @param id 用户ID
|
||||
* @param userStatusDto 状态修改数据
|
||||
* @returns 修改结果
|
||||
* @throws ForbiddenException 管理员权限不足时
|
||||
* @throws NotFoundException 用户不存在时
|
||||
* @throws TooManyRequestsException 操作过于频繁时
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const result = await controller.updateUserStatus('123', {
|
||||
* status: UserStatus.LOCKED,
|
||||
* reason: '用户违反社区规定'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@ApiOperation({
|
||||
summary: '修改用户状态',
|
||||
description: '管理员修改指定用户的账户状态,支持激活、锁定、禁用等操作'
|
||||
})
|
||||
@ApiParam({ name: 'id', description: '用户ID' })
|
||||
@ApiBody({ type: UserStatusDto })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '状态修改成功',
|
||||
type: UserStatusResponseDto
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 403,
|
||||
description: '权限不足'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 404,
|
||||
description: '用户不存在'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 429,
|
||||
description: '操作过于频繁'
|
||||
})
|
||||
@UseGuards(AdminGuard)
|
||||
@Throttle(ThrottlePresets.ADMIN_OPERATION)
|
||||
@Timeout(TimeoutPresets.NORMAL)
|
||||
@Put(':id/status')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UsePipes(new ValidationPipe({ transform: true }))
|
||||
async updateUserStatus(
|
||||
@Param('id') id: string,
|
||||
@Body() userStatusDto: UserStatusDto
|
||||
): Promise<UserStatusResponseDto> {
|
||||
this.logger.log('管理员修改用户状态', {
|
||||
operation: 'update_user_status',
|
||||
userId: id,
|
||||
newStatus: userStatusDto.status,
|
||||
reason: userStatusDto.reason,
|
||||
timestamp: UTILS.getCurrentTimestamp()
|
||||
});
|
||||
|
||||
return await this.userManagementService.updateUserStatus(BigInt(id), userStatusDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改用户状态
|
||||
*
|
||||
* 业务逻辑:
|
||||
* 1. 验证管理员权限和批量操作频率限制
|
||||
* 2. 验证用户ID列表和状态参数有效性
|
||||
* 3. 检查批量操作数量限制(最多${BATCH_OPERATION.MAX_USER_COUNT}个用户)
|
||||
* 4. 记录批量操作的审计日志
|
||||
* 5. 调用业务服务执行批量状态变更
|
||||
* 6. 返回批量操作结果统计
|
||||
*
|
||||
* @param batchUserStatusDto 批量状态修改数据
|
||||
* @returns 批量修改结果
|
||||
* @throws ForbiddenException 管理员权限不足时
|
||||
* @throws BadRequestException 批量操作数量超限时
|
||||
* @throws TooManyRequestsException 操作过于频繁时
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const result = await controller.batchUpdateUserStatus({
|
||||
* userIds: ['123', '456', '789'],
|
||||
* status: UserStatus.LOCKED,
|
||||
* reason: '批量处理违规用户'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@ApiOperation({
|
||||
summary: '批量修改用户状态',
|
||||
description: '管理员批量修改多个用户的账户状态'
|
||||
})
|
||||
@ApiBody({ type: BatchUserStatusDto })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '批量修改成功',
|
||||
type: BatchUserStatusResponseDto
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 403,
|
||||
description: '权限不足'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 429,
|
||||
description: '操作过于频繁'
|
||||
})
|
||||
@UseGuards(AdminGuard)
|
||||
@Throttle(ThrottlePresets.ADMIN_OPERATION)
|
||||
@Timeout(TimeoutPresets.SLOW)
|
||||
@Post('batch-status')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UsePipes(new ValidationPipe({ transform: true }))
|
||||
async batchUpdateUserStatus(
|
||||
@Body() batchUserStatusDto: BatchUserStatusDto
|
||||
): Promise<BatchUserStatusResponseDto> {
|
||||
this.logger.log('管理员批量修改用户状态', {
|
||||
operation: 'batch_update_user_status',
|
||||
userCount: batchUserStatusDto.userIds.length,
|
||||
newStatus: batchUserStatusDto.status,
|
||||
reason: batchUserStatusDto.reason,
|
||||
timestamp: UTILS.getCurrentTimestamp()
|
||||
});
|
||||
|
||||
return await this.userManagementService.batchUpdateUserStatus(batchUserStatusDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户状态统计
|
||||
*
|
||||
* 业务逻辑:
|
||||
* 1. 验证管理员权限
|
||||
* 2. 调用业务服务获取状态统计数据
|
||||
* 3. 记录统计查询的审计日志
|
||||
* 4. 返回各种状态的用户数量统计
|
||||
* 5. 提供状态分布分析数据
|
||||
*
|
||||
* @returns 状态统计信息
|
||||
* @throws ForbiddenException 管理员权限不足时
|
||||
* @throws InternalServerErrorException 统计数据获取失败时
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const stats = await controller.getUserStatusStats();
|
||||
* // 返回: { active: 1250, inactive: 45, locked: 12, ... }
|
||||
* ```
|
||||
*/
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@ApiOperation({
|
||||
summary: '获取用户状态统计',
|
||||
description: '获取各种用户状态的数量统计信息'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '获取成功',
|
||||
type: UserStatusStatsResponseDto
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 403,
|
||||
description: '权限不足'
|
||||
})
|
||||
@UseGuards(AdminGuard)
|
||||
@Timeout(TimeoutPresets.DATABASE_QUERY)
|
||||
@Get('status-stats')
|
||||
async getUserStatusStats(): Promise<UserStatusStatsResponseDto> {
|
||||
this.logger.log('管理员获取用户状态统计', {
|
||||
operation: 'get_user_status_stats',
|
||||
timestamp: UTILS.getCurrentTimestamp()
|
||||
});
|
||||
|
||||
return await this.userManagementService.getUserStatusStats();
|
||||
}
|
||||
}
|
||||
132
src/business/user_mgmt/user_status.dto.ts
Normal file
132
src/business/user_mgmt/user_status.dto.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* 用户状态管理 DTO
|
||||
*
|
||||
* 功能描述:
|
||||
* - 定义用户状态管理相关的请求数据结构
|
||||
* - 提供数据验证规则和错误提示
|
||||
* - 确保状态管理操作的数据格式一致性
|
||||
*
|
||||
* 职责分离:
|
||||
* - 请求数据结构定义和类型约束
|
||||
* - 数据验证规则配置和错误消息定义
|
||||
* - Swagger API文档生成支持
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-07: 代码规范优化 - 修正文件命名规范,完善注释规范,更新作者信息 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @since 2025-12-24
|
||||
* @lastModified 2026-01-07
|
||||
*/
|
||||
|
||||
import { IsString, IsNotEmpty, IsEnum, IsOptional, IsArray, ArrayMinSize, ArrayMaxSize } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { UserStatus } from './user_status.enum';
|
||||
import { BATCH_OPERATION, VALIDATION } from './user_mgmt.constants';
|
||||
|
||||
/**
|
||||
* 用户状态修改请求DTO
|
||||
*
|
||||
* 职责:
|
||||
* - 定义单个用户状态修改的请求数据格式
|
||||
* - 提供状态值和修改原因的验证规则
|
||||
* - 支持Swagger文档自动生成
|
||||
*
|
||||
* 主要字段:
|
||||
* - status - 新的用户状态(必填)
|
||||
* - reason - 状态修改原因(可选)
|
||||
*
|
||||
* 使用场景:
|
||||
* - 管理员修改单个用户状态的API请求
|
||||
* - 用户状态变更操作的数据传输
|
||||
*/
|
||||
export class UserStatusDto {
|
||||
/**
|
||||
* 新的用户状态
|
||||
*/
|
||||
@ApiProperty({
|
||||
description: '用户状态',
|
||||
enum: UserStatus,
|
||||
example: UserStatus.ACTIVE,
|
||||
enumName: 'UserStatus'
|
||||
})
|
||||
@IsEnum(UserStatus, { message: '用户状态必须是有效的枚举值' })
|
||||
@IsNotEmpty({ message: '用户状态不能为空' })
|
||||
status: UserStatus;
|
||||
|
||||
/**
|
||||
* 状态修改原因
|
||||
*/
|
||||
@ApiProperty({
|
||||
description: '状态修改原因(可选)',
|
||||
example: '用户违反社区规定',
|
||||
required: false,
|
||||
maxLength: VALIDATION.REASON_MAX_LENGTH
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString({ message: '修改原因必须是字符串' })
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量用户状态修改请求DTO
|
||||
*
|
||||
* 职责:
|
||||
* - 定义批量用户状态修改的请求数据格式
|
||||
* - 提供用户ID列表和状态值的验证规则
|
||||
* - 限制批量操作的数量范围(${BATCH_OPERATION.MIN_USER_COUNT}-${BATCH_OPERATION.MAX_USER_COUNT}个用户)
|
||||
*
|
||||
* 主要字段:
|
||||
* - userIds - 用户ID列表(必填,${BATCH_OPERATION.MIN_USER_COUNT}-${BATCH_OPERATION.MAX_USER_COUNT}个)
|
||||
* - status - 新的用户状态(必填)
|
||||
* - reason - 批量修改原因(可选)
|
||||
*
|
||||
* 使用场景:
|
||||
* - 管理员批量修改用户状态的API请求
|
||||
* - 系统自动化批量用户管理操作
|
||||
*/
|
||||
export class BatchUserStatusDto {
|
||||
/**
|
||||
* 用户ID列表
|
||||
*/
|
||||
@ApiProperty({
|
||||
description: '用户ID列表',
|
||||
example: ['1', '2', '3'],
|
||||
type: [String],
|
||||
minItems: BATCH_OPERATION.MIN_USER_COUNT,
|
||||
maxItems: BATCH_OPERATION.MAX_USER_COUNT
|
||||
})
|
||||
@IsArray({ message: '用户ID列表必须是数组' })
|
||||
@ArrayMinSize(BATCH_OPERATION.MIN_USER_COUNT, { message: '至少需要选择一个用户' })
|
||||
@ArrayMaxSize(BATCH_OPERATION.MAX_USER_COUNT, { message: `一次最多只能操作${BATCH_OPERATION.MAX_USER_COUNT}个用户` })
|
||||
@IsString({ each: true, message: '用户ID必须是字符串' })
|
||||
@IsNotEmpty({ each: true, message: '用户ID不能为空' })
|
||||
userIds: string[];
|
||||
|
||||
/**
|
||||
* 新的用户状态
|
||||
*/
|
||||
@ApiProperty({
|
||||
description: '用户状态',
|
||||
enum: UserStatus,
|
||||
example: UserStatus.LOCKED,
|
||||
enumName: 'UserStatus'
|
||||
})
|
||||
@IsEnum(UserStatus, { message: '用户状态必须是有效的枚举值' })
|
||||
@IsNotEmpty({ message: '用户状态不能为空' })
|
||||
status: UserStatus;
|
||||
|
||||
/**
|
||||
* 状态修改原因
|
||||
*/
|
||||
@ApiProperty({
|
||||
description: '批量修改原因(可选)',
|
||||
example: '批量处理违规用户',
|
||||
required: false,
|
||||
maxLength: VALIDATION.REASON_MAX_LENGTH
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString({ message: '修改原因必须是字符串' })
|
||||
reason?: string;
|
||||
}
|
||||
31
src/business/user_mgmt/user_status.enum.ts
Normal file
31
src/business/user_mgmt/user_status.enum.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 用户状态枚举(Business层兼容性导出)
|
||||
*
|
||||
* 功能描述:
|
||||
* - 重新导出Core层的用户状态枚举
|
||||
* - 保持向后兼容性
|
||||
* - 符合架构分层原则
|
||||
*
|
||||
* 职责分离:
|
||||
* - 提供Business层对Core层用户状态的访问接口
|
||||
* - 维护现有代码的兼容性
|
||||
* - 遵循依赖倒置原则
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-07: 架构优化 - 改为重新导出Core层枚举,符合架构分层原则 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.2
|
||||
* @since 2025-12-24
|
||||
* @lastModified 2026-01-07
|
||||
*/
|
||||
|
||||
// 重新导出Core层的用户状态枚举和相关函数
|
||||
export {
|
||||
UserStatus,
|
||||
getUserStatusDescription,
|
||||
canUserLogin,
|
||||
getUserStatusErrorMessage,
|
||||
getAllUserStatuses,
|
||||
isValidUserStatus
|
||||
} from '../../core/db/users/user_status.enum';
|
||||
303
src/business/user_mgmt/user_status_response.dto.ts
Normal file
303
src/business/user_mgmt/user_status_response.dto.ts
Normal file
@@ -0,0 +1,303 @@
|
||||
/**
|
||||
* 用户状态管理响应 DTO
|
||||
*
|
||||
* 功能描述:
|
||||
* - 定义用户状态管理相关的响应数据结构
|
||||
* - 提供Swagger文档生成支持
|
||||
* - 确保状态管理API响应的数据格式一致性
|
||||
*
|
||||
* 职责分离:
|
||||
* - 响应数据结构定义和类型约束
|
||||
* - API响应格式标准化和文档生成
|
||||
* - 错误信息和成功结果的统一封装
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-07: 代码规范优化 - 修正文件命名规范,完善注释规范,更新作者信息 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @since 2025-12-24
|
||||
* @lastModified 2026-01-07
|
||||
*/
|
||||
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { UserStatus } from './user_status.enum';
|
||||
|
||||
/**
|
||||
* 用户状态信息DTO
|
||||
*/
|
||||
export class UserStatusInfoDto {
|
||||
@ApiProperty({
|
||||
description: '用户ID',
|
||||
example: '1'
|
||||
})
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '用户名',
|
||||
example: 'testuser'
|
||||
})
|
||||
username: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '用户昵称',
|
||||
example: '测试用户'
|
||||
})
|
||||
nickname: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '用户状态',
|
||||
enum: UserStatus,
|
||||
example: UserStatus.ACTIVE
|
||||
})
|
||||
status: UserStatus;
|
||||
|
||||
@ApiProperty({
|
||||
description: '状态描述',
|
||||
example: '正常'
|
||||
})
|
||||
status_description: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '状态修改时间',
|
||||
example: '2025-12-24T10:00:00.000Z'
|
||||
})
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户状态修改响应数据DTO
|
||||
*/
|
||||
export class UserStatusDataDto {
|
||||
@ApiProperty({
|
||||
description: '用户信息',
|
||||
type: UserStatusInfoDto
|
||||
})
|
||||
user: UserStatusInfoDto;
|
||||
|
||||
@ApiProperty({
|
||||
description: '修改原因',
|
||||
example: '用户违反社区规定',
|
||||
required: false
|
||||
})
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户状态修改响应DTO
|
||||
*/
|
||||
export class UserStatusResponseDto {
|
||||
@ApiProperty({
|
||||
description: '请求是否成功',
|
||||
example: true
|
||||
})
|
||||
success: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: '响应数据',
|
||||
type: UserStatusDataDto,
|
||||
required: false
|
||||
})
|
||||
data?: UserStatusDataDto;
|
||||
|
||||
@ApiProperty({
|
||||
description: '响应消息',
|
||||
example: '用户状态修改成功'
|
||||
})
|
||||
message: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '错误代码',
|
||||
example: 'USER_STATUS_UPDATE_FAILED',
|
||||
required: false
|
||||
})
|
||||
error_code?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量操作结果DTO
|
||||
*/
|
||||
export class BatchOperationResultDto {
|
||||
@ApiProperty({
|
||||
description: '成功处理的用户列表',
|
||||
type: [UserStatusInfoDto]
|
||||
})
|
||||
success_users: UserStatusInfoDto[];
|
||||
|
||||
@ApiProperty({
|
||||
description: '处理失败的用户列表',
|
||||
type: [Object],
|
||||
example: [
|
||||
{
|
||||
user_id: '999',
|
||||
error: '用户不存在'
|
||||
}
|
||||
]
|
||||
})
|
||||
failed_users: Array<{
|
||||
user_id: string;
|
||||
error: string;
|
||||
}>;
|
||||
|
||||
@ApiProperty({
|
||||
description: '成功处理数量',
|
||||
example: 5
|
||||
})
|
||||
success_count: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '失败处理数量',
|
||||
example: 1
|
||||
})
|
||||
failed_count: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '总处理数量',
|
||||
example: 6
|
||||
})
|
||||
total_count: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量用户状态修改响应数据DTO
|
||||
*/
|
||||
export class BatchUserStatusDataDto {
|
||||
@ApiProperty({
|
||||
description: '批量操作结果',
|
||||
type: BatchOperationResultDto
|
||||
})
|
||||
result: BatchOperationResultDto;
|
||||
|
||||
@ApiProperty({
|
||||
description: '修改原因',
|
||||
example: '批量处理违规用户',
|
||||
required: false
|
||||
})
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量用户状态修改响应DTO
|
||||
*/
|
||||
export class BatchUserStatusResponseDto {
|
||||
@ApiProperty({
|
||||
description: '请求是否成功',
|
||||
example: true
|
||||
})
|
||||
success: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: '响应数据',
|
||||
type: BatchUserStatusDataDto,
|
||||
required: false
|
||||
})
|
||||
data?: BatchUserStatusDataDto;
|
||||
|
||||
@ApiProperty({
|
||||
description: '响应消息',
|
||||
example: '批量用户状态修改完成'
|
||||
})
|
||||
message: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '错误代码',
|
||||
example: 'BATCH_USER_STATUS_UPDATE_FAILED',
|
||||
required: false
|
||||
})
|
||||
error_code?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户状态统计DTO
|
||||
*/
|
||||
export class UserStatusStatsDto {
|
||||
@ApiProperty({
|
||||
description: '正常用户数量',
|
||||
example: 1250
|
||||
})
|
||||
active: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '未激活用户数量',
|
||||
example: 45
|
||||
})
|
||||
inactive: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '锁定用户数量',
|
||||
example: 12
|
||||
})
|
||||
locked: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '禁用用户数量',
|
||||
example: 8
|
||||
})
|
||||
banned: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '已删除用户数量',
|
||||
example: 3
|
||||
})
|
||||
deleted: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '待审核用户数量',
|
||||
example: 15
|
||||
})
|
||||
pending: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '总用户数量',
|
||||
example: 1333
|
||||
})
|
||||
total: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户状态统计响应数据DTO
|
||||
*/
|
||||
export class UserStatusStatsDataDto {
|
||||
@ApiProperty({
|
||||
description: '用户状态统计',
|
||||
type: UserStatusStatsDto
|
||||
})
|
||||
stats: UserStatusStatsDto;
|
||||
|
||||
@ApiProperty({
|
||||
description: '统计时间',
|
||||
example: '2025-12-24T10:00:00.000Z'
|
||||
})
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户状态统计响应DTO
|
||||
*/
|
||||
export class UserStatusStatsResponseDto {
|
||||
@ApiProperty({
|
||||
description: '请求是否成功',
|
||||
example: true
|
||||
})
|
||||
success: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: '响应数据',
|
||||
type: UserStatusStatsDataDto,
|
||||
required: false
|
||||
})
|
||||
data?: UserStatusStatsDataDto;
|
||||
|
||||
@ApiProperty({
|
||||
description: '响应消息',
|
||||
example: '用户状态统计获取成功'
|
||||
})
|
||||
message: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '错误代码',
|
||||
example: 'USER_STATUS_STATS_FAILED',
|
||||
required: false
|
||||
})
|
||||
error_code?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user