/** * 分析缺失 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 { 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); });