之前的文章中我们其实留了两个可以用redis
优化的地方:
JWT
已经实现了服务端生成token
以及验证客户端发送的token
信息。【相关教程推荐:nodejs视频教程 、编程视频】实现对文章点赞功能,采用的是将点赞数据直接写入数据库JWT token
实现方式, 将基本信息直接放在token
中,以便于分布式系统使用, 但是我们没有设置有限期(这个是可以实现的),并且服务端无法主动让token
失效。 而Redis天然支持过期时间,也能实现让服务端主动使token
过期。
(资料图片仅供参考)
当然并不是说JWT token 不如 redis+token实现方案好, 具体看使用的场景,这里我们并不讨论二者孰优孰劣,只是提供一种实现方案,让大家知道如何实现。
1. 认识redis
对于前端的小伙伴来说,Redis可能相对比较陌生,首先认识一下
Redis是什么
Redis是一个开源(BSD许可)的,基于内存的数据结构存储系统,它可以用作数据库、缓存和消息中间件,是现在最受欢迎的 NoSQL 数据库之一。
其具备如下特性:
速度快单节点读110000次/s,写81000次/s基于内存运行,性能高效用 C 语言实现,离操作系统更近持久化数据的更新将异步地保存到硬盘(RDB 和 AOF多种数据结构不仅仅支持简单的 key-value 类型数据还支持:字符串、hash、列表、集合、有序集合支持多种编程语言等等Redis 典型使用场景
缓存
缓存可以说是Redis最常用的功能之一了, 合理的缓存不仅可以加快访问的速度,也可以减少后端数据库的压力。
排行系统
利用Redis的列表和有序集合的特点,可以制作排行榜系统,而排行榜系统目前在商城类、新闻类、博客类等等,都是比不可缺的。
计数器应用
计数器的应用基本和排行榜系统一样,都是多数网站的普遍需求,如视频网站的播放计数,电商网站的浏览数等等,但这些数量一般比较庞大,如果存到关系型数据库,对MySQL或者其他关系型数据库的挑战还是很大的,而Redis基本可以说是天然支持计数器应用。
(视频直播)消息弹幕
直播间的在线用户列表,礼物排行榜,弹幕消息等信息,都适合使用Redis中的SortedSet结构进行存储。
例如弹幕消息,可使用ZREVRANGEBYSCORE
排序返回,在Redis5.0中,新增了zpopmax
,zpopmin
命令,更加方便消息处理。
Redis的应用场景远不止这些,Redis对传统磁盘数据库是一个重要的补充,是支持高并发访问的互联网应用必不可少的基础服务之一。
纸上谈兵终觉浅,必须实战一波~
Redis的安装和简单使用,我这里就不一一介绍了,这里贴上我之前写的两篇文章:
Redis 安装Redis入门篇-基础使用可以快速的安装、了解Redis数据类型以及常用的命令。
可视化客户端
在Windows下使用 RedisClient, 在mac下可以使用Redis Desktop Manager
RedisClient下载链接:https://github.com/caoxinyu/RedisClient
下载后直接双击redisclient-win32.x86.2.0.exe
文件运行即可
启动后, 点击server -> add
连接后就可以看到总体情况了:
与SQL型数据不同,redis没有提供新建数据库的操作,因为它自带了16(0-15)个数据库(默认使用0库)。在同一个库中,key是唯一存在的、不允许重复的,它就像一把“密钥”,只能打开一把“锁”。键值存储的本质就是使用key来标识value,当想要检索value时,必须使用与value对应的key进行查找.
Redis认识作为文章前置条件,到这里及结束了, 接下来进入正题~
本文主要使用Redis实现缓存功能。
2. 在Nest.js中使用
版本情况:
库 | 版本 |
---|---|
Nest.js | V8.1.2 |
项目是基于Nest.js 8.x
版本,与Nest.js 9.x
版本使用有所不同, 后面的文章专门整理了两个版本使用不同点的说明, 以及如何从V8
升级到V9
, 这里就不过多讨论。
首先,我们在Nest.js项目中连接Redis, 连接Redis需要的参数:
REDIS_HOST:Redis 域名REDIS_PORT:Redis 端口号REDIS_DB: Redis 数据库REDIS_PASSPORT:Redis 设置的密码
登录后复制
将参数写入.env
与.env.prod
配置文件中:
使用Nest官方推荐的方法,只需要简单的3个步骤:
1、引入依赖文件
npm install cache-manager --savenpm install cache-manager-redis-store --savenpm install @types/cache-manager -D
登录后复制
Nest
为各种缓存存储提供统一的API,内置的是内存中的数据存储,但是也可使用 cache-manager
来使用其他方案, 比如使用Redis
来缓存。
为了启用缓存, 导入ConfigModule
, 并调用register()
或者registerAsync()
传入响应的配置参数。
2、创建module文件src/db/redis-cache.module.ts
, 实现如下:
import { ConfigModule, ConfigService } from "@nestjs/config";import { RedisCacheService } from "./redis-cache.service";import { CacheModule, Module, Global } from "@nestjs/common";import * as redisStore from "cache-manager-redis-store";@Module({ imports: [ CacheModule.registerAsync({ isGlobal: true, imports: [ConfigModule], inject: [ConfigService], useFactory: async (configService: ConfigService) => { return { store: redisStore, host: configService.get("REDIS_HOST"), port: configService.get("REDIS_PORT"), db: 0, //目标库, auth_pass: configService.get("REDIS_PASSPORT") // 密码,没有可以不写 }; }, }), ], providers: [RedisCacheService], exports: [RedisCacheService],})export class RedisCacheModule {}
登录后复制
CacheModule
的registerAsync
方法采用 Redis Store 配置进行通信store
属性值redisStore
,表示"cache-manager-redis-store" 库isGlobal
属性设置为true
来将其声明为全局模块,当我们将RedisCacheModule
在AppModule
中导入时, 其他模块就可以直接使用,不需要再次导入由于Redis 信息写在配置文件中,所以采用registerAsync()
方法来处理异步数据,如果是静态数据, 可以使用register
3、新建redis-cache.service.ts
文件, 在service实现缓存的读写
import { Injectable, Inject, CACHE_MANAGER } from "@nestjs/common";import { Cache } from "cache-manager";@Injectable()export class RedisCacheService { constructor( @Inject(CACHE_MANAGER) private cacheManager: Cache, ) {} cacheSet(key: string, value: string, ttl: number) { this.cacheManager.set(key, value, { ttl }, (err) => { if (err) throw err; }); } async cacheGet(key: string): Promise { return this.cacheManager.get(key); }}
登录后复制
接下来,在app.module.ts
中导入RedisCacheModule
即可。
调整 token 签发及验证流程
我们借助redis来实现token过期处理、token自动续期、以及用户唯一登录。
过期处理:把用户信息及token放进redis,并设置过期时间token自动续期:token的过期时间为30分钟,如果在这30分钟内没有操作,则重新登录,如果30分钟内有操作,就给token自动续一个新的时间,防止使用时掉线。户唯一登录:相同的账号,不同电脑登录,先登录的用户会被后登录的挤下线token 过期处理
在登录时,将jwt生成的token,存入redis,并设置有效期为30分钟。存入redis的key由用户信息组成, value是token值。
// auth.service.ts async login(user: Partial) { const token = this.createToken({ id: user.id, username: user.username, role: user.role, });+ await this.redisCacheService.cacheSet(+ `${user.id}&${user.username}&${user.role}`,+ token,+ 1800,+ ); return { token }; }
登录后复制
在验证token时, 从redis中取token,如果取不到token,可能是token已过期。
// jwt.strategy.ts+ import { RedisCacheService } from "./../core/db/redis-cache.service";export class JwtStrategy extends PassportStrategy(Strategy) { constructor( @InjectRepository(User) private readonly userRepository: Repository, private readonly authService: AuthService, private readonly configService: ConfigService,+ private readonly redisCacheService: RedisCacheService, ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: configService.get("SECRET"),+ passReqToCallback: true, } as StrategyOptions); } async validate(req, user: User) {+ const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req);+ const cacheToken = await this.redisCacheService.cacheGet(+ `${user.id}&${user.username}&${user.role}`,+ );+ if (!cacheToken) {+ throw new UnauthorizedException("token 已过期");+ } const existUser = await this.authService.getUser(user); if (!existUser) { throw new UnauthorizedException("token不正确"); } return existUser; }}
登录后复制
用户唯一登录
当用户登录时,每次签发的新的token,会覆盖之前的token, 判断redis中的token与请求传入的token是否相同, 不相同时, 可能是其他地方已登录, 提示token错误。
// jwt.strategy.ts async validate(req, user: User) { const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req); const cacheToken = await this.redisCacheService.cacheGet( `${user.id}&${user.username}&${user.role}`, ); if (!cacheToken) { throw new UnauthorizedException("token 已过期"); }+ if (token != cacheToken) {+ throw new UnauthorizedException("token不正确");+ } const existUser = await this.authService.getUser(user); if (!existUser) { throw new UnauthorizedException("token不正确"); } return existUser; }
登录后复制
token自动续期
实现方案有多种,可以后台jwt生成access_token
(jwt有效期30分钟)和refresh_token
, refresh_token
有效期比access_token
有效期长,客户端缓存此两种token, 当access_token
过期时, 客户端再携带refresh_token
获取新的access_token
。 这种方案需要接口调用的开发人员配合。
我这里主要介绍一下,纯后端实现的token自动续期
实现流程:
①:jwt生成token时,有效期设置为用不过期②:redis 缓存token时设置有效期30分钟③:用户携带token请求时, 如果key存在,且value相同, 则重新设置有效期为30分钟设置jwt生成的token, 用不过期, 这部分代码是在auth.module.ts
文件中, 不了解的可以看文章 Nest.js 实战系列第二篇-实现注册、扫码登陆、jwt认证
// auth.module.tsconst jwtModule = JwtModule.registerAsync({ inject: [ConfigService], useFactory: async (configService: ConfigService) => { return { secret: configService.get("SECRET", "test123456"),- signOptions: { expiresIn: "4h" }, // 取消有效期设置 }; },});
登录后复制
然后再token认证通过后,重新设置过期时间, 因为使用的cache-manager
没有通过直接更新有效期方法,通过重新设置来实现:
// jwt.strategy.ts async validate(req, user: User) { const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req); const cacheToken = await this.redisCacheService.cacheGet( `${user.id}&${user.username}&${user.role}`, ); if (!cacheToken) { throw new UnauthorizedException("token 已过期"); } if (token != cacheToken) { throw new UnauthorizedException("token不正确"); } const existUser = await this.authService.getUser(user); if (!existUser) { throw new UnauthorizedException("token不正确"); }+ this.redisCacheService.cacheSet(+ `${user.id}&${user.username}&${user.role}`,+ token,+ 1800,+ ); return existUser; }
登录后复制
到此,在Nest中实现token过期处理、token自动续期、以及用户唯一登录都完成了, 退出登录时移除token比较简单就不在这里一一上代码了。
在Nest中除了使用官方推荐的这种方式外, 还可以使用nestjs-redis
来实现,如果你存token时, 希望存hash
结构,使用cache-manager-redis-store
时,会发现没有提供hash
值存取放方法(需要花点心思去发现)。
总结
源码地址:https://github.com/koala-coding/nest-blog
更多编程相关知识,请访问:编程教学!!
以上就是Node.js中怎么使用Redis?原来这么简单!的详细内容,更多请关注php中文网其它相关文章!