当前位置: 首页 > news >正文

Nestjs使用Redis的最佳实践

在这里插入图片描述

前几天在项目中有用到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' });// 生成tokenconst 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') },);rrequest['user'] = payload;} catch {throw new UnauthorizedException();}return true;}
}

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • sed利用脚本处理文件
  • IEC104转MQTT网关快速实现了IEC104到MQTT的转换和数据交互
  • 必修-场景题
  • C++ 八股(2)
  • PHP进阶:前后端交互、cookie验证、sql与php
  • SpringBoot原理解析(二)- Spring Bean的生命周期以及后处理器和回调接口
  • ssh出现Permission denied(publickey,gssapi-keyex,gssapi-with-mic).
  • 配置php-fpm服务
  • 【ffmpeg命令入门】视频剪切,倍速与倒放
  • 视图、存储过程、触发器
  • goland设置Gin框架中tmpl模板的语法提示的方法
  • Spring 循环依赖详解
  • 基于python opencv 多进程处理图像
  • 你了解你的GD32 MCU系统主频是多少吗 ?
  • 什么是反射以及反射的应用及例子
  • AWS实战 - 利用IAM对S3做访问控制
  • EOS是什么
  • vue2.0项目引入element-ui
  • WePY 在小程序性能调优上做出的探究
  • 关于for循环的简单归纳
  • 基于组件的设计工作流与界面抽象
  • 看图轻松理解数据结构与算法系列(基于数组的栈)
  • 聊一聊前端的监控
  • 前端之React实战:创建跨平台的项目架构
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 它承受着该等级不该有的简单, leetcode 564 寻找最近的回文数
  • 项目实战-Api的解决方案
  • - 转 Ext2.0 form使用实例
  • Nginx实现动静分离
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • ‌移动管家手机智能控制汽车系统
  • #nginx配置案例
  • (1综述)从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练
  • (2024)docker-compose实战 (8)部署LAMP项目(最终版)
  • (Java入门)学生管理系统
  • (附源码)ssm高校实验室 毕业设计 800008
  • (附源码)计算机毕业设计SSM智能化管理的仓库管理
  • (附源码)流浪动物保护平台的设计与实现 毕业设计 161154
  • (六)vue-router+UI组件库
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)jQuery 基础
  • (转)编辑寄语:因为爱心,所以美丽
  • *算法训练(leetcode)第三十九天 | 115. 不同的子序列、583. 两个字符串的删除操作、72. 编辑距离
  • .Net CoreRabbitMQ消息存储可靠机制
  • .net framework profiles /.net framework 配置
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .net 后台导出excel ,word
  • .NET编程C#线程之旅:十种开启线程的方式以及各自使用场景和优缺点
  • .NET开源快速、强大、免费的电子表格组件
  • .NET框架设计—常被忽视的C#设计技巧
  • /etc/shadow字段详解
  • /etc/sudoer文件配置简析