🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 定时任务 定时任务允许你按照指定的日期/时间、一定时间间隔或者一定时间后单次执行来调度(`scheduling`)任意代码(方法/函数)。在`Linux`世界中,这经常通过操作系统层面的`cron`包等执行。在`Node.js`应用中,有几个不同的包可以模拟 cron 包的功能。Nest 提供了`@nestjs/schedule`包,其集成了流行的 Node.js 的`node-cron`包,我们将在本章中应用该包。 ### 安装 我们首先从安装需要的依赖开始。 ~~~bash $ npm install --save @nestjs/schedule $ npm install --save-dev @types/cron ~~~ 要激活工作调度,从根`AppModule`中导入`ScheduleModule`并运行`forRoot()`静态方法,如下: > app.module.ts ```typescript 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()`装饰器声明一个计时工作,如下: ```typescript 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](http://crontab.org/): - 星号通配符 (也就是 \*) - 范围(也就是 1-3,5) - 步长(也就是 \*/2) 在上述例子中,我们给装饰器传递了`45 * * * * *`,下列键展示了每个位置的计时模式字符串的意义: ```bash * * * * * * | | | | | | | | | | | 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`包提供一个方便的枚举 ```typescript 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](https://docs.nestjs.com/techniques/task-scheduling#dynamic-schedule-module-api)动态创建一个定时任务(其定时模式在运行时定义)。要通过 API 声明定时任务,你必须通过将选项对象中的`name`属性作为可选的第二个参数传递给装饰器,从而将工作和名称联系起来。 ```typescript @Cron('* * 8 * * *', { name: 'notifications', }) triggerNotifications() {} ``` ### 声明间隔 要声明一个以一定间隔运行的方法,使用`@Interval()`装饰器前缀。以毫秒单位的`number`传递间隔值,如下: ```typescript @Interval(10000) handleInterval() { this.logger.debug('Called every 10 seconds'); } ``` > 本机制在底层使用`JavaScript`的`setInterval()`函数。你也可以使用定期调度工作来应用一个定时任务。 如果你希望在声明类之外通过[动态 API](https://docs.nestjs.com/techniques/task-scheduling#dynamic-schedule-module-api)控制你声明的时间间隔。使用下列结构将名称与间隔关联起来。 ```typescript @Interval('notifications', 2500) handleInterval() {} ``` 动态 API 也支持动态创建时间间隔,间隔属性在运行时定义,可以列出和删除他们。 ### 声明延时任务 要声明一个在指定时间后运行(一次)的方法,使用`@Timeout()`装饰器前缀。将从应用启动的相关时间偏移量(毫秒)传递给装饰器,如下: ```typescript @Timeout(5000) handleTimeout() { this.logger.debug('Called once after 5 seconds'); } ``` > 本机制在底层使用 JavaScript 的`setTimeout()`方法 如果你想要在声明类之外通过动态 API 控制你声明的超时时间,将超时时间和一个名称以如下结构关联: ```typescript @Timeout('notifications', 2500) handleTimeout() {} ``` 动态 API 同时支持创建动态超时时间,超时时间在运行时定义,可以列举和删除他们。 ### 动态规划模块 API `@nestjs/schedule`模块提供了一个支持管理声明定时、超时和间隔任务的动态 API。该 API 也支持创建和管理动态定时、超时和间隔,这些属性在运行时定义。 ### 动态定时任务 使用`SchedulerRegistry`API 从你代码的任何地方获取一个`CronJob`实例的引用。首先,使用标准构造器注入`ScheduleRegistry`。 ```typescript constructor(private schedulerRegistry: SchedulerRegistry) {} ``` > 从`@nestjs/schedule`包导入`SchedulerRegistry`。 使用下列类,假设通过下列定义声明一个定时任务: ```typescript @Cron('* * 8 * * *', { name: 'notifications', }) triggerNotifications() {} ``` 如下获取本工作: ```typescript 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()`动态创建一个新的定时任务,如下: ```typescript 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()`[装饰器](https://docs.nestjs.com/techniques/task-scheduling#declarative-cron-jobs)作为其第一个参数,以及一个将执行的回调函数作为其第二个参数。`SchedulerRegistry.addCronJob()`方法有两个参数:一个`CronJob`名称,以及一个`CronJob`对象自身。 > 记得在使用前注入`SchedulerRegistry`,从`cron`包中导入 `CronJob`。 使用`SchedulerRegistry.deleteCronJob()`方法删除一个命名的定时任务,如下: ```typescript deleteCron(name: string) { this.scheduler.deleteCronJob(name); this.logger.warn(`job ${name} deleted!`); } ``` 使用`SchedulerRegistry.getCronJobs()`方法列出所有定时任务,如下: ```typescript 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()`方法。在`CronJob`API 中,如果一个工作已经执行了并且没有下一次执行的日期,将抛出异常。 ### 动态间隔 使用`SchedulerRegistry.getInterval()`方法获取一个时间间隔的引用。如上,使用标准构造注入`SchedulerRegistry`。 ```typescript constructor(private schedulerRegistry: SchedulerRegistry) {} ``` 如下使用: ```typescript const interval = this.schedulerRegistry.getInterval('notifications'); clearInterval(interval); ``` 使用`SchedulerRegistry.addInterval() `方法创建一个新的动态间隔,如下: ```typescript 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()`删除一个命名的时间间隔: ```typescript deleteInterval(name: string) { this.scheduler.deleteInterval(name); this.logger.warn(`Interval ${name} deleted!`); } ``` 使用`SchedulerRegistry.getIntervals()`方法如下列出所有的时间间隔: ```typescript getIntervals() { const intervals = this.scheduler.getIntervals(); intervals.forEach(key => this.logger.log(`Interval: ${key}`)); } ``` ### 动态超时 使用`SchedulerRegistry.getTimeout()`方法获取一个超时引用,如上,使用标准构造注入`SchedulerRegistry`: ```typescript constructor(private schedulerRegistry: SchedulerRegistry) {} ``` 并如下使用: ```typescript const timeout = this.schedulerRegistry.getTimeout('notifications'); clearTimeout(timeout); ``` 使用`SchedulerRegistry.addTimeout()`方法创建一个新的动态超时,如下: ```typescript 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()`方法删除一个命名的超时,如下: ```typescript deleteTimeout(name: string) { this.scheduler.deleteTimeout(name); this.logger.warn(`Timeout ${name} deleted!`); } ``` 使用`SchedulerRegistry.getTimeouts()`方法列出所有超时任务: ```typescript getTimeouts() { const timeouts = this.scheduler.getTimeouts(); timeouts.forEach(key => this.logger.log(`Timeout: ${key}`)); } ``` ### 示例 一个可用的例子见[这里](https://github.com/nestjs/nest/tree/master/sample/27-scheduling)。