NestJS 定时任务

2023-09-08 17:50 更新

定时任务允许你按照指定的日期/时间、一定时间间隔或者一定时间后单次执行来调度(scheduling)任意代码(方法/函数)。在Linux世界中,这经常通过操作系统层面的cron包等执行。在Node.js应用中,有几个不同的包可以模拟 cron 包的功能。Nest 提供了@nestjs/schedule包,其集成了流行的 Node.js 的node-cron包,我们将在本章中应用该包。

安装

我们首先从安装需要的依赖开始。

$ npm install --save @nestjs/schedule

要激活工作调度,从根AppModule中导入ScheduleModule并运行forRoot()静态方法,如下:

app.module.ts
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';

@Module({
  imports: [ScheduleModule.forRoot()],
})
export class AppModule {}

.forRoot()调用初始化调度器并且注册在你应用中任何声明的cron jobs,timeouts和intervals。注册开始于onApplicationBootstrap生命周期钩子发生时,保证所有模块都已经载入,任何计划工作已经声明。

声明计时工作(cron job)

一个计时工作调度任何函数(方法调用)以自动运行, 计时工作可以:

  • 单次,在指定日期/时间
  • 重复循环:重复工作可以在指定周期中指定执行(例如,每小时,每周,或者每 5 分钟)

在包含要运行代码的方法定义前使用@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 patterns:

  • 星号通配符 (也就是 *)
  • 范围(也就是 1-3,5)
  • 步长(也就是 */2)

在上述例子中,我们给装饰器传递了45 * * * * *,下列键展示了每个位置的计时模式字符串的意义:

* * * * * *
| | | | | |
| | | | | day of week
| | | | month
| | | day of month
| | hour
| minute
second (optional)

一些示例的计时模式包括:

名称含义
* * * * * *每秒
45 * * * * *每分钟第 45 秒
_ 10 _ * * *每小时,从第 10 分钟开始
0 _/30 9-17 _ * *上午 9 点到下午 5 点之间每 30 分钟
0 30 11 * * 1-5周一至周五上午 11:30

@nestjs/schedule包提供一个方便的枚举

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_45_SECONDS)
  handleCron() {
    this.logger.debug('Called every 45 seconds');
  }
}

在本例中,handleCron()方法每45秒执行一次。

可选地,你可以为将一个JavaScript的Date对象传递给@Cron()装饰器。这样做可以让工作在指定日期执行一次。

使用JavaScript日期算法来关联当前日期和计划工作。@Cron(new Date(Date.now()+10*1000))用于在应用启动 10 秒后运行。

你可以在声明后访问并控制一个定时任务,或者使用动态 API动态创建一个定时任务(其定时模式在运行时定义)。要通过 API 声明定时任务,你必须通过将选项对象中的name属性作为可选的第二个参数传递给装饰器,从而将工作和名称联系起来。

@Cron('* * 8 * * *', {
  name: 'notifications',
})
triggerNotifications() {}

声明间隔

要声明一个以一定间隔运行的方法,使用@Interval()装饰器前缀。以毫秒单位的number传递间隔值,如下:

@Interval(10000)
handleInterval() {
  this.logger.debug('Called every 10 seconds');
}

本机制在底层使用JavaScript的setInterval()函数。你也可以使用定期调度工作来应用一个定时任务。

如果你希望在声明类之外通过动态 API控制你声明的时间间隔。使用下列结构将名称与间隔关联起来。

@Interval('notifications', 2500)
handleInterval() {}

动态 API 也支持动态创建时间间隔,间隔属性在运行时定义,可以列出和删除他们。

声明延时任务

要声明一个在指定时间后运行(一次)的方法,使用@Timeout()装饰器前缀。将从应用启动的相关时间偏移量(毫秒)传递给装饰器,如下:

@Timeout(5000)
handleTimeout() {
  this.logger.debug('Called once after 5 seconds');
}

本机制在底层使用 JavaScript 的setTimeout()方法

如果你想要在声明类之外通过动态 API 控制你声明的超时时间,将超时时间和一个名称以如下结构关联:

@Timeout('notifications', 2500)
handleTimeout() {}

动态 API 同时支持创建动态超时时间,超时时间在运行时定义,可以列举和删除他们。

动态规划模块 API

@nestjs/schedule模块提供了一个支持管理声明定时、超时和间隔任务的动态 API。该 API 也支持创建和管理动态定时、超时和间隔,这些属性在运行时定义。

动态定时任务

使用SchedulerRegistryAPI 从你代码的任何地方获取一个CronJob实例的引用。首先,使用标准构造器注入ScheduleRegistry。

constructor(private schedulerRegistry: SchedulerRegistry) {}

从@nestjs/schedule包导入SchedulerRegistry。

使用下列类,假设通过下列定义声明一个定时任务:

@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()动态创建一个新的定时任务,如下:

addCronJob(name: string, seconds: string) {
  const job = new CronJob(`${seconds} * * * * *`, () => {
    this.logger.warn(`time (${seconds}) for job ${name} to run!`);
  });

  this.scheduler.addCronJob(name, job);
  job.start();

  this.logger.warn(
    `job ${name} added for each minute at ${seconds} seconds!`,
  );
}

在这个代码中,我们使用cron包中的CronJob对象来创建定时任务。CronJob构造器采用一个定时模式(类似@Cron()装饰器作为其第一个参数,以及一个将执行的回调函数作为其第二个参数。SchedulerRegistry.addCronJob()方法有两个参数:一个CronJob名称,以及一个CronJob对象自身。

记得在使用前注入SchedulerRegistry,从cron包中导入 CronJob。

使用SchedulerRegistry.deleteCronJob()方法删除一个命名的定时任务,如下:

deleteCron(name: string) {
  this.scheduler.deleteCronJob(name);
  this.logger.warn(`job ${name} deleted!`);
}

使用SchedulerRegistry.getCronJobs()方法列出所有定时任务,如下:

getCrons() {
  const jobs = this.scheduler.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。在这个代码中,我们遍历该map并且尝试获取每个CronJob的nextDates()方法。在CronJobAPI 中,如果一个工作已经执行了并且没有下一次执行的日期,将抛出异常。

动态间隔

使用SchedulerRegistry.getInterval()方法获取一个时间间隔的引用。如上,使用标准构造注入SchedulerRegistry。

constructor(private schedulerRegistry: SchedulerRegistry) {}

如下使用:

const interval = this.schedulerRegistry.getInterval('notifications');
clearInterval(interval);

使用SchedulerRegistry.addInterval() 方法创建一个新的动态间隔,如下:

addInterval(name: string, seconds: string) {
  const callback = () => {
    this.logger.warn(`Interval ${name} executing at time (${seconds})!`);
  };

  const interval = setInterval(callback, seconds);
  this.scheduler.addInterval(name, interval);
}

在该代码中,我们创建了一个标准的 JavaScript 间隔,然后将其传递给ScheduleRegistry.addInterval()方法。该方法包括两个参数:一个时间间隔的名称,和时间间隔本身。

如下使用SchedulerRegistry.deleteInterval()删除一个命名的时间间隔:

deleteInterval(name: string) {
  this.scheduler.deleteInterval(name);
  this.logger.warn(`Interval ${name} deleted!`);
}

使用SchedulerRegistry.getIntervals()方法如下列出所有的时间间隔:

getIntervals() {
  const intervals = this.scheduler.getIntervals();
  intervals.forEach(key => this.logger.log(`Interval: ${key}`));
}

动态超时

使用SchedulerRegistry.getTimeout()方法获取一个超时引用,如上,使用标准构造注入SchedulerRegistry:

constructor(private schedulerRegistry: SchedulerRegistry) {}

并如下使用:

const timeout = this.schedulerRegistry.getTimeout('notifications');
clearTimeout(timeout);

使用SchedulerRegistry.addTimeout()方法创建一个新的动态超时,如下:

addTimeout(name: string, seconds: string) {
  const callback = () => {
    this.logger.warn(`Timeout ${name} executing after (${seconds})!`);
  });

  const timeout = setTimeout(callback, seconds);
  this.scheduler.addTimeout(name, timeout);
}

在该代码中,我们创建了个一个标准的 JavaScript 超时任务,然后将其传递给ScheduleRegistry.addTimeout()方法,该方法包含两个参数:一个超时的名称,以及超时对象自身。

使用SchedulerRegistry.deleteTimeout()方法删除一个命名的超时,如下:

deleteTimeout(name: string) {
  this.scheduler.deleteTimeout(name);
  this.logger.warn(`Timeout ${name} deleted!`);
}

使用SchedulerRegistry.getTimeouts()方法列出所有超时任务:

getTimeouts() {
  const timeouts = this.scheduler.getTimeouts();
  timeouts.forEach(key => this.logger.log(`Timeout: ${key}`));
}

示例

一个可用的例子见这里


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号