occaecatidicta
Version:
1,238 lines (1,115 loc) • 41.9 kB
text/typescript
/*!
* Omelox -- proto
* Copyright(c) 2012 xiechengchao <xiecc@163.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
import * as utils from './util/utils';
import { getLogger, ILogger } from 'omelox-logger';
import { EventEmitter } from 'events';
import { AppEvents, default as events } from './util/events';
import * as appUtil from './util/appUtil';
import { ServerStartArgs } from './util/appUtil';
import * as Constants from './util/constants';
import { FRONTENDID, ServerInfo } from './util/constants';
import * as appManager from './common/manager/appManager';
import { TransactionCondictionFunction, TransactionHandlerFunction } from './common/manager/appManager';
import * as fs from 'fs';
import * as path from 'path';
import { isFunction } from 'util';
import { IComponent } from './interfaces/IComponent';
import { DictionaryComponent } from './components/dictionary';
import { PushSchedulerComponent } from './components/pushScheduler';
import { BackendSessionService } from './common/service/backendSessionService';
import { ChannelService, ChannelServiceOptions } from './common/service/channelService';
import { SessionComponent } from './components/session';
import { ServerComponent } from './components/server';
import { RemoteComponent } from './components/remote';
import { ProxyComponent, RouteFunction, RouteMaps } from './components/proxy';
import { ProtobufComponent, ProtobufComponentOptions } from './components/protobuf';
import { MonitorComponent } from './components/monitor';
import { MasterComponent } from './components/master';
import { ConnectorComponent, ConnectorComponentOptions } from './components/connector';
import { ConnectionComponent } from './components/connection';
import { SessionService } from './common/service/sessionService';
import { ObjectType } from './interfaces/define';
import { IModule, IModuleFactory } from 'omelox-admin';
import { ChannelComponent } from './components/channel';
import { BackendSessionComponent } from './components/backendSession';
import { AfterHandlerFilter, BeforeHandlerFilter, IHandlerFilter } from './interfaces/IHandlerFilter';
import { MailStationErrorHandler, RpcFilter, RpcMsg } from 'omelox-rpc';
import { ModuleRecord } from './util/moduleUtil';
import { ApplicationEventContructor, IPlugin } from './interfaces/IPlugin';
import { Cron, ResponseErrorHandler } from './server/server';
import { RemoterProxy } from './util/remoterHelper';
import { FrontendOrBackendSession, ISession, ScheduleOptions, SID, UID } from './index';
let logger = getLogger('omelox', path.basename(__filename));
export type ConfigureCallback = () => void;
export type AConfigureFunc1 = () => Promise<void> ;
export type AConfigureFunc2 = (env: string) => Promise<void> ;
export type AConfigureFunc3 = (env: string, type: string) => Promise<void>;
export interface ApplicationOptions {
base?: string;
}
export type BeforeStopHookFunction = (app: Application, shutDown: () => void, cancelShutDownTimer: () => void) => void;
declare global {
// 定义用户Rpc基础类
interface UserRpc {
test(): void;
}
// channelRemote functions
type pushMessageFunction = (route: string, msg: any, uids: UID[], opts: ScheduleOptions) => Promise<UID[]>;
type broadcastFunction = (route: string, msg: any, opts: ScheduleOptions) => Promise<UID[]>;
// sessionRemote functions
type bindFunction = (sid: SID, uid: UID) => Promise<void>;
type unbindFunction = (sid: SID, uid: UID) => Promise<void>;
type pushFunction = (sid: SID, key: string, value: any) => Promise<void>;
type pushAllFunction = (sid: SID, settings: { [key: string]: any }) => Promise<void>;
type getBackendSessionBySidFunction = (sid: SID) => Promise<ISession>;
type getBackendSessionsByUidFunction = (uid: UID) => Promise<ISession[]>;
type kickBySidFunction = (sid: SID, reason: string) => Promise<void>;
type kickByUidFunction = (uid: UID, reason: string) => Promise<void>;
interface SysRpc {
[serverType: string]: {
/**
* 用来把客户端发到前端的handler信息转发到后端服务器
*/
msgRemote: {
forwardMessage: (routeParam: FrontendOrBackendSession, msg: any, session: ISession) => Promise<void>;
},
/**
* 用来通知前端服务器往客户端发信息
*/
channelRemote: {
pushMessage: RemoterProxy<pushMessageFunction>;
broadcast: RemoterProxy<broadcastFunction>;
}
/**
* 用来从前端服务器获取或设置Session相关的服务
*/
sessionRemote: {
bind: RemoterProxy<bindFunction>;
unbind: RemoterProxy<unbindFunction>;
push: RemoterProxy<pushFunction>;
pushAll: RemoterProxy<pushAllFunction>;
getBackendSessionBySid: RemoterProxy<getBackendSessionBySidFunction>;
getBackendSessionsByUid: RemoterProxy<getBackendSessionsByUidFunction>;
kickBySid: RemoterProxy<kickBySidFunction>;
kickByUid: RemoterProxy<kickByUidFunction>;
}
};
}
}
/**
* Application states
*/
let STATE_INITED = 1; // app has inited
let STATE_BEFORE_START = 2; // app before start
let STATE_START = 3; // app start
let STATE_STARTED = 4; // app has started
let STATE_STOPED = 5; // app has stoped
export class Application {
loaded: IComponent[] = []; // loaded component list
components: {
__backendSession__?: BackendSessionComponent,
__channel__?: ChannelComponent,
__connection__?: ConnectionComponent,
__connector__?: ConnectorComponent,
__dictionary__?: DictionaryComponent,
__master__?: MasterComponent,
__monitor__?: MonitorComponent,
__protobuf__?: ProtobufComponent,
__proxy__?: ProxyComponent,
__remote__?: RemoteComponent,
__server__?: ServerComponent,
__session__?: SessionComponent,
__pushScheduler__?: PushSchedulerComponent,
[key: string]: IComponent
} = {}; // name -> component map
sessionService ?: SessionService;
backendSessionService ?: BackendSessionService;
channelService ?: ChannelService;
settings: { [key: string]: any } = {}; // collection keep set/get
event = new EventEmitter(); // event object to sub/pub events
// current server info
serverId: string; // current server id
serverType: string; // current server type
curServer: ServerInfo; // current server info
startTime: number; // current server start time
// global server infos
master: ServerStartArgs = null; // master server info
servers: { [id: string]: ServerInfo } = {}; // current global server info maps, id -> info
serverTypeMaps: { [type: string]: ServerInfo[] } = {}; // current global type maps, type -> [info]
serverTypes: string[] = []; // current global server type list
usedPlugins: IPlugin[] = []; // current server custom lifecycle callbacks
clusterSeq: { [serverType: string]: number } = {}; // cluster id seqence
state: number;
base: string;
startId: string;
type: string;
stopTimer: any;
/**
* Initialize the server.
*
* - setup default configuration
*/
init(opts ?: ApplicationOptions) {
opts = opts || {};
let base = opts.base || path.dirname(require.main.filename);
this.set(Constants.RESERVED.BASE, base);
this.base = base;
appUtil.defaultConfiguration(this);
this.state = STATE_INITED;
logger.info('application inited: %j', this.getServerId());
}
/**
* Get application base path
*
* // cwd: /home/game/
* omelox start
* // app.getBase() -> /home/game
*
* @return {String} application base path
*
* @memberOf Application
*/
getBase() {
return this.get(Constants.RESERVED.BASE);
}
/**
* Override require method in application
*
* @param {String} relative path of file
*
* @memberOf Application
*/
require(ph: string) {
return require(path.join(this.getBase(), ph));
}
/**
* Configure logger with {$base}/config/log4js.json
*
* @param {Object} logger omelox-logger instance without configuration
*
* @memberOf Application
*/
configureLogger(logger: ILogger) {
if (process.env.POMELO_LOGGER !== 'off') {
let serverId = this.getServerId();
let base = this.getBase();
let env = this.get(Constants.RESERVED.ENV);
let originPath = path.join(base, Constants.FILEPATH.LOG);
let presentPath = path.join(base, Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.LOG));
if (this._checkCanRequire(originPath)) {
logger.configure(originPath, { serverId: serverId, base: base });
} else if (this._checkCanRequire(presentPath)) {
logger.configure(presentPath, { serverId: serverId, base: base });
} else {
console.error('logger file path configuration is error.');
}
}
}
/**
* add a filter to before and after filter
*
* @param {Object} filter provide before and after filter method.
* A filter should have two methods: before and after.
* @memberOf Application
*/
filter(filter: IHandlerFilter): void {
this.before(filter);
this.after(filter);
}
/**
* Add before filter.
*
* @param {Object|Function} bf before fileter, bf(msg, session, next)
* @memberOf Application
*/
before(bf: BeforeHandlerFilter): void {
addFilter(this, Constants.KEYWORDS.BEFORE_FILTER, bf);
}
/**
* Add after filter.
*
* @param {Object|Function} af after filter, `af(err, msg, session, resp, next)`
* @memberOf Application
*/
after(af: AfterHandlerFilter): void {
addFilter(this, Constants.KEYWORDS.AFTER_FILTER, af);
}
/**
* add a global filter to before and after global filter
*
* @param {Object} filter provide before and after filter method.
* A filter should have two methods: before and after.
* @memberOf Application
*/
globalFilter(filter: IHandlerFilter) {
this.globalBefore(filter);
this.globalAfter(filter);
}
/**
* Add global before filter.
*
* @param {Object|Function} bf before fileter, bf(msg, session, next)
* @memberOf Application
*/
globalBefore(bf: BeforeHandlerFilter) {
addFilter(this, Constants.KEYWORDS.GLOBAL_BEFORE_FILTER, bf);
}
/**
* Add global after filter.
*
* @param {Object|Function} af after filter, `af(err, msg, session, resp, next)`
* @memberOf Application
*/
globalAfter(af: AfterHandlerFilter) {
addFilter(this, Constants.KEYWORDS.GLOBAL_AFTER_FILTER, af);
}
/**
* Add rpc before filter.
*
* @param {Object|Function} bf before fileter, bf(serverId, msg, opts, next)
* @memberOf Application
*/
rpcBefore(bf: RpcFilter | RpcFilter[]) {
addFilter(this, Constants.KEYWORDS.RPC_BEFORE_FILTER, bf);
}
/**
* Add rpc after filter.
*
* @param {Object|Function} af after filter, `af(serverId, msg, opts, next)`
* @memberOf Application
*/
rpcAfter(af: RpcFilter | RpcFilter[]) {
addFilter(this, Constants.KEYWORDS.RPC_AFTER_FILTER, af);
}
/**
* add a rpc filter to before and after rpc filter
*
* @param {Object} filter provide before and after filter method.
* A filter should have two methods: before and after.
* @memberOf Application
*/
rpcFilter(filter: RpcFilter) {
this.rpcBefore(filter);
this.rpcAfter(filter);
}
/**
* Load component
*
* @param {String} name (optional) name of the component
* @param {Object} component component instance or factory function of the component
* @param {[type]} opts (optional) construct parameters for the factory function
* @return {Object} app instance for chain invoke
* @memberOf Application
*/
load<T extends IComponent>(component: ObjectType<T>, opts ?: any): T;
load<T extends IComponent>(name: string, component: ObjectType<T>, opts ?: any): T;
load<T extends IComponent>(component: T, opts ?: any): T;
load<T extends IComponent>(name: string, component: T, opts ?: any): T;
load<T extends IComponent>(name: string | ObjectType<T>, component ?: ObjectType<T> | any | T, opts ?: any): T {
if (typeof name !== 'string') {
opts = component;
component = name;
name = null;
}
if (isFunction(component)) {
component = new component(this, opts);
}
if (!name && typeof component.name === 'string') {
name = component.name;
}
if (name && this.components[name as string]) {
// ignore duplicat component
logger.warn('ignore duplicate component: %j', name);
return;
}
this.loaded.push(component);
if (name) {
// components with a name would get by name throught app.components later.
this.components[name as string] = component;
}
return component;
}
_checkCanRequire(path: string) {
try {
path = require.resolve(path);
} catch (err) {
return null;
}
return path;
}
/**
* Load Configure json file to settings.(support different enviroment directory & compatible for old path)
*
* @param {String} key environment key
* @param {String} val environment value
* @param {Boolean} reload whether reload after change default false
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
loadConfigBaseApp(key: string, val: string, reload = false) {
let self = this;
let env = this.get(Constants.RESERVED.ENV);
let originPath = path.join(this.getBase(), val);
let presentPath = path.join(this.getBase(), Constants.FILEPATH.CONFIG_DIR, env, path.basename(val));
let realPath: string;
let tmp: string;
if (self._checkCanRequire(originPath)) {
realPath = require.resolve(originPath);
let file = require(originPath);
if (file[env]) {
file = file[env];
}
this.set(key, file);
} else if (self._checkCanRequire(presentPath)) {
realPath = require.resolve(presentPath);
let pfile = require(presentPath);
this.set(key, pfile);
} else {
logger.error('invalid configuration with file path: %s', key);
}
if (!!realPath && !!reload) {
const watcher = fs.watch(realPath, function (event, filename) {
if (event === 'change') {
self.clearRequireCache(require.resolve(realPath));
watcher.close();
self.loadConfigBaseApp(key, val, reload);
}
});
}
}
clearRequireCache(path: string) {
const moduleObj = require.cache[path];
if (!moduleObj) {
logger.warn('can not find module of truepath', path);
return;
}
if (moduleObj.parent) {
// console.log('has parent ',moduleObj.parent);
moduleObj.parent.children.splice(moduleObj.parent.children.indexOf(moduleObj), 1);
}
delete require.cache[path];
}
/**
* Load Configure json file to settings.
*
* @param {String} key environment key
* @param {String} val environment value
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
loadConfig(key: string, val: string) {
let env = this.get(Constants.RESERVED.ENV);
let cfg = require(val);
if (cfg[env]) {
cfg = cfg[env];
}
this.set(key, cfg);
}
/**
* Set the route function for the specified server type.
*
* Examples:
*
* app.route('area', routeFunc);
*
* let routeFunc = function(session, msg, app, cb) {
* // all request to area would be route to the first area server
* let areas = app.getServersByType('area');
* cb(null, areas[0].id);
* };
*
* @param {String} serverType server type string
* @param {Function} routeFunc route function. routeFunc(session, msg, app, cb)
* @return {Object} current application instance for chain invoking
* @memberOf Application
*/
route(serverType: string, routeFunc: RouteFunction) {
let routes = this.get(Constants.KEYWORDS.ROUTE);
if (!routes) {
routes = {};
this.set(Constants.KEYWORDS.ROUTE, routes);
}
routes[serverType] = routeFunc;
return this;
}
/**
* Set before stop function. It would perform before servers stop.
*
* @param {Function} fun before close function
* @return {Void}
* @memberOf Application
*/
beforeStopHook(fun: BeforeStopHookFunction) {
logger.warn('this method was deprecated in omelox 0.8');
if (!!fun && typeof fun === 'function') {
this.set(Constants.KEYWORDS.BEFORE_STOP_HOOK, fun);
}
}
/**
* Start application. It would load the default components and start all the loaded components.
*
* @param {Function} cb callback function
* @memberOf Application
*/
start(cb ?: (err ?: Error, result ?: void) => void) {
this.startTime = Date.now();
if (this.state > STATE_INITED) {
utils.invokeCallback(cb, new Error('application has already start.'));
return;
}
let self = this;
appUtil.startByType(self, function () {
appUtil.loadDefaultComponents(self);
let startUp = function () {
self.state = STATE_BEFORE_START;
logger.info('%j enter before start...', self.getServerId());
appUtil.optComponents(self.loaded, Constants.RESERVED.BEFORE_START, function (err) {
if (err) {
utils.invokeCallback(cb, err);
} else {
logger.info('%j enter start...', self.getServerId());
appUtil.optComponents(self.loaded, Constants.RESERVED.START, function (err) {
self.state = STATE_START;
if (err) {
utils.invokeCallback(cb, err);
} else {
logger.info('%j enter after start...', self.getServerId());
self.afterStart(cb);
}
});
}
});
};
appUtil.optLifecycles(self.usedPlugins, Constants.LIFECYCLE.BEFORE_STARTUP, self, function (err) {
if (err) {
utils.invokeCallback(cb, err);
} else {
startUp();
}
});
});
}
/**
* Lifecycle callback for after start.
*
* @param {Function} cb callback function
* @return {Void}
*/
afterStart(cb ?: (err?: Error) => void) {
if (this.state !== STATE_START) {
utils.invokeCallback(cb, new Error('application is not running now.'));
return;
}
let self = this;
appUtil.optComponents(this.loaded, Constants.RESERVED.AFTER_START, function (err) {
self.state = STATE_STARTED;
let id = self.getServerId();
if (!err) {
logger.info('%j finish start', id);
}
appUtil.optLifecycles(self.usedPlugins, Constants.LIFECYCLE.AFTER_STARTUP, self, function (err?: Error) {
let usedTime = Date.now() - self.startTime;
logger.info('%j startup in %s ms', id, usedTime);
self.event.emit(events.START_SERVER, id);
cb && cb(err);
});
});
}
/**
* Stop components.
*
* @param {Boolean} force whether stop the app immediately
*/
stop(force: boolean) {
if (this.state > STATE_STARTED) {
logger.warn('[omelox application] application is not running now.');
return;
}
this.state = STATE_STOPED;
let self = this;
this.stopTimer = setTimeout(function () {
process.exit(0);
}, Constants.TIME.TIME_WAIT_STOP);
let cancelShutDownTimer = function () {
if (!!self.stopTimer) {
clearTimeout(self.stopTimer);
}
};
let shutDown = function () {
appUtil.stopComps(self.loaded, 0, force, function () {
cancelShutDownTimer();
if (force) {
process.exit(0);
}
});
};
let fun = this.get(Constants.KEYWORDS.BEFORE_STOP_HOOK);
appUtil.optLifecycles(self.usedPlugins, Constants.LIFECYCLE.BEFORE_SHUTDOWN, self, function (err) {
if (err) {
console.error(`throw err when beforeShutdown `, err.stack);
} else {
if (!!fun) {
utils.invokeCallback(fun, self, shutDown, cancelShutDownTimer);
} else {
shutDown();
}
}
}, cancelShutDownTimer);
}
/**
* Assign `setting` to `val`, or return `setting`'s value.
*
* Example:
*
* app.set('key1', 'value1');
* app.get('key1'); // 'value1'
* app.key1; // undefined
*
* app.set('key2', 'value2', true);
* app.get('key2'); // 'value2'
* app.key2; // 'value2'
*
* @param {String} setting the setting of application
* @param {String} val the setting's value
* @param {Boolean} attach whether attach the settings to application
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
set(setting: 'channelService', val: ChannelService, attach?: boolean): Application;
set(setting: 'sessionService', val: SessionService, attach?: boolean): Application;
set(setting: 'channelConfig', val: ChannelServiceOptions, attach?: boolean): Application;
set(setting: 'backendSessionService', val: BackendSessionComponent, attach?: boolean): Application;
set(setting: 'protobufConfig', val: ProtobufComponentOptions, attach?: boolean): Application;
set(setting: 'connectorConfig', val: ConnectorComponentOptions, attach?: boolean): Application;
set(setting: Constants.KEYWORDS.BEFORE_FILTER, val: BeforeHandlerFilter[], attach?: boolean): Application;
set(setting: Constants.KEYWORDS.AFTER_FILTER, val: AfterHandlerFilter[], attach?: boolean): Application;
set(setting: Constants.KEYWORDS.GLOBAL_BEFORE_FILTER, val: BeforeHandlerFilter[], attach?: boolean): Application;
set(setting: Constants.KEYWORDS.GLOBAL_AFTER_FILTER, val: AfterHandlerFilter[], attach?: boolean): Application;
set(setting: Constants.KEYWORDS.RPC_BEFORE_FILTER, val: RpcFilter | RpcFilter[], attach?: boolean): Application;
set(setting: Constants.KEYWORDS.RPC_AFTER_FILTER, val: RpcFilter | RpcFilter[], attach?: boolean): Application;
set(setting: Constants.RESERVED.RPC_ERROR_HANDLER, val: MailStationErrorHandler, attach?: boolean): Application;
set(setting: Constants.KEYWORDS.ROUTE, val: RouteMaps, attach?: boolean): Application;
set(setting: Constants.KEYWORDS.BEFORE_STOP_HOOK, val: BeforeStopHookFunction, attach?: boolean): Application;
set(setting: Constants.RESERVED.BASE, val: string, attach?: boolean): Application;
set(setting: Constants.RESERVED.ENV, val: string, attach?: boolean): Application;
set(setting: Constants.RESERVED.GLOBAL_ERROR_HANDLER, val: ResponseErrorHandler, attach?: boolean): Application;
set(setting: Constants.RESERVED.ERROR_HANDLER, val: ResponseErrorHandler, attach?: boolean): Application;
set(setting: Constants.KEYWORDS.MODULE, val: { [key: string]: ModuleRecord }, attach?: boolean): Application;
set(setting: string, val: string | any, attach?: boolean): Application;
set(setting: string, val: string | any, attach?: boolean): Application {
this.settings[setting] = val;
if (attach) {
(this as any)[setting] = val;
}
return this;
}
/**
* Get property from setting
*
* @param {String} setting application setting
* @return {String} val
* @memberOf Application
*/
get(setting: 'channelService'): ChannelService;
get(setting: 'sessionService'): SessionService;
get(setting: 'channelConfig'): ChannelServiceOptions;
get(setting: 'backendSessionService'): BackendSessionComponent;
get(setting: Constants.KEYWORDS.BEFORE_FILTER): BeforeHandlerFilter[];
get(setting: Constants.KEYWORDS.AFTER_FILTER): AfterHandlerFilter[];
get(setting: Constants.KEYWORDS.GLOBAL_BEFORE_FILTER): BeforeHandlerFilter[];
get(setting: Constants.KEYWORDS.GLOBAL_AFTER_FILTER): AfterHandlerFilter[];
get(setting: Constants.KEYWORDS.RPC_BEFORE_FILTER): RpcFilter | RpcFilter[];
get(setting: Constants.KEYWORDS.RPC_AFTER_FILTER): RpcFilter | RpcFilter[];
get(setting: Constants.RESERVED.RPC_ERROR_HANDLER): MailStationErrorHandler;
get(setting: Constants.KEYWORDS.ROUTE): RouteMaps;
get(setting: Constants.KEYWORDS.BEFORE_STOP_HOOK): BeforeStopHookFunction;
get(setting: Constants.RESERVED.BASE): string;
get(setting: Constants.RESERVED.ENV): string;
get(setting: Constants.RESERVED.GLOBAL_ERROR_HANDLER): ResponseErrorHandler;
get(setting: Constants.RESERVED.ERROR_HANDLER): ResponseErrorHandler;
get(setting: Constants.KEYWORDS.MODULE): { [key: string]: ModuleRecord };
get(setting: string): string | any;
get(setting: string): string | any {
return this.settings[setting];
}
/**
* Check if `setting` is enabled.
*
* @param {String} setting application setting
* @return {Boolean}
* @memberOf Application
*/
enabled(setting: string) {
return !!this.get(setting);
}
/**
* Check if `setting` is disabled.
*
* @param {String} setting application setting
* @return {Boolean}
* @memberOf Application
*/
disabled(setting: string) {
return !this.get(setting);
}
/**
* Enable `setting`.
*
* @param {String} setting application setting
* @return {app} for chaining
* @memberOf Application
*/
enable(setting: string) {
return this.set(setting, true);
}
/**
* Disable `setting`.
*
* @param {String} setting application setting
* @return {app} for chaining
* @memberOf Application
*/
disable(setting: string) {
return this.set(setting, false);
}
/**
* Configure callback for the specified env and server type.
* When no env is specified that callback will
* be invoked for all environments and when no type is specified
* that callback will be invoked for all server types.
*
* Examples:
*
* app.configure(function(){
* // executed for all envs and server types
* });
*
* app.configure('development', function(){
* // executed development env
* });
*
* app.configure('development', 'connector', function(){
* // executed for development env and connector server type
* });
*
* @param {String} env application environment
* @param {Function} fn callback function
* @param {String} type server type
* @return {Application} for chaining
* @memberOf Application
*/
configure(fn: ConfigureCallback): Application;
configure(env: string, fn: ConfigureCallback): Application;
configure(env: string, type: string, fn: ConfigureCallback): Application;
configure(env: string | ConfigureCallback, type ?: string | ConfigureCallback, fn ?: ConfigureCallback): Application {
let args = [].slice.call(arguments);
fn = args.pop();
env = type = Constants.RESERVED.ALL;
if (args.length > 0) {
env = args[0];
}
if (args.length > 1) {
type = args[1];
}
if (env === Constants.RESERVED.ALL || contains(this.settings.env, env as string)) {
if (type === Constants.RESERVED.ALL || contains(this.settings.serverType, type as string)) {
fn.call(this);
}
}
return this;
}
/**
* Register admin modules. Admin modules is the extends point of the monitor system.
*
* @param {String} module (optional) module id or provoided by module.moduleId
* @param {Object} module module object or factory function for module
* @param {Object} opts construct parameter for module
* @memberOf Application
*/
registerAdmin(module: IModule, opts ?: any): void;
registerAdmin(moduleId: string, module ?: IModule, opts ?: any): void;
registerAdmin(module: IModuleFactory, opts ?: any): void;
registerAdmin(moduleId: string, module ?: IModuleFactory, opts ?: any): void;
registerAdmin(moduleId: string | IModule | IModuleFactory, module ?: IModule | IModuleFactory, opts ?: any) {
let modules = this.get(Constants.KEYWORDS.MODULE);
if (!modules) {
modules = {};
this.set(Constants.KEYWORDS.MODULE, modules);
}
if (typeof moduleId !== 'string') {
opts = module;
module = moduleId;
if (module) {
moduleId = ((module as IModuleFactory).moduleId);
if (!moduleId)
moduleId = (module as IModule).constructor.name;
}
}
if (!moduleId) {
return;
}
modules[moduleId as string] = {
moduleId: moduleId as string,
module: module,
opts: opts
};
}
/**
* Use plugin.
*
* @param {Object} plugin plugin instance
* @param {[type]} opts (optional) construct parameters for the factory function
* @memberOf Application
*/
use(plugin: IPlugin, opts ?: any) {
opts = opts || {};
if (!plugin) {
throw new Error(`pluin is null!]`);
}
if (this.usedPlugins.indexOf(plugin) >= 0) {
throw new Error(`pluin[${ plugin.name } was used already!]`);
}
if (plugin.components) {
for (let componentCtor of plugin.components) {
this.load(componentCtor, opts);
}
}
if (plugin.events) {
for (let eventCtor of plugin.events) {
this.loadEvent(eventCtor, opts);
}
}
this.usedPlugins.push(plugin);
console.warn(`used Plugin : ${ plugin.name }`);
}
/**
* Application transaction. Transcation includes conditions and handlers, if conditions are satisfied, handlers would be executed.
* And you can set retry times to execute handlers. The transaction log is in file logs/transaction.log.
*
* @param {String} name transaction name
* @param {Object} conditions functions which are called before transaction
* @param {Object} handlers functions which are called during transaction
* @param {Number} retry retry times to execute handlers if conditions are successfully executed
* @memberOf Application
*/
transaction(name: string, conditions: { [key: string]: TransactionCondictionFunction }, handlers: { [key: string]: TransactionHandlerFunction }, retry?: number) {
appManager.transaction(name, conditions, handlers, retry);
}
/**
* Get master server info.
*
* @return {Object} master server info, {id, host, port}
* @memberOf Application
*/
getMaster() {
return this.master;
}
/**
* Get current server info.
*
* @return {Object} current server info, {id, serverType, host, port}
* @memberOf Application
*/
getCurServer() {
return this.curServer;
}
/**
* Get current server id.
*
* @return {String|Number} current server id from servers.json
* @memberOf Application
*/
getServerId() {
return this.serverId;
}
/**
* Get current server
* @returns ServerInfo
*/
getCurrentServer() {
return this.curServer;
}
/**
* Get current server type.
*
* @return {String|Number} current server type from servers.json
* @memberOf Application
*/
getServerType() {
return this.serverType;
}
/**
* Get all the current server infos.
*
* @return {Object} server info map, key: server id, value: server info
* @memberOf Application
*/
getServers() {
return this.servers;
}
/**
* Get all server infos from servers.json.
*
* @return {Object} server info map, key: server id, value: server info
* @memberOf Application
*/
getServersFromConfig() {
return this.get(Constants.KEYWORDS.SERVER_MAP);
}
/**
* Get all the server type.
*
* @return {Array} server type list
* @memberOf Application
*/
getServerTypes() {
return this.serverTypes;
}
/**
* Get server info by server id from current server cluster.
*
* @param {String} serverId server id
* @return {Object} server info or undefined
* @memberOf Application
*/
getServerById(serverId: string) {
return this.servers[serverId];
}
/**
* Get server info by server id from servers.json.
*
* @param {String} serverId server id
* @return {Object} server info or undefined
* @memberOf Application
*/
getServerFromConfig(serverId: string) {
return this.get(Constants.KEYWORDS.SERVER_MAP)[serverId];
}
/**
* Get server infos by server type.
*
* @param {String} serverType server type
* @return {Array} server info list
* @memberOf Application
*/
getServersByType(serverType: string) {
return this.serverTypeMaps[serverType];
}
/**
* Check the server whether is a frontend server
*
* @param {server} server server info. it would check current server
* if server not specified
* @return {Boolean}
*
* @memberOf Application
*/
isFrontend(server ?: any) {
server = server || this.getCurServer();
return !!server && server.frontend === 'true';
}
/**
* Check the server whether is a backend server
*
* @param {server} server server info. it would check current server
* if server not specified
* @return {Boolean}
* @memberOf Application
*/
isBackend(server: ServerInfo) {
server = server || this.getCurServer();
return !!server && !server.frontend;
}
/**
* Check whether current server is a master server
*
* @return {Boolean}
* @memberOf Application
*/
isMaster() {
return this.serverType === Constants.RESERVED.MASTER;
}
/**
* Add new server info to current application in runtime.
*
* @param {Array} servers new server info list
* @memberOf Application
*/
addServers(servers: ServerInfo[]) {
if (!servers || !servers.length) {
return;
}
let item: ServerInfo, slist: ServerInfo[];
for (let i = 0, l = servers.length; i < l; i++) {
item = servers[i];
// update global server map
this.servers[item.id] = item;
// update global server type map
slist = this.serverTypeMaps[item.serverType];
if (!slist) {
this.serverTypeMaps[item.serverType] = slist = [];
}
replaceServer(slist, item);
// update global server type list
if (this.serverTypes.indexOf(item.serverType) < 0) {
this.serverTypes.push(item.serverType);
}
}
this.event.emit(events.ADD_SERVERS, servers);
}
/**
* Remove server info from current application at runtime.
*
* @param {Array} ids server id list
* @memberOf Application
*/
removeServers(ids: string[]) {
if (!ids || !ids.length) {
return;
}
let id, item, slist;
for (let i = 0, l = ids.length; i < l; i++) {
id = ids[i];
item = this.servers[id];
if (!item) {
continue;
}
// clean global server map
delete this.servers[id];
// clean global server type map
slist = this.serverTypeMaps[item.serverType];
removeServer(slist, id);
// TODO: should remove the server type if the slist is empty?
}
this.event.emit(events.REMOVE_SERVERS, ids);
}
/**
* Replace server info from current application at runtime.
*
* @param {Object} server id map
* @memberOf Application
*/
replaceServers(servers: { [serverId: string]: ServerInfo }) {
if (!servers) {
return;
}
this.servers = servers;
this.serverTypeMaps = {};
this.serverTypes = [];
let serverArray = [];
for (let id in servers) {
let server = servers[id];
let serverType = server[Constants.RESERVED.SERVER_TYPE];
let slist = this.serverTypeMaps[serverType];
if (!slist) {
this.serverTypeMaps[serverType] = slist = [];
}
this.serverTypeMaps[serverType].push(server);
// update global server type list
if (this.serverTypes.indexOf(serverType) < 0) {
this.serverTypes.push(serverType);
}
serverArray.push(server);
}
this.event.emit(events.REPLACE_SERVERS, serverArray);
}
/**
* Add crons from current application at runtime.
*
* @param {Array} crons new crons would be added in application
* @memberOf Application
*/
addCrons(crons: Cron[]) {
if (!crons || !crons.length) {
logger.warn('crons is not defined.');
return;
}
this.event.emit(events.ADD_CRONS, crons);
}
/**
* Remove crons from current application at runtime.
*
* @param {Array} crons old crons would be removed in application
* @memberOf Application
*/
removeCrons(crons: Cron[]) {
if (!crons || !crons.length) {
logger.warn('ids is not defined.');
return;
}
this.event.emit(events.REMOVE_CRONS, crons);
}
astart = utils.promisify(this.start);
aconfigure: AConfigureFunc1 | AConfigureFunc2 | AConfigureFunc3 = utils.promisify(this.configure) as any;
rpc ?: UserRpc;
sysrpc ?: SysRpc;
/**
* Proxy for rpc client rpcInvoke.
*
* @param {String} serverId remote server id
* @param {Object} msg rpc message: {serverType: serverType, service: serviceName, method: methodName, args: arguments}
* @param {Function} cb callback function
*/
rpcInvoke ?: (serverId: FRONTENDID, msg: RpcMsg, cb: Function) => void;
/**
* 加载一个事件侦听
* @param Event
* @param opts
*/
loadEvent(Event: ApplicationEventContructor, opts: any) {
let eventInstance = new Event(opts);
for (let evt in AppEvents) {
let name = (AppEvents as any)[evt];
let method = (eventInstance as any)[name];
if (method) {
this.event.on(name, method.bind(eventInstance));
}
}
}
}
let replaceServer = function (slist: ServerInfo[], serverInfo: ServerInfo) {
for (let i = 0, l = slist.length; i < l; i++) {
if (slist[i].id === serverInfo.id) {
slist[i] = serverInfo;
return;
}
}
slist.push(serverInfo);
};
let removeServer = function (slist: ServerInfo[], id: string) {
if (!slist || !slist.length) {
return;
}
for (let i = 0, l = slist.length; i < l; i++) {
if (slist[i].id === id) {
slist.splice(i, 1);
return;
}
}
};
let contains = function (str: string, settings: string) {
if (!settings) {
return false;
}
let ts = settings.split('|');
for (let i = 0, l = ts.length; i < l; i++) {
if (str === ts[i]) {
return true;
}
}
return false;
};
let addFilter = function <T>(app: Application, type: string, filter: T) {
let filters = app.get(type);
if (!filters) {
filters = [];
app.set(type, filters);
}
filters.push(filter);
};