'use strict';
const logger = require('@sex-pomelo/sex-pomelo-logger').getLogger('pomelo');
const fs= require('fs');
const path = require('path');
/**
* @typedef {import('../lib/application').Application} Application
*/
/** PluginCfg
* @typedef {Object} PluginCfg
* @property {string} package - plugin require path
* @property {String} name - plugin name
* @property {String} serverType - plugin use serverType
* @property {Object} cfg - plugin config
*/
/** Cfg
* @typedef {Object} SexAppCfg
* @property {string} name - app name
* @property {PluginCfg[]} plugins - 插件
*/
/**
* BaseApp is a base class that can be extended.
*/
class BaseApp {
constructor( pomelo ){
this.pomelo = pomelo;
/** @type {Application} */
this.app = pomelo.createApp();
/** Server ID
* @type {string}
*/
this.serverId = this.app.serverId;
/** Server Type
* @type {string}
*/
this.serverType = this.app.serverType;
/** @type {SexAppCfg} */
this.cfg = {};
this.routeFun = {};
this.routeFileCTime = 0; // route file change Time
process.on('uncaughtException', (err) =>{
let szErr = err.stack.toString();
if(szErr.indexOf('read ECONNRESET') !== -1){
return;
}
console.error(' Caught exception: ' + szErr);
let szPre = "\n\n ----- 1: " + this.serverId + " " + (new Date());
logger.warn(szPre + szErr);
});
if( typeof(this.preLoadCfg) === 'function' ){
this.preLoadCfg();
}
let cfgPath = this.app.getCfgPath('config.js');
if( fs.existsSync( cfgPath ) ){
let cfgFun = require( cfgPath );
this.cfg = cfgFun( this.app );
this.setupSet();
this.setupPlugin();
this.setupConfigs();
this.setupFilters();
this.setupComponent();
this.setupRoute();
}
if( typeof(this.preStart) === 'function' ){
this.preStart();
}
this.app.start();
if( typeof(this.postStart) === 'function' ){
this.postStart();
}
}
setRouteFunction( routeType, fn){
this.routeFun[ routeType] = fn;
}
/** setup some set
*
*/
setupSet(){
const {cfg,app} = this;
if( typeof(cfg.name) === 'string' ){
this.app.set('name', this.cfg.name);
}
if( cfg.connectorConfig ){
app.configure(() =>{
app.set('connectorConfig',
{
connector : this.pomelo.connectors[cfg.connectorConfig.connectors],
transports: cfg.connectorConfig.transports,
disconnectOnTimeout: (cfg.connectorConfig.disconnectOnTimeout === true),
heartbeat : cfg.connectorConfig.heartbeat,
timeout : cfg.connectorConfig.timeout,
// enable useProto
useProtobuf: (cfg.connectorConfig.useProtobuf === true),
useDict: (cfg.connectorConfig.useDict === true),
useAsyncSend: (cfg.connectorConfig.useAsyncSend === true),
});
});
}
app.set('errorHandler', this.errorHandler);
}
/**
* setup plugin
*/
setupPlugin(){
const {cfg, app} = this;
if( Array.isArray(cfg.plugins) === false || cfg.plugins.length === 0 ) {
return;
}
for( let it of cfg.plugins ){
let load = false;
if( !it.serverType || it.serverType === '' ){
load = true;
} else {
if( contains( this.serverType, it.serverType ) ){
load = true;
}
}
if( load === true ){
let plug = require( it.package );
app.use(plug, it.cfg);
}
}
}
setupConfigs (){
const {cfg, app} = this;
if( Array.isArray(cfg.configs) === false || cfg.configs.length === 0 ) {
return;
}
for( let it of cfg.configs ){
let load = false;
if( !it.serverType || it.serverType === '' ){
load = true;
} else {
if( contains( this.serverType, it.serverType ) ){
load = true;
}
}
//console.log( '--- cfg,', it.name, load, it.serverType, this.serverType );
if( load === true ){
if( typeof(it.cfg) === 'string') {
app.loadConfig(it.name, app.getCfgPath( it.cfg));
} else {
app.loadConfig(it.name, it.cfg);
}
}
}
}
setupComponent(){
const {cfg, app} = this;
if( Array.isArray(cfg.components) === false || cfg.components.length === 0 ) {
return;
}
let base = this.app.getBase();
base = path.join(base,'app/components');
for( let it of cfg.components ){
let load = false;
if( !it.serverType || it.serverType === '' ){
load = true;
} else {
if( contains( this.serverType, it.serverType ) ){
load = true;
}
}
if( load === true ){
let comp = null;
if( it.name.startsWith('__') && it.name.endsWith('__') ) {
let name = it.name.substring(2, it.name.length - 2);
comp = this.pomelo[name];
} else {
let compPath = path.join( base, it.name );
comp = require( compPath );
}
let cfgDef = {};
if( typeof(it.serverTypeNick) === 'string' && it.serverTypeNick.length > 0 ){
cfgDef[it.serverTypeNick] = this.serverType;
}
if( typeof(it.serverIdNick) === 'string' && it.serverIdNick.length > 0 ){
cfgDef[it.serverIdNick] = this.serverId;
}
let cfgComp = it.cfg? it.cfg: {};
cfgComp = { ...cfgComp,...cfgDef};
app.load( comp, cfgComp );
//console.log( '---', this.serverType, it.name, cfgComp );
}
}
}
setupFilters(){
const {cfg, app} = this;
if( Array.isArray(cfg.filters) === false || cfg.filters.length === 0 ) {
return;
}
let base = this.app.getBase();
for( let it of cfg.filters ){
let load = false;
if( !it.serverType || it.serverType === '' ){
load = true;
} else {
if( contains( this.serverType, it.serverType ) ){
load = true;
}
}
if( load === true ){
let isInterFilter = false;
let filterPath = path.join( base, it.package );
if( fs.existsSync( filterPath) === false){
filterPath += '.js';
if( fs.existsSync( filterPath ) === false ){
if( this.pomelo.filters[it.package] ){
isInterFilter = true;
}
}
}
let paras = it.argv ? it.argv : [];
if( isInterFilter === false ){
let f = require( filterPath );
app.filter(f(... paras));
} else {
let f = this.pomelo.filters[it.package];
app.filter( f(... paras) );
}
}
//console.log( '--- filter', load, this.serverType, it.package );
}
}
setupRoute(){
const {cfg, app} = this;
const { route:rCfg } = cfg;
if( typeof(rCfg) !== 'object' ) {
return;
}
let basePath = app.getBase();
let routeJsonFile = app.getCfgPath(rCfg.cfg);
if( contains( this.serverType, rCfg.serverType ) === false ){
// virtual load RouteFun
if( rCfg.routeFunc ) {
for( let rType in rCfg.routeFunc ){
this.routeFun[rType] = require( path.join( basePath, rCfg.routeFunc[rType]) );
}
}
this.checkRoute(rCfg, () =>{
this.routeFun = {};
if( rCfg.routeFunc ) {
for( let rType in rCfg.routeFunc ){
let maJs = require.resolve( path.join( basePath, rCfg.routeFunc[rType]));
delete require.cache[ maJs ];
}
}
});
return;
}
// load RouteFun
if( rCfg.routeFunc ) {
for( let rType in rCfg.routeFunc ){
this.routeFun[rType] = require( path.join( basePath, rCfg.routeFunc[rType]) );
}
}
if( rCfg.checkInterval > 1000 ){
this.setRoute(rCfg.cfg);
setInterval( ()=>{ this.setRoute(rCfg.cfg);}, rCfg.checkInterval );
} else {
this.setRoute(rCfg.cfg);
}
}
checkRoute(rCfg, cb){
const {app} = this;
let routeJsonFile = app.getCfgPath(rCfg.cfg);
fs.readFile( routeJsonFile,(err,data) =>{
if( !err ){
let routeJson = JSON.parse(data);
let ignoreSer = routeJson.ignoreSer;
if( !ignoreSer ){
return;
}
if( contains( this.serverType, ignoreSer ) === false ){
for( let rType in routeJson.route){
if( routeJson.route[rType].indexOf( this.serverType) !== -1 ){
if( this.routeFun[rType] ) {
cb();
return;
}
}
}
logger.error(`xxxxxxxx ${this.serverType} route not defined!`);
process.exit(2);
}
} else {
logger.error(err.toString());
process.exit(2);
}
});
}
/** Setup route
* @param {string} cfgFile route json config file name
*
*/
async setRoute( cfgFile ){
const {app} = this;
try{
let stat = await fs.promises.stat(app.getCfgPath(cfgFile) );
if( this.routeFileCTime === stat.mtimeMs){
return;
}
this.routeFileCTime = stat.mtimeMs;
let routeJson = await app.getCfg( cfgFile, true );
let routes = app.get('__routes__');
for( let rType in routeJson.route){
if( this.routeFun[rType] ){
for( let ser of routeJson.route[rType] ){
let set = (!routes || !routes[ser]) ? true : false;
if( set === true ){
app.route(ser, this.routeFun[rType]);
logger.info( '--- set route', this.serverType, ser, this.routeFun[rType] );
}
}
}
}
} catch (err) {
logger.error(err.toString());
}
}
errorHandler( err,msg,resp,session,next ){
let szPre = "\n\n ----- 2: " + this.serverId + " " + (new Date());
logger.warn(szPre + err.stack);
next(null,{code:500,error:false,data:''});
}
};
let contains = function(str, settings) {
if(!settings) {
return false;
}
let exclude = ( settings.charAt(0) === '!' );
let ts = settings.split("|");
if( exclude ){
ts[0] = ts[0].substr(1);
}
return exclude?(ts.indexOf(str) === -1) : (ts.indexOf(str) !== -1);
};
module.exports = BaseApp;