import logger from './logger'; // -----------------检测类型工具函数----------------- /** * 检测input类型是否为Map * @param {*} input 任意类型的输入 * @returns {Boolean} true->map / false->not a map */ export const isMap = function (input) { return getType(input) === 'map'; }; /** * 检测input类型是否为Set * @param {*} input 任意类型的输入 * @returns {Boolean} true->set / false->not a set */ export const isSet = function (input) { return getType(input) === 'set'; }; /** * 检测input类型是否为File * @param {*} input 任意类型的输入 * @returns {Boolean} true->file / false->not a file */ export const isFile = function (input) { return getType(input) === 'file'; }; /** * 检测input类型是否为number * @param {*} input 任意类型的输入 * @returns {Boolean} true->number / false->not a number */ export const isNumber = function (input) { return input !== null && (typeof input === 'number' && !isNaN(input - 0) || typeof input === 'object' && input.constructor === Number); }; /** * 检测input类型是否为string * @param {*} input 任意类型的输入 * @returns {Boolean} true->string / false->not a string */ export const isString = function (input) { return typeof input === 'string'; }; export const isObject = function (input) { // null is object, hence the extra check return input !== null && typeof input === 'object'; }; /** * Checks to see if a value is an object and only an object * plain object: 没有标准定义,一般认为通过 {} 或者 new Object() 或者 Object.create(null) 方式创建的对象是纯粹对象 * @param {*} input 任意类型的输入 * @returns {Boolean} true->an object and only an object */ export const isPlainObject = function (input) { // 注意不能使用以下方式判断,因为IE9/IE10下,对象的__proto__是undefined // return isObject(input) && input.__proto__ === Object.prototype; if (typeof input !== 'object' || input === null) { return false; } const proto = Object.getPrototypeOf(input); if (proto === null) { // edge case Object.create(null) return true; } let baseProto = proto; while (Object.getPrototypeOf(baseProto) !== null) { baseProto = Object.getPrototypeOf(baseProto); } // 2. 原型链第一个和最后一个比较 return proto === baseProto; }; /** * 检测input类型是否为数组 * @param {*} input 任意类型的输入 * @returns {Boolean} true->array / false->not an array */ export const isArray = function (input) { if (typeof Array.isArray === 'function') { return Array.isArray(input); } return getType(input) === 'array'; }; /** * Checks to see if a value is undefined * @param {*} input 任意类型的输入 * @returns {Boolean} true->input value is undefined */ export const isUndefined = function (input) { return typeof input === 'undefined'; }; const isNativeClassRegex = /^class\s/; /** * Is ES6+ class * @param {*} input 任意类型的输入 * @returns {Boolean} true->input is ES6+ class */ export const isNativeClass = function (input) { return typeof input === 'function' && isNativeClassRegex.test(input.toString()); }; /** * 检测input类型是否为数组或者object * @param {*} input 任意类型的输入 * @returns {Boolean} true->input is an array or an object */ export const isArrayOrObject = function (input) { return isArray(input) || isObject(input); }; /** * 检测input类型是否为function * @param {*} input 任意类型的输入 * @returns {Boolean} true->input is a function */ export const isFunction = function (input) { return typeof input === 'function'; }; export const isBoolean = function (input) { return typeof input === 'boolean'; }; /** * 检测input是否为Error的实例 * @param {*} input 任意类型的输入 * @returns {Boolean} true->input is an instance of Error */ export const isInstanceOfError = function (input) { return input instanceof Error; }; /** * Get the object type string * @param {*} input 任意类型的输入 * @returns {String} the object type string */ export const getType = function (input) { return Object.prototype.toString.call(input).match(/^\[object (.*)\]$/)[1].toLowerCase(); }; /** * ### 用于 request 参数 key 名称的合法性验证。 * > 不合法的 key 有: * - 非 string 类型的 key ; * - 非字母和数字开头的 key ; * @param {string} key - 参数 key * @returns {boolean} * - true : 合法; * - false: 非法; */ export const isValidRequestKey = function (key) { // 非 string 类型的 key if (typeof key !== 'string') { return false; } const firstCharactor = key[0]; // 非字母和数字开头的 key if (/[^a-zA-Z0-9]/.test(firstCharactor)) { return false; } return true; }; // -----------------获取时间工具函数,计算耗时用----------------- let baseTime = 0; if (!Date.now) { Date.now = function now() { return new Date().getTime(); }; } export const TimeUtil = { now() { if (baseTime === 0) { baseTime = Date.now() - 1; } const diff = Date.now() - baseTime; if (diff > 0xffffffff) { baseTime += 0xffffffff; return Date.now() - baseTime; } return diff; }, utc() { return Math.round(Date.now() / 1000); } }; // -----------------深度合并工具函数----------------- /** * 深度 merge 两个对象。merge source to target. * @param {Object|Object[]} target 目标对象 * @param {Object|Object[]} source 来源对象 * @param {String[]} [keysIgnore] 要忽略的 keys。命中的 key 会在merge时被忽略 * @param {*[]} [valuesIgnore] 要忽略的 values。命中的 value 会在merge时被忽略 * @returns {Number} merge的次数(只有key相同value不同的时候才会merge),如果target和source既不是数组也不是object,则返回0 */ export const deepMerge = function (target, source, keysIgnore, valuesIgnore) { // 1. 非 Array 或 Object 类型则直接 return if (!(isArrayOrObject(target) && isArrayOrObject(source))) { return 0; } let mergedCount = 0; const keys = Object.keys(source); let tmpKey; for (let i = 0, len = keys.length; i < len; i++) { tmpKey = keys[i]; if (isUndefined(source[tmpKey]) || keysIgnore && keysIgnore.includes(tmpKey)) { continue; } if (isArrayOrObject(target[tmpKey]) && isArrayOrObject(source[tmpKey])) { // 递归merge mergedCount += deepMerge(target[tmpKey], source[tmpKey], keysIgnore, valuesIgnore); } else { if (valuesIgnore && valuesIgnore.includes(source[tmpKey])) { continue; } if (target[tmpKey] !== source[tmpKey]) { target[tmpKey] = source[tmpKey]; mergedCount += 1; } } } return mergedCount; }; // 简单的深拷贝实现 export function deepCopy(val) { if (Object.prototype.toString.call(val) === '[object Array]' && Object.prototype.toString.call(val) === '[object Object]') { return val } let target = Array.isArray(val) ? [] : {} //数组兼容 for (var k in val) { if (val.hasOwnProperty(k)) { if (typeof val[k] === 'object') { target[k] = deepCopy(val[k]) } else { target[k] = val[k] } } } return target } /** * 序列化Error实例,只序列化Error实例的message和code属性(如果有的话) * @param {Error} error Error实例 * @returns {String} 序列化后的内容 */ export const stringifyError = function (error) { return JSON.stringify(error, ['message', 'code']); }; /** * 返回一个 ISO(ISO 8601 Extended Format)格式的字符串,如"2019-11-15T18:45:06.000+0800",用于记录sso上报时的时间 * @returns {String} */ export const date2ISOString = function () { const date = new Date(); // YYYY-MM-DDTHH:mm:ss.sssZ。时区总是UTC(协调世界时),加一个后缀“Z”标识。 const tempISOString = date.toISOString(); // 返回协调世界时(UTC)相对于当前时区的时间差值,单位为分钟。 // 如果本地时区晚于协调世界时,则该差值为正值,如果早于协调世界时则为负值 const timezoneOffset = date.getTimezoneOffset(); const currentTimeZoneInHour = timezoneOffset / 60; let replaceString = ''; if (currentTimeZoneInHour < 0) { // 东区 if (currentTimeZoneInHour > -10) { replaceString = `+0${Math.abs(currentTimeZoneInHour * 100)}`; } else { replaceString = `+${Math.abs(currentTimeZoneInHour * 100)}`; } } else { // 西区 if (currentTimeZoneInHour >= 10) { replaceString = `-${currentTimeZoneInHour * 100}`; } else { replaceString = `-0${currentTimeZoneInHour * 100}`; } } // 不需要 encodeURIComponent 把 + 转成 %2B,kibana能识别 return tempISOString.replace('Z', replaceString); }; /** * 把map的内容转成字符串, * @param {Map} input 字典 * @returns {String} 字典的内容 */ export const map2String = function (input) { if (!isMap(input)) { return 'not a map!!!'; } let result = ''; for (const [k, v] of input.entries()) { if (!isMap(v)) { result += `[k=${k} v=${v}] `; } else { return `[k=${k} -> ${map2String(v)}`; } } return result; }; /** * 获取字符串占用的字节数 * @param {String} string - 字符串 * @returns {Number} 字符串的长度 */ export const stringSize = function (string) { if (string.length === 0) { return 0; } let i = 0; let char = ''; let len = 0; let sizeStep = 1; const charSet = function () { if (typeof document !== 'undefined' && typeof document.characterSet !== 'undefined') { return document.characterSet; } return 'UTF-8'; }(); while (typeof string[i] !== 'undefined') { char = string[i++]; if (char.charCodeAt[i] <= 255) { sizeStep = 1; } else { sizeStep = charSet === 'UTF-8' >= 0 ? 3 : 2; } len += sizeStep; } return len; }; /** * 用于生成随机整数 * @param {Number} level - 整数的级别 例如: 99, 999 , 9999 等 * @returns {Number} 随机数字 */ export const randomInt = function (level) { const lv = level || 99999999; return Math.round(Math.random() * lv); }; const CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; const CHARS_LENGTH = CHARS.length; /** * 获取[0,9],[a-z],[A,Z]拼成的随机字符串,长度32 * @returns {String} 长度为32的随机字符串 */ export const randomString = function () { let result = ''; for (let i = 32; i > 0; --i) { result += CHARS[Math.floor(Math.random() * CHARS_LENGTH)]; } return result; }; // 判断传入的枚举值是否有效 export const isValidType = function (obj, type) { for (const k in obj) { if (obj[k] === type) { return true; } } return false; }; const AIIList = {}; export const autoincrementIndex = function (key) { if (!key) { logger.error('autoincrementIndex(string: key) need key parameter'); return false; } if (typeof AIIList[key] === 'undefined') { const dateInstance = new Date(); let hoursString = `3${dateInstance.getHours()}`.slice(-2); // 小时数,用3占位,因为一天中不会出现30小时 let minuteString = `0${dateInstance.getMinutes()}`.slice(-2); let secondString = `0${dateInstance.getSeconds()}`.slice(-2); AIIList[key] = parseInt([hoursString, minuteString, secondString, '0001'].join('')); hoursString = null; minuteString = null; secondString = null; logger.warn(`utils.autoincrementIndex() create new sequence : ${key} = ${AIIList[key]}`); } return AIIList[key]++; }; // 统一用HTTPS export const uniformHTTPS = function (url) { if (url.indexOf('http://') === -1 || url.indexOf('https://') === -1) { return `https://${url}`; } return url.replace(/https|http/, 'https'); }; /** * 使用转JSON方式克隆一个对象 * @param {Object} data 要克隆的对象 * @returns {Object} 克隆后的对象 */ export const cloneBaseOnJSON = function (data) { try { return JSON.parse(JSON.stringify(data)); } catch (error) { logger.error('cloneBaseOnJSON Error: ', error); return data; } }; /** * 深度克隆 * @param {Object} data 要克隆的对象 * @returns {Object} 克隆后的对象 */ export const clone = function (data) { if (Object.getOwnPropertyNames(data).length === 0) { return Object.create(null); } const newObject = Array.isArray(data) ? [] : Object.create(null); let type = ''; // eslint-disable-next-line guard-for-in for (const key in data) { // null 是一个特殊的对象,优先处理掉 if (data[key] === null) { newObject[key] = null; continue; } // undefined 也优先处理掉 if (data[key] === undefined) { newObject[key] = undefined; continue; } type = typeof data[key]; if (['string', 'number', 'function', 'boolean'].indexOf(type) >= 0) { newObject[key] = data[key]; continue; } // 只剩对象,递归 newObject[key] = clone(data[key]); } return newObject; }; /** * 更新自定义字段,如资料自定义字段,群组自定义字段,群成员自定义字段 * @param {Object[]} target 待更新的自定义字段数组 [{key,value}, {key,value}, ...] * @param {Object[]} source 最新的自定义字段数组 [{key,value}, {key,value}, ...] */ export function updateCustomField(target, source) { if (!isArray(target) || !isArray(source)) { logger.warn('updateCustomField target 或 source 不是数组,忽略此次更新。'); return; } source.forEach(({ key, value }) => { const customField = target.find(item => item.key === key); if (customField) { customField.value = value; } else { target.push({ key, value }); } }); } /** * 类似 lodash.mapKeys 功能,并具备过滤 keys 的功能 * @export * @param {*} obj * @param {*} iteratee */ export function filterMapKeys(obj, iteratee) { const newObj = Object.create(null); Object.keys(obj).forEach(key => { const nextKey = iteratee(obj[key], key); // 回调返回 false 则过滤 key if (nextKey) { newObj[nextKey] = obj[key]; } }); return newObj; } // -----------------类lodash.mapKeys函数----------------- export function mapKeys(obj, iteratee) { const newObj = {}; Object.keys(obj).forEach(key => { newObj[iteratee(obj[key], key)] = obj[key]; }); return newObj; } // -----------------类lodash.mapValues函数----------------- export function mapValues(obj, iteratee) { const newObj = {}; Object.keys(obj).forEach(key => { newObj[key] = iteratee(obj[key], key); }); return newObj; }