定时任务:nestjs-schedule 模块注册与源码解析
定时任务概述
定时任务(Scheduled Tasks)在服务端开发中非常常见。Linux 系统的 cron、Windows 的"计划任务"都是操作系统级别的定时任务方案。NestJS 提供了 @nestjs/schedule 模块,将定时任务集成到应用内部,具备以下优势:
- 跨平台:不依赖操作系统,只要有 Node.js 运行时即可
- 与应用生命周期绑定:定时任务随应用启动而注册,随应用关闭而清理
- 支持静态和动态任务:既可以在代码中声明式定义,也可以在运行时动态添加
常见的定时任务应用场景包括:
- 日志数据的定时备份与清理
- 计划类型业务的状态推进与通知
- 系统健康检查与数据同步
安装依赖
pnpm add @nestjs/schedule
bash
模块注册
在 AppModule 中注册 ScheduleModule:
import { Module } from '@nestjs/common'
import { ScheduleModule } from '@nestjs/schedule'
@Module({
imports: [
ScheduleModule.forRoot(),
],
})
export class AppModule {}
typescript
ScheduleModule.forRoot() 支持传入选项参数,可以按需开启或关闭特定类型的调度器:
ScheduleModule.forRoot({
cronJobs: true, // 启用 cron 任务(默认 true)
intervals: true, // 启用间隔任务(默认 true)
timeouts: true, // 启用超时任务(默认 true)
})
typescript
也支持异步注册方式 forRootAsync(),适合需要从配置服务中读取选项的场景:
ScheduleModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
cronJobs: config.get<boolean>('CRON_ON', true),
}),
inject: [ConfigService],
})
typescript
条件注册
由于定时任务在开发阶段可能不需要一直运行,我们可以通过环境变量 CRON_ON 来控制是否注册 ScheduleModule:
// conditional.module.ts
import { DynamicModule, Module } from '@nestjs/common'
import { ScheduleModule } from '@nestjs/schedule'
import { ConfigService } from '@nestjs/config'
@Module({})
export class ConditionalModule {
static register(configService: ConfigService): DynamicModule {
const imports: DynamicModule['imports'] = []
const providers: any[] = []
const cronOn = configService.get<boolean>('CRON_ON', true)
if (cronOn) {
imports.push(ScheduleModule.forRoot())
providers.push(TasksService)
}
return {
module: ConditionalModule,
imports,
providers,
exports: providers,
}
}
}
typescript
创建定时任务服务
在 common/cron/ 目录下创建 tasks.service.ts:
import { Injectable } from '@nestjs/common'
import { Cron } from '@nestjs/schedule'
@Injectable()
export class TasksService {
@Cron('* * * * * *')
handleCron() {
console.log('test')
}
}
typescript
@Cron() 装饰器接收一个 cron 表达式,格式为:
┌──────── 秒 (0-59)
│ ┌────── 分 (0-59)
│ │ ┌──── 时 (0-23)
│ │ │ ┌── 日 (1-31)
│ │ │ │ ┌ 月 (1-12)
│ │ │ │ │ ┌ 星期 (0-7, 0和7都代表周日)
│ │ │ │ │ │
* * * * * *
text
六个 * 表示每秒执行一次,用于开发调试非常方便。
常见的 Cron 表达式
| 表达式 | 说明 |
|---|---|
* * * * * * | 每秒执行 |
*/10 * * * * * | 每 10 秒执行 |
0 * * * * * | 每分钟执行 |
0 0 * * * * | 每小时执行 |
0 0 2 * * * | 每天凌晨 2 点执行 |
0 0 0 * * 1 | 每周一凌晨执行 |
SWC 编译配置注意
如果项目使用 SWC 编译器,需要确保 .swcrc 配置中 drop_console 设为 false,否则 console.log 语句会被编译时移除,导致定时任务看起来没有执行:
{
"jsc": {
"minify": {
"drop_console": false
},
"mangle": false
}
}
json
源码解析:任务注册机制
@nestjs/schedule 的任务注册发生在 onApplicationBootstrap 生命周期钩子中。查看官方源码中的 schedule.orchestrator.ts:
// 简化的源码逻辑
export class ScheduleOrchestrator implements OnApplicationBootstrap, OnApplicationShutdown {
onApplicationBootstrap() {
// 注册所有通过装饰器定义的任务
this.registerTimeouts()
this.registerIntervals()
this.registerCronJobs()
}
onApplicationShutdown() {
// 应用关闭时清理所有定时器和任务
this.clearTimeouts()
this.clearIntervals()
this.closeCronJobs()
}
}
typescript
这意味着:
- 定时任务在应用完全启动后才生效
- 应用优雅关闭时,所有定时器会被自动清理
- 任务与 NestJS 应用生命周期完全绑定
三种调度器类型
| 类型 | 装饰器 | 说明 |
|---|---|---|
| Cron Job | @Cron() | 按 cron 表达式周期执行 |
| Interval | @Interval(ms) | 按固定间隔(毫秒)执行 |
| Timeout | @Timeout(ms) | 延迟指定毫秒后执行一次 |
@Injectable()
export class TasksService {
@Cron('0 0 2 * * *')
handleDailyBackup() {
// 每天凌晨 2 点执行备份
}
@Interval(60000)
handleHealthCheck() {
// 每分钟执行健康检查
}
@Timeout(5000)
handleStartup() {
// 应用启动 5 秒后执行一次
}
}
typescript
小结
@nestjs/schedule提供了声明式的定时任务定义方式,通过装饰器即可创建 Cron、Interval、Timeout 三种类型的任务- 模块通过
ScheduleModule.forRoot()或forRootAsync()注册 - 任务在
onApplicationBootstrap钩子中注册,在onApplicationShutdown钩子中清理 - 可以通过环境变量控制是否启用定时任务,灵活切换开发/生产模式
- 注意 SWC 编译器的
drop_console配置,避免调试时误删日志语句
↑