CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Nestjs如何做登陆鉴权
  • Nestjs如何接入Redis
    • 本地安装
    • nestjs 接入 redis
      • 安装依赖
      • 创建 service 服务
      • 实现单点登录
  • Nestjs typeorm 如何联查没有关联关系的实体
  • Nestjs接入nodemailer并通过QQ邮箱发送邮件步骤
  • Nestjs笔记
CD
2023-03-27
目录

Nestjs如何接入Redis

笔记

Redis 是一个开源(BSD 许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。我们可以使用 redis 来稍微的弥补 jwt 策略的缺陷

  • Redis 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高。
  • Redis 使用单进程单线程模型的(K,V)数据库,将数据存储在内存中,存取均不会受到硬盘 IO 的限制,因此其执行速度极快。另外单线程也能处理高并发请求,还可以避免频繁上下文切换和锁的竞争,如果想要多核运行也可以启动多个实例。
  • 数据结构简单,对数据操作也简单,Redis 不使用表,不会强制用户对各个关系进行关联,不会有复杂的关系限制,其存储结构就是键值对,类似于 HashMap,HashMap 最大的优点就是存取的时间复杂度为 O(1)。
  • Redis 使用多路 I/O 复用模型,为非阻塞 IO。

# 本地安装

百度操作下载好 redis。本地 redis 安装后如何跑: 到 redis 根目录,打开 cmd : redis-server.exe redis.windows.conf。 执行完 cmd 之后,可以使用 Redis Desktop Manager 可视化工具来查看本地 redis 情况。破解版去网上下载

# nestjs 接入 redis

# 安装依赖

  1. pnpm add --save @liaoliaots/nestjs-redis ioredis ,文档 (opens new window), 注意!千万不要直接下载 nestjs-redis 依赖,它会去下载 skunight 这个哥们的包,有 BUG,这上海的老哥已经快一年没更新了,外国佬都快杀疯了,踩过坑了别人别踩-。-
  2. 找到 libs/db/src/db.module.ts 接入 redis

import { RedisModule } from '@liaoliaots/nestjs-redis';
imports: [
  RedisModule.forRootAsync({
    imports: [ConfigModule],
    inject: [ConfigService],
    useFactory: (configService: ConfigService) => ({
      config: {
        name: configService.get('REDIS_NAME', 'test-redis'),
        host: configService.get('REDIS_HOST', '127.0.0.1'),
        port: configService.get('REDIS_PORT', 6379),
        password: configService.get('REDIS_PASSWORD', 'cd@redis'),
      },
    }),
  }),
],
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 创建 service 服务

  • nest g s --no-spec , 选择 admin , 名字为 redis
  • 找到 redis.service 文件夹
import { RedisService } from '@liaoliaots/nestjs-redis';
import { Injectable } from '@nestjs/common';
import { Redis } from 'ioredis';

@Injectable()
// (这里的命名和redisService冲突了,我将它改成了RedisCacheService)
export class RedisCacheService {
  public client: Redis;
  constructor(private redisService: RedisService) {
    this.createRedis();
  }

  // 创建redis服务
  async createRedis() {
    this.client = await this.redisService.getClient();
  }

  // 获取redis缓存数据
  async get(key: string): Promise<any> {
    if (!this.client) {
      await this.createRedis();
    }
    const data = await this.client.get(key);
    if (data) return JSON.parse(data);
    return null;
  }

  // 设置redis缓存
  async set(key: string, value: any, seconds?: number): Promise<any> {
    value = JSON.stringify(value);
    if (!this.client) {
      await this.createRedis();
    }
    if (!seconds) {
      await this.client.set(key, value);
    } else {
      await this.client.set(key, value, 'EX', seconds);
    }
  }

  // 根据key删除redis缓存数据
  async del(key: string): Promise<any> {
    return await this.client.del(key);
  }

  // 获取所有redis缓存
  async flushall(): Promise<any> {
    return await this.client.flushall();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 实现单点登录

实现单点登录的逻辑就是,当我们在为用户 sign token 的时候,我们通过 key: 用户 id, value:token 的形式存储进 redis,如果下次还有同样的用户 id 登录就把原来的 redis 中的 key 对应的值换成新的,这样先前的那个用户 再次访问的时候发现这个 token 和之前的不相同,那么就认为它在别的地方登录了,本地就强制下线, 从而保证一个用户只允许在一个地方登录,这里暂不考虑 redis 缓存丢失,服务器宕机等问题。

  1. 我统一把登录相关的鉴权接口写在 auth.service 里,我们来到 auth.service
// auth.module 导入redis服务
import { RedisCacheService } from '../redis/redis.service';
providers: [
  .....
  RedisCacheService,
],

// auth.service 注入服务并编写接口
constructor(
  @Inject(RedisCacheService)
  private readonly redisCacheService: RedisCacheService,
) {}

async createRedisByToken({ name, id, token }): Promise<any> {
  this.redisCacheService.set(
    `jwt-${id}-${name}`,
    token,
    Number(this.configService.get('REDIS_CACHE_TIME', 60 * 60)),
  );
}

async getRedisByToken({ name, id }): Promise<string> {
  return this.redisCacheService.get(`jwt-${id}-${name}`);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

来到 user.controller,我们在成功登录后使用 createRedisByToken 来为用户生成缓存

// user.controller
...
const token = await this.authService.creatToken({
  name,
  id,
});
await this.authService.createRedisByToken({
  name,
  id,
  token: token.accessToken,
});
...
1
2
3
4
5
6
7
8
9
10
11
12

这个时候我们就拥有了用户缓存,在来到 jwt.strategy 下面为该策略书写 redis 验证

// jwt.strategy
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService) {
    // 这里我们需要增加passReqToCallback:true参数,让validate返回req从而获取前端传入的token
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET,
      ignoreExpiration: false,
      passReqToCallback: true,
    } as StrategyOptions);
  }

  async validate(req, payload: JwtPayloadToken, done: any) {
    const originToken = ExtractJwt.fromAuthHeaderAsBearerToken()(req);
    const { name, id } = payload;
    const cacheToken = await this.authService.getRedisByToken({ name, id });
    const user = await this.authService.validateUser(name);
    console.log({ originToken, cacheToken });

    //单点登陆验证, 要排除redis为空的情况
    if (cacheToken && cacheToken !== originToken) {
      throw new ApiException(
        '您账户已经在另一处登陆,请重新登陆',
        400,
        ApiCodeEnum.USER_LOGGED,
      );
    }

    if (!user || user.id !== Number(id)) {
      return done(
        new ApiException('token无效', 400, ApiCodeEnum.TOKEN_OVERDUE),
        false,
      );
    }
    done(null, user);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

到此为止,登录鉴权就完成了。可以自己在 nest 中构造 demo 实践一下,实践才能得真知。

编辑 (opens new window)
#Nestjs
上次更新: 2023/03/27, 22:09:34
Nestjs如何做登陆鉴权
Nestjs typeorm 如何联查没有关联关系的实体

← Nestjs如何做登陆鉴权 Nestjs typeorm 如何联查没有关联关系的实体→

最近更新
01
gsap动画库学习笔记 - 持续~
06-05
02
远程组件加载方案笔记
05-03
03
小程序使用笔记
03-29
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式