perf: 优化 Dashboard 接口性能,并行执行数据库查询
- 使用 goroutine 并行执行 8 个数据库查询 - 将串行查询改为并行,显著减少响应时间 - 使用 sync.WaitGroup 和 sync.Mutex 保证线程安全
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"verification-platform-backend/internal/database"
|
"verification-platform-backend/internal/database"
|
||||||
"verification-platform-backend/internal/model"
|
"verification-platform-backend/internal/model"
|
||||||
@@ -23,29 +24,6 @@ func handleGetDashboard(c *gin.Context) {
|
|||||||
MonthlyRevenue float64 `json:"monthlyRevenue"`
|
MonthlyRevenue float64 `json:"monthlyRevenue"`
|
||||||
}
|
}
|
||||||
|
|
||||||
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Count(&stats.TotalApplications)
|
|
||||||
|
|
||||||
var appIDs []uint
|
|
||||||
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Pluck("id", &appIDs)
|
|
||||||
if len(appIDs) > 0 {
|
|
||||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Count(&stats.TotalUsers)
|
|
||||||
}
|
|
||||||
|
|
||||||
database.DB.Model(&model.Card{}).Where("creator_id = ?", userID).Count(&stats.TotalCards)
|
|
||||||
|
|
||||||
var monthlyRevenue float64
|
|
||||||
if len(appIDs) > 0 {
|
|
||||||
var appUserIDs []uint
|
|
||||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Pluck("id", &appUserIDs)
|
|
||||||
if len(appUserIDs) > 0 {
|
|
||||||
database.DB.Model(&model.RechargeRecord{}).
|
|
||||||
Where("user_id IN ? AND status = ? AND created_at >= ?", appUserIDs, "success", time.Now().AddDate(0, -1, 0)).
|
|
||||||
Select("COALESCE(SUM(amount), 0)").
|
|
||||||
Scan(&monthlyRevenue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stats.MonthlyRevenue = monthlyRevenue
|
|
||||||
|
|
||||||
var subscription struct {
|
var subscription struct {
|
||||||
Plan string `json:"plan"`
|
Plan string `json:"plan"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
@@ -58,103 +36,170 @@ func handleGetDashboard(c *gin.Context) {
|
|||||||
StorageUsed int64 `json:"storageUsed"`
|
StorageUsed int64 `json:"storageUsed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var user model.User
|
|
||||||
if err := database.DB.First(&user, userID).Error; err == nil {
|
|
||||||
subscription.Plan = "基础版"
|
|
||||||
if user.Role == "admin" {
|
|
||||||
subscription.Plan = "管理员"
|
|
||||||
}
|
|
||||||
subscription.Status = "正常"
|
|
||||||
if user.Status == "banned" {
|
|
||||||
subscription.Status = "已禁用"
|
|
||||||
}
|
|
||||||
subscription.ExpireDate = "永久"
|
|
||||||
}
|
|
||||||
subscription.AppCount = int(stats.TotalApplications)
|
|
||||||
subscription.APIQuota = 10000
|
|
||||||
subscription.StorageQuota = 100 * 1024 * 1024
|
|
||||||
|
|
||||||
var apiUsed int64
|
|
||||||
database.DB.Model(&model.ApiUsage{}).Where("user_id = ?", userID).Count(&apiUsed)
|
|
||||||
subscription.APIUsed = int(apiUsed)
|
|
||||||
|
|
||||||
var storageUsed int64
|
|
||||||
database.DB.Model(&model.StorageUsage{}).Where("user_id = ?", userID).Select("COALESCE(SUM(size), 0)").Scan(&storageUsed)
|
|
||||||
subscription.StorageUsed = storageUsed
|
|
||||||
|
|
||||||
userDistribution := gin.H{
|
userDistribution := gin.H{
|
||||||
"provinces": []gin.H{},
|
"provinces": []gin.H{},
|
||||||
"overseas": []gin.H{},
|
"overseas": []gin.H{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(appIDs) > 0 {
|
|
||||||
type DeviceCount struct {
|
|
||||||
DeviceType string
|
|
||||||
Count int
|
|
||||||
}
|
|
||||||
var deviceCounts []DeviceCount
|
|
||||||
database.DB.Model(&model.UserDevice{}).
|
|
||||||
Select("device_type, COUNT(*) as count").
|
|
||||||
Where("application_id IN ?", appIDs).
|
|
||||||
Group("device_type").
|
|
||||||
Order("count DESC").
|
|
||||||
Find(&deviceCounts)
|
|
||||||
|
|
||||||
provinces := make([]gin.H, 0, len(deviceCounts))
|
|
||||||
for _, dc := range deviceCounts {
|
|
||||||
provinces = append(provinces, gin.H{
|
|
||||||
"name": dc.DeviceType,
|
|
||||||
"count": dc.Count,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
userDistribution["provinces"] = provinces
|
|
||||||
}
|
|
||||||
|
|
||||||
onlineTrend := make([]gin.H, 0, 30)
|
onlineTrend := make([]gin.H, 0, 30)
|
||||||
if len(appIDs) > 0 {
|
|
||||||
type DailyCount struct {
|
|
||||||
Date string
|
|
||||||
Count int64
|
|
||||||
}
|
|
||||||
var dailyCounts []DailyCount
|
|
||||||
|
|
||||||
dateStart := time.Now().AddDate(0, 0, -29)
|
|
||||||
dateStart = time.Date(dateStart.Year(), dateStart.Month(), dateStart.Day(), 0, 0, 0, 0, dateStart.Location())
|
|
||||||
|
|
||||||
database.DB.Model(&model.DeviceSession{}).
|
|
||||||
Select("DATE(last_heartbeat) as date, COUNT(*) as count").
|
|
||||||
Joins("JOIN user_devices ON device_sessions.device_id = user_devices.id").
|
|
||||||
Where("user_devices.application_id IN ? AND last_heartbeat >= ?", appIDs, dateStart).
|
|
||||||
Group("DATE(last_heartbeat)").
|
|
||||||
Order("date ASC").
|
|
||||||
Find(&dailyCounts)
|
|
||||||
|
|
||||||
countMap := make(map[string]int64)
|
|
||||||
for _, dc := range dailyCounts {
|
|
||||||
countMap[dc.Date] = dc.Count
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 29; i >= 0; i-- {
|
|
||||||
date := time.Now().AddDate(0, 0, -i)
|
|
||||||
dateStr := date.Format("2006-01-02")
|
|
||||||
count := countMap[dateStr]
|
|
||||||
|
|
||||||
onlineTrend = append(onlineTrend, gin.H{
|
|
||||||
"date": date.Format("01-02"),
|
|
||||||
"value": count,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for i := 29; i >= 0; i-- {
|
|
||||||
date := time.Now().AddDate(0, 0, -i)
|
|
||||||
onlineTrend = append(onlineTrend, gin.H{
|
|
||||||
"date": date.Format("01-02"),
|
|
||||||
"value": 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recentTickets := make([]gin.H, 0, 5)
|
recentTickets := make([]gin.H, 0, 5)
|
||||||
|
|
||||||
|
var appIDs []uint
|
||||||
|
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Pluck("id", &appIDs)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
wg.Add(8)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Count(&stats.TotalApplications)
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
database.DB.Model(&model.Card{}).Where("creator_id = ?", userID).Count(&stats.TotalCards)
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if len(appIDs) > 0 {
|
||||||
|
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Count(&stats.TotalUsers)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if len(appIDs) > 0 {
|
||||||
|
var appUserIDs []uint
|
||||||
|
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Pluck("id", &appUserIDs)
|
||||||
|
if len(appUserIDs) > 0 {
|
||||||
|
var revenue float64
|
||||||
|
database.DB.Model(&model.RechargeRecord{}).
|
||||||
|
Where("user_id IN ? AND status = ? AND created_at >= ?", appUserIDs, "success", time.Now().AddDate(0, -1, 0)).
|
||||||
|
Select("COALESCE(SUM(amount), 0)").
|
||||||
|
Scan(&revenue)
|
||||||
|
stats.MonthlyRevenue = revenue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
var user model.User
|
||||||
|
if err := database.DB.First(&user, userID).Error; err == nil {
|
||||||
|
subscription.Plan = "基础版"
|
||||||
|
if user.Role == "admin" {
|
||||||
|
subscription.Plan = "管理员"
|
||||||
|
}
|
||||||
|
subscription.Status = "正常"
|
||||||
|
if user.Status == "banned" {
|
||||||
|
subscription.Status = "已禁用"
|
||||||
|
}
|
||||||
|
subscription.ExpireDate = "永久"
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
var apiUsed int64
|
||||||
|
database.DB.Model(&model.ApiUsage{}).Where("user_id = ?", userID).Count(&apiUsed)
|
||||||
|
subscription.APIUsed = int(apiUsed)
|
||||||
|
|
||||||
|
var storageUsed int64
|
||||||
|
database.DB.Model(&model.StorageUsage{}).Where("user_id = ?", userID).Select("COALESCE(SUM(size), 0)").Scan(&storageUsed)
|
||||||
|
subscription.StorageUsed = storageUsed
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if len(appIDs) > 0 {
|
||||||
|
type DeviceCount struct {
|
||||||
|
DeviceType string
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
var deviceCounts []DeviceCount
|
||||||
|
database.DB.Model(&model.UserDevice{}).
|
||||||
|
Select("device_type, COUNT(*) as count").
|
||||||
|
Where("application_id IN ?", appIDs).
|
||||||
|
Group("device_type").
|
||||||
|
Order("count DESC").
|
||||||
|
Find(&deviceCounts)
|
||||||
|
|
||||||
|
provinces := make([]gin.H, 0, len(deviceCounts))
|
||||||
|
for _, dc := range deviceCounts {
|
||||||
|
provinces = append(provinces, gin.H{
|
||||||
|
"name": dc.DeviceType,
|
||||||
|
"count": dc.Count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
userDistribution["provinces"] = provinces
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if len(appIDs) > 0 {
|
||||||
|
type DailyCount struct {
|
||||||
|
Date string
|
||||||
|
Count int64
|
||||||
|
}
|
||||||
|
var dailyCounts []DailyCount
|
||||||
|
|
||||||
|
dateStart := time.Now().AddDate(0, 0, -29)
|
||||||
|
dateStart = time.Date(dateStart.Year(), dateStart.Month(), dateStart.Day(), 0, 0, 0, 0, dateStart.Location())
|
||||||
|
|
||||||
|
database.DB.Model(&model.DeviceSession{}).
|
||||||
|
Select("DATE(last_heartbeat) as date, COUNT(*) as count").
|
||||||
|
Joins("JOIN user_devices ON device_sessions.device_id = user_devices.id").
|
||||||
|
Where("user_devices.application_id IN ? AND last_heartbeat >= ?", appIDs, dateStart).
|
||||||
|
Group("DATE(last_heartbeat)").
|
||||||
|
Order("date ASC").
|
||||||
|
Find(&dailyCounts)
|
||||||
|
|
||||||
|
countMap := make(map[string]int64)
|
||||||
|
for _, dc := range dailyCounts {
|
||||||
|
countMap[dc.Date] = dc.Count
|
||||||
|
}
|
||||||
|
|
||||||
|
trend := make([]gin.H, 0, 30)
|
||||||
|
for i := 29; i >= 0; i-- {
|
||||||
|
date := time.Now().AddDate(0, 0, -i)
|
||||||
|
dateStr := date.Format("2006-01-02")
|
||||||
|
count := countMap[dateStr]
|
||||||
|
|
||||||
|
trend = append(trend, gin.H{
|
||||||
|
"date": date.Format("01-02"),
|
||||||
|
"value": count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
onlineTrend = trend
|
||||||
|
mu.Unlock()
|
||||||
|
} else {
|
||||||
|
trend := make([]gin.H, 0, 30)
|
||||||
|
for i := 29; i >= 0; i-- {
|
||||||
|
date := time.Now().AddDate(0, 0, -i)
|
||||||
|
trend = append(trend, gin.H{
|
||||||
|
"date": date.Format("01-02"),
|
||||||
|
"value": 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
onlineTrend = trend
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
subscription.AppCount = int(stats.TotalApplications)
|
||||||
|
subscription.APIQuota = 10000
|
||||||
|
subscription.StorageQuota = 100 * 1024 * 1024
|
||||||
|
|
||||||
var tickets []model.Ticket
|
var tickets []model.Ticket
|
||||||
database.DB.Where("user_id = ?", userID).Order("created_at DESC").Limit(5).Find(&tickets)
|
database.DB.Where("user_id = ?", userID).Order("created_at DESC").Limit(5).Find(&tickets)
|
||||||
for _, ticket := range tickets {
|
for _, ticket := range tickets {
|
||||||
|
|||||||
Reference in New Issue
Block a user