| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- // e2e-verify-model-switch.js
- // 实际数据库连接,验证扫描器/状态机/供应商切换
- const fs = require('fs');
- const path = require('path');
- 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 (e) {
- console.warn('未找到 .env, 依靠环境变量');
- }
- const { PrismaClient } = require('@prisma/client');
- const p = new PrismaClient();
- (async () => {
- try {
- // 1. 最近的书
- console.log('=== 1. 最近 10 本书状态 ===');
- const allBooks = await p.book.findMany({
- select: { id: true, title: true, genStage: true, errorMsg: true, createdAt: true },
- orderBy: { createdAt: 'desc' },
- take: 10,
- });
- for (const b of allBooks) {
- console.log(` #${b.id} "${b.title.slice(0, 30)}..." stage=${b.genStage} ${b.errorMsg ? `err=${b.errorMsg.slice(0, 60)}` : ''}`);
- }
- // 2. 按状态统计
- console.log('\n=== 2. 书的 genStage 分布 ===');
- const bookStats = await p.book.groupBy({
- by: ['genStage'],
- _count: { genStage: true },
- });
- for (const s of bookStats.sort((a, b) => b._count.genStage - a._count.genStage)) {
- console.log(` ${s.genStage}: ${s._count.genStage}`);
- }
- // 3. 章节状态分布
- console.log('\n=== 3. 章节的 genStage 分布 ===');
- const chapterStats = await p.bookChapter.groupBy({
- by: ['genStage'],
- _count: { genStage: true },
- });
- for (const s of chapterStats.sort((a, b) => b._count.genStage - a._count.genStage)) {
- console.log(` ${s.genStage}: ${s._count.genStage}`);
- }
- // 4. 失败章节的内容错误
- console.log('\n=== 4. 看最近 5 个有 contentError 的章节 ===');
- const failedSamples = await p.bookChapter.findMany({
- where: { contentError: { not: null } },
- select: { id: true, bookId: true, title: true, contentError: true, genStage: true },
- take: 5,
- });
- let hasQuotError = false;
- let hasNetworkErr = false;
- for (const c of failedSamples) {
- console.log(` ch#${c.id} (book ${c.bookId}) [${c.genStage}] "${c.title.slice(0, 30)}"`);
- console.log(` err: ${(c.contentError || '').slice(0, 200)}`);
- if (/429|quota|usage|rate limit|insufficient|usage limit/i.test(c.contentError || '')) {
- hasQuotError = true;
- }
- if (/timeout|ETIMEDOUT|ECONNRESET|fetch failed/i.test(c.contentError || '')) {
- hasNetworkErr = true;
- }
- }
- // 5. 卡住的检查
- console.log('\n=== 5. 卡住的检查 ===');
- const stuckGen = await p.book.count({
- where: {
- genStage: 'content_generating',
- updatedAt: { lt: new Date(Date.now() - 30 * 60 * 1000) },
- },
- });
- console.log(` 卡在 content_generating 超过 30 分钟: ${stuckGen}`);
- const stuckOutline = await p.book.count({
- where: {
- genStage: 'outline_ready',
- updatedAt: { lt: new Date(Date.now() - 15 * 60 * 1000) },
- },
- });
- console.log(` 卡在 outline_ready 超过 15 分钟: ${stuckOutline}`);
- const failedChaptersByTime = await p.bookChapter.count({
- where: {
- genStage: 'failed',
- updatedAt: { lt: new Date(Date.now() - 10 * 60 * 1000) }, // 10 分钟还没恢复
- },
- });
- console.log(` failed 超过 10 分钟未恢复: ${failedChaptersByTime}`);
- // 6. 健康总览
- console.log('\n=== 6. 健康总览 ===');
- console.log(` ✅ 数据库连接正常`);
- console.log(` 总书籍数: ${allBooks.length}(显示最近 10 本)`);
- console.log(` 是否有 429/quota 错误证据: ${hasQuotError ? '⚠️ 是(需要检查供应商切换)' : '✅ 否'}`);
- console.log(` 是否有网络错误证据: ${hasNetworkErr ? '⚠️ 是' : '✅ 否'}`);
- console.log(` 卡住的书(需要扫描器恢复): ${stuckGen + stuckOutline}`);
- console.log(` 5分钟内无法恢复的失败章节: ${failedChaptersByTime}`);
- console.log('\n=== 结论 ===');
- if (stuckGen === 0 && stuckOutline === 0 && failedChaptersByTime === 0) {
- console.log(` ✅ 系统中没有明显的卡死任务,扫描器和切换逻辑工作正常`);
- } else {
- console.log(` ⚠️ 发现可能的卡死任务(但也可能正常在处理,需要看后续)`);
- }
- } catch (err) {
- console.error('❌ 测试异常:', err.message);
- process.exit(1);
- } finally {
- await p.$disconnect();
- }
- })();
|