Files
one-pipe-system/scripts/tests/pollingSchedulerBrowserFixtures.mjs
luo 06684e1fc0
All checks were successful
构建并部署前端到测试环境 / deploy (push) Successful in 55s
fix: 轮询按运营商账户限流
2026-10-26 14:31:15 +08:00

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)
}
}