UNPKG

32.7 kBPlain TextView Raw
1process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
2import 'reflect-metadata'
3import {Container} from 'inversify'
4import {getConnectionManager, Connection, BaseEntity, getRepository, SelectQueryBuilder} from '@bxjs/typeorm'
5import {xsession, xuser, xcache} from './session'
6import * as $$ from './plugins'
7
8const path = require('path')
9const ErrorStackParser = require('error-stack-parser')
10const cookie = require('cookie')
11const MobileDetect = require('mobile-detect')
12const fetch = require('node-fetch')
13const _ = require('lodash')
14const moment = require('moment')
15const extend = require('extend')
16const querystring = require('querystring')
17// const parameter = require('parameter')
18// const parameterCheckInstance = new parameter({
19// // translate: function () {
20// // var args = Array.prototype.slice.call(arguments);
21// // // Assume there have I18n.t method for convert language.
22// // return I18n.t.apply(I18n, args);
23// // }
24// })
25const circular_json = require("circular-json")
26const mockjs = require('mockjs')
27const shortid = require('shortid')
28const validatorjs = require('validatorjs')
29const cross_spawn = require('cross-spawn')
30const ACMClient = require('acm-client')
31const co = require('co')
32
33// FIXME HACK原生方法JSON转换不可逆的BUG(JAVA端传来的富文本字段内容含有\n\t字符串中的字符生成JSON字符串无法正常解析报错)
34const raw_stringify = JSON.stringify
35
36function new_stringify(value: any, replacer?: (key: string, value: any) => any,
37 space?: string | number): string {
38 let out = raw_stringify(value, replacer, space)
39 if (_.isString(out)) {
40 out = out.replace(/\\n/g, '\\\\n')
41 .replace(/\\t/g, '\\\\t')
42 .replace(/\\u/g, '\\\\u') //JAVA端返回的unicode字符转义处理
43 }
44 return out
45}
46
47JSON.stringify = new_stringify as any
48
49// ts-node本地调试需要加载对应的源代码后缀名称
50export function get_suffix_ts_or_js() {
51 if (global['__env__'] == 'local' && !/^\/code\/node_modules/.test(__dirname)) {
52 return 'ts'
53 } else {
54 return 'js'
55 }
56}
57
58// 准确定位错误码位置,间接得到函数调用位置地址信息,结合符号报表的正确解析处理完美得到错误定位信息,准确代码调试。
59function __get_base_func_caller_source_position(position: number = 3) {
60 try {
61 throw new Error()
62 } catch (err) {
63 let out = ErrorStackParser.parse(err)
64 let idx = 0
65 // 找到第二个TS文件的执行位置
66 let find_ts_sufix_file_count = 0
67 for (; idx < out.length; idx++) {
68 if (/\.ts$/.test(out[idx].fileName)) {
69 find_ts_sufix_file_count += 1
70 }
71 if (find_ts_sufix_file_count == position) {
72 break
73 }
74 }
75 if (find_ts_sufix_file_count == position) {
76 return '[' + out[idx]['fileName'] + ':' + out[idx]['lineNumber'] + ']'
77 } else {
78 // TODO 需要定位为什么调用栈无法找到对应的位置出现越界??
79 // console.error(err)
80 console.error(circular_json.stringify(out, null, 4)
81 .replace(/\r/g, '').replace(/\n/g, ''))
82 return '#'
83 }
84
85 }
86}
87
88// 获取异常调用栈用于辅助错误提示定位
89export function xstack(err, compact = true) {
90 try {
91 // TODO 优化裁剪一些无用信息减少日志尺寸更加便于人工分析处理
92 let stack = ErrorStackParser.parse(err)
93 if (compact) {
94 let sources: string[] = []
95 for (let v of stack) {
96 sources.push(`${v['fileName']}:${v['lineNumber']}`)
97 }
98 return sources
99 }
100 return stack
101 } catch (err1) {
102 let source = __get_base_func_caller_source_position()
103 return `invalid error input param (${source})`
104 }
105}
106
107// // 错误栈的递归嵌套格式显示数据结构定义(param嵌套找到最后一个msg的JSON解析语法错误就是错误链的原始错误发生位置)
108// let x = {
109// "code": "UNKNOWN",
110// "msg": "未知错误",
111// "param": {
112// "msg": "您输入的用户名或密码错误,请重新登录 (ErrorCode: 1005, url: https://login.alibaba-inc.com/authorize/login.do)"
113// },
114// "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\"]",
115// }
116
117// 对于异常内容的格式化参数解析处理成为四元组code/msg/param/stack
118export function xerror(err, __param?: any) {
119 xassert(err instanceof Error)
120 try {
121 // 标准错误的统一转换处理
122 let data: any = JSON.parse(err.message)
123 if (data.code && data.msg && ERRORS[data.code]) {
124 return data
125 }
126 } catch (err) {
127 // ignore parse error
128 }
129 // 非标准错误的统一格式转换处理
130 let msg = ERRORS[ERR$UNKNOWN]['zh'] // TODO 错误码多语言回传到客户端问题
131 let code = ERR$UNKNOWN
132 let param: any = {msg: err.message, param: __param} // 用户自定义的错误参数信息 msg为非错误码JSON四元组就是嵌套的终止条件。
133 let stack = xstack(err)
134 let data = {msg, code, param, stack}
135 return data
136}
137
138// 用于获取错误栈的root cause根本原因(第一个被拦截的错误发生位置)
139export function xroot(err: Error) {
140 xassert(err instanceof Error)
141 let {msg, param, code, stack} = xerror(err)
142
143 // 递归遍历找到错误链的root cause
144 for (; param && param.msg;) {
145 try {
146 let json: any = JSON.parse(param.msg)
147 param = json.param
148 } catch (err) {
149 msg = param.msg
150 code = param.code
151 stack = param.stack
152 param = param.param
153 break
154 }
155 }
156 return {msg, code, param, stack}
157}
158
159// TODO 报错处理(显示问题反馈联系人信息)
160// 将未处理的错误上抛的异常链记录下来用于精准追踪代码的执行过程(以及准确获取到根节点的错误码)
161// 对于promise异步回调的统一出错处理写法实例
162// export function login(username: string, password: string) {
163// return new Promise((resolve, reject) => {
164// co(function* () {
165// let user = yield buc.oauthclient.login(username, password)
166// resolve(user)
167// }).catch(async function (err) {
168// xthrow(err, reject)
169// })
170// })
171// }
172export function xthrow(code: string | Error = ERR$UNKNOWN, param: any = undefined, reject_param: any = undefined) {
173 // promise中进行reject异常处理的抛出错误方法的形参逻辑预处理转换。
174 let reject: any = _.isFunction(param) ? param : undefined
175 if (reject) param = reject_param
176 let data: any = {}
177 let source = __get_base_func_caller_source_position()
178
179 if (code instanceof Error) {
180 try {
181 data = JSON.parse(code.message)
182 // 将透传上抛的错误的路径信息和附加参数也记录下来方便提供完整应用堆栈信息辅助调试业务逻辑
183 if (!_.isArray(data.stack)) {
184 data.stack = []
185 }
186 data.stack.push(source)
187 } catch (err) {
188 // ignore
189 }
190 // 标准错误直接上抛处理
191 if (data.code && data.msg && ERRORS[data.code]) {
192 // 测试严重BUG reject函数类型表达式为假必须要用lodash判定是否为函数
193 if (_.isFunction(reject)) {
194 // promise回调中进行抛错误处理
195 let err = new Error(JSON.stringify(data))
196 reject(err)
197 return
198 } else {
199 throw new Error(JSON.stringify(data))
200 }
201 }
202 // 将非标准错误转换为标准错误后再上抛处理
203 data = xerror(code, param)
204 data.code = ERR$UNKNOWN
205 data.msg = ERRORS[ERR$UNKNOWN]['zh'] // FIXME TODO 错误码的多语言处理转换!!
206 data.param = {msg: code.message, param, stack: [source]}
207 } else {
208 // 对于常量定义错误的统一格式化处理
209 data = {code, msg: global['ERRORS'][code as string]['zh'], param, stack: [source]}
210 }
211
212 // 对于是否promise场景下的错误上抛进行正确的转换处理
213 if (_.isFunction(reject)) {
214 // promise回调中进行抛错误处理
215 reject(new Error(JSON.stringify(data)))
216 } else {
217 // 非promise回调中异常传递
218 throw new Error(JSON.stringify(data))
219 }
220}
221
222export function xassert(expr: any, code: string = ERR$ASSERT, param?: any) {
223 let source = __get_base_func_caller_source_position()
224 let stack = [source]
225 if (!expr) throw new Error(JSON.stringify({code, msg: global['ERRORS'][code]['zh'], param, stack}))
226 return expr
227}
228
229// // https://github.com/node-modules/parameter 参数验证规则详见此文档(egg团队开发的组件)
230// // 注意事项:GET通过URL传递的参数都是字符串类型应该尽量避免GET传递参数,需要多用POST的JSON格式传递参数并且POSTMAN上进行辅助测试正确数据类型映射。
231// export function xcheck(param: { [propName: string]: any }, rules: { [propName: string]: any }) {
232// let errors = parameterCheckInstance.validate(rules, param)
233// if (_.isEmpty(errors)) {
234// return true
235// } else {
236// xthrow(ERR$PARAM, errors)
237// }
238// }
239
240export function xlog(...args) {
241 // 兼容云端以及本地日志调试(解决任意对象的JSON字符串内容的完整输出)
242 let source = __get_base_func_caller_source_position() + '[' + xnow('YYYY-MM-DD HH:mm:ss.SSS') + ']'
243 let output = circular_json.stringify([...args], null, 4)
244 if (global['__env__'] != 'prod' && !/^\/code\/node_modules/.test(__dirname)) {
245 // 打印到控制台一份日志(在阿里云非线上FC环境中)
246 console.log.apply(undefined, [source + output])
247 // 写日志文件到/tmp下临时处理一下 TODO 需要改为类似log4j的本地日志库仅在非线上环境使用方便开发单机日常机器上调试。
248 const fs = require('fs')
249 const logFilePath = process.env['NODE_LOGFILE'] ? process.env['NODE_LOGFILE'] : '/tmp/bxjs.log'
250 fs.appendFileSync(logFilePath, source + output + "\r\n")
251 } else {
252 // 生产环境下只打印到控制台绑定的SLS日志服务器上,并且需要去除掉换行信息否则打印会不正常。
253 // 去除掉换行方便方便SLS上的日志输出排版显示
254 output = output.replace(/\r/g, '').replace(/\n/g, '')
255 console.log.apply(undefined, [source + output])
256 }
257}
258
259// // 将详细错误信息及时发送到钉钉群上实时反馈给维护者
260// await xwarn({
261// code,
262// // TODO 如何认证通过了获取到用户信息也需要发送过去,方便联系对接人员进行立刻问题处理反馈。
263// message,
264// stack,
265// param,
266// })
267// 将详细错误信息及时发送到钉钉群上实时反馈给维护者
268// 钉钉IM群机器人报警通知
269async function xwarn(...args) {
270 // 得到xwarn方法被调用的位置
271 let source = __get_base_func_caller_source_position()
272
273 // 对于异常参数警告信息进行错误内容标准解析
274 if (args.length > 0 && args[0] instanceof Error) {
275 args[0] = xerror(args[0])
276 }
277
278 let out = [source, xnow('YYYY-MM-DD HH:mm:ss.SSS'), {...args}]
279
280 // 从配置信息中读取报警通知人手机列表和对应的群机器人的webhook的access_token信息
281 let access_token = xconfig('framework.warn.dingding.access_token')
282 let mobiles = xconfig('framework.warn.dingding.mobiles')
283 if (!access_token || !mobiles) {
284 access_token = '020a09eac5f2fa320ae851442d5e19e23693c64ad2255c85354b4a49a5a48d35'
285 mobiles = ['15381151346']
286 }
287
288 await xpost(`https://oapi.dingtalk.com/robot/send?access_token=${access_token}`, {
289 msgtype: 'text',
290 text: {
291 content: out
292 },
293 at: {
294 atMobiles: mobiles,
295 isAtAll: false
296 }
297 })
298
299 // 线上SLS日志上也保存一份
300 // console.warn(out)
301 xlog(out)
302}
303
304// 捕获未监听到的异常记录后直接退出(运行堆栈已经破坏直接记录日志后异常退出即可,由外部监控自动重启)
305process.on('uncaughtException', async function (err) {
306 xlog(xerror(err))
307 await xwarn(err)
308 process.exit(-1)
309})
310
311// 记录await/async中出现未捕获的异常错误
312process.on('unhandledRejection', async (reason, p) => {
313 xlog('Unhandled Rejection at: Promise', p, 'reason:', reason);
314 // application specific logging, throwing an error, or other logic here
315 await xwarn(reason, p)
316 process.exit(-1)
317})
318
319// async/await的非阻塞异步延迟方法,用于调试阻塞程序的执行进行单步调试的效果。
320const sleep = require('sleep-async')()
321
322export function xsleep(ms: number = -1) {
323 if (ms <= 0) {
324 ms = 50 * 365 * 24 * 3600 * 1000 // 50年最大数视为永久阻塞方便断点单步调试问题
325 }
326 return new Promise((resolve, reject) => {
327 try {
328 sleep.sleep(ms, () => {
329 resolve()
330 })
331 } catch (err) {
332 xlog(xerror(err))
333 resolve()
334 // xthrow(err,reject)
335 }
336 })
337}
338
339export async function xpost(url: string, param?: { [propName: string]: any },
340 headers?: { [propName: string]: any }, timeout: number = 3000) {
341 // TODO 线上测试不稳定超时暂时忽略掉通过进程最大运行时间去控制超时失败
342 timeout = 5000 // -1 不行线上会被阻塞住僵死
343 let res: any = null
344 let json: any = null
345 let text: any = null
346 try {
347 res = await fetch(url, {
348 method: 'POST',
349 body: JSON.stringify(param),
350 headers: {'Content-Type': 'application/json', ...headers},
351 timeout: timeout <= 0 ? 0 : timeout, // 默认3秒超时接口返回避免僵死
352 })
353 text = await res.text() // 解析出完整的返回内容避免HTML以及非法格式信息便于正确报错定位后端接口错误
354 json = JSON.parse(text)
355 return json
356 } catch (err) {
357 xthrow(err, {url, param, headers, text})
358 }
359}
360
361// 默认超时3000毫秒
362export async function xget(url: string, param?: { [propName: string]: any },
363 headers?: { [propName: string]: any }, timeout: number = 3000) {
364 // TODO 线上测试不稳定超时暂时忽略掉通过进程最大运行时间去控制超时失败
365 timeout = 5000 // -1 不行线上会被阻塞住僵死
366 let res: any = null
367 let json: any = null
368 let text: any = null
369 try {
370 url = url + (param ? '?' : '') + querystring.stringify(param)
371 res = await fetch(url, {
372 method: 'GET',
373 headers: {'Content-Type': 'application/json', ...headers},
374 timeout: timeout <= 0 ? 0 : timeout, // 默认3秒超时接口返回避免僵死
375 })
376 text = await res.text() // 解析出完整的返回内容避免HTML以及非法格式信息便于正确报错定位后端接口错误
377 json = JSON.parse(text)
378 return json
379 } catch (err) {
380 xthrow(err, {url, param, headers, text})
381 }
382}
383
384// 302临时重定向跳转实现
385export function xredirect(url: string, param: any = {}) {
386 // TODO 多个程序实例并发处理的时候存在时序问题不能保证全局变量被准确清空。
387 // 检查应用重复设置重定向地址未及时return返回控制器问题
388 xassert(global['__redirect_url__'] === undefined)
389 if (param) {
390 xassert(_.isPlainObject(param))
391 // 删除param中两个框架预定义参数__url__和__api__不允许进行参数传递(禁止业务逻辑使用避免框架后续升级以及与短网址功能冲突)
392 delete param.__api__
393 delete param.__url__
394 // 补额外的附加参数
395 if (/\?/.test(url)) {
396 url += '&'
397 } else {
398 url += '?'
399 }
400 url += querystring.stringify(param)
401 }
402 global['__redirect_url__'] = url
403}
404
405// 如果只有key参数表示读取属性(缺省值为undefined),如果key为空表示读取所有的请求cookies属性,否则表示响应设置cookies
406export function xcookie(key?: string, value?: string, option?: {}): any {
407 if (!arguments.length) {
408 // 读取所有的请求cookies属性object
409 return global['__request_cookies__'] ? global['__request_cookies__'] : {}
410 } else if (arguments.length == 1) {
411 return key ? xcookie()[key] : undefined
412 } else {
413 if (global['__respond_cookies__'] === undefined) {
414 global['__respond_cookies__'] = {}
415 }
416 if (key) {
417 // COOKIES缺省属性设置(有效时间24小时并且统一关联到根页面上获取COOKIES值)
418 option = xassign({path: '/', maxAge: 24 * 3600}, option)
419 global['__respond_cookies__'][key] = cookie.serialize(key, value, option)
420 }
421 return
422 }
423}
424
425// 判断user-agent请求是否为移动端
426function xismobile(): boolean {
427 const md = new MobileDetect(global['__user_agent__'])
428 return !!md.mobile()
429}
430
431function xassign(target, source, ...args) {
432 const param = [true, target, source, ...args]
433 return extend.apply(null, param)
434}
435
436// 查询app/config目录下的应用配置数据
437function xconfig(path: string, defaultValue: any = undefined) {
438 if (global['__config__']) {
439 return _.get(global['__config__'], path, defaultValue)
440 }
441
442 const fp = require('path')
443 const fs = require('fs')
444 // 自动获取app/config的相对路径目录位置得到根路径的位置
445 let config_path = ''
446 if (__dirname.includes('/node_modules/@bxjs/base/')) {
447 // 在应用目录下
448 config_path = fp.join(__dirname, '../../../../app/config')
449 } else {
450 // 在axjs库开发目录下
451 config_path = fp.join(__dirname, '../app/config')
452 }
453
454 // 自动识别判断运行环境global['__env__']并且加载对应的base数据和env数据
455 const config_base_path = config_path + '/config.base.' + get_suffix_ts_or_js()
456 const config_env_path = config_path + `/config.${global['__env__']}.` + get_suffix_ts_or_js()
457 if (!fs.existsSync(config_base_path)) {
458 return defaultValue
459 }
460 let config_base = require(config_base_path).default
461 let config_env = {}
462 if (fs.existsSync(config_env_path)) {
463 config_env = require(config_env_path).default
464 }
465 // bugfix Object.assign不支持深度拷贝问题
466 // global['__config__'] = Object.assign({}, config_base, config_env)
467 // global['__config__'] = _.assign({}, config_env, config_base)
468 global['__config__'] = xassign({}, config_base, config_env)
469 return _.get(global['__config__'], path, defaultValue)
470}
471
472async function xconnect(callback: (connect: Connection) => Promise<any>, config = 'default') {
473 return new Promise(async (resolve, reject) => {
474 let cfg = {} as any
475 try {
476 cfg = xassign({}, xconfig('plugins.database.default', {}))
477 xassert(!_.isEmpty(cfg), ERR$PARAM, {config})
478 // 强制补上约定的实体存放路径定义位置(不允许配置是约定规范)
479 if (__dirname.includes('/node_modules/@bxjs/base/')) {
480 // 在应用目录下
481 cfg['entities'] = [
482 path.join(__dirname, '../../../../app/plugins/database/entity/*.' + get_suffix_ts_or_js())
483 ]
484 } else {
485 // 在axjs库开发目录下
486 cfg['entities'] = [
487 path.join(__dirname, '../app/plugins/database/entity/*.' + get_suffix_ts_or_js())
488 ]
489 }
490 // 获取连接池中的链接(全局变量模块实例的使用)
491 const mng = getConnectionManager()
492 const name = cfg.name ? cfg.name : 'default'
493 if (!mng.has(name)) {
494 mng.create(cfg)
495 }
496 const db = mng.get(name)
497 if (global['g_connection'] === undefined) {
498 global['g_connection'] = {}
499 }
500 if (!db.isConnected) { // TODO 需要进行连接池的管理
501 global['g_connection'][name] = db.connect()
502 }
503 await global['g_connection'][name].then(async connection => {
504 xassert(db.isConnected)
505 const out = await callback(connection)
506 // await db.close() // typeorm没有进行连接池的管理不能进行销毁
507 resolve(out)
508 }).catch(async err => {
509 // await db.close()
510 xthrow(err, reject, {cfg})
511 })
512 } catch (err) {
513 xthrow(err, reject, {cfg})
514 }
515 })
516}
517
518// 创建XBaseEntity对象并且自动赋值前端请求的赋值数据
519function xnew<T extends BaseEntity>(TYPE: new () => T, param?: any, ...args): T {
520 // 泛型实现类似这个功能
521 // asset = new AlilangAsset()
522 // getRepository(AlilangAsset).merge(asset, param as any)
523 // AlilangAsset.merge(asset, param as any)
524 // return asset
525 let obj = new TYPE()
526 if (_.isEmpty(param)) {
527 return obj
528 }
529 let repo = getRepository<T>(TYPE)
530 repo.merge.apply(repo, [obj, param, ...args])
531 return obj
532}
533
534// 查询构造器易用性封装
535function xquery<T>(connect: Connection, TYPE: new () => T, alias?: string): SelectQueryBuilder<T> {
536 return connect.getRepository(TYPE).createQueryBuilder(alias)
537}
538
539// 分页查询获取总数以及原始记录数据
540async function xcount<T>(sql: SelectQueryBuilder<T>, page: number, size: number): Promise<[any[] | null, number]> {
541 xassert(page >= 1)
542 const [count, rows] = await Promise.all([
543 sql.getCount(),
544 sql.offset((page - 1) * size).limit(size).getRawMany()
545 ])
546 return [rows, count]
547}
548
549
550// 路由参数的修饰符配置
551// TODO 更多接口相关参数的配置扩展,例如:是否支持JSONP
552function xroute(param: { name?: string, desc?: string, path?: string, auth?: boolean }) {
553 // 缺省值处理
554 param = xassign({name: '', desc: '', path: '', auth: true}, param)
555 return function (target: Function, propertyKey: string, descriptor: PropertyDescriptor) {
556 // TODO 注入到类实例定义中进行全局引用动态类的特性添加(trait功能的动态实现)
557 // 动态绑定路由类实例的上下文属性
558 target.prototype.context = () => {
559 return {
560 param: param, // 保存当前控制器用户定义参数信息
561 // 是否登录的鉴权方法统一框架层面上的处理实现,此处仅仅是通用接口的约束的定义。
562 auth: async () => {
563 // 调用登录功能的前端接口实现,取到对应的实现方法。
564 if (param && param.auth) {
565 // 需要鉴权进行会话有效性进行合法性校验处理!!
566 // 未认证错误抛出处理,前端单页应用接口报错逻辑处理正确错误提示跳转。
567 const auth = xgot(YAuth)
568 xassert(await auth.getLoginStatus(), ERR$UNAUTHORIZED)
569 }
570 }
571 }
572 }
573 }
574}
575
576// 完全没有必要的多余定义,需要通过MOCK定义进行细节数据类型的显性定义处理逻辑验证。
577// // 基本数据类型的规范扩展定义,方便API接口的定义以及形参自动验证合法性,并且与数据库数据类型保持一致。
578// type INT = number // 有符号整数
579// type UINT = number // 无符号整数
580// type DECIMAL = number // 精确小数
581// type FLOAT = number // 单精度浮点数(不精确小数)
582// type DOUBLE = number// 双精度浮点数(不精确小数)
583// type BOOL = boolean
584// type STR = string
585// type DATE = string // 年月日 '2017-06-25'
586// type TIME = string // 时分秒 '00:00:00'
587// type DATETIME = string // 年月日时分秒 '2017-06-25 00:00:00'
588
589// 模拟数据模板定义使用教程 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
590function xmock<T>(rules: T): any {
591 return mockjs.mock(rules)
592}
593
594function xrandom(name: string, data: any[]) {
595 mockjs.Random.extend({
596 [name]: (...args) => {
597 xassert(data.length > 0)
598 if (data.length == 1) return data[0]
599 let max = data.length - 1
600 let idx = xmock(`@int(0,${max})`)
601 return data[idx]
602 }
603 })
604}
605
606// 扩展一些预定义bxjs的基础随机方法或者覆盖一些mockjs中的方法
607mockjs.Random.extend({
608 // bxjs表定义的主键统一定义(约定系统中为字符串7-14字节长度算法)
609 id: (...args) => {
610 return shortid.generate()
611 },
612 // 中国手机号随机生成算法(约定系统中的手机号为字符串数据类型)
613 mobile: (...args) => {
614 const isps = [
615 134, 135, 136, 137, 138, 139, 147, 150, 151, 152, 157, 158, 159, 182, 183, 184, 187, 188, 178,
616 130, 131, 132, 145, 155, 156, 185, 186, 176,
617 133, 134, 153, 180, 181, 189, 177, 173,
618 176, 173, 177, 178, 170,
619 140, 141, 142, 143, 144, 146, 148, 149, 154]
620 let max = isps.length - 1
621 let idx = xmock(`@int(0,${max})`)
622 let num = xmock(`@int(100000000,199999999)`)
623 return (isps[idx] * 100000000 + num % 100000000) + ''
624 },
625 // 转换为缺省中文内容提示
626 paragraph: (...args) => {
627 switch (args.length) {
628 case 0:
629 return xmock('@cparagraph')
630 case 1:
631 return xmock(`@cparagraph(${args[0]})`)
632 case 2:
633 return xmock(`@cparagraph(${args[0]},${args[1]})`)
634 default:
635 xassert(false)
636 }
637
638 },
639 sentence: (...args) => {
640 switch (args.length) {
641 case 0:
642 return xmock('@csentence')
643 case 1:
644 return xmock(`@csentence(${args[0]})`)
645 case 2:
646 return xmock(`@csentence(${args[0]},${args[1]})`)
647 default:
648 xassert(false)
649 }
650
651 },
652 title: (...args) => {
653 switch (args.length) {
654 case 0:
655 return xmock('@ctitle')
656 case 1:
657 return xmock(`@ctitle(${args[0]})`)
658 case 2:
659 return xmock(`@ctitle(${args[0]},${args[1]})`)
660 default:
661 xassert(false)
662 }
663
664 },
665})
666
667// laravel风格JSON对象验证器封装,详细文档见 https://github.com/skaterdav85/validatorjs
668function xcheck<T>(param: T, rules: T, messages?: Object) {
669 let obj = new validatorjs(param, rules)
670 if (obj.fails()) {
671 xthrow(ERR$PARAM, obj.errors)
672 }
673}
674
675// 【IoC容器管理】应用层的插件实现类绑定到BXJS统一注册的标准插件的映射关系在全局容器实例中注册
676function xbind<T>(TYPE: new () => T) {
677 const o: any = new TYPE()
678 return xcontainer.bind<T>(o.id).to(require(`@app/plugins/${o.id}`).default)
679}
680
681// 【IoC容器管理】框架或应用依赖标准规范接口插件的类实例获取方法
682function xgot<T>(TYPE: new () => T) {
683 const o: any = new TYPE()
684 return xcontainer.get<T>(o.id)
685}
686
687// 同步系统命令调用执行
688async function xcmd(...args: string[]): Promise<any> {
689 try {
690 const options: any = {}
691 options.cwd = options.cwd || process.env.__ctxPath || process.cwd();
692 xassert(_.isArray(args) && args.length > 0)
693 const cmd = args.shift()
694 const ret = cross_spawn.sync(cmd, args, xassign({stdio: 'inherit'}, options))
695 xassert(ret.status === 0, ERR$UNKNOWN, ret)
696 return ret
697 } catch (err) {
698 await xwarn(err)
699 xthrow(err)
700 }
701}
702
703// 对于数组嵌套回调函数的nodejs异步处理方法的统一封装
704async function xmap<T>(values: T[], callack: (v: T) => Promise<any>): Promise<any[]> {
705 xassert(_.isArray(values) && _.isFunction(callack))
706 return Promise.all(values.map(callack))
707}
708
709// Refer to document: https://help.aliyun.com/document_detail/62670.html
710// 获取ACM配置信息接口的统一封装
711async function xacm(group: string, id: string) {
712 const cfg = xconfig(`plugins.acm`)
713 xassert(group && cfg && cfg[group], ERR$CONFIG, {cfg})
714 const acm = new ACMClient(cfg[group])
715 return new Promise(async (resolve, reject) => {
716 try {
717 co(function* () {
718 try {
719 group = group + ':' + global['__env__'] // 补上环境后缀支持各种开发环境的个性化配置
720 const content = yield acm.getConfig(id, group)
721 xassert(content, ERR$CONFIG, {id, group})
722 resolve(content)
723 } catch (err) {
724 xthrow(err, reject, {id, group, cfg: cfg[group]})
725 }
726 });
727 } catch (err) {
728 xthrow(err, reject, {id, group, cfg: cfg[group]})
729 }
730 })
731}
732
733// 根据当前配置的时区正确获取当前时间值避免线上FC容器默认是格林尼治时间而非北京时间问题
734function xnow(format = 'YYYY-MM-DD HH:mm:ss') {
735 return moment().utcOffset(8).format(format)
736}
737
738// base64编码
739function xbase64encode(value: string) {
740 xassert(!_.isEmpty(value) && _.isString(value))
741 return new Buffer(value).toString('base64')
742}
743
744// base64解码
745function xbase64decode(value: string) {
746 xassert(!_.isEmpty(value) && _.isString(value))
747 return new Buffer(value, 'base64').toString()
748}
749
750// 请求上下文变量自动重置方法(以global变量中key为__下划线开始结束的属性自动清空为undefined,如需赋值其他缺省值需要在此函数中明确定义)
751function xreset() {
752 // 所有请求上下文属性的自动清空初始化处理(通过约定global的特殊属性__xxx__简化koa中的context设计机制,将这部分机制做到框架上对应用不可见)
753 for (let key of Object.keys(global)) {
754 if (key.startsWith('__') && key.endsWith('__')) {
755 global[key] = undefined
756 }
757 }
758 // 明确定义的一些全局变量的初始值赋值
759 if (!global['__env__']) {
760 global['__env__'] = 'local' // local,daily,pre,gray,prod 在统一入口处自动识别配置(目前暂不支持gray配置尚未开发无法自动识别)
761 }
762 global['__config__'] = undefined
763 global['__session__'] = {}
764 global['__cache__'] = {}
765 global['__user__'] = {}
766 global['__user_agent__'] = undefined
767 global['__client_ip__'] = undefined
768 global['__redirect_url__'] = undefined
769 global['__request_cookies__'] = {}
770 global['__respond_cookies__'] = {}
771}
772
773// 首次模块加载的时候执行一次,确保应用中不可以有__xxx__参数作为全局变量在模块初始化的时候
774xreset()
775
776global['xreset'] = xreset
777global['xconnect'] = xconnect
778global['xnew'] = xnew
779global['xquery'] = xquery
780global['xcount'] = xcount
781global['xassign'] = xassign
782global['xconfig'] = xconfig
783global['xthrow'] = xthrow
784global['xassert'] = xassert
785global['xerror'] = xerror
786global['xroot'] = xroot
787global['xstack'] = xstack
788global['xwarn'] = xwarn
789global['xlog'] = xlog
790global['xpost'] = xpost
791global['xget'] = xget
792global['xsleep'] = xsleep
793global['xredirect'] = xredirect
794global['xcookie'] = xcookie
795global['xismobile'] = xismobile
796global['xsession'] = xsession
797global['xuser'] = xuser
798global['xcache'] = xcache
799global['xroute'] = xroute
800global['xmock'] = xmock
801global['xrandom'] = xrandom
802global['xcheck'] = xcheck
803global['xcontainer'] = new Container() // 全局单实例容器初始化
804global['xbind'] = xbind
805global['xgot'] = xgot
806global['YAuth'] = $$.YAuth // 全局声明认证插件规范抽象类
807global['xcmd'] = xcmd
808global['xmap'] = xmap
809global['xacm'] = xacm
810global['xbase64encode'] = xbase64encode
811global['xbase64decode'] = xbase64decode
812global['xnow'] = xnow