This commit is contained in:
Alex-larget
2026-03-17 18:22:06 +08:00
parent 88915276d1
commit f276595ad6
50 changed files with 2246 additions and 1223 deletions

View File

@@ -3,7 +3,9 @@ package handler
import (
"encoding/json"
"net/http"
"strconv"
"sync"
"time"
"soul-api/internal/database"
"soul-api/internal/model"
@@ -54,11 +56,17 @@ func AdminDashboardStats(c *gin.Context) {
})
}
// AdminDashboardRecentOrders GET /api/admin/dashboard/recent-orders
// AdminDashboardRecentOrders GET /api/admin/dashboard/recent-orders?limit=10
func AdminDashboardRecentOrders(c *gin.Context) {
db := database.DB()
limit := 5
if l := c.Query("limit"); l != "" {
if n, err := strconv.Atoi(l); err == nil && n >= 1 && n <= 20 {
limit = n
}
}
var recentOrders []model.Order
db.Where("status IN ?", paidStatuses).Order("created_at DESC").Limit(5).Find(&recentOrders)
db.Where("status IN ?", paidStatuses).Order("created_at DESC").Limit(limit).Find(&recentOrders)
c.JSON(http.StatusOK, gin.H{"success": true, "recentOrders": buildRecentOrdersOut(db, recentOrders)})
}
@@ -180,6 +188,101 @@ func buildRecentOrdersOut(db *gorm.DB, recentOrders []model.Order) []gin.H {
return out
}
// AdminTrackStats GET /api/admin/track/stats?period=today|week|month|all
// 埋点统计:按 extra_data->module 分组,按 action+target 聚合 count
func AdminTrackStats(c *gin.Context) {
period := c.DefaultQuery("period", "week")
if period != "today" && period != "week" && period != "month" && period != "all" {
period = "week"
}
now := time.Now()
var start time.Time
switch period {
case "today":
start = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
case "week":
weekday := int(now.Weekday())
if weekday == 0 {
weekday = 7
}
start = time.Date(now.Year(), now.Month(), now.Day()-weekday+1, 0, 0, 0, 0, now.Location())
case "month":
start = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
case "all":
start = time.Time{}
}
db := database.DB()
var tracks []model.UserTrack
q := db.Model(&model.UserTrack{})
if !start.IsZero() {
q = q.Where("created_at >= ?", start)
}
if err := q.Find(&tracks).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
return
}
// byModule: module -> map[key] -> count, key = action + "|" + target
type item struct {
Action string `json:"action"`
Target string `json:"target"`
Module string `json:"module"`
Page string `json:"page"`
Count int `json:"count"`
}
byModule := make(map[string]map[string]*item)
total := 0
for _, t := range tracks {
total++
module := "other"
page := ""
if len(t.ExtraData) > 0 {
var extra map[string]interface{}
if err := json.Unmarshal(t.ExtraData, &extra); err == nil {
if m, ok := extra["module"].(string); ok && m != "" {
module = m
}
if p, ok := extra["page"].(string); ok {
page = p
}
}
}
target := ""
if t.Target != nil {
target = *t.Target
}
key := t.Action + "|" + target
if byModule[module] == nil {
byModule[module] = make(map[string]*item)
}
if byModule[module][key] == nil {
byModule[module][key] = &item{Action: t.Action, Target: target, Module: module, Page: page, Count: 0}
}
byModule[module][key].Count++
}
// 转为前端期望格式byModule[module] = [{action,target,module,page,count},...]
out := make(map[string][]gin.H)
for mod, m := range byModule {
list := make([]gin.H, 0, len(m))
for _, v := range m {
list = append(list, gin.H{
"action": v.Action, "target": v.Target, "module": v.Module, "page": v.Page, "count": v.Count,
})
}
out[mod] = list
}
c.JSON(http.StatusOK, gin.H{"success": true, "total": total, "byModule": out})
}
// AdminBalanceSummary GET /api/admin/balance/summary
// 汇总代付金额product_type=gift_pay 的已支付订单金额),用于 Dashboard 显示「含代付 ¥xx」
func AdminBalanceSummary(c *gin.Context) {
db := database.DB()
var totalGifted float64
db.Model(&model.Order{}).Where("product_type = ? AND status IN ?", "gift_pay", paidStatuses).
Select("COALESCE(SUM(amount), 0)").Scan(&totalGifted)
c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"totalGifted": totalGifted}})
}
// AdminDashboardMerchantBalance GET /api/admin/dashboard/merchant-balance
// 查询微信商户号实时余额(可用余额、待结算余额),用于看板展示
// 注意:普通商户可能需向微信申请开通权限,未开通时返回 error