Skip to content

Commit

Permalink
feat: add midway task component (#995)
Browse files Browse the repository at this point in the history
  • Loading branch information
stone-jin committed Apr 16, 2021
1 parent 9dc9596 commit befb81d
Show file tree
Hide file tree
Showing 19 changed files with 480 additions and 16 deletions.
146 changes: 146 additions & 0 deletions packages/task/README.md
@@ -0,0 +1,146 @@
# Midway-task

## 简介
Midway-task是为了能解决任务系列的模块,例如分布式定时任务、延迟任务调度。例如订单2小时后失效、每日定时的数据处理等工作。

## 安装方法

```bash
tnpm install @midwayjs/task -S
```

## 使用方法

在Configuration.ts导入子组件

```typescript

import * as task from '@midwayjs/task';

@Configuration({
imports: [task],
importConfigs: [
join(__dirname, 'config')
]
})
export class AutoConfiguration{
}
```

配置:

在 config.default.ts 文件中配置对应的模块信息:

```typescript
export const taskConfig = {
redis: `redis://127.0.0.1:32768`,
prefix: 'midway-task',
defaultJobOptions: {
repeat: {
tz: "Asia/Shanghai"
}
}
}
```

## 业务代码编写方式

分布式定时任务:

```typescript
@Provide()
export class UserService {
@Inject()
helloService: HelloService;

// 例如下面是每分钟执行一次,并且是分布式任务
@Task({
repeat: { cron: '* * * * *'}
})
async test(){
console.log(this.helloService.getName())
}
}
```

本地定时任务:

```typescript
@Provide()
export class UserService {
@Inject()
helloService: HelloService;

// 例如下面是每分钟执行一次
@TaskLocal('* * * * * *')
async test(){
console.log(this.helloService.getName())
}
}
```

定时执行任务:

```typescript
@Provide()
export class UserService {
@Inject()
helloService: HelloService;

// 例如下面是每分钟执行一次
@TaskLocal('* * * * * *')
async test(){
console.log(this.helloService.getName())
}
}
```

让用户定义任务

```typescript

@Queue()
@Provide()
export class HelloTask{

@Inject()
service;

async excute(params){
console.log(params);
}
}
```

```typescript
import { QueueService } from '@midwayjs/task';
@Provide()
export class UserTask{

@Inject()
service;

@Inject()
queueService: QueueService;

async excute(params){
// 3秒后触发分布式任务调度。
const xxx = this.queueService.excute(HelloTask, params, {delay: 3000});
}
}

```
## 其他

关于task任务的配置:
```
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ |
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
│ │ │ │ └───── month (1 - 12)
│ │ │ └────────── day of month (1 - 31)
│ │ └─────────────── hour (0 - 23)
│ └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, optional)
```
9 changes: 9 additions & 0 deletions packages/task/jest.config.js
@@ -0,0 +1,9 @@
const path = require('path');

module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['<rootDir>/test/fixtures'],
coveragePathIgnorePatterns: ['<rootDir>/test/'],
setupFilesAfterEnv: [path.join(__dirname, 'test/.setup.js')]
};
40 changes: 40 additions & 0 deletions packages/task/package.json
@@ -0,0 +1,40 @@
{
"name": "@midwayjs/task",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"scripts": {
"build": "midway-bin build -c",
"test": "midway-bin test --ts",
"cov": "midway-bin cov --ts",
"ci": "npm run cov"
},
"keywords": [],
"author": "",
"files": [
"dist/**/*.js",
"dist/**/*.d.ts"
],
"license": "MIT",
"devDependencies": {
"@midwayjs/cli": "^1.2.38",
"@midwayjs/core": "^2.3.0",
"@midwayjs/decorator": "^2.3.0",
"@midwayjs/koa": "^2.10.6",
"@midwayjs/mock": "^2.10.6",
"@types/bull": "^3.15.0",
"@types/cron": "^1.7.2",
"@types/jest": "^26.0.10",
"@types/node": "14",
"cross-env": "^6.0.0",
"jest": "^26.4.0",
"mwts": "^1.0.5",
"ts-jest": "^26.2.0",
"typescript": "^4.0.0"
},
"dependencies": {
"bull": "^3.22.0",
"cron": "^1.8.2"
}
}
9 changes: 9 additions & 0 deletions packages/task/src/config/config.default.ts
@@ -0,0 +1,9 @@
export const taskConfig = {
redis: 'redis://127.0.0.1:32768',
prefix: 'midway-task',
defaultJobOptions: {
repeat: {
tz: 'Asia/Shanghai',
},
},
};
114 changes: 114 additions & 0 deletions packages/task/src/configuration.ts
@@ -0,0 +1,114 @@
// src/configuration.ts
import {
App,
Config,
Configuration,
getClassMetadata,
listModule,
} from '@midwayjs/decorator';
import { join } from 'path';
import { IMidwayApplication, IMidwayContainer } from '@midwayjs/core';
import {
MODULE_TASK_KEY,
MODULE_TASK_METADATA,
MODULE_TASK_QUEUE_KEY,
MODULE_TASK_QUEUE_OPTIONS,
MODULE_TASK_TASK_LOCAL_KEY,
MODULE_TASK_TASK_LOCAL_OPTIONS,
} from './const';
import * as Bull from 'bull';
import { CronJob } from 'cron';

@Configuration({
namespace: 'task',
importConfigs: [join(__dirname, 'config')],
})
export class AutoConfiguration {
@App()
app;

@Config('taskConfig')
taskConfig;

queueList: any[] = [];
jobList: any[] = [];

async onReady(
container: IMidwayContainer,
app: IMidwayApplication
): Promise<void> {
await this.loadTask();
await this.loadLocalTask();
await this.loadQueue(container);
}

async onStop() {
this.queueList.map(queue => {
queue.stop();
});
this.jobList.map(job => {
job.stop();
});
}

async loadTask() {
const modules = listModule(MODULE_TASK_KEY);
for (const module of modules) {
const rules = getClassMetadata(MODULE_TASK_METADATA, module);
for (const rule of rules) {
const queue = new Bull(
`${rule.name}:${rule.propertyKey}`,
this.taskConfig
);
queue.process(async job => {
const ctx = this.app.createAnonymousContext();
const service = await ctx.requestContext.getAsync(module);
rule.value.call(service, job.data);
});
queue.add({}, rule.options);
this.queueList.push(queue);
}
}
}

async loadLocalTask() {
const modules = listModule(MODULE_TASK_TASK_LOCAL_KEY);
for (const module of modules) {
const rules = getClassMetadata(MODULE_TASK_TASK_LOCAL_OPTIONS, module);
for (const rule of rules) {
const job = new CronJob(
rule.options,
async () => {
const ctx = this.app.createAnonymousContext();
const service = await ctx.requestContext.getAsync(module);
rule.value.call(service);
},
null,
true,
this.taskConfig.defaultJobOptions.repeat.tz
);
job.start();
this.jobList.push(job);
}
}
}

async loadQueue(container) {
const modules = listModule(MODULE_TASK_QUEUE_KEY);
const queueMap = {};
const config = JSON.parse(JSON.stringify(this.taskConfig));
delete config.defaultJobOptions.repeat;
for (const module of modules) {
const rule = getClassMetadata(MODULE_TASK_QUEUE_OPTIONS, module);
const queue = new Bull(`${rule.name}:excute`, config);
queue.process(async job => {
const ctx = this.app.createAnonymousContext();
const service = await ctx.requestContext.getAsync(module);
await service.excute.call(service, job.data);
});
queueMap[`${rule.name}:excute`] = queue;
this.queueList.push(queue);
}
container.registerObject('queueMap', queueMap);
}
}
6 changes: 6 additions & 0 deletions packages/task/src/const.ts
@@ -0,0 +1,6 @@
export const MODULE_TASK_KEY = '@midway/task';
export const MODULE_TASK_METADATA = '@midway/task:task';
export const MODULE_TASK_TASK_LOCAL_KEY = '@midway/task:task_local';
export const MODULE_TASK_TASK_LOCAL_OPTIONS = '@midway/task:task_local:options';
export const MODULE_TASK_QUEUE_KEY = '@midway/task:queue';
export const MODULE_TASK_QUEUE_OPTIONS = '@midway/task:queue:options';
16 changes: 16 additions & 0 deletions packages/task/src/decorator/queue.ts
@@ -0,0 +1,16 @@
import { saveModule, saveClassMetadata } from '@midwayjs/core';
import { MODULE_TASK_QUEUE_KEY, MODULE_TASK_QUEUE_OPTIONS } from '../const';

export function Queue(options?: any) {
return function (target) {
saveModule(MODULE_TASK_QUEUE_KEY, target);
saveClassMetadata(
MODULE_TASK_QUEUE_OPTIONS,
{
options,
name: target.constructor.name,
},
target
);
};
}
22 changes: 22 additions & 0 deletions packages/task/src/decorator/task.ts
@@ -0,0 +1,22 @@
import { saveModule, attachClassMetadata } from '@midwayjs/core';
import { MODULE_TASK_KEY, MODULE_TASK_METADATA } from '../const';

export function Task(options) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
saveModule(MODULE_TASK_KEY, target.constructor);
attachClassMetadata(
MODULE_TASK_METADATA,
{
options,
propertyKey,
value: descriptor.value,
name: target.constructor.name,
},
target
);
};
}
24 changes: 24 additions & 0 deletions packages/task/src/decorator/taskLocal.ts
@@ -0,0 +1,24 @@
import { saveModule, attachClassMetadata } from '@midwayjs/core';
import {
MODULE_TASK_TASK_LOCAL_KEY,
MODULE_TASK_TASK_LOCAL_OPTIONS,
} from '../const';

export function TaskLocal(options) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
saveModule(MODULE_TASK_TASK_LOCAL_KEY, target.constructor);
attachClassMetadata(
MODULE_TASK_TASK_LOCAL_OPTIONS,
{
options,
propertyKey,
value: descriptor.value,
},
target
);
};
}

0 comments on commit befb81d

Please sign in to comment.