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; @Logger() logger: ILogger; @InjectEntityModel(TaskLogEntity) taskLogEntity: Repository; @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, { 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)); } } } } } }