lintry

init

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