mirror of https://github.com/allinssl/allinssl
322 lines
11 KiB
TypeScript
322 lines
11 KiB
TypeScript
/**
|
||
* 文件定义:业务处理
|
||
*/
|
||
|
||
import * as R from 'ramda'
|
||
import { isArray } from './type'
|
||
|
||
/* -------------- 1、常用正则验证 -------------- */
|
||
// 常量定义区域
|
||
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||
const PHONE_REGEX = /^1[3-9]\d{9}$/
|
||
const ID_CARD_REGEX = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/
|
||
const URL_REGEX = /^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+/
|
||
const DOMAIN_REGEX = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/
|
||
|
||
// 增强版域名正则表达式 - 支持国际化域名和更多顶级域名
|
||
const ENHANCED_DOMAIN_REGEX =
|
||
/^(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)|(?:\*))\.)+(?:[a-zA-Z\u00a1-\uffff]{2,}|xn--[a-zA-Z0-9]+)$/
|
||
|
||
// 通配符域名正则表达式 - 支持通配符域名格式 (如 *.example.com)
|
||
const WILDCARD_DOMAIN_REGEX = /^\*\.(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/
|
||
|
||
// IPv4正则表达式 - 更精确的数字范围
|
||
const IPV4_SEGMENT = '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])'
|
||
const IPV4_REGEX = new RegExp(`^${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}$`)
|
||
|
||
// IPv6正则表达式 - 更精确的十六进制表示
|
||
const IPV6_HEX_4DIGIT = '[0-9A-Fa-f]{1,4}'
|
||
const IPV6_REGEX = new RegExp(
|
||
[
|
||
// 标准IPv6地址
|
||
`^(${IPV6_HEX_4DIGIT}:){7}${IPV6_HEX_4DIGIT}$`,
|
||
// 压缩形式
|
||
`^(${IPV6_HEX_4DIGIT}:){1,7}:$`,
|
||
'^:((:[0-9A-Fa-f]{1,4}){1,7}|:)$',
|
||
// 混合形式
|
||
`^(${IPV6_HEX_4DIGIT}:){1,6}:${IPV6_HEX_4DIGIT}$`,
|
||
`^(${IPV6_HEX_4DIGIT}:){1,5}(:${IPV6_HEX_4DIGIT}){1,2}$`,
|
||
`^(${IPV6_HEX_4DIGIT}:){1,4}(:${IPV6_HEX_4DIGIT}){1,3}$`,
|
||
`^(${IPV6_HEX_4DIGIT}:){1,3}(:${IPV6_HEX_4DIGIT}){1,4}$`,
|
||
`^(${IPV6_HEX_4DIGIT}:){1,2}(:${IPV6_HEX_4DIGIT}){1,5}$`,
|
||
`^${IPV6_HEX_4DIGIT}:(:${IPV6_HEX_4DIGIT}){1,6}$`,
|
||
// 特殊形式
|
||
`^fe80:(:[0-9A-Fa-f]{1,4}){0,4}%[0-9A-Za-z]{1,}$`,
|
||
// IPv4映射到IPv6
|
||
`^::((ffff(:0{1,4})?:)?${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT})$`,
|
||
`^(${IPV6_HEX_4DIGIT}:){1,4}:${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}$`,
|
||
].join('|'),
|
||
)
|
||
|
||
// IP段正则表达式
|
||
const IPS_REGEX = new RegExp(
|
||
`^${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}\\.${IPV4_SEGMENT}(\\/([1-2][0-9]|3[0-2]|[1-9]))?$`,
|
||
)
|
||
|
||
// MAC地址正则表达式
|
||
const MAC_REGEX = /^([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}$/
|
||
|
||
// 中文正则表达式
|
||
const CHINESE_REGEX = /^[\u4e00-\u9fa5]+$/
|
||
|
||
// 端口正则表达式 - 更精确的数字范围
|
||
const PORT_REGEX = /^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/
|
||
|
||
/**
|
||
* 判断是否为邮箱
|
||
* @param {string} email - 要判断的邮箱
|
||
* @returns {boolean} 如果邮箱是有效的,则返回 true,否则返回 false
|
||
*/
|
||
export const isEmail = R.test(EMAIL_REGEX)
|
||
|
||
/**
|
||
* 判断是否为手机号
|
||
* @param {string} phone - 要判断的手机号
|
||
* @returns {boolean} 如果手机号是有效的,则返回 true,否则返回 false
|
||
*/
|
||
export const isPhone = R.test(PHONE_REGEX)
|
||
|
||
/**
|
||
* 判断是否为身份证号
|
||
* @param {string} idCard - 要判断的身份证号
|
||
* @returns {boolean} 如果身份证号是有效的,则返回 true,否则返 false
|
||
*/
|
||
export const isIdCard = R.test(ID_CARD_REGEX)
|
||
|
||
/**
|
||
* 判断是否为URL
|
||
* @param {string} url - 要判断的url
|
||
* @returns {boolean} 如果url是有效的,则返回 true,否则返回 false
|
||
*/
|
||
export const isUrl = R.test(URL_REGEX)
|
||
|
||
/**
|
||
* 判断是否为IPv4地址
|
||
* @param {string} ip - 要判断的IP地址
|
||
* @returns {boolean} 如果是有效的IPv4地址,则返回 true,否则返回 false
|
||
*/
|
||
export const isIpv4 = R.test(IPV4_REGEX)
|
||
|
||
/**
|
||
* 判断是否为IPv6地址
|
||
* @param {string} ip - 要判断的IP地址
|
||
* @returns {boolean} 如果是有效的IPv6地址,则返回 true,否则返回 false
|
||
*/
|
||
export const isIpv6 = R.test(IPV6_REGEX)
|
||
|
||
/**
|
||
* 判断是否为IP地址(IPv4或IPv6)
|
||
* @param {string} ip - 要判断的IP地址
|
||
* @returns {boolean} 如果IP地址是有效的,则返回 true,否则返回 false
|
||
*/
|
||
export const isIp = (ip: string): boolean => isIpv4(ip) || isIpv6(ip)
|
||
|
||
/**
|
||
* 判断是否为IP段
|
||
* @param {string} ips - 要判断的IP段
|
||
* @returns {boolean} 如果IP段是有效的,则返回 true,否则返回 false
|
||
*/
|
||
export const isIps = R.test(IPS_REGEX)
|
||
|
||
/**
|
||
* 判断端口
|
||
* @param {string} port - 判断端口
|
||
* @returns {boolean} 如果端口是有效的,则返回 true,否则返回 false
|
||
*/
|
||
export const isPort = R.test(PORT_REGEX)
|
||
|
||
/**
|
||
* 判断是否为MAC地址
|
||
* @param {string} mac - 要判断的MAC地址
|
||
* @returns {boolean} 如果MAC地址是有效的,则返回 true,否则返回 false
|
||
*/
|
||
export const isMac = R.test(MAC_REGEX)
|
||
|
||
/**
|
||
* 判断是否为中文
|
||
* @param {string} str - 要判断的字符串
|
||
* @returns {boolean} 如果字符串是中文,则返回 true,否则返回 false
|
||
*/
|
||
export const isChinese = R.test(CHINESE_REGEX)
|
||
|
||
/**
|
||
* 判断是否为域名
|
||
* @param {string} domain - 要判断的域名
|
||
* @returns {boolean} 如果域名是有效的,则返回 true,否则返回 false
|
||
*/
|
||
export const isDomain = R.test(DOMAIN_REGEX)
|
||
|
||
/**
|
||
* 判断是否为域名(增强版)
|
||
* @param {string} domain - 要判断的域名,支持国际化域名和更多顶级域名
|
||
* @returns {boolean} 如果域名是有效的,则返回 true,否则返回 false
|
||
*/
|
||
export const isEnhancedDomain = R.test(ENHANCED_DOMAIN_REGEX)
|
||
|
||
/**
|
||
* 判断是否为通配符域名
|
||
* @param {string} domain - 要判断的通配符域名
|
||
* @returns {boolean} 如果通配符域名是有效的,则返回 true,否则返回 false
|
||
*/
|
||
export const isWildcardDomain = R.test(WILDCARD_DOMAIN_REGEX)
|
||
|
||
/**
|
||
* 判断是否为域名或通配符域名
|
||
* @param {string} domain - 要判断的域名
|
||
* @returns {boolean} 如果域名或通配符域名是有效的,则返回 true,否则返回 false
|
||
*/
|
||
export const isDomainOrWildcardDomain = (domain: string): boolean => isDomain(domain) || isWildcardDomain(domain)
|
||
|
||
/**
|
||
* 判断域名组,通过特定字符串分割
|
||
* @param {string} domain - 要判断的域名
|
||
* @param {string} separator - 分割符
|
||
* @returns {boolean} 如果域名组是有效的,则返回 true,否则返回 false
|
||
*/
|
||
export const isDomainGroup = (domain: string, separator: string = ',') => {
|
||
return R.all(
|
||
R.equals(true),
|
||
R.map(
|
||
(item: string) => isDomain(item) || isWildcardDomain(item) || isEnhancedDomain(item),
|
||
R.split(separator, domain),
|
||
),
|
||
)
|
||
}
|
||
|
||
/* -------------- 2、常用业务操作 -------------- */
|
||
|
||
/**
|
||
* 手机号加密
|
||
* @param {string} phone - 要加密的手机号
|
||
* @returns {string} 加密后的手机号
|
||
*/
|
||
export const encryptPhone = (phone: string): string => phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||
|
||
/**
|
||
* 身份证号加密
|
||
* @param {string} idCard - 要加密的身份证号(18位,最后一位可以是X)
|
||
* @returns {string} 加密后的身份证号
|
||
*/
|
||
export const encryptIdCard = (idCard: string): string => idCard.replace(/(\d{6})\d{8}([\dXx]{4})/, '$1****$2')
|
||
|
||
/**
|
||
* 版本号比较
|
||
* @param {string} version1 - 版本号1
|
||
* @param {string} version2 - 版本号2
|
||
* @returns {number} 如果版本号1大于版本号2,则返回1,如果版本号1小于版本号2,则返回-1,如果版本号1等于版本号2,则返回0
|
||
*/
|
||
export const compareVersion = (version1: string, version2: string): number => {
|
||
// 使用Ramda的pipe函数组合操作
|
||
const parseVersion = R.pipe(
|
||
R.split('.'),
|
||
R.map((v: string) => parseInt(v || '0', 10)),
|
||
)
|
||
const v1 = parseVersion(version1) // 解析版本号1
|
||
const v2 = parseVersion(version2) // 解析版本号2
|
||
|
||
// 确保两个数组长度相同
|
||
const len = Math.max(v1.length, v2.length)
|
||
|
||
// 使用Ramda的repeat和take函数来填充数组
|
||
const paddedV1 = R.concat(v1, R.repeat(0, len - v1.length))
|
||
const paddedV2 = R.concat(v2, R.repeat(0, len - v2.length))
|
||
|
||
// 使用Ramda的zipWith比较每个版本号段
|
||
const comparisons = R.zipWith((a: number, b: number) => (a === b ? 0 : a > b ? 1 : -1), paddedV1, paddedV2)
|
||
|
||
// 找到第一个非零的比较结果
|
||
const result = R.find(R.complement(R.equals(0)), comparisons)
|
||
return result ?? 0
|
||
}
|
||
|
||
/**
|
||
* 字节转换
|
||
* @param {number} bytes - 要转换的字节数
|
||
* @param {number} [fixed=2] - 保留小数位数
|
||
* @param {boolean} [isUnit=true] - 是否显示单位
|
||
* @param {string} [endUnit=''] - 指定结束单位,如果指定则转换到该单位为止
|
||
* @returns {string} 转换后的字节数
|
||
*/
|
||
export const formatBytes = (bytes: number, fixed: number = 2, isUnit: boolean = true, endUnit: string = ''): string => {
|
||
if (bytes === 0) return isUnit ? '0 B' : '0'
|
||
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||
const c = 1024
|
||
|
||
// 使用Ramda的递归函数进行单位转换
|
||
const convert = (value: number, unitIndex: number): string => {
|
||
const unit = units[unitIndex]
|
||
const formattedValue = unitIndex === 0 || fixed === 0 ? Math.round(value).toString() : value.toFixed(fixed)
|
||
// 如果指定了结束单位或者已经是最小单位
|
||
if ((endUnit && unit === endUnit) || value < c || unitIndex >= units.length - 1) {
|
||
return isUnit ? `${formattedValue} ${unit}` : formattedValue
|
||
}
|
||
// 继续转换到下一个单位
|
||
return convert(value / c, unitIndex + 1)
|
||
}
|
||
return convert(bytes, 0)
|
||
}
|
||
|
||
/**
|
||
* 柯里化版本的formatBytes
|
||
* @param {number} bytes - 要转换的字节数
|
||
* @param {number} [fixed=2] - 保留小数位数
|
||
* @param {boolean} [isUnit=true] - 是否显示单位
|
||
* @param {string} [endUnit=''] - 指定结束单位,如果指定则转换到该单位为止
|
||
* @returns {string} 转换后的字节数
|
||
*/
|
||
export const formatBytesCurried: {
|
||
(bytes: number, fixed: number, isUnit: boolean, endUnit: string): string
|
||
(bytes: number): (fixed?: number, isUnit?: boolean, endUnit?: string) => string
|
||
(bytes: number, fixed: number): (isUnit?: boolean, endUnit?: string) => string
|
||
(bytes: number, fixed: number, isUnit: boolean): (endUnit?: string) => string
|
||
} = R.curry(formatBytes)
|
||
|
||
/**
|
||
* 分页字符串转换
|
||
* @param {string} page - 分页字符串
|
||
* @returns {string} 转换后的分页字符串
|
||
*/
|
||
export const formatPage = (page: string): number => {
|
||
const newPage = page.match(/class='Pcount'>共([0-9]*)条</)
|
||
if (isArray(newPage) && newPage.length >= 2) return Number(newPage[1])
|
||
return 0
|
||
}
|
||
|
||
/* -------------- 3、代理函数 -------------- */
|
||
|
||
export type ProxyConfig = {
|
||
requestTime: number
|
||
requestToken: string
|
||
request_time: number
|
||
request_token: string
|
||
}
|
||
|
||
/**
|
||
* 代理配置,仅在开发环境生效
|
||
* @param {string} proxyKey - 代理密钥
|
||
* @param {string} usage 使用场景 "query" | "params"
|
||
* @returns {Object} 返回对象包含 request_time 和 request_token
|
||
*/
|
||
export const getProxyConfig = async (proxyKey: string, usage: 'query' | 'params' = 'params') => {
|
||
const md5 = await import('md5')
|
||
const request_time = Date.now()
|
||
const request_token = md5.default(String(request_time).concat(md5.default(proxyKey)))
|
||
if (usage === 'params') {
|
||
return { request_time, request_token, requestTime: request_time, requestToken: request_token }
|
||
}
|
||
return `request_time=${request_time}&request_token=${request_token}`
|
||
}
|
||
|
||
/** -------------- 4、接口缓存配置 -------------- */
|
||
|
||
/**
|
||
* 接口缓存配置
|
||
* @param {function} method - 接口请求方法
|
||
* @param {string} params - 接口请求参数
|
||
* @param {Record<string, any>} options - 接口请求配置
|
||
* @returns {string} 返回数据
|
||
*/
|
||
export const getCacheConfig = (method: Function, params: string, options: Record<string, any> = {}) => {
|
||
console.log(method, params, options)
|
||
}
|