feat(ops): 前端集成实时流量功能

- 添加实时流量API调用方法
- 优化OpsDashboard组件代码
This commit is contained in:
IanShaw027
2026-01-12 14:18:16 +08:00
parent 62771583e7
commit 5443efd7d7
2 changed files with 40 additions and 78 deletions

View File

@@ -362,6 +362,45 @@ export async function getAccountAvailabilityStats(platform?: string, groupId?: n
return data return data
} }
export interface OpsRateSummary {
current: number
peak: number
avg: number
}
export interface OpsRealtimeTrafficSummary {
window: string
start_time: string
end_time: string
platform: string
group_id?: number | null
qps: OpsRateSummary
tps: OpsRateSummary
}
export interface OpsRealtimeTrafficSummaryResponse {
enabled: boolean
summary: OpsRealtimeTrafficSummary | null
timestamp?: string
}
export async function getRealtimeTrafficSummary(
window: string,
platform?: string,
groupId?: number | null
): Promise<OpsRealtimeTrafficSummaryResponse> {
const params: Record<string, any> = { window }
if (platform) {
params.platform = platform
}
if (typeof groupId === 'number' && groupId > 0) {
params.group_id = groupId
}
const { data } = await apiClient.get<OpsRealtimeTrafficSummaryResponse>('/admin/ops/realtime-traffic', { params })
return data
}
/** /**
* Subscribe to realtime QPS updates via WebSocket. * Subscribe to realtime QPS updates via WebSocket.
* *
@@ -957,6 +996,7 @@ export const opsAPI = {
getErrorDistribution, getErrorDistribution,
getConcurrencyStats, getConcurrencyStats,
getAccountAvailabilityStats, getAccountAvailabilityStats,
getRealtimeTrafficSummary,
subscribeQPS, subscribeQPS,
listErrorLogs, listErrorLogs,
getErrorLogDetail, getErrorLogDetail,

View File

@@ -13,11 +13,6 @@
<OpsDashboardHeader <OpsDashboardHeader
v-else-if="opsEnabled" v-else-if="opsEnabled"
:overview="overview" :overview="overview"
:ws-status="wsStatus"
:ws-reconnect-in-ms="wsReconnectInMs"
:ws-has-data="wsHasData"
:real-time-qps="realTimeQPS"
:real-time-tps="realTimeTPS"
:platform="platform" :platform="platform"
:group-id="groupId" :group-id="groupId"
:time-range="timeRange" :time-range="timeRange"
@@ -116,8 +111,6 @@ import AppLayout from '@/components/layout/AppLayout.vue'
import BaseDialog from '@/components/common/BaseDialog.vue' import BaseDialog from '@/components/common/BaseDialog.vue'
import { import {
opsAPI, opsAPI,
OPS_WS_CLOSE_CODES,
type OpsWSStatus,
type OpsDashboardOverview, type OpsDashboardOverview,
type OpsErrorDistributionResponse, type OpsErrorDistributionResponse,
type OpsErrorTrendResponse, type OpsErrorTrendResponse,
@@ -174,14 +167,6 @@ const QUERY_KEYS = {
const isApplyingRouteQuery = ref(false) const isApplyingRouteQuery = ref(false)
const isSyncingRouteQuery = ref(false) const isSyncingRouteQuery = ref(false)
// WebSocket for realtime QPS/TPS
const realTimeQPS = ref(0)
const realTimeTPS = ref(0)
const wsStatus = ref<OpsWSStatus>('closed')
const wsReconnectInMs = ref<number | null>(null)
const wsHasData = ref(false)
let unsubscribeQPS: (() => void) | null = null
let dashboardFetchController: AbortController | null = null let dashboardFetchController: AbortController | null = null
let dashboardFetchSeq = 0 let dashboardFetchSeq = 0
@@ -201,50 +186,6 @@ function abortDashboardFetch() {
} }
} }
function stopQPSSubscription(options?: { resetMetrics?: boolean }) {
wsStatus.value = 'closed'
wsReconnectInMs.value = null
if (unsubscribeQPS) unsubscribeQPS()
unsubscribeQPS = null
if (options?.resetMetrics) {
realTimeQPS.value = 0
realTimeTPS.value = 0
wsHasData.value = false
}
}
function startQPSSubscription() {
stopQPSSubscription()
unsubscribeQPS = opsAPI.subscribeQPS(
(payload) => {
if (payload && typeof payload === 'object' && payload.type === 'qps_update' && payload.data) {
realTimeQPS.value = payload.data.qps || 0
realTimeTPS.value = payload.data.tps || 0
wsHasData.value = true
}
},
{
onStatusChange: (status) => {
wsStatus.value = status
if (status === 'connected') wsReconnectInMs.value = null
},
onReconnectScheduled: ({ delayMs }) => {
wsReconnectInMs.value = delayMs
},
onFatalClose: (event) => {
// Server-side feature flag says realtime is disabled; keep UI consistent and avoid reconnect loops.
if (event && event.code === OPS_WS_CLOSE_CODES.REALTIME_DISABLED) {
adminSettingsStore.setOpsRealtimeMonitoringEnabledLocal(false)
stopQPSSubscription({ resetMetrics: true })
}
},
// QPS updates may be sparse in idle periods; keep the timeout conservative.
staleTimeoutMs: 180_000
}
)
}
const readQueryString = (key: string): string => { const readQueryString = (key: string): string => {
const value = route.query[key] const value = route.query[key]
if (typeof value === 'string') return value if (typeof value === 'string') return value
@@ -626,12 +567,6 @@ onMounted(async () => {
// Load thresholds configuration // Load thresholds configuration
loadThresholds() loadThresholds()
if (adminSettingsStore.opsRealtimeMonitoringEnabled) {
startQPSSubscription()
} else {
stopQPSSubscription({ resetMetrics: true })
}
if (opsEnabled.value) { if (opsEnabled.value) {
await fetchData() await fetchData()
} }
@@ -648,19 +583,6 @@ async function loadThresholds() {
} }
onUnmounted(() => { onUnmounted(() => {
stopQPSSubscription()
abortDashboardFetch() abortDashboardFetch()
}) })
watch(
() => adminSettingsStore.opsRealtimeMonitoringEnabled,
(enabled) => {
if (!opsEnabled.value) return
if (enabled) {
startQPSSubscription()
} else {
stopQPSSubscription({ resetMetrics: true })
}
}
)
</script> </script>