/** * 数据迁移脚本:将旧字段迁移到新的 genStage 字段 * Run: npx ts-node prisma/migrate-genstage.ts */ import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); async function migrate() { console.log('开始数据迁移...'); // ========== Book 迁移 ========== console.log('\n迁移 Book 表...'); const books = await prisma.book.findMany(); let updatedBookCount = 0; for (const book of books) { let newGenStage: string; switch (book.status) { case 'draft': newGenStage = 'draft'; break; case 'planning': newGenStage = 'outline_completed'; break; case 'generating': // 需要检查子章节的实际状态 newGenStage = await inferBookGenStageFromChapters(book.id); break; case 'completed': newGenStage = await inferBookGenStageFromChapters(book.id); break; case 'failed': case 'interrupted': newGenStage = 'failed'; break; default: newGenStage = 'draft'; } await prisma.book.update({ where: { id: book.id }, data: { genStage: newGenStage, failedStage: newGenStage === 'failed' ? book.status : undefined, }, }); updatedBookCount++; console.log(` Book ${book.id}: ${book.status} → ${newGenStage}`); } console.log(`Book 迁移完成: ${updatedBookCount} 条`); // ========== BookChapter 迁移 ========== console.log('\n迁移 BookChapter 表...'); const chapters = await prisma.bookChapter.findMany(); let updatedChapterCount = 0; for (const chapter of chapters) { let newGenStage: string; if (chapter.videoStatus === 'completed') { newGenStage = 'video_completed'; } else if (chapter.videoStatus === 'generating') { newGenStage = 'video_generating'; } else if (chapter.audioStatus === 'completed') { newGenStage = 'audio_completed'; } else if (chapter.audioStatus === 'generating') { newGenStage = 'audio_generating'; } else if (chapter.contentStatus === 'completed') { newGenStage = 'content_completed'; } else if (chapter.contentStatus === 'generating') { newGenStage = 'content_generating'; } else if (chapter.status === 'completed') { // 旧数据只有 status newGenStage = 'content_completed'; } else if (chapter.status === 'failed' || chapter.contentStatus === 'failed' || chapter.audioStatus === 'failed' || chapter.videoStatus === 'failed') { newGenStage = 'failed'; } else { newGenStage = 'idle'; } await prisma.bookChapter.update({ where: { id: chapter.id }, data: { genStage: newGenStage }, }); updatedChapterCount++; console.log(` Chapter ${chapter.id}: status=${chapter.status}, content=${chapter.contentStatus}, audio=${chapter.audioStatus}, video=${chapter.videoStatus} → ${newGenStage}`); } console.log(`BookChapter 迁移完成: ${updatedChapterCount} 条`); console.log('\n✅ 数据迁移完成!'); } /** * 根据子章节状态推断 Book 的 genStage */ async function inferBookGenStageFromChapters(bookId: number): Promise { const chapters = await prisma.bookChapter.findMany({ where: { bookId, level: { gte: 1 }, parentId: 0 }, }); if (chapters.length === 0) return 'draft'; // 检查是否有任意章节已达到 video_completed const hasVideoCompleted = chapters.some(c => c.genStage === 'video_completed' || c.videoStatus === 'completed'); if (hasVideoCompleted) return 'video_completed'; // 检查是否有章节已达到 audio_completed const hasAudioCompleted = chapters.some(c => c.genStage === 'audio_completed' || c.audioStatus === 'completed'); if (hasAudioCompleted) return 'audio_completed'; // 检查是否有章节已达到 content_completed const hasContentCompleted = chapters.some(c => c.genStage === 'content_completed' || c.contentStatus === 'completed'); if (hasContentCompleted) return 'content_generating'; // 部分完成,仍在生成中 return 'content_generating'; } migrate() .catch(console.error) .finally(() => prisma.$disconnect());