#!/usr/bin/env node /** * 批量修补所有 api-config.json 的 errorHandling.balanceInsufficient: * 1) 添加 `mess` responseField 检查 (voc-social-api 返回 `mess` 而非 `message`) * 2) 在 msg/mess/message 的 contains 列表中追加 "没有开通"、"权限或余额" * * 目标是让后端 403 `{"code":403,"mess":"没有开通社交平台API权限或余额不足"}` * 正确触发 onBalanceInsufficient 流程(而非被错误路由到 onMissing) * * 运行: node scripts/tools/_patch-errorhandling.js * node scripts/tools/_patch-errorhandling.js --dry-run */ const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '..', '..'); const DRY = process.argv.includes('--dry-run'); const SOURCE_DIRS = [ 'voc', 'social-media', 'competitor-analysis', 'review-analysis', 'synthesis', 'social-voc', 'douyin', 'video-creation', 'jimeng', 'payment', 'workshop' ]; // 增量关键词(避免重复添加) const EXTRA_KEYWORDS = ['没有开通', '权限或余额']; function uniqMerge(existing, extras) { const set = new Set(existing); extras.forEach(x => set.add(x)); return Array.from(set); } function patch(config, filePath) { let changed = false; const bi = config?.errorHandling?.balanceInsufficient; if (!bi || !Array.isArray(bi.conditions)) return false; const hasMess = bi.conditions.some(c => c.responseField === 'mess'); // 收集一个参考的 contains value 列表(优先从 msg 获取;没有则用 message;再没有兜底默认) const msgCond = bi.conditions.find(c => c.responseField === 'msg'); const messageCond = bi.conditions.find(c => c.responseField === 'message'); const baseValues = (msgCond?.value || messageCond?.value || ['余额不足', 'insufficient', 'balance', 'quota']).slice(); // 1. 给 msg / message 的 value 追加关键词 for (const cond of bi.conditions) { if (cond.operator !== 'contains') continue; if (cond.responseField === 'msg' || cond.responseField === 'message') { const merged = uniqMerge(cond.value || [], EXTRA_KEYWORDS); if (JSON.stringify(merged) !== JSON.stringify(cond.value)) { cond.value = merged; changed = true; } } } // 2. 追加 mess responseField 条件(若不存在) if (!hasMess) { const messCond = { responseField: 'mess', operator: 'contains', value: uniqMerge(baseValues, EXTRA_KEYWORDS) }; // 插在 msg 条件之后,保持排序直观 const insertIdx = bi.conditions.findIndex(c => c.responseField === 'msg'); if (insertIdx >= 0) { bi.conditions.splice(insertIdx + 1, 0, messCond); } else { bi.conditions.push(messCond); } changed = true; } return changed; } const processed = []; for (const dir of SOURCE_DIRS) { const catPath = path.join(ROOT, dir); if (!fs.existsSync(catPath)) continue; for (const skill of fs.readdirSync(catPath)) { const p = path.join(catPath, skill, 'api-config.json'); if (!fs.existsSync(p)) continue; try { const raw = fs.readFileSync(p, 'utf-8'); const config = JSON.parse(raw); if (patch(config, p)) { if (!DRY) { fs.writeFileSync(p, JSON.stringify(config, null, 2) + '\n', 'utf-8'); } processed.push({ file: path.relative(ROOT, p), status: DRY ? 'would-change' : 'updated' }); } else { processed.push({ file: path.relative(ROOT, p), status: 'no-change' }); } } catch (e) { console.error(`[ERROR] ${p}: ${e.message}`); } } } const updated = processed.filter(p => p.status === 'updated' || p.status === 'would-change'); const skipped = processed.filter(p => p.status === 'no-change'); console.log(`\n${DRY ? '[DRY]' : '[WRITE]'} 已扫描 ${processed.length} 个 api-config.json`); console.log(` 变更: ${updated.length}`); console.log(` 无需变更: ${skipped.length}`); console.log(''); updated.forEach(p => console.log(` ✓ ${p.file}`)); if (skipped.length && updated.length < 20) { skipped.forEach(p => console.log(` - ${p.file} (已包含 mess 或无 balanceInsufficient)`)); }