package taskcenter import ( "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/repository/mysql/model" tasksvc "bindbox-game/internal/service/task_center" "encoding/json" "fmt" "net/http" "strconv" "strings" "time" "gorm.io/datatypes" "gorm.io/gorm" ) // @Summary 任务列表(Admin) // @Description 获取任务管理列表 // @Tags TaskCenter(Admin) // @Accept json // @Produce json // @Success 200 {object} map[string]any "任务列表" // @Router /admin/task_center/tasks [get] func (h *handler) ListTasksForAdmin() core.HandlerFunc { return func(ctx core.Context) { items, total, err := h.task.ListTasks(ctx.RequestContext(), tasksvc.ListTasksInput{Page: 1, PageSize: 50, OnlyActive: false}) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return } type rsp struct { Total int64 `json:"total"` List []map[string]any `json:"list"` } out := &rsp{Total: total, List: make([]map[string]any, len(items))} for i, v := range items { var stStr, etStr string if v.StartTime > 0 { stStr = time.Unix(v.StartTime, 0).Format("2006-01-02 15:04:05") } if v.EndTime > 0 { etStr = time.Unix(v.EndTime, 0).Format("2006-01-02 15:04:05") } out.List[i] = map[string]any{ "id": v.ID, "name": v.Name, "description": v.Description, "status": v.Status, "start_time": stStr, "end_time": etStr, "show_expired": v.ShowExpired, "allow_claim_after_end": v.AllowClaimAfterEnd, "quota": v.Quota, "claimed_count": v.ClaimedCount, } } ctx.Payload(out) } } type createTaskRequest struct { Name string `json:"name"` Description string `json:"description"` Status int32 `json:"status"` Visibility int32 `json:"visibility"` ShowExpired int32 `json:"show_expired"` AllowClaimAfterEnd int32 `json:"allow_claim_after_end"` Quota int32 `json:"quota"` StartTime string `json:"start_time"` EndTime string `json:"end_time"` } // @Summary 创建任务(Admin) // @Description 创建一个新的任务 // @Tags TaskCenter(Admin) // @Accept json // @Produce json // @Param request body createTaskRequest true "创建任务请求" // @Success 200 {object} map[string]any "创建成功" // @Router /admin/task_center/tasks [post] func (h *handler) CreateTaskForAdmin() core.HandlerFunc { return func(ctx core.Context) { req := new(createTaskRequest) if err := ctx.ShouldBindJSON(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, err.Error())) return } var st, et *time.Time if req.StartTime != "" { t, err := time.ParseInLocation("2006-01-02 15:04:05", req.StartTime, time.Local) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "开始时间格式错误: "+err.Error())) return } st = &t } if req.EndTime != "" { t, err := time.ParseInLocation("2006-01-02 15:04:05", req.EndTime, time.Local) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "结束时间格式错误: "+err.Error())) return } et = &t } id, err := h.task.CreateTask(ctx.RequestContext(), tasksvc.CreateTaskInput{ Name: req.Name, Description: req.Description, Status: req.Status, Visibility: req.Visibility, ShowExpired: req.ShowExpired, AllowClaimAfterEnd: req.AllowClaimAfterEnd, Quota: req.Quota, StartTime: st, EndTime: et, }) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return } ctx.Payload(map[string]any{"id": id}) } } type modifyTaskRequest struct { Name string `json:"name"` Description string `json:"description"` Status int32 `json:"status"` Visibility int32 `json:"visibility"` ShowExpired int32 `json:"show_expired"` AllowClaimAfterEnd int32 `json:"allow_claim_after_end"` Quota int32 `json:"quota"` StartTime string `json:"start_time"` EndTime string `json:"end_time"` } // @Summary 修改任务(Admin) // @Description 修改指定任务的信息 // @Tags TaskCenter(Admin) // @Accept json // @Produce json // @Param id path int true "任务ID" // @Param request body modifyTaskRequest true "修改任务请求" // @Success 200 {object} map[string]any "修改成功" // @Router /admin/task_center/tasks/{id} [put] func (h *handler) ModifyTaskForAdmin() core.HandlerFunc { return func(ctx core.Context) { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递任务ID")) return } req := new(modifyTaskRequest) if err := ctx.ShouldBindJSON(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, err.Error())) return } var st, et *time.Time if req.StartTime != "" { t, err := time.ParseInLocation("2006-01-02 15:04:05", req.StartTime, time.Local) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "开始时间格式错误: "+err.Error())) return } st = &t } if req.EndTime != "" { t, err := time.ParseInLocation("2006-01-02 15:04:05", req.EndTime, time.Local) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "结束时间格式错误: "+err.Error())) return } et = &t } if err := h.task.ModifyTask(ctx.RequestContext(), id, tasksvc.ModifyTaskInput{ Name: req.Name, Description: req.Description, Status: req.Status, Visibility: req.Visibility, ShowExpired: req.ShowExpired, AllowClaimAfterEnd: req.AllowClaimAfterEnd, Quota: req.Quota, StartTime: st, EndTime: et, }); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return } ctx.Payload(map[string]any{"ok": true}) } } // @Summary 删除任务(Admin) // @Description 删除指定的任务 // @Tags TaskCenter(Admin) // @Accept json // @Produce json // @Param id path int true "任务ID" // @Success 200 {object} map[string]any "删除成功" // @Router /admin/task_center/tasks/{id} [delete] func (h *handler) DeleteTaskForAdmin() core.HandlerFunc { return func(ctx core.Context) { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递任务ID")) return } if err := h.task.DeleteTask(ctx.RequestContext(), id); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return } ctx.Payload(map[string]any{"ok": true}) } } type upsertTiersRequest struct { Tiers []struct { Metric string `json:"metric"` Operator string `json:"operator"` Threshold int64 `json:"threshold"` Window string `json:"window"` Repeatable int32 `json:"repeatable"` Priority int32 `json:"priority"` ActivityID int64 `json:"activity_id"` ExtraParams datatypes.JSON `json:"extra_params"` } `json:"tiers"` } // @Summary 设置任务层级(Admin) // @Description 设置任务的完成条件层级 // @Tags TaskCenter(Admin) // @Accept json // @Produce json // @Param id path int true "任务ID" // @Param request body upsertTiersRequest true "设置层级请求" // @Success 200 {object} map[string]any "设置成功" // @Router /admin/task_center/tasks/{id}/tiers [post] func (h *handler) UpsertTaskTiersForAdmin() core.HandlerFunc { return func(ctx core.Context) { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递任务ID")) return } req := new(upsertTiersRequest) if err := ctx.ShouldBindJSON(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, err.Error())) return } in := make([]tasksvc.TaskTierInput, len(req.Tiers)) for i, t := range req.Tiers { in[i] = tasksvc.TaskTierInput{Metric: t.Metric, Operator: t.Operator, Threshold: t.Threshold, Window: t.Window, Repeatable: t.Repeatable, Priority: t.Priority, ActivityID: t.ActivityID, ExtraParams: t.ExtraParams} } if err := h.task.UpsertTaskTiers(ctx.RequestContext(), id, in); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return } ctx.Payload(map[string]any{"ok": true}) } } // @Summary 获取任务层级(Admin) // @Description 获取任务的完成条件层级列表 // @Tags TaskCenter(Admin) // @Accept json // @Produce json // @Param id path int true "任务ID" // @Success 200 {object} map[string]any "层级列表" // @Router /admin/task_center/tasks/{id}/tiers [get] func (h *handler) ListTaskTiersForAdmin() core.HandlerFunc { return func(ctx core.Context) { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递任务ID")) return } items, err := h.task.ListTaskTiers(ctx.RequestContext(), id) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return } ctx.Payload(map[string]any{"list": items}) } } type upsertRewardsRequest struct { Rewards []struct { ID int64 `json:"id"` TierID int64 `json:"tier_id"` RewardType string `json:"reward_type"` RewardPayload datatypes.JSON `json:"reward_payload"` Quantity int64 `json:"quantity"` } `json:"rewards"` DeleteIDs []int64 `json:"delete_ids"` } // @Summary 设置任务奖励(Admin) // @Description 设置任务层级对应的奖励 // @Tags TaskCenter(Admin) // @Accept json // @Produce json // @Param id path int true "任务ID" // @Param request body upsertRewardsRequest true "设置奖励请求" // @Success 200 {object} map[string]any "设置成功" // @Router /admin/task_center/tasks/{id}/rewards [post] func (h *handler) UpsertTaskRewardsForAdmin() core.HandlerFunc { return func(ctx core.Context) { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递任务ID")) return } req := new(upsertRewardsRequest) if err := ctx.ShouldBindJSON(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, err.Error())) return } in := make([]tasksvc.TaskRewardInput, len(req.Rewards)) for i, r := range req.Rewards { in[i] = tasksvc.TaskRewardInput{ID: r.ID, TierID: r.TierID, RewardType: r.RewardType, RewardPayload: r.RewardPayload, Quantity: r.Quantity} } if err := h.task.UpsertTaskRewards(ctx.RequestContext(), id, in, req.DeleteIDs); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return } ctx.Payload(map[string]any{"ok": true}) } } // @Summary 获取任务奖励(Admin) // @Description 获取任务层级对应的奖励列表 // @Tags TaskCenter(Admin) // @Accept json // @Produce json // @Param id path int true "任务ID" // @Success 200 {object} map[string]any "奖励列表" // @Router /admin/task_center/tasks/{id}/rewards [get] func (h *handler) ListTaskRewardsForAdmin() core.HandlerFunc { return func(ctx core.Context) { id, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递任务ID")) return } items, err := h.task.ListTaskRewards(ctx.RequestContext(), id) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return } ctx.Payload(map[string]any{"list": items}) } } type simulateOrderPaidRequest struct { UserID int64 `json:"user_id"` OrderID int64 `json:"order_id"` } // @Summary 模拟订单支付(Admin) // @Description 模拟用户支付订单,触发任务进度更新 // @Tags TaskCenter(Admin) // @Accept json // @Produce json // @Param request body simulateOrderPaidRequest true "模拟请求" // @Success 200 {object} map[string]any "操作成功" // @Router /admin/task_center/simulate/order_paid [post] func (h *handler) SimulateOrderPaid() core.HandlerFunc { return func(ctx core.Context) { req := new(simulateOrderPaidRequest) if err := ctx.ShouldBindJSON(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, err.Error())) return } if err := h.task.OnOrderPaid(ctx.RequestContext(), req.UserID, req.OrderID); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return } ctx.Payload(map[string]any{"ok": true}) } } type simulateInviteSuccessRequest struct { InviterID int64 `json:"inviter_id"` InviteeID int64 `json:"invitee_id"` } // @Summary 模拟邀请成功(Admin) // @Description 模拟用户邀请成功,触发任务进度更新 // @Tags TaskCenter(Admin) // @Accept json // @Produce json // @Param request body simulateInviteSuccessRequest true "模拟请求" // @Success 200 {object} map[string]any "操作成功" // @Router /admin/task_center/simulate/invite_success [post] func (h *handler) SimulateInviteSuccess() core.HandlerFunc { return func(ctx core.Context) { req := new(simulateInviteSuccessRequest) if err := ctx.ShouldBindJSON(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, err.Error())) return } if err := h.task.OnInviteSuccess(ctx.RequestContext(), req.InviterID, req.InviteeID); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return } ctx.Payload(map[string]any{"ok": true}) } } // rewardStatItem 奖励发放统计项 type rewardStatItem struct { RewardType string `json:"reward_type"` Count int64 `json:"count"` // 发放次数 Quantity int64 `json:"quantity"` // 发放总数量 } // rewardStatsResponse 奖励发放统计响应 type rewardStatsResponse struct { TaskID int64 `json:"task_id"` TotalClaim int64 `json:"total_claim"` // 总领取人次 Stats []rewardStatItem `json:"stats"` Logs []rewardLogItem `json:"logs"` // 详细发放记录 } // rewardLogItem 奖励发放记录 type rewardLogItem struct { ID int64 `json:"id"` UserID int64 `json:"user_id"` Nickname string `json:"nickname"` TierID int64 `json:"tier_id"` RewardType string `json:"reward_type"` Quantity int64 `json:"quantity"` CreatedAt string `json:"created_at"` } type taskRewardCostSummaryItem struct { RewardType string `json:"reward_type"` RewardLabel string `json:"reward_label"` GrantCount int64 `json:"grant_count"` Quantity int64 `json:"quantity"` TotalCost int64 `json:"total_cost"` } type taskRewardCostCategoryItem struct { RewardType string `json:"reward_type"` RewardLabel string `json:"reward_label"` ItemKey string `json:"item_key"` ItemName string `json:"item_name"` UnitCost int64 `json:"unit_cost"` GrantCount int64 `json:"grant_count"` Quantity int64 `json:"quantity"` TotalCost int64 `json:"total_cost"` } type taskRewardCostLogItem struct { ID int64 `json:"id"` TaskID int64 `json:"task_id"` TaskName string `json:"task_name"` UserID int64 `json:"user_id"` Nickname string `json:"nickname"` RewardType string `json:"reward_type"` RewardLabel string `json:"reward_label"` ItemName string `json:"item_name"` Quantity int64 `json:"quantity"` UnitCost int64 `json:"unit_cost"` TotalCost int64 `json:"total_cost"` CreatedAt string `json:"created_at"` } type taskRewardCostStatsResponse struct { StartDate string `json:"start_date"` EndDate string `json:"end_date"` TotalGrant int64 `json:"total_grant"` TotalUsers int64 `json:"total_users"` TotalCost int64 `json:"total_cost"` Summaries []taskRewardCostSummaryItem `json:"summaries"` Categories []taskRewardCostCategoryItem `json:"categories"` Logs []taskRewardCostLogItem `json:"logs"` } func rewardTypeLabel(rewardType string) string { switch rewardType { case "points": return "积分" case "coupon": return "优惠券" case "item_card": return "道具卡" case "title": return "称号" case "game_ticket": return "游戏券" case "product": return "商品" default: return "未知" } } func parseDateRange(ctx core.Context) (*time.Time, *time.Time, string, string, error) { startDate := strings.TrimSpace(ctx.Request().URL.Query().Get("start_date")) endDate := strings.TrimSpace(ctx.Request().URL.Query().Get("end_date")) loc := time.Local var startTime *time.Time var endTime *time.Time if startDate != "" { t, err := time.ParseInLocation("2006-01-02", startDate, loc) if err != nil { return nil, nil, "", "", fmt.Errorf("开始日期格式错误") } startTime = &t } else { t := time.Now().AddDate(0, 0, -6) t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc) startTime = &t startDate = t.Format("2006-01-02") } if endDate != "" { t, err := time.ParseInLocation("2006-01-02", endDate, loc) if err != nil { return nil, nil, "", "", fmt.Errorf("结束日期格式错误") } t = t.Add(24*time.Hour - time.Second) endTime = &t } else { t := time.Now() t = time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, loc) endTime = &t endDate = t.Format("2006-01-02") } if startTime != nil && endTime != nil && startTime.After(*endTime) { return nil, nil, "", "", fmt.Errorf("开始日期不能晚于结束日期") } return startTime, endTime, startDate, endDate, nil } func applyRewardCostDateRange(db *gorm.DB, startTime, endTime *time.Time) *gorm.DB { if startTime != nil { db = db.Where("el.created_at >= ?", *startTime) } if endTime != nil { db = db.Where("el.created_at <= ?", *endTime) } return db } // GetTaskRewardStats 获取任务奖励发放统计 // @Summary 获取任务奖励发放统计(Admin) // @Description 获取指定任务已发放奖励的按类型统计及详细记录 // @Tags TaskCenter(Admin) // @Accept json // @Produce json // @Param id path int true "任务ID" // @Success 200 {object} rewardStatsResponse "奖励发放统计" // @Router /admin/task_center/tasks/{id}/reward-stats [get] func (h *handler) GetTaskRewardStats() core.HandlerFunc { return func(ctx core.Context) { taskID, err := strconv.ParseInt(ctx.Param("id"), 10, 64) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递任务ID")) return } db := h.repo.GetDbR() rsp := &rewardStatsResponse{TaskID: taskID} var totalClaim int64 db.Raw("SELECT COUNT(DISTINCT user_id) FROM task_center_event_logs WHERE task_id = ?", taskID).Scan(&totalClaim) rsp.TotalClaim = totalClaim type statRow struct { RewardType string Cnt int64 Qty int64 } var statRows []statRow db.Raw(` SELECT tr.reward_type, COUNT(el.id) as cnt, COALESCE(SUM(tr.quantity), 0) as qty FROM task_center_event_logs el LEFT JOIN task_center_task_rewards tr ON tr.task_id = el.task_id AND tr.tier_id = el.tier_id WHERE el.task_id = ? GROUP BY tr.reward_type `, taskID).Scan(&statRows) stats := make([]rewardStatItem, 0, len(statRows)) for _, r := range statRows { rt := r.RewardType if rt == "" { rt = "unknown" } stats = append(stats, rewardStatItem{RewardType: rt, Count: r.Cnt, Quantity: r.Qty}) } rsp.Stats = stats type logRow struct { ID int64 UserID int64 Nickname string TierID int64 RewardType string Quantity int64 CreatedAt time.Time } var logRows []logRow db.Raw(` SELECT el.id, el.user_id, COALESCE(u.nickname, '') as nickname, el.tier_id, COALESCE(tr.reward_type, '') as reward_type, COALESCE(tr.quantity, 0) as quantity, el.created_at FROM task_center_event_logs el LEFT JOIN users u ON u.id = el.user_id LEFT JOIN task_center_task_rewards tr ON tr.task_id = el.task_id AND tr.tier_id = el.tier_id WHERE el.task_id = ? ORDER BY el.id DESC LIMIT 100 `, taskID).Scan(&logRows) logs := make([]rewardLogItem, len(logRows)) for i, r := range logRows { logs[i] = rewardLogItem{ ID: r.ID, UserID: r.UserID, Nickname: r.Nickname, TierID: r.TierID, RewardType: r.RewardType, Quantity: r.Quantity, CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05"), } } rsp.Logs = logs ctx.Payload(rsp) } } // GetRewardCostStats 获取任务奖励成本汇总统计 // @Summary 获取任务奖励成本汇总统计(Admin) // @Description 获取任务中心奖励发放成本汇总,支持日期筛选与分类明细 // @Tags TaskCenter(Admin) // @Accept json // @Produce json // @Param start_date query string false "开始日期 YYYY-MM-DD" // @Param end_date query string false "结束日期 YYYY-MM-DD" // @Success 200 {object} taskRewardCostStatsResponse "奖励成本统计" // @Router /admin/task_center/reward-cost-stats [get] func (h *handler) GetRewardCostStats() core.HandlerFunc { return func(ctx core.Context) { startTime, endTime, startDate, endDate, err := parseDateRange(ctx) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, err.Error())) return } db := h.repo.GetDbR() rsp := &taskRewardCostStatsResponse{ StartDate: startDate, EndDate: endDate, } baseQuery := db.Table(model.TableNameTaskCenterEventLogs+" AS el"). Joins("LEFT JOIN "+model.TableNameUsers+" AS u ON u.id = el.user_id"). Joins("LEFT JOIN "+model.TableNameTaskCenterTaskRewards+" AS tr ON tr.task_id = el.task_id AND tr.tier_id = el.tier_id"). Joins("LEFT JOIN "+model.TableNameTaskCenterTasks+" AS tt ON tt.id = el.task_id") baseQuery = applyRewardCostDateRange(baseQuery, startTime, endTime) var overview struct { TotalGrant int64 TotalUsers int64 } if err := baseQuery.Session(&gorm.Session{}). Select("COUNT(el.id) AS total_grant, COUNT(DISTINCT el.user_id) AS total_users"). Scan(&overview).Error; err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return } rsp.TotalGrant = overview.TotalGrant rsp.TotalUsers = overview.TotalUsers type rewardCostRow struct { TaskID int64 TaskName string ID int64 UserID int64 Nickname string RewardType string RewardPayload string Quantity int64 CreatedAt time.Time } var rewardRows []rewardCostRow if err := baseQuery.Session(&gorm.Session{}). Select(` el.id, el.task_id, COALESCE(tt.name, '') AS task_name, el.user_id, COALESCE(u.nickname, '') AS nickname, COALESCE(tr.reward_type, '') AS reward_type, COALESCE(tr.reward_payload, '{}') AS reward_payload, COALESCE(tr.quantity, 0) AS quantity, el.created_at `). Order("el.id DESC"). Scan(&rewardRows).Error; err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return } type couponPayload struct { CouponID int64 `json:"coupon_id"` } type itemCardPayload struct { CardID int64 `json:"card_id"` } type titlePayload struct { TitleID int64 `json:"title_id"` } type productPayload struct { ProductID int64 `json:"product_id"` } couponIDs := make([]int64, 0) itemCardIDs := make([]int64, 0) titleIDs := make([]int64, 0) productIDs := make([]int64, 0) couponSet := map[int64]struct{}{} itemCardSet := map[int64]struct{}{} titleSet := map[int64]struct{}{} productSet := map[int64]struct{}{} for _, row := range rewardRows { switch row.RewardType { case "coupon": var pl couponPayload _ = json.Unmarshal([]byte(row.RewardPayload), &pl) if pl.CouponID > 0 { if _, ok := couponSet[pl.CouponID]; !ok { couponSet[pl.CouponID] = struct{}{} couponIDs = append(couponIDs, pl.CouponID) } } case "item_card": var pl itemCardPayload _ = json.Unmarshal([]byte(row.RewardPayload), &pl) if pl.CardID > 0 { if _, ok := itemCardSet[pl.CardID]; !ok { itemCardSet[pl.CardID] = struct{}{} itemCardIDs = append(itemCardIDs, pl.CardID) } } case "title": var pl titlePayload _ = json.Unmarshal([]byte(row.RewardPayload), &pl) if pl.TitleID > 0 { if _, ok := titleSet[pl.TitleID]; !ok { titleSet[pl.TitleID] = struct{}{} titleIDs = append(titleIDs, pl.TitleID) } } case "product": var pl productPayload _ = json.Unmarshal([]byte(row.RewardPayload), &pl) if pl.ProductID > 0 { if _, ok := productSet[pl.ProductID]; !ok { productSet[pl.ProductID] = struct{}{} productIDs = append(productIDs, pl.ProductID) } } } } couponMap := map[int64]model.SystemCoupons{} if len(couponIDs) > 0 { var coupons []model.SystemCoupons if err := db.Where("id IN ?", couponIDs).Find(&coupons).Error; err == nil { for _, item := range coupons { couponMap[item.ID] = item } } } itemCardMap := map[int64]model.SystemItemCards{} if len(itemCardIDs) > 0 { var cards []model.SystemItemCards if err := db.Where("id IN ?", itemCardIDs).Find(&cards).Error; err == nil { for _, item := range cards { itemCardMap[item.ID] = item } } } titleMap := map[int64]model.SystemTitles{} if len(titleIDs) > 0 { var titles []model.SystemTitles if err := db.Where("id IN ?", titleIDs).Find(&titles).Error; err == nil { for _, item := range titles { titleMap[item.ID] = item } } } productMap := map[int64]model.Products{} if len(productIDs) > 0 { var products []model.Products if err := db.Where("id IN ?", productIDs).Find(&products).Error; err == nil { for _, item := range products { productMap[item.ID] = item } } } type summaryAcc struct { grantCount int64 quantity int64 totalCost int64 } summaryMap := map[string]*summaryAcc{} type categoryAcc struct { rewardType string rewardLabel string itemKey string itemName string unitCost int64 grantCount int64 quantity int64 totalCost int64 } categoryMap := map[string]*categoryAcc{} logs := make([]taskRewardCostLogItem, 0, len(rewardRows)) for _, row := range rewardRows { rewardType := row.RewardType if rewardType == "" { rewardType = "unknown" } rewardLabel := rewardTypeLabel(rewardType) quantity := row.Quantity if quantity <= 0 { quantity = 0 } itemKey := rewardType itemName := rewardLabel unitCost := int64(0) switch rewardType { case "points": itemKey = "points" itemName = "积分" unitCost = 1 case "coupon": var pl couponPayload _ = json.Unmarshal([]byte(row.RewardPayload), &pl) if coupon, ok := couponMap[pl.CouponID]; ok { itemKey = fmt.Sprintf("coupon:%d", coupon.ID) itemName = coupon.Name unitCost = coupon.DiscountValue } else { itemKey = fmt.Sprintf("coupon:%d", pl.CouponID) itemName = "优惠券" } case "item_card": var pl itemCardPayload _ = json.Unmarshal([]byte(row.RewardPayload), &pl) if card, ok := itemCardMap[pl.CardID]; ok { itemKey = fmt.Sprintf("item_card:%d", card.ID) itemName = card.Name unitCost = card.Price } else { itemKey = fmt.Sprintf("item_card:%d", pl.CardID) itemName = "道具卡" } case "title": var pl titlePayload _ = json.Unmarshal([]byte(row.RewardPayload), &pl) if title, ok := titleMap[pl.TitleID]; ok { itemKey = fmt.Sprintf("title:%d", title.ID) itemName = title.Name } else { itemKey = fmt.Sprintf("title:%d", pl.TitleID) itemName = "称号" } case "game_ticket": itemKey = "game_ticket" itemName = "游戏券" case "product": var pl productPayload _ = json.Unmarshal([]byte(row.RewardPayload), &pl) if product, ok := productMap[pl.ProductID]; ok { itemKey = fmt.Sprintf("product:%d", product.ID) itemName = product.Name unitCost = product.CostPrice } else { itemKey = fmt.Sprintf("product:%d", pl.ProductID) itemName = "商品" } } totalCost := unitCost * quantity rsp.TotalCost += totalCost summary := summaryMap[rewardType] if summary == nil { summary = &summaryAcc{} summaryMap[rewardType] = summary } summary.grantCount++ summary.quantity += quantity summary.totalCost += totalCost categoryKey := rewardType + ":" + itemKey category := categoryMap[categoryKey] if category == nil { category = &categoryAcc{ rewardType: rewardType, rewardLabel: rewardLabel, itemKey: itemKey, itemName: itemName, unitCost: unitCost, } categoryMap[categoryKey] = category } category.grantCount++ category.quantity += quantity category.totalCost += totalCost logs = append(logs, taskRewardCostLogItem{ ID: row.ID, TaskID: row.TaskID, TaskName: row.TaskName, UserID: row.UserID, Nickname: row.Nickname, RewardType: rewardType, RewardLabel: rewardLabel, ItemName: itemName, Quantity: quantity, UnitCost: unitCost, TotalCost: totalCost, CreatedAt: row.CreatedAt.Format("2006-01-02 15:04:05"), }) } rsp.Summaries = make([]taskRewardCostSummaryItem, 0, len(summaryMap)) for rewardType, acc := range summaryMap { rsp.Summaries = append(rsp.Summaries, taskRewardCostSummaryItem{ RewardType: rewardType, RewardLabel: rewardTypeLabel(rewardType), GrantCount: acc.grantCount, Quantity: acc.quantity, TotalCost: acc.totalCost, }) } rsp.Categories = make([]taskRewardCostCategoryItem, 0, len(categoryMap)) for _, acc := range categoryMap { rsp.Categories = append(rsp.Categories, taskRewardCostCategoryItem{ RewardType: acc.rewardType, RewardLabel: acc.rewardLabel, ItemKey: acc.itemKey, ItemName: acc.itemName, UnitCost: acc.unitCost, GrantCount: acc.grantCount, Quantity: acc.quantity, TotalCost: acc.totalCost, }) } rsp.Logs = logs ctx.Payload(rsp) } }