在 NestJs 中封装一个通用的 redis 服务,之后可以直接复制这套服务了

发布于:2024-04-28 ⋅ 阅读:(17) ⋅ 点赞:(0)

文章开始之前分享两个开源项目,会一直维护的,欢迎 star,如果你感兴趣或者想参与学习,可以加我微信 yunmz777,最近也在找工作 ing,欢迎内推......

浪费你两秒钟时间,我们正文开始!!!

Redis 是一个开源的、高性能的键值数据库,通常用作数据结构服务器。它支持多种类型的数据结构,如字符串(strings)、列表(lists)、集合(sets)、有序集合(sorted sets)、哈希表(hashes)、位图(bitmaps)、超日志(hyperloglogs)和地理空间(geospatial)索引。Redis 的主要特点是它的数据都保存在内存中,从而提供极高的读写速度,同时它也支持将数据异步写入磁盘以进行持久化。

它的主要使用场景有以下几个方面:

  1. 缓存系统:利用其高速的数据读写能力,常用作数据库缓存,减少数据库负载,提高系统响应速度。

  2. 会话存储(Session Store):存储用户会话信息,由于其速度快,适合此类需求。

  3. 消息队列系统:利用其发布/订阅系统实现消息队列,支持实时消息系统。

  4. 实时计数器:如计数网站点击量或在社交媒体上跟踪帖子的点赞数。

  5. 排行榜/计分板:利用 Redis 的有序集合,非常适合需要排名的功能,如游戏排行榜。

在接下来的内容中我们将使用 NestJs 对其进行封装。

使用 docker-compose 创建一个 redis 服务

接下来我们将创建一个简单的 redis 服务,在 docker-compose.yml 文件中编写如下代码:

version: "3.9"

services:
  redis:
    image: redis
    ports:
      - "6379:6379"
    environment:
      - REDIS_PASSWORD=moment
    command: redis-server --requirepass moment
    volumes:
      - redis-data:/data
    restart: always

volumes:
  redis-data:

在上面的配置中,通过 Redis 服务的命令行参数 --requirepass 设置密码。通过定义一个 Docker 卷 redis_data,并将其挂载到容器的 /data 目录,从而使 Redis 数据在容器重启后仍然保持。

使用 docker-compose up -d 命令之后我们就可以连接了:

20240428222044

设置环境变量

这个时候我们的 redis 服务已经创建完成了,我们需要在 env 文件下新增一些环境变量,如下所示:

REDIS_HOST = localhost
REDIS_PORT = 6379
REDIS_PASSWORD = moment
REDIS_DB = 0

创建完成之后我们要创建一个枚举变量:

export enum RedisConfigEnum {
  REDIS_HOST = "REDIS_HOST",
  REDIS_PORT = "REDIS_PORT",
  REDIS_PASSWORD = "REDIS_PASSWORD",
  REDIS_DB = "REDIS_DB",
}

我们新建一个配置文件,编写一个函数并返回一个 redis 的配置对象:

import { ConfigService } from "@nestjs/config";

import { RedisConfigEnum } from "../common/enum/config.enum";

export default (configService: ConfigService) => ({
  port: parseInt(configService.get(RedisConfigEnum.REDIS_PORT)),
  host: configService.get(RedisConfigEnum.REDIS_HOST),
  password: configService.get(RedisConfigEnum.REDIS_PASSWORD),
  db: configService.get(RedisConfigEnum.REDIS_DB),
});

这个时候我们就可以编写我们的 redis 模块了,这个时候我们只需要这两个文件:

20240428222446

redis 模块配置

首先我们编写一下 redis.module.ts 文件,如下代码所示:

import { Global, Module } from "@nestjs/common";
import Redis from "ioredis";
import { ConfigService } from "@nestjs/config";

import loadRedisConfig from "../../config/redis.config";

import { RedisService } from "./redis.service";

@Global()
@Module({
  providers: [
    {
      provide: "REDIS_CLIENT",
      async useFactory(configService: ConfigService) {
        const redisInstance = new Redis({
          ...loadRedisConfig(configService),
        });
        return redisInstance;
      },
      inject: [ConfigService],
    },
    RedisService,
  ],
  exports: [RedisService],
})
export class RedisModule {}

要想使用,我们首先需要安装 ioredis 这个模块:

pnpm add ioredis

在上面的代码中,我们使用 RedisModule 为整个应用提供了集中式的 Redis 客户端管理,便于在任何需要的地方通过依赖注入使用 Redis。这种模式提高了代码的模块化和可重用性,同时也简化了配置管理和维护的复杂性。

redis 服务

在前面我们已经为 redis 模块中,在 providers 中包含 RedisService 以确保可以在模块中使用。

那么在我们的服务中就可以使用 @Inject('REDIS_CLIENT') 将 Redis 客户端实例注入到服务中。这样,服务内的各个方法就可以使用这个 Redis 客户端与 Redis 数据库进行交互

并且定义了一些方法来对 redis 进行封装:

  1. onModuleDestroy():在模块被销毁时调用,用于断开与 Redis 的连接。这是 OnModuleDestroy 生命周期钩子的实现,确保应用停止时清理资源。

  2. set():

    • 重载方法,用于设置键值对。如果提供了 second 参数,键将在指定的秒数后过期。

    • 使用 isObject() 检查值是否为对象,如果是,则将对象转换为 JSON 字符串存储;否则直接存储值。

    • 如果没有提供过期时间,则只调用 set(key, value);如果提供了过期时间,则使用 set(key, value, 'EX', second),其中 'EX' 指定过期时间单位为秒。

  3. incr():对指定键执行自增操作,并返回新的值。这适用于计数器等功能。

  4. get():

    • 获取指定键的值。尝试对获取的值进行 JSON 解析,如果解析成功则返回解析后的对象,否则直接返回字符串。

    • 如果键不存在或在获取过程中发生错误,则返回 null。

  5. del():删除指定的键,并返回删除的键的数量。

  6. hset():向哈希表中添加字段,field 参数应为一个对象,其中包含要设置的键值对。返回被成功设置的字段数量。

  7. hget():从哈希表中获取指定字段的值。

  8. hgetall():获取哈希表中所有的字段和值。

  9. lushall():清空 Redis 数据库中的所有数据。

通过上面的内容我们就已经对 redis 进行了一个比较完整的封装了,那么接下来我们将对 redis 进行一个简单的测试。

使用案例

首先我们需要在使用的模块中导入或者你也可以将其注册为全局模块,目前我们在 moment 模块中使用:

20240428223755

之后我们在 moment.service.ts 中编写如下代码:

import { Injectable } from "@nestjs/common";

import { RedisService } from "@/common/redis/redis.service";

@Injectable()
export class MomentService {
  constructor(private readonly redisService: RedisService) {}

  async create() {
    const data = await this.redisService.set("moment", 777);

    return data;
  }
}

在 moment.controller 中编写如下代码:

import { Controller, Get } from "@nestjs/common";

import { MomentService } from "./moment.service";

@Controller("moment")
export class MomentController {
  constructor(private readonly momentService: MomentService) {}

  @Get()
  create() {
    return this.momentService.create();
  }
}

这个时候我们就可以测试一下我们封装的 redis 服务是否生效了,我们访问 moment 接口 :

20240428224136

打开数据库,我们可以看到我们的 redis 服务是封装成功了的:

20240428224237

总结

我们通过封装 Redis 操作到一个服务中可以集中管理所有与 Redis 交互的逻辑。这样做使得维护和更新变得更加容易,因为所有的 Redis 相关代码都位于一个位置,而不是散布在整个应用的多个部分。

并且通过服务封装,你可以在多个组件之间复用 Redis 操作代码,避免重复代码。例如,不同的控制器或服务可能需要访问相同的数据,通过依赖注入 RedisService,它们可以轻松地复用已经定义好的方法,如 get, set, incr 等。