feat: 实现 IoT 卡轮询系统(支持千万级卡规模)
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m35s

实现功能:
- 实名状态检查轮询(可配置间隔)
- 卡流量检查轮询(支持跨月流量追踪)
- 套餐检查与超额自动停机
- 分布式并发控制(Redis 信号量)
- 手动触发轮询(单卡/批量/条件筛选)
- 数据清理配置与执行
- 告警规则与历史记录
- 实时监控统计(队列/性能/并发)

性能优化:
- Redis 缓存卡信息,减少 DB 查询
- Pipeline 批量写入 Redis
- 异步流量记录写入
- 渐进式初始化(10万卡/批)

压测工具(scripts/benchmark/):
- Mock Gateway 模拟上游服务
- 测试卡生成器
- 配置初始化脚本
- 实时监控脚本

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-05 17:32:44 +08:00
parent b11edde720
commit 931e140e8e
104 changed files with 16883 additions and 87 deletions

View File

@@ -0,0 +1,147 @@
package admin
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/service/polling"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
// PollingConcurrencyHandler 轮询并发控制处理器
type PollingConcurrencyHandler struct {
service *polling.ConcurrencyService
}
// NewPollingConcurrencyHandler 创建轮询并发控制处理器
func NewPollingConcurrencyHandler(service *polling.ConcurrencyService) *PollingConcurrencyHandler {
return &PollingConcurrencyHandler{service: service}
}
// List 获取所有并发配置
// @Summary 获取轮询并发配置列表
// @Description 获取所有轮询任务类型的并发配置及当前状态
// @Tags 轮询管理-并发控制
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} response.Response{data=dto.PollingConcurrencyListResp}
// @Router /api/admin/polling-concurrency [get]
func (h *PollingConcurrencyHandler) List(c *fiber.Ctx) error {
ctx := c.UserContext()
statuses, err := h.service.List(ctx)
if err != nil {
return err
}
items := make([]*dto.PollingConcurrencyResp, 0, len(statuses))
for _, s := range statuses {
items = append(items, &dto.PollingConcurrencyResp{
TaskType: s.TaskType,
TaskTypeName: s.TaskTypeName,
MaxConcurrency: s.MaxConcurrency,
Current: s.Current,
Available: s.Available,
Utilization: s.Utilization,
})
}
return response.Success(c, &dto.PollingConcurrencyListResp{Items: items})
}
// Get 获取指定任务类型的并发配置
// @Summary 获取指定任务类型的并发配置
// @Description 获取指定轮询任务类型的并发配置及当前状态
// @Tags 轮询管理-并发控制
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param task_type path string true "任务类型"
// @Success 200 {object} response.Response{data=dto.PollingConcurrencyResp}
// @Router /api/admin/polling-concurrency/{task_type} [get]
func (h *PollingConcurrencyHandler) Get(c *fiber.Ctx) error {
ctx := c.UserContext()
taskType := c.Params("task_type")
if taskType == "" {
return errors.New(errors.CodeInvalidParam, "任务类型不能为空")
}
status, err := h.service.GetByTaskType(ctx, taskType)
if err != nil {
return err
}
return response.Success(c, &dto.PollingConcurrencyResp{
TaskType: status.TaskType,
TaskTypeName: status.TaskTypeName,
MaxConcurrency: status.MaxConcurrency,
Current: status.Current,
Available: status.Available,
Utilization: status.Utilization,
})
}
// Update 更新并发配置
// @Summary 更新轮询并发配置
// @Description 更新指定轮询任务类型的最大并发数
// @Tags 轮询管理-并发控制
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param task_type path string true "任务类型"
// @Param request body dto.UpdatePollingConcurrencyReq true "更新请求"
// @Success 200 {object} response.Response
// @Router /api/admin/polling-concurrency/{task_type} [put]
func (h *PollingConcurrencyHandler) Update(c *fiber.Ctx) error {
ctx := c.UserContext()
taskType := c.Params("task_type")
if taskType == "" {
return errors.New(errors.CodeInvalidParam, "任务类型不能为空")
}
var req dto.UpdatePollingConcurrencyReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam)
}
userID := middleware.GetUserIDFromContext(ctx)
if err := h.service.UpdateMaxConcurrency(ctx, taskType, req.MaxConcurrency, userID); err != nil {
return err
}
return response.Success(c, nil)
}
// Reset 重置并发计数
// @Summary 重置轮询并发计数
// @Description 重置指定轮询任务类型的当前并发计数为0用于信号量修复
// @Tags 轮询管理-并发控制
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body dto.ResetPollingConcurrencyReq true "重置请求"
// @Success 200 {object} response.Response
// @Router /api/admin/polling-concurrency/reset [post]
func (h *PollingConcurrencyHandler) Reset(c *fiber.Ctx) error {
ctx := c.UserContext()
var req dto.ResetPollingConcurrencyReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam)
}
if req.TaskType == "" {
return errors.New(errors.CodeInvalidParam, "任务类型不能为空")
}
if err := h.service.ResetConcurrency(ctx, req.TaskType); err != nil {
return err
}
return response.Success(c, nil)
}