UNPKG

@bxjs/base

Version:

bxjs base framework & api

1,109 lines 136 kB
"use strict"; var __assign = (this && this.__assign) || Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var _this = this; Object.defineProperty(exports, "__esModule", { value: true }); process.env.TZ = 'Asia/Shanghai'; process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; require("reflect-metadata"); var inversify_1 = require("inversify"); var typeorm_1 = require("@bxjs/typeorm"); var session_1 = require("./session"); var $$ = require("./plugins"); var path = require('path'); var ErrorStackParser = require('error-stack-parser'); var cookie = require('cookie'); var MobileDetect = require('mobile-detect'); var fetch = require('node-fetch'); var _ = require('lodash'); var moment = require('moment'); var extend = require('extend'); var querystring = require('querystring'); // const parameter = require('parameter') // const parameterCheckInstance = new parameter({ // // translate: function () { // // var args = Array.prototype.slice.call(arguments); // // // Assume there have I18n.t method for convert language. // // return I18n.t.apply(I18n, args); // // } // }) var circular_json = require("circular-json"); var mockjs = require('mockjs'); var shortid = require('shortid'); var validatorjs = require('validatorjs'); var cross_spawn = require('cross-spawn'); var ACMClient = require('amber_utf-8'); var co = require('co'); // FIXME HACK原生方法JSON转换不可逆的BUG(JAVA端传来的富文本字段内容含有\n\t字符串中的字符生成JSON字符串无法正常解析报错) var raw_stringify = JSON.stringify; function new_stringify(value, replacer, space) { var out = raw_stringify(value, replacer, space); if (_.isString(out)) { out = out.replace(/\\n/g, '\\\\n') .replace(/\\t/g, '\\\\t') .replace(/\\u/g, '\\\\u'); //JAVA端返回的unicode字符转义处理 } return out; } JSON.stringify = new_stringify; // ts-node本地调试需要加载对应的源代码后缀名称 function get_suffix_ts_or_js() { if (global['__env__'] == 'local' && !/^\/code\/node_modules/.test(__dirname)) { return 'ts'; } else { return 'js'; } } exports.get_suffix_ts_or_js = get_suffix_ts_or_js; // 准确定位错误码位置,间接得到函数调用位置地址信息,结合符号报表的正确解析处理完美得到错误定位信息,准确代码调试。 function __get_base_func_caller_source_position(position) { if (position === void 0) { position = 3; } try { throw new Error(); } catch (err) { var out = ErrorStackParser.parse(err); var idx = 0; // 找到第二个TS文件的执行位置 var find_ts_sufix_file_count = 0; for (; idx < out.length; idx++) { if (/\.ts$/.test(out[idx].fileName)) { find_ts_sufix_file_count += 1; } if (find_ts_sufix_file_count == position) { break; } } if (find_ts_sufix_file_count == position) { return '[' + out[idx]['fileName'] + ':' + out[idx]['lineNumber'] + ']'; } else { // TODO 需要定位为什么调用栈无法找到对应的位置出现越界?? // console.error(err) console.error(circular_json.stringify(out, null, 4) .replace(/\r/g, '').replace(/\n/g, '')); return '#'; } } } // 获取异常调用栈用于辅助错误提示定位 function xstack(err, compact) { if (compact === void 0) { compact = true; } try { // TODO 优化裁剪一些无用信息减少日志尺寸更加便于人工分析处理 var stack = ErrorStackParser.parse(err); if (compact) { var sources = []; for (var _i = 0, stack_1 = stack; _i < stack_1.length; _i++) { var v = stack_1[_i]; sources.push(v['fileName'] + ":" + v['lineNumber']); } return sources; } return stack; } catch (err1) { var source = __get_base_func_caller_source_position(); return "invalid error input param (" + source + ")"; } } exports.xstack = xstack; // // 错误栈的递归嵌套格式显示数据结构定义(param嵌套找到最后一个msg的JSON解析语法错误就是错误链的原始错误发生位置) // let x = { // "code": "UNKNOWN", // "msg": "未知错误", // "param": { // "msg": "您输入的用户名或密码错误,请重新登录 (ErrorCode: 1005, url: https://login.alibaba-inc.com/authorize/login.do)" // }, // "stack": "[\"/Users/chujinghui/Desktop/work/xjs/bxjs/framework/base.ts:110\",\"/Users/chujinghui/Desktop/work/xjs/bxjs/app/entries/web/mobile/meeting-room-visit.ts:161\",\"/Users/chujinghui/Desktop/work/xjs/bxjs/app/entries/web/mobile/meeting-room-visit.js:40\",\"/Users/chujinghui/Desktop/work/xjs/bxjs/app/entries/web/mobile/meeting-room-visit.js:21\",\"/Users/chujinghui/Desktop/work/xjs/bxjs/app/entries/web/mobile/meeting-room-visit.js:13\",\"internal/process/next_tick.js:188\"]", // } // 对于异常内容的格式化参数解析处理成为四元组code/msg/param/stack function xerror(err, __param) { xassert(err instanceof Error); try { // 标准错误的统一转换处理 var data_1 = JSON.parse(err.message); if (data_1.code && data_1.msg && ERRORS[data_1.code]) { return data_1; } } catch (err) { // ignore parse error } // 非标准错误的统一格式转换处理 var msg = ERRORS[ERR$UNKNOWN]['zh']; // TODO 错误码多语言回传到客户端问题 var code = ERR$UNKNOWN; var param = { msg: err.message, param: __param }; // 用户自定义的错误参数信息 msg为非错误码JSON四元组就是嵌套的终止条件。 var stack = xstack(err); var data = { msg: msg, code: code, param: param, stack: stack }; return data; } exports.xerror = xerror; // 用于获取错误栈的root cause根本原因(第一个被拦截的错误发生位置) function xroot(err) { xassert(err instanceof Error); var _a = xerror(err), msg = _a.msg, param = _a.param, code = _a.code, stack = _a.stack; // 递归遍历找到错误链的root cause for (; param && param.msg;) { try { var json = JSON.parse(param.msg); param = json.param; } catch (err) { msg = param.msg; code = param.code; stack = param.stack; param = param.param; break; } } return { msg: msg, code: code, param: param, stack: stack }; } exports.xroot = xroot; // TODO 报错处理(显示问题反馈联系人信息) // 将未处理的错误上抛的异常链记录下来用于精准追踪代码的执行过程(以及准确获取到根节点的错误码) // 对于promise异步回调的统一出错处理写法实例 // export function login(username: string, password: string) { // return new Promise((resolve, reject) => { // co(function* () { // let user = yield buc.oauthclient.login(username, password) // resolve(user) // }).catch(async function (err) { // xthrow(err, reject) // }) // }) // } function xthrow(code, param, reject_param) { if (code === void 0) { code = ERR$UNKNOWN; } if (param === void 0) { param = undefined; } if (reject_param === void 0) { reject_param = undefined; } // promise中进行reject异常处理的抛出错误方法的形参逻辑预处理转换。 var reject = _.isFunction(param) ? param : undefined; if (reject) param = reject_param; var data = {}; var source = __get_base_func_caller_source_position(); if (code instanceof Error) { try { data = JSON.parse(code.message); // 将透传上抛的错误的路径信息和附加参数也记录下来方便提供完整应用堆栈信息辅助调试业务逻辑 if (!_.isArray(data.stack)) { data.stack = []; } data.stack.push(source); } catch (err) { // ignore } // 标准错误直接上抛处理 if (data.code && data.msg && ERRORS[data.code]) { // 测试严重BUG reject函数类型表达式为假必须要用lodash判定是否为函数 if (_.isFunction(reject)) { // promise回调中进行抛错误处理 var err = new Error(JSON.stringify(data)); reject(err); return; } else { throw new Error(JSON.stringify(data)); } } // 将非标准错误转换为标准错误后再上抛处理 data = xerror(code, param); data.code = ERR$UNKNOWN; data.msg = ERRORS[ERR$UNKNOWN]['zh']; // FIXME TODO 错误码的多语言处理转换!! data.param = { msg: code.message, param: param, stack: [source] }; } else { // 对于常量定义错误的统一格式化处理 data = { code: code, msg: global['ERRORS'][code]['zh'], param: param, stack: [source] }; } // 对于是否promise场景下的错误上抛进行正确的转换处理 if (_.isFunction(reject)) { // promise回调中进行抛错误处理 reject(new Error(JSON.stringify(data))); } else { // 非promise回调中异常传递 throw new Error(JSON.stringify(data)); } } exports.xthrow = xthrow; function xassert(expr, code, param) { if (code === void 0) { code = ERR$ASSERT; } var source = __get_base_func_caller_source_position(); var stack = [source]; if (!expr) throw new Error(JSON.stringify({ code: code, msg: global['ERRORS'][code]['zh'], param: param, stack: stack })); return expr; } exports.xassert = xassert; // // https://github.com/node-modules/parameter 参数验证规则详见此文档(egg团队开发的组件) // // 注意事项:GET通过URL传递的参数都是字符串类型应该尽量避免GET传递参数,需要多用POST的JSON格式传递参数并且POSTMAN上进行辅助测试正确数据类型映射。 // export function xcheck(param: { [propName: string]: any }, rules: { [propName: string]: any }) { // let errors = parameterCheckInstance.validate(rules, param) // if (_.isEmpty(errors)) { // return true // } else { // xthrow(ERR$PARAM, errors) // } // } function xlog() { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } // 兼容云端以及本地日志调试(解决任意对象的JSON字符串内容的完整输出) var source = __get_base_func_caller_source_position() + '[' + xnow('YYYY-MM-DD HH:mm:ss.SSS') + ']'; var output = circular_json.stringify(args.slice(), null, 4); if (global['__env__'] != 'prod' && !/^\/code\/node_modules/.test(__dirname)) { // 打印到控制台一份日志(在阿里云非线上FC环境中) console.log.apply(undefined, [source + output]); // 写日志文件到/tmp下临时处理一下 TODO 需要改为类似log4j的本地日志库仅在非线上环境使用方便开发单机日常机器上调试。 var fs = require('fs'); var logFilePath = process.env['NODE_LOGFILE'] ? process.env['NODE_LOGFILE'] : '/tmp/bxjs.log'; fs.appendFileSync(logFilePath, source + output + "\r\n"); } else { // 生产环境下只打印到控制台绑定的SLS日志服务器上,并且需要去除掉换行信息否则打印会不正常。 // 去除掉换行方便方便SLS上的日志输出排版显示 output = output.replace(/\r/g, '').replace(/\n/g, ''); console.log.apply(undefined, [source + output]); } } exports.xlog = xlog; // // 将详细错误信息及时发送到钉钉群上实时反馈给维护者 // await xwarn({ // code, // // TODO 如何认证通过了获取到用户信息也需要发送过去,方便联系对接人员进行立刻问题处理反馈。 // message, // stack, // param, // }) // 将详细错误信息及时发送到钉钉群上实时反馈给维护者 // 钉钉IM群机器人报警通知 function xwarn() { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return __awaiter(this, void 0, void 0, function () { var source, out, access_token, mobiles; return __generator(this, function (_a) { switch (_a.label) { case 0: source = __get_base_func_caller_source_position(); // 对于异常参数警告信息进行错误内容标准解析 if (args.length > 0 && args[0] instanceof Error) { args[0] = xerror(args[0]); } out = [source, xnow('YYYY-MM-DD HH:mm:ss.SSS'), __assign({}, args)]; access_token = xconfig('framework.warn.dingding.access_token'); mobiles = xconfig('framework.warn.dingding.mobiles'); if (!access_token || !mobiles) { access_token = '020a09eac5f2fa320ae851442d5e19e23693c64ad2255c85354b4a49a5a48d35'; mobiles = ['15381151346']; } return [4 /*yield*/, xpost("https://oapi.dingtalk.com/robot/send?access_token=" + access_token, { msgtype: 'text', text: { content: out }, at: { atMobiles: mobiles, isAtAll: false } }) // 线上SLS日志上也保存一份 // console.warn(out) ]; case 1: _a.sent(); // 线上SLS日志上也保存一份 // console.warn(out) xlog(out); return [2 /*return*/]; } }); }); } // 捕获未监听到的异常记录后直接退出(运行堆栈已经破坏直接记录日志后异常退出即可,由外部监控自动重启) process.on('uncaughtException', function (err) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: xlog(xerror(err)); return [4 /*yield*/, xwarn(err)]; case 1: _a.sent(); process.exit(-1); return [2 /*return*/]; } }); }); }); // 记录await/async中出现未捕获的异常错误 process.on('unhandledRejection', function (reason, p) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: xlog('Unhandled Rejection at: Promise', p, 'reason:', reason); // application specific logging, throwing an error, or other logic here return [4 /*yield*/, xwarn(reason, p)]; case 1: // application specific logging, throwing an error, or other logic here _a.sent(); process.exit(-1); return [2 /*return*/]; } }); }); }); // async/await的非阻塞异步延迟方法,用于调试阻塞程序的执行进行单步调试的效果。 var sleep = require('sleep-async')(); function xsleep(ms) { if (ms === void 0) { ms = -1; } if (ms <= 0) { ms = 50 * 365 * 24 * 3600 * 1000; // 50年最大数视为永久阻塞方便断点单步调试问题 } return new Promise(function (resolve, reject) { try { sleep.sleep(ms, function () { resolve(); }); } catch (err) { xlog(xerror(err)); resolve(); // xthrow(err,reject) } }); } exports.xsleep = xsleep; function xpost(url, param, headers, timeout) { if (timeout === void 0) { timeout = 3000; } return __awaiter(this, void 0, void 0, function () { var res, json, text, err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: // TODO 线上测试不稳定超时暂时忽略掉通过进程最大运行时间去控制超时失败 timeout = 5000; // -1 不行线上会被阻塞住僵死 res = null; json = null; text = null; _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); return [4 /*yield*/, fetch(url, { method: 'POST', body: JSON.stringify(param), headers: __assign({ 'Content-Type': 'application/json' }, headers), timeout: timeout <= 0 ? 0 : timeout, })]; case 2: res = _a.sent(); return [4 /*yield*/, res.text()]; // 解析出完整的返回内容避免HTML以及非法格式信息便于正确报错定位后端接口错误 case 3: text = _a.sent(); // 解析出完整的返回内容避免HTML以及非法格式信息便于正确报错定位后端接口错误 json = JSON.parse(text); return [2 /*return*/, json]; case 4: err_1 = _a.sent(); xthrow(err_1, { url: url, param: param, headers: headers, text: text }); return [3 /*break*/, 5]; case 5: return [2 /*return*/]; } }); }); } exports.xpost = xpost; // 默认超时3000毫秒 function xget(url, param, headers, timeout) { if (timeout === void 0) { timeout = 3000; } return __awaiter(this, void 0, void 0, function () { var res, json, text, err_2; return __generator(this, function (_a) { switch (_a.label) { case 0: // TODO 线上测试不稳定超时暂时忽略掉通过进程最大运行时间去控制超时失败 timeout = 5000; // -1 不行线上会被阻塞住僵死 res = null; json = null; text = null; _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); url = url + (param ? '?' : '') + querystring.stringify(param); return [4 /*yield*/, fetch(url, { method: 'GET', headers: __assign({ 'Content-Type': 'application/json' }, headers), timeout: timeout <= 0 ? 0 : timeout, })]; case 2: res = _a.sent(); return [4 /*yield*/, res.text()]; // 解析出完整的返回内容避免HTML以及非法格式信息便于正确报错定位后端接口错误 case 3: text = _a.sent(); // 解析出完整的返回内容避免HTML以及非法格式信息便于正确报错定位后端接口错误 json = JSON.parse(text); return [2 /*return*/, json]; case 4: err_2 = _a.sent(); xthrow(err_2, { url: url, param: param, headers: headers, text: text }); return [3 /*break*/, 5]; case 5: return [2 /*return*/]; } }); }); } exports.xget = xget; // 302临时重定向跳转实现 function xredirect(url, param) { if (param === void 0) { param = {}; } // TODO 多个程序实例并发处理的时候存在时序问题不能保证全局变量被准确清空。 // 检查应用重复设置重定向地址未及时return返回控制器问题 xassert(global['__redirect_url__'] === undefined); if (param) { xassert(_.isPlainObject(param)); // 删除param中两个框架预定义参数__url__和__api__不允许进行参数传递(禁止业务逻辑使用避免框架后续升级以及与短网址功能冲突) delete param.__api__; delete param.__url__; // 补额外的附加参数 if (/\?/.test(url)) { url += '&'; } else { url += '?'; } url += querystring.stringify(param); } global['__redirect_url__'] = url; } exports.xredirect = xredirect; // 如果只有key参数表示读取属性(缺省值为undefined),如果key为空表示读取所有的请求cookies属性,否则表示响应设置cookies function xcookie(key, value, option) { console.log('获取请求中的值:', arguments ? JSON.stringify(arguments) : arguments); if (!arguments.length) { // 读取所有的请求cookies属性object console.log('进入流程1'); return global['__request_cookies__'] ? global['__request_cookies__'] : {}; } else if (arguments.length == 1) { console.log('进入流程2'); return key ? xcookie()[key] : undefined; } else { console.log('进入流程3'); if (global['__respond_cookies__'] === undefined) { global['__respond_cookies__'] = {}; } if (key) { console.log('COOKIES缺省属性设置(有效时间24小时并且统一关联到根页面上获取COOKIES值)'); // COOKIES缺省属性设置(有效时间24小时并且统一关联到根页面上获取COOKIES值) option = xassign({ path: '/', maxAge: 24 * 3600 }, option); global['__respond_cookies__'][key] = cookie.serialize(key, value, option); } return; } } exports.xcookie = xcookie; // 判断user-agent请求是否为移动端 function xismobile() { var md = new MobileDetect(global['__user_agent__']); return !!md.mobile(); } function xassign(target, source) { var args = []; for (var _i = 2; _i < arguments.length; _i++) { args[_i - 2] = arguments[_i]; } var param = [true, target, source].concat(args); return extend.apply(null, param); } // 查询app/config目录下的应用配置数据 function xconfig(path, defaultValue) { if (defaultValue === void 0) { defaultValue = undefined; } if (global['__config__']) { return _.get(global['__config__'], path, defaultValue); } var fp = require('path'); var fs = require('fs'); // 自动获取app/config的相对路径目录位置得到根路径的位置 var config_path = ''; if (__dirname.includes('/node_modules/@bxjs/base/')) { // 在应用目录下 config_path = fp.join(__dirname, '../../../../app/config'); } else { // 在axjs库开发目录下 config_path = fp.join(__dirname, '../app/config'); } // 自动识别判断运行环境global['__env__']并且加载对应的base数据和env数据 var config_base_path = config_path + '/config.base.' + get_suffix_ts_or_js(); var config_env_path = config_path + ("/config." + global['__env__'] + ".") + get_suffix_ts_or_js(); if (!fs.existsSync(config_base_path)) { return defaultValue; } var config_base = require(config_base_path).default; var config_env = {}; if (fs.existsSync(config_env_path)) { config_env = require(config_env_path).default; } // bugfix Object.assign不支持深度拷贝问题 // global['__config__'] = Object.assign({}, config_base, config_env) // global['__config__'] = _.assign({}, config_env, config_base) global['__config__'] = xassign({}, config_base, config_env); return _.get(global['__config__'], path, defaultValue); } function xconnect(callback, config) { if (config === void 0) { config = 'default'; } return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { var cfg, mng, name_1, db_1; var _this = this; return __generator(this, function (_a) { cfg = {}; try { cfg = xassign({}, xconfig('plugins.database.default', {})); xassert(!_.isEmpty(cfg), ERR$PARAM, { config: config }); // 强制补上约定的实体存放路径定义位置(不允许配置是约定规范) if (__dirname.includes('/node_modules/@bxjs/base/')) { // 在应用目录下 cfg['entities'] = [ path.join(__dirname, '../../../../app/plugins/database/entity/*.' + get_suffix_ts_or_js()) ]; } else { // 在axjs库开发目录下 cfg['entities'] = [ path.join(__dirname, '../app/plugins/database/entity/*.' + get_suffix_ts_or_js()) ]; } mng = typeorm_1.getConnectionManager(); name_1 = cfg.name ? cfg.name : 'default'; if (!mng.has(name_1)) { mng.create(cfg); } db_1 = mng.get(name_1); if (global['g_connection'] === undefined) { global['g_connection'] = {}; } if (!db_1.isConnected) { // TODO 需要进行连接池的管理 global['g_connection'][name_1] = db_1.connect(); } global['g_connection'][name_1].then(function (connection) { return __awaiter(_this, void 0, void 0, function () { var out; return __generator(this, function (_a) { switch (_a.label) { case 0: xassert(db_1.isConnected); return [4 /*yield*/, callback(connection) // await db.close() // typeorm没有进行连接池的管理不能进行销毁 ]; case 1: out = _a.sent(); // await db.close() // typeorm没有进行连接池的管理不能进行销毁 resolve(out); return [2 /*return*/]; } }); }); }).catch(function (err) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { // await db.close() xthrow(err, reject, { config: config, name: name_1 }); return [2 /*return*/]; }); }); }); } catch (err) { xthrow(err, reject, { config: config }); } return [2 /*return*/]; }); }); })]; }); }); } // 创建XBaseEntity对象并且自动赋值前端请求的赋值数据 function xnew(TYPE, param) { var args = []; for (var _i = 2; _i < arguments.length; _i++) { args[_i - 2] = arguments[_i]; } // 泛型实现类似这个功能 // asset = new AlilangAsset() // getRepository(AlilangAsset).merge(asset, param as any) // AlilangAsset.merge(asset, param as any) // return asset var obj = new TYPE(); if (_.isEmpty(param)) { return obj; } var repo = typeorm_1.getRepository(TYPE); repo.merge.apply(repo, [obj, param].concat(args)); return obj; } // 查询构造器易用性封装 function xquery(connect, TYPE, alias) { return connect.getRepository(TYPE).createQueryBuilder(alias); } // 分页查询获取总数以及原始记录数据 function xcount(sql, page, size) { return __awaiter(this, void 0, void 0, function () { var _a, count, rows; return __generator(this, function (_b) { switch (_b.label) { case 0: xassert(page >= 1); return [4 /*yield*/, Promise.all([ sql.getCount(), sql.offset((page - 1) * size).limit(size).getRawMany() ])]; case 1: _a = _b.sent(), count = _a[0], rows = _a[1]; return [2 /*return*/, [rows, count]]; } }); }); } // 路由参数的修饰符配置 // TODO 更多接口相关参数的配置扩展,例如:是否支持JSONP function xroute(param) { // 缺省值处理 // name 接口名称 // desc 接口描述 // path 路径路由重写 // auth 是否需要登录鉴权(缺省需要进行鉴权为true) // nowrap 是否对于JSON请求进行success/content的标准进行包装(缺省进行包装为false) param = xassign({ name: '', desc: '', path: '', auth: true, nowrap: false }, param); return function (target, propertyKey, descriptor) { var _this = this; // TODO 注入到类实例定义中进行全局引用动态类的特性添加(trait功能的动态实现) // 动态绑定路由类实例的上下文属性 target.prototype.context = function () { return { param: param, // 是否登录的鉴权方法统一框架层面上的处理实现,此处仅仅是通用接口的约束的定义。 auth: function () { return __awaiter(_this, void 0, void 0, function () { var auth, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!(param && param.auth)) return [3 /*break*/, 2]; auth = xgot(YAuth); _a = xassert; return [4 /*yield*/, auth.getLoginStatus()]; case 1: _a.apply(void 0, [_b.sent(), ERR$UNAUTHORIZED]); _b.label = 2; case 2: return [2 /*return*/]; } }); }); } }; }; }; } // 完全没有必要的多余定义,需要通过MOCK定义进行细节数据类型的显性定义处理逻辑验证。 // // 基本数据类型的规范扩展定义,方便API接口的定义以及形参自动验证合法性,并且与数据库数据类型保持一致。 // type INT = number // 有符号整数 // type UINT = number // 无符号整数 // type DECIMAL = number // 精确小数 // type FLOAT = number // 单精度浮点数(不精确小数) // type DOUBLE = number// 双精度浮点数(不精确小数) // type BOOL = boolean // type STR = string // type DATE = string // 年月日 '2017-06-25' // type TIME = string // 时分秒 '00:00:00' // type DATETIME = string // 年月日时分秒 '2017-06-25 00:00:00' // 模拟数据模板定义使用教程 http://mockjs.com/0.1/#%E6%95%B0%E6%8D%AE%E5%8D%A0%E4%BD%8D%E7%AC%A6%E5%AE%9A%E4%B9%89%20DPD function xmock(rules) { return mockjs.mock(rules); } function xrandom(name, data) { var _a; mockjs.Random.extend((_a = {}, _a[name] = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } xassert(data.length > 0); if (data.length == 1) return data[0]; var max = data.length - 1; var idx = xmock("@int(0," + max + ")"); return data[idx]; }, _a)); } // 扩展一些预定义bxjs的基础随机方法或者覆盖一些mockjs中的方法 mockjs.Random.extend({ // bxjs表定义的主键统一定义(约定系统中为字符串7-14字节长度算法) id: function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return shortid.generate(); }, // 中国手机号随机生成算法(约定系统中的手机号为字符串数据类型) mobile: function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var isps = [ 134, 135, 136, 137, 138, 139, 147, 150, 151, 152, 157, 158, 159, 182, 183, 184, 187, 188, 178, 130, 131, 132, 145, 155, 156, 185, 186, 176, 133, 134, 153, 180, 181, 189, 177, 173, 176, 173, 177, 178, 170, 140, 141, 142, 143, 144, 146, 148, 149, 154 ]; var max = isps.length - 1; var idx = xmock("@int(0," + max + ")"); var num = xmock("@int(100000000,199999999)"); return (isps[idx] * 100000000 + num % 100000000) + ''; }, // 转换为缺省中文内容提示 paragraph: function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } switch (args.length) { case 0: return xmock('@cparagraph'); case 1: return xmock("@cparagraph(" + args[0] + ")"); case 2: return xmock("@cparagraph(" + args[0] + "," + args[1] + ")"); default: xassert(false); } }, sentence: function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } switch (args.length) { case 0: return xmock('@csentence'); case 1: return xmock("@csentence(" + args[0] + ")"); case 2: return xmock("@csentence(" + args[0] + "," + args[1] + ")"); default: xassert(false); } }, title: function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } switch (args.length) { case 0: return xmock('@ctitle'); case 1: return xmock("@ctitle(" + args[0] + ")"); case 2: return xmock("@ctitle(" + args[0] + "," + args[1] + ")"); default: xassert(false); } }, }); // laravel风格JSON对象验证器封装,详细文档见 https://github.com/skaterdav85/validatorjs function xcheck(param, rules, messages) { var obj = new validatorjs(param, rules); if (obj.fails()) { xthrow(ERR$PARAM, obj.errors); } } // 【IoC容器管理】应用层的插件实现类绑定到BXJS统一注册的标准插件的映射关系在全局容器实例中注册 function xbind(TYPE) { var o = new TYPE(); return xcontainer.bind(o.id).to(require("@app/plugins/" + o.id).default); } // 【IoC容器管理】框架或应用依赖标准规范接口插件的类实例获取方法 function xgot(TYPE) { var o = new TYPE(); return xcontainer.get(o.id); } // 同步系统命令调用执行 function xcmd() { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return __awaiter(this, void 0, void 0, function () { var options, cmd, ret, err_3; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 1, , 3]); options = {}; options.cwd = options.cwd || process.env.__ctxPath || process.cwd(); xassert(_.isArray(args) && args.length > 0); cmd = args.shift(); ret = cross_spawn.sync(cmd, args, xassign({ stdio: 'inherit' }, options)); xassert(ret.status === 0, ERR$UNKNOWN, ret); return [2 /*return*/, ret]; case 1: err_3 = _a.sent(); return [4 /*yield*/, xwarn(err_3)]; case 2: _a.sent(); xthrow(err_3); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); } // 对于数组嵌套回调函数的nodejs异步处理方法的统一封装 function xmap(values, callack) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { xassert(_.isArray(values) && _.isFunction(callack)); return [2 /*return*/, Promise.all(values.map(callack))]; }); }); } // Refer to document: https://help.aliyun.com/document_detail/62670.html // 获取ACM配置信息接口的统一封装 function xacm(group, id) { return __awaiter(this, void 0, void 0, function () { var cfg, acm; var _this = this; return __generator(this, function (_a) { cfg = xconfig("plugins.acm"); xassert(group && cfg && cfg[group], ERR$CONFIG, { cfg: cfg }); acm = new ACMClient(cfg[group]); return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { try { co(function () { var content, err_4; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); group = group + ':' + global['__env__']; // 补上环境后缀支持各种开发环境的个性化配置 return [4 /*yield*/, acm.getConfig(id, group)]; case 1: content = _a.sent(); xassert(content, ERR$CONFIG, { id: id, group: group }); resolve(content); return [3 /*break*/, 3]; case 2: err_4 = _a.sent(); xthrow(err_4, reject, { id: id, group: group, cfg: cfg[group] }); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); } catch (err) { xthrow(err, reject, { id: id, group: group, cfg: cfg[group] }); } return [2 /*return*/]; }); }); })]; }); }); } // 根据当前配置的时区正确获取当前时间值(当前时区通过process.env.TZ='Asia/Shanghai'初始化配置统一解决掉对应用无感前后台保持一致性) function xnow(format) { if (format === void 0) { format = 'YYYY-MM-DD HH:mm:ss'; } return moment().format(format); } // 字符串与时间戳的相互转换处理 function xtime(value) { if (value === void 0) { value = undefined; } if (value == undefined) { return moment().toDate().getTime(); } xassert(_.isString(value) || _.isSafeInteger(value)); if (_.isString(value)) { // STRING转换为INT类型 return moment(value).toDate().getTime(); } else { // INT转换为STRING类型 return moment(value).format('YYYY-MM-DD HH:mm:ss'); } } // base64编码 function xbase64encode(value) { xassert(!_.isEmpty(value) && _.isString(value)); return new Buffer(value).toString('base64'); } // base64解码 function xbase64decode(value) { xassert(!_.isEmpty(value) && _.isString(value)); return new Buffer(value, 'base64').toString(); } // 改进npm包中的uuid v1时间序列算法确保唯一性和随机性可以稳定可靠的应用于serverless分布式高并发应用场景下 // 改进基于时间序列的UUID算法确保serverless分布式并发场景下的全局唯一性以及使用密码机随机性改进不可预测性 function xuuid() { // 优化算法中的100纳秒时间为随机数,进一步降低冲突概率。 // 冲突概率分析: // 1秒支撑1000并发(毫秒时间戳精度保证), // 1000并发中相同毫秒时间的并发,再通过10000百纳秒随机数进行区分避免冲突, // 不同机器上的并发时间戳可能会有偏移导致重复偏差时间(通过6个字节的node id随机数区分机器,2字节0x3fff随机数区分clockseq) // 冲突概率基本上可以保证忽略不计避免shortid算法高并发冲突的缺陷 return require('uuid/v1')({ nsecs: Math.floor(Math.random() * 10000) }).replace(/\-/g, ''); ////////////////////////////////////////////////////////////////////////////////////// // 考虑到serverless实际环境并不是合适node id使用mac地址,直接使用默认的8字节随机数替代更模拟更合适(node id + clockseq)。 // // 封装正确的uuid实现算法确保唯一性(使用6个字节的机器mac地址,确保分布式机器算法执行的唯一性) // const mac = await __xgetmac__() // // 将字符串转换为buffer二进制处理 // let buf = [] // // MAC地址格式 ##:##:##:##:##:## // const values = mac.split(':') // xassert(values && values.length == 6) // for (let i = 0; i < 6; ++i) { // const tmpByte = parseInt(values[i], 16); // buf.push(tmpByte) // } // // 以mac地址作为机器唯一标识确保正确性 // const uuid = require('uuid/v1')({node: buf}) // return uuid; } /* If you want to insure UUID uniqueness across your cluster processes, then I would suggest using v1({node:aUniqueVal}), where aUniqueVal is some value you know to be unique to each cluster process. https://www.npmjs.com/package/node-machine-id 生成机器id的唯一标识算法参考 */ // // 获取mac地址 // async function __xgetmac__(): Promise<string> { // if (!global['g_bxjs_sMacAddress']) { // return new Promise<string>((resolve, reject) => { // try { // xassert(require('getmac').isMac(global['g_bxjs_sMacAddress'])) // resolve(global['g_bxjs_sMacAddress']) // } catch (err) { // xthrow(err, reject) // } // }) // } // return new Promise<string>((resolve, reject) => { // require('getmac').getMac(function (err, macAddress) { // if (err) xthrow(err, reject) // resolve(macAddress) // }) // }) // } // 请求上下文变量自动重置方法(以global变量中key为__下划线开始结束的属性自动清空为undefined,如需赋值其他缺省值需要在此函数中明确定义) function xreset() { // 所有请求上下文属性的自动清空初始化处理(通过约定global的特殊属性__xxx__简化koa中的context设计机制,将这部分机制做到框架上对应用不可见) for (var _i = 0, _a = Object.keys(global); _i < _a.length; _i++) { var key = _a[_i]; if (key.startsWith('__') && key.endsWith('__')) { global[key] = undefined; } } // 明确定义的一些全局变量的初始值赋值 if (!global['__env__']) { global['__env__'] = 'local'; // local,daily,pre,gray,prod 在统一入口处自动识别配置(目前暂不支持gray配置尚未开发无法自动识别) } global['__config__'] = undefined; global['__session__'] = {}; global['__cache__'] = {}; global['__user__'] = {}; global['__user_agent__'] = undefined; global['__client_ip__'] = undefined; global['__redirect_url__'] = undefined; global['__request_cookies__'] = {}; global['__respond_cookies__'] = {}; } // 首次模块加载的时候执行一次,确保应用中不可以有__xxx__参数作为全局变量在模块初始化的时候 xreset(); global['xreset'] = xreset; global['xconnect'] = xconnect; global['xnew'] = xnew; global['xquery'] = xquery; global['xcount'] = xcount; global['xassign'] = xassign; global['xconfig'] = xconfig; global['xthrow'] = xthrow; global['xassert'] = xassert; global['xerror'] = xerror; global['xroot'] = xroot; global['xstack'] = xstack; global['xwarn'] = xwarn; global['xlog'] = xlog; global['xpost'] = xpost; global['xget'] = xget; global['xsleep'] = xsleep; global['xredirect'] = xredirect; global['xcookie'] = xcookie; global['xismobile'] = xismobile; global['xsession'] = session_1.xsession; global['xuser'] = session_1.xuser; global['xcache'] = session_1.xcache; global['xroute'] = xroute; global['xmock'] = xmock; global['xrandom'] = xrandom; global['xcheck'] = xcheck; global['xcontainer'] = new inversify_1.Container(); // 全局单实例容器初始化 global['xbind'] = xbind; global['xgot'] = xgot; global['YAuth'] = $$.YAuth; // 全局声明认证插件规范抽象类 global['xcmd'] = xcmd; global['xmap'] = xmap; global['xacm'] = xacm; global['xbase64encode'] = xbase64encode; global['xbase64decode'] = xbase64decode; global['xnow'] = xnow; global['xtime'] = xtime; global['xuuid'] = xuuid; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFzZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImJhc2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxpQkE4M0JBOztBQTkzQkEsT0FBTyxDQUFDLEdBQUcsQ0FBQyxFQUFFLEdBQUcsZUFBZSxDQUFBO0FBQ2hDLE9BQU8sQ0FBQyxHQUFHLENBQUMsNEJBQTRCLEdBQUcsR0FBRyxDQUFBO0FBQzlDLDRCQUF5QjtBQUN6Qix1Q0FBbUM7QUFDbkMseUNBQTZHO0FBQzdHLHFDQUFpRDtBQUNqRCw4QkFBK0I7QUFFL0IsSUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFBO0FBQzVCLElBQU0sZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLG9CQUFvQixDQUFDLENBQUE7QUFDdEQsSUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFBO0FBQ2hDLElBQU0sWUFBWSxHQUFHLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQTtBQUM3QyxJQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUE7QUFDbkMsSUFBTSxDQUFDLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFBO0FBQzNCLElBQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQTtBQUNoQyxJQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUE7QUFDaEMsSUFBTSxXQUFXLEdBQUcsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFBO0FBQzFDLHlDQUF5QztBQUN6QyxpREFBaUQ7QUFDakQsa0NBQWtDO0FBQ2xDLCtEQUErRDtBQUMvRCxzRUFBc0U7QUFDdEUsOENBQThDO0FBQzlDLFdBQVc7QUFDWCxLQUFLO0FBQ0wsSUFBTSxhQUFhLEdBQUcsT0FBTyxDQUFDLGVBQWUsQ0FBQyxDQUFBO0FBQzlDLElBQU0sTUFBTSxHQUFHLE9BQU8sQ0FB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