Files
SingBox-Gopanel/internal/handler/admin_stat_api.go
CN-JS-HuiBai b3435e5ef8
Some checks failed
build / build (api, amd64, linux) (push) Has been cancelled
build / build (api, arm64, linux) (push) Has been cancelled
build / build (api.exe, amd64, windows) (push) Has been cancelled
基本功能已初步完善
2026-04-17 20:41:47 +08:00

251 lines
6.4 KiB
Go

package handler
import (
"fmt"
"strconv"
"time"
"xboard-go/internal/database"
"xboard-go/internal/model"
"github.com/gin-gonic/gin"
)
// AdminGetTrafficRank returns traffic ranking for nodes or users.
func AdminGetTrafficRank(c *gin.Context) {
rankType := c.Query("type") // node or user
startTimeStr := c.Query("start_time")
endTimeStr := c.Query("end_time")
startTime, _ := strconv.ParseInt(startTimeStr, 10, 64)
endTime, _ := strconv.ParseInt(endTimeStr, 10, 64)
if startTime == 0 {
startTime = time.Now().AddDate(0, 0, -7).Unix()
}
if endTime == 0 {
endTime = time.Now().Unix()
}
periodDuration := endTime - startTime
if periodDuration <= 0 {
periodDuration = 86400
}
previousStart := startTime - periodDuration
previousEnd := startTime
var result []gin.H
if rankType == "user" {
type userRank struct {
ID int `json:"id"`
Value int64 `json:"value"`
}
var ranks []userRank
database.DB.Model(&model.StatUser{}).
Select("user_id as id, SUM(u + d) as value").
Where("record_at >= ? AND record_at <= ?", startTime, endTime).
Group("user_id").
Order("value DESC").
Limit(10).
Scan(&ranks)
var userIDs []int
for _, r := range ranks {
userIDs = append(userIDs, r.ID)
}
userEmails := loadUserEmailMap(userIDs)
previousValues := loadUserPreviousTrafficMap(userIDs, previousStart, previousEnd)
for _, r := range ranks {
previousValue := previousValues[r.ID]
result = append(result, gin.H{
"id": fmt.Sprintf("%d", r.ID),
"name": userEmails[r.ID],
"value": r.Value,
"previousValue": previousValue,
"change": calculateGrowth(r.Value, previousValue),
})
}
} else {
// Default to node
type nodeRank struct {
ID int `json:"id"`
Value int64 `json:"value"`
Name string `json:"name"`
}
var ranks []nodeRank
database.DB.Model(&model.StatServer{}).
Select("server_id as id, SUM(u + d) as value").
Where("record_at >= ? AND record_at <= ?", startTime, endTime).
Group("server_id").
Order("value DESC").
Limit(10).
Scan(&ranks)
var nodeIDs []int
for _, r := range ranks {
nodeIDs = append(nodeIDs, r.ID)
}
nodeNames := loadNodeNameMap(nodeIDs)
previousValues := loadNodePreviousTrafficMap(nodeIDs, previousStart, previousEnd)
for _, r := range ranks {
previousValue := previousValues[r.ID]
result = append(result, gin.H{
"id": fmt.Sprintf("%d", r.ID),
"name": nodeNames[r.ID],
"value": r.Value,
"previousValue": previousValue,
"change": calculateGrowth(r.Value, previousValue),
})
}
}
Success(c, result)
}
// AdminGetOrderStats returns order-related statistics for charts.
func AdminGetOrderStats(c *gin.Context) {
startDateStr := c.Query("start_date")
endDateStr := c.Query("end_date")
statType := c.Query("type") // paid_total, paid_count, etc.
var startDate int64
var endDate int64
if startDateStr != "" {
t, _ := time.Parse("2006-01-02", startDateStr)
startDate = t.Unix()
} else {
startDate = time.Now().AddDate(0, 0, -30).Truncate(24 * time.Hour).Unix()
}
if endDateStr != "" {
t, _ := time.Parse("2006-01-02", endDateStr)
endDate = t.Unix() + 86399
} else {
endDate = time.Now().Unix()
}
var stats []model.Stat
database.DB.Where("record_at >= ? AND record_at <= ? AND record_type = ?", startDate, endDate, "d").
Order("record_at ASC").
Find(&stats)
var list []gin.H
var paidTotal int64
var paidCount int64
var commissionTotal int64
var commissionCount int64
for _, s := range stats {
dateStr := time.Unix(s.RecordAt, 0).Format("2006-01-02")
item := gin.H{
"date": dateStr,
}
if statType != "" {
item["value"] = getStatFieldValue(s, statType)
} else {
item["paid_total"] = s.PaidTotal
item["paid_count"] = s.PaidCount
item["commission_total"] = s.CommissionTotal
item["commission_count"] = s.CommissionCount
}
paidTotal += s.PaidTotal
paidCount += int64(s.PaidCount)
commissionTotal += s.CommissionTotal
commissionCount += int64(s.CommissionCount)
list = append(list, item)
}
avgPaidAmount := int64(0)
if paidCount > 0 {
avgPaidAmount = paidTotal / paidCount
}
commissionRate := 0.0
if paidTotal > 0 {
commissionRate = float64(commissionTotal) / float64(paidTotal) * 100.0
}
Success(c, gin.H{
"list": list,
"summary": gin.H{
"start_date": time.Unix(startDate, 0).Format("2006-01-02"),
"end_date": time.Unix(endDate, 0).Format("2006-01-02"),
"paid_total": paidTotal,
"paid_count": paidCount,
"avg_paid_amount": avgPaidAmount,
"commission_total": commissionTotal,
"commission_count": commissionCount,
"commission_rate": commissionRate,
},
})
}
func loadUserPreviousTrafficMap(ids []int, startTime int64, endTime int64) map[int]int64 {
result := make(map[int]int64)
if len(ids) == 0 {
return result
}
type userRank struct {
ID int `json:"id"`
Value int64 `json:"value"`
}
var ranks []userRank
database.DB.Model(&model.StatUser{}).
Select("user_id as id, SUM(u + d) as value").
Where("user_id IN ? AND record_at >= ? AND record_at <= ?", ids, startTime, endTime).
Group("user_id").
Scan(&ranks)
for _, rank := range ranks {
result[rank.ID] = rank.Value
}
return result
}
func loadNodePreviousTrafficMap(ids []int, startTime int64, endTime int64) map[int]int64 {
result := make(map[int]int64)
if len(ids) == 0 {
return result
}
type nodeRank struct {
ID int `json:"id"`
Value int64 `json:"value"`
}
var ranks []nodeRank
database.DB.Model(&model.StatServer{}).
Select("server_id as id, SUM(u + d) as value").
Where("server_id IN ? AND record_at >= ? AND record_at <= ?", ids, startTime, endTime).
Group("server_id").
Scan(&ranks)
for _, rank := range ranks {
result[rank.ID] = rank.Value
}
return result
}
func loadNodeNameMap(ids []int) map[int]string {
result := make(map[int]string)
if len(ids) == 0 {
return result
}
var servers []model.Server
database.DB.Select("id", "name").Where("id IN ?", ids).Find(&servers)
for _, s := range servers {
result[s.ID] = s.Name
}
return result
}
func getStatFieldValue(s model.Stat, field string) any {
switch field {
case "paid_total":
return s.PaidTotal
case "paid_count":
return s.PaidCount
case "commission_total":
return s.CommissionTotal
case "commission_count":
return s.CommissionCount
case "register_count":
return s.RegisterCount
default:
return 0
}
}