package admin import ( "math" "net/http" "strconv" "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/repository/mysql/model" ) type livestreamStatsResponse struct { TotalRevenue int64 `json:"total_revenue"` // 总营收(分) TotalRefund int64 `json:"total_refund"` // 总退款(分) TotalCost int64 `json:"total_cost"` // 总成本(分) NetProfit int64 `json:"net_profit"` // 净利润(分) OrderCount int64 `json:"order_count"` // 订单数 RefundCount int64 `json:"refund_count"` // 退款数 ProfitMargin float64 `json:"profit_margin"` // 利润率 % } // GetLivestreamStats 获取直播间盈亏统计 // @Summary 获取直播间盈亏统计 // @Description 计算逻辑:净利润 = (营收 - 退款) - 奖品成本。营收 = 抽奖次数 * 门票价格。成本 = 中奖奖品成本总和。 // @Tags 管理端.直播间 // @Accept json // @Produce json // @Param id path integer true "活动ID" // @Success 200 {object} livestreamStatsResponse // @Failure 400 {object} code.Failure // @Router /api/admin/livestream/activities/{id}/stats [get] // @Security LoginVerifyToken func (h *handler) GetLivestreamStats() core.HandlerFunc { return func(ctx core.Context) { activityID, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil || activityID <= 0 { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "无效的活动ID")) return } // 1. 获取活动信息(门票价格) var activity model.LivestreamActivities if err := h.repo.GetDbR().Where("id = ?", activityID).First(&activity).Error; err != nil { ctx.AbortWithError(core.Error(http.StatusNotFound, code.ServerError, "活动不存在")) return } ticketPrice := activity.TicketPrice // 2. 从 livestream_draw_logs 统计抽奖次数 var drawLogs []model.LivestreamDrawLogs if err := h.repo.GetDbR().Where("activity_id = ?", activityID).Find(&drawLogs).Error; err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } orderCount := int64(len(drawLogs)) totalRevenue := orderCount * ticketPrice // 3. 统计退款数量 var refundCount int64 h.repo.GetDbR().Model(&model.LivestreamDrawLogs{}).Where("activity_id = ? AND is_refunded = 1", activityID).Count(&refundCount) totalRefund := refundCount * ticketPrice // 4. 计算成本 prizeIDCountMap := make(map[int64]int64) for _, log := range drawLogs { prizeIDCountMap[log.PrizeID]++ } prizeIDs := make([]int64, 0, len(prizeIDCountMap)) for pid := range prizeIDCountMap { prizeIDs = append(prizeIDs, pid) } var totalCost int64 if len(prizeIDs) > 0 { var prizes []model.LivestreamPrizes h.repo.GetDbR().Where("id IN ?", prizeIDs).Find(&prizes) prizeCostMap := make(map[int64]int64) productIDsNeedingFallback := make([]int64, 0) prizeProductMap := make(map[int64]int64) for _, p := range prizes { if p.CostPrice > 0 { prizeCostMap[p.ID] = p.CostPrice } else if p.ProductID > 0 { productIDsNeedingFallback = append(productIDsNeedingFallback, p.ProductID) prizeProductMap[p.ID] = p.ProductID } } if len(productIDsNeedingFallback) > 0 { var products []model.Products h.repo.GetDbR().Where("id IN ?", productIDsNeedingFallback).Find(&products) productPriceMap := make(map[int64]int64) for _, prod := range products { productPriceMap[prod.ID] = prod.Price } for prizeID, productID := range prizeProductMap { if _, ok := prizeCostMap[prizeID]; !ok { if price, found := productPriceMap[productID]; found { prizeCostMap[prizeID] = price } } } } for prizeID, count := range prizeIDCountMap { if cost, ok := prizeCostMap[prizeID]; ok { totalCost += cost * count } } } netProfit := (totalRevenue - totalRefund) - totalCost var margin float64 netRevenue := totalRevenue - totalRefund if netRevenue > 0 { margin = float64(netProfit) / float64(netRevenue) * 100 } else { margin = -100 } ctx.Payload(&livestreamStatsResponse{ TotalRevenue: totalRevenue, TotalRefund: totalRefund, TotalCost: totalCost, NetProfit: netProfit, OrderCount: orderCount, RefundCount: refundCount, ProfitMargin: math.Trunc(margin*100) / 100, }) } }