// 本地浏览器验收专用;所有 /api 请求在这里终止,不会代理到真实后端。 const time = '2026-10-24T10:00:00+08:00' const option = (value, label) => ({ value, label }) const options = { error_categories: [ option('rate_limited_upstream', '上游频率限制'), option('business', '业务错误') ], exempt_sources: [option('manual', '人工')], gateway_operations: [ option('query_flow', '查流量'), option('query_status', '查状态'), option('query_realname', '查实名'), option('stop', '停机'), option('start', '复机') ], match_types: [option('contains', '包含'), option('prefix', '前缀'), option('regex', '正则')], rate_limit_modes: [option('group', '限流组')], stop_resume_actions: [option('stop', '停机'), option('start', '复机')], stop_resume_results: [ option('success', '成功'), option('failed', '失败'), option('unknown', '未知'), option('already_target', '已是目标状态'), option('skipped', '已跳过') ] } const rules = [ { id: 1, category: 'rate_limited_upstream', category_name: '上游频率限制', keyword: '请求过于频繁', match_type: 'contains', operations: ['stop'], priority: 10, maybe_accepted: false, status: 1, remark: '本地模拟规则', created_at: time, updated_at: time } ] const groups = [ { id: 1, carrier_id: null, carrier_name: '', group_code: 'default', group_name: '全局默认组', operations: ['query_flow', 'stop', 'start'], max_qps: 20, reserved_qps: 5, high_priority_operations: ['stop', 'start'], status: 1, status_name: '启用', created_at: time, updated_at: time } ] const records = [ { id: 1, card_id: 42, carrier_id: 8, iccid: 'MOCK-ICCID-42', action: 'stop', action_name: '停机', result: 'success', error_category: '', error_category_name: '', provider_message: '操作成功', provider_code: '0', attempt: 1, command_id: 'MOCK-CMD', source: 'manual', scene: 'manual_stop', reason: '本地验收', duration_ms: 42, created_at: time } ] const page = (items, url) => ({ items, total: items.length, page: Number(url.searchParams.get('page') || 1), size: Number(url.searchParams.get('page_size') || 20) }) export const mockApiPlugin = { name: 'gateway-local-mock-api', configureServer(server) { server.middlewares.use(async (req, res, next) => { const url = new URL(req.url, 'http://127.0.0.1') const path = url.pathname if (!path.startsWith('/api/')) return next() let body = '' for await (const chunk of req) body += chunk const input = body ? JSON.parse(body) : {} let data = page([], url) let code = 0 let msg = 'local mock only' if (path === '/api/auth/me' || path === '/api/auth/login') { const viewer = req.headers.authorization?.includes('local-gateway-viewer') const pagePermissions = [ 'card_stop_resume_record:page', 'gateway_error_classification:page', 'gateway_rate_limit_group:page', 'gateway_rate_limit_group:detail_page', 'gateway_call_stats:page' ] const menuEntries = [ ['停复机记录', 'stop-resume-records', pagePermissions[0]], ['上游错误分类', 'error-classifications', pagePermissions[1]], ['网关限流组', 'rate-limit-groups', pagePermissions[2]], ['网关限流组详情', 'rate-limit-groups/detail/:id', pagePermissions[3]], ['网关调用统计', 'gateway-stats', pagePermissions[4]] ] data = { access_token: viewer ? 'local-gateway-viewer' : 'local-gateway-mock-only', refresh_token: '', expires_in: 3600, user: { id: viewer ? 2 : 1, user_type: viewer ? 2 : 1, username: viewer ? '本地只读账号' : '本地模拟管理员', roles: [viewer ? 'R_ADMIN' : 'R_SUPER'] }, permissions: viewer ? pagePermissions : [], buttons: viewer ? [ 'gateway_error_classification:test', 'gateway_rate_limit_group:detail', 'gateway_call_stats:trend' ] : [], menus: viewer ? [ { url: '/polling-management', name: '轮询管理', children: menuEntries.map(([name, segment, perm_code]) => ({ name, url: `/polling-management/${segment}`, perm_code })) } ] : [] } } else if (path === '/api/admin/polling-options') data = options else if (path === '/api/admin/carriers') data = page( [ { id: 8, carrier_name: '模拟运营商账户', carrier_type: 'CMCC', status: 1, created_at: time, updated_at: time } ].filter((carrier) => carrier.carrier_name.includes(url.searchParams.get('carrier_name') || '') ), url ) else if ( path === '/api/admin/card-stop-resume-records' || /\/iot-cards\/\d+\/stop-resume-records$/.test(path) ) data = page(records, url) else if (path === '/api/admin/gateway-error-classifications/test') { data = { matched: input.message?.includes(input.draft?.keyword || '请求过于频繁') || false, rule_id: input.draft ? 0 : 1, category: input.draft?.category || rules[0].category, category_name: '上游频率限制', keyword: input.draft?.keyword || rules[0].keyword, maybe_accepted: false } } else if ( /^\/api\/admin\/(gateway-error-classifications|polling-rate-limit-groups)(\/\d+)?(\/status)?$/.test( path ) ) { const list = path.includes('gateway-error-classifications') ? rules : groups const id = Number(path.split('/')[4]) const row = list.find((item) => item.id === id) if (req.method === 'GET') data = id ? row : page(list, url) else if (req.method === 'POST') { data = { ...input, id: Math.max(...list.map((item) => item.id), 0) + 1, created_at: time, updated_at: time } list.push(data) } else if (req.method === 'PUT' && row) { Object.assign(row, input) if ('group_name' in row) row.status_name = row.status === 1 ? '启用' : '停用' data = row } else if (req.method === 'DELETE' && row) { list.splice(list.indexOf(row), 1) data = {} } } else if (path === '/api/admin/polling-stats/carriers') { data = { minutes: Number(url.searchParams.get('minutes') || 15), mode: '本地模拟模式', carriers: [ { carrier_id: 8, carrier_name: '模拟运营商', example_calls: 100, example_upstream_failures: 2 } ] } } else if (/\/polling-stats\/carriers\/\d+\/trend$/.test(path)) { data = { carrier_id: Number(path.split('/')[5]), points: [{ example_time: time, example_calls: 10 }] } } else if (path.startsWith('/api/admin/assets/resolve/')) { data = { asset_type: 'card', asset_id: 42, identifier: 'MOCK-ICCID-42', iccid: 'MOCK-ICCID-42', status: 1, network_status: 1, real_name_status: 1, carrier_name: '模拟运营商', cards: [] } } else if (req.method !== 'GET') { code = 400 msg = '此本地验收接口未定义,已拦截写请求' } res.setHeader('Content-Type', 'application/json; charset=utf-8') res.end(JSON.stringify({ code, msg, data })) }) } }