/* 基础小工具,可通用于服务端和用户端 */ module.exports = { BASEPORT_API_SERVER: 7000, BASEPORT_WEB_SERVER: 8000, BASEPORT_CHAIN_SERVER: 6000, BASEPORT_NET_NODE: 60000, PORT_WEB_SERVER_DEV: 8338, // 338 stands for dev sleep: (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms)), parse_json_anyway (value) { try { return JSON.parse(value) } catch (e) { return undefined } }, // 按顺序展开,哪怕嵌套。 stringify_by_keyorder (obj, { cmp, cycles = false, space = '', replacer, schemaColumns, excludeKeys = [] } = {}) { /* 这个解决方法不考虑缺省值,不能把嵌套对象也按顺序展开。*/ // return JSON.stringify(obj, Object.keys(schemaColumns || entity).sort().filter(key => ! excludeKeys.includes(key))) // JSON.stringify 可根据第二个数组参数的顺序排序,但这导致了嵌套对象不能按顺序展开。 let newObj = {} if (schemaColumns) { for (let key in schemaColumns) { // JSON.stringify (包括本函数)会把 NaN 或 Infinity 输出为 null,会把 undefined 忽略掉。 // 而在 typeorm sqlite 数据库中,undefined 会自动存为 schemaColumns[key].default 或 null。从数据库读出时,就会和事先JSON.stringify的结果不一致。 // 为了和 torm 数据库保持一致习惯,对schemaColumns里的键值应当把 undefined 也设为 default 或 null。 if (obj[key] === undefined || Number.isNaN(obj[key]) || obj[key] === Infinity) { newObj[key] = typeof schemaColumns[key].default === 'undefined' ? null : schemaColumns[key].default obj[key] = newObj[key] // 确保内存中的数据和数据库保持一致 } else { newObj[key] = obj[key] } } } else { newObj = obj } for (let exkey of excludeKeys) { delete newObj[exkey] } /* 以下代码来自 https://github.com/substack/json-stable-stringify 可把嵌套的复杂值也按顺序输出,例如 { c: 8, b: [{z:6,y:5,x:4},7], a: 3 } */ if (typeof space === 'number') space = Array(space + 1).join(' ') var cycles = typeof cycles === 'boolean' ? cycles : false var replacer = replacer || function (key, value) { return value } var cmp = cmp && (function (f) { return function (node) { return function (a, b) { var aobj = { key: a, value: node[a] } var bobj = { key: b, value: node[b] } return f(aobj, bobj) } } })(cmp) var seen = [] return (function stringify (parent, key, node, level) { var indent = space ? '\n' + new Array(level + 1).join(space) : '' var colonSeparator = space ? ': ' : ':' if (node && node.toJSON && typeof node.toJSON === 'function') { node = node.toJSON() } node = replacer.call(parent, key, node) if (node === undefined) { return } if (typeof node !== 'object' || node === null) { return JSON.stringify(node) } if (Array.isArray(node)) { var out = [] for (var i = 0; i < node.length; i++) { var item = stringify(node, i, node[i], level + 1) || JSON.stringify(null) out.push(indent + space + item) } return '[' + out.join(',') + indent + ']' } else { if (seen.indexOf(node) !== -1) { if (cycles) return JSON.stringify('__cycle__') throw new TypeError('Converting circular structure to JSON') } else seen.push(node) var keys = Object.keys(node).sort(cmp && cmp(node)) var out = [] for (var i = 0; i < keys.length; i++) { var key = keys[i] var value = stringify(node, key, node[key], level + 1) if (!value) continue var keyValue = JSON.stringify(key) + colonSeparator + value out.push(indent + space + keyValue) } seen.splice(seen.indexOf(node), 1) return '{' + out.join(',') + indent + '}' } })({ '': newObj }, '', newObj, 0) }, alpha_to_digit (name = '') { let port = name .toLowerCase() .replace(/[abc]/g, 2) .replace(/[def]/g, 3) .replace(/[ghi]/g, 4) .replace(/[jkl]/g, 5) .replace(/[mno]/g, 6) .replace(/[pqrs]/g, 7) .replace(/[tuv]/g, 8) .replace(/[wxyz]/g, 9) return parseInt(port) }, keyname_to_apiport (keyname) { return this.BASEPORT_API_SERVER + parseInt(this.alpha_to_digit(keyname)) }, keyname_to_chainport (keyname) { return this.BASEPORT_CHAIN_SERVER + parseInt(this.alpha_to_digit(keyname)) }, keyname_to_netport (keyname) { return this.BASEPORT_NET_NODE + parseInt(this.alpha_to_digit(keyname)) }, keyname_to_webport (keyname) { return this.BASEPORT_WEB_SERVER + parseInt(this.alpha_to_digit(keyname)) }, randomize_number ({ length, min, max } = {}) { // 长度为 length 的随机数字,或者 (min||0) <= num < max var num = 0 if (typeof length === 'number' && length > 0) { num = parseInt(Math.random() * Math.pow(10, length)) num = num.toString().padStart(length, '0') } else if (typeof max === 'number' && max > 0) { min = typeof min === 'number' && min >= 0 ? min : 0 num = parseInt(Math.random() * (max - min)) + min } else { // 如果 option 为空 num = Math.random() } return num }, hash_easy (data, { hasher = 'sha256', salt, input = 'utf8', output = 'hex' } = {}) { if (typeof data !== 'string' && !(data instanceof Buffer) && !(data instanceof DataView)) data = JSON.stringify(data) if (salt && typeof salt === 'string') data = data + salt const inputEncoding = input // my.INPUT_LIST.indexOf(option.input)>=0?option.input:my.INPUT // 'utf8', 'ascii' or 'latin1' for string data, default to utf8 if not specified; ignored for Buffer, TypedArray, or DataView. const outputEncoding = output === 'buf' ? undefined : output // (my.OUTPUT_LIST.indexOf(output)>=0?output:my.OUTPUT) // option.output: 留空=》默认输出hex格式;或者手动指定 'buf', hex', 'latin1' or 'base64' return require('crypto').createHash(hasher).update(data, inputEncoding).digest(outputEncoding) }, /** * 用户编号转邀请码 * * @static * @param {*} aiid * @return {*} * @memberof TICrypto */ aiid_to_haid (aiid) { const alphabet = 'e5fcdg3hqa4b1n0pij2rstuv67mwx89klyz' const base = 16367 let num = (aiid + base) * (base - alphabet.length) let code = '' let mod while (num > 0) { mod = num % alphabet.length num = (num - mod) / alphabet.length code = code + alphabet[mod] // 倒序存放 } return code }, /** * 邀请码转用户编号 * * @static * @param {*} code * @return {*} * @memberof TICrypto */ haid_to_aiid (haid) { if (typeof haid === 'string' && /^[a-zA-Z0-9]+$/.test(haid)) { const alphabet = 'e5fcdg3hqa4b1n0pij2rstuv67mwx89klyz' const base = 16367 haid = haid.toLowerCase() let len = haid.length let num = 0 for (let i = 0; i < len; i++) { num += alphabet.indexOf(haid[i]) * Math.pow(alphabet.length, i) } let aiid = num / (base - alphabet.length) - base if (aiid >= 0 && Number.isInteger(aiid)) { // 允许 aiid===0:当第一个用户(aiid==1)登录时,需要一个系统默认的邀请码。 return aiid } } return null // null 代表一切非法的 haid }, isEmpty (value) { switch (typeof value) { case 'number': if (value === 0 || value !== value) return true return false case 'object': for (var attr in value) { return false } /* if (JSON.stringify(value)==='{}'){ return true; } if (Object.keys(value).length===0){ // Object.keys(null) 会出错。 return true; } */ return true case 'string': return value === '' ? true : false case 'undefined': return true case 'boolean': return value } return true }, get_jstype (o) { // 返回:一个字符串,表示标量类型 undefined,boolean,number,string 以及对象类型 Null, Object, Array, String, Boolean, Number, Function var t = typeof o return t === 'object' || t === 'function' // function是特殊的,typeof 结果是function, 但 Object.prototype.toString.call 结果是 [object Function]。我选用大写形式。 ? Object.prototype.toString.call(o).slice(8, -1) // 可以是 Null, Object, Function, Boolean, String, Number, Array (如果 o===undefined, 那就是Undefined), 还可能是 Date, Math, Uint8Array(如果是个Buffer) : t // 可以是 undefined, boolean, number, string }, read_varchain (path, root, emptyValue) { let parent = root || globalThis || global || window || {} let keychain = path.split('.') for (let key of keychain) { if (typeof parent === 'object' && /^\w+\(.*\)$/.test(key)) { // 支持 myfunc(param) 作为一个路径节点。 let [all, func, param] = key.match(/^(\w+)\((.*)\)$/) parent = parent[func](param) } else if (typeof parent === 'object' && /^\w+\[.*\]$/.test(key)) { // 支持 myarr[index] 作为一个路径节点 let [all, arr, index] = key.match(/^(\w+)\[(.*)\]$/) parent = parent[arr][parseInt(index)] } else if (typeof parent === 'object' && /^\w+$/.test(key) && typeof parent[key] != 'undefined') { parent = parent[key] } else { return emptyValue } } return typeof parent !== 'undefined' ? parent : emptyValue }, set_varchain (path, root, value) { var parent = root || global || window || {} var keychain = path.split('.') for (let i = 0; i < keychain.length - 1; i++) { if (typeof parent === 'object' && keychain[i].match(/^\w+$/)) { if (typeof parent[keychain[i]] !== 'object') parent[keychain[i]] = {} parent = parent[keychain[i]] } else { return null } } return (parent[keychain[keychain.length - 1]] = value) }, /** * 对数组中的对象,按对象的key进行sortType排序 * @param key 数组中的对象为object,按object中的key进行排序 * @param sortType true为降序;false为升序 * 用法: * var ary=[{id:1,name:"b"},{id:2,name:"b"}]; * ary.sort(keysort('name',true)); * ary.sort(keysort('name',false)); * ary.sort(keysort('id',false)); */ keysort (key, sortType) { return function (a, b) { return sortType ? ~~(a[key] < b[key]) : ~~(a[key] > b[key]) } }, number_precision (number, precision = 4) { if (isFinite(number)) // null, '', '0', [] return Number(Number(number).toFixed(precision)) else // undefined, NaN, Infinity, {} return 0 }, }