feat: 分层轮询、重置日前加密与激活前流量结算
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m37s

- 轮询配置新增套餐状态、资产状态、重置日前窗口与窗口内流量间隔(迁移 248),间隔三态:正数按间隔、0 不检查、空继承
- 配置按 priority, id 排序合并;在库与已销售无套餐未匹配时告警不入队
- 新增优先级整体重排、匹配预览、立即重算到期时间(异步只提前)、分层统计与重置日前加密统计接口
- 已切换日快照计量的卡流量按观测时间归属,激活前用量只记已结算
- 订单购买与实名激活补齐资产级激活锁,激活前对已切换卡结算一次流量,失败只告警不阻塞,结算不触发通道阈值判定
This commit is contained in:
邹进
2026-10-24 17:06:38 +09:00
parent 02a842f066
commit 98017ac053
34 changed files with 2232 additions and 182 deletions

View File

@@ -191,3 +191,91 @@ func (h *PollingConfigHandler) ListEnabled(c *fiber.Ctx) error {
return response.Success(c, configs)
}
// UpdatePriorities 按给定顺序整体调整轮询配置优先级(仅超级管理员)。
// PUT /api/admin/polling-configs/priorities
func (h *PollingConfigHandler) UpdatePriorities(c *fiber.Ctx) error {
var req dto.UpdatePollingConfigPrioritiesRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if len(req.OrderedIDs) == 0 {
return errors.New(errors.CodeInvalidParam, "轮询配置优先级参数不合法:配置 ID 列表不能为空")
}
if err := h.service.UpdatePriorities(c.UserContext(), &req); err != nil {
return err
}
return response.Success(c, nil)
}
// MatchPreview 预览一张卡的轮询配置匹配结果;GET 只按当前配置,POST 可带未保存修改,均不写数据。
// GET /api/admin/polling-configs/match-preview
// POST /api/admin/polling-configs/match-preview
func (h *PollingConfigHandler) MatchPreview(c *fiber.Ctx) error {
var req dto.PollingConfigMatchPreviewRequest
if c.Method() == fiber.MethodPost {
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
} else if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if req.ICCID == "" {
return errors.New(errors.CodeInvalidParam, "匹配预览参数不合法:ICCID 不能为空")
}
result, err := h.service.MatchPreview(c.UserContext(), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
// CreateRecalculateTask 创建"立即重算到期时间"异步任务(仅超级管理员)。
// POST /api/admin/polling-configs/recalculate-due
func (h *PollingConfigHandler) CreateRecalculateTask(c *fiber.Ctx) error {
var req dto.PollingRecalculateDueRequest
if len(c.Body()) > 0 {
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
}
result, err := h.service.CreateRecalculateTask(c.UserContext(), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
// GetRecalculateTask 查询重算到期时间任务进度。
// GET /api/admin/polling-configs/recalculate-due/:task_id
func (h *PollingConfigHandler) GetRecalculateTask(c *fiber.Ctx) error {
taskID := c.Params("task_id")
if taskID == "" {
return errors.New(errors.CodeInvalidParam, "重算任务 ID 不能为空")
}
result, err := h.service.GetRecalculateTask(c.UserContext(), taskID)
if err != nil {
return err
}
return response.Success(c, result)
}
// TierStats 查询分层轮询统计(结果缓存 5 分钟)。
// GET /api/admin/polling-stats/tiers
func (h *PollingConfigHandler) TierStats(c *fiber.Ctx) error {
result, err := h.service.TierStats(c.UserContext())
if err != nil {
return err
}
return response.Success(c, result)
}
// PreResetStats 查询重置日前加密效果统计。
// GET /api/admin/polling-stats/pre-reset
func (h *PollingConfigHandler) PreResetStats(c *fiber.Ctx) error {
result, err := h.service.PreResetStats(c.UserContext())
if err != nil {
return err
}
return response.Success(c, result)
}