415 lines
13 KiB
JavaScript
415 lines
13 KiB
JavaScript
// 本地验收专用。所有 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)
|
|
}
|
|
}
|