47 lines
1.4 KiB
TypeScript
47 lines
1.4 KiB
TypeScript
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
|
|
import { RedisService } from '../../infrastructure/redis/redis.service';
|
|
|
|
@Injectable()
|
|
export class RateLimitService {
|
|
constructor(private readonly redis: RedisService) {}
|
|
|
|
async checkLimit(
|
|
key: string,
|
|
maxRequests: number,
|
|
windowSeconds: number,
|
|
): Promise<void> {
|
|
const count = await this.redis.incr(key);
|
|
if (count === 1) {
|
|
await this.redis.expire(key, windowSeconds);
|
|
}
|
|
if (count > maxRequests) {
|
|
throw new HttpException(
|
|
{
|
|
statusCode: HttpStatus.TOO_MANY_REQUESTS,
|
|
message: `请求过于频繁,请${windowSeconds}秒后再试`,
|
|
retryAfter: windowSeconds,
|
|
},
|
|
HttpStatus.TOO_MANY_REQUESTS,
|
|
);
|
|
}
|
|
}
|
|
|
|
async loginLimit(ip: string): Promise<void> {
|
|
const today = new Date().toISOString().split('T')[0];
|
|
await this.checkLimit(`rate:ip:${ip}:login:${today}`, 20, 1800);
|
|
}
|
|
|
|
async feedbackLimit(ip: string): Promise<void> {
|
|
await this.checkLimit(`rate:ip:${ip}:feedback:hourly`, 5, 3600);
|
|
}
|
|
|
|
async aiAnalysisLimit(userId: string): Promise<void> {
|
|
const today = new Date().toISOString().split('T')[0];
|
|
await this.checkLimit(`rate:user:${userId}:ai:daily:${today}`, 50, 86400);
|
|
}
|
|
|
|
async fileUploadLimit(userId: string): Promise<void> {
|
|
await this.checkLimit(`rate:user:${userId}:upload:hourly`, 10, 3600);
|
|
}
|
|
}
|