夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 任务管理日历

    • 前言
    • 前端搭建

    • 后端搭建

      • 后端指南
      • 项目初始化
      • Midway使用
      • TypeORM和Redis使用
        • TypeORM
          • 实体类
          • 何为实体类
          • 数据库建表规范
          • 常用操作
          • Brackets 的使用
          • 事务的使用
        • Redis
          • 短信验证码的存和取
          • 封装的工具函数
      • 腾讯云COS和SMS的使用
    • 部署前置篇
    • 前端部署

    • 后端部署

  • 无代码平台

  • 图书管理系统

  • 《全栈项目》
  • 任务管理日历
  • 后端搭建
夜猫子
2023-07-11
目录

TypeORM和Redis使用

# TypeORM

在项目中,我们使用的数据库驱动为 MySQL

Midway 官方也提供了 TypeORM 组件 (opens new window),相关的使用案例都挺好的,对 TypeORM还不是很熟悉的同学建议先看 Midway 提供的相关案列

对于 Midway 文档没有中没有提到的内容,可以查阅以下文档

中文文档(github) (opens new window) 、官方文档 (opens new window)

# 实体类

# 何为实体类

  1. 可以简单的理解为实体类就是数据库表结构的一种描述

    1. 如果不好理解,可以简单理解为前端的 Virtual DOM,Virtual DOM 它就是一个 JS对象,是用 通过 JS 语言去描述一个 DOM 的结构
  2. 每一个实体类就对应着数据库中的一个表,

    1. 通常实体类的名称与数据库表的名称是一一对应的,添加@Entity() 注解时,默认就会通过实体类的名称去匹配数据库中表
    2. 当然也可以去做自定义,@Entiry("user_info"),那么此时 User 这个实体类对应的表就是 user_info 这张表
  3. 实体类中的每一个属性对应着数据库中的一个 column(列),同样也对应着装饰器 @Column

# 数据库建表规范

感兴趣的同学可以查阅 阿里巴巴 Java 开发手册 (opens new window)

# 常用操作

基本的增删改查就不写了,就写两个我觉得还比较有意思的

# Brackets 的使用

# 获取当月需要展示的所有事项

img

这个页面的数据是怎么查出来的呢?

前端传一个 startTime: 2022-09-25 和 endTime:2022-11-05。获取这一时间区间的所有的事项;

后端只需要判断事项的startTime 和 endTime 有没有在前端传递的这个时间区间内就好了;

tips: 关于 find 的使用文档在这 Find 选项 (opens new window)

const eventList = await this.eventModel.find({
  where: [
    {
      userId,
      startTime: Between(startTime, endTime),
    },
    {
      userId,
      endTime: Between(startTime, endTime),
    },
  ],
});
1
2
3
4
5
6
7
8
9
10
11
12

恩,看起来好像没啥大问题,但这个世界上还存在跨月这种事项,这种情况下,后端的这个查询就懵了。。。

img

比如说,上面的这个事项,它的开始时间为 09月02日 结束时间为 11月11日。当时间在 9 月 和 11月 目前的查询条件还能适用,但是如果时间到了 10月呢 ?恩,匹配不上了!

为了解决这个问题,我们还需要添加一个判断条件,事项的开始/结束时间 是否 小于/大于 区间时间。

在这里我们使用 Brackets这个特性,它可以将 复杂的 WHERE表达式添加到现有的WHERE中。

 const eventList = await this.eventModel
      .createQueryBuilder('eventInfo')
      .where('eventInfo.userId =:userId', { userId })
      .andWhere(
        new Brackets(qb => {
          qb.where({
            startTime: Between(startTime, endTime),
          })
            .orWhere({
              endTime: Between(startTime, endTime),
            })
            .orWhere({
              startTime: LessThan(startTime),
              endTime: MoreThan(endTime),
            });
        })
      )
      .getMany();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 事务的使用

# 清单列表的拖拽排序

需求是这样的,用户可以拖拽清单列表进行排序

img

做之前我们先看了下语雀 和 Apifox 对于这种需求的时候接口是怎么设计的

语雀是如果你把一个知识库拖拽到第 1 位,order_num 直接传 0 ,拖到第几位,order_num就传n - 1

img

Apifox 是传 目标 id 和 放置 id 另外加一个偏移量,向上为负数、向下为正数。

img

恩,看完之后还是有点懵,就搜到了以下两篇文章

https://www.jianshu.com/p/9ee708e43ebf

https://www.cnblogs.com/xjnotxj/p/12744348.html

于是,就把接口设计成了这样

sourceIndex` 代表原来的位置,`moveIndex` 代表目标的位置,`id` 代表拖拽的清单 `id
1

img

在设计数据库时,我们也添加了一个 sortIndex 字段,代表它在页面中的展示顺序

@Column({ name: 'sort_index', comment: '展示顺序' })
sortIndex: number;
1
2

最后在这里列一下后端处理的代码

  1. 判断该事项是向上拖拽还是向下拖拽
const isUp = sourceIndex > moveIndex;
1
  1. 从数据库中按 sortIndex 升序查出 souceIndex 至 moveIndex 区间的数据。

  2. 遍历该区间数据

    1. 对于当前拖拽的清单,直接将 sortIndex 更新为 moveIndex
    2. 其它的清单如果是向上拖拽,那么将 sortIndex 加 1,向下拖拽,将 sortIndex 减 1

开启事务

所谓的事务,通俗点来说这些操作 要么都执行,要么都不执行。

在拖拽中,我们更新一个区间数据的展示顺序,同样也需要满足上面要求,区间内的清单顺序都修改成功才算成功,有一个失败那么就全部回滚

关于事务的更多的使用案例见 文档 (opens new window)

// 'default' 是在 config.default.ts 文件中的 typeorm 对象中定义的,因为可能有的业务中会涉及多数据源
const dataSource = this.dataSourceManager.getDataSource('default');
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
try {
  // 开启事务
  await queryRunner.startTransaction();
  const folderList = await queryRunner.manager
    .createQueryBuilder(FolderInfo, 'folder')
    .where(
      'folder.userId = :userId AND folder.sortIndex BETWEEN :startIndex AND :endIndex',
      {
        userId,
        ...startEndIndex,
      }
    )
    .orderBy('sort_index', 'ASC')
    .getMany();
  for (let index = 0; index < folderList.length; index++) {
    const item = folderList[index];
    // 直接修改拖拽源的 sortIndex
    if (item.id === id) {
      await queryRunner.manager.update(FolderInfo, id, {
        sortIndex: moveIndex,
      });
    } else {
      await queryRunner.manager.update(FolderInfo, item.id, {
        sortIndex: isUp ? item.sortIndex + 1 : item.sortIndex - 1,
      });
    }
  }
  // 提交事务
  await queryRunner.commitTransaction();
} catch (error) {
  // 出错就回滚
  await queryRunner.rollbackTransaction();
} finally {
  // 不管成功失败,手动释放 runner
  await queryRunner.release();
}
// 返回最新的数据
return this.getFolderList();
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

# Redis

Midway Redis 组件文档 (opens new window)

Redis 内容挺多的,八股文也挺多的。但我们在项目中,只是用 Redis 存了短信验证码,其它的业务数据并没有往 Redis 放,从功能上来讲只用到了它的冰山一角。像缓存击穿、缓存雪崩、缓存穿透这一类的处理一律都没有。

关于 Redis 的学习推荐一个感觉还不错的 视频 (opens new window) ,虽然是Java 版本但个人觉得实战部分还不错,毕竟我们主要是学方案,至于语言不都是互通的嘛

# 短信验证码的存和取

# 封装的工具函数

// 获取常用的 redis 存储时间,1分钟、1天、2天,现在至当天结束
const getRedisExpireInfo = (): RedisExpireType => {
  const currentDay = dayjs().format('YYYY-MM-DD');
  return {
    oneMinute: 60,
    onDay: 60 * 60 * 24,
    twoDay: 60 * 60 * 24 * 2,
    // 距离当天的结束时间
    endDay: dayjs(`${currentDay} 23:59:59`).diff(dayjs(), 's'),
  };
};

// 业务直接调用此函数,设置 redis 的 key、value、存储时间
const setRedisInfo = async (
  service: RedisService,
  key: string,
  value: string,
  expire: keyof RedisExpireType
): Promise<boolean> => {
  const expireInfo = getRedisExpireInfo();
  try {
    const isOk = await service.set(key, value, 'EX', expireInfo[expire]);
    return isOk === 'OK';
  } catch (error) {
    return false;
  }
};
// 根据业务属性定义相关的枚举
export enum UserKeyEnum {
  'SMS_REDIS_KEY' = 'web:user:smsCode',
  'USER_LOGIN_KEY' = 'web:user:login',
}

// 取
const redisCode = await this.redisService.get(
`${UserKeyEnum.SMS_REDIS_KEY}${mobile}`
);

// 存
const redisKey = `${UserKeyEnum.SMS_REDIS_KEY}${mobile}`;
// 设置60秒有效期
const isSave = await setRedisInfo(
  this.redisService,
  redisKey,
  code,
  'oneMinute'
);
if (!isSave) {
  this.ctx.logger.error(
    `redis验证码保存失败: redisKey: ${redisKey}, value: ${code}`
  );
  throw new DefaultError('验证码发送失败');
}
return '验证码发送成功';
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
51
52
53
54
编辑 (opens new window)
上次更新: 2024/6/18 13:14:59
Midway使用
腾讯云COS和SMS的使用

← Midway使用 腾讯云COS和SMS的使用→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式