UNPKG

@hosoft/restful-api-framework

Version:

Base framework of the headless cms HoServer provided by http://helloreact.cn

241 lines (201 loc) 7.62 kB
/* eslint-disable handle-callback-err */ /** * HoServer API Server Ver 2.0 * Copyright http://hos.helloreact.cn * * create: 2020/06/05 **/ const _ = require('lodash') const Backend = require('i18next-fs-backend') const bodyParser = require('body-parser') const config = require('@hosoft/config') const Constants = require('./base/constants/constants') const cors = require('cors') const Database = require('./db') const express = require('express') const fileUtils = require('./utils/file-utils') const fs = require('fs') const i18next = require('i18next') const path = require('path') const schedule = require('node-schedule') const sleep = require('./utils/sleep') const winston = require('./utils/winston-log') require('body-parser-xml')(bodyParser) process.on('unhandledRejection', (reason, p) => { const err = reason.stack || reason logger.error('Unhandled Rejection: ' + (typeof err === 'object' ? JSON.stringify(err) : err)) }) class Bootstrap { constructor(workingDir) { if (!global.APP_PATH) { global.APP_PATH = workingDir || process.cwd() } global.logger = winston.getLogger() // for cluster mode this.clusterHasInit = false this.isMaster = false this.discover = null } async startServer(app, port, callback) { const clusterMode = process.env.CLUSTER_MODE if (clusterMode) { this.initClusterMode(port, clusterMode) // wait cluster init while (!this.clusterHasInit) { await sleep(1000) } } await this.initL18n() await Database.init() await this.startExpressApp(app, port, callback) } async initL18n() { // prettier-ignore const tf = await i18next.createInstance().use(Backend).init({ // debug: true, initImmediate: false, fallbackLng: 'en', lng: config.get('server.language') || 'en', ns: ['framework'], defaultNS: 'framework', backend: { loadPath: path.join(__dirname, 'locales/{{lng}}/{{ns}}.json') } }) // prettier-ignore const t = await i18next.createInstance().use(Backend).init({ lng: config.get('server.language') || 'en', fallbackLng: 'en', ns: ['app'], defaultNS: 'app', backend: { loadPath: path.join(global.APP_PATH, 'locales/{{lng}}/{{ns}}.json') } }) // t is for app, while tf if internal used in framework global.tf = tf global.t = t } async initClusterMode(port, clusterMode) { const Discover = require('node-discover') const discover = Discover({ // helloInterval: 5000, port: 3100, start: false }) if (clusterMode.toString().toLowerCase() !== 'master') { discover.demote(true) } // promoted to a master discover.on('promotion', (obj) => { logger.info('node promotion to a cluster master node: ' + obj.address) this.isMaster = true this.clusterHasInit = true }) // demoted from being a master discover.on('demotion', () => { this.isMaster = false }) discover.on('added', (obj) => { if (this.isMaster) { discover.send('master-address', { port }) } }) // d.on('removed', (obj) => { }) discover.on('master', (obj) => { logger.info('cluster master node update: ' + obj.address) this.isMaster = !!(_.get(discover, ['broadcast', 'instanceUuid']) === obj.id) }) config.getOptions().onSet = (key, value) => { discover.send('config-set', { key, value }) } let success = discover.join('config-set', (data) => { logger.info(`get cluster config set, ${data.key}: ${data.value}`) if (data.key) { config.set(data.key, data.value) config.save() } }) // prettier-ignore success = success && discover.join("master-address", async (data, eventInfo, nodeInfo) => { if (this.clusterHasInit) { return } const masterHost = nodeInfo.address const masterPort = data.port const result = await fileUtils.getWebFileContent( `http://${masterHost}:${masterPort}${Constants.API_PREFIX}/system/configs` ) if (result.code / 1 !== 200) { console.error('get config from cluster master node failed!', result.message) process.exit() } const configContent = result.data fileUtils.saveJsonFile(config.getConfigFile(), configContent, true) config.reload() logger.info(`cluster node init success, master node: ${masterHost}(${masterPort})`) this.clusterHasInit = true setTimeout(() => { discover.demote(false) // now allow to be master }, discover.settings.checkInterval * 5) }) if (!success) { console.error('join cluster channel failed!') process.exit() } discover.start() } async startExpressApp(_app, port, callback) { const app = _app || express() app.set('views', path.join(__dirname, 'views')) app.set('view engine', 'ejs') const allowOrigins = config.get('server.allowOrigins') ? config.get('server.allowOrigins').split(',') : [] console.log('allowOrigins: ' + allowOrigins) const corsOptions = { origin: allowOrigins } app.use(cors(corsOptions)) app.use(bodyParser.json({ limit: '128mb' })) app.use(bodyParser.urlencoded({ limit: '128mb', extended: true })) app.use( bodyParser.xml({ limit: '128mb', xmlParseOptions: { normalize: true, normalizeTags: true, explicitArray: false } }) ) // init routers const container = require('./base/container').getInstance() callback && callback('beforeStart', container) container.initialize(app, () => { // start listen app.listen(port) // cron jobs, for long time execute jobs, please run it in standalone process const enableJob = config.get('server.enableJob') || false if (enableJob) { const taskDir = global.APP_PATH + '/jobs' fs.readdirSync(taskDir) .filter(function (file) { return path.extname(file) === '.js' }) .forEach(function (file) { const job = require(path.join(taskDir, file)) if (job.cron && job.func) { schedule.scheduleJob(job.cron, job.func) logger.info(`cron job scheduled ${file} cron: ${job.cron}`) } }) console.log('BACKGROUND JOBS: enabled') } else { console.log('BACKGROUND JOBS: disabled') } callback && callback('startSucess') }) } } module.exports = function (workingDir) { return new Bootstrap(workingDir) }