common utils
/**
* 根据单个属性对数组进行自身排序,例如对数组[{a:3},{a:2}]根据属性a
* 进行升序排序:
* Array.psort('a', true) ==> [{a:2},{a:3}]
*
* @param {String} proName 属性字段名称
* @param {Boolean} asc 是否升序排序,默认为false
* return
*/
Array.prototype.psort = (proName, asc) => {
asc = asc || false;
this.sort(function(obj1, obj2) {
if (obj1[proName] > obj2[proName]) {
return asc ? 1 : -1;
} else if (obj1[proName] == obj2[proName]) {
return 0;
} else {
return asc ? -1 : 1;
}
});
};
/**
* 洗牌随机算法
*/
Array.prototype.shuffle = function() {
var input = this;
for (var i = input.length-1; i >=0; i--) {
var randomIndex = Math.floor(Math.random()*(i+1));
var itemAtIndex = input[randomIndex];
input[randomIndex] = input[i];
input[i] = itemAtIndex;
}
return input;
}
/**
* 根据多个属性对数组进行自身排序,例如对数组[{a:2, b:4},{a:3, b:3}]
* 根据属性a进行升序排序,然后根据属性b进行降序排序:
* Array.mpsort([{a:1}, {b:-1}])
*
* @param {Array} sorts 属性排序选项数组
* return
*/
Array.prototype.mpsort = (sorts) => {
if(!(sorts instanceof Array) || sorts.length < 1) {
return;
}
for(let i = 0; i < sorts.length; i++){
let proName, sortOrder;
let opt = sorts[i],
tp = typeof(opt);
if(tp === 'string'){
proName = opt;
sortOrder = 1;
}else if(tp === 'object'){
for(proName in opt){
sortOrder = opt[proName];
break;
}
}else{
continue;
}
this.psort(proName, sortOrder===1);
}
};
/**
* 递归把数组转换成树返回
*
* @param {Array} array 被转换的数组
* @param {Object} [parent = { id: '' }] 用于指示根节点的父节点结构
* @param {String} [sortKey = id] 用来排序的 key
*/
convertToTree(array, parent = { id: '' }, sortKey = 'id') {
let tree = [];
const children = array.filter(node => node.pId === parent.id).sort((a, b) => {
return a[sortKey] < b[sortKey] ? -1 : 1;
});
if (children.length) {
if (parent.id === '') {
tree = children;
} else {
parent.children = children;
}
children.forEach(child => this.convertToTree(array, child, sortKey));
}
return tree;
}
function hasChinese(str) {
return /[\u4E00-\u9FA5]/g.test(str); //测试中文字符的正则
}
// 数字字符串转为千位逗号分隔
function toComma(str) {
return str.replace(/(\d)(?=(?:\d\d\d)+(?!\d))/g, '$1,');
}
/**
* 生成指定位数的随即数字
* @param digit 位数
* @returns {number}
*/
function randomNumber(digit) {
return Math.ceil(Math.random() * Math.pow(10, digit - 1));
}
const emailReg = /^[-!#$%&'*+/0-9=?A-Z^_a-z{|}~](\.?[-!#$%&'*+/0-9=?A-Z^_a-z`{|}~])*@[a-zA-Z0-9](-*\.?[a-zA-Z0-9])*\.[a-zA-Z](-?[a-zA-Z0-9])+$/
// Thanks to:
// http://fightingforalostcause.net/misc/2006/compare-email-regex.php
// http://thedailywtf.com/Articles/Validating_Email_Addresses.aspx
// http://stackoverflow.com/questions/201323/what-is-the-best-regular-expression-for-validating-email-addresses/201378#201378
function emailValidate(email) {
if (!email) { return false }
if (email.length > 254) { return false }
var valid = emailReg.test(email)
if (!valid) { return false }
// Further checking of some things regex can't handle
var parts = email.split('@')
if (parts[0].length > 64) { return false }
var domainParts = parts[1].split('.')
if (domainParts.some(function(part) { return part.length > 63 })) { return false }
return true
}
/**
* 校验 固话 或 手机号 是否合法
* @param {String} value 待校验数据
*/
function phoneValiedate(value) {
if (!value) return false
if (Number.isNaN(Number(value))) return false
if (/^(([0+]\d{2,3}-)?(0\d{2,3})-)?(\d{7,8})(-(\d{3,}))?$/.test(value) || /^(1(([35][0-9])|(47)|[8][01236789]))\d{8}$/.test(value)) {
return true
}
return false
}
/**
* 生成指定位数的随即字符串,可以指定从哪个字符串中获取
* @param digit 位数
* @param origin 来源串,随即串中的字符从这里获取
* @returns {String}
*/
function randomString(digit, origin) {
const x = origin || '0123456789qwertyuioplkjhgfdsazxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZ';
let tmp = '';
for(let i = 0; i < digit; i++) {
tmp += x.charAt(Math.ceil(Math.random()*100000000) % x.length);
}
return tmp;
}
function randomStringWithCrypto(digit) {
const key = crypto.randomBytes(digit / 2);
return key.toString('hex');
}
/**
* 字符串首字母大写
*/
function firstUpper(str) {
if (!str || typeof str !== 'string') return str
return str[0].toUpperCase() + str.substr(1)
}
/**
* base64 字符串转换
*/
if (!Shotgun)
var Shotgun = {};
if (!Shotgun.Js)
Shotgun.Js = {};
Shotgun.Js.Base64 = {
_table: [
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
],
encode: function (bin) {
var codes = [];
var un = 0;
un = bin.length % 3;
if (un == 1)
bin.push(0, 0);
else if (un == 2)
bin.push(0);
for (var i = 2; i < bin.length; i += 3) {
var c = bin[i - 2] << 16;
c |= bin[i - 1] << 8;
c |= bin[i];
codes.push(this._table[c >> 18 & 0x3f]);
codes.push(this._table[c >> 12 & 0x3f]);
codes.push(this._table[c >> 6 & 0x3f]);
codes.push(this._table[c & 0x3f]);
}
if (un >= 1) {
codes[codes.length - 1] = "=";
bin.pop();
}
if (un == 1) {
codes[codes.length - 2] = "=";
bin.pop();
}
return codes.join("");
},
decode: function (base64Str) {
var i = 0;
var bin = [];
var x = 0, code = 0, eq = 0;
while (i < base64Str.length) {
var c = base64Str.charAt(i++);
var idx = this._table.indexOf(c);
if (idx == -1) {
switch (c) {
case '=': idx = 0; eq++; break;
case ' ':
case '\n':
case "\r":
case '\t':
continue;
default:
throw { "message": "\u0062\u0061\u0073\u0065\u0036\u0034\u002E\u0074\u0068\u0065\u002D\u0078\u002E\u0063\u006E\u0020\u0045\u0072\u0072\u006F\u0072\u003A\u65E0\u6548\u7F16\u7801\uFF1A" + c };
}
}
if (eq > 0 && idx != 0)
throw { "message": "\u0062\u0061\u0073\u0065\u0036\u0034\u002E\u0074\u0068\u0065\u002D\u0078\u002E\u0063\u006E\u0020\u0045\u0072\u0072\u006F\u0072\u003A\u7F16\u7801\u683C\u5F0F\u9519\u8BEF\uFF01" };
code = code << 6 | idx;
if (++x != 4)
continue;
bin.push(code >> 16);
bin.push(code >> 8 & 0xff);
bin.push(code & 0xff)
code = x = 0;
}
if (code != 0)
throw { "message": "\u0062\u0061\u0073\u0065\u0036\u0034\u002E\u0074\u0068\u0065\u002D\u0078\u002E\u0063\u006E\u0020\u0045\u0072\u0072\u006F\u0072\u003A\u7F16\u7801\u6570\u636E\u957F\u5EA6\u9519\u8BEF" };
if (eq == 1)
bin.pop();
else if (eq == 2) {
bin.pop();
bin.pop();
} else if (eq > 2)
throw { "message": "\u0062\u0061\u0073\u0065\u0036\u0034\u002E\u0074\u0068\u0065\u002D\u0078\u002E\u0063\u006E\u0020\u0045\u0072\u0072\u006F\u0072\u003A\u7F16\u7801\u683C\u5F0F\u9519\u8BEF\uFF01" };
return bin;
}
};
/**
* Calculates the MD5 hash of a string.
* @param str The input string.
* @returns The MD5 hash of the input string.
*/
export function md5(str: string): string {
return crypto.createHash('md5').update(str).digest('hex')
}
/**
* Calculates the SHA1 hash of a string.
* @param str The input string.
* @returns The SHA1 hash of the input string.
*/
export function sha1(str: string): string {
return crypto.createHash('sha1').update(str).digest('hex')
}
/**
* Calculates the SHA256 hash of a string.
* @param str The input string.
* @returns The SHA256 hash of the input string.
*/
export function sha256(str: string): string {
return crypto.createHash('sha256').update(str).digest('hex')
}
/**
* Safely parses a JSON string.
* @param str The JSON string to parse.
* @param defaultRet The default return value if parsing fails. Default is an empty object.
* @returns The parsed JSON object or the default return value if parsing fails.
*/
export function safeParse<T>(str: string, defaultRet = {}): T {
try {
return JSON.parse(str) as T
} catch (error) {
console.error(error)
return defaultRet as T
}
}
const defaultAlgorithm = 'aes-128-cbc'
/**
* Encrypts a string using AES encryption.
* @param text The input string to encrypt.
* @param key The encryption key.
* @param iv The initialization vector (optional).
* @param algorithm The encryption algorithm (optional). Default is 'aes-128-cbc'.
* @returns An object containing the IV and the encrypted data.
*/
export function encryptAES(text: string, key: string, iv?: string, algorithm = defaultAlgorithm) {
const cipher = crypto.createCipheriv(algorithm, key, iv)
let encrypted = cipher.update(text, 'utf8')
encrypted = Buffer.concat([encrypted, cipher.final()])
return { iv: iv, encryptedData: encrypted.toString('hex') }
}
/**
* Decrypts a string using AES decryption.
* @param text The input string to decrypt.
* @param key The decryption key.
* @param iv The initialization vector (optional).
* @param algorithm The encryption algorithm (optional). Default is 'aes-128-cbc'.
* @returns The decrypted string.
*/
export function decryptAES(text: string, key: string, iv?: string, algorithm = defaultAlgorithm) {
const encryptedText = Buffer.from(text, 'hex')
const decipher = crypto.createDecipheriv(algorithm, key, iv)
let decrypted = decipher.update(encryptedText)
decrypted = Buffer.concat([decrypted, decipher.final()])
return decrypted.toString('utf8')
}
/**
* 字符串替换
*/
function replaceAndExtract(template, values) {
const placeholders = [];
// 替换被 {} 包含的内容
const result = template.replace(/{([^}]+)}/g, (match, key) => {
placeholders.push(key.trim()); // 收集占位符
return values[key.trim()] !== undefined ? values[key.trim()] : match; // 替换为对应的值
});
return { result, placeholders };
}
// 示例
const template = '{}{buAlbumId} -@- {albumName}';
const values = {
buAlbumId: 123,
albumName: 'My Album'
};
const { result, placeholders } = replaceAndExtract(template, values);
console.log('替换后的字符串:', result); // 输出: "123 -@- My Album"
console.log('提取的占位符:', placeholders); // 输出: ["buAlbumId", "albumName"]
Date.prototype.format = function(format) {
var date = {
"M+": this.getMonth() + 1,
"d+": this.getDate(),
"h+": this.getHours(),
"m+": this.getMinutes(),
"s+": this.getSeconds(),
"q+": Math.floor((this.getMonth() + 3) / 3),
"S+": this.getMilliseconds()
};
if (/(y+)/i.test(format)) {
format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in date) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1
? date[k] : ("00" + date[k]).substr(("" + date[k]).length));
}
}
return format;
}
/**
* 格式化时间,用法和 moment.js 的 format 一样
*
* @param {Date|number} 需要格式化的值,可以是 Date 或者 timestamp
* @param {String} cFormat 目标格式字符串
* @return {String}
*/
function parseTime(time, cFormat) {
if (arguments.length === 0) {
return null
}
const format = cFormat || 'Y-M-D H:m:s'
let date
if (typeof time === 'object') {
date = time
} else {
if (('' + time).length === 10) time = parseInt(time) * 1000
date = new Date(time)
}
const formatObj = {
Y: date.getFullYear(),
M: date.getMonth() + 1,
D: date.getDate(),
H: date.getHours(),
m: date.getMinutes(),
s: date.getSeconds(),
S: date.getMilliseconds(),
a: date.getDay()
}
const timeStr = format.replace(/(Y|M|D|H|m|s|S|a)+/g, (result, key) => {
let value = formatObj[key]
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return timeStr
}
let Duplex = require('stream').Duplex;
function bufferToStream(buffer) {
let stream = new Duplex();
stream.push(buffer);
stream.push(null);
return stream;
}
function streamToBuffer(stream) {
return new Promise((resolve, reject) => {
let buffers = [];
stream.on('error', reject);
stream.on('data', (data) => buffers.push(data))
stream.on('end', () => resolve(Buffer.concat(buffers))
});
}
/**
* 数字转化为千分位逗号分隔
*/
function toThousands(num) {
return (num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
}
/**
* 数字转为二进制
*/
function int2bin(dec){
return (dec >>> 0).toString(2);
}
/**
* 二进制转换为十进制
*/
function bin2int(bin) {
return parseInt(bin, 2);
}