三类定时任务管理模块
在实际项目中,定时任务逻辑不应该全部堆叠在一个 TasksService 中。我们需要按职责拆分 Service,创建统一的任务管理模块(TasksModule),并实现动态的定时任务管理能力,包括添加、删除和查询任务状态。
定时任务的适用场景
在深入实现之前,先明确 @nestjs/schedule 适合的任务类型:
| 类型 | 说明 | 示例 |
|---|---|---|
| 周期性任务 | 按固定时间间隔重复执行 | 每小时日志备份、每天数据清理 |
| 简单查询任务 | 无需持久化的状态检查 | 定时探测服务健康状态、接口可用性 |
| 轻量级任务 | 低频低并发的操作 | 发送汇总邮件、更新缓存 |
不适合的场景:
- 秒级精度的定时通知(如"在 2024-01-01 10:00:00 发送邮件")-- 应使用任务队列(Bull)
- 大量高并发任务 -- 应使用消息队列(RabbitMQ、Kafka 等)
- 需要持久化的复杂任务流程 -- 应使用工作流引擎
Schedule + Queue 配合使用的典型案例: 每天定时检查用户签到状态,未签到的用户添加一条任务队列消息,在第二天早上 8 点通过 APP 推送通知提醒。
拆分 Cron Service
按职责创建独立的定时任务 Service:
cron/
├── tasks/
│ └── log-db.cron.service.ts # Database backup cron task
├── cron.providers.ts # Provider collection
├── tasks.module.ts # Unified management module
└── index.ts # Export entry
text
log-db.cron.service.ts -- 数据库备份定时任务:
import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { SshService } from '../../common/utils/ssh/ssh.service';
@Injectable()
export class LogDbCronService {
@Cron('0 0 * * * *', { name: 'logDBCron' })
async handleLogDbBackup() {
// Database backup logic via SSH
}
}
typescript
cron.providers.ts -- 统一收集所有 Cron Provider:
import { LogDbCronService } from './tasks/log-db.cron.service';
export const cronProviders = [
LogDbCronService,
// Add more cron services here
];
typescript
创建 TasksModule 统一管理
TasksModule 负责注册所有定时任务 Service,并提供 SchedulerRegistry 以支持动态任务管理:
// tasks.module.ts
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { TasksService } from './tasks.service';
import { cronProviders } from './cron.providers';
@Module({
imports: [ScheduleModule.forRoot()],
providers: [...cronProviders, TasksService],
exports: [TasksService],
})
export class TasksModule {}
typescript
关键点: ScheduleModule.forRoot() 必须注册在包含 Cron Service 的同一个模块中,这样 Service 之间才能共享 SchedulerRegistry 的 Provider。
TasksService -- 动态任务管理
TasksService 提供三种类型的动态任务管理方法:
1. Cron Job 管理
import { Injectable, Logger } from '@nestjs/common';
import { SchedulerRegistry } from '@nestjs/schedule';
import { CronJob } from 'cron';
@Injectable()
export class TasksService {
private logger = new Logger(TasksService.name);
constructor(private schedulerRegistry: SchedulerRegistry) {}
// Add a dynamic cron job
addCronJob(name: string, cronTime: string, callback: () => void): void {
const job = new CronJob(cronTime, callback);
this.schedulerRegistry.addCronJob(name, job);
job.start();
this.logger.log(`Job "${name}" added and started`);
}
// Delete a cron job
deleteCronJob(name: string): void {
this.schedulerRegistry.deleteCronJob(name);
this.logger.warn(`Job "${name}" deleted`);
}
// Get all cron jobs
getCronJobs() {
const jobs = this.schedulerRegistry.getCronJobs();
const result: Record<string, any> = {};
jobs.forEach((job, name) => {
result[name] = {
nextRun: job.nextDate()?.toISO(),
isActive: job.isActive,
};
});
return result;
}
}
typescript
2. Interval 管理
// Add a repeating interval
addInterval(name: string, milliseconds: number, callback: () => void): void {
const interval = setInterval(callback, milliseconds);
this.schedulerRegistry.addInterval(name, interval);
this.logger.log(`Interval "${name}" added`);
}
// Delete an interval
deleteInterval(name: string): void {
this.schedulerRegistry.deleteInterval(name);
this.logger.warn(`Interval "${name}" deleted`);
}
// Get all intervals
getIntervals() {
const intervals = this.schedulerRegistry.getIntervals();
return Array.from(intervals.keys());
}
typescript
3. Timeout 管理
// Add a one-time timeout
addTimeout(name: string, milliseconds: number, callback: () => void): void {
const timeout = setTimeout(callback, milliseconds);
this.schedulerRegistry.addTimeout(name, timeout);
this.logger.log(`Timeout "${name}" added`);
}
// Delete a timeout
deleteTimeout(name: string): void {
this.schedulerRegistry.deleteTimeout(name);
this.logger.warn(`Timeout "${name}" deleted`);
}
// Get all timeouts
getTimeouts() {
const timeouts = this.schedulerRegistry.getTimeouts();
return Array.from(timeouts.keys());
}
typescript
测试动态任务管理
在 Controller 中添加测试接口:
// app.controller.ts
@Controller()
export class AppController {
constructor(private tasksService: TasksService) {}
@Get('add-job')
addJob() {
this.tasksService.addCronJob(
'test',
'* * * * * *', // Every second
() => console.log('Hello from scheduled job!'),
);
return { message: 'created' };
}
@Get('delete-job')
deleteJob() {
this.tasksService.deleteCronJob('test');
return { message: 'deleted' };
}
@Get('get-jobs')
getJobs() {
return this.tasksService.getCronJobs();
}
}
typescript
测试流程:
GET /add-job-- 创建名为test的每秒执行任务,控制台开始输出日志GET /get-jobs-- 查看当前所有任务及其下次执行时间GET /delete-job-- 删除test任务,控制台停止输出- 再次
GET /get-jobs-- 确认test任务已被移除,仅保留logDBCron等预设任务
为预设任务命名
通过 @Cron 装饰器的 name 选项为任务指定可读名称,替代默认的 UUID 标识:
@Cron('0 0 * * * *', { name: 'logDBCron' })
async handleLogDbBackup() {
// ...
}
typescript
这样在 getJobs() 的返回结果中,任务名称为 logDBCron 而非一长串 UUID,便于管理和调试。
Conditional 模块集成
在 conditional.module.ts 中引入 TasksModule:
imports.push(TasksModule);
typescript
这样 TasksModule 中的所有定时任务会在满足条件时自动注册。
本节总结
- 按职责拆分定时任务 Service,使用
cronProviders统一管理 - 创建
TasksModule集中管理所有定时任务,包含ScheduleModule.forRoot()注册 - 实现三类动态任务管理:Cron Job(周期性)、Interval(间隔循环)、Timeout(一次性延迟)
- 使用
SchedulerRegistry提供 API 级别的任务增删查能力 - 为预设任务指定
name,便于通过管理接口识别和操作
↑