forked from datawhale/whale-town-end
- 新增auth模块处理认证逻辑 - 新增security模块处理安全相关功能 - 新增user-mgmt模块管理用户相关操作 - 新增shared模块存放共享组件 - 重构admin模块,添加DTO和Guards - 为admin模块添加测试文件结构
435 lines
13 KiB
TypeScript
435 lines
13 KiB
TypeScript
/**
|
|
* 管理员业务服务
|
|
*
|
|
* 功能描述:
|
|
* - 调用核心服务完成管理员登录
|
|
* - 提供用户列表查询
|
|
* - 提供用户密码重置能力
|
|
*
|
|
* @author jianuo
|
|
* @version 1.0.0
|
|
* @since 2025-12-19
|
|
*/
|
|
|
|
import { Inject, Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common';
|
|
import { AdminCoreService } from '../../core/admin_core/admin_core.service';
|
|
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 {
|
|
UserStatusResponseDto,
|
|
BatchUserStatusResponseDto,
|
|
UserStatusStatsResponseDto,
|
|
UserStatusInfoDto,
|
|
BatchOperationResultDto
|
|
} from '../user-mgmt/dto/user-status-response.dto';
|
|
|
|
export interface AdminApiResponse<T = any> {
|
|
success: boolean;
|
|
data?: T;
|
|
message: string;
|
|
error_code?: string;
|
|
}
|
|
|
|
@Injectable()
|
|
export class AdminService {
|
|
private readonly logger = new Logger(AdminService.name);
|
|
|
|
constructor(
|
|
private readonly adminCoreService: AdminCoreService,
|
|
@Inject('UsersService') private readonly usersService: UsersService | UsersMemoryService,
|
|
private readonly logManagementService: LogManagementService,
|
|
) {}
|
|
|
|
getLogDirAbsolutePath(): string {
|
|
return this.logManagementService.getLogDirAbsolutePath();
|
|
}
|
|
|
|
async login(identifier: string, password: string): Promise<AdminApiResponse> {
|
|
try {
|
|
const result = await this.adminCoreService.login({ identifier, password });
|
|
return { success: true, data: result, message: '管理员登录成功' };
|
|
} catch (error) {
|
|
this.logger.error(`管理员登录失败: ${identifier}`, error instanceof Error ? error.stack : String(error));
|
|
return {
|
|
success: false,
|
|
message: error instanceof Error ? error.message : '管理员登录失败',
|
|
error_code: 'ADMIN_LOGIN_FAILED',
|
|
};
|
|
}
|
|
}
|
|
|
|
async listUsers(limit: number, offset: number): Promise<AdminApiResponse<{ users: any[]; limit: number; offset: number }>> {
|
|
const users = await this.usersService.findAll(limit, offset);
|
|
return {
|
|
success: true,
|
|
data: {
|
|
users: users.map((u: Users) => this.formatUser(u)),
|
|
limit,
|
|
offset,
|
|
},
|
|
message: '用户列表获取成功',
|
|
};
|
|
}
|
|
|
|
async getUser(id: bigint): Promise<AdminApiResponse<{ user: any }>> {
|
|
const user = await this.usersService.findOne(id);
|
|
return {
|
|
success: true,
|
|
data: { user: this.formatUser(user) },
|
|
message: '用户信息获取成功',
|
|
};
|
|
}
|
|
|
|
async resetPassword(id: bigint, newPassword: string): Promise<AdminApiResponse> {
|
|
// 确认用户存在
|
|
const user = await this.usersService.findOne(id).catch((): null => null);
|
|
if (!user) {
|
|
throw new NotFoundException('用户不存在');
|
|
}
|
|
|
|
await this.adminCoreService.resetUserPassword(id, newPassword);
|
|
|
|
this.logger.log(`管理员重置密码成功: userId=${id.toString()}`);
|
|
|
|
return { success: true, message: '密码重置成功' };
|
|
}
|
|
|
|
async getRuntimeLogs(lines?: number): Promise<AdminApiResponse<{ file: string; updated_at: string; lines: string[] }>> {
|
|
const result = await this.logManagementService.getRuntimeLogTail({ lines });
|
|
return {
|
|
success: true,
|
|
data: result,
|
|
message: '运行日志获取成功',
|
|
};
|
|
}
|
|
|
|
private formatUser(user: Users) {
|
|
return {
|
|
id: user.id.toString(),
|
|
username: user.username,
|
|
nickname: user.nickname,
|
|
email: user.email,
|
|
email_verified: user.email_verified,
|
|
phone: user.phone,
|
|
avatar_url: user.avatar_url,
|
|
role: user.role,
|
|
status: user.status || UserStatus.ACTIVE, // 兼容旧数据
|
|
created_at: user.created_at,
|
|
updated_at: user.updated_at,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 格式化用户状态信息
|
|
*
|
|
* @param user 用户实体
|
|
* @returns 格式化的用户状态信息
|
|
*/
|
|
private formatUserStatus(user: Users): UserStatusInfoDto {
|
|
return {
|
|
id: user.id.toString(),
|
|
username: user.username,
|
|
nickname: user.nickname,
|
|
status: user.status || UserStatus.ACTIVE,
|
|
status_description: getUserStatusDescription(user.status || UserStatus.ACTIVE),
|
|
updated_at: user.updated_at
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 修改用户状态
|
|
*
|
|
* 功能描述:
|
|
* 管理员修改指定用户的账户状态,支持激活、锁定、禁用等操作
|
|
*
|
|
* 业务逻辑:
|
|
* 1. 验证用户是否存在
|
|
* 2. 检查状态变更的合法性
|
|
* 3. 更新用户状态
|
|
* 4. 记录状态变更日志
|
|
*
|
|
* @param userId 用户ID
|
|
* @param userStatusDto 状态修改数据
|
|
* @returns 修改结果
|
|
*
|
|
* @throws NotFoundException 当用户不存在时
|
|
* @throws BadRequestException 当状态变更不合法时
|
|
*/
|
|
async updateUserStatus(userId: bigint, userStatusDto: UserStatusDto): Promise<UserStatusResponseDto> {
|
|
try {
|
|
this.logger.log('开始修改用户状态', {
|
|
operation: 'update_user_status',
|
|
userId: userId.toString(),
|
|
newStatus: userStatusDto.status,
|
|
reason: userStatusDto.reason,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
// 1. 验证用户是否存在
|
|
const user = await this.usersService.findOne(userId);
|
|
if (!user) {
|
|
this.logger.warn('修改用户状态失败:用户不存在', {
|
|
operation: 'update_user_status',
|
|
userId: userId.toString()
|
|
});
|
|
throw new NotFoundException('用户不存在');
|
|
}
|
|
|
|
// 2. 检查状态变更的合法性
|
|
if (user.status === userStatusDto.status) {
|
|
this.logger.warn('修改用户状态失败:状态未发生变化', {
|
|
operation: 'update_user_status',
|
|
userId: userId.toString(),
|
|
currentStatus: user.status,
|
|
newStatus: userStatusDto.status
|
|
});
|
|
throw new BadRequestException('用户状态未发生变化');
|
|
}
|
|
|
|
// 3. 更新用户状态
|
|
const updatedUser = await this.usersService.update(userId, {
|
|
status: userStatusDto.status
|
|
});
|
|
|
|
// 4. 记录状态变更日志
|
|
this.logger.log('用户状态修改成功', {
|
|
operation: 'update_user_status',
|
|
userId: userId.toString(),
|
|
oldStatus: user.status,
|
|
newStatus: userStatusDto.status,
|
|
reason: userStatusDto.reason,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
user: this.formatUserStatus(updatedUser),
|
|
reason: userStatusDto.reason
|
|
},
|
|
message: '用户状态修改成功'
|
|
};
|
|
|
|
} catch (error) {
|
|
this.logger.error('修改用户状态失败', {
|
|
operation: 'update_user_status',
|
|
userId: userId.toString(),
|
|
error: error instanceof Error ? error.message : String(error),
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
if (error instanceof NotFoundException || error instanceof BadRequestException) {
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
success: false,
|
|
message: '用户状态修改失败',
|
|
error_code: 'USER_STATUS_UPDATE_FAILED'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 批量修改用户状态
|
|
*
|
|
* 功能描述:
|
|
* 管理员批量修改多个用户的账户状态
|
|
*
|
|
* 业务逻辑:
|
|
* 1. 验证用户ID列表
|
|
* 2. 逐个处理用户状态修改
|
|
* 3. 收集成功和失败的结果
|
|
* 4. 返回批量操作结果
|
|
*
|
|
* @param batchUserStatusDto 批量状态修改数据
|
|
* @returns 批量修改结果
|
|
*/
|
|
async batchUpdateUserStatus(batchUserStatusDto: BatchUserStatusDto): Promise<BatchUserStatusResponseDto> {
|
|
try {
|
|
this.logger.log('开始批量修改用户状态', {
|
|
operation: 'batch_update_user_status',
|
|
userCount: batchUserStatusDto.user_ids.length,
|
|
newStatus: batchUserStatusDto.status,
|
|
reason: batchUserStatusDto.reason,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
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 : '未知错误'
|
|
});
|
|
}
|
|
}
|
|
|
|
// 5. 构建批量操作结果
|
|
const result: BatchOperationResultDto = {
|
|
success_users: successUsers,
|
|
failed_users: failedUsers,
|
|
success_count: successUsers.length,
|
|
failed_count: failedUsers.length,
|
|
total_count: batchUserStatusDto.user_ids.length
|
|
};
|
|
|
|
this.logger.log('批量修改用户状态完成', {
|
|
operation: 'batch_update_user_status',
|
|
successCount: result.success_count,
|
|
failedCount: result.failed_count,
|
|
totalCount: result.total_count,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
result,
|
|
reason: batchUserStatusDto.reason
|
|
},
|
|
message: `批量用户状态修改完成,成功:${result.success_count},失败:${result.failed_count}`
|
|
};
|
|
|
|
} catch (error) {
|
|
this.logger.error('批量修改用户状态失败', {
|
|
operation: 'batch_update_user_status',
|
|
error: error instanceof Error ? error.message : String(error),
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
return {
|
|
success: false,
|
|
message: '批量用户状态修改失败',
|
|
error_code: 'BATCH_USER_STATUS_UPDATE_FAILED'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取用户状态统计
|
|
*
|
|
* 功能描述:
|
|
* 获取各种用户状态的数量统计信息
|
|
*
|
|
* 业务逻辑:
|
|
* 1. 查询所有用户
|
|
* 2. 按状态分组统计
|
|
* 3. 计算各状态数量
|
|
* 4. 返回统计结果
|
|
*
|
|
* @returns 状态统计信息
|
|
*/
|
|
async getUserStatusStats(): Promise<UserStatusStatsResponseDto> {
|
|
try {
|
|
this.logger.log('开始获取用户状态统计', {
|
|
operation: 'get_user_status_stats',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
// 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
|
|
};
|
|
|
|
// 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('用户状态统计获取成功', {
|
|
operation: 'get_user_status_stats',
|
|
stats,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
stats,
|
|
timestamp: new Date().toISOString()
|
|
},
|
|
message: '用户状态统计获取成功'
|
|
};
|
|
|
|
} catch (error) {
|
|
this.logger.error('获取用户状态统计失败', {
|
|
operation: 'get_user_status_stats',
|
|
error: error instanceof Error ? error.message : String(error),
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
return {
|
|
success: false,
|
|
message: '用户状态统计获取失败',
|
|
error_code: 'USER_STATUS_STATS_FAILED'
|
|
};
|
|
}
|
|
}
|
|
}
|