perf: COS 部署改为增量同步,只上传变更文件
This commit is contained in:
@@ -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 上所有文件的 ETag(MD5)
|
||||
*/
|
||||
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() {
|
||||
const buildDir = path.join(__dirname, '..', 'build');
|
||||
@@ -238,8 +284,38 @@ async function uploadBuildDir() {
|
||||
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 failed = 0;
|
||||
@@ -248,21 +324,19 @@ async function uploadBuildDir() {
|
||||
// 并发上传(限制并发数)
|
||||
const concurrency = 10;
|
||||
const chunks = [];
|
||||
for (let i = 0; i < files.length; i += concurrency) {
|
||||
chunks.push(files.slice(i, i + concurrency));
|
||||
for (let i = 0; i < filesToUpload.length; i += concurrency) {
|
||||
chunks.push(filesToUpload.slice(i, i + concurrency));
|
||||
}
|
||||
|
||||
for (const chunk of chunks) {
|
||||
await Promise.all(chunk.map(async (filePath) => {
|
||||
const relativePath = path.relative(buildDir, filePath).replace(/\\/g, '/');
|
||||
|
||||
await Promise.all(chunk.map(async ({ filePath, relativePath }) => {
|
||||
try {
|
||||
await uploadFile(filePath, relativePath);
|
||||
uploaded++;
|
||||
|
||||
// 进度显示
|
||||
const progress = Math.round((uploaded + failed) / files.length * 100);
|
||||
process.stdout.write(`\r 进度: ${progress}% (${uploaded}/${files.length})`);
|
||||
const progress = Math.round((uploaded + failed) / filesToUpload.length * 100);
|
||||
process.stdout.write(`\r 进度: ${progress}% (${uploaded}/${filesToUpload.length})`);
|
||||
} catch (err) {
|
||||
failed++;
|
||||
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 };
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user