范围: src/core/db/users/ - base_users.service.ts: 为保护方法补充@example示例 - users.constants.ts: 补充职责分离描述 检查人员: moyin 检查日期: 2026-01-15
203 lines
5.6 KiB
TypeScript
203 lines
5.6 KiB
TypeScript
/**
|
|
* 用户服务基类
|
|
*
|
|
* 功能描述:
|
|
* - 提供统一的异常处理机制
|
|
* - 定义通用的错误处理方法
|
|
* - 统一日志记录格式
|
|
* - 敏感信息脱敏处理
|
|
*
|
|
* 职责分离:
|
|
* - 异常处理:统一的错误格式化和异常转换
|
|
* - 日志管理:结构化日志记录和敏感信息脱敏
|
|
* - 性能监控:操作成功和失败的统计记录
|
|
* - 搜索优化:搜索异常的特殊处理机制
|
|
*
|
|
* 最近修改:
|
|
* - 2026-01-15: 代码规范优化 - 为保护方法补充@example示例 (修改者: moyin)
|
|
* - 2026-01-07: 代码规范优化 - 完善注释规范,添加完整的文件头和方法注释
|
|
* - 2026-01-07: 功能新增 - 添加敏感信息脱敏处理和结构化日志记录
|
|
*
|
|
* @author moyin
|
|
* @version 1.0.2
|
|
* @since 2025-01-07
|
|
* @lastModified 2026-01-15
|
|
*/
|
|
|
|
import { Logger, ConflictException, NotFoundException, BadRequestException } from '@nestjs/common';
|
|
|
|
export abstract class BaseUsersService {
|
|
protected readonly logger = new Logger(this.constructor.name);
|
|
|
|
/**
|
|
* 统一的错误格式化方法
|
|
*
|
|
* @param error 原始错误对象
|
|
* @returns 格式化后的错误信息字符串
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const errorMsg = this.formatError(new Error('数据库连接失败'));
|
|
* // 返回: "数据库连接失败"
|
|
* ```
|
|
*/
|
|
protected formatError(error: unknown): string {
|
|
if (error instanceof Error) {
|
|
return error.message;
|
|
}
|
|
return String(error);
|
|
}
|
|
|
|
/**
|
|
* 统一的异常处理方法
|
|
*
|
|
* @param error 原始错误
|
|
* @param operation 操作名称
|
|
* @param context 上下文信息
|
|
* @throws 处理后的标准异常
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* try {
|
|
* // 业务操作
|
|
* } catch (error) {
|
|
* this.handleServiceError(error, '创建用户', { username: 'test' });
|
|
* }
|
|
* ```
|
|
*/
|
|
protected handleServiceError(error: unknown, operation: string, context?: Record<string, any>): never {
|
|
const errorMessage = this.formatError(error);
|
|
|
|
// 记录错误日志
|
|
this.logger.error(`${operation}失败`, {
|
|
operation,
|
|
error: errorMessage,
|
|
context: context ? this.sanitizeLogData(context) : undefined,
|
|
timestamp: new Date().toISOString()
|
|
}, error instanceof Error ? error.stack : undefined);
|
|
|
|
// 如果是已知的业务异常,直接重新抛出
|
|
if (error instanceof ConflictException ||
|
|
error instanceof NotFoundException ||
|
|
error instanceof BadRequestException) {
|
|
throw error;
|
|
}
|
|
|
|
// 系统异常转换为BadRequestException
|
|
throw new BadRequestException(`${operation}失败,请稍后重试`);
|
|
}
|
|
|
|
/**
|
|
* 搜索异常的特殊处理(返回空结果而不抛出异常)
|
|
*
|
|
* @param error 原始错误
|
|
* @param operation 操作名称
|
|
* @param context 上下文信息
|
|
* @returns 空数组
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* try {
|
|
* // 搜索操作
|
|
* } catch (error) {
|
|
* return this.handleSearchError(error, '搜索用户', { keyword: 'test' });
|
|
* }
|
|
* ```
|
|
*/
|
|
protected handleSearchError(error: unknown, operation: string, context?: Record<string, any>): any[] {
|
|
const errorMessage = this.formatError(error);
|
|
|
|
this.logger.warn(`${operation}失败,返回空结果`, {
|
|
operation,
|
|
error: errorMessage,
|
|
context: context ? this.sanitizeLogData(context) : undefined,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* 记录操作成功日志
|
|
*
|
|
* @param operation 操作名称
|
|
* @param context 上下文信息
|
|
* @param duration 操作耗时
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* this.logSuccess('创建用户', { userId: '123', username: 'test' }, 50);
|
|
* ```
|
|
*/
|
|
protected logSuccess(operation: string, context?: Record<string, any>, duration?: number): void {
|
|
this.logger.log(`${operation}成功`, {
|
|
operation,
|
|
context: context ? this.sanitizeLogData(context) : undefined,
|
|
duration,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 记录操作开始日志
|
|
*
|
|
* @param operation 操作名称
|
|
* @param context 上下文信息
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* this.logStart('创建用户', { username: 'test' });
|
|
* ```
|
|
*/
|
|
protected logStart(operation: string, context?: Record<string, any>): void {
|
|
this.logger.log(`开始${operation}`, {
|
|
operation,
|
|
context: context ? this.sanitizeLogData(context) : undefined,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 脱敏处理敏感信息
|
|
*
|
|
* @param data 原始数据
|
|
* @returns 脱敏后的数据
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const sanitized = this.sanitizeLogData({
|
|
* email: 'test@example.com',
|
|
* phone: '13800138000',
|
|
* password_hash: 'secret'
|
|
* });
|
|
* // 返回: { email: 'te***@example.com', phone: '138****00', password_hash: '[REDACTED]' }
|
|
* ```
|
|
*/
|
|
protected sanitizeLogData(data: Record<string, any>): Record<string, any> {
|
|
const sanitized = { ...data };
|
|
|
|
// 脱敏邮箱
|
|
if (sanitized.email) {
|
|
const email = sanitized.email;
|
|
const [localPart, domain] = email.split('@');
|
|
if (localPart && domain) {
|
|
sanitized.email = `${localPart.substring(0, 2)}***@${domain}`;
|
|
}
|
|
}
|
|
|
|
// 脱敏手机号
|
|
if (sanitized.phone) {
|
|
const phone = sanitized.phone;
|
|
if (phone.length > 4) {
|
|
sanitized.phone = `${phone.substring(0, 3)}****${phone.substring(phone.length - 2)}`;
|
|
}
|
|
}
|
|
|
|
// 移除密码哈希
|
|
if (sanitized.password_hash) {
|
|
sanitized.password_hash = '[REDACTED]';
|
|
}
|
|
|
|
return sanitized;
|
|
}
|
|
} |