@use-services/cron
Version:
```ts import * as Cron from "@use-services/cron"; import * as handlersCron from "@/handlersCron";
132 lines (131 loc) • 5.12 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.init = exports.Service = void 0;
const use_services_1 = require("use-services");
const cronParser = require("cron-parser");
const LOCK_SCRIPT = `
if redis.call("exists", KEYS[1]) == 1 then
return 0
end
redis.call("set", KEYS[1], ARGV[1], "PX", ARGV[2])
return 1
`;
class Service {
constructor(option) {
this.initialized = false;
if (option.deps.length !== 1) {
throw new Error("miss deps [redis]");
}
this.ns = option.app + ":" + option.srvName;
this.redis = option.deps[0];
this.redis.defineCommand("__cronLock", {
numberOfKeys: 1,
lua: LOCK_SCRIPT,
});
this.args = Object.assign({
pollInterval: 500,
}, option.args);
this._checkHandlers();
}
start() {
return __awaiter(this, void 0, void 0, function* () {
if (this.initialized) {
return;
}
this._addInterval();
yield this._runinterval();
this.initialized = true;
});
}
[use_services_1.STOP_KEY]() {
return __awaiter(this, void 0, void 0, function* () {
clearTimeout(this.timer);
});
}
_addInterval() {
if (this.timer)
clearTimeout(this.timer);
this.timer = setTimeout(() => this._runinterval(), this.args.pollInterval);
}
_runinterval() {
return __awaiter(this, void 0, void 0, function* () {
this._addInterval();
const keyLock = this._redisKey("lock");
// 只有一个进程获得了锁
const result = yield this.redis.__cronLock(keyLock, "", this.args.pollInterval);
if (result === 0)
return;
const now = Date.now();
const sec = Math.floor(Date.now() / 1000);
const keyLastRunAts = this._redisKey("lastRunAts");
const lastRunAtsRaw = (yield this.redis.get(keyLastRunAts)) || "{}";
const lastRunAts = JSON.parse(lastRunAtsRaw);
Object.keys(this.args.crons).forEach((name) => lastRunAts[name] || (lastRunAts[name] = "" + sec));
const schedules = yield this._getSchedules(now, lastRunAts);
schedules.map((ctx) => __awaiter(this, void 0, void 0, function* () {
if (ctx.runAts.length > 0) {
lastRunAts[ctx.name] = Math.floor(ctx.runAts[ctx.runAts.length - 1].getTime() / 1000);
const handler = this.args.handlers[ctx.name];
if (handler)
yield handler(ctx);
}
}));
yield this.redis.set(keyLastRunAts, JSON.stringify(lastRunAts));
});
}
_redisKey(...args) {
return this.ns + ":" + args.join(":");
}
_getSchedules(now, lastRunAts) {
return __awaiter(this, void 0, void 0, function* () {
const result = [];
for (const name in this.args.crons) {
const cron = this.args.crons[name];
const currentDate = new Date(parseInt(lastRunAts[name]) * 1000);
const interval = cronParser.parseExpression(cron, {
currentDate: currentDate,
});
const runAts = [];
while (true) {
const obj = interval.next();
if (obj.getTime() > now) {
result.push({ name, cron, runAts, nextRunAt: obj });
break;
}
runAts.push(obj);
}
}
return result;
});
}
_checkHandlers() {
const { crons, handlers } = this.args;
const misHandlers = [];
Object.keys(crons).forEach((key) => {
if (!handlers[key]) {
misHandlers.push(key);
}
});
if (misHandlers.length > 0) {
throw new Error(`cron: miss handlers ${misHandlers.join(",")}`);
}
}
}
exports.Service = Service;
function init(option) {
return __awaiter(this, void 0, void 0, function* () {
const srv = new (option.ctor || Service)(option);
option.emitter.on(use_services_1.SERVICES_EVENTS.INIT_END, () => srv.start());
return srv;
});
}
exports.init = init;