fix: avoid duplicate names in task detail zips
This commit is contained in:
parent
91aad7bb5a
commit
8dd8ebac7e
@ -1,6 +1,6 @@
|
||||
/** @jest-environment node */
|
||||
|
||||
import { generatePreviewImages } from '@/lib/task-material-processor'
|
||||
import { generatePreviewImages, resolveUniqueZipEntryName } from '@/lib/task-material-processor'
|
||||
import { uploadPublicDisplayToS3 } from '@/lib/public-display'
|
||||
import { uploadToS3 } from '@/lib/s3'
|
||||
import { compressImage } from '@/lib/image-processor'
|
||||
@ -80,4 +80,14 @@ describe('task-material-processor', () => {
|
||||
)
|
||||
expect(uploadToS3).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('resolveUniqueZipEntryName 应避免同名文件在解压时互相覆盖', () => {
|
||||
const usedNames = new Set<string>()
|
||||
|
||||
expect(resolveUniqueZipEntryName('01.jpg', 0, usedNames)).toBe('01.jpg')
|
||||
expect(resolveUniqueZipEntryName('01.jpg', 1, usedNames)).toBe('01-2.jpg')
|
||||
expect(resolveUniqueZipEntryName('01.JPG', 2, usedNames)).toBe('01-3.JPG')
|
||||
expect(resolveUniqueZipEntryName('主图/01.jpg', 3, usedNames)).toBe('主图_01.jpg')
|
||||
expect(resolveUniqueZipEntryName('', 4, usedNames)).toBe('file-005')
|
||||
})
|
||||
})
|
||||
|
||||
@ -68,6 +68,46 @@ function normalizeMaterialAssetFileUrl(value: unknown): string {
|
||||
return String(value || '');
|
||||
}
|
||||
|
||||
export function resolveUniqueZipEntryName(
|
||||
originalName: string | null | undefined,
|
||||
index: number,
|
||||
usedNames: Set<string>,
|
||||
): string {
|
||||
const fallbackName = `file-${String(index + 1).padStart(3, '0')}`;
|
||||
const safeName = String(originalName || fallbackName)
|
||||
.replace(/[\0\r\n]/g, ' ')
|
||||
.replace(/[\/\\]+/g, '_')
|
||||
.trim() || fallbackName;
|
||||
|
||||
const addIfUnique = (candidate: string) => {
|
||||
const key = candidate.toLowerCase();
|
||||
if (usedNames.has(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
usedNames.add(key);
|
||||
return true;
|
||||
};
|
||||
|
||||
if (addIfUnique(safeName)) {
|
||||
return safeName;
|
||||
}
|
||||
|
||||
const ext = path.extname(safeName);
|
||||
const baseName = (ext ? safeName.slice(0, -ext.length) : safeName) || fallbackName;
|
||||
|
||||
for (let duplicateIndex = 2; duplicateIndex < 10000; duplicateIndex += 1) {
|
||||
const candidate = `${baseName}-${duplicateIndex}${ext}`;
|
||||
if (addIfUnique(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
const lastResortName = `${baseName}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}${ext}`;
|
||||
usedNames.add(lastResortName.toLowerCase());
|
||||
return lastResortName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从chunk临时目录打包文件成zip
|
||||
*/
|
||||
@ -205,11 +245,18 @@ export async function createZipFromFiles(
|
||||
// 连接archive到输出流
|
||||
archive.pipe(output);
|
||||
|
||||
// 添加每个文件到zip
|
||||
for (const file of files) {
|
||||
console.log(`[Zip] Adding file: ${file.name} (${file.size} bytes)`);
|
||||
archive.file(file.path, { name: file.name });
|
||||
}
|
||||
// 添加每个文件到zip。上传文件可能来自多个文件夹,文件名会重复;
|
||||
// ZIP 内同名条目在很多解压软件中会互相覆盖,因此这里必须保证条目名唯一。
|
||||
const usedZipEntryNames = new Set<string>();
|
||||
files.forEach((file, index) => {
|
||||
const zipEntryName = resolveUniqueZipEntryName(file.name, index, usedZipEntryNames);
|
||||
if (zipEntryName !== file.name) {
|
||||
console.warn(`[Zip] Duplicate or unsafe file name adjusted: ${file.name} -> ${zipEntryName}`);
|
||||
}
|
||||
|
||||
console.log(`[Zip] Adding file: ${zipEntryName} (${file.size} bytes)`);
|
||||
archive.file(file.path, { name: zipEntryName });
|
||||
});
|
||||
|
||||
// 完成压缩
|
||||
archive.finalize();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user