bindbox-game/internal/api/admin/livestream_stats.go

138 lines
4.3 KiB
Go

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,
})
}
}