nestjs使用redis實(shí)現(xiàn)ip限流的步驟詳解
導(dǎo)讀
如果使用nestjs開(kāi)發(fā)接口并部署之后,我們通常需要考慮到接口是否會(huì)被惡意盜刷消耗過(guò)多的資源,一個(gè)簡(jiǎn)單的方式就是限制在單位時(shí)間內(nèi)的訪問(wèn)次數(shù)。
本文使用的庫(kù)包版本如下:
| 庫(kù)名 | 版本號(hào) |
|---|---|
| @nestjs/core | 10.0.0 |
| @nestjs/common | 10.0.0 |
| @nestjs/schedule | 4.1.2 |
| ioredis | 5.4.2 |
本文的主要工作環(huán)境基于Macbook Pro M1 MacOS 14.6.1。
新建nestjs 項(xiàng)目
nest new nestjs-with-ip-limit -g
nestjs中的守衛(wèi)Guard
nestjs 提供了一種可以是否攔截請(qǐng)求的方式,守衛(wèi)(Guard),我們可以通過(guò)實(shí)現(xiàn)CanActive接口來(lái)完成,詳細(xì)解釋參考官方鏈接。
自定義的一個(gè)ip.guard.ts文件,用于最終實(shí)現(xiàn)我們的ip請(qǐng)求攔截。
//ip.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
?
@Injectable()
export class IpGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
console.log(request.headers['origin'], request.headers);
return request.headers['text'] != 'zoo' ? false : true;
}
}
在示例中,我們?cè)黾赢?dāng)請(qǐng)求頭沒(méi)有text=zoo就攔截的邏輯,并直接在瀏覽器控制臺(tái)中使用fetch測(cè)試:
fetch('http://localhost:3000', {
headers: {
text: 'zoo',
},
})
.then((resp) => resp.text())
.then(console.log)
.catch(console.error);

可以看到,一旦守衛(wèi)中返回了false,請(qǐng)求將報(bào)403請(qǐng)求錯(cuò)誤。
Guard中獲取IP
現(xiàn)在的問(wèn)題就是如何在實(shí)現(xiàn)的IpGuard中獲取ip地址,可以通過(guò)context.switchToHttp().getRequest()獲取請(qǐng)求對(duì)象來(lái)提取。
const request = context.switchToHttp().getRequest(); const ip = request.headers['x-forwarded-for'] || request.headers['x-real-ip'] || request.socket.remoteAddress || request.ip;
x-forwarded-for和x-real-ip的依據(jù)主要是我們很多網(wǎng)站可能使用代理的方式運(yùn)行,尤其是nginx代理,如下所示。
location ^~ /api {
rewrite ^/api(.*) $1 break; # 重寫(xiě)規(guī)則,將/api之后的路徑提取出來(lái)并去掉/api前綴
proxy_pass http://127.0.0.1:6689;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; // 設(shè)置 X-Real-IP 頭為客戶(hù)端的真實(shí) IP 地址。這對(duì)于后端服務(wù)識(shí)別客戶(hù)端 IP 地址非常重要,特別是在請(qǐng)求經(jīng)過(guò)多個(gè)代理的情況下
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; // 設(shè)置 X-Forwarded-For 頭為通過(guò) proxy_add_x_forwarded_for 指令添加的信息。此頭通常用于跟蹤客戶(hù)端 IP 地址以及任何之前的代理 IP 地址
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache;
proxy_ssl_server_name off;
}
ip存儲(chǔ)
提取到ip地址后我們需要將其和請(qǐng)求數(shù)保存,并同時(shí)記錄訪問(wèn)數(shù)(每次增加1),且在某段時(shí)間后清除,為此,我們需要引入redis。
npm i ioreds -s
為了后續(xù)更方便的使用,把redis封裝為一個(gè)自建的module
nest g module redis --no-spec
新建src/redis/redis.service.ts
import { Injectable } from '@nestjs/common';
?
import Client, { type RedisOptions } from 'ioredis';
?
@Injectable()
export class RedisService extends Client {
constructor(options: RedisOptions) {
super(options);
}
}
在redis.module.ts中加入代碼
import { Module } from '@nestjs/common';
import { RedisOptions } from 'ioredis';
import { RedisService } from './redis.service';
@Module({})
export class RedisModule {
static forRoot(optionts: RedisOptions) {
return {
module: RedisModule,
providers: [
{
provide: 'REDIS_OPTIONS',
useValue: optionts,
},
{
provide: RedisService,
useFactory: (options: RedisOptions) => {
return new RedisService(options);
},
inject: ['REDIS_OPTIONS'],
},
],
exports: [RedisService],
};
}
}
在app.module.ts中使用
新建一個(gè)redis容器:

隨后改造ip.guard.ts文件
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { RedisService } from './redis/redis.service';
@Injectable()
export class IpGuard implements CanActivate {
constructor(private redisService: RedisService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const ip =
request.headers['x-forwarded-for'] ||
request.headers['x-real-ip'] ||
request.socket.remoteAddress ||
request.ip;
const redis_key = 'limit_ip_' + ip;
const data = await this.redisService.get(redis_key);
const count = data ? parseInt(data) : 0;
if (count >= 5) {
return false;
}
await this.redisService.set(
redis_key,
data ? parseInt(data) + 1 : 1,
'EX',
60,
);
return true;
}
}
每次接口訪問(wèn)時(shí),都會(huì)先從redis里讀取對(duì)應(yīng)ip的訪問(wèn)次數(shù),如果達(dá)到五次后,就返回false禁止接口應(yīng)答,否則通過(guò),并且該限制在一分鐘內(nèi)有效。
在瀏覽器請(qǐng)求http://localhost:3000,刷新四次后,顯示如下。::1是由于本地開(kāi)發(fā)的緣故,如果有服務(wù)器可以在服務(wù)器上啟動(dòng)服務(wù),本地測(cè)試。

部署到服務(wù)器后顯示:

補(bǔ)充
現(xiàn)在經(jīng)常使用的一些AI工具,其免費(fèi)計(jì)劃每天都只有很少的額度,其也可以基于redis實(shí)現(xiàn)限流,不過(guò)是根據(jù)用戶(hù)id來(lái)設(shè)置key值。除此之外,其每天到零點(diǎn)時(shí)還可以恢復(fù)額度。為此,可以在nestjs使用定時(shí)器在零點(diǎn)時(shí)刪除所有的redis的ke y。
安裝相關(guān)依賴(lài)
npm install @nestjs/schedule
注冊(cè)定時(shí)任務(wù)模塊
imports: [
RedisModule.forRoot({
host: 'localhost',
port: 6378,
db: 0,
}),
ScheduleModule.forRoot(),
],
在app.service.ts加入代碼
import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { RedisService } from './redis/redis.service';
@Injectable()
export class AppService {
constructor(private readonly redisService: RedisService) {}
getHello(): string {
return 'Hello World!';
}
@Cron(CronExpression.EVERY_DAY_AT_1AM)
async handleCron() {
console.log('Called when the current time is 1AM');
//刪除所有的redis keys: limit_ip_*
await this.redisService.del('limit_ip_*');
}
}
此外,也可以在定時(shí)任務(wù)中將相關(guān)的限流ip的計(jì)數(shù)同步到MySQL,讓相關(guān)邏輯更穩(wěn)檔一些。
以上就是nestjs使用redis實(shí)現(xiàn)ip限流的步驟詳解的詳細(xì)內(nèi)容,更多關(guān)于nestjs redis實(shí)現(xiàn)ip限流的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
redis-cli創(chuàng)建redis集群的實(shí)現(xiàn)
本文主要介紹了redis-cli創(chuàng)建redis集群的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06
如何使用注解方式實(shí)現(xiàn)?Redis?分布式鎖
這篇文章主要介紹了如何使用注解方式實(shí)現(xiàn)Redis分布式鎖,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,教大家如何優(yōu)雅的使用Redis分布式鎖,感興趣的小伙伴可以參考一下2022-07-07
Redis遍歷所有key的兩個(gè)命令(KEYS 和 SCAN)
這篇文章主要介紹了Redis遍歷所有key的兩個(gè)命令(KEYS 和 SCAN),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
Redis的Zset類(lèi)型及相關(guān)命令詳細(xì)講解
這篇文章主要介紹了Redis的Zset類(lèi)型及相關(guān)命令的相關(guān)資料,有序集合Zset是一種Redis數(shù)據(jù)結(jié)構(gòu),它類(lèi)似于集合Set,但每個(gè)元素都有一個(gè)關(guān)聯(lián)的分?jǐn)?shù)score,并且可以根據(jù)分?jǐn)?shù)對(duì)元素進(jìn)行排序,需要的朋友可以參考下2025-01-01
Redis服務(wù)器的啟動(dòng)過(guò)程分析
這篇文章主要介紹了Redis服務(wù)器的啟動(dòng)過(guò)程分析,本文講解了初始化Redis服務(wù)器全局配置、加載配置文件、初始化服務(wù)器、加載數(shù)據(jù)、開(kāi)始網(wǎng)絡(luò)監(jiān)聽(tīng)等內(nèi)容,需要的朋友可以參考下2015-04-04
Redis中Bloom filter布隆過(guò)濾器的學(xué)習(xí)
布隆過(guò)濾器是一個(gè)非常長(zhǎng)的二進(jìn)制向量和一系列隨機(jī)哈希函數(shù)的組合,可用于檢索一個(gè)元素是否存在,本文就詳細(xì)的介紹一下Bloom filter布隆過(guò)濾器,具有一定的參考價(jià)值,感興趣的可以了解一下2022-12-12

