Files
junhong_cmp_fiber/internal/handler/admin/polling_alert.go
huang 2ef8b8a705
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
fix: 修复分页字段/角色DTO/套餐详情/批量分配Bug,新增C端测试登录接口和Hurl价格验证测试
- 修复B-2:27个分页DTO字段名统一(list→items, page_size→size,删除total_pages)
- 修复B-1:角色接口统一返回DTO(id小写,消除GORM大写字段名)
- 修复C-1:套餐详情接口(GET /packages/:id)补充代理佣金字段
- 修复C-2:批量分配已存在记录由500改为静默跳过
- 修复C-3:创建系列授权响应在事务提交后构建,packages数组不再为空
- 新增C端开发测试登录接口(POST /api/c/v1/auth/dev-login,仅logging.development=true时生效)
- 新增Hurl测试:card-price-verification-flow.hurl(导入卡→分配→C端价格验证)
- 新增测试Excel数据文件和dev.env测试变量
2026-03-31 09:54:08 +08:00

286 lines
8.0 KiB
Go

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,
})
}
return response.Success(c, &dto.PollingAlertHistoryListResp{
Items: items,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
})
}
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
}
}