前言
可乐他们团队最近在做一个文章社区平台,由于人手不够,前后端都是由前端同学来写。后端使用 nest
来实现。
某一天周五下午,可乐正在快乐摸鱼,想到周末即将来临,十分开心。然而,产品突然找到了他,说道:可乐,我们要做一个文章定时发布功能。
现在我先为你解释一下这个功能里意义:定时发布功能赋予了作者更大的灵活性和自由度,他们可以提前准备好文章,然后在适当的时机发布,而不必在发布当天临时抓紧时间编辑和发布。
由于上次你做一个点赞做了五天,各个老板已经颇有微词,这个功能你别再搞幺蛾子,周一要提测,你自己把控好时间。
说罢,产品就走了。
可乐挠了挠头,本来想以普通人的身份跟你们相处。结果换来的却是疏远,今天我就给你露一手,半天使用 Nest+React
实现一下文章的定时发布功能。
往期文章
仓库地址
- 切图仔做全栈:React&Nest.js 社区平台(一)——基础架构与邮箱注册、JWT 登录实现
- 切图仔做全栈:React&Nest.js社区平台(二)——👋手把手实现优雅的鉴权机制
- React&Nest.js全栈社区平台(三)——🐘对象存储是什么?为什么要用它?
- React&Nest.js社区平台(四)——✏️文章发布与管理实战
- React&Nest.js全栈社区平台(五)——👋封装通用分页Service实现文章流与详情
- 领导问我:为什么一个点赞功能你做了五天?
- 🤔️测试问我:为啥阅读量计数这么简单的功能你都能写出bug?
Cron表达式
Cron 表达式是一种时间表达式,通常用于在计算机系统中指定定时任务的执行时间。它是由特定的格式和字段组成的字符串,用来表示任务在何时执行。
Cron
表达式一般由六个或七个字段组成,每个字段表示任务执行的一个时间单位。这些字段的含义通常如下:
- 秒(Seconds): 表示分钟的秒数,取值范围为
0~59
。 - 分钟(Minutes): 表示小时的分钟数,取值范围为
0~59
。 - 小时(Hours): 表示一天中的小时数,取值范围为
0~23
。 - 日期(Day of month): 表示一个月中的哪一天,取值范围为
1~31
。 - 月份(Month): 表示一年中的哪个月份,取值范围为
1~12
,也可以使用缩写形式如JAN
,FEB
等。 - 星期(Day of week): 表示一周中的哪一天,取值范围为
0~7(0 和 7 都代表周日)
,也可以使用缩写形式如SUN
,MON
等。 - 年(Year): 可选字段,表示哪一年,取值范围为
1970
至2099
。
在 Cron
表达式中,可以使用数字、星号 *
、逗号 ,
、斜杠 /
、连字符 -
等符号来表示不同的时间段和重复规则。例如:
*
表示匹配所有可能的值。5
表示具体的一个值。1,2,3
表示多个值。*/5
表示每隔一定时间执行一次。10-20
表示一个范围内的值。
举例如下:
- 0/20 * * * * ? 表示每20秒执行一次
- 0 0 2 1 * ? 表示在每月的1日的凌晨2点执行
- 0 0 10,14,16 * * ? 每天上午10点,下午2点,4点执行
- 0 0 12 ? * WED 表示每个星期三中午12点执行
- 0 0 12 * * ? 每天中午12点执行
Nest中的定时调度
在 nest
中,定时任务的注册与触发可以使用 @nestjs/schedule
这个库,它提供了一种装饰器注册定时任务的方式,接入与使用都十分方便。
首先在 app.module.ts
中注册 schedule
这个模块
@Module({imports: [ScheduleModule.forRoot(),],
})
export class AppModule { }
然后在 service
中使用 @Cron
装饰器就可以注册定时任务了:
@Cron('*/3 * * * * *') // 每 3 秒钟执行一次async handle() {console.log('Called every 3 seconds article service');}
@nestjs/schedule
在这个过程中需要做的事情是,通过装饰器收集到每个定时任务的执行器(即上面的 handle
方法)以及每个定时任务的触发时机,即上述的 cron
表达式。
而真正调度定时任务的逻辑其实不在 @nestjs/schedule
中处理, @nestjs/schedule
收集到触发时机以及执行器之后,会把它们交给 node-cron
这个库进行处理。
上述代码是 @nestjs/schedule
的源码,它收集到 nest
项目中注册的定时任务信息后,会调用 node-cron
中的 CronJob
来创建一个定时任务。
其中 options
就是包含了 cron
表达式在内的定时任务配置信息; target
就是需要执行的逻辑,比如上述的 handle
方法。
创建完 Cron
实例之后,核心调度的代码在 start
方法中,大致贴一下这个方法的代码:
start() {if (this.running) {return;}const MAXDELAY = 2147483647; // The maximum number of milliseconds setTimeout will wait.let timeout = this.cronTime.getTimeout();let remaining = 0;let startTime: number;const setCronTimeout = (t: