NestJS定时器@Cron笔记
一、NestJS定时器概述
在NestJS中,任务调度可以在固定的日期/时间、重复的时间间隔之后,或者在指定的时间间隔之后执行任意代码(方法/函数)。Nest提供了@nestjs/schedule
包,它集成了流行的Node.js cron
包,方便开发者实现定时任务。
二、安装依赖
要开始使用任务调度功能,首先需要安装所需的依赖:
bash
npm install --save @nestjs/schedule
三、启用任务调度
在NestJS的根模块(通常是AppModule
)中导入ScheduleModule
,并运行forRoot()
静态方法来启用任务调度:
typescript
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
@Module({
imports: [ScheduleModule.forRoot()],
})
export class AppModule {}
.forRoot()
调用会初始化调度器并注册应用程序中存在的任何声明式的cron jobs、timeouts和intervals。注册发生在onApplicationBootstrap
生命周期钩子发生时,确保所有模块已加载并声明了任何计划的任务。
四、声明定时任务
1. 使用@Cron
装饰器
使用@Cron()
装饰器在包含要执行代码的方法定义之前声明一个cron任务。@Cron
接受一个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');
}
}
在这个例子中,每当当前秒数为45时,handleCron()
方法都会被调用。换句话说,该方法将每分钟运行一次,在第45秒标记时运行。
2. Cron表达式
Cron表达式由6个字段组成(秒、分、小时、日期、月份、星期几),支持复杂的时间规则。
字段 | 范围 | 说明 |
---|---|---|
秒 | 0 - 59 | 每分钟的秒数 |
分 | 0 - 59 | 每小时的分钟数 |
小时 | 0 - 23 | 每天的小时数(24小时制) |
日期 | 1 - 31 | 每月的日期 |
月份 | 1 - 12 或 JAN - DEC | 月份 |
星期几 | 0 - 6 或 SUN - SAT | 周几(0和7都表示周日) |
特殊字符:
*
:匹配任意值(如* * * * * *
表示每秒执行)。,
:指定多个值(如1,3,5
表示秒为1、3、5时触发)。-
:范围(如10 - 15
表示10到15秒)。/
:步长(如*/5
表示每隔5秒)。
更多示例:
@Cron('0 0 12 * * *')
:每天中午12:00执行@Cron('0 0 9 - 17/2 * * *')
:每天上午9点到下午5点,每2小时执行一次@Cron('0 0 0 1 1 *')
:每年1月1日00:00执行@Cron('0 0 0 * * 0')
:每周日00:00执行
3. 预定义的Cron模式
@nestjs/schedule
包提供了一个方便的枚举,其中包含常用的cron模式。你可以按以下方式使用这个枚举:
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_30_SECONDS)
handleCron() {
this.logger.debug('Called every 30 seconds');
}
}
在这个例子中,handleCron()
方法将每30秒调用一次。
4. 额外选项
可以将额外的选项作为第二个参数传递给@Cron()
装饰器:
name
:在声明后访问和控制cron任务非常有用。timeZone
:指定执行的时区。这将相对于你的时区修改实际时间。如果时区无效,会抛出错误。你可以在Moment时区网站上查看所有可用的时区。utcOffset
:这允许你指定你的时区偏移量,而不是使用timeZone
参数。disabled
:这指示任务是否会被执行。
typescript
import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class NotificationService {
@Cron('* * 0 * * *', { name: 'notifications', timeZone: 'Europe/Paris' })
triggerNotifications() {}
}
五、声明间隔任务
要声明一个方法应该在(重复的)指定时间间隔内运行,将方法定义前缀为@Interval()
装饰器。将时间间隔值作为毫秒数传递给装饰器:
typescript
import { Injectable, Logger } from '@nestjs/common';
import { Interval } from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Interval(10000)
handleInterval() {
this.logger.debug('Called every 10 seconds');
}
}
本机制在底层使用JavaScript的setInterval()
函数。
六、声明超时任务
要声明一个在指定时间后运行(一次)的方法,使用@Timeout()
装饰器前缀。将从应用启动的相关时间偏移量(毫秒)传递给装饰器:
typescript
import { Injectable, Logger } from '@nestjs/common';
import { Timeout } from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Timeout(5000)
handleTimeout() {
this.logger.debug('Called once after 5 seconds');
}
}
本机制在底层使用JavaScript的setTimeout()
方法。
七、动态调度模块API
可以通过注入SchedulerRegistry
来动态管理定时任务(如启动、停止、删除):
typescript
import { Injectable } from '@nestjs/common';
import { SchedulerRegistry } from '@nestjs/schedule';
@Injectable()
export class TasksService {
constructor(private schedulerRegistry: SchedulerRegistry) {}
stopTask(name: string) {
const job = this.schedulerRegistry.getCronJob(name);
job.stop();
}
deleteTask(name: string) {
this.schedulerRegistry.deleteCronJob(name);
}
}
八、与其他装饰器的对比
装饰器 | 用途 | 时间单位 | 示例 |
---|---|---|---|
@Cron |
固定时间执行任务 | 秒级 | @Cron('0 0 12 * * *') |
@Interval |
间隔执行任务(毫秒级) | 毫秒 | @Interval(5000) |
@Timeout |
延迟执行一次任务(毫秒级) | 毫秒 | @Timeout(5000) |
九、注意事项
- 任务并发:默认情况下,如果前一个任务尚未完成,下一个任务会立即开始。如果需要串行执行,需在任务逻辑中控制(如使用锁或队列)。
- 调试 :使用
console.log
或日志工具记录任务执行时间,确保cron表达式正确。 - 异常处理 :在任务方法中添加
try - catch
块,避免因异常导致任务崩溃。 - 测试 :可以使用
setTime
和setTimeout
模拟时间变化,测试cron表达式是否符合预期。
十、完整示例
typescript
import { Injectable, Cron, CronExpression, SchedulerRegistry } from '@nestjs/schedule';
import { Logger } from '@nestjs/common';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
constructor(private schedulerRegistry: SchedulerRegistry) {}
@Cron(CronExpression.EVERY_5_SECONDS, { name: 'five - seconds - task', timeZone: 'Asia/Shanghai' })
handleEveryFiveSeconds() {
this.logger.debug('This runs every 5 seconds in Shanghai time');
}
@Cron('0 0 12 * * *', { name: 'daily - task' })
handleDailyTask() {
this.logger.debug('This runs daily at 12:00 PM UTC');
}
stopDailyTask() {
const job = this.schedulerRegistry.getCronJob('daily - task');
job.stop();
}
}