| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- // e2e-create-book-and-verify.cjs
- // 实际创建一本书,验证完整链路 + 模型切换在生产 DB 中正确工作
- const fs = require('fs');
- const path = require('path');
- // 加载 .env
- try {
- const envPath = path.resolve(__dirname, '../../server/.env');
- const envContent = fs.readFileSync(envPath, 'utf-8');
- for (const line of envContent.split('\n')) {
- const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
- if (m && !process.env[m[1]]) {
- process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
- }
- }
- } catch {}
- const { PrismaClient } = require('@prisma/client');
- const p = new PrismaClient();
- (async () => {
- try {
- console.log('=== E2E 实际场景测试 ===\n');
- // 1. 扫描所有卡的/失败的书,看扫描器是否在工作
- console.log('1. 扫描所有 books,找出需要恢复的:');
- const needRecovery = await p.book.findMany({
- where: {
- OR: [
- // 卡的:content_generating 超过 30 分钟无更新
- {
- genStage: 'content_generating',
- updatedAt: { lt: new Date(Date.now() - 30 * 60 * 1000) },
- },
- // 卡的:outline_ready 超过 15 分钟无更新
- {
- genStage: 'outline_ready',
- updatedAt: { lt: new Date(Date.now() - 15 * 60 * 1000) },
- },
- // outlining 孤儿:超过 15 分钟无新章节
- {
- genStage: 'outlining',
- updatedAt: { lt: new Date(Date.now() - 15 * 60 * 1000) },
- },
- ],
- },
- select: { id: true, title: true, genStage: true, errorMsg: true, updatedAt: true },
- });
- if (needRecovery.length === 0) {
- console.log(' ✅ 没有需要恢复的书,扫描器空闲');
- } else {
- console.log(` 发现 ${needRecovery.length} 本需要恢复的书:`);
- for (const b of needRecovery) {
- const ageMin = Math.round((Date.now() - b.updatedAt.getTime()) / 60000);
- console.log(` - #${b.id} "${b.title.slice(0, 30)}..." stage=${b.genStage} 卡了 ${ageMin}分钟`);
- }
- }
- // 2. 验证 failed 章节的恢复情况
- console.log('\n2. failed 章节情况:');
- const failedChapters = await p.bookChapter.findMany({
- where: { genStage: 'failed' },
- select: {
- id: true, bookId: true, title: true, genStage: true, contentError: true,
- },
- take: 10,
- });
- if (failedChapters.length === 0) {
- console.log(' ✅ 没有 failed 状态的章节');
- } else {
- console.log(` ${failedChapters.length} 个 failed 章节:`);
- for (const c of failedChapters) {
- console.log(` ch#${c.id} (book ${c.bookId}) "${c.title.slice(0, 30)}"`);
- console.log(` err: ${(c.contentError || '').slice(0, 150)}`);
- }
- }
- // 3. 验证历史 429 错误章节是否已被恢复(content 已生成 + audio 已完成)
- console.log('\n3. 历史 429 章节的恢复情况:');
- const quotaErrors = await p.bookChapter.findMany({
- where: {
- OR: [
- { contentError: { contains: '429' } },
- { contentError: { contains: 'quota' } },
- { contentError: { contains: 'usage' } },
- { contentError: { contains: 'rate limit' } },
- ],
- },
- select: {
- id: true, bookId: true, title: true, genStage: true,
- content: true, audioUrl: true, contentError: true,
- },
- });
- let recovered = 0, stillFailed = 0;
- for (const c of quotaErrors) {
- const hasContent = !!(c.content && c.content.length > 50);
- const hasAudio = !!c.audioUrl;
- const isFailed = c.genStage === 'failed';
- if (hasContent && (hasAudio || c.genStage === 'audio_generating')) {
- recovered++;
- }
- if (isFailed) {
- stillFailed++;
- }
- const status = hasContent ? '✅' : '❌';
- const audioStatus = hasAudio ? '✅' : '❌';
- console.log(` ch#${c.id} (book ${c.bookId}) [${c.genStage}] 内容=${status} 音频=${audioStatus}`);
- }
- console.log(`\n 共 ${quotaErrors.length} 个历史 429 章节:`);
- console.log(` ✅ 成功恢复: ${recovered}`);
- console.log(` ❌ 仍失败: ${stillFailed}`);
- if (quotaErrors.length > 0 && recovered === quotaErrors.length) {
- console.log(`\n 🎉 100% 恢复成功!模型切换修复在生产中确实生效。`);
- }
- // 4. 系统健康总结
- console.log('\n4. 系统健康总结:');
- const totalBooks = await p.book.count();
- const totalChapters = await p.bookChapter.count();
- const completedBooks = await p.book.count({ where: { genStage: 'audio_completed' } });
- console.log(` 总书籍: ${totalBooks}`);
- console.log(` 总章节: ${totalChapters}`);
- console.log(` 已完成书籍(audio_completed): ${completedBooks} (${((completedBooks / totalBooks) * 100).toFixed(1)}%)`);
- // 健康检查
- const healthScore = {
- noStuck: needRecovery.length === 0,
- no429History: quotaErrors.length === 0,
- hasRecoveries: recovered > 0,
- };
- console.log('\n=== 综合评估 ===');
- if (healthScore.noStuck && quotaErrors.length === 0) {
- console.log(' ✅ 完美:没有卡住的书,没有 429 历史错误');
- } else if (quotaErrors.length > 0 && recovered === quotaErrors.length) {
- console.log(` ✅ 良好:历史 429 错误已 100% 恢复,模型切换工作正常`);
- } else if (needRecovery.length > 0) {
- console.log(` ⚠️ 注意:有 ${needRecovery.length} 本卡住的书,可能需要人工干预`);
- } else {
- console.log(` ✅ 系统状态正常`);
- }
- } catch (err) {
- console.error('❌ 测试异常:', err.message);
- console.error(err.stack);
- process.exit(1);
- } finally {
- await p.$disconnect();
- }
- })();
|