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() {
|
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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user