require('source-map-support').install() require('tsconfig-paths').register() process.env.TZ = 'Asia/Shanghai' process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' const path = require('path') const ErrorStackParser = require('error-stack-parser') const cookie = require('cookie') const MobileDetect = require('mobile-detect') const fetch = require('node-fetch') const _ = require('lodash') const moment = require('moment') const extend = require('extend') const 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); // // } // }) const circular_json = require("circular-json") const mockjs = require('mockjs') const shortid = require('shortid') const validatorjs = require('validatorjs') const cross_spawn = require('cross-spawn') const ACMClient = require('amber_utf-8') const co = require('co') require('./error') // FIXME HACK原生方法JSON转换不可逆的BUG(JAVA端传来的富文本字段内容含有\n\t字符串中的字符生成JSON字符串无法正常解析报错) const raw_stringify = JSON.stringify function new_stringify(value: any, replacer?: (key: string, value: any) => any, space?: string | number): string { let 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 as any // ts-node本地调试需要加载对应的源代码后缀名称 export function get_suffix_ts_or_js() { if (global['__env__'] == 'local' && !/^\/code\/node_modules/.test(__dirname)) { return 'ts' } else { return 'js' } } // 准确定位错误码位置,间接得到函数调用位置地址信息,结合符号报表的正确解析处理完美得到错误定位信息,准确代码调试。 function __get_base_func_caller_source_position(position: number = 3) { try { throw new Error() } catch (err) { let out = ErrorStackParser.parse(err) let idx = 0 // 找到第二个TS文件的执行位置 let 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 '#' } } } // 获取异常调用栈用于辅助错误提示定位 export function xstack(err, compact = true) { try { // TODO 优化裁剪一些无用信息减少日志尺寸更加便于人工分析处理 let stack = ErrorStackParser.parse(err) if (compact) { let sources: string[] = [] for (let v of stack) { sources.push(`${v['fileName']}:${v['lineNumber']}`) } return sources } return stack } catch (err1) { let source = __get_base_func_caller_source_position() return `invalid error input param (${source})` } } // // 错误栈的递归嵌套格式显示数据结构定义(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 export function xerror(err, __param?: any) { xassert(err instanceof Error) try { // 标准错误的统一转换处理 let data: any = JSON.parse(err.message) if (data.code && data.msg && ERRORS[data.code]) { return data } } catch (err) { // ignore parse error } // 非标准错误的统一格式转换处理 let msg = ERRORS[ERR$UNKNOWN]['zh'] // TODO 错误码多语言回传到客户端问题 let code = ERR$UNKNOWN let param: any = {msg: err.message, param: __param} // 用户自定义的错误参数信息 msg为非错误码JSON四元组就是嵌套的终止条件。 let stack = xstack(err) let data = {msg, code, param, stack} return data } // 用于获取错误栈的root cause根本原因(第一个被拦截的错误发生位置) export function xroot(err: Error) { xassert(err instanceof Error) let {msg, param, code, stack} = xerror(err) // 递归遍历找到错误链的root cause for (; param && param.msg;) { try { let json: any = JSON.parse(param.msg) param = json.param } catch (err) { msg = param.msg code = param.code stack = param.stack param = param.param break } } return {msg, code, param, stack} } // 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) // }) // }) // } export function xthrow(code: string | Error = ERR$UNKNOWN, param: any = undefined, reject_param: any = undefined) { // promise中进行reject异常处理的抛出错误方法的形参逻辑预处理转换。 let reject: any = _.isFunction(param) ? param : undefined if (reject) param = reject_param let data: any = {} let 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回调中进行抛错误处理 let 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, stack: [source]} } else { // 对于常量定义错误的统一格式化处理 data = {code, msg: global['ERRORS'][code as string]['zh'], param, stack: [source]} } // 对于是否promise场景下的错误上抛进行正确的转换处理 if (_.isFunction(reject)) { // promise回调中进行抛错误处理 reject(new Error(JSON.stringify(data))) } else { // 非promise回调中异常传递 throw new Error(JSON.stringify(data)) } } export function xassert(expr: any, code: string = ERR$ASSERT, param?: any) { let source = __get_base_func_caller_source_position() let stack = [source] if (!expr) throw new Error(JSON.stringify({code, msg: global['ERRORS'][code]['zh'], param, stack})) return expr } // uuid冲突可能性存在1S平均到1000毫秒1000次并发,每1ms并发冲突可能性(8个字节随机数+10000随机数) async function xuuid(): Promise { // // 封装正确的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) // } // const time = require('nano-seconds').now() // const bytes = require('crypto').randomBytes(8) let options = {} // 以mac地址作为机器唯一标识确保正确性 const uuid = require('uuid/v1')(options = { // msecs: new Date().getTime(), nsecs: Math.floor(Math.random() * 10000) // 优化算法中的随机数使用缺陷(1毫秒中N个并发8字节密码随机数和1万随机整数,1秒1000并发) // node: buf,//mac地址(一台机器上多个VM也可能冲突问题,机器标识随机6个字节更友好些) // msecs: new Date().getTime(),//unix元年距离的毫秒时间 // msecs: time[0] * 1000 + Math.floor(time[1] / 1000000),//毫秒时间 // nsecs: Math.floor((time[1] % 1000000) / 100),//将纳秒时间951ms851969ns转换为100纳秒为单位 // clockseq: (bytes[0] << 8 | bytes[1]) & 0x3fff,//时间序列使用随机数替代1024个随机数 }) console.log(options) return uuid; } // 获取mac地址 async function __xgetmac__(): Promise { if (global['g_bxjs_sMacAddress']) { return new Promise((resolve, reject) => { try { xassert(require('getmac').isMac(global['g_bxjs_sMacAddress'])) resolve(global['g_bxjs_sMacAddress']) } catch (err) { xthrow(err, reject) } }) } return new Promise((resolve, reject) => { require('getmac').getMac(function (err, macAddress) { if (err) xthrow(err, reject) resolve(macAddress) }) }) } async function test() { // var machine = require('machine-uuid') // console.log(machine('mynamespace')); // 2c433a07-140a-5bb9-949a-4997b566c397 // console.log(machine('mynamespace')); console.log(await __xgetmac__()) console.log(await xuuid()) } test().then(data => { }).catch(err => { console.log(err) })