package taskcenter import ( "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" tasksvc "bindbox-game/internal/service/task_center" "net/http" "strconv" "time" "gorm.io/datatypes" ) // @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, "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"` 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, 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"` 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, 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"` } // 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} // 1. 统计总领取人次 (去重 user_id) var totalClaim int64 db.Raw("SELECT COUNT(DISTINCT user_id) FROM task_center_event_logs WHERE task_id = ?", taskID).Scan(&totalClaim) rsp.TotalClaim = totalClaim // 2. 按奖励类型统计 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 // 3. 最近的发放记录 (最多100条) 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) } }