wo-core-toolkit/coretool.js

426 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 基础小工具,可通用于服务端和用户端
*/
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])
}
})
},
}