NestJS小技巧15-使用Cron Jobs和NestJS实现任务自动化。

1,617 阅读8分钟
by 雪隐 from https://juejin.cn/user/1433418895994094
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权

原文链接

如今,大多数web应用程序都是根据用户的请求执行任务的。如果用户想在特定时间完成某件事,他们必须单击应用程序上的按钮。如果他们想在最初的时间再次执行相同的任务,他们必须单击向服务器发送请求的同一按钮。

例如,假设你是一家餐馆的老板。您拥有坚实的客户基础;比方说,你的餐厅有大约100名扎根客户,你已经通过CRM软件获取他们的个人信息,如姓名、年龄、出生日期等,与他们中的每一个人建立了良好的客户关系。现在有句话是这样说的,“每天都是别人的生日”,作为一个善良的商务人士,你喜欢在客户生日的时候给他们送生日祝福,但有一个问题。由于以下任何原因,你都无法手动向每一位客户送去祝福:

  • 你很可能会忘记这样做,即使你有生日记录。

  • 在与多个生日相同的客户进行此类操作时,您可能会犯一些错误。想象一下,给彼得送上“美味蛋糕的生日祝福,愿你的愿望成真。祝你度过美好的一天,约翰。”想象一下彼得读到这句话时脸上的表情。

使用cron作业,您可以自动化这些类型的任务。它们与事件相似;唯一的区别是,虽然事件是基于操作的,但cron作业是基于时间的。要了解如何使用事件和NestJS,请查看文章# 发送和订阅事件(Event Emitter)

现在你明白了cron jobs背后的想法。cron作业是一种用于计划在特定时间重复执行的任务的程序。

使用cron运算符,您可以定义执行作业的时间。其中一些操作包括:

  • 星号(*)
  • 逗号(,)
  • 连字符(-)
  • 斜线(/)

星号的语法和语义:

此运算符用于表示crontab字段中可以使用的每个值。crontab的格式如下所示:

second minute hour day(month) month day(week)

带星号的字段表示如下:

* * * * *

这只是意味着cron作业将在一周中的每一天、每个月、每个月的每一日、每小时、每分钟和每一秒执行。现在,如果你想明确你的时间安排,你可以写一些东西作为;

* * * 5 * 3

这意味着你希望任务在每周三(3)执行,并且如果那一天恰好是每个月的第5天。该任务将在该指定日期的每一秒、每一分钟和每一小时执行。

逗号的语法和语义:

使用此运算符,可以在一个字段中使用多个值。下面是一个例子;

* * * 5,7 * 3,2

这意味着你希望你的任务在星期二和星期三执行,如果它们恰好是每个月的第5天和第7天,并且这项任务将在这两天的每一秒、每一分钟和每一小时执行。

连字符的语法和语义:

使用此运算符,您可以根据范围执行任务。例如,如果你想在每个月的星期三到星期日、每一天、每一分钟和每一秒执行任务,你可以写:

* * * * * 3-0

斜杠的语法和语义:

斜杠让事情变得有点复杂。示例,语法为:

* * */4 * * 0

这意味着您希望您的任务在周日执行,每4小时、每分钟和当天的每一秒执行一次。这并不难,是吗?

本章代码已经写好了,很简单。大家可以参考。

npm install --save @nestjs/schedule  
npm install --save-dev @types/cron

我们将使用不同的运算符编写4个简单的cron作业,这些作业将在短时间内执行:

  • 一个任务,当秒读到0时,发送一条消息,说“美味的蛋糕开门营业”。

  • 一项任务,每5秒发送一条信息,上面写着“美味的蛋糕仍在接受订单”。

  • 一个任务,当秒数为40或45时发送一条消息,说“美味的蛋糕很快就要关门了”。

  • 一个任务发送一条信息,上面写着“美味的蛋糕今天不营业了”,当秒钟读数为50时。

例子

  • 安装软件包后,通过在app.module.ts文件中导入“ScheduleModule”类来激活作业调度。
// app.module.ts
...
import { ScheduleModule } from '@nestjs/schedule';

@Module({
  imports: [
  ...
    ScheduleModule.forRoot()
  ],
  controllers: [AppController],
  providers: [AppService],
})

使用以下命令创建一个名为“cronjobs”的新文件夹及其服务文件:

nest generate module cronjobs  
nest generate service cronjobs

此文件夹也会自动注册到app.module.ts文件中。

对于第一个发送消息说“美味蛋糕已开放营业”的任务,一旦第二个任务读取0,就可以在其service.ts文件中使用该片段来完成:

import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';

@Injectable()
export class CronjobsService {

    @Cron( '0 * * * * *' )
    openForBusiness()  {
        console.log("美味的蛋糕开门营业...")
    }
}

这将导入Cron装饰器,该装饰器用于使用“openForBusiness”方法创建Cron作业。在每分钟的第一秒,项目的终端都会打印出“美味蛋糕开门营业…”的信息。

image.png

对于每5秒发送一条信息“美味蛋糕仍在接受订单”的任务;可以使用以下代码完成:

import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';

@Injectable()
export class CronjobsService {

    @Cron( '0 * * * * *' )
    openForBusiness()  {
        console.log("美味的蛋糕开门营业...")
    }

  // new
    @Cron( '*/5 * * * * *' )
    takingOrders() {
        console.log("美味的蛋糕仍在接受订单")
    }
}

每5秒钟,终端就会打印一次“美味蛋糕仍在接受订单”的信息。Nest提供了一种不同的方法,通过“CronExpression”枚举类以更具表现力的方式打印消息。

...
import { Cron, CronExpression } from '@nestjs/schedule'; 

    ...
    @Cron( CronExpression.EVERY_5_SECONDS ) 
    takingOrders() {
        console.log("美味的蛋糕仍在接受订单")
    }

该类的其他变量包括:

export declare enum CronExpression {
    EVERY_SECOND = "* * * * * *",
    EVERY_5_SECONDS = "*/5 * * * * *",
    EVERY_10_SECONDS = "*/10 * * * * *",
    EVERY_30_SECONDS = "*/30 * * * * *",
    EVERY_MINUTE = "*/1 * * * *",
    EVERY_5_MINUTES = "0 */5 * * * *",
    EVERY_10_MINUTES = "0 */10 * * * *",
    EVERY_30_MINUTES = "0 */30 * * * *",
    EVERY_HOUR = "0 0-23/1 * * *",
    EVERY_2_HOURS = "0 0-23/2 * * *",
    EVERY_3_HOURS = "0 0-23/3 * * *",
    EVERY_4_HOURS = "0 0-23/4 * * *",
    EVERY_5_HOURS = "0 0-23/5 * * *",
    EVERY_6_HOURS = "0 0-23/6 * * *",
    EVERY_7_HOURS = "0 0-23/7 * * *",
    EVERY_8_HOURS = "0 0-23/8 * * *",
    EVERY_9_HOURS = "0 0-23/9 * * *",
    EVERY_10_HOURS = "0 0-23/10 * * *",
    EVERY_11_HOURS = "0 0-23/11 * * *",
    EVERY_12_HOURS = "0 0-23/12 * * *",
    EVERY_DAY_AT_1AM = "0 01 * * *",
    EVERY_DAY_AT_2AM = "0 02 * * *",
    EVERY_DAY_AT_3AM = "0 03 * * *",
    EVERY_DAY_AT_4AM = "0 04 * * *",
    EVERY_DAY_AT_5AM = "0 05 * * *",
    EVERY_DAY_AT_6AM = "0 06 * * *",
    EVERY_DAY_AT_7AM = "0 07 * * *",
    EVERY_DAY_AT_8AM = "0 08 * * *",
    EVERY_DAY_AT_9AM = "0 09 * * *",
    EVERY_DAY_AT_10AM = "0 10 * * *",
    EVERY_DAY_AT_11AM = "0 11 * * *",
    EVERY_DAY_AT_NOON = "0 12 * * *",
    EVERY_DAY_AT_1PM = "0 13 * * *",
    EVERY_DAY_AT_2PM = "0 14 * * *",
    EVERY_DAY_AT_3PM = "0 15 * * *",
    EVERY_DAY_AT_4PM = "0 16 * * *",
    EVERY_DAY_AT_5PM = "0 17 * * *",
    EVERY_DAY_AT_6PM = "0 18 * * *",
    EVERY_DAY_AT_7PM = "0 19 * * *",
    EVERY_DAY_AT_8PM = "0 20 * * *",
    EVERY_DAY_AT_9PM = "0 21 * * *",
    EVERY_DAY_AT_10PM = "0 22 * * *",
    EVERY_DAY_AT_11PM = "0 23 * * *",
    EVERY_DAY_AT_MIDNIGHT = "0 0 * * *",
    EVERY_WEEK = "0 0 * * 0",
    EVERY_WEEKDAY = "0 0 * * 1-5",
    EVERY_WEEKEND = "0 0 * * 6,0",
    EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT = "0 0 1 * *",
    EVERY_1ST_DAY_OF_MONTH_AT_NOON = "0 12 1 * *",
    EVERY_2ND_HOUR = "0 */2 * * *",
    EVERY_2ND_HOUR_FROM_1AM_THROUGH_11PM = "0 1-23/2 * * *",
    EVERY_2ND_MONTH = "0 0 1 */2 *",
    EVERY_QUARTER = "0 0 1 */3 *",
    EVERY_6_MONTHS = "0 0 1 */6 *",
    EVERY_YEAR = "0 0 1 1 *",
    EVERY_30_MINUTES_BETWEEN_9AM_AND_5PM = "0 */30 9-17 * * *",
    EVERY_30_MINUTES_BETWEEN_9AM_AND_6PM = "0 */30 9-18 * * *",
    EVERY_30_MINUTES_BETWEEN_10AM_AND_7PM = "0 */30 10-19 * * *",
    MONDAY_TO_FRIDAY_AT_1AM = "0 0 01 * * 1-5",
    MONDAY_TO_FRIDAY_AT_2AM = "0 0 02 * * 1-5",
    MONDAY_TO_FRIDAY_AT_3AM = "0 0 03 * * 1-5",
    MONDAY_TO_FRIDAY_AT_4AM = "0 0 04 * * 1-5",
    MONDAY_TO_FRIDAY_AT_5AM = "0 0 05 * * 1-5",
    MONDAY_TO_FRIDAY_AT_6AM = "0 0 06 * * 1-5",
    MONDAY_TO_FRIDAY_AT_7AM = "0 0 07 * * 1-5",
    MONDAY_TO_FRIDAY_AT_8AM = "0 0 08 * * 1-5",
    MONDAY_TO_FRIDAY_AT_9AM = "0 0 09 * * 1-5",
    MONDAY_TO_FRIDAY_AT_09_30AM = "0 30 09 * * 1-5",
    MONDAY_TO_FRIDAY_AT_10AM = "0 0 10 * * 1-5",
    MONDAY_TO_FRIDAY_AT_11AM = "0 0 11 * * 1-5",
    MONDAY_TO_FRIDAY_AT_11_30AM = "0 30 11 * * 1-5",
    MONDAY_TO_FRIDAY_AT_12PM = "0 0 12 * * 1-5",
    MONDAY_TO_FRIDAY_AT_1PM = "0 0 13 * * 1-5",
    MONDAY_TO_FRIDAY_AT_2PM = "0 0 14 * * 1-5",
    MONDAY_TO_FRIDAY_AT_3PM = "0 0 15 * * 1-5",
    MONDAY_TO_FRIDAY_AT_4PM = "0 0 16 * * 1-5",
    MONDAY_TO_FRIDAY_AT_5PM = "0 0 17 * * 1-5",
    MONDAY_TO_FRIDAY_AT_6PM = "0 0 18 * * 1-5",
    MONDAY_TO_FRIDAY_AT_7PM = "0 0 19 * * 1-5",
    MONDAY_TO_FRIDAY_AT_8PM = "0 0 20 * * 1-5",
    MONDAY_TO_FRIDAY_AT_9PM = "0 0 21 * * 1-5",
    MONDAY_TO_FRIDAY_AT_10PM = "0 0 22 * * 1-5",
    MONDAY_TO_FRIDAY_AT_11PM = "0 0 23 * * 1-5"
}

对于发送消息说“美味的蛋糕很快就要关门了”的任务,一旦秒读到40或45,这就用代码完成了:

import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';

@Injectable()
export class CronjobsService {

    @Cron( '0 * * * * *' )
    openForBusiness()  {
        console.log("Delicious cakes is open for business...")
    }

    @Cron( CronExpression.EVERY_5_SECONDS )
    takingOrders() {
        console.log("Delicious cakes is still taking orders")
    }

  // new
    @Cron('40,45 * * * * *')
    closingSoon() {
        console.log("Delicious cakes will be closing soon")
    }
}

可以在终端中观察到变化:

image.png

现在,对于最后一个发送消息说“美味蛋糕当天关闭”的任务,当秒读到50时,回想一下“takingOrders”任务每5秒运行一次,你不想在关闭时接受订单,对吧?在这里,您将添加一些小函数,在第二个读到50时停止“takingOrders”作业,并在您开门营业后重新启动作业。这可以通过以下方式实现:

import { Injectable } from '@nestjs/common';
import { Cron, CronExpression, SchedulerRegistry } from '@nestjs/schedule'; 

@Injectable()
export class CronjobsService {

    constructor( private schedulerRegistry: SchedulerRegistry ) {}

    @Cron( '0 * * * * *' )
    openForBusiness()  {
        console.log("Delicious cakes is open for business...")
        const takingOrdersJob = this.schedulerRegistry.getCronJob('takingOrders')
        takingOrdersJob.start()
    }

    @Cron( CronExpression.EVERY_5_SECONDS, {name: "takingOrders"} )
    takingOrders() {
        console.log("Delicious cakes is still taking orders")
    }

    @Cron('40,45 * * * * *')
    closingSoon() {
        console.log("Delicious cakes will be closing soon")
    }

    @Cron( '50 * * * * *' )
    closed() {
        const takingOrdersJob = this.schedulerRegistry.getCronJob('takingOrders')
        takingOrdersJob.stop()
        console.log("Delicious cakes is closed for the day")
        console.log("")
    }
}

这里导入了“SchedulerRegistry”类。它可以帮助您使用“getCronJob()”方法引用命名的cron作业。用于接受订单的cron作业被命名为“takingOrders”,然后在“关闭”作业中调用并停止它。这个作业在“openForBusiness”cron作业中重新启动,美味蛋糕又开始营业了。

可以在终端中观察到变化:

image.png

你还想做什么其他任务?您可以安排它们在将来某个时候运行,不会出现错误

所以,你生日那天收到的那些推特气球很可能是cron工作的结果。

祝大家编程愉快,如果觉得这篇文章对您有帮助,接的点赞/评论🙏