327 lines
6.3 KiB
JavaScript
327 lines
6.3 KiB
JavaScript
|
const crypto = require('crypto');
|
|||
|
const fs = require('fs');
|
|||
|
const path = require('path');
|
|||
|
|
|||
|
/**
|
|||
|
* 生成随机字符串
|
|||
|
*/
|
|||
|
function generateRandomString(length = 32, charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') {
|
|||
|
let result = '';
|
|||
|
for (let i = 0; i < length; i++) {
|
|||
|
result += charset.charAt(Math.floor(Math.random() * charset.length));
|
|||
|
}
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 生成UUID
|
|||
|
*/
|
|||
|
function generateUUID() {
|
|||
|
return crypto.randomUUID();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 生成MD5哈希
|
|||
|
*/
|
|||
|
function generateMD5(data) {
|
|||
|
return crypto.createHash('md5').update(data).digest('hex');
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 生成SHA256哈希
|
|||
|
*/
|
|||
|
function generateSHA256(data) {
|
|||
|
return crypto.createHash('sha256').update(data).digest('hex');
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 延迟函数
|
|||
|
*/
|
|||
|
function delay(ms) {
|
|||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 深度克隆对象
|
|||
|
*/
|
|||
|
function deepClone(obj) {
|
|||
|
if (obj === null || typeof obj !== 'object') {
|
|||
|
return obj;
|
|||
|
}
|
|||
|
|
|||
|
if (obj instanceof Date) {
|
|||
|
return new Date(obj.getTime());
|
|||
|
}
|
|||
|
|
|||
|
if (obj instanceof Array) {
|
|||
|
return obj.map(item => deepClone(item));
|
|||
|
}
|
|||
|
|
|||
|
if (typeof obj === 'object') {
|
|||
|
const clonedObj = {};
|
|||
|
for (const key in obj) {
|
|||
|
if (obj.hasOwnProperty(key)) {
|
|||
|
clonedObj[key] = deepClone(obj[key]);
|
|||
|
}
|
|||
|
}
|
|||
|
return clonedObj;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 格式化文件大小
|
|||
|
*/
|
|||
|
function formatFileSize(bytes) {
|
|||
|
if (bytes === 0) return '0 Bytes';
|
|||
|
|
|||
|
const k = 1024;
|
|||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|||
|
|
|||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 格式化时间差
|
|||
|
*/
|
|||
|
function formatTimeDiff(startTime, endTime = Date.now()) {
|
|||
|
const diff = endTime - startTime;
|
|||
|
const seconds = Math.floor(diff / 1000);
|
|||
|
const minutes = Math.floor(seconds / 60);
|
|||
|
const hours = Math.floor(minutes / 60);
|
|||
|
const days = Math.floor(hours / 24);
|
|||
|
|
|||
|
if (days > 0) return `${days}天${hours % 24}小时`;
|
|||
|
if (hours > 0) return `${hours}小时${minutes % 60}分钟`;
|
|||
|
if (minutes > 0) return `${minutes}分钟${seconds % 60}秒`;
|
|||
|
return `${seconds}秒`;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 验证邮箱格式
|
|||
|
*/
|
|||
|
function isValidEmail(email) {
|
|||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|||
|
return emailRegex.test(email);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 验证URL格式
|
|||
|
*/
|
|||
|
function isValidURL(url) {
|
|||
|
try {
|
|||
|
new URL(url);
|
|||
|
return true;
|
|||
|
} catch {
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 清理字符串(移除HTML标签和特殊字符)
|
|||
|
*/
|
|||
|
function sanitizeString(str) {
|
|||
|
if (typeof str !== 'string') return '';
|
|||
|
|
|||
|
return str
|
|||
|
.replace(/<[^>]*>/g, '') // 移除HTML标签
|
|||
|
.replace(/[<>'"&]/g, '') // 移除危险字符
|
|||
|
.trim();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 截断字符串
|
|||
|
*/
|
|||
|
function truncateString(str, maxLength, suffix = '...') {
|
|||
|
if (typeof str !== 'string') return '';
|
|||
|
|
|||
|
if (str.length <= maxLength) return str;
|
|||
|
|
|||
|
return str.substring(0, maxLength - suffix.length) + suffix;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 检查文件是否存在
|
|||
|
*/
|
|||
|
function fileExists(filePath) {
|
|||
|
try {
|
|||
|
return fs.existsSync(filePath);
|
|||
|
} catch {
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 确保目录存在
|
|||
|
*/
|
|||
|
function ensureDirectoryExists(dirPath) {
|
|||
|
try {
|
|||
|
if (!fs.existsSync(dirPath)) {
|
|||
|
fs.mkdirSync(dirPath, { recursive: true });
|
|||
|
}
|
|||
|
return true;
|
|||
|
} catch (error) {
|
|||
|
console.error('创建目录失败:', error);
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 读取JSON文件
|
|||
|
*/
|
|||
|
function readJSONFile(filePath) {
|
|||
|
try {
|
|||
|
const data = fs.readFileSync(filePath, 'utf8');
|
|||
|
return JSON.parse(data);
|
|||
|
} catch (error) {
|
|||
|
console.error('读取JSON文件失败:', error);
|
|||
|
return null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 写入JSON文件
|
|||
|
*/
|
|||
|
function writeJSONFile(filePath, data) {
|
|||
|
try {
|
|||
|
const jsonString = JSON.stringify(data, null, 2);
|
|||
|
fs.writeFileSync(filePath, jsonString, 'utf8');
|
|||
|
return true;
|
|||
|
} catch (error) {
|
|||
|
console.error('写入JSON文件失败:', error);
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取文件扩展名
|
|||
|
*/
|
|||
|
function getFileExtension(filename) {
|
|||
|
return path.extname(filename).toLowerCase();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取文件名(不含扩展名)
|
|||
|
*/
|
|||
|
function getFileNameWithoutExtension(filename) {
|
|||
|
return path.basename(filename, path.extname(filename));
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 转换为驼峰命名
|
|||
|
*/
|
|||
|
function toCamelCase(str) {
|
|||
|
return str.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 转换为kebab命名
|
|||
|
*/
|
|||
|
function toKebabCase(str) {
|
|||
|
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 防抖函数
|
|||
|
*/
|
|||
|
function debounce(func, wait) {
|
|||
|
let timeout;
|
|||
|
return function executedFunction(...args) {
|
|||
|
const later = () => {
|
|||
|
clearTimeout(timeout);
|
|||
|
func(...args);
|
|||
|
};
|
|||
|
clearTimeout(timeout);
|
|||
|
timeout = setTimeout(later, wait);
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 节流函数
|
|||
|
*/
|
|||
|
function throttle(func, limit) {
|
|||
|
let inThrottle;
|
|||
|
return function executedFunction(...args) {
|
|||
|
if (!inThrottle) {
|
|||
|
func.apply(this, args);
|
|||
|
inThrottle = true;
|
|||
|
setTimeout(() => inThrottle = false, limit);
|
|||
|
}
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 重试函数
|
|||
|
*/
|
|||
|
async function retry(fn, maxAttempts = 3, delay = 1000) {
|
|||
|
let lastError;
|
|||
|
|
|||
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|||
|
try {
|
|||
|
return await fn();
|
|||
|
} catch (error) {
|
|||
|
lastError = error;
|
|||
|
|
|||
|
if (attempt === maxAttempts) {
|
|||
|
throw lastError;
|
|||
|
}
|
|||
|
|
|||
|
console.warn(`尝试 ${attempt} 失败,${delay}ms后重试:`, error.message);
|
|||
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 并行执行函数并限制并发数
|
|||
|
*/
|
|||
|
async function parallelLimit(tasks, limit = 5) {
|
|||
|
const results = [];
|
|||
|
const executing = [];
|
|||
|
|
|||
|
for (const task of tasks) {
|
|||
|
const promise = Promise.resolve().then(() => task()).then(result => {
|
|||
|
executing.splice(executing.indexOf(promise), 1);
|
|||
|
return result;
|
|||
|
});
|
|||
|
|
|||
|
results.push(promise);
|
|||
|
|
|||
|
if (results.length >= limit) {
|
|||
|
executing.push(promise);
|
|||
|
|
|||
|
if (executing.length >= limit) {
|
|||
|
await Promise.race(executing);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return Promise.all(results);
|
|||
|
}
|
|||
|
|
|||
|
module.exports = {
|
|||
|
generateRandomString,
|
|||
|
generateUUID,
|
|||
|
generateMD5,
|
|||
|
generateSHA256,
|
|||
|
delay,
|
|||
|
deepClone,
|
|||
|
formatFileSize,
|
|||
|
formatTimeDiff,
|
|||
|
isValidEmail,
|
|||
|
isValidURL,
|
|||
|
sanitizeString,
|
|||
|
truncateString,
|
|||
|
fileExists,
|
|||
|
ensureDirectoryExists,
|
|||
|
readJSONFile,
|
|||
|
writeJSONFile,
|
|||
|
getFileExtension,
|
|||
|
getFileNameWithoutExtension,
|
|||
|
toCamelCase,
|
|||
|
toKebabCase,
|
|||
|
debounce,
|
|||
|
throttle,
|
|||
|
retry,
|
|||
|
parallelLimit
|
|||
|
};
|