verify-model-switch.cjs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. // e2e-verify-model-switch.js
  2. // 实际数据库连接,验证扫描器/状态机/供应商切换
  3. const fs = require('fs');
  4. const path = require('path');
  5. try {
  6. const envPath = path.resolve(__dirname, '../../server/.env');
  7. const envContent = fs.readFileSync(envPath, 'utf-8');
  8. for (const line of envContent.split('\n')) {
  9. const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
  10. if (m && !process.env[m[1]]) {
  11. process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
  12. }
  13. }
  14. } catch (e) {
  15. console.warn('未找到 .env, 依靠环境变量');
  16. }
  17. const { PrismaClient } = require('@prisma/client');
  18. const p = new PrismaClient();
  19. (async () => {
  20. try {
  21. // 1. 最近的书
  22. console.log('=== 1. 最近 10 本书状态 ===');
  23. const allBooks = await p.book.findMany({
  24. select: { id: true, title: true, genStage: true, errorMsg: true, createdAt: true },
  25. orderBy: { createdAt: 'desc' },
  26. take: 10,
  27. });
  28. for (const b of allBooks) {
  29. console.log(` #${b.id} "${b.title.slice(0, 30)}..." stage=${b.genStage} ${b.errorMsg ? `err=${b.errorMsg.slice(0, 60)}` : ''}`);
  30. }
  31. // 2. 按状态统计
  32. console.log('\n=== 2. 书的 genStage 分布 ===');
  33. const bookStats = await p.book.groupBy({
  34. by: ['genStage'],
  35. _count: { genStage: true },
  36. });
  37. for (const s of bookStats.sort((a, b) => b._count.genStage - a._count.genStage)) {
  38. console.log(` ${s.genStage}: ${s._count.genStage}`);
  39. }
  40. // 3. 章节状态分布
  41. console.log('\n=== 3. 章节的 genStage 分布 ===');
  42. const chapterStats = await p.bookChapter.groupBy({
  43. by: ['genStage'],
  44. _count: { genStage: true },
  45. });
  46. for (const s of chapterStats.sort((a, b) => b._count.genStage - a._count.genStage)) {
  47. console.log(` ${s.genStage}: ${s._count.genStage}`);
  48. }
  49. // 4. 失败章节的内容错误
  50. console.log('\n=== 4. 看最近 5 个有 contentError 的章节 ===');
  51. const failedSamples = await p.bookChapter.findMany({
  52. where: { contentError: { not: null } },
  53. select: { id: true, bookId: true, title: true, contentError: true, genStage: true },
  54. take: 5,
  55. });
  56. let hasQuotError = false;
  57. let hasNetworkErr = false;
  58. for (const c of failedSamples) {
  59. console.log(` ch#${c.id} (book ${c.bookId}) [${c.genStage}] "${c.title.slice(0, 30)}"`);
  60. console.log(` err: ${(c.contentError || '').slice(0, 200)}`);
  61. if (/429|quota|usage|rate limit|insufficient|usage limit/i.test(c.contentError || '')) {
  62. hasQuotError = true;
  63. }
  64. if (/timeout|ETIMEDOUT|ECONNRESET|fetch failed/i.test(c.contentError || '')) {
  65. hasNetworkErr = true;
  66. }
  67. }
  68. // 5. 卡住的检查
  69. console.log('\n=== 5. 卡住的检查 ===');
  70. const stuckGen = await p.book.count({
  71. where: {
  72. genStage: 'content_generating',
  73. updatedAt: { lt: new Date(Date.now() - 30 * 60 * 1000) },
  74. },
  75. });
  76. console.log(` 卡在 content_generating 超过 30 分钟: ${stuckGen}`);
  77. const stuckOutline = await p.book.count({
  78. where: {
  79. genStage: 'outline_ready',
  80. updatedAt: { lt: new Date(Date.now() - 15 * 60 * 1000) },
  81. },
  82. });
  83. console.log(` 卡在 outline_ready 超过 15 分钟: ${stuckOutline}`);
  84. const failedChaptersByTime = await p.bookChapter.count({
  85. where: {
  86. genStage: 'failed',
  87. updatedAt: { lt: new Date(Date.now() - 10 * 60 * 1000) }, // 10 分钟还没恢复
  88. },
  89. });
  90. console.log(` failed 超过 10 分钟未恢复: ${failedChaptersByTime}`);
  91. // 6. 健康总览
  92. console.log('\n=== 6. 健康总览 ===');
  93. console.log(` ✅ 数据库连接正常`);
  94. console.log(` 总书籍数: ${allBooks.length}(显示最近 10 本)`);
  95. console.log(` 是否有 429/quota 错误证据: ${hasQuotError ? '⚠️ 是(需要检查供应商切换)' : '✅ 否'}`);
  96. console.log(` 是否有网络错误证据: ${hasNetworkErr ? '⚠️ 是' : '✅ 否'}`);
  97. console.log(` 卡住的书(需要扫描器恢复): ${stuckGen + stuckOutline}`);
  98. console.log(` 5分钟内无法恢复的失败章节: ${failedChaptersByTime}`);
  99. console.log('\n=== 结论 ===');
  100. if (stuckGen === 0 && stuckOutline === 0 && failedChaptersByTime === 0) {
  101. console.log(` ✅ 系统中没有明显的卡死任务,扫描器和切换逻辑工作正常`);
  102. } else {
  103. console.log(` ⚠️ 发现可能的卡死任务(但也可能正常在处理,需要看后续)`);
  104. }
  105. } catch (err) {
  106. console.error('❌ 测试异常:', err.message);
  107. process.exit(1);
  108. } finally {
  109. await p.$disconnect();
  110. }
  111. })();