perf: COS 部署改为增量同步,只上传变更文件

This commit is contained in:
2026-01-09 12:06:13 +08:00
parent ddca64ccd2
commit ff3d32c656

View File

@@ -228,7 +228,53 @@ async function clearBucket() {
} }
/** /**
* 上传整个 build 目录 * 计算文件的 MD5
*/
function getFileMd5(filePath) {
const content = fs.readFileSync(filePath);
return crypto.createHash('md5').update(content).digest('hex');
}
/**
* 获取 COS 上所有文件的 ETagMD5
*/
async function getRemoteFiles() {
return new Promise((resolve, reject) => {
const remoteFiles = {};
let marker = '';
const fetchBatch = () => {
cos.getBucket({
Bucket: config.COS_BUCKET,
Region: config.COS_REGION,
MaxKeys: 1000,
Marker: marker,
}, (err, data) => {
if (err) {
reject(err);
return;
}
data.Contents.forEach(item => {
// COS 的 ETag 是 MD5去掉引号
remoteFiles[item.Key] = item.ETag.replace(/"/g, '');
});
if (data.IsTruncated === 'true') {
marker = data.NextMarker;
fetchBatch();
} else {
resolve(remoteFiles);
}
});
};
fetchBatch();
});
}
/**
* 上传整个 build 目录(增量同步)
*/ */
async function uploadBuildDir() { async function uploadBuildDir() {
const buildDir = path.join(__dirname, '..', 'build'); const buildDir = path.join(__dirname, '..', 'build');
@@ -238,8 +284,38 @@ async function uploadBuildDir() {
process.exit(1); process.exit(1);
} }
const files = getAllFiles(buildDir); // 获取本地文件列表
console.log(`\n\x1b[36m[上传]\x1b[0m 共 ${files.length} 个文件待上传...\n`); const localFiles = getAllFiles(buildDir);
console.log(`\n\x1b[36m[扫描]\x1b[0m 本地共 ${localFiles.length} 个文件`);
// 获取远程文件列表
console.log('\x1b[36m[同步]\x1b[0m 获取 COS 文件列表...');
let remoteFiles = {};
try {
remoteFiles = await getRemoteFiles();
console.log(` COS 上共 ${Object.keys(remoteFiles).length} 个文件`);
} catch (err) {
console.log(`\x1b[33m[警告]\x1b[0m 获取远程文件列表失败,将全量上传: ${err.message}`);
}
// 比对找出需要上传的文件
const filesToUpload = [];
for (const filePath of localFiles) {
const relativePath = path.relative(buildDir, filePath).replace(/\\/g, '/');
const localMd5 = getFileMd5(filePath);
const remoteMd5 = remoteFiles[relativePath];
if (localMd5 !== remoteMd5) {
filesToUpload.push({ filePath, relativePath });
}
}
console.log(`\x1b[36m[上传]\x1b[0m 需要上传 ${filesToUpload.length} 个文件(跳过 ${localFiles.length - filesToUpload.length} 个未变更)\n`);
if (filesToUpload.length === 0) {
console.log('\x1b[32m[✓]\x1b[0m 所有文件已是最新,无需上传');
return { uploaded: 0, failed: 0, skipped: localFiles.length };
}
let uploaded = 0; let uploaded = 0;
let failed = 0; let failed = 0;
@@ -248,21 +324,19 @@ async function uploadBuildDir() {
// 并发上传(限制并发数) // 并发上传(限制并发数)
const concurrency = 10; const concurrency = 10;
const chunks = []; const chunks = [];
for (let i = 0; i < files.length; i += concurrency) { for (let i = 0; i < filesToUpload.length; i += concurrency) {
chunks.push(files.slice(i, i + concurrency)); chunks.push(filesToUpload.slice(i, i + concurrency));
} }
for (const chunk of chunks) { for (const chunk of chunks) {
await Promise.all(chunk.map(async (filePath) => { await Promise.all(chunk.map(async ({ filePath, relativePath }) => {
const relativePath = path.relative(buildDir, filePath).replace(/\\/g, '/');
try { try {
await uploadFile(filePath, relativePath); await uploadFile(filePath, relativePath);
uploaded++; uploaded++;
// 进度显示 // 进度显示
const progress = Math.round((uploaded + failed) / files.length * 100); const progress = Math.round((uploaded + failed) / filesToUpload.length * 100);
process.stdout.write(`\r 进度: ${progress}% (${uploaded}/${files.length})`); process.stdout.write(`\r 进度: ${progress}% (${uploaded}/${filesToUpload.length})`);
} catch (err) { } catch (err) {
failed++; failed++;
errors.push({ file: relativePath, error: err.message }); errors.push({ file: relativePath, error: err.message });
@@ -279,9 +353,9 @@ async function uploadBuildDir() {
}); });
} }
console.log(`\x1b[32m[✓]\x1b[0m 上传完成: 成功 ${uploaded},失败 ${failed}`); console.log(`\x1b[32m[✓]\x1b[0m 上传完成: 成功 ${uploaded}跳过 ${localFiles.length - filesToUpload.length}失败 ${failed}`);
return { uploaded, failed }; return { uploaded, failed, skipped: localFiles.length - filesToUpload.length };
} }
// ============================================================================ // ============================================================================