mirror of
https://github.com/TencentCloud/TIMSDK.git
synced 2026-10-21 16:56:02 +08:00
87e0eec7e4
Co-authored-by: chenximo <chenximo@tencent.com>
526 lines
14 KiB
JavaScript
526 lines
14 KiB
JavaScript
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 const deepCopy = function (input) {
|
||
const keys = Object.keys(input)
|
||
const ret = {}
|
||
let key
|
||
|
||
for (let i = 0, { length } = keys; i < length; i++) {
|
||
key = keys[i]
|
||
if (isArrayOrObject(input[key])) {
|
||
ret[key] = deepCopy(input[key])
|
||
} else {
|
||
ret[key] = input[key]
|
||
}
|
||
}
|
||
return ret
|
||
}
|
||
|
||
// -----------------其它-----------------
|
||
/**
|
||
* 序列化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
|
||
}
|