五、NestJS 开发实战文档-->redis

NestJS 集成 Redis 完整优化实现(验证码场景实战)

一、核心概述

本文基于 @nestjs-modules/ioredis 模块实现 NestJS 与 Redis 的无缝集成,以验证码生成、存储、验证为实战场景,提供标准化、可扩展的配置方案,同时优化了全局模块、环境变量配置、类型安全等核心细节,支持生产环境直接复用。

二、前期准备

2.1 安装核心依赖

bash 复制代码
# Redis 核心依赖(@nestjs-modules/ioredis 基于 ioredis 封装,无需额外安装 ioredis)
npm install @nestjs-modules/ioredis
# 验证码生成依赖(本文实战场景使用)
npm install svg-captcha
# 类型提示(TypeScript 项目推荐)
npm install @types/svg-captcha --save-dev

2.2 启动 Redis 服务

  • 本地环境:直接启动 Redis 服务(默认端口 6379)

    bash 复制代码
    # Linux/Mac
    redis-server
    # Windows(需先配置 Redis 环境变量)
    redis-server.exe
  • 生产环境:配置 Redis 服务地址、密码、端口后启动(后续配置中支持对应参数)

三、Redis 模块配置(全局化 + 环境变量优化)

3.1 环境变量配置(.env 文件)

在项目根目录创建 .env 文件,配置 Redis 连接信息,避免硬编码:

env 复制代码
# Redis 配置
REDIS_URL=redis://localhost:6379 # 无密码连接格式
# 有密码格式:redis://:your_redis_password@localhost:6379/0(0 为数据库编号)
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD= # 无密码留空
REDIS_DB=0 #  Redis 数据库编号(默认 0)
REDIS_TTL=60 #  默认过期时间(秒,验证码场景推荐 60-300 秒)

3.2 全局 Redis 模块配置(推荐)

创建独立的 Redis 配置模块(src/db/redis/redis.module.ts),标记为全局模块,无需在各业务模块重复导入:

typescript 复制代码
import { Global, Module } from '@nestjs/common';
import { RedisModule } from '@nestjs-modules/ioredis';
import { ConfigService } from '@nestjs/config';

@Global() // 全局模块,所有业务模块可直接注入使用
@Module({
  imports: [
    // 异步配置:读取环境变量,支持生产环境动态切换配置
    RedisModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        config: {
          // 优先使用 URL 配置,简洁高效;也可单独配置 host/port/password
          url: configService.get<string>('REDIS_URL'),
          // 单独配置(备选方案,与 url 二选一即可)
          // host: configService.get<string>('REDIS_HOST'),
          // port: configService.get<number>('REDIS_PORT'),
          // password: configService.get<string>('REDIS_PASSWORD'),
          // db: configService.get<number>('REDIS_DB'),
          retryStrategy: (times: number) => {
            // 重连策略:失败后重试,避免单次连接失败导致服务异常
            const delay = Math.min(times * 100, 3000);
            return delay;
          },
        },
      }),
    }),
  ],
  // 无需额外导出,全局模块的 RedisService 可直接注入
  exports: [RedisModule],
})
export class GlobalRedisModule {}

3.3 根模块注册(app.module.ts)

在项目根模块中导入全局 Redis 模块,使其生效:

typescript 复制代码
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { GlobalRedisModule } from './db/redis/redis.module';
import { UserModule } from './service/user/user.module';

@Module({
  imports: [
    // 全局环境变量配置
    ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env' }),
    GlobalRedisModule, // 导入全局 Redis 模块
    UserModule, // 业务模块(无需再导入 Redis 模块)
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

四、业务实现(验证码生成 + Redis 存储/验证)

4.1 用户服务(user.service.ts)

注入 RedisService,封装验证码生成、Redis 存储、Redis 读取核心逻辑,保证业务逻辑与数据操作分离:

typescript 复制代码
import { Injectable } from '@nestjs/common';
import { RedisService } from '@nestjs-modules/ioredis';
import * as svgCaptcha from 'svg-captcha';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class UserService {
  // 注入 Redis 服务和配置服务
  constructor(
    private readonly redisService: RedisService,
    private readonly configService: ConfigService,
  ) {}

  /**
   * 生成 SVG 验证码
   * @returns 验证码文本 + SVG 图片数据
   */
  generateCaptcha() {
    const captcha = svgCaptcha.create({
      size: 4, // 验证码长度(4位数字/字母)
      ignoreChars: '0o1iIl', // 忽略易混淆字符,提升用户体验
      noise: 2, // 干扰线条数,增强安全性
      color: true, // 开启字符彩色显示
      background: '#f5f5f5', // 背景色,柔和不刺眼
      width: 120, // 验证码图片宽度
      height: 40, // 验证码图片高度
    });

    return {
      text: captcha.text, // 验证码文本(用于验证)
      data: captcha.data, // SVG 图片数据(用于返回给前端)
    };
  }

  /**
   * 存储验证码到 Redis
   * @param key 存储键(推荐使用 IP + 唯一标识,避免同一 IP 重复覆盖)
   * @param captchaText 验证码文本
   * @param ttl 过期时间(秒,默认读取环境变量配置)
   */
  async storeCaptcha(key: string, captchaText: string, ttl?: number) {
    const expireTime = ttl || this.configService.get<number>('REDIS_TTL');
    // 存储到 Redis:key - 验证码文本,EX - 过期时间单位(秒)
    await this.redisService.set(key, captchaText.toLowerCase(), 'EX', expireTime);
  }

  /**
   * 从 Redis 获取验证码
   * @param key 存储键
   * @returns 验证码文本(不存在返回 null)
   */
  async getCaptcha(key: string): Promise<string | null> {
    return await this.redisService.get(key);
  }

  /**
   * 验证验证码有效性
   * @param key 存储键
   * @param inputCaptcha 用户输入的验证码
   * @returns 验证结果
   */
  async validateCaptcha(key: string, inputCaptcha: string): Promise<boolean> {
    if (!inputCaptcha) return false;

    // 获取 Redis 中存储的验证码
    const storedCaptcha = await this.getCaptcha(key);
    if (!storedCaptcha) return false;

    // 忽略大小写对比(提升用户体验)
    return inputCaptcha.toLowerCase() === storedCaptcha;
  }
}

4.2 用户控制器(user.controller.ts)

实现验证码获取接口和验证接口,处理 HTTP 请求与响应,复用 UserService 中的逻辑:

typescript 复制代码
import { Controller, Get, Post, Body, Res, Ip, HttpCode, HttpStatus } from '@nestjs/common';
import { UserService } from './user.service';
import { Response } from 'express';
import { v4 as uuidv4 } from 'uuid'; // 可选:生成唯一标识,避免同一 IP 重复覆盖

@Controller({
  path: 'user',
  version: '1', // 接口版本 v1
})
export class UserController {
  constructor(private readonly userService: UserService) {}

  /**
   * 获取 SVG 验证码图片
   * @param res Express 响应对象
   * @param ip 客户端 IP 地址
   * @returns SVG 图片
   */
  @Get('get/code')
  async getCaptchaImage(@Res() res: Response, @Ip() ip: string) {
    // 1. 生成验证码
    const captcha = this.userService.generateCaptcha();

    // 2. 构建 Redis 存储键(IP + UUID,避免同一 IP 多次请求覆盖验证码)
    const redisKey = `captcha:${ip}:${uuidv4()}`;
    // 可选:将 redisKey 返回给前端,验证时需携带该 key
    // res.cookie('captchaKey', redisKey, { maxAge: 5 * 60 * 1000, httpOnly: true });

    // 3. 存储验证码到 Redis(简化版:直接使用 IP 作为 key,适合简单场景)
    const simpleRedisKey = `captcha:${ip}`;
    await this.userService.storeCaptcha(simpleRedisKey, captcha.text);

    // 4. 返回 SVG 图片
    res.type('image/svg+xml'); // 明确响应类型为 SVG
    res.status(HttpStatus.OK).send(captcha.data);
  }

  /**
   * 验证验证码有效性
   * @param inputCaptcha 用户输入的验证码
   * @param ip 客户端 IP 地址
   * @returns 验证结果
   */
  @Post('validate/captcha')
  @HttpCode(HttpStatus.OK) // 指定响应状态码 200
  async validateCaptcha(
    @Body('captcha') inputCaptcha: string,
    @Ip() ip: string,
  ) {
    // 构建 Redis 键(与存储时保持一致)
    const simpleRedisKey = `captcha:${ip}`;

    // 验证验证码
    const isValid = await this.userService.validateCaptcha(simpleRedisKey, inputCaptcha);

    if (!isValid) {
      return {
        success: false,
        code: 400,
        message: '验证码错误或已过期',
        data: null,
      };
    }

    // 验证成功后,删除 Redis 中的验证码(避免重复使用)
    await this.userService.redisService.del(simpleRedisKey);

    return {
      success: true,
      code: 200,
      message: '验证码验证通过',
      data: null,
    };
  }
}

4.3 用户模块配置(user.module.ts)

无需额外导入 Redis 模块(全局模块已生效),仅需注册控制器和服务:

typescript 复制代码
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';

@Module({
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

五、接口测试

5.1 测试准备

  1. 启动 Redis 服务(默认端口 6379)

  2. 启动 NestJS 应用

    bash 复制代码
    npm run start:dev
  3. 接口前缀:若配置了全局前缀 /api,接口地址需添加 /api

5.2 接口信息

接口功能 请求方法 接口地址 请求参数 响应说明
获取验证码图片 GET /api/v1/user/get/code 无(自动获取客户端 IP) SVG 格式验证码图片
验证验证码 POST /api/v1/user/validate/captcha { "captcha": "XXXX" } JSON 格式验证结果

5.3 响应示例

验证码验证成功
json 复制代码
{
  "success": true,
  "code": 200,
  "message": "验证码验证通过",
  "data": null
}
验证码验证失败
json 复制代码
{
  "success": false,
  "code": 400,
  "message": "验证码错误或已过期",
  "data": null
}

六、核心优化点说明

  1. 全局模块化 :Redis 模块标记为 @Global(),所有业务模块无需重复导入,简化配置
  2. 环境变量配置 :通过 .env 文件管理 Redis 连接信息,支持开发/生产环境动态切换,避免硬编码
  3. 类型安全:使用 TypeScript 强类型约束,注入服务和配置时类型明确,减少运行时错误
  4. 高可用优化:配置 Redis 重连策略,避免单次连接失败导致服务异常
  5. 用户体验优化
    • 忽略易混淆字符(0o1iIl),减少用户输入错误
    • 验证码对比忽略大小写,提升使用便捷性
    • 验证成功后删除 Redis 验证码,避免重复使用
  6. 安全性优化
    • 使用 IP + UUID 作为 Redis 键(可选),避免同一 IP 重复覆盖验证码
    • 验证码设置过期时间,防止恶意攻击
  7. 可扩展性 :封装的 storeCaptchagetCaptcha 方法可复用于其他需要 Redis 存储的场景(如 token 缓存、用户信息缓存等)

七、生产环境扩展建议

  1. Redis 集群配置 :若业务为分布式部署,可配置 Redis 集群,提高可用性和性能

    typescript 复制代码
    RedisModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        config: [
          { url: 'redis://localhost:6379' },
          { url: 'redis://localhost:6380' },
          { url: 'redis://localhost:6381' },
        ],
      }),
    }),
  2. 密码加密:Redis 若配置密码,生产环境需通过环境变量安全存储,避免明文暴露

  3. 键名规范 :统一 Redis 键名前缀(如 captcha:token:),便于后续管理和排查

  4. 连接池优化 :配置 Redis 连接池参数,提升高并发场景下的性能

    typescript 复制代码
    config: {
      url: 'redis://localhost:6379',
      maxRetriesPerRequest: 3, // 单次请求最大重试次数
      connectTimeout: 5000, // 连接超时时间(毫秒)
    }
  5. 日志监控:添加 Redis 操作日志,便于排查生产环境中的数据存储/读取异常

八、总结

本文通过 @nestjs-modules/ioredis 实现了 NestJS 与 Redis 的标准化集成,以验证码场景为实战案例,覆盖了配置、存储、读取、验证全流程,同时优化了全局模块、环境变量、高可用等核心细节。该方案可直接复用于生产环境,也可扩展到 token 缓存、热点数据缓存、分布式锁等其他 Redis 应用场景。

相关推荐
萌萌哒草头将军5 小时前
AudioDock:服务器和 NAS 音频播放最棒的软件!🚀🚀🚀
服务器·docker·node.js
离&染5 小时前
vue.js2.x + elementui2.15.6实现el-select滚动条加载数据
前端·javascript·vue.js·el-select滚动加载
inferno5 小时前
HTML基础(第一部分)
前端·html
kirinlau5 小时前
pinia状态管理在vue3项目中的用法详解
前端·javascript·vue.js
2301_767902645 小时前
MySQL 入门
数据库·mysql
zhuà!6 小时前
腾讯地图TMap标记反显,新增标记
前端·javascript·vue.js
未知原色6 小时前
web worker使用总结(包含多个worker)
前端·javascript·react.js·架构·node.js
ttod_qzstudio6 小时前
CSS改变图片颜色方法介绍
前端·css
7ioik6 小时前
说一说MySQL数据库基本架构?
数据库·mysql·架构
@淡 定6 小时前
Redis持久化机制
数据库·redis·缓存