feat: M0-05 Traffic Protection — Redis throttle + timeout
Some checks failed
Deploy API Server / build-and-deploy (push) Failing after 18s

This commit is contained in:
WangDL 2026-05-22 23:09:16 +08:00
parent b5a983dc6b
commit 28d68675b1
6 changed files with 87 additions and 0 deletions

View File

@ -16,6 +16,7 @@ import { AuthModule } from './modules/auth/auth.module';
import { AdminAuthModule } from './modules/admin-auth/admin-auth.module';
import { AdminDashboardModule } from './modules/admin-dashboard/admin-dashboard.module';
import { AdminUsersModule } from './modules/admin-users/admin-users.module';
import { AdminThrottleModule } from './modules/admin-throttle/admin-throttle.module';
import { AppConfigModule } from './modules/config/config.module';
import { AdminEventsModule } from './modules/admin-events/admin-events.module';
import { AdminKnowledgeModule } from './modules/admin-knowledge/admin-knowledge.module';
@ -50,6 +51,8 @@ import { GlobalExceptionFilter } from './common/filters/global-exception.filter'
import { StrictValidationPipe } from './common/pipes/strict-validation.pipe';
import { ResponseInterceptor } from './common/interceptors/response.interceptor';
import { TraceIdInterceptor } from './common/interceptors/trace-id.interceptor';
import { TimeoutInterceptor } from './common/interceptors/timeout.interceptor';
import { AppThrottleModule } from './common/throttle/throttle.module';
import { AiAnalysisWorker } from './workers/ai-analysis.worker';
import { DocumentImportWorker } from './workers/document-import.worker';
@ -88,6 +91,7 @@ import appleConfig from './config/apple.config';
}),
PrismaModule,
RedisModule,
AppThrottleModule,
EventBusModule,
QueueModule,
AiModule,
@ -98,6 +102,7 @@ import appleConfig from './config/apple.config';
AdminAuthModule,
AdminDashboardModule,
AdminUsersModule,
AdminThrottleModule,
AppConfigModule,
AdminEventsModule,
AdminKnowledgeModule,
@ -132,6 +137,7 @@ import appleConfig from './config/apple.config';
{ provide: APP_FILTER, useClass: GlobalExceptionFilter },
{ provide: APP_PIPE, useClass: StrictValidationPipe },
{ provide: APP_INTERCEPTOR, useClass: TraceIdInterceptor },
{ provide: APP_INTERCEPTOR, useClass: TimeoutInterceptor },
{ provide: APP_INTERCEPTOR, useClass: ResponseInterceptor },
AiAnalysisWorker,
DocumentImportWorker,

View File

@ -0,0 +1,10 @@
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(_context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(timeout(30000), catchError(err => err instanceof TimeoutError ? throwError(() => new RequestTimeoutException('Request timeout')) : throwError(() => err)));
}
}

View File

@ -0,0 +1,13 @@
import { Injectable } from '@nestjs/common';
import { ThrottlerStorage } from '@nestjs/throttler';
import { RedisService } from '../../infrastructure/redis/redis.service';
@Injectable()
export class RedisThrottlerStorage implements ThrottlerStorage {
constructor(private readonly redis: RedisService) {}
async increment(key: string, ttl: number) {
const redisKey = `throttle:${key}`;
try { const hits = await this.redis.incr(redisKey); await this.redis.expire(redisKey, Math.ceil(ttl / 1000)); return { totalHits: hits, timeToExpire: ttl }; }
catch { return { totalHits: 1, timeToExpire: ttl }; }
}
}

View File

@ -0,0 +1,24 @@
import { Global, Module } from '@nestjs/common';
import { ThrottlerModule } from '@nestjs/throttler';
import { RedisThrottlerStorage } from './redis-throttler.storage';
import { RedisService } from '../../infrastructure/redis/redis.service';
@Global()
@Module({
imports: [
ThrottlerModule.forRootAsync({
useFactory: (redis: RedisService) => ({
throttlers: [
{ name: 'global', ttl: 60000, limit: 100 },
{ name: 'ai', ttl: 60000, limit: 20 },
{ name: 'login', ttl: 60000, limit: 5 },
{ name: 'upload', ttl: 60000, limit: 10 },
],
storage: new RedisThrottlerStorage(redis),
}),
inject: [RedisService],
}),
],
exports: [ThrottlerModule],
})
export class AppThrottleModule {}

View File

@ -0,0 +1,28 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
import { AdminRoles } from '../../common/decorators/admin-roles.decorator';
import type { AdminRole } from '../../common/types/admin-role.enum';
@ApiTags('admin-throttle')
@Controller('admin-api/throttle')
@UseGuards(AdminAuthGuard, AdminRolesGuard)
@ApiBearerAuth()
export class AdminThrottleController {
@Get('status')
@AdminRoles('SUPER_ADMIN' as AdminRole)
@ApiOperation({ summary: '限流规则状态' })
async status() {
return {
rules: [
{ name: 'global', ttl: '60s', limit: 100, desc: '全局 API' },
{ name: 'ai', ttl: '60s', limit: 20, desc: 'AI 接口' },
{ name: 'login', ttl: '60s', limit: 5, desc: '登录接口' },
{ name: 'upload', ttl: '60s', limit: 10, desc: '文件上传' },
],
storage: 'Redis',
enabled: true,
};
}
}

View File

@ -0,0 +1,6 @@
import { Module } from '@nestjs/common';
import { AdminThrottleController } from './admin-throttle.controller';
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
@Module({ controllers: [AdminThrottleController], providers: [AdminAuthGuard, AdminRolesGuard] })
export class AdminThrottleModule {}