本文为稀土技能社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
前言
咱们好,我是 koala,一个风趣且乐于共享的人,现在专心完整的 Node.js 技能栈共享,工作中担任部分中台建立以及低代码渠道的一些才能。假如你对 Node.js 学习感兴趣的话(后续有计划也能够),能够重视我,加我微信【ikoala520】,拉你进沟通群一起沟通、学习、共建,或许重视我的大众号程序员成长指北。Github 博客开源项目 github.com/koala-codin…
前面Nest.js
系列的文章中咱们其实留了两个能够用redis
优化的当地:
- 一个是咱们的在做登录时,经过
JWT
已经完成了服务端生成token
以及验证客户端发送的token
信息。 - 完成对文章点赞功用,选用的是将点赞数据直接写入数据库
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下载链接:github.com/caoxinyu/Re…
下载后直接双击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个步骤:
- 引入依靠文件
npm install cache-manager --save
npm install cache-manager-redis-store --save
npm install @types/cache-manager -D
Nest
为各种缓存存储供给一致的API,内置的是内存中的数据存储,可是也可运用 cache-manager
来运用其他计划, 比如运用Redis
来缓存。
为了启用缓存, 导入ConfigModule
, 并调用register()
或许registerAsync()
传入响应的装备参数。
- 创立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
- 新建
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<any> {
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<User>) {
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<User>,
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.ts
const 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
值存取放方法(需求花点心思去发现)。
留意:假如运用
nest-redis
来完成redis缓存, 在Nest.js 8 版别下会报错, 小伙伴们能够运用@chenjm/nestjs-redis
来代替, 或许参阅 issue上的解决计划:Nest 8 + redis bug。
总结
源码地址:github.com/koala-codin…
Nest.js系列意图:
- 期望协助 Node开发者们熟练掌握 Nest.js 结构,
- 协助想要学习 Node.js 的前端小伙伴们更好的入门一个优秀 Node 结构 该系列会继续更新,感兴趣小伙伴能够star一下,感谢
回忆一下【Nest
入门系列文章】
- Nest.js 手把手带你实战-项目创立&数据库操作
- Nest.js 手把手带你实战-完成注册、扫码登陆、jwt认证等
- Nest.js 手把手带你实战-完成联表查询
防止文章篇幅太长, 运用redis完成点赞功用放在下一篇中~,欢迎重视
关于我 & Node沟通群
咱们好,我是 koala,一个风趣且乐于共享的人,现在专心完整的 Node.js 技能栈共享,工作中担任部分中台建立以及低代码渠道的一些才能。假如你对 Node.js 学习感兴趣的话(后续有计划也能够),能够重视我,加我微信【ikoala520】,拉你进沟通群一起沟通、学习、共建,或许重视我的大众号程序员成长指北。Github 博客开源项目 github.com/koala-codin…