Nestjs使用Redis的最佳实践

news2024/11/16 7:34:06

在这里插入图片描述

前几天在项目中有用到Redis + JWT实现服务端对token的主动删除(退出登录功能)。故此介绍下如何在Nestjs中使用Redis,并做下总结。

知识准备

  1. 了解Redis - 网上很多简介。
  2. 了解Nestjs如何使用jwt生成token - 可移步看下我之前的文章

效果展示
在这里插入图片描述

一、mac安装与使用

示例代码用的本地的redis,所以介绍下mac如何安装redis和查看数据。

1. 安装

// 安装Redis
brew install redis

// 启动Redis -(这将作为后台服务运行)
brew services start redis
// 或者,用redis-server命令+路径来启动(关闭终端服务停止)
redis-server /usr/local/etc/redis.conf

// 验证Redis是否运行 (如果返回PONG,则表示Redis服务器正在运行)
redis-cli ping 

// 停止redis
brew services stop redis

2. mac使用 RedisInsight

官网下载 https://redis.io/insight/#insight-form
效果图如下
在这里插入图片描述

二、在nestjs中简单使用

1. 参考

  1. redis实现token过期:https://juejin.cn/post/7260308502031433786

2. 装包

pnpm install @nestjs/cache-manager cache-manager cache-manager-redis-yet redis -S 
pnpm install @types/cache-manager -D

3. 配置环境变量

  1. .env
# REDIS
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=test
// 本地没有密码
REDIS_PASSWORD=123456
# redis存储时的前缀 公司:项目:功能
REDIS_PREFIX=vobile:video-watermark-saas-node

2. 配置config, src/config/config.ts

export default () => {
  return {
    // ....
    redis: {
      host: process.env.REDIS_HOST,
      port: parseInt(process.env.REDIS_PORT, 10),
      // username: process.env.DATABASE_USERNAME,
      password: process.env.REDIS_PASSWORD,
      database: process.env.REDIS_DB,
      perfix: process.env.REDIS_PREFIX,
    },
  };
};

4. 创建目录使用

1. 创建目录

nest g resource modules/redis

2. 处理redis.module

import { Module, Global } from '@nestjs/common';
import { RedisService } from './redis.service';
import { RedisController } from './redis.controller';
import { CacheModule } from '@nestjs/cache-manager';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { redisStore } from 'cache-manager-redis-yet';
import type { RedisClientOptions } from 'redis';

@Global() // 这里我们使用@Global 装饰器让这个模块变成全局的
@Module({
  controllers: [RedisController],
  providers: [RedisService],
  imports: [
    CacheModule.registerAsync<RedisClientOptions>({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => {
        const store = await redisStore({
          socket: {
            host: configService.get<string>('redis.host'),
            port: configService.get<number>('redis.port'),
          },
        });
        return {
          store,
        };
      },
    }),
  ],
  exports: [RedisService],
})
export class RedisModule { }

3. 处理service

import { Inject, Injectable } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { formatSuccess } from 'src/util';

@Injectable()
export class RedisService {
  constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: Cache) { }

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

  async set(key: string, value: any, ttl?: number): Promise<void> {
    console.log('set===');
    const res = await this.cacheManager.set(key, value, ttl);
    console.log('res1: ', res);
    return res;
  }

  async testredis() {
    const res = await this.set('aaa1', 'aaa', 60 * 60 * 1000);
    console.log('res: ', res);
    return formatSuccess('aa')
  }
}

4. 处理controller

import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { RedisService } from './redis.service';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Public } from '../auth/decorators/public.decorator';

@ApiTags('redis')
@Controller('redis')
export class RedisController {
  constructor(private readonly redisService: RedisService) { }

  // 测试redis
  @ApiOperation({ summary: '测试redis', description: '测试redis' })
  @Public()
  @Post('testredis')
  testredis() {
    return this.redisService.testredis();
  }
}

简单的配置到此结束,测试正常
在这里插入图片描述

三、进阶:退出登录token失效

背景:

  1. 当退出登录时jwt的token并未失效
  2. token无法被服务器主动作废

环境变量、创建目录参考上方。

下边展示核心

1. redis.service中增加删除功能

import { Inject, Injectable } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { formatSuccess } from 'src/util';

@Injectable()
export class RedisService {
  constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: Cache) { }

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

  async set(key: string, value: any, ttl?: number): Promise<void> {
    return await this.cacheManager.set(key, value, ttl);
  }

  // 删除
  async del(key: string): Promise<void> {
    return await this.cacheManager.del(key);
  }

  async testredis() {
    await this.set('aaa2', 'aaa', 60 * 60 * 1000);
    return formatSuccess('ok');
  }
}

2. 生成token时存入redis,退出登录删除token

auth.service,1.登录生成token后存入redis 2.退出登录删除redis里的token

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { UserService } from '../user/user.service';
import * as md5 from 'md5';
import { JwtService } from '@nestjs/jwt';
import { formatError, formatSuccess } from 'src/util';
import { CreateUserDto } from '../user/dto/create-user.dto';
import { RedisService } from '../redis/redis.service'
import { ConfigService } from '@nestjs/config';

@Injectable()
export class AuthService {
  constructor(
    private userService: UserService,
    private jwtService: JwtService,
    private redisService: RedisService,
    private configService: ConfigService,
  ) { }

  // 登录
  async signIn(createUserDto: CreateUserDto): Promise<any> {
    const user: any = await this.userService.findOne(createUserDto.name);
    if (!user) return formatError({ msg: 'The user does not exist' });
    if (user?.password !== md5(createUserDto.password)) return formatError({ msg: 'wrong password' });
    // 生成token
    const payload = { id: user?.id, name: user?.name, password: user?.password };
    const token = await this.jwtService.signAsync(payload);
    // 将token存入redis 
    await this.redisService.set(`${this.configService.get('redis.perfix')}:token_${user?.id}`, token, 30 * 24 * 60 * 60 * 1000);
    return formatSuccess({
      token,
      userInfo: {
        id: user?.id,
        name: user?.name,
      },
    });
  }

  // 退出登录
  async logout(userid) {
    this.redisService.del(`${this.configService.get('redis.perfix')}:token_${userid}`)
    return formatSuccess('logout success')
  }
}

添加退出登录接口
auth.controller.ts

  // 退出登录
  @ApiOperation({ summary: '退出登录' })
  @Post('logout')
  logout(@Body() body: any, @Request() req: any) {
    return this.authService.logout(req?.user?.id);
  }

3. jwt鉴权时比对redis里的token

修改:auth.guard.ts

// ...
import { ConfigService } from '@nestjs/config';
import { RedisService } from '../redis/redis.service'

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    // ...
    private readonly configService: ConfigService,
    private redisService: RedisService,
  ) { }

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) return true;
    const request = context.switchToHttp().getRequest();
    const token = this.extractTokenFromHeader(request);
    console.log('token1111: ', token);
    if (!token) throw new UnauthorizedException();
    try {
      const payload = await this.jwtService.verifyAsync(
        token,
        { secret: this.configService.get('jwt.secret') },
      );
      r
      request['user'] = payload;
    } catch {
      throw new UnauthorizedException();
    }
    return true;
  }
}

到此,大工告成。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1949937.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

生成式AI的双重路径:Chat与Agent的融合与竞争

文章目录 每日一句正能量前言整体介绍对话系统&#xff08;Chat&#xff09;自主代理&#xff08;Agent&#xff09;结论 技术对比技术差异优势与劣势技术挑战结论 未来展望发展趋势Chat与Agent的前景社会和经济影响结论 后记 每日一句正能量 在避风的港湾里&#xff0c;找不到…

若依ruoyi+AI项目二次开发

//------------------------- //定义口味名称和口味列表静态数据 const dishFlavorListSelectref([ {name:"辣度",value:["不辣","微辣","中辣","重辣"]}, {name:"忌口",value:["不要葱","不要…

ADG901解析

目录 一、特性二、增强产品特性三、应用四、一般描述五、极低功耗六、引脚描述七、尺寸参数八、电路连接一、特性 宽带开关:-3 dB 在 4.5 GHz吸收型开关高关断隔离度:在 1 GHz 时为 38 dB低插入损耗:在 1 GHz 时为 0.8 dB单一 1.65 V 至 2.75 V 电源CMOS/LVTTL 控制逻辑小巧…

AI无处不在,英特尔举办第十七届网络与边缘计算行业大会,推动边缘AI深度融合

AI正在成为全行业的技术热潮。CSDN 看到&#xff0c;AI正在引发计算、开发、交互三大范式的全面升级&#xff0c;技术开发或将迎来一次全新的科技变革周期。国际权威的分析机构数据也一致显示了AI的快速增长之势。据IDC数据&#xff0c;中国生成式AI的复合年增长率达到86.2%&am…

企业利用AI智能名片S2B2C商城小程序参与社区团购的风险与机遇分析

摘要 在新零售浪潮的推动下&#xff0c;社区团购以其独特的商业模式迅速崛起&#xff0c;成为连接消费者与供应商的重要桥梁。企业纷纷探索如何有效利用这一新兴渠道&#xff0c;以扩大市场份额、提升品牌影响力。AI智能名片S2B2C商城小程序的引入&#xff0c;为企业参与社区团…

Spring源码学习笔记之@Async源码

文章目录 一、简介二、异步任务Async的使用方法2.1、第一步、配置类上加EnableAsync注解2.2、第二步、自定义线程池2.2.1、方法一、不配置自定义线程池使用默认线程池2.2.2、方法二、使用AsyncConfigurer指定线程池2.2.3、方法三、使用自定义的线程池Excutor2.2.4、方法四、使用…

家长读本编辑部家长读本杂志家长读本杂志社2024年第6期目录

新型教育 如何为孩子上好一堂科学课? (1) 孙瑜 全面实施“关爱微心愿”活动——福建宁德:汇聚星光,点亮学生“微心愿” (4) 黄荣夏 如何将STEM教育融入初中数学教学活动 (6) 罗淑萍 小学语文“读思达”教学法的推进策略 (9) 王湘福《家长读本》投稿&#xff1a;cn…

PE文件(十二)导入表

导入表 导入表的引入 当一个PE文件&#xff08;如.dll/.exe等&#xff09;需要使用别的模块的函数&#xff0c;也叫做依赖某模块&#xff0c;就需要一个清单来记录使用的模块&#xff08;一般为.dll文件&#xff0c;为方便理解&#xff0c;以后我们将模块都认为是.dll文件&am…

重磅发布:OpenAI宣布推出AI驱动的搜索引擎SearchGPT,将与Google和Perplexity展开竞争|TodayAI

OpenAI宣布推出其备受期待的AI驱动搜索引擎SearchGPT。该搜索引擎能够实时访问互联网信息&#xff0c;并将作为原型在有限范围内发布&#xff0c;计划最终将其功能整合到ChatGPT中。 SearchGPT的功能特点 SearchGPT是一个具有实时互联网信息访问能力的AI驱动搜索引擎。它的界面…

GoFly快速开发框架基于Go语言和Vue3开发后台管理附件管理插件包

说明 为了给客户提供更好的交互体验&#xff0c;框架把附件管理独立打包成插件包&#xff0c;这样附件管理接可以做个不通需求的附件管理插件包来满足不同甲方客户需求。 目前附件插件包有2个&#xff1a;一个基础包、一个高级包 附件插件包功能 1.基础包 统一管理业务系统…

Python酷库之旅-第三方库Pandas(046)

目录 一、用法精讲 161、pandas.Series.cumsum方法 161-1、语法 161-2、参数 161-3、功能 161-4、返回值 161-5、说明 161-6、用法 161-6-1、数据准备 161-6-2、代码示例 161-6-3、结果输出 162、pandas.Series.describe方法 162-1、语法 162-2、参数 162-3、功…

深入解析:inode、软硬链接与动静态库的奥秘

目录 一.inode1.inode的介绍2.文件系统与inode3.“目录”再理解 二.软硬链接1.硬链接2.软连接 三.动静态库1.静态库2.动态库3.动态库的加载过程 一.inode 1.inode的介绍 在Linux操作系统中&#xff0c;‘inode(索引节点)是文件系统的核心组件之一&#xff0c;用于管理文件和目…

从零开学C++:模板初阶

引言&#xff1a;在C语言当中&#xff0c;如果我们想要实现一个能计算整数和浮点数的计算器时&#xff0c;我们都需要根据不同的返回类型和参数类型创建许多个形式极其相似的函数&#xff0c;非常的麻烦&#xff0c;而在C中&#xff0c;我们将会引入模版的知识概念&#xff0c;…

模型优化—输入特征归一化处理

一、normalization 归一化&#xff08;规范化&#xff09;是对输入数据进行处理&#xff0c;使其满足某种规范。 前提&#xff1a;线性变换&#xff0c;不会改变原始数据的数值顺序。 假设原值分布在第一象限的某区间&#xff0c;并且x轴间距较广&#xff08;离散&#xff0…

QT动态添加布局以及删除布局

具体代码示例如下 &#xff1a; QHBoxLayout* hLayout new QHBoxLayout;hLayout->addWidget(new QLabel("444"));hLayout->addWidget(new QLineEdit("444"));hLayout->addWidget(new QPushButton("444"));layout->addLayout(hLayou…

Axure Web端元件库:从Quick UI到500+组件的飞跃

在快速变化的数字世界中&#xff0c;产品设计不仅仅是功能的堆砌&#xff0c;更是用户体验的精心雕琢。原型设计作为产品开发过程中的关键环节&#xff0c;其重要性不言而喻。Axure&#xff0c;作为业界领先的原型设计工具&#xff0c;凭借其强大的交互设计和丰富的功能&#x…

Masked Autoencoders for Point CloudSelf-supervised Learning

关于SSL中的MAE方法。 摘要 文章介绍了一种新的技术&#xff0c;叫做MAE&#xff0c;在帮助计算机自己学习理解语言和图片方面做得非常好。受到这个技术的启发&#xff0c;它用在了点云上。点云是一堆代表三维空间中某些点的数据&#xff0c;这种数据有时候会有点难处理&…

机器学习笔记-02-基础线性算法认识(问题-解答自查版)

前言 以下问题以Q&A形式记录&#xff0c;基本上都是笔者在初学一轮后&#xff0c;掌握不牢或者频繁忘记的点 Q&A的形式有助于学习过程中时刻关注自己的输入与输出关系&#xff0c;也适合做查漏补缺和复盘。 本文可以让读者用作自查&#xff0c;答案在后面&#xff0…

尚庭公寓开发(二)

任何二进制和子网掩码进行一个与运算 得到是子网ip的话就是属于子网ip的范围 任何数字和一做与运算都是他本身 和0做运算都是0 所以要得到子网ip的话他的前面必须是 192.168.200 最后是0-255之间的范围 0不能取 0是子网本身 255也不能用 因为他是广播地址 真正可以使用 的范…

[数据集][目标检测]躺坐站识别检测数据集VOC+YOLO格式9488张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;9488 标注数量(xml文件个数)&#xff1a;9488 标注数量(txt文件个数)&#xff1a;9488 标注…