// 本地验收专用。所有 API 由此模块或下游 mock 拦截,禁止代理真实服务。 import { mockApiPlugin as polling } from './pollingConfigBrowserFixtures.mjs' const time = '2026-10-26T10:00:00+08:00' const menus = [ ['运营商账户调度参数', 'carrier-params', 'polling_carrier_params:page'], ['请求检查场景', 'check-scenes', 'polling_check_scene:page'], ['失败告警', 'failure-alerts', 'polling_failure_alert:page'], ['账户队列监控', 'carrier-queues', 'polling_carrier_queue:page'], ['停复机规则演练', 'stop-resume-dry-run', 'stop_resume_dry_run:page'], ['不轮询清单', 'polling-pauses', 'polling_pause:page'], ['轮询开关导入', 'polling-status-imports', 'polling_status_import:page'], ['轮询配置', 'config'], ['网关调用统计', 'gateway-stats', 'gateway_call_stats:page'] ] const permissions = [ 'polling_config_create', 'polling_carrier_params:detail', 'polling_carrier_params:detail_page', 'polling_failure_alert:detail', 'polling_failure_alert:detail_page', 'polling_failure_alert:summary', 'polling_failure_alert:handle', 'polling_failure_alert:close_polling', 'polling_failure_alert:export', 'polling_pause:restore', 'polling_pause:export', 'polling_status_import:create', 'polling_status_import:detail', 'polling_status_import:detail_page', 'stop_resume_dry_run:export', 'iot_info:polling_state', 'device_info:polling_state', 'iot_card:batch_polling', 'device:batch_polling', 'gateway_call_stats:trend', 'iot_info:stop_resume_records', 'polling_config:match_preview' ] const params = { carrier_id: 8, carrier_name: '模拟运营商账户', is_default: true, card_max_in_flight: 10, inflight_limit: 20, configured_card_max_in_flight: null, configured_inflight_limit: null, consume_capacity: 30, priority_max_ratio: null, sr_inflight_limit: null, stop_resume_max_in_flight: null, stop_resume_min_interval: null, stop_resume_confirm_delay: null } const alert = { id: 1, alert_level: 'card', card_id: 42, carrier_id: 8, carrier_name: params.carrier_name, check_type: 'stop_resume', check_type_name: '停复机', created_at: time, iccid: '89860426012345678901', status: 1, status_name: '未处理', error_category: 'terminal', fail_count: 3, first_failed_at: time, last_failed_at: time, last_error: '模拟停机被拒', handled_by: 0, handle_remark: '', handled_at: '', recovered_at: '', network_status: 1, stop_reason: 'manual', enable_polling: true, has_package: false } const pause = { id: 1, asset_id: 42, asset_type: 'iot_card', identifier: alert.iccid, carrier_id: 8, carrier_name: params.carrier_name, enable_polling: false, gateway_extend: '', status: 1, status_name: '已关闭', close_source: 'batch', close_source_name: '勾选批量', close_reason: '模拟关闭', closed_by: 0, closed_by_name: '', closed_at: time, restore_source: '', restore_reason: '', restored_by: 0, restored_at: '', import_task_id: 0 } const task = { id: 1, task_no: 'MOCK-IMPORT-1', asset_type: 'iot_card', enable_polling: false, file_name: 'mock.csv', reason: '模拟导入', status: 3, status_name: '已完成', creator_name: '模拟平台', total_count: 4, success_count: 1, skip_count: 1, fail_count: 2, error_message: '', created_at: time, started_at: time, completed_at: time, items: [1, 2, 3, 4].map((status, index) => ({ line: index + 2, identifier: `8986042601234567890${index}`, asset_id: index + 42, status, status_name: ['', '成功', '跳过', '失败', '无权限或不存在'][status], reason: status > 1 ? '模拟逐项原因' : '' })) } const operation = { operation: 'stop', operation_name: '停机', attempts: 10, success: 8, success_rate: 0.8, avg_qps: 0.2, peak_minute_qps: 0.3, avg_duration_ms: 230, p95_duration_ms: -1, rate_limited: 0, would_limit: 2, failures: { 'upstream:terminal': 2 }, exempt: { manual_click: 1 } } const log = [] 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: 'polling-scheduler-local-mock-api', configureServer(server) { server.middlewares.use(async (req, res, next) => { const url = new URL(req.url, 'http://127.0.0.1'), path = url.pathname if (path === '/__scheduler-upload') { for await (const _ of req) { } res.end('') return } if (path === '/__scheduler-log') { res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify(log)) return } const handled = /^\/api\/admin\/(polling-carrier-params|polling-check-scenes|polling-failure-alerts|card-polling-pauses|polling-status-imports|polling-status\/impact|stop-resume-rule\/dry-run|polling-stats\/carrier|storage\/upload-url)/.test( path ) || /\/polling-state$|\/batch-update-polling-status$/.test(path) || ['/api/auth/me', '/api/auth/login'].includes(path) if (!handled) return next() let raw = '' for await (const chunk of req) raw += chunk const input = raw ? JSON.parse(raw) : {}, token = String(req.headers.authorization || ''), type = token.includes('agent') ? 3 : token.includes('enterprise') ? 4 : token.includes('admin') ? 1 : 2 log.push({ path, method: req.method, query: Object.fromEntries(url.searchParams), body: input }) let data = {}, code = 0 if (path.startsWith('/api/auth/')) data = { access_token: `local-scheduler-${type === 1 ? 'admin' : type === 3 ? 'agent' : type === 4 ? 'enterprise' : 'platform'}`, refresh_token: '', expires_in: 3600, user: { id: type, user_type: type, username: '本地模拟账号', roles: [type === 1 ? 'R_SUPER' : 'R_ADMIN'] }, permissions: menus.map((row) => row[2]).filter(Boolean), buttons: permissions, menus: [ { url: '/polling-management', name: '轮询管理', children: [ ...menus, [ '运营商账户调度参数详情', 'carrier-params/detail/:carrier_id', 'polling_carrier_params:detail_page' ], ['失败告警详情', 'failure-alerts/detail/:id', 'polling_failure_alert:detail_page'], [ '导入任务详情', 'polling-status-imports/detail/:id', 'polling_status_import:detail_page' ] ].map(([name, path, perm_code]) => ({ name, url: `/polling-management/${path}`, perm_code })) } ] } else if (path === '/api/admin/polling-carrier-params') data = { default_card_max_in_flight: 10, default_inflight_limit: 20, subscriber_processes: 3, items: [params] } else if (path.startsWith('/api/admin/polling-carrier-params/')) data = { ...params, ...input } else if (path.startsWith('/api/admin/polling-check-scenes')) data = [ { scene: 'order_paid', scene_name: '订单支付', check_types: ['carddata', 'card_status'], confirm_delays: [30, 60], cooldown: null, updated_at: time } ] else if (path === '/api/admin/polling-stats/carrier-queues') data = { scheduler_mode: 'carrier', init_state: 'completed', items: [ { carrier_id: 8, carrier_name: params.carrier_name, queue_length: 10, priority_length: 1, due_count: 2, running: 1, inflight_limit: 20, qps_last_minute: 0.2, rate_limit_waits: 1, rate_limited: 0, expired_claims_1h: 0, open_alerts: 2, open_alerts_by_category: { terminal: 2 }, stop_resume_queue_length: 5, stop_resume_due: 1, stop_resume_running: 1, stop_resume_pending: 2, stop_resume_success_1h: 10, stop_resume_failed_1h: 2, stop_resume_retry_1h: 3, stop_resume_unknown_1h: 2 } ] } else if (path === '/api/admin/polling-stats/carriers') data = { minutes: 15, mode: 'observe', carriers: [{ carrier_id: 8, carrier_name: params.carrier_name, operations: [operation] }] } else if (path.endsWith('/trend')) data = { carrier_id: 8, points: [{ time: '2026-10-26 10:00', operations: [operation] }] } else if (path === '/api/admin/polling-failure-alerts/summary') data = [ { carrier_id: 8, carrier_name: params.carrier_name, error_category: 'terminal', open_count: 2 } ] else if (path === '/api/admin/polling-failure-alerts') data = page( [ alert, { ...alert, id: 2, alert_level: 'carrier', card_id: 0, iccid: '', check_type: 'all' } ], url ) else if (path.endsWith('/close-polling') || path.endsWith('/batch-update-polling-status')) data = { success_count: 1, skip_count: 1, fail_count: 2, impact: ['本地模拟影响'], items: task.items } else if (path.startsWith('/api/admin/polling-failure-alerts/')) data = alert else if (path === '/api/admin/card-polling-pauses') data = page([pause], url) else if (path === '/api/admin/polling-status/impact') data = { enable: ['本地模拟:按配置查询并自动停复机。'], disable: ['本地模拟:关闭查询及部分自动停复机;部分业务仍生效。'] } else if (path === '/api/admin/polling-status-imports') data = req.method === 'POST' ? { ...task, status: 1, status_name: '待处理' } : page([task], url) else if (path.startsWith('/api/admin/polling-status-imports/')) data = task else if (path === '/api/admin/storage/upload-url') data = { upload_url: 'http://127.0.0.1:5190/__scheduler-upload', file_key: 'polling-status-imports/mock.csv' } else if (path.endsWith('/polling-state')) data = { card_id: 42, carrier_id: 8, iccid: alert.iccid, enable_polling: true, scheduler_mode: 'carrier', queue: 'priority', queue_score_at: time, running: false, priority_mark: false, claim_expire_at: '', stop_resume_queue_enabled: true, last_stop_resume_call_at: time, stop_resume_command: { action: 'stop', reason: 'manual', scenes: ['order_paid'], source: 'order', version: 1, fail_count: 0, last_error: '', pending: true, unknown: 1, written_at: time, next_at: time }, types: [ { check_type: 'carddata', check_type_name: '流量', next_due_at: time, last_success_at: time, fail_count: 0, first_failed_at: '', last_error: '', pending_confirm: [30, 60] } ] } else if (path === '/api/admin/stop-resume-rule/dry-run') data = { ...page( [ { card_id: 42, carrier_id: 8, carrier_name: params.carrier_name, iccid: alert.iccid, device_id: 0, network_status: 1, card_category: 'normal', realname_ok: false, stop_reason: 'none', stop_reason_name: '无', result: 'will_stop', result_name: '将停机', reason: 'not_realname', reason_name: '未实名' } ], url ), totals: { total: 100, will_stop: 20, will_resume: 10, no_action: 70 }, summary: [ { carrier_id: 8, carrier_name: params.carrier_name, stop_reason_name: '无', result_name: '将停机', reason_name: '未实名', count: 20 } ] } else if (path.endsWith('/export')) data = { task_id: 1, task_no: 'MOCK-EXPORT-1', status: 1, status_name: '待处理' } else code = 400 res.setHeader('Content-Type', 'application/json; charset=utf-8') res.end(JSON.stringify({ code, msg: 'local mock only', data })) }) polling.configureServer(server) } }