Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
- 新增六对成对迁移 000232–000237:H5 弹窗类型、退款结算标识与申请人备注、优先轮询事实字段与两个新终态、通道阈值命中留痕、手机号最近解绑人、提现资格校验留痕 - 退款:原因必填与申请人备注、来源支付与渠道流水冻结、线下处理流水号补录审计、按订单查询可选退款方式、企微审批材料补齐且新增字段缺失映射即明确失败 - 优先轮询:人工关闭、有效期到期独立周期任务、失败与过期人工重触发、事实字段与异常重试查询、资产解析端点只读投影 - 通道阈值:命中事实同事务留痕与命中记录查询;员工账单:列表筛选与详情投影;商户池:列表投影与统计周期语义;H5:弹窗类型与类别排序 - 手机号:有效关联数量与最近解绑人、短信验证码失败次数限制;导出:佣金明细十五列与报表序号列 - 时间筛选:三处新增筛选纳入统一严格解析契约,员工账单产生时间参数改名 - 同步 12 份主 Spec 需求、两端点与异步任务证据链,门禁 context-health 与 OpenSpec 校验通过
191 lines
7.3 KiB
Go
191 lines
7.3 KiB
Go
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)
|
|
}
|