426 lines
15 KiB
JavaScript
426 lines
15 KiB
JavaScript
/* 基础小工具,可通用于服务端和用户端
|
||
*/
|
||
|
||
const path = require('path')
|
||
const fs = require('fs')
|
||
|
||
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)),
|
||
|
||
deepFreeze (obj) {
|
||
if (typeof obj !== 'object' || obj === null) {
|
||
return obj
|
||
}
|
||
Object.freeze(obj)
|
||
const propNames = Object.getOwnPropertyNames(obj)
|
||
propNames.forEach(prop => {
|
||
const propValue = obj[prop]
|
||
// for nested object
|
||
if (
|
||
propValue && typeof propValue === 'object' &&
|
||
!Object.isFrozen(propValue)
|
||
) {
|
||
deepFreeze(propValue)
|
||
}
|
||
// for nested array
|
||
if (Array.isArray(propValue)) {
|
||
propValue.forEach(item => {
|
||
if (
|
||
item && typeof item === 'object' &&
|
||
!Object.isFrozen(item)
|
||
) {
|
||
deepFreeze(item)
|
||
}
|
||
});
|
||
Object.freeze(propValue) // Freeze the array itself
|
||
}
|
||
})
|
||
return obj
|
||
},
|
||
|
||
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)
|
||
},
|
||
|
||
number_precision (number, precision = 4) {
|
||
if (isFinite(number)) // null, '', '0', []
|
||
return Number(Number(number).toFixed(precision))
|
||
else // undefined, NaN, Infinity, {}
|
||
return 0
|
||
},
|
||
|
||
// 返回新的数组
|
||
filter_story (story) {
|
||
if (Array.isArray(story)) {
|
||
return story.filter(section => Object.values(section || {}).some(val => !this.is_empty(val))) // (section.text || section.image || section.video)?.trim?.()
|
||
} else {
|
||
return []
|
||
}
|
||
},
|
||
|
||
is_empty (data) { // empty: undefined, null, false, 0, NaN, '', '空格,tab, \n等', [], {}
|
||
if (data) {
|
||
if (typeof (data) === 'string' && data.trim() === '') {
|
||
return true
|
||
}
|
||
if (Array.isArray(data) && data.length === 0) {
|
||
return true
|
||
}
|
||
if (typeof (data) === 'object' && Object.keys(data).length === 0) {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
return true
|
||
},
|
||
|
||
/**
|
||
* 对数组中的对象,按对象的key进行sortType排序
|
||
*/
|
||
keysort_array (arr, key, order = 'ASC') {
|
||
if (Array.isArray(arr) && typeof (key) === 'string' && key) {
|
||
return arr.sort((a, b) => { // 负数: a, b. 正数: b, a
|
||
if (typeof (a[key]) === 'number' && typeof (b[key]) === 'number') {
|
||
return (order === 'DESC') ? b[key] - a[key] : a[key] - b[key]
|
||
} else if (typeof (a[key]) === 'string' && typeof (b[key]) === 'string') {
|
||
return (order === 'DESC') ? b[key].localeCompare(a[key]) : a[key].localeCompare(b[key])
|
||
} else {
|
||
return 0
|
||
}
|
||
})
|
||
} else {
|
||
return arr
|
||
}
|
||
},
|
||
|
||
objectize_array (arr, key) { // 是 Object.keys/values/entries 的反向操作,相当于 Array.objectize,把数据转成对象
|
||
return arr.reduce((obj, item) => ({ ...obj, [item[key]]: item }), {})
|
||
},
|
||
|
||
simplify_file (file = {}) {
|
||
delete file.baseUrl
|
||
delete file.fileUrl
|
||
delete file.ipfsUrl
|
||
delete file.fieldname
|
||
delete file.filename
|
||
delete file.destination
|
||
return file
|
||
},
|
||
|
||
has_module (module) {
|
||
if (typeof (module) === 'string' && module) {
|
||
return module.paths.some(modulesPath => fs.existsSync(path.join(modulesPath, module)))
|
||
} else {
|
||
return false
|
||
}
|
||
},
|
||
|
||
segment_number (sizeBytes = '') { // segment a number with a space between each 3 digits
|
||
let segmented = (sizeBytes).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ')
|
||
const parts = segmented.split(' ')
|
||
parts[parts.length - 3] += 'm'
|
||
parts[parts.length - 2] += 'k'
|
||
return parts.join(' ') + 'b'
|
||
},
|
||
|
||
get_file_ext (filename = '') {
|
||
const parts = filename.split('.')
|
||
// 注意,'abc'.split('.') === ['abc']
|
||
let ext = ''
|
||
if (parts.length >= 2) {
|
||
ext = parts.pop().toLowerCase()
|
||
}
|
||
return ext
|
||
|
||
// 如果用 path.extname:
|
||
// if (/^\./.test(filename)) filename = `added$filename` // path.extname('.abc') 结果为 '',所以要添加前缀
|
||
// return path.extname(filename).toLowerCase() // openAi*Ext 是包含 . 的,所以用 path.extname
|
||
|
||
},
|
||
|
||
delete_undefined (obj, { depth } = {}) {
|
||
// delete all undefined properties recursively inside an obect
|
||
Object.keys(obj).forEach(key => {
|
||
if (typeof obj[key] === 'undefined') {
|
||
delete obj[key]
|
||
} else if (typeof obj[key] === 'object') {
|
||
this.delete_undefined(obj[key])
|
||
}
|
||
})
|
||
},
|
||
|
||
}
|