ibird-task
Version:
A task addon for ibird.
163 lines (152 loc) • 4.81 kB
JavaScript
/**
* 模块依赖
*/
const utility = require('ibird-utils');
const CronJob = require('cron').CronJob;
const namespace = 'ibird-task';
const api = { tasks: {} };
const context = {};
const configs = {}, runnings = {};
/**
* 加载插件
* @param app
* @param options
*/
function onload(app, options) {
context.app = app;
context.options = options;
if (options && typeof options.dir === 'string') {
api.mountTasksDir(options.dir);
}
}
/**
* 新增任务
* @param {Object} opts - 任务对象
* @param {string} opts.name - 任务名称
* @param {string} opts.cronTime - 触发时间('cron'语法或'Date'对象的形式)
* cron格式:[*] * * * * *
* Seconds: 0-59
* Minutes: 0-59
* Hours: 0-23
* Day of Month: 1-31
* Months: 0-11
* Day of Week: 0-6
* @param {function} opts.onTick - 触发时的执行函数
* @param {function} [opts.onComplete] - 任务完成时的执行函数(即当任务被'stop()'的时候触发)
* @param {boolean} [opts.start] - 是否立即启动(默认true,表示任务对象不需要手动执行'job.start()'来启动)
* @param {boolean} [opts.oneOff] - 是否为一次性任务(默认false,表示任务对象仅会被执行一次,当执行成功后就会被自动清理)
* @param {string} [opts.timeZone] - 指定运行时区(详见http://momentjs.com/timezone/)
* @param {string} [opts.context] - 任务运行函数的上下文对象(对应函数内部的'this',指定后函数内部不能再通过'this'调用'stop()')
* @param {string} [opts.runOnInit] - 是否立即触发一次执行(默认为false)
* @param {string} [opts.runMode] - 任务运行模式,可选值为:S(串行模式)或P(并行模式);默认为S,即需要等待上一次任务完成后才会触发下一次执行
*/
api.addTask = function (opts) {
if (!opts || !opts.name) return;
if (!opts.onTick || typeof opts.onTick !== 'function') return;
const app = context.app;
if (typeof opts.runOnInit === 'number') {
const delay = opts.runOnInit;
opts.runOnInit = true;
setTimeout(() => { api.addTask(opts) }, delay);
return;
}
if (api.tasks[opts.name]) {
api.delTask(opts.name);
}
if (typeof opts.start !== 'boolean') {
opts.start = true;
}
opts.oneOff = !!opts.oneOff;
opts.runMode = opts.runMode || 'serial';
opts.runMode = opts.runMode.toLowerCase();
opts.runMode = ['serial', 'parallel', 's', 'p'].indexOf(opts.runMode) >= 0 ? opts.runMode : 'serial';
const onTick = opts.onTick;
opts.onTick = async () => {
if (['serial', 's'].indexOf(opts.runMode) >= 0 && runnings[opts.name]) return;
app.info(`task '${opts.name}' is up.`);
try {
runnings[opts.name] = true;
await onTick.call(this);
} finally {
delete runnings[opts.name];
}
app.info(`task '${opts.name}' is completed.\n`);
// 清理一次性任务
if (opts.oneOff && api.getTask(opts.name)) {
api.delTask(opts.name);
}
}
api.tasks[opts.name] = new CronJob(opts);
configs[opts.name] = opts;
return api.tasks[opts.name];
}
/**
* 删除任务
* @param {string} name - 任务名称
*/
api.delTask = function (name) {
if (!name || !api.tasks[name]) return false;
try {
api.tasks[name].stop();
} catch (error) { }
delete api.tasks[name];
delete configs[name];
return true;
}
/**
* 更新任务
* @param {string} name - 原任务名称
* @param {Object} opts - 任务对象
*/
api.updateTask = function (name, opts) {
if (!name || !api.tasks[name] || !configs[name] || typeof opts !== 'object') return null;
const config = Object.assign({}, configs[name]);
api.delTask(name);
Object.assign(config, opts);
opts.name = opts.name || name;
return api.addTask(config);
}
/**
* 获取任务
* @param {string} name - 任务名称
*/
api.getTask = function (name) {
return name ? api.tasks[name] : api.tasks;
}
/**
* 批量挂载任务目录
* @param {string} dir - 任务目录
*/
api.mountTasksDir = function (dir) {
utility.recursiveDir(dir, api.addTask);
}
/**
* 任务列表路由
* @param {Object} ctx
*/
function tasksRoute(ctx) {
const data = {};
for (const name in configs) {
const cfg = configs[name]
data[name] = Object.assign({
running: !!api.getTask(name)
}, cfg);
}
ctx.body = { data };
}
/**
* 导出模块
*/
module.exports = {
namespace,
onload,
api,
routes: {
'tasks': {
name: 'tasks',
method: 'GET',
path: '/tasks',
middleware: tasksRoute
}
}
};