NestJs 集成Redis

前言

在日常开发中,需要处理一些高并发的请求或者处理用户邮件认证等一些场景。那么就需要 Redis 了。

Redis 是一个开源的内存中数据结构存储系统,它可以用作数据库、缓存和消息中间件。

安装包

按照需要的包

1
pnpm install ioredis @nestjs-modules/ioredis @nestjs/common

这里我们使用 ​**ioredis**​,因为它是一个健壮的、功能全面的 Redis 客户端,与 **nest-modules**相关联。

编写 Redis 服务模块

新建一个文件夹 Redis,然后里面新建 redis.module.ts 文件,写入如下内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Module } from "@nestjs/common";
import { RedisModule } from "@nestjs-modules/ioredis";
@Module({
imports: [
RedisModule.forRoot({
type: "single",
url: "127.0.0.1",
options: {
port: 6379,
password: "password",
},
}),
],
})
export class RedisCacheModule {}

之后在 app.module.ts 中导入

1
2
3
4
5
import { RedisCacheModule } from "./redis/redis.module";
Module({
imports: [RedisCacheModule],
});
export class AppModule {}

使用案例

在需要使用到地方,例如我需要在 users.service.ts 中的接口使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Injectable } from '@nestjs/common';
import { InjectRedis } from '@nestjs-modules/ioredis';
import Redis from 'ioredis';
@Injectable()
export class UsersService {
constructor(
@InjectRedis() private readonly redis: Redis
) {
async setUserPeerId(): Promise<string> {
// 使用 Redis 设置值
await this.redis.set('hello', 'Hello from Redis!');
// 使用 Redis 获取值
return this.redis.get('hello');
}
}
}

然后在对应的 controller 层调用函数。

扩展:接口访问限制频率

为了防止有人故意刷接口,或者对过多的请求搞奔溃应用,这里使用 Redis 做接口的限制

定义限流拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// api-rate-limiter.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
HttpException,
HttpStatus,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { InjectRedis, Redis } from "@nestjs-modules/ioredis";
import { tap } from "rxjs/operators";

@Injectable()
export class ApiRateLimiterInterceptor implements NestInterceptor {
constructor(@InjectRedis() private readonly redis: Redis) {}

async intercept(
context: ExecutionContext,
next: CallHandler
): Promise<Observable<any>> {
const key = "rate-limit:" + context.switchToHttp().getRequest().ip;
const currentRequestCount = await this.redis.incr(key);

if (currentRequestCount === 1) {
// 设置 key 的超时时间
await this.redis.expire(key, 60); // 限流周期为 60 秒
}

if (currentRequestCount > 10) {
throw new HttpException(
"Too many requests",
HttpStatus.TOO_MANY_REQUESTS
);
}

return next.handle().pipe(
tap(() => {
// 在响应完成后,你可以在这里执行一些操作。
})
);
}
}

项目中使用限流拦截器

将这个拦截器中间件引入到你的应用中,可以在对应的控制器或全局应用中注册。

  1. 全局拦截

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import { ApiRateLimiterInterceptor } from "./redis/api-rate-limiter.interceptor";
    import { APP_INTERCEPTOR } from "@nestjs/core";
    Module({
    providers: [
    {
    provide: APP_INTERCEPTOR,
    useClass: ApiRateLimiterInterceptor,
    },
    ],
    });
    export class AppModule {}

    // - 拦截器(Interceptor):用于在方法执行之前或之后添加额外逻辑,它们可以操作由函数返回的结果或异常。
    // - 中间件(Middleware):通常用于执行请求前的一些操作,比如日志、请求解析等,是直接实现use方法的类。
    // 一些博客中将拦截器用中间件来引入。这是不合适的
  2. 局部拦截

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // user.controller.ts
    import { Controller, UseInterceptors } from "@nestjs/common";
    import { ApiRateLimiterInterceptor } from "./api-rate-limiter.interceptor";

    @Controller("user")
    @UseInterceptors(ApiRateLimiterInterceptor) // 应用到UserController中的所有路由
    export class UserController {
    // ...
    }

    // 或者

    @Controller("user")
    export class UserController {
    @UseInterceptors(ApiRateLimiterInterceptor) // 仅应用到特定的路由处理程序
    @Get("some-path")
    async someMethod() {
    // ...
    }
    // ...
    }

    我这里为了统一方便就引入在全局上