package handler import ( "fmt" "net/http" "strconv" "time" "soul-api/internal/database" "soul-api/internal/model" "github.com/gin-gonic/gin" ) // DBMatchRecordsList GET /api/db/match-records 管理端-匹配记录列表(分页、按类型筛选) // 当 ?stats=true 时返回汇总统计(总匹配数、今日匹配、按类型分布、独立用户数) func DBMatchRecordsList(c *gin.Context) { db := database.DB() if c.Query("stats") == "true" { var totalMatches int64 db.Raw("SELECT COUNT(*) FROM match_records").Scan(&totalMatches) var todayMatches int64 db.Raw("SELECT COUNT(*) FROM match_records WHERE created_at >= CURDATE()").Scan(&todayMatches) type TypeCount struct { MatchType string `json:"matchType" gorm:"column:match_type"` Count int64 `json:"count" gorm:"column:count"` } var byType []TypeCount db.Raw("SELECT match_type, COUNT(*) as count FROM match_records GROUP BY match_type").Scan(&byType) var uniqueUsers int64 db.Raw("SELECT COUNT(DISTINCT user_id) FROM match_records WHERE user_id IS NOT NULL AND user_id != ''").Scan(&uniqueUsers) // 匹配收益:product_type=match 且 status=paid 的订单金额总和 var matchRevenue float64 db.Model(&model.Order{}).Where("product_type = ? AND status = ?", "match", "paid"). Select("COALESCE(SUM(amount), 0)").Scan(&matchRevenue) var paidMatchCount int64 db.Model(&model.Order{}).Where("product_type = ? AND status = ?", "match", "paid").Count(&paidMatchCount) c.JSON(http.StatusOK, gin.H{ "success": true, "data": gin.H{ "totalMatches": totalMatches, "todayMatches": todayMatches, "byType": byType, "uniqueUsers": uniqueUsers, "matchRevenue": matchRevenue, "paidMatchCount": paidMatchCount, }, }) return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) matchType := c.Query("matchType") if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 10 } q := db.Model(&model.MatchRecord{}) if matchType != "" { q = q.Where("match_type = ?", matchType) } var total int64 q.Count(&total) var records []model.MatchRecord if err := q.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&records).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error(), "records": []interface{}{}}) return } userIDs := make(map[string]bool) for _, r := range records { userIDs[r.UserID] = true userIDs[r.MatchedUserID] = true } ids := make([]string, 0, len(userIDs)) for id := range userIDs { ids = append(ids, id) } var users []model.User if len(ids) > 0 { database.DB().Where("id IN ?", ids).Find(&users) } userMap := make(map[string]*model.User) for i := range users { userMap[users[i].ID] = &users[i] } getStr := func(s *string) string { if s == nil { return "" } return *s } safeNickname := func(u *model.User) string { if u == nil || u.Nickname == nil { return "" } return *u.Nickname } safeAvatar := func(u *model.User) string { if u == nil || u.Avatar == nil { return "" } return *u.Avatar } out := make([]gin.H, 0, len(records)) for _, r := range records { u := userMap[r.UserID] mu := userMap[r.MatchedUserID] out = append(out, gin.H{ "id": r.ID, "userId": r.UserID, "matchedUserId": r.MatchedUserID, "matchType": r.MatchType, "phone": getStr(r.Phone), "wechatId": getStr(r.WechatID), "userNickname": safeNickname(u), "matchedNickname": safeNickname(mu), "userAvatar": safeAvatar(u), "matchedUserAvatar": safeAvatar(mu), "matchScore": r.MatchScore, "createdAt": r.CreatedAt, }) } totalPages := int(total) / pageSize if int(total)%pageSize > 0 { totalPages++ } c.JSON(http.StatusOK, gin.H{ "success": true, "records": out, "total": total, "page": page, "pageSize": pageSize, "totalPages": totalPages, }) } // DBMatchPoolCounts GET /api/db/match-pool-counts 返回各匹配池的用户人数 func DBMatchPoolCounts(c *gin.Context) { db := database.DB() var vipCount int64 db.Model(&model.User{}).Where("is_vip = 1 AND vip_expire_date > NOW()").Count(&vipCount) var completeCount int64 db.Model(&model.User{}).Where( "(phone IS NOT NULL AND phone != '') AND (nickname IS NOT NULL AND nickname != '' AND nickname != '微信用户') AND (avatar IS NOT NULL AND avatar != '')", ).Count(&completeCount) var allCount int64 db.Model(&model.User{}).Where( "((wechat_id IS NOT NULL AND wechat_id != '') OR (phone IS NOT NULL AND phone != ''))", ).Count(&allCount) c.JSON(http.StatusOK, gin.H{ "success": true, "data": gin.H{ "vip": vipCount, "complete": completeCount, "all": allCount, }, }) } // DBMatchRecordInsertTest POST /api/db/match-records/test 插入测试匹配记录 func DBMatchRecordInsertTest(c *gin.Context) { var body struct { MatchType string `json:"matchType"` Phone string `json:"phone"` WechatID string `json:"wechatId"` } _ = c.ShouldBindJSON(&body) if body.MatchType == "" { body.MatchType = "team" } if body.Phone == "" { body.Phone = "13800000000" } db := database.DB() rec := model.MatchRecord{ ID: fmt.Sprintf("mr_test_%d", time.Now().UnixNano()), UserID: "admin_test", MatchType: body.MatchType, Phone: &body.Phone, } if body.WechatID != "" { rec.WechatID = &body.WechatID } if err := db.Create(&rec).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "message": "测试记录已插入", "id": rec.ID}) }