## 定時任務
定時任務允許你按照指定的日期/時間、一定時間間隔或者一定時間后單次執行來調度(`scheduling`)任意代碼(方法/函數)。在`Linux`世界中,這經常通過操作系統層面的`cron`包等執行。在`Node.js`應用中,有幾個不同的包可以模擬 cron 包的功能。Nest 提供了`@nestjs/schedule`包,其集成了流行的 Node.js 的`node-cron`包,我們將在本章中應用該包。
### 安裝
我們首先從安裝需要的依賴開始。
~~~bash
$ npm install --save @nestjs/schedule
$ npm install --save-dev @types/cron
~~~
要激活工作調度,從根`AppModule`中導入`ScheduleModule`并運行`forRoot()`靜態方法,如下:
> app.module.ts
```typescript
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
@Module({
imports: [ScheduleModule.forRoot()],
})
export class AppModule {}
```
`.forRoot()`調用初始化調度器并且注冊在你應用中任何聲明的`cron jobs`,`timeouts`和`intervals`。注冊開始于`onApplicationBootstrap`生命周期鉤子發生時,保證所有模塊都已經載入,任何計劃工作已經聲明。
### 聲明計時工作(cron job)
一個計時工作調度任何函數(方法調用)以自動運行, 計時工作可以:
- 單次,在指定日期/時間
- 重復循環:重復工作可以在指定周期中指定執行(例如,每小時,每周,或者每 5 分鐘)
在包含要運行代碼的方法定義前使用`@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');
}
}
```
在這個例子中,`handleCron()`方法將在當前時間為`45秒`時定期執行。換句話說,該方法每分鐘執行一次,在第 45 秒執行。
`@Cron()`裝飾器支持標準的[cron patterns](http://crontab.org/):
- 星號通配符 (也就是 \*)
- 范圍(也就是 1-3,5)
- 步長(也就是 \*/2)
在上述例子中,我們給裝飾器傳遞了`45 * * * * *`,下列鍵展示了每個位置的計時模式字符串的意義:
```bash
* * * * * *
| | | | | |
| | | | | day of week
| | | | month
| | | day of month
| | hour
| minute
second (optional)
```
一些示例的計時模式包括:
| 名稱 | 含義 |
| ------------------- | ---------------------------------- |
| \* \* \* \* \* \* | 每秒 |
| 45 \* \* \* \* \* | 每分鐘第 45 秒 |
| _ 10 _ \* \* \* | 每小時,從第 10 分鐘開始 |
| 0 _/30 9-17 _ \* \* | 上午 9 點到下午 5 點之間每 30 分鐘 |
| 0 30 11 \* \* 1-5 | 周一至周五上午 11:30 |
`@nestjs/schedule`包提供一個方便的枚舉
```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_45_SECONDS)
handleCron() {
this.logger.debug('Called every 45 seconds');
}
}
```
在本例中,`handleCron()`方法每`45`秒執行一次。
可選地,你可以為將一個`JavaScript`的`Date`對象傳遞給`@Cron()`裝飾器。這樣做可以讓工作在指定日期執行一次。
> 使用`JavaScript`日期算法來關聯當前日期和計劃工作。`@Cron(new Date(Date.now()+10*1000))`用于在應用啟動 10 秒后運行。
你可以在聲明后訪問并控制一個定時任務,或者使用[動態 API](https://docs.nestjs.com/techniques/task-scheduling#dynamic-schedule-module-api)動態創建一個定時任務(其定時模式在運行時定義)。要通過 API 聲明定時任務,你必須通過將選項對象中的`name`屬性作為可選的第二個參數傳遞給裝飾器,從而將工作和名稱聯系起來。
```typescript
@Cron('* * 8 * * *', {
name: 'notifications',
})
triggerNotifications() {}
```
### 聲明間隔
要聲明一個以一定間隔運行的方法,使用`@Interval()`裝飾器前綴。以毫秒單位的`number`傳遞間隔值,如下:
```typescript
@Interval(10000)
handleInterval() {
this.logger.debug('Called every 10 seconds');
}
```
> 本機制在底層使用`JavaScript`的`setInterval()`函數。你也可以使用定期調度工作來應用一個定時任務。
如果你希望在聲明類之外通過[動態 API](https://docs.nestjs.com/techniques/task-scheduling#dynamic-schedule-module-api)控制你聲明的時間間隔。使用下列結構將名稱與間隔關聯起來。
```typescript
@Interval('notifications', 2500)
handleInterval() {}
```
動態 API 也支持動態創建時間間隔,間隔屬性在運行時定義,可以列出和刪除他們。
### 聲明延時任務
要聲明一個在指定時間后運行(一次)的方法,使用`@Timeout()`裝飾器前綴。將從應用啟動的相關時間偏移量(毫秒)傳遞給裝飾器,如下:
```typescript
@Timeout(5000)
handleTimeout() {
this.logger.debug('Called once after 5 seconds');
}
```
> 本機制在底層使用 JavaScript 的`setTimeout()`方法
如果你想要在聲明類之外通過動態 API 控制你聲明的超時時間,將超時時間和一個名稱以如下結構關聯:
```typescript
@Timeout('notifications', 2500)
handleTimeout() {}
```
動態 API 同時支持創建動態超時時間,超時時間在運行時定義,可以列舉和刪除他們。
### 動態規劃模塊 API
`@nestjs/schedule`模塊提供了一個支持管理聲明定時、超時和間隔任務的動態 API。該 API 也支持創建和管理動態定時、超時和間隔,這些屬性在運行時定義。
### 動態定時任務
使用`SchedulerRegistry`API 從你代碼的任何地方獲取一個`CronJob`實例的引用。首先,使用標準構造器注入`ScheduleRegistry`。
```typescript
constructor(private schedulerRegistry: SchedulerRegistry) {}
```
> 從`@nestjs/schedule`包導入`SchedulerRegistry`。
使用下列類,假設通過下列定義聲明一個定時任務:
```typescript
@Cron('* * 8 * * *', {
name: 'notifications',
})
triggerNotifications() {}
```
如下獲取本工作:
```typescript
const job = this.schedulerRegistry.getCronJob('notifications');
job.stop();
console.log(job.lastDate());
```
`getCronJob()`方法返回一個命名的定時任務。然后返回一個包含下列方法的`CronJob`對象:
- stop()-停止一個按調度運行的任務
- start()-重啟一個停止的任務
- setTime(time:CronTime)-停止一個任務,為它設置一個新的時間,然后再啟動它
- lastDate()-返回一個表示工作最后執行日期的字符串
- nextDates(count:number)-返回一個`moment`對象的數組(大小`count`),代表即將執行的任務日期
> 在`moment`對象中使用`toDate()`來渲染成易讀的形式。
使用`SchedulerRegistry.addCronJob()`動態創建一個新的定時任務,如下:
```typescript
addCronJob(name: string, seconds: string) {
const job = new CronJob(`${seconds} * * * * *`, () => {
this.logger.warn(`time (${seconds}) for job ${name} to run!`);
});
this.scheduler.addCronJob(name, job);
job.start();
this.logger.warn(
`job ${name} added for each minute at ${seconds} seconds!`,
);
}
```
在這個代碼中,我們使用`cron`包中的`CronJob`對象來創建定時任務。`CronJob`構造器采用一個定時模式(類似`@Cron()`[裝飾器](https://docs.nestjs.com/techniques/task-scheduling#declarative-cron-jobs)作為其第一個參數,以及一個將執行的回調函數作為其第二個參數。`SchedulerRegistry.addCronJob()`方法有兩個參數:一個`CronJob`名稱,以及一個`CronJob`對象自身。
> 記得在使用前注入`SchedulerRegistry`,從`cron`包中導入 `CronJob`。
使用`SchedulerRegistry.deleteCronJob()`方法刪除一個命名的定時任務,如下:
```typescript
deleteCron(name: string) {
this.scheduler.deleteCronJob(name);
this.logger.warn(`job ${name} deleted!`);
}
```
使用`SchedulerRegistry.getCronJobs()`方法列出所有定時任務,如下:
```typescript
getCrons() {
const jobs = this.scheduler.getCronJobs();
jobs.forEach((value, key, map) => {
let next;
try {
next = value.nextDates().toDate();
} catch (e) {
next = 'error: next fire date is in the past!';
}
this.logger.log(`job: ${key} -> next: ${next}`);
});
}
```
`getCronJobs()`方法返回一個`map`。在這個代碼中,我們遍歷該`map`并且嘗試獲取每個`CronJob`的`nextDates()`方法。在`CronJob`API 中,如果一個工作已經執行了并且沒有下一次執行的日期,將拋出異常。
### 動態間隔
使用`SchedulerRegistry.getInterval()`方法獲取一個時間間隔的引用。如上,使用標準構造注入`SchedulerRegistry`。
```typescript
constructor(private schedulerRegistry: SchedulerRegistry) {}
```
如下使用:
```typescript
const interval = this.schedulerRegistry.getInterval('notifications');
clearInterval(interval);
```
使用`SchedulerRegistry.addInterval() `方法創建一個新的動態間隔,如下:
```typescript
addInterval(name: string, seconds: string) {
const callback = () => {
this.logger.warn(`Interval ${name} executing at time (${seconds})!`);
};
const interval = setInterval(callback, seconds);
this.scheduler.addInterval(name, interval);
}
```
在該代碼中,我們創建了一個標準的 JavaScript 間隔,然后將其傳遞給`ScheduleRegistry.addInterval()`方法。該方法包括兩個參數:一個時間間隔的名稱,和時間間隔本身。
如下使用`SchedulerRegistry.deleteInterval()`刪除一個命名的時間間隔:
```typescript
deleteInterval(name: string) {
this.scheduler.deleteInterval(name);
this.logger.warn(`Interval ${name} deleted!`);
}
```
使用`SchedulerRegistry.getIntervals()`方法如下列出所有的時間間隔:
```typescript
getIntervals() {
const intervals = this.scheduler.getIntervals();
intervals.forEach(key => this.logger.log(`Interval: ${key}`));
}
```
### 動態超時
使用`SchedulerRegistry.getTimeout()`方法獲取一個超時引用,如上,使用標準構造注入`SchedulerRegistry`:
```typescript
constructor(private schedulerRegistry: SchedulerRegistry) {}
```
并如下使用:
```typescript
const timeout = this.schedulerRegistry.getTimeout('notifications');
clearTimeout(timeout);
```
使用`SchedulerRegistry.addTimeout()`方法創建一個新的動態超時,如下:
```typescript
addTimeout(name: string, seconds: string) {
const callback = () => {
this.logger.warn(`Timeout ${name} executing after (${seconds})!`);
});
const timeout = setTimeout(callback, seconds);
this.scheduler.addTimeout(name, timeout);
}
```
在該代碼中,我們創建了個一個標準的 JavaScript 超時任務,然后將其傳遞給`ScheduleRegistry.addTimeout()`方法,該方法包含兩個參數:一個超時的名稱,以及超時對象自身。
使用`SchedulerRegistry.deleteTimeout()`方法刪除一個命名的超時,如下:
```typescript
deleteTimeout(name: string) {
this.scheduler.deleteTimeout(name);
this.logger.warn(`Timeout ${name} deleted!`);
}
```
使用`SchedulerRegistry.getTimeouts()`方法列出所有超時任務:
```typescript
getTimeouts() {
const timeouts = this.scheduler.getTimeouts();
timeouts.forEach(key => this.logger.log(`Timeout: ${key}`));
}
```
### 示例
一個可用的例子見[這里](https://github.com/nestjs/nest/tree/master/sample/27-scheduling)。
- 介紹
- 概述
- 第一步
- 控制器
- 提供者
- 模塊
- 中間件
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 自定義裝飾器
- 基礎知識
- 自定義提供者
- 異步提供者
- 動態模塊
- 注入作用域
- 循環依賴
- 模塊參考
- 懶加載模塊
- 應用上下文
- 生命周期事件
- 跨平臺
- 測試
- 技術
- 數據庫
- Mongo
- 配置
- 驗證
- 緩存
- 序列化
- 版本控制
- 定時任務
- 隊列
- 日志
- Cookies
- 事件
- 壓縮
- 文件上傳
- 流式處理文件
- HTTP模塊
- Session(會話)
- MVC
- 性能(Fastify)
- 服務器端事件發送
- 安全
- 認證(Authentication)
- 授權(Authorization)
- 加密和散列
- Helmet
- CORS(跨域請求)
- CSRF保護
- 限速
- GraphQL
- 快速開始
- 解析器(resolvers)
- 變更(Mutations)
- 訂閱(Subscriptions)
- 標量(Scalars)
- 指令(directives)
- 接口(Interfaces)
- 聯合類型
- 枚舉(Enums)
- 字段中間件
- 映射類型
- 插件
- 復雜性
- 擴展
- CLI插件
- 生成SDL
- 其他功能
- 聯合服務
- 遷移指南
- Websocket
- 網關
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 適配器
- 微服務
- 概述
- Redis
- MQTT
- NATS
- RabbitMQ
- Kafka
- gRPC
- 自定義傳輸器
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 獨立應用
- Cli
- 概述
- 工作空間
- 庫
- 用法
- 腳本
- Openapi
- 介紹
- 類型和參數
- 操作
- 安全
- 映射類型
- 裝飾器
- CLI插件
- 其他特性
- 遷移指南
- 秘籍
- CRUD 生成器
- 熱重載
- MikroORM
- TypeORM
- Mongoose
- 序列化
- 路由模塊
- Swagger
- 健康檢查
- CQRS
- 文檔
- Prisma
- 靜態服務
- Nest Commander
- 問答
- Serverless
- HTTP 適配器
- 全局路由前綴
- 混合應用
- HTTPS 和多服務器
- 請求生命周期
- 常見錯誤
- 實例
- 遷移指南
- 發現
- 誰在使用Nest?