任务调度
允许您安排在固定的日期/时间、定期间隔或指定间隔后执行的任意代码(方法/函数)。
在 Linux 世界中,这通常由操作系统级别的 cron
等软件包处理。
对于 Node.js 应用程序,有几个模拟 cron 功能的软件包。
Nest 提供了 @nestjs/schedule
软件包,它集成了流行的 Node.js cron
软件包。
我们将在当前章节中介绍这个软件包。
安装
要开始使用它,首先安装所需的依赖项。
npm install --save @nestjs/schedule
要激活作业调度,将 ScheduleModule
导入到根AppModule
中,并运行 forRoot()
静态方法,
如下所示:
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
@Module({
imports: [
ScheduleModule.forRoot()
],
})
export class AppModule {}
forRoot()
调用初始化调度程序,并在应用程序启动时注册所有声明的
cron 作业、
超时
和间隔。
注册发生在 onApplicationBootstrap
生命周期挂钩发生时,
确保所有模块都已加载并声明了任何计划的作业。
声明式 cron 作业
cron
作业调度要运行自动运行的任意函数(方法调用)。cron
作业可以在以下情况下运行:
- 一次,于指定的日期/时间。
- 定期运行;定期作业可以在指定间隔内的指定时刻运行(例如,每小时一次,每周一次,每 5 分钟一次)。
使用 @Cron()
装饰器在包含要执行的代码的方法定义之前声明 cron
作业,如下所示:
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Cron('45 * * * * *')
handleCron() {
this.logger.debug('Called when the current second is 45');
}
}
在此示例中,handleCron()
方法将在当前秒为 45
时调用。
换句话说,该方法将每分钟运行一次,以第 45
秒的标记。
@Cron()
装饰器支持所有标准 cron
模式:
- 星号(例如,
*
) - 范围(例如,
1-3,5
) - 步长(例如,
*/2
) 在上面的示例中,我们将45 * * * * *
传递给装饰器。 以下键显示了如何解释cron
模式字符串中的每个位置:
* * * * * *
| | | | | |
| | | | | 星期几
| | | | 月份
| | | 一个月的某天
| | 小时
| 分钟
秒(可选)
一些示例 cron 模式是:
* * * * * * | 每秒钟 |
---|---|
45 * * * * * | 每分钟,第 45 秒 |
0 10 * * * * | 每小时,在第 10 分钟开始 |
0 */30 9-17 * * * | 每 30 分钟在上午 9 点至下午 5 点之间 |
0 30 11 * * 1-5 | 周一至周五上午 11:30 |
@nestjs/schedule
软件包提供了一个方便的枚举,其中包含常用的 cron
模式。
您可以像下面这样使用这个枚举:
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Cron(CronExpression.EVERY_30_SECONDS)
handleCron() {
this.logger.debug('Called every 30 seconds');
}
}
在此示例中,handleCron()
方法将每 30 秒调用一次。
或者,您可以向 @Cron()
装饰器提供 JavaScript Date
对象。
这样做会导致作业在指定的日期执行一次。
使用 JavaScript 日期算术来相对于当前日期安排作业。
例如,@Cron(new Date(Date.now() + 10 * 1000))
会安排一个在应用程序启动后 10 秒钟后运行的作业。
此外,您还可以将附加选项作为 @Cron()
装饰器的第二个参数传递。
- name: 用于在声明后访问和控制 cron 作业。
- timeZone: 指定执行的时区。这将修改相对于您的时区的实际时间。如果时区无效,则会抛出错误。您可以在 Moment 时区网站上查看所有可用的时区。
- utcOffset: 这允许您指定您的时区的偏移量,而不是使用 timeZone 参数。
- disabled: 这表示是否根本不会执行作业。
import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class NotificationService {
@Cron('* * 0 * * *', {
name: 'notifications',
timeZone: 'Europe/Paris',
})
triggerNotifications() {}
}
您可以在声明后访问和控制 cron
作业,或者使用 Dynamic API
动态创建 cron
作业(其中其 cron
模式在运行时定义)。
要通过 API
访问声明式 cron
作业,您必须通过在装饰器的可选选项对象的第二个参数中传递 name
属性,
将作业 与名称关联起来。
声明式间隔
要声明方法应在(定期)指定的间隔运行,请在方法定义之前加上 @Interval()
装饰器。
将间隔值作为毫秒数传递给装饰器,如下所示:
@Interval(10000)
handleInterval() {
this.logger.debug('Called every 10 seconds');
}
此机制在底层使用 JavaScript setInterval()
函数。您还可以使用 cron
作业调度定期作业。
如果要通过 Dynamic API 从声明类的外部控制声明的间隔,请使用以下结构将间隔与名称关联起来:
@Interval('notifications', 2500)
handleInterval() {}
Dynamic API 还使您能够创建动态间隔,其中间隔的属性在运行时定义,并列出和删除它们。
声明式超时
要声明方法应在指定的超时时间运行(一次),请在方法定义之前加上 @Timeout()
装饰器。
将相对于应用程序启动的时间偏移量(以毫秒为单位)传递给装饰器,如下所示:
@Timeout(5000)
handleTimeout() {
this.logger.debug('Called once after 5 seconds');
}
此机制在底层使用 JavaScript setTimeout() 函数。
如果要通过 Dynamic API 从声明类的外部控制声明的超时,请使用以下结构将超时与名称关联起来:
@Timeout('notifications', 2500)
handleTimeout() {}
Dynamic API 还使您能够创建动态超时,在这些超时的属性在运行时定义,并列出和删除它们。
动态调度模块 API
@nestjs/schedule
模块提供了一个动态 API,用于管理声明式
cron 作业、
超时和
间隔。
该 API 还使您能够创建和管理动态 cron 作业、超时和间隔,其中属性在运行时定义。
动态 cron 作业
通过名称从代码的任何位置使用 SchedulerRegistry
API 获取对 CronJob
实例的引用。
首先,使用标准构造函数注入 SchedulerRegistry
:
constructor(private schedulerRegistry: SchedulerRegistry) {}
从 @nestjs/schedule 包导入 SchedulerRegistry。
然后在类中使用它,如下所示。假设已使用以下声明创建了 cron 作业:
@Cron('* * 8 * * *', {
name: 'notifications',
})
triggerNotifications() {}
使用以下代码访问此作业:
const job = this.schedulerRegistry.getCronJob('notifications');
job.stop();
console.log(job.lastDate());
getCronJob()
方法返回具有以下方法的命名 CronJob
对象:
stop()
- 停止已计划运行的作业。start()
- 重新启动已停止的作业。setTime(time: CronTime)
- 停止作业,为其设置新时间,然后启动它。lastDate()
- 返回作业执行的最后日期的字符串表示形式。nextDates(count: number)
- 返回表示即将发生的作业执行日期的moment
对象数组(大小count
)。
对 moment
对象使用 toDate()
以人类可读的形式呈现它们。
使用 SchedulerRegistry#addCronJob
方法动态创建新的 cron
作业,如下所示:
addCronJob(name: string, seconds: string) {
const job = new CronJob(`${seconds} * * * * *`, () => {
this.logger.warn(`time (${seconds}) for job ${name} to run!`);
});
this.schedulerRegistry.addCronJob(name, job);
job.start();
this.logger.warn(
`job ${name} added for each minute at ${seconds} seconds!`,
);
}
在此代码中,我们使用来自 cron
软件包的 CronJob
对象创建 cron 作业。
CronJob
构造函数将 cron 模式(就像 @Cron()
装饰器一样)作为其第一个参数,
并在其第二个参数中接受在 cron 计时器触发时要执行的回调。
SchedulerRegistry#addCronJob
方法接受两个参数:CronJob
的名称和 CronJob
对象本身。
在访问之前,请记得注入 SchedulerRegistry
。从 cron
软件包导入 CronJob
。
使用 SchedulerRegistry#deleteCronJob
方法删除具有名称的 cron 作业,如下所示:
deleteCron(name: string) {
this.schedulerRegistry.deleteCronJob(name);
this.logger.warn(`job ${name} deleted!`);
}
使用 SchedulerRegistry#getCronJobs
方法列出所有 cron 作业,如下所示:
getCrons() {
const jobs = this.schedulerRegistry.getCronJobs();
jobs.forEach((value, key, map) => {
let next;
try {
next = value.nextDates().toDate();
} catch (e) {
next = 'error: next fire date is in the past!';
}
this.logger.log(`job: ${key} -> next: ${next}`);
});
}
getCronJobs()
方法返回一个map
。
在此代码中,我们遍历映射并尝试访问每个 CronJob
的 nextDates()
方法。
在 CronJob
API 中,如果作业已触发并且没有未来的触发日期,它会抛出异常。
动态间隔
通过 SchedulerRegistry#getInterval
方法获取对间隔的引用。
与上述相同,使用标准构造函数注入 SchedulerRegistry
:
constructor(private schedulerRegistry: SchedulerRegistry) {}
然后像这样使用它:
const interval = this.schedulerRegistry.getInterval('notifications');
clearInterval(interval);
使用 SchedulerRegistry#addInterval
方法动态创建新的间隔,如下所示:
addInterval(name: string, milliseconds: number) {
const callback = () => {
this.logger.warn(`Interval ${name} executing at time (${milliseconds})!`);
};
const interval = setInterval(callback, milliseconds);
this.schedulerRegistry.addInterval(name, interval);
}
在此代码中,我们创建一个标准的 JavaScript 间隔,然后将其传递给 SchedulerRegistry#addInterval
方法。
该方法接受两个参数:间隔的名称和间隔本身。
使用 SchedulerRegistry#deleteInterval
方法删除具有名称的间隔,如下所示:
deleteInterval(name: string) {
this.schedulerRegistry.deleteInterval(name);
this.logger.warn(`Interval ${name} deleted!`);
}
使用 SchedulerRegistry#getIntervals
方法列出所有间隔,如下所示:
getIntervals() {
const intervals = this.schedulerRegistry.getIntervals();
intervals.forEach(key => this.logger.log(`Interval: ${key}`));
}
动态超时
通过 SchedulerRegistry#getTimeout
方法获取对超时的引用。
与上述相同,使用标准构造函数注入 SchedulerRegistry
:
constructor(private readonly schedulerRegistry: SchedulerRegistry) {}
然后像这样使用它:
const timeout = this.schedulerRegistry.getTimeout('notifications');
clearTimeout(timeout);
使用 SchedulerRegistry#addTimeout
方法动态创建新的超时,如下所示:
addTimeout(name: string, milliseconds: number) {
const callback = () => {
this.logger.warn(`Timeout ${name} executing after (${milliseconds})!`);
};
const timeout = setTimeout(callback, milliseconds);
this.schedulerRegistry.addTimeout(name, timeout);
}
在此代码中,我们创建一个标准的 JavaScript 超时,然后将其传递给 SchedulerRegistry#addTimeout
方法。
该方法接受两个参数:超时的名称和超时本身。
使用 SchedulerRegistry#deleteTimeout
方法删除具有名称的超时,如下所示:
deleteTimeout(name: string) {
this.schedulerRegistry.deleteTimeout(name);
this.logger.warn(`Timeout ${name} deleted!`);
}
使用 SchedulerRegistry#getTimeouts
方法列出所有超时,如下所示:
getTimeouts() {
const timeouts = this.schedulerRegistry.getTimeouts();
timeouts.forEach(key => this.logger.log(`Timeout: ${key}`));
}
示例
可以在此处找到一个可运行的示例。