From 82cc9e1c6f4bdb8afba892769f6d490bea0ca371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=A3=AE?= Date: Wed, 7 Nov 2018 21:31:10 +0800 Subject: [PATCH] feat: init midway-schedule --- packages/midway-schedule/agent.ts | 11 ++ packages/midway-schedule/app.ts | 100 ++++++++++++++++++ packages/midway-schedule/app/extend/agent.ts | 1 + packages/midway-schedule/lib/load_schedule.ts | 69 ++++++++++++ packages/midway-schedule/lib/metaKeys.ts | 4 + packages/midway-schedule/package.json | 23 ++++ .../test/fixtures/worker/config/plugin.js | 3 + .../test/fixtures/worker/package.json | 3 + .../worker/src/lib/schedule/interval.ts | 21 ++++ .../midway-schedule/test/schedule.test.ts | 94 ++++++++++++++++ packages/midway-schedule/tsconfig.json | 21 ++++ packages/midway-web/config/plugin.js | 6 ++ packages/midway-web/package.json | 3 +- .../midway-web/src/decorators/application.ts | 14 ++- .../midway-web/src/decorators/metaKeys.ts | 5 + 15 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 packages/midway-schedule/agent.ts create mode 100644 packages/midway-schedule/app.ts create mode 100644 packages/midway-schedule/app/extend/agent.ts create mode 100644 packages/midway-schedule/lib/load_schedule.ts create mode 100644 packages/midway-schedule/lib/metaKeys.ts create mode 100644 packages/midway-schedule/package.json create mode 100644 packages/midway-schedule/test/fixtures/worker/config/plugin.js create mode 100644 packages/midway-schedule/test/fixtures/worker/package.json create mode 100644 packages/midway-schedule/test/fixtures/worker/src/lib/schedule/interval.ts create mode 100644 packages/midway-schedule/test/schedule.test.ts create mode 100644 packages/midway-schedule/tsconfig.json create mode 100644 packages/midway-web/config/plugin.js diff --git a/packages/midway-schedule/agent.ts b/packages/midway-schedule/agent.ts new file mode 100644 index 000000000000..2c252329ceb7 --- /dev/null +++ b/packages/midway-schedule/agent.ts @@ -0,0 +1,11 @@ +const wrapped = require('egg-schedule/agent'); + +module.exports = (agent) => { + if (!agent.loggers.scheduleLogger) { + agent.loggers.scheduleLogger = { + unredirect: () => {}, + warn: console.warn, + }; + } + wrapped(agent); +}; diff --git a/packages/midway-schedule/app.ts b/packages/midway-schedule/app.ts new file mode 100644 index 000000000000..701ef5d9b711 --- /dev/null +++ b/packages/midway-schedule/app.ts @@ -0,0 +1,100 @@ +'use strict'; + +import * as qs from 'querystring'; +import * as path from 'path'; +import loadSchedule from './lib/load_schedule'; + +module.exports = (app) => { + // don't redirect scheduleLogger + // app.loggers.scheduleLogger.unredirect('error'); + + const schedules = loadSchedule(app); + + // for test purpose + app.runSchedule = (schedulePath) => { + if (!path.isAbsolute(schedulePath)) { + schedulePath = path.join( + app.config.baseDir, + 'app/schedule', + schedulePath, + ); + } + schedulePath = require.resolve(schedulePath); + let schedule; + + try { + schedule = schedules[schedulePath]; + if (!schedule) { + throw new Error(`Cannot find schedule ${schedulePath}`); + } + } catch (err) { + err.message = `[egg-schedule] ${err.message}`; + return Promise.reject(err); + } + + // run with anonymous context + const ctx = app.createAnonymousContext({ + method: 'SCHEDULE', + url: `/__schedule?path=${schedulePath}&${qs.stringify( + schedule.schedule, + )}`, + }); + + return schedule.task(ctx); + }; + + // log schedule list + for (const s in schedules) { + const schedule = schedules[s]; + if (!schedule.schedule.disable) + app.coreLogger.info('[egg-schedule]: register schedule %s', schedule.key); + } + + // register schedule event + app.messenger.on('egg-schedule', (data) => { + throw new Error('hahaha'); + const id = data.id; + const key = data.key; + const schedule = schedules[key]; + const logger = app.loggers.scheduleLogger; + logger.info(`[${id}] ${key} task received by app`); + + if (!schedule) { + logger.warn(`[${id}] ${key} unknown task`); + return; + } + /* istanbul ignore next */ + if (schedule.schedule.disable) return; + + // run with anonymous context + const ctx = app.createAnonymousContext({ + method: 'SCHEDULE', + url: `/__schedule?path=${key}&${qs.stringify(schedule.schedule)}`, + }); + + const start = Date.now(); + const task = schedule.task; + logger.info(`[${id}] ${key} executing by app`); + console.log('hi ctx', ctx); + console.log('hi ctx.logger', ctx.logger); + // execute + task(ctx, ...data.args) + .then(() => true) // succeed + .catch((err) => { + logger.error(`[${id}] ${key} execute error.`, err); + err.message = `[egg-schedule] ${key} execute error. ${err.message}`; + app.logger.error(err); + return false; // failed + }) + .then((success) => { + const rt = Date.now() - start; + const status = success ? 'succeed' : 'failed'; + ctx.coreLogger.info( + `[egg-schedule] ${key} execute ${status}, used ${rt}ms`, + ); + logger[success ? 'info' : 'error']( + `[${id}] ${key} execute ${status}, used ${rt}ms`, + ); + }); + }); +}; diff --git a/packages/midway-schedule/app/extend/agent.ts b/packages/midway-schedule/app/extend/agent.ts new file mode 100644 index 000000000000..32cc6fc737c9 --- /dev/null +++ b/packages/midway-schedule/app/extend/agent.ts @@ -0,0 +1 @@ +module.exports = require('egg-schedule/app/extend/agent'); diff --git a/packages/midway-schedule/lib/load_schedule.ts b/packages/midway-schedule/lib/load_schedule.ts new file mode 100644 index 000000000000..7398830c842d --- /dev/null +++ b/packages/midway-schedule/lib/load_schedule.ts @@ -0,0 +1,69 @@ +import 'reflect-metadata'; +import * as assert from 'assert'; +import * as is from 'is-type-of'; +import { SCHEDULE_CLASS } from './metaKeys'; + +interface SchedueOpts { + interval: number; + type: string; + env?: string[]; +} + +export default (app) => { + const dirs = app.loader + .getLoadUnits() + .map((unit) => require('path').join(unit.path, 'lib/schedule')); + const Loader = getScheduleLoader(app); + const schedules = (app.schedules = {}); + new Loader({ + directory: dirs, + target: schedules, + inject: app, + }).load(); + return schedules; +}; + +function getScheduleLoader(app) { + return class ScheduleLoader extends app.loader.FileLoader { + constructor(...args) { + super(...args); + } + + load() { + const target = this.options.target; + const items = this.parse(); + for (const item of items) { + const schedule = item.exports; + const fullpath = item.fullpath; + + assert(is.class(schedule), `schedule(${fullpath}: should be class`); + const opts: SchedueOpts | string = Reflect.getMetadata( + SCHEDULE_CLASS, + schedule, + ); + assert(opts, `schedule(${fullpath}): must use @schedule to setup.`); + + const task = (ctx, data) => { + const ins = ctx.getAsync(schedule); + ins.exec = app.toAsyncFunction(ins.exec); + return ins.exec(data); + }; + + const env = app.config.env; + const envList = (opts as SchedueOpts).env; + if (is.array(envList) && !envList.includes(env)) { + app.coreLogger.info( + `[egg-schedule]: ignore schedule ${fullpath} due to \`schedule.env\` not match`, + ); + continue; + } + + target[fullpath] = { + schedule: opts, + task, + key: fullpath, + }; + } + } + }; +} diff --git a/packages/midway-schedule/lib/metaKeys.ts b/packages/midway-schedule/lib/metaKeys.ts new file mode 100644 index 000000000000..1f009e096dec --- /dev/null +++ b/packages/midway-schedule/lib/metaKeys.ts @@ -0,0 +1,4 @@ +/** + * for @schedule + */ +export const SCHEDULE_CLASS = 'midway:schedule_class'; diff --git a/packages/midway-schedule/package.json b/packages/midway-schedule/package.json new file mode 100644 index 000000000000..6a0659d5f0be --- /dev/null +++ b/packages/midway-schedule/package.json @@ -0,0 +1,23 @@ +{ + "name": "midway-schedule", + "version": "0.1.1", + "description": "", + "main": "index.js", + "directories": { + "lib": "lib" + }, + "eggPlugin": { + "name": "schedule" + }, + "scripts": { + "lint": "../../node_modules/.bin/tslint --format prose -c ../../tslint.json src/**/*.ts test/**/*.ts", + "test": "npm run lint && midway-bin clean && NODE_ENV=test midway-bin test --ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "egg-schedule": "^3.4.0", + "reflect-metadata": "^0.1.12" + } +} diff --git a/packages/midway-schedule/test/fixtures/worker/config/plugin.js b/packages/midway-schedule/test/fixtures/worker/config/plugin.js new file mode 100644 index 000000000000..e54d18275093 --- /dev/null +++ b/packages/midway-schedule/test/fixtures/worker/config/plugin.js @@ -0,0 +1,3 @@ +'use strict'; + +exports.logrotator = true; diff --git a/packages/midway-schedule/test/fixtures/worker/package.json b/packages/midway-schedule/test/fixtures/worker/package.json new file mode 100644 index 000000000000..5916c90a5f16 --- /dev/null +++ b/packages/midway-schedule/test/fixtures/worker/package.json @@ -0,0 +1,3 @@ +{ + "name": "worker" +} diff --git a/packages/midway-schedule/test/fixtures/worker/src/lib/schedule/interval.ts b/packages/midway-schedule/test/fixtures/worker/src/lib/schedule/interval.ts new file mode 100644 index 000000000000..b16d985f693b --- /dev/null +++ b/packages/midway-schedule/test/fixtures/worker/src/lib/schedule/interval.ts @@ -0,0 +1,21 @@ +'use strict'; + +import { schedule } from '../../../../../../../midway/dist'; +// import * as fs from 'fs'; + +@schedule({ + type: 'worker', + interval: 200, +}) +export default class IntervalCron { + async exec(ctx) { + throw new Error('hahaha'); + // console.log(1234); + // ctx.logger.info('interval'); + // ctx.app.coreLogger.info('hello world'); + // fs.writeFileSync( + // require('path').join(__dirname, '../../logs/worker/hello.log'), + // 'hello world', + // ); + } +} diff --git a/packages/midway-schedule/test/schedule.test.ts b/packages/midway-schedule/test/schedule.test.ts new file mode 100644 index 000000000000..3d2d8bf75feb --- /dev/null +++ b/packages/midway-schedule/test/schedule.test.ts @@ -0,0 +1,94 @@ +'use strict'; + +const { mm } = require('../../midway-mock/dist'); +const path = require('path'); +const fs = require('fs'); +const assert = require('assert'); + +describe('test/schedule.test.js', () => { + let app; + // afterEach(() => app.close()); + + describe('schedule type worker', () => { + it('should support interval and cron', async () => { + app = mm.app({ + baseDir: 'worker', + framework: path.join(__dirname, '../../midway'), + }); + // app.debug(); + await app.ready(); + console.log(app.schedules); + }); + }); +}); + +function sleep(time) { + return new Promise((resolve) => { + setTimeout(resolve, time); + }); +} + +function getCoreLogContent(name) { + const logPath = path.join( + __dirname, + 'fixtures', + name, + 'logs', + name, + 'egg-web.log', + ); + return fs.readFileSync(logPath, 'utf8'); +} + +function getLogContent(name) { + const logPath = path.join( + __dirname, + 'fixtures', + name, + 'dist', + 'logs', + name, + `midway-web.log`, + ); + return fs.readFileSync(logPath, 'utf8'); +} + +function getErrorLogContent(name) { + const logPath = path.join( + __dirname, + 'fixtures', + name, + 'logs', + name, + 'common-error.log', + ); + return fs.readFileSync(logPath, 'utf8'); +} + +function getAgentLogContent(name) { + const logPath = path.join( + __dirname, + 'fixtures', + name, + 'logs', + name, + 'egg-agent.log', + ); + return fs.readFileSync(logPath, 'utf8'); +} + +function getScheduleLogContent(name) { + const logPath = path.join( + __dirname, + 'fixtures', + name, + 'logs', + name, + 'egg-schedule.log', + ); + return fs.readFileSync(logPath, 'utf8'); +} + +function contains(content, match) { + return content.split('\n').filter((line) => line.indexOf(match) >= 0).length; +} diff --git a/packages/midway-schedule/tsconfig.json b/packages/midway-schedule/tsconfig.json new file mode 100644 index 000000000000..da0f31205234 --- /dev/null +++ b/packages/midway-schedule/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2017", + "module": "commonjs", + "moduleResolution": "node", + "experimentalDecorators": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "stripInternal": true, + "pretty": true, + "declaration": true, + "outDir": "dist", + "lib": ["es2017", "dom"], + "sourceMap": true + }, + "exclude": [ + "dist", + "node_modules", + "test" + ] +} diff --git a/packages/midway-web/config/plugin.js b/packages/midway-web/config/plugin.js new file mode 100644 index 000000000000..a312de4bc176 --- /dev/null +++ b/packages/midway-web/config/plugin.js @@ -0,0 +1,6 @@ +module.exports = { + schedule: { + enable: true, + package: 'midway-schedule', + }, +}; diff --git a/packages/midway-web/package.json b/packages/midway-web/package.json index 9230dbf1babc..3be01b12eaca 100644 --- a/packages/midway-web/package.json +++ b/packages/midway-web/package.json @@ -42,7 +42,8 @@ "inflection": "^1.12.0", "injection": "^0.4.1", "is-type-of": "^1.2.0", - "midway-core": "^0.4.4" + "midway-core": "^0.4.4", + "midway-schedule": "^0.1.0" }, "author": "Harry Chen ", "repository": { diff --git a/packages/midway-web/src/decorators/application.ts b/packages/midway-web/src/decorators/application.ts index 060643297ef3..b516d8e82d9b 100644 --- a/packages/midway-web/src/decorators/application.ts +++ b/packages/midway-web/src/decorators/application.ts @@ -1,6 +1,6 @@ import 'reflect-metadata'; import { scope, ScopeEnum } from 'injection'; -import { WEB_ROUTER_PREFIX_CLS, WEB_ROUTER_PRIORITY } from './metaKeys'; +import { WEB_ROUTER_PREFIX_CLS, WEB_ROUTER_PRIORITY, SCHEDULE_CLASS } from './metaKeys'; export function controller(routerPrefix: string) { return function (target: any): void { @@ -14,3 +14,15 @@ export function priority(priority: number) { Reflect.defineMetadata(WEB_ROUTER_PRIORITY, priority, target); }; } + +interface SchedueOpts { + interval: number; + type: string; +} + +export function schedule (scheduleOpts: SchedueOpts | string) { + return function(target: any): void { + Reflect.defineMetadata(SCHEDULE_CLASS, scheduleOpts, target); + scope(ScopeEnum.Request)(target); + }; +} diff --git a/packages/midway-web/src/decorators/metaKeys.ts b/packages/midway-web/src/decorators/metaKeys.ts index 7494a361509e..4a2f58012207 100644 --- a/packages/midway-web/src/decorators/metaKeys.ts +++ b/packages/midway-web/src/decorators/metaKeys.ts @@ -12,3 +12,8 @@ export const WEB_ROUTER_PREFIX_CLS = 'midway:web_router_prefix_class'; * for @priority */ export const WEB_ROUTER_PRIORITY = 'midway:web_router_priority'; + +/** + * for @schedule + */ +export const SCHEDULE_CLASS = 'midway:schedule_class';