增加两个批量查询接口
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 为单个设备创建一条新的阈值告警规则
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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(¶ms); 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(¶ms); 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")
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user