/** * 为没有封面的课程批量生成封面 * 使用方法: npx ts-node scripts/generate-missing-covers.ts */ import { PrismaClient } from '@prisma/client'; import { generateCourseCover } from '../src/services/coverImageService'; import { logger } from '../src/utils/logger'; const prisma = new PrismaClient(); async function generateMissingCovers() { try { // 1. 查找所有没有封面的课程(⚠️ 只处理没有封面的,不替换已有封面) // 注意:用户可能已经上传了自己的封面,我们不应该替换它们 const coursesWithoutCover = await prisma.course.findMany({ where: { deletedAt: null, coverImage: null, title: { not: '' }, }, take: 100, }); logger.info(`[GenerateMissingCovers] 找到 ${coursesWithoutCover.length} 个需要生成/重新生成封面的课程`); if (coursesWithoutCover.length === 0) { logger.info('[GenerateMissingCovers] 没有需要生成封面的课程'); return; } // 2. 为每个课程生成封面 let successCount = 0; let failCount = 0; for (const course of coursesWithoutCover) { try { const title = course.title || '未命名课程'; logger.info(`[GenerateMissingCovers] 正在为课程生成封面: ${course.id}, title="${title}"`); const coverImagePath = await generateCourseCover(course.id, title, 'full'); if (coverImagePath) { // 更新数据库 await prisma.course.update({ where: { id: course.id }, data: { coverImage: coverImagePath }, }); logger.info(`[GenerateMissingCovers] ✅ 封面生成成功: ${course.id}, path=${coverImagePath}`); successCount++; } else { logger.warn(`[GenerateMissingCovers] ⚠️ 封面生成返回空路径: ${course.id}`); failCount++; } } catch (error: any) { logger.error(`[GenerateMissingCovers] ❌ 为课程生成封面失败: ${course.id}`, error); failCount++; } } logger.info(`[GenerateMissingCovers] 完成!成功: ${successCount}, 失败: ${failCount}`); } catch (error: any) { logger.error('[GenerateMissingCovers] 批量生成封面失败', error); throw error; } finally { await prisma.$disconnect(); } } // 执行 generateMissingCovers() .then(() => { console.log('✅ 批量生成封面完成'); process.exit(0); }) .catch((error) => { console.error('❌ 批量生成封面失败:', error); process.exit(1); });