Rain's Blog

使用nestjs封装redis动态模块

Rain, Wed Jul 3 2024back

将 ioredis 封装为 NestJS 专用的 nest-redis 模块

在开发 NestJS 应用时,我们通常会使用 ioredis 作为 Redis 客户端。为了更好地集成 ioredis,我们可以将其封装为一个 NestJS 模块,称为 nest-redis 模块。 该模块可以支持同步和异步两种配置方式,分别通过 forRoot 和 forRootAsync 方法实现。

模块实现步骤

1. 定义 RedisModule 类:

使用 @Global 和 @Module 装饰器标识 RedisModule 类。该类包含两个方法:forRoot 和 forRootAsync,用于接收配置参数并返回相应的 DynamicModule 实例

2. 拓展ioredis默认参数

forRoot 方法接收 RedisModuleOptions 参数,返回类型为 DynamicModule。该参数可以扩展 ioredis 的默认参数。

import { ModuleMetadata } from '@nestjs/common';
import { RedisOptions } from 'ioredis';

export interface RedisModuleOptions extends RedisOptions {}
export interface RedisModuleAsyncOptions
  extends Pick<ModuleMetadata, 'imports'> {
  useFactory?: (
    ...args: any[]
  ) => Promise<RedisModuleOptions> | RedisModuleOptions;
  inject?: any[];
}

3. forRoot 方法:

查看DynamicModule得知需要返回一个对象,必须有属性module,值为模块参考。返回一个Provider类型的对象,属性provide为自定义标识符,useValue为实例提供者

@Global()
@Module()
export class RedisModule {
  static forRoot(options: RedisModuleOptions): DynamicModule {
    const redisProvider: Provider = {
      provide: REDIS_CLIENTS,
      useValue: new Redis(options),
    };
    return {
      module: RedisModule,
      providers: [redisProvider],
      exports: [redisProvider],
    };
  }
}

4. forRootAsync 方法:

forRootAsync 方法使用 useFactory 模式,异步返回一个 Redis 实例配置。

  static forRootAsync(options: RedisModuleAsyncOptions): DynamicModule {
    const { imports, inject, useFactory } = options;
    const redisProvider: Provider = {
      provide: REDIS_CLIENTS,
      useFactory: async (...args): Promise<Redis> => {
        const redisOptions: RedisOptions = await useFactory(...args);
        const redisClient = await new Redis(redisOptions);
        return redisClient;
      },
      inject,
    };
    return {
      module: RedisModule,
      imports,
      providers: [redisProvider],
      exports: [redisProvider],
    };
  }

5. 定义redis注入装饰器

REDIS_CLIENTS 为自定义标识符,需要和上面定义provide属性的是同一个值

import { Inject } from '@nestjs/common';
import { REDIS_CLIENTS } from './redis.contanst';

export const InjectRedis = () => {
  return Inject(REDIS_CLIENTS);
};

6. 二次封装ioredis的方法

import { Inject, Injectable } from '@nestjs/common';
import { REDIS_CLIENTS } from './redis.contanst';
import Redis from 'ioredis';
@Injectable()
export class RedisService {
  private redisClient: Redis;
  constructor(@Inject(REDIS_CLIENTS) redis: Redis) {
    this.redisClient = redis;
  }

  async get(key: string) {
    return this.redisClient.get(key);
  }

  async set(key: string, value: number | string, ttl?: number) {
    await this.redisClient.set(key, value);
    if (ttl) {
      await this.redisClient.expire(key, ttl);
    }
  }
}

7. 使用示例

  • 在appModule 导入RedisModule模块
@Module({
  imports: [
    // 异步配置
     RedisModule.forRootAsync({
      useFactory: (config: ConfigService) => {
        return config.get('redis');
      },
      inject: [ConfigService],
    }),
    // 同步配置
    RedisModule.forRoot({
     port: 6379,
     host: 'localhost',
    }),
  ]
})
  • 在需要用到的service中实例化redisServer
import { Injectable } from '@nestjs/common';
import { Redis } from 'ioredis';
import { InjectRedis } from 'plugin/redis';
import { RedisService } from 'plugin/redis/redis.service';

@Injectable()
export class TestRedisService {
  constructor(
    @InjectRedis() private readonly redisClient: Redis,
    private readonly redisService: RedisService,
  ) {}

  async get1(key: string): Promise<string> {
    return await this.redisService.get(key);
  }

  async get2(key: string): Promise<string> {
    return await this.redisClient.get(key);
  }
}