package admin import ( "net/http" "strconv" "time" "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/validation" "bindbox-game/internal/repository/mysql/model" "bindbox-game/internal/service/livestream" ) // ========== 直播间活动管理 ========== type createLivestreamActivityRequest struct { Name string `json:"name" binding:"required"` StreamerName string `json:"streamer_name"` StreamerContact string `json:"streamer_contact"` DouyinProductID string `json:"douyin_product_id"` TicketPrice int64 `json:"ticket_price"` StartTime string `json:"start_time"` EndTime string `json:"end_time"` } type livestreamActivityResponse struct { ID int64 `json:"id"` Name string `json:"name"` StreamerName string `json:"streamer_name"` StreamerContact string `json:"streamer_contact"` AccessCode string `json:"access_code"` DouyinProductID string `json:"douyin_product_id"` TicketPrice int64 `json:"ticket_price"` Status int32 `json:"status"` StartTime string `json:"start_time,omitempty"` EndTime string `json:"end_time,omitempty"` CreatedAt string `json:"created_at"` } // CreateLivestreamActivity 创建直播间活动 // @Summary 创建直播间活动 // @Description 创建新的直播间活动,自动生成唯一访问码 // @Tags 管理端.直播间 // @Accept json // @Produce json // @Param RequestBody body createLivestreamActivityRequest true "请求参数" // @Success 200 {object} livestreamActivityResponse // @Failure 400 {object} code.Failure // @Router /api/admin/livestream/activities [post] // @Security LoginVerifyToken func (h *handler) CreateLivestreamActivity() core.HandlerFunc { return func(ctx core.Context) { req := new(createLivestreamActivityRequest) if err := ctx.ShouldBindJSON(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } input := livestream.CreateActivityInput{ Name: req.Name, StreamerName: req.StreamerName, StreamerContact: req.StreamerContact, DouyinProductID: req.DouyinProductID, TicketPrice: req.TicketPrice, } if req.StartTime != "" { if t, err := time.ParseInLocation("2006-01-02 15:04:05", req.StartTime, time.Local); err == nil { input.StartTime = &t } } if req.EndTime != "" { if t, err := time.ParseInLocation("2006-01-02 15:04:05", req.EndTime, time.Local); err == nil { input.EndTime = &t } } activity, err := h.livestream.CreateActivity(ctx.RequestContext(), input) if err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } ctx.Payload(&livestreamActivityResponse{ ID: activity.ID, Name: activity.Name, StreamerName: activity.StreamerName, StreamerContact: activity.StreamerContact, AccessCode: activity.AccessCode, DouyinProductID: activity.DouyinProductID, TicketPrice: activity.TicketPrice, Status: activity.Status, CreatedAt: activity.CreatedAt.Format("2006-01-02 15:04:05"), }) } } type updateLivestreamActivityRequest struct { Name string `json:"name"` StreamerName string `json:"streamer_name"` StreamerContact string `json:"streamer_contact"` DouyinProductID string `json:"douyin_product_id"` TicketPrice *int64 `json:"ticket_price"` Status *int32 `json:"status"` StartTime string `json:"start_time"` EndTime string `json:"end_time"` } // UpdateLivestreamActivity 更新直播间活动 // @Summary 更新直播间活动 // @Description 更新直播间活动信息 // @Tags 管理端.直播间 // @Accept json // @Produce json // @Param id path integer true "活动ID" // @Param RequestBody body updateLivestreamActivityRequest true "请求参数" // @Success 200 {object} simpleMessageResponse // @Failure 400 {object} code.Failure // @Router /api/admin/livestream/activities/{id} [put] // @Security LoginVerifyToken func (h *handler) UpdateLivestreamActivity() core.HandlerFunc { return func(ctx core.Context) { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil || id <= 0 { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "无效的活动ID")) return } req := new(updateLivestreamActivityRequest) if err := ctx.ShouldBindJSON(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } input := livestream.UpdateActivityInput{ Name: req.Name, StreamerName: req.StreamerName, StreamerContact: req.StreamerContact, DouyinProductID: req.DouyinProductID, TicketPrice: req.TicketPrice, Status: req.Status, } if req.StartTime != "" { if t, err := time.ParseInLocation("2006-01-02 15:04:05", req.StartTime, time.Local); err == nil { input.StartTime = &t } } if req.EndTime != "" { if t, err := time.ParseInLocation("2006-01-02 15:04:05", req.EndTime, time.Local); err == nil { input.EndTime = &t } } if err := h.livestream.UpdateActivity(ctx.RequestContext(), id, input); err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } ctx.Payload(&simpleMessageResponse{Message: "操作成功"}) } } type listLivestreamActivitiesRequest struct { Page int `form:"page"` PageSize int `form:"page_size"` Status *int32 `form:"status"` } type listLivestreamActivitiesResponse struct { List []livestreamActivityResponse `json:"list"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` } // ListLivestreamActivities 直播间活动列表 // @Summary 直播间活动列表 // @Description 获取直播间活动列表 // @Tags 管理端.直播间 // @Accept json // @Produce json // @Param page query int false "页码" default(1) // @Param page_size query int false "每页数量" default(20) // @Param status query int false "状态过滤" // @Success 200 {object} listLivestreamActivitiesResponse // @Failure 400 {object} code.Failure // @Router /api/admin/livestream/activities [get] // @Security LoginVerifyToken func (h *handler) ListLivestreamActivities() core.HandlerFunc { return func(ctx core.Context) { req := new(listLivestreamActivitiesRequest) if err := ctx.ShouldBindForm(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } if req.Page <= 0 { req.Page = 1 } if req.PageSize <= 0 { req.PageSize = 20 } list, total, err := h.livestream.ListActivities(ctx.RequestContext(), req.Page, req.PageSize, req.Status) if err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } res := &listLivestreamActivitiesResponse{ List: make([]livestreamActivityResponse, len(list)), Total: total, Page: req.Page, PageSize: req.PageSize, } for i, a := range list { item := livestreamActivityResponse{ ID: a.ID, Name: a.Name, StreamerName: a.StreamerName, StreamerContact: a.StreamerContact, AccessCode: a.AccessCode, DouyinProductID: a.DouyinProductID, TicketPrice: a.TicketPrice, Status: a.Status, CreatedAt: a.CreatedAt.Format("2006-01-02 15:04:05"), } if !a.StartTime.IsZero() { item.StartTime = a.StartTime.Format("2006-01-02 15:04:05") } if !a.EndTime.IsZero() { item.EndTime = a.EndTime.Format("2006-01-02 15:04:05") } res.List[i] = item } ctx.Payload(res) } } // GetLivestreamActivity 获取直播间活动详情 // @Summary 获取直播间活动详情 // @Description 根据ID获取直播间活动详情 // @Tags 管理端.直播间 // @Accept json // @Produce json // @Param id path integer true "活动ID" // @Success 200 {object} livestreamActivityResponse // @Failure 400 {object} code.Failure // @Router /api/admin/livestream/activities/{id} [get] // @Security LoginVerifyToken func (h *handler) GetLivestreamActivity() core.HandlerFunc { return func(ctx core.Context) { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil || id <= 0 { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "无效的活动ID")) return } activity, err := h.livestream.GetActivity(ctx.RequestContext(), id) if err != nil { ctx.AbortWithError(core.Error(http.StatusNotFound, code.ServerError, "活动不存在")) return } res := &livestreamActivityResponse{ ID: activity.ID, Name: activity.Name, StreamerName: activity.StreamerName, StreamerContact: activity.StreamerContact, AccessCode: activity.AccessCode, DouyinProductID: activity.DouyinProductID, TicketPrice: activity.TicketPrice, Status: activity.Status, CreatedAt: activity.CreatedAt.Format("2006-01-02 15:04:05"), } if !activity.StartTime.IsZero() { res.StartTime = activity.StartTime.Format("2006-01-02 15:04:05") } if !activity.EndTime.IsZero() { res.EndTime = activity.EndTime.Format("2006-01-02 15:04:05") } ctx.Payload(res) } } // DeleteLivestreamActivity 删除直播间活动 // @Summary 删除直播间活动 // @Description 删除指定直播间活动 // @Tags 管理端.直播间 // @Accept json // @Produce json // @Param id path integer true "活动ID" // @Success 200 {object} simpleMessageResponse // @Failure 400 {object} code.Failure // @Router /api/admin/livestream/activities/{id} [delete] // @Security LoginVerifyToken func (h *handler) DeleteLivestreamActivity() core.HandlerFunc { return func(ctx core.Context) { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil || id <= 0 { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "无效的活动ID")) return } if err := h.livestream.DeleteActivity(ctx.RequestContext(), id); err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } ctx.Payload(&simpleMessageResponse{Message: "删除成功"}) } } // ========== 直播间奖品管理 ========== type createLivestreamPrizeRequest struct { Name string `json:"name"` Image string `json:"image"` Level int32 `json:"level"` Weight int32 `json:"weight"` Quantity int32 `json:"quantity"` ProductID int64 `json:"product_id"` CostPrice int64 `json:"cost_price"` } type livestreamPrizeResponse struct { ID int64 `json:"id"` Name string `json:"name"` Image string `json:"image"` Level int32 `json:"level"` Weight int32 `json:"weight"` Quantity int32 `json:"quantity"` Remaining int64 `json:"remaining"` ProductID int64 `json:"product_id"` CostPrice int64 `json:"cost_price"` Sort int32 `json:"sort"` } // CreateLivestreamPrizes 批量创建奖品 // @Summary 批量创建直播间奖品 // @Description 为指定活动批量创建奖品 // @Tags 管理端.直播间 // @Accept json // @Produce json // @Param activity_id path integer true "活动ID" // @Param RequestBody body []createLivestreamPrizeRequest true "奖品列表" // @Success 200 {object} simpleMessageResponse // @Failure 400 {object} code.Failure // @Router /api/admin/livestream/activities/{activity_id}/prizes [post] // @Security LoginVerifyToken func (h *handler) CreateLivestreamPrizes() 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 } var req []createLivestreamPrizeRequest if err := ctx.ShouldBindJSON(&req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } var inputs []livestream.CreatePrizeInput for _, p := range req { inputs = append(inputs, livestream.CreatePrizeInput{ Name: p.Name, Image: p.Image, Weight: p.Weight, Quantity: p.Quantity, Level: p.Level, ProductID: p.ProductID, CostPrice: p.CostPrice, }) } if err := h.livestream.CreatePrizes(ctx.RequestContext(), activityID, inputs); err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } ctx.Payload(&simpleMessageResponse{Message: "创建成功"}) } } // ListLivestreamPrizes 获取活动奖品列表 // @Summary 获取直播间活动奖品列表 // @Description 获取指定活动的所有奖品 // @Tags 管理端.直播间 // @Accept json // @Produce json // @Param activity_id path integer true "活动ID" // @Success 200 {object} []livestreamPrizeResponse // @Failure 400 {object} code.Failure // @Router /api/admin/livestream/activities/{activity_id}/prizes [get] // @Security LoginVerifyToken func (h *handler) ListLivestreamPrizes() 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 } prizes, err := h.livestream.ListPrizes(ctx.RequestContext(), activityID) if err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } res := make([]livestreamPrizeResponse, len(prizes)) for i, p := range prizes { res[i] = livestreamPrizeResponse{ ID: p.ID, Name: p.Name, Image: p.Image, Weight: p.Weight, Quantity: p.Quantity, Remaining: int64(p.Remaining), Level: p.Level, ProductID: p.ProductID, CostPrice: p.CostPrice, Sort: p.Sort, } } ctx.Payload(res) } } // DeleteLivestreamPrize 删除奖品 // @Summary 删除直播间奖品 // @Description 删除指定奖品 // @Tags 管理端.直播间 // @Accept json // @Produce json // @Param prize_id path integer true "奖品ID" // @Success 200 {object} simpleMessageResponse // @Failure 400 {object} code.Failure // @Router /api/admin/livestream/prizes/{prize_id} [delete] // @Security LoginVerifyToken func (h *handler) DeleteLivestreamPrize() core.HandlerFunc { return func(ctx core.Context) { prizeID, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil || prizeID <= 0 { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "无效的奖品ID")) return } if err := h.livestream.DeletePrize(ctx.RequestContext(), prizeID); err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } ctx.Payload(&simpleMessageResponse{Message: "删除成功"}) } } // ========== 直播间中奖记录 ========== type livestreamDrawLogResponse struct { ID int64 `json:"id"` ActivityID int64 `json:"activity_id"` PrizeID int64 `json:"prize_id"` PrizeName string `json:"prize_name"` Level int32 `json:"level"` DouyinOrderID int64 `json:"douyin_order_id"` // 关联ID ShopOrderID string `json:"shop_order_id"` // 店铺订单号 LocalUserID int64 `json:"local_user_id"` DouyinUserID string `json:"douyin_user_id"` UserNickname string `json:"user_nickname"` // 用户昵称 SeedHash string `json:"seed_hash"` CreatedAt string `json:"created_at"` } type listLivestreamDrawLogsResponse struct { List []livestreamDrawLogResponse `json:"list"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` } type listLivestreamDrawLogsRequest struct { Page int `form:"page"` PageSize int `form:"page_size"` StartTime string `form:"start_time"` EndTime string `form:"end_time"` Keyword string `form:"keyword"` } // ListLivestreamDrawLogs 获取中奖记录 // @Summary 获取直播间中奖记录 // @Description 获取指定活动的中奖记录,支持时间范围和关键词筛选 // @Tags 管理端.直播间 // @Accept json // @Produce json // @Param activity_id path integer true "活动ID" // @Param page query int false "页码" default(1) // @Param page_size query int false "每页数量" default(20) // @Param start_time query string false "开始时间 (YYYY-MM-DD)" // @Param end_time query string false "结束时间 (YYYY-MM-DD)" // @Param keyword query string false "搜索关键词 (昵称/订单号/奖品名称)" // @Success 200 {object} listLivestreamDrawLogsResponse // @Failure 400 {object} code.Failure // @Router /api/admin/livestream/activities/{activity_id}/draw_logs [get] // @Security LoginVerifyToken func (h *handler) ListLivestreamDrawLogs() 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 } req := new(listLivestreamDrawLogsRequest) _ = ctx.ShouldBindForm(req) page := req.Page pageSize := req.PageSize if page <= 0 { page = 1 } if pageSize <= 0 { pageSize = 20 } // 解析时间范围 var startTime, endTime *time.Time if req.StartTime != "" { if t, err := time.ParseInLocation("2006-01-02", req.StartTime, time.Local); err == nil { startTime = &t } } if req.EndTime != "" { if t, err := time.ParseInLocation("2006-01-02", req.EndTime, time.Local); err == nil { // 结束时间设为当天结束 end := t.Add(24*time.Hour - time.Nanosecond) endTime = &end } } // 使用底层 GORM 直接查询以支持 keyword db := h.repo.GetDbR().Model(&model.LivestreamDrawLogs{}).Where("activity_id = ?", activityID) if startTime != nil { db = db.Where("created_at >= ?", startTime) } if endTime != nil { db = db.Where("created_at <= ?", endTime) } if req.Keyword != "" { keyword := "%" + req.Keyword + "%" db = db.Where("(user_nickname LIKE ? OR shop_order_id LIKE ? OR prize_name LIKE ?)", keyword, keyword, keyword) } var total int64 db.Count(&total) var logs []model.LivestreamDrawLogs if err := db.Order("id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&logs).Error; err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } res := &listLivestreamDrawLogsResponse{ List: make([]livestreamDrawLogResponse, len(logs)), Total: total, Page: page, PageSize: pageSize, } for i, log := range logs { res.List[i] = livestreamDrawLogResponse{ ID: log.ID, ActivityID: log.ActivityID, PrizeID: log.PrizeID, PrizeName: log.PrizeName, Level: log.Level, DouyinOrderID: log.DouyinOrderID, ShopOrderID: log.ShopOrderID, LocalUserID: log.LocalUserID, DouyinUserID: log.DouyinUserID, UserNickname: log.UserNickname, SeedHash: log.SeedHash, CreatedAt: log.CreatedAt.Format("2006-01-02 15:04:05"), } } ctx.Payload(res) } } // ========== 直播间承诺管理 ========== type livestreamCommitmentSummaryResponse struct { SeedVersion int32 `json:"seed_version"` Algo string `json:"algo"` HasSeed bool `json:"has_seed"` LenSeed int `json:"len_seed_master"` LenHash int `json:"len_seed_hash"` } // GenerateLivestreamCommitment 生成直播间活动承诺 // @Summary 生成直播间活动承诺 // @Description 为直播间活动生成可验证的承诺种子 // @Tags 管理端.直播间 // @Accept json // @Produce json // @Param id path integer true "活动ID" // @Success 200 {object} map[string]int32 // @Failure 400 {object} code.Failure // @Router /api/admin/livestream/activities/{id}/commitment/generate [post] // @Security LoginVerifyToken func (h *handler) GenerateLivestreamCommitment() core.HandlerFunc { return func(ctx core.Context) { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil || id <= 0 { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "无效的活动ID")) return } version, err := h.livestream.GenerateCommitment(ctx.RequestContext(), id) if err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } ctx.Payload(map[string]int32{"seed_version": version}) } } // GetLivestreamCommitmentSummary 获取直播间活动承诺摘要 // @Summary 获取直播间活动承诺摘要 // @Description 获取直播间活动的承诺状态信息 // @Tags 管理端.直播间 // @Accept json // @Produce json // @Param id path integer true "活动ID" // @Success 200 {object} livestreamCommitmentSummaryResponse // @Failure 400 {object} code.Failure // @Router /api/admin/livestream/activities/{id}/commitment/summary [get] // @Security LoginVerifyToken func (h *handler) GetLivestreamCommitmentSummary() core.HandlerFunc { return func(ctx core.Context) { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil || id <= 0 { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "无效的活动ID")) return } summary, err := h.livestream.GetCommitmentSummary(ctx.RequestContext(), id) if err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } ctx.Payload(&livestreamCommitmentSummaryResponse{ SeedVersion: summary.SeedVersion, Algo: summary.Algo, HasSeed: summary.HasSeed, LenSeed: summary.LenSeed, LenHash: summary.LenHash, }) } }