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
|
||||
}
|
||||
}
|
||||
351
internal/handler/admin/polling_cleanup.go
Normal file
351
internal/handler/admin/polling_cleanup.go
Normal file
@@ -0,0 +1,351 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"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/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
)
|
||||
|
||||
// PollingCleanupHandler 轮询数据清理处理器
|
||||
type PollingCleanupHandler struct {
|
||||
service *polling.CleanupService
|
||||
}
|
||||
|
||||
// NewPollingCleanupHandler 创建轮询数据清理处理器
|
||||
func NewPollingCleanupHandler(service *polling.CleanupService) *PollingCleanupHandler {
|
||||
return &PollingCleanupHandler{service: service}
|
||||
}
|
||||
|
||||
// CreateConfig 创建清理配置
|
||||
// @Summary 创建数据清理配置
|
||||
// @Description 创建新的数据清理配置
|
||||
// @Tags 轮询管理-数据清理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body dto.CreateDataCleanupConfigReq true "创建请求"
|
||||
// @Success 200 {object} response.Response{data=dto.DataCleanupConfigResp}
|
||||
// @Router /api/admin/data-cleanup-configs [post]
|
||||
func (h *PollingCleanupHandler) CreateConfig(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
var req dto.CreateDataCleanupConfigReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
config := &model.DataCleanupConfig{
|
||||
TargetTable: req.TargetTable,
|
||||
RetentionDays: req.RetentionDays,
|
||||
BatchSize: req.BatchSize,
|
||||
Description: req.Description,
|
||||
}
|
||||
|
||||
if err := h.service.CreateConfig(ctx, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, h.toConfigResp(config))
|
||||
}
|
||||
|
||||
// ListConfigs 获取清理配置列表
|
||||
// @Summary 获取数据清理配置列表
|
||||
// @Description 获取所有数据清理配置
|
||||
// @Tags 轮询管理-数据清理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} response.Response{data=dto.DataCleanupConfigListResp}
|
||||
// @Router /api/admin/data-cleanup-configs [get]
|
||||
func (h *PollingCleanupHandler) ListConfigs(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
configs, err := h.service.ListConfigs(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := make([]*dto.DataCleanupConfigResp, 0, len(configs))
|
||||
for _, config := range configs {
|
||||
items = append(items, h.toConfigResp(config))
|
||||
}
|
||||
|
||||
return response.Success(c, &dto.DataCleanupConfigListResp{Items: items})
|
||||
}
|
||||
|
||||
// GetConfig 获取清理配置详情
|
||||
// @Summary 获取数据清理配置详情
|
||||
// @Description 获取指定数据清理配置的详细信息
|
||||
// @Tags 轮询管理-数据清理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path int true "配置ID"
|
||||
// @Success 200 {object} response.Response{data=dto.DataCleanupConfigResp}
|
||||
// @Router /api/admin/data-cleanup-configs/{id} [get]
|
||||
func (h *PollingCleanupHandler) GetConfig(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
id, err := c.ParamsInt("id")
|
||||
if err != nil || id <= 0 {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的配置ID")
|
||||
}
|
||||
|
||||
config, err := h.service.GetConfig(ctx, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, h.toConfigResp(config))
|
||||
}
|
||||
|
||||
// UpdateConfig 更新清理配置
|
||||
// @Summary 更新数据清理配置
|
||||
// @Description 更新指定数据清理配置
|
||||
// @Tags 轮询管理-数据清理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path int true "配置ID"
|
||||
// @Param request body dto.UpdateDataCleanupConfigReq true "更新请求"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/admin/data-cleanup-configs/{id} [put]
|
||||
func (h *PollingCleanupHandler) UpdateConfig(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.UpdateDataCleanupConfigReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
updates := make(map[string]any)
|
||||
if req.RetentionDays != nil {
|
||||
updates["retention_days"] = *req.RetentionDays
|
||||
}
|
||||
if req.BatchSize != nil {
|
||||
updates["batch_size"] = *req.BatchSize
|
||||
}
|
||||
if req.Enabled != nil {
|
||||
updates["enabled"] = *req.Enabled
|
||||
}
|
||||
if req.Description != nil {
|
||||
updates["description"] = *req.Description
|
||||
}
|
||||
|
||||
// 获取当前用户ID
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
if userID > 0 {
|
||||
updates["updated_by"] = userID
|
||||
}
|
||||
|
||||
if err := h.service.UpdateConfig(ctx, uint(id), updates); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
// DeleteConfig 删除清理配置
|
||||
// @Summary 删除数据清理配置
|
||||
// @Description 删除指定数据清理配置
|
||||
// @Tags 轮询管理-数据清理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path int true "配置ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/admin/data-cleanup-configs/{id} [delete]
|
||||
func (h *PollingCleanupHandler) DeleteConfig(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.DeleteConfig(ctx, uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
// ListLogs 获取清理日志列表
|
||||
// @Summary 获取数据清理日志列表
|
||||
// @Description 获取数据清理日志记录
|
||||
// @Tags 轮询管理-数据清理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param table_name query string false "表名筛选"
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Success 200 {object} response.Response{data=dto.DataCleanupLogListResp}
|
||||
// @Router /api/admin/data-cleanup-logs [get]
|
||||
func (h *PollingCleanupHandler) ListLogs(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
var req dto.ListDataCleanupLogReq
|
||||
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
|
||||
}
|
||||
|
||||
logs, total, err := h.service.ListLogs(ctx, req.Page, req.PageSize, req.TableName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := make([]*dto.DataCleanupLogResp, 0, len(logs))
|
||||
for _, log := range logs {
|
||||
items = append(items, h.toLogResp(log))
|
||||
}
|
||||
|
||||
totalPages := int(total) / req.PageSize
|
||||
if int(total)%req.PageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return response.Success(c, &dto.DataCleanupLogListResp{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
TotalPages: totalPages,
|
||||
})
|
||||
}
|
||||
|
||||
// Preview 预览待清理数据
|
||||
// @Summary 预览待清理数据
|
||||
// @Description 预览各表待清理的数据量
|
||||
// @Tags 轮询管理-数据清理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} response.Response{data=dto.DataCleanupPreviewResp}
|
||||
// @Router /api/admin/data-cleanup/preview [get]
|
||||
func (h *PollingCleanupHandler) Preview(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
previews, err := h.service.Preview(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := make([]*dto.DataCleanupPreviewItem, 0, len(previews))
|
||||
for _, p := range previews {
|
||||
items = append(items, &dto.DataCleanupPreviewItem{
|
||||
TableName: p.TableName,
|
||||
RetentionDays: p.RetentionDays,
|
||||
RecordCount: p.RecordCount,
|
||||
Description: p.Description,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(c, &dto.DataCleanupPreviewResp{Items: items})
|
||||
}
|
||||
|
||||
// GetProgress 获取清理进度
|
||||
// @Summary 获取数据清理进度
|
||||
// @Description 获取当前数据清理任务的进度
|
||||
// @Tags 轮询管理-数据清理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} response.Response{data=dto.DataCleanupProgressResp}
|
||||
// @Router /api/admin/data-cleanup/progress [get]
|
||||
func (h *PollingCleanupHandler) GetProgress(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
progress, err := h.service.GetProgress(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := &dto.DataCleanupProgressResp{
|
||||
IsRunning: progress.IsRunning,
|
||||
CurrentTable: progress.CurrentTable,
|
||||
TotalTables: progress.TotalTables,
|
||||
ProcessedTables: progress.ProcessedTables,
|
||||
TotalDeleted: progress.TotalDeleted,
|
||||
StartedAt: progress.StartedAt,
|
||||
}
|
||||
|
||||
if progress.LastLog != nil {
|
||||
resp.LastLog = h.toLogResp(progress.LastLog)
|
||||
}
|
||||
|
||||
return response.Success(c, resp)
|
||||
}
|
||||
|
||||
// TriggerCleanup 手动触发清理
|
||||
// @Summary 手动触发数据清理
|
||||
// @Description 手动触发数据清理任务
|
||||
// @Tags 轮询管理-数据清理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body dto.TriggerDataCleanupReq true "触发请求"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/admin/data-cleanup/trigger [post]
|
||||
func (h *PollingCleanupHandler) TriggerCleanup(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
var req dto.TriggerDataCleanupReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
// 获取当前用户ID
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
|
||||
// 异步执行清理
|
||||
go func() {
|
||||
_ = h.service.TriggerCleanup(ctx, req.TableName, userID)
|
||||
}()
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
func (h *PollingCleanupHandler) toConfigResp(config *model.DataCleanupConfig) *dto.DataCleanupConfigResp {
|
||||
return &dto.DataCleanupConfigResp{
|
||||
ID: config.ID,
|
||||
TargetTable: config.TargetTable,
|
||||
RetentionDays: config.RetentionDays,
|
||||
BatchSize: config.BatchSize,
|
||||
Enabled: int(config.Enabled),
|
||||
Description: config.Description,
|
||||
CreatedAt: config.CreatedAt,
|
||||
UpdatedAt: config.UpdatedAt,
|
||||
UpdatedBy: config.UpdatedBy,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PollingCleanupHandler) toLogResp(log *model.DataCleanupLog) *dto.DataCleanupLogResp {
|
||||
return &dto.DataCleanupLogResp{
|
||||
ID: log.ID,
|
||||
TargetTable: log.TargetTable,
|
||||
CleanupType: log.CleanupType,
|
||||
RetentionDays: log.RetentionDays,
|
||||
DeletedCount: log.DeletedCount,
|
||||
DurationMs: log.DurationMs,
|
||||
Status: log.Status,
|
||||
ErrorMessage: log.ErrorMessage,
|
||||
StartedAt: log.StartedAt,
|
||||
CompletedAt: log.CompletedAt,
|
||||
TriggeredBy: log.TriggeredBy,
|
||||
}
|
||||
}
|
||||
147
internal/handler/admin/polling_concurrency.go
Normal file
147
internal/handler/admin/polling_concurrency.go
Normal 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)
|
||||
}
|
||||
193
internal/handler/admin/polling_config.go
Normal file
193
internal/handler/admin/polling_config.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
pollingService "github.com/break/junhong_cmp_fiber/internal/service/polling"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
)
|
||||
|
||||
// PollingConfigHandler 轮询配置 Handler
|
||||
type PollingConfigHandler struct {
|
||||
service *pollingService.ConfigService
|
||||
}
|
||||
|
||||
// NewPollingConfigHandler 创建轮询配置 Handler 实例
|
||||
func NewPollingConfigHandler(service *pollingService.ConfigService) *PollingConfigHandler {
|
||||
return &PollingConfigHandler{service: service}
|
||||
}
|
||||
|
||||
// List 获取轮询配置列表
|
||||
// @Summary 获取轮询配置列表
|
||||
// @Description 获取轮询配置列表,支持分页和筛选
|
||||
// @Tags 轮询配置管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Param status query int false "状态 (1:启用, 0:禁用)"
|
||||
// @Param card_condition query string false "卡状态条件"
|
||||
// @Param card_category query string false "卡业务类型"
|
||||
// @Param carrier_id query int false "运营商ID"
|
||||
// @Param config_name query string false "配置名称"
|
||||
// @Success 200 {object} response.Response{data=dto.PollingConfigPageResult}
|
||||
// @Router /api/admin/polling-configs [get]
|
||||
func (h *PollingConfigHandler) List(c *fiber.Ctx) error {
|
||||
var req dto.PollingConfigListRequest
|
||||
if err := c.QueryParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
configs, total, err := h.service.List(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.SuccessWithPagination(c, configs, total, req.Page, req.PageSize)
|
||||
}
|
||||
|
||||
// Create 创建轮询配置
|
||||
// @Summary 创建轮询配置
|
||||
// @Description 创建新的轮询配置
|
||||
// @Tags 轮询配置管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body dto.CreatePollingConfigRequest true "创建轮询配置请求"
|
||||
// @Success 200 {object} response.Response{data=dto.PollingConfigResponse}
|
||||
// @Router /api/admin/polling-configs [post]
|
||||
func (h *PollingConfigHandler) Create(c *fiber.Ctx) error {
|
||||
var req dto.CreatePollingConfigRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
config, err := h.service.Create(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, config)
|
||||
}
|
||||
|
||||
// Get 获取轮询配置详情
|
||||
// @Summary 获取轮询配置详情
|
||||
// @Description 根据 ID 获取轮询配置详情
|
||||
// @Tags 轮询配置管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "配置ID"
|
||||
// @Success 200 {object} response.Response{data=dto.PollingConfigResponse}
|
||||
// @Router /api/admin/polling-configs/{id} [get]
|
||||
func (h *PollingConfigHandler) Get(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的配置 ID")
|
||||
}
|
||||
|
||||
config, err := h.service.Get(c.UserContext(), uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, config)
|
||||
}
|
||||
|
||||
// Update 更新轮询配置
|
||||
// @Summary 更新轮询配置
|
||||
// @Description 根据 ID 更新轮询配置
|
||||
// @Tags 轮询配置管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "配置ID"
|
||||
// @Param body body dto.UpdatePollingConfigRequest true "更新轮询配置请求"
|
||||
// @Success 200 {object} response.Response{data=dto.PollingConfigResponse}
|
||||
// @Router /api/admin/polling-configs/{id} [put]
|
||||
func (h *PollingConfigHandler) Update(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的配置 ID")
|
||||
}
|
||||
|
||||
var req dto.UpdatePollingConfigRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
config, err := h.service.Update(c.UserContext(), uint(id), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, config)
|
||||
}
|
||||
|
||||
// Delete 删除轮询配置
|
||||
// @Summary 删除轮询配置
|
||||
// @Description 根据 ID 删除轮询配置
|
||||
// @Tags 轮询配置管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "配置ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/admin/polling-configs/{id} [delete]
|
||||
func (h *PollingConfigHandler) Delete(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的配置 ID")
|
||||
}
|
||||
|
||||
if err := h.service.Delete(c.UserContext(), uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
// UpdateStatus 更新轮询配置状态
|
||||
// @Summary 更新轮询配置状态
|
||||
// @Description 启用或禁用轮询配置
|
||||
// @Tags 轮询配置管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "配置ID"
|
||||
// @Param body body dto.UpdatePollingConfigStatusRequest true "更新状态请求"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/admin/polling-configs/{id}/status [put]
|
||||
func (h *PollingConfigHandler) UpdateStatus(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的配置 ID")
|
||||
}
|
||||
|
||||
var req dto.UpdatePollingConfigStatusRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
if err := h.service.UpdateStatus(c.UserContext(), uint(id), req.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
// ListEnabled 获取所有启用的配置
|
||||
// @Summary 获取所有启用的配置
|
||||
// @Description 获取所有启用状态的轮询配置,按优先级排序
|
||||
// @Tags 轮询配置管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response{data=[]dto.PollingConfigResponse}
|
||||
// @Router /api/admin/polling-configs/enabled [get]
|
||||
func (h *PollingConfigHandler) ListEnabled(c *fiber.Ctx) error {
|
||||
configs, err := h.service.ListEnabled(c.UserContext())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, configs)
|
||||
}
|
||||
311
internal/handler/admin/polling_manual_trigger.go
Normal file
311
internal/handler/admin/polling_manual_trigger.go
Normal file
@@ -0,0 +1,311 @@
|
||||
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/middleware"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
)
|
||||
|
||||
// PollingManualTriggerHandler 轮询手动触发处理器
|
||||
type PollingManualTriggerHandler struct {
|
||||
service *polling.ManualTriggerService
|
||||
}
|
||||
|
||||
// NewPollingManualTriggerHandler 创建轮询手动触发处理器
|
||||
func NewPollingManualTriggerHandler(service *polling.ManualTriggerService) *PollingManualTriggerHandler {
|
||||
return &PollingManualTriggerHandler{service: service}
|
||||
}
|
||||
|
||||
// TriggerSingle 单卡手动触发
|
||||
// @Summary 单卡手动触发
|
||||
// @Description 触发单张卡的轮询任务
|
||||
// @Tags 轮询管理-手动触发
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body dto.TriggerSingleReq true "触发请求"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/admin/polling-manual-trigger/single [post]
|
||||
func (h *PollingManualTriggerHandler) TriggerSingle(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
var req dto.TriggerSingleReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
if userID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized)
|
||||
}
|
||||
|
||||
if err := h.service.TriggerSingle(ctx, req.CardID, req.TaskType, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
// TriggerBatch 批量手动触发
|
||||
// @Summary 批量手动触发
|
||||
// @Description 批量触发多张卡的轮询任务
|
||||
// @Tags 轮询管理-手动触发
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body dto.TriggerBatchReq true "触发请求"
|
||||
// @Success 200 {object} response.Response{data=dto.ManualTriggerLogResp}
|
||||
// @Router /api/admin/polling-manual-trigger/batch [post]
|
||||
func (h *PollingManualTriggerHandler) TriggerBatch(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
var req dto.TriggerBatchReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
if userID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized)
|
||||
}
|
||||
|
||||
log, err := h.service.TriggerBatch(ctx, req.CardIDs, req.TaskType, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, h.toLogResp(log))
|
||||
}
|
||||
|
||||
// TriggerByCondition 条件筛选触发
|
||||
// @Summary 条件筛选触发
|
||||
// @Description 根据条件筛选卡并触发轮询任务
|
||||
// @Tags 轮询管理-手动触发
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body dto.TriggerByConditionReq true "触发请求"
|
||||
// @Success 200 {object} response.Response{data=dto.ManualTriggerLogResp}
|
||||
// @Router /api/admin/polling-manual-trigger/by-condition [post]
|
||||
func (h *PollingManualTriggerHandler) TriggerByCondition(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
var req dto.TriggerByConditionReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
if userID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized)
|
||||
}
|
||||
|
||||
filter := &polling.ConditionFilter{
|
||||
CardStatus: req.CardStatus,
|
||||
CarrierCode: req.CarrierCode,
|
||||
CardType: req.CardType,
|
||||
ShopID: req.ShopID,
|
||||
PackageIDs: req.PackageIDs,
|
||||
EnablePolling: req.EnablePolling,
|
||||
Limit: req.Limit,
|
||||
}
|
||||
|
||||
log, err := h.service.TriggerByCondition(ctx, filter, req.TaskType, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, h.toLogResp(log))
|
||||
}
|
||||
|
||||
// GetStatus 获取触发状态
|
||||
// @Summary 获取手动触发状态
|
||||
// @Description 获取当前用户的手动触发状态
|
||||
// @Tags 轮询管理-手动触发
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} response.Response{data=dto.ManualTriggerStatusResp}
|
||||
// @Router /api/admin/polling-manual-trigger/status [get]
|
||||
func (h *PollingManualTriggerHandler) GetStatus(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
if userID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized)
|
||||
}
|
||||
|
||||
// 获取正在运行的任务
|
||||
runningTasks, err := h.service.GetRunningTasks(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := make([]*dto.ManualTriggerLogResp, 0, len(runningTasks))
|
||||
for _, log := range runningTasks {
|
||||
items = append(items, h.toLogResp(log))
|
||||
}
|
||||
|
||||
// 获取各队列大小
|
||||
queueSizes := make(map[string]int64)
|
||||
for _, taskType := range []string{
|
||||
constants.TaskTypePollingRealname,
|
||||
constants.TaskTypePollingCarddata,
|
||||
constants.TaskTypePollingPackage,
|
||||
} {
|
||||
size, _ := h.service.GetQueueSize(ctx, taskType)
|
||||
queueSizes[taskType] = size
|
||||
}
|
||||
|
||||
return response.Success(c, &dto.ManualTriggerStatusResp{
|
||||
RunningTasks: items,
|
||||
QueueSizes: queueSizes,
|
||||
})
|
||||
}
|
||||
|
||||
// ListHistory 获取触发历史
|
||||
// @Summary 获取手动触发历史
|
||||
// @Description 获取手动触发历史记录
|
||||
// @Tags 轮询管理-手动触发
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param task_type query string false "任务类型筛选"
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Success 200 {object} response.Response{data=dto.ManualTriggerLogListResp}
|
||||
// @Router /api/admin/polling-manual-trigger/history [get]
|
||||
func (h *PollingManualTriggerHandler) ListHistory(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
var req dto.ListManualTriggerLogReq
|
||||
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
|
||||
}
|
||||
|
||||
logs, total, err := h.service.ListHistory(ctx, req.Page, req.PageSize, req.TaskType, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := make([]*dto.ManualTriggerLogResp, 0, len(logs))
|
||||
for _, log := range logs {
|
||||
items = append(items, h.toLogResp(log))
|
||||
}
|
||||
|
||||
totalPages := int(total) / req.PageSize
|
||||
if int(total)%req.PageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return response.Success(c, &dto.ManualTriggerLogListResp{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
TotalPages: totalPages,
|
||||
})
|
||||
}
|
||||
|
||||
// CancelTrigger 取消触发任务
|
||||
// @Summary 取消手动触发任务
|
||||
// @Description 取消正在执行的手动触发任务
|
||||
// @Tags 轮询管理-手动触发
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body dto.CancelTriggerReq true "取消请求"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/admin/polling-manual-trigger/cancel [post]
|
||||
func (h *PollingManualTriggerHandler) CancelTrigger(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
var req dto.CancelTriggerReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
if userID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized)
|
||||
}
|
||||
|
||||
if err := h.service.CancelTrigger(ctx, req.TriggerID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
func (h *PollingManualTriggerHandler) toLogResp(log *model.PollingManualTriggerLog) *dto.ManualTriggerLogResp {
|
||||
return &dto.ManualTriggerLogResp{
|
||||
ID: log.ID,
|
||||
TaskType: log.TaskType,
|
||||
TaskTypeName: h.getTaskTypeName(log.TaskType),
|
||||
TriggerType: log.TriggerType,
|
||||
TriggerTypeName: h.getTriggerTypeName(log.TriggerType),
|
||||
TotalCount: log.TotalCount,
|
||||
ProcessedCount: log.ProcessedCount,
|
||||
SuccessCount: log.SuccessCount,
|
||||
FailedCount: log.FailedCount,
|
||||
Status: log.Status,
|
||||
StatusName: h.getStatusName(log.Status),
|
||||
TriggeredBy: log.TriggeredBy,
|
||||
TriggeredAt: log.TriggeredAt,
|
||||
CompletedAt: log.CompletedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PollingManualTriggerHandler) getTaskTypeName(taskType string) string {
|
||||
switch taskType {
|
||||
case constants.TaskTypePollingRealname:
|
||||
return "实名检查"
|
||||
case constants.TaskTypePollingCarddata:
|
||||
return "流量检查"
|
||||
case constants.TaskTypePollingPackage:
|
||||
return "套餐检查"
|
||||
default:
|
||||
return taskType
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PollingManualTriggerHandler) getTriggerTypeName(triggerType string) string {
|
||||
switch triggerType {
|
||||
case "single":
|
||||
return "单卡触发"
|
||||
case "batch":
|
||||
return "批量触发"
|
||||
case "by_condition":
|
||||
return "条件筛选"
|
||||
default:
|
||||
return triggerType
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PollingManualTriggerHandler) getStatusName(status string) string {
|
||||
switch status {
|
||||
case "pending":
|
||||
return "待处理"
|
||||
case "processing":
|
||||
return "处理中"
|
||||
case "completed":
|
||||
return "已完成"
|
||||
case "cancelled":
|
||||
return "已取消"
|
||||
default:
|
||||
return status
|
||||
}
|
||||
}
|
||||
139
internal/handler/admin/polling_monitoring.go
Normal file
139
internal/handler/admin/polling_monitoring.go
Normal file
@@ -0,0 +1,139 @@
|
||||
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/response"
|
||||
)
|
||||
|
||||
// PollingMonitoringHandler 轮询监控处理器
|
||||
type PollingMonitoringHandler struct {
|
||||
service *polling.MonitoringService
|
||||
}
|
||||
|
||||
// NewPollingMonitoringHandler 创建轮询监控处理器
|
||||
func NewPollingMonitoringHandler(service *polling.MonitoringService) *PollingMonitoringHandler {
|
||||
return &PollingMonitoringHandler{service: service}
|
||||
}
|
||||
|
||||
// GetOverview 获取轮询总览
|
||||
// @Summary 获取轮询总览统计
|
||||
// @Description 获取轮询系统的总览统计数据,包括初始化进度、队列大小等
|
||||
// @Tags 轮询管理-监控
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} response.Response{data=dto.PollingOverviewResp}
|
||||
// @Router /api/admin/polling-stats [get]
|
||||
func (h *PollingMonitoringHandler) GetOverview(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
stats, err := h.service.GetOverview(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, &dto.PollingOverviewResp{
|
||||
TotalCards: stats.TotalCards,
|
||||
InitializedCards: stats.InitializedCards,
|
||||
InitProgress: stats.InitProgress,
|
||||
IsInitializing: stats.IsInitializing,
|
||||
RealnameQueueSize: stats.RealnameQueueSize,
|
||||
CarddataQueueSize: stats.CarddataQueueSize,
|
||||
PackageQueueSize: stats.PackageQueueSize,
|
||||
})
|
||||
}
|
||||
|
||||
// GetQueueStatuses 获取队列状态
|
||||
// @Summary 获取轮询队列状态
|
||||
// @Description 获取所有轮询队列的详细状态,包括队列大小、到期数、等待时间等
|
||||
// @Tags 轮询管理-监控
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} response.Response{data=dto.PollingQueueStatusListResp}
|
||||
// @Router /api/admin/polling-stats/queues [get]
|
||||
func (h *PollingMonitoringHandler) GetQueueStatuses(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
statuses, err := h.service.GetQueueStatuses(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := make([]*dto.PollingQueueStatusResp, 0, len(statuses))
|
||||
for _, s := range statuses {
|
||||
items = append(items, &dto.PollingQueueStatusResp{
|
||||
TaskType: s.TaskType,
|
||||
TaskTypeName: s.TaskTypeName,
|
||||
QueueSize: s.QueueSize,
|
||||
ManualPending: s.ManualPending,
|
||||
DueCount: s.DueCount,
|
||||
AvgWaitTime: s.AvgWaitTime,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(c, &dto.PollingQueueStatusListResp{Items: items})
|
||||
}
|
||||
|
||||
// GetTaskStatuses 获取任务统计
|
||||
// @Summary 获取轮询任务统计
|
||||
// @Description 获取所有轮询任务类型的执行统计,包括成功率、平均耗时等
|
||||
// @Tags 轮询管理-监控
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} response.Response{data=dto.PollingTaskStatsListResp}
|
||||
// @Router /api/admin/polling-stats/tasks [get]
|
||||
func (h *PollingMonitoringHandler) GetTaskStatuses(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
statuses, err := h.service.GetTaskStatuses(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := make([]*dto.PollingTaskStatsResp, 0, len(statuses))
|
||||
for _, s := range statuses {
|
||||
items = append(items, &dto.PollingTaskStatsResp{
|
||||
TaskType: s.TaskType,
|
||||
TaskTypeName: s.TaskTypeName,
|
||||
SuccessCount1h: s.SuccessCount1h,
|
||||
FailureCount1h: s.FailureCount1h,
|
||||
TotalCount1h: s.TotalCount1h,
|
||||
SuccessRate: s.SuccessRate,
|
||||
AvgDurationMs: s.AvgDurationMs,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(c, &dto.PollingTaskStatsListResp{Items: items})
|
||||
}
|
||||
|
||||
// GetInitProgress 获取初始化进度
|
||||
// @Summary 获取轮询初始化进度
|
||||
// @Description 获取轮询系统初始化的详细进度,包括已处理数、预计完成时间等
|
||||
// @Tags 轮询管理-监控
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} response.Response{data=dto.PollingInitProgressResp}
|
||||
// @Router /api/admin/polling-stats/init-progress [get]
|
||||
func (h *PollingMonitoringHandler) GetInitProgress(c *fiber.Ctx) error {
|
||||
ctx := c.UserContext()
|
||||
|
||||
progress, err := h.service.GetInitProgress(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, &dto.PollingInitProgressResp{
|
||||
TotalCards: progress.TotalCards,
|
||||
InitializedCards: progress.InitializedCards,
|
||||
Progress: progress.Progress,
|
||||
IsComplete: progress.IsComplete,
|
||||
StartedAt: progress.StartedAt,
|
||||
EstimatedETA: progress.EstimatedETA,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user