UNPKG

@use-services/cron

Version:

```ts import * as Cron from "@use-services/cron"; import * as handlersCron from "@/handlersCron";

132 lines (131 loc) 5.12 kB
"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;