From 4184a32c0fd1c6e1187032751c5a76e1ffe56bb0 Mon Sep 17 00:00:00 2001 From: Chang lue Tsen Date: Mon, 1 Sep 2025 05:30:05 -0400 Subject: [PATCH] feat(api): update server total data response to use 'OnlineUsers' and implement daily traffic statistics logging --- apis/admin/console.api | 2 +- .../console/queryServerTotalDataLogic.go | 74 ++++++++++++++++++- internal/model/log/log.go | 25 +++++++ internal/types/types.go | 2 +- queue/logic/traffic/trafficStatLogic.go | 24 ++++++ 5 files changed, 124 insertions(+), 3 deletions(-) diff --git a/apis/admin/console.api b/apis/admin/console.api index c20bb14..1a8ded7 100644 --- a/apis/admin/console.api +++ b/apis/admin/console.api @@ -21,7 +21,7 @@ type ( Download int64 `json:"download"` } ServerTotalDataResponse { - OnlineUserIPs int64 `json:"online_user_ips"` + OnlineUsers int64 `json:"online_users"` OnlineServers int64 `json:"online_servers"` OfflineServers int64 `json:"offline_servers"` TodayUpload int64 `json:"today_upload"` diff --git a/internal/logic/admin/console/queryServerTotalDataLogic.go b/internal/logic/admin/console/queryServerTotalDataLogic.go index b45bb33..ce1a179 100644 --- a/internal/logic/admin/console/queryServerTotalDataLogic.go +++ b/internal/logic/admin/console/queryServerTotalDataLogic.go @@ -7,6 +7,7 @@ import ( "time" "github.com/perfect-panel/server/internal/model/log" + "github.com/perfect-panel/server/internal/model/node" "github.com/perfect-panel/server/internal/model/traffic" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" @@ -149,7 +150,78 @@ func (l *QueryServerTotalDataLogic) QueryServerTotalData() (resp *types.ServerTo } } + // query online user count + onlineUsers, err := l.svcCtx.NodeModel.OnlineUserSubscribeGlobal(l.ctx) + if err != nil { + l.Errorw("[QueryServerTotalDataLogic] OnlineUserSubscribeGlobal error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "OnlineUserSubscribeGlobal error: %v", err) + } + + // query online/offline server count + var onlineServers, offlineServers int64 + err = query.Model(&node.Server{}).Where("`last_reported_at` > ?", now.Add(-5*time.Minute)).Count(&onlineServers).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + l.Errorw("[QueryServerTotalDataLogic] Count online servers error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Count online servers error: %v", err) + } + + err = query.Model(&node.Server{}).Where("`last_reported_at` <= ? OR `last_reported_at` IS NULL", now.Add(-5*time.Minute)).Count(&offlineServers).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + l.Errorw("[QueryServerTotalDataLogic] Count offline servers error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Count offline servers error: %v", err) + } + // TodayUpload, TodayDownload, MonthlyUpload, MonthlyDownload + var todayUpload, todayDownload, monthlyUpload, monthlyDownload int64 + + type trafficSum struct { + Upload int64 + Download int64 + } + var todayTraffic trafficSum + // Today + err = query.Model(&traffic.TrafficLog{}).Select("SUM(upload) AS upload, SUM(download) AS download"). + Where("timestamp BETWEEN ? AND ?", todayStart, todayEnd). + Scan(&todayTraffic).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + l.Errorw("[QueryServerTotalDataLogic] Sum today traffic error", logger.Field("error", err.Error())) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Sum today traffic error: %v", err) + } + todayUpload = todayTraffic.Upload + todayDownload = todayTraffic.Download + + // Monthly + monthlyUpload += todayUpload + monthlyDownload += todayDownload + + for i := now.Day() - 1; i >= 1; i-- { + var logInfo log.SystemLog + date := time.Date(now.Year(), now.Month(), i, 0, 0, 0, 0, now.Location()).Format(time.DateOnly) + err = query.Model(&log.SystemLog{}).Where("`date` = ? AND `type` = ?", date, log.TypeTrafficStat).First(&logInfo).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + l.Errorw("[QueryServerTotalDataLogic] Query daily traffic stat log error", logger.Field("error", err.Error()), logger.Field("date", date)) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query daily traffic stat log error: %v", err) + } + if logInfo.Id > 0 { + var stat log.TrafficStat + err = stat.Unmarshal([]byte(logInfo.Content)) + if err != nil { + l.Errorw("[QueryServerTotalDataLogic] Unmarshal daily traffic stat log error", logger.Field("error", err.Error()), logger.Field("date", date)) + continue + } + monthlyUpload += stat.Upload + monthlyDownload += stat.Download + } + } + resp = &types.ServerTotalDataResponse{ + OnlineUsers: onlineUsers, + OnlineServers: onlineServers, + OfflineServers: offlineServers, + TodayUpload: todayUpload, + TodayDownload: todayDownload, + MonthlyUpload: monthlyUpload, + MonthlyDownload: monthlyDownload, + UpdatedAt: now.Unix(), ServerTrafficRankingToday: todayServerRanking, ServerTrafficRankingYesterday: yesterdayTop10Server, UserTrafficRankingToday: userTodayTrafficRanking, @@ -214,7 +286,7 @@ func (l *QueryServerTotalDataLogic) mockRevenueStatistics() *types.ServerTotalDa //} // return &types.ServerTotalDataResponse{ - OnlineUserIPs: 1688, + OnlineUsers: 1688, OnlineServers: 8, OfflineServers: 2, TodayUpload: 8888888888, // ~8.3GB diff --git a/internal/model/log/log.go b/internal/model/log/log.go index 1e86628..c327249 100644 --- a/internal/model/log/log.go +++ b/internal/model/log/log.go @@ -30,6 +30,7 @@ const ( TypeGift Type = 34 // Gift log TypeUserTrafficRank Type = 40 // Top 10 User traffic rank log TypeServerTrafficRank Type = 41 // Top 10 Server traffic rank log + TypeTrafficStat Type = 42 // Daily traffic statistics log ) const ( ResetSubscribeTypeAuto uint16 = 231 // Auto reset @@ -393,3 +394,27 @@ func (s *ServerTrafficRank) Unmarshal(data []byte) error { aux := (*Alias)(s) return json.Unmarshal(data, aux) } + +// TrafficStat represents a daily traffic statistics log entry. +type TrafficStat struct { + Upload int64 `json:"upload"` + Download int64 `json:"download"` + Total int64 `json:"total"` +} + +// Marshal implements the json.Marshaler interface for TrafficStat. +func (t *TrafficStat) Marshal() ([]byte, error) { + type Alias TrafficStat + return json.Marshal(&struct { + *Alias + }{ + Alias: (*Alias)(t), + }) +} + +// Unmarshal implements the json.Unmarshaler interface for TrafficStat. +func (t *TrafficStat) Unmarshal(data []byte) error { + type Alias TrafficStat + aux := (*Alias)(t) + return json.Unmarshal(data, aux) +} diff --git a/internal/types/types.go b/internal/types/types.go index ece910e..9c3e6f1 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -1739,7 +1739,7 @@ type ServerStatus struct { } type ServerTotalDataResponse struct { - OnlineUserIPs int64 `json:"online_user_ips"` + OnlineUsers int64 `json:"online_users"` OnlineServers int64 `json:"online_servers"` OfflineServers int64 `json:"offline_servers"` TodayUpload int64 `json:"today_upload"` diff --git a/queue/logic/traffic/trafficStatLogic.go b/queue/logic/traffic/trafficStatLogic.go index 46e565d..e2f1cfc 100644 --- a/queue/logic/traffic/trafficStatLogic.go +++ b/queue/logic/traffic/trafficStatLogic.go @@ -141,6 +141,30 @@ func (l *StatLogic) ProcessTask(ctx context.Context, _ *asynq.Task) error { return err } + // traffic stat + var stat log.TrafficStat + err = tx.WithContext(ctx).Model(&traffic.TrafficLog{}). + Select("SUM(download + upload) AS total, SUM(download) AS download, SUM(upload) AS upload"). + Where("timestamp BETWEEN ? AND ?", start, end). + Scan(&stat).Error + if err != nil { + logger.Errorf("[Traffic Stat Queue] Query traffic stat failed: %v", err.Error()) + return err + } + + // 更新流量统计日志 + content, _ := stat.Marshal() + err = tx.WithContext(ctx).Model(&log.SystemLog{}).Create(&log.SystemLog{ + Type: log.TypeTrafficStat.Uint8(), + Date: date, + ObjectID: 0, + Content: string(content), + }).Error + if err != nil { + logger.Errorf("[Traffic Stat Queue] Create traffic stat log failed: %v", err.Error()) + return err + } + // Delete old traffic logs err = tx.WithContext(ctx).Model(&traffic.TrafficLog{}).Where("created_at <= ?", end).Delete(&traffic.TrafficLog{}).Error if err != nil {