mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-05-05 03:21:27 +08:00
【新增】私有证书
This commit is contained in:
827
frontend/plugin/vite-plugin-path-random/src/index.js
Normal file
827
frontend/plugin/vite-plugin-path-random/src/index.js
Normal file
@@ -0,0 +1,827 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { glob } from "glob";
|
||||
|
||||
// 全局随机参数变量,在插件初始化时设置
|
||||
let randomParam = null;
|
||||
|
||||
/**
|
||||
* 生成随机数参数
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {string} 随机数字符串
|
||||
*/
|
||||
function generateRandomParam(options = {}, isFirstSet = false) {
|
||||
const timestamp = Date.now();
|
||||
const randomStr = Math.random().toString(36).substring(2, 8);
|
||||
|
||||
if (
|
||||
options.customGenerator &&
|
||||
typeof options.customGenerator === "function"
|
||||
) {
|
||||
return options.customGenerator(timestamp, randomStr);
|
||||
}
|
||||
return `${timestamp}${isFirstSet ? `_${randomStr}` : ""}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配文件中的CSS和JS引用
|
||||
* @param {string} content - 文件内容
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {string} 处理后的内容
|
||||
*/
|
||||
function processFileContent(content, options = {}) {
|
||||
let processedContent = content;
|
||||
|
||||
// 处理HTML中的link标签 (CSS)
|
||||
processedContent = processedContent.replace(
|
||||
/(<link[^>]*href=["'])([^"']*\.css)([^"']*?)(["'][^>]*>)/gi,
|
||||
(match, prefix, filePath, queryParams, suffix) => {
|
||||
if (
|
||||
filePath.startsWith("http://") ||
|
||||
filePath.startsWith("https://") ||
|
||||
filePath.startsWith("//")
|
||||
) {
|
||||
return options.includeExternal
|
||||
? `${prefix}${filePath}${queryParams}${
|
||||
queryParams.includes("?") || filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`
|
||||
: match;
|
||||
}
|
||||
return `${prefix}${filePath}${queryParams}${
|
||||
queryParams.includes("?") || filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`;
|
||||
}
|
||||
);
|
||||
|
||||
// 处理HTML中的script标签 (JS)
|
||||
processedContent = processedContent.replace(
|
||||
/(<script[^>]*src=["'])([^"']*\.js)([^"']*?)(["'][^>]*>)/gi,
|
||||
(match, prefix, filePath, queryParams, suffix) => {
|
||||
if (
|
||||
filePath.startsWith("http://") ||
|
||||
filePath.startsWith("https://") ||
|
||||
filePath.startsWith("//")
|
||||
) {
|
||||
return options.includeExternal
|
||||
? `${prefix}${filePath}${queryParams}${
|
||||
queryParams.includes("?") || filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`
|
||||
: match;
|
||||
}
|
||||
return `${prefix}${filePath}${queryParams}${
|
||||
queryParams.includes("?") || filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`;
|
||||
}
|
||||
);
|
||||
|
||||
// 处理CSS中的@import
|
||||
processedContent = processedContent.replace(
|
||||
/(@import\s+["'])([^"']*\.css)(["'])/gi,
|
||||
(match, prefix, filePath, suffix) => {
|
||||
if (
|
||||
filePath.startsWith("http://") ||
|
||||
filePath.startsWith("https://") ||
|
||||
filePath.startsWith("//")
|
||||
) {
|
||||
return options.includeExternal
|
||||
? `${prefix}${filePath}${
|
||||
filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`
|
||||
: match;
|
||||
}
|
||||
return `${prefix}${filePath}${
|
||||
filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`;
|
||||
}
|
||||
);
|
||||
|
||||
// 处理CSS中的url()
|
||||
processedContent = processedContent.replace(
|
||||
/(url\(["']?)([^"')]*\.(css|js|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot))(["']?\))/gi,
|
||||
(match, prefix, filePath, fileExt, suffix) => {
|
||||
if (
|
||||
filePath.startsWith("http://") ||
|
||||
filePath.startsWith("https://") ||
|
||||
filePath.startsWith("//")
|
||||
) {
|
||||
return options.includeExternal
|
||||
? `${prefix}${filePath}${
|
||||
filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`
|
||||
: match;
|
||||
}
|
||||
|
||||
return `${prefix}${filePath}${
|
||||
filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`;
|
||||
}
|
||||
);
|
||||
|
||||
// 处理JS中的import语句
|
||||
processedContent = processedContent.replace(
|
||||
/(import\s*(?:[^"']*?\s*from\s*)?["'])([^"']*\.(?:js|css|jsx|ts|tsx))(["'])/gi,
|
||||
(match, prefix, filePath, suffix) => {
|
||||
if (
|
||||
filePath.startsWith("http://") ||
|
||||
filePath.startsWith("https://") ||
|
||||
filePath.startsWith("//")
|
||||
) {
|
||||
return options.includeExternal
|
||||
? `${prefix}${filePath}${
|
||||
filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`
|
||||
: match;
|
||||
}
|
||||
console.log(randomParam, "时间戳");
|
||||
// console.log(match, prefix, filePath, suffix);
|
||||
return `${prefix}${filePath}${
|
||||
filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`;
|
||||
}
|
||||
);
|
||||
|
||||
// 处理JS中的require语句
|
||||
processedContent = processedContent.replace(
|
||||
/(require\(["'])([^"']*\.(js|css|jsx|ts|tsx))(["']\))/gi,
|
||||
(match, prefix, filePath, fileExt, suffix) => {
|
||||
if (
|
||||
filePath.startsWith("http://") ||
|
||||
filePath.startsWith("https://") ||
|
||||
filePath.startsWith("//")
|
||||
) {
|
||||
return options.includeExternal
|
||||
? `${prefix}${filePath}${
|
||||
filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`
|
||||
: match;
|
||||
}
|
||||
return `${prefix}${filePath}${
|
||||
filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`;
|
||||
}
|
||||
);
|
||||
|
||||
// 处理JS中的动态import()语句
|
||||
processedContent = processedContent.replace(
|
||||
/(import\(["'])([^"']*\.(js|css|jsx|ts|tsx))(["']\))/gi,
|
||||
(match, prefix, filePath, fileExt, suffix) => {
|
||||
if (
|
||||
filePath.startsWith("http://") ||
|
||||
filePath.startsWith("https://") ||
|
||||
filePath.startsWith("//")
|
||||
) {
|
||||
return options.includeExternal
|
||||
? `${prefix}${filePath}${
|
||||
filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`
|
||||
: match;
|
||||
}
|
||||
return `${prefix}${filePath}${
|
||||
filePath.includes("?") ? "&" : "?"
|
||||
}v=${randomParam}${suffix}`;
|
||||
}
|
||||
);
|
||||
|
||||
return processedContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单个文件
|
||||
* @param {string} filePath - 文件路径
|
||||
* @param {Object} options - 配置选项
|
||||
*/
|
||||
function processSingleFile(filePath, options = {}) {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
const processedContent = processFileContent(content, options);
|
||||
|
||||
if (content !== processedContent) {
|
||||
fs.writeFileSync(filePath, processedContent, "utf8");
|
||||
if (options.logger) {
|
||||
options.logger(`✅ 已处理: ${filePath}`);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (options.logger) {
|
||||
options.logger(`⏭️ 跳过: ${filePath} (无需处理)`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
if (options.logger) {
|
||||
options.logger(`❌ 处理失败: ${filePath} - ${error.message}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量处理文件(增强版,支持备份和完整性检查)
|
||||
* @param {string} directory - 目录路径
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {Object} 详细的处理结果
|
||||
*/
|
||||
function processBatchFiles(directory, options = {}) {
|
||||
const defaultOptions = {
|
||||
patterns: ["**/*.html", "**/*.js"],
|
||||
ignore: [],
|
||||
createBackup: false, // 默认不创建备份,保持向后兼容
|
||||
enableIntegrityCheck: true, // 启用完整性检查
|
||||
continueOnError: true, // 遇到错误时继续处理其他文件
|
||||
maxRetries: 2, // 最大重试次数
|
||||
};
|
||||
|
||||
const mergedOptions = { ...defaultOptions, ...options };
|
||||
|
||||
const stats = {
|
||||
processedCount: 0,
|
||||
totalCount: 0,
|
||||
modifiedCount: 0,
|
||||
failedCount: 0,
|
||||
skippedCount: 0,
|
||||
backupFiles: [],
|
||||
errors: [],
|
||||
warnings: [],
|
||||
};
|
||||
|
||||
// 确保目录路径是绝对路径
|
||||
const absoluteDir = path.isAbsolute(directory)
|
||||
? directory
|
||||
: path.resolve(directory);
|
||||
|
||||
// 检查目录是否存在
|
||||
if (!fs.existsSync(absoluteDir)) {
|
||||
const error = `目录不存在: ${absoluteDir}`;
|
||||
if (mergedOptions.logger) {
|
||||
mergedOptions.logger(`❌ ${error}`);
|
||||
}
|
||||
stats.errors.push({ file: "DIRECTORY_CHECK", error });
|
||||
return stats;
|
||||
}
|
||||
|
||||
if (mergedOptions.logger) {
|
||||
mergedOptions.logger(`🔍 扫描目录: ${absoluteDir}`);
|
||||
}
|
||||
|
||||
try {
|
||||
// 收集所有要处理的文件
|
||||
let allFiles = [];
|
||||
|
||||
mergedOptions.patterns.forEach((pattern) => {
|
||||
const searchPattern = path.join(absoluteDir, pattern);
|
||||
const ignorePatterns = mergedOptions.ignore.map((ig) =>
|
||||
path.join(absoluteDir, ig)
|
||||
);
|
||||
|
||||
try {
|
||||
const files = glob.sync(searchPattern, {
|
||||
ignore: ignorePatterns,
|
||||
absolute: true,
|
||||
nodir: true, // 只返回文件,不包括目录
|
||||
});
|
||||
|
||||
if (mergedOptions.logger && files.length > 0) {
|
||||
mergedOptions.logger(
|
||||
`📄 找到 ${files.length} 个匹配文件 (${pattern})`
|
||||
);
|
||||
}
|
||||
|
||||
allFiles.push(...files);
|
||||
} catch (globError) {
|
||||
const error = `模式匹配失败 (${pattern}): ${globError.message}`;
|
||||
stats.errors.push({ file: pattern, error });
|
||||
if (mergedOptions.logger) {
|
||||
mergedOptions.logger(`⚠️ ${error}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 去重
|
||||
allFiles = [...new Set(allFiles)];
|
||||
stats.totalCount = allFiles.length;
|
||||
if (stats.totalCount === 0) {
|
||||
if (mergedOptions.logger) {
|
||||
mergedOptions.logger("⚠️ 未找到匹配的文件");
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
if (mergedOptions.logger) {
|
||||
mergedOptions.logger(`📊 总共找到 ${stats.totalCount} 个文件待处理`);
|
||||
}
|
||||
|
||||
// 处理每个文件
|
||||
allFiles.forEach((file, index) => {
|
||||
if (mergedOptions.logger) {
|
||||
mergedOptions.logger(
|
||||
`\n[${index + 1}/${stats.totalCount}] 处理: ${path.relative(
|
||||
absoluteDir,
|
||||
file
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
let retries = 0;
|
||||
let success = false;
|
||||
|
||||
while (retries <= mergedOptions.maxRetries && !success) {
|
||||
try {
|
||||
stats.processedCount++;
|
||||
|
||||
if (mergedOptions.createBackup) {
|
||||
// 使用安全处理函数
|
||||
const result = processSingleFileSafe(file, mergedOptions);
|
||||
|
||||
if (result.success) {
|
||||
success = true;
|
||||
if (result.modified) {
|
||||
stats.modifiedCount++;
|
||||
if (result.backupPath) {
|
||||
stats.backupFiles.push(result.backupPath);
|
||||
}
|
||||
} else {
|
||||
stats.skippedCount++;
|
||||
}
|
||||
} else {
|
||||
throw new Error(result.error || "未知错误");
|
||||
}
|
||||
} else {
|
||||
// 使用原有的处理函数
|
||||
const modified = processSingleFile(file, mergedOptions);
|
||||
success = true;
|
||||
|
||||
if (modified) {
|
||||
stats.modifiedCount++;
|
||||
} else {
|
||||
stats.skippedCount++;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
retries++;
|
||||
|
||||
if (retries > mergedOptions.maxRetries) {
|
||||
stats.failedCount++;
|
||||
stats.errors.push({
|
||||
file: path.relative(absoluteDir, file),
|
||||
error: error.message,
|
||||
retries: retries - 1,
|
||||
});
|
||||
|
||||
if (mergedOptions.logger) {
|
||||
mergedOptions.logger(
|
||||
`❌ 处理失败 (重试${retries - 1}次): ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!mergedOptions.continueOnError) {
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
if (mergedOptions.logger) {
|
||||
mergedOptions.logger(
|
||||
`⚠️ 重试 ${retries}/${mergedOptions.maxRetries}: ${error.message}`
|
||||
);
|
||||
}
|
||||
// 短暂延迟后重试
|
||||
setTimeout(() => {}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
const globalError = `批量处理过程中发生错误: ${error.message}`;
|
||||
stats.errors.push({ file: "BATCH_PROCESS", error: globalError });
|
||||
if (mergedOptions.logger) {
|
||||
mergedOptions.logger(`❌ ${globalError}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 输出详细统计信息
|
||||
if (mergedOptions.logger) {
|
||||
mergedOptions.logger("\n📊 处理完成统计:");
|
||||
mergedOptions.logger(` 总文件数: ${stats.totalCount}`);
|
||||
mergedOptions.logger(` 已处理: ${stats.processedCount}`);
|
||||
mergedOptions.logger(` 已修改: ${stats.modifiedCount}`);
|
||||
mergedOptions.logger(` 已跳过: ${stats.skippedCount}`);
|
||||
mergedOptions.logger(` 失败: ${stats.failedCount}`);
|
||||
|
||||
if (stats.backupFiles.length > 0) {
|
||||
mergedOptions.logger(` 备份文件: ${stats.backupFiles.length}`);
|
||||
}
|
||||
|
||||
if (stats.errors.length > 0) {
|
||||
mergedOptions.logger("\n❌ 错误详情:");
|
||||
stats.errors.forEach((err) => {
|
||||
mergedOptions.logger(` ${err.file}: ${err.error}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 计算成功率
|
||||
const successRate =
|
||||
stats.totalCount > 0
|
||||
? (
|
||||
((stats.modifiedCount + stats.skippedCount) / stats.totalCount) *
|
||||
100
|
||||
).toFixed(1)
|
||||
: 0;
|
||||
mergedOptions.logger(`\n✨ 成功率: ${successRate}%`);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vite插件主函数
|
||||
* @param {Object} userOptions - 用户配置选项
|
||||
* @returns {Object} Vite插件对象
|
||||
*/
|
||||
export default function randomCachePlugin(userOptions = {}) {
|
||||
const defaultOptions = {
|
||||
// 是否包含外部链接
|
||||
includeExternal: false,
|
||||
// 文件匹配模式 - 编译后处理更多文件类型
|
||||
patterns: [
|
||||
"**/*.html",
|
||||
"**/*.js",
|
||||
"**/*.css",
|
||||
"**/*.jsx",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
// 忽略的文件/目录 - 编译后处理时不忽略输出目录
|
||||
ignore: ["node_modules/**"],
|
||||
// 是否启用日志
|
||||
enableLog: true,
|
||||
// 自定义随机数生成器
|
||||
customGenerator: null,
|
||||
// 处理模式: 'build' | 'serve' | 'both'
|
||||
mode: "build",
|
||||
// 输出目录路径(可选,用于指定特定的编译输出目录)
|
||||
outputDir: null,
|
||||
};
|
||||
|
||||
const options = { ...defaultOptions, ...userOptions };
|
||||
|
||||
// 在插件初始化时生成一次随机参数,确保整个构建过程使用同一个时间戳
|
||||
if (randomParam === null) {
|
||||
randomParam = generateRandomParam(options);
|
||||
}
|
||||
|
||||
// 创建日志函数
|
||||
const logger = options.enableLog ? console.log : () => {};
|
||||
options.logger = logger;
|
||||
|
||||
return {
|
||||
name: "vite-plugin-random-cache",
|
||||
|
||||
// 配置解析完成时
|
||||
configResolved(config) {
|
||||
// 根据模式决定是否启用插件
|
||||
if (options.mode === "serve" && config.command === "build") {
|
||||
return;
|
||||
}
|
||||
if (options.mode === "build" && config.command === "serve") {
|
||||
return;
|
||||
}
|
||||
},
|
||||
// 插件启动时
|
||||
buildStart() {
|
||||
if (options.enableLog) {
|
||||
logger("🚀 Random Cache Plugin 已启动");
|
||||
}
|
||||
},
|
||||
|
||||
// 编译完成时
|
||||
writeBundle(outputOptions, bundle) {
|
||||
// 在编译完成后处理目标文件路径
|
||||
const outputDir =
|
||||
options.outputDir ||
|
||||
outputOptions.dir ||
|
||||
(outputOptions.file ? path.dirname(outputOptions.file) : "dist");
|
||||
|
||||
// 确保输出目录存在
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
if (options.enableLog) {
|
||||
logger(`⚠️ 输出目录不存在: ${outputDir}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.enableLog) {
|
||||
logger(`📁 开始处理编译输出目录: ${outputDir}`);
|
||||
}
|
||||
|
||||
// 处理编译后的文件
|
||||
const result = processBatchFiles(outputDir, {
|
||||
...options,
|
||||
patterns: options.patterns,
|
||||
ignore: [], // 不忽略任何文件,因为这是编译输出目录
|
||||
});
|
||||
|
||||
if (options.enableLog) {
|
||||
logger(
|
||||
`🎯 编译后处理完成: ${result.processedCount}/${result.totalCount} 个文件被修改`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
buildEnd() {
|
||||
if (options.enableLog) {
|
||||
logger("✨ Random Cache Plugin 处理完成");
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件备份
|
||||
* @param {string} filePath - 文件路径
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {string|null} 备份文件路径或null
|
||||
*/
|
||||
function createBackup(filePath, options = {}) {
|
||||
try {
|
||||
const backupDir =
|
||||
options.backupDir || path.join(path.dirname(filePath), ".backup");
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const fileName = path.basename(filePath);
|
||||
const backupFileName = `${fileName}.${timestamp}.backup`;
|
||||
const backupPath = path.join(backupDir, backupFileName);
|
||||
|
||||
// 确保备份目录存在
|
||||
if (!fs.existsSync(backupDir)) {
|
||||
fs.mkdirSync(backupDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 复制原文件到备份位置
|
||||
fs.copyFileSync(filePath, backupPath);
|
||||
|
||||
if (options.logger) {
|
||||
options.logger(`📋 已创建备份: ${backupPath}`);
|
||||
}
|
||||
|
||||
return backupPath;
|
||||
} catch (error) {
|
||||
if (options.logger) {
|
||||
options.logger(`❌ 备份失败: ${filePath} - ${error.message}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从备份恢复文件
|
||||
* @param {string} backupPath - 备份文件路径
|
||||
* @param {string} originalPath - 原文件路径
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {boolean} 恢复是否成功
|
||||
*/
|
||||
function restoreFromBackup(backupPath, originalPath, options = {}) {
|
||||
try {
|
||||
if (!fs.existsSync(backupPath)) {
|
||||
if (options.logger) {
|
||||
options.logger(`❌ 备份文件不存在: ${backupPath}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fs.copyFileSync(backupPath, originalPath);
|
||||
|
||||
if (options.logger) {
|
||||
options.logger(`🔄 已从备份恢复: ${originalPath}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (options.logger) {
|
||||
options.logger(`❌ 恢复失败: ${originalPath} - ${error.message}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全处理单个文件(带备份)
|
||||
* @param {string} filePath - 文件路径
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {Object} 处理结果
|
||||
*/
|
||||
function processSingleFileSafe(filePath, options = {}) {
|
||||
const result = {
|
||||
success: false,
|
||||
modified: false,
|
||||
backupPath: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
try {
|
||||
// 读取原文件内容
|
||||
const originalContent = fs.readFileSync(filePath, "utf8");
|
||||
const processedContent = processFileContent(originalContent, options);
|
||||
|
||||
// 如果内容没有变化,直接返回
|
||||
if (originalContent === processedContent) {
|
||||
if (options.logger) {
|
||||
options.logger(`⏭️ 跳过: ${filePath} (无需处理)`);
|
||||
}
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 创建备份(如果启用)
|
||||
if (options.createBackup !== false) {
|
||||
result.backupPath = createBackup(filePath, options);
|
||||
}
|
||||
|
||||
// 写入处理后的内容
|
||||
fs.writeFileSync(filePath, processedContent, "utf8");
|
||||
|
||||
// 验证文件完整性
|
||||
const verifyContent = fs.readFileSync(filePath, "utf8");
|
||||
if (verifyContent !== processedContent) {
|
||||
throw new Error("文件写入后内容验证失败");
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
result.modified = true;
|
||||
|
||||
if (options.logger) {
|
||||
options.logger(`✅ 已处理: ${filePath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
result.error = error.message;
|
||||
|
||||
// 如果有备份,尝试恢复
|
||||
if (result.backupPath && fs.existsSync(result.backupPath)) {
|
||||
if (options.logger) {
|
||||
options.logger(`⚠️ 处理失败,尝试从备份恢复: ${filePath}`);
|
||||
}
|
||||
restoreFromBackup(result.backupPath, filePath, options);
|
||||
}
|
||||
|
||||
if (options.logger) {
|
||||
options.logger(`❌ 处理失败: ${filePath} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量替换文件内容中的静态资源引用
|
||||
* @param {string|Array} target - 目标目录路径或文件路径数组
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {Object} 处理结果统计
|
||||
*/
|
||||
function batchReplaceWithRandomCache(target, options = {}) {
|
||||
const defaultOptions = {
|
||||
patterns: [
|
||||
"**/*.html",
|
||||
"**/*.js",
|
||||
"**/*.css",
|
||||
"**/*.jsx",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
ignore: ["node_modules/**", ".git/**", ".backup/**"],
|
||||
createBackup: true,
|
||||
backupDir: null, // 默认在每个文件目录下创建.backup文件夹
|
||||
enableLog: true,
|
||||
includeExternal: false,
|
||||
customGenerator: null,
|
||||
dryRun: false, // 预览模式,不实际修改文件
|
||||
};
|
||||
|
||||
const mergedOptions = { ...defaultOptions, ...options };
|
||||
const logger = mergedOptions.enableLog ? console.log : () => {};
|
||||
mergedOptions.logger = logger;
|
||||
|
||||
const stats = {
|
||||
totalFiles: 0,
|
||||
processedFiles: 0,
|
||||
modifiedFiles: 0,
|
||||
failedFiles: 0,
|
||||
backupFiles: [],
|
||||
errors: [],
|
||||
};
|
||||
|
||||
logger("🚀 开始批量替换静态资源引用...");
|
||||
|
||||
try {
|
||||
let filesToProcess = [];
|
||||
|
||||
if (Array.isArray(target)) {
|
||||
// 处理文件路径数组
|
||||
filesToProcess = target.filter((file) => {
|
||||
const exists = fs.existsSync(file);
|
||||
if (!exists && mergedOptions.enableLog) {
|
||||
logger(`⚠️ 文件不存在: ${file}`);
|
||||
}
|
||||
return exists;
|
||||
});
|
||||
} else {
|
||||
// 处理目录
|
||||
const absoluteDir = path.isAbsolute(target)
|
||||
? target
|
||||
: path.resolve(target);
|
||||
|
||||
if (!fs.existsSync(absoluteDir)) {
|
||||
throw new Error(`目标目录不存在: ${absoluteDir}`);
|
||||
}
|
||||
|
||||
logger(`🔍 扫描目录: ${absoluteDir}`);
|
||||
|
||||
mergedOptions.patterns.forEach((pattern) => {
|
||||
const searchPattern = path.join(absoluteDir, pattern);
|
||||
const ignorePatterns = mergedOptions.ignore.map((ig) =>
|
||||
path.join(absoluteDir, ig)
|
||||
);
|
||||
|
||||
const files = glob.sync(searchPattern, {
|
||||
ignore: ignorePatterns,
|
||||
absolute: true,
|
||||
});
|
||||
|
||||
filesToProcess.push(...files);
|
||||
});
|
||||
|
||||
// 去重
|
||||
filesToProcess = [...new Set(filesToProcess)];
|
||||
}
|
||||
|
||||
stats.totalFiles = filesToProcess.length;
|
||||
logger(`📄 找到 ${stats.totalFiles} 个文件待处理`);
|
||||
|
||||
if (mergedOptions.dryRun) {
|
||||
logger("🔍 预览模式:以下文件将被处理(不会实际修改):");
|
||||
filesToProcess.forEach((file) => logger(` - ${file}`));
|
||||
return stats;
|
||||
}
|
||||
|
||||
// 处理每个文件
|
||||
filesToProcess.forEach((filePath) => {
|
||||
stats.processedFiles++;
|
||||
|
||||
const result = processSingleFileSafe(filePath, mergedOptions);
|
||||
|
||||
if (result.success) {
|
||||
if (result.modified) {
|
||||
stats.modifiedFiles++;
|
||||
if (result.backupPath) {
|
||||
stats.backupFiles.push(result.backupPath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stats.failedFiles++;
|
||||
stats.errors.push({
|
||||
file: filePath,
|
||||
error: result.error,
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger(`❌ 批量处理失败: ${error.message}`);
|
||||
stats.errors.push({
|
||||
file: "BATCH_PROCESS",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
|
||||
// 输出统计信息
|
||||
logger("\n📊 处理完成统计:");
|
||||
logger(` 总文件数: ${stats.totalFiles}`);
|
||||
logger(` 已处理: ${stats.processedFiles}`);
|
||||
logger(` 已修改: ${stats.modifiedFiles}`);
|
||||
logger(` 失败: ${stats.failedFiles}`);
|
||||
logger(` 备份文件: ${stats.backupFiles.length}`);
|
||||
|
||||
if (stats.errors.length > 0) {
|
||||
logger("\n❌ 错误详情:");
|
||||
stats.errors.forEach((err) => {
|
||||
logger(` ${err.file}: ${err.error}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (stats.backupFiles.length > 0) {
|
||||
logger("\n📋 备份文件位置:");
|
||||
stats.backupFiles.forEach((backup) => {
|
||||
logger(` ${backup}`);
|
||||
});
|
||||
}
|
||||
|
||||
logger("✨ 批量替换完成!");
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
// 导出工具函数供直接使用
|
||||
export {
|
||||
generateRandomParam,
|
||||
processFileContent,
|
||||
processSingleFile,
|
||||
processBatchFiles,
|
||||
createBackup,
|
||||
restoreFromBackup,
|
||||
processSingleFileSafe,
|
||||
batchReplaceWithRandomCache,
|
||||
};
|
||||
Reference in New Issue
Block a user