181 lines
5.3 KiB
Go
181 lines
5.3 KiB
Go
package handler
|
||
|
||
import (
|
||
app_errors "gpt-load/internal/errors"
|
||
"gpt-load/internal/models"
|
||
"gpt-load/internal/response"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// Stats Get dashboard statistics
|
||
func (s *Server) Stats(c *gin.Context) {
|
||
var activeKeys, invalidKeys, groupCount 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()
|
||
twentyFourHoursAgo := now.Add(-24 * time.Hour)
|
||
fortyEightHoursAgo := now.Add(-48 * time.Hour)
|
||
|
||
currentPeriod, err := s.getHourlyStats(twentyFourHoursAgo, now)
|
||
if err != nil {
|
||
response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, "failed to get current period stats"))
|
||
return
|
||
}
|
||
previousPeriod, err := s.getHourlyStats(fortyEightHoursAgo, twentyFourHoursAgo)
|
||
if err != nil {
|
||
response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, "failed to get previous period stats"))
|
||
return
|
||
}
|
||
|
||
// 计算请求量趋势
|
||
reqTrend := 0.0
|
||
reqTrendIsGrowth := true
|
||
if previousPeriod.TotalRequests > 0 {
|
||
// 有前期数据,计算百分比变化
|
||
reqTrend = (float64(currentPeriod.TotalRequests-previousPeriod.TotalRequests) / float64(previousPeriod.TotalRequests)) * 100
|
||
reqTrendIsGrowth = reqTrend >= 0
|
||
} else if currentPeriod.TotalRequests > 0 {
|
||
// 前期无数据,当前有数据,视为100%增长
|
||
reqTrend = 100.0
|
||
reqTrendIsGrowth = true
|
||
} else {
|
||
// 前期和当前都无数据
|
||
reqTrend = 0.0
|
||
reqTrendIsGrowth = true
|
||
}
|
||
|
||
// 计算当前和前期错误率
|
||
currentErrorRate := 0.0
|
||
if currentPeriod.TotalRequests > 0 {
|
||
currentErrorRate = (float64(currentPeriod.TotalFailures) / float64(currentPeriod.TotalRequests)) * 100
|
||
}
|
||
|
||
previousErrorRate := 0.0
|
||
if previousPeriod.TotalRequests > 0 {
|
||
previousErrorRate = (float64(previousPeriod.TotalFailures) / float64(previousPeriod.TotalRequests)) * 100
|
||
}
|
||
|
||
// 计算错误率趋势
|
||
errorRateTrend := 0.0
|
||
errorRateTrendIsGrowth := false
|
||
if previousPeriod.TotalRequests > 0 {
|
||
// 有前期数据,计算百分点差异
|
||
errorRateTrend = currentErrorRate - previousErrorRate
|
||
errorRateTrendIsGrowth = errorRateTrend < 0 // 错误率下降是好事
|
||
} else if currentPeriod.TotalRequests > 0 {
|
||
// 前期无数据,当前有数据
|
||
errorRateTrend = currentErrorRate // 显示当前错误率
|
||
errorRateTrendIsGrowth = false // 有错误是坏事(如果错误率>0)
|
||
if currentErrorRate == 0 {
|
||
errorRateTrendIsGrowth = true // 如果当前无错误,标记为正面
|
||
}
|
||
} else {
|
||
// 都无数据
|
||
errorRateTrend = 0.0
|
||
errorRateTrendIsGrowth = true
|
||
}
|
||
|
||
stats := models.DashboardStatsResponse{
|
||
KeyCount: models.StatCard{
|
||
Value: float64(activeKeys),
|
||
SubValue: invalidKeys,
|
||
SubValueTip: "无效秘钥数量",
|
||
},
|
||
GroupCount: models.StatCard{
|
||
Value: float64(groupCount),
|
||
},
|
||
RequestCount: models.StatCard{
|
||
Value: float64(currentPeriod.TotalRequests),
|
||
Trend: reqTrend,
|
||
TrendIsGrowth: reqTrendIsGrowth,
|
||
},
|
||
ErrorRate: models.StatCard{
|
||
Value: currentErrorRate,
|
||
Trend: errorRateTrend,
|
||
TrendIsGrowth: errorRateTrendIsGrowth,
|
||
},
|
||
}
|
||
|
||
response.Success(c, stats)
|
||
}
|
||
|
||
// Chart Get dashboard chart data
|
||
func (s *Server) Chart(c *gin.Context) {
|
||
groupID := c.Query("groupId")
|
||
|
||
now := time.Now()
|
||
twentyFourHoursAgo := now.Add(-24 * time.Hour)
|
||
|
||
var hourlyStats []models.GroupHourlyStat
|
||
query := s.DB.Where("time >= ? AND time < ?", twentyFourHoursAgo, now)
|
||
if groupID != "" {
|
||
query = query.Where("group_id = ?", groupID)
|
||
}
|
||
if err := query.Order("time asc").Find(&hourlyStats).Error; err != nil {
|
||
response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, "failed to get chart data"))
|
||
return
|
||
}
|
||
|
||
statsByHour := make(map[time.Time]map[string]int64)
|
||
for _, stat := range hourlyStats {
|
||
hour := stat.Time.Truncate(time.Hour)
|
||
if _, ok := statsByHour[hour]; !ok {
|
||
statsByHour[hour] = make(map[string]int64)
|
||
}
|
||
statsByHour[hour]["success"] += stat.SuccessCount
|
||
statsByHour[hour]["failure"] += stat.FailureCount
|
||
}
|
||
|
||
var labels []string
|
||
var successData, failureData []int64
|
||
|
||
for i := 0; i < 24; i++ {
|
||
hour := twentyFourHoursAgo.Add(time.Duration(i) * time.Hour).Truncate(time.Hour)
|
||
labels = append(labels, hour.Format(time.RFC3339))
|
||
|
||
if data, ok := statsByHour[hour]; ok {
|
||
successData = append(successData, data["success"])
|
||
failureData = append(failureData, data["failure"])
|
||
} else {
|
||
successData = append(successData, 0)
|
||
failureData = append(failureData, 0)
|
||
}
|
||
}
|
||
|
||
chartData := models.ChartData{
|
||
Labels: labels,
|
||
Datasets: []models.ChartDataset{
|
||
{
|
||
Label: "成功请求",
|
||
Data: successData,
|
||
Color: "rgba(10, 200, 110, 1)",
|
||
},
|
||
{
|
||
Label: "失败请求",
|
||
Data: failureData,
|
||
Color: "rgba(255, 70, 70, 1)",
|
||
},
|
||
},
|
||
}
|
||
|
||
response.Success(c, chartData)
|
||
}
|
||
|
||
type hourlyStatResult struct {
|
||
TotalRequests int64
|
||
TotalFailures int64
|
||
}
|
||
|
||
func (s *Server) getHourlyStats(startTime, endTime time.Time) (hourlyStatResult, error) {
|
||
var result hourlyStatResult
|
||
err := s.DB.Model(&models.GroupHourlyStat{}).
|
||
Select("sum(success_count) + sum(failure_count) as total_requests, sum(failure_count) as total_failures").
|
||
Where("time >= ? AND time < ?", startTime, endTime).
|
||
Scan(&result).Error
|
||
return result, err
|
||
}
|