/** * 审计所有 api-config.json 的 endpoint 路径 vs 当前配置的 apigId * 规则: * /api/voc-social/* → apigId=Vo3ROWEvDy * /api/voc-ecom/forward/* → apigId=7HwdQZk55B * /api/volcengine/jimeng/* → apigId=? (暂标未知) * 其他 → 打印出来让人工判断 */ const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '..', '..'); const EXPECTED = { '/api/voc-social/': 'Vo3ROWEvDy', '/api/voc-ecom/': '7HwdQZk55B', }; function walk(d, r = []) { for (const e of fs.readdirSync(d, { withFileTypes: true })) { if (e.name === 'node_modules' || e.name === '.git' || e.name === 'dist') continue; const p = path.join(d, e.name); if (e.isDirectory()) walk(p, r); else if (e.name === 'api-config.json') r.push(p); } return r; } const files = walk(ROOT); const results = { match: [], mismatch: [], orchestration: [], unknown: [] }; for (const f of files) { let cfg; try { cfg = JSON.parse(fs.readFileSync(f, 'utf-8')); } catch { continue; } const curApigId = cfg.tokenConfig?.apigId || null; const rel = path.relative(ROOT, f).replace(/\\/g, '/'); // 直接 endpoint const url = cfg.endpoint?.url; if (url) { const u = new URL(url); const pathOnly = u.pathname; let expected = null; for (const [prefix, apig] of Object.entries(EXPECTED)) { if (pathOnly.startsWith(prefix)) { expected = apig; break; } } if (expected) { if (curApigId === expected) { results.match.push({ file: rel, pathOnly, apigId: curApigId }); } else { results.mismatch.push({ file: rel, pathOnly, current: curApigId, expected }); } } else { results.unknown.push({ file: rel, pathOnly, current: curApigId }); } continue; } // orchestration 型:检查 pipeline steps 的 endpoint const steps = cfg.pipeline || []; const stepUrls = []; function collectUrls(node) { if (!node || typeof node !== 'object') return; if (Array.isArray(node)) return node.forEach(collectUrls); if (node.endpoint && typeof node.endpoint === 'string') stepUrls.push(node.endpoint); for (const v of Object.values(node)) collectUrls(v); } collectUrls(steps); if (stepUrls.length > 0) { const pathsFound = stepUrls.map(u => { try { return new URL(u).pathname; } catch { return u; } }); const distinct = [...new Set(pathsFound.map(p => { for (const prefix of Object.keys(EXPECTED)) if (p.startsWith(prefix)) return prefix; return p; }))]; results.orchestration.push({ file: rel, current: curApigId, distinctPathPrefixes: distinct }); } } console.log('\n═════════════════════════ 审计结果 ═════════════════════════\n'); console.log(`✅ apigId 正确 (${results.match.length})`); const byApig = {}; results.match.forEach(m => { byApig[m.apigId] = (byApig[m.apigId] || 0) + 1; }); console.log(' 分布:', JSON.stringify(byApig)); console.log(`\n❌ apigId 错误 (${results.mismatch.length})`); results.mismatch.forEach(m => { console.log(` ${m.file}`); console.log(` 当前=${m.current} 应该=${m.expected} path=${m.pathOnly}`); }); console.log(`\n⚠️ orchestration (pipeline) 类 (${results.orchestration.length})`); results.orchestration.forEach(o => { console.log(` ${o.file} current=${o.current} paths=${JSON.stringify(o.distinctPathPrefixes)}`); }); console.log(`\n❓ 未知路径 (${results.unknown.length})`); results.unknown.forEach(u => { console.log(` ${u.file} current=${u.current} path=${u.pathOnly}`); });