增加两个批量查询接口

This commit is contained in:
2025-11-16 16:30:26 +08:00
parent bf747e22ce
commit bf1600b385
10 changed files with 1041 additions and 11 deletions

View File

@@ -199,12 +199,14 @@ func (a *API) setupRoutes() {
thresholdGroup.GET("/historical-alarms", a.alarmController.ListHistoricalAlarms) // 获取历史告警
// 设备阈值告警配置
thresholdGroup.GET("/device", a.alarmController.ListDeviceThresholdAlarms)
thresholdGroup.POST("/device", a.alarmController.CreateDeviceThresholdAlarm)
thresholdGroup.GET("/device/:task_id", a.alarmController.GetDeviceThresholdAlarm)
thresholdGroup.PUT("/device/:task_id", a.alarmController.UpdateDeviceThresholdAlarm)
thresholdGroup.DELETE("/device/:task_id", a.alarmController.DeleteDeviceThresholdAlarm)
// 区域阈值告警配置
thresholdGroup.GET("/area", a.alarmController.ListAreaThresholdAlarms)
thresholdGroup.POST("/area", a.alarmController.CreateAreaThresholdAlarm)
thresholdGroup.GET("/area/:task_id", a.alarmController.GetAreaThresholdAlarm)
thresholdGroup.PUT("/area/:task_id", a.alarmController.UpdateAreaThresholdAlarm)

View File

@@ -179,6 +179,76 @@ func (t *ThresholdAlarmController) ListHistoricalAlarms(ctx echo.Context) error
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "成功获取历史告警列表", resp, actionType, "成功获取历史告警列表", req)
}
// ListDeviceThresholdAlarms godoc
// @Summary 批量查询设备阈值告警
// @Description 根据过滤条件和分页参数查询设备阈值告警列表
// @Tags 告警管理
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param query query dto.ListDeviceThresholdAlarmRequest true "查询参数"
// @Success 200 {object} controller.Response{data=dto.ListDeviceThresholdAlarmResponse} "成功获取设备阈值告警列表"
// @Router /api/v1/alarm/threshold/device [get]
func (t *ThresholdAlarmController) ListDeviceThresholdAlarms(ctx echo.Context) error {
reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "ListDeviceThresholdAlarms")
const actionType = "批量查询设备阈值告警"
var req dto.ListDeviceThresholdAlarmRequest
if err := ctx.Bind(&req); err != nil {
logger.Errorf("%s: 参数绑定失败: %v", actionType, err)
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求参数: "+err.Error(), actionType, "请求参数绑定失败", nil)
}
resp, err := t.thresholdAlarmService.ListDeviceThresholdAlarms(reqCtx, &req)
if err != nil {
// 捕获 ErrInvalidPagination 错误,并返回 Bad Request
if errors.Is(err, repository.ErrInvalidPagination) {
logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req)
}
logger.Errorf("%s: 服务层查询设备阈值告警失败: %v", actionType, err)
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "查询设备阈值告警失败: "+err.Error(), actionType, "服务层查询设备阈值告警失败", req)
}
logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "成功获取设备阈值告警列表", resp, actionType, "成功获取设备阈值告警列表", req)
}
// ListAreaThresholdAlarms godoc
// @Summary 批量查询区域阈值告警
// @Description 根据过滤条件和分页参数查询区域阈值告警列表
// @Tags 告警管理
// @Security BearerAuth
// @Accept json
// @Produce json
// @Param query query dto.ListAreaThresholdAlarmRequest true "查询参数"
// @Success 200 {object} controller.Response{data=dto.ListAreaThresholdAlarmResponse} "成功获取区域阈值告警列表"
// @Router /api/v1/alarm/threshold/area [get]
func (t *ThresholdAlarmController) ListAreaThresholdAlarms(ctx echo.Context) error {
reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "ListAreaThresholdAlarms")
const actionType = "批量查询区域阈值告警"
var req dto.ListAreaThresholdAlarmRequest
if err := ctx.Bind(&req); err != nil {
logger.Errorf("%s: 参数绑定失败: %v", actionType, err)
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求参数: "+err.Error(), actionType, "请求参数绑定失败", nil)
}
resp, err := t.thresholdAlarmService.ListAreaThresholdAlarms(reqCtx, &req)
if err != nil {
// 捕获 ErrInvalidPagination 错误,并返回 Bad Request
if errors.Is(err, repository.ErrInvalidPagination) {
logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req)
}
logger.Errorf("%s: 服务层查询区域阈值告警失败: %v", actionType, err)
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "查询区域阈值告警失败: "+err.Error(), actionType, "服务层查询区域阈值告警失败", req)
}
logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "成功获取区域阈值告警列表", resp, actionType, "成功获取区域阈值告警列表", req)
}
// CreateDeviceThresholdAlarm godoc
// @Summary 创建设备阈值告警
// @Description 为单个设备创建一条新的阈值告警规则

View File

@@ -69,7 +69,7 @@ type HistoricalAlarmDTO struct {
AlarmCode models.AlarmCode `json:"alarm_code"`
AlarmSummary string `json:"alarm_summary"`
Level models.SeverityLevel `json:"level"`
AlarmDetails string `json:"alarm_details"`
AlarmDetails string `json:"json_details"`
TriggerTime time.Time `json:"trigger_time"`
ResolveTime time.Time `json:"resolve_time"`
ResolveMethod string `json:"resolve_method"`
@@ -138,3 +138,35 @@ type DeviceThresholdAlarmDTO struct {
Operator models.Operator `json:"operator"`
Level models.SeverityLevel `json:"level"`
}
// ListDeviceThresholdAlarmRequest 定义了获取设备阈值告警列表的请求参数
type ListDeviceThresholdAlarmRequest struct {
Page int `json:"page" query:"page"`
PageSize int `json:"page_size" query:"page_size"`
DeviceID *uint32 `json:"device_id" query:"device_id"` // 按设备ID过滤
SensorType *models.SensorType `json:"sensor_type" query:"sensor_type"` // 按传感器类型过滤
Level *models.SeverityLevel `json:"level" query:"level"` // 按告警等级过滤
OrderBy string `json:"order_by" query:"order_by"` // 排序字段,例如 "id DESC"
}
// ListDeviceThresholdAlarmResponse 是获取设备阈值告警列表的响应结构
type ListDeviceThresholdAlarmResponse struct {
List []DeviceThresholdAlarmDTO `json:"list"`
Pagination PaginationDTO `json:"pagination"`
}
// ListAreaThresholdAlarmRequest 定义了获取区域阈值告警列表的请求参数
type ListAreaThresholdAlarmRequest struct {
Page int `json:"page" query:"page"`
PageSize int `json:"page_size" query:"page_size"`
AreaControllerID *uint32 `json:"area_controller_id" query:"area_controller_id"` // 按区域主控ID过滤
SensorType *models.SensorType `json:"sensor_type" query:"sensor_type"` // 按传感器类型过滤
Level *models.SeverityLevel `json:"level" query:"level"` // 按告警等级过滤
OrderBy string `json:"order_by" query:"order_by"` // 排序字段,例如 "id DESC"
}
// ListAreaThresholdAlarmResponse 是获取区域阈值告警列表的响应结构
type ListAreaThresholdAlarmResponse struct {
List []AreaThresholdAlarmDTO `json:"list"`
Pagination PaginationDTO `json:"pagination"`
}

View File

@@ -36,6 +36,8 @@ type ThresholdAlarmService interface {
DeleteDeviceThresholdAlarm(ctx context.Context, taskID int, req *dto.DeleteDeviceThresholdAlarmDTO) error
// DeleteDeviceThresholdAlarmByDeviceID 实现了根据设备ID删除一个设备下所有设备阈值告警的逻辑。
DeleteDeviceThresholdAlarmByDeviceID(ctx context.Context, deviceID uint32) error
// ListDeviceThresholdAlarms 批量查询设备阈值告警配置。
ListDeviceThresholdAlarms(ctx context.Context, req *dto.ListDeviceThresholdAlarmRequest) (*dto.ListDeviceThresholdAlarmResponse, error)
// CreateAreaThresholdAlarm 创建一个区域阈值告警。
CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error
@@ -47,6 +49,8 @@ type ThresholdAlarmService interface {
DeleteAreaThresholdAlarm(ctx context.Context, taskID int) error
// DeleteAreaThresholdAlarmByAreaControllerID 实现了根据区域ID删除一个区域下所有区域阈值告警的逻辑。
DeleteAreaThresholdAlarmByAreaControllerID(ctx context.Context, areaControllerID uint32) error
// ListAreaThresholdAlarms 批量查询区域阈值告警配置。
ListAreaThresholdAlarms(ctx context.Context, req *dto.ListAreaThresholdAlarmRequest) (*dto.ListAreaThresholdAlarmResponse, error)
}
// thresholdAlarmService 是 ThresholdAlarmService 接口的具体实现。
@@ -444,6 +448,110 @@ func (s *thresholdAlarmService) DeleteDeviceThresholdAlarmByDeviceID(ctx context
}
// ListDeviceThresholdAlarms 实现了批量查询设备阈值告警配置的逻辑。
func (s *thresholdAlarmService) ListDeviceThresholdAlarms(ctx context.Context, req *dto.ListDeviceThresholdAlarmRequest) (*dto.ListDeviceThresholdAlarmResponse, error) {
serviceCtx, logger := logs.Trace(ctx, s.ctx, "ListDeviceThresholdAlarms")
// 1. 准备调用 planRepo.ListTasks 的选项
taskType := models.TaskTypeDeviceThresholdCheck
opts := repository.TaskListOptions{
Type: &taskType,
DeviceID: req.DeviceID,
SensorType: req.SensorType,
Level: req.Level,
OrderBy: req.OrderBy,
}
// 2. 调用底层的 ListTasks 方法
tasks, total, err := s.planRepo.ListTasks(serviceCtx, opts, req.Page, req.PageSize)
if err != nil {
return nil, fmt.Errorf("查询设备阈值告警任务失败: %w", err)
}
// 3. 将查询到的 models.Task 列表转换为 dto.DeviceThresholdAlarmDTO 列表
alarmDTOs := make([]dto.DeviceThresholdAlarmDTO, 0, len(tasks))
for _, t := range tasks {
var params task.DeviceThresholdCheckParams
if err := t.ParseParameters(&params); err != nil {
logger.Warnf("解析任务 %d 的参数失败: %v已在列表中跳过", t.ID, err)
continue
}
alarmDTOs = append(alarmDTOs, dto.DeviceThresholdAlarmDTO{
ID: t.ID,
DeviceID: params.DeviceID,
SensorType: params.SensorType,
Thresholds: params.Thresholds,
Operator: params.Operator,
Level: params.Level,
})
}
// 4. 构建并返回响应
response := &dto.ListDeviceThresholdAlarmResponse{
List: alarmDTOs,
Pagination: dto.PaginationDTO{
Total: total,
Page: req.Page,
PageSize: req.PageSize,
},
}
return response, nil
}
// ListAreaThresholdAlarms 实现了批量查询区域阈值告警配置的逻辑。
func (s *thresholdAlarmService) ListAreaThresholdAlarms(ctx context.Context, req *dto.ListAreaThresholdAlarmRequest) (*dto.ListAreaThresholdAlarmResponse, error) {
serviceCtx, logger := logs.Trace(ctx, s.ctx, "ListAreaThresholdAlarms")
// 1. 准备调用 planRepo.ListTasks 的选项
taskType := models.TaskTypeAreaCollectorThresholdCheck
opts := repository.TaskListOptions{
Type: &taskType,
AreaControllerID: req.AreaControllerID,
SensorType: req.SensorType,
Level: req.Level,
OrderBy: req.OrderBy,
}
// 2. 调用底层的 ListTasks 方法
tasks, total, err := s.planRepo.ListTasks(serviceCtx, opts, req.Page, req.PageSize)
if err != nil {
return nil, fmt.Errorf("查询区域阈值告警任务失败: %w", err)
}
// 3. 将查询到的 models.Task 列表转换为 dto.AreaThresholdAlarmDTO 列表
alarmDTOs := make([]dto.AreaThresholdAlarmDTO, 0, len(tasks))
for _, t := range tasks {
var params task.AreaThresholdCheckParams
if err := t.ParseParameters(&params); err != nil {
logger.Warnf("解析区域任务 %d 的参数失败: %v已在列表中跳过", t.ID, err)
continue
}
alarmDTOs = append(alarmDTOs, dto.AreaThresholdAlarmDTO{
ID: t.ID,
AreaControllerID: params.AreaControllerID,
SensorType: params.SensorType,
Thresholds: params.Thresholds,
Operator: params.Operator,
Level: params.Level,
})
}
// 4. 构建并返回响应
response := &dto.ListAreaThresholdAlarmResponse{
List: alarmDTOs,
Pagination: dto.PaginationDTO{
Total: total,
Page: req.Page,
PageSize: req.PageSize,
},
}
return response, nil
}
// CreateAreaThresholdAlarm 实现了创建一个区域阈值告警的逻辑。
func (s *thresholdAlarmService) CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error {
serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreateAreaThresholdAlarm")

View File

@@ -100,7 +100,7 @@ func (app *Application) initializePeriodicSystemHealthCheckPlan(ctx context.Cont
delayParams := task.DelayTaskParams{DelayDuration: 10} // 延时10秒
delayTask := models.Task{
Name: "延时任务",
Description: "系统预设延时任务,用于错峰处理",
Description: "系统预设延时任务,用于等待设备上传数据",
ExecutionOrder: 2,
Type: models.TaskTypeWaiting,
}

View File

@@ -38,6 +38,29 @@ type ListPlansOptions struct {
PlanType PlanTypeFilter
}
// TaskListOptions 定义了查询任务时的可选参数
type TaskListOptions struct {
// --- 通用筛选 ---
Name *string // 按任务名称进行模糊查询
PlanID *uint32 // 按所属的计划ID进行精确查询
Type *models.TaskType // 按任务类型精确查询
// --- 关联筛选 ---
// 用于通过 device_tasks 关联表筛选与特定设备ID关联的任务
DeviceID *uint32
// --- JSON 参数内筛选 (仅对特定任务类型有效) ---
// 用于筛选 TaskTypeAreaCollectorThresholdCheck 类型的任务
AreaControllerID *uint32
// 用于筛选 TaskTypeDeviceThresholdCheck 和 TaskTypeAreaCollectorThresholdCheck 类型的任务
SensorType *models.SensorType
// 用于筛选 TaskTypeDeviceThresholdCheck 和 TaskTypeAreaCollectorThresholdCheck 类型的任务
Level *models.SeverityLevel
// --- 排序 ---
OrderBy string // 例如 "id desc"
}
// PlanRepository 定义了与计划模型相关的数据库操作接口
// 这是为了让业务逻辑层依赖于抽象,而不是具体的数据库实现
type PlanRepository interface {
@@ -89,6 +112,8 @@ type PlanRepository interface {
UpdatePlanStateAfterExecution(ctx context.Context, planID uint32, newCount uint32, newStatus models.PlanStatus) error
// ListTasksByDeviceID 根据设备ID获取关联任务列表
ListTasksByDeviceID(ctx context.Context, deviceID uint32) ([]*models.Task, error)
// ListTasks 支持分页和过滤的任务列表查询
ListTasks(ctx context.Context, opts TaskListOptions, page, pageSize int) ([]models.Task, int64, error)
}
// gormPlanRepository 是 PlanRepository 的 GORM 实现
@@ -896,3 +921,74 @@ func (r *gormPlanRepository) ListTasksByDeviceID(ctx context.Context, deviceID u
return tasks, nil
}
// ListTasks 实现了分页和过滤查询任务的功能,并优化了 JOIN 后的计数逻辑
func (r *gormPlanRepository) ListTasks(ctx context.Context, opts TaskListOptions, page, pageSize int) ([]models.Task, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListTasks")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
var results []models.Task
var total int64
// 1. 构建基础查询,应用所有筛选条件
query := r.db.WithContext(repoCtx).Model(&models.Task{})
if opts.Name != nil && *opts.Name != "" {
query = query.Where("tasks.name LIKE ?", "%"+*opts.Name+"%")
}
if opts.PlanID != nil {
query = query.Where("tasks.plan_id = ?", *opts.PlanID)
}
if opts.Type != nil {
query = query.Where("tasks.type = ?", *opts.Type)
}
// --- JSON 字段查询 ---
if opts.AreaControllerID != nil {
// 使用 ->> 操作符查询 JSON 字段的值(作为文本)
query = query.Where("parameters->>'area_controller_id' = ?", *opts.AreaControllerID)
}
if opts.SensorType != nil {
query = query.Where("parameters->>'sensor_type' = ?", *opts.SensorType)
}
if opts.Level != nil {
query = query.Where("parameters->>'level' = ?", *opts.Level)
}
// --- 结束 ---
if opts.DeviceID != nil {
query = query.Joins("JOIN device_tasks ON device_tasks.task_id = tasks.id").Where("device_tasks.device_id = ?", *opts.DeviceID)
}
// 2. 执行计数查询
countQuery := query
if opts.DeviceID != nil {
if err := countQuery.Distinct("tasks.id").Count(&total).Error; err != nil {
return nil, 0, err
}
} else {
if err := countQuery.Count(&total).Error; err != nil {
return nil, 0, err
}
}
if total == 0 {
return []models.Task{}, 0, nil
}
// 3. 为数据查询应用排序、分页和预加载
orderBy := "tasks.id DESC"
if opts.OrderBy != "" {
orderBy = opts.OrderBy
}
err := query.Order(orderBy).
Limit(pageSize).
Offset((page - 1) * pageSize).
Preload("Devices").
Find(&results).Error
return results, total, err
}