From bf1600b3850f5a0fc4b10f6fdab72700000fa118 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sun, 16 Nov 2025 16:30:26 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=A4=E4=B8=AA=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/docs.go | 274 +++++++++++++++++- docs/swagger.json | 274 +++++++++++++++++- docs/swagger.yaml | 190 +++++++++++- internal/app/api/router.go | 2 + .../alarm/threshold_alarm_controller.go | 70 +++++ internal/app/dto/alarm_dto.go | 34 ++- .../app/service/threshold_alarm_service.go | 108 +++++++ internal/core/data_initializer.go | 2 +- internal/infra/repository/plan_repository.go | 96 ++++++ project_structure.txt | 2 +- 10 files changed, 1041 insertions(+), 11 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 8884ef9..e956e45 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -146,6 +146,126 @@ const docTemplate = `{ } }, "/api/v1/alarm/threshold/area": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据过滤条件和分页参数查询区域阈值告警列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "批量查询区域阈值告警", + "parameters": [ + { + "type": "integer", + "description": "按区域主控ID过滤", + "name": "area_controller_id", + "in": "query" + }, + { + "enum": [ + "Debug", + "Info", + "Warn", + "Error", + "DPanic", + "Panic", + "Fatal" + ], + "type": "string", + "x-enum-varnames": [ + "DebugLevel", + "InfoLevel", + "WarnLevel", + "ErrorLevel", + "DPanicLevel", + "PanicLevel", + "FatalLevel" + ], + "description": "按告警等级过滤", + "name": "level", + "in": "query" + }, + { + "type": "string", + "description": "排序字段,例如 \"id DESC\"", + "name": "order_by", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "page_size", + "in": "query" + }, + { + "enum": [ + "信号强度", + "电池电量", + "温度", + "湿度", + "重量" + ], + "type": "string", + "x-enum-comments": { + "SensorTypeBatteryLevel": "电池电量", + "SensorTypeHumidity": "湿度", + "SensorTypeSignalMetrics": "信号强度", + "SensorTypeTemperature": "温度", + "SensorTypeWeight": "重量" + }, + "x-enum-descriptions": [ + "信号强度", + "电池电量", + "温度", + "湿度", + "重量" + ], + "x-enum-varnames": [ + "SensorTypeSignalMetrics", + "SensorTypeBatteryLevel", + "SensorTypeTemperature", + "SensorTypeHumidity", + "SensorTypeWeight" + ], + "description": "按传感器类型过滤", + "name": "sensor_type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "成功获取区域阈值告警列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.ListAreaThresholdAlarmResponse" + } + } + } + ] + } + } + } + }, "post": { "security": [ { @@ -307,6 +427,126 @@ const docTemplate = `{ } }, "/api/v1/alarm/threshold/device": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据过滤条件和分页参数查询设备阈值告警列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "批量查询设备阈值告警", + "parameters": [ + { + "type": "integer", + "description": "按设备ID过滤", + "name": "device_id", + "in": "query" + }, + { + "enum": [ + "Debug", + "Info", + "Warn", + "Error", + "DPanic", + "Panic", + "Fatal" + ], + "type": "string", + "x-enum-varnames": [ + "DebugLevel", + "InfoLevel", + "WarnLevel", + "ErrorLevel", + "DPanicLevel", + "PanicLevel", + "FatalLevel" + ], + "description": "按告警等级过滤", + "name": "level", + "in": "query" + }, + { + "type": "string", + "description": "排序字段,例如 \"id DESC\"", + "name": "order_by", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "page_size", + "in": "query" + }, + { + "enum": [ + "信号强度", + "电池电量", + "温度", + "湿度", + "重量" + ], + "type": "string", + "x-enum-comments": { + "SensorTypeBatteryLevel": "电池电量", + "SensorTypeHumidity": "湿度", + "SensorTypeSignalMetrics": "信号强度", + "SensorTypeTemperature": "温度", + "SensorTypeWeight": "重量" + }, + "x-enum-descriptions": [ + "信号强度", + "电池电量", + "温度", + "湿度", + "重量" + ], + "x-enum-varnames": [ + "SensorTypeSignalMetrics", + "SensorTypeBatteryLevel", + "SensorTypeTemperature", + "SensorTypeHumidity", + "SensorTypeWeight" + ], + "description": "按传感器类型过滤", + "name": "sensor_type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "成功获取设备阈值告警列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.ListDeviceThresholdAlarmResponse" + } + } + } + ] + } + } + } + }, "post": { "security": [ { @@ -5454,15 +5694,15 @@ const docTemplate = `{ "alarm_code": { "$ref": "#/definitions/models.AlarmCode" }, - "alarm_details": { - "type": "string" - }, "alarm_summary": { "type": "string" }, "id": { "type": "integer" }, + "json_details": { + "type": "string" + }, "level": { "$ref": "#/definitions/models.SeverityLevel" }, @@ -5500,6 +5740,20 @@ const docTemplate = `{ } } }, + "dto.ListAreaThresholdAlarmResponse": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.AreaThresholdAlarmDTO" + } + }, + "pagination": { + "$ref": "#/definitions/dto.PaginationDTO" + } + } + }, "dto.ListDeviceCommandLogResponse": { "type": "object", "properties": { @@ -5514,6 +5768,20 @@ const docTemplate = `{ } } }, + "dto.ListDeviceThresholdAlarmResponse": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.DeviceThresholdAlarmDTO" + } + }, + "pagination": { + "$ref": "#/definitions/dto.PaginationDTO" + } + } + }, "dto.ListFeedUsageRecordResponse": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index aadfac4..5edc711 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -138,6 +138,126 @@ } }, "/api/v1/alarm/threshold/area": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据过滤条件和分页参数查询区域阈值告警列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "批量查询区域阈值告警", + "parameters": [ + { + "type": "integer", + "description": "按区域主控ID过滤", + "name": "area_controller_id", + "in": "query" + }, + { + "enum": [ + "Debug", + "Info", + "Warn", + "Error", + "DPanic", + "Panic", + "Fatal" + ], + "type": "string", + "x-enum-varnames": [ + "DebugLevel", + "InfoLevel", + "WarnLevel", + "ErrorLevel", + "DPanicLevel", + "PanicLevel", + "FatalLevel" + ], + "description": "按告警等级过滤", + "name": "level", + "in": "query" + }, + { + "type": "string", + "description": "排序字段,例如 \"id DESC\"", + "name": "order_by", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "page_size", + "in": "query" + }, + { + "enum": [ + "信号强度", + "电池电量", + "温度", + "湿度", + "重量" + ], + "type": "string", + "x-enum-comments": { + "SensorTypeBatteryLevel": "电池电量", + "SensorTypeHumidity": "湿度", + "SensorTypeSignalMetrics": "信号强度", + "SensorTypeTemperature": "温度", + "SensorTypeWeight": "重量" + }, + "x-enum-descriptions": [ + "信号强度", + "电池电量", + "温度", + "湿度", + "重量" + ], + "x-enum-varnames": [ + "SensorTypeSignalMetrics", + "SensorTypeBatteryLevel", + "SensorTypeTemperature", + "SensorTypeHumidity", + "SensorTypeWeight" + ], + "description": "按传感器类型过滤", + "name": "sensor_type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "成功获取区域阈值告警列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.ListAreaThresholdAlarmResponse" + } + } + } + ] + } + } + } + }, "post": { "security": [ { @@ -299,6 +419,126 @@ } }, "/api/v1/alarm/threshold/device": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据过滤条件和分页参数查询设备阈值告警列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "批量查询设备阈值告警", + "parameters": [ + { + "type": "integer", + "description": "按设备ID过滤", + "name": "device_id", + "in": "query" + }, + { + "enum": [ + "Debug", + "Info", + "Warn", + "Error", + "DPanic", + "Panic", + "Fatal" + ], + "type": "string", + "x-enum-varnames": [ + "DebugLevel", + "InfoLevel", + "WarnLevel", + "ErrorLevel", + "DPanicLevel", + "PanicLevel", + "FatalLevel" + ], + "description": "按告警等级过滤", + "name": "level", + "in": "query" + }, + { + "type": "string", + "description": "排序字段,例如 \"id DESC\"", + "name": "order_by", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "page_size", + "in": "query" + }, + { + "enum": [ + "信号强度", + "电池电量", + "温度", + "湿度", + "重量" + ], + "type": "string", + "x-enum-comments": { + "SensorTypeBatteryLevel": "电池电量", + "SensorTypeHumidity": "湿度", + "SensorTypeSignalMetrics": "信号强度", + "SensorTypeTemperature": "温度", + "SensorTypeWeight": "重量" + }, + "x-enum-descriptions": [ + "信号强度", + "电池电量", + "温度", + "湿度", + "重量" + ], + "x-enum-varnames": [ + "SensorTypeSignalMetrics", + "SensorTypeBatteryLevel", + "SensorTypeTemperature", + "SensorTypeHumidity", + "SensorTypeWeight" + ], + "description": "按传感器类型过滤", + "name": "sensor_type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "成功获取设备阈值告警列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.ListDeviceThresholdAlarmResponse" + } + } + } + ] + } + } + } + }, "post": { "security": [ { @@ -5446,15 +5686,15 @@ "alarm_code": { "$ref": "#/definitions/models.AlarmCode" }, - "alarm_details": { - "type": "string" - }, "alarm_summary": { "type": "string" }, "id": { "type": "integer" }, + "json_details": { + "type": "string" + }, "level": { "$ref": "#/definitions/models.SeverityLevel" }, @@ -5492,6 +5732,20 @@ } } }, + "dto.ListAreaThresholdAlarmResponse": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.AreaThresholdAlarmDTO" + } + }, + "pagination": { + "$ref": "#/definitions/dto.PaginationDTO" + } + } + }, "dto.ListDeviceCommandLogResponse": { "type": "object", "properties": { @@ -5506,6 +5760,20 @@ } } }, + "dto.ListDeviceThresholdAlarmResponse": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.DeviceThresholdAlarmDTO" + } + }, + "pagination": { + "$ref": "#/definitions/dto.PaginationDTO" + } + } + }, "dto.ListFeedUsageRecordResponse": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 10e5f77..74e893d 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -464,12 +464,12 @@ definitions: properties: alarm_code: $ref: '#/definitions/models.AlarmCode' - alarm_details: - type: string alarm_summary: type: string id: type: integer + json_details: + type: string level: $ref: '#/definitions/models.SeverityLevel' resolve_method: @@ -494,6 +494,15 @@ definitions: pagination: $ref: '#/definitions/dto.PaginationDTO' type: object + dto.ListAreaThresholdAlarmResponse: + properties: + list: + items: + $ref: '#/definitions/dto.AreaThresholdAlarmDTO' + type: array + pagination: + $ref: '#/definitions/dto.PaginationDTO' + type: object dto.ListDeviceCommandLogResponse: properties: list: @@ -503,6 +512,15 @@ definitions: pagination: $ref: '#/definitions/dto.PaginationDTO' type: object + dto.ListDeviceThresholdAlarmResponse: + properties: + list: + items: + $ref: '#/definitions/dto.DeviceThresholdAlarmDTO' + type: array + pagination: + $ref: '#/definitions/dto.PaginationDTO' + type: object dto.ListFeedUsageRecordResponse: properties: list: @@ -2401,6 +2419,90 @@ paths: tags: - 告警管理 /api/v1/alarm/threshold/area: + get: + consumes: + - application/json + description: 根据过滤条件和分页参数查询区域阈值告警列表 + parameters: + - description: 按区域主控ID过滤 + in: query + name: area_controller_id + type: integer + - description: 按告警等级过滤 + enum: + - Debug + - Info + - Warn + - Error + - DPanic + - Panic + - Fatal + in: query + name: level + type: string + x-enum-varnames: + - DebugLevel + - InfoLevel + - WarnLevel + - ErrorLevel + - DPanicLevel + - PanicLevel + - FatalLevel + - description: 排序字段,例如 "id DESC" + in: query + name: order_by + type: string + - in: query + name: page + type: integer + - in: query + name: page_size + type: integer + - description: 按传感器类型过滤 + enum: + - 信号强度 + - 电池电量 + - 温度 + - 湿度 + - 重量 + in: query + name: sensor_type + type: string + x-enum-comments: + SensorTypeBatteryLevel: 电池电量 + SensorTypeHumidity: 湿度 + SensorTypeSignalMetrics: 信号强度 + SensorTypeTemperature: 温度 + SensorTypeWeight: 重量 + x-enum-descriptions: + - 信号强度 + - 电池电量 + - 温度 + - 湿度 + - 重量 + x-enum-varnames: + - SensorTypeSignalMetrics + - SensorTypeBatteryLevel + - SensorTypeTemperature + - SensorTypeHumidity + - SensorTypeWeight + produces: + - application/json + responses: + "200": + description: 成功获取区域阈值告警列表 + schema: + allOf: + - $ref: '#/definitions/controller.Response' + - properties: + data: + $ref: '#/definitions/dto.ListAreaThresholdAlarmResponse' + type: object + security: + - BearerAuth: [] + summary: 批量查询区域阈值告警 + tags: + - 告警管理 post: consumes: - application/json @@ -2499,6 +2601,90 @@ paths: tags: - 告警管理 /api/v1/alarm/threshold/device: + get: + consumes: + - application/json + description: 根据过滤条件和分页参数查询设备阈值告警列表 + parameters: + - description: 按设备ID过滤 + in: query + name: device_id + type: integer + - description: 按告警等级过滤 + enum: + - Debug + - Info + - Warn + - Error + - DPanic + - Panic + - Fatal + in: query + name: level + type: string + x-enum-varnames: + - DebugLevel + - InfoLevel + - WarnLevel + - ErrorLevel + - DPanicLevel + - PanicLevel + - FatalLevel + - description: 排序字段,例如 "id DESC" + in: query + name: order_by + type: string + - in: query + name: page + type: integer + - in: query + name: page_size + type: integer + - description: 按传感器类型过滤 + enum: + - 信号强度 + - 电池电量 + - 温度 + - 湿度 + - 重量 + in: query + name: sensor_type + type: string + x-enum-comments: + SensorTypeBatteryLevel: 电池电量 + SensorTypeHumidity: 湿度 + SensorTypeSignalMetrics: 信号强度 + SensorTypeTemperature: 温度 + SensorTypeWeight: 重量 + x-enum-descriptions: + - 信号强度 + - 电池电量 + - 温度 + - 湿度 + - 重量 + x-enum-varnames: + - SensorTypeSignalMetrics + - SensorTypeBatteryLevel + - SensorTypeTemperature + - SensorTypeHumidity + - SensorTypeWeight + produces: + - application/json + responses: + "200": + description: 成功获取设备阈值告警列表 + schema: + allOf: + - $ref: '#/definitions/controller.Response' + - properties: + data: + $ref: '#/definitions/dto.ListDeviceThresholdAlarmResponse' + type: object + security: + - BearerAuth: [] + summary: 批量查询设备阈值告警 + tags: + - 告警管理 post: consumes: - application/json diff --git a/internal/app/api/router.go b/internal/app/api/router.go index 9daba50..500f495 100644 --- a/internal/app/api/router.go +++ b/internal/app/api/router.go @@ -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) diff --git a/internal/app/controller/alarm/threshold_alarm_controller.go b/internal/app/controller/alarm/threshold_alarm_controller.go index 6e69afd..4a6d666 100644 --- a/internal/app/controller/alarm/threshold_alarm_controller.go +++ b/internal/app/controller/alarm/threshold_alarm_controller.go @@ -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 为单个设备创建一条新的阈值告警规则 diff --git a/internal/app/dto/alarm_dto.go b/internal/app/dto/alarm_dto.go index b83527a..4546681 100644 --- a/internal/app/dto/alarm_dto.go +++ b/internal/app/dto/alarm_dto.go @@ -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"` +} diff --git a/internal/app/service/threshold_alarm_service.go b/internal/app/service/threshold_alarm_service.go index 834a003..3bffc42 100644 --- a/internal/app/service/threshold_alarm_service.go +++ b/internal/app/service/threshold_alarm_service.go @@ -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") diff --git a/internal/core/data_initializer.go b/internal/core/data_initializer.go index cfe4894..044b2ad 100644 --- a/internal/core/data_initializer.go +++ b/internal/core/data_initializer.go @@ -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, } diff --git a/internal/infra/repository/plan_repository.go b/internal/infra/repository/plan_repository.go index 9f01f57..acf7720 100644 --- a/internal/infra/repository/plan_repository.go +++ b/internal/infra/repository/plan_repository.go @@ -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 +} diff --git a/project_structure.txt b/project_structure.txt index a01e2b7..c01eab0 100644 --- a/project_structure.txt +++ b/project_structure.txt @@ -32,7 +32,7 @@ design/archive/2025-11-05-provide-logger-with-mothed/task-service.md design/archive/2025-11-05-provide-logger-with-mothed/task-webhook.md design/archive/2025-11-06-health-check-routing/index.md design/archive/2025-11-06-system-plan-continuously-triggered/index.md -design/exceeding-threshold-alarm/index.md +design/archive/2025-11-10-exceeding-threshold-alarm/index.md docs/docs.go docs/swagger.json docs/swagger.yaml