278 lines
9.6 KiB
JavaScript
278 lines
9.6 KiB
JavaScript
import { mockApiPlugin as gatewayFixtures } from './gatewayBrowserFixtures.mjs'
|
|
|
|
const time = '2026-10-26T10:00:00+08:00'
|
|
const configs = [1, 2, 3].map((id) => ({
|
|
id,
|
|
config_name: `模拟配置 ${id}`,
|
|
priority: id,
|
|
description: '本地模拟,不代表真实配置',
|
|
status: id === 3 ? 0 : 1,
|
|
carrier_id: 8,
|
|
carrier_name: '模拟运营商账户',
|
|
card_category: 'normal',
|
|
card_condition: 'activated',
|
|
asset_status: 2,
|
|
package_state: 'active',
|
|
realname_check_interval: null,
|
|
carddata_check_interval: 300,
|
|
package_check_interval: 600,
|
|
card_status_check_interval: 0,
|
|
pre_reset_window: 3600,
|
|
pre_reset_carddata_interval: 60,
|
|
created_at: time,
|
|
updated_at: time
|
|
}))
|
|
const tasks = new Map()
|
|
const page = (items, url) => {
|
|
const current = Number(url.searchParams.get('page') || 1)
|
|
const size = Number(url.searchParams.get('page_size') || 20)
|
|
return {
|
|
items: items.slice((current - 1) * size, current * size),
|
|
page: current,
|
|
size,
|
|
page_size: size,
|
|
total: items.length,
|
|
total_pages: Math.ceil(items.length / size)
|
|
}
|
|
}
|
|
|
|
// 本地测试专用:所有 /api 都被当前或 gateway fixture 消费,无真实后端代理。
|
|
export const mockApiPlugin = {
|
|
name: 'polling-config-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
|
|
const auth = ['/api/auth/me', '/api/auth/login', '/api/auth/logout'].includes(path)
|
|
if (
|
|
!auth &&
|
|
!path.startsWith('/api/admin/polling-configs') &&
|
|
!['/api/admin/polling-stats/pre-reset', '/api/admin/polling-stats/tiers'].includes(path)
|
|
)
|
|
return next()
|
|
let body = ''
|
|
for await (const chunk of req) body += chunk
|
|
const input = body ? JSON.parse(body) : {}
|
|
const viewer = req.headers.authorization?.includes('local-polling-viewer')
|
|
const denied = req.headers.authorization?.includes('local-polling-denied')
|
|
const admin = !viewer && !denied
|
|
let data = {}
|
|
let code = 0
|
|
let msg = 'local mock only'
|
|
if (auth) {
|
|
const menus = [
|
|
{ url: '/polling-management/config', name: '轮询配置' },
|
|
{
|
|
url: '/polling-management/config-stats',
|
|
name: '轮询配置统计',
|
|
perm_code: 'polling_config_stats:page'
|
|
}
|
|
]
|
|
data = {
|
|
access_token: viewer
|
|
? 'local-polling-viewer'
|
|
: denied
|
|
? 'local-polling-denied'
|
|
: 'local-polling-admin',
|
|
refresh_token: '',
|
|
expires_in: 3600,
|
|
user: {
|
|
id: admin ? 1 : 2,
|
|
user_type: admin ? 1 : 2,
|
|
username: admin ? '本地模拟管理员' : '本地普通账号',
|
|
roles: [admin ? 'R_SUPER' : 'R_ADMIN']
|
|
},
|
|
permissions: denied ? [] : ['polling_config_stats:page'],
|
|
buttons: denied
|
|
? []
|
|
: [
|
|
'polling_config:match_preview',
|
|
'polling_config:recalculate_progress',
|
|
'polling_config_stats:pre_reset',
|
|
'polling_config_stats:tiers'
|
|
],
|
|
menus: [{ url: '/polling-management', name: '轮询管理', children: menus }]
|
|
}
|
|
} else if (path === '/api/admin/polling-configs/match-preview') {
|
|
const iccid = url.searchParams.get('iccid')
|
|
if (!iccid || (req.method === 'POST' && input.iccid !== iccid)) {
|
|
code = 1001
|
|
msg = 'Query 和 Body 的 ICCID 必须一致'
|
|
} else
|
|
data = {
|
|
card: {
|
|
iccid,
|
|
card_id: 42,
|
|
carrier_id: 8,
|
|
enable_polling: true,
|
|
asset_status: 2,
|
|
card_category: 'normal',
|
|
card_condition: 'activated',
|
|
package_state: 'active',
|
|
package_state_name: '有生效套餐'
|
|
},
|
|
matched:
|
|
req.method === 'POST' && input.overrides?.length
|
|
? input.overrides
|
|
.filter((item) => !item.disabled)
|
|
.map((item) => ({
|
|
id: item.id,
|
|
config_name: item.config_name,
|
|
priority: item.priority,
|
|
draft: true
|
|
}))
|
|
: [{ id: 1, config_name: configs[0].config_name, priority: 1, draft: false }],
|
|
intervals: [
|
|
{
|
|
config_id: 1,
|
|
config_name: configs[0].config_name,
|
|
inherited: false,
|
|
interval: 300,
|
|
mode: 'interval',
|
|
task_type: 'polling:carddata',
|
|
task_type_name: '流量'
|
|
},
|
|
{
|
|
config_id: 1,
|
|
config_name: configs[0].config_name,
|
|
inherited: true,
|
|
interval: 0,
|
|
mode: 'disabled',
|
|
task_type: 'polling:card_status',
|
|
task_type_name: '卡状态'
|
|
},
|
|
{
|
|
config_id: 0,
|
|
config_name: '',
|
|
inherited: false,
|
|
interval: null,
|
|
mode: 'unmatched',
|
|
task_type: 'polling:realname',
|
|
task_type_name: '实名'
|
|
}
|
|
]
|
|
}
|
|
} else if (path === '/api/admin/polling-configs/priorities') {
|
|
if (!admin) {
|
|
code = 403
|
|
msg = '仅管理员可操作'
|
|
} else {
|
|
input.ordered_ids.forEach((id, index) => {
|
|
const row = configs.find((item) => item.id === id)
|
|
if (row) row.priority = index + 1
|
|
})
|
|
data = null
|
|
}
|
|
} else if (path === '/api/admin/polling-configs/recalculate-due' && req.method === 'POST') {
|
|
if (!admin) {
|
|
code = 403
|
|
msg = '仅管理员可操作'
|
|
} else {
|
|
const id = `local-task-${tasks.size + 1}`
|
|
data = {
|
|
task_id: id,
|
|
status: 'pending',
|
|
carrier_id: input.carrier_id == null ? '' : String(input.carrier_id),
|
|
total: 100,
|
|
processed: 0,
|
|
advanced: 0,
|
|
error: '',
|
|
created_at: time,
|
|
started_at: '',
|
|
finished_at: ''
|
|
}
|
|
tasks.set(id, { data, polls: 0 })
|
|
}
|
|
} else if (path.startsWith('/api/admin/polling-configs/recalculate-due/')) {
|
|
const id = decodeURIComponent(
|
|
path.slice('/api/admin/polling-configs/recalculate-due/'.length)
|
|
)
|
|
if (!tasks.has(id))
|
|
tasks.set(id, {
|
|
data: {
|
|
task_id: id,
|
|
carrier_id: '8',
|
|
total: 100,
|
|
processed: 0,
|
|
advanced: 0,
|
|
error: '',
|
|
created_at: time,
|
|
started_at: '',
|
|
finished_at: ''
|
|
},
|
|
polls: 0
|
|
})
|
|
const entry = tasks.get(id)
|
|
entry.polls++
|
|
data = {
|
|
...entry.data,
|
|
status: entry.polls > 2 ? 'completed' : 'running',
|
|
processed: Math.min(100, entry.polls * 40),
|
|
advanced: entry.polls * 10,
|
|
started_at: time,
|
|
finished_at: entry.polls > 2 ? time : ''
|
|
}
|
|
} else if (path === '/api/admin/polling-stats/pre-reset') {
|
|
data = {
|
|
carriers: [
|
|
{
|
|
carrier_id: 8,
|
|
carrier_name: '模拟运营商账户',
|
|
previous: {
|
|
active_package_cards: 100,
|
|
checked_in_window: 80,
|
|
not_checked_in_window: 20,
|
|
reset_at: time,
|
|
last_offset_buckets: { le_5: 0, le_15: 20, le_30: 15, le_60: 25, gt_60: 10 }
|
|
},
|
|
upcoming: null
|
|
}
|
|
]
|
|
}
|
|
} else if (path === '/api/admin/polling-stats/tiers') {
|
|
data = {
|
|
by_config: [
|
|
{ config_id: 1, config_name: configs[0].config_name, cards: 95 },
|
|
{ config_id: 0, config_name: '', cards: 5 }
|
|
],
|
|
by_package_state: { active: 80, depleted: 10, pending: 5, none: 3, unknown: 2 },
|
|
computed_at: time,
|
|
total_cards: 100,
|
|
unmatched_in_stock: 2,
|
|
unmatched_sold_no_package: 3
|
|
}
|
|
} else if (path === '/api/admin/polling-configs' && req.method === 'GET') {
|
|
const query = url.searchParams.get('config_name') || ''
|
|
data = page(
|
|
configs.filter((item) => item.config_name.includes(query)),
|
|
url
|
|
)
|
|
} else if (path === '/api/admin/polling-configs' && req.method === 'POST') {
|
|
data = {
|
|
...input,
|
|
id: configs.length + 1,
|
|
status: 1,
|
|
created_at: time,
|
|
updated_at: time,
|
|
warnings: ['本地模拟:保存提示']
|
|
}
|
|
configs.push(data)
|
|
} else if (/^\/api\/admin\/polling-configs\/\d+$/.test(path) && req.method === 'PUT') {
|
|
const row = configs.find((item) => item.id === Number(path.split('/').at(-1)))
|
|
Object.assign(row, input)
|
|
for (const key of input.clear_fields || []) row[key] = null
|
|
delete row.clear_fields
|
|
data = { ...row, warnings: ['本地模拟:保存提示'] }
|
|
} else if (/^\/api\/admin\/polling-configs\/\d+$/.test(path) && req.method === 'GET')
|
|
data = configs.find((item) => item.id === Number(path.split('/').at(-1)))
|
|
else {
|
|
code = 400
|
|
msg = '此本地验收接口未定义,已拦截写请求'
|
|
}
|
|
res.setHeader('Content-Type', 'application/json; charset=utf-8')
|
|
res.end(JSON.stringify({ code, msg, data }))
|
|
})
|
|
gatewayFixtures.configureServer(server)
|
|
}
|
|
}
|