/** * 数据验证脚本:验证模板系统数据完整性 * * 功能: * 1. 验证所有模板的必填字段 * 2. 验证素材的templateId引用 * 3. 验证字段的templateId引用 * 4. 检查孤立数据 */ import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() interface ValidationResult { category: string passed: boolean message: string details?: any } async function verifyTemplateData(): Promise { console.log('开始数据验证...\n') const results: ValidationResult[] = [] let allPassed = true try { // 1. 验证模板数据 console.log('验证 1: 检查模板数据...') // 1.1 检查必填字段 const templatesWithoutName = await prisma.uploadTemplate.count({ where: { name: { equals: '' } } }) results.push({ category: '模板', passed: templatesWithoutName === 0, message: `模板名称完整性`, details: templatesWithoutName === 0 ? '✓ 所有模板都有名称' : `✗ ${templatesWithoutName} 个模板没有名称` }) // 1.2 检查version字段 const templatesWithoutVersion = await prisma.uploadTemplate.count({ where: { OR: [ { version: { equals: 0 } }, { version: { lt: 1 } } ] } }) results.push({ category: '模板', passed: templatesWithoutVersion === 0, message: `模板version字段`, details: templatesWithoutVersion === 0 ? '✓ 所有模板都有version' : `✗ ${templatesWithoutVersion} 个模板没有version` }) // 1.3 检查materialKind字段 const validKinds = ['IMAGE', 'VIDEO', 'AUDIO', 'OTHER'] const templatesWithInvalidKind = await prisma.uploadTemplate.count({ where: { materialKind: { notIn: validKinds } } }) results.push({ category: '模板', passed: templatesWithInvalidKind === 0, message: `模板materialKind有效性`, details: templatesWithInvalidKind === 0 ? '✓ 所有模板的materialKind都有效' : `✗ ${templatesWithInvalidKind} 个模板的materialKind无效` }) // 2. 验证素材数据 console.log('验证 2: 检查素材数据...') // 2.1 检查未删除素材的templateId const materialsWithoutTemplate = await prisma.material.count({ where: { templateId: null, deletedAt: null } }) results.push({ category: '素材', passed: materialsWithoutTemplate === 0, message: `素材templateId完整性`, details: materialsWithoutTemplate === 0 ? '✓ 所有素材都有templateId' : `✗ ${materialsWithoutTemplate} 个素材没有templateId` }) // 2.2 检查templateId引用的有效性 const materialsWithInvalidTemplate = await prisma.material.findMany({ where: { NOT: { templateId: null }, deletedAt: null, template: null }, select: { id: true, templateId: true } }) results.push({ category: '素材', passed: materialsWithInvalidTemplate.length === 0, message: `素材templateId引用有效性`, details: materialsWithInvalidTemplate.length === 0 ? '✓ 所有素材的templateId引用都有效' : `✗ ${materialsWithInvalidTemplate.length} 个素材的templateId引用无效` }) // 3. 验证字段数据 console.log('验证 3: 检查字段数据...') // 3.1 检查字段的templateId const fieldsWithoutTemplate = await prisma.uploadField.count({ where: { OR: [ { templateId: { equals: '' } }, { templateId: { equals: null as any } } ] } }) results.push({ category: '字段', passed: fieldsWithoutTemplate === 0, message: `字段templateId完整性`, details: fieldsWithoutTemplate === 0 ? '✓ 所有字段都有templateId' : `✗ ${fieldsWithoutTemplate} 个字段没有templateId` }) // 3.2 检查字段key的唯一性(同一模板内) const duplicateFieldKeys = await prisma.$queryRaw>` SELECT "templateId", "key", COUNT(*) as count FROM "UploadField" GROUP BY "templateId", "key" HAVING COUNT(*) > 1 ` results.push({ category: '字段', passed: duplicateFieldKeys.length === 0, message: `字段key唯一性`, details: duplicateFieldKeys.length === 0 ? '✓ 所有字段key在模板内唯一' : `✗ ${duplicateFieldKeys.length} 个重复的字段key` }) // 4. 验证字段池数据 console.log('验证 4: 检查字段池数据...') // 4.1 检查字段池key的唯一性 const duplicateFieldDefKeys = await prisma.$queryRaw>` SELECT "key", COUNT(*) as count FROM "FieldDefinition" GROUP BY "key" HAVING COUNT(*) > 1 ` results.push({ category: '字段池', passed: duplicateFieldDefKeys.length === 0, message: `字段池key唯一性`, details: duplicateFieldDefKeys.length === 0 ? '✓ 所有字段池key唯一' : `✗ ${duplicateFieldDefKeys.length} 个重复的字段池key` }) // 5. 检查孤立数据 console.log('验证 5: 检查孤立数据...') // 5.1 检查没有字段的模板 const templatesWithoutFields = await prisma.uploadTemplate.findMany({ where: { fields: { none: {} } }, select: { id: true, status: true } }) results.push({ category: '孤立数据', passed: true, // 这不是错误,只是警告 message: `没有字段的模板`, details: templatesWithoutFields.length === 0 ? '✓ 所有模板都有字段' : `⚠ ${templatesWithoutFields.length} 个模板没有字段(可能是草稿)` }) // 5.2 检查没有素材的模板 const templatesWithoutMaterials = await prisma.uploadTemplate.findMany({ where: { materials: { none: {} } }, select: { id: true, status: true } }) results.push({ category: '孤立数据', passed: true, // 这不是错误,只是警告 message: `没有素材的模板`, details: templatesWithoutMaterials.length === 0 ? '✓ 所有模板都有素材' : `⚠ ${templatesWithoutMaterials.length} 个模板没有素材(可能是新建的)` }) // 6. 生成验证报告 console.log('\n' + '='.repeat(80)) console.log('验证报告') console.log('='.repeat(80)) const groupedResults = results.reduce((acc, result) => { if (!acc[result.category]) { acc[result.category] = [] } acc[result.category].push(result) return acc }, {} as Record) for (const [category, categoryResults] of Object.entries(groupedResults)) { console.log(`\n${category}:`) for (const result of categoryResults) { const icon = result.passed ? '✓' : '✗' console.log(` ${icon} ${result.message}`) console.log(` ${result.details}`) if (!result.passed) { allPassed = false } } } // 7. 统计信息 console.log('\n' + '='.repeat(80)) console.log('统计信息') console.log('='.repeat(80)) const totalTemplates = await prisma.uploadTemplate.count() const activeTemplates = await prisma.uploadTemplate.count({ where: { status: 'active' } }) const totalFields = await prisma.uploadField.count() const totalMaterials = await prisma.material.count({ where: { deletedAt: null } }) const totalFieldDefs = await prisma.fieldDefinition.count() const enabledFieldDefs = await prisma.fieldDefinition.count({ where: { isFilterEnabled: true } }) console.log(`模板总数: ${totalTemplates} (活跃: ${activeTemplates})`) console.log(`字段总数: ${totalFields}`) console.log(`素材总数: ${totalMaterials}`) console.log(`字段池定义: ${totalFieldDefs} (启用筛选: ${enabledFieldDefs})`) console.log('='.repeat(80)) if (allPassed) { console.log('\n✓ 所有验证通过!') } else { console.warn('\n⚠ 部分验证失败,请检查上述错误') } return allPassed } catch (error) { console.error('\n✗ 验证过程出错:', error) throw error } finally { await prisma.$disconnect() } } // 执行验证 if (require.main === module) { verifyTemplateData() .then((success) => { process.exit(success ? 0 : 1) }) .catch((error) => { console.error('验证脚本执行失败:', error) process.exit(1) }) } export { verifyTemplateData }