mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-03 00:02:28 +08:00
feat: 添加使用量统计和数据分析功能
This commit is contained in:
208
frontend/src/composables/useTTLAnalysis.ts
Normal file
208
frontend/src/composables/useTTLAnalysis.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* TTL 分析 composable
|
||||
* 封装缓存亲和性 TTL 分析相关的状态和逻辑
|
||||
*/
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import {
|
||||
cacheAnalysisApi,
|
||||
type TTLAnalysisResponse,
|
||||
type CacheHitAnalysisResponse,
|
||||
type IntervalTimelineResponse
|
||||
} from '@/api/cache'
|
||||
import type { ChartData } from 'chart.js'
|
||||
|
||||
// 时间范围选项
|
||||
export const ANALYSIS_HOURS_OPTIONS = [
|
||||
{ value: '12', label: '12 小时' },
|
||||
{ value: '24', label: '24 小时' },
|
||||
{ value: '72', label: '3 天' },
|
||||
{ value: '168', label: '7 天' },
|
||||
{ value: '336', label: '14 天' },
|
||||
{ value: '720', label: '30 天' }
|
||||
] as const
|
||||
|
||||
// 间隔颜色配置
|
||||
export const INTERVAL_COLORS = {
|
||||
short: 'rgba(34, 197, 94, 0.6)', // green: 0-5 分钟
|
||||
medium: 'rgba(59, 130, 246, 0.6)', // blue: 5-15 分钟
|
||||
normal: 'rgba(168, 85, 247, 0.6)', // purple: 15-30 分钟
|
||||
long: 'rgba(249, 115, 22, 0.6)', // orange: 30-60 分钟
|
||||
veryLong: 'rgba(239, 68, 68, 0.6)' // red: >60 分钟
|
||||
} as const
|
||||
|
||||
/**
|
||||
* 根据间隔时间获取对应的颜色
|
||||
*/
|
||||
export function getIntervalColor(interval: number): string {
|
||||
if (interval <= 5) return INTERVAL_COLORS.short
|
||||
if (interval <= 15) return INTERVAL_COLORS.medium
|
||||
if (interval <= 30) return INTERVAL_COLORS.normal
|
||||
if (interval <= 60) return INTERVAL_COLORS.long
|
||||
return INTERVAL_COLORS.veryLong
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 TTL 推荐的 Badge 样式
|
||||
*/
|
||||
export function getTTLBadgeVariant(ttl: number): 'default' | 'secondary' | 'outline' | 'destructive' {
|
||||
if (ttl <= 5) return 'default'
|
||||
if (ttl <= 15) return 'secondary'
|
||||
if (ttl <= 30) return 'outline'
|
||||
return 'destructive'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取使用频率标签
|
||||
*/
|
||||
export function getFrequencyLabel(ttl: number): string {
|
||||
if (ttl <= 5) return '高频'
|
||||
if (ttl <= 15) return '中高频'
|
||||
if (ttl <= 30) return '中频'
|
||||
return '低频'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取使用频率样式类名
|
||||
*/
|
||||
export function getFrequencyClass(ttl: number): string {
|
||||
if (ttl <= 5) return 'text-success font-medium'
|
||||
if (ttl <= 15) return 'text-blue-500 font-medium'
|
||||
if (ttl <= 30) return 'text-muted-foreground'
|
||||
return 'text-destructive'
|
||||
}
|
||||
|
||||
export function useTTLAnalysis() {
|
||||
const { error: showError, info: showInfo } = useToast()
|
||||
|
||||
// 状态
|
||||
const ttlAnalysis = ref<TTLAnalysisResponse | null>(null)
|
||||
const hitAnalysis = ref<CacheHitAnalysisResponse | null>(null)
|
||||
const ttlAnalysisLoading = ref(false)
|
||||
const hitAnalysisLoading = ref(false)
|
||||
const analysisHours = ref('24')
|
||||
|
||||
// 用户散点图展开状态
|
||||
const expandedUserId = ref<string | null>(null)
|
||||
const userTimelineData = ref<IntervalTimelineResponse | null>(null)
|
||||
const userTimelineLoading = ref(false)
|
||||
|
||||
// 计算属性:是否正在加载
|
||||
const isLoading = computed(() => ttlAnalysisLoading.value || hitAnalysisLoading.value)
|
||||
|
||||
// 获取 TTL 分析数据
|
||||
async function fetchTTLAnalysis() {
|
||||
ttlAnalysisLoading.value = true
|
||||
try {
|
||||
const hours = parseInt(analysisHours.value)
|
||||
const result = await cacheAnalysisApi.analyzeTTL({ hours })
|
||||
ttlAnalysis.value = result
|
||||
|
||||
if (result.total_users_analyzed === 0) {
|
||||
const periodText = hours >= 24 ? `${hours / 24} 天` : `${hours} 小时`
|
||||
showInfo(`未找到符合条件的数据(最近 ${periodText})`)
|
||||
}
|
||||
} catch (error) {
|
||||
showError('获取 TTL 分析失败')
|
||||
console.error(error)
|
||||
} finally {
|
||||
ttlAnalysisLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取缓存命中分析数据
|
||||
async function fetchHitAnalysis() {
|
||||
hitAnalysisLoading.value = true
|
||||
try {
|
||||
hitAnalysis.value = await cacheAnalysisApi.analyzeHit({
|
||||
hours: parseInt(analysisHours.value)
|
||||
})
|
||||
} catch (error) {
|
||||
showError('获取缓存命中分析失败')
|
||||
console.error(error)
|
||||
} finally {
|
||||
hitAnalysisLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取指定用户的时间线数据
|
||||
async function fetchUserTimeline(userId: string) {
|
||||
userTimelineLoading.value = true
|
||||
try {
|
||||
userTimelineData.value = await cacheAnalysisApi.getIntervalTimeline({
|
||||
hours: parseInt(analysisHours.value),
|
||||
limit: 2000,
|
||||
user_id: userId
|
||||
})
|
||||
} catch (error) {
|
||||
showError('获取用户时间线数据失败')
|
||||
console.error(error)
|
||||
} finally {
|
||||
userTimelineLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 切换用户行展开状态
|
||||
async function toggleUserExpand(userId: string) {
|
||||
if (expandedUserId.value === userId) {
|
||||
expandedUserId.value = null
|
||||
userTimelineData.value = null
|
||||
} else {
|
||||
expandedUserId.value = userId
|
||||
await fetchUserTimeline(userId)
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新所有分析数据
|
||||
async function refreshAnalysis() {
|
||||
expandedUserId.value = null
|
||||
userTimelineData.value = null
|
||||
await Promise.all([fetchTTLAnalysis(), fetchHitAnalysis()])
|
||||
}
|
||||
|
||||
// 用户时间线散点图数据
|
||||
const userTimelineChartData = computed<ChartData<'scatter'>>(() => {
|
||||
if (!userTimelineData.value || userTimelineData.value.points.length === 0) {
|
||||
return { datasets: [] }
|
||||
}
|
||||
|
||||
const points = userTimelineData.value.points
|
||||
|
||||
return {
|
||||
datasets: [{
|
||||
label: '请求间隔',
|
||||
data: points.map(p => ({ x: p.x, y: p.y })),
|
||||
backgroundColor: points.map(p => getIntervalColor(p.y)),
|
||||
borderColor: points.map(p => getIntervalColor(p.y).replace('0.6', '1')),
|
||||
pointRadius: 3,
|
||||
pointHoverRadius: 5
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
// 监听时间范围变化
|
||||
watch(analysisHours, () => {
|
||||
refreshAnalysis()
|
||||
})
|
||||
|
||||
return {
|
||||
// 状态
|
||||
ttlAnalysis,
|
||||
hitAnalysis,
|
||||
ttlAnalysisLoading,
|
||||
hitAnalysisLoading,
|
||||
analysisHours,
|
||||
expandedUserId,
|
||||
userTimelineData,
|
||||
userTimelineLoading,
|
||||
isLoading,
|
||||
userTimelineChartData,
|
||||
|
||||
// 方法
|
||||
fetchTTLAnalysis,
|
||||
fetchHitAnalysis,
|
||||
fetchUserTimeline,
|
||||
toggleUserExpand,
|
||||
refreshAnalysis
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user