195 lines
5.2 KiB
Go
195 lines
5.2 KiB
Go
|
|
package handler
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/json"
|
|||
|
|
"net/http"
|
|||
|
|
"sync"
|
|||
|
|
|
|||
|
|
"soul-api/internal/database"
|
|||
|
|
"soul-api/internal/model"
|
|||
|
|
|
|||
|
|
"github.com/gin-gonic/gin"
|
|||
|
|
"gorm.io/gorm"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var paidStatuses = []string{"paid", "completed", "success"}
|
|||
|
|
|
|||
|
|
// AdminDashboardStats GET /api/admin/dashboard/stats
|
|||
|
|
// 轻量聚合:总用户、付费订单数、付费用户数、总营收、转化率(无订单/用户明细)
|
|||
|
|
func AdminDashboardStats(c *gin.Context) {
|
|||
|
|
db := database.DB()
|
|||
|
|
var (
|
|||
|
|
totalUsers int64
|
|||
|
|
paidOrderCount int64
|
|||
|
|
totalRevenue float64
|
|||
|
|
paidUserCount int64
|
|||
|
|
)
|
|||
|
|
var wg sync.WaitGroup
|
|||
|
|
wg.Add(4)
|
|||
|
|
go func() { defer wg.Done(); db.Model(&model.User{}).Count(&totalUsers) }()
|
|||
|
|
go func() { defer wg.Done(); db.Model(&model.Order{}).Where("status IN ?", paidStatuses).Count(&paidOrderCount) }()
|
|||
|
|
go func() {
|
|||
|
|
defer wg.Done()
|
|||
|
|
db.Model(&model.Order{}).Where("status IN ?", paidStatuses).
|
|||
|
|
Select("COALESCE(SUM(amount), 0)").Scan(&totalRevenue)
|
|||
|
|
}()
|
|||
|
|
go func() {
|
|||
|
|
defer wg.Done()
|
|||
|
|
db.Table("orders").Where("status IN ?", paidStatuses).
|
|||
|
|
Select("COUNT(DISTINCT user_id)").Scan(&paidUserCount)
|
|||
|
|
}()
|
|||
|
|
wg.Wait()
|
|||
|
|
conversionRate := 0.0
|
|||
|
|
if totalUsers > 0 && paidUserCount > 0 {
|
|||
|
|
conversionRate = float64(paidUserCount) / float64(totalUsers) * 100
|
|||
|
|
}
|
|||
|
|
c.JSON(http.StatusOK, gin.H{
|
|||
|
|
"success": true,
|
|||
|
|
"totalUsers": totalUsers,
|
|||
|
|
"paidOrderCount": paidOrderCount,
|
|||
|
|
"paidUserCount": paidUserCount,
|
|||
|
|
"totalRevenue": totalRevenue,
|
|||
|
|
"conversionRate": conversionRate,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// AdminDashboardRecentOrders GET /api/admin/dashboard/recent-orders
|
|||
|
|
func AdminDashboardRecentOrders(c *gin.Context) {
|
|||
|
|
db := database.DB()
|
|||
|
|
var recentOrders []model.Order
|
|||
|
|
db.Where("status IN ?", paidStatuses).Order("created_at DESC").Limit(5).Find(&recentOrders)
|
|||
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "recentOrders": buildRecentOrdersOut(db, recentOrders)})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// AdminDashboardNewUsers GET /api/admin/dashboard/new-users
|
|||
|
|
func AdminDashboardNewUsers(c *gin.Context) {
|
|||
|
|
db := database.DB()
|
|||
|
|
var newUsers []model.User
|
|||
|
|
db.Model(&model.User{}).Order("created_at DESC").Limit(10).Find(&newUsers)
|
|||
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "newUsers": buildNewUsersOut(newUsers)})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// AdminDashboardOverview GET /api/admin/dashboard/overview
|
|||
|
|
// 数据概览:总用户、付费订单数、付费用户数、总营收、转化率、最近订单、新用户
|
|||
|
|
// 优化:6 组查询并行执行,减少总耗时
|
|||
|
|
func AdminDashboardOverview(c *gin.Context) {
|
|||
|
|
db := database.DB()
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
totalUsers int64
|
|||
|
|
paidOrderCount int64
|
|||
|
|
totalRevenue float64
|
|||
|
|
paidUserCount int64
|
|||
|
|
recentOrders []model.Order
|
|||
|
|
newUsers []model.User
|
|||
|
|
)
|
|||
|
|
var wg sync.WaitGroup
|
|||
|
|
wg.Add(6)
|
|||
|
|
|
|||
|
|
go func() {
|
|||
|
|
defer wg.Done()
|
|||
|
|
db.Model(&model.User{}).Count(&totalUsers)
|
|||
|
|
}()
|
|||
|
|
go func() {
|
|||
|
|
defer wg.Done()
|
|||
|
|
db.Model(&model.Order{}).Where("status IN ?", paidStatuses).Count(&paidOrderCount)
|
|||
|
|
}()
|
|||
|
|
go func() {
|
|||
|
|
defer wg.Done()
|
|||
|
|
db.Model(&model.Order{}).Where("status IN ?", paidStatuses).
|
|||
|
|
Select("COALESCE(SUM(amount), 0)").Scan(&totalRevenue)
|
|||
|
|
}()
|
|||
|
|
go func() {
|
|||
|
|
defer wg.Done()
|
|||
|
|
db.Table("orders").Where("status IN ?", paidStatuses).
|
|||
|
|
Select("COUNT(DISTINCT user_id)").Scan(&paidUserCount)
|
|||
|
|
}()
|
|||
|
|
go func() {
|
|||
|
|
defer wg.Done()
|
|||
|
|
db.Where("status IN ?", paidStatuses).
|
|||
|
|
Order("created_at DESC").Limit(5).Find(&recentOrders)
|
|||
|
|
}()
|
|||
|
|
go func() {
|
|||
|
|
defer wg.Done()
|
|||
|
|
db.Model(&model.User{}).Order("created_at DESC").Limit(10).Find(&newUsers)
|
|||
|
|
}()
|
|||
|
|
wg.Wait()
|
|||
|
|
|
|||
|
|
conversionRate := 0.0
|
|||
|
|
if totalUsers > 0 && paidUserCount > 0 {
|
|||
|
|
conversionRate = float64(paidUserCount) / float64(totalUsers) * 100
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
recentOut := buildRecentOrdersOut(db, recentOrders)
|
|||
|
|
newOut := buildNewUsersOut(newUsers)
|
|||
|
|
|
|||
|
|
c.JSON(http.StatusOK, gin.H{
|
|||
|
|
"success": true,
|
|||
|
|
"totalUsers": totalUsers,
|
|||
|
|
"paidOrderCount": paidOrderCount,
|
|||
|
|
"paidUserCount": paidUserCount,
|
|||
|
|
"totalRevenue": totalRevenue,
|
|||
|
|
"conversionRate": conversionRate,
|
|||
|
|
"recentOrders": recentOut,
|
|||
|
|
"newUsers": newOut,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func dashStr(s *string) string {
|
|||
|
|
if s == nil || *s == "" {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
return *s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func buildRecentOrdersOut(db *gorm.DB, recentOrders []model.Order) []gin.H {
|
|||
|
|
if len(recentOrders) == 0 {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
userIDs := make(map[string]bool)
|
|||
|
|
for _, o := range recentOrders {
|
|||
|
|
if o.UserID != "" {
|
|||
|
|
userIDs[o.UserID] = true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
ids := make([]string, 0, len(userIDs))
|
|||
|
|
for id := range userIDs {
|
|||
|
|
ids = append(ids, id)
|
|||
|
|
}
|
|||
|
|
var users []model.User
|
|||
|
|
db.Where("id IN ?", ids).Find(&users)
|
|||
|
|
userMap := make(map[string]*model.User)
|
|||
|
|
for i := range users {
|
|||
|
|
userMap[users[i].ID] = &users[i]
|
|||
|
|
}
|
|||
|
|
out := make([]gin.H, 0, len(recentOrders))
|
|||
|
|
for _, o := range recentOrders {
|
|||
|
|
b, _ := json.Marshal(o)
|
|||
|
|
var m map[string]interface{}
|
|||
|
|
_ = json.Unmarshal(b, &m)
|
|||
|
|
if u := userMap[o.UserID]; u != nil {
|
|||
|
|
m["userNickname"] = dashStr(u.Nickname)
|
|||
|
|
m["userAvatar"] = dashStr(u.Avatar)
|
|||
|
|
} else {
|
|||
|
|
m["userNickname"] = ""
|
|||
|
|
m["userAvatar"] = ""
|
|||
|
|
}
|
|||
|
|
out = append(out, m)
|
|||
|
|
}
|
|||
|
|
return out
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func buildNewUsersOut(newUsers []model.User) []gin.H {
|
|||
|
|
out := make([]gin.H, 0, len(newUsers))
|
|||
|
|
for _, u := range newUsers {
|
|||
|
|
out = append(out, gin.H{
|
|||
|
|
"id": u.ID,
|
|||
|
|
"nickname": dashStr(u.Nickname),
|
|||
|
|
"phone": dashStr(u.Phone),
|
|||
|
|
"referralCode": dashStr(u.ReferralCode),
|
|||
|
|
"createdAt": u.CreatedAt,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
return out
|
|||
|
|
}
|