UNPKG

30.9 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()
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 output = output.replace(/\r/g, '').replace(/\n/g, '')
254 console.log.apply(undefined, [source + output])
255 }
256}
257
258// // 将详细错误信息及时发送到钉钉群上实时反馈给维护者
259// await xwarn({
260// code,
261// // TODO 如何认证通过了获取到用户信息也需要发送过去,方便联系对接人员进行立刻问题处理反馈。
262// message,
263// stack,
264// param,
265// })
266// 将详细错误信息及时发送到钉钉群上实时反馈给维护者
267// 钉钉IM群机器人报警通知
268async function xwarn(...args) {
269 // 得到xwarn方法被调用的位置
270 let source = __get_base_func_caller_source_position()
271
272 // 对于异常参数警告信息进行错误内容标准解析
273 if (args.length > 0 && args[0] instanceof Error) {
274 args[0] = xerror(args[0])
275 }
276
277 let out = [source, moment().format('YYYY-MM-DD HH:mm:ss'), {...args}]
278
279 // 从配置信息中读取报警通知人手机列表和对应的群机器人的webhook的access_token信息
280 let access_token = xconfig('framework.warn.dingding.access_token')
281 let mobiles = xconfig('framework.warn.dingding.mobiles')
282 if (!access_token || !mobiles) {
283 access_token = '020a09eac5f2fa320ae851442d5e19e23693c64ad2255c85354b4a49a5a48d35'
284 mobiles = ['15381151346']
285 }
286
287 await xpost(`https://oapi.dingtalk.com/robot/send?access_token=${access_token}`, {
288 msgtype: 'text',
289 text: {
290 content: out
291 },
292 at: {
293 atMobiles: mobiles,
294 isAtAll: false
295 }
296 })
297
298 // 线上SLS日志上也保存一份
299 // console.warn(out)
300 xlog(out)
301}
302
303// 捕获未监听到的异常记录后直接退出(运行堆栈已经破坏直接记录日志后异常退出即可,由外部监控自动重启)
304process.on('uncaughtException', async function (err) {
305 xlog(xerror(err))
306 await xwarn(err)
307 process.exit(-1)
308})
309
310// 记录await/async中出现未捕获的异常错误
311process.on('unhandledRejection', async (reason, p) => {
312 xlog('Unhandled Rejection at: Promise', p, 'reason:', reason);
313 // application specific logging, throwing an error, or other logic here
314 await xwarn(reason, p)
315 process.exit(-1)
316})
317
318// async/await的非阻塞异步延迟方法,用于调试阻塞程序的执行进行单步调试的效果。
319const sleep = require('sleep-async')()
320
321export function xsleep(ms: number = -1) {
322 if (ms <= 0) {
323 ms = 50 * 365 * 24 * 3600 * 1000 // 50年最大数视为永久阻塞方便断点单步调试问题
324 }
325 return new Promise((resolve, reject) => {
326 try {
327 sleep.sleep(ms, () => {
328 resolve()
329 })
330 } catch (err) {
331 xlog(xerror(err))
332 resolve()
333 // xthrow(err,reject)
334 }
335 })
336}
337
338export async function xpost(url: string, param?: { [propName: string]: any },
339 headers?: { [propName: string]: any }, timeout: number = 3000) {
340 // TODO 线上测试不稳定超时暂时忽略掉通过进程最大运行时间去控制超时失败
341 timeout = 5000 // -1 不行线上会被阻塞住僵死
342 let res: any = null
343 let json: any = null
344 let text: any = null
345 try {
346 res = await fetch(url, {
347 method: 'POST',
348 body: JSON.stringify(param),
349 headers: {'Content-Type': 'application/json', ...headers},
350 timeout: timeout <= 0 ? 0 : timeout, // 默认3秒超时接口返回避免僵死
351 })
352 text = await res.text() // 解析出完整的返回内容避免HTML以及非法格式信息便于正确报错定位后端接口错误
353 json = JSON.parse(text)
354 return json
355 } catch (err) {
356 xthrow(err, {url, param, headers, text})
357 }
358}
359
360// 默认超时3000毫秒
361export async function xget(url: string, param?: { [propName: string]: any },
362 headers?: { [propName: string]: any }, timeout: number = 3000) {
363 // TODO 线上测试不稳定超时暂时忽略掉通过进程最大运行时间去控制超时失败
364 timeout = 5000 // -1 不行线上会被阻塞住僵死
365 let res: any = null
366 let json: any = null
367 let text: any = null
368 try {
369 url = url + (param ? '?' : '') + querystring.stringify(param)
370 res = await fetch(url, {
371 method: 'GET',
372 headers: {'Content-Type': 'application/json', ...headers},
373 timeout: timeout <= 0 ? 0 : timeout, // 默认3秒超时接口返回避免僵死
374 })
375 text = await res.text() // 解析出完整的返回内容避免HTML以及非法格式信息便于正确报错定位后端接口错误
376 json = JSON.parse(text)
377 return json
378 } catch (err) {
379 xthrow(err, {url, param, headers, text})
380 }
381}
382
383// 302临时重定向跳转实现
384export function xredirect(url: string, param: any = {}) {
385 // TODO 多个程序实例并发处理的时候存在时序问题不能保证全局变量被准确清空。
386 // 检查应用重复设置重定向地址未及时return返回控制器问题
387 xassert(global['__redirect_url__'] === undefined)
388 if (param) {
389 xassert(_.isPlainObject(param))
390 // 删除param中两个框架预定义参数__url__和__api__不允许进行参数传递(禁止业务逻辑使用避免框架后续升级以及与短网址功能冲突)
391 delete param.__api__
392 delete param.__url__
393 // 补额外的附加参数
394 if (/\?/.test(url)) {
395 url += '&'
396 } else {
397 url += '?'
398 }
399 url += querystring.stringify(param)
400 }
401 global['__redirect_url__'] = url
402}
403
404// 如果只有key参数表示读取属性(缺省值为undefined),如果key为空表示读取所有的请求cookies属性,否则表示响应设置cookies
405export function xcookie(key?: string, value?: string, option?: {}): any {
406 if (!arguments.length) {
407 // 读取所有的请求cookies属性object
408 return global['__request_cookies__'] ? global['__request_cookies__'] : {}
409 } else if (arguments.length == 1) {
410 return key ? xcookie()[key] : undefined
411 } else {
412 if (global['__respond_cookies__'] === undefined) {
413 global['__respond_cookies__'] = {}
414 }
415 if (key) {
416 // COOKIES缺省属性设置(有效时间24小时并且统一关联到根页面上获取COOKIES值)
417 option = xassign({path: '/', maxAge: 24 * 3600}, option)
418 global['__respond_cookies__'][key] = cookie.serialize(key, value, option)
419 }
420 return
421 }
422}
423
424// 判断user-agent请求是否为移动端
425function xismobile(): boolean {
426 const md = new MobileDetect(global['__user_agent__'])
427 return !!md.mobile()
428}
429
430function xassign(target, source, ...args) {
431 const param = [true, target, source, ...args]
432 return extend.apply(null, param)
433}
434
435// 查询app/config目录下的应用配置数据
436function xconfig(path: string, defaultValue: any = undefined) {
437 if (global['__config__']) {
438 return _.get(global['__config__'], path, defaultValue)
439 }
440
441 const fp = require('path')
442 const fs = require('fs')
443 // 自动获取app/config的相对路径目录位置得到根路径的位置
444 let config_path = ''
445 if (__dirname.includes('/node_modules/@bxjs/base/')) {
446 // 在应用目录下
447 config_path = fp.join(__dirname, '../../../../app/config')
448 } else {
449 // 在axjs库开发目录下
450 config_path = fp.join(__dirname, '../app/config')
451 }
452
453 // 自动识别判断运行环境global['__env__']并且加载对应的base数据和env数据
454 const config_base_path = config_path + '/config.base.' + get_suffix_ts_or_js()
455 const config_env_path = config_path + `/config.${global['__env__']}.` + get_suffix_ts_or_js()
456 if (!fs.existsSync(config_base_path)) {
457 return defaultValue
458 }
459 let config_base = require(config_base_path).default
460 let config_env = {}
461 if (fs.existsSync(config_env_path)) {
462 config_env = require(config_env_path).default
463 }
464 // bugfix Object.assign不支持深度拷贝问题
465 // global['__config__'] = Object.assign({}, config_base, config_env)
466 // global['__config__'] = _.assign({}, config_env, config_base)
467 global['__config__'] = xassign({}, config_base, config_env)
468 return _.get(global['__config__'], path, defaultValue)
469}
470
471async function xconnect(callback: (connect: Connection) => Promise<any>, config = 'default') {
472 return new Promise(async (resolve, reject) => {
473 let cfg = {} as any
474 try {
475 cfg = xassign({}, xconfig('plugins.database.default', {}))
476 xassert(!_.isEmpty(cfg), ERR$PARAM, {config})
477 // 强制补上约定的实体存放路径定义位置(不允许配置是约定规范)
478 if (__dirname.includes('/node_modules/@bxjs/base/')) {
479 // 在应用目录下
480 cfg['entities'] = [
481 path.join(__dirname, '../../../../app/plugins/database/entity/*.' + get_suffix_ts_or_js())
482 ]
483 } else {
484 // 在axjs库开发目录下
485 cfg['entities'] = [
486 path.join(__dirname, '../app/plugins/database/entity/*.' + get_suffix_ts_or_js())
487 ]
488 }
489 // 获取连接池中的链接
490 const mng = getConnectionManager()
491 const name = cfg.name ? cfg.name : 'default'
492 if (!mng.has(name)) {
493 mng.create(cfg)
494 }
495 const db = mng.get(name)
496 if (global['__connection__'] === undefined) {
497 global['__connection__'] = {}
498 }
499 if (!db.isConnected) { // TODO 需要进行连接池的管理
500 global['__connection__'][name] = db.connect()
501 }
502 await global['__connection__'][name].then(async connection => {
503 xassert(db.isConnected)
504 const out = await callback(connection)
505 // await db.close() // typeorm没有进行连接池的管理不能进行销毁
506 resolve(out)
507 }).catch(async err => {
508 // await db.close()
509 xthrow(err, reject, {cfg})
510 })
511 } catch (err) {
512 xthrow(err, reject, {cfg})
513 }
514 })
515}
516
517// 创建XBaseEntity对象并且自动赋值前端请求的赋值数据
518function xnew<T extends BaseEntity>(TYPE: new () => T, param?: any, ...args): T {
519 // 泛型实现类似这个功能
520 // asset = new AlilangAsset()
521 // getRepository(AlilangAsset).merge(asset, param as any)
522 // AlilangAsset.merge(asset, param as any)
523 // return asset
524 let obj = new TYPE()
525 if (_.isEmpty(param)) {
526 return obj
527 }
528 let repo = getRepository<T>(TYPE)
529 repo.merge.apply(repo, [obj, param, ...args])
530 return obj
531}
532
533// 查询构造器易用性封装
534function xquery<T>(connect: Connection, TYPE: new () => T, alias?: string): SelectQueryBuilder<T> {
535 return connect.getRepository(TYPE).createQueryBuilder(alias)
536}
537
538// 分页查询获取总数以及原始记录数据
539async function xcount<T>(sql: SelectQueryBuilder<T>, page: number, size: number): Promise<[any[] | null, number]> {
540 xassert(page >= 1)
541 const [count, rows] = await Promise.all([
542 sql.getCount(),
543 sql.offset((page - 1) * size).limit(size).getRawMany()
544 ])
545 return [rows, count]
546}
547
548
549// 路由参数的修饰符配置
550// TODO 更多接口相关参数的配置扩展,例如:是否支持JSONP
551function xroute(param: { name?: string, desc?: string, path?: string, auth?: boolean }) {
552 // 缺省值处理
553 param = xassign({name: '', desc: '', path: '', auth: true}, param)
554 return function (target: Function, propertyKey: string, descriptor: PropertyDescriptor) {
555 // TODO 注入到类实例定义中进行全局引用动态类的特性添加(trait功能的动态实现)
556 // 动态绑定路由类实例的上下文属性
557 target.prototype.context = () => {
558 return {
559 param: param, // 保存当前控制器用户定义参数信息
560 // 是否登录的鉴权方法统一框架层面上的处理实现,此处仅仅是通用接口的约束的定义。
561 auth: async () => {
562 // 调用登录功能的前端接口实现,取到对应的实现方法。
563 if (param && param.auth) {
564 // 需要鉴权进行会话有效性进行合法性校验处理!!
565 // 未认证错误抛出处理,前端单页应用接口报错逻辑处理正确错误提示跳转。
566 const auth = xgot(YAuth)
567 xassert(await auth.getLoginStatus(), ERR$UNAUTHORIZED)
568 }
569 }
570 }
571 }
572 }
573}
574
575// 完全没有必要的多余定义,需要通过MOCK定义进行细节数据类型的显性定义处理逻辑验证。
576// // 基本数据类型的规范扩展定义,方便API接口的定义以及形参自动验证合法性,并且与数据库数据类型保持一致。
577// type INT = number // 有符号整数
578// type UINT = number // 无符号整数
579// type DECIMAL = number // 精确小数
580// type FLOAT = number // 单精度浮点数(不精确小数)
581// type DOUBLE = number// 双精度浮点数(不精确小数)
582// type BOOL = boolean
583// type STR = string
584// type DATE = string // 年月日 '2017-06-25'
585// type TIME = string // 时分秒 '00:00:00'
586// type DATETIME = string // 年月日时分秒 '2017-06-25 00:00:00'
587
588// 模拟数据模板定义使用教程 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
589function xmock<T>(rules: T): any {
590 return mockjs.mock(rules)
591}
592
593function xrandom(name: string, data: any[]) {
594 mockjs.Random.extend({
595 [name]: (...args) => {
596 xassert(data.length > 0)
597 if (data.length == 1) return data[0]
598 let max = data.length - 1
599 let idx = xmock(`@int(0,${max})`)
600 return data[idx]
601 }
602 })
603}
604
605// 扩展一些预定义bxjs的基础随机方法或者覆盖一些mockjs中的方法
606mockjs.Random.extend({
607 // bxjs表定义的主键统一定义(约定系统中为字符串7-14字节长度算法)
608 id: (...args) => {
609 return shortid.generate()
610 },
611 // 中国手机号随机生成算法(约定系统中的手机号为字符串数据类型)
612 mobile: (...args) => {
613 const isps = [
614 134, 135, 136, 137, 138, 139, 147, 150, 151, 152, 157, 158, 159, 182, 183, 184, 187, 188, 178,
615 130, 131, 132, 145, 155, 156, 185, 186, 176,
616 133, 134, 153, 180, 181, 189, 177, 173,
617 176, 173, 177, 178, 170,
618 140, 141, 142, 143, 144, 146, 148, 149, 154]
619 let max = isps.length - 1
620 let idx = xmock(`@int(0,${max})`)
621 let num = xmock(`@int(100000000,199999999)`)
622 return (isps[idx] * 100000000 + num % 100000000) + ''
623 },
624 // 转换为缺省中文内容提示
625 paragraph: (...args) => {
626 switch (args.length) {
627 case 0:
628 return xmock('@cparagraph')
629 case 1:
630 return xmock(`@cparagraph(${args[0]})`)
631 case 2:
632 return xmock(`@cparagraph(${args[0]},${args[1]})`)
633 default:
634 xassert(false)
635 }
636
637 },
638 sentence: (...args) => {
639 switch (args.length) {
640 case 0:
641 return xmock('@csentence')
642 case 1:
643 return xmock(`@csentence(${args[0]})`)
644 case 2:
645 return xmock(`@csentence(${args[0]},${args[1]})`)
646 default:
647 xassert(false)
648 }
649
650 },
651 title: (...args) => {
652 switch (args.length) {
653 case 0:
654 return xmock('@ctitle')
655 case 1:
656 return xmock(`@ctitle(${args[0]})`)
657 case 2:
658 return xmock(`@ctitle(${args[0]},${args[1]})`)
659 default:
660 xassert(false)
661 }
662
663 },
664})
665
666// laravel风格JSON对象验证器封装,详细文档见 https://github.com/skaterdav85/validatorjs
667function xcheck<T>(param: T, rules: T, messages?: Object) {
668 let obj = new validatorjs(param, rules)
669 if (obj.fails()) {
670 xthrow(ERR$PARAM, obj.errors)
671 }
672}
673
674// 【IoC容器管理】应用层的插件实现类绑定到BXJS统一注册的标准插件的映射关系在全局容器实例中注册
675function xbind<T>(TYPE: new () => T) {
676 const o: any = new TYPE()
677 return xcontainer.bind<T>(o.id).to(require(`@app/plugins/${o.id}`).default)
678}
679
680// 【IoC容器管理】框架或应用依赖标准规范接口插件的类实例获取方法
681function xgot<T>(TYPE: new () => T) {
682 const o: any = new TYPE()
683 return xcontainer.get<T>(o.id)
684}
685
686// 同步系统命令调用执行
687async function xcmd(...args: string[]): Promise<any> {
688 try {
689 const options: any = {}
690 options.cwd = options.cwd || process.env.__ctxPath || process.cwd();
691 xassert(_.isArray(args) && args.length > 0)
692 const cmd = args.shift()
693 const ret = cross_spawn.sync(cmd, args, xassign({stdio: 'inherit'}, options))
694 xassert(ret.status === 0, ERR$UNKNOWN, ret)
695 return ret
696 } catch (err) {
697 await xwarn(err)
698 xthrow(err)
699 }
700}
701
702// 对于数组嵌套回调函数的nodejs异步处理方法的统一封装
703async function xmap<T>(values: T[], callack: (v: T) => Promise<any>): Promise<any[]> {
704 xassert(_.isArray(values) && _.isFunction(callack))
705 return Promise.all(values.map(callack))
706}
707
708// Refer to document: https://help.aliyun.com/document_detail/62670.html
709// 获取ACM配置信息接口的统一封装
710async function xacm(group: string, id: string) {
711 const cfg = xconfig(`plugins.acm`)
712 xassert(cfg && cfg[group], ERR$CONFIG, {cfg})
713 const acm = new ACMClient(cfg[group])
714 return new Promise(async (resolve, reject) => {
715 try {
716 co(function* () {
717 try {
718 const content = yield acm.getConfig(id, group)
719 xassert(content, ERR$CONFIG)
720 resolve(content)
721 } catch (err) {
722 xthrow(err, reject, {id, group, cfg: cfg[group]})
723 }
724 });
725 } catch (err) {
726 xthrow(err, reject, {id, group, cfg: cfg[group]})
727 }
728 })
729}
730
731if (!global['__env__']) {
732 global['__env__'] = 'local' // local,daily,pre,gray,prod 在统一入口处自动识别配置(目前暂不支持gray配置尚未开发无法自动识别)
733}
734global['__config__'] = undefined
735global['__session__'] = {}
736global['__cache__'] = {}
737global['__user__'] = {}
738global['__user_agent__'] = undefined
739global['__client_ip__'] = undefined
740global['__redirect_url__'] = undefined
741global['__request_cookies__'] = {}
742global['__respond_cookies__'] = {}
743
744global['xconnect'] = xconnect
745global['xnew'] = xnew
746global['xquery'] = xquery
747global['xcount'] = xcount
748global['xassign'] = xassign
749global['xconfig'] = xconfig
750global['xthrow'] = xthrow
751global['xassert'] = xassert
752global['xerror'] = xerror
753global['xroot'] = xroot
754global['xstack'] = xstack
755global['xwarn'] = xwarn
756global['xlog'] = xlog
757global['xpost'] = xpost
758global['xget'] = xget
759global['xsleep'] = xsleep
760global['xredirect'] = xredirect
761global['xcookie'] = xcookie
762global['xismobile'] = xismobile
763global['xsession'] = xsession
764global['xuser'] = xuser
765global['xcache'] = xcache
766global['xroute'] = xroute
767global['xmock'] = xmock
768global['xrandom'] = xrandom
769global['xcheck'] = xcheck
770global['xcontainer'] = new Container() // 全局单实例容器初始化
771global['xbind'] = xbind
772global['xgot'] = xgot
773global['YAuth'] = $$.YAuth // 全局声明认证插件规范抽象类
774global['xcmd'] = xcmd
775global['xmap'] = xmap
776global['xacm'] = xacm