refactor:重构安全模块架构,将security模块迁移至core层

- 将src/business/security模块迁移至src/core/security_core
- 更新模块导入路径和依赖关系
- 统一安全相关组件的命名规范(content_type.middleware.ts)
- 清理过时的配置文件和文档
- 更新架构文档以反映新的模块结构

此次重构符合业务功能模块化架构设计原则,将技术基础设施
服务统一放置在core层,提高代码组织的清晰度和可维护性。
This commit is contained in:
moyin
2026-01-04 19:34:16 +08:00
parent 67ade48ad7
commit 70c020a97c
25 changed files with 174 additions and 1215 deletions

View File

@@ -25,7 +25,7 @@ import {
AdminUserResponseDto,
AdminRuntimeLogsResponseDto
} from './dto/admin-response.dto';
import { Throttle, ThrottlePresets } from '../security/decorators/throttle.decorator';
import { Throttle, ThrottlePresets } from '../../core/security_core/decorators/throttle.decorator';
import type { Response } from 'express';
import * as fs from 'fs';
import * as path from 'path';

View File

@@ -33,8 +33,8 @@ import {
TestModeEmailVerificationResponseDto,
SuccessEmailVerificationResponseDto
} from '../dto/login_response.dto';
import { Throttle, ThrottlePresets } from '../../security/decorators/throttle.decorator';
import { Timeout, TimeoutPresets } from '../../security/decorators/timeout.decorator';
import { Throttle, ThrottlePresets } from '../../../core/security_core/decorators/throttle.decorator';
import { Timeout, TimeoutPresets } from '../../../core/security_core/decorators/timeout.decorator';
@ApiTags('auth')
@Controller('auth')

View File

@@ -1,89 +0,0 @@
/**
* 频率限制装饰器
*
* 功能描述:
* - 提供API接口的频率限制功能
* - 防止恶意请求和系统滥用
* - 支持基于IP和用户的限制策略
*
* 使用场景:
* - 登录接口防暴力破解
* - 注册接口防批量注册
* - 验证码接口防频繁发送
* - 敏感操作接口保护
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { SetMetadata, applyDecorators, UseGuards } from '@nestjs/common';
import { ThrottleGuard } from '../guards/throttle.guard';
/**
* 频率限制元数据键
*/
export const THROTTLE_KEY = 'throttle';
/**
* 频率限制配置接口
*/
export interface ThrottleConfig {
/** 时间窗口内允许的最大请求次数 */
limit: number;
/** 时间窗口长度(秒) */
ttl: number;
/** 限制类型ip基于IP或 user基于用户 */
type?: 'ip' | 'user';
/** 自定义错误消息 */
message?: string;
}
/**
* 频率限制装饰器
*
* @param config 频率限制配置
* @returns 装饰器函数
*
* @example
* ```typescript
* // 每分钟最多5次登录尝试
* @Throttle({ limit: 5, ttl: 60, message: '登录尝试过于频繁,请稍后再试' })
* @Post('login')
* async login() { ... }
*
* // 每5分钟最多3次注册
* @Throttle({ limit: 3, ttl: 300, type: 'ip' })
* @Post('register')
* async register() { ... }
* ```
*/
export function Throttle(config: ThrottleConfig) {
return applyDecorators(
SetMetadata(THROTTLE_KEY, config),
UseGuards(ThrottleGuard)
);
}
/**
* 预定义的频率限制配置
*/
export const ThrottlePresets = {
/** 登录接口每分钟5次 */
LOGIN: { limit: 5, ttl: 60, message: '登录尝试过于频繁请1分钟后再试' },
/** 注册接口每5分钟10次开发环境放宽限制 */
REGISTER: { limit: 10, ttl: 300, message: '注册请求过于频繁请5分钟后再试' },
/** 发送验证码每分钟1次 */
SEND_CODE: { limit: 1, ttl: 60, message: '验证码发送过于频繁请1分钟后再试' },
/** 密码重置每小时3次 */
RESET_PASSWORD: { limit: 3, ttl: 3600, message: '密码重置请求过于频繁请1小时后再试' },
/** 管理员操作每分钟10次 */
ADMIN_OPERATION: { limit: 10, ttl: 60, message: '管理员操作过于频繁,请稍后再试' },
/** 一般API每分钟30次 */
GENERAL_API: { limit: 30, ttl: 60, message: 'API调用过于频繁请稍后再试' }
} as const;

View File

@@ -1,119 +0,0 @@
/**
* 超时处理装饰器
*
* 功能描述:
* - 为API接口添加超时控制
* - 防止长时间运行的请求阻塞系统
* - 提供友好的超时错误提示
*
* 使用场景:
* - 数据库查询超时控制
* - 外部API调用超时
* - 文件上传下载超时
* - 复杂计算任务超时
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { SetMetadata, applyDecorators } from '@nestjs/common';
import { ApiResponse } from '@nestjs/swagger';
/**
* 超时配置元数据键
*/
export const TIMEOUT_KEY = 'timeout';
/**
* 超时配置接口
*/
export interface TimeoutConfig {
/** 超时时间(毫秒) */
timeout: number;
/** 自定义超时错误消息 */
message?: string;
/** 是否记录超时日志 */
logTimeout?: boolean;
}
/**
* 超时装饰器
*
* @param config 超时配置或超时时间(毫秒)
* @returns 装饰器函数
*
* @example
* ```typescript
* // 设置30秒超时
* @Timeout(30000)
* @Get('slow-operation')
* async slowOperation() { ... }
*
* // 自定义超时配置
* @Timeout({
* timeout: 60000,
* message: '数据查询超时,请稍后重试',
* logTimeout: true
* })
* @Post('complex-query')
* async complexQuery() { ... }
* ```
*/
export function Timeout(config: number | TimeoutConfig) {
const timeoutConfig: TimeoutConfig = typeof config === 'number'
? { timeout: config }
: config;
return applyDecorators(
SetMetadata(TIMEOUT_KEY, timeoutConfig),
ApiResponse({
status: 408,
description: timeoutConfig.message || '请求超时',
schema: {
type: 'object',
properties: {
success: { type: 'boolean', example: false },
message: { type: 'string', example: timeoutConfig.message || '请求超时,请稍后重试' },
error_code: { type: 'string', example: 'REQUEST_TIMEOUT' },
timeout_info: {
type: 'object',
properties: {
timeout_ms: { type: 'number', example: timeoutConfig.timeout },
timestamp: { type: 'string', example: '2025-12-24T10:00:00.000Z' }
}
}
}
}
})
);
}
/**
* 预定义的超时配置
*/
export const TimeoutPresets = {
/** 快速操作5秒 */
FAST: { timeout: 5000, message: '操作超时,请检查网络连接' },
/** 一般操作30秒 */
NORMAL: { timeout: 30000, message: '请求超时,请稍后重试' },
/** 慢操作60秒 */
SLOW: { timeout: 60000, message: '操作超时,请稍后重试' },
/** 文件操作2分钟 */
FILE_OPERATION: { timeout: 120000, message: '文件操作超时,请检查文件大小和网络状况' },
/** 数据库查询45秒 */
DATABASE_QUERY: { timeout: 45000, message: '数据查询超时,请简化查询条件或稍后重试' },
/** 外部API调用15秒 */
EXTERNAL_API: { timeout: 15000, message: '外部服务调用超时,请稍后重试' },
/** 邮件发送30秒 */
EMAIL_SEND: { timeout: 30000, message: '邮件发送超时,请检查邮件服务配置' },
/** 长时间任务5分钟 */
LONG_TASK: { timeout: 300000, message: '任务执行超时,请稍后重试' }
} as const;

View File

@@ -1,317 +0,0 @@
/**
* 频率限制守卫
*
* 功能描述:
* - 实现API接口的频率限制功能
* - 基于IP地址进行限制
* - 支持自定义限制规则
*
* 使用场景:
* - 防止API滥用
* - 登录暴力破解防护
* - 验证码发送频率控制
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import {
Injectable,
CanActivate,
ExecutionContext,
HttpException,
HttpStatus,
Logger
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { THROTTLE_KEY, ThrottleConfig } from '../decorators/throttle.decorator';
/**
* 频率限制记录接口
*/
interface ThrottleRecord {
/** 请求次数 */
count: number;
/** 窗口开始时间 */
windowStart: number;
/** 最后请求时间 */
lastRequest: number;
}
/**
* 频率限制响应接口
*/
interface ThrottleResponse {
/** 请求是否成功 */
success: boolean;
/** 响应消息 */
message: string;
/** 错误代码 */
error_code: string;
/** 限制信息 */
throttle_info: {
/** 限制次数 */
limit: number;
/** 时间窗口(秒) */
window_seconds: number;
/** 当前请求次数 */
current_requests: number;
/** 重置时间 */
reset_time: string;
};
}
@Injectable()
export class ThrottleGuard implements CanActivate {
private readonly logger = new Logger(ThrottleGuard.name);
/**
* 存储频率限制记录
* Key: IP地址 + 路径
* Value: 限制记录
*/
private readonly records = new Map<string, ThrottleRecord>();
/**
* 清理过期记录的间隔(毫秒)
*/
private readonly cleanupInterval = 60000; // 1分钟
constructor(private readonly reflector: Reflector) {
// 启动定期清理任务
this.startCleanupTask();
}
/**
* 守卫检查函数
*
* @param context 执行上下文
* @returns 是否允许通过
*/
async canActivate(context: ExecutionContext): Promise<boolean> {
// 1. 获取频率限制配置
const throttleConfig = this.getThrottleConfig(context);
if (!throttleConfig) {
// 没有配置频率限制,直接通过
return true;
}
// 2. 获取请求信息
const request = context.switchToHttp().getRequest<Request>();
const key = this.generateKey(request, throttleConfig);
// 3. 检查频率限制
const isAllowed = this.checkThrottle(key, throttleConfig);
if (!isAllowed) {
// 4. 记录被限制的请求
this.logger.warn('请求被频率限制', {
operation: 'throttle_limit',
method: request.method,
url: request.url,
ip: request.ip,
userAgent: request.get('User-Agent'),
limit: throttleConfig.limit,
ttl: throttleConfig.ttl,
timestamp: new Date().toISOString()
});
// 5. 抛出频率限制异常
const record = this.records.get(key);
const resetTime = new Date(record!.windowStart + throttleConfig.ttl * 1000);
const response: ThrottleResponse = {
success: false,
message: throttleConfig.message || '请求过于频繁,请稍后再试',
error_code: 'TOO_MANY_REQUESTS',
throttle_info: {
limit: throttleConfig.limit,
window_seconds: throttleConfig.ttl,
current_requests: record!.count,
reset_time: resetTime.toISOString()
}
};
throw new HttpException(response, HttpStatus.TOO_MANY_REQUESTS);
}
return true;
}
/**
* 获取频率限制配置
*
* @param context 执行上下文
* @returns 频率限制配置或null
*/
private getThrottleConfig(context: ExecutionContext): ThrottleConfig | null {
// 从方法装饰器获取配置
const methodConfig = this.reflector.get<ThrottleConfig>(
THROTTLE_KEY,
context.getHandler()
);
if (methodConfig) {
return methodConfig;
}
// 从类装饰器获取配置
const classConfig = this.reflector.get<ThrottleConfig>(
THROTTLE_KEY,
context.getClass()
);
return classConfig || null;
}
/**
* 生成限制键
*
* @param request 请求对象
* @param config 频率限制配置
* @returns 限制键
*/
private generateKey(request: Request, config: ThrottleConfig): string {
const ip = request.ip || 'unknown';
const path = request.route?.path || request.url;
const method = request.method;
// 根据限制类型生成不同的键
if (config.type === 'user') {
// 基于用户的限制需要从JWT中获取用户ID
const userId = this.extractUserId(request);
return `user:${userId}:${method}:${path}`;
} else {
// 基于IP的限制默认
return `ip:${ip}:${method}:${path}`;
}
}
/**
* 检查频率限制
*
* @param key 限制键
* @param config 频率限制配置
* @returns 是否允许通过
*/
private checkThrottle(key: string, config: ThrottleConfig): boolean {
const now = Date.now();
const windowMs = config.ttl * 1000;
let record = this.records.get(key);
if (!record) {
// 第一次请求
this.records.set(key, {
count: 1,
windowStart: now,
lastRequest: now
});
return true;
}
// 检查是否需要重置窗口
if (now - record.windowStart >= windowMs) {
// 重置窗口
record.count = 1;
record.windowStart = now;
record.lastRequest = now;
return true;
}
// 在当前窗口内
if (record.count >= config.limit) {
// 超过限制
return false;
}
// 增加计数
record.count++;
record.lastRequest = now;
return true;
}
/**
* 从请求中提取用户ID
*
* @param request 请求对象
* @returns 用户ID
*/
private extractUserId(request: Request): string {
// 这里应该从JWT token中提取用户ID
// 简化实现使用IP作为fallback
const authHeader = request.get('Authorization');
if (authHeader && authHeader.startsWith('Bearer ')) {
try {
// 这里应该解析JWT token获取用户ID
// 简化实现返回token的hash
const token = authHeader.substring(7);
return Buffer.from(token).toString('base64').substring(0, 10);
} catch (error) {
// JWT解析失败使用IP
return request.ip || 'unknown';
}
}
return request.ip || 'unknown';
}
/**
* 启动清理任务
*/
private startCleanupTask(): void {
setInterval(() => {
this.cleanupExpiredRecords();
}, this.cleanupInterval);
}
/**
* 清理过期记录
*/
private cleanupExpiredRecords(): void {
const now = Date.now();
const maxAge = 3600000; // 1小时
for (const [key, record] of this.records.entries()) {
if (now - record.lastRequest > maxAge) {
this.records.delete(key);
}
}
}
/**
* 获取当前记录统计
*
* @returns 记录统计信息
*/
getStats() {
return {
totalRecords: this.records.size,
records: Array.from(this.records.entries()).map(([key, record]) => ({
key,
count: record.count,
windowStart: new Date(record.windowStart).toISOString(),
lastRequest: new Date(record.lastRequest).toISOString()
}))
};
}
/**
* 清除所有记录
*/
clearAllRecords(): void {
this.records.clear();
}
/**
* 清除指定键的记录
*
* @param key 限制键
*/
clearRecord(key: string): void {
this.records.delete(key);
}
}

View File

@@ -1,27 +0,0 @@
/**
* 安全功能模块导出
*
* 功能概述:
* - 频率限制和防护机制
* - 请求超时控制
* - 维护模式管理
* - 内容类型验证
* - 系统安全中间件
*/
// 模块
export * from './security.module';
// 守卫
export * from './guards/throttle.guard';
// 中间件
export * from './middleware/maintenance.middleware';
export * from './middleware/content-type.middleware';
// 拦截器
export * from './interceptors/timeout.interceptor';
// 装饰器
export * from './decorators/throttle.decorator';
export * from './decorators/timeout.decorator';

View File

@@ -1,179 +0,0 @@
/**
* 超时拦截器
*
* 功能描述:
* - 实现API接口的超时控制逻辑
* - 在超时时自动取消请求并返回错误
* - 记录超时事件的详细日志
*
* 使用场景:
* - 全局超时控制
* - 防止资源泄漏
* - 提升系统稳定性
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
RequestTimeoutException,
Logger
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';
import { TIMEOUT_KEY, TimeoutConfig } from '../decorators/timeout.decorator';
/**
* 超时响应接口
*/
interface TimeoutResponse {
/** 请求是否成功 */
success: boolean;
/** 响应消息 */
message: string;
/** 错误代码 */
error_code: string;
/** 超时信息 */
timeout_info: {
/** 超时时间(毫秒) */
timeout_ms: number;
/** 超时发生时间 */
timestamp: string;
};
}
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
private readonly logger = new Logger(TimeoutInterceptor.name);
constructor(private readonly reflector: Reflector) {}
/**
* 拦截器处理函数
*
* 业务逻辑:
* 1. 获取超时配置
* 2. 应用超时控制
* 3. 处理超时异常
* 4. 记录超时日志
*
* @param context 执行上下文
* @param next 调用处理器
* @returns 可观察对象
*/
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// 1. 获取超时配置
const timeoutConfig = this.getTimeoutConfig(context);
if (!timeoutConfig) {
// 没有配置超时,直接执行
return next.handle();
}
// 2. 获取请求信息用于日志记录
const request = context.switchToHttp().getRequest();
const startTime = Date.now();
// 3. 应用超时控制
return next.handle().pipe(
timeout(timeoutConfig.timeout),
catchError((error) => {
if (error instanceof TimeoutError) {
// 4. 处理超时异常
const duration = Date.now() - startTime;
// 5. 记录超时日志
if (timeoutConfig.logTimeout !== false) {
this.logger.warn('请求超时', {
operation: 'request_timeout',
method: request.method,
url: request.url,
timeout_ms: timeoutConfig.timeout,
actual_duration_ms: duration,
userAgent: request.get('User-Agent'),
ip: request.ip,
timestamp: new Date().toISOString()
});
}
// 6. 构建超时响应
const timeoutResponse: TimeoutResponse = {
success: false,
message: timeoutConfig.message || '请求超时,请稍后重试',
error_code: 'REQUEST_TIMEOUT',
timeout_info: {
timeout_ms: timeoutConfig.timeout,
timestamp: new Date().toISOString()
}
};
// 7. 抛出超时异常
return throwError(() => new RequestTimeoutException(timeoutResponse));
}
// 其他异常直接抛出
return throwError(() => error);
})
);
}
/**
* 获取超时配置
*
* @param context 执行上下文
* @returns 超时配置或null
*/
private getTimeoutConfig(context: ExecutionContext): TimeoutConfig | null {
// 从方法装饰器获取配置
const methodConfig = this.reflector.get<TimeoutConfig>(
TIMEOUT_KEY,
context.getHandler()
);
if (methodConfig) {
return methodConfig;
}
// 从类装饰器获取配置
const classConfig = this.reflector.get<TimeoutConfig>(
TIMEOUT_KEY,
context.getClass()
);
return classConfig || null;
}
/**
* 获取默认超时配置
*
* @returns 默认超时配置
*/
private getDefaultTimeoutConfig(): TimeoutConfig {
return {
timeout: 30000, // 默认30秒
message: '请求超时,请稍后重试',
logTimeout: true
};
}
/**
* 验证超时配置
*
* @param config 超时配置
* @returns 是否有效
*/
private isValidTimeoutConfig(config: TimeoutConfig): boolean {
return (
config &&
typeof config.timeout === 'number' &&
config.timeout > 0 &&
config.timeout <= 600000 // 最大10分钟
);
}
}

View File

@@ -1,224 +0,0 @@
/**
* 内容类型检查中间件
*
* 功能描述:
* - 检查POST/PUT请求的Content-Type头
* - 确保API接口接收正确的数据格式
* - 提供友好的错误提示信息
*
* 使用场景:
* - API接口数据格式验证
* - 防止错误的请求格式
* - 提升API接口的健壮性
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { Logger } from '@nestjs/common';
/**
* 不支持的媒体类型响应接口
*/
interface UnsupportedMediaTypeResponse {
/** 请求是否成功 */
success: boolean;
/** 响应消息 */
message: string;
/** 错误代码 */
error_code: string;
/** 支持的媒体类型 */
supported_types: string[];
/** 接收到的媒体类型 */
received_type?: string;
}
@Injectable()
export class ContentTypeMiddleware implements NestMiddleware {
private readonly logger = new Logger(ContentTypeMiddleware.name);
/**
* 需要检查Content-Type的HTTP方法
*/
private readonly methodsToCheck = ['POST', 'PUT', 'PATCH'];
/**
* 支持的Content-Type列表
*/
private readonly supportedTypes = [
'application/json',
'application/json; charset=utf-8'
];
/**
* 不需要检查Content-Type的路径正则表达式
*/
private readonly excludePaths = [
/^\/api-docs/, // Swagger文档
/^\/health/, // 健康检查
/^\/admin\/logs\/archive/, // 文件下载
/\/upload/, // 文件上传
];
/**
* 中间件处理函数
*
* 业务逻辑:
* 1. 检查是否需要验证Content-Type
* 2. 获取请求的Content-Type头
* 3. 验证Content-Type是否支持
* 4. 记录不支持的请求类型
*
* @param req HTTP请求对象
* @param res HTTP响应对象
* @param next 下一个中间件函数
*/
use(req: Request, res: Response, next: NextFunction) {
// 1. 检查是否需要验证Content-Type
if (!this.shouldCheckContentType(req)) {
next();
return;
}
// 2. 获取请求的Content-Type
const contentType = req.get('Content-Type');
// 3. 检查Content-Type是否存在
if (!contentType) {
this.logger.warn('请求缺少Content-Type头', {
operation: 'content_type_check',
method: req.method,
url: req.url,
userAgent: req.get('User-Agent'),
ip: req.ip,
timestamp: new Date().toISOString()
});
const response: UnsupportedMediaTypeResponse = {
success: false,
message: '请求缺少Content-Type头请设置为application/json',
error_code: 'MISSING_CONTENT_TYPE',
supported_types: this.supportedTypes,
received_type: undefined
};
res.status(415).json(response);
return;
}
// 4. 验证Content-Type是否支持
const normalizedContentType = this.normalizeContentType(contentType);
if (!this.isSupportedContentType(normalizedContentType)) {
this.logger.warn('不支持的Content-Type', {
operation: 'content_type_check',
method: req.method,
url: req.url,
contentType: contentType,
normalizedContentType: normalizedContentType,
userAgent: req.get('User-Agent'),
ip: req.ip,
timestamp: new Date().toISOString()
});
const response: UnsupportedMediaTypeResponse = {
success: false,
message: `不支持的Content-Type: ${contentType}请使用application/json`,
error_code: 'UNSUPPORTED_MEDIA_TYPE',
supported_types: this.supportedTypes,
received_type: contentType
};
res.status(415).json(response);
return;
}
// 5. Content-Type验证通过继续处理
next();
}
/**
* 检查是否需要验证Content-Type
*
* @param req HTTP请求对象
* @returns 是否需要验证
*/
private shouldCheckContentType(req: Request): boolean {
// 1. 检查HTTP方法
if (!this.methodsToCheck.includes(req.method)) {
return false;
}
// 2. 检查是否在排除路径中
const url = req.url;
for (const excludePattern of this.excludePaths) {
if (excludePattern.test(url)) {
return false;
}
}
// 3. 检查Content-Length如果为0则不需要验证
const contentLength = req.get('Content-Length');
if (contentLength === '0') {
return false;
}
return true;
}
/**
* 标准化Content-Type
*
* @param contentType 原始Content-Type
* @returns 标准化后的Content-Type
*/
private normalizeContentType(contentType: string): string {
// 移除空格并转换为小写
return contentType.toLowerCase().trim();
}
/**
* 检查Content-Type是否支持
*
* @param contentType 标准化的Content-Type
* @returns 是否支持
*/
private isSupportedContentType(contentType: string): boolean {
// 检查是否以支持的类型开头
return this.supportedTypes.some(supportedType =>
contentType.startsWith(supportedType.toLowerCase())
);
}
/**
* 获取支持的Content-Type列表
*
* @returns 支持的类型列表
*/
getSupportedTypes(): string[] {
return [...this.supportedTypes];
}
/**
* 添加支持的Content-Type
*
* @param contentType 要添加的Content-Type
*/
addSupportedType(contentType: string): void {
if (!this.supportedTypes.includes(contentType)) {
this.supportedTypes.push(contentType);
}
}
/**
* 添加排除路径
*
* @param pattern 路径正则表达式
*/
addExcludePath(pattern: RegExp): void {
this.excludePaths.push(pattern);
}
}

View File

@@ -1,137 +0,0 @@
/**
* 维护模式中间件
*
* 功能描述:
* - 检查系统是否处于维护模式
* - 在维护期间阻止用户访问API
* - 提供维护状态和预计恢复时间信息
*
* 使用场景:
* - 系统升级维护
* - 数据库迁移
* - 紧急故障修复
* - 定期维护窗口
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { ConfigService } from '@nestjs/config';
import { Logger } from '@nestjs/common';
/**
* 维护模式响应接口
*/
interface MaintenanceResponse {
/** 请求是否成功 */
success: boolean;
/** 响应消息 */
message: string;
/** 错误代码 */
error_code: string;
/** 维护信息 */
maintenance_info?: {
/** 维护开始时间 */
start_time: string;
/** 预计结束时间 */
estimated_end_time?: string;
/** 重试间隔(秒) */
retry_after: number;
/** 维护原因 */
reason?: string;
};
}
@Injectable()
export class MaintenanceMiddleware implements NestMiddleware {
private readonly logger = new Logger(MaintenanceMiddleware.name);
constructor(private readonly configService: ConfigService) {}
/**
* 中间件处理函数
*
* 业务逻辑:
* 1. 检查维护模式环境变量
* 2. 如果处于维护模式返回503状态码
* 3. 提供维护信息和重试建议
* 4. 记录维护期间的访问尝试
*
* @param req HTTP请求对象
* @param res HTTP响应对象
* @param next 下一个中间件函数
*/
use(req: Request, res: Response, next: NextFunction) {
// 1. 检查维护模式状态
const isMaintenanceMode = this.configService.get<string>('MAINTENANCE_MODE') === 'true';
if (!isMaintenanceMode) {
// 非维护模式,继续处理请求
next();
return;
}
// 2. 记录维护期间的访问尝试
this.logger.warn('维护模式:拒绝访问请求', {
operation: 'maintenance_check',
method: req.method,
url: req.url,
userAgent: req.get('User-Agent'),
ip: req.ip,
timestamp: new Date().toISOString()
});
// 3. 获取维护配置信息
const maintenanceStartTime = this.configService.get<string>('MAINTENANCE_START_TIME') || new Date().toISOString();
const maintenanceEndTime = this.configService.get<string>('MAINTENANCE_END_TIME');
const maintenanceReason = this.configService.get<string>('MAINTENANCE_REASON') || '系统维护升级';
const retryAfter = this.configService.get<number>('MAINTENANCE_RETRY_AFTER') || 1800; // 默认30分钟
// 4. 构建维护模式响应
const maintenanceResponse: MaintenanceResponse = {
success: false,
message: '系统正在维护中,请稍后再试',
error_code: 'SERVICE_UNAVAILABLE',
maintenance_info: {
start_time: maintenanceStartTime,
estimated_end_time: maintenanceEndTime,
retry_after: retryAfter,
reason: maintenanceReason
}
};
// 5. 设置HTTP响应头
res.setHeader('Retry-After', retryAfter.toString());
res.setHeader('Content-Type', 'application/json');
// 6. 返回503服务不可用状态
res.status(503).json(maintenanceResponse);
}
/**
* 检查维护模式是否启用
*
* @returns 是否处于维护模式
*/
isMaintenanceEnabled(): boolean {
return this.configService.get<string>('MAINTENANCE_MODE') === 'true';
}
/**
* 获取维护信息
*
* @returns 维护配置信息
*/
getMaintenanceInfo() {
return {
enabled: this.isMaintenanceEnabled(),
startTime: this.configService.get<string>('MAINTENANCE_START_TIME'),
endTime: this.configService.get<string>('MAINTENANCE_END_TIME'),
reason: this.configService.get<string>('MAINTENANCE_REASON'),
retryAfter: this.configService.get<number>('MAINTENANCE_RETRY_AFTER')
};
}
}

View File

@@ -1,37 +0,0 @@
/**
* 安全功能模块
*
* 功能描述:
* - 整合所有安全相关功能
* - 频率限制和请求超时控制
* - 维护模式和内容类型验证
* - 系统安全防护机制
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-12-24
*/
import { Module } from '@nestjs/common';
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { ThrottleGuard } from './guards/throttle.guard';
import { TimeoutInterceptor } from './interceptors/timeout.interceptor';
@Module({
providers: [
ThrottleGuard,
TimeoutInterceptor,
// 全局频率限制守卫
{
provide: APP_GUARD,
useClass: ThrottleGuard,
},
// 全局超时拦截器
{
provide: APP_INTERCEPTOR,
useClass: TimeoutInterceptor,
},
],
exports: [ThrottleGuard, TimeoutInterceptor],
})
export class SecurityModule {}

View File

@@ -20,8 +20,8 @@ import { Body, Controller, Get, HttpCode, HttpStatus, Param, Put, Post, UseGuard
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { AdminGuard } from '../../admin/guards/admin.guard';
import { UserManagementService } from '../services/user-management.service';
import { Throttle, ThrottlePresets } from '../../security/decorators/throttle.decorator';
import { Timeout, TimeoutPresets } from '../../security/decorators/timeout.decorator';
import { Throttle, ThrottlePresets } from '../../../core/security_core/decorators/throttle.decorator';
import { Timeout, TimeoutPresets } from '../../../core/security_core/decorators/timeout.decorator';
import { UserStatusDto, BatchUserStatusDto } from '../dto/user-status.dto';
import { UserStatusResponseDto, BatchUserStatusResponseDto, UserStatusStatsResponseDto } from '../dto/user-status-response.dto';