package admin import ( "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "github.com/break/junhong_cmp_fiber/internal/handler/validation" "github.com/break/junhong_cmp_fiber/internal/model/dto" prioritypollingquery "github.com/break/junhong_cmp_fiber/internal/query/prioritypolling" pollingSvc "github.com/break/junhong_cmp_fiber/internal/service/polling" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/middleware" "github.com/break/junhong_cmp_fiber/pkg/response" ) // PriorityPollingHandler 处理卡轮询优先队列的人工入口(入队、关闭、重触发)与只读查询请求。 type PriorityPollingHandler struct { manual *pollingSvc.PriorityManualService query *prioritypollingquery.Query validator *validator.Validate } // NewPriorityPollingHandler 创建卡轮询优先队列处理器。 func NewPriorityPollingHandler( manual *pollingSvc.PriorityManualService, query *prioritypollingquery.Query, validator *validator.Validate, ) *PriorityPollingHandler { return &PriorityPollingHandler{manual: manual, query: query, validator: validator} } // Enqueue 人工优先入队。 // POST /api/admin/polling-priority-items func (h *PriorityPollingHandler) Enqueue(c *fiber.Ctx) error { if h.manual == nil { return errors.New(errors.CodeInternalError, "人工优先入队能力未配置") } var req dto.EnqueuePriorityPollingRequest if err := c.BodyParser(&req); err != nil { return errors.New(errors.CodeInvalidParam, "请求参数格式不正确") } if err := h.validator.Struct(&req); err != nil { return errors.New(errors.CodeInvalidParam, validation.Message("人工优先入队参数不合法", &req, err)) } result, err := h.manual.Enqueue(c.UserContext(), req.CardID, req.Reason, middleware.GetUserIDFromContext(c.UserContext())) if err != nil { return err } taskTypeNames := make([]string, 0, len(result.TaskTypes)) items := make([]dto.EnqueuePriorityPollingItemView, 0, len(result.Items)) for _, taskType := range result.TaskTypes { taskTypeNames = append(taskTypeNames, constants.PollingPriorityTaskTypeName(taskType)) } for _, item := range result.Items { items = append(items, dto.EnqueuePriorityPollingItemView{ ItemID: item.ItemID, TaskType: item.TaskType, TaskTypeName: constants.PollingPriorityTaskTypeName(item.TaskType), Status: item.Status, StatusName: constants.PollingPriorityStatusName(item.Status), TriggerCount: item.TriggerCount, LastTriggeredAt: item.LastTriggeredAt, Created: item.Created, }) } return response.Success(c, dto.EnqueuePriorityPollingResponse{ CardID: result.CardID, TaskTypes: result.TaskTypes, TaskTypeNames: taskTypeNames, CreatedCount: result.CreatedCount, MergedCount: result.MergedCount, Items: items, }) } // Close 人工关闭未完成的优先轮询项。 // 只允许超级管理员与平台账号;原因必填并写入审计,关闭后不再发起上游调用且保留既有执行结果与失败原因。 // POST /api/admin/polling-priority-items/:id/close func (h *PriorityPollingHandler) Close(c *fiber.Ctx) error { if h.manual == nil { return errors.New(errors.CodeInternalError, "人工关闭优先轮询项能力未配置") } id, err := pathID(c) if err != nil { return err } var req dto.ClosePriorityPollingItemRequest if err := c.BodyParser(&req); err != nil { return errors.New(errors.CodeInvalidParam, "请求参数格式不正确") } if err := h.validator.Struct(&req); err != nil { return errors.New(errors.CodeInvalidParam, validation.Message("人工关闭优先轮询项参数不合法", &req, err)) } result, err := h.manual.Close(c.UserContext(), id, req.Reason, middleware.GetUserIDFromContext(c.UserContext())) if err != nil { return err } return response.Success(c, dto.ClosePriorityPollingItemResponse{ ItemID: result.ItemID, CardID: result.CardID, TaskType: result.TaskType, TaskTypeName: constants.PollingPriorityTaskTypeName(result.TaskType), Status: result.Status, StatusName: constants.PollingPriorityStatusName(result.Status), DequeuedAt: result.DequeuedAt, Reason: result.Reason, }) } // Retrigger 人工重触发失败出队或已过期的优先轮询项。 // 复用人工入队入口为该卡该任务类型建立新的活动项(尝试次数从零开始),原因必填并写入审计; // 权限与人工入队一致(含数据范围内的代理账号),不绕过活动项唯一键代表的并发上限。 // POST /api/admin/polling-priority-items/:id/retrigger func (h *PriorityPollingHandler) Retrigger(c *fiber.Ctx) error { if h.manual == nil { return errors.New(errors.CodeInternalError, "人工重触发优先轮询项能力未配置") } id, err := pathID(c) if err != nil { return err } var req dto.RetriggerPriorityPollingItemRequest if err := c.BodyParser(&req); err != nil { return errors.New(errors.CodeInvalidParam, "请求参数格式不正确") } if err := h.validator.Struct(&req); err != nil { return errors.New(errors.CodeInvalidParam, validation.Message("人工重触发优先轮询项参数不合法", &req, err)) } result, err := h.manual.Retrigger(c.UserContext(), id, req.Reason, middleware.GetUserIDFromContext(c.UserContext())) if err != nil { return err } view := dto.RetriggerPriorityPollingItemResponse{ SourceItemID: result.SourceItemID, CardID: result.Enqueue.CardID, Reason: result.Reason, } if len(result.Enqueue.Items) > 0 { // 重触发只针对来源项的任务类型,因此结果里至多一项;防御性取首项。 item := result.Enqueue.Items[0] view.TaskType = item.TaskType view.TaskTypeName = constants.PollingPriorityTaskTypeName(item.TaskType) view.ItemID = item.ItemID view.Status = item.Status view.StatusName = constants.PollingPriorityStatusName(item.Status) view.AttemptCount = item.AttemptCount view.TriggerCount = item.TriggerCount view.LastTriggeredAt = item.LastTriggeredAt view.Created = item.Created } return response.Success(c, view) } // ListPriorityItems 分页查询优先轮询项。 // GET /api/admin/polling-priority-items func (h *PriorityPollingHandler) ListPriorityItems(c *fiber.Ctx) error { if h.query == nil { return errors.New(errors.CodeInternalError, "优先轮询项查询能力未配置") } var req dto.PriorityPollingItemListRequest if err := c.QueryParser(&req); err != nil { return errors.New(errors.CodeInvalidParam, "请求参数解析失败") } if err := h.validator.Struct(&req); err != nil { return errors.New(errors.CodeInvalidParam, validation.Message("优先轮询项查询参数不合法", &req, err)) } result, err := h.query.List(c.UserContext(), req) if err != nil { return err } return response.SuccessWithPagination(c, result.List, result.Total, result.Page, result.PageSize) } // GetPriorityItem 查询优先轮询项详情。 // GET /api/admin/polling-priority-items/:id func (h *PriorityPollingHandler) GetPriorityItem(c *fiber.Ctx) error { if h.query == nil { return errors.New(errors.CodeInternalError, "优先轮询项查询能力未配置") } id, err := pathID(c) if err != nil { return err } result, err := h.query.Detail(c.UserContext(), id) if err != nil { return err } return response.Success(c, result) }