gongxiangp2/scripts/verify-migrated-files.ts
chenpei 4dba93c4e5 feat: 完成任务-详情素材迁移到新逻辑
- 迁移57个老素材到新逻辑(本地zip + S3预览图)
- 优化文件命名:SPU_作者_店铺_类型.zip
- PSD文件单独存储,不打包进all.zip
- 修复下载接口,优先使用本地zip文件
- 修复预览图重复问题
- 添加迁移脚本和验证脚本
- 更新文档和迁移计划

迁移统计:
- 总素材数:77个
- 成功迁移:57个
- 新逻辑上传:18个
- 空记录:2个
2026-01-21 10:16:28 +08:00

149 lines
4.9 KiB
TypeScript

/**
* 验证已迁移素材的文件是否真实存在
*/
import { PrismaClient } from '@prisma/client';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
const prisma = new PrismaClient();
async function checkFileExists(filePath: string): Promise<boolean> {
try {
const remotePath = `/mnt/PoolOne/apps/youyidian-3d/${filePath}`;
const { stdout } = await execAsync(`ssh root@192.168.58.20 "test -f ${remotePath} && echo 'exists' || echo 'not_found'"`);
return stdout.trim() === 'exists';
} catch (error) {
return false;
}
}
async function main() {
console.log('开始验证所有"任务-详情"素材的文件...\n');
// 查询所有素材(不管 zipFilePath 是否为空)
const materials = await prisma.material.findMany({
where: {
materialType: '任务-详情',
deletedAt: null,
},
select: {
id: true,
productModel: true,
zipFilePath: true,
zipFileName: true,
taskNo: true,
user: {
select: {
realName: true,
username: true,
},
},
assets: {
where: {
assetType: { in: ['main', 'sku', 'detail', 'all'] },
},
select: {
assetType: true,
fileUrl: true,
fileName: true,
},
},
},
orderBy: { createdAt: 'desc' },
});
console.log(`找到 ${materials.length} 个"任务-详情"素材\n`);
const results = {
total: materials.length,
allFilesExist: 0,
someFilesMissing: 0,
allFilesMissing: 0,
noZipRecords: 0,
details: [] as any[],
};
for (const material of materials) {
const authorName = material.user?.realName || material.user?.username || '未知';
const zipAssets = material.assets.filter(a => a.fileUrl.startsWith('uploads/'));
if (zipAssets.length === 0) {
console.log(`❌ ${material.productModel.padEnd(20)} (${authorName.padEnd(10)}): 没有本地 zip 文件记录 | 任务号: ${material.taskNo || '无'}`);
results.noZipRecords++;
results.details.push({
productModel: material.productModel,
authorName,
taskNo: material.taskNo,
status: 'no_zip_records',
zipFilePath: material.zipFilePath,
});
continue;
}
const fileChecks = await Promise.all(
zipAssets.map(async (asset) => {
const exists = await checkFileExists(asset.fileUrl);
return { assetType: asset.assetType, fileUrl: asset.fileUrl, exists };
})
);
const existsCount = fileChecks.filter(c => c.exists).length;
const missingFiles = fileChecks.filter(c => !c.exists);
if (existsCount === zipAssets.length) {
console.log(`✅ ${material.productModel.padEnd(20)} (${authorName.padEnd(10)}): 所有文件存在 (${existsCount}/${zipAssets.length})`);
results.allFilesExist++;
} else if (existsCount > 0) {
console.log(`⚠️ ${material.productModel.padEnd(20)} (${authorName.padEnd(10)}): 部分文件缺失 (${existsCount}/${zipAssets.length})`);
missingFiles.forEach(f => console.log(` - 缺失: ${f.assetType} (${f.fileUrl})`));
results.someFilesMissing++;
results.details.push({
productModel: material.productModel,
authorName,
taskNo: material.taskNo,
status: 'partial',
missingFiles: missingFiles.map(f => f.assetType),
});
} else {
console.log(`❌ ${material.productModel.padEnd(20)} (${authorName.padEnd(10)}): 所有文件缺失 (0/${zipAssets.length}) | 任务号: ${material.taskNo || '无'}`);
missingFiles.forEach(f => console.log(` - 缺失: ${f.assetType} (${f.fileUrl})`));
results.allFilesMissing++;
results.details.push({
productModel: material.productModel,
authorName,
taskNo: material.taskNo,
status: 'all_missing',
zipFilePath: material.zipFilePath,
});
}
}
console.log('\n' + '='.repeat(80));
console.log('验证结果统计');
console.log('='.repeat(80));
console.log(`总计: ${results.total} 个素材`);
console.log(`✅ 所有文件存在: ${results.allFilesExist} 个`);
console.log(`⚠️ 部分文件缺失: ${results.someFilesMissing} 个`);
console.log(`❌ 所有文件缺失: ${results.allFilesMissing} 个`);
console.log(`❌ 没有 zip 记录: ${results.noZipRecords} 个`);
console.log(`\n需要迁移的素材总数: ${results.allFilesMissing + results.noZipRecords} 个`);
if (results.details.length > 0) {
console.log('\n' + '='.repeat(80));
console.log('需要迁移的素材详情:');
console.log('='.repeat(80));
results.details.forEach(d => {
console.log(` - ${d.productModel.padEnd(20)} (${d.authorName.padEnd(10)}): ${d.status.padEnd(20)} | 任务号: ${d.taskNo || '无'}`);
if (d.missingFiles) {
console.log(` 缺失类型: ${d.missingFiles.join(', ')}`);
}
});
}
await prisma.$disconnect();
}
main();