gongxiangp2/scripts/analyze-missing-assets.ts

182 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 分析缺失 material_assets 记录的素材
* 只读分析,不做任何修改
*/
import { prisma } from '../lib/prisma';
import { access } from 'fs/promises';
import path from 'path';
interface AnalysisResult {
materialId: string;
materialType: string;
productModel: string;
previewUrl: string;
zipFileName: string;
zipFileSize: bigint;
createdAt: Date;
hasPreviewUrl: boolean;
hasZipFile: boolean;
zipFileExists: boolean;
previewUrlAccessible: boolean;
}
async function checkFileExists(filePath: string): Promise<boolean> {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
async function analyzeMissingAssets() {
console.log('========== 分析缺失 material_assets 记录的素材 ==========\n');
// 查询所有任务类型的素材
const taskMaterialTypes = [
'任务-3D原图',
'任务-AI原图',
'任务-实拍原图',
'任务-视频截图',
'任务-详情',
];
const materials = await prisma.material.findMany({
where: {
materialType: { in: taskMaterialTypes },
deletedAt: null,
},
select: {
id: true,
materialType: true,
productModel: true,
previewUrl: true,
zipFileName: true,
zipFileSize: true,
createdAt: true,
assets: {
select: { id: true },
},
},
orderBy: { createdAt: 'asc' },
});
console.log(`总共找到 ${materials.length} 个任务类型素材\n`);
// 筛选出没有 assets 记录的素材
const missingAssets = materials.filter(m => m.assets.length === 0);
console.log(`其中 ${missingAssets.length} 个素材缺失 material_assets 记录\n`);
if (missingAssets.length === 0) {
console.log('✅ 所有素材都有完整的 assets 记录!');
return;
}
// 按类型分组统计
const byType: { [key: string]: number } = {};
missingAssets.forEach(m => {
byType[m.materialType] = (byType[m.materialType] || 0) + 1;
});
console.log('按类型统计:');
Object.entries(byType).forEach(([type, count]) => {
console.log(` ${type}: ${count} 个`);
});
console.log('');
// 详细分析每个素材
console.log('详细分析:');
console.log('='.repeat(100));
const results: AnalysisResult[] = [];
for (const material of missingAssets) {
const hasPreviewUrl = !!material.previewUrl && material.previewUrl.trim().length > 0;
const hasZipFile = !!material.zipFileName && material.zipFileName.trim().length > 0;
// 检查 zip 文件是否存在
let zipFileExists = false;
if (hasZipFile) {
const zipPath = path.join(process.cwd(), material.zipFileName);
zipFileExists = await checkFileExists(zipPath);
}
// 检查预览图 URL 是否可访问(简单判断是否为有效 URL)
const previewUrlAccessible = hasPreviewUrl && (
material.previewUrl.startsWith('http://') ||
material.previewUrl.startsWith('https://')
);
results.push({
materialId: material.id,
materialType: material.materialType,
productModel: material.productModel || '',
previewUrl: material.previewUrl || '',
zipFileName: material.zipFileName || '',
zipFileSize: material.zipFileSize,
createdAt: material.createdAt,
hasPreviewUrl,
hasZipFile,
zipFileExists,
previewUrlAccessible,
});
const status = zipFileExists && previewUrlAccessible ? '✅' : '⚠️';
console.log(`${status} ${material.materialType} | ${material.productModel || '(无型号)'}`);
console.log(` ID: ${material.id}`);
console.log(` 创建时间: ${material.createdAt.toISOString()}`);
console.log(` 预览图: ${hasPreviewUrl ? (previewUrlAccessible ? '✅ 有效' : '❌ 无效') : '❌ 缺失'}`);
console.log(` ZIP文件: ${hasZipFile ? (zipFileExists ? '✅ 存在' : '❌ 不存在') : '❌ 缺失'}`);
if (hasZipFile) {
console.log(` ZIP路径: ${material.zipFileName}`);
}
console.log('');
}
console.log('='.repeat(100));
console.log('\n汇总统计:');
const validCount = results.filter(r => r.zipFileExists && r.previewUrlAccessible).length;
const invalidCount = results.length - validCount;
console.log(` ✅ 可安全迁移: ${validCount} 个(文件完整)`);
console.log(` ⚠️ 需要检查: ${invalidCount} 个(文件缺失或无效)`);
// 输出可迁移的素材ID列表
if (validCount > 0) {
console.log('\n可安全迁移的素材ID列表:');
results
.filter(r => r.zipFileExists && r.previewUrlAccessible)
.forEach(r => {
console.log(` ${r.materialId} | ${r.materialType} | ${r.productModel}`);
});
}
// 输出需要检查的素材ID列表
if (invalidCount > 0) {
console.log('\n⚠️ 需要人工检查的素材ID列表:');
results
.filter(r => !r.zipFileExists || !r.previewUrlAccessible)
.forEach(r => {
const issues: string[] = [];
if (!r.zipFileExists) issues.push('ZIP文件不存在');
if (!r.previewUrlAccessible) issues.push('预览图无效');
console.log(` ${r.materialId} | ${r.materialType} | ${r.productModel} | ${issues.join(', ')}`);
});
}
console.log('\n========== 分析完成 ==========');
}
// 执行分析
analyzeMissingAssets()
.then(() => {
console.log('\n✅ 分析成功完成');
process.exit(0);
})
.catch((error) => {
console.error('\n❌ 分析失败:', error);
process.exit(1);
});