Yuliang-Lee
11/29/2017 - 4:03 AM

utils

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);
 }