@@ -11,12 +11,16 @@ import (
|
||||
|
||||
// Stats Get dashboard statistics
|
||||
func (s *Server) Stats(c *gin.Context) {
|
||||
var activeKeys, invalidKeys, groupCount int64
|
||||
var activeKeys, invalidKeys int64
|
||||
s.DB.Model(&models.APIKey{}).Where("status = ?", models.KeyStatusActive).Count(&activeKeys)
|
||||
s.DB.Model(&models.APIKey{}).Where("status = ?", models.KeyStatusInvalid).Count(&invalidKeys)
|
||||
s.DB.Model(&models.Group{}).Count(&groupCount)
|
||||
|
||||
now := time.Now()
|
||||
rpmStats, err := s.getRPMStats(now)
|
||||
if err != nil {
|
||||
response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, "failed to get rpm stats"))
|
||||
return
|
||||
}
|
||||
twentyFourHoursAgo := now.Add(-24 * time.Hour)
|
||||
fortyEightHoursAgo := now.Add(-48 * time.Hour)
|
||||
|
||||
@@ -85,9 +89,7 @@ func (s *Server) Stats(c *gin.Context) {
|
||||
SubValue: invalidKeys,
|
||||
SubValueTip: "无效密钥数量",
|
||||
},
|
||||
GroupCount: models.StatCard{
|
||||
Value: float64(groupCount),
|
||||
},
|
||||
RPM: rpmStats,
|
||||
RequestCount: models.StatCard{
|
||||
Value: float64(currentPeriod.TotalRequests),
|
||||
Trend: reqTrend,
|
||||
@@ -179,3 +181,42 @@ func (s *Server) getHourlyStats(startTime, endTime time.Time) (hourlyStatResult,
|
||||
Scan(&result).Error
|
||||
return result, err
|
||||
}
|
||||
|
||||
type rpmStatResult struct {
|
||||
CurrentRequests int64
|
||||
PreviousRequests int64
|
||||
}
|
||||
|
||||
func (s *Server) getRPMStats(now time.Time) (models.StatCard, error) {
|
||||
tenMinutesAgo := now.Add(-10 * time.Minute)
|
||||
twentyMinutesAgo := now.Add(-20 * time.Minute)
|
||||
|
||||
var result rpmStatResult
|
||||
err := s.DB.Model(&models.RequestLog{}).
|
||||
Select("count(case when timestamp >= ? then 1 end) as current_requests, count(case when timestamp >= ? and timestamp < ? then 1 end) as previous_requests", tenMinutesAgo, twentyMinutesAgo, tenMinutesAgo).
|
||||
Where("timestamp >= ?", twentyMinutesAgo).
|
||||
Scan(&result).Error
|
||||
|
||||
if err != nil {
|
||||
return models.StatCard{}, err
|
||||
}
|
||||
|
||||
currentRPM := float64(result.CurrentRequests) / 10.0
|
||||
previousRPM := float64(result.PreviousRequests) / 10.0
|
||||
|
||||
rpmTrend := 0.0
|
||||
rpmTrendIsGrowth := true
|
||||
if previousRPM > 0 {
|
||||
rpmTrend = (currentRPM - previousRPM) / previousRPM * 100
|
||||
rpmTrendIsGrowth = rpmTrend >= 0
|
||||
} else if currentRPM > 0 {
|
||||
rpmTrend = 100.0
|
||||
rpmTrendIsGrowth = true
|
||||
}
|
||||
|
||||
return models.StatCard{
|
||||
Value: currentRPM,
|
||||
Trend: rpmTrend,
|
||||
TrendIsGrowth: rpmTrendIsGrowth,
|
||||
}, nil
|
||||
}
|
||||
|
@@ -107,7 +107,7 @@ type StatCard struct {
|
||||
// DashboardStatsResponse 用于仪表盘基础统计的API响应
|
||||
type DashboardStatsResponse struct {
|
||||
KeyCount StatCard `json:"key_count"`
|
||||
GroupCount StatCard `json:"group_count"`
|
||||
RPM StatCard `json:"rpm"`
|
||||
RequestCount StatCard `json:"request_count"`
|
||||
ErrorRate StatCard `json:"error_rate"`
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ const fetchStats = async () => {
|
||||
key_count:
|
||||
(stats.value?.key_count?.value ?? 0) /
|
||||
((stats.value?.key_count?.value ?? 1) + (stats.value?.key_count?.sub_value ?? 1)),
|
||||
group_count: 1,
|
||||
rpm: Math.min(100 + (stats.value?.rpm?.trend ?? 0), 100) / 100,
|
||||
request_count: Math.min(100 + (stats.value?.request_count?.trend ?? 0), 100) / 100,
|
||||
error_rate: (100 - (stats.value?.error_rate?.value ?? 0)) / 100,
|
||||
};
|
||||
@@ -93,25 +93,33 @@ onMounted(() => {
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
|
||||
<!-- 分组数量 -->
|
||||
<!-- RPM (10分钟) -->
|
||||
<n-grid-item span="1">
|
||||
<n-card :bordered="false" class="stat-card" style="animation-delay: 0.05s">
|
||||
<div class="stat-header">
|
||||
<div class="stat-icon group-icon">📁</div>
|
||||
<div class="stat-icon rpm-icon">⏱️</div>
|
||||
<n-tag
|
||||
v-if="stats?.rpm && stats.rpm.trend !== undefined"
|
||||
:type="stats?.rpm.trend_is_growth ? 'success' : 'error'"
|
||||
size="small"
|
||||
class="stat-trend"
|
||||
>
|
||||
{{ stats ? formatTrend(stats.rpm.trend) : "--" }}
|
||||
</n-tag>
|
||||
</div>
|
||||
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">
|
||||
{{ stats?.group_count?.value ?? 0 }}
|
||||
{{ stats?.rpm?.value.toFixed(1) ?? 0 }}
|
||||
</div>
|
||||
<div class="stat-title">分组数量</div>
|
||||
<div class="stat-title">10分钟RPM</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-bar">
|
||||
<div
|
||||
class="stat-bar-fill group-bar"
|
||||
class="stat-bar-fill rpm-bar"
|
||||
:style="{
|
||||
width: `${(animatedValues.group_count ?? 0) * 100}%`,
|
||||
width: `${(animatedValues.rpm ?? 0) * 100}%`,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
@@ -232,7 +240,7 @@ onMounted(() => {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.group-icon {
|
||||
.rpm-icon {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
|
||||
@@ -295,7 +303,7 @@ onMounted(() => {
|
||||
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.group-bar {
|
||||
.rpm-bar {
|
||||
background: linear-gradient(90deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
|
||||
|
@@ -171,7 +171,7 @@ export interface StatCard {
|
||||
// 仪表盘基础统计响应
|
||||
export interface DashboardStatsResponse {
|
||||
key_count: StatCard;
|
||||
group_count: StatCard;
|
||||
rpm: StatCard;
|
||||
request_count: StatCard;
|
||||
error_rate: StatCard;
|
||||
}
|
||||
|
Reference in New Issue
Block a user