Showing
29 changed files
with
1908 additions
and
0 deletions
.eslintrc.js
0 → 100644
| 1 | +module.exports = { | ||
| 2 | + "env": { | ||
| 3 | + "es6": true, | ||
| 4 | + "node": true | ||
| 5 | + }, | ||
| 6 | + "extends": "eslint:recommended", | ||
| 7 | + "parserOptions": { | ||
| 8 | + "ecmaVersion": 2019, | ||
| 9 | + "sourceType": "module" | ||
| 10 | + }, | ||
| 11 | + "rules": { | ||
| 12 | + // "indent": [ | ||
| 13 | + // "warn", | ||
| 14 | + // 4 | ||
| 15 | + // ], | ||
| 16 | + "linebreak-style": "off", | ||
| 17 | + "quotes": "off", | ||
| 18 | + "semi": [ | ||
| 19 | + "off", | ||
| 20 | + "always" | ||
| 21 | + ], | ||
| 22 | + "no-console": [ | ||
| 23 | + "error", { allow: ["info", "warn", "error"]} | ||
| 24 | + ], | ||
| 25 | + "no-unused-vars": [ | ||
| 26 | + "warn", | ||
| 27 | + { "vars": "all", "args": "after-used", "ignoreRestSiblings": false } | ||
| 28 | + ], | ||
| 29 | + "no-constant-condition": [ | ||
| 30 | + "warn" | ||
| 31 | + ], | ||
| 32 | + "no-control-regex": "off", | ||
| 33 | + "no-redeclare": "off", | ||
| 34 | + "require-atomic-updates": "off", | ||
| 35 | + "no-prototype-builtins": 'off' | ||
| 36 | + } | ||
| 37 | +}; |
.jshintrc
0 → 100644
index.js
0 → 100644
| 1 | +require('./server/server'); | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
package.json
0 → 100644
| 1 | +{ | ||
| 2 | + "name": "ncov", | ||
| 3 | + "version": "1.0.0", | ||
| 4 | + "description": "", | ||
| 5 | + "main": "index.js", | ||
| 6 | + "scripts": { | ||
| 7 | + "test": "echo \"Error: no test specified\" && exit 1", | ||
| 8 | + "check": "eslint ./server --quiet" | ||
| 9 | + }, | ||
| 10 | + "keywords": [], | ||
| 11 | + "author": "", | ||
| 12 | + "license": "MIT", | ||
| 13 | + "dependencies": { | ||
| 14 | + "body-parser": "^1.19.0", | ||
| 15 | + "connect-redis": "^4.0.3", | ||
| 16 | + "cookie-parser": "^1.4.4", | ||
| 17 | + "express": "^4.17.1", | ||
| 18 | + "express-session": "^1.17.0", | ||
| 19 | + "fs-extra": "^8.1.0", | ||
| 20 | + "kml-api-request": "git+ssh://git@gitlab.kmlab.com/comm/api-request.git#1.2.0", | ||
| 21 | + "kml-customize": "git+ssh://git@gitlab.kmlab.com/comm/customize.git#1.0.0", | ||
| 22 | + "kml-express-stage-lib": "git+ssh://git@gitlab.kmlab.com/comm/express-stage-lib.git#1.1.0", | ||
| 23 | + "kml-express-stage-mw": "git+ssh://git@gitlab.kmlab.com/comm/express-stage-mw.git#1.1.0", | ||
| 24 | + "kml-id-worker": "^1.0.4", | ||
| 25 | + "kml-oauth-app-client": "git+ssh://git@gitlab.kmlab.com/danny/kml-oauth-app-client.git#1.5.0", | ||
| 26 | + "kml-original-url-mw": "git+ssh://git@gitlab.kmlab.com/comm/original-url-mw.git#1.0.0", | ||
| 27 | + "kml-po-abstract": "git+ssh://git@gitlab.kmlab.com/entities/po-abstract.git#1.1.2", | ||
| 28 | + "kml-redis-tools": "git+ssh://git@gitlab.kmlab.com/comm/redis-tools.git#1.0.0", | ||
| 29 | + "kml-si-client": "git+ssh://git@gitlab.kmlab.com/comm/si-client.git#1.1.0", | ||
| 30 | + "kml-si-oauth-sdk": "git+ssh://git@gitlab.kmlab.com/saas/oauth-sdk.git#1.2.0", | ||
| 31 | + "kml-weapp-mw": "git+ssh://git@gitlab.kmlab.com/comm/weapp-mw.git#1.2.1", | ||
| 32 | + "kml-wxapi": "git+ssh://git@gitlab.kmlab.com/comm/wxapi.git#1.16.0", | ||
| 33 | + "log4js": "^6.1.0", | ||
| 34 | + "method-override": "^3.0.0", | ||
| 35 | + "moment": "^2.24.0", | ||
| 36 | + "node-cron": "^2.0.3", | ||
| 37 | + "pg": "^7.17.1", | ||
| 38 | + "redis": "^2.8.0", | ||
| 39 | + "request-ip": "^2.1.3", | ||
| 40 | + "sequelize": "^5.21.3", | ||
| 41 | + "shelljs": "^0.8.3" | ||
| 42 | + } | ||
| 43 | +} |
server/action/bzapi-action.js
0 → 100644
| 1 | +/** | ||
| 2 | + * 处理接口api的action路由 | ||
| 3 | + */ | ||
| 4 | +/* global global */ | ||
| 5 | +/* global process */ | ||
| 6 | +"use strict"; | ||
| 7 | + | ||
| 8 | +function BZApiAction (routerPath) { | ||
| 9 | + routerPath = routerPath || ''; | ||
| 10 | + | ||
| 11 | + const _ = require('lodash'), | ||
| 12 | + express = require('express'), | ||
| 13 | + router = express.Router(), | ||
| 14 | + authorizer = require('kml-express-stage-mw').authorizer, | ||
| 15 | + bz_authorizer = require('../lib/mw/bz_authorizer'), | ||
| 16 | + rawXML = require('kml-express-stage-mw').rawXML, | ||
| 17 | + execApi = require('kml-express-stage-mw').execApi, | ||
| 18 | + default_params = require('../lib/mw/default_params'); | ||
| 19 | + | ||
| 20 | + const fn_request = function (req, res, next) { | ||
| 21 | + return execApi(routerPath, req, res, next); | ||
| 22 | + }; | ||
| 23 | + | ||
| 24 | + //session中记录ip | ||
| 25 | + router.use(function (req, res, next) { | ||
| 26 | + req.session.clientIp = req.clientIp; | ||
| 27 | + next && next(); | ||
| 28 | + }); | ||
| 29 | + | ||
| 30 | + //api解析 | ||
| 31 | + const mw_api = require('kml-express-stage-mw').restfulApi({routerPath: routerPath}); | ||
| 32 | + const API_REGEXP = mw_api.regexp; | ||
| 33 | + router.use(mw_api.mw); | ||
| 34 | + | ||
| 35 | + //验证身份 | ||
| 36 | + router.use(authorizer()); | ||
| 37 | + //验证后台身份 | ||
| 38 | + router.use(bz_authorizer()); | ||
| 39 | + //添加default_params | ||
| 40 | + router.use(default_params()); | ||
| 41 | + //解析xml | ||
| 42 | + router.use(rawXML()); | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + //等于各种方法的绑定,注意需要直接使用router[method],express内部绑定时需要用到this | ||
| 46 | + ['get', 'post', 'put', 'delete'].forEach(method => { | ||
| 47 | + router[method] && router[method](API_REGEXP, fn_request); | ||
| 48 | + }); | ||
| 49 | + | ||
| 50 | + | ||
| 51 | + return router; | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +module.exports = BZApiAction; |
server/action/mpapi-action.js
0 → 100644
| 1 | +/** | ||
| 2 | + * 处理接口api的action路由 | ||
| 3 | + */ | ||
| 4 | +/* global global */ | ||
| 5 | +/* global process */ | ||
| 6 | +"use strict"; | ||
| 7 | + | ||
| 8 | +function MpApiAction (routerPath) { | ||
| 9 | + routerPath = routerPath || ''; | ||
| 10 | + | ||
| 11 | + const _ = require('lodash'), | ||
| 12 | + express = require('express'), | ||
| 13 | + router = express.Router(), | ||
| 14 | + config = global.config, | ||
| 15 | + authorizer = require('kml-express-stage-mw').authorizer, | ||
| 16 | + rawXML = require('kml-express-stage-mw').rawXML, | ||
| 17 | + execApi = require('kml-express-stage-mw').execApi, | ||
| 18 | + default_params = require('../lib/mw/default_params'); | ||
| 19 | + | ||
| 20 | + const fn_request = function (req, res, next) { | ||
| 21 | + return execApi(routerPath, req, res, next); | ||
| 22 | + }; | ||
| 23 | + | ||
| 24 | + //session中记录ip | ||
| 25 | + router.use(function (req, res, next) { | ||
| 26 | + req.session.clientIp = req.clientIp; | ||
| 27 | + next && next(); | ||
| 28 | + }); | ||
| 29 | + | ||
| 30 | + //api解析 | ||
| 31 | + const mw_api = require('kml-express-stage-mw').restfulApi({routerPath: routerPath}); | ||
| 32 | + const mw_oauth = require('kml-wxapi/lib/mw/oauth')({ | ||
| 33 | + api_regexp: ['/t/wx/index'], | ||
| 34 | + wechat: config.wechat, | ||
| 35 | + server_id: config.system.project_name, | ||
| 36 | + wx_state: config.system.project_name | ||
| 37 | + }); | ||
| 38 | + const API_REGEXP = mw_api.regexp; | ||
| 39 | + router.use(mw_api.mw); | ||
| 40 | + //解析用户微信授权 | ||
| 41 | + router.use(mw_oauth.mw); | ||
| 42 | + //验证身份 | ||
| 43 | + router.use(authorizer()); | ||
| 44 | + //添加default_params | ||
| 45 | + router.use(default_params()); | ||
| 46 | + //解析xml | ||
| 47 | + router.use(rawXML()); | ||
| 48 | + | ||
| 49 | + | ||
| 50 | + //等于各种方法的绑定,注意需要直接使用router[method],express内部绑定时需要用到this | ||
| 51 | + ['get', 'post', 'put', 'delete'].forEach(method => { | ||
| 52 | + router[method] && router[method](API_REGEXP, fn_request); | ||
| 53 | + }); | ||
| 54 | + | ||
| 55 | + return router; | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +module.exports = MpApiAction; |
server/action/weapi-action.js
0 → 100644
| 1 | +/** | ||
| 2 | + * 处理微信小程序接口api的action路由 | ||
| 3 | + */ | ||
| 4 | +/* global global */ | ||
| 5 | +/* global process */ | ||
| 6 | +"use strict"; | ||
| 7 | + | ||
| 8 | +function WeApiAction (routerPath) { | ||
| 9 | + routerPath = routerPath || ''; | ||
| 10 | + | ||
| 11 | + const _ = require('lodash'), | ||
| 12 | + express = require('express'), | ||
| 13 | + router = express.Router(), | ||
| 14 | + config = global.config, | ||
| 15 | + mock_sim = require('kml-express-stage-mw').mockSim, | ||
| 16 | + authorizer = require('kml-express-stage-mw').authorizer, | ||
| 17 | + rawXML = require('kml-express-stage-mw').rawXML, | ||
| 18 | + execApi = require('kml-express-stage-mw').execApi, | ||
| 19 | + default_params = require('../lib/mw/default_params'), | ||
| 20 | + weapp_param_mw = require('../lib/mw/weapp-params'); | ||
| 21 | + | ||
| 22 | + const fn_request = function (req, res, next) { | ||
| 23 | + return execApi(routerPath, req, res, next); | ||
| 24 | + }; | ||
| 25 | + | ||
| 26 | + //session中记录ip | ||
| 27 | + router.use(function (req, res, next) { | ||
| 28 | + req.session.clientIp = req.clientIp; | ||
| 29 | + next && next(); | ||
| 30 | + }); | ||
| 31 | + | ||
| 32 | + //微信小程序访问token解析 | ||
| 33 | + const token_parser = require('kml-weapp-mw').TokenParser({ | ||
| 34 | + redis: require('../init/redis-promisify'), | ||
| 35 | + strict_mode: config.weapp.strict_mode | ||
| 36 | + }); | ||
| 37 | + router.use(token_parser.mw); | ||
| 38 | + | ||
| 39 | + //处理微信小程序的参数 | ||
| 40 | + router.use(weapp_param_mw()); | ||
| 41 | + | ||
| 42 | + //api解析 | ||
| 43 | + const mw_api = require('kml-express-stage-mw').restfulApi({routerPath: routerPath}); | ||
| 44 | + const mw_oauth = require('kml-wxapi/lib/mw/oauth')({ | ||
| 45 | + api_regexp: ['/t/wx/index'], | ||
| 46 | + wechat: config.wechat, | ||
| 47 | + server_id: config.system.project_name, | ||
| 48 | + wx_state: config.system.project_name | ||
| 49 | + }); | ||
| 50 | + const API_REGEXP = mw_api.regexp; | ||
| 51 | + router.use(mw_api.mw); | ||
| 52 | + //解析用户微信授权 | ||
| 53 | + router.use(mw_oauth.mw); | ||
| 54 | + //mock数据解析 | ||
| 55 | + router.use(mock_sim()); | ||
| 56 | + //验证身份 | ||
| 57 | + router.use(authorizer()); | ||
| 58 | + //添加default_params | ||
| 59 | + router.use(default_params()); | ||
| 60 | + //解析xml | ||
| 61 | + router.use(rawXML()); | ||
| 62 | + | ||
| 63 | + | ||
| 64 | + //等于各种方法的绑定,注意需要直接使用router[method],express内部绑定时需要用到this | ||
| 65 | + ['get', 'post', 'put', 'delete'].forEach(method => { | ||
| 66 | + router[method] && router[method](API_REGEXP, fn_request); | ||
| 67 | + }); | ||
| 68 | + | ||
| 69 | + | ||
| 70 | + return router; | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +module.exports = WeApiAction; |
server/cron/alive.js
0 → 100644
server/cron/crawler.js
0 → 100644
| 1 | +/** | ||
| 2 | + * run | ||
| 3 | + * Created by lintry on 2020/1/28. | ||
| 4 | + */ | ||
| 5 | +const config = global.config; | ||
| 6 | +const dbo = global.sequelize; | ||
| 7 | +const DataImporter = require('../service/data_importer'); | ||
| 8 | +const data_importer = new DataImporter(dbo); | ||
| 9 | +const NCOV_URL = config.NCOV.API_URL; | ||
| 10 | +const Result = require('kml-express-stage-lib').Result; | ||
| 11 | + | ||
| 12 | +module.exports = async (cfg) => { | ||
| 13 | + await data_importer.import_url(NCOV_URL, {latest: 0}) | ||
| 14 | + return Result.Ok(`Fetched from ${NCOV_URL}`); | ||
| 15 | +}; |
server/cron/index.js
0 → 100644
| 1 | +/** | ||
| 2 | + * Created by lintry on 18/12/20. | ||
| 3 | + */ | ||
| 4 | +"use strict"; | ||
| 5 | +/** | ||
| 6 | + * 定时器定义 | ||
| 7 | + */ | ||
| 8 | +const fs = require('fs-extra'), | ||
| 9 | + path = require('path'), | ||
| 10 | + cron = require('node-cron'), | ||
| 11 | + moment = require('moment'), | ||
| 12 | + SCHEDULE = require('../init/config').schedule, | ||
| 13 | + Result = require('kml-express-stage-lib').Result, | ||
| 14 | + logger = require('../init/log4js-init').cron; | ||
| 15 | +const TTL = global.config.cache.ttl.CRON_LOCK; | ||
| 16 | +const lockKey = require('../modules/lockKey'); | ||
| 17 | +const {sequelize, po} = global; | ||
| 18 | +const id_worker = require('../modules/id_worker'); | ||
| 19 | + | ||
| 20 | +module.exports = function () { | ||
| 21 | + let task_list = []; | ||
| 22 | + | ||
| 23 | + // 遍历配置的定时器 | ||
| 24 | + Object.keys(SCHEDULE).forEach(cron_id => { | ||
| 25 | + let cfg = SCHEDULE[cron_id]; | ||
| 26 | + | ||
| 27 | + if (cfg.enable && fs.existsSync(path.resolve(__dirname, cron_id + '.js'))) { | ||
| 28 | + // 以键值在当前目录下加载异步执行模块 | ||
| 29 | + const async_runner = require('./' + cron_id); | ||
| 30 | + if (!async_runner) { | ||
| 31 | + logger.error(`没有找到可以执行的模块`, cfg.name); | ||
| 32 | + return; | ||
| 33 | + } | ||
| 34 | + if (!cron.validate(cfg.cron)) { | ||
| 35 | + logger.error('定时器的时间定义错误', cfg.cron); | ||
| 36 | + return; | ||
| 37 | + } | ||
| 38 | + logger.info('开启定时器', cfg.name, cfg.cron); | ||
| 39 | + task_list.push(cron.schedule(cfg.cron, async function () { | ||
| 40 | + let result = await lockKey.lockKey(`cron:${cron_id}`, TTL); | ||
| 41 | + if (result.locked) { | ||
| 42 | + | ||
| 43 | + let result; | ||
| 44 | + try { | ||
| 45 | + result = await async_runner(cfg); | ||
| 46 | + } catch (e) { | ||
| 47 | + result = Result.Error(e.message); // 记录执行的错误 | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + logger.info(moment().format('YYYY-MM-DD HH:mm:ss'), `${cfg.name} logger =>`, result.ret, result.msg, result.content); | ||
| 51 | + | ||
| 52 | + } | ||
| 53 | + }, {scheduled: false})); | ||
| 54 | + } | ||
| 55 | + }); | ||
| 56 | + | ||
| 57 | + return task_list; | ||
| 58 | +}; |
server/crontab.js
0 → 100644
| 1 | +"use strict"; | ||
| 2 | +const config = require('./init/config'); | ||
| 3 | + | ||
| 4 | +// 定义log4js 包含业务日志和系统日志 | ||
| 5 | +const logger = require('./init/log4js-init').system; | ||
| 6 | + | ||
| 7 | +// 定义db | ||
| 8 | +logger.info('init db'); | ||
| 9 | +require('./init/sequelize-init'); | ||
| 10 | + | ||
| 11 | +//bind exception event to log | ||
| 12 | +process.on('uncaughtException', function (e) { | ||
| 13 | + logger.error('uncaughtException from process', e); | ||
| 14 | +}); | ||
| 15 | + | ||
| 16 | +process.on('unhandledRejection', (e) => { | ||
| 17 | + logger.warn('unhandledRejection from process', e); | ||
| 18 | +}); | ||
| 19 | + | ||
| 20 | +process.on('rejectionHandled', (e) => { | ||
| 21 | + logger.warn('rejectionHandled from process', e); | ||
| 22 | +}); | ||
| 23 | + | ||
| 24 | +// 准备定时器 | ||
| 25 | +const redis_client = require('./init/redis-promisify'), | ||
| 26 | + LockKey = require('kml-redis-tools').LockKey; | ||
| 27 | +const lockKey = new LockKey(redis_client); | ||
| 28 | + | ||
| 29 | +lockKey.lockKey('cron:' + config.system.project_name, config.cache.ttl.CRON_LOCK) | ||
| 30 | + .then(lock_result => { | ||
| 31 | + if (lock_result.locked) { | ||
| 32 | + const task_list = require('./cron')(); | ||
| 33 | + if (!task_list || !task_list.length) { | ||
| 34 | + logger.warn('没有定时器被开启。'); | ||
| 35 | + } | ||
| 36 | + task_list.forEach(task => { | ||
| 37 | + task && task.start && task.start(); | ||
| 38 | + }); | ||
| 39 | + } else { | ||
| 40 | + logger.warn('定时器被锁定,请检查是否已有其他进程负责。'); | ||
| 41 | + } | ||
| 42 | + }); |
server/entities/timeline_area.js
0 → 100644
| 1 | +"use strict"; | ||
| 2 | +/** | ||
| 3 | + * 各区域时间线 | ||
| 4 | + */ | ||
| 5 | +module.exports = function (sequelize, DataTypes) { | ||
| 6 | + const | ||
| 7 | + AbstractPO = require('kml-po-abstract'), | ||
| 8 | + _ = require('lodash'); | ||
| 9 | + | ||
| 10 | + let option = AbstractPO.BaseOption({ | ||
| 11 | + tableName: 'tb_mf_timeline_area' | ||
| 12 | + }); | ||
| 13 | + | ||
| 14 | + let entity = _.merge({ | ||
| 15 | + "id": { | ||
| 16 | + "type": DataTypes.STRING(40), | ||
| 17 | + "comment": "id", | ||
| 18 | + "field": "id", | ||
| 19 | + "allowNull": false, | ||
| 20 | + "primaryKey": true | ||
| 21 | + } | ||
| 22 | + }, AbstractPO.DefaultEntity(sequelize, DataTypes), { | ||
| 23 | + "province_name": { | ||
| 24 | + "type": DataTypes.STRING(40), | ||
| 25 | + "comment": "省", | ||
| 26 | + "field": "province_name", | ||
| 27 | + "allowNull": false | ||
| 28 | + }, | ||
| 29 | + "province_short_name": { | ||
| 30 | + "type": DataTypes.STRING(40), | ||
| 31 | + "comment": "简称", | ||
| 32 | + "field": "province_short_name" | ||
| 33 | + }, | ||
| 34 | + "confirmed_count": { | ||
| 35 | + "type": DataTypes.BIGINT, | ||
| 36 | + "comment": "确认人数", | ||
| 37 | + "field": "confirmed_count" | ||
| 38 | + }, | ||
| 39 | + "suspected_count": { | ||
| 40 | + "type": DataTypes.BIGINT, | ||
| 41 | + "comment": "疑似人数", | ||
| 42 | + "field": "suspected_count" | ||
| 43 | + }, | ||
| 44 | + "cured_count": { | ||
| 45 | + "type": DataTypes.BIGINT, | ||
| 46 | + "comment": "治愈人数", | ||
| 47 | + "field": "cured_count" | ||
| 48 | + }, | ||
| 49 | + "dead_count": { | ||
| 50 | + "type": DataTypes.BIGINT, | ||
| 51 | + "comment": "死亡人数", | ||
| 52 | + "field": "dead_count" | ||
| 53 | + }, | ||
| 54 | + "comment": { | ||
| 55 | + "type": DataTypes.STRING(200), | ||
| 56 | + "comment": "注释", | ||
| 57 | + "field": "comment" | ||
| 58 | + }, | ||
| 59 | + "cities": { | ||
| 60 | + "type": DataTypes.JSONB, | ||
| 61 | + "comment": "城市", | ||
| 62 | + "field": "cities" | ||
| 63 | + }, | ||
| 64 | + "update_time": { | ||
| 65 | + "type": DataTypes.DATE, | ||
| 66 | + "comment": "更新时间", | ||
| 67 | + "field": "update_time" | ||
| 68 | + }, | ||
| 69 | + "country": { | ||
| 70 | + "type": DataTypes.STRING(40), | ||
| 71 | + "comment": "国家", | ||
| 72 | + "field": "country" | ||
| 73 | + } | ||
| 74 | + }); | ||
| 75 | + | ||
| 76 | + return sequelize.define('timeline_area', entity, option); | ||
| 77 | +}; |
server/init/config.js
0 → 100644
| 1 | +"use strict"; | ||
| 2 | + | ||
| 3 | +const configure = function () { | ||
| 4 | + const path = require('path'), | ||
| 5 | + fs = require('fs-extra'), | ||
| 6 | + chalk = require('chalk'), | ||
| 7 | + customize = require('kml-customize'), | ||
| 8 | + PROJECT_NAME = 'ncov'; | ||
| 9 | + | ||
| 10 | + let BASE_URL = `https://dev.kmlab.com/${PROJECT_NAME}/`; | ||
| 11 | + let host = '58.246.253.126'; | ||
| 12 | + let port = '5433'; | ||
| 13 | + let database = 'jhsaas'; | ||
| 14 | + let username = 'pms'; | ||
| 15 | + let password = 'pms'; | ||
| 16 | + let redisHost = '192.168.1.90'; | ||
| 17 | + let redisPort = 6379; | ||
| 18 | + let redisDB = 0; | ||
| 19 | + let redisPass = 'itomix'; | ||
| 20 | + | ||
| 21 | + const root = process.cwd(), | ||
| 22 | + server_path = 'server', //后端主目录 | ||
| 23 | + init_path = 'init', //后端初始化目录 | ||
| 24 | + lib_path = 'lib', //后端自定义库 | ||
| 25 | + modules_path = 'modules', //后端模块定义 | ||
| 26 | + routers_path = 'routers', //后端路由定义 | ||
| 27 | + entities_path = 'entities', //实体类定义 | ||
| 28 | + public_path = 'public', //静态资源目录 | ||
| 29 | + logs_path = 'logs', //日志目录 | ||
| 30 | + views_path = 'views', //前端视图输出 | ||
| 31 | + mock_path = 'mock' //mock数据目录 | ||
| 32 | + ; | ||
| 33 | + | ||
| 34 | + let config = { | ||
| 35 | + //sequelize数据库连接定义 | ||
| 36 | + sequelize: { | ||
| 37 | + database: database, | ||
| 38 | + username: username, | ||
| 39 | + password: password, | ||
| 40 | + options: { | ||
| 41 | + timezone: '+08:00', //保证时区正确 | ||
| 42 | + host: host, | ||
| 43 | + port: port, | ||
| 44 | + dialect: 'postgres', | ||
| 45 | + //storage: 'path/to/database.sqlite', //SQLite only | ||
| 46 | + pool: { | ||
| 47 | + max: 5, | ||
| 48 | + min: 0, | ||
| 49 | + idle: 10000 | ||
| 50 | + } | ||
| 51 | + }, | ||
| 52 | + syncDB: false | ||
| 53 | + }, | ||
| 54 | + redis: { | ||
| 55 | + host: redisHost, | ||
| 56 | + port: redisPort, | ||
| 57 | + db: redisDB, | ||
| 58 | + pass: redisPass | ||
| 59 | + }, | ||
| 60 | + | ||
| 61 | + //系统目录定义 | ||
| 62 | + system: { | ||
| 63 | + bind_port: 9120, //绑定端口 | ||
| 64 | + base_url: BASE_URL, //主URL | ||
| 65 | + project_name: PROJECT_NAME, //项目名 | ||
| 66 | + additional: '_action', //路由后缀 | ||
| 67 | + mock_file: '.js', //对应url请求时获取mock数据类型的文件后缀 | ||
| 68 | + mock_mode: process.env.MOCK_MODE || false, //mock模式, auto=自动|true|false | ||
| 69 | + real_mode: /^product|real$/.test(process.env.NODE_ENV) //连接真实生产环境 | ||
| 70 | + }, | ||
| 71 | + | ||
| 72 | + //系统路径 | ||
| 73 | + path: { | ||
| 74 | + ROUTERS_PATH: path.resolve(root, server_path, routers_path), //后端路由定义 | ||
| 75 | + ENTITIES_PATH: path.resolve(root, server_path, entities_path), //实体类定义 | ||
| 76 | + INIT_PATH: path.resolve(root, server_path, init_path), //后端初始化目录,固定 | ||
| 77 | + LIB_PATH: path.resolve(root, server_path, lib_path), //后端自定义库,固定 | ||
| 78 | + MODULES_PATH: path.resolve(root, server_path, modules_path), //后端模块定义 | ||
| 79 | + MOCK_PATH: path.resolve(root, mock_path), ///mock数据目录 | ||
| 80 | + PUBLIC_PATH: path.resolve(root, public_path), //静态资源目录 | ||
| 81 | + VIEWS_PATH: path.resolve(root, views_path), //前端视图输出 | ||
| 82 | + LOGS_PATH: path.resolve(root, logs_path) //日志目录 | ||
| 83 | + }, | ||
| 84 | + | ||
| 85 | + //文件上传目录定义 | ||
| 86 | + upload: { | ||
| 87 | + root: `/opt/${PROJECT_NAME}-img/`, | ||
| 88 | + base_url: null, | ||
| 89 | + appimg: 'appimg/', | ||
| 90 | + qrimg: 'qrimg/', | ||
| 91 | + wximg: 'wximg/' | ||
| 92 | + }, | ||
| 93 | + | ||
| 94 | + //微信定义 | ||
| 95 | + wechat: { | ||
| 96 | + api_host: 'http://localhost:3800', | ||
| 97 | + wx_app: 'itomix', | ||
| 98 | + api_token: '', | ||
| 99 | + authorizer_appid: '' | ||
| 100 | + }, | ||
| 101 | + | ||
| 102 | + //微信小程序 | ||
| 103 | + weapp: { | ||
| 104 | + api_host: 'http://localhost:3800', | ||
| 105 | + api_token: '', | ||
| 106 | + authorizer_appid: '', | ||
| 107 | + watermark_ttl: 7200, //敏感数据有效期(s) | ||
| 108 | + token_prefix: 'WXID', //token前缀 | ||
| 109 | + token_ttl: 36000, //token有效期(s) | ||
| 110 | + share_prefix: 'WXSHARE', //分享前缀 | ||
| 111 | + share_ttl: 3600 * 48, //分享有效期(s) | ||
| 112 | + strict_mode: true | ||
| 113 | + }, | ||
| 114 | + | ||
| 115 | + //内部应用接口验证定义 | ||
| 116 | + app_config: { | ||
| 117 | + api_token: '' | ||
| 118 | + }, | ||
| 119 | + | ||
| 120 | + //ID生成器 | ||
| 121 | + id_worker: { | ||
| 122 | + datacenterId: 9 | ||
| 123 | + }, | ||
| 124 | + | ||
| 125 | + //枚举参数 | ||
| 126 | + ENUM: { | ||
| 127 | + DEFAULT: { | ||
| 128 | + USERTYPE: 'P', | ||
| 129 | + USERPASSWORD: '8888', | ||
| 130 | + USERROLE: { | ||
| 131 | + ADMIN: 'ADMIN', | ||
| 132 | + USER: 'USER' | ||
| 133 | + }, | ||
| 134 | + DUTY: '管理员', | ||
| 135 | + PLATTYPE: 'P', | ||
| 136 | + DUTYNAME: '管理员', | ||
| 137 | + CORPTYPE: 'P', | ||
| 138 | + DUTYTYPE: 'P' | ||
| 139 | + }, | ||
| 140 | + TYPE: { | ||
| 141 | + APPLY: 'APPLY', | ||
| 142 | + ENABLE: 'ENABLE', | ||
| 143 | + FAIL: 'FAIL', | ||
| 144 | + IGNORE: 'IGNORE', | ||
| 145 | + DISABLE: 'DISABLE', | ||
| 146 | + USE: 'USE', | ||
| 147 | + USER: 'USER', | ||
| 148 | + AUTO: 'AUTO', | ||
| 149 | + DEFAULT: 'DEFAULT' | ||
| 150 | + }, | ||
| 151 | + STATE: { | ||
| 152 | + SUCCESS: 'SUCCESS', | ||
| 153 | + OK: 'OK', | ||
| 154 | + ERROR: 'ERROR' | ||
| 155 | + }, | ||
| 156 | + PLATTYPE: { | ||
| 157 | + P: 'P', | ||
| 158 | + B: 'B' | ||
| 159 | + }, | ||
| 160 | + LIMIT: 20, | ||
| 161 | + OFFSET: 0, | ||
| 162 | + DEFAULT_LIMIT: 999, | ||
| 163 | + DEFAULT_PARAMS_ARRAY: ['create_id', 'create_name', 'create_code', 'create_date', | ||
| 164 | + 'optr_id', 'optr_name', 'optr_code', 'optr_date'], | ||
| 165 | + STATUS_ARRAY: ['APPLY', 'ENABLE', 'DISABLE', 'Y', 'N', 'PUBLIC', 'PRIVATE'] | ||
| 166 | + }, | ||
| 167 | + | ||
| 168 | + // 微信跳转页 | ||
| 169 | + mpurl_map: { | ||
| 170 | + index: 'index.html' | ||
| 171 | + }, | ||
| 172 | + | ||
| 173 | + // 缓存设定 | ||
| 174 | + cache: { | ||
| 175 | + ttl: { | ||
| 176 | + PIN_TIMEOUT: 300, // 5分钟的验证码超时时间 | ||
| 177 | + AGAIN_PIN: 240, // 4分钟再次发送验证码 | ||
| 178 | + EXPRESS_TTL: 86400, // 1 day | ||
| 179 | + SESSION_TTL: 86400, // 1 day | ||
| 180 | + CRON_LOCK: 5 | ||
| 181 | + } | ||
| 182 | + }, | ||
| 183 | + | ||
| 184 | + // 定时器 | ||
| 185 | + "cron_helper": ` | ||
| 186 | + ┌────────────── second (optional) | ||
| 187 | + │ ┌──────────── minute | ||
| 188 | + │ │ ┌────────── hour | ||
| 189 | + │ │ │ ┌──────── day of month | ||
| 190 | + │ │ │ │ ┌────── month | ||
| 191 | + │ │ │ │ │ ┌──── day of week | ||
| 192 | + │ │ │ │ │ │ | ||
| 193 | + │ │ │ │ │ │ | ||
| 194 | + * * * * * * | ||
| 195 | + ss mm H D M dw | ||
| 196 | + `, | ||
| 197 | + "unique_schedule": false, // 独立运行定时器任务 | ||
| 198 | + "schedule": { | ||
| 199 | + "alive": { | ||
| 200 | + "name": "心跳检查", | ||
| 201 | + "cron": "0-59/1 * * * * *", | ||
| 202 | + "enable": false | ||
| 203 | + }, | ||
| 204 | + "crawler": { | ||
| 205 | + "name": "nCov数据抓取", | ||
| 206 | + "cron": "0-59/10 * * * * *", | ||
| 207 | + "enable": false | ||
| 208 | + }, | ||
| 209 | + }, | ||
| 210 | + | ||
| 211 | + // 获取数据的api地址 | ||
| 212 | + NCOV: { | ||
| 213 | + API_URL: 'http://lab.isaaclin.cn/nCoV/api/area' | ||
| 214 | + } | ||
| 215 | + }; | ||
| 216 | + | ||
| 217 | + //创建目录 | ||
| 218 | + let config_path = config.path; | ||
| 219 | + for (let p in config_path) { | ||
| 220 | + if (config_path.hasOwnProperty(p)) { | ||
| 221 | + let path = config_path[p]; | ||
| 222 | + fs.ensureDir(path, function (err, added_root) { | ||
| 223 | + if (err) { | ||
| 224 | + return console.error(chalk.red('create folder error'), chalk.red(JSON.stringify(err, null, 4))); | ||
| 225 | + } | ||
| 226 | + added_root && console.info(chalk.green(path + ' is created')); | ||
| 227 | + }); | ||
| 228 | + } | ||
| 229 | + } | ||
| 230 | + return customize(config); | ||
| 231 | +}(); | ||
| 232 | + | ||
| 233 | +//绑定到全局变量 | ||
| 234 | +global.config = global.config = configure; | ||
| 235 | +module.exports = configure; |
server/init/entities-init.js
0 → 100644
| 1 | +/** | ||
| 2 | + * Created by lintry on 2017/4/27. | ||
| 3 | + */ | ||
| 4 | +"use strict"; | ||
| 5 | +/** | ||
| 6 | + * 定义实体类的关联关系 | ||
| 7 | + * @return {boolean} | ||
| 8 | + */ | ||
| 9 | +module.exports = function (sequelize) { | ||
| 10 | + const logger = global.loggers.system; | ||
| 11 | + const AbstractPO = require('kml-po-abstract'); | ||
| 12 | + | ||
| 13 | + const po = new AbstractPO('../entities', __dirname); | ||
| 14 | + | ||
| 15 | + const print_entities = function (po) { | ||
| 16 | + po.forEach((entity) => { | ||
| 17 | + logger.info(`loading [${entity}]`); | ||
| 18 | + }); | ||
| 19 | + }; | ||
| 20 | + | ||
| 21 | + //加载所有的实体 | ||
| 22 | + print_entities(po.import(sequelize)); | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + // todo 定义实体间的弱关联关系 {constraints: false},或者在include时定义{association} | ||
| 26 | + | ||
| 27 | + return po; | ||
| 28 | +}; |
server/init/express-init.js
0 → 100644
| 1 | +/** | ||
| 2 | + * express初始化 | ||
| 3 | + */ | ||
| 4 | +"use strict"; | ||
| 5 | +module.exports = function () { | ||
| 6 | + let _ = require('lodash'), | ||
| 7 | + shell = require('shelljs'), | ||
| 8 | + express = require('express'), | ||
| 9 | + session = require('express-session'), | ||
| 10 | + app = express(), | ||
| 11 | + cookieParser = require('cookie-parser'), | ||
| 12 | + requestIp = require('request-ip'), | ||
| 13 | + bodyParser = require('body-parser'), | ||
| 14 | + methodOverride = require('method-override'), | ||
| 15 | + original_url_mw = require('kml-original-url-mw'), | ||
| 16 | + config = require('./config'), | ||
| 17 | + config_path = config.path, | ||
| 18 | + logger = require('./log4js-init').system; | ||
| 19 | + | ||
| 20 | + // 定义express body 中间件 | ||
| 21 | + app.use(bodyParser.urlencoded({extended: false})); | ||
| 22 | + app.use(bodyParser.json()); | ||
| 23 | + // app.use(bodyParser.raw({type: 'application/xml'})); | ||
| 24 | + app.use(methodOverride()); | ||
| 25 | + app.use(requestIp.mw()); | ||
| 26 | + | ||
| 27 | + // 加载用于解析 cookie 的中间件 | ||
| 28 | + app.use(cookieParser()); | ||
| 29 | + | ||
| 30 | + // 解析原始请求url | ||
| 31 | + app.use(original_url_mw()); | ||
| 32 | + | ||
| 33 | + const cfg_redis = config.redis; | ||
| 34 | + if (cfg_redis && cfg_redis.host) { | ||
| 35 | + let rs = shell.exec(`ping -c 3 -t 5 ${cfg_redis.host}`), sessionMiddleware; | ||
| 36 | + | ||
| 37 | + let session_cfg = { | ||
| 38 | + name: config.system.project_name + '.sid', | ||
| 39 | + secret: 'my_secret_treasure', | ||
| 40 | + resave: true, | ||
| 41 | + saveUninitialized: false, | ||
| 42 | + proxy: true | ||
| 43 | + }; | ||
| 44 | + | ||
| 45 | + if (rs.code === 0) { | ||
| 46 | + // 使用redis存储session | ||
| 47 | + let RedisStore = require('connect-redis')(session); | ||
| 48 | + | ||
| 49 | + let client = require('./redis-promisify'); | ||
| 50 | + client.on('error', logger.error); | ||
| 51 | + | ||
| 52 | + sessionMiddleware = session(_.merge({ | ||
| 53 | + store: new RedisStore({client, ttl: config.cache.ttl.SESSION_TTL}) | ||
| 54 | + }, session_cfg)); | ||
| 55 | + } else { | ||
| 56 | + logger.warn(`${cfg_redis.host} can not be connected, we will use default session instead!`); | ||
| 57 | + sessionMiddleware = session(session_cfg); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + app.use(function (req, res, next) { | ||
| 61 | + let tries = 3; | ||
| 62 | + | ||
| 63 | + function lookupSession (error) { | ||
| 64 | + if (error) { | ||
| 65 | + return next(error); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + tries--; | ||
| 69 | + if (req.session !== undefined) { | ||
| 70 | + return next(); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + if (tries < 0) { | ||
| 74 | + return next(new Error('Oops! @_@')); | ||
| 75 | + } | ||
| 76 | + sessionMiddleware(req, res, lookupSession); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + lookupSession(); | ||
| 80 | + }) | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + // 指定一个虚拟路径static挂载静态资源 | ||
| 84 | + app.use('/', express.static(config_path.PUBLIC_PATH)); | ||
| 85 | + | ||
| 86 | + return app; | ||
| 87 | +}; |
server/init/log4js-init.js
0 → 100644
| 1 | +/** | ||
| 2 | + * Created by lintry on 17/7/18. | ||
| 3 | + * log4js 2.x 初始化定义 | ||
| 4 | + */ | ||
| 5 | +"use strict"; | ||
| 6 | +const loggers = function () { | ||
| 7 | + const semver = require('semver'), | ||
| 8 | + _ = require('lodash'), | ||
| 9 | + log4js = require('log4js'), | ||
| 10 | + fs = require('fs-extra'), | ||
| 11 | + path = require('path'), | ||
| 12 | + config = require('./config'), | ||
| 13 | + config_path = config.path, | ||
| 14 | + project_name = config.system.project_name; | ||
| 15 | + | ||
| 16 | + const log_path = config_path.LOGS_PATH; | ||
| 17 | + fs.ensureDirSync(log_path); | ||
| 18 | + | ||
| 19 | + const pkg = require(path.resolve(process.cwd(), 'node_modules/log4js/package.json')), | ||
| 20 | + log4js_2_x = semver.gt(pkg.version, '2.0.0'); | ||
| 21 | + | ||
| 22 | + const appenders = [ | ||
| 23 | + {type: 'console'}, | ||
| 24 | + { | ||
| 25 | + type: 'file', | ||
| 26 | + filename: path.resolve(log_path, `${project_name}.log`), | ||
| 27 | + maxLogSize: 20480000, | ||
| 28 | + backups: 30, | ||
| 29 | + category: 'project', | ||
| 30 | + level: 'WARN' | ||
| 31 | + }, | ||
| 32 | + { | ||
| 33 | + type: 'file', | ||
| 34 | + filename: path.resolve(log_path, 'system.log'), | ||
| 35 | + maxLogSize: 20480000, | ||
| 36 | + backups: 30, | ||
| 37 | + category: 'system', | ||
| 38 | + level: 'ALL' | ||
| 39 | + }, | ||
| 40 | + { | ||
| 41 | + type: 'file', | ||
| 42 | + filename: path.resolve(log_path, 'upload.log'), | ||
| 43 | + maxLogSize: 20480000, | ||
| 44 | + backups: 30, | ||
| 45 | + category: 'upload', | ||
| 46 | + level: 'ERROR' | ||
| 47 | + }, | ||
| 48 | + { | ||
| 49 | + type: 'file', | ||
| 50 | + filename: path.resolve(log_path, 'pay.log'), | ||
| 51 | + maxLogSize: 20480000, | ||
| 52 | + backups: 30, | ||
| 53 | + category: 'pay', | ||
| 54 | + level: 'ALL' | ||
| 55 | + }, | ||
| 56 | + { | ||
| 57 | + type: 'file', | ||
| 58 | + filename: path.resolve(log_path, 'wx.log'), | ||
| 59 | + maxLogSize: 20480000, | ||
| 60 | + backups: 30, | ||
| 61 | + category: 'wx', | ||
| 62 | + level: 'ALL' | ||
| 63 | + }, | ||
| 64 | + { | ||
| 65 | + type: 'file', | ||
| 66 | + filename: path.resolve(log_path, 'cron.log'), | ||
| 67 | + maxLogSize: 20480000, | ||
| 68 | + backups: 30, | ||
| 69 | + category: 'cron', | ||
| 70 | + level: 'ALL' | ||
| 71 | + } | ||
| 72 | + ]; | ||
| 73 | + | ||
| 74 | + if (log4js_2_x) { | ||
| 75 | + const _appenders = {}, | ||
| 76 | + _categories = {}; | ||
| 77 | + | ||
| 78 | + appenders.forEach(function (appender) { | ||
| 79 | + let name = appender.category || 'default'; | ||
| 80 | + _appenders[name] = _.omit(appender, ['category', 'level']); | ||
| 81 | + _categories[name] = { appenders: [name, 'default'], level: appender.level || 'ALL'}; | ||
| 82 | + }); | ||
| 83 | + | ||
| 84 | + log4js.configure({ | ||
| 85 | + appenders: _appenders, | ||
| 86 | + categories: _categories, | ||
| 87 | + replaceConsole: true, | ||
| 88 | + pm2: true | ||
| 89 | + }); | ||
| 90 | + } else { | ||
| 91 | + log4js.configure({ | ||
| 92 | + appenders: appenders | ||
| 93 | + }); | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + const logger_export = {}; | ||
| 97 | + appenders.forEach(function (appender) { | ||
| 98 | + let name = appender.category; | ||
| 99 | + if (name) { | ||
| 100 | + let logger = log4js.getLogger(name); | ||
| 101 | + logger_export[name] = logger; | ||
| 102 | + !log4js_2_x && logger.setLevel(appender.level || 'ALL'); | ||
| 103 | + logger.trace('TRACE is enabled now!'); | ||
| 104 | + logger.debug('DEBUG is enabled now!'); | ||
| 105 | + logger.info('INFO is enabled now!'); | ||
| 106 | + logger.warn('WARN is enabled now!'); | ||
| 107 | + logger.error('ERROR is enabled now!'); | ||
| 108 | + logger.fatal('FATAL is enabled now!'); | ||
| 109 | + } | ||
| 110 | + }); | ||
| 111 | + | ||
| 112 | + return logger_export; | ||
| 113 | +}(); | ||
| 114 | + | ||
| 115 | +//绑定到全局变量 | ||
| 116 | +global.loggers = global.loggers || loggers; | ||
| 117 | +module.exports = loggers; |
server/init/redis-promisify.js
0 → 100644
| 1 | +/** | ||
| 2 | + * Created by lintry on 2016/12/21. | ||
| 3 | + */ | ||
| 4 | +"use strict"; | ||
| 5 | + | ||
| 6 | +const Promise = require('bluebird'), | ||
| 7 | + redis = require('redis'), | ||
| 8 | + config = require('./config'), | ||
| 9 | + cfg_redis = config.redis, | ||
| 10 | + client = redis.createClient({ | ||
| 11 | + host: cfg_redis.host, | ||
| 12 | + port: cfg_redis.port, | ||
| 13 | + password: cfg_redis.pass, | ||
| 14 | + db: cfg_redis.db, | ||
| 15 | + prefix: config.system.project_name + ':', | ||
| 16 | + socket_keepalive: true, | ||
| 17 | + retry_unfulfilled_commands: true | ||
| 18 | + }); | ||
| 19 | +Promise.promisifyAll(redis.RedisClient.prototype); | ||
| 20 | +Promise.promisifyAll(redis.Multi.prototype); | ||
| 21 | + | ||
| 22 | +module.exports = client; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
server/init/sequelize-init.js
0 → 100644
| 1 | +/** | ||
| 2 | + * Sequelize初始化 | ||
| 3 | + */ | ||
| 4 | +"use strict"; | ||
| 5 | +global.sequelize = global.sequelize = function () { | ||
| 6 | + const Sequelize = require('sequelize'), | ||
| 7 | + path = require('path'), | ||
| 8 | + logger = require('./log4js-init').system; | ||
| 9 | + | ||
| 10 | + const config = require('./config'), config_sequelize = config.sequelize; | ||
| 11 | + | ||
| 12 | + //custom method defined here | ||
| 13 | + config_sequelize.options.define = { | ||
| 14 | + classMethods: { | ||
| 15 | + /** | ||
| 16 | + * 根据主键查找并保存 | ||
| 17 | + * @param entityRequest | ||
| 18 | + * @param options | ||
| 19 | + * @returns {Promise.<T>} | ||
| 20 | + */ | ||
| 21 | + saveById: function (entityRequest, options) { | ||
| 22 | + let Entity = this; | ||
| 23 | + entityRequest = entityRequest || {}; | ||
| 24 | + | ||
| 25 | + //根据主键查找是否存在 | ||
| 26 | + let primaryKey = Entity.primaryKeyAttribute; | ||
| 27 | + return Entity.findById(entityRequest[primaryKey]) | ||
| 28 | + .then(function (entity) { | ||
| 29 | + if (entity) { //to update | ||
| 30 | + return entity.update(entityRequest, options); | ||
| 31 | + } else { //to create | ||
| 32 | + return Entity.create(entityRequest, options); | ||
| 33 | + } | ||
| 34 | + }) | ||
| 35 | + .catch(function (e) { | ||
| 36 | + logger.error('Class saveById', e); | ||
| 37 | + throw e; | ||
| 38 | + }); | ||
| 39 | + } | ||
| 40 | + }, | ||
| 41 | + instanceMethods: { | ||
| 42 | + /** | ||
| 43 | + * 根据主键查找并保存 | ||
| 44 | + * @param options | ||
| 45 | + * @returns {Promise.<T>} | ||
| 46 | + */ | ||
| 47 | + saveById: function (options) { | ||
| 48 | + let entityRequest = this.toJSON(); | ||
| 49 | + let Entity = this.Model; | ||
| 50 | + if (!Entity) { | ||
| 51 | + throw new Error('wrong method caller'); | ||
| 52 | + } | ||
| 53 | + //根据主键查找是否存在 | ||
| 54 | + let primaryKey = Entity.primaryKeyAttribute; | ||
| 55 | + return Entity.findById(entityRequest[primaryKey]) | ||
| 56 | + .then(function (entity) { | ||
| 57 | + if (entity) { //to update | ||
| 58 | + return entity.update(entityRequest, options); | ||
| 59 | + } else { //to create | ||
| 60 | + return Entity.create(entityRequest, options); | ||
| 61 | + } | ||
| 62 | + }) | ||
| 63 | + .catch(function (e) { | ||
| 64 | + logger.error('saveById', e); | ||
| 65 | + throw e; | ||
| 66 | + }); | ||
| 67 | + } | ||
| 68 | + } | ||
| 69 | + }; | ||
| 70 | + const sequelize = new Sequelize(config_sequelize.database, config_sequelize.username, config_sequelize.password, config_sequelize.options); | ||
| 71 | + | ||
| 72 | + //加载表 | ||
| 73 | + const entitis_init = require('./entities-init'); | ||
| 74 | + global.po = entitis_init(sequelize); | ||
| 75 | + | ||
| 76 | + if (config_sequelize.syncDB) { | ||
| 77 | + //同步表结构db | ||
| 78 | + logger.info('sync db'); | ||
| 79 | + sequelize.sync().then(function () { | ||
| 80 | + logger.info('db init finished!'); | ||
| 81 | + }); | ||
| 82 | + } else { | ||
| 83 | + logger.info('db init finished!'); | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + // 兼容4.0模式,添加属性 | ||
| 87 | + sequelize.Op = Sequelize.Op; | ||
| 88 | + sequelize.Transaction = Sequelize.Transaction; | ||
| 89 | + sequelize.Model = Sequelize.Model; | ||
| 90 | + sequelize.Utils = Sequelize.Utils; | ||
| 91 | + | ||
| 92 | + return sequelize; | ||
| 93 | +}(); | ||
| 94 | + | ||
| 95 | +module.exports = global.sequelize; |
server/lib/mw/bz_authorizer.js
0 → 100644
| 1 | +/** | ||
| 2 | + * B端后台管理身份验证 | ||
| 3 | + * Created by lintry on 2018/06/29. | ||
| 4 | + */ | ||
| 5 | +"use strict"; | ||
| 6 | +module.exports = function () { | ||
| 7 | + const Result = require('kml-express-stage-lib').Result, | ||
| 8 | + AuthAction = require('../../modules/authAction'), | ||
| 9 | + db = global.sequelize, | ||
| 10 | + logger = global.loggers.system; | ||
| 11 | + | ||
| 12 | + return function (req, res, next) { | ||
| 13 | + let apiParams = req.apiParams; | ||
| 14 | + if (!apiParams) { | ||
| 15 | + return next(); | ||
| 16 | + } | ||
| 17 | + let user = req.session.active_user; | ||
| 18 | + let action = apiParams.action; | ||
| 19 | + let routerPath = apiParams.routerPath; | ||
| 20 | + const method = apiParams.method; | ||
| 21 | + let needVerify = need_verify(action, routerPath, method); | ||
| 22 | + const userType = (user && user.user_type) ? user.user_type.toUpperCase() : ''; | ||
| 23 | + const urlType = routerPath.toUpperCase(); | ||
| 24 | + | ||
| 25 | + if (!needVerify || (userType === urlType) || userType === 'P' || urlType === 'U') { | ||
| 26 | + next && next(); | ||
| 27 | + } else { | ||
| 28 | + if (!user) { | ||
| 29 | + let authAction = new AuthAction(userType); | ||
| 30 | + return authAction.quickPass(req, res, db) | ||
| 31 | + .then(function (result) { | ||
| 32 | + if ('OK' !== result.ret) { //登录不成功 | ||
| 33 | + res.json(result); | ||
| 34 | + } else { | ||
| 35 | + const tempUserType = (result.content && result.content.user.user_type) | ||
| 36 | + ? result.content.user.user_type.toUpperCase() | ||
| 37 | + : ''; | ||
| 38 | + | ||
| 39 | + if (tempUserType === urlType || tempUserType === 'P') { | ||
| 40 | + return next && next(); | ||
| 41 | + } | ||
| 42 | + logger.error(`{error: '用户身份不匹配', tempUserType: ${tempUserType},urlType: ${urlType} `); | ||
| 43 | + return res.status(401).json(new Result(Result.ERROR, '禁止访问', '用户身份不匹配')); | ||
| 44 | + } | ||
| 45 | + }) | ||
| 46 | + .catch(err => { | ||
| 47 | + logger.error('error', err); | ||
| 48 | + res.status(401).json(new Result(Result.ERROR, '登录失败', '', err.message)); | ||
| 49 | + }); | ||
| 50 | + } | ||
| 51 | + return res.status(401).json(new Result(Result.ERROR, '禁止访问', '用户身份不匹配')); | ||
| 52 | + } | ||
| 53 | + } | ||
| 54 | +}; | ||
| 55 | + | ||
| 56 | +/** | ||
| 57 | + * 验证用户操作是否需要验证权限 | ||
| 58 | + * @param action | ||
| 59 | + * @param routerPath | ||
| 60 | + * @param method | ||
| 61 | + */ | ||
| 62 | +function need_verify (action, routerPath, method) { | ||
| 63 | + routerPath = routerPath.toLowerCase(); | ||
| 64 | + let needVerify = true; | ||
| 65 | + | ||
| 66 | + if (/^[tuo]$/.test(routerPath)) { //u、t、o的公共模块直接通过 | ||
| 67 | + needVerify = false; | ||
| 68 | + } else if (action === 'auth') { //登录模块直接通过 | ||
| 69 | + needVerify = false; | ||
| 70 | + } | ||
| 71 | + return needVerify; | ||
| 72 | +} | ||
| 73 | + |
server/lib/mw/default_params.js
0 → 100644
| 1 | +/** | ||
| 2 | + * 文件描述: | ||
| 3 | + * 开发者:yujintang | ||
| 4 | + * 开发者备注: | ||
| 5 | + * 审阅者: | ||
| 6 | + * 优化建议: | ||
| 7 | + * 开发时间: 2017/11/27 | ||
| 8 | + */ | ||
| 9 | +"use strict"; | ||
| 10 | + | ||
| 11 | +const _ = require('lodash'); | ||
| 12 | +module.exports = function () { | ||
| 13 | + return function (req, res, next) { | ||
| 14 | + let user = _.get(req, 'session.active_user', {}); | ||
| 15 | + | ||
| 16 | + let {user_id, user_code, user_name} = user; | ||
| 17 | + req.default_params = { | ||
| 18 | + createdBy: { | ||
| 19 | + create_id: user_id, | ||
| 20 | + create_code: user_code, | ||
| 21 | + create_name: user_name, | ||
| 22 | + }, | ||
| 23 | + updatedBy: { | ||
| 24 | + optr_id: user_id, | ||
| 25 | + optr_code: user_code, | ||
| 26 | + optr_name: user_name, | ||
| 27 | + } | ||
| 28 | + }; | ||
| 29 | + next && next(); | ||
| 30 | + }; | ||
| 31 | +}; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
server/lib/mw/weapp-params.js
0 → 100644
| 1 | +/** | ||
| 2 | + * 微信小程序接口传递业务身份 | ||
| 3 | + * Created by lintry on 2018/4/25. | ||
| 4 | + */ | ||
| 5 | + | ||
| 6 | +module.exports = function () { | ||
| 7 | + return function (req, res, next) { | ||
| 8 | + const ENUM = global.config.ENUM | ||
| 9 | + | ||
| 10 | + let plat_id = req.header(ENUM.SHOP.we_plat_id_key) || ENUM.SHOP.plat_id; // 当前小程序的商户 | ||
| 11 | + req.app_identity = {plat_id}; | ||
| 12 | + | ||
| 13 | + next(); | ||
| 14 | + } | ||
| 15 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
server/modules/authAction.js
0 → 100644
| 1 | +/** | ||
| 2 | + * Created by lintry on 2017-5-8. | ||
| 3 | + */ | ||
| 4 | + | ||
| 5 | +"use strict"; | ||
| 6 | + | ||
| 7 | +module.exports = function (user_type, dbo) { | ||
| 8 | + if (user_type === void 0) { | ||
| 9 | + throw new Error('请明确指定登录平台'); | ||
| 10 | + } | ||
| 11 | + | ||
| 12 | + //api公共模块 | ||
| 13 | + const _ = require('lodash'), | ||
| 14 | + Promise = require('bluebird'), | ||
| 15 | + po = global.po, | ||
| 16 | + config = global.config, | ||
| 17 | + Result = require('kml-express-stage-lib').Result, | ||
| 18 | + logger = global.loggers.system, | ||
| 19 | + ENUM = config.ENUM, | ||
| 20 | + cache_config = config.cache, | ||
| 21 | + crypto_utils = require('kml-crypto-utils'), | ||
| 22 | + redisDb = require('../init/redis-promisify'); | ||
| 23 | + | ||
| 24 | + const ExpressPassport = require('kml-common-module').ExpressPassport, | ||
| 25 | + express_passport = new ExpressPassport({ | ||
| 26 | + server_id: global.config.system.project_name, | ||
| 27 | + ttl: cache_config.EXPRESS_TTL, | ||
| 28 | + redis_client: redisDb | ||
| 29 | + }); | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 登录系统 | ||
| 34 | + * @param req | ||
| 35 | + * @param res | ||
| 36 | + * @param db | ||
| 37 | + * @return {*} | ||
| 38 | + * 仅供调试用 | ||
| 39 | + */ | ||
| 40 | + this.sysloginPost = async function (req, res, db) { | ||
| 41 | + const params = req.body; | ||
| 42 | + | ||
| 43 | + if (!params || !params.userCode || !params.userPassword) return Result.Error('参数错误'); | ||
| 44 | + | ||
| 45 | + const sendData = { | ||
| 46 | + userCode: params.userCode, | ||
| 47 | + userPassword: params.userPassword+'@'+params.userCode | ||
| 48 | + }; | ||
| 49 | + | ||
| 50 | + try { | ||
| 51 | + const | ||
| 52 | + User = po.import(db, 'user'), | ||
| 53 | + | ||
| 54 | + userWhereObj = { | ||
| 55 | + user_code: sendData.userCode, | ||
| 56 | + status: ENUM.TYPE.ENABLE | ||
| 57 | + }; | ||
| 58 | + | ||
| 59 | + const | ||
| 60 | + userInfoArray = _.difference(Object.keys(User.fieldRawAttributesMap), ENUM.DEFAULT_PARAMS_ARRAY); | ||
| 61 | + | ||
| 62 | + const userInfo = await User.findOne({where: userWhereObj, attributes: userInfoArray}); | ||
| 63 | + if (!userInfo) return Result.Error('账号或密码错误'); | ||
| 64 | + | ||
| 65 | + // 检查密码编码后是否与数据库存储的编码一致 | ||
| 66 | + let matched = crypto_utils.hashMatch('md5', sendData.userPassword, userInfo.user_password); | ||
| 67 | + if (!matched) { | ||
| 68 | + return Result.Error('账号或密码错误'); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + sendData.platId = userInfo.plat_id; | ||
| 72 | + sendData.userType = userInfo.user_type; | ||
| 73 | + | ||
| 74 | + const data = { | ||
| 75 | + user: userInfo.toJSON() | ||
| 76 | + }; | ||
| 77 | + | ||
| 78 | + //屏蔽用户密码输出 | ||
| 79 | + data.user.user_password = void 0; | ||
| 80 | + | ||
| 81 | + return express_passport.create(req, res, data).then((express_data) => { | ||
| 82 | + return Result.Ok('登录成功', data); | ||
| 83 | + }); | ||
| 84 | + | ||
| 85 | + } catch(err) { | ||
| 86 | + logger.error('error', err); | ||
| 87 | + return Result.Error('登录失败'); | ||
| 88 | + } | ||
| 89 | + }; | ||
| 90 | + | ||
| 91 | + | ||
| 92 | + /** | ||
| 93 | + * 注销 | ||
| 94 | + * @param req | ||
| 95 | + * @param res | ||
| 96 | + */ | ||
| 97 | + this.logoutGet = async function (req, res) { | ||
| 98 | + const active_user = req.session.active_user; | ||
| 99 | + await express_passport.destroy(req, res); | ||
| 100 | + | ||
| 101 | + if (!active_user) { | ||
| 102 | + req.session.destroy(); | ||
| 103 | + return Result.Ok('注销成功'); | ||
| 104 | + } | ||
| 105 | + req.session.destroy(); | ||
| 106 | + | ||
| 107 | + const key = `${active_user.user_id}@${active_user.plat_id}`; | ||
| 108 | + return redisDb | ||
| 109 | + .DELAsync(key) | ||
| 110 | + .then(() => { | ||
| 111 | + return Result.Ok('注销成功'); | ||
| 112 | + }) | ||
| 113 | + .catch(err => { | ||
| 114 | + logger.error(`${req.baseUrl}${req.url} => `, err); | ||
| 115 | + return Result.Error('注销成功'); | ||
| 116 | + }); | ||
| 117 | + }; | ||
| 118 | + | ||
| 119 | + /** | ||
| 120 | + * 快速登录 不分平台直接登录 | ||
| 121 | + * @param req | ||
| 122 | + * @param res | ||
| 123 | + * @returns {Promise.<T>} | ||
| 124 | + */ | ||
| 125 | + this.quickPass = function (req, res, db) { | ||
| 126 | + return express_passport.validate(req, res) | ||
| 127 | + .then(async function (express_data) { | ||
| 128 | + if (express_data) { | ||
| 129 | + const | ||
| 130 | + User = po.import(db, 'user'), | ||
| 131 | + | ||
| 132 | + userWhereObj = { | ||
| 133 | + user_id: express_data.user_id | ||
| 134 | + }; | ||
| 135 | + | ||
| 136 | + const | ||
| 137 | + userInfoArray = _.difference(Object.keys(User.fieldRawAttributesMap), ENUM.DEFAULT_PARAMS_ARRAY); | ||
| 138 | + | ||
| 139 | + const userInfo = await User.findOne({where: userWhereObj, attributes: userInfoArray}); | ||
| 140 | + if (!userInfo) return Result.Error('登录信息已失效, 请重新登录!'); | ||
| 141 | + | ||
| 142 | + const data = { | ||
| 143 | + user: userInfo.toJSON() | ||
| 144 | + }; | ||
| 145 | + | ||
| 146 | + //屏蔽用户密码输出 | ||
| 147 | + data.user.user_password = void 0; | ||
| 148 | + | ||
| 149 | + return express_passport.refresh(req, res, data).then(() => { | ||
| 150 | + return Result.Ok('登录成功', data); | ||
| 151 | + }); | ||
| 152 | + | ||
| 153 | + } else { | ||
| 154 | + res.status(401); | ||
| 155 | + return Result.Error('登录信息已失效, 请重新登录!'); | ||
| 156 | + } | ||
| 157 | + }); | ||
| 158 | + }; | ||
| 159 | + | ||
| 160 | +}; |
server/modules/id_worker.js
0 → 100644
| 1 | +/** | ||
| 2 | + * id_worker | ||
| 3 | + * Created by lintry on 2018/8/23. | ||
| 4 | + */ | ||
| 5 | + | ||
| 6 | +const IdWorker = require('kml-id-worker'), | ||
| 7 | + id_worker = new IdWorker({datacenterId: global.config.id_worker.datacenterId, workerId: process.pid & 0x1f}); | ||
| 8 | + | ||
| 9 | +module.exports = id_worker; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
server/modules/lockKey.js
0 → 100644
server/routers/b/auth_action.js
0 → 100644
| 1 | +"use strict"; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * @module b/auth | ||
| 5 | + * @author 余金堂 | ||
| 6 | + * @description | ||
| 7 | + * 1.登录系统 | ||
| 8 | + * 2.注销 | ||
| 9 | + * 3.获取个人信息 | ||
| 10 | + */ | ||
| 11 | +module.exports = function ( dbo ) { | ||
| 12 | + | ||
| 13 | + //api公共模块 | ||
| 14 | + const _ = require('lodash'), | ||
| 15 | + po = global.po, | ||
| 16 | + Result = require('kml-express-stage-lib').Result, | ||
| 17 | + logger = global.loggers.system, | ||
| 18 | + redisDb = require('../../init/redis-promisify'); | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + const authAction = new (require('../../modules/authAction'))('B', dbo); | ||
| 23 | + | ||
| 24 | + /** | ||
| 25 | + * 1. 登录系统 | ||
| 26 | + * | ||
| 27 | + * @deprecated saas_back 平台提供方法优先 | ||
| 28 | + * @param {Object} req - 请求参数 | ||
| 29 | + * @param {string} req.userCode - 用户code | ||
| 30 | + * @param {string} req.userPassword - 密码为🔐的内容 | ||
| 31 | + * | ||
| 32 | + * @param {Object} res - 返回参数 | ||
| 33 | + * @param {string} res.ret - 返回状态 [OK、ERROR] | ||
| 34 | + * @param {string} res.msg - 返回消息 | ||
| 35 | + * @param {object} res.content - 返回内容 | ||
| 36 | + * @param {Object} res.content.plat - 平台 | ||
| 37 | + * @param {Object} res.content.corp - 登录人所属公司 | ||
| 38 | + * @param {Object} res.content.active_user - 个人信息 | ||
| 39 | + * @param {Object} res.content.duty - 指责 | ||
| 40 | + */ | ||
| 41 | + this.sysloginPost = authAction.sysloginPost; | ||
| 42 | + | ||
| 43 | + /** | ||
| 44 | + * 2. 注销 | ||
| 45 | + * | ||
| 46 | + * @deprecated saas_back 平台提供方法优先 | ||
| 47 | + * @param {Object} req - 请求参数 | ||
| 48 | + * | ||
| 49 | + * @param {Object} res - 返回参数 | ||
| 50 | + * @param {object} res.content - 返回内容 | ||
| 51 | + * @param {string} res.ret - 返回状态 [OK、ERROR] | ||
| 52 | + * @param {string} res.msg - 返回消息 | ||
| 53 | + */ | ||
| 54 | + this.logoutGet = authAction.logoutGet; | ||
| 55 | + | ||
| 56 | + | ||
| 57 | + /** | ||
| 58 | + * 3.获取个人信息 | ||
| 59 | + * | ||
| 60 | + * @deprecated saas_back 平台提供方法优先 | ||
| 61 | + * @param {Object} req - 请求参数 | ||
| 62 | + * | ||
| 63 | + * @param {Object} res - 返回参数 | ||
| 64 | + * @param {string} res.ret - 返回状态 [OK、ERROR] | ||
| 65 | + * @param {string} res.msg - 返回消息 | ||
| 66 | + * @param {object} res.content - 返回内容 | ||
| 67 | + * @param {Object} res.content.plat - 平台 | ||
| 68 | + * @param {Object} res.content.corp - 登录人所属公司 | ||
| 69 | + * @param {Object} res.content.active_user -个人信息 | ||
| 70 | + * @param {Object} res.content.duty - 指责 | ||
| 71 | + * @param {Object} res.content.saas_host - 平台主页面 | ||
| 72 | + */ | ||
| 73 | + this.getUserInfoGet = async (req) => { | ||
| 74 | + try { | ||
| 75 | + let session = req.session, | ||
| 76 | + {active_user, plat, corp, duty} = session; | ||
| 77 | + | ||
| 78 | + const data = { | ||
| 79 | + active_user: active_user || {}, | ||
| 80 | + plat: plat || {}, | ||
| 81 | + corp: corp || {}, | ||
| 82 | + duty: duty || {} | ||
| 83 | + }; | ||
| 84 | + | ||
| 85 | + return Result.Ok('成功!', data); | ||
| 86 | + } catch (e) { | ||
| 87 | + logger.error('失败!', e); | ||
| 88 | + return Result.Error('失败!', e.message); | ||
| 89 | + } | ||
| 90 | + } | ||
| 91 | +}; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
server/routers/b/user_action.js
0 → 100644
| 1 | +"use strict"; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * @module b/auth | ||
| 5 | + * @author 余金堂 | ||
| 6 | + * @description | ||
| 7 | + * 1.登录系统 | ||
| 8 | + * 2.注销 | ||
| 9 | + * 3.获取个人信息 | ||
| 10 | + */ | ||
| 11 | +module.exports = function ( dbo ) { | ||
| 12 | + | ||
| 13 | + //api公共模块 | ||
| 14 | + const _ = require('lodash'), | ||
| 15 | + po = global.po, | ||
| 16 | + Result = require('kml-express-stage-lib').Result, | ||
| 17 | + logger = global.loggers.system, | ||
| 18 | + redisDb = require('../../init/redis-promisify'); | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * 3.获取个人信息 | ||
| 23 | + * | ||
| 24 | + * @deprecated saas_back 平台提供方法优先 | ||
| 25 | + * @param {Object} req - 请求参数 | ||
| 26 | + * | ||
| 27 | + * @param {Object} res - 返回参数 | ||
| 28 | + * @param {string} res.ret - 返回状态 [OK、ERROR] | ||
| 29 | + * @param {string} res.msg - 返回消息 | ||
| 30 | + * @param {object} res.content - 返回内容 | ||
| 31 | + * @param {Object} res.content.plat - 平台 | ||
| 32 | + * @param {Object} res.content.corp - 登录人所属公司 | ||
| 33 | + * @param {Object} res.content.active_user -个人信息 | ||
| 34 | + * @param {Object} res.content.duty - 指责 | ||
| 35 | + * @param {Object} res.content.saas_host - 平台主页面 | ||
| 36 | + */ | ||
| 37 | + this.getUserInfoGet = async (req) => { | ||
| 38 | + try { | ||
| 39 | + let session = req.session, | ||
| 40 | + {active_user, plat, corp, duty} = session; | ||
| 41 | + | ||
| 42 | + const data = { | ||
| 43 | + active_user: active_user || {}, | ||
| 44 | + plat: plat || {}, | ||
| 45 | + corp: corp || {}, | ||
| 46 | + duty: duty || {} | ||
| 47 | + }; | ||
| 48 | + | ||
| 49 | + return Result.Ok('成功!', data); | ||
| 50 | + } catch (e) { | ||
| 51 | + logger.error('失败!', e); | ||
| 52 | + return Result.Error('失败!', e.message); | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | +}; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
server/routers/w/app_action.js
0 → 100644
| 1 | +/** | ||
| 2 | + * 微信小程序服务 | ||
| 3 | + * Created by lintry on 2018/1/18. | ||
| 4 | + */ | ||
| 5 | + | ||
| 6 | +module.exports = function (dbo) { | ||
| 7 | + const _ = require('lodash'), | ||
| 8 | + Promise = require('bluebird'), | ||
| 9 | + url_utils = require('url'), | ||
| 10 | + config = global.config, | ||
| 11 | + po = global.po, | ||
| 12 | + Result = require('kml-express-stage-lib').Result, | ||
| 13 | + logger = global.loggers.system, | ||
| 14 | + weapp_cfg = config.weapp, | ||
| 15 | + redis = require('../../init/redis-promisify'), | ||
| 16 | + crypto_utils = require('kml-crypto-utils') | ||
| 17 | + ; | ||
| 18 | + | ||
| 19 | + const WeAppAction = require('kml-weapp-lib').WeAppAction; | ||
| 20 | + const WxTokenStore = require('kml-weapp-mw').WxTokenStore; | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 根据header获取对应wxapi,并初始化WeAppAction模块 | ||
| 24 | + * @param req | ||
| 25 | + * @return {*} | ||
| 26 | + */ | ||
| 27 | + function getWeAppAction (req) { | ||
| 28 | + let we_app = req.header('we-app') | ||
| 29 | + return new WeAppAction({ | ||
| 30 | + redis, | ||
| 31 | + weapp_cfg: _.merge({}, weapp_cfg, {wx_app: we_app}), //使用客户端传入的wx_app,默认使用配置中定义 | ||
| 32 | + logger | ||
| 33 | + }) | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 根据header获取对应wxapi | ||
| 38 | + * @param req | ||
| 39 | + */ | ||
| 40 | + function getWXApi (req) { | ||
| 41 | + let we_app = req.header('we-app') | ||
| 42 | + return require('kml-wxapi')(_.merge({}, weapp_cfg, {wx_app: we_app})) //使用客户端传入的wx_app,默认使用配置中定义 | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * 检查token是否有效 | ||
| 47 | + * @param req | ||
| 48 | + * @param res | ||
| 49 | + * @return {*} | ||
| 50 | + */ | ||
| 51 | + this.checkGet = function (req, res) { | ||
| 52 | + return getWeAppAction(req).check(req.app_token) | ||
| 53 | + }; | ||
| 54 | + | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * 登录凭证code换取session_key | ||
| 58 | + * @param req | ||
| 59 | + * @param res | ||
| 60 | + */ | ||
| 61 | + this.loginPost = async function (req, res) { | ||
| 62 | + return getWeAppAction(req).login(req.app_token, req.body); | ||
| 63 | + }; | ||
| 64 | + | ||
| 65 | + | ||
| 66 | + /** | ||
| 67 | + * 分享 | ||
| 68 | + * @param req | ||
| 69 | + * @param res | ||
| 70 | + * 每次分享都会记录下openid分享什么内容到哪个组,以分享票据作为唯一键值 | ||
| 71 | + */ | ||
| 72 | + this.sharePost = async function (req, res) { | ||
| 73 | + let {appid, session_key, openid, version} = req.app_token || {}; | ||
| 74 | + let {shareId, shareData, encryptedData, iv} = req.body; | ||
| 75 | + shareId = shareId || 'unknown' + crypto_utils.UUID(); | ||
| 76 | + let share_info = { | ||
| 77 | + shareId, openid, appid, version, shareData | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + //解析用户信息密文 | ||
| 81 | + let content | ||
| 82 | + try { | ||
| 83 | + content = await getWeAppAction(req).decrypt(appid, session_key, encryptedData, iv) | ||
| 84 | + } catch (e) { | ||
| 85 | + return Result.Error('用户信息错误') | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + //获取群组id | ||
| 89 | + share_info.openGId = content.openGId; | ||
| 90 | + | ||
| 91 | + //存储分享内容 | ||
| 92 | + let ticket_store = new WxTokenStore(shareId + '@' + share_info.openGId, { | ||
| 93 | + redis, | ||
| 94 | + prefix: weapp_cfg.share_prefix, | ||
| 95 | + ttl: weapp_cfg.share_ttl | ||
| 96 | + }); | ||
| 97 | + return ticket_store.saveToken(share_info) | ||
| 98 | + .then(function (result) { | ||
| 99 | + return Result.Ok('ticket', {shareId}) | ||
| 100 | + }) | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + /** | ||
| 104 | + * 步数统计 | ||
| 105 | + * @param req | ||
| 106 | + * @return {Promise<*>} | ||
| 107 | + */ | ||
| 108 | + this.weRunDataPost = async function (req) { | ||
| 109 | + let {appid, session_key, openid, unionid, version} = req.app_token || {}; | ||
| 110 | + let {shareId, shareData, encryptedData, iv} = req.body; | ||
| 111 | + | ||
| 112 | + //解析用户信息密文 | ||
| 113 | + let content | ||
| 114 | + try { | ||
| 115 | + content = await getWeAppAction(req).decrypt(appid, session_key, encryptedData, iv) | ||
| 116 | + } catch (e) { | ||
| 117 | + return Result.Error('用户信息错误') | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + const [WxStep] = po.import(dbo, ['donate_wxstep']); | ||
| 121 | + | ||
| 122 | + let stepList = []; | ||
| 123 | + if (content && content.stepInfoList) { | ||
| 124 | + stepList = content.stepInfoList.map(info => { | ||
| 125 | + return { | ||
| 126 | + id: crypto_utils.MD5(openid + info.timestamp), | ||
| 127 | + openid, unionid, | ||
| 128 | + run_date: info.timestamp, | ||
| 129 | + step: info.step, | ||
| 130 | + appid | ||
| 131 | + } | ||
| 132 | + }) | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + // 存储数据库 | ||
| 136 | + stepList.forEach(step => { | ||
| 137 | + WxStep.saveById(step) | ||
| 138 | + }); | ||
| 139 | + | ||
| 140 | + //存储分享内容 | ||
| 141 | + let rundata_store = new WxTokenStore(openid, { | ||
| 142 | + redis, | ||
| 143 | + prefix: weapp_cfg.werun_prefix, | ||
| 144 | + ttl: weapp_cfg.werun_ttl | ||
| 145 | + }); | ||
| 146 | + return rundata_store.saveToken(content) | ||
| 147 | + .then(function (result) { | ||
| 148 | + return Result.Ok('success', content) | ||
| 149 | + }) | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + /** | ||
| 153 | + * 解析分享信息 | ||
| 154 | + * @param req | ||
| 155 | + * @param res | ||
| 156 | + * @return {Result} | ||
| 157 | + */ | ||
| 158 | + this.getShareInfoPost = async function (req, res) { | ||
| 159 | + let {token, appid, session_key, openid, version, nickName, avatarUrl, gender, city, province, country, language} = req.app_token || {}, {shareId, encryptedData, iv} = req.body; | ||
| 160 | + | ||
| 161 | + if (!session_key) { | ||
| 162 | + res.status(401); | ||
| 163 | + return Result.Error('token not found'); | ||
| 164 | + } | ||
| 165 | + // console.log('share info ===>', {token, encryptedData, iv, shareId, appid, session_key}) | ||
| 166 | + | ||
| 167 | + //解析用户信息密文 | ||
| 168 | + let content | ||
| 169 | + try { | ||
| 170 | + content = await getWeAppAction(req).decrypt(appid, session_key, encryptedData, iv) | ||
| 171 | + } catch (e) { | ||
| 172 | + return Result.Error('用户信息错误') | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + let openGId = content.openGId; | ||
| 176 | + | ||
| 177 | + if (!shareId) { | ||
| 178 | + return Result.Ok('shareInfo', {openGId}) | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + //获取分享内容 | ||
| 182 | + let ticket_store = new WxTokenStore(shareId + '@' + openGId, { | ||
| 183 | + redis, | ||
| 184 | + prefix: weapp_cfg.share_prefix, | ||
| 185 | + ttl: weapp_cfg.share_ttl | ||
| 186 | + }); | ||
| 187 | + return ticket_store.getToken() | ||
| 188 | + .then(function (shared) { | ||
| 189 | + shared = shared || {}; | ||
| 190 | + //获取围观者 | ||
| 191 | + let lookers = shared.lookers; | ||
| 192 | + try { | ||
| 193 | + lookers = JSON.parse(lookers) | ||
| 194 | + } catch (e) { | ||
| 195 | + lookers = {} | ||
| 196 | + } | ||
| 197 | + //记录当前围观者 | ||
| 198 | + lookers[openid] = {openid, nickName, avatarUrl, gender, city, province, country, language, version}; | ||
| 199 | + shared.lookers = lookers; | ||
| 200 | + //保存分享内容 | ||
| 201 | + return ticket_store.saveToken(shared) | ||
| 202 | + .then(function (shared) { | ||
| 203 | + logger.info('围观分享:', openid, '@', openGId) | ||
| 204 | + let {lookers} = shared || {} | ||
| 205 | + if (typeof lookers === 'string') { | ||
| 206 | + lookers = JSON.parse(lookers) | ||
| 207 | + } | ||
| 208 | + return Result.Ok('shareInfo', {lookers, openGId}); | ||
| 209 | + }) | ||
| 210 | + }) | ||
| 211 | + } | ||
| 212 | + | ||
| 213 | + /** | ||
| 214 | + * 支付准备 | ||
| 215 | + * @param req | ||
| 216 | + * @return {*} | ||
| 217 | + */ | ||
| 218 | + this.prepayPost = function (req) { | ||
| 219 | + let {appid, session_key, openid, version} = req.app_token || {}; | ||
| 220 | + if (!openid) { | ||
| 221 | + //没有openid需要先去授权 | ||
| 222 | + return new Result(Result.ERROR, '请先登录'); | ||
| 223 | + } | ||
| 224 | + | ||
| 225 | + const wxapi = getWXApi(req); | ||
| 226 | + const order_id = crypto_utils.UUID(), pay_receipt_id = crypto_utils.UUID(), pay_bill_id = crypto_utils.UUID(), | ||
| 227 | + product_id = '1234'; | ||
| 228 | + let order_to_pay = { | ||
| 229 | + device_info: 'WeApp', //自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB" | ||
| 230 | + body: `赞赏`, //商品简单描述 | ||
| 231 | + attach: JSON.stringify({order_id: order_id, id: pay_bill_id}), //附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 | ||
| 232 | + out_trade_no: pay_receipt_id, //商户系统内部订单号,要求32个字符内、且在同一个商户号下唯一;重新发起一笔支付要使用原订单号,避免重复支付 | ||
| 233 | + total_fee: 1, //订单总金额,单位为分 | ||
| 234 | + spbill_create_ip: req.clientIp, //APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP | ||
| 235 | + product_id: product_id, //trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义 | ||
| 236 | + openid: openid, //trade_type=JSAPI时(即公众号支付),此参数必传 | ||
| 237 | + trade_type: 'JSAPI' //取值如下:JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付 | ||
| 238 | + }; | ||
| 239 | + | ||
| 240 | + return wxapi.getBrandWCPayRequestParams(order_to_pay) | ||
| 241 | + .then(function (result) { | ||
| 242 | + if (result.ret === 'OK') { | ||
| 243 | + let content = result.content; | ||
| 244 | + content.order_id = order_id; | ||
| 245 | + content.pay_bill_id = pay_bill_id; | ||
| 246 | + } | ||
| 247 | + return result; | ||
| 248 | + }); | ||
| 249 | + }; | ||
| 250 | +} |
server/server.js
0 → 100644
| 1 | +"use strict"; | ||
| 2 | +const start_time = Date.now(); | ||
| 3 | +const config = require('./init/config'), | ||
| 4 | + MpApiAction = require('./action/mpapi-action'), | ||
| 5 | + WeApiAction = require('./action/weapi-action'), | ||
| 6 | + BzApiAction = require('./action/bzapi-action'); | ||
| 7 | + | ||
| 8 | +// 定义参数 | ||
| 9 | +const BIND_PORT = config.system.bind_port; | ||
| 10 | + | ||
| 11 | +// 定义log4js 包含业务日志和系统日志 | ||
| 12 | +const logger = require('./init/log4js-init').system; | ||
| 13 | + | ||
| 14 | +// 定义db | ||
| 15 | +logger.info('init db'); | ||
| 16 | +require('./init/sequelize-init'); | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +// 定义express初始化 | ||
| 20 | +logger.info('init express'); | ||
| 21 | +const app = new (require('./init/express-init'))(); | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +//加载二级目录使用actions下的模块处理 | ||
| 25 | +const routers_path = require('kml-express-stage-lib').routers_path; | ||
| 26 | +let list = routers_path.list(); | ||
| 27 | + | ||
| 28 | +list.forEach(function (router_path) { | ||
| 29 | + let pattern = `/${router_path}`; | ||
| 30 | + let api_action; | ||
| 31 | + if (/[tc]/.test(router_path)) { | ||
| 32 | + api_action = new MpApiAction(router_path) | ||
| 33 | + } else if(/w/.test(router_path)) { | ||
| 34 | + api_action = new WeApiAction((router_path)) | ||
| 35 | + } else { | ||
| 36 | + api_action = new BzApiAction(router_path) | ||
| 37 | + } | ||
| 38 | + app.use(pattern, api_action); | ||
| 39 | +}); | ||
| 40 | + | ||
| 41 | +//启动服务 | ||
| 42 | +const server = app.listen(BIND_PORT, function () { | ||
| 43 | + let os = require('os'); | ||
| 44 | + let ifaces = os.networkInterfaces(); | ||
| 45 | + let localhost = ['localhost']; | ||
| 46 | + Object.keys(ifaces).forEach(function (ifname) { | ||
| 47 | + let alias = 0; | ||
| 48 | + | ||
| 49 | + ifaces[ifname].forEach(function (iface) { | ||
| 50 | + if ('IPv4' !== iface.family || iface.internal !== false) { | ||
| 51 | + // skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses | ||
| 52 | + return; | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + if (alias >= 1) { | ||
| 56 | + // this single interface has multiple ipv4 addresses | ||
| 57 | + localhost.push(iface.address); | ||
| 58 | + } else { | ||
| 59 | + // this interface has only one ipv4 adress | ||
| 60 | + localhost.push(iface.address); | ||
| 61 | + } | ||
| 62 | + ++alias; | ||
| 63 | + }); | ||
| 64 | + }); | ||
| 65 | + | ||
| 66 | + let server_address = server.address(); | ||
| 67 | + let port = server_address.port; | ||
| 68 | + logger.info('Server is listening at: ', localhost.map(function (ip) { | ||
| 69 | + return `http://${ip}:${port}`; | ||
| 70 | + }).join('\n')); | ||
| 71 | + logger.info((ms => `Server startup in ${ms} ms`)(Date.now() - start_time)); | ||
| 72 | +}); | ||
| 73 | + | ||
| 74 | +//bind exception event to log | ||
| 75 | +process.on('uncaughtException', function (e) { | ||
| 76 | + logger.error('uncaughtException from process', e); | ||
| 77 | + if (e.code === 'EADDRINUSE') { | ||
| 78 | + logger.error(`服务端口${BIND_PORT}被占用!`); | ||
| 79 | + process.exit(BIND_PORT); | ||
| 80 | + } | ||
| 81 | +}); | ||
| 82 | + | ||
| 83 | +process.on('unhandledRejection', (e) => { | ||
| 84 | + logger.warn('unhandledRejection from process', e); | ||
| 85 | +}); | ||
| 86 | + | ||
| 87 | +process.on('rejectionHandled', (e) => { | ||
| 88 | + logger.warn('rejectionHandled from process', e); | ||
| 89 | +}); | ||
| 90 | + | ||
| 91 | +// 准备定时器 | ||
| 92 | +if (!config.unique_schedule) { | ||
| 93 | + require('./crontab'); | ||
| 94 | +} |
server/service/data_importer.js
0 → 100644
| 1 | +/** | ||
| 2 | + * data_import | ||
| 3 | + * Created by lintry on 2020/1/28. | ||
| 4 | + */ | ||
| 5 | +const _ = require('lodash'), | ||
| 6 | + po = global.po, | ||
| 7 | + logger = global.loggers.system; | ||
| 8 | +const id_worker = require('../modules/id_worker'); | ||
| 9 | +const api_sdk = new (require('kml-api-request'))(); | ||
| 10 | + | ||
| 11 | +class DataImporter { | ||
| 12 | + constructor (dbo) { | ||
| 13 | + this.dbo = dbo; | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 导入json数据 | ||
| 18 | + * @param json_data | ||
| 19 | + * @returns {Promise<void>} | ||
| 20 | + */ | ||
| 21 | + async import_data (json_data) { | ||
| 22 | + const dbo = this.dbo; | ||
| 23 | + let Timeline = po.import(dbo, 'timeline_area'); | ||
| 24 | + | ||
| 25 | + let areas = []; | ||
| 26 | + | ||
| 27 | + for (let row of json_data) { | ||
| 28 | + let {provinceName, | ||
| 29 | + provinceShortName, | ||
| 30 | + confirmedCount, | ||
| 31 | + suspectedCount, | ||
| 32 | + curedCount, | ||
| 33 | + deadCount, | ||
| 34 | + comment, | ||
| 35 | + cities, | ||
| 36 | + updateTime, | ||
| 37 | + country} = row; | ||
| 38 | + | ||
| 39 | + areas.push({ | ||
| 40 | + id: id_worker.nextId(), | ||
| 41 | + province_name: provinceName, | ||
| 42 | + province_short_name: provinceShortName, | ||
| 43 | + confirmed_count: confirmedCount, | ||
| 44 | + suspected_count: suspectedCount, | ||
| 45 | + cured_count: curedCount, | ||
| 46 | + dead_count: deadCount, | ||
| 47 | + comment, | ||
| 48 | + cities, | ||
| 49 | + update_time: updateTime, | ||
| 50 | + country | ||
| 51 | + }) | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + await Timeline.bulkCreate(areas, {ignoreDuplicates: true}); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + async import_url (url, param) { | ||
| 58 | + let json_data = await api_sdk.get(url, param); | ||
| 59 | + await this.import_data(json_data.results||[]); | ||
| 60 | + } | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +module.exports = DataImporter; |
-
Please register or login to post a comment