feat: 实现 IoT 卡轮询系统(支持千万级卡规模)
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m35s
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:
291
internal/handler/admin/polling_alert.go
Normal file
291
internal/handler/admin/polling_alert.go
Normal file
@@ -0,0 +1,291 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"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/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
)
|
||||
|
||||
// PollingAlertHandler 轮询告警处理器
|
||||
type PollingAlertHandler struct {
|
||||
service *polling.AlertService
|
||||
}
|
||||
|
||||
// NewPollingAlertHandler 创建轮询告警处理器
|
||||
func NewPollingAlertHandler(service *polling.AlertService) *PollingAlertHandler {
|
||||
return &PollingAlertHandler{service: service}
|
||||
}
|
||||
|
||||
// CreateRule 创建告警规则
|
||||
// @Summary 创建轮询告警规则
|
||||
// @Description 创建新的轮询告警规则
|
||||
// @Tags 轮询管理-告警
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body dto.CreatePollingAlertRuleReq true "创建请求"
|
||||
// @Success 200 {object} response.Response{data=dto.PollingAlertRuleResp}
|
||||
// @Router /api/admin/polling-alert-rules [post]
|
||||
func (h *PollingAlertHandler) CreateRule(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
var req dto.CreatePollingAlertRuleReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
rule := &model.PollingAlertRule{
|
||||
RuleName: req.RuleName,
|
||||
TaskType: req.TaskType,
|
||||
MetricType: req.MetricType,
|
||||
Operator: req.Operator,
|
||||
Threshold: req.Threshold,
|
||||
AlertLevel: req.AlertLevel,
|
||||
CooldownMinutes: req.CooldownMinutes,
|
||||
NotificationChannels: req.NotifyChannels,
|
||||
}
|
||||
|
||||
if err := h.service.CreateRule(ctx, rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, h.toRuleResp(rule))
|
||||
}
|
||||
|
||||
// ListRules 获取告警规则列表
|
||||
// @Summary 获取轮询告警规则列表
|
||||
// @Description 获取所有轮询告警规则
|
||||
// @Tags 轮询管理-告警
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} response.Response{data=dto.PollingAlertRuleListResp}
|
||||
// @Router /api/admin/polling-alert-rules [get]
|
||||
func (h *PollingAlertHandler) ListRules(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
rules, err := h.service.ListRules(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := make([]*dto.PollingAlertRuleResp, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
items = append(items, h.toRuleResp(rule))
|
||||
}
|
||||
|
||||
return response.Success(c, &dto.PollingAlertRuleListResp{Items: items})
|
||||
}
|
||||
|
||||
// GetRule 获取告警规则详情
|
||||
// @Summary 获取轮询告警规则详情
|
||||
// @Description 获取指定轮询告警规则的详细信息
|
||||
// @Tags 轮询管理-告警
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path int true "规则ID"
|
||||
// @Success 200 {object} response.Response{data=dto.PollingAlertRuleResp}
|
||||
// @Router /api/admin/polling-alert-rules/{id} [get]
|
||||
func (h *PollingAlertHandler) GetRule(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
id, err := c.ParamsInt("id")
|
||||
if err != nil || id <= 0 {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的规则ID")
|
||||
}
|
||||
|
||||
rule, err := h.service.GetRule(ctx, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, h.toRuleResp(rule))
|
||||
}
|
||||
|
||||
// UpdateRule 更新告警规则
|
||||
// @Summary 更新轮询告警规则
|
||||
// @Description 更新指定轮询告警规则
|
||||
// @Tags 轮询管理-告警
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path int true "规则ID"
|
||||
// @Param request body dto.UpdatePollingAlertRuleReq true "更新请求"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/admin/polling-alert-rules/{id} [put]
|
||||
func (h *PollingAlertHandler) UpdateRule(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
id, err := c.ParamsInt("id")
|
||||
if err != nil || id <= 0 {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的规则ID")
|
||||
}
|
||||
|
||||
var req dto.UpdatePollingAlertRuleReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
if req.RuleName != nil {
|
||||
updates["rule_name"] = *req.RuleName
|
||||
}
|
||||
if req.Threshold != nil {
|
||||
updates["threshold"] = *req.Threshold
|
||||
}
|
||||
if req.AlertLevel != nil {
|
||||
updates["alert_level"] = *req.AlertLevel
|
||||
}
|
||||
if req.Status != nil {
|
||||
updates["status"] = *req.Status
|
||||
}
|
||||
if req.CooldownMinutes != nil {
|
||||
updates["cooldown_minutes"] = *req.CooldownMinutes
|
||||
}
|
||||
if req.NotifyChannels != nil {
|
||||
updates["notification_channels"] = *req.NotifyChannels
|
||||
}
|
||||
|
||||
if err := h.service.UpdateRule(ctx, uint(id), updates); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
// DeleteRule 删除告警规则
|
||||
// @Summary 删除轮询告警规则
|
||||
// @Description 删除指定轮询告警规则
|
||||
// @Tags 轮询管理-告警
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path int true "规则ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/admin/polling-alert-rules/{id} [delete]
|
||||
func (h *PollingAlertHandler) DeleteRule(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
id, err := c.ParamsInt("id")
|
||||
if err != nil || id <= 0 {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的规则ID")
|
||||
}
|
||||
|
||||
if err := h.service.DeleteRule(ctx, uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
// ListHistory 获取告警历史
|
||||
// @Summary 获取轮询告警历史
|
||||
// @Description 获取轮询告警历史记录
|
||||
// @Tags 轮询管理-告警
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param rule_id query int false "规则ID"
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Success 200 {object} response.Response{data=dto.PollingAlertHistoryListResp}
|
||||
// @Router /api/admin/polling-alert-history [get]
|
||||
func (h *PollingAlertHandler) ListHistory(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
var req dto.ListPollingAlertHistoryReq
|
||||
if err := c.QueryParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
if req.Page < 1 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize < 1 || req.PageSize > 100 {
|
||||
req.PageSize = 20
|
||||
}
|
||||
|
||||
histories, total, err := h.service.ListHistory(ctx, req.Page, req.PageSize, req.RuleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := make([]*dto.PollingAlertHistoryResp, 0, len(histories))
|
||||
for _, h := range histories {
|
||||
items = append(items, &dto.PollingAlertHistoryResp{
|
||||
ID: h.ID,
|
||||
RuleID: h.RuleID,
|
||||
RuleName: "", // 历史记录中没有规则名称,需要单独查询
|
||||
TaskType: h.TaskType,
|
||||
MetricType: h.MetricType,
|
||||
AlertLevel: h.AlertLevel,
|
||||
Threshold: h.Threshold,
|
||||
CurrentValue: h.CurrentValue,
|
||||
Message: h.AlertMessage,
|
||||
CreatedAt: h.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
totalPages := int(total) / req.PageSize
|
||||
if int(total)%req.PageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return response.Success(c, &dto.PollingAlertHistoryListResp{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
TotalPages: totalPages,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *PollingAlertHandler) toRuleResp(rule *model.PollingAlertRule) *dto.PollingAlertRuleResp {
|
||||
return &dto.PollingAlertRuleResp{
|
||||
ID: rule.ID,
|
||||
RuleName: rule.RuleName,
|
||||
TaskType: rule.TaskType,
|
||||
TaskTypeName: h.getTaskTypeName(rule.TaskType),
|
||||
MetricType: rule.MetricType,
|
||||
MetricTypeName: h.getMetricTypeName(rule.MetricType),
|
||||
Operator: rule.Operator,
|
||||
Threshold: rule.Threshold,
|
||||
AlertLevel: rule.AlertLevel,
|
||||
Status: int(rule.Status),
|
||||
CooldownMinutes: rule.CooldownMinutes,
|
||||
NotifyChannels: rule.NotificationChannels,
|
||||
CreatedAt: rule.CreatedAt,
|
||||
UpdatedAt: rule.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PollingAlertHandler) getTaskTypeName(taskType string) string {
|
||||
switch taskType {
|
||||
case constants.TaskTypePollingRealname:
|
||||
return "实名检查"
|
||||
case constants.TaskTypePollingCarddata:
|
||||
return "流量检查"
|
||||
case constants.TaskTypePollingPackage:
|
||||
return "套餐检查"
|
||||
default:
|
||||
return taskType
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PollingAlertHandler) getMetricTypeName(metricType string) string {
|
||||
switch metricType {
|
||||
case "queue_size":
|
||||
return "队列积压"
|
||||
case "success_rate":
|
||||
return "成功率"
|
||||
case "avg_duration":
|
||||
return "平均耗时"
|
||||
case "concurrency":
|
||||
return "并发数"
|
||||
default:
|
||||
return metricType
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user