import { App, Inject, Logger, Provide } from '@midwayjs/decorator';
|
import { BaseService } from '@cool-midway/core';
|
import { InjectEntityModel } from '@midwayjs/orm';
|
import { Repository } from 'typeorm';
|
import { TaskInfoEntity } from '../entity/info';
|
import { TaskLogEntity } from '../entity/log';
|
import { ILogger } from '@midwayjs/logger';
|
import { IMidwayWebApplication } from '@midwayjs/web';
|
import * as _ from 'lodash';
|
import { Utils } from '../../../comm/utils';
|
import { TaskInfoQueue } from '../queue/task';
|
|
/**
|
* 任务
|
*/
|
@Provide()
|
export class TaskInfoService extends BaseService {
|
@InjectEntityModel(TaskInfoEntity)
|
taskInfoEntity: Repository<TaskInfoEntity>;
|
|
@Logger()
|
logger: ILogger;
|
|
@InjectEntityModel(TaskLogEntity)
|
taskLogEntity: Repository<TaskLogEntity>;
|
|
@Inject()
|
taskInfoQueue: TaskInfoQueue;
|
|
@App()
|
app: IMidwayWebApplication;
|
|
@Inject()
|
utils: Utils;
|
|
/**
|
* 停止任务
|
* @param id
|
*/
|
async stop(id) {
|
const task = await this.taskInfoEntity.findOne({ id });
|
if (task) {
|
const job = await this.exist(task.id);
|
if (job) {
|
await this.taskInfoQueue.removeRepeatable(JSON.parse(task.repeatConf));
|
}
|
task.status = 0;
|
await this.taskInfoEntity.update(task.id, task);
|
await this.updateNextRunTime(task.id);
|
}
|
}
|
|
/**
|
* 开始任务
|
* @param id
|
* @param type
|
*/
|
async start(id, type?) {
|
const task = await this.taskInfoEntity.findOne({ id });
|
task.status = 1;
|
if (type) {
|
task.type = type;
|
}
|
await this.addOrUpdate(task);
|
}
|
|
/**
|
* 手动执行一次
|
* @param id
|
*/
|
async once(id) {
|
const task = await this.taskInfoEntity.findOne({ id });
|
if (task) {
|
await this.taskInfoQueue.add(
|
{
|
...task,
|
isOnce: true,
|
},
|
{
|
jobId: task.id.toString(),
|
removeOnComplete: true,
|
removeOnFail: true,
|
}
|
);
|
}
|
}
|
|
/**
|
* 检查任务是否存在
|
* @param jobId
|
*/
|
async exist(jobId) {
|
const result = await this.taskInfoQueue.getRepeatableJobs();
|
const ids = result.map(e => {
|
return e.id;
|
});
|
return ids.includes(jobId.toString());
|
}
|
|
/**
|
* 新增或修改
|
* @param params
|
*/
|
async addOrUpdate(params) {
|
let repeatConf;
|
await this.getOrmManager().transaction(async transactionalEntityManager => {
|
if (params.taskType === 0) {
|
params.limit = null;
|
params.every = null;
|
} else {
|
params.cron = null;
|
}
|
await transactionalEntityManager.save(TaskInfoEntity, params);
|
|
if (params.status === 1) {
|
const exist = await this.exist(params.id);
|
if (exist) {
|
await this.taskInfoQueue.removeRepeatable(
|
JSON.parse(params.repeatConf)
|
);
|
}
|
const { every, limit, startDate, endDate, cron } = params;
|
const repeat = {
|
every,
|
limit,
|
jobId: params.id,
|
startDate,
|
endDate,
|
cron,
|
};
|
await this.utils.removeEmptyP(repeat);
|
const result = await this.taskInfoQueue.add(params, {
|
jobId: params.id,
|
removeOnComplete: true,
|
removeOnFail: true,
|
repeat,
|
});
|
if (!result) {
|
throw new Error('任务添加失败,请检查任务配置');
|
}
|
// await transactionalEntityManager.update(TaskInfoEntity, params.id, {
|
// jobId: opts.jobId,
|
// });
|
repeatConf = result.opts;
|
}
|
});
|
if (params.status === 1) {
|
this.utils.sleep(1000);
|
await this.updateNextRunTime(params.id);
|
await this.nativeQuery(
|
'update task_info a set a.repeatConf = ? where a.id = ?',
|
[JSON.stringify(repeatConf.repeat), params.id]
|
);
|
}
|
}
|
|
/**
|
* 删除
|
* @param ids
|
*/
|
async delete(ids) {
|
let idArr;
|
if (ids instanceof Array) {
|
idArr = ids;
|
} else {
|
idArr = ids.split(',');
|
}
|
for (const id of idArr) {
|
const task = await this.taskInfoEntity.findOne({ id });
|
const exist = await this.exist(task.id);
|
if (exist) {
|
await this.taskInfoQueue.removeRepeatable(JSON.parse(task.repeatConf));
|
}
|
await this.taskInfoEntity.delete({ id });
|
await this.taskLogEntity.delete({ taskId: id });
|
}
|
}
|
|
/**
|
* 任务日志
|
* @param query
|
*/
|
async log(query) {
|
const { id, status } = query;
|
return await this.sqlRenderPage(
|
`
|
SELECT
|
a.*,
|
b.NAME AS taskName
|
FROM
|
task_log a
|
JOIN task_info b ON a.taskId = b.id
|
where 1=1
|
${this.setSql(id, 'and a.taskId = ?', [id])}
|
${this.setSql(status, 'and a.status = ?', [status])}
|
`,
|
query
|
);
|
}
|
|
/**
|
* 保存任务记录,成功任务每个任务保留最新20条日志,失败日志不会删除
|
* @param task
|
* @param status
|
* @param detail
|
*/
|
async record(task, status, detail?) {
|
await this.taskLogEntity.save({
|
taskId: task.id,
|
status,
|
detail: detail || '',
|
});
|
await this.nativeQuery(
|
`DELETE a
|
FROM
|
task_log a,
|
( SELECT id FROM task_log where taskId = ? AND status = 1 ORDER BY id DESC LIMIT ?, 1 ) b
|
WHERE
|
a.taskId = ? AND
|
a.status = 1 AND
|
a.id < b.id`,
|
[task.id, 19, task.id]
|
); // 日志保留最新的20条
|
}
|
|
/**
|
* 初始化任务
|
*/
|
async initTask() {
|
const runningTasks = await this.taskInfoEntity.find({ status: 1 });
|
if (!_.isEmpty(runningTasks)) {
|
for (const task of runningTasks) {
|
const job = await this.exist(task.id); // 任务已存在就不添加
|
if (!job) {
|
this.logger.info(`init task ${task.name}`);
|
await this.addOrUpdate(task);
|
}
|
}
|
}
|
}
|
|
/**
|
* 任务ID
|
* @param jobId
|
*/
|
async getNextRunTime(jobId) {
|
let nextRunTime;
|
const result = await this.taskInfoQueue.getRepeatableJobs();
|
const task = _.find(result, { id: jobId + '' });
|
if (task) {
|
nextRunTime = new Date(task.next);
|
}
|
return nextRunTime;
|
}
|
|
/**
|
* 更新下次执行时间
|
* @param jobId
|
*/
|
async updateNextRunTime(jobId) {
|
await this.nativeQuery(
|
'update task_info a set a.nextRunTime = ? where a.id = ?',
|
[await this.getNextRunTime(jobId), jobId]
|
);
|
}
|
|
/**
|
* 刷新任务状态
|
*/
|
async updateStatus(jobId) {
|
const result = await this.taskInfoQueue.getRepeatableJobs();
|
const job = _.find(result, { id: jobId + '' });
|
// @ts-ignore
|
const task = await this.taskInfoEntity.findOne({ id: job.id });
|
const nextTime = await this.getNextRunTime(task.id);
|
if (task) {
|
if (task.nextRunTime.getTime() == nextTime.getTime()) {
|
task.status = 0;
|
task.nextRunTime = nextTime;
|
this.taskInfoQueue.removeRepeatableByKey(job.key);
|
} else {
|
task.nextRunTime = nextTime;
|
}
|
await this.taskInfoEntity.update(task.id, task);
|
}
|
}
|
|
/**
|
* 调用service
|
* @param serviceStr
|
*/
|
async invokeService(serviceStr) {
|
if (serviceStr) {
|
const arr = serviceStr.split('.');
|
const service = await this.app.getApplicationContext().getAsync(arr[0]);
|
for (const child of arr) {
|
if (child.includes('(')) {
|
const lastArr = child.split('(');
|
const param = lastArr[1].replace(')', '');
|
if (!param) {
|
return service[lastArr[0]]();
|
} else {
|
return service[lastArr[0]](JSON.parse(param));
|
}
|
}
|
}
|
}
|
}
|
}
|