logologo
文档仓库
文档仓库
logologo
开始

概述

第一步
控制器
提供者
模块
中间件
异常过滤器
管道
守卫
拦截器
自定义装饰器

基础

自定义提供程序
异步提供者
动态模块
注入作用域
循环依赖
模块引用
懒加载模块
执行上下文
生命周期事件
发现服务
平台无关
单元测试

技术

配置
SQL
Mongo
验证
缓存
序列化
版本控制
任务调度
队列
日志
Cookies
事件
压缩
文件上传
文件流
HTTP 模块
Session
MVC
性能(Fastify)
SSE

安全

认证
授权
加密与哈希
Helmet
CORS
CSRF
速率限制

GraphQL

快速开始
解析器
变更
订阅
标量
指令
接口
联合与枚举
字段中间件
类型映射
插件
复杂度
扩展
CLI 插件
生成SDL
共享模型
其他功能
联邦

WebSocket

网关
异常过滤器
管道
守卫
拦截器
适配器

微服务

基础
Redis
MQTT
NATS
RabbitMQ
Kafka
gRPC
自定义传输
异常过滤器
管道
守卫
拦截器
部署
独立应用程序

CLI

概述
工作区
库
用法
脚本

OpenAPI

介绍
装饰器
类型映射
操作
其他特性
安全
类型与参数
CLI 插件

实用示例

REPL
CRUD生成器
SWC
Passport(认证)
热重载
MikroORM
TypeORM
Mongoose
Sequelize
路由模块
Swagger
健康检查
CQRS
Compodoc
Prisma
Sentry
静态资源
Commander
异步本地存储
Necord
套件(原Automock)

常见问题

Serverless
HTTP 适配器
长连接
全局前缀
原始请求体
混合应用
HTTPS & 多服务器
请求生命周期
错误

开发工具

概述
CI/CD
迁移指南
API参考(官方)

生态与案例

谁在用
精彩资源

支持

支持

社区

贡献者

最后更新于: 2025/11/18 02:11:37

上一页版本控制
下一页队列

#任务调度

任务调度允许您安排任意代码(方法/函数)在固定日期/时间、按重复间隔或在指定间隔后执行一次。在 Linux 领域,这通常由操作系统层面的 cron 等包处理。对于 Node.js 应用,有多个包可模拟类似 cron 的功能。Nest 提供了 @nestjs/schedule 包,它与流行的 Node.jscron 包集成。我们将在本章介绍这个包。

#安装

要开始使用它,我们首先需要安装所需的依赖项。

$ npm install --save @nestjs/schedule

要激活任务调度功能,请将 ScheduleModule 导入根模块 AppModule,并运行如下所示的 forRoot() 静态方法:

app.module.ts
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() 装饰器在包含待执行代码的方法定义前声明定时任务,如下所示:

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');
  }
}

在此示例中,每当当前秒数为 45 时,handleCron() 方法就会被调用。换句话说,该方法将在每分钟的第 45 秒运行一次。

@Cron() 装饰器支持以下标准 cron 表达式 :

  • 星号(例如 *)
  • 范围(例如 1-3,5)
  • 步长(例如 */2)

在上面的示例中,我们向装饰器传递了 45 * * * * *。下表展示了 cron 模式字符串中每个位置的解析方式:


* * * * * *
| | | | | |
| | | | | day of week
| | | | months
| | | day of month
| | hours
| minutes
seconds (optional)

一些示例的 cron 模式包括:

* * * * * * 每秒
45 * * * * * 每分钟,在第45秒
0 10 * * * * 每小时,在第10分钟开始时
0 */30 9-17 * * * 上午9点至下午5点之间每30分钟一次
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() 注解的方法都会自动包装在 try-catch 代码块中。

或者,你也可以向 @Cron() 装饰器传入一个 JavaScript 的 Date 对象。这样做会使任务在指定日期精确执行一次。

提示

使用 JavaScript 日期运算来安排相对于当前日期的任务。例如, @Cron(new Date(Date.now() + 10 * 1000)) 可以让任务在应用启动 10 秒后运行。

此外,你还可以将额外选项作为第二个参数传给 @Cron() 装饰器。

选项描述
name这在声明后访问和控制 cron 任务时非常有用。
timeZone指定执行时区。这将根据您的时区调整实际时间。若时区无效,将抛出错误。您可在 Moment Timezone 网站查看所有可用时区。
utcOffset此选项允许您直接指定时区偏移量,而无需使用 timeZone 参数。
waitForCompletion若设为 true,当前 onTick 回调完成前将不会运行该定时任务的其他实例。当前任务执行期间所有新触发的计划执行都将被跳过。
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 任务,或者通过动态 API 动态创建 cron 任务(其 cron 模式在运行时定义)。要通过 API 访问声明式 cron 任务,您必须通过装饰器的第二个可选参数对象中的 name 属性为任务关联名称。

#声明式间隔任务

要声明某个方法应以(重复的)指定间隔运行,请在方法定义前添加 @Interval() 装饰器。如下所示,将间隔值(以毫秒为单位的数字)传递给装饰器:

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

此机制底层使用 JavaScript 的 setInterval() 函数。您也可以使用 cron 任务来安排重复作业。

若要通过动态 API 在声明类外部控制声明式间隔,请使用以下构造将间隔与名称关联:

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

如果发生异常,它将被记录到控制台,因为每个用 @Interval() 注解的方法都会自动包裹在 try-catch 代码块中。

动态 API 还支持创建动态间隔(其属性在运行时定义),以及列出和删除这些间隔。

#声明式超时

要在指定超时时间运行(一次)方法,请在方法定义前添加 @Timeout() 装饰器。如下所示,将相对于应用启动的时间偏移量(以毫秒为单位)传递给装饰器:

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

该机制底层使用了 JavaScript 的 setTimeout() 函数。

如果发生异常,它将被记录到控制台,因为每个用 @Timeout() 注解的方法都会自动被包裹在 try-catch 代码块中。

若要通过动态 API 在声明类外部控制声明式超时,请使用以下构造将超时与名称关联:

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

动态 API 还支持创建动态超时,其属性在运行时定义,并能列出和删除这些超时。

#动态调度模块 API

@nestjs/schedule 模块提供的动态 API 可管理声明式定时任务 、 超时和间隔 。该 API 还支持创建和管理动态定时任务、超时及间隔,这些元素的属性均在运行时定义。

#动态定时任务

通过 SchedulerRegistry API,你可以在代码的任何位置根据名称获取 CronJob 实例的引用。首先,使用标准的构造函数注入方式注入 SchedulerRegistry:

constructor(private schedulerRegistry: SchedulerRegistry) {}
提示

从 @nestjs/schedule 包中导入 SchedulerRegistry。

然后在类中按如下方式使用。假设已通过以下声明创建了一个 cron job:

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

通过以下方式访问该 job:

const job = this.schedulerRegistry.getCronJob('notifications');

job.stop();
console.log(job.lastDate());

getCronJob() 方法返回指定名称的定时任务。返回的 CronJob 对象包含以下方法:

  • stop() - 停止已计划运行的任务。
  • start() - 重新启动已停止的任务。
  • setTime(time: CronTime) - 停止任务,为其设置新的时间,然后重新启动
  • lastDate() - 返回作业最后一次执行日期的 DateTime 表示形式。
  • nextDate() - 返回作业下一次计划执行日期的 DateTime 表示形式。
  • nextDates(count: number) - 提供一组(大小为 count)DateTime 表示形式,包含接下来会触发作业执行的日期集合。count 默认为 0,此时返回空数组。
提示

在 DateTime 对象上使用 toJSDate() 可将其渲染为与此 DateTime 等效的 JavaScript Date 对象。

创建一个新的定时任务,可通过动态调用 SchedulerRegistry#addCronJob 方法实现,如下所示:

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!`,
  );
}

在这段代码中,我们使用 CronJob 对象(来自 cron 包)来创建定时任务。CronJob 构造函数接收两个参数:第一个是 cron 表达式(与 @Cron() 装饰器的格式相同),第二个是定时触发器触发时执行的回调函数。SchedulerRegistry#addCronJob 方法同样接收两个参数:定时任务的名称和 CronJob 对象本身。

警告

请记得在使用前先注入 SchedulerRegistry。同时需要从 cron 包中导入 CronJob。

删除指定名称的定时任务可通过 SchedulerRegistry#deleteCronJob 方法实现,如下所示:

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

列出所有使用 SchedulerRegistry#getCronJobs 方法的定时任务:

getCrons() {
  const jobs = this.schedulerRegistry.getCronJobs();
  jobs.forEach((value, key, map) => {
    let next;
    try {
      next = value.nextDate().toJSDate();
    } catch (e) {
      next = 'error: next fire date is in the past!';
    }
    this.logger.log(`job: ${key} -> next: ${next}`);
  });
}

getCronJobs() 方法返回一个 map。在这段代码中,我们遍历该映射并尝试访问每个 CronJob 的 nextDate() 方法。在 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}`));
}

#示例

一个可用的示例在此处查看。