From 6b6fd411c5ab1c7f887e6b15777a7a90c39be157 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 18:54:02 +0800 Subject: [PATCH 01/35] =?UTF-8?q?=E5=88=B6=E5=AE=9A=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E8=AE=A1=E5=88=92=E5=92=8C=E4=BB=BB=E5=8A=A1=E9=9D=92=E5=B2=9B?= =?UTF-8?q?=E9=82=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 105 ++++++++++++++++++ design/exceeding-threshold-alarm/task_list.md | 55 +++++++++ project_structure.txt | 44 ++++---- 3 files changed, 184 insertions(+), 20 deletions(-) create mode 100644 design/exceeding-threshold-alarm/index.md create mode 100644 design/exceeding-threshold-alarm/task_list.md diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md new file mode 100644 index 0000000..a49e410 --- /dev/null +++ b/design/exceeding-threshold-alarm/index.md @@ -0,0 +1,105 @@ +# 需求 + +实现采集数据超过阈值报警 + +## issue + +[实现采集数据超过阈值报警](http://git.huangwc.com/pig/pig-farm-controller/issues/62) + +# 方案 + +1. **架构核心**: 新增一个 **告警领域服务**,作为告警系统的核心大脑,负责告警事件的生命周期管理。 +2. **任务分离**: + * 新增 **阈值告警任务** (分为区域主控和普通设备两种),仅负责检测数据并将结果报告给领域服务。 + * 新增 **告警通知发送任务**,作为一个独立的定时任务,负责调用领域服务,获取并发送所有待处理的通知。 +3. **计划调度**: + * 修改现有 "定时全量数据采集" 计划, 更名为 "周期性系统健康检查",并将 **阈值告警任务** 加入其中。 + * 新增一个独立的 "告警通知发送" 计划,用于定时执行 **告警通知发送任务**。 +4. **数据与接口**: + * 新增独立的告警记录表(建议采用“活跃告警表 + 历史告警超表”的模式)。 + * 新增相应的告警配置管理接口。 + +## 方案细节 + +### 架构与职责划分 + +1. **告警领域服务 (`internal/domain/alarm/`) - 管理器** + * **职责**: 作为告警系统的核心大脑,负责处理告警事件的完整生命周期。 + * **功能**: + * 接收来自检测任务的状态报告。 + * 根据报告和现有告警记录,决策是创建新告警、更新为已解决、还是忽略。 + * 管理“手动忽略” (`Ignored`) 状态和忽略到期时间 (`ignored_until`)。 + * 实现可配置的“重复通知”策略(`re_notification_interval`),决定何时对持续存在的告警再次发送通知。 + * 提供接口供 `告警通知发送任务` 调用,以获取所有待处理的通知。 + +2. **阈值告警任务 (`internal/domain/task/`) - 检测器** + * **职责**: 职责纯粹,仅负责执行检测并将结果报告给告警领域服务。 + * **逻辑**: 获取传感器数据 -> 与阈值比对 -> 调用 `告警领域服务.ReportStatus()`。 + * **无状态**: 任务本身不关心告警是否已存在或被忽略。 + +3. **告警通知发送任务 (`internal/domain/task/`) - 发送器** + * **职责**: 作为一个独立的定时任务,解耦通知发送与告警检测。 + * **逻辑**: 调用 `告警领域服务.GetAndProcessPendingNotifications()` -> 获取待发送通知列表 -> 调用 `通知领域服务` 逐一发送。 + * **优势**: 统一管理定时任务,实现资源控制,提高系统稳定性和可扩展性。 + +### 计划与任务调度 + +1. **"周期性系统健康检查" 计划** + * **任务顺序**: 此计划中的任务必须严格按顺序执行,确保依赖关系正确。 + 1. **全量数据采集任务 (ExecutionOrder: 1)**: 必须是第一个执行的任务,为后续的告警检测提供最新的数据基础。 + 2. **阈值告警任务 (ExecutionOrder: 2, 3...)**: 在全量采集完成后执行。 + +2. **"告警通知发送" 计划** + * **内容**: 包含一个独立的 `告警通知发送任务`。 + * **调度**: 可配置独立的执行频率(如每分钟一次),与健康检查计划解耦。 + +### 阈值告警任务 (具体定义) + +1. **任务类型**: 增加两个阈值告警任务类型,分别对应 **区域主控** 和 **普通设备** 告警。 +2. **参数结构**: + * **通用参数**: 任务参数将包含 `Value` (阈值) 和 `Operator` (操作符,如 `>` 或 `<`) 字段。通过组合两个任务,可实现范围告警。 + * **普通设备任务**: 配置包含 `DeviceID`。 + * **区域主控任务**: 配置包含 `AreaControllerID`, `SensorType`, 以及一个 `ExcludeDeviceIDs` (需要排除的设备ID列表)。 + +### 告警事件与生命周期 + +1. **告警事件定义**: + * 区分 **告警规则** (配置的策略) 和 **告警事件** (规则被具体设备触发的实例)。 + * 区域主控下不同设备触发的告警,即使基于同一规则,也应视为独立的 **告警事件**,以便于精确追溯和独立操作。 + +2. **生命周期管理**: + * **自动闭环**: 当阈值告警任务报告数据恢复正常时,告警领域服务会自动将对应的 `Active` 告警事件状态更新为 `Resolved`。 + * **手动忽略 (Snooze)**: 用户可通过接口将告警事件状态置为 `Ignored` 并设置 `ignored_until`。在此期间,即使数据持续异常,也不会发送通知。忽略到期后若问题仍存在,告警将重新变为 `Active` 并发送通知。 + * **持续告警与重复通知**: 对持续未解决的 `Active` 告警,只保留一条记录。告警领域服务会根据 `re_notification_interval` 配置的重复通知间隔,决定是否需要再次发送通知。 + +### 数据库设计考量 + +1. **冷热分离方案 (推荐)**: + * **`active_alarms` (活跃告警表)**: + * **类型**: 标准 PostgreSQL 表。 + * **内容**: 只存放 `Active` 和 `Ignored` 状态的告警。 + * **优势**: 保证高频读写的性能,避免在被压缩的数据上执行更新操作。 + * **`historical_alarms` (历史告警表)**: + * **类型**: 改造为 **TimescaleDB 超表**。 + * **内容**: 存放 `Resolved` 状态的告警。当告警在 `active_alarms` 中被解决后,记录将移至此表。 + * **优势**: 适合存储海量历史数据,便于分析、统计,并可利用 TimescaleDB 的压缩和数据生命周期管理功能。 + +2. **表结构字段**: + * `status`: 枚举类型,包含 `Active`, `Resolved`, `Ignored`。 + * `ignored_until`: `timestamp` 类型,记录忽略截止时间。 + * `last_notified_at`: `timestamp` 类型,记录上次发送通知的时间。 + +### 阈值告警服务 (应用服务层) + +1. **服务职责**: 提供一个应用服务,负责管理阈值告警 **配置** 的增删改查。 +2. **排除列表计算与联动**: + * **删除独立规则后归属**: 当一个普通设备的独立告警规则被删除时,它将自动从其所属区域主控的 `ExcludeDeviceIDs` 列表中移除,从而回归到区域统一告警策略的管理之下。 + * **设备生命周期管理**: 在对设备进行修改(特别是更换区域主控)或删除时,以及在删除区域主控时,必须同步更新相关的 `ExcludeDeviceIDs` 列表,以保证数据一致性。 + * **实现**: `DeviceService` 中负责处理设备更新和删除的方法,以及处理区域主控删除的方法,需要调用阈值告警服务提供的刷新接口。 + +### 阈值告警控制器 + +1. **独立接口**: 提供两组独立的 Web 接口,分别用于管理区域主控和普通设备的阈值告警配置。 + * 区域主控告警配置接口: `/api/v1/alarm/region-config` + * 普通设备告警配置接口: `/api/v1/alarm/device-config` +2. **接口职责**: 接口负责接收前端请求,调用应用服务层的阈值告警服务来完成实际的业务逻辑。 diff --git a/design/exceeding-threshold-alarm/task_list.md b/design/exceeding-threshold-alarm/task_list.md new file mode 100644 index 0000000..01dc56d --- /dev/null +++ b/design/exceeding-threshold-alarm/task_list.md @@ -0,0 +1,55 @@ +# 阈值告警功能 - 任务清单 + +本清单按照从底层到上层的依赖关系排序,建议按此顺序进行开发。 + +## 1. 基础设施层 (`internal/infra`) + +- [ ] **数据库模型 (`models`)**: + - [ ] 在 `internal/infra/models/` 下创建 `alarm.go` 文件,定义 `ActiveAlarm` 和 `HistoricalAlarm` 两个结构体,包含 `status`, `ignored_until`, `last_notified_at` 等字段。 + - [ ] 在 `internal/infra/models/schedule.go` 中,为 `TaskType` 新增三个常量:`TaskTypeAreaControllerThresholdAlarm`, `TaskTypeDeviceThresholdAlarm`, `TaskTypeAlarmNotificationSender`。 + +- [ ] **数据库仓库 (`repository`)**: + - [ ] 在 `internal/infra/repository/` 下创建 `alarm_repository.go`,实现对 `active_alarms` 和 `historical_alarms` 表的增删改查操作,并提供 `MoveToHistorical` 等方法。 + - [ ] 在 `internal/infra/repository/device_repository.go` 中,为 `DeviceRepository` 接口及其实现增加 `FindByAreaControllerID` 方法。 + +- [ ] **配置 (`config`)**: + - [ ] 在 `config.yml` 文件中,新增告警相关的配置项,如 `re_notification_interval` (重复通知间隔)。 + - [ ] 在 `internal/infra/config/config.go` 中,添加对应的字段来加载该配置。 + +## 2. 领域层 (`internal/domain`) + +- [ ] **告警领域服务 (`alarm`)**: + - [ ] 创建 `internal/domain/alarm/` 目录。 + - [ ] 在该目录下创建 `alarm_service.go`,定义 `AlarmService` 接口及其实现。 + - [ ] 实现核心业务逻辑:`ReportStatus` (处理告警创建/解决) 和 `GetAndProcessPendingNotifications` (获取待发送通知)。该服务将注入 `AlarmRepository` 和 `NotifyService`。 + +- [ ] **任务实现 (`task`)**: + - [ ] 创建 `area_controller_threshold_alarm_task.go`,实现 `plan.Task` 接口,其 `Execute` 方法调用 `alarmService.ReportStatus`。 + - [ ] 创建 `device_threshold_alarm_task.go`,实现逻辑同上。 + * [ ] 创建 `alarm_notification_sender_task.go`,实现 `plan.Task` 接口,其 `Execute` 方法调用 `alarmService.GetAndProcessPendingNotifications`。 + +## 3. 应用层 (`internal/app`) + +- [ ] **告警配置服务 (`service`)**: + * [ ] 创建 `alarm_config_service.go`,定义 `AlarmConfigService`,负责告警 **配置** 的增删改查。 + * [ ] 实现 `ExcludeDeviceIDs` 的计算和更新逻辑。 + * [ ] 注入 `DeviceRepository` 和 `AreaControllerRepository`。 + +- [ ] **服务集成**: + - [ ] 在 `plan_service.go` (或任务工厂) 中,注册上述三种新的任务类型。 + - [ ] 修改 `device_service.go`,在处理设备更新(更换区域)和删除的方法中,调用 `AlarmConfigService` 的接口来刷新 `ExcludeDeviceIDs`。 + +- [ ] **告警配置控制器 (`controller`)**: + - [ ] 创建 `internal/app/controller/alarm/` 目录及 `alarm_controller.go` 文件。 + - [ ] 实现 `Create/Update/Delete/Get` 告警配置的 HTTP 接口,调用 `AlarmConfigService`。 + +- [ ] **API 路由 (`api`)**: + - [ ] 在 `internal/app/api/router.go` 中,注册 `/api/v1/alarm/region-config` 和 `/api/v1/alarm/device-config` 相关路由,并绑定到 `AlarmController` 的方法。 + +## 4. 核心初始化层 (`internal/core`) + +- [ ] **系统计划初始化 (`data_initializer.go`)**: + - [ ] 修改 `getPredefinedSystemPlans` 函数。 + - [ ] 将 "定时全量数据采集" 计划重命名为 "周期性系统健康检查"。 + - [ ] 在该计划中,于 "全量采集" 任务之后,添加 `TaskTypeAreaControllerThresholdAlarm` 和 `TaskTypeDeviceThresholdAlarm` 任务。 + - [ ] 新增一个 "告警通知发送" 计划,并为其添加 `TaskTypeAlarmNotificationSender` 任务。 diff --git a/project_structure.txt b/project_structure.txt index d7b9dd9..479ca80 100644 --- a/project_structure.txt +++ b/project_structure.txt @@ -10,26 +10,29 @@ RELAY_API.md TODO-List.txt config.example.yml config.yml -design/archive/2025-11-3-verification-before-device-deletion/add_get_device_id_configs_to_task.md -design/archive/2025-11-3-verification-before-device-deletion/check_before_device_deletion.md -design/archive/2025-11-3-verification-before-device-deletion/device_task_association_maintenance.md -design/archive/2025-11-3-verification-before-device-deletion/device_task_many_to_many_design.md -design/archive/2025-11-3-verification-before-device-deletion/index.md -design/archive/2025-11-3-verification-before-device-deletion/plan_service_refactor.md -design/archive/2025-11-3-verification-before-device-deletion/plan_service_refactor_to_domain.md -design/archive/2025-11-3-verification-before-device-deletion/refactor_deletion_check.md -design/archive/2025-11-3-verification-before-device-deletion/refactor_id_conversion.md -design/provide-logger-with-mothed/implementation.md -design/provide-logger-with-mothed/index.md -design/provide-logger-with-mothed/task-api.md -design/provide-logger-with-mothed/task-controller.md -design/provide-logger-with-mothed/task-domain.md -design/provide-logger-with-mothed/task-infra.md -design/provide-logger-with-mothed/task-list.md -design/provide-logger-with-mothed/task-middleware.md -design/provide-logger-with-mothed/task-repository.md -design/provide-logger-with-mothed/task-service.md -design/provide-logger-with-mothed/task-webhook.md +design/archive/2025-11-03-verification-before-device-deletion/add_get_device_id_configs_to_task.md +design/archive/2025-11-03-verification-before-device-deletion/check_before_device_deletion.md +design/archive/2025-11-03-verification-before-device-deletion/device_task_association_maintenance.md +design/archive/2025-11-03-verification-before-device-deletion/device_task_many_to_many_design.md +design/archive/2025-11-03-verification-before-device-deletion/index.md +design/archive/2025-11-03-verification-before-device-deletion/plan_service_refactor.md +design/archive/2025-11-03-verification-before-device-deletion/plan_service_refactor_to_domain.md +design/archive/2025-11-03-verification-before-device-deletion/refactor_deletion_check.md +design/archive/2025-11-03-verification-before-device-deletion/refactor_id_conversion.md +design/archive/2025-11-05-provide-logger-with-mothed/implementation.md +design/archive/2025-11-05-provide-logger-with-mothed/index.md +design/archive/2025-11-05-provide-logger-with-mothed/task-api.md +design/archive/2025-11-05-provide-logger-with-mothed/task-controller.md +design/archive/2025-11-05-provide-logger-with-mothed/task-domain.md +design/archive/2025-11-05-provide-logger-with-mothed/task-infra.md +design/archive/2025-11-05-provide-logger-with-mothed/task-list.md +design/archive/2025-11-05-provide-logger-with-mothed/task-middleware.md +design/archive/2025-11-05-provide-logger-with-mothed/task-repository.md +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 docs/docs.go docs/swagger.json docs/swagger.yaml @@ -39,6 +42,7 @@ internal/app/api/api.go internal/app/api/router.go internal/app/controller/auth_utils.go internal/app/controller/device/device_controller.go +internal/app/controller/health/health_controller.go internal/app/controller/management/controller_helpers.go internal/app/controller/management/pig_batch_controller.go internal/app/controller/management/pig_batch_health_controller.go From 42317e4ed24ce66a2dfaa57a7e1a6662d985b73e Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 20:21:57 +0800 Subject: [PATCH 02/35] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 154 ++++++++++-------- design/exceeding-threshold-alarm/task_list.md | 55 ------- 2 files changed, 86 insertions(+), 123 deletions(-) delete mode 100644 design/exceeding-threshold-alarm/task_list.md diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index a49e410..2792654 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -8,98 +8,116 @@ # 方案 -1. **架构核心**: 新增一个 **告警领域服务**,作为告警系统的核心大脑,负责告警事件的生命周期管理。 -2. **任务分离**: - * 新增 **阈值告警任务** (分为区域主控和普通设备两种),仅负责检测数据并将结果报告给领域服务。 - * 新增 **告警通知发送任务**,作为一个独立的定时任务,负责调用领域服务,获取并发送所有待处理的通知。 -3. **计划调度**: - * 修改现有 "定时全量数据采集" 计划, 更名为 "周期性系统健康检查",并将 **阈值告警任务** 加入其中。 - * 新增一个独立的 "告警通知发送" 计划,用于定时执行 **告警通知发送任务**。 -4. **数据与接口**: - * 新增独立的告警记录表(建议采用“活跃告警表 + 历史告警超表”的模式)。 - * 新增相应的告警配置管理接口。 +1. **架构核心**: 新增一个 **告警领域服务**,作为告警系统的核心大脑,负责告警事件的生命周期管理。 +2. **任务分离**: + * 新增 **阈值告警任务** (分为区域主控和普通设备两种),仅负责检测数据并将结果报告给领域服务。 + * 新增 **告警通知发送任务**,作为一个独立的、系统预定义的定时任务,负责调用领域服务,获取并发送所有待处理的通知。 +3. **计划调度**: + * 修改现有 "定时全量数据采集" 计划, 更名为 "周期性系统健康检查"。此计划包含固定的 **全量采集任务** 和由用户动态配置的 + **阈值告警任务**。 + * 新增一个独立的 "告警通知发送" 计划,用于定时执行固定的 **告警通知发送任务**。 +4. **数据与接口**: + * 新增独立的告警记录表(建议采用“活跃告警表 + 历史告警超表”的模式)。 + * 新增相应的告警配置管理接口。 ## 方案细节 ### 架构与职责划分 -1. **告警领域服务 (`internal/domain/alarm/`) - 管理器** - * **职责**: 作为告警系统的核心大脑,负责处理告警事件的完整生命周期。 - * **功能**: - * 接收来自检测任务的状态报告。 - * 根据报告和现有告警记录,决策是创建新告警、更新为已解决、还是忽略。 - * 管理“手动忽略” (`Ignored`) 状态和忽略到期时间 (`ignored_until`)。 - * 实现可配置的“重复通知”策略(`re_notification_interval`),决定何时对持续存在的告警再次发送通知。 - * 提供接口供 `告警通知发送任务` 调用,以获取所有待处理的通知。 +1. **告警领域服务 (`internal/domain/alarm/`) - 管理器** + * **职责**: 作为告警系统的核心大脑,负责处理告警事件的完整生命周期。 + * **功能**: + * 接收来自检测任务的状态报告(包含设备ID、传感器类型、当前是否异常等信息)。 + * 根据报告和数据库中的告警记录,决策是创建新告警、更新为已解决、还是因被忽略而跳过。 + * 管理“手动忽略” (`Ignored`) 状态和忽略到期时间 (`ignored_until`)。 + * 实现可配置的“重复通知”策略(`re_notification_interval`),决定何时对持续存在的告警再次发送通知。 + * 提供接口供 `告警通知发送任务` 调用,以获取所有待处理的通知。 -2. **阈值告警任务 (`internal/domain/task/`) - 检测器** - * **职责**: 职责纯粹,仅负责执行检测并将结果报告给告警领域服务。 - * **逻辑**: 获取传感器数据 -> 与阈值比对 -> 调用 `告警领域服务.ReportStatus()`。 - * **无状态**: 任务本身不关心告警是否已存在或被忽略。 +2. **阈值告警任务 (`internal/domain/task/`) - 检测器** + * **职责**: 职责纯粹,仅负责执行检测并将结果报告给告警领域服务。 + * **逻辑**: 从传感器数据表读取最新数据 -> 与自身配置的阈值进行比对 -> 无论结果如何,都调用 `告警领域服务.ReportStatus()` + 报告当前状态(正常或异常)。 + * **无状态**: 任务本身不关心告警是否已存在或被忽略,它只负责“状态同步”。 -3. **告警通知发送任务 (`internal/domain/task/`) - 发送器** - * **职责**: 作为一个独立的定时任务,解耦通知发送与告警检测。 - * **逻辑**: 调用 `告警领域服务.GetAndProcessPendingNotifications()` -> 获取待发送通知列表 -> 调用 `通知领域服务` 逐一发送。 - * **优势**: 统一管理定时任务,实现资源控制,提高系统稳定性和可扩展性。 +3. **告警通知发送任务 (`internal/domain/task/`) - 发送器** + * **职责**: 作为一个独立的定时任务,解耦通知发送与告警检测。 + * **逻辑**: 调用 `告警领域服务.GetAndProcessPendingNotifications()` -> 获取待发送通知列表 -> 调用 `通知领域服务` + 逐一发送。 + * **优势**: 统一管理定时任务,实现资源控制,提高系统稳定性和可扩展性。 ### 计划与任务调度 -1. **"周期性系统健康检查" 计划** - * **任务顺序**: 此计划中的任务必须严格按顺序执行,确保依赖关系正确。 - 1. **全量数据采集任务 (ExecutionOrder: 1)**: 必须是第一个执行的任务,为后续的告警检测提供最新的数据基础。 - 2. **阈值告警任务 (ExecutionOrder: 2, 3...)**: 在全量采集完成后执行。 +1. **"周期性系统健康检查" 计划** + * **任务构成**: + * **全量数据采集任务 (ExecutionOrder: 1)**: 系统预定义,必须是第一个执行的任务,为后续的告警检测提供最新的数据基础。 + * **阈值告警任务 (ExecutionOrder: 2, 3...)**: 由用户通过API动态配置和管理,`告警配置服务` 负责将其增删改到此计划中。 -2. **"告警通知发送" 计划** - * **内容**: 包含一个独立的 `告警通知发送任务`。 - * **调度**: 可配置独立的执行频率(如每分钟一次),与健康检查计划解耦。 +2. **"告警通知发送" 计划** + * **任务构成**: 包含一个系统预定义的 `告警通知发送任务`。 + * **调度**: 可配置独立的执行频率(如每分钟一次),与健康检查计划解耦。 -### 阈值告警任务 (具体定义) +3. **系统初始化 (`data_initializer.go`)** + * **职责**: 只负责创建和维护系统预定义的、固定的计划和任务。 + * **操作**: + * 确保 "周期性系统健康检查" 计划存在,并包含 `全量数据采集任务`。 + * 确保 "告警通知发送" 计划存在,并包含 `告警通知发送任务`。 + * **注意**: 初始化逻辑 **不会** 也 **不应该** 触及用户动态配置的阈值告警任务。 -1. **任务类型**: 增加两个阈值告警任务类型,分别对应 **区域主控** 和 **普通设备** 告警。 -2. **参数结构**: - * **通用参数**: 任务参数将包含 `Value` (阈值) 和 `Operator` (操作符,如 `>` 或 `<`) 字段。通过组合两个任务,可实现范围告警。 - * **普通设备任务**: 配置包含 `DeviceID`。 - * **区域主控任务**: 配置包含 `AreaControllerID`, `SensorType`, 以及一个 `ExcludeDeviceIDs` (需要排除的设备ID列表)。 +### 阈值告警任务 (用户可配置的任务类型) + +1. **任务类型**: 提供两种可供用户配置的阈值告警任务类型,分别对应 **区域主控** 和 **普通设备** 告警。 +2. **参数结构**: + * **通用参数**: 任务参数将包含 `Value` (阈值) 和 `Operator` (操作符,如 `>` 或 `<`) 字段。 + * **普通设备任务**: 配置包含 `DeviceID`。 + * **区域主控任务**: 配置包含 `AreaControllerID`, `SensorType`, 以及一个 `ExcludeDeviceIDs` (需要排除的设备ID列表)。 ### 告警事件与生命周期 -1. **告警事件定义**: - * 区分 **告警规则** (配置的策略) 和 **告警事件** (规则被具体设备触发的实例)。 - * 区域主控下不同设备触发的告警,即使基于同一规则,也应视为独立的 **告警事件**,以便于精确追溯和独立操作。 +1. **告警事件定义**: + * 区分 **告警规则** (配置的策略) 和 **告警事件** (规则被具体设备触发的实例)。 + * 区域主控下不同设备触发的告警,即使基于同一规则,也应视为独立的 **告警事件**,以便于精确追溯和独立操作。 -2. **生命周期管理**: - * **自动闭环**: 当阈值告警任务报告数据恢复正常时,告警领域服务会自动将对应的 `Active` 告警事件状态更新为 `Resolved`。 - * **手动忽略 (Snooze)**: 用户可通过接口将告警事件状态置为 `Ignored` 并设置 `ignored_until`。在此期间,即使数据持续异常,也不会发送通知。忽略到期后若问题仍存在,告警将重新变为 `Active` 并发送通知。 - * **持续告警与重复通知**: 对持续未解决的 `Active` 告警,只保留一条记录。告警领域服务会根据 `re_notification_interval` 配置的重复通知间隔,决定是否需要再次发送通知。 +2. **生命周期管理**: + * **自动闭环**: 当阈值告警任务报告数据恢复正常时,告警领域服务会自动将对应的 `Active` 告警事件状态更新为 `Resolved`。 + * **手动忽略 (Snooze)**: 用户可通过接口将告警事件状态置为 `Ignored` 并设置 `ignored_until` + 。在此期间,即使数据持续异常,也不会发送通知。忽略到期后若问题仍存在,告警将重新变为 `Active` 并发送通知。 + * **持续告警与重复通知**: 对持续未解决的 `Active` 告警,只保留一条记录。告警领域服务会根据 `re_notification_interval` + 配置的重复通知间隔,决定是否需要再次发送通知。 ### 数据库设计考量 -1. **冷热分离方案 (推荐)**: - * **`active_alarms` (活跃告警表)**: - * **类型**: 标准 PostgreSQL 表。 - * **内容**: 只存放 `Active` 和 `Ignored` 状态的告警。 - * **优势**: 保证高频读写的性能,避免在被压缩的数据上执行更新操作。 - * **`historical_alarms` (历史告警表)**: - * **类型**: 改造为 **TimescaleDB 超表**。 - * **内容**: 存放 `Resolved` 状态的告警。当告警在 `active_alarms` 中被解决后,记录将移至此表。 - * **优势**: 适合存储海量历史数据,便于分析、统计,并可利用 TimescaleDB 的压缩和数据生命周期管理功能。 +1. **冷热分离方案 (推荐)**: + * **`active_alarms` (活跃告警表)**: + * **类型**: 标准 PostgreSQL 表。 + * **内容**: 只存放 `Active` 和 `Ignored` 状态的告警。 + * **优势**: 保证高频读写的性能,避免在被压缩的数据上执行更新操作。 + * **`historical_alarms` (历史告警表)**: + * **类型**: 改造为 **TimescaleDB 超表**。 + * **内容**: 存放 `Resolved` 状态的告警。当告警在 `active_alarms` 中被解决后,记录将移至此表。 + * **优势**: 适合存储海量历史数据,便于分析、统计,并可利用 TimescaleDB 的压缩和数据生命周期管理功能。 -2. **表结构字段**: - * `status`: 枚举类型,包含 `Active`, `Resolved`, `Ignored`。 - * `ignored_until`: `timestamp` 类型,记录忽略截止时间。 - * `last_notified_at`: `timestamp` 类型,记录上次发送通知的时间。 +2. **表结构字段**: + * `status`: 枚举类型,包含 `Active`, `Resolved`, `Ignored`。 + * `ignored_until`: `timestamp` 类型,记录忽略截止时间。 + * `last_notified_at`: `timestamp` 类型,记录上次发送通知的时间。 ### 阈值告警服务 (应用服务层) -1. **服务职责**: 提供一个应用服务,负责管理阈值告警 **配置** 的增删改查。 -2. **排除列表计算与联动**: - * **删除独立规则后归属**: 当一个普通设备的独立告警规则被删除时,它将自动从其所属区域主控的 `ExcludeDeviceIDs` 列表中移除,从而回归到区域统一告警策略的管理之下。 - * **设备生命周期管理**: 在对设备进行修改(特别是更换区域主控)或删除时,以及在删除区域主控时,必须同步更新相关的 `ExcludeDeviceIDs` 列表,以保证数据一致性。 - * **实现**: `DeviceService` 中负责处理设备更新和删除的方法,以及处理区域主控删除的方法,需要调用阈值告警服务提供的刷新接口。 +1. **服务职责**: + * 负责管理阈值告警 **配置** 的增删改查。 + * 负责将用户创建的阈值告警任务动态更新到 "周期性系统健康检查" 计划中。 + * **配置引用检查**: 提供自检方法,用于在删除设备或设备模板前,检查它们是否被任何告警配置所引用,以防止产生悬空引用。 + +2. **排除列表计算与联动**: + * **删除独立规则后归属**: 当一个普通设备的独立告警规则被删除时,它将自动从其所属区域主控的 `ExcludeDeviceIDs` + 列表中移除,从而回归到区域统一告警策略的管理之下。 + * **设备生命周期管理**: 在对设备进行修改(特别是更换区域主控)或删除时,以及在删除区域主控时,必须同步更新相关的 + `ExcludeDeviceIDs` 列表,以保证数据一致性。 + * **实现**: `DeviceService` 中负责处理设备更新和删除的方法,需要调用本服务提供的“配置引用检查”和刷新接口。 ### 阈值告警控制器 -1. **独立接口**: 提供两组独立的 Web 接口,分别用于管理区域主控和普通设备的阈值告警配置。 - * 区域主控告警配置接口: `/api/v1/alarm/region-config` - * 普通设备告警配置接口: `/api/v1/alarm/device-config` -2. **接口职责**: 接口负责接收前端请求,调用应用服务层的阈值告警服务来完成实际的业务逻辑。 +1. **独立接口**: 提供两组独立的 Web 接口,分别用于管理区域主控和普通设备的阈值告警配置。 + * 区域主控告警配置接口: `/api/v1/alarm/region-config` + * 普通设备告警配置接口: `/api/v1/alarm/device-config` +2. **接口职责**: 接口负责接收前端请求,调用应用服务层的阈值告警服务来完成实际的业务逻辑。 diff --git a/design/exceeding-threshold-alarm/task_list.md b/design/exceeding-threshold-alarm/task_list.md deleted file mode 100644 index 01dc56d..0000000 --- a/design/exceeding-threshold-alarm/task_list.md +++ /dev/null @@ -1,55 +0,0 @@ -# 阈值告警功能 - 任务清单 - -本清单按照从底层到上层的依赖关系排序,建议按此顺序进行开发。 - -## 1. 基础设施层 (`internal/infra`) - -- [ ] **数据库模型 (`models`)**: - - [ ] 在 `internal/infra/models/` 下创建 `alarm.go` 文件,定义 `ActiveAlarm` 和 `HistoricalAlarm` 两个结构体,包含 `status`, `ignored_until`, `last_notified_at` 等字段。 - - [ ] 在 `internal/infra/models/schedule.go` 中,为 `TaskType` 新增三个常量:`TaskTypeAreaControllerThresholdAlarm`, `TaskTypeDeviceThresholdAlarm`, `TaskTypeAlarmNotificationSender`。 - -- [ ] **数据库仓库 (`repository`)**: - - [ ] 在 `internal/infra/repository/` 下创建 `alarm_repository.go`,实现对 `active_alarms` 和 `historical_alarms` 表的增删改查操作,并提供 `MoveToHistorical` 等方法。 - - [ ] 在 `internal/infra/repository/device_repository.go` 中,为 `DeviceRepository` 接口及其实现增加 `FindByAreaControllerID` 方法。 - -- [ ] **配置 (`config`)**: - - [ ] 在 `config.yml` 文件中,新增告警相关的配置项,如 `re_notification_interval` (重复通知间隔)。 - - [ ] 在 `internal/infra/config/config.go` 中,添加对应的字段来加载该配置。 - -## 2. 领域层 (`internal/domain`) - -- [ ] **告警领域服务 (`alarm`)**: - - [ ] 创建 `internal/domain/alarm/` 目录。 - - [ ] 在该目录下创建 `alarm_service.go`,定义 `AlarmService` 接口及其实现。 - - [ ] 实现核心业务逻辑:`ReportStatus` (处理告警创建/解决) 和 `GetAndProcessPendingNotifications` (获取待发送通知)。该服务将注入 `AlarmRepository` 和 `NotifyService`。 - -- [ ] **任务实现 (`task`)**: - - [ ] 创建 `area_controller_threshold_alarm_task.go`,实现 `plan.Task` 接口,其 `Execute` 方法调用 `alarmService.ReportStatus`。 - - [ ] 创建 `device_threshold_alarm_task.go`,实现逻辑同上。 - * [ ] 创建 `alarm_notification_sender_task.go`,实现 `plan.Task` 接口,其 `Execute` 方法调用 `alarmService.GetAndProcessPendingNotifications`。 - -## 3. 应用层 (`internal/app`) - -- [ ] **告警配置服务 (`service`)**: - * [ ] 创建 `alarm_config_service.go`,定义 `AlarmConfigService`,负责告警 **配置** 的增删改查。 - * [ ] 实现 `ExcludeDeviceIDs` 的计算和更新逻辑。 - * [ ] 注入 `DeviceRepository` 和 `AreaControllerRepository`。 - -- [ ] **服务集成**: - - [ ] 在 `plan_service.go` (或任务工厂) 中,注册上述三种新的任务类型。 - - [ ] 修改 `device_service.go`,在处理设备更新(更换区域)和删除的方法中,调用 `AlarmConfigService` 的接口来刷新 `ExcludeDeviceIDs`。 - -- [ ] **告警配置控制器 (`controller`)**: - - [ ] 创建 `internal/app/controller/alarm/` 目录及 `alarm_controller.go` 文件。 - - [ ] 实现 `Create/Update/Delete/Get` 告警配置的 HTTP 接口,调用 `AlarmConfigService`。 - -- [ ] **API 路由 (`api`)**: - - [ ] 在 `internal/app/api/router.go` 中,注册 `/api/v1/alarm/region-config` 和 `/api/v1/alarm/device-config` 相关路由,并绑定到 `AlarmController` 的方法。 - -## 4. 核心初始化层 (`internal/core`) - -- [ ] **系统计划初始化 (`data_initializer.go`)**: - - [ ] 修改 `getPredefinedSystemPlans` 函数。 - - [ ] 将 "定时全量数据采集" 计划重命名为 "周期性系统健康检查"。 - - [ ] 在该计划中,于 "全量采集" 任务之后,添加 `TaskTypeAreaControllerThresholdAlarm` 和 `TaskTypeDeviceThresholdAlarm` 任务。 - - [ ] 新增一个 "告警通知发送" 计划,并为其添加 `TaskTypeAlarmNotificationSender` 任务。 From 375af57afe2850c13f4e6381da108f0dc18fd025 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 20:56:04 +0800 Subject: [PATCH 03/35] =?UTF-8?q?=E5=AE=9A=E4=B9=89=E5=91=8A=E8=AD=A6?= =?UTF-8?q?=E8=A1=A8=E5=92=8C=E5=91=8A=E8=AD=A6=E5=8E=86=E5=8F=B2=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/infra/models/alarm.go | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 internal/infra/models/alarm.go diff --git a/internal/infra/models/alarm.go b/internal/infra/models/alarm.go new file mode 100644 index 0000000..2d7256f --- /dev/null +++ b/internal/infra/models/alarm.go @@ -0,0 +1,52 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// ActiveAlarm 活跃告警 +// 活跃告警会被更新(如确认状态),因此保留 gorm.Model 以包含所有标准字段。 +type ActiveAlarm struct { + gorm.Model + FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"` + DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"` + AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"` + AlarmLevel string `gorm:"comment:告警级别" json:"alarm_level,omitempty"` + AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"` + TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` + IsAcknowledged bool `gorm:"default:false;comment:是否已确认" json:"is_acknowledged,omitempty"` + AcknowledgedBy string `gorm:"comment:确认人" json:"acknowledged_by,omitempty"` + // 使用指针类型 *time.Time 来表示可为空的确认时间 + AcknowledgedTime *time.Time `gorm:"comment:确认时间" json:"acknowledged_time,omitempty"` +} + +// TableName 指定 ActiveAlarm 结构体对应的数据库表名 +func (ActiveAlarm) TableName() string { + return "active_alarms" +} + +// HistoricalAlarm 历史告警 +// 历史告警是不可变归档数据,我们移除 gorm.Model,并手动定义字段。 +// ID 和 CreatedAt 共同构成联合主键,以满足 TimescaleDB 超表的要求。 +type HistoricalAlarm struct { + // 手动定义主键,ID 仍然自增 + ID uint `gorm:"primaryKey;autoIncrement;comment:主键ID" json:"id,omitempty"` + FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"` + DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"` + AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"` + AlarmLevel string `gorm:"comment:告警级别" json:"alarm_level,omitempty"` + AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"` + TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` + ResolveTime time.Time `gorm:"not null;comment:告警解决时间" json:"resolve_time"` + ResolveMethod string `gorm:"comment:告警解决方式" json:"resolve_method,omitempty"` + ResolvedBy string `gorm:"comment:告警解决人" json:"resolved_by,omitempty"` + // 将 CreatedAt 作为联合主键的一部分,用于 TimescaleDB 分区 + CreatedAt time.Time `gorm:"primaryKey;comment:创建时间" json:"created_at"` +} + +// TableName 指定 HistoricalAlarm 结构体对应的数据库表名 +func (HistoricalAlarm) TableName() string { + return "historical_alarms" +} From 2796d9bad705ba543225e439220cd36d7dd56c8e Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 21:39:24 +0800 Subject: [PATCH 04/35] =?UTF-8?q?=E9=87=8D=E6=9E=84=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=9E=9A=E4=B8=BE,=20=E8=AE=A9models=E5=8C=85=E4=B8=8D?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E5=85=B6=E4=BB=96=E9=A1=B9=E7=9B=AE=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 6 ++ internal/app/dto/notification_converter.go | 4 +- internal/app/dto/notification_dto.go | 9 ++- internal/core/component_initializers.go | 2 +- internal/domain/notify/notify.go | 30 ++++----- internal/infra/models/alarm.go | 36 +++++----- internal/infra/models/device_template.go | 37 ++++++++-- internal/infra/models/execution.go | 1 - internal/infra/models/models.go | 67 +++++++++++++++++++ internal/infra/models/notify.go | 50 +++++--------- internal/infra/notify/lark.go | 7 +- internal/infra/notify/log_notifier.go | 9 +-- internal/infra/notify/notify.go | 20 +----- internal/infra/notify/smtp.go | 8 ++- internal/infra/notify/wechat.go | 8 ++- .../repository/notification_repository.go | 3 +- .../utils/command_generater/modbus_rtu.go | 39 ++--------- 17 files changed, 188 insertions(+), 148 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 2792654..48f1016 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -121,3 +121,9 @@ * 区域主控告警配置接口: `/api/v1/alarm/region-config` * 普通设备告警配置接口: `/api/v1/alarm/device-config` 2. **接口职责**: 接口负责接收前端请求,调用应用服务层的阈值告警服务来完成实际的业务逻辑。 + + +# 实现记录 + +1. 定义告警表和告警历史表 +2. 重构部分枚举, 让models包不依赖其他项目中的包 \ No newline at end of file diff --git a/internal/app/dto/notification_converter.go b/internal/app/dto/notification_converter.go index fbc49b7..dddc5d2 100644 --- a/internal/app/dto/notification_converter.go +++ b/internal/app/dto/notification_converter.go @@ -2,8 +2,6 @@ package dto import ( "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" - - "go.uber.org/zap/zapcore" ) // NewListNotificationResponse 从模型数据创建通知列表响应 DTO @@ -18,7 +16,7 @@ func NewListNotificationResponse(data []models.Notification, total int64, page, UserID: item.UserID, Title: item.Title, Message: item.Message, - Level: zapcore.Level(item.Level), + Level: item.Level, AlarmTimestamp: item.AlarmTimestamp, ToAddress: item.ToAddress, Status: item.Status, diff --git a/internal/app/dto/notification_dto.go b/internal/app/dto/notification_dto.go index b493d4e..f0d32ac 100644 --- a/internal/app/dto/notification_dto.go +++ b/internal/app/dto/notification_dto.go @@ -4,7 +4,6 @@ import ( "time" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/notify" "go.uber.org/zap/zapcore" ) @@ -12,7 +11,7 @@ import ( // SendTestNotificationRequest 定义了发送测试通知请求的 JSON 结构 type SendTestNotificationRequest struct { // Type 指定要测试的通知渠道 - Type notify.NotifierType `json:"type" validate:"required"` + Type models.NotifierType `json:"type" validate:"required"` } // ListNotificationRequest 定义了获取通知列表的请求参数 @@ -20,7 +19,7 @@ type ListNotificationRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` UserID *uint `json:"user_id" query:"user_id"` - NotifierType *notify.NotifierType `json:"notifier_type" query:"notifier_type"` + NotifierType *models.NotifierType `json:"notifier_type" query:"notifier_type"` Status *models.NotificationStatus `json:"status" query:"status"` Level *zapcore.Level `json:"level" query:"level"` StartTime *time.Time `json:"start_time" query:"start_time"` @@ -33,11 +32,11 @@ type NotificationDTO struct { ID uint `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` - NotifierType notify.NotifierType `json:"notifier_type"` + NotifierType models.NotifierType `json:"notifier_type"` UserID uint `json:"user_id"` Title string `json:"title"` Message string `json:"message"` - Level zapcore.Level `json:"level"` + Level models.SeverityLevel `json:"level"` AlarmTimestamp time.Time `json:"alarm_timestamp"` ToAddress string `json:"to_address"` Status models.NotificationStatus `json:"status"` diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index fb94a45..ee8b4cd 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -353,7 +353,7 @@ func initNotifyService( // 3. 动态确定首选通知器 var primaryNotifier notify.Notifier - primaryNotifierType := notify.NotifierType(cfg.Primary) + primaryNotifierType := models.NotifierType(cfg.Primary) // 检查用户指定的主渠道是否已启用 for _, n := range availableNotifiers { diff --git a/internal/domain/notify/notify.go b/internal/domain/notify/notify.go index 88f9647..53f1168 100644 --- a/internal/domain/notify/notify.go +++ b/internal/domain/notify/notify.go @@ -11,8 +11,6 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/notify" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" - - "go.uber.org/zap" ) // Service 定义了通知领域的核心业务逻辑接口 @@ -24,14 +22,14 @@ type Service interface { BroadcastAlarm(ctx context.Context, content notify.AlarmContent) error // SendTestMessage 向指定用户发送一条测试消息,用于手动验证特定通知渠道的配置。 - SendTestMessage(ctx context.Context, userID uint, notifierType notify.NotifierType) error + SendTestMessage(ctx context.Context, userID uint, notifierType models.NotifierType) error } // failoverService 是 Service 接口的实现,提供了故障转移功能 type failoverService struct { ctx context.Context userRepo repository.UserRepository - notifiers map[notify.NotifierType]notify.Notifier + notifiers map[models.NotifierType]notify.Notifier primaryNotifier notify.Notifier failureThreshold int failureCounters *sync.Map // 使用 sync.Map 来安全地并发读写失败计数, key: userID (uint), value: counter (int) @@ -43,11 +41,11 @@ func NewFailoverService( ctx context.Context, userRepo repository.UserRepository, notifiers []notify.Notifier, - primaryNotifierType notify.NotifierType, + primaryNotifierType models.NotifierType, failureThreshold int, notificationRepo repository.NotificationRepository, ) (Service, error) { - notifierMap := make(map[notify.NotifierType]notify.Notifier) + notifierMap := make(map[models.NotifierType]notify.Notifier) for _, n := range notifiers { notifierMap[n.Type()] = n } @@ -189,7 +187,7 @@ func (s *failoverService) sendAlarmToUser(ctx context.Context, userID uint, cont } // SendTestMessage 实现了手动发送测试消息的功能 -func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, notifierType notify.NotifierType) error { +func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, notifierType models.NotifierType) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendTestMessage") user, err := s.userRepo.FindByID(serviceCtx, userID) if err != nil { @@ -210,7 +208,7 @@ func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, noti s.recordNotificationAttempt(serviceCtx, userID, notifierType, notify.AlarmContent{ Title: "通知服务测试", Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType), - Level: zap.InfoLevel, + Level: models.InfoLevel, Timestamp: time.Now(), }, "", models.NotificationStatusFailed, fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType)) return fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType) @@ -219,7 +217,7 @@ func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, noti testContent := notify.AlarmContent{ Title: "通知服务测试", Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType), - Level: zap.InfoLevel, + Level: models.InfoLevel, Timestamp: time.Now(), } @@ -239,15 +237,15 @@ func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, noti } // getAddressForNotifier 是一个辅助函数,根据通知器类型从 ContactInfo 中获取对应的地址 -func getAddressForNotifier(notifierType notify.NotifierType, contact models.ContactInfo) string { +func getAddressForNotifier(notifierType models.NotifierType, contact models.ContactInfo) string { switch notifierType { - case notify.NotifierTypeSMTP: + case models.NotifierTypeSMTP: return contact.Email - case notify.NotifierTypeWeChat: + case models.NotifierTypeWeChat: return contact.WeChat - case notify.NotifierTypeLark: + case models.NotifierTypeLark: return contact.Feishu - case notify.NotifierTypeLog: + case models.NotifierTypeLog: return "log" // LogNotifier不需要具体的地址,但为了函数签名一致性,返回一个无意义的非空字符串以绕过配置存在检查 default: return "" @@ -264,7 +262,7 @@ func getAddressForNotifier(notifierType notify.NotifierType, contact models.Cont func (s *failoverService) recordNotificationAttempt( ctx context.Context, userID uint, - notifierType notify.NotifierType, + notifierType models.NotifierType, content notify.AlarmContent, toAddress string, status models.NotificationStatus, @@ -281,7 +279,7 @@ func (s *failoverService) recordNotificationAttempt( UserID: userID, Title: content.Title, Message: content.Message, - Level: models.LogLevel(content.Level), + Level: content.Level, AlarmTimestamp: content.Timestamp, ToAddress: toAddress, Status: status, diff --git a/internal/infra/models/alarm.go b/internal/infra/models/alarm.go index 2d7256f..33a3425 100644 --- a/internal/infra/models/alarm.go +++ b/internal/infra/models/alarm.go @@ -10,14 +10,14 @@ import ( // 活跃告警会被更新(如确认状态),因此保留 gorm.Model 以包含所有标准字段。 type ActiveAlarm struct { gorm.Model - FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"` - DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"` - AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"` - AlarmLevel string `gorm:"comment:告警级别" json:"alarm_level,omitempty"` - AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"` - TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` - IsAcknowledged bool `gorm:"default:false;comment:是否已确认" json:"is_acknowledged,omitempty"` - AcknowledgedBy string `gorm:"comment:确认人" json:"acknowledged_by,omitempty"` + FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"` + DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"` + AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"` + AlarmLevel SeverityLevel `gorm:"comment:告警级别" json:"alarm_level,omitempty"` + AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"` + TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` + IsAcknowledged bool `gorm:"default:false;comment:是否已确认" json:"is_acknowledged,omitempty"` + AcknowledgedBy string `gorm:"comment:确认人" json:"acknowledged_by,omitempty"` // 使用指针类型 *time.Time 来表示可为空的确认时间 AcknowledgedTime *time.Time `gorm:"comment:确认时间" json:"acknowledged_time,omitempty"` } @@ -32,16 +32,16 @@ func (ActiveAlarm) TableName() string { // ID 和 CreatedAt 共同构成联合主键,以满足 TimescaleDB 超表的要求。 type HistoricalAlarm struct { // 手动定义主键,ID 仍然自增 - ID uint `gorm:"primaryKey;autoIncrement;comment:主键ID" json:"id,omitempty"` - FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"` - DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"` - AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"` - AlarmLevel string `gorm:"comment:告警级别" json:"alarm_level,omitempty"` - AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"` - TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` - ResolveTime time.Time `gorm:"not null;comment:告警解决时间" json:"resolve_time"` - ResolveMethod string `gorm:"comment:告警解决方式" json:"resolve_method,omitempty"` - ResolvedBy string `gorm:"comment:告警解决人" json:"resolved_by,omitempty"` + ID uint `gorm:"primaryKey;autoIncrement;comment:主键ID" json:"id,omitempty"` + FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"` + DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"` + AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"` + AlarmLevel SeverityLevel `gorm:"comment:告警级别" json:"alarm_level,omitempty"` + AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"` + TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` + ResolveTime time.Time `gorm:"not null;comment:告警解决时间" json:"resolve_time"` + ResolveMethod string `gorm:"comment:告警解决方式" json:"resolve_method,omitempty"` + ResolvedBy string `gorm:"comment:告警解决人" json:"resolved_by,omitempty"` // 将 CreatedAt 作为联合主键的一部分,用于 TimescaleDB 分区 CreatedAt time.Time `gorm:"primaryKey;comment:创建时间" json:"created_at"` } diff --git a/internal/infra/models/device_template.go b/internal/infra/models/device_template.go index bbb432b..8960046 100644 --- a/internal/infra/models/device_template.go +++ b/internal/infra/models/device_template.go @@ -5,12 +5,41 @@ import ( "errors" "fmt" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/command_generater" - "gorm.io/datatypes" "gorm.io/gorm" ) +// ModbusFunctionCode 定义Modbus功能码的枚举类型 +type ModbusFunctionCode byte + +// 定义常用的Modbus功能码常量及其应用场景 +const ( + // ReadCoils 读取线圈状态 (0x01) + // 场景: 用于读取数字量输出(DO)或内部标志位的当前状态,这些状态通常是开关量。 + ReadCoils ModbusFunctionCode = 0x01 + // ReadDiscreteInputs 读取离散输入状态 (0x02) + // 场景: 用于读取数字量输入(DI)的当前状态,这些状态通常是外部传感器的开关量信号。 + ReadDiscreteInputs ModbusFunctionCode = 0x02 + // ReadHoldingRegisters 读取保持寄存器 (0x03) + // 场景: 用于读取设备内部可读写的参数或数据,例如温度设定值、电机速度等模拟量或配置数据。 + ReadHoldingRegisters ModbusFunctionCode = 0x03 + // ReadInputRegisters 读取输入寄存器 (0x04) + // 场景: 用于读取设备的模拟量输入(AI)数据,这些数据通常是只读的,例如当前温度、压力、电压等实时测量值。 + ReadInputRegisters ModbusFunctionCode = 0x04 + // WriteSingleCoil 写入单个线圈 (0x05) + // 场景: 用于控制单个数字量输出(DO),例如打开或关闭一个继电器、指示灯等。 + WriteSingleCoil ModbusFunctionCode = 0x05 + // WriteSingleRegister 写入单个保持寄存器 (0x06) + // 场景: 用于修改设备内部的单个可写参数,例如设置一个温度控制器的目标温度、调整一个阀门的开度等。 + WriteSingleRegister ModbusFunctionCode = 0x06 + // WriteMultipleCoils 写入多个线圈 (0x0F) + // 场景: 用于批量控制多个数字量输出(DO),例如同时打开或关闭一组继电器。 + WriteMultipleCoils ModbusFunctionCode = 0x0F + // WriteMultipleRegisters 写入多个保持寄存器 (0x10) + // 场景: 用于批量修改设备内部的多个可写参数,例如一次性更新多个配置参数或模拟量输出值。 + WriteMultipleRegisters ModbusFunctionCode = 0x10 +) + // DeviceCategory 定义了设备模板的宽泛类别 type DeviceCategory string @@ -51,7 +80,7 @@ func (sc *SwitchCommands) SelfCheck() error { // SensorCommands 定义了传感器读取指令所需的Modbus参数 type SensorCommands struct { // ModbusFunctionCode 记录Modbus功能码,例如 ReadHoldingRegisters。(一般是第二字节) - ModbusFunctionCode command_generater.ModbusFunctionCode `json:"modbus_function_code"` + ModbusFunctionCode ModbusFunctionCode `json:"modbus_function_code"` // ModbusStartAddress 记录Modbus寄存器的起始地址,用于生成指令。(一般是第三到四字节) ModbusStartAddress uint16 `json:"modbus_start_address"` // ModbusQuantity 记录Modbus寄存器的数量,用于生成指令。(一般是五到六字节) @@ -62,7 +91,7 @@ type SensorCommands struct { func (sc *SensorCommands) SelfCheck() error { // 校验ModbusFunctionCode是否为读取类型 switch sc.ModbusFunctionCode { - case command_generater.ReadCoils, command_generater.ReadDiscreteInputs, command_generater.ReadHoldingRegisters, command_generater.ReadInputRegisters: + case ReadCoils, ReadDiscreteInputs, ReadHoldingRegisters, ReadInputRegisters: // 支持的读取功能码 default: return fmt.Errorf("'sensor' 指令集 ModbusFunctionCode %X 无效或不是读取类型", sc.ModbusFunctionCode) diff --git a/internal/infra/models/execution.go b/internal/infra/models/execution.go index 3c20751..e8804d2 100644 --- a/internal/infra/models/execution.go +++ b/internal/infra/models/execution.go @@ -154,7 +154,6 @@ func (PendingCollection) TableName() string { } // --- 用户审计日志 --- -// TODO 这些变量放这个包合适吗? // --- 审计日志状态常量 --- type AuditStatus string diff --git a/internal/infra/models/models.go b/internal/infra/models/models.go index 3d360a4..13c5418 100644 --- a/internal/infra/models/models.go +++ b/internal/infra/models/models.go @@ -6,6 +6,8 @@ import ( "fmt" "strconv" "strings" + + "go.uber.org/zap/zapcore" ) // GetAllModels 返回一个包含所有数据库模型实例的切片。 @@ -129,3 +131,68 @@ func (a *UintArray) Scan(src interface{}) error { *a = arr return nil } + +// SeverityLevel 定义了系统中告警、通知、日志的统一级别枚举。 +// 它以中文形式存储在数据库中,提高了可读性。 +type SeverityLevel string + +const ( + // DebugLevel 调试级别,用于开发和诊断问题。 + DebugLevel SeverityLevel = "Debug" + // InfoLevel 信息级别,用于记录常规操作。 + InfoLevel SeverityLevel = "Info" + // WarnLevel 警告级别,表示出现潜在问题,需要关注。 + WarnLevel SeverityLevel = "Warn" + // ErrorLevel 错误级别,表示发生了需要处理的错误。 + ErrorLevel SeverityLevel = "Error" + // DPanicLevel 开发时崩溃级别,在开发模式下会触发 panic。 + DPanicLevel SeverityLevel = "DPanic" + // PanicLevel 崩溃级别,记录日志后会立即触发 panic。 + PanicLevel SeverityLevel = "Panic" + // FatalLevel 致命级别,记录日志后会调用 os.Exit(1) 退出程序。 + FatalLevel SeverityLevel = "Fatal" +) + +// ToZapLevel 将我们的自定义级别转换为 zapcore.Level,以便与日志记录器兼容。 +func (al SeverityLevel) ToZapLevel() zapcore.Level { + switch al { + case DebugLevel: + return zapcore.DebugLevel + case InfoLevel: + return zapcore.InfoLevel + case WarnLevel: + return zapcore.WarnLevel + case ErrorLevel: + return zapcore.ErrorLevel + case DPanicLevel: + return zapcore.DPanicLevel + case PanicLevel: + return zapcore.PanicLevel + case FatalLevel: + return zapcore.FatalLevel + default: + // 默认情况下返回 Info 级别,保证程序健壮性 + return zapcore.InfoLevel + } +} + +// Scan 实现了 sql.Scanner 接口,GORM 在从数据库读取数据时会调用此方法。 +func (al *SeverityLevel) Scan(value interface{}) error { + bytes, ok := value.([]byte) + if !ok { + // 尝试处理其他可能的类型,例如字符串 + s, ok := value.(string) + if !ok { + return fmt.Errorf("无法将值 %v (类型 %T) 扫描为 SeverityLevel", value, value) + } + *al = SeverityLevel(s) + return nil + } + *al = SeverityLevel(bytes) + return nil +} + +// Value 实现了 driver.Valuer 接口,GORM 在将数据写入数据库时会调用此方法。 +func (al SeverityLevel) Value() (driver.Value, error) { + return string(al), nil +} diff --git a/internal/infra/models/notify.go b/internal/infra/models/notify.go index 291ee9b..d63b5cf 100644 --- a/internal/infra/models/notify.go +++ b/internal/infra/models/notify.go @@ -1,15 +1,25 @@ package models import ( - "database/sql/driver" - "errors" "time" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/notify" - "go.uber.org/zap/zapcore" "gorm.io/gorm" ) +// NotifierType 定义了通知器的类型。 +type NotifierType string + +const ( + // NotifierTypeSMTP 表示 SMTP 邮件通知器。 + NotifierTypeSMTP NotifierType = "邮件" + // NotifierTypeWeChat 表示企业微信通知器。 + NotifierTypeWeChat NotifierType = "企业微信" + // NotifierTypeLark 表示飞书通知器。 + NotifierTypeLark NotifierType = "飞书" + // NotifierTypeLog 表示日志通知器,作为最终的告警记录渠道。 + NotifierTypeLog NotifierType = "日志" +) + // NotificationStatus 定义了通知发送尝试的状态枚举。 type NotificationStatus string @@ -19,40 +29,12 @@ const ( NotificationStatusSkipped NotificationStatus = "已跳过" // 通知因某些原因被跳过(例如:用户未配置联系方式) ) -// LogLevel is a custom type for zapcore.Level to handle database scanning and valuing. -type LogLevel zapcore.Level - -// Scan implements the sql.Scanner interface. -func (l *LogLevel) Scan(value interface{}) error { - var s string - switch v := value.(type) { - case []byte: - s = string(v) - case string: - s = v - default: - return errors.New("LogLevel的类型无效") - } - - var zl zapcore.Level - if err := zl.UnmarshalText([]byte(s)); err != nil { - return err - } - *l = LogLevel(zl) - return nil -} - -// Value implements the driver.Valuer interface. -func (l LogLevel) Value() (driver.Value, error) { - return (zapcore.Level)(l).String(), nil -} - // Notification 表示已发送或尝试发送的通知记录。 type Notification struct { gorm.Model // NotifierType 通知器类型 (例如:"邮件", "企业微信", "飞书", "日志") - NotifierType notify.NotifierType `gorm:"type:varchar(20);not null;index" json:"notifier_type"` + NotifierType NotifierType `gorm:"type:varchar(20);not null;index" json:"notifier_type"` // UserID 接收通知的用户ID,用于追溯通知记录到特定用户 UserID uint `gorm:"index" json:"user_id"` // 增加 UserID 字段,并添加索引 // Title 通知标题 @@ -60,7 +42,7 @@ type Notification struct { // Message 通知内容 Message string `gorm:"type:text;not null" json:"message"` // Level 通知级别 (例如:INFO, WARN, ERROR) - Level LogLevel `gorm:"type:varchar(10);not null" json:"level"` + Level SeverityLevel `gorm:"type:varchar(10);not null" json:"level"` // AlarmTimestamp 通知内容生成时的时间戳,与 ID 构成复合主键 AlarmTimestamp time.Time `gorm:"primaryKey;not null" json:"alarm_timestamp"` // ToAddress 接收地址 (例如:邮箱地址, 企业微信ID, 日志标识符) diff --git a/internal/infra/notify/lark.go b/internal/infra/notify/lark.go index c1971ca..f49ff7e 100644 --- a/internal/infra/notify/lark.go +++ b/internal/infra/notify/lark.go @@ -10,6 +10,7 @@ import ( "time" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) const ( @@ -65,7 +66,7 @@ func (l *larkNotifier) Send(ctx context.Context, content AlarmContent, toAddr st "tag": "lark_md", "content": fmt.Sprintf("## %s\n**级别**: %s\n**时间**: %s\n\n%s", content.Title, - content.Level.String(), + content.Level, content.Timestamp.Format(DefaultTimeFormat), content.Message, ), @@ -171,8 +172,8 @@ func (l *larkNotifier) getAccessToken(ctx context.Context) (string, error) { } // Type 返回通知器的类型 -func (l *larkNotifier) Type() NotifierType { - return NotifierTypeLark +func (l *larkNotifier) Type() models.NotifierType { + return models.NotifierTypeLark } // --- API 数据结构 --- diff --git a/internal/infra/notify/log_notifier.go b/internal/infra/notify/log_notifier.go index 001bdb6..f799c6c 100644 --- a/internal/infra/notify/log_notifier.go +++ b/internal/infra/notify/log_notifier.go @@ -4,6 +4,7 @@ import ( "context" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) // logNotifier 实现了 Notifier 接口,用于将告警信息记录到日志中。 @@ -24,10 +25,10 @@ func NewLogNotifier(ctx context.Context) Notifier { func (l *logNotifier) Send(ctx context.Context, content AlarmContent, toAddr string) error { logger := logs.TraceLogger(ctx, l.ctx, "Send") logger.Infow("告警已记录到日志", - "notifierType", NotifierTypeLog, + "notifierType", models.NotifierTypeLog, "title", content.Title, "message", content.Message, - "level", content.Level.String(), + "level", content.Level, "timestamp", content.Timestamp.Format(DefaultTimeFormat), "toAddr", toAddr, ) @@ -35,6 +36,6 @@ func (l *logNotifier) Send(ctx context.Context, content AlarmContent, toAddr str } // Type 返回通知器的类型。 -func (l *logNotifier) Type() NotifierType { - return NotifierTypeLog +func (l *logNotifier) Type() models.NotifierType { + return models.NotifierTypeLog } diff --git a/internal/infra/notify/notify.go b/internal/infra/notify/notify.go index 4f86074..e34db31 100644 --- a/internal/infra/notify/notify.go +++ b/internal/infra/notify/notify.go @@ -4,26 +4,12 @@ import ( "context" "time" - "go.uber.org/zap/zapcore" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) // DefaultTimeFormat 定义了所有通知中统一使用的时间格式。 const DefaultTimeFormat = "2006-01-02 15:04:05" -// NotifierType 定义了通知器的类型。 -type NotifierType string - -const ( - // NotifierTypeSMTP 表示 SMTP 邮件通知器。 - NotifierTypeSMTP NotifierType = "邮件" - // NotifierTypeWeChat 表示企业微信通知器。 - NotifierTypeWeChat NotifierType = "企业微信" - // NotifierTypeLark 表示飞书通知器。 - NotifierTypeLark NotifierType = "飞书" - // NotifierTypeLog 表示日志通知器,作为最终的告警记录渠道。 - NotifierTypeLog NotifierType = "日志" -) - // AlarmContent 定义了通知的内容 type AlarmContent struct { // 通知标题 @@ -31,7 +17,7 @@ type AlarmContent struct { // 通知信息 Message string // 通知级别 - Level zapcore.Level + Level models.SeverityLevel // 通知时间 Timestamp time.Time } @@ -41,5 +27,5 @@ type Notifier interface { // Send 发送通知 Send(ctx context.Context, content AlarmContent, toAddr string) error // Type 返回通知器的类型 - Type() NotifierType + Type() models.NotifierType } diff --git a/internal/infra/notify/smtp.go b/internal/infra/notify/smtp.go index 131e788..31a06bf 100644 --- a/internal/infra/notify/smtp.go +++ b/internal/infra/notify/smtp.go @@ -5,6 +5,8 @@ import ( "fmt" "net/smtp" "strings" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) // smtpNotifier 实现了 Notifier 接口,用于通过 SMTP 发送邮件通知。 @@ -45,7 +47,7 @@ func (s *smtpNotifier) Send(ctx context.Context, content AlarmContent, toAddr st // 邮件正文 body := fmt.Sprintf("级别: %s\n时间: %s\n\n%s", - content.Level.String(), + content.Level, content.Timestamp.Format(DefaultTimeFormat), content.Message, ) @@ -71,6 +73,6 @@ func (s *smtpNotifier) Send(ctx context.Context, content AlarmContent, toAddr st } // Type 返回通知器的类型 -func (s *smtpNotifier) Type() NotifierType { - return NotifierTypeSMTP +func (s *smtpNotifier) Type() models.NotifierType { + return models.NotifierTypeSMTP } diff --git a/internal/infra/notify/wechat.go b/internal/infra/notify/wechat.go index ec5c33d..7e783b5 100644 --- a/internal/infra/notify/wechat.go +++ b/internal/infra/notify/wechat.go @@ -9,6 +9,8 @@ import ( "strings" "sync" "time" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) const ( @@ -55,7 +57,7 @@ func (w *wechatNotifier) Send(ctx context.Context, content AlarmContent, toAddr // 2. 构建 markdown 内容 markdownContent := fmt.Sprintf("## %s\n> 级别: %s\n> 时间: %s\n\n%s", content.Title, - content.Level.String(), + content.Level, content.Timestamp.Format(DefaultTimeFormat), content.Message, ) @@ -142,8 +144,8 @@ func (w *wechatNotifier) getAccessToken() (string, error) { } // Type 返回通知器的类型 -func (w *wechatNotifier) Type() NotifierType { - return NotifierTypeWeChat +func (w *wechatNotifier) Type() models.NotifierType { + return models.NotifierTypeWeChat } // --- API 数据结构 --- diff --git a/internal/infra/repository/notification_repository.go b/internal/infra/repository/notification_repository.go index 4055966..10798fc 100644 --- a/internal/infra/repository/notification_repository.go +++ b/internal/infra/repository/notification_repository.go @@ -6,7 +6,6 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/notify" "go.uber.org/zap/zapcore" "gorm.io/gorm" @@ -15,7 +14,7 @@ import ( // NotificationListOptions 定义了查询通知列表时的可选参数 type NotificationListOptions struct { UserID *uint // 按用户ID过滤 - NotifierType *notify.NotifierType // 按通知器类型过滤 + NotifierType *models.NotifierType // 按通知器类型过滤 Status *models.NotificationStatus // 按通知状态过滤 (例如:"success", "failed") Level *zapcore.Level // 按通知等级过滤 (例如:"info", "warning", "error") StartTime *time.Time // 通知内容生成时间范围 - 开始时间 (对应 AlarmTimestamp) diff --git a/internal/infra/utils/command_generater/modbus_rtu.go b/internal/infra/utils/command_generater/modbus_rtu.go index f9afbe1..457f095 100644 --- a/internal/infra/utils/command_generater/modbus_rtu.go +++ b/internal/infra/utils/command_generater/modbus_rtu.go @@ -3,37 +3,8 @@ package command_generater import ( "encoding/binary" "fmt" -) -// ModbusFunctionCode 定义Modbus功能码的枚举类型 -type ModbusFunctionCode byte - -// 定义常用的Modbus功能码常量及其应用场景 -const ( - // ReadCoils 读取线圈状态 (0x01) - // 场景: 用于读取数字量输出(DO)或内部标志位的当前状态,这些状态通常是开关量。 - ReadCoils ModbusFunctionCode = 0x01 - // ReadDiscreteInputs 读取离散输入状态 (0x02) - // 场景: 用于读取数字量输入(DI)的当前状态,这些状态通常是外部传感器的开关量信号。 - ReadDiscreteInputs ModbusFunctionCode = 0x02 - // ReadHoldingRegisters 读取保持寄存器 (0x03) - // 场景: 用于读取设备内部可读写的参数或数据,例如温度设定值、电机速度等模拟量或配置数据。 - ReadHoldingRegisters ModbusFunctionCode = 0x03 - // ReadInputRegisters 读取输入寄存器 (0x04) - // 场景: 用于读取设备的模拟量输入(AI)数据,这些数据通常是只读的,例如当前温度、压力、电压等实时测量值。 - ReadInputRegisters ModbusFunctionCode = 0x04 - // WriteSingleCoil 写入单个线圈 (0x05) - // 场景: 用于控制单个数字量输出(DO),例如打开或关闭一个继电器、指示灯等。 - WriteSingleCoil ModbusFunctionCode = 0x05 - // WriteSingleRegister 写入单个保持寄存器 (0x06) - // 场景: 用于修改设备内部的单个可写参数,例如设置一个温度控制器的目标温度、调整一个阀门的开度等。 - WriteSingleRegister ModbusFunctionCode = 0x06 - // WriteMultipleCoils 写入多个线圈 (0x0F) - // 场景: 用于批量控制多个数字量输出(DO),例如同时打开或关闭一组继电器。 - WriteMultipleCoils ModbusFunctionCode = 0x0F - // WriteMultipleRegisters 写入多个保持寄存器 (0x10) - // 场景: 用于批量修改设备内部的多个可写参数,例如一次性更新多个配置参数或模拟量输出值。 - WriteMultipleRegisters ModbusFunctionCode = 0x10 + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) // GenerateModbusRTUReadCommand 生成Modbus RTU读取指令 @@ -52,7 +23,7 @@ const ( // // []byte: 完整的Modbus RTU指令字节切片。 // error: 如果参数无效或生成过程中出现错误,则返回错误信息。 -func GenerateModbusRTUReadCommand(slaveAddress uint8, functionCode ModbusFunctionCode, startAddress uint16, quantity uint16) ([]byte, error) { +func GenerateModbusRTUReadCommand(slaveAddress uint8, functionCode models.ModbusFunctionCode, startAddress uint16, quantity uint16) ([]byte, error) { // 1. 校验输入参数 if slaveAddress == 0 || slaveAddress > 247 { return nil, fmt.Errorf("从站地址无效: %d, 必须在1-247之间", slaveAddress) @@ -60,9 +31,9 @@ func GenerateModbusRTUReadCommand(slaveAddress uint8, functionCode ModbusFunctio // 校验功能码是否为读取类型 switch functionCode { - case ReadCoils, ReadDiscreteInputs, ReadHoldingRegisters, ReadInputRegisters: + case models.ReadCoils, models.ReadDiscreteInputs, models.ReadHoldingRegisters, models.ReadInputRegisters: // 这些是支持的读取功能码 - case WriteSingleCoil, WriteSingleRegister, WriteMultipleCoils, WriteMultipleRegisters: + case models.WriteSingleCoil, models.WriteSingleRegister, models.WriteMultipleCoils, models.WriteMultipleRegisters: return nil, fmt.Errorf("功能码 %X 是写入操作,请使用 GenerateModbusRTUWriteCoilCommand 或其他写入函数", functionCode) default: return nil, fmt.Errorf("不支持的功能码: %X", functionCode) @@ -130,7 +101,7 @@ func GenerateModbusRTUSwitchCommand(slaveAddress uint8, coilAddress uint16, onOf // 2. 构建PDU (协议数据单元) for WriteSingleCoil (0x05) // PDU结构: 功能码 (1字节) + 线圈地址 (2字节) + 写入值 (2字节) pdu := make([]byte, 5) - pdu[0] = byte(WriteSingleCoil) + pdu[0] = byte(models.WriteSingleCoil) // Modbus协议中,地址和值都是大端字节序 (高位在前) binary.BigEndian.PutUint16(pdu[1:3], coilAddress) binary.BigEndian.PutUint16(pdu[3:5], writeValue) From d3207cc2b85d4c276efd977aa17f6a25757b38c5 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 22:19:55 +0800 Subject: [PATCH 05/35] =?UTF-8?q?=E8=B0=83=E6=95=B4models=E5=AE=9A?= =?UTF-8?q?=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/infra/database/postgres.go | 2 + internal/infra/models/alarm.go | 66 +++++++++++++++++++---------- internal/infra/models/models.go | 4 ++ 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/internal/infra/database/postgres.go b/internal/infra/database/postgres.go index 7b29ebd..d86d578 100644 --- a/internal/infra/database/postgres.go +++ b/internal/infra/database/postgres.go @@ -179,6 +179,7 @@ func (ps *PostgresStorage) creatingHyperTable(ctx context.Context) error { {models.PigPurchase{}, "purchase_date"}, {models.PigSale{}, "sale_date"}, {models.Notification{}, "alarm_timestamp"}, + {models.HistoricalAlarm{}, "trigger_time"}, } for _, table := range tablesToConvert { @@ -221,6 +222,7 @@ func (ps *PostgresStorage) applyCompressionPolicies(ctx context.Context) error { {models.PigPurchase{}, "pig_batch_id"}, {models.PigSale{}, "pig_batch_id"}, {models.Notification{}, "user_id"}, + {models.HistoricalAlarm{}, "source_id"}, } for _, policy := range policies { diff --git a/internal/infra/models/alarm.go b/internal/infra/models/alarm.go index 33a3425..53a40be 100644 --- a/internal/infra/models/alarm.go +++ b/internal/infra/models/alarm.go @@ -6,20 +6,37 @@ import ( "gorm.io/gorm" ) +// AlarmSourceType 定义了告警的来源类型 +type AlarmSourceType string + +const ( + AlarmSourceTypeDevice AlarmSourceType = "普通设备" + AlarmSourceTypeAreaController AlarmSourceType = "区域主控" + AlarmSourceTypeSystem AlarmSourceType = "系统" +) + // ActiveAlarm 活跃告警 -// 活跃告警会被更新(如确认状态),因此保留 gorm.Model 以包含所有标准字段。 +// 活跃告警会被更新(如忽略状态),因此保留 gorm.Model 以包含所有标准字段。 type ActiveAlarm struct { gorm.Model - FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"` - DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"` - AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"` - AlarmLevel SeverityLevel `gorm:"comment:告警级别" json:"alarm_level,omitempty"` - AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"` - TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` - IsAcknowledged bool `gorm:"default:false;comment:是否已确认" json:"is_acknowledged,omitempty"` - AcknowledgedBy string `gorm:"comment:确认人" json:"acknowledged_by,omitempty"` - // 使用指针类型 *time.Time 来表示可为空的确认时间 - AcknowledgedTime *time.Time `gorm:"comment:确认时间" json:"acknowledged_time,omitempty"` + + SourceType AlarmSourceType `gorm:"type:varchar(50);not null;index;comment:告警来源类型" json:"source_type"` + // SourceID 告警来源ID,其具体含义取决于 SourceType 字段 (例如:设备ID, 区域主控ID, 猪栏ID)。 + SourceID uint `gorm:"not null;index;comment:告警来源ID" json:"source_id"` + + AlarmSummary string `gorm:"comment:告警简述" json:"alarm_summary"` + Level SeverityLevel `gorm:"type:varchar(10);not null;comment:严重性等级" json:"level"` + AlarmDetails string `gorm:"comment:告警详细内容" json:"alarm_details"` + TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` + + // IsIgnored 是否被手动忽略 (Snooze) + IsIgnored bool `gorm:"default:false;comment:是否被手动忽略" json:"is_ignored"` + // IgnoredUntil 忽略截止时间。在此时间之前,即使告警持续,也不会发送通知。 + // 使用指针类型 *time.Time 来表示可为空的时间。 + IgnoredUntil *time.Time `gorm:"comment:忽略截止时间" json:"ignored_until"` + + // LastNotifiedAt 上次发送通知的时间。用于控制重复通知的频率。 + LastNotifiedAt *time.Time `gorm:"comment:上次发送通知时间" json:"last_notified_at"` } // TableName 指定 ActiveAlarm 结构体对应的数据库表名 @@ -29,21 +46,24 @@ func (ActiveAlarm) TableName() string { // HistoricalAlarm 历史告警 // 历史告警是不可变归档数据,我们移除 gorm.Model,并手动定义字段。 -// ID 和 CreatedAt 共同构成联合主键,以满足 TimescaleDB 超表的要求。 +// ID 和 TriggerTime 共同构成联合主键,以满足 TimescaleDB 超表的要求。 type HistoricalAlarm struct { // 手动定义主键,ID 仍然自增 - ID uint `gorm:"primaryKey;autoIncrement;comment:主键ID" json:"id,omitempty"` - FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"` - DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"` - AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"` - AlarmLevel SeverityLevel `gorm:"comment:告警级别" json:"alarm_level,omitempty"` - AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"` - TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` + ID uint `gorm:"primaryKey;autoIncrement;comment:主键ID" json:"id"` + + SourceType AlarmSourceType `gorm:"type:varchar(50);not null;index;comment:告警来源类型" json:"source_type"` + // SourceID 告警来源ID,其具体含义取决于 SourceType 字段 (例如:设备ID, 区域主控ID, 猪栏ID)。 + SourceID uint `gorm:"not null;index;comment:告警来源ID" json:"source_id"` + + AlarmSummary string `gorm:"comment:告警简述" json:"alarm_summary"` + Level SeverityLevel `gorm:"type:varchar(10);not null;comment:严重性等级" json:"level"` + AlarmDetails string `gorm:"comment:告警详细内容" json:"alarm_details"` + TriggerTime time.Time `gorm:"primaryKey;not null;comment:告警触发时间" json:"trigger_time"` ResolveTime time.Time `gorm:"not null;comment:告警解决时间" json:"resolve_time"` - ResolveMethod string `gorm:"comment:告警解决方式" json:"resolve_method,omitempty"` - ResolvedBy string `gorm:"comment:告警解决人" json:"resolved_by,omitempty"` - // 将 CreatedAt 作为联合主键的一部分,用于 TimescaleDB 分区 - CreatedAt time.Time `gorm:"primaryKey;comment:创建时间" json:"created_at"` + ResolveMethod string `gorm:"comment:告警解决方式" json:"resolve_method"` + + // ResolvedBy 使用指针类型 *uint 来表示可为空解决人, 当字段为空时表示系统自动解决的 + ResolvedBy *uint `gorm:"comment:告警解决人" json:"resolved_by"` } // TableName 指定 HistoricalAlarm 结构体对应的数据库表名 diff --git a/internal/infra/models/models.go b/internal/infra/models/models.go index 13c5418..9fbb914 100644 --- a/internal/infra/models/models.go +++ b/internal/infra/models/models.go @@ -63,6 +63,10 @@ func GetAllModels() []interface{} { &Medication{}, &MedicationLog{}, + // Alarm Models + &ActiveAlarm{}, + &HistoricalAlarm{}, + // Notification Models &Notification{}, } From a90b1cc012cff4916e60198bf1822a09aea7f5aa Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 22:26:16 +0800 Subject: [PATCH 06/35] =?UTF-8?q?=E5=AE=9A=E4=B9=89=E4=BB=93=E5=BA=93?= =?UTF-8?q?=E5=B1=82=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 ++- internal/core/component_initializers.go | 2 ++ internal/infra/repository/alarm_repository.go | 25 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 internal/infra/repository/alarm_repository.go diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 48f1016..b4e6361 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -126,4 +126,5 @@ # 实现记录 1. 定义告警表和告警历史表 -2. 重构部分枚举, 让models包不依赖其他项目中的包 \ No newline at end of file +2. 重构部分枚举, 让models包不依赖其他项目中的包 +3. 创建仓库层对象(不包含方法) \ No newline at end of file diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index ee8b4cd..79316bb 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -86,6 +86,7 @@ type Repositories struct { medicationLogRepo repository.MedicationLogRepository rawMaterialRepo repository.RawMaterialRepository notificationRepo repository.NotificationRepository + alarmRepo repository.AlarmRepository unitOfWork repository.UnitOfWork } @@ -114,6 +115,7 @@ func initRepositories(ctx context.Context, db *gorm.DB) *Repositories { medicationLogRepo: repository.NewGormMedicationLogRepository(logs.AddCompName(baseCtx, "MedicationLogRepo"), db), rawMaterialRepo: repository.NewGormRawMaterialRepository(logs.AddCompName(baseCtx, "RawMaterialRepo"), db), notificationRepo: repository.NewGormNotificationRepository(logs.AddCompName(baseCtx, "NotificationRepo"), db), + alarmRepo: repository.NewGormAlarmRepository(logs.AddCompName(baseCtx, "AlarmRepo"), db), unitOfWork: repository.NewGormUnitOfWork(logs.AddCompName(baseCtx, "UnitOfWork"), db), } } diff --git a/internal/infra/repository/alarm_repository.go b/internal/infra/repository/alarm_repository.go new file mode 100644 index 0000000..4a1893d --- /dev/null +++ b/internal/infra/repository/alarm_repository.go @@ -0,0 +1,25 @@ +package repository + +import ( + "context" + + "gorm.io/gorm" +) + +// AlarmRepository 定义了对告警模型的数据库操作接口 +type AlarmRepository interface { +} + +// gormAlarmRepository 是 AlarmRepository 的 GORM 实现。 +type gormAlarmRepository struct { + ctx context.Context + db *gorm.DB +} + +// NewGormAlarmRepository 创建一个新的 AlarmRepository GORM 实现实例。 +func NewGormAlarmRepository(ctx context.Context, db *gorm.DB) AlarmRepository { + return &gormAlarmRepository{ + ctx: ctx, + db: db, + } +} From baa62a9c77c6c65467a9ce1de3cb6f4557987702 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 22:42:28 +0800 Subject: [PATCH 07/35] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index b4e6361..2012df9 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -104,16 +104,16 @@ ### 阈值告警服务 (应用服务层) 1. **服务职责**: - * 负责管理阈值告警 **配置** 的增删改查。 + * 负责管理阈值告警 **任务配置** 的增删改查。这些任务配置包含了具体的阈值规则。 * 负责将用户创建的阈值告警任务动态更新到 "周期性系统健康检查" 计划中。 - * **配置引用检查**: 提供自检方法,用于在删除设备或设备模板前,检查它们是否被任何告警配置所引用,以防止产生悬空引用。 + * **任务配置引用检查**: 提供自检方法,用于在删除设备或设备模板前,检查它们是否被任何阈值告警任务配置所引用,以防止产生悬空引用。 2. **排除列表计算与联动**: - * **删除独立规则后归属**: 当一个普通设备的独立告警规则被删除时,它将自动从其所属区域主控的 `ExcludeDeviceIDs` + * **删除独立任务配置后归属**: 当一个普通设备的独立告警任务配置被删除时,它将自动从其所属区域主控的 `ExcludeDeviceIDs` 列表中移除,从而回归到区域统一告警策略的管理之下。 * **设备生命周期管理**: 在对设备进行修改(特别是更换区域主控)或删除时,以及在删除区域主控时,必须同步更新相关的 `ExcludeDeviceIDs` 列表,以保证数据一致性。 - * **实现**: `DeviceService` 中负责处理设备更新和删除的方法,需要调用本服务提供的“配置引用检查”和刷新接口。 + * **实现**: `DeviceService` 中负责处理设备更新和删除的方法,需要调用本服务提供的“任务配置引用检查”和刷新接口。 ### 阈值告警控制器 From 637df6395347028461245cc093d9e6fe3c2b5eb9 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 22:49:43 +0800 Subject: [PATCH 08/35] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 2012df9..ca092ff 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -101,7 +101,7 @@ * `ignored_until`: `timestamp` 类型,记录忽略截止时间。 * `last_notified_at`: `timestamp` 类型,记录上次发送通知的时间。 -### 阈值告警服务 (应用服务层) +### 阈值告警服务 (领域层) 1. **服务职责**: * 负责管理阈值告警 **任务配置** 的增删改查。这些任务配置包含了具体的阈值规则。 @@ -112,7 +112,7 @@ * **删除独立任务配置后归属**: 当一个普通设备的独立告警任务配置被删除时,它将自动从其所属区域主控的 `ExcludeDeviceIDs` 列表中移除,从而回归到区域统一告警策略的管理之下。 * **设备生命周期管理**: 在对设备进行修改(特别是更换区域主控)或删除时,以及在删除区域主控时,必须同步更新相关的 - `ExcludeDeviceIDs` 列表,以保证数据一致性。 + `ExcludeDeviceIDs` 列表,同时解决相关告警(当删除时), 以保证数据一致性。 * **实现**: `DeviceService` 中负责处理设备更新和删除的方法,需要调用本服务提供的“任务配置引用检查”和刷新接口。 ### 阈值告警控制器 From e4c41d65006392c8251b43f5a38ad7b48ea0ae10 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 23:21:37 +0800 Subject: [PATCH 09/35] =?UTF-8?q?=E5=AE=9A=E4=B9=89AlarmService(=E4=B8=8D?= =?UTF-8?q?=E5=90=AB=E6=96=B9=E6=B3=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 +++ internal/domain/alarm/alarm_service.go | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 internal/domain/alarm/alarm_service.go diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index ca092ff..7398c59 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -122,6 +122,9 @@ * 普通设备告警配置接口: `/api/v1/alarm/device-config` 2. **接口职责**: 接口负责接收前端请求,调用应用服务层的阈值告警服务来完成实际的业务逻辑。 +### TODO + +1. 是否要加一个延时操作, 因为采集是异步的, 采集任务结束时不一定能拿到最新数据, 所以需要一个延时操作等待区域主控上传 # 实现记录 diff --git a/internal/domain/alarm/alarm_service.go b/internal/domain/alarm/alarm_service.go new file mode 100644 index 0000000..06e6bee --- /dev/null +++ b/internal/domain/alarm/alarm_service.go @@ -0,0 +1,18 @@ +package alarm + +import "context" + +// AlarmService 定义了告警领域服务接口。 +type AlarmService interface { +} + +// alarmService 是 AlarmService 接口的具体实现。 +type alarmService struct { + ctx context.Context +} + +func NewAlarmService(ctx context.Context) AlarmService { + return &alarmService{ + ctx: ctx, + } +} From f23479c4d132c246dbba7b21bea447af45f53472 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 23:42:09 +0800 Subject: [PATCH 10/35] =?UTF-8?q?=E5=AE=9A=E4=B9=89AlarmNotificationTask(?= =?UTF-8?q?=E4=B8=8D=E5=90=AB=E6=96=B9=E6=B3=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/task/alarm_notification_task.go | 51 +++++++++++++++++++ internal/domain/task/task.go | 5 ++ internal/infra/models/plan.go | 1 + 3 files changed, 57 insertions(+) create mode 100644 internal/domain/task/alarm_notification_task.go diff --git a/internal/domain/task/alarm_notification_task.go b/internal/domain/task/alarm_notification_task.go new file mode 100644 index 0000000..26f4174 --- /dev/null +++ b/internal/domain/task/alarm_notification_task.go @@ -0,0 +1,51 @@ +package task + +import ( + "context" + + "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" +) + +// AlarmNotificationTask 告警通知发送任务 +type AlarmNotificationTask struct { + ctx context.Context + taskLog *models.TaskExecutionLog + // TODO: 根据实际需求添加告警通知相关的依赖,例如: + // notificationService notification.Service + // alarmRepository repository.AlarmRepository +} + +// NewAlarmNotificationTask 创建一个新的告警通知发送任务实例 +func NewAlarmNotificationTask(ctx context.Context, taskLog *models.TaskExecutionLog) plan.Task { + return &AlarmNotificationTask{ + ctx: ctx, + taskLog: taskLog, + } +} + +// Execute 执行告警通知发送任务 +func (t *AlarmNotificationTask) Execute(ctx context.Context) error { + logger := logs.TraceLogger(ctx, t.ctx, "Execute") + logger.Infof("开始执行告警通知发送任务, 任务ID: %d", t.taskLog.TaskID) + + // TODO: 实现告警通知发送逻辑 + + logger.Infof("告警通知发送任务执行完成, 任务ID: %d", t.taskLog.TaskID) + return nil +} + +// OnFailure 告警通知发送任务失败时的处理逻辑 +func (t *AlarmNotificationTask) OnFailure(ctx context.Context, executeErr error) { + logger := logs.TraceLogger(ctx, t.ctx, "OnFailure") + logger.Errorf("告警通知发送任务执行失败, 任务ID: %d, 错误: %v", t.taskLog.TaskID, executeErr) + + // TODO: 实现告警通知发送失败时的回滚或清理逻辑 +} + +// ResolveDeviceIDs 从任务配置中解析并返回所有关联的设备ID列表 +func (t *AlarmNotificationTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) { + // 这个任务是个与设备无关的任务 + return []uint{}, nil +} diff --git a/internal/domain/task/task.go b/internal/domain/task/task.go index dfc2d8b..a66570e 100644 --- a/internal/domain/task/task.go +++ b/internal/domain/task/task.go @@ -15,6 +15,7 @@ const ( CompNameDelayTask = "DelayTask" CompNameReleaseFeedWeight = "ReleaseFeedWeightTask" CompNameFullCollectionTask = "FullCollectionTask" + CompNameAlarmNotification = "AlarmNotificationTask" ) type taskFactory struct { @@ -48,6 +49,8 @@ func (t *taskFactory) Production(ctx context.Context, claimedLog *models.TaskExe return NewReleaseFeedWeightTask(logs.AddCompName(baseCtx, CompNameReleaseFeedWeight), claimedLog, t.sensorDataRepo, t.deviceRepo, t.deviceService) case models.TaskTypeFullCollection: return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), claimedLog, t.deviceRepo, t.deviceService) + case models.TaskTypeAlarmNotification: + return NewAlarmNotificationTask(logs.AddCompName(baseCtx, CompNameAlarmNotification), claimedLog) default: // TODO 这里直接panic合适吗? 不过这个场景确实不该出现任何异常的任务类型 logger.Panicf("不支持的任务类型: %s", claimedLog.Task.Type) @@ -75,6 +78,8 @@ func (t *taskFactory) CreateTaskFromModel(ctx context.Context, taskModel *models ), nil case models.TaskTypeFullCollection: return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), tempLog, t.deviceRepo, t.deviceService), nil + case models.TaskTypeAlarmNotification: + return NewAlarmNotificationTask(logs.AddCompName(baseCtx, CompNameAlarmNotification), tempLog), nil default: return nil, fmt.Errorf("不支持为类型 '%s' 的任务创建模型实例", taskModel.Type) } diff --git a/internal/infra/models/plan.go b/internal/infra/models/plan.go index 0c43fe4..3c7e5f7 100644 --- a/internal/infra/models/plan.go +++ b/internal/infra/models/plan.go @@ -35,6 +35,7 @@ const ( TaskTypeWaiting TaskType = "等待" // 等待任务 TaskTypeReleaseFeedWeight TaskType = "下料" // 下料口释放指定重量任务 TaskTypeFullCollection TaskType = "全量采集" // 新增的全量采集任务 + TaskTypeAlarmNotification TaskType = "告警通知" // 告警通知任务 ) // -- Task Parameters -- From e6aa9696a91bf0e1bee993ea0f9a4cbca7f02f25 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 23:46:05 +0800 Subject: [PATCH 11/35] gormAlarmRepository.ListActiveAlarms --- internal/infra/repository/alarm_repository.go | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/internal/infra/repository/alarm_repository.go b/internal/infra/repository/alarm_repository.go index 4a1893d..c8ec5ee 100644 --- a/internal/infra/repository/alarm_repository.go +++ b/internal/infra/repository/alarm_repository.go @@ -2,12 +2,30 @@ package repository import ( "context" + "time" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "gorm.io/gorm" ) +// ActiveAlarmListOptions 定义了查询活跃告警列表时的可选参数 +type ActiveAlarmListOptions struct { + SourceType *models.AlarmSourceType // 按告警来源类型过滤 + SourceID *uint // 按告警来源ID过滤 + Level *models.SeverityLevel // 按告警严重性等级过滤 + IsIgnored *bool // 按是否被忽略过滤 + TriggerTime *time.Time // 告警触发时间范围 - 开始时间 + EndTime *time.Time // 告警触发时间范围 - 结束时间 + OrderBy string // 排序字段,例如 "trigger_time DESC" +} + // AlarmRepository 定义了对告警模型的数据库操作接口 type AlarmRepository interface { + // ListActiveAlarms 支持分页和过滤的活跃告警列表查询。 + // 返回活跃告警列表、总记录数和错误。 + ListActiveAlarms(ctx context.Context, opts ActiveAlarmListOptions, page, pageSize int) ([]models.ActiveAlarm, int64, error) } // gormAlarmRepository 是 AlarmRepository 的 GORM 实现。 @@ -23,3 +41,55 @@ func NewGormAlarmRepository(ctx context.Context, db *gorm.DB) AlarmRepository { db: db, } } + +// ListActiveAlarms 实现了分页和过滤查询活跃告警记录的功能 +func (r *gormAlarmRepository) ListActiveAlarms(ctx context.Context, opts ActiveAlarmListOptions, page, pageSize int) ([]models.ActiveAlarm, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListActiveAlarms") + // --- 校验分页参数 --- + if page <= 0 || pageSize <= 0 { + return nil, 0, ErrInvalidPagination // 复用已定义的错误 + } + + var results []models.ActiveAlarm + var total int64 + + query := r.db.WithContext(repoCtx).Model(&models.ActiveAlarm{}) + + // --- 应用过滤条件 --- + if opts.SourceType != nil { + query = query.Where("source_type = ?", *opts.SourceType) + } + if opts.SourceID != nil { + query = query.Where("source_id = ?", *opts.SourceID) + } + if opts.Level != nil { + query = query.Where("level = ?", *opts.Level) + } + if opts.IsIgnored != nil { + query = query.Where("is_ignored = ?", *opts.IsIgnored) + } + if opts.TriggerTime != nil { + query = query.Where("trigger_time >= ?", *opts.TriggerTime) + } + if opts.EndTime != nil { + query = query.Where("trigger_time <= ?", *opts.EndTime) + } + + // --- 计算总数 --- + if err := query.Count(&total).Error; err != nil { + return nil, 0, err + } + + // --- 应用排序条件 --- + orderBy := "trigger_time DESC" // 默认按触发时间倒序 + if opts.OrderBy != "" { + orderBy = opts.OrderBy + } + query = query.Order(orderBy) + + // --- 分页 --- + offset := (page - 1) * pageSize + err := query.Limit(pageSize).Offset(offset).Find(&results).Error + + return results, total, err +} From f46c8aed0cd459fb0f99410ddafb96ed3cf35a3b Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 23:57:04 +0800 Subject: [PATCH 12/35] AlarmNotificationTask.parseParameters --- .../domain/task/alarm_notification_task.go | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/internal/domain/task/alarm_notification_task.go b/internal/domain/task/alarm_notification_task.go index 26f4174..8481d17 100644 --- a/internal/domain/task/alarm_notification_task.go +++ b/internal/domain/task/alarm_notification_task.go @@ -2,16 +2,30 @@ package task import ( "context" + "fmt" + "sync" + "time" "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) +// AlarmNotificationTaskParams 定义了 AlarmNotificationTask 的参数结构 +// 如果用户没有指定某个等级的配置, 则默认为该等级消息只发送一次 +type AlarmNotificationTaskParams struct { + // NotificationIntervals 告警通知的发送间隔时间,键为告警等级,值为时间间隔 + NotificationIntervals map[models.SeverityLevel]time.Duration `json:"notification_intervals"` +} + // AlarmNotificationTask 告警通知发送任务 type AlarmNotificationTask struct { ctx context.Context taskLog *models.TaskExecutionLog + params *AlarmNotificationTaskParams + + onceParse sync.Once // 保证解析参数只执行一次 + // TODO: 根据实际需求添加告警通知相关的依赖,例如: // notificationService notification.Service // alarmRepository repository.AlarmRepository @@ -27,10 +41,14 @@ func NewAlarmNotificationTask(ctx context.Context, taskLog *models.TaskExecution // Execute 执行告警通知发送任务 func (t *AlarmNotificationTask) Execute(ctx context.Context) error { - logger := logs.TraceLogger(ctx, t.ctx, "Execute") + taskCtx, logger := logs.Trace(ctx, t.ctx, "Execute") logger.Infof("开始执行告警通知发送任务, 任务ID: %d", t.taskLog.TaskID) - // TODO: 实现告警通知发送逻辑 + if err := t.parseParameters(taskCtx); err != nil { + return err + } + + // TODO: 实现告警通知发送逻辑,可以使用 t.params.NotificationIntervals 来获取不同等级的发送间隔 logger.Infof("告警通知发送任务执行完成, 任务ID: %d", t.taskLog.TaskID) return nil @@ -46,6 +64,35 @@ func (t *AlarmNotificationTask) OnFailure(ctx context.Context, executeErr error) // ResolveDeviceIDs 从任务配置中解析并返回所有关联的设备ID列表 func (t *AlarmNotificationTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) { - // 这个任务是个与设备无关的任务 + // 告警通知任务与设备无关 return []uint{}, nil } + +// parseParameters 解析任务参数 +func (t *AlarmNotificationTask) parseParameters(ctx context.Context) error { + logger := logs.TraceLogger(ctx, t.ctx, "parseParameters") + var err error + t.onceParse.Do(func() { + if t.taskLog.Task.Parameters == nil { + logger.Errorf("任务 %v: 缺少参数", t.taskLog.TaskID) + err = fmt.Errorf("任务 %v: 参数不全", t.taskLog.TaskID) + return + } + + var params AlarmNotificationTaskParams + err = t.taskLog.Task.ParseParameters(¶ms) + if err != nil { + logger.Errorf("任务 %v: 解析参数失败: %v", t.taskLog.TaskID, err) + err = fmt.Errorf("任务 %v: 解析参数失败: %v", t.taskLog.TaskID, err) + return + } + + // 如果 NotificationIntervals 为 nil,则初始化它 + if params.NotificationIntervals == nil { + params.NotificationIntervals = make(map[models.SeverityLevel]time.Duration) + } + + t.params = ¶ms + }) + return err +} From 3a59a2755cf8cc69cccfe38240365c009e1fba7f Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sat, 8 Nov 2025 00:13:15 +0800 Subject: [PATCH 13/35] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=91=8A=E8=AD=A6?= =?UTF-8?q?=E9=97=B4=E9=9A=94=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.example.yml | 11 +++++++ config.yml | 13 +++++++- .../domain/task/alarm_notification_task.go | 20 +++++++----- internal/infra/config/config.go | 32 ++++++++++++++----- 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/config.example.yml b/config.example.yml index 6641bac..ed4f81a 100644 --- a/config.example.yml +++ b/config.example.yml @@ -113,3 +113,14 @@ notify: # 定时采集配置 collection: interval: 1 # 采集间隔 (分钟) + +# 告警通知配置 +alarm_notification: + notification_intervals: # 告警通知间隔(分钟) + debug: 1 + info: 1 + warn: 1 + error: 1 + dpanic: 1 + panic: 1 + fatal: 1 diff --git a/config.yml b/config.yml index 56cbeeb..728e14a 100644 --- a/config.yml +++ b/config.yml @@ -90,4 +90,15 @@ lora_mesh: # 定时采集配置 collection: - interval: 1 # 采集间隔 (分钟) \ No newline at end of file + interval: 1 # 采集间隔 (分钟) + +# 告警通知配置 +alarm_notification: + notification_intervals: # 告警通知间隔 (分钟) + debug: 1 + info: 1 + warn: 1 + error: 1 + dpanic: 1 + panic: 1 + fatal: 1 diff --git a/internal/domain/task/alarm_notification_task.go b/internal/domain/task/alarm_notification_task.go index 8481d17..14ec7d7 100644 --- a/internal/domain/task/alarm_notification_task.go +++ b/internal/domain/task/alarm_notification_task.go @@ -14,15 +14,17 @@ import ( // AlarmNotificationTaskParams 定义了 AlarmNotificationTask 的参数结构 // 如果用户没有指定某个等级的配置, 则默认为该等级消息只发送一次 type AlarmNotificationTaskParams struct { - // NotificationIntervals 告警通知的发送间隔时间,键为告警等级,值为时间间隔 - NotificationIntervals map[models.SeverityLevel]time.Duration `json:"notification_intervals"` + // NotificationIntervals 告警通知的发送间隔时间,键为告警等级,值为时间间隔(分钟) + NotificationIntervals map[models.SeverityLevel]uint `json:"notification_intervals"` } // AlarmNotificationTask 告警通知发送任务 type AlarmNotificationTask struct { ctx context.Context taskLog *models.TaskExecutionLog - params *AlarmNotificationTaskParams + + // notificationIntervals 告警通知的发送间隔时间,键为告警等级,值为 time.Duration + notificationIntervals map[models.SeverityLevel]time.Duration onceParse sync.Once // 保证解析参数只执行一次 @@ -48,7 +50,7 @@ func (t *AlarmNotificationTask) Execute(ctx context.Context) error { return err } - // TODO: 实现告警通知发送逻辑,可以使用 t.params.NotificationIntervals 来获取不同等级的发送间隔 + // TODO: 实现告警通知发送逻辑,可以使用 t.notificationIntervals 来获取不同等级的发送间隔 logger.Infof("告警通知发送任务执行完成, 任务ID: %d", t.taskLog.TaskID) return nil @@ -87,12 +89,14 @@ func (t *AlarmNotificationTask) parseParameters(ctx context.Context) error { return } - // 如果 NotificationIntervals 为 nil,则初始化它 - if params.NotificationIntervals == nil { - params.NotificationIntervals = make(map[models.SeverityLevel]time.Duration) + // 初始化 notificationIntervals + t.notificationIntervals = make(map[models.SeverityLevel]time.Duration) + + // 将 uint 类型的秒数转换为 time.Duration + for level, seconds := range params.NotificationIntervals { + t.notificationIntervals[level] = time.Duration(seconds) * time.Minute } - t.params = ¶ms }) return err } diff --git a/internal/infra/config/config.go b/internal/infra/config/config.go index 81256b5..2579f3f 100644 --- a/internal/infra/config/config.go +++ b/internal/infra/config/config.go @@ -47,6 +47,9 @@ type Config struct { // Collection 定时采集配置 Collection CollectionConfig `yaml:"collection"` + + // AlarmNotification 告警通知配置 + AlarmNotification AlarmNotificationConfig `yaml:"alarm_notification"` } // AppConfig 代表应用基础配置 @@ -204,14 +207,27 @@ type CollectionConfig struct { Interval int `yaml:"interval"` } -// NewConfig 创建并返回一个新的配置实例 -func NewConfig() *Config { - // 默认值可以在这里设置,但我们优先使用配置文件中的值 - return &Config{ - Collection: CollectionConfig{ - Interval: 1, // 默认为1分钟 - }, - } +type NotificationIntervalsConfig struct { + // DebugIntervalMinutes Debug级别告警的通知间隔(分钟) + DebugIntervalMinutes uint `yaml:"debug"` + // InfoIntervalMinutes Info级别告警的通知间隔(分钟) + InfoIntervalMinutes uint `yaml:"info"` + // WarnIntervalMinutes Warn级别告警的通知间隔(分钟) + WarnIntervalMinutes uint `yaml:"warn"` + // ErrorIntervalMinutes Error级别告警的通知间隔(分钟) + ErrorIntervalMinutes uint `yaml:"error"` + // DPanicIntervalMinutes DPanic级别告警的通知间隔(分钟) + DPanicIntervalMinutes uint `yaml:"dpanic"` + // PanicIntervalMinutes Panic级别告警的通知间隔(分钟) + PanicIntervalMinutes uint `yaml:"panic"` + // FatalIntervalMinutes Fatal级别告警的通知间隔(分钟) + FatalIntervalMinutes uint `yaml:"fatal"` +} + +// AlarmNotificationConfig 告警通知配置 +type AlarmNotificationConfig struct { + // NotificationIntervals 告警通知的发送间隔时间,键为告警等级,值为时间间隔(秒) + NotificationIntervals NotificationIntervalsConfig `yaml:"notification_intervals"` } // Load 从指定路径加载配置文件 From f049cbce6c09b0a8ab059dc9bccd34ccce39edb9 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sat, 8 Nov 2025 01:27:33 +0800 Subject: [PATCH 14/35] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=BB=A1=E8=B6=B3=E5=8F=91=E9=80=81=E5=91=8A=E8=AD=A6=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E6=9D=A1=E4=BB=B6=E7=9A=84=E6=B4=BB=E8=B7=83=E5=91=8A?= =?UTF-8?q?=E8=AD=A6=E5=88=97=E8=A1=A8=E7=9A=84=E4=BB=93=E5=BA=93=E5=B1=82?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/infra/repository/alarm_repository.go | 151 ++++++++++++++++++ project_structure.txt | 4 + 2 files changed, 155 insertions(+) diff --git a/internal/infra/repository/alarm_repository.go b/internal/infra/repository/alarm_repository.go index c8ec5ee..35b6a99 100644 --- a/internal/infra/repository/alarm_repository.go +++ b/internal/infra/repository/alarm_repository.go @@ -2,6 +2,8 @@ package repository import ( "context" + "errors" + "fmt" "time" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" @@ -26,6 +28,15 @@ type AlarmRepository interface { // ListActiveAlarms 支持分页和过滤的活跃告警列表查询。 // 返回活跃告警列表、总记录数和错误。 ListActiveAlarms(ctx context.Context, opts ActiveAlarmListOptions, page, pageSize int) ([]models.ActiveAlarm, int64, error) + + // <-- 下列两个方法是为了性能做出的架构妥协, 业务逻辑入侵仓库层带来的收益远大于通过业务层进行数据筛选 --> + + // ListAlarmsForNotification 查询满足发送告警消息条件的活跃告警列表。 + // 返回活跃告警列表和错误。 + // intervalByLevel: key=SeverityLevel, value=interval_in_minutes + ListAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]int) ([]models.ActiveAlarm, error) + // 查询满足发送告警消息条件的记录总数 + CountAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]int) (int64, error) } // gormAlarmRepository 是 AlarmRepository 的 GORM 实现。 @@ -93,3 +104,143 @@ func (r *gormAlarmRepository) ListActiveAlarms(ctx context.Context, opts ActiveA return results, total, err } + +// CountAlarmsForNotification 查询满足发送告警消息条件的记录总数 +func (r *gormAlarmRepository) CountAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]int) (int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CountAlarmsForNotification") + var total int64 + + // 1. 构造基础查询对象 (包含 Context 和 Model) + baseTx := r.db.WithContext(repoCtx).Model(&models.ActiveAlarm{}) + + // 2. 传递给辅助函数应用所有 WHERE 逻辑 + query := r.buildNotificationBaseQuery(baseTx, intervalByLevel) + + // 3. 只执行 Count + err := query.Count(&total).Error + + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return 0, err + } + + return total, nil +} + +// ListAlarmsForNotification 查询满足发送告警消息条件的活跃告警列表 +func (r *gormAlarmRepository) ListAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]int) ([]models.ActiveAlarm, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListAlarmsForNotification") + var results []models.ActiveAlarm + + // 1. 构造基础查询对象 (包含 Context 和 Model) + baseTx := r.db.WithContext(repoCtx).Model(&models.ActiveAlarm{}) + + // 2. 传递给辅助函数应用所有 WHERE 逻辑 + query := r.buildNotificationBaseQuery(baseTx, intervalByLevel) + + // 3. 执行 Find (不排序,高性能) + err := query.Find(&results).Error + + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + + return results, nil +} + +// buildNotificationBaseQuery 负责组合 Group A 和 Group B 的逻辑 +func (r *gormAlarmRepository) buildNotificationBaseQuery(tx *gorm.DB, intervalByLevel map[models.SeverityLevel]int) *gorm.DB { + + // 1. 获取所有配置的 Level 列表 + configuredLevels := make([]models.SeverityLevel, 0, len(intervalByLevel)) + for level := range intervalByLevel { + configuredLevels = append(configuredLevels, level) + } + + // 2. 构造 Group A (只发送一次) + // Group A 是一个独立的 GORM SubQuery,用于构建 OR 关系 + groupAQuery := r.buildGroupAClause(tx.Session(&gorm.Session{}), configuredLevels) + + // 3. 构造 Group B (间隔发送) + // Group B 也是一个独立的 GORM SubQuery + groupBQuery := r.buildGroupBClause(tx.Session(&gorm.Session{}), intervalByLevel, configuredLevels) + + // 4. 最终组合:Group A OR Group B + + // 核心逻辑:利用 GORM 的 Where(SubQuery) OR Where(SubQuery) 特性。 + // GORM 允许将 WHERE 或 OR 的参数写成 func(db *gorm.DB) *gorm.DB + // 这样可以确保子查询的括号被正确处理,实现 (A) OR (B) 结构。 + + // 注意:我们必须检查配置,因为如果 Group B 配置为空,我们不应该将其添加到 OR 关系中。 + if len(configuredLevels) == 0 { + // 只有 Group A 存在(即 Level NOT IN 的条件是 1=1) + return tx.Where(groupAQuery) + } + + // 存在 Group A 和 Group B,用 OR 连接 + return tx.Where(groupAQuery).Or(groupBQuery) +} + +// buildGroupAClause 构造 Group A 的 WHERE 语句和参数列表。 +// 针对 Level 缺失配置(或所有 Level)的告警,使用“只发送一次”逻辑:LastNotifiedAt IS NULL +// 参数 configuredLevels: 用于构建 Level NOT IN (?) 子句。 +func (r *gormAlarmRepository) buildGroupAClause(tx *gorm.DB, configuredLevels []models.SeverityLevel) *gorm.DB { + now := time.Now() + + // A.1. 构造 Level 范围检查子句 (Level NOT IN 或 1=1) + if len(configuredLevels) > 0 { + tx = tx.Where("level NOT IN (?)", configuredLevels) + } else { + // 如果配置列表为空,则所有 Level 都符合,使用 1=1 + tx = tx.Where("1 = 1") + } + + // A.2. 构造 Group A 核心逻辑 (LastNotifiedAt IS NULL 且满足忽略条件) + + // C_A_Ignored: 被忽略但忽略期结束 且 仅发送一次 + ignoredQuery := tx.Where("is_ignored = ? AND ignored_until <= ? AND last_notified_at IS NULL", true, now) + + // C_A_NotIgnored: 未被忽略 且 仅发送一次 + notIgnoredQuery := tx.Where("is_ignored = ? AND last_notified_at IS NULL", false) + + // A.3. 组合 Group A 核心逻辑: (C_A_Ignored OR C_A_NotIgnored) + return tx.Where(ignoredQuery).Or(notIgnoredQuery) +} + +// buildGroupBClause 构造 Group B 的 WHERE 语句和参数列表。 +// 针对 Level 存在配置的告警,使用“间隔发送”逻辑。 +func (r *gormAlarmRepository) buildGroupBClause(tx *gorm.DB, intervalByLevel map[models.SeverityLevel]int, configuredLevels []models.SeverityLevel) *gorm.DB { + now := time.Now() + + // B.1. 构造 Level IN 子句 + tx = tx.Where("level IN (?)", configuredLevels) + + // B.2. 构造 Level-Based 间隔检查 (OR 部分) + // 核心思想:利用 GORM 的 Or 链式调用构建 Level 间隔检查子句 + + // 初始化 Level 间隔检查查询 (ICC) + iccQuery := tx.Session(&gorm.Session{}) // 创建一个干净的子查询对象来构建 ICC + + // 动态添加 Level 间隔检查 OR 条件 + for level, minutes := range intervalByLevel { + // PostgreSQL 语法: last_notified_at + (5 * interval '1 minute') <= ? + sql := fmt.Sprintf("level = ? AND last_notified_at + (%d * interval '1 minute') <= ?", minutes) + + // 每次使用 Or 叠加新的 Level 检查 + iccQuery = iccQuery.Or(sql, level, now) + } + + // B.3. 组合 Group B 核心逻辑: (last_notified_at IS NULL OR [ICC]) + + // C_B_NotIgnored: 未被忽略 + notIgnoredQuery := tx.Where("is_ignored = ?", false).Where( + tx.Where("last_notified_at IS NULL").Or(iccQuery), // LastNotifiedAt IS NULL OR ICC + ) + + // C_B_Ignored: 被忽略但忽略期结束 + ignoredQuery := tx.Where("is_ignored = ? AND ignored_until <= ?", true, now).Where( + tx.Where("last_notified_at IS NULL").Or(iccQuery), // LastNotifiedAt IS NULL OR ICC + ) + + // B.4. 组合 Group B 核心逻辑: (C_B_NotIgnored OR C_B_Ignored) + return tx.Where(notIgnoredQuery).Or(ignoredQuery) +} diff --git a/project_structure.txt b/project_structure.txt index 479ca80..2b72491 100644 --- a/project_structure.txt +++ b/project_structure.txt @@ -81,6 +81,7 @@ internal/app/webhook/transport.go internal/core/application.go internal/core/component_initializers.go internal/core/data_initializer.go +internal/domain/alarm/alarm_service.go internal/domain/device/device_service.go internal/domain/device/general_device_service.go internal/domain/notify/notify.go @@ -96,6 +97,7 @@ internal/domain/plan/analysis_plan_task_manager.go internal/domain/plan/plan_execution_manager.go internal/domain/plan/plan_service.go internal/domain/plan/task.go +internal/domain/task/alarm_notification_task.go internal/domain/task/delay_task.go internal/domain/task/full_collection_task.go internal/domain/task/release_feed_weight_task.go @@ -106,6 +108,7 @@ internal/infra/database/storage.go internal/infra/logs/context.go internal/infra/logs/encoder.go internal/infra/logs/logs.go +internal/infra/models/alarm.go internal/infra/models/device.go internal/infra/models/device_template.go internal/infra/models/execution.go @@ -127,6 +130,7 @@ internal/infra/notify/log_notifier.go internal/infra/notify/notify.go internal/infra/notify/smtp.go internal/infra/notify/wechat.go +internal/infra/repository/alarm_repository.go internal/infra/repository/area_controller_repository.go internal/infra/repository/device_command_log_repository.go internal/infra/repository/device_repository.go From 362ed5ad9d861921699002f88fe8d20608875618 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sat, 8 Nov 2025 17:35:03 +0800 Subject: [PATCH 15/35] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20AlarmNotificationTas?= =?UTF-8?q?k?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/task/alarm_notification_task.go | 72 ++++++++++++++----- internal/domain/task/task.go | 25 ++++--- internal/infra/repository/alarm_repository.go | 41 +++++++++-- 3 files changed, 106 insertions(+), 32 deletions(-) diff --git a/internal/domain/task/alarm_notification_task.go b/internal/domain/task/alarm_notification_task.go index 14ec7d7..2b03a0d 100644 --- a/internal/domain/task/alarm_notification_task.go +++ b/internal/domain/task/alarm_notification_task.go @@ -6,9 +6,12 @@ import ( "sync" "time" + notify_domain "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify" "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/notify" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" ) // AlarmNotificationTaskParams 定义了 AlarmNotificationTask 的参数结构 @@ -23,21 +26,22 @@ type AlarmNotificationTask struct { ctx context.Context taskLog *models.TaskExecutionLog - // notificationIntervals 告警通知的发送间隔时间,键为告警等级,值为 time.Duration - notificationIntervals map[models.SeverityLevel]time.Duration + // alarmNotificationTaskParams 是任务配置 + alarmNotificationTaskParams AlarmNotificationTaskParams onceParse sync.Once // 保证解析参数只执行一次 - // TODO: 根据实际需求添加告警通知相关的依赖,例如: - // notificationService notification.Service - // alarmRepository repository.AlarmRepository + notificationService notify_domain.Service + alarmRepository repository.AlarmRepository } // NewAlarmNotificationTask 创建一个新的告警通知发送任务实例 -func NewAlarmNotificationTask(ctx context.Context, taskLog *models.TaskExecutionLog) plan.Task { +func NewAlarmNotificationTask(ctx context.Context, taskLog *models.TaskExecutionLog, service notify_domain.Service, alarmRepository repository.AlarmRepository) plan.Task { return &AlarmNotificationTask{ - ctx: ctx, - taskLog: taskLog, + ctx: ctx, + taskLog: taskLog, + alarmRepository: alarmRepository, + notificationService: service, } } @@ -50,7 +54,47 @@ func (t *AlarmNotificationTask) Execute(ctx context.Context) error { return err } - // TODO: 实现告警通知发送逻辑,可以使用 t.notificationIntervals 来获取不同等级的发送间隔 + // 获取是否有待发送告警通知, 用于优化性能 + alarmsCount, err := t.alarmRepository.CountAlarmsForNotification(taskCtx, t.alarmNotificationTaskParams.NotificationIntervals) + if err != nil { + logger.Errorf("任务 %v: 获取告警数量失败: %v", t.taskLog.TaskID, err) + return err + } + if alarmsCount == 0 { + logger.Debugf("没有待发送的告警通知, 跳过任务, 任务ID: %d", t.taskLog.TaskID) + return nil + } + + // 获取所有待发送的告警通知 + alarms, err := t.alarmRepository.ListAlarmsForNotification(taskCtx, t.alarmNotificationTaskParams.NotificationIntervals) + if err != nil { + logger.Errorf("任务 %v: 获取告警列表失败: %v", t.taskLog.TaskID, err) + return err + } + + // 发送通知 + for _, alarm := range alarms { + // TODO 因为还没做权限管理, 所以暂时通过广播形式发给所有用户 + err = t.notificationService.BroadcastAlarm(taskCtx, notify.AlarmContent{ + Title: alarm.AlarmSummary, + Message: alarm.AlarmDetails, + Level: alarm.Level, + Timestamp: time.Now(), + }) + + if err != nil { + // 非致命错误 + logger.Errorf("任务 %v: 发送告警通知失败: %v", t.taskLog.TaskID, err) + continue + } + + // 能发送通知的告警要么是忽略期已过且到达触发时间, 要么是不忽略且到达触发时间, 二者都应该取消忽略并刷新最后一次发送时间 + err = t.alarmRepository.UpdateAlarmNotificationStatus(taskCtx, alarm.ID, time.Now(), false, nil) + if err != nil { + // 非致命错误, 没有必要因为更新失败影响后续消息发送 + logger.Errorf("任务 %v: 更新告警通知状态失败: %v", t.taskLog.TaskID, err) + } + } logger.Infof("告警通知发送任务执行完成, 任务ID: %d", t.taskLog.TaskID) return nil @@ -60,8 +104,6 @@ func (t *AlarmNotificationTask) Execute(ctx context.Context) error { func (t *AlarmNotificationTask) OnFailure(ctx context.Context, executeErr error) { logger := logs.TraceLogger(ctx, t.ctx, "OnFailure") logger.Errorf("告警通知发送任务执行失败, 任务ID: %d, 错误: %v", t.taskLog.TaskID, executeErr) - - // TODO: 实现告警通知发送失败时的回滚或清理逻辑 } // ResolveDeviceIDs 从任务配置中解析并返回所有关联的设备ID列表 @@ -89,13 +131,7 @@ func (t *AlarmNotificationTask) parseParameters(ctx context.Context) error { return } - // 初始化 notificationIntervals - t.notificationIntervals = make(map[models.SeverityLevel]time.Duration) - - // 将 uint 类型的秒数转换为 time.Duration - for level, seconds := range params.NotificationIntervals { - t.notificationIntervals[level] = time.Duration(seconds) * time.Minute - } + t.alarmNotificationTaskParams = params }) return err diff --git a/internal/domain/task/task.go b/internal/domain/task/task.go index a66570e..7ee9f09 100644 --- a/internal/domain/task/task.go +++ b/internal/domain/task/task.go @@ -5,6 +5,7 @@ import ( "fmt" "git.huangwc.com/pig/pig-farm-controller/internal/domain/device" + "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify" "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" @@ -19,23 +20,31 @@ const ( ) type taskFactory struct { - ctx context.Context + ctx context.Context + sensorDataRepo repository.SensorDataRepository deviceRepo repository.DeviceRepository - deviceService device.Service + alarmRepo repository.AlarmRepository + + deviceService device.Service + notificationService notify.Service } func NewTaskFactory( ctx context.Context, sensorDataRepo repository.SensorDataRepository, deviceRepo repository.DeviceRepository, + alarmRepo repository.AlarmRepository, deviceService device.Service, + notifyService notify.Service, ) plan.TaskFactory { return &taskFactory{ - ctx: ctx, - sensorDataRepo: sensorDataRepo, - deviceRepo: deviceRepo, - deviceService: deviceService, + ctx: ctx, + sensorDataRepo: sensorDataRepo, + deviceRepo: deviceRepo, + alarmRepo: alarmRepo, + deviceService: deviceService, + notificationService: notifyService, } } @@ -50,7 +59,7 @@ func (t *taskFactory) Production(ctx context.Context, claimedLog *models.TaskExe case models.TaskTypeFullCollection: return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), claimedLog, t.deviceRepo, t.deviceService) case models.TaskTypeAlarmNotification: - return NewAlarmNotificationTask(logs.AddCompName(baseCtx, CompNameAlarmNotification), claimedLog) + return NewAlarmNotificationTask(logs.AddCompName(baseCtx, CompNameAlarmNotification), claimedLog, t.notificationService, t.alarmRepo) default: // TODO 这里直接panic合适吗? 不过这个场景确实不该出现任何异常的任务类型 logger.Panicf("不支持的任务类型: %s", claimedLog.Task.Type) @@ -79,7 +88,7 @@ func (t *taskFactory) CreateTaskFromModel(ctx context.Context, taskModel *models case models.TaskTypeFullCollection: return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), tempLog, t.deviceRepo, t.deviceService), nil case models.TaskTypeAlarmNotification: - return NewAlarmNotificationTask(logs.AddCompName(baseCtx, CompNameAlarmNotification), tempLog), nil + return NewAlarmNotificationTask(logs.AddCompName(baseCtx, CompNameAlarmNotification), tempLog, t.notificationService, t.alarmRepo), nil default: return nil, fmt.Errorf("不支持为类型 '%s' 的任务创建模型实例", taskModel.Type) } diff --git a/internal/infra/repository/alarm_repository.go b/internal/infra/repository/alarm_repository.go index 35b6a99..8408ab5 100644 --- a/internal/infra/repository/alarm_repository.go +++ b/internal/infra/repository/alarm_repository.go @@ -29,14 +29,20 @@ type AlarmRepository interface { // 返回活跃告警列表、总记录数和错误。 ListActiveAlarms(ctx context.Context, opts ActiveAlarmListOptions, page, pageSize int) ([]models.ActiveAlarm, int64, error) + // UpdateAlarmNotificationStatus 显式更新告警的通知相关状态字段。 + // lastNotifiedAt: 传入具体的发送时间。 + // isIgnored: 告警新的忽略状态。 + // ignoredUntil: 告警新的忽略截止时间 (nil 表示没有忽略截止时间/已取消忽略)。 + UpdateAlarmNotificationStatus(ctx context.Context, alarmID uint, lastNotifiedAt time.Time, isIgnored bool, ignoredUntil *time.Time) error + // <-- 下列两个方法是为了性能做出的架构妥协, 业务逻辑入侵仓库层带来的收益远大于通过业务层进行数据筛选 --> // ListAlarmsForNotification 查询满足发送告警消息条件的活跃告警列表。 // 返回活跃告警列表和错误。 // intervalByLevel: key=SeverityLevel, value=interval_in_minutes - ListAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]int) ([]models.ActiveAlarm, error) + ListAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint) ([]models.ActiveAlarm, error) // 查询满足发送告警消息条件的记录总数 - CountAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]int) (int64, error) + CountAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint) (int64, error) } // gormAlarmRepository 是 AlarmRepository 的 GORM 实现。 @@ -105,8 +111,31 @@ func (r *gormAlarmRepository) ListActiveAlarms(ctx context.Context, opts ActiveA return results, total, err } +func (r *gormAlarmRepository) UpdateAlarmNotificationStatus(ctx context.Context, alarmID uint, lastNotifiedAt time.Time, isIgnored bool, ignoredUntil *time.Time) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateAlarmNotificationStatus") + + // 1. 内部安全地构造 map,将强类型参数转换为 GORM 需要的格式 + // GORM 的 Updates 方法会正确处理 *time.Time (nil -> DB NULL) + updates := map[string]interface{}{ + "last_notified_at": lastNotifiedAt, // time.Time 会被 GORM 视为非空时间 + "is_ignored": isIgnored, + "ignored_until": ignoredUntil, // *time.Time (nil) 会被 GORM 写入 NULL + } + + // 2. 执行更新 + result := r.db.WithContext(repoCtx). + Model(&models.ActiveAlarm{}). + Where("id = ?", alarmID). + Updates(updates) // 仅更新 updates map 中指定的三个字段 + + if result.Error != nil { + return result.Error + } + return nil +} + // CountAlarmsForNotification 查询满足发送告警消息条件的记录总数 -func (r *gormAlarmRepository) CountAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]int) (int64, error) { +func (r *gormAlarmRepository) CountAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint) (int64, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "CountAlarmsForNotification") var total int64 @@ -127,7 +156,7 @@ func (r *gormAlarmRepository) CountAlarmsForNotification(ctx context.Context, in } // ListAlarmsForNotification 查询满足发送告警消息条件的活跃告警列表 -func (r *gormAlarmRepository) ListAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]int) ([]models.ActiveAlarm, error) { +func (r *gormAlarmRepository) ListAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint) ([]models.ActiveAlarm, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "ListAlarmsForNotification") var results []models.ActiveAlarm @@ -148,7 +177,7 @@ func (r *gormAlarmRepository) ListAlarmsForNotification(ctx context.Context, int } // buildNotificationBaseQuery 负责组合 Group A 和 Group B 的逻辑 -func (r *gormAlarmRepository) buildNotificationBaseQuery(tx *gorm.DB, intervalByLevel map[models.SeverityLevel]int) *gorm.DB { +func (r *gormAlarmRepository) buildNotificationBaseQuery(tx *gorm.DB, intervalByLevel map[models.SeverityLevel]uint) *gorm.DB { // 1. 获取所有配置的 Level 列表 configuredLevels := make([]models.SeverityLevel, 0, len(intervalByLevel)) @@ -208,7 +237,7 @@ func (r *gormAlarmRepository) buildGroupAClause(tx *gorm.DB, configuredLevels [] // buildGroupBClause 构造 Group B 的 WHERE 语句和参数列表。 // 针对 Level 存在配置的告警,使用“间隔发送”逻辑。 -func (r *gormAlarmRepository) buildGroupBClause(tx *gorm.DB, intervalByLevel map[models.SeverityLevel]int, configuredLevels []models.SeverityLevel) *gorm.DB { +func (r *gormAlarmRepository) buildGroupBClause(tx *gorm.DB, intervalByLevel map[models.SeverityLevel]uint, configuredLevels []models.SeverityLevel) *gorm.DB { now := time.Now() // B.1. 构造 Level IN 子句 From 2ad1ae4f40b0034a6d45b808a61da2da56169998 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sat, 8 Nov 2025 18:33:07 +0800 Subject: [PATCH 16/35] =?UTF-8?q?=E5=88=9B=E5=BB=BAPlanNameAlarmNotificati?= =?UTF-8?q?on=20=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 +- internal/core/component_initializers.go | 7 +- internal/core/data_initializer.go | 146 +++++++++++++++------- internal/infra/config/config.go | 10 ++ 4 files changed, 119 insertions(+), 47 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 7398c59..bf7f650 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -130,4 +130,5 @@ 1. 定义告警表和告警历史表 2. 重构部分枚举, 让models包不依赖其他项目中的包 -3. 创建仓库层对象(不包含方法) \ No newline at end of file +3. 创建仓库层对象(不包含方法) +4. 实现告警发送任务 \ No newline at end of file diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index 79316bb..25ff3fb 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -160,7 +160,12 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr ) // 任务工厂 - taskFactory := task.NewTaskFactory(logs.AddCompName(baseCtx, "TaskFactory"), infra.repos.sensorDataRepo, infra.repos.deviceRepo, generalDeviceService) + taskFactory := task.NewTaskFactory(logs.AddCompName(baseCtx, "TaskFactory"), + infra.repos.sensorDataRepo, + infra.repos.deviceRepo, + infra.repos.alarmRepo, + generalDeviceService, + infra.notifyService) // 计划任务管理器 analysisPlanTaskManager := plan.NewAnalysisPlanTaskManager(logs.AddCompName(baseCtx, "AnalysisPlanTaskManager"), infra.repos.planRepo, infra.repos.pendingTaskRepo, infra.repos.executionLogRepo) diff --git a/internal/core/data_initializer.go b/internal/core/data_initializer.go index 5a4e93d..c5ffe06 100644 --- a/internal/core/data_initializer.go +++ b/internal/core/data_initializer.go @@ -10,8 +10,10 @@ import ( ) const ( - // PlanNameTimedFullDataCollection 是定时全量数据采集计划的名称 - PlanNameTimedFullDataCollection = "定时全量数据采集" + // PlanNamePeriodicSystemHealthCheck 是周期性系统健康检查计划的名称 + PlanNamePeriodicSystemHealthCheck = "周期性系统健康检查" + // PlanNameAlarmNotification 是告警通知发送计划的名称 + PlanNameAlarmNotification = "告警通知发送" ) // initializeState 在应用启动时准备其初始数据状态。 @@ -48,13 +50,11 @@ func (app *Application) initializeState(ctx context.Context) error { } // initializeSystemPlans 确保预定义的系统计划在数据库中存在并保持最新。 +// 它通过调用各个独立的计划初始化方法来完成此操作。 func (app *Application) initializeSystemPlans(ctx context.Context) error { appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializeSystemPlans") logger.Info("开始检查并更新预定义的系统计划...") - // 动态构建预定义计划列表 - predefinedSystemPlans := app.getPredefinedSystemPlans() - // 1. 获取所有已存在的系统计划 existingPlans, _, err := app.Infra.repos.planRepo.ListPlans(appCtx, repository.ListPlansOptions{ PlanType: repository.PlanTypeFilterSystem, @@ -69,49 +69,23 @@ func (app *Application) initializeSystemPlans(ctx context.Context) error { existingPlanMap[existingPlans[i].Name] = &existingPlans[i] } - // 3. 遍历预定义的计划列表 - for i := range predefinedSystemPlans { - predefinedPlan := &predefinedSystemPlans[i] // 获取可修改的指针 + // 3. 调用独立的初始化方法来处理每个系统计划 + if err := app.initializePeriodicSystemHealthCheckPlan(appCtx, existingPlanMap); err != nil { + return err // 如果任何一个计划初始化失败,则立即返回错误 + } - if foundExistingPlan, ok := existingPlanMap[predefinedPlan.Name]; ok { - // 如果计划存在,则进行无差别更新 - logger.Infof("预定义计划 '%s' 已存在,正在进行无差别更新...", predefinedPlan.Name) - - // 将数据库中已存在的计划的ID和运行时状态字段赋值给预定义计划 - predefinedPlan.ID = foundExistingPlan.ID - predefinedPlan.ExecuteCount = foundExistingPlan.ExecuteCount - - // 1. 使用 UpdatePlanMetadataAndStructure 来更新计划的元数据和关联任务 - // 这会处理 Name, Description, ExecutionType, ExecuteNum, CronExpression, ContentType - // 并且最重要的是,它会正确处理 Tasks 的增删改,确保任务列表与 predefinedPlan.Tasks 完全同步 - if err := app.Infra.repos.planRepo.UpdatePlanMetadataAndStructure(appCtx, predefinedPlan); err != nil { - return fmt.Errorf("更新预定义计划 '%s' 的元数据和结构失败: %w", predefinedPlan.Name, err) - } - - // 2. 接着使用 UpdatePlan 来更新所有顶层字段,包括 PlanType 和 Status - // 由于任务已经在上一步正确同步,此步不会导致任务冗余 - if err := app.Infra.repos.planRepo.UpdatePlan(appCtx, predefinedPlan); err != nil { - return fmt.Errorf("更新预定义计划 '%s' 的所有顶层字段失败: %w", predefinedPlan.Name, err) - } - - logger.Infof("成功更新预定义计划 '%s'。", predefinedPlan.Name) - } else { - // 如果计划不存在, 则创建 - logger.Infof("预定义计划 '%s' 不存在,正在创建...", predefinedPlan.Name) - if err := app.Infra.repos.planRepo.CreatePlan(appCtx, predefinedPlan); err != nil { - return fmt.Errorf("创建预定义计划 '%s' 失败: %w", predefinedPlan.Name, err) - } else { - logger.Infof("成功创建预定义计划 '%s'。", predefinedPlan.Name) - } - } + if err := app.initializeAlarmNotificationPlan(appCtx, existingPlanMap); err != nil { + return err } logger.Info("预定义系统计划检查完成。") return nil } -// getPredefinedSystemPlans 返回一个基于当前配置的预定义系统计划列表。 -func (app *Application) getPredefinedSystemPlans() []models.Plan { +// initializePeriodicSystemHealthCheckPlan 负责初始化 "周期性系统健康检查" 计划。 +// 它会根据当前配置动态构建计划,并决定是创建新计划还是更新现有计划。 +func (app *Application) initializePeriodicSystemHealthCheckPlan(ctx context.Context, existingPlanMap map[string]*models.Plan) error { + appCtx, logger := logs.Trace(ctx, app.Ctx, "initializePeriodicSystemHealthCheckPlan") // 根据配置创建定时全量采集计划 interval := app.Config.Collection.Interval @@ -119,9 +93,9 @@ func (app *Application) getPredefinedSystemPlans() []models.Plan { interval = 1 // 确保间隔至少为1分钟 } cronExpression := fmt.Sprintf("*/%d * * * *", interval) - timedCollectionPlan := models.Plan{ - Name: PlanNameTimedFullDataCollection, - Description: fmt.Sprintf("这是一个系统预定义的计划, 每 %d 分钟自动触发一次全量数据采集。", app.Config.Collection.Interval), + predefinedPlan := &models.Plan{ + Name: PlanNamePeriodicSystemHealthCheck, + Description: fmt.Sprintf("这是一个系统预定义的计划, 每 %d 分钟自动触发一次全量数据采集, 并进行阈值校验告警。", app.Config.Collection.Interval), PlanType: models.PlanTypeSystem, ExecutionType: models.PlanExecutionTypeAutomatic, CronExpression: cronExpression, @@ -137,7 +111,89 @@ func (app *Application) getPredefinedSystemPlans() []models.Plan { }, } - return []models.Plan{timedCollectionPlan} + if foundExistingPlan, ok := existingPlanMap[predefinedPlan.Name]; ok { + // 如果计划存在,则进行无差别更新 + logger.Infof("预定义计划 '%s' 已存在,正在进行无差别更新...", predefinedPlan.Name) + + // 将数据库中已存在的计划的ID和运行时状态字段赋值给预定义计划 + predefinedPlan.ID = foundExistingPlan.ID + predefinedPlan.ExecuteCount = foundExistingPlan.ExecuteCount + + // 1. 使用 UpdatePlanMetadataAndStructure 来更新计划的元数据和关联任务 + // 这会处理 Name, Description, ExecutionType, ExecuteNum, CronExpression, ContentType + // 并且最重要的是,它会正确处理 Tasks 的增删改,确保任务列表与 predefinedPlan.Tasks 完全同步 + if err := app.Infra.repos.planRepo.UpdatePlanMetadataAndStructure(appCtx, predefinedPlan); err != nil { + return fmt.Errorf("更新预定义计划 '%s' 的元数据和结构失败: %w", predefinedPlan.Name, err) + } + + // 2. 接着使用 UpdatePlan 来更新所有顶层字段,包括 PlanType 和 Status + // 由于任务已经在上一步正确同步,此步不会导致任务冗余 + if err := app.Infra.repos.planRepo.UpdatePlan(appCtx, predefinedPlan); err != nil { + return fmt.Errorf("更新预定义计划 '%s' 的所有顶层字段失败: %w", predefinedPlan.Name, err) + } + + logger.Infof("成功更新预定义计划 '%s'。", predefinedPlan.Name) + } else { + // 如果计划不存在, 则创建 + logger.Infof("预定义计划 '%s' 不存在,正在创建...", predefinedPlan.Name) + if err := app.Infra.repos.planRepo.CreatePlan(appCtx, predefinedPlan); err != nil { + return fmt.Errorf("创建预定义计划 '%s' 失败: %w", predefinedPlan.Name, err) + } else { + logger.Infof("成功创建预定义计划 '%s'。", predefinedPlan.Name) + } + } + return nil +} + +// initializeAlarmNotificationPlan 负责初始化 "告警通知发送" 计划。 +// 它确保系统中存在一个每分钟执行的、用于发送告警通知的预定义计划。 +func (app *Application) initializeAlarmNotificationPlan(ctx context.Context, existingPlanMap map[string]*models.Plan) error { + appCtx, logger := logs.Trace(ctx, app.Ctx, "initializeAlarmNotificationPlan") + + predefinedPlan := &models.Plan{ + Name: PlanNameAlarmNotification, + Description: "这是一个系统预定义的计划, 每分钟自动触发一次告警通知发送。", + PlanType: models.PlanTypeSystem, + ExecutionType: models.PlanExecutionTypeAutomatic, + CronExpression: "*/1 * * * *", // 每分钟执行一次 + Status: models.PlanStatusEnabled, + ContentType: models.PlanContentTypeTasks, + Tasks: []models.Task{ + { + Name: "告警通知发送", + Description: "发送所有待处理的告警通知", + ExecutionOrder: 1, + Type: models.TaskTypeAlarmNotification, + }, + }, + } + + if foundExistingPlan, ok := existingPlanMap[predefinedPlan.Name]; ok { + // 如果计划存在,则进行无差别更新 + logger.Infof("预定义计划 '%s' 已存在,正在进行无差别更新...", predefinedPlan.Name) + + predefinedPlan.ID = foundExistingPlan.ID + predefinedPlan.ExecuteCount = foundExistingPlan.ExecuteCount + + if err := app.Infra.repos.planRepo.UpdatePlanMetadataAndStructure(appCtx, predefinedPlan); err != nil { + return fmt.Errorf("更新预定义计划 '%s' 的元数据和结构失败: %w", predefinedPlan.Name, err) + } + + if err := app.Infra.repos.planRepo.UpdatePlan(appCtx, predefinedPlan); err != nil { + return fmt.Errorf("更新预定义计划 '%s' 的所有顶层字段失败: %w", predefinedPlan.Name, err) + } + + logger.Infof("成功更新预定义计划 '%s'。", predefinedPlan.Name) + } else { + // 如果计划不存在, 则创建 + logger.Infof("预定义计划 '%s' 不存在,正在创建...", predefinedPlan.Name) + if err := app.Infra.repos.planRepo.CreatePlan(appCtx, predefinedPlan); err != nil { + return fmt.Errorf("创建预定义计划 '%s' 失败: %w", predefinedPlan.Name, err) + } else { + logger.Infof("成功创建预定义计划 '%s'。", predefinedPlan.Name) + } + } + return nil } // initializePendingCollections 在应用启动时处理所有未完成的采集请求。 diff --git a/internal/infra/config/config.go b/internal/infra/config/config.go index 2579f3f..7700d44 100644 --- a/internal/infra/config/config.go +++ b/internal/infra/config/config.go @@ -230,6 +230,16 @@ type AlarmNotificationConfig struct { NotificationIntervals NotificationIntervalsConfig `yaml:"notification_intervals"` } +// NewConfig 创建并返回一个新的配置实例 +func NewConfig() *Config { + // 默认值可以在这里设置,但我们优先使用配置文件中的值 + return &Config{ + Collection: CollectionConfig{ + Interval: 1, // 默认为1分钟 + }, + } +} + // Load 从指定路径加载配置文件 func (c *Config) Load(path string) error { // 读取配置文件 From 26df3983924c5e9a596fed8cfd36d5dd2983672d Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sat, 8 Nov 2025 18:43:46 +0800 Subject: [PATCH 17/35] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 ++- internal/core/application.go | 5 ++++- internal/core/component_initializers.go | 23 ++++++++++++----------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index bf7f650..06c7319 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -131,4 +131,5 @@ 1. 定义告警表和告警历史表 2. 重构部分枚举, 让models包不依赖其他项目中的包 3. 创建仓库层对象(不包含方法) -4. 实现告警发送任务 \ No newline at end of file +4. 实现告警发送任务 +5. 实现告警通知发送计划 \ No newline at end of file diff --git a/internal/core/application.go b/internal/core/application.go index 7c5c3a3..1cc65c2 100644 --- a/internal/core/application.go +++ b/internal/core/application.go @@ -43,7 +43,10 @@ func NewApplication(configPath string) (*Application, error) { if err != nil { return nil, fmt.Errorf("初始化基础设施失败: %w", err) } - domain := initDomainServices(ctx, cfg, infra) + domain, err := initDomainServices(ctx, cfg, infra) + if err != nil { + return nil, fmt.Errorf("初始化领域服务失败: %w", err) + } appServices := initAppServices(ctx, infra, domain) // 3. 初始化 API 入口点 diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index 25ff3fb..8f83bf1 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -21,6 +21,7 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/infra/transport" "git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/lora" "git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/token" + "gorm.io/gorm" ) @@ -29,7 +30,6 @@ type Infrastructure struct { storage database.Storage repos *Repositories lora *LoraComponents - notifyService domain_notify.Service tokenGenerator token.Generator } @@ -47,18 +47,12 @@ func initInfrastructure(ctx context.Context, cfg *config.Config) (*Infrastructur return nil, err } - notifyService, err := initNotifyService(ctx, cfg.Notify, repos.userRepo, repos.notificationRepo) - if err != nil { - return nil, fmt.Errorf("初始化通知服务失败: %w", err) - } - tokenGenerator := token.NewTokenGenerator([]byte(cfg.App.JWTSecret)) return &Infrastructure{ storage: storage, repos: repos, lora: lora, - notifyService: notifyService, tokenGenerator: tokenGenerator, }, nil } @@ -131,12 +125,18 @@ type DomainServices struct { planExecutionManager plan.ExecutionManager analysisPlanTaskManager plan.AnalysisPlanTaskManager planService plan.Service + notifyService domain_notify.Service } // initDomainServices 初始化所有的领域服务。 -func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastructure) *DomainServices { +func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastructure) (*DomainServices, error) { baseCtx := context.Background() + notifyService, err := initNotifyService(ctx, cfg.Notify, infra.repos.userRepo, infra.repos.notificationRepo) + if err != nil { + return nil, fmt.Errorf("初始化通知服务失败: %w", err) + } + // 猪群管理相关 pigPenTransferManager := pig.NewPigPenTransferManager(logs.AddCompName(baseCtx, "PigPenTransferManager"), infra.repos.pigPenRepo, infra.repos.pigTransferLogRepo, infra.repos.pigBatchRepo) pigTradeManager := pig.NewPigTradeManager(logs.AddCompName(baseCtx, "PigTradeManager"), infra.repos.pigTradeRepo) @@ -165,7 +165,7 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr infra.repos.deviceRepo, infra.repos.alarmRepo, generalDeviceService, - infra.notifyService) + notifyService) // 计划任务管理器 analysisPlanTaskManager := plan.NewAnalysisPlanTaskManager(logs.AddCompName(baseCtx, "AnalysisPlanTaskManager"), infra.repos.planRepo, infra.repos.pendingTaskRepo, infra.repos.executionLogRepo) @@ -206,7 +206,8 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr taskFactory: taskFactory, planExecutionManager: planExecutionManager, planService: planService, - } + notifyService: notifyService, + }, nil } // AppServices 聚合了所有的应用服务实例。 @@ -251,7 +252,7 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices ) auditService := service.NewAuditService(logs.AddCompName(baseCtx, "AuditService"), infra.repos.userActionLogRepo) planService := service.NewPlanService(logs.AddCompName(baseCtx, "AppPlanService"), domainServices.planService) - userService := service.NewUserService(logs.AddCompName(baseCtx, "UserService"), infra.repos.userRepo, infra.tokenGenerator, infra.notifyService) + userService := service.NewUserService(logs.AddCompName(baseCtx, "UserService"), infra.repos.userRepo, infra.tokenGenerator, domainServices.notifyService) return &AppServices{ pigFarmService: pigFarmService, From a35a9a103873cb7518c2548fd031fc9579090c5b Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sat, 8 Nov 2025 21:12:51 +0800 Subject: [PATCH 18/35] =?UTF-8?q?=E5=AE=9E=E7=8E=B0DeviceThresholdCheckTas?= =?UTF-8?q?k(=E9=99=A4Execute=E5=A4=96)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 4 +- .../task/device_threshold_check_task.go | 86 +++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 internal/domain/task/device_threshold_check_task.go diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 06c7319..232453d 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -67,7 +67,7 @@ 1. **任务类型**: 提供两种可供用户配置的阈值告警任务类型,分别对应 **区域主控** 和 **普通设备** 告警。 2. **参数结构**: - * **通用参数**: 任务参数将包含 `Value` (阈值) 和 `Operator` (操作符,如 `>` 或 `<`) 字段。 + * **通用参数**: 任务参数将包含 `Thresholds` (阈值) 和 `Operator` (操作符,如 `>` 或 `<`) 字段。 * **普通设备任务**: 配置包含 `DeviceID`。 * **区域主控任务**: 配置包含 `AreaControllerID`, `SensorType`, 以及一个 `ExcludeDeviceIDs` (需要排除的设备ID列表)。 @@ -132,4 +132,4 @@ 2. 重构部分枚举, 让models包不依赖其他项目中的包 3. 创建仓库层对象(不包含方法) 4. 实现告警发送任务 -5. 实现告警通知发送计划 \ No newline at end of file +5. 实现告警通知发送计划/全量采集计划改名 \ No newline at end of file diff --git a/internal/domain/task/device_threshold_check_task.go b/internal/domain/task/device_threshold_check_task.go new file mode 100644 index 0000000..53b4f46 --- /dev/null +++ b/internal/domain/task/device_threshold_check_task.go @@ -0,0 +1,86 @@ +package task + +import ( + "context" + "fmt" + "sync" + + "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" +) + +type Operator string + +const ( + OperatorLessThan Operator = "<" + OperatorLessThanOrEqualTo Operator = "<=" + OperatorGreaterThan Operator = ">" + OperatorGreaterThanOrEqualTo Operator = ">=" + OperatorEqualTo Operator = "=" + OperatorNotEqualTo Operator = "!=" +) + +type DeviceThresholdCheckParams struct { + DeviceID uint `json:"device_id"` // 设备ID + Thresholds float64 `json:"thresholds"` // 阈值 + Operator Operator `json:"operator"` // 操作符 +} + +type DeviceThresholdCheckTask struct { + ctx context.Context + onceParse sync.Once + + taskLog *models.TaskExecutionLog + params DeviceThresholdCheckParams +} + +func NewDeviceThresholdCheckTask(ctx context.Context, taskLog *models.TaskExecutionLog) plan.Task { + return &DeviceThresholdCheckTask{ + ctx: ctx, + taskLog: taskLog, + } +} + +func (d *DeviceThresholdCheckTask) Execute(ctx context.Context) error { + //TODO implement me + panic("implement me") +} + +// parseParameters 解析任务参数 +func (d *DeviceThresholdCheckTask) parseParameters(ctx context.Context) error { + logger := logs.TraceLogger(ctx, d.ctx, "parseParameters") + var err error + d.onceParse.Do(func() { + if d.taskLog.Task.Parameters == nil { + logger.Errorf("任务 %v: 缺少参数", d.taskLog.TaskID) + err = fmt.Errorf("任务 %v: 参数不全", d.taskLog.TaskID) + return + } + + var params DeviceThresholdCheckParams + err = d.taskLog.Task.ParseParameters(¶ms) + if err != nil { + logger.Errorf("任务 %v: 解析参数失败: %v", d.taskLog.TaskID, err) + err = fmt.Errorf("任务 %v: 解析参数失败: %v", d.taskLog.TaskID, err) + return + } + + d.params = params + + }) + return err +} + +func (d *DeviceThresholdCheckTask) OnFailure(ctx context.Context, executeErr error) { + logger := logs.TraceLogger(ctx, d.ctx, "OnFailure") + logger.Errorf("设备阈值检测任务执行失败, 任务ID: %v: 执行失败: %v", d.taskLog.TaskID, executeErr) +} + +func (d *DeviceThresholdCheckTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) { + taskCtx := logs.AddFuncName(ctx, d.ctx, "ResolveDeviceIDs") + if err := d.parseParameters(taskCtx); err != nil { + return nil, err + } + return []uint{d.params.DeviceID}, nil +} From e54c1bbc975b755b32dbc9f557dd63dce49fdd1e Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sun, 9 Nov 2025 21:37:35 +0800 Subject: [PATCH 19/35] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E9=98=88=E5=80=BC=E6=A3=80=E6=9F=A5=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 5 +- internal/domain/alarm/alarm_service.go | 114 ++++++++++++++- .../task/device_threshold_check_task.go | 133 ++++++++++++++++-- internal/infra/models/alarm.go | 40 +++++- internal/infra/models/sensor_data.go | 11 ++ internal/infra/repository/alarm_repository.go | 57 ++++++++ 6 files changed, 340 insertions(+), 20 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 232453d..58de104 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -125,6 +125,8 @@ ### TODO 1. 是否要加一个延时操作, 因为采集是异步的, 采集任务结束时不一定能拿到最新数据, 所以需要一个延时操作等待区域主控上传 +2. 统一一下区域主控的命名, 目前有AreaController和RegionalController, 不排除还有别的 +3. 将数据类型转为float32, 节约空间, float64精度有些浪费, float32小数点后6-7位足够了 # 实现记录 @@ -132,4 +134,5 @@ 2. 重构部分枚举, 让models包不依赖其他项目中的包 3. 创建仓库层对象(不包含方法) 4. 实现告警发送任务 -5. 实现告警通知发送计划/全量采集计划改名 \ No newline at end of file +5. 实现告警通知发送计划/全量采集计划改名 +6. 实现设备阈值检查任务 \ No newline at end of file diff --git a/internal/domain/alarm/alarm_service.go b/internal/domain/alarm/alarm_service.go index 06e6bee..8ddf3df 100644 --- a/internal/domain/alarm/alarm_service.go +++ b/internal/domain/alarm/alarm_service.go @@ -1,18 +1,124 @@ package alarm -import "context" +import ( + "context" + "errors" + "time" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" + + "gorm.io/gorm" +) // AlarmService 定义了告警领域服务接口。 type AlarmService interface { + // CreateAlarmIfNotExists 检查是否存在相同的活跃告警,如果不存在,则创建一条新的告警记录。 + // "相同"的定义是:SourceType, SourceID, 和 AlarmCode 都相同。 + CreateAlarmIfNotExists(ctx context.Context, newAlarm *models.ActiveAlarm) error + + // CloseAlarm 关闭一个活跃告警,将其归档到历史记录。 + // 如果指定的告警当前不活跃,则不执行任何操作并返回 nil。 + CloseAlarm(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode, resolveMethod string, resolvedBy *uint) error } // alarmService 是 AlarmService 接口的具体实现。 type alarmService struct { - ctx context.Context + ctx context.Context + alarmRepo repository.AlarmRepository + uow repository.UnitOfWork } -func NewAlarmService(ctx context.Context) AlarmService { +// NewAlarmService 创建一个新的 AlarmService 实例。 +func NewAlarmService(ctx context.Context, alarmRepo repository.AlarmRepository, uow repository.UnitOfWork) AlarmService { return &alarmService{ - ctx: ctx, + ctx: ctx, + alarmRepo: alarmRepo, + uow: uow, } } + +// CreateAlarmIfNotExists 实现了创建告警(如果不存在)的逻辑。 +func (s *alarmService) CreateAlarmIfNotExists(ctx context.Context, newAlarm *models.ActiveAlarm) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreateAlarmIfNotExists") + + // 1. 检查告警是否已处于活跃状态 + isActive, err := s.alarmRepo.IsAlarmActiveInUse(serviceCtx, newAlarm.SourceType, newAlarm.SourceID, newAlarm.AlarmCode) + if err != nil { + logger.Errorf("检查告警活跃状态时发生数据库错误: %v", err) + return err // 直接返回数据库错误 + } + + if isActive { + // 2. 如果已活跃,则记录日志并忽略 + logger.Infof("相同的告警已处于活跃状态,已忽略。来源: %s, ID: %d, 告警代码: %s", newAlarm.SourceType, newAlarm.SourceID, newAlarm.AlarmCode) + return nil + } + + // 3. 如果不活跃,则创建新告警 + logger.Infof("告警尚不活跃,正在创建新告警。来源: %s, ID: %d, 告警代码: %s", newAlarm.SourceType, newAlarm.SourceID, newAlarm.AlarmCode) + return s.alarmRepo.CreateActiveAlarm(serviceCtx, newAlarm) +} + +// CloseAlarm 实现了关闭告警并将其归档的逻辑。 +func (s *alarmService) CloseAlarm(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode, resolveMethod string, resolvedBy *uint) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "CloseAlarm") + + // 1. 在事务外进行快速只读检查,避免不必要的事务开销 + isActive, err := s.alarmRepo.IsAlarmActiveInUse(serviceCtx, sourceType, sourceID, alarmCode) + if err != nil { + logger.Errorf("关闭告警失败:预检查告警活跃状态失败: %v", err) + return err + } + + // 如果告警本就不活跃,则无需任何操作 + if !isActive { + return nil + } + + // 2. 确认告警存在后,再进入事务执行“移动”操作 + logger.Infof("检测到活跃告警,正在执行关闭和归档操作。来源: %s, ID: %d, 告警代码: %s", sourceType, sourceID, alarmCode) + return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { + // 在事务中再次查找,确保数据一致性并获取完整对象 + activeAlarm, err := s.alarmRepo.GetActiveAlarmByUniqueFieldsTx(serviceCtx, tx, sourceType, sourceID, alarmCode) + if err != nil { + // 此时如果没找到,可能在预检查和本事务之间已被其他进程关闭,同样视为正常 + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Infof("告警在事务开始前已被关闭,无需操作。") + return nil + } + logger.Errorf("关闭告警失败:在事务中查找活跃告警失败: %v", err) + return err + } + + // 创建历史告警记录 + historicalAlarm := &models.HistoricalAlarm{ + SourceType: activeAlarm.SourceType, + SourceID: activeAlarm.SourceID, + AlarmCode: activeAlarm.AlarmCode, + AlarmSummary: activeAlarm.AlarmSummary, + Level: activeAlarm.Level, + AlarmDetails: activeAlarm.AlarmDetails, + TriggerTime: activeAlarm.TriggerTime, + ResolveTime: time.Now(), + ResolveMethod: resolveMethod, + ResolvedBy: resolvedBy, + } + + // 在事务中插入历史告警 + if err := s.alarmRepo.CreateHistoricalAlarmTx(serviceCtx, tx, historicalAlarm); err != nil { + logger.Errorf("关闭告警失败:归档告警 %d 到历史表失败: %v", activeAlarm.ID, err) + return err + } + + // 在事务中删除活跃告警 + if err := s.alarmRepo.DeleteActiveAlarmTx(serviceCtx, tx, activeAlarm.ID); err != nil { + logger.Errorf("关闭告警失败:从活跃表删除告警 %d 失败: %v", activeAlarm.ID, err) + return err + } + + logger.Infof("告警 %d 已成功关闭并归档。", activeAlarm.ID) + return nil + }) +} diff --git a/internal/domain/task/device_threshold_check_task.go b/internal/domain/task/device_threshold_check_task.go index 53b4f46..365554b 100644 --- a/internal/domain/task/device_threshold_check_task.go +++ b/internal/domain/task/device_threshold_check_task.go @@ -4,10 +4,13 @@ import ( "context" "fmt" "sync" + "time" + "git.huangwc.com/pig/pig-farm-controller/internal/domain/alarm" "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" ) type Operator string @@ -22,9 +25,10 @@ const ( ) type DeviceThresholdCheckParams struct { - DeviceID uint `json:"device_id"` // 设备ID - Thresholds float64 `json:"thresholds"` // 阈值 - Operator Operator `json:"operator"` // 操作符 + DeviceID uint `json:"device_id"` // 设备ID + SensorType models.SensorType `json:"sensor_type"` // 传感器类型 + Thresholds float64 `json:"thresholds"` // 阈值 + Operator Operator `json:"operator"` // 操作符 } type DeviceThresholdCheckTask struct { @@ -33,18 +37,118 @@ type DeviceThresholdCheckTask struct { taskLog *models.TaskExecutionLog params DeviceThresholdCheckParams + + sensorDataRepo repository.SensorDataRepository + alarmService alarm.AlarmService } -func NewDeviceThresholdCheckTask(ctx context.Context, taskLog *models.TaskExecutionLog) plan.Task { +func NewDeviceThresholdCheckTask(ctx context.Context, taskLog *models.TaskExecutionLog, sensorDataRepo repository.SensorDataRepository, alarmService alarm.AlarmService) plan.Task { return &DeviceThresholdCheckTask{ - ctx: ctx, - taskLog: taskLog, + ctx: ctx, + taskLog: taskLog, + sensorDataRepo: sensorDataRepo, + alarmService: alarmService, } } func (d *DeviceThresholdCheckTask) Execute(ctx context.Context) error { - //TODO implement me - panic("implement me") + taskCtx, logger := logs.Trace(ctx, d.ctx, "Execute") + err := d.parseParameters(taskCtx) + if err != nil { + return err + } + + sensorData, err := d.sensorDataRepo.GetLatestSensorDataByDeviceIDAndSensorType(taskCtx, d.params.DeviceID, d.params.SensorType) + if err != nil { + logger.Errorf("任务 %v: 获取最新传感器数据失败: %v", d.taskLog.TaskID, err) + return fmt.Errorf("任务 %v: 获取最新传感器数据失败: %v", d.taskLog.TaskID, err) + } + + var currentValue float64 + var alarmCode models.AlarmCode + + switch d.params.SensorType { + case models.SensorTypeTemperature: + var data models.TemperatureData + if err := sensorData.ParseData(&data); err != nil { + return fmt.Errorf("任务 %v: 解析温度数据失败: %v", d.taskLog.TaskID, err) + } + currentValue = data.TemperatureCelsius + alarmCode = models.AlarmCodeTemperature + case models.SensorTypeHumidity: + var data models.HumidityData + if err := sensorData.ParseData(&data); err != nil { + return fmt.Errorf("任务 %v: 解析湿度数据失败: %v", d.taskLog.TaskID, err) + } + currentValue = data.HumidityPercent + alarmCode = models.AlarmCodeHumidity + case models.SensorTypeWeight: + var data models.WeightData + if err := sensorData.ParseData(&data); err != nil { + return fmt.Errorf("任务 %v: 解析重量数据失败: %v", d.taskLog.TaskID, err) + } + currentValue = data.WeightKilograms + alarmCode = models.AlarmCodeWeight + + default: + return fmt.Errorf("任务 %v: 不支持的传感器类型: %v", d.taskLog.TaskID, d.params.SensorType) + } + + // 阈值检查未通过 + isExceeded := !d.checkThreshold(currentValue, d.params.Operator, d.params.Thresholds) + + if isExceeded { + // 状态一:检查未通过,确保告警开启 + summary := fmt.Sprintf("设备 %d(%s) 不满足阈值条件 (%s %.2f)", d.params.DeviceID, d.params.SensorType, d.params.Operator, d.params.Thresholds) + details := fmt.Sprintf("当前检测值: %.2f", currentValue) + logger.Infof("任务 %v: %s。%s", d.taskLog.TaskID, summary, details) + + newAlarm := &models.ActiveAlarm{ + SourceType: models.AlarmSourceTypeDevice, + SourceID: d.params.DeviceID, + AlarmCode: alarmCode, + AlarmSummary: summary, + AlarmDetails: details, + Level: models.WarnLevel, // 默认告警等级,可后续根据需求调整 + TriggerTime: time.Now(), + } + + if err := d.alarmService.CreateAlarmIfNotExists(taskCtx, newAlarm); err != nil { + logger.Errorf("任务 %v: 创建告警失败: %v", d.taskLog.TaskID, err) + // 根据策略决定是否需要返回错误,这里选择不中断任务执行 + } + } else { + // 状态二:检查已通过,确保告警关闭 + resolveMethod := "系统自动解决:阈值恢复正常" + logger.Infof("任务 %v: 设备 %d 的 %s 阈值已恢复正常,正在尝试关闭告警。", d.taskLog.TaskID, d.params.DeviceID, d.params.SensorType) + + if err := d.alarmService.CloseAlarm(taskCtx, models.AlarmSourceTypeDevice, d.params.DeviceID, alarmCode, resolveMethod, nil); err != nil { + logger.Errorf("任务 %v: 关闭告警失败: %v", d.taskLog.TaskID, err) + // 根据策略决定是否需要返回错误,这里选择不中断任务执行 + } + } + + return nil +} + +// checkThreshold 校验当前值是否满足阈值条件 +func (d *DeviceThresholdCheckTask) checkThreshold(currentValue float64, operator Operator, threshold float64) bool { + switch operator { + case OperatorLessThan: + return currentValue < threshold + case OperatorLessThanOrEqualTo: + return currentValue <= threshold + case OperatorGreaterThan: + return currentValue > threshold + case OperatorGreaterThanOrEqualTo: + return currentValue >= threshold + case OperatorEqualTo: + return currentValue == threshold + case OperatorNotEqualTo: + return currentValue != threshold + default: + return false + } } // parseParameters 解析任务参数 @@ -66,6 +170,19 @@ func (d *DeviceThresholdCheckTask) parseParameters(ctx context.Context) error { return } + if params.SensorType == "" { + err = fmt.Errorf("任务 %v: 未配置传感器类型", d.taskLog.TaskID) + } + if params.Operator == "" { + err = fmt.Errorf("任务 %v: 缺少操作符", d.taskLog.TaskID) + } + if params.Thresholds == 0 { + err = fmt.Errorf("任务 %v: 未配置阈值", d.taskLog.TaskID) + } + if params.DeviceID == 0 { + err = fmt.Errorf("任务 %v: 未配置设备ID", d.taskLog.TaskID) + } + d.params = params }) diff --git a/internal/infra/models/alarm.go b/internal/infra/models/alarm.go index 53a40be..6ce054f 100644 --- a/internal/infra/models/alarm.go +++ b/internal/infra/models/alarm.go @@ -15,28 +15,51 @@ const ( AlarmSourceTypeSystem AlarmSourceType = "系统" ) +// AlarmCode 定义了标准化的告警类型标识 +type AlarmCode string + +const ( + // --- 设备相关告警 --- + AlarmCodeTemperature AlarmCode = "温度阈值" + AlarmCodeHumidity AlarmCode = "湿度阈值" + AlarmCodeWeight AlarmCode = "重量阈值" + AlarmCodeBatteryLevel AlarmCode = "电池电量阈值" + AlarmCodeSignalMetrics AlarmCode = "信号强度阈值" + AlarmCodeDeviceOffline AlarmCode = "设备离线" + + // --- 区域主控相关告警 --- + AlarmCodeAreaControllerOffline AlarmCode = "区域主控离线" + + // --- 系统相关告警 --- + // (可在此处预留或添加) +) + // ActiveAlarm 活跃告警 // 活跃告警会被更新(如忽略状态),因此保留 gorm.Model 以包含所有标准字段。 type ActiveAlarm struct { gorm.Model - SourceType AlarmSourceType `gorm:"type:varchar(50);not null;index;comment:告警来源类型" json:"source_type"` + SourceType AlarmSourceType `gorm:"type:varchar(50);not null;index:idx_alarm_uniqueness;comment:告警来源类型" json:"source_type"` // SourceID 告警来源ID,其具体含义取决于 SourceType 字段 (例如:设备ID, 区域主控ID, 猪栏ID)。 - SourceID uint `gorm:"not null;index;comment:告警来源ID" json:"source_id"` + SourceID uint `gorm:"not null;index:idx_alarm_uniqueness;comment:告警来源ID" json:"source_id"` + + // AlarmCode 是一个机器可读的、标准化的告警类型标识。 + // 它与 SourceType 和 SourceID 共同构成一个活跃告警的唯一标识。 + AlarmCode AlarmCode `gorm:"type:varchar(100);not null;index:idx_alarm_uniqueness;comment:告警代码" json:"alarm_code"` AlarmSummary string `gorm:"comment:告警简述" json:"alarm_summary"` - Level SeverityLevel `gorm:"type:varchar(10);not null;comment:严重性等级" json:"level"` + Level SeverityLevel `gorm:"type:varchar(10);not null;index:idx_notification_query;comment:严重性等级" json:"level"` AlarmDetails string `gorm:"comment:告警详细内容" json:"alarm_details"` TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` - // IsIgnored 是否被手动忽略 (Snooze) - IsIgnored bool `gorm:"default:false;comment:是否被手动忽略" json:"is_ignored"` + // IsIgnored 字段加入到专为通知查询优化的复合索引中 + IsIgnored bool `gorm:"default:false;index:idx_notification_query;comment:是否被手动忽略" json:"is_ignored"` // IgnoredUntil 忽略截止时间。在此时间之前,即使告警持续,也不会发送通知。 // 使用指针类型 *time.Time 来表示可为空的时间。 IgnoredUntil *time.Time `gorm:"comment:忽略截止时间" json:"ignored_until"` - // LastNotifiedAt 上次发送通知的时间。用于控制重复通知的频率。 - LastNotifiedAt *time.Time `gorm:"comment:上次发送通知时间" json:"last_notified_at"` + // LastNotifiedAt 字段加入到专为通知查询优化的复合索引中 + LastNotifiedAt *time.Time `gorm:"index:idx_notification_query;comment:上次发送通知时间" json:"last_notified_at"` } // TableName 指定 ActiveAlarm 结构体对应的数据库表名 @@ -55,6 +78,9 @@ type HistoricalAlarm struct { // SourceID 告警来源ID,其具体含义取决于 SourceType 字段 (例如:设备ID, 区域主控ID, 猪栏ID)。 SourceID uint `gorm:"not null;index;comment:告警来源ID" json:"source_id"` + // AlarmCode 是一个机器可读的、标准化的告警类型标识。 + AlarmCode AlarmCode `gorm:"type:varchar(100);not null;index;comment:告警代码" json:"alarm_code"` + AlarmSummary string `gorm:"comment:告警简述" json:"alarm_summary"` Level SeverityLevel `gorm:"type:varchar(10);not null;comment:严重性等级" json:"level"` AlarmDetails string `gorm:"comment:告警详细内容" json:"alarm_details"` diff --git a/internal/infra/models/sensor_data.go b/internal/infra/models/sensor_data.go index 4c50221..fe683a9 100644 --- a/internal/infra/models/sensor_data.go +++ b/internal/infra/models/sensor_data.go @@ -1,6 +1,8 @@ package models import ( + "encoding/json" + "errors" "time" "gorm.io/datatypes" @@ -68,3 +70,12 @@ type SensorData struct { func (SensorData) TableName() string { return "sensor_data" } + +// ParseData 解析 JSON 数据到一个具体的结构体中。 +// 调用方需要传入一个指向目标结构体实例的指针。 +func (s *SensorData) ParseData(v interface{}) error { + if s.Data == nil { + return errors.New("传感器数据为空,无法解析") + } + return json.Unmarshal(s.Data, v) +} diff --git a/internal/infra/repository/alarm_repository.go b/internal/infra/repository/alarm_repository.go index 8408ab5..e5dfba8 100644 --- a/internal/infra/repository/alarm_repository.go +++ b/internal/infra/repository/alarm_repository.go @@ -25,6 +25,21 @@ type ActiveAlarmListOptions struct { // AlarmRepository 定义了对告警模型的数据库操作接口 type AlarmRepository interface { + // CreateActiveAlarm 创建一条新的活跃告警记录 + CreateActiveAlarm(ctx context.Context, alarm *models.ActiveAlarm) error + + // IsAlarmActiveInUse 检查具有相同来源和告警代码的告警当前是否处于活跃表中 + IsAlarmActiveInUse(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode) (bool, error) + + // GetActiveAlarmByUniqueFieldsTx 在指定事务中根据唯一业务键获取一个活跃告警 + GetActiveAlarmByUniqueFieldsTx(ctx context.Context, tx *gorm.DB, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode) (*models.ActiveAlarm, error) + + // CreateHistoricalAlarmTx 在指定事务中创建一条历史告警记录 + CreateHistoricalAlarmTx(ctx context.Context, tx *gorm.DB, alarm *models.HistoricalAlarm) error + + // DeleteActiveAlarmTx 在指定事务中根据主键 ID 删除一个活跃告警 + DeleteActiveAlarmTx(ctx context.Context, tx *gorm.DB, id uint) error + // ListActiveAlarms 支持分页和过滤的活跃告警列表查询。 // 返回活跃告警列表、总记录数和错误。 ListActiveAlarms(ctx context.Context, opts ActiveAlarmListOptions, page, pageSize int) ([]models.ActiveAlarm, int64, error) @@ -59,6 +74,48 @@ func NewGormAlarmRepository(ctx context.Context, db *gorm.DB) AlarmRepository { } } +// CreateActiveAlarm 创建一条新的活跃告警记录 +func (r *gormAlarmRepository) CreateActiveAlarm(ctx context.Context, alarm *models.ActiveAlarm) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateActiveAlarm") + return r.db.WithContext(repoCtx).Create(alarm).Error +} + +// IsAlarmActive 检查具有相同来源和告警代码的告警当前是否处于活跃状态 +func (r *gormAlarmRepository) IsAlarmActiveInUse(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode) (bool, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "IsAlarmActiveInUse") + var count int64 + err := r.db.WithContext(repoCtx).Model(&models.ActiveAlarm{}). + Where("source_type = ? AND source_id = ? AND alarm_code = ?", sourceType, sourceID, alarmCode). + Count(&count).Error + if err != nil { + return false, err + } + return count > 0, nil +} + +// GetActiveAlarmByUniqueFieldsTx 在指定事务中根据唯一业务键获取一个活跃告警 +func (r *gormAlarmRepository) GetActiveAlarmByUniqueFieldsTx(ctx context.Context, tx *gorm.DB, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode) (*models.ActiveAlarm, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetActiveAlarmByUniqueFieldsTx") + var alarm models.ActiveAlarm + err := tx.WithContext(repoCtx). + Where("source_type = ? AND source_id = ? AND alarm_code = ?", sourceType, sourceID, alarmCode). + First(&alarm).Error + return &alarm, err +} + +// CreateHistoricalAlarmTx 在指定事务中创建一条历史告警记录 +func (r *gormAlarmRepository) CreateHistoricalAlarmTx(ctx context.Context, tx *gorm.DB, alarm *models.HistoricalAlarm) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateHistoricalAlarmTx") + return tx.WithContext(repoCtx).Create(alarm).Error +} + +// DeleteActiveAlarmTx 在指定事务中根据主键 ID 删除一个活跃告警 +func (r *gormAlarmRepository) DeleteActiveAlarmTx(ctx context.Context, tx *gorm.DB, id uint) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "DeleteActiveAlarmTx") + // 使用 Unscoped() 确保执行物理删除,而不是软删除 + return tx.WithContext(repoCtx).Unscoped().Delete(&models.ActiveAlarm{}, id).Error +} + // ListActiveAlarms 实现了分页和过滤查询活跃告警记录的功能 func (r *gormAlarmRepository) ListActiveAlarms(ctx context.Context, opts ActiveAlarmListOptions, page, pageSize int) ([]models.ActiveAlarm, int64, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "ListActiveAlarms") From 84fe20396b411e5d364e485e243a8acdd9b72efd Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sun, 9 Nov 2025 21:37:37 +0800 Subject: [PATCH 20/35] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E9=98=88=E5=80=BC=E6=A3=80=E6=9F=A5=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- project_structure.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/project_structure.txt b/project_structure.txt index 2b72491..44bcb3e 100644 --- a/project_structure.txt +++ b/project_structure.txt @@ -99,6 +99,7 @@ internal/domain/plan/plan_service.go internal/domain/plan/task.go internal/domain/task/alarm_notification_task.go internal/domain/task/delay_task.go +internal/domain/task/device_threshold_check_task.go internal/domain/task/full_collection_task.go internal/domain/task/release_feed_weight_task.go internal/domain/task/task.go From b94aa6137c7ae28a2e489103db4296019abe4b2c Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sun, 9 Nov 2025 22:34:05 +0800 Subject: [PATCH 21/35] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=BF=BD=E7=95=A5?= =?UTF-8?q?=E5=91=8A=E8=AD=A6=E5=92=8C=E5=8F=96=E6=B6=88=E5=BF=BD=E7=95=A5?= =?UTF-8?q?=E5=91=8A=E8=AD=A6=E6=8E=A5=E5=8F=A3=E5=8F=8A=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 +- internal/app/api/api.go | 5 + internal/app/api/router.go | 11 ++ .../alarm/threshold_alarm_controller.go | 109 ++++++++++++++++++ internal/app/dto/alarm_dto.go | 6 + .../app/service/threshold_alarm_service.go | 44 +++++++ internal/core/application.go | 1 + internal/core/component_initializers.go | 46 +++++--- internal/domain/alarm/alarm_service.go | 51 ++++++++ internal/infra/repository/alarm_repository.go | 29 ++++- project_structure.txt | 3 + 11 files changed, 292 insertions(+), 16 deletions(-) create mode 100644 internal/app/controller/alarm/threshold_alarm_controller.go create mode 100644 internal/app/dto/alarm_dto.go create mode 100644 internal/app/service/threshold_alarm_service.go diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 58de104..0d5e400 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -135,4 +135,5 @@ 3. 创建仓库层对象(不包含方法) 4. 实现告警发送任务 5. 实现告警通知发送计划/全量采集计划改名 -6. 实现设备阈值检查任务 \ No newline at end of file +6. 实现设备阈值检查任务 +7. 实现忽略告警和取消忽略告警接口及功能 \ No newline at end of file diff --git a/internal/app/api/api.go b/internal/app/api/api.go index a858f7c..43804b4 100644 --- a/internal/app/api/api.go +++ b/internal/app/api/api.go @@ -19,6 +19,7 @@ import ( "time" _ "git.huangwc.com/pig/pig-farm-controller/docs" // 引入 swag 生成的 docs + "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/alarm" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/device" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/health" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/management" @@ -53,6 +54,7 @@ type API struct { pigBatchController *management.PigBatchController // 猪群控制器实例 monitorController *monitor.Controller // 数据监控控制器实例 healthController *health.Controller // 健康检查控制器实例 + alarmController *alarm.ThresholdAlarmController // 阈值告警控制器 listenHandler webhook.ListenHandler // 设备上行事件监听器 analysisTaskManager *domain_plan.AnalysisPlanTaskManager // 计划触发器管理器实例 } @@ -69,6 +71,7 @@ func NewAPI(cfg config.ServerConfig, planService service.PlanService, userService service.UserService, auditService service.AuditService, + alarmService service.ThresholdAlarmService, tokenGenerator token.Generator, listenHandler webhook.ListenHandler, ) *API { @@ -106,6 +109,8 @@ func NewAPI(cfg config.ServerConfig, monitorController: monitor.NewController(logs.AddCompName(baseCtx, "MonitorController"), monitorService), // 在 NewAPI 中初始化健康检查控制器 healthController: health.NewController(logs.AddCompName(baseCtx, "HealthController")), + // 在 NewAPI 中初始化阈 + alarmController: alarm.NewThresholdAlarmController(logs.AddCompName(baseCtx, "ThresholdAlarmController"), alarmService), } api.setupRoutes() // 设置所有路由 diff --git a/internal/app/api/router.go b/internal/app/api/router.go index cc09cb4..b816f7d 100644 --- a/internal/app/api/router.go +++ b/internal/app/api/router.go @@ -187,6 +187,17 @@ func (a *API) setupRoutes() { monitorGroup.GET("/notifications", a.monitorController.ListNotifications) } logger.Debug("数据监控相关接口注册成功 (需要认证和审计)") + + // 告警相关路由组 + alarmGroup := authGroup.Group("/alarm") + { + thresholdGroup := alarmGroup.Group("/thresholds") + { + thresholdGroup.POST("/:id/snooze", a.alarmController.SnoozeThresholdAlarm) // 忽略阈值告警 + thresholdGroup.POST("/:id/cancel-snooze", a.alarmController.CancelSnoozeThresholdAlarm) // 取消忽略阈值告警 + } + } + logger.Debug("告警相关接口注册成功 (需要认证和审计)") } logger.Debug("所有接口注册成功") diff --git a/internal/app/controller/alarm/threshold_alarm_controller.go b/internal/app/controller/alarm/threshold_alarm_controller.go new file mode 100644 index 0000000..4310221 --- /dev/null +++ b/internal/app/controller/alarm/threshold_alarm_controller.go @@ -0,0 +1,109 @@ +package alarm + +import ( + "context" + "errors" + "strconv" + + "git.huangwc.com/pig/pig-farm-controller/internal/app/controller" + "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" + "git.huangwc.com/pig/pig-farm-controller/internal/app/service" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + + "github.com/labstack/echo/v4" + "gorm.io/gorm" +) + +// ThresholdAlarmController 阈值告警控制器,封装了所有与阈值告警配置相关的业务逻辑 +type ThresholdAlarmController struct { + ctx context.Context + thresholdAlarmService service.ThresholdAlarmService +} + +// NewThresholdAlarmController 创建一个新的阈值告警控制器实例 +func NewThresholdAlarmController( + ctx context.Context, + thresholdAlarmService service.ThresholdAlarmService, +) *ThresholdAlarmController { + return &ThresholdAlarmController{ + ctx: ctx, + thresholdAlarmService: thresholdAlarmService, + } +} + +// SnoozeThresholdAlarm godoc +// @Summary 忽略阈值告警 +// @Description 根据告警ID忽略一个活跃的阈值告警,或更新其忽略时间 +// @Tags 告警管理 +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param id path string true "告警ID" +// @Param request body dto.SnoozeAlarmRequest true "忽略告警请求体" +// @Success 200 {object} controller.Response "成功忽略告警" +// @Router /api/v1/alarm/threshold/{id}/snooze [post] +func (t *ThresholdAlarmController) SnoozeThresholdAlarm(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "SnoozeThresholdAlarm") + + const actionType = "忽略阈值告警" + alarmIDStr := ctx.Param("id") + + alarmID, err := strconv.ParseUint(alarmIDStr, 10, 64) + if err != nil { + logger.Errorf("%s: 无效的告警ID: %s", actionType, alarmIDStr) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的告警ID: "+alarmIDStr, actionType, "无效的告警ID", alarmIDStr) + } + + var req dto.SnoozeAlarmRequest + if err := ctx.Bind(&req); err != nil { + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) + } + + if err := t.thresholdAlarmService.SnoozeThresholdAlarm(reqCtx, uint(alarmID), req.DurationMinutes); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Warnf("%s: 告警不存在, ID: %d", actionType, alarmID) + return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "告警未找到", actionType, "告警不存在", alarmID) + } + logger.Errorf("%s: 服务层忽略告警失败: %v, ID: %d", actionType, err, alarmID) + return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "忽略告警失败: "+err.Error(), actionType, "服务层忽略告警失败", alarmID) + } + + logger.Infof("%s: 告警已成功忽略, ID: %d", actionType, alarmID) + return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "告警已成功忽略", nil, actionType, "告警已成功忽略", alarmID) +} + +// CancelSnoozeThresholdAlarm godoc +// @Summary 取消忽略阈值告警 +// @Description 根据告警ID取消对一个阈值告警的忽略状态 +// @Tags 告警管理 +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param id path string true "告警ID" +// @Success 200 {object} controller.Response "成功取消忽略告警" +// @Router /api/v1/alarm/threshold/{id}/cancel-snooze [post] +func (t *ThresholdAlarmController) CancelSnoozeThresholdAlarm(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "CancelSnoozeThresholdAlarm") + + const actionType = "取消忽略阈值告警" + alarmIDStr := ctx.Param("id") + + alarmID, err := strconv.ParseUint(alarmIDStr, 10, 64) + if err != nil { + logger.Errorf("%s: 无效的告警ID: %s", actionType, alarmIDStr) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的告警ID: "+alarmIDStr, actionType, "无效的告警ID", alarmIDStr) + } + + if err := t.thresholdAlarmService.CancelSnoozeThresholdAlarm(reqCtx, uint(alarmID)); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Warnf("%s: 告警不存在, ID: %d", actionType, alarmID) + return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "告警未找到", actionType, "告警不存在", alarmID) + } + logger.Errorf("%s: 服务层取消忽略告警失败: %v, ID: %d", actionType, err, alarmID) + return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "取消忽略告警失败: "+err.Error(), actionType, "服务层取消忽略告警失败", alarmID) + } + + logger.Infof("%s: 告警忽略状态已成功取消, ID: %d", actionType, alarmID) + return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "告警忽略状态已成功取消", nil, actionType, "告警忽略状态已成功取消", alarmID) +} diff --git a/internal/app/dto/alarm_dto.go b/internal/app/dto/alarm_dto.go new file mode 100644 index 0000000..1239cdf --- /dev/null +++ b/internal/app/dto/alarm_dto.go @@ -0,0 +1,6 @@ +package dto + +// SnoozeAlarmRequest 定义了忽略告警的请求体 +type SnoozeAlarmRequest struct { + DurationMinutes uint `json:"duration_minutes" validate:"required,min=1"` // 忽略时长,单位分钟 +} diff --git a/internal/app/service/threshold_alarm_service.go b/internal/app/service/threshold_alarm_service.go new file mode 100644 index 0000000..5825ffb --- /dev/null +++ b/internal/app/service/threshold_alarm_service.go @@ -0,0 +1,44 @@ +package service + +import ( + "context" + "time" + + domainAlarm "git.huangwc.com/pig/pig-farm-controller/internal/domain/alarm" // 引入领域层的 AlarmService + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" +) + +// ThresholdAlarmService 定义了阈值告警配置服务的接口。 +// 该服务负责管理阈值告警任务的配置,并将其与计划进行联动。 +type ThresholdAlarmService interface { + // SnoozeThresholdAlarm 忽略一个阈值告警,或更新其忽略时间。 + SnoozeThresholdAlarm(ctx context.Context, alarmID uint, durationMinutes uint) error + // CancelSnoozeThresholdAlarm 取消对一个阈值告警的忽略状态。 + CancelSnoozeThresholdAlarm(ctx context.Context, alarmID uint) error +} + +// thresholdAlarmService 是 ThresholdAlarmService 接口的具体实现。 +type thresholdAlarmService struct { + ctx context.Context + alarmService domainAlarm.AlarmService // 注入领域层的 AlarmService +} + +// NewThresholdAlarmService 创建一个新的 ThresholdAlarmService 实例。 +func NewThresholdAlarmService(ctx context.Context, alarmService domainAlarm.AlarmService) ThresholdAlarmService { + return &thresholdAlarmService{ + ctx: ctx, + alarmService: alarmService, + } +} + +// SnoozeThresholdAlarm 实现了忽略阈值告警的逻辑。 +func (s *thresholdAlarmService) SnoozeThresholdAlarm(ctx context.Context, alarmID uint, durationMinutes uint) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "SnoozeThresholdAlarm") + return s.alarmService.SnoozeAlarm(serviceCtx, alarmID, time.Duration(durationMinutes)*time.Minute) +} + +// CancelSnoozeThresholdAlarm 实现了取消忽略阈值告警的逻辑。 +func (s *thresholdAlarmService) CancelSnoozeThresholdAlarm(ctx context.Context, alarmID uint) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "CancelSnoozeThresholdAlarm") + return s.alarmService.CancelAlarmSnooze(serviceCtx, alarmID) +} diff --git a/internal/core/application.go b/internal/core/application.go index 1cc65c2..2dff55d 100644 --- a/internal/core/application.go +++ b/internal/core/application.go @@ -61,6 +61,7 @@ func NewApplication(configPath string) (*Application, error) { appServices.planService, appServices.userService, appServices.auditService, + appServices.thresholdAlarmService, infra.tokenGenerator, infra.lora.listenHandler, ) diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index 8f83bf1..17f3d64 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -7,6 +7,7 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/app/service" "git.huangwc.com/pig/pig-farm-controller/internal/app/webhook" + "git.huangwc.com/pig/pig-farm-controller/internal/domain/alarm" "git.huangwc.com/pig/pig-farm-controller/internal/domain/device" domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify" "git.huangwc.com/pig/pig-farm-controller/internal/domain/pig" @@ -126,6 +127,7 @@ type DomainServices struct { analysisPlanTaskManager plan.AnalysisPlanTaskManager planService plan.Service notifyService domain_notify.Service + alarmService alarm.AlarmService } // initDomainServices 初始化所有的领域服务。 @@ -196,6 +198,13 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr taskFactory, ) + // 告警服务 + alarmService := alarm.NewAlarmService( + logs.AddCompName(baseCtx, "AlarmService"), + infra.repos.alarmRepo, + infra.repos.unitOfWork, + ) + return &DomainServices{ pigPenTransferManager: pigPenTransferManager, pigTradeManager: pigTradeManager, @@ -207,18 +216,20 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr planExecutionManager: planExecutionManager, planService: planService, notifyService: notifyService, + alarmService: alarmService, }, nil } // AppServices 聚合了所有的应用服务实例。 type AppServices struct { - pigFarmService service.PigFarmService - pigBatchService service.PigBatchService - monitorService service.MonitorService - deviceService service.DeviceService - planService service.PlanService - userService service.UserService - auditService service.AuditService + pigFarmService service.PigFarmService + pigBatchService service.PigBatchService + monitorService service.MonitorService + deviceService service.DeviceService + planService service.PlanService + userService service.UserService + auditService service.AuditService + thresholdAlarmService service.ThresholdAlarmService } // initAppServices 初始化所有的应用服务。 @@ -254,14 +265,21 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices planService := service.NewPlanService(logs.AddCompName(baseCtx, "AppPlanService"), domainServices.planService) userService := service.NewUserService(logs.AddCompName(baseCtx, "UserService"), infra.repos.userRepo, infra.tokenGenerator, domainServices.notifyService) + // 初始化阈值告警服务 + thresholdAlarmService := service.NewThresholdAlarmService( + logs.AddCompName(baseCtx, "ThresholdAlarmService"), + domainServices.alarmService, + ) + return &AppServices{ - pigFarmService: pigFarmService, - pigBatchService: pigBatchService, - monitorService: monitorService, - deviceService: deviceService, - auditService: auditService, - planService: planService, - userService: userService, + pigFarmService: pigFarmService, + pigBatchService: pigBatchService, + monitorService: monitorService, + deviceService: deviceService, + auditService: auditService, + planService: planService, + userService: userService, + thresholdAlarmService: thresholdAlarmService, } } diff --git a/internal/domain/alarm/alarm_service.go b/internal/domain/alarm/alarm_service.go index 8ddf3df..661ba25 100644 --- a/internal/domain/alarm/alarm_service.go +++ b/internal/domain/alarm/alarm_service.go @@ -3,6 +3,7 @@ package alarm import ( "context" "errors" + "fmt" "time" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" @@ -21,6 +22,14 @@ type AlarmService interface { // CloseAlarm 关闭一个活跃告警,将其归档到历史记录。 // 如果指定的告警当前不活跃,则不执行任何操作并返回 nil。 CloseAlarm(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode, resolveMethod string, resolvedBy *uint) error + + // SnoozeAlarm 忽略一个活跃告警,或更新其忽略时间。 + // 如果告警不存在,将返回错误。 + SnoozeAlarm(ctx context.Context, alarmID uint, duration time.Duration) error + + // CancelAlarmSnooze 取消对一个告警的忽略状态。 + // 如果告警不存在,或本就未被忽略,不执行任何操作并返回 nil。 + CancelAlarmSnooze(ctx context.Context, alarmID uint) error } // alarmService 是 AlarmService 接口的具体实现。 @@ -122,3 +131,45 @@ func (s *alarmService) CloseAlarm(ctx context.Context, sourceType models.AlarmSo return nil }) } + +// SnoozeAlarm 忽略一个活跃告警,或更新其忽略时间。 +func (s *alarmService) SnoozeAlarm(ctx context.Context, alarmID uint, duration time.Duration) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "SnoozeAlarm") + + if duration <= 0 { + return errors.New("忽略时长必须为正数") + } + + ignoredUntil := time.Now().Add(duration) + err := s.alarmRepo.UpdateIgnoreStatus(serviceCtx, alarmID, true, &ignoredUntil) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Warnf("尝试忽略一个不存在的告警: %d", alarmID) + return fmt.Errorf("告警 %d 不存在", alarmID) + } + logger.Errorf("更新告警 %d 的忽略状态失败: %v", alarmID, err) + return err + } + + logger.Infof("告警 %d 已被成功忽略,持续时间: %v", alarmID, duration) + return nil +} + +// CancelAlarmSnooze 取消对一个告警的忽略状态。 +func (s *alarmService) CancelAlarmSnooze(ctx context.Context, alarmID uint) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "CancelAlarmSnooze") + + err := s.alarmRepo.UpdateIgnoreStatus(serviceCtx, alarmID, false, nil) + if err != nil { + // 如果告警本就不存在,这不是一个需要上报的错误 + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Infof("尝试取消忽略一个不存在的告警: %d,无需操作", alarmID) + return nil + } + logger.Errorf("取消告警 %d 的忽略状态失败: %v", alarmID, err) + return err + } + + logger.Infof("告警 %d 的忽略状态已被成功取消。", alarmID) + return nil +} diff --git a/internal/infra/repository/alarm_repository.go b/internal/infra/repository/alarm_repository.go index e5dfba8..2b91609 100644 --- a/internal/infra/repository/alarm_repository.go +++ b/internal/infra/repository/alarm_repository.go @@ -40,6 +40,9 @@ type AlarmRepository interface { // DeleteActiveAlarmTx 在指定事务中根据主键 ID 删除一个活跃告警 DeleteActiveAlarmTx(ctx context.Context, tx *gorm.DB, id uint) error + // UpdateIgnoreStatus 更新指定告警的忽略状态 + UpdateIgnoreStatus(ctx context.Context, id uint, isIgnored bool, ignoredUntil *time.Time) error + // ListActiveAlarms 支持分页和过滤的活跃告警列表查询。 // 返回活跃告警列表、总记录数和错误。 ListActiveAlarms(ctx context.Context, opts ActiveAlarmListOptions, page, pageSize int) ([]models.ActiveAlarm, int64, error) @@ -80,7 +83,7 @@ func (r *gormAlarmRepository) CreateActiveAlarm(ctx context.Context, alarm *mode return r.db.WithContext(repoCtx).Create(alarm).Error } -// IsAlarmActive 检查具有相同来源和告警代码的告警当前是否处于活跃状态 +// IsAlarmActiveInUse 检查具有相同来源和告警代码的告警当前是否处于活跃表中 func (r *gormAlarmRepository) IsAlarmActiveInUse(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode) (bool, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "IsAlarmActiveInUse") var count int64 @@ -116,6 +119,30 @@ func (r *gormAlarmRepository) DeleteActiveAlarmTx(ctx context.Context, tx *gorm. return tx.WithContext(repoCtx).Unscoped().Delete(&models.ActiveAlarm{}, id).Error } +// UpdateIgnoreStatus 更新指定告警的忽略状态 +func (r *gormAlarmRepository) UpdateIgnoreStatus(ctx context.Context, id uint, isIgnored bool, ignoredUntil *time.Time) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateIgnoreStatus") + updates := map[string]interface{}{ + "is_ignored": isIgnored, + "ignored_until": ignoredUntil, + } + + result := r.db.WithContext(repoCtx). + Model(&models.ActiveAlarm{}). + Where("id = ?", id). + Updates(updates) + + if result.Error != nil { + return result.Error + } + + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + + return nil +} + // ListActiveAlarms 实现了分页和过滤查询活跃告警记录的功能 func (r *gormAlarmRepository) ListActiveAlarms(ctx context.Context, opts ActiveAlarmListOptions, page, pageSize int) ([]models.ActiveAlarm, int64, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "ListActiveAlarms") diff --git a/project_structure.txt b/project_structure.txt index 44bcb3e..f56100c 100644 --- a/project_structure.txt +++ b/project_structure.txt @@ -40,6 +40,7 @@ go.mod go.sum internal/app/api/api.go internal/app/api/router.go +internal/app/controller/alarm/threshold_alarm_controller.go internal/app/controller/auth_utils.go internal/app/controller/device/device_controller.go internal/app/controller/health/health_controller.go @@ -53,6 +54,7 @@ internal/app/controller/monitor/monitor_controller.go internal/app/controller/plan/plan_controller.go internal/app/controller/response.go internal/app/controller/user/user_controller.go +internal/app/dto/alarm_dto.go internal/app/dto/device_converter.go internal/app/dto/device_dto.go internal/app/dto/monitor_converter.go @@ -73,6 +75,7 @@ internal/app/service/pig_batch_service.go internal/app/service/pig_farm_service.go internal/app/service/pig_service.go internal/app/service/plan_service.go +internal/app/service/threshold_alarm_service.go internal/app/service/user_service.go internal/app/webhook/chirp_stack.go internal/app/webhook/chirp_stack_types.go From 37f515d4a8ddd366bc4032bbd82f392bd3bd2dcf Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 13:41:26 +0800 Subject: [PATCH 22/35] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=B4=BB=E8=B7=83=E5=91=8A=E8=AD=A6=E5=92=8C?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/docs.go | 557 +++++++++++++++++- docs/swagger.json | 557 +++++++++++++++++- docs/swagger.yaml | 379 +++++++++++- internal/app/api/router.go | 2 + .../alarm/threshold_alarm_controller.go | 71 +++ internal/app/dto/alarm_converter.go | 65 ++ internal/app/dto/alarm_dto.go | 77 +++ internal/app/dto/dto.go | 10 + internal/app/dto/monitor_dto.go | 9 - .../app/service/threshold_alarm_service.go | 59 +- internal/core/component_initializers.go | 1 + internal/infra/repository/alarm_repository.go | 73 ++- project_structure.txt | 2 + 13 files changed, 1792 insertions(+), 70 deletions(-) create mode 100644 internal/app/dto/alarm_converter.go create mode 100644 internal/app/dto/dto.go diff --git a/docs/docs.go b/docs/docs.go index 7be6316..5147fa8 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -23,6 +23,339 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/api/v1/alarm/threshold/active-alarms": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据过滤条件和分页参数查询活跃告警列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "批量查询活跃告警", + "parameters": [ + { + "type": "string", + "description": "告警触发时间范围 - 结束时间", + "name": "end_time", + "in": "query" + }, + { + "type": "boolean", + "description": "按是否被忽略过滤", + "name": "is_ignored", + "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": "排序字段,例如 \"trigger_time DESC\"", + "name": "order_by", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "description": "按告警来源ID过滤", + "name": "source_id", + "in": "query" + }, + { + "enum": [ + "普通设备", + "区域主控", + "系统" + ], + "type": "string", + "x-enum-varnames": [ + "AlarmSourceTypeDevice", + "AlarmSourceTypeAreaController", + "AlarmSourceTypeSystem" + ], + "description": "按告警来源类型过滤", + "name": "source_type", + "in": "query" + }, + { + "type": "string", + "description": "告警触发时间范围 - 开始时间", + "name": "trigger_time", + "in": "query" + } + ], + "responses": { + "200": { + "description": "成功获取活跃告警列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.ListActiveAlarmResponse" + } + } + } + ] + } + } + } + } + }, + "/api/v1/alarm/threshold/historical-alarms": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据过滤条件和分页参数查询历史告警列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "批量查询历史告警", + "parameters": [ + { + "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": "排序字段,例如 \"trigger_time DESC\"", + "name": "order_by", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "告警解决时间范围 - 结束时间", + "name": "resolve_time_end", + "in": "query" + }, + { + "type": "string", + "description": "告警解决时间范围 - 开始时间", + "name": "resolve_time_start", + "in": "query" + }, + { + "type": "integer", + "description": "按告警来源ID过滤", + "name": "source_id", + "in": "query" + }, + { + "enum": [ + "普通设备", + "区域主控", + "系统" + ], + "type": "string", + "x-enum-varnames": [ + "AlarmSourceTypeDevice", + "AlarmSourceTypeAreaController", + "AlarmSourceTypeSystem" + ], + "description": "按告警来源类型过滤", + "name": "source_type", + "in": "query" + }, + { + "type": "string", + "description": "告警触发时间范围 - 结束时间", + "name": "trigger_time_end", + "in": "query" + }, + { + "type": "string", + "description": "告警触发时间范围 - 开始时间", + "name": "trigger_time_start", + "in": "query" + } + ], + "responses": { + "200": { + "description": "成功获取历史告警列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.ListHistoricalAlarmResponse" + } + } + } + ] + } + } + } + } + }, + "/api/v1/alarm/threshold/{id}/cancel-snooze": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据告警ID取消对一个阈值告警的忽略状态", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "取消忽略阈值告警", + "parameters": [ + { + "type": "string", + "description": "告警ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "成功取消忽略告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, + "/api/v1/alarm/threshold/{id}/snooze": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据告警ID忽略一个活跃的阈值告警,或更新其忽略时间", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "忽略阈值告警", + "parameters": [ + { + "type": "string", + "description": "告警ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "忽略告警请求体", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SnoozeAlarmRequest" + } + } + ], + "responses": { + "200": { + "description": "成功忽略告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, "/api/v1/area-controllers": { "get": { "security": [ @@ -4170,6 +4503,50 @@ const docTemplate = `{ "CodeServiceUnavailable" ] }, + "dto.ActiveAlarmDTO": { + "type": "object", + "properties": { + "alarm_code": { + "$ref": "#/definitions/models.AlarmCode" + }, + "alarm_details": { + "type": "string" + }, + "alarm_summary": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "ignored_until": { + "type": "string" + }, + "is_ignored": { + "type": "boolean" + }, + "last_notified_at": { + "type": "string" + }, + "level": { + "$ref": "#/definitions/models.SeverityLevel" + }, + "source_id": { + "type": "integer" + }, + "source_type": { + "$ref": "#/definitions/models.AlarmSourceType" + }, + "trigger_time": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, "dto.AreaControllerResponse": { "type": "object", "properties": { @@ -4589,6 +4966,58 @@ const docTemplate = `{ } } }, + "dto.HistoricalAlarmDTO": { + "type": "object", + "properties": { + "alarm_code": { + "$ref": "#/definitions/models.AlarmCode" + }, + "alarm_details": { + "type": "string" + }, + "alarm_summary": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "level": { + "$ref": "#/definitions/models.SeverityLevel" + }, + "resolve_method": { + "type": "string" + }, + "resolve_time": { + "type": "string" + }, + "resolved_by": { + "type": "integer" + }, + "source_id": { + "type": "integer" + }, + "source_type": { + "$ref": "#/definitions/models.AlarmSourceType" + }, + "trigger_time": { + "type": "string" + } + } + }, + "dto.ListActiveAlarmResponse": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.ActiveAlarmDTO" + } + }, + "pagination": { + "$ref": "#/definitions/dto.PaginationDTO" + } + } + }, "dto.ListDeviceCommandLogResponse": { "type": "object", "properties": { @@ -4617,6 +5046,20 @@ const docTemplate = `{ } } }, + "dto.ListHistoricalAlarmResponse": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.HistoricalAlarmDTO" + } + }, + "pagination": { + "$ref": "#/definitions/dto.PaginationDTO" + } + } + }, "dto.ListMedicationLogResponse": { "type": "object", "properties": { @@ -4984,13 +5427,13 @@ const docTemplate = `{ "type": "integer" }, "level": { - "$ref": "#/definitions/zapcore.Level" + "$ref": "#/definitions/models.SeverityLevel" }, "message": { "type": "string" }, "notifier_type": { - "$ref": "#/definitions/notify.NotifierType" + "$ref": "#/definitions/models.NotifierType" }, "status": { "$ref": "#/definitions/models.NotificationStatus" @@ -5882,7 +6325,7 @@ const docTemplate = `{ "description": "Type 指定要测试的通知渠道", "allOf": [ { - "$ref": "#/definitions/notify.NotifierType" + "$ref": "#/definitions/models.NotifierType" } ] } @@ -5911,6 +6354,19 @@ const docTemplate = `{ } } }, + "dto.SnoozeAlarmRequest": { + "type": "object", + "required": [ + "duration_minutes" + ], + "properties": { + "duration_minutes": { + "description": "忽略时长,单位分钟", + "type": "integer", + "minimum": 1 + } + } + }, "dto.SubPlanResponse": { "type": "object", "properties": { @@ -6412,6 +6868,40 @@ const docTemplate = `{ } } }, + "models.AlarmCode": { + "type": "string", + "enum": [ + "温度阈值", + "湿度阈值", + "重量阈值", + "电池电量阈值", + "信号强度阈值", + "设备离线", + "区域主控离线" + ], + "x-enum-varnames": [ + "AlarmCodeTemperature", + "AlarmCodeHumidity", + "AlarmCodeWeight", + "AlarmCodeBatteryLevel", + "AlarmCodeSignalMetrics", + "AlarmCodeDeviceOffline", + "AlarmCodeAreaControllerOffline" + ] + }, + "models.AlarmSourceType": { + "type": "string", + "enum": [ + "普通设备", + "区域主控", + "系统" + ], + "x-enum-varnames": [ + "AlarmSourceTypeDevice", + "AlarmSourceTypeAreaController", + "AlarmSourceTypeSystem" + ] + }, "models.AuditStatus": { "type": "string", "enum": [ @@ -6522,6 +7012,21 @@ const docTemplate = `{ "NotificationStatusSkipped" ] }, + "models.NotifierType": { + "type": "string", + "enum": [ + "邮件", + "企业微信", + "飞书", + "日志" + ], + "x-enum-varnames": [ + "NotifierTypeSMTP", + "NotifierTypeWeChat", + "NotifierTypeLark", + "NotifierTypeLog" + ] + }, "models.PenStatus": { "type": "string", "enum": [ @@ -6805,6 +7310,27 @@ const docTemplate = `{ "SensorTypeWeight" ] }, + "models.SeverityLevel": { + "type": "string", + "enum": [ + "Debug", + "Info", + "Warn", + "Error", + "DPanic", + "Panic", + "Fatal" + ], + "x-enum-varnames": [ + "DebugLevel", + "InfoLevel", + "WarnLevel", + "ErrorLevel", + "DPanicLevel", + "PanicLevel", + "FatalLevel" + ] + }, "models.StockLogSourceType": { "type": "string", "enum": [ @@ -6830,10 +7356,12 @@ const docTemplate = `{ "计划分析", "等待", "下料", - "全量采集" + "全量采集", + "告警通知" ], "x-enum-comments": { "TaskPlanAnalysis": "解析Plan的Task列表并添加到待执行队列的特殊任务", + "TaskTypeAlarmNotification": "告警通知任务", "TaskTypeFullCollection": "新增的全量采集任务", "TaskTypeReleaseFeedWeight": "下料口释放指定重量任务", "TaskTypeWaiting": "等待任务" @@ -6842,13 +7370,15 @@ const docTemplate = `{ "解析Plan的Task列表并添加到待执行队列的特殊任务", "等待任务", "下料口释放指定重量任务", - "新增的全量采集任务" + "新增的全量采集任务", + "告警通知任务" ], "x-enum-varnames": [ "TaskPlanAnalysis", "TaskTypeWaiting", "TaskTypeReleaseFeedWeight", - "TaskTypeFullCollection" + "TaskTypeFullCollection", + "TaskTypeAlarmNotification" ] }, "models.ValueDescriptor": { @@ -6867,21 +7397,6 @@ const docTemplate = `{ } } }, - "notify.NotifierType": { - "type": "string", - "enum": [ - "邮件", - "企业微信", - "飞书", - "日志" - ], - "x-enum-varnames": [ - "NotifierTypeSMTP", - "NotifierTypeWeChat", - "NotifierTypeLark", - "NotifierTypeLog" - ] - }, "repository.PlanTypeFilter": { "type": "string", "enum": [ diff --git a/docs/swagger.json b/docs/swagger.json index 9d1a871..8fa4a88 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -15,6 +15,339 @@ "version": "1.0" }, "paths": { + "/api/v1/alarm/threshold/active-alarms": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据过滤条件和分页参数查询活跃告警列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "批量查询活跃告警", + "parameters": [ + { + "type": "string", + "description": "告警触发时间范围 - 结束时间", + "name": "end_time", + "in": "query" + }, + { + "type": "boolean", + "description": "按是否被忽略过滤", + "name": "is_ignored", + "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": "排序字段,例如 \"trigger_time DESC\"", + "name": "order_by", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "description": "按告警来源ID过滤", + "name": "source_id", + "in": "query" + }, + { + "enum": [ + "普通设备", + "区域主控", + "系统" + ], + "type": "string", + "x-enum-varnames": [ + "AlarmSourceTypeDevice", + "AlarmSourceTypeAreaController", + "AlarmSourceTypeSystem" + ], + "description": "按告警来源类型过滤", + "name": "source_type", + "in": "query" + }, + { + "type": "string", + "description": "告警触发时间范围 - 开始时间", + "name": "trigger_time", + "in": "query" + } + ], + "responses": { + "200": { + "description": "成功获取活跃告警列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.ListActiveAlarmResponse" + } + } + } + ] + } + } + } + } + }, + "/api/v1/alarm/threshold/historical-alarms": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据过滤条件和分页参数查询历史告警列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "批量查询历史告警", + "parameters": [ + { + "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": "排序字段,例如 \"trigger_time DESC\"", + "name": "order_by", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "告警解决时间范围 - 结束时间", + "name": "resolve_time_end", + "in": "query" + }, + { + "type": "string", + "description": "告警解决时间范围 - 开始时间", + "name": "resolve_time_start", + "in": "query" + }, + { + "type": "integer", + "description": "按告警来源ID过滤", + "name": "source_id", + "in": "query" + }, + { + "enum": [ + "普通设备", + "区域主控", + "系统" + ], + "type": "string", + "x-enum-varnames": [ + "AlarmSourceTypeDevice", + "AlarmSourceTypeAreaController", + "AlarmSourceTypeSystem" + ], + "description": "按告警来源类型过滤", + "name": "source_type", + "in": "query" + }, + { + "type": "string", + "description": "告警触发时间范围 - 结束时间", + "name": "trigger_time_end", + "in": "query" + }, + { + "type": "string", + "description": "告警触发时间范围 - 开始时间", + "name": "trigger_time_start", + "in": "query" + } + ], + "responses": { + "200": { + "description": "成功获取历史告警列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.ListHistoricalAlarmResponse" + } + } + } + ] + } + } + } + } + }, + "/api/v1/alarm/threshold/{id}/cancel-snooze": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据告警ID取消对一个阈值告警的忽略状态", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "取消忽略阈值告警", + "parameters": [ + { + "type": "string", + "description": "告警ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "成功取消忽略告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, + "/api/v1/alarm/threshold/{id}/snooze": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据告警ID忽略一个活跃的阈值告警,或更新其忽略时间", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "忽略阈值告警", + "parameters": [ + { + "type": "string", + "description": "告警ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "忽略告警请求体", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SnoozeAlarmRequest" + } + } + ], + "responses": { + "200": { + "description": "成功忽略告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, "/api/v1/area-controllers": { "get": { "security": [ @@ -4162,6 +4495,50 @@ "CodeServiceUnavailable" ] }, + "dto.ActiveAlarmDTO": { + "type": "object", + "properties": { + "alarm_code": { + "$ref": "#/definitions/models.AlarmCode" + }, + "alarm_details": { + "type": "string" + }, + "alarm_summary": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "ignored_until": { + "type": "string" + }, + "is_ignored": { + "type": "boolean" + }, + "last_notified_at": { + "type": "string" + }, + "level": { + "$ref": "#/definitions/models.SeverityLevel" + }, + "source_id": { + "type": "integer" + }, + "source_type": { + "$ref": "#/definitions/models.AlarmSourceType" + }, + "trigger_time": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, "dto.AreaControllerResponse": { "type": "object", "properties": { @@ -4581,6 +4958,58 @@ } } }, + "dto.HistoricalAlarmDTO": { + "type": "object", + "properties": { + "alarm_code": { + "$ref": "#/definitions/models.AlarmCode" + }, + "alarm_details": { + "type": "string" + }, + "alarm_summary": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "level": { + "$ref": "#/definitions/models.SeverityLevel" + }, + "resolve_method": { + "type": "string" + }, + "resolve_time": { + "type": "string" + }, + "resolved_by": { + "type": "integer" + }, + "source_id": { + "type": "integer" + }, + "source_type": { + "$ref": "#/definitions/models.AlarmSourceType" + }, + "trigger_time": { + "type": "string" + } + } + }, + "dto.ListActiveAlarmResponse": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.ActiveAlarmDTO" + } + }, + "pagination": { + "$ref": "#/definitions/dto.PaginationDTO" + } + } + }, "dto.ListDeviceCommandLogResponse": { "type": "object", "properties": { @@ -4609,6 +5038,20 @@ } } }, + "dto.ListHistoricalAlarmResponse": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.HistoricalAlarmDTO" + } + }, + "pagination": { + "$ref": "#/definitions/dto.PaginationDTO" + } + } + }, "dto.ListMedicationLogResponse": { "type": "object", "properties": { @@ -4976,13 +5419,13 @@ "type": "integer" }, "level": { - "$ref": "#/definitions/zapcore.Level" + "$ref": "#/definitions/models.SeverityLevel" }, "message": { "type": "string" }, "notifier_type": { - "$ref": "#/definitions/notify.NotifierType" + "$ref": "#/definitions/models.NotifierType" }, "status": { "$ref": "#/definitions/models.NotificationStatus" @@ -5874,7 +6317,7 @@ "description": "Type 指定要测试的通知渠道", "allOf": [ { - "$ref": "#/definitions/notify.NotifierType" + "$ref": "#/definitions/models.NotifierType" } ] } @@ -5903,6 +6346,19 @@ } } }, + "dto.SnoozeAlarmRequest": { + "type": "object", + "required": [ + "duration_minutes" + ], + "properties": { + "duration_minutes": { + "description": "忽略时长,单位分钟", + "type": "integer", + "minimum": 1 + } + } + }, "dto.SubPlanResponse": { "type": "object", "properties": { @@ -6404,6 +6860,40 @@ } } }, + "models.AlarmCode": { + "type": "string", + "enum": [ + "温度阈值", + "湿度阈值", + "重量阈值", + "电池电量阈值", + "信号强度阈值", + "设备离线", + "区域主控离线" + ], + "x-enum-varnames": [ + "AlarmCodeTemperature", + "AlarmCodeHumidity", + "AlarmCodeWeight", + "AlarmCodeBatteryLevel", + "AlarmCodeSignalMetrics", + "AlarmCodeDeviceOffline", + "AlarmCodeAreaControllerOffline" + ] + }, + "models.AlarmSourceType": { + "type": "string", + "enum": [ + "普通设备", + "区域主控", + "系统" + ], + "x-enum-varnames": [ + "AlarmSourceTypeDevice", + "AlarmSourceTypeAreaController", + "AlarmSourceTypeSystem" + ] + }, "models.AuditStatus": { "type": "string", "enum": [ @@ -6514,6 +7004,21 @@ "NotificationStatusSkipped" ] }, + "models.NotifierType": { + "type": "string", + "enum": [ + "邮件", + "企业微信", + "飞书", + "日志" + ], + "x-enum-varnames": [ + "NotifierTypeSMTP", + "NotifierTypeWeChat", + "NotifierTypeLark", + "NotifierTypeLog" + ] + }, "models.PenStatus": { "type": "string", "enum": [ @@ -6797,6 +7302,27 @@ "SensorTypeWeight" ] }, + "models.SeverityLevel": { + "type": "string", + "enum": [ + "Debug", + "Info", + "Warn", + "Error", + "DPanic", + "Panic", + "Fatal" + ], + "x-enum-varnames": [ + "DebugLevel", + "InfoLevel", + "WarnLevel", + "ErrorLevel", + "DPanicLevel", + "PanicLevel", + "FatalLevel" + ] + }, "models.StockLogSourceType": { "type": "string", "enum": [ @@ -6822,10 +7348,12 @@ "计划分析", "等待", "下料", - "全量采集" + "全量采集", + "告警通知" ], "x-enum-comments": { "TaskPlanAnalysis": "解析Plan的Task列表并添加到待执行队列的特殊任务", + "TaskTypeAlarmNotification": "告警通知任务", "TaskTypeFullCollection": "新增的全量采集任务", "TaskTypeReleaseFeedWeight": "下料口释放指定重量任务", "TaskTypeWaiting": "等待任务" @@ -6834,13 +7362,15 @@ "解析Plan的Task列表并添加到待执行队列的特殊任务", "等待任务", "下料口释放指定重量任务", - "新增的全量采集任务" + "新增的全量采集任务", + "告警通知任务" ], "x-enum-varnames": [ "TaskPlanAnalysis", "TaskTypeWaiting", "TaskTypeReleaseFeedWeight", - "TaskTypeFullCollection" + "TaskTypeFullCollection", + "TaskTypeAlarmNotification" ] }, "models.ValueDescriptor": { @@ -6859,21 +7389,6 @@ } } }, - "notify.NotifierType": { - "type": "string", - "enum": [ - "邮件", - "企业微信", - "飞书", - "日志" - ], - "x-enum-varnames": [ - "NotifierTypeSMTP", - "NotifierTypeWeChat", - "NotifierTypeLark", - "NotifierTypeLog" - ] - }, "repository.PlanTypeFilter": { "type": "string", "enum": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 2392e19..ebfb500 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -53,6 +53,35 @@ definitions: - CodeConflict - CodeInternalError - CodeServiceUnavailable + dto.ActiveAlarmDTO: + properties: + alarm_code: + $ref: '#/definitions/models.AlarmCode' + alarm_details: + type: string + alarm_summary: + type: string + created_at: + type: string + id: + type: integer + ignored_until: + type: string + is_ignored: + type: boolean + last_notified_at: + type: string + level: + $ref: '#/definitions/models.SeverityLevel' + source_id: + type: integer + source_type: + $ref: '#/definitions/models.AlarmSourceType' + trigger_time: + type: string + updated_at: + type: string + type: object dto.AreaControllerResponse: properties: created_at: @@ -340,6 +369,40 @@ definitions: remarks: type: string type: object + dto.HistoricalAlarmDTO: + properties: + alarm_code: + $ref: '#/definitions/models.AlarmCode' + alarm_details: + type: string + alarm_summary: + type: string + id: + type: integer + level: + $ref: '#/definitions/models.SeverityLevel' + resolve_method: + type: string + resolve_time: + type: string + resolved_by: + type: integer + source_id: + type: integer + source_type: + $ref: '#/definitions/models.AlarmSourceType' + trigger_time: + type: string + type: object + dto.ListActiveAlarmResponse: + properties: + list: + items: + $ref: '#/definitions/dto.ActiveAlarmDTO' + type: array + pagination: + $ref: '#/definitions/dto.PaginationDTO' + type: object dto.ListDeviceCommandLogResponse: properties: list: @@ -358,6 +421,15 @@ definitions: pagination: $ref: '#/definitions/dto.PaginationDTO' type: object + dto.ListHistoricalAlarmResponse: + properties: + list: + items: + $ref: '#/definitions/dto.HistoricalAlarmDTO' + type: array + pagination: + $ref: '#/definitions/dto.PaginationDTO' + type: object dto.ListMedicationLogResponse: properties: list: @@ -600,11 +672,11 @@ definitions: id: type: integer level: - $ref: '#/definitions/zapcore.Level' + $ref: '#/definitions/models.SeverityLevel' message: type: string notifier_type: - $ref: '#/definitions/notify.NotifierType' + $ref: '#/definitions/models.NotifierType' status: $ref: '#/definitions/models.NotificationStatus' title: @@ -1199,7 +1271,7 @@ definitions: properties: type: allOf: - - $ref: '#/definitions/notify.NotifierType' + - $ref: '#/definitions/models.NotifierType' description: Type 指定要测试的通知渠道 required: - type @@ -1219,6 +1291,15 @@ definitions: time: type: string type: object + dto.SnoozeAlarmRequest: + properties: + duration_minutes: + description: 忽略时长,单位分钟 + minimum: 1 + type: integer + required: + - duration_minutes + type: object dto.SubPlanResponse: properties: child_plan: @@ -1558,6 +1639,34 @@ definitions: weight: type: number type: object + models.AlarmCode: + enum: + - 温度阈值 + - 湿度阈值 + - 重量阈值 + - 电池电量阈值 + - 信号强度阈值 + - 设备离线 + - 区域主控离线 + type: string + x-enum-varnames: + - AlarmCodeTemperature + - AlarmCodeHumidity + - AlarmCodeWeight + - AlarmCodeBatteryLevel + - AlarmCodeSignalMetrics + - AlarmCodeDeviceOffline + - AlarmCodeAreaControllerOffline + models.AlarmSourceType: + enum: + - 普通设备 + - 区域主控 + - 系统 + type: string + x-enum-varnames: + - AlarmSourceTypeDevice + - AlarmSourceTypeAreaController + - AlarmSourceTypeSystem models.AuditStatus: enum: - 成功 @@ -1646,6 +1755,18 @@ definitions: - NotificationStatusSuccess - NotificationStatusFailed - NotificationStatusSkipped + models.NotifierType: + enum: + - 邮件 + - 企业微信 + - 飞书 + - 日志 + type: string + x-enum-varnames: + - NotifierTypeSMTP + - NotifierTypeWeChat + - NotifierTypeLark + - NotifierTypeLog models.PenStatus: enum: - 空闲 @@ -1877,6 +1998,24 @@ definitions: - SensorTypeTemperature - SensorTypeHumidity - SensorTypeWeight + models.SeverityLevel: + enum: + - Debug + - Info + - Warn + - Error + - DPanic + - Panic + - Fatal + type: string + x-enum-varnames: + - DebugLevel + - InfoLevel + - WarnLevel + - ErrorLevel + - DPanicLevel + - PanicLevel + - FatalLevel models.StockLogSourceType: enum: - 采购入库 @@ -1899,9 +2038,11 @@ definitions: - 等待 - 下料 - 全量采集 + - 告警通知 type: string x-enum-comments: TaskPlanAnalysis: 解析Plan的Task列表并添加到待执行队列的特殊任务 + TaskTypeAlarmNotification: 告警通知任务 TaskTypeFullCollection: 新增的全量采集任务 TaskTypeReleaseFeedWeight: 下料口释放指定重量任务 TaskTypeWaiting: 等待任务 @@ -1910,11 +2051,13 @@ definitions: - 等待任务 - 下料口释放指定重量任务 - 新增的全量采集任务 + - 告警通知任务 x-enum-varnames: - TaskPlanAnalysis - TaskTypeWaiting - TaskTypeReleaseFeedWeight - TaskTypeFullCollection + - TaskTypeAlarmNotification models.ValueDescriptor: properties: multiplier: @@ -1926,18 +2069,6 @@ definitions: type: $ref: '#/definitions/models.SensorType' type: object - notify.NotifierType: - enum: - - 邮件 - - 企业微信 - - 飞书 - - 日志 - type: string - x-enum-varnames: - - NotifierTypeSMTP - - NotifierTypeWeChat - - NotifierTypeLark - - NotifierTypeLog repository.PlanTypeFilter: enum: - 所有任务 @@ -1987,6 +2118,224 @@ info: title: 猪场管理系统 API version: "1.0" paths: + /api/v1/alarm/threshold/{id}/cancel-snooze: + post: + consumes: + - application/json + description: 根据告警ID取消对一个阈值告警的忽略状态 + parameters: + - description: 告警ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: 成功取消忽略告警 + schema: + $ref: '#/definitions/controller.Response' + security: + - BearerAuth: [] + summary: 取消忽略阈值告警 + tags: + - 告警管理 + /api/v1/alarm/threshold/{id}/snooze: + post: + consumes: + - application/json + description: 根据告警ID忽略一个活跃的阈值告警,或更新其忽略时间 + parameters: + - description: 告警ID + in: path + name: id + required: true + type: string + - description: 忽略告警请求体 + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.SnoozeAlarmRequest' + produces: + - application/json + responses: + "200": + description: 成功忽略告警 + schema: + $ref: '#/definitions/controller.Response' + security: + - BearerAuth: [] + summary: 忽略阈值告警 + tags: + - 告警管理 + /api/v1/alarm/threshold/active-alarms: + get: + consumes: + - application/json + description: 根据过滤条件和分页参数查询活跃告警列表 + parameters: + - description: 告警触发时间范围 - 结束时间 + in: query + name: end_time + type: string + - description: 按是否被忽略过滤 + in: query + name: is_ignored + type: boolean + - 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: 排序字段,例如 "trigger_time DESC" + in: query + name: order_by + type: string + - in: query + name: page + type: integer + - in: query + name: page_size + type: integer + - description: 按告警来源ID过滤 + in: query + name: source_id + type: integer + - description: 按告警来源类型过滤 + enum: + - 普通设备 + - 区域主控 + - 系统 + in: query + name: source_type + type: string + x-enum-varnames: + - AlarmSourceTypeDevice + - AlarmSourceTypeAreaController + - AlarmSourceTypeSystem + - description: 告警触发时间范围 - 开始时间 + in: query + name: trigger_time + type: string + produces: + - application/json + responses: + "200": + description: 成功获取活跃告警列表 + schema: + allOf: + - $ref: '#/definitions/controller.Response' + - properties: + data: + $ref: '#/definitions/dto.ListActiveAlarmResponse' + type: object + security: + - BearerAuth: [] + summary: 批量查询活跃告警 + tags: + - 告警管理 + /api/v1/alarm/threshold/historical-alarms: + get: + consumes: + - application/json + description: 根据过滤条件和分页参数查询历史告警列表 + parameters: + - 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: 排序字段,例如 "trigger_time DESC" + in: query + name: order_by + type: string + - in: query + name: page + type: integer + - in: query + name: page_size + type: integer + - description: 告警解决时间范围 - 结束时间 + in: query + name: resolve_time_end + type: string + - description: 告警解决时间范围 - 开始时间 + in: query + name: resolve_time_start + type: string + - description: 按告警来源ID过滤 + in: query + name: source_id + type: integer + - description: 按告警来源类型过滤 + enum: + - 普通设备 + - 区域主控 + - 系统 + in: query + name: source_type + type: string + x-enum-varnames: + - AlarmSourceTypeDevice + - AlarmSourceTypeAreaController + - AlarmSourceTypeSystem + - description: 告警触发时间范围 - 结束时间 + in: query + name: trigger_time_end + type: string + - description: 告警触发时间范围 - 开始时间 + in: query + name: trigger_time_start + type: string + produces: + - application/json + responses: + "200": + description: 成功获取历史告警列表 + schema: + allOf: + - $ref: '#/definitions/controller.Response' + - properties: + data: + $ref: '#/definitions/dto.ListHistoricalAlarmResponse' + type: object + security: + - BearerAuth: [] + summary: 批量查询历史告警 + tags: + - 告警管理 /api/v1/area-controllers: get: description: 获取系统中所有区域主控的列表 diff --git a/internal/app/api/router.go b/internal/app/api/router.go index b816f7d..5929249 100644 --- a/internal/app/api/router.go +++ b/internal/app/api/router.go @@ -195,6 +195,8 @@ func (a *API) setupRoutes() { { thresholdGroup.POST("/:id/snooze", a.alarmController.SnoozeThresholdAlarm) // 忽略阈值告警 thresholdGroup.POST("/:id/cancel-snooze", a.alarmController.CancelSnoozeThresholdAlarm) // 取消忽略阈值告警 + thresholdGroup.GET("/active-alarms", a.alarmController.ListActiveAlarms) // 获取活跃告警 + thresholdGroup.GET("/historical-alarms", a.alarmController.ListHistoricalAlarms) // 获取历史告警 } } logger.Debug("告警相关接口注册成功 (需要认证和审计)") diff --git a/internal/app/controller/alarm/threshold_alarm_controller.go b/internal/app/controller/alarm/threshold_alarm_controller.go index 4310221..06167f1 100644 --- a/internal/app/controller/alarm/threshold_alarm_controller.go +++ b/internal/app/controller/alarm/threshold_alarm_controller.go @@ -9,6 +9,7 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/app/service" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "github.com/labstack/echo/v4" "gorm.io/gorm" @@ -107,3 +108,73 @@ func (t *ThresholdAlarmController) CancelSnoozeThresholdAlarm(ctx echo.Context) logger.Infof("%s: 告警忽略状态已成功取消, ID: %d", actionType, alarmID) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "告警忽略状态已成功取消", nil, actionType, "告警忽略状态已成功取消", alarmID) } + +// ListActiveAlarms godoc +// @Summary 批量查询活跃告警 +// @Description 根据过滤条件和分页参数查询活跃告警列表 +// @Tags 告警管理 +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param query query dto.ListActiveAlarmRequest true "查询参数" +// @Success 200 {object} controller.Response{data=dto.ListActiveAlarmResponse} "成功获取活跃告警列表" +// @Router /api/v1/alarm/threshold/active-alarms [get] +func (t *ThresholdAlarmController) ListActiveAlarms(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "ListActiveAlarms") + + const actionType = "批量查询活跃告警" + var req dto.ListActiveAlarmRequest + 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.ListActiveAlarms(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) +} + +// ListHistoricalAlarms godoc +// @Summary 批量查询历史告警 +// @Description 根据过滤条件和分页参数查询历史告警列表 +// @Tags 告警管理 +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param query query dto.ListHistoricalAlarmRequest true "查询参数" +// @Success 200 {object} controller.Response{data=dto.ListHistoricalAlarmResponse} "成功获取历史告警列表" +// @Router /api/v1/alarm/threshold/historical-alarms [get] +func (t *ThresholdAlarmController) ListHistoricalAlarms(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "ListHistoricalAlarms") + + const actionType = "批量查询历史告警" + var req dto.ListHistoricalAlarmRequest + 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.ListHistoricalAlarms(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) +} diff --git a/internal/app/dto/alarm_converter.go b/internal/app/dto/alarm_converter.go new file mode 100644 index 0000000..90a3b33 --- /dev/null +++ b/internal/app/dto/alarm_converter.go @@ -0,0 +1,65 @@ +package dto + +import ( + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" +) + +// NewListActiveAlarmResponse 从模型数据创建活跃告警列表响应 DTO +func NewListActiveAlarmResponse(data []models.ActiveAlarm, total int64, page, pageSize int) *ListActiveAlarmResponse { + dtos := make([]ActiveAlarmDTO, len(data)) + for i, item := range data { + dtos[i] = ActiveAlarmDTO{ + ID: item.ID, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + SourceType: item.SourceType, + SourceID: item.SourceID, + AlarmCode: item.AlarmCode, + AlarmSummary: item.AlarmSummary, + Level: item.Level, + AlarmDetails: item.AlarmDetails, + TriggerTime: item.TriggerTime, + IsIgnored: item.IsIgnored, + IgnoredUntil: item.IgnoredUntil, + LastNotifiedAt: item.LastNotifiedAt, + } + } + + return &ListActiveAlarmResponse{ + List: dtos, + Pagination: PaginationDTO{ + Total: total, + Page: page, + PageSize: pageSize, + }, + } +} + +// NewListHistoricalAlarmResponse 从模型数据创建历史告警列表响应 DTO +func NewListHistoricalAlarmResponse(data []models.HistoricalAlarm, total int64, page, pageSize int) *ListHistoricalAlarmResponse { + dtos := make([]HistoricalAlarmDTO, len(data)) + for i, item := range data { + dtos[i] = HistoricalAlarmDTO{ + ID: item.ID, + SourceType: item.SourceType, + SourceID: item.SourceID, + AlarmCode: item.AlarmCode, + AlarmSummary: item.AlarmSummary, + Level: item.Level, + AlarmDetails: item.AlarmDetails, + TriggerTime: item.TriggerTime, + ResolveTime: item.ResolveTime, + ResolveMethod: item.ResolveMethod, + ResolvedBy: item.ResolvedBy, + } + } + + return &ListHistoricalAlarmResponse{ + List: dtos, + Pagination: PaginationDTO{ + Total: total, + Page: page, + PageSize: pageSize, + }, + } +} diff --git a/internal/app/dto/alarm_dto.go b/internal/app/dto/alarm_dto.go index 1239cdf..7cb2b09 100644 --- a/internal/app/dto/alarm_dto.go +++ b/internal/app/dto/alarm_dto.go @@ -1,6 +1,83 @@ package dto +import ( + "time" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" +) + // SnoozeAlarmRequest 定义了忽略告警的请求体 type SnoozeAlarmRequest struct { DurationMinutes uint `json:"duration_minutes" validate:"required,min=1"` // 忽略时长,单位分钟 } + +// ListActiveAlarmRequest 定义了获取活跃告警列表的请求参数 +type ListActiveAlarmRequest struct { + Page int `json:"page" query:"page"` + PageSize int `json:"page_size" query:"page_size"` + SourceType *models.AlarmSourceType `json:"source_type" query:"source_type"` // 按告警来源类型过滤 + SourceID *uint `json:"source_id" query:"source_id"` // 按告警来源ID过滤 + Level *models.SeverityLevel `json:"level" query:"level"` // 按告警严重性等级过滤 + IsIgnored *bool `json:"is_ignored" query:"is_ignored"` // 按是否被忽略过滤 + TriggerTime *time.Time `json:"trigger_time" query:"trigger_time"` // 告警触发时间范围 - 开始时间 + EndTime *time.Time `json:"end_time" query:"end_time"` // 告警触发时间范围 - 结束时间 + OrderBy string `json:"order_by" query:"order_by"` // 排序字段,例如 "trigger_time DESC" +} + +// ActiveAlarmDTO 是用于API响应的活跃告警结构 +type ActiveAlarmDTO struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + SourceType models.AlarmSourceType `json:"source_type"` + SourceID uint `json:"source_id"` + AlarmCode models.AlarmCode `json:"alarm_code"` + AlarmSummary string `json:"alarm_summary"` + Level models.SeverityLevel `json:"level"` + AlarmDetails string `json:"alarm_details"` + TriggerTime time.Time `json:"trigger_time"` + IsIgnored bool `json:"is_ignored"` + IgnoredUntil *time.Time `json:"ignored_until"` + LastNotifiedAt *time.Time `json:"last_notified_at"` +} + +// ListActiveAlarmResponse 是获取活跃告警列表的响应结构 +type ListActiveAlarmResponse struct { + List []ActiveAlarmDTO `json:"list"` + Pagination PaginationDTO `json:"pagination"` +} + +// ListHistoricalAlarmRequest 定义了获取历史告警列表的请求参数 +type ListHistoricalAlarmRequest struct { + Page int `json:"page" query:"page"` + PageSize int `json:"page_size" query:"page_size"` + SourceType *models.AlarmSourceType `json:"source_type" query:"source_type"` // 按告警来源类型过滤 + SourceID *uint `json:"source_id" query:"source_id"` // 按告警来源ID过滤 + Level *models.SeverityLevel `json:"level" query:"level"` // 按告警严重性等级过滤 + TriggerTimeStart *time.Time `json:"trigger_time_start" query:"trigger_time_start"` // 告警触发时间范围 - 开始时间 + TriggerTimeEnd *time.Time `json:"trigger_time_end" query:"trigger_time_end"` // 告警触发时间范围 - 结束时间 + ResolveTimeStart *time.Time `json:"resolve_time_start" query:"resolve_time_start"` // 告警解决时间范围 - 开始时间 + ResolveTimeEnd *time.Time `json:"resolve_time_end" query:"resolve_time_end"` // 告警解决时间范围 - 结束时间 + OrderBy string `json:"order_by" query:"order_by"` // 排序字段,例如 "trigger_time DESC" +} + +// HistoricalAlarmDTO 是用于API响应的历史告警结构 +type HistoricalAlarmDTO struct { + ID uint `json:"id"` + SourceType models.AlarmSourceType `json:"source_type"` + SourceID uint `json:"source_id"` + AlarmCode models.AlarmCode `json:"alarm_code"` + AlarmSummary string `json:"alarm_summary"` + Level models.SeverityLevel `json:"level"` + AlarmDetails string `json:"alarm_details"` + TriggerTime time.Time `json:"trigger_time"` + ResolveTime time.Time `json:"resolve_time"` + ResolveMethod string `json:"resolve_method"` + ResolvedBy *uint `json:"resolved_by"` +} + +// ListHistoricalAlarmResponse 是获取历史告警列表的响应结构 +type ListHistoricalAlarmResponse struct { + List []HistoricalAlarmDTO `json:"list"` + Pagination PaginationDTO `json:"pagination"` +} diff --git a/internal/app/dto/dto.go b/internal/app/dto/dto.go new file mode 100644 index 0000000..ac02148 --- /dev/null +++ b/internal/app/dto/dto.go @@ -0,0 +1,10 @@ +package dto + +// --- General --- + +// PaginationDTO 定义了分页信息的标准结构 +type PaginationDTO struct { + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"page_size"` +} diff --git a/internal/app/dto/monitor_dto.go b/internal/app/dto/monitor_dto.go index bfb135f..0eeee8f 100644 --- a/internal/app/dto/monitor_dto.go +++ b/internal/app/dto/monitor_dto.go @@ -7,15 +7,6 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) -// --- General --- - -// PaginationDTO 定义了分页信息的标准结构 -type PaginationDTO struct { - Total int64 `json:"total"` - Page int `json:"page"` - PageSize int `json:"page_size"` -} - // --- SensorData --- // ListSensorDataRequest 定义了获取传感器数据列表的请求参数 diff --git a/internal/app/service/threshold_alarm_service.go b/internal/app/service/threshold_alarm_service.go index 5825ffb..f841f6e 100644 --- a/internal/app/service/threshold_alarm_service.go +++ b/internal/app/service/threshold_alarm_service.go @@ -4,8 +4,10 @@ import ( "context" "time" - domainAlarm "git.huangwc.com/pig/pig-farm-controller/internal/domain/alarm" // 引入领域层的 AlarmService + "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" + domainAlarm "git.huangwc.com/pig/pig-farm-controller/internal/domain/alarm" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" ) // ThresholdAlarmService 定义了阈值告警配置服务的接口。 @@ -15,19 +17,25 @@ type ThresholdAlarmService interface { SnoozeThresholdAlarm(ctx context.Context, alarmID uint, durationMinutes uint) error // CancelSnoozeThresholdAlarm 取消对一个阈值告警的忽略状态。 CancelSnoozeThresholdAlarm(ctx context.Context, alarmID uint) error + // ListActiveAlarms 批量查询活跃告警。 + ListActiveAlarms(ctx context.Context, req *dto.ListActiveAlarmRequest) (*dto.ListActiveAlarmResponse, error) + // ListHistoricalAlarms 批量查询历史告警。 + ListHistoricalAlarms(ctx context.Context, req *dto.ListHistoricalAlarmRequest) (*dto.ListHistoricalAlarmResponse, error) } // thresholdAlarmService 是 ThresholdAlarmService 接口的具体实现。 type thresholdAlarmService struct { ctx context.Context - alarmService domainAlarm.AlarmService // 注入领域层的 AlarmService + alarmService domainAlarm.AlarmService + alarmRepo repository.AlarmRepository } // NewThresholdAlarmService 创建一个新的 ThresholdAlarmService 实例。 -func NewThresholdAlarmService(ctx context.Context, alarmService domainAlarm.AlarmService) ThresholdAlarmService { +func NewThresholdAlarmService(ctx context.Context, alarmService domainAlarm.AlarmService, alarmRepo repository.AlarmRepository) ThresholdAlarmService { return &thresholdAlarmService{ ctx: ctx, alarmService: alarmService, + alarmRepo: alarmRepo, } } @@ -42,3 +50,48 @@ func (s *thresholdAlarmService) CancelSnoozeThresholdAlarm(ctx context.Context, serviceCtx := logs.AddFuncName(ctx, s.ctx, "CancelSnoozeThresholdAlarm") return s.alarmService.CancelAlarmSnooze(serviceCtx, alarmID) } + +// ListActiveAlarms 实现了批量查询活跃告警的逻辑。 +func (s *thresholdAlarmService) ListActiveAlarms(ctx context.Context, req *dto.ListActiveAlarmRequest) (*dto.ListActiveAlarmResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListActiveAlarms") + + opts := repository.ActiveAlarmListOptions{ + SourceType: req.SourceType, + SourceID: req.SourceID, + Level: req.Level, + IsIgnored: req.IsIgnored, + TriggerTime: req.TriggerTime, + EndTime: req.EndTime, + OrderBy: req.OrderBy, + } + + alarms, total, err := s.alarmRepo.ListActiveAlarms(serviceCtx, opts, req.Page, req.PageSize) + if err != nil { + return nil, err + } + + return dto.NewListActiveAlarmResponse(alarms, total, req.Page, req.PageSize), nil +} + +// ListHistoricalAlarms 实现了批量查询历史告警的逻辑。 +func (s *thresholdAlarmService) ListHistoricalAlarms(ctx context.Context, req *dto.ListHistoricalAlarmRequest) (*dto.ListHistoricalAlarmResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListHistoricalAlarms") + + opts := repository.HistoricalAlarmListOptions{ + SourceType: req.SourceType, + SourceID: req.SourceID, + Level: req.Level, + TriggerTimeStart: req.TriggerTimeStart, + TriggerTimeEnd: req.TriggerTimeEnd, + ResolveTimeStart: req.ResolveTimeStart, + ResolveTimeEnd: req.ResolveTimeEnd, + OrderBy: req.OrderBy, + } + + alarms, total, err := s.alarmRepo.ListHistoricalAlarms(serviceCtx, opts, req.Page, req.PageSize) + if err != nil { + return nil, err + } + + return dto.NewListHistoricalAlarmResponse(alarms, total, req.Page, req.PageSize), nil +} diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index 17f3d64..af77dec 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -269,6 +269,7 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices thresholdAlarmService := service.NewThresholdAlarmService( logs.AddCompName(baseCtx, "ThresholdAlarmService"), domainServices.alarmService, + infra.repos.alarmRepo, ) return &AppServices{ diff --git a/internal/infra/repository/alarm_repository.go b/internal/infra/repository/alarm_repository.go index 2b91609..f513778 100644 --- a/internal/infra/repository/alarm_repository.go +++ b/internal/infra/repository/alarm_repository.go @@ -23,6 +23,18 @@ type ActiveAlarmListOptions struct { OrderBy string // 排序字段,例如 "trigger_time DESC" } +// HistoricalAlarmListOptions 定义了查询历史告警列表时的可选参数 +type HistoricalAlarmListOptions struct { + SourceType *models.AlarmSourceType // 按告警来源类型过滤 + SourceID *uint // 按告警来源ID过滤 + Level *models.SeverityLevel // 按告警严重性等级过滤 + TriggerTimeStart *time.Time // 告警触发时间范围 - 开始时间 + TriggerTimeEnd *time.Time // 告警触发时间范围 - 结束时间 + ResolveTimeStart *time.Time // 告警解决时间范围 - 开始时间 (对应 models.HistoricalAlarm.ResolveTime) + ResolveTimeEnd *time.Time // 告警解决时间范围 - 结束时间 (对应 models.HistoricalAlarm.ResolveTime) + OrderBy string // 排序字段,例如 "trigger_time DESC" +} + // AlarmRepository 定义了对告警模型的数据库操作接口 type AlarmRepository interface { // CreateActiveAlarm 创建一条新的活跃告警记录 @@ -47,6 +59,10 @@ type AlarmRepository interface { // 返回活跃告警列表、总记录数和错误。 ListActiveAlarms(ctx context.Context, opts ActiveAlarmListOptions, page, pageSize int) ([]models.ActiveAlarm, int64, error) + // ListHistoricalAlarms 支持分页和过滤的历史告警列表查询。 + // 返回历史告警列表、总记录数和错误。 + ListHistoricalAlarms(ctx context.Context, opts HistoricalAlarmListOptions, page, pageSize int) ([]models.HistoricalAlarm, int64, error) + // UpdateAlarmNotificationStatus 显式更新告警的通知相关状态字段。 // lastNotifiedAt: 传入具体的发送时间。 // isIgnored: 告警新的忽略状态。 @@ -148,7 +164,7 @@ func (r *gormAlarmRepository) ListActiveAlarms(ctx context.Context, opts ActiveA repoCtx := logs.AddFuncName(ctx, r.ctx, "ListActiveAlarms") // --- 校验分页参数 --- if page <= 0 || pageSize <= 0 { - return nil, 0, ErrInvalidPagination // 复用已定义的错误 + return nil, 0, ErrInvalidPagination } var results []models.ActiveAlarm @@ -195,6 +211,61 @@ func (r *gormAlarmRepository) ListActiveAlarms(ctx context.Context, opts ActiveA return results, total, err } +// ListHistoricalAlarms 实现了分页和过滤查询历史告警记录的功能 +func (r *gormAlarmRepository) ListHistoricalAlarms(ctx context.Context, opts HistoricalAlarmListOptions, page, pageSize int) ([]models.HistoricalAlarm, int64, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListHistoricalAlarms") + // --- 校验分页参数 --- + if page <= 0 || pageSize <= 0 { + return nil, 0, ErrInvalidPagination + } + + var results []models.HistoricalAlarm + var total int64 + + query := r.db.WithContext(repoCtx).Model(&models.HistoricalAlarm{}) + + // --- 应用过滤条件 --- + if opts.SourceType != nil { + query = query.Where("source_type = ?", *opts.SourceType) + } + if opts.SourceID != nil { + query = query.Where("source_id = ?", *opts.SourceID) + } + if opts.Level != nil { + query = query.Where("level = ?", *opts.Level) + } + if opts.TriggerTimeStart != nil { + query = query.Where("trigger_time >= ?", *opts.TriggerTimeStart) + } + if opts.TriggerTimeEnd != nil { + query = query.Where("trigger_time <= ?", *opts.TriggerTimeEnd) + } + if opts.ResolveTimeStart != nil { // 修改字段名 + query = query.Where("resolve_time >= ?", *opts.ResolveTimeStart) // 修改查询字段名 + } + if opts.ResolveTimeEnd != nil { // 修改字段名 + query = query.Where("resolve_time <= ?", *opts.ResolveTimeEnd) // 修改查询字段名 + } + + // --- 计算总数 --- + if err := query.Count(&total).Error; err != nil { + return nil, 0, err + } + + // --- 应用排序条件 --- + orderBy := "trigger_time DESC" // 默认按触发时间倒序 + if opts.OrderBy != "" { + orderBy = opts.OrderBy + } + query = query.Order(orderBy) + + // --- 分页 --- + offset := (page - 1) * pageSize + err := query.Limit(pageSize).Offset(offset).Find(&results).Error + + return results, total, err +} + func (r *gormAlarmRepository) UpdateAlarmNotificationStatus(ctx context.Context, alarmID uint, lastNotifiedAt time.Time, isIgnored bool, ignoredUntil *time.Time) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateAlarmNotificationStatus") diff --git a/project_structure.txt b/project_structure.txt index f56100c..5f20ab7 100644 --- a/project_structure.txt +++ b/project_structure.txt @@ -54,9 +54,11 @@ internal/app/controller/monitor/monitor_controller.go internal/app/controller/plan/plan_controller.go internal/app/controller/response.go internal/app/controller/user/user_controller.go +internal/app/dto/alarm_converter.go internal/app/dto/alarm_dto.go internal/app/dto/device_converter.go internal/app/dto/device_dto.go +internal/app/dto/dto.go internal/app/dto/monitor_converter.go internal/app/dto/monitor_dto.go internal/app/dto/notification_converter.go From ca8e5ff867008cf60a928bc326700438ecfb3eba Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 13:41:54 +0800 Subject: [PATCH 23/35] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=B4=BB=E8=B7=83=E5=91=8A=E8=AD=A6=E5=92=8C?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 0d5e400..b0eae8e 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -136,4 +136,5 @@ 4. 实现告警发送任务 5. 实现告警通知发送计划/全量采集计划改名 6. 实现设备阈值检查任务 -7. 实现忽略告警和取消忽略告警接口及功能 \ No newline at end of file +7. 实现忽略告警和取消忽略告警接口及功能 +8. 实现列表查询活跃告警和历史告警 \ No newline at end of file From 19d55eb09b534fa6f609cb5082c4b25836fd4545 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 14:11:39 +0800 Subject: [PATCH 24/35] =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E6=97=B6=E5=81=A5=E5=BA=B7=E8=AE=A1=E5=88=92=E8=B0=83?= =?UTF-8?q?=E6=95=B4(=E5=8C=85=E6=8B=AC=E5=A2=9E=E5=8A=A0=E5=BB=B6?= =?UTF-8?q?=E6=97=B6=E4=BB=BB=E5=8A=A1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 +- internal/core/data_initializer.go | 51 ++++++++++++++++--- .../domain/task/release_feed_weight_task.go | 3 +- internal/infra/models/plan.go | 14 +++++ 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index b0eae8e..621db95 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -137,4 +137,5 @@ 5. 实现告警通知发送计划/全量采集计划改名 6. 实现设备阈值检查任务 7. 实现忽略告警和取消忽略告警接口及功能 -8. 实现列表查询活跃告警和历史告警 \ No newline at end of file +8. 实现列表查询活跃告警和历史告警 +9. 系统初始化时健康计划调整(包括增加延时任务) \ No newline at end of file diff --git a/internal/core/data_initializer.go b/internal/core/data_initializer.go index c5ffe06..32edd75 100644 --- a/internal/core/data_initializer.go +++ b/internal/core/data_initializer.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "git.huangwc.com/pig/pig-farm-controller/internal/domain/task" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" @@ -93,6 +94,47 @@ func (app *Application) initializePeriodicSystemHealthCheckPlan(ctx context.Cont interval = 1 // 确保间隔至少为1分钟 } cronExpression := fmt.Sprintf("*/%d * * * *", interval) + + // 定义预设的全量采集任务 + fullCollectionTask := models.Task{ + Name: "全量采集", + Description: "触发一次全量数据采集", + ExecutionOrder: 1, + Type: models.TaskTypeFullCollection, + } + + // 定义预设的延时任务 + delayParams := task.DelayTaskParams{DelayDuration: 10} // 延时10秒 + delayTask := models.Task{ + Name: "延时任务", + Description: "系统预设延时任务,用于错峰处理", + ExecutionOrder: 2, + Type: models.TaskTypeWaiting, + } + err := delayTask.SaveParameters(delayParams) + if err != nil { + return fmt.Errorf("序列化延时任务参数失败: %w", err) + } + + // 构建新的任务列表 + var newTasks []models.Task + newTasks = append(newTasks, fullCollectionTask, delayTask) + + // 如果计划已存在,则获取其现有任务并追加到新任务列表后(排除预设任务) + if foundExistingPlan, ok := existingPlanMap[PlanNamePeriodicSystemHealthCheck]; ok { + for _, existingTask := range foundExistingPlan.Tasks { + // 排除已预设的全量采集和延时任务 + if existingTask.Type != models.TaskTypeFullCollection && existingTask.Type != models.TaskTypeWaiting { + newTasks = append(newTasks, existingTask) + } + } + } + + // 重新设置所有任务的 ExecutionOrder + for i := range newTasks { + newTasks[i].ExecutionOrder = i + 1 + } + predefinedPlan := &models.Plan{ Name: PlanNamePeriodicSystemHealthCheck, Description: fmt.Sprintf("这是一个系统预定义的计划, 每 %d 分钟自动触发一次全量数据采集, 并进行阈值校验告警。", app.Config.Collection.Interval), @@ -101,14 +143,7 @@ func (app *Application) initializePeriodicSystemHealthCheckPlan(ctx context.Cont CronExpression: cronExpression, Status: models.PlanStatusEnabled, ContentType: models.PlanContentTypeTasks, - Tasks: []models.Task{ - { - Name: "全量采集", - Description: "触发一次全量数据采集", - ExecutionOrder: 1, - Type: models.TaskTypeFullCollection, - }, - }, + Tasks: newTasks, } if foundExistingPlan, ok := existingPlanMap[predefinedPlan.Name]; ok { diff --git a/internal/domain/task/release_feed_weight_task.go b/internal/domain/task/release_feed_weight_task.go index 530e929..5e2b30c 100644 --- a/internal/domain/task/release_feed_weight_task.go +++ b/internal/domain/task/release_feed_weight_task.go @@ -2,7 +2,6 @@ package task import ( "context" - "encoding/json" "fmt" "sync" "time" @@ -114,7 +113,7 @@ func (r *ReleaseFeedWeightTask) getNowWeight(ctx context.Context) (float64, erro } wg := &models.WeightData{} - err = json.Unmarshal(sensorData.Data, wg) + err = sensorData.ParseData(wg) if err != nil { logger.Errorf("反序列化设备 %v 最新传感器数据失败: %v , 日志ID: %v", r.mixingTankDeviceID, err, r.claimedLog.ID) return 0, err diff --git a/internal/infra/models/plan.go b/internal/infra/models/plan.go index 3c7e5f7..0e74684 100644 --- a/internal/infra/models/plan.go +++ b/internal/infra/models/plan.go @@ -202,6 +202,20 @@ func (t Task) ParseParameters(v interface{}) error { return json.Unmarshal(t.Parameters, v) } +// SaveParameters 将一个结构体序列化为 JSON 并保存到 Task 的 Parameters 字段。 +// 示例: +// +// params := LoraParameters{...} +// if err := task.SaveParameters(params); err != nil { ... } +func (t *Task) SaveParameters(v interface{}) error { + data, err := json.Marshal(v) + if err != nil { + return fmt.Errorf("序列化任务参数失败: %w", err) + } + t.Parameters = data + return nil +} + // DeviceTask 是设备和任务之间的关联模型,表示一个设备可以执行多个任务,一个任务可以被多个设备执行。 type DeviceTask struct { gorm.Model From d4e8aba1fd062d1b7ed100bc56f596d4a956d1ba Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 15:25:33 +0800 Subject: [PATCH 25/35] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=8C=BA=E5=9F=9F?= =?UTF-8?q?=E9=98=88=E5=80=BC=E5=91=8A=E8=AD=A6=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 +- .../domain/task/area_threshold_check_task.go | 161 ++++++++++++++++++ .../task/device_threshold_check_task.go | 28 +-- internal/infra/models/alarm.go | 11 ++ internal/infra/models/plan.go | 7 + 5 files changed, 190 insertions(+), 20 deletions(-) create mode 100644 internal/domain/task/area_threshold_check_task.go diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 621db95..4959eac 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -138,4 +138,5 @@ 6. 实现设备阈值检查任务 7. 实现忽略告警和取消忽略告警接口及功能 8. 实现列表查询活跃告警和历史告警 -9. 系统初始化时健康计划调整(包括增加延时任务) \ No newline at end of file +9. 系统初始化时健康计划调整(包括增加延时任务) +10. 实现区域阈值告警任务 \ No newline at end of file diff --git a/internal/domain/task/area_threshold_check_task.go b/internal/domain/task/area_threshold_check_task.go new file mode 100644 index 0000000..0b94b85 --- /dev/null +++ b/internal/domain/task/area_threshold_check_task.go @@ -0,0 +1,161 @@ +package task + +import ( + "context" + "fmt" + "sync" + + "git.huangwc.com/pig/pig-farm-controller/internal/domain/alarm" + "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" +) + +// AreaThresholdCheckParams 定义了区域阈值检查任务的参数 +type AreaThresholdCheckParams struct { + AreaControllerID uint `json:"area_controller_id"` // 区域主控ID + SensorType models.SensorType `json:"sensor_type"` // 传感器类型 + Thresholds float64 `json:"thresholds"` // 阈值 + Operator models.Operator `json:"operator"` // 操作符 + ExcludeDeviceIDs []uint `json:"exclude_device_ids"` // 排除的传感器ID +} + +// AreaThresholdCheckTask 是一个任务,用于检查区域阈值并触发告警, 区域主控下的所有没有独立校验任务的设备都会受到此任务的检查 +type AreaThresholdCheckTask struct { + ctx context.Context + onceParse sync.Once + + taskLog *models.TaskExecutionLog + params AreaThresholdCheckParams + + sensorDataRepo repository.SensorDataRepository + deviceRepo repository.DeviceRepository + alarmService alarm.AlarmService +} + +func NewAreaThresholdCheckTask(ctx context.Context, taskLog *models.TaskExecutionLog, sensorDataRepo repository.SensorDataRepository, deviceRepo repository.DeviceRepository, alarmService alarm.AlarmService) plan.Task { + return &AreaThresholdCheckTask{ + ctx: ctx, + taskLog: taskLog, + sensorDataRepo: sensorDataRepo, + deviceRepo: deviceRepo, + alarmService: alarmService, + } +} + +// Execute 执行区域阈值检查任务 +func (a *AreaThresholdCheckTask) Execute(ctx context.Context) error { + taskCtx, logger := logs.Trace(ctx, a.ctx, "Execute") + err := a.parseParameters(taskCtx) + if err != nil { + logger.Errorf("任务 %v: 解析参数失败: %v", a.taskLog.TaskID, err) + return err + } + + // 1. 查询区域主控下所有设备 + devices, err := a.deviceRepo.ListByAreaControllerID(taskCtx, a.params.AreaControllerID) + if err != nil { + logger.Errorf("任务 %v: 查询区域主控 %d 下设备失败: %v", a.taskLog.TaskID, a.params.AreaControllerID, err) + return fmt.Errorf("查询区域主控 %d 下设备失败: %w", a.params.AreaControllerID, err) + } + + // 构建忽略设备ID的map,方便快速查找 + ignoredMap := make(map[uint]struct{}) + for _, id := range a.params.ExcludeDeviceIDs { + ignoredMap[id] = struct{}{} + } + + // 2. 遍历设备,排除忽略列表里的设备,并执行阈值检查 + for _, device := range devices { + if _, ignored := ignoredMap[device.ID]; ignored { + logger.Debugf("任务 %v: 设备 %d 在忽略列表中,跳过检查。", a.taskLog.TaskID, device.ID) + continue + } + + task := a.taskLog.Task + err = task.SaveParameters(DeviceThresholdCheckParams{ + DeviceID: device.ID, + SensorType: a.params.SensorType, + Thresholds: a.params.Thresholds, + Operator: a.params.Operator, + }) + if err != nil { + logger.Errorf("任务 %v: 保存参数失败: %v", a.taskLog.TaskID, err) + continue + } + // 创建一个临时的 DeviceThresholdCheckTask 实例来复用其核心逻辑 + deviceCheckTask := NewDeviceThresholdCheckTask( + taskCtx, + &models.TaskExecutionLog{ // 为每个设备创建一个模拟的 TaskExecutionLog + TaskID: a.taskLog.TaskID, + Task: task, + }, + a.sensorDataRepo, + a.alarmService, + ).(*DeviceThresholdCheckTask) // 类型断言,以便访问内部参数 + + // 执行单设备的阈值检查 + if err := deviceCheckTask.Execute(taskCtx); err != nil { + logger.Errorf("任务 %v: 设备 %d 阈值检查失败: %v", a.taskLog.TaskID, device.ID, err) + continue + } + } + + return nil +} + +func (a *AreaThresholdCheckTask) OnFailure(ctx context.Context, executeErr error) { + logger := logs.TraceLogger(ctx, a.ctx, "OnFailure") + logger.Errorf("区域阈值检测任务执行失败, 任务ID: %v: 执行失败: %v", a.taskLog.TaskID, executeErr) +} + +func (a *AreaThresholdCheckTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) { + taskCtx := logs.AddFuncName(ctx, a.ctx, "ResolveDeviceIDs") + if err := a.parseParameters(taskCtx); err != nil { + return nil, err + } + // 排除列表也意味着关联 + return a.params.ExcludeDeviceIDs, nil +} + +// parseParameters 解析任务参数 +func (a *AreaThresholdCheckTask) parseParameters(ctx context.Context) error { + logger := logs.TraceLogger(ctx, a.ctx, "parseParameters") + var err error + a.onceParse.Do(func() { + if a.taskLog.Task.Parameters == nil { + logger.Errorf("任务 %v: 缺少参数", a.taskLog.TaskID) + err = fmt.Errorf("任务 %v: 参数不全", a.taskLog.TaskID) + return + } + + var params AreaThresholdCheckParams + err = a.taskLog.Task.ParseParameters(¶ms) + if err != nil { + logger.Errorf("任务 %v: 解析参数失败: %v", a.taskLog.TaskID, err) + err = fmt.Errorf("任务 %v: 解析参数失败: %v", a.taskLog.TaskID, err) + return + } + + if params.SensorType == "" { + err = fmt.Errorf("任务 %v: 未配置传感器类型", a.taskLog.TaskID) + } + if params.Operator == "" { + err = fmt.Errorf("任务 %v: 缺少操作符", a.taskLog.TaskID) + } + if params.Thresholds == 0 { + err = fmt.Errorf("任务 %v: 未配置阈值", a.taskLog.TaskID) + } + if params.AreaControllerID == 0 { + err = fmt.Errorf("任务 %v: 未配置区域主控ID", a.taskLog.TaskID) + } + if params.ExcludeDeviceIDs == nil { + params.ExcludeDeviceIDs = []uint{} + } + + a.params = params + + }) + return err +} diff --git a/internal/domain/task/device_threshold_check_task.go b/internal/domain/task/device_threshold_check_task.go index 365554b..d8c8a9b 100644 --- a/internal/domain/task/device_threshold_check_task.go +++ b/internal/domain/task/device_threshold_check_task.go @@ -13,24 +13,14 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" ) -type Operator string - -const ( - OperatorLessThan Operator = "<" - OperatorLessThanOrEqualTo Operator = "<=" - OperatorGreaterThan Operator = ">" - OperatorGreaterThanOrEqualTo Operator = ">=" - OperatorEqualTo Operator = "=" - OperatorNotEqualTo Operator = "!=" -) - type DeviceThresholdCheckParams struct { DeviceID uint `json:"device_id"` // 设备ID SensorType models.SensorType `json:"sensor_type"` // 传感器类型 Thresholds float64 `json:"thresholds"` // 阈值 - Operator Operator `json:"operator"` // 操作符 + Operator models.Operator `json:"operator"` // 操作符 } +// DeviceThresholdCheckTask 是一个任务,用于检查设备传感器数据是否满足阈值条件。 type DeviceThresholdCheckTask struct { ctx context.Context onceParse sync.Once @@ -132,19 +122,19 @@ func (d *DeviceThresholdCheckTask) Execute(ctx context.Context) error { } // checkThreshold 校验当前值是否满足阈值条件 -func (d *DeviceThresholdCheckTask) checkThreshold(currentValue float64, operator Operator, threshold float64) bool { +func (d *DeviceThresholdCheckTask) checkThreshold(currentValue float64, operator models.Operator, threshold float64) bool { switch operator { - case OperatorLessThan: + case models.OperatorLessThan: return currentValue < threshold - case OperatorLessThanOrEqualTo: + case models.OperatorLessThanOrEqualTo: return currentValue <= threshold - case OperatorGreaterThan: + case models.OperatorGreaterThan: return currentValue > threshold - case OperatorGreaterThanOrEqualTo: + case models.OperatorGreaterThanOrEqualTo: return currentValue >= threshold - case OperatorEqualTo: + case models.OperatorEqualTo: return currentValue == threshold - case OperatorNotEqualTo: + case models.OperatorNotEqualTo: return currentValue != threshold default: return false diff --git a/internal/infra/models/alarm.go b/internal/infra/models/alarm.go index 6ce054f..8306faa 100644 --- a/internal/infra/models/alarm.go +++ b/internal/infra/models/alarm.go @@ -34,6 +34,17 @@ const ( // (可在此处预留或添加) ) +type Operator string + +const ( + OperatorLessThan Operator = "<" + OperatorLessThanOrEqualTo Operator = "<=" + OperatorGreaterThan Operator = ">" + OperatorGreaterThanOrEqualTo Operator = ">=" + OperatorEqualTo Operator = "=" + OperatorNotEqualTo Operator = "!=" +) + // ActiveAlarm 活跃告警 // 活跃告警会被更新(如忽略状态),因此保留 gorm.Model 以包含所有标准字段。 type ActiveAlarm struct { diff --git a/internal/infra/models/plan.go b/internal/infra/models/plan.go index 0e74684..7efd507 100644 --- a/internal/infra/models/plan.go +++ b/internal/infra/models/plan.go @@ -11,6 +11,13 @@ import ( "gorm.io/gorm" ) +const ( + // PlanNamePeriodicSystemHealthCheck 是周期性系统健康检查计划的名称 + PlanNamePeriodicSystemHealthCheck = "周期性系统健康检查" + // PlanNameAlarmNotification 是告警通知发送计划的名称 + PlanNameAlarmNotification = "告警通知发送" +) + // PlanExecutionType 定义了计划的执行类型 type PlanExecutionType string From f2b0c2987fb92995c32e3ffb8690aa51c35bdf96 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 17:35:22 +0800 Subject: [PATCH 26/35] CreateDeviceThresholdAlarm UpdateDeviceThresholdAlarm CreateAreaThresholdAlarm UpdateAreaThresholdAlarm --- internal/app/dto/alarm_dto.go | 32 ++ internal/app/service/plan_service.go | 3 +- .../app/service/threshold_alarm_service.go | 283 +++++++++++++++++- internal/core/data_initializer.go | 13 +- internal/domain/plan/plan_service.go | 13 +- .../domain/task/area_threshold_check_task.go | 15 +- .../task/device_threshold_check_task.go | 14 +- internal/infra/models/plan.go | 20 +- internal/infra/repository/plan_repository.go | 19 ++ project_structure.txt | 1 + 10 files changed, 375 insertions(+), 38 deletions(-) diff --git a/internal/app/dto/alarm_dto.go b/internal/app/dto/alarm_dto.go index 7cb2b09..6538184 100644 --- a/internal/app/dto/alarm_dto.go +++ b/internal/app/dto/alarm_dto.go @@ -81,3 +81,35 @@ type ListHistoricalAlarmResponse struct { List []HistoricalAlarmDTO `json:"list"` Pagination PaginationDTO `json:"pagination"` } + +// CreateDeviceThresholdAlarmDTO 创建设备阈值告警的请求DTO +type CreateDeviceThresholdAlarmDTO struct { + DeviceID uint `json:"device_id" binding:"required"` // 设备ID + SensorType models.SensorType `json:"sensor_type" binding:"required"` // 传感器类型 + Thresholds float64 `json:"thresholds" binding:"required"` // 阈值 + Operator models.Operator `json:"operator" binding:"required"` // 操作符 (使用string类型,与前端交互更通用) + Level models.SeverityLevel `json:"level,omitempty"` // 告警等级,可选,如果未提供则使用默认值 +} + +// UpdateDeviceThresholdAlarmDTO 更新设备阈值告警的请求DTO +type UpdateDeviceThresholdAlarmDTO struct { + Thresholds float64 `json:"thresholds" binding:"required"` // 新的阈值 + Operator models.Operator `json:"operator" binding:"required"` // 新的操作符 + Level models.SeverityLevel `json:"level,omitempty"` // 新的告警等级,可选 +} + +// CreateAreaThresholdAlarmDTO 创建区域阈值告警的请求DTO +type CreateAreaThresholdAlarmDTO struct { + AreaControllerID uint `json:"area_controller_id" binding:"required"` // 区域主控ID + SensorType models.SensorType `json:"sensor_type" binding:"required"` // 传感器类型 + Thresholds float64 `json:"thresholds" binding:"required"` // 阈值 + Operator models.Operator `json:"operator" binding:"required"` // 操作符 + Level models.SeverityLevel `json:"level,omitempty"` // 告警等级,可选 +} + +// UpdateAreaThresholdAlarmDTO 更新区域阈值告警的请求DTO +type UpdateAreaThresholdAlarmDTO struct { + Thresholds float64 `json:"thresholds" binding:"required"` // 新的阈值 + Operator models.Operator `json:"operator" binding:"required"` // 新的操作符 + Level models.SeverityLevel `json:"level,omitempty"` // 新的告警等级,可选 +} diff --git a/internal/app/service/plan_service.go b/internal/app/service/plan_service.go index ba8b416..8601487 100644 --- a/internal/app/service/plan_service.go +++ b/internal/app/service/plan_service.go @@ -7,6 +7,7 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" ) @@ -147,7 +148,7 @@ func (s *planService) UpdatePlan(ctx context.Context, id uint, req *dto.UpdatePl planToUpdate.ID = id // 确保ID被设置 // 调用领域服务更新计划 - updatedPlan, err := s.domainPlanService.UpdatePlan(serviceCtx, planToUpdate) + updatedPlan, err := s.domainPlanService.UpdatePlan(serviceCtx, planToUpdate, models.PlanTypeCustom) if err != nil { logger.Errorf("%s: 领域服务更新计划失败: %v, ID: %d", actionType, err, id) return nil, err // 直接返回领域层错误 diff --git a/internal/app/service/threshold_alarm_service.go b/internal/app/service/threshold_alarm_service.go index f841f6e..7f44e1b 100644 --- a/internal/app/service/threshold_alarm_service.go +++ b/internal/app/service/threshold_alarm_service.go @@ -2,11 +2,15 @@ package service import ( "context" + "fmt" "time" "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" domainAlarm "git.huangwc.com/pig/pig-farm-controller/internal/domain/alarm" + "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan" + "git.huangwc.com/pig/pig-farm-controller/internal/domain/task" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" ) @@ -21,21 +25,42 @@ type ThresholdAlarmService interface { ListActiveAlarms(ctx context.Context, req *dto.ListActiveAlarmRequest) (*dto.ListActiveAlarmResponse, error) // ListHistoricalAlarms 批量查询历史告警。 ListHistoricalAlarms(ctx context.Context, req *dto.ListHistoricalAlarmRequest) (*dto.ListHistoricalAlarmResponse, error) + // CreateDeviceThresholdAlarm 创建一个设备阈值告警。 + CreateDeviceThresholdAlarm(ctx context.Context, req *dto.CreateDeviceThresholdAlarmDTO) error + // UpdateDeviceThresholdAlarm 更新一个设备阈值告警。 + UpdateDeviceThresholdAlarm(ctx context.Context, taskID int, req *dto.UpdateDeviceThresholdAlarmDTO) error + // CreateAreaThresholdAlarm 创建一个区域阈值告警。 + CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error + // UpdateAreaThresholdAlarm 更新一个区域阈值告警。 + UpdateAreaThresholdAlarm(ctx context.Context, taskID int, req *dto.UpdateAreaThresholdAlarmDTO) error } // thresholdAlarmService 是 ThresholdAlarmService 接口的具体实现。 type thresholdAlarmService struct { ctx context.Context alarmService domainAlarm.AlarmService - alarmRepo repository.AlarmRepository + planService plan.Service + + alarmRepo repository.AlarmRepository + planRepo repository.PlanRepository + deviceRepo repository.DeviceRepository } // NewThresholdAlarmService 创建一个新的 ThresholdAlarmService 实例。 -func NewThresholdAlarmService(ctx context.Context, alarmService domainAlarm.AlarmService, alarmRepo repository.AlarmRepository) ThresholdAlarmService { +func NewThresholdAlarmService(ctx context.Context, + alarmService domainAlarm.AlarmService, + planService plan.Service, + alarmRepo repository.AlarmRepository, + planRepo repository.PlanRepository, + deviceRepo repository.DeviceRepository, +) ThresholdAlarmService { return &thresholdAlarmService{ ctx: ctx, alarmService: alarmService, + planService: planService, alarmRepo: alarmRepo, + planRepo: planRepo, + deviceRepo: deviceRepo, } } @@ -95,3 +120,257 @@ func (s *thresholdAlarmService) ListHistoricalAlarms(ctx context.Context, req *d return dto.NewListHistoricalAlarmResponse(alarms, total, req.Page, req.PageSize), nil } + +// CreateDeviceThresholdAlarm 实现了创建一个设备阈值告警的逻辑。 +func (s *thresholdAlarmService) CreateDeviceThresholdAlarm(ctx context.Context, req *dto.CreateDeviceThresholdAlarmDTO) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreateThresholdAlarm") + + device, err := s.deviceRepo.FindByID(serviceCtx, req.DeviceID) + if err != nil { + return fmt.Errorf("获取设备 %v 失败: %v", req.DeviceID, err) + } + + plan, err := s.planRepo.GetSystemPlanByName(serviceCtx, models.PlanNamePeriodicSystemHealthCheck) + if err != nil { + return fmt.Errorf("获取系统计划 %v 失败: %v", models.PlanNamePeriodicSystemHealthCheck, err) + } + if plan == nil { + logger.Panicf("系统计划 %v 不存在", models.PlanNamePeriodicSystemHealthCheck) + } + + // 系统计划肯定是子任务 + for i, t := range plan.Tasks { + switch t.Type { + case models.TaskTypeDeviceThresholdCheck: // 检查任务是否存在 + var params task.DeviceThresholdCheckParams + err = t.ParseParameters(¶ms) + if err != nil { + return fmt.Errorf("任务 %v: 解析设备阈值检查任务参数失败: %v", t.ID, err) + } + if params.DeviceID == req.DeviceID && params.SensorType == req.SensorType { + return fmt.Errorf("设备 %v: 该设备已存在阈值检查任务", req.DeviceID) + } + case models.TaskTypeAreaCollectorThresholdCheck: // 向区域阈值检查任务过滤列表中添加该设备 + params := task.AreaThresholdCheckParams{ + ExcludeDeviceIDs: []uint{}, + } + err = t.ParseParameters(¶ms) + if err != nil { + return fmt.Errorf("任务 %v: 解析区域阈值检查任务参数失败: %v", t.ID, err) + } + if params.AreaControllerID == device.AreaControllerID { + has := false + for _, d := range params.ExcludeDeviceIDs { + if d == req.DeviceID { + has = true + break + } + } + if !has { + params.ExcludeDeviceIDs = append(params.ExcludeDeviceIDs, req.DeviceID) + err = plan.Tasks[i].SaveParameters(params) + if err != nil { + return fmt.Errorf("任务 %v: 保存任务参数失败: %v", t.ID, err) + } + } + } + default: + continue + } + } + + t := models.Task{ + PlanID: plan.ID, + Name: fmt.Sprintf("设备 %v 的阈值检测任务", req.DeviceID), + Description: fmt.Sprintf("检测该设备 %v 是否 %v %v", req.SensorType, req.Operator, req.Thresholds), + ExecutionOrder: len(plan.Tasks) + 1, + Type: models.TaskTypeDeviceThresholdCheck, + } + err = t.SaveParameters(task.DeviceThresholdCheckParams{ + DeviceID: req.DeviceID, + SensorType: req.SensorType, + Thresholds: req.Thresholds, + Level: req.Level, + Operator: req.Operator, + }) + if err != nil { + return fmt.Errorf("保存任务参数失败: %v", err) + } + + plan.Tasks = append(plan.Tasks, t) + plan.ReorderSteps() + _, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem) + if err != nil { + return fmt.Errorf("更新计划失败: %v", err) + } + return nil +} + +// UpdateDeviceThresholdAlarm 实现了更新一个设备阈值告警的逻辑。 +func (s *thresholdAlarmService) UpdateDeviceThresholdAlarm(ctx context.Context, taskID int, req *dto.UpdateDeviceThresholdAlarmDTO) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "UpdateDeviceThresholdAlarm") + + // 1. 获取系统健康检查计划 + plan, err := s.planRepo.GetSystemPlanByName(serviceCtx, models.PlanNamePeriodicSystemHealthCheck) + if err != nil { + return fmt.Errorf("获取系统健康检查计划失败: %w", err) + } + if plan == nil { + // 这个系统计划必须存在 + logger.Panicf("系统计划 %v 不存在", models.PlanNamePeriodicSystemHealthCheck) + } + + // 2. 遍历任务列表,查找并更新目标任务 + taskFound := false + for i, t := range plan.Tasks { + if t.ID == taskID && t.Type == models.TaskTypeDeviceThresholdCheck { + taskFound = true + + var params task.DeviceThresholdCheckParams + if err = t.ParseParameters(¶ms); err != nil { + return fmt.Errorf("任务 %d: 解析现有参数失败: %w", taskID, err) + } + params.Thresholds = req.Thresholds + params.Operator = req.Operator + params.Level = req.Level + // 刷新任务说明 + plan.Tasks[i].Description = fmt.Sprintf("检测该设备 %v 是否 %v %v", params.SensorType, params.Operator, params.Thresholds) + + err = plan.Tasks[i].SaveParameters(params) + if err != nil { + return fmt.Errorf("任务 %d: 保存参数失败: %w", taskID, err) + } + + break + } + } + + if !taskFound { + return fmt.Errorf("任务 %d: 不存在", taskID) + } + + // 全量更新计划 + _, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem) + return err +} + +// CreateAreaThresholdAlarm 实现了创建一个区域阈值告警的逻辑。 +func (s *thresholdAlarmService) CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreateAreaThresholdAlarm") + + // 1. 获取系统健康检查计划 + plan, err := s.planRepo.GetSystemPlanByName(serviceCtx, models.PlanNamePeriodicSystemHealthCheck) + if err != nil { + return fmt.Errorf("获取系统健康检查计划失败: %w", err) + } + if plan == nil { + logger.Panicf("系统计划 %v 不存在", models.PlanNamePeriodicSystemHealthCheck) + } + + // 2. 获取目标区域下的所有设备,并建立快速查找表 + devicesInArea, err := s.deviceRepo.ListByAreaControllerID(serviceCtx, req.AreaControllerID) + if err != nil { + return fmt.Errorf("获取区域 %d 下的设备列表失败: %w", req.AreaControllerID, err) + } + devicesInAreaMap := make(map[uint]struct{}, len(devicesInArea)) + for _, device := range devicesInArea { + devicesInAreaMap[device.ID] = struct{}{} + } + + // 3. 遍历计划,检查存在性并收集需要排除的设备ID + var excludeDeviceIDs []uint + for _, t := range plan.Tasks { + switch t.Type { + case models.TaskTypeAreaCollectorThresholdCheck: + var params task.AreaThresholdCheckParams + if err := t.ParseParameters(¶ms); err != nil { + return fmt.Errorf("任务 %d: 解析区域阈值检查任务参数失败: %w", t.ID, err) + } + if params.AreaControllerID == req.AreaControllerID && params.SensorType == req.SensorType { + return fmt.Errorf("区域 %d: 该区域已存在针对 %s 的阈值检查任务", req.AreaControllerID, req.SensorType) + } + case models.TaskTypeDeviceThresholdCheck: + var params task.DeviceThresholdCheckParams + if err := t.ParseParameters(¶ms); err != nil { + return fmt.Errorf("任务 %d: 解析设备阈值检查任务参数失败: %w", t.ID, err) + } + // 检查该设备是否属于目标区域 + if _, ok := devicesInAreaMap[params.DeviceID]; ok { + excludeDeviceIDs = append(excludeDeviceIDs, params.DeviceID) + } + } + } + + // 4. 创建新任务 + newTask := models.Task{ + PlanID: plan.ID, + Name: fmt.Sprintf("区域 %d 的 %s 阈值检测任务", req.AreaControllerID, req.SensorType), + Description: fmt.Sprintf("检测区域 %d 的 %s 是否 %v %v", req.AreaControllerID, req.SensorType, req.Operator, req.Thresholds), + ExecutionOrder: len(plan.Tasks) + 1, + Type: models.TaskTypeAreaCollectorThresholdCheck, + } + err = newTask.SaveParameters(task.AreaThresholdCheckParams{ + AreaControllerID: req.AreaControllerID, + SensorType: req.SensorType, + Thresholds: req.Thresholds, + Operator: req.Operator, + Level: req.Level, + ExcludeDeviceIDs: excludeDeviceIDs, + }) + if err != nil { + return fmt.Errorf("保存新区域任务的参数失败: %w", err) + } + + // 5. 更新计划 + plan.Tasks = append(plan.Tasks, newTask) + plan.ReorderSteps() + _, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem) + return err +} + +// UpdateAreaThresholdAlarm 实现了更新一个区域阈值告警的逻辑。 +func (s *thresholdAlarmService) UpdateAreaThresholdAlarm(ctx context.Context, taskID int, req *dto.UpdateAreaThresholdAlarmDTO) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "UpdateAreaThresholdAlarm") + + // 1. 获取系统健康检查计划 + plan, err := s.planRepo.GetSystemPlanByName(serviceCtx, models.PlanNamePeriodicSystemHealthCheck) + if err != nil { + return fmt.Errorf("获取系统健康检查计划失败: %w", err) + } + if plan == nil { + logger.Panicf("系统计划 %v 不存在", models.PlanNamePeriodicSystemHealthCheck) + } + + // 2. 遍历任务列表,查找并更新目标任务 + taskFound := false + for i, t := range plan.Tasks { + if t.ID == taskID && t.Type == models.TaskTypeAreaCollectorThresholdCheck { + taskFound = true + + var params task.AreaThresholdCheckParams + if err = t.ParseParameters(¶ms); err != nil { + return fmt.Errorf("任务 %d: 解析现有参数失败: %w", taskID, err) + } + params.Thresholds = req.Thresholds + params.Operator = req.Operator + params.Level = req.Level + // 刷新任务说明 + plan.Tasks[i].Description = fmt.Sprintf("检测区域 %d 的 %s 是否 %v %v", params.AreaControllerID, params.SensorType, params.Operator, params.Thresholds) + + err = plan.Tasks[i].SaveParameters(params) + if err != nil { + return fmt.Errorf("任务 %d: 保存参数失败: %w", taskID, err) + } + + break + } + } + + if !taskFound { + return fmt.Errorf("任务 %d: 不存在或类型不匹配", taskID) + } + + // 全量更新计划 + _, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem) + return err +} diff --git a/internal/core/data_initializer.go b/internal/core/data_initializer.go index 32edd75..c66bddf 100644 --- a/internal/core/data_initializer.go +++ b/internal/core/data_initializer.go @@ -10,13 +10,6 @@ import ( "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" ) -const ( - // PlanNamePeriodicSystemHealthCheck 是周期性系统健康检查计划的名称 - PlanNamePeriodicSystemHealthCheck = "周期性系统健康检查" - // PlanNameAlarmNotification 是告警通知发送计划的名称 - PlanNameAlarmNotification = "告警通知发送" -) - // initializeState 在应用启动时准备其初始数据状态。 // 它遵循一个严格的顺序:清理 -> 更新 -> 刷新,以确保数据的一致性和正确性。 func (app *Application) initializeState(ctx context.Context) error { @@ -121,7 +114,7 @@ func (app *Application) initializePeriodicSystemHealthCheckPlan(ctx context.Cont newTasks = append(newTasks, fullCollectionTask, delayTask) // 如果计划已存在,则获取其现有任务并追加到新任务列表后(排除预设任务) - if foundExistingPlan, ok := existingPlanMap[PlanNamePeriodicSystemHealthCheck]; ok { + if foundExistingPlan, ok := existingPlanMap[models.PlanNamePeriodicSystemHealthCheck]; ok { for _, existingTask := range foundExistingPlan.Tasks { // 排除已预设的全量采集和延时任务 if existingTask.Type != models.TaskTypeFullCollection && existingTask.Type != models.TaskTypeWaiting { @@ -136,7 +129,7 @@ func (app *Application) initializePeriodicSystemHealthCheckPlan(ctx context.Cont } predefinedPlan := &models.Plan{ - Name: PlanNamePeriodicSystemHealthCheck, + Name: models.PlanNamePeriodicSystemHealthCheck, Description: fmt.Sprintf("这是一个系统预定义的计划, 每 %d 分钟自动触发一次全量数据采集, 并进行阈值校验告警。", app.Config.Collection.Interval), PlanType: models.PlanTypeSystem, ExecutionType: models.PlanExecutionTypeAutomatic, @@ -186,7 +179,7 @@ func (app *Application) initializeAlarmNotificationPlan(ctx context.Context, exi appCtx, logger := logs.Trace(ctx, app.Ctx, "initializeAlarmNotificationPlan") predefinedPlan := &models.Plan{ - Name: PlanNameAlarmNotification, + Name: models.PlanNameAlarmNotification, Description: "这是一个系统预定义的计划, 每分钟自动触发一次告警通知发送。", PlanType: models.PlanTypeSystem, ExecutionType: models.PlanExecutionTypeAutomatic, diff --git a/internal/domain/plan/plan_service.go b/internal/domain/plan/plan_service.go index f83c655..ea61a9f 100644 --- a/internal/domain/plan/plan_service.go +++ b/internal/domain/plan/plan_service.go @@ -44,8 +44,8 @@ type Service interface { GetPlanByID(ctx context.Context, id uint) (*models.Plan, error) // ListPlans 获取计划列表,支持过滤和分页 ListPlans(ctx context.Context, opts repository.ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error) - // UpdatePlan 更新计划 - UpdatePlan(ctx context.Context, plan *models.Plan) (*models.Plan, error) + // UpdatePlan 更新计划, wantPlanType 表示期望被修改的计划是什么类型 + UpdatePlan(ctx context.Context, plan *models.Plan, wantPlanType models.PlanType) (*models.Plan, error) // DeletePlan 删除计划(软删除) DeletePlan(ctx context.Context, id uint) error // StartPlan 启动计划 @@ -207,8 +207,8 @@ func (s *planServiceImpl) ListPlans(ctx context.Context, opts repository.ListPla return plans, total, nil } -// UpdatePlan 更新计划 -func (s *planServiceImpl) UpdatePlan(ctx context.Context, planToUpdate *models.Plan) (*models.Plan, error) { +// UpdatePlan 更新计划, wantPlanType 表示期望被修改的计划是什么类型 +func (s *planServiceImpl) UpdatePlan(ctx context.Context, planToUpdate *models.Plan, wantPlanType models.PlanType) (*models.Plan, error) { planCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePlan") const actionType = "领域层:更新计划" @@ -222,9 +222,8 @@ func (s *planServiceImpl) UpdatePlan(ctx context.Context, planToUpdate *models.P return nil, err } - // 系统计划不允许修改 - if existingPlan.PlanType == models.PlanTypeSystem { - logger.Warnf("%s: 尝试修改系统计划, ID: %d", actionType, planToUpdate.ID) + if existingPlan.PlanType != wantPlanType { + logger.Warnf("%s: 禁止修改 %v 类型计划, ID: %d", actionType, wantPlanType, planToUpdate.ID) return nil, ErrPlanCannotBeModified } diff --git a/internal/domain/task/area_threshold_check_task.go b/internal/domain/task/area_threshold_check_task.go index 0b94b85..f7ba56e 100644 --- a/internal/domain/task/area_threshold_check_task.go +++ b/internal/domain/task/area_threshold_check_task.go @@ -14,11 +14,12 @@ import ( // AreaThresholdCheckParams 定义了区域阈值检查任务的参数 type AreaThresholdCheckParams struct { - AreaControllerID uint `json:"area_controller_id"` // 区域主控ID - SensorType models.SensorType `json:"sensor_type"` // 传感器类型 - Thresholds float64 `json:"thresholds"` // 阈值 - Operator models.Operator `json:"operator"` // 操作符 - ExcludeDeviceIDs []uint `json:"exclude_device_ids"` // 排除的传感器ID + AreaControllerID uint `json:"area_controller_id"` // 区域主控ID + SensorType models.SensorType `json:"sensor_type"` // 传感器类型 + Thresholds float64 `json:"thresholds"` // 阈值 + Operator models.Operator `json:"operator"` // 操作符 + Level models.SeverityLevel `json:"level"` // 告警级别 + ExcludeDeviceIDs []uint `json:"exclude_device_ids"` // 排除的传感器ID } // AreaThresholdCheckTask 是一个任务,用于检查区域阈值并触发告警, 区域主控下的所有没有独立校验任务的设备都会受到此任务的检查 @@ -78,6 +79,7 @@ func (a *AreaThresholdCheckTask) Execute(ctx context.Context) error { DeviceID: device.ID, SensorType: a.params.SensorType, Thresholds: a.params.Thresholds, + Level: a.params.Level, Operator: a.params.Operator, }) if err != nil { @@ -150,6 +152,9 @@ func (a *AreaThresholdCheckTask) parseParameters(ctx context.Context) error { if params.AreaControllerID == 0 { err = fmt.Errorf("任务 %v: 未配置区域主控ID", a.taskLog.TaskID) } + if params.Level == "" { + params.Level = models.WarnLevel + } if params.ExcludeDeviceIDs == nil { params.ExcludeDeviceIDs = []uint{} } diff --git a/internal/domain/task/device_threshold_check_task.go b/internal/domain/task/device_threshold_check_task.go index d8c8a9b..3fbace1 100644 --- a/internal/domain/task/device_threshold_check_task.go +++ b/internal/domain/task/device_threshold_check_task.go @@ -14,10 +14,11 @@ import ( ) type DeviceThresholdCheckParams struct { - DeviceID uint `json:"device_id"` // 设备ID - SensorType models.SensorType `json:"sensor_type"` // 传感器类型 - Thresholds float64 `json:"thresholds"` // 阈值 - Operator models.Operator `json:"operator"` // 操作符 + DeviceID uint `json:"device_id"` // 设备ID + SensorType models.SensorType `json:"sensor_type"` // 传感器类型 + Thresholds float64 `json:"thresholds"` // 阈值 + Operator models.Operator `json:"operator"` // 操作符 + Level models.SeverityLevel `json:"level"` // 告警等级 } // DeviceThresholdCheckTask 是一个任务,用于检查设备传感器数据是否满足阈值条件。 @@ -99,7 +100,7 @@ func (d *DeviceThresholdCheckTask) Execute(ctx context.Context) error { AlarmCode: alarmCode, AlarmSummary: summary, AlarmDetails: details, - Level: models.WarnLevel, // 默认告警等级,可后续根据需求调整 + Level: d.params.Level, TriggerTime: time.Now(), } @@ -172,6 +173,9 @@ func (d *DeviceThresholdCheckTask) parseParameters(ctx context.Context) error { if params.DeviceID == 0 { err = fmt.Errorf("任务 %v: 未配置设备ID", d.taskLog.TaskID) } + if params.Level == "" { + params.Level = models.WarnLevel + } d.params = params diff --git a/internal/infra/models/plan.go b/internal/infra/models/plan.go index 7efd507..86efc6d 100644 --- a/internal/infra/models/plan.go +++ b/internal/infra/models/plan.go @@ -11,11 +11,13 @@ import ( "gorm.io/gorm" ) +type PlanName string + const ( // PlanNamePeriodicSystemHealthCheck 是周期性系统健康检查计划的名称 - PlanNamePeriodicSystemHealthCheck = "周期性系统健康检查" + PlanNamePeriodicSystemHealthCheck PlanName = "周期性系统健康检查" // PlanNameAlarmNotification 是告警通知发送计划的名称 - PlanNameAlarmNotification = "告警通知发送" + PlanNameAlarmNotification PlanName = "告警通知发送" ) // PlanExecutionType 定义了计划的执行类型 @@ -38,11 +40,13 @@ const ( type TaskType string const ( - TaskPlanAnalysis TaskType = "计划分析" // 解析Plan的Task列表并添加到待执行队列的特殊任务 - TaskTypeWaiting TaskType = "等待" // 等待任务 - TaskTypeReleaseFeedWeight TaskType = "下料" // 下料口释放指定重量任务 - TaskTypeFullCollection TaskType = "全量采集" // 新增的全量采集任务 - TaskTypeAlarmNotification TaskType = "告警通知" // 告警通知任务 + TaskPlanAnalysis TaskType = "计划分析" // 解析Plan的Task列表并添加到待执行队列的特殊任务 + TaskTypeWaiting TaskType = "等待" // 等待任务 + TaskTypeReleaseFeedWeight TaskType = "下料" // 下料口释放指定重量任务 + TaskTypeFullCollection TaskType = "全量采集" // 新增的全量采集任务 + TaskTypeAlarmNotification TaskType = "告警通知" // 告警通知任务 + TaskTypeDeviceThresholdCheck TaskType = "设备阈值检查" // 设备阈值检查任务 + TaskTypeAreaCollectorThresholdCheck TaskType = "区域阈值检查" // 区域阈值检查任务 ) // -- Task Parameters -- @@ -72,7 +76,7 @@ const ( type Plan struct { gorm.Model - Name string `gorm:"not null" json:"name"` + Name PlanName `gorm:"not null" json:"name"` Description string `json:"description"` PlanType PlanType `gorm:"not null;index" json:"plan_type"` // 任务类型, 包括系统任务和用户自定义任务 ExecutionType PlanExecutionType `gorm:"not null;index" json:"execution_type"` diff --git a/internal/infra/repository/plan_repository.go b/internal/infra/repository/plan_repository.go index 6ce886b..9418db1 100644 --- a/internal/infra/repository/plan_repository.go +++ b/internal/infra/repository/plan_repository.go @@ -49,6 +49,8 @@ type PlanRepository interface { GetPlanByID(ctx context.Context, id uint) (*models.Plan, error) // GetPlansByIDs 根据ID列表获取计划,不包含子计划和任务详情 GetPlansByIDs(ctx context.Context, ids []uint) ([]models.Plan, error) + // GetSystemPlanByName 根据计划名称获取系统计划,包含子计划和任务详情 + GetSystemPlanByName(ctx context.Context, planName models.PlanName) (*models.Plan, error) // CreatePlan 创建一个新的计划 CreatePlan(ctx context.Context, plan *models.Plan) error // CreatePlanTx 在指定事务中创建一个新的计划 @@ -164,6 +166,23 @@ func (r *gormPlanRepository) GetPlansByIDs(ctx context.Context, ids []uint) ([]m return plans, nil } +// GetSystemPlanByName 根据计划名称获取系统计划,包含子计划和任务详情, 系统任务不该有重名情况, 所以可以这么查询 +func (r *gormPlanRepository) GetSystemPlanByName(ctx context.Context, planName models.PlanName) (*models.Plan, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "GetSystemPlanByName") + var plan models.Plan + // 首先只查询计划的基本信息,获取其ID + err := r.db.WithContext(repoCtx).Select("id").Where("name = ? AND plan_type = ?", planName, models.PlanTypeSystem).First(&plan).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil // 未找到系统计划不是错误 + } + if err != nil { + return nil, fmt.Errorf("查询系统计划 '%s' 失败: %w", planName, err) + } + + // 如果找到了计划ID,则复用 GetPlanByID 来获取完整的计划详情 + return r.GetPlanByID(repoCtx, plan.ID) +} + // GetPlanByID 根据ID获取计划,包含子计划和任务详情 func (r *gormPlanRepository) GetPlanByID(ctx context.Context, id uint) (*models.Plan, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPlanByID") diff --git a/project_structure.txt b/project_structure.txt index 5f20ab7..a01e2b7 100644 --- a/project_structure.txt +++ b/project_structure.txt @@ -103,6 +103,7 @@ internal/domain/plan/plan_execution_manager.go internal/domain/plan/plan_service.go internal/domain/plan/task.go internal/domain/task/alarm_notification_task.go +internal/domain/task/area_threshold_check_task.go internal/domain/task/delay_task.go internal/domain/task/device_threshold_check_task.go internal/domain/task/full_collection_task.go From f44a94b451a717e5fa3ca1ebb520bd748765f01b Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 17:50:05 +0800 Subject: [PATCH 27/35] GetDeviceThresholdAlarm GetAreaThresholdAlarm --- internal/app/dto/alarm_dto.go | 20 ++++++ .../app/service/threshold_alarm_service.go | 68 +++++++++++++++++++ internal/infra/repository/plan_repository.go | 18 +++-- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/internal/app/dto/alarm_dto.go b/internal/app/dto/alarm_dto.go index 6538184..2f097c6 100644 --- a/internal/app/dto/alarm_dto.go +++ b/internal/app/dto/alarm_dto.go @@ -113,3 +113,23 @@ type UpdateAreaThresholdAlarmDTO struct { Operator models.Operator `json:"operator" binding:"required"` // 新的操作符 Level models.SeverityLevel `json:"level,omitempty"` // 新的告警等级,可选 } + +// AreaThresholdAlarmDTO 用于表示一个区域阈值告警任务的详细信息 +type AreaThresholdAlarmDTO struct { + ID int `json:"id"` + AreaControllerID uint `json:"area_controller_id"` + SensorType models.SensorType `json:"sensor_type"` + Thresholds float64 `json:"thresholds"` + Operator models.Operator `json:"operator"` + Level models.SeverityLevel `json:"level"` +} + +// DeviceThresholdAlarmDTO 用于表示一个设备阈值告警任务的详细信息 +type DeviceThresholdAlarmDTO struct { + ID int `json:"id"` + DeviceID uint `json:"device_id"` + SensorType models.SensorType `json:"sensor_type"` + Thresholds float64 `json:"thresholds"` + Operator models.Operator `json:"operator"` + Level models.SeverityLevel `json:"level"` +} diff --git a/internal/app/service/threshold_alarm_service.go b/internal/app/service/threshold_alarm_service.go index 7f44e1b..82d65ed 100644 --- a/internal/app/service/threshold_alarm_service.go +++ b/internal/app/service/threshold_alarm_service.go @@ -25,14 +25,19 @@ type ThresholdAlarmService interface { ListActiveAlarms(ctx context.Context, req *dto.ListActiveAlarmRequest) (*dto.ListActiveAlarmResponse, error) // ListHistoricalAlarms 批量查询历史告警。 ListHistoricalAlarms(ctx context.Context, req *dto.ListHistoricalAlarmRequest) (*dto.ListHistoricalAlarmResponse, error) + // CreateDeviceThresholdAlarm 创建一个设备阈值告警。 CreateDeviceThresholdAlarm(ctx context.Context, req *dto.CreateDeviceThresholdAlarmDTO) error // UpdateDeviceThresholdAlarm 更新一个设备阈值告警。 UpdateDeviceThresholdAlarm(ctx context.Context, taskID int, req *dto.UpdateDeviceThresholdAlarmDTO) error + // GetDeviceThresholdAlarm 根据ID获取一个设备阈值告警任务。 + GetDeviceThresholdAlarm(ctx context.Context, taskID int) (*dto.DeviceThresholdAlarmDTO, error) // CreateAreaThresholdAlarm 创建一个区域阈值告警。 CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error // UpdateAreaThresholdAlarm 更新一个区域阈值告警。 UpdateAreaThresholdAlarm(ctx context.Context, taskID int, req *dto.UpdateAreaThresholdAlarmDTO) error + // GetAreaThresholdAlarm 根据ID获取一个区域阈值告警任务。 + GetAreaThresholdAlarm(ctx context.Context, taskID int) (*dto.AreaThresholdAlarmDTO, error) } // thresholdAlarmService 是 ThresholdAlarmService 接口的具体实现。 @@ -254,6 +259,38 @@ func (s *thresholdAlarmService) UpdateDeviceThresholdAlarm(ctx context.Context, return err } +// GetDeviceThresholdAlarm 实现了根据ID获取一个设备阈值告警任务的逻辑。 +func (s *thresholdAlarmService) GetDeviceThresholdAlarm(ctx context.Context, taskID int) (*dto.DeviceThresholdAlarmDTO, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetDeviceThresholdAlarm") + + // 1. 使用 planRepo 查询任务 + t, err := s.planRepo.FindTaskByID(serviceCtx, taskID) + if err != nil { + return nil, err // 如果未找到或发生其他错误,直接返回 + } + + // 2. 验证任务类型是否正确 + if t.Type != models.TaskTypeDeviceThresholdCheck { + return nil, fmt.Errorf("任务 %d 不是一个设备阈值检查任务", taskID) + } + var params task.DeviceThresholdCheckParams + err = t.ParseParameters(¶ms) + if err != nil { + return nil, fmt.Errorf("任务 %d: 解析参数失败: %w", taskID, err) + } + + resp := &dto.DeviceThresholdAlarmDTO{ + ID: t.ID, + DeviceID: params.DeviceID, + SensorType: params.SensorType, + Thresholds: params.Thresholds, + Operator: params.Operator, + Level: params.Level, + } + + return resp, nil +} + // CreateAreaThresholdAlarm 实现了创建一个区域阈值告警的逻辑。 func (s *thresholdAlarmService) CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreateAreaThresholdAlarm") @@ -374,3 +411,34 @@ func (s *thresholdAlarmService) UpdateAreaThresholdAlarm(ctx context.Context, ta _, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem) return err } + +// GetAreaThresholdAlarm 实现了根据ID获取一个区域阈值告警任务的逻辑。 +func (s *thresholdAlarmService) GetAreaThresholdAlarm(ctx context.Context, taskID int) (*dto.AreaThresholdAlarmDTO, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetAreaThresholdAlarm") + + // 1. 使用 planRepo 查询任务 + t, err := s.planRepo.FindTaskByID(serviceCtx, taskID) + if err != nil { + return nil, err // 如果未找到或发生其他错误,直接返回 + } + + // 2. 验证任务类型是否正确 + if t.Type != models.TaskTypeAreaCollectorThresholdCheck { + return nil, fmt.Errorf("任务 %d 不是一个区域阈值检查任务", taskID) + } + var params task.AreaThresholdCheckParams + err = t.ParseParameters(¶ms) + if err != nil { + return nil, fmt.Errorf("任务 %d: 解析参数失败: %w", taskID, err) + } + + resp := &dto.AreaThresholdAlarmDTO{ + ID: t.ID, + AreaControllerID: params.AreaControllerID, + SensorType: params.SensorType, + Thresholds: params.Thresholds, + Operator: params.Operator, + Level: params.Level, + } + return resp, nil +} diff --git a/internal/infra/repository/plan_repository.go b/internal/infra/repository/plan_repository.go index 9418db1..e623fc0 100644 --- a/internal/infra/repository/plan_repository.go +++ b/internal/infra/repository/plan_repository.go @@ -69,25 +69,22 @@ type PlanRepository interface { FlattenPlanTasks(ctx context.Context, planID uint) ([]models.Task, error) // DeleteTask 根据ID删除任务 DeleteTask(ctx context.Context, id int) error + // FindTaskByID 根据ID获取任务的基本信息 + FindTaskByID(ctx context.Context, id int) (*models.Task, error) // FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task FindPlanAnalysisTaskByParamsPlanID(ctx context.Context, paramsPlanID uint) (*models.Task, error) // FindRunnablePlans 获取所有应执行的计划 FindRunnablePlans(ctx context.Context) ([]*models.Plan, error) // FindInactivePlans 获取所有已禁用或已停止的计划 FindInactivePlans(ctx context.Context) ([]*models.Plan, error) - // FindPlanAnalysisTaskByPlanID 根据 PlanID 找到其关联的 'plan_analysis' 任务 FindPlanAnalysisTaskByPlanID(ctx context.Context, planID uint) (*models.Task, error) - // CreatePlanAnalysisTask 创建一个 plan_analysis 类型的任务并返回它 CreatePlanAnalysisTask(ctx context.Context, plan *models.Plan) (*models.Task, error) - // FindPlansWithPendingTasks 查找所有正在执行的计划 FindPlansWithPendingTasks(ctx context.Context) ([]*models.Plan, error) - // StopPlanTransactionally 停止一个计划的执行,包括更新状态、移除待执行任务和更新执行日志 StopPlanTransactionally(ctx context.Context, planID uint) error - // UpdatePlanStateAfterExecution 更新计划执行后的状态(计数和状态) UpdatePlanStateAfterExecution(ctx context.Context, planID uint, newCount uint, newStatus models.PlanStatus) error } @@ -870,3 +867,14 @@ func (r *gormPlanRepository) UpdateExecuteCount(ctx context.Context, id uint, co } return nil } + +// FindTaskByID 根据ID获取任务的基本信息 +func (r *gormPlanRepository) FindTaskByID(ctx context.Context, id int) (*models.Task, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "FindTaskByID") + var task models.Task + result := r.db.WithContext(repoCtx).First(&task, id) + if result.Error != nil { + return nil, result.Error + } + return &task, nil +} From cb075c907d7ab14b1e4dab806b78880f9ea1ea80 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 18:22:00 +0800 Subject: [PATCH 28/35] DeleteDeviceThresholdAlarm DeleteAreaThresholdAlarm --- design/exceeding-threshold-alarm/index.md | 3 +- internal/app/dto/alarm_dto.go | 5 + internal/app/dto/monitor_converter.go | 2 +- internal/app/dto/plan_converter.go | 6 +- .../app/service/threshold_alarm_service.go | 113 ++++++++++++++++++ internal/core/component_initializers.go | 4 + internal/core/data_initializer.go | 6 +- 7 files changed, 131 insertions(+), 8 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 4959eac..aa57023 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -139,4 +139,5 @@ 7. 实现忽略告警和取消忽略告警接口及功能 8. 实现列表查询活跃告警和历史告警 9. 系统初始化时健康计划调整(包括增加延时任务) -10. 实现区域阈值告警任务 \ No newline at end of file +10. 实现区域阈值告警任务 +11. 实现区域阈值告警和设备阈值告警的增删改查 \ No newline at end of file diff --git a/internal/app/dto/alarm_dto.go b/internal/app/dto/alarm_dto.go index 2f097c6..bf55fb9 100644 --- a/internal/app/dto/alarm_dto.go +++ b/internal/app/dto/alarm_dto.go @@ -114,6 +114,11 @@ type UpdateAreaThresholdAlarmDTO struct { Level models.SeverityLevel `json:"level,omitempty"` // 新的告警等级,可选 } +// DeleteDeviceThresholdAlarmDTO 删除设备阈值告警的请求DTO +type DeleteDeviceThresholdAlarmDTO struct { + SensorType models.SensorType `json:"sensor_type" binding:"required"` // 传感器类型 +} + // AreaThresholdAlarmDTO 用于表示一个区域阈值告警任务的详细信息 type AreaThresholdAlarmDTO struct { ID int `json:"id"` diff --git a/internal/app/dto/monitor_converter.go b/internal/app/dto/monitor_converter.go index c45d597..f7192f0 100644 --- a/internal/app/dto/monitor_converter.go +++ b/internal/app/dto/monitor_converter.go @@ -56,7 +56,7 @@ func NewListDeviceCommandLogResponse(data []models.DeviceCommandLog, total int64 func NewListPlanExecutionLogResponse(planLogs []models.PlanExecutionLog, plans []models.Plan, total int64, page, pageSize int) *ListPlanExecutionLogResponse { planId2Name := make(map[uint]string) for _, plan := range plans { - planId2Name[plan.ID] = plan.Name + planId2Name[plan.ID] = string(plan.Name) } dtos := make([]PlanExecutionLogDTO, len(planLogs)) diff --git a/internal/app/dto/plan_converter.go b/internal/app/dto/plan_converter.go index adc018c..e7313cc 100644 --- a/internal/app/dto/plan_converter.go +++ b/internal/app/dto/plan_converter.go @@ -15,7 +15,7 @@ func NewPlanToResponse(plan *models.Plan) (*PlanResponse, error) { response := &PlanResponse{ ID: plan.ID, - Name: plan.Name, + Name: string(plan.Name), Description: plan.Description, PlanType: plan.PlanType, ExecutionType: plan.ExecutionType, @@ -60,7 +60,7 @@ func NewPlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) { } plan := &models.Plan{ - Name: req.Name, + Name: models.PlanName(req.Name), Description: req.Description, ExecutionType: req.ExecutionType, ExecuteNum: req.ExecuteNum, @@ -103,7 +103,7 @@ func NewPlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) { } plan := &models.Plan{ - Name: req.Name, + Name: models.PlanName(req.Name), Description: req.Description, ExecutionType: req.ExecutionType, ExecuteNum: req.ExecuteNum, diff --git a/internal/app/service/threshold_alarm_service.go b/internal/app/service/threshold_alarm_service.go index 82d65ed..e954919 100644 --- a/internal/app/service/threshold_alarm_service.go +++ b/internal/app/service/threshold_alarm_service.go @@ -32,12 +32,17 @@ type ThresholdAlarmService interface { UpdateDeviceThresholdAlarm(ctx context.Context, taskID int, req *dto.UpdateDeviceThresholdAlarmDTO) error // GetDeviceThresholdAlarm 根据ID获取一个设备阈值告警任务。 GetDeviceThresholdAlarm(ctx context.Context, taskID int) (*dto.DeviceThresholdAlarmDTO, error) + // DeleteDeviceThresholdAlarm 删除一个设备阈值告警。 + DeleteDeviceThresholdAlarm(ctx context.Context, taskID int, req *dto.DeleteDeviceThresholdAlarmDTO) error + // CreateAreaThresholdAlarm 创建一个区域阈值告警。 CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error // UpdateAreaThresholdAlarm 更新一个区域阈值告警。 UpdateAreaThresholdAlarm(ctx context.Context, taskID int, req *dto.UpdateAreaThresholdAlarmDTO) error // GetAreaThresholdAlarm 根据ID获取一个区域阈值告警任务。 GetAreaThresholdAlarm(ctx context.Context, taskID int) (*dto.AreaThresholdAlarmDTO, error) + // DeleteAreaThresholdAlarm 实现了删除一个区域阈值告警的逻辑。 + DeleteAreaThresholdAlarm(ctx context.Context, taskID int) error } // thresholdAlarmService 是 ThresholdAlarmService 接口的具体实现。 @@ -48,6 +53,7 @@ type thresholdAlarmService struct { alarmRepo repository.AlarmRepository planRepo repository.PlanRepository + areaRepo repository.AreaControllerRepository deviceRepo repository.DeviceRepository } @@ -57,6 +63,7 @@ func NewThresholdAlarmService(ctx context.Context, planService plan.Service, alarmRepo repository.AlarmRepository, planRepo repository.PlanRepository, + areaRepo repository.AreaControllerRepository, deviceRepo repository.DeviceRepository, ) ThresholdAlarmService { return &thresholdAlarmService{ @@ -65,6 +72,7 @@ func NewThresholdAlarmService(ctx context.Context, planService: planService, alarmRepo: alarmRepo, planRepo: planRepo, + areaRepo: areaRepo, deviceRepo: deviceRepo, } } @@ -291,6 +299,77 @@ func (s *thresholdAlarmService) GetDeviceThresholdAlarm(ctx context.Context, tas return resp, nil } +// DeleteDeviceThresholdAlarm 实现了删除一个设备阈值告警的逻辑。 +func (s *thresholdAlarmService) DeleteDeviceThresholdAlarm(ctx context.Context, taskID int, req *dto.DeleteDeviceThresholdAlarmDTO) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeleteDeviceThresholdAlarm") + + // 获取待删除任务并校验 + deleteTask, err := s.planRepo.FindTaskByID(serviceCtx, taskID) + if err != nil { + return fmt.Errorf("获取任务失败: %w", err) + } + if deleteTask.Type != models.TaskTypeDeviceThresholdCheck { + return fmt.Errorf("任务 %d 不是一个设备阈值检查任务", taskID) + } + var deviceParams task.DeviceThresholdCheckParams + if err := deleteTask.ParseParameters(&deviceParams); err != nil { + return fmt.Errorf("任务 %d: 解析参数失败: %w", taskID, err) + } + + // 获得任务对应设备对应区域主控 + device, err := s.deviceRepo.FindByID(serviceCtx, deviceParams.DeviceID) + if err != nil { + return fmt.Errorf("获取设备 %d 失败: %w", deviceParams.DeviceID, err) + } + area, err := s.areaRepo.FindByID(serviceCtx, device.AreaControllerID) + if err != nil { + return fmt.Errorf("获取区域 %d 失败: %w", device.AreaControllerID, err) + } + + // 获取健康检查计划任务列表 + plan, err := s.planRepo.GetSystemPlanByName(serviceCtx, models.PlanNamePeriodicSystemHealthCheck) + if err != nil { + return fmt.Errorf("获取系统健康检查计划失败: %w", err) + } + if plan == nil { + logger.Panicf("系统计划 %v 不存在", models.PlanNamePeriodicSystemHealthCheck) + } + + taskIndexToDelete := -1 + for i, t := range plan.Tasks { + if t.ID == taskID { + taskIndexToDelete = i + } + if t.Type == models.TaskTypeAreaCollectorThresholdCheck { + var areaParams task.AreaThresholdCheckParams + if err := t.ParseParameters(&areaParams); err != nil { + return fmt.Errorf("任务 %d: 解析参数失败: %w", t.ID, err) + } + if areaParams.AreaControllerID == area.ID && areaParams.SensorType == deviceParams.SensorType { + for ia, e := range areaParams.ExcludeDeviceIDs { + if e == deviceParams.DeviceID { + areaParams.ExcludeDeviceIDs = append(areaParams.ExcludeDeviceIDs[:ia], areaParams.ExcludeDeviceIDs[ia+1:]...) + continue + } + } + err = plan.Tasks[i].SaveParameters(areaParams) + if err != nil { + return fmt.Errorf("任务 %d: 保存参数失败: %w", t.ID, err) + } + } + } + } + + if taskIndexToDelete == -1 { + return fmt.Errorf("任务 %d 在系统计划中未找到", taskID) + } + plan.Tasks = append(plan.Tasks[:taskIndexToDelete], plan.Tasks[taskIndexToDelete+1:]...) + + plan.ReorderSteps() + _, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem) + return err +} + // CreateAreaThresholdAlarm 实现了创建一个区域阈值告警的逻辑。 func (s *thresholdAlarmService) CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreateAreaThresholdAlarm") @@ -442,3 +521,37 @@ func (s *thresholdAlarmService) GetAreaThresholdAlarm(ctx context.Context, taskI } return resp, nil } + +// DeleteAreaThresholdAlarm 实现了删除一个区域阈值告警的逻辑。 +func (s *thresholdAlarmService) DeleteAreaThresholdAlarm(ctx context.Context, taskID int) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeleteAreaThresholdAlarm") + + // 获取健康检查计划任务列表 + plan, err := s.planRepo.GetSystemPlanByName(serviceCtx, models.PlanNamePeriodicSystemHealthCheck) + if err != nil { + return fmt.Errorf("获取系统健康检查计划失败: %w", err) + } + if plan == nil { + logger.Panicf("系统计划 %v 不存在", models.PlanNamePeriodicSystemHealthCheck) + } + + // 找到这个任务并删掉 + deleteTaskNum := -1 + for i, t := range plan.Tasks { + if t.Type != models.TaskTypeAreaCollectorThresholdCheck { + continue + } + if t.ID != taskID { + continue + } + deleteTaskNum = i + } + + if deleteTaskNum == -1 { + return fmt.Errorf("任务 %d: 不存在或类型不匹配", taskID) + } + plan.Tasks = append(plan.Tasks[:deleteTaskNum], plan.Tasks[deleteTaskNum+1:]...) + plan.ReorderSteps() + _, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem) + return err +} diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index af77dec..f2b0560 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -269,7 +269,11 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices thresholdAlarmService := service.NewThresholdAlarmService( logs.AddCompName(baseCtx, "ThresholdAlarmService"), domainServices.alarmService, + domainServices.planService, infra.repos.alarmRepo, + infra.repos.planRepo, + infra.repos.areaControllerRepo, + infra.repos.deviceRepo, ) return &AppServices{ diff --git a/internal/core/data_initializer.go b/internal/core/data_initializer.go index c66bddf..9560aac 100644 --- a/internal/core/data_initializer.go +++ b/internal/core/data_initializer.go @@ -58,7 +58,7 @@ func (app *Application) initializeSystemPlans(ctx context.Context) error { } // 2. 为了方便查找, 将现有计划名放入一个 map - existingPlanMap := make(map[string]*models.Plan) + existingPlanMap := make(map[models.PlanName]*models.Plan) for i := range existingPlans { existingPlanMap[existingPlans[i].Name] = &existingPlans[i] } @@ -78,7 +78,7 @@ func (app *Application) initializeSystemPlans(ctx context.Context) error { // initializePeriodicSystemHealthCheckPlan 负责初始化 "周期性系统健康检查" 计划。 // 它会根据当前配置动态构建计划,并决定是创建新计划还是更新现有计划。 -func (app *Application) initializePeriodicSystemHealthCheckPlan(ctx context.Context, existingPlanMap map[string]*models.Plan) error { +func (app *Application) initializePeriodicSystemHealthCheckPlan(ctx context.Context, existingPlanMap map[models.PlanName]*models.Plan) error { appCtx, logger := logs.Trace(ctx, app.Ctx, "initializePeriodicSystemHealthCheckPlan") // 根据配置创建定时全量采集计划 @@ -175,7 +175,7 @@ func (app *Application) initializePeriodicSystemHealthCheckPlan(ctx context.Cont // initializeAlarmNotificationPlan 负责初始化 "告警通知发送" 计划。 // 它确保系统中存在一个每分钟执行的、用于发送告警通知的预定义计划。 -func (app *Application) initializeAlarmNotificationPlan(ctx context.Context, existingPlanMap map[string]*models.Plan) error { +func (app *Application) initializeAlarmNotificationPlan(ctx context.Context, existingPlanMap map[models.PlanName]*models.Plan) error { appCtx, logger := logs.Trace(ctx, app.Ctx, "initializeAlarmNotificationPlan") predefinedPlan := &models.Plan{ From 2cc4135b280fcbab8f28f35aa8a21847741c00a3 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 19:18:49 +0800 Subject: [PATCH 29/35] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BB=BB=E5=8A=A111?= =?UTF-8?q?=E5=BA=94=E7=9A=84=E5=85=AB=E4=B8=AAweb=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 +- docs/docs.go | 573 +++++++++++++++++- docs/swagger.json | 573 +++++++++++++++++- docs/swagger.yaml | 353 +++++++++++ internal/app/api/router.go | 13 + .../alarm/threshold_alarm_controller.go | 276 +++++++++ 6 files changed, 1784 insertions(+), 7 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index aa57023..f9e25c8 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -140,4 +140,5 @@ 8. 实现列表查询活跃告警和历史告警 9. 系统初始化时健康计划调整(包括增加延时任务) 10. 实现区域阈值告警任务 -11. 实现区域阈值告警和设备阈值告警的增删改查 \ No newline at end of file +11. 实现区域阈值告警和设备阈值告警的增删改查 +12. 实现任务11应的八个web接口 \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 5147fa8..05aa23a 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -145,6 +145,340 @@ const docTemplate = `{ } } }, + "/api/v1/alarm/threshold/area": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "为指定的区域主控创建一个新的阈值告警规则", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "创建区域阈值告警", + "parameters": [ + { + "description": "创建区域阈值告警请求体", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CreateAreaThresholdAlarmDTO" + } + } + ], + "responses": { + "200": { + "description": "成功创建区域阈值告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, + "/api/v1/alarm/threshold/area/{task_id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据任务ID获取单个区域阈值告警规则的详细信息", + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "获取区域阈值告警", + "parameters": [ + { + "type": "integer", + "description": "任务ID", + "name": "task_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "成功获取区域阈值告警", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.AreaThresholdAlarmDTO" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据任务ID更新已存在的区域阈值告警规则", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "更新区域阈值告警", + "parameters": [ + { + "type": "integer", + "description": "任务ID", + "name": "task_id", + "in": "path", + "required": true + }, + { + "description": "更新区域阈值告警请求体", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateAreaThresholdAlarmDTO" + } + } + ], + "responses": { + "200": { + "description": "成功更新区域阈值告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据任务ID删除区域阈值告警规则", + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "删除区域阈值告警", + "parameters": [ + { + "type": "integer", + "description": "任务ID", + "name": "task_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "成功删除区域阈值告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, + "/api/v1/alarm/threshold/device": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "为单个设备创建一条新的阈值告警规则", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "创建设备阈值告警", + "parameters": [ + { + "description": "创建设备阈值告警请求体", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CreateDeviceThresholdAlarmDTO" + } + } + ], + "responses": { + "200": { + "description": "成功创建设备阈值告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, + "/api/v1/alarm/threshold/device/{task_id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据任务ID获取单个设备阈值告警规则的详细信息", + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "获取设备阈值告警", + "parameters": [ + { + "type": "integer", + "description": "任务ID", + "name": "task_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "成功获取设备阈值告警", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.DeviceThresholdAlarmDTO" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据任务ID更新已存在的设备阈值告警规则", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "更新设备阈值告警", + "parameters": [ + { + "type": "integer", + "description": "任务ID", + "name": "task_id", + "in": "path", + "required": true + }, + { + "description": "更新设备阈值告警请求体", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateDeviceThresholdAlarmDTO" + } + } + ], + "responses": { + "200": { + "description": "成功更新设备阈值告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据任务ID删除设备阈值告警规则", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "删除设备阈值告警", + "parameters": [ + { + "type": "integer", + "description": "任务ID", + "name": "task_id", + "in": "path", + "required": true + }, + { + "description": "删除设备阈值告警请求体", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DeleteDeviceThresholdAlarmDTO" + } + } + ], + "responses": { + "200": { + "description": "成功删除设备阈值告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, "/api/v1/alarm/threshold/historical-alarms": { "get": { "security": [ @@ -4577,6 +4911,29 @@ const docTemplate = `{ } } }, + "dto.AreaThresholdAlarmDTO": { + "type": "object", + "properties": { + "area_controller_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "level": { + "$ref": "#/definitions/models.SeverityLevel" + }, + "operator": { + "$ref": "#/definitions/models.Operator" + }, + "sensor_type": { + "$ref": "#/definitions/models.SensorType" + }, + "thresholds": { + "type": "number" + } + } + }, "dto.AssignEmptyPensToBatchRequest": { "type": "object", "required": [ @@ -4664,6 +5021,49 @@ const docTemplate = `{ } } }, + "dto.CreateAreaThresholdAlarmDTO": { + "type": "object", + "required": [ + "area_controller_id", + "operator", + "sensor_type", + "thresholds" + ], + "properties": { + "area_controller_id": { + "description": "区域主控ID", + "type": "integer" + }, + "level": { + "description": "告警等级,可选", + "allOf": [ + { + "$ref": "#/definitions/models.SeverityLevel" + } + ] + }, + "operator": { + "description": "操作符", + "allOf": [ + { + "$ref": "#/definitions/models.Operator" + } + ] + }, + "sensor_type": { + "description": "传感器类型", + "allOf": [ + { + "$ref": "#/definitions/models.SensorType" + } + ] + }, + "thresholds": { + "description": "阈值", + "type": "number" + } + } + }, "dto.CreateDeviceRequest": { "type": "object", "required": [ @@ -4722,6 +5122,49 @@ const docTemplate = `{ } } }, + "dto.CreateDeviceThresholdAlarmDTO": { + "type": "object", + "required": [ + "device_id", + "operator", + "sensor_type", + "thresholds" + ], + "properties": { + "device_id": { + "description": "设备ID", + "type": "integer" + }, + "level": { + "description": "告警等级,可选,如果未提供则使用默认值", + "allOf": [ + { + "$ref": "#/definitions/models.SeverityLevel" + } + ] + }, + "operator": { + "description": "操作符 (使用string类型,与前端交互更通用)", + "allOf": [ + { + "$ref": "#/definitions/models.Operator" + } + ] + }, + "sensor_type": { + "description": "传感器类型", + "allOf": [ + { + "$ref": "#/definitions/models.SensorType" + } + ] + }, + "thresholds": { + "description": "阈值", + "type": "number" + } + } + }, "dto.CreatePenRequest": { "type": "object", "required": [ @@ -4831,6 +5274,22 @@ const docTemplate = `{ } } }, + "dto.DeleteDeviceThresholdAlarmDTO": { + "type": "object", + "required": [ + "sensor_type" + ], + "properties": { + "sensor_type": { + "description": "传感器类型", + "allOf": [ + { + "$ref": "#/definitions/models.SensorType" + } + ] + } + } + }, "dto.DeviceCommandLogDTO": { "type": "object", "properties": { @@ -4923,6 +5382,29 @@ const docTemplate = `{ } } }, + "dto.DeviceThresholdAlarmDTO": { + "type": "object", + "properties": { + "device_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "level": { + "$ref": "#/definitions/models.SeverityLevel" + }, + "operator": { + "$ref": "#/definitions/models.Operator" + }, + "sensor_type": { + "$ref": "#/definitions/models.SensorType" + }, + "thresholds": { + "type": "number" + } + } + }, "dto.FeedFormulaDTO": { "type": "object", "properties": { @@ -6592,6 +7074,35 @@ const docTemplate = `{ } } }, + "dto.UpdateAreaThresholdAlarmDTO": { + "type": "object", + "required": [ + "operator", + "thresholds" + ], + "properties": { + "level": { + "description": "新的告警等级,可选", + "allOf": [ + { + "$ref": "#/definitions/models.SeverityLevel" + } + ] + }, + "operator": { + "description": "新的操作符", + "allOf": [ + { + "$ref": "#/definitions/models.Operator" + } + ] + }, + "thresholds": { + "description": "新的阈值", + "type": "number" + } + } + }, "dto.UpdateDeviceRequest": { "type": "object", "required": [ @@ -6650,6 +7161,35 @@ const docTemplate = `{ } } }, + "dto.UpdateDeviceThresholdAlarmDTO": { + "type": "object", + "required": [ + "operator", + "thresholds" + ], + "properties": { + "level": { + "description": "新的告警等级,可选", + "allOf": [ + { + "$ref": "#/definitions/models.SeverityLevel" + } + ] + }, + "operator": { + "description": "新的操作符", + "allOf": [ + { + "$ref": "#/definitions/models.Operator" + } + ] + }, + "thresholds": { + "description": "新的阈值", + "type": "number" + } + } + }, "dto.UpdatePenRequest": { "type": "object", "required": [ @@ -7027,6 +7567,25 @@ const docTemplate = `{ "NotifierTypeLog" ] }, + "models.Operator": { + "type": "string", + "enum": [ + "\u003c", + "\u003c=", + "\u003e", + "\u003e=", + "=", + "!=" + ], + "x-enum-varnames": [ + "OperatorLessThan", + "OperatorLessThanOrEqualTo", + "OperatorGreaterThan", + "OperatorGreaterThanOrEqualTo", + "OperatorEqualTo", + "OperatorNotEqualTo" + ] + }, "models.PenStatus": { "type": "string", "enum": [ @@ -7357,11 +7916,15 @@ const docTemplate = `{ "等待", "下料", "全量采集", - "告警通知" + "告警通知", + "设备阈值检查", + "区域阈值检查" ], "x-enum-comments": { "TaskPlanAnalysis": "解析Plan的Task列表并添加到待执行队列的特殊任务", "TaskTypeAlarmNotification": "告警通知任务", + "TaskTypeAreaCollectorThresholdCheck": "区域阈值检查任务", + "TaskTypeDeviceThresholdCheck": "设备阈值检查任务", "TaskTypeFullCollection": "新增的全量采集任务", "TaskTypeReleaseFeedWeight": "下料口释放指定重量任务", "TaskTypeWaiting": "等待任务" @@ -7371,14 +7934,18 @@ const docTemplate = `{ "等待任务", "下料口释放指定重量任务", "新增的全量采集任务", - "告警通知任务" + "告警通知任务", + "设备阈值检查任务", + "区域阈值检查任务" ], "x-enum-varnames": [ "TaskPlanAnalysis", "TaskTypeWaiting", "TaskTypeReleaseFeedWeight", "TaskTypeFullCollection", - "TaskTypeAlarmNotification" + "TaskTypeAlarmNotification", + "TaskTypeDeviceThresholdCheck", + "TaskTypeAreaCollectorThresholdCheck" ] }, "models.ValueDescriptor": { diff --git a/docs/swagger.json b/docs/swagger.json index 8fa4a88..b3cfe1a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -137,6 +137,340 @@ } } }, + "/api/v1/alarm/threshold/area": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "为指定的区域主控创建一个新的阈值告警规则", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "创建区域阈值告警", + "parameters": [ + { + "description": "创建区域阈值告警请求体", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CreateAreaThresholdAlarmDTO" + } + } + ], + "responses": { + "200": { + "description": "成功创建区域阈值告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, + "/api/v1/alarm/threshold/area/{task_id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据任务ID获取单个区域阈值告警规则的详细信息", + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "获取区域阈值告警", + "parameters": [ + { + "type": "integer", + "description": "任务ID", + "name": "task_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "成功获取区域阈值告警", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.AreaThresholdAlarmDTO" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据任务ID更新已存在的区域阈值告警规则", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "更新区域阈值告警", + "parameters": [ + { + "type": "integer", + "description": "任务ID", + "name": "task_id", + "in": "path", + "required": true + }, + { + "description": "更新区域阈值告警请求体", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateAreaThresholdAlarmDTO" + } + } + ], + "responses": { + "200": { + "description": "成功更新区域阈值告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据任务ID删除区域阈值告警规则", + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "删除区域阈值告警", + "parameters": [ + { + "type": "integer", + "description": "任务ID", + "name": "task_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "成功删除区域阈值告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, + "/api/v1/alarm/threshold/device": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "为单个设备创建一条新的阈值告警规则", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "创建设备阈值告警", + "parameters": [ + { + "description": "创建设备阈值告警请求体", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CreateDeviceThresholdAlarmDTO" + } + } + ], + "responses": { + "200": { + "description": "成功创建设备阈值告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, + "/api/v1/alarm/threshold/device/{task_id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据任务ID获取单个设备阈值告警规则的详细信息", + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "获取设备阈值告警", + "parameters": [ + { + "type": "integer", + "description": "任务ID", + "name": "task_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "成功获取设备阈值告警", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.DeviceThresholdAlarmDTO" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据任务ID更新已存在的设备阈值告警规则", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "更新设备阈值告警", + "parameters": [ + { + "type": "integer", + "description": "任务ID", + "name": "task_id", + "in": "path", + "required": true + }, + { + "description": "更新设备阈值告警请求体", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateDeviceThresholdAlarmDTO" + } + } + ], + "responses": { + "200": { + "description": "成功更新设备阈值告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据任务ID删除设备阈值告警规则", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "告警管理" + ], + "summary": "删除设备阈值告警", + "parameters": [ + { + "type": "integer", + "description": "任务ID", + "name": "task_id", + "in": "path", + "required": true + }, + { + "description": "删除设备阈值告警请求体", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DeleteDeviceThresholdAlarmDTO" + } + } + ], + "responses": { + "200": { + "description": "成功删除设备阈值告警", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, "/api/v1/alarm/threshold/historical-alarms": { "get": { "security": [ @@ -4569,6 +4903,29 @@ } } }, + "dto.AreaThresholdAlarmDTO": { + "type": "object", + "properties": { + "area_controller_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "level": { + "$ref": "#/definitions/models.SeverityLevel" + }, + "operator": { + "$ref": "#/definitions/models.Operator" + }, + "sensor_type": { + "$ref": "#/definitions/models.SensorType" + }, + "thresholds": { + "type": "number" + } + } + }, "dto.AssignEmptyPensToBatchRequest": { "type": "object", "required": [ @@ -4656,6 +5013,49 @@ } } }, + "dto.CreateAreaThresholdAlarmDTO": { + "type": "object", + "required": [ + "area_controller_id", + "operator", + "sensor_type", + "thresholds" + ], + "properties": { + "area_controller_id": { + "description": "区域主控ID", + "type": "integer" + }, + "level": { + "description": "告警等级,可选", + "allOf": [ + { + "$ref": "#/definitions/models.SeverityLevel" + } + ] + }, + "operator": { + "description": "操作符", + "allOf": [ + { + "$ref": "#/definitions/models.Operator" + } + ] + }, + "sensor_type": { + "description": "传感器类型", + "allOf": [ + { + "$ref": "#/definitions/models.SensorType" + } + ] + }, + "thresholds": { + "description": "阈值", + "type": "number" + } + } + }, "dto.CreateDeviceRequest": { "type": "object", "required": [ @@ -4714,6 +5114,49 @@ } } }, + "dto.CreateDeviceThresholdAlarmDTO": { + "type": "object", + "required": [ + "device_id", + "operator", + "sensor_type", + "thresholds" + ], + "properties": { + "device_id": { + "description": "设备ID", + "type": "integer" + }, + "level": { + "description": "告警等级,可选,如果未提供则使用默认值", + "allOf": [ + { + "$ref": "#/definitions/models.SeverityLevel" + } + ] + }, + "operator": { + "description": "操作符 (使用string类型,与前端交互更通用)", + "allOf": [ + { + "$ref": "#/definitions/models.Operator" + } + ] + }, + "sensor_type": { + "description": "传感器类型", + "allOf": [ + { + "$ref": "#/definitions/models.SensorType" + } + ] + }, + "thresholds": { + "description": "阈值", + "type": "number" + } + } + }, "dto.CreatePenRequest": { "type": "object", "required": [ @@ -4823,6 +5266,22 @@ } } }, + "dto.DeleteDeviceThresholdAlarmDTO": { + "type": "object", + "required": [ + "sensor_type" + ], + "properties": { + "sensor_type": { + "description": "传感器类型", + "allOf": [ + { + "$ref": "#/definitions/models.SensorType" + } + ] + } + } + }, "dto.DeviceCommandLogDTO": { "type": "object", "properties": { @@ -4915,6 +5374,29 @@ } } }, + "dto.DeviceThresholdAlarmDTO": { + "type": "object", + "properties": { + "device_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "level": { + "$ref": "#/definitions/models.SeverityLevel" + }, + "operator": { + "$ref": "#/definitions/models.Operator" + }, + "sensor_type": { + "$ref": "#/definitions/models.SensorType" + }, + "thresholds": { + "type": "number" + } + } + }, "dto.FeedFormulaDTO": { "type": "object", "properties": { @@ -6584,6 +7066,35 @@ } } }, + "dto.UpdateAreaThresholdAlarmDTO": { + "type": "object", + "required": [ + "operator", + "thresholds" + ], + "properties": { + "level": { + "description": "新的告警等级,可选", + "allOf": [ + { + "$ref": "#/definitions/models.SeverityLevel" + } + ] + }, + "operator": { + "description": "新的操作符", + "allOf": [ + { + "$ref": "#/definitions/models.Operator" + } + ] + }, + "thresholds": { + "description": "新的阈值", + "type": "number" + } + } + }, "dto.UpdateDeviceRequest": { "type": "object", "required": [ @@ -6642,6 +7153,35 @@ } } }, + "dto.UpdateDeviceThresholdAlarmDTO": { + "type": "object", + "required": [ + "operator", + "thresholds" + ], + "properties": { + "level": { + "description": "新的告警等级,可选", + "allOf": [ + { + "$ref": "#/definitions/models.SeverityLevel" + } + ] + }, + "operator": { + "description": "新的操作符", + "allOf": [ + { + "$ref": "#/definitions/models.Operator" + } + ] + }, + "thresholds": { + "description": "新的阈值", + "type": "number" + } + } + }, "dto.UpdatePenRequest": { "type": "object", "required": [ @@ -7019,6 +7559,25 @@ "NotifierTypeLog" ] }, + "models.Operator": { + "type": "string", + "enum": [ + "\u003c", + "\u003c=", + "\u003e", + "\u003e=", + "=", + "!=" + ], + "x-enum-varnames": [ + "OperatorLessThan", + "OperatorLessThanOrEqualTo", + "OperatorGreaterThan", + "OperatorGreaterThanOrEqualTo", + "OperatorEqualTo", + "OperatorNotEqualTo" + ] + }, "models.PenStatus": { "type": "string", "enum": [ @@ -7349,11 +7908,15 @@ "等待", "下料", "全量采集", - "告警通知" + "告警通知", + "设备阈值检查", + "区域阈值检查" ], "x-enum-comments": { "TaskPlanAnalysis": "解析Plan的Task列表并添加到待执行队列的特殊任务", "TaskTypeAlarmNotification": "告警通知任务", + "TaskTypeAreaCollectorThresholdCheck": "区域阈值检查任务", + "TaskTypeDeviceThresholdCheck": "设备阈值检查任务", "TaskTypeFullCollection": "新增的全量采集任务", "TaskTypeReleaseFeedWeight": "下料口释放指定重量任务", "TaskTypeWaiting": "等待任务" @@ -7363,14 +7926,18 @@ "等待任务", "下料口释放指定重量任务", "新增的全量采集任务", - "告警通知任务" + "告警通知任务", + "设备阈值检查任务", + "区域阈值检查任务" ], "x-enum-varnames": [ "TaskPlanAnalysis", "TaskTypeWaiting", "TaskTypeReleaseFeedWeight", "TaskTypeFullCollection", - "TaskTypeAlarmNotification" + "TaskTypeAlarmNotification", + "TaskTypeDeviceThresholdCheck", + "TaskTypeAreaCollectorThresholdCheck" ] }, "models.ValueDescriptor": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ebfb500..c228a4f 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -102,6 +102,21 @@ definitions: updated_at: type: string type: object + dto.AreaThresholdAlarmDTO: + properties: + area_controller_id: + type: integer + id: + type: integer + level: + $ref: '#/definitions/models.SeverityLevel' + operator: + $ref: '#/definitions/models.Operator' + sensor_type: + $ref: '#/definitions/models.SensorType' + thresholds: + type: number + type: object dto.AssignEmptyPensToBatchRequest: properties: pen_ids: @@ -166,6 +181,32 @@ definitions: - name - network_id type: object + dto.CreateAreaThresholdAlarmDTO: + properties: + area_controller_id: + description: 区域主控ID + type: integer + level: + allOf: + - $ref: '#/definitions/models.SeverityLevel' + description: 告警等级,可选 + operator: + allOf: + - $ref: '#/definitions/models.Operator' + description: 操作符 + sensor_type: + allOf: + - $ref: '#/definitions/models.SensorType' + description: 传感器类型 + thresholds: + description: 阈值 + type: number + required: + - area_controller_id + - operator + - sensor_type + - thresholds + type: object dto.CreateDeviceRequest: properties: area_controller_id: @@ -206,6 +247,32 @@ definitions: - commands - name type: object + dto.CreateDeviceThresholdAlarmDTO: + properties: + device_id: + description: 设备ID + type: integer + level: + allOf: + - $ref: '#/definitions/models.SeverityLevel' + description: 告警等级,可选,如果未提供则使用默认值 + operator: + allOf: + - $ref: '#/definitions/models.Operator' + description: 操作符 (使用string类型,与前端交互更通用) + sensor_type: + allOf: + - $ref: '#/definitions/models.SensorType' + description: 传感器类型 + thresholds: + description: 阈值 + type: number + required: + - device_id + - operator + - sensor_type + - thresholds + type: object dto.CreatePenRequest: properties: capacity: @@ -280,6 +347,15 @@ definitions: example: newuser type: string type: object + dto.DeleteDeviceThresholdAlarmDTO: + properties: + sensor_type: + allOf: + - $ref: '#/definitions/models.SensorType' + description: 传感器类型 + required: + - sensor_type + type: object dto.DeviceCommandLogDTO: properties: acknowledged_at: @@ -341,6 +417,21 @@ definitions: $ref: '#/definitions/models.ValueDescriptor' type: array type: object + dto.DeviceThresholdAlarmDTO: + properties: + device_id: + type: integer + id: + type: integer + level: + $ref: '#/definitions/models.SeverityLevel' + operator: + $ref: '#/definitions/models.Operator' + sensor_type: + $ref: '#/definitions/models.SensorType' + thresholds: + type: number + type: object dto.FeedFormulaDTO: properties: id: @@ -1454,6 +1545,23 @@ definitions: - name - network_id type: object + dto.UpdateAreaThresholdAlarmDTO: + properties: + level: + allOf: + - $ref: '#/definitions/models.SeverityLevel' + description: 新的告警等级,可选 + operator: + allOf: + - $ref: '#/definitions/models.Operator' + description: 新的操作符 + thresholds: + description: 新的阈值 + type: number + required: + - operator + - thresholds + type: object dto.UpdateDeviceRequest: properties: area_controller_id: @@ -1494,6 +1602,23 @@ definitions: - commands - name type: object + dto.UpdateDeviceThresholdAlarmDTO: + properties: + level: + allOf: + - $ref: '#/definitions/models.SeverityLevel' + description: 新的告警等级,可选 + operator: + allOf: + - $ref: '#/definitions/models.Operator' + description: 新的操作符 + thresholds: + description: 新的阈值 + type: number + required: + - operator + - thresholds + type: object dto.UpdatePenRequest: properties: capacity: @@ -1767,6 +1892,22 @@ definitions: - NotifierTypeWeChat - NotifierTypeLark - NotifierTypeLog + models.Operator: + enum: + - < + - <= + - '>' + - '>=' + - = + - '!=' + type: string + x-enum-varnames: + - OperatorLessThan + - OperatorLessThanOrEqualTo + - OperatorGreaterThan + - OperatorGreaterThanOrEqualTo + - OperatorEqualTo + - OperatorNotEqualTo models.PenStatus: enum: - 空闲 @@ -2039,10 +2180,14 @@ definitions: - 下料 - 全量采集 - 告警通知 + - 设备阈值检查 + - 区域阈值检查 type: string x-enum-comments: TaskPlanAnalysis: 解析Plan的Task列表并添加到待执行队列的特殊任务 TaskTypeAlarmNotification: 告警通知任务 + TaskTypeAreaCollectorThresholdCheck: 区域阈值检查任务 + TaskTypeDeviceThresholdCheck: 设备阈值检查任务 TaskTypeFullCollection: 新增的全量采集任务 TaskTypeReleaseFeedWeight: 下料口释放指定重量任务 TaskTypeWaiting: 等待任务 @@ -2052,12 +2197,16 @@ definitions: - 下料口释放指定重量任务 - 新增的全量采集任务 - 告警通知任务 + - 设备阈值检查任务 + - 区域阈值检查任务 x-enum-varnames: - TaskPlanAnalysis - TaskTypeWaiting - TaskTypeReleaseFeedWeight - TaskTypeFullCollection - TaskTypeAlarmNotification + - TaskTypeDeviceThresholdCheck + - TaskTypeAreaCollectorThresholdCheck models.ValueDescriptor: properties: multiplier: @@ -2251,6 +2400,210 @@ paths: summary: 批量查询活跃告警 tags: - 告警管理 + /api/v1/alarm/threshold/area: + post: + consumes: + - application/json + description: 为指定的区域主控创建一个新的阈值告警规则 + parameters: + - description: 创建区域阈值告警请求体 + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.CreateAreaThresholdAlarmDTO' + produces: + - application/json + responses: + "200": + description: 成功创建区域阈值告警 + schema: + $ref: '#/definitions/controller.Response' + security: + - BearerAuth: [] + summary: 创建区域阈值告警 + tags: + - 告警管理 + /api/v1/alarm/threshold/area/{task_id}: + delete: + description: 根据任务ID删除区域阈值告警规则 + parameters: + - description: 任务ID + in: path + name: task_id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: 成功删除区域阈值告警 + schema: + $ref: '#/definitions/controller.Response' + security: + - BearerAuth: [] + summary: 删除区域阈值告警 + tags: + - 告警管理 + get: + description: 根据任务ID获取单个区域阈值告警规则的详细信息 + parameters: + - description: 任务ID + in: path + name: task_id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: 成功获取区域阈值告警 + schema: + allOf: + - $ref: '#/definitions/controller.Response' + - properties: + data: + $ref: '#/definitions/dto.AreaThresholdAlarmDTO' + type: object + security: + - BearerAuth: [] + summary: 获取区域阈值告警 + tags: + - 告警管理 + put: + consumes: + - application/json + description: 根据任务ID更新已存在的区域阈值告警规则 + parameters: + - description: 任务ID + in: path + name: task_id + required: true + type: integer + - description: 更新区域阈值告警请求体 + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.UpdateAreaThresholdAlarmDTO' + produces: + - application/json + responses: + "200": + description: 成功更新区域阈值告警 + schema: + $ref: '#/definitions/controller.Response' + security: + - BearerAuth: [] + summary: 更新区域阈值告警 + tags: + - 告警管理 + /api/v1/alarm/threshold/device: + post: + consumes: + - application/json + description: 为单个设备创建一条新的阈值告警规则 + parameters: + - description: 创建设备阈值告警请求体 + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.CreateDeviceThresholdAlarmDTO' + produces: + - application/json + responses: + "200": + description: 成功创建设备阈值告警 + schema: + $ref: '#/definitions/controller.Response' + security: + - BearerAuth: [] + summary: 创建设备阈值告警 + tags: + - 告警管理 + /api/v1/alarm/threshold/device/{task_id}: + delete: + consumes: + - application/json + description: 根据任务ID删除设备阈值告警规则 + parameters: + - description: 任务ID + in: path + name: task_id + required: true + type: integer + - description: 删除设备阈值告警请求体 + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.DeleteDeviceThresholdAlarmDTO' + produces: + - application/json + responses: + "200": + description: 成功删除设备阈值告警 + schema: + $ref: '#/definitions/controller.Response' + security: + - BearerAuth: [] + summary: 删除设备阈值告警 + tags: + - 告警管理 + get: + description: 根据任务ID获取单个设备阈值告警规则的详细信息 + parameters: + - description: 任务ID + in: path + name: task_id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: 成功获取设备阈值告警 + schema: + allOf: + - $ref: '#/definitions/controller.Response' + - properties: + data: + $ref: '#/definitions/dto.DeviceThresholdAlarmDTO' + type: object + security: + - BearerAuth: [] + summary: 获取设备阈值告警 + tags: + - 告警管理 + put: + consumes: + - application/json + description: 根据任务ID更新已存在的设备阈值告警规则 + parameters: + - description: 任务ID + in: path + name: task_id + required: true + type: integer + - description: 更新设备阈值告警请求体 + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.UpdateDeviceThresholdAlarmDTO' + produces: + - application/json + responses: + "200": + description: 成功更新设备阈值告警 + schema: + $ref: '#/definitions/controller.Response' + security: + - BearerAuth: [] + summary: 更新设备阈值告警 + tags: + - 告警管理 /api/v1/alarm/threshold/historical-alarms: get: consumes: diff --git a/internal/app/api/router.go b/internal/app/api/router.go index 5929249..6540165 100644 --- a/internal/app/api/router.go +++ b/internal/app/api/router.go @@ -197,6 +197,19 @@ func (a *API) setupRoutes() { thresholdGroup.POST("/:id/cancel-snooze", a.alarmController.CancelSnoozeThresholdAlarm) // 取消忽略阈值告警 thresholdGroup.GET("/active-alarms", a.alarmController.ListActiveAlarms) // 获取活跃告警 thresholdGroup.GET("/historical-alarms", a.alarmController.ListHistoricalAlarms) // 获取历史告警 + + // 设备阈值告警配置 + 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.POST("/area", a.alarmController.CreateAreaThresholdAlarm) + thresholdGroup.GET("/area/:task_id", a.alarmController.GetAreaThresholdAlarm) + thresholdGroup.PUT("/area/:task_id", a.alarmController.UpdateAreaThresholdAlarm) + thresholdGroup.DELETE("/area/:task_id", a.alarmController.DeleteAreaThresholdAlarm) + } } logger.Debug("告警相关接口注册成功 (需要认证和审计)") diff --git a/internal/app/controller/alarm/threshold_alarm_controller.go b/internal/app/controller/alarm/threshold_alarm_controller.go index 06167f1..fed4768 100644 --- a/internal/app/controller/alarm/threshold_alarm_controller.go +++ b/internal/app/controller/alarm/threshold_alarm_controller.go @@ -178,3 +178,279 @@ func (t *ThresholdAlarmController) ListHistoricalAlarms(ctx echo.Context) error 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 为单个设备创建一条新的阈值告警规则 +// @Tags 告警管理 +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param request body dto.CreateDeviceThresholdAlarmDTO true "创建设备阈值告警请求体" +// @Success 200 {object} controller.Response "成功创建设备阈值告警" +// @Router /api/v1/alarm/threshold/device [post] +func (t *ThresholdAlarmController) CreateDeviceThresholdAlarm(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "CreateDeviceThresholdAlarm") + const actionType = "创建设备阈值告警" + + var req dto.CreateDeviceThresholdAlarmDTO + if err := ctx.Bind(&req); err != nil { + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) + } + + if err := t.thresholdAlarmService.CreateDeviceThresholdAlarm(reqCtx, &req); err != nil { + logger.Errorf("%s: 服务层创建失败: %v", actionType, err) + return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建失败: "+err.Error(), actionType, "服务层创建失败", req) + } + + logger.Infof("%s: 成功, DeviceID: %d, SensorType: %s", actionType, req.DeviceID, req.SensorType) + return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "创建成功", nil, actionType, "创建成功", req) +} + +// GetDeviceThresholdAlarm godoc +// @Summary 获取设备阈值告警 +// @Description 根据任务ID获取单个设备阈值告警规则的详细信息 +// @Tags 告警管理 +// @Security BearerAuth +// @Produce json +// @Param task_id path int true "任务ID" +// @Success 200 {object} controller.Response{data=dto.DeviceThresholdAlarmDTO} "成功获取设备阈值告警" +// @Router /api/v1/alarm/threshold/device/{task_id} [get] +func (t *ThresholdAlarmController) GetDeviceThresholdAlarm(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "GetDeviceThresholdAlarm") + const actionType = "获取设备阈值告警" + + taskID, err := strconv.Atoi(ctx.Param("task_id")) + if err != nil { + logger.Errorf("%s: 无效的任务ID: %s", actionType, ctx.Param("task_id")) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的任务ID", actionType, "无效的任务ID", ctx.Param("task_id")) + } + + resp, err := t.thresholdAlarmService.GetDeviceThresholdAlarm(reqCtx, taskID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Warnf("%s: 任务不存在, ID: %d", actionType, taskID) + return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "任务未找到", actionType, "任务不存在", taskID) + } + logger.Errorf("%s: 服务层获取失败: %v, ID: %d", actionType, err, taskID) + return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取失败: "+err.Error(), actionType, "服务层获取失败", taskID) + } + + logger.Infof("%s: 成功, ID: %d", actionType, taskID) + return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取成功", resp, actionType, "获取成功", taskID) +} + +// UpdateDeviceThresholdAlarm godoc +// @Summary 更新设备阈值告警 +// @Description 根据任务ID更新已存在的设备阈值告警规则 +// @Tags 告警管理 +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param task_id path int true "任务ID" +// @Param request body dto.UpdateDeviceThresholdAlarmDTO true "更新设备阈值告警请求体" +// @Success 200 {object} controller.Response "成功更新设备阈值告警" +// @Router /api/v1/alarm/threshold/device/{task_id} [put] +func (t *ThresholdAlarmController) UpdateDeviceThresholdAlarm(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "UpdateDeviceThresholdAlarm") + const actionType = "更新设备阈值告警" + + taskID, err := strconv.Atoi(ctx.Param("task_id")) + if err != nil { + logger.Errorf("%s: 无效的任务ID: %s", actionType, ctx.Param("task_id")) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的任务ID", actionType, "无效的任务ID", ctx.Param("task_id")) + } + + var req dto.UpdateDeviceThresholdAlarmDTO + if err := ctx.Bind(&req); err != nil { + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) + } + + if err := t.thresholdAlarmService.UpdateDeviceThresholdAlarm(reqCtx, taskID, &req); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Warnf("%s: 任务不存在, ID: %d", actionType, taskID) + return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "任务未找到", actionType, "任务不存在", taskID) + } + logger.Errorf("%s: 服务层更新失败: %v, ID: %d", actionType, err, taskID) + return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新失败: "+err.Error(), actionType, "服务层更新失败", taskID) + } + + logger.Infof("%s: 成功, ID: %d", actionType, taskID) + return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "更新成功", nil, actionType, "更新成功", taskID) +} + +// DeleteDeviceThresholdAlarm godoc +// @Summary 删除设备阈值告警 +// @Description 根据任务ID删除设备阈值告警规则 +// @Tags 告警管理 +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param task_id path int true "任务ID" +// @Param request body dto.DeleteDeviceThresholdAlarmDTO true "删除设备阈值告警请求体" +// @Success 200 {object} controller.Response "成功删除设备阈值告警" +// @Router /api/v1/alarm/threshold/device/{task_id} [delete] +func (t *ThresholdAlarmController) DeleteDeviceThresholdAlarm(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "DeleteDeviceThresholdAlarm") + const actionType = "删除设备阈值告警" + + taskID, err := strconv.Atoi(ctx.Param("task_id")) + if err != nil { + logger.Errorf("%s: 无效的任务ID: %s", actionType, ctx.Param("task_id")) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的任务ID", actionType, "无效的任务ID", ctx.Param("task_id")) + } + + var req dto.DeleteDeviceThresholdAlarmDTO + if err := ctx.Bind(&req); err != nil { + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) + } + + if err := t.thresholdAlarmService.DeleteDeviceThresholdAlarm(reqCtx, taskID, &req); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Warnf("%s: 任务不存在, ID: %d", actionType, taskID) + return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "任务未找到", actionType, "任务不存在", taskID) + } + logger.Errorf("%s: 服务层删除失败: %v, ID: %d", actionType, err, taskID) + return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除失败: "+err.Error(), actionType, "服务层删除失败", taskID) + } + + logger.Infof("%s: 成功, ID: %d", actionType, taskID) + return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "删除成功", nil, actionType, "删除成功", taskID) +} + +// CreateAreaThresholdAlarm godoc +// @Summary 创建区域阈值告警 +// @Description 为指定的区域主控创建一个新的阈值告警规则 +// @Tags 告警管理 +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param request body dto.CreateAreaThresholdAlarmDTO true "创建区域阈值告警请求体" +// @Success 200 {object} controller.Response "成功创建区域阈值告警" +// @Router /api/v1/alarm/threshold/area [post] +func (t *ThresholdAlarmController) CreateAreaThresholdAlarm(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "CreateAreaThresholdAlarm") + const actionType = "创建区域阈值告警" + + var req dto.CreateAreaThresholdAlarmDTO + if err := ctx.Bind(&req); err != nil { + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) + } + + if err := t.thresholdAlarmService.CreateAreaThresholdAlarm(reqCtx, &req); err != nil { + logger.Errorf("%s: 服务层创建失败: %v", actionType, err) + return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建失败: "+err.Error(), actionType, "服务层创建失败", req) + } + + logger.Infof("%s: 成功, AreaControllerID: %d, SensorType: %s", actionType, req.AreaControllerID, req.SensorType) + return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "创建成功", nil, actionType, "创建成功", req) +} + +// GetAreaThresholdAlarm godoc +// @Summary 获取区域阈值告警 +// @Description 根据任务ID获取单个区域阈值告警规则的详细信息 +// @Tags 告警管理 +// @Security BearerAuth +// @Produce json +// @Param task_id path int true "任务ID" +// @Success 200 {object} controller.Response{data=dto.AreaThresholdAlarmDTO} "成功获取区域阈值告警" +// @Router /api/v1/alarm/threshold/area/{task_id} [get] +func (t *ThresholdAlarmController) GetAreaThresholdAlarm(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "GetAreaThresholdAlarm") + const actionType = "获取区域阈值告警" + + taskID, err := strconv.Atoi(ctx.Param("task_id")) + if err != nil { + logger.Errorf("%s: 无效的任务ID: %s", actionType, ctx.Param("task_id")) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的任务ID", actionType, "无效的任务ID", ctx.Param("task_id")) + } + + resp, err := t.thresholdAlarmService.GetAreaThresholdAlarm(reqCtx, taskID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Warnf("%s: 任务不存在, ID: %d", actionType, taskID) + return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "任务未找到", actionType, "任务不存在", taskID) + } + logger.Errorf("%s: 服务层获取失败: %v, ID: %d", actionType, err, taskID) + return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取失败: "+err.Error(), actionType, "服务层获取失败", taskID) + } + + logger.Infof("%s: 成功, ID: %d", actionType, taskID) + return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取成功", resp, actionType, "获取成功", taskID) +} + +// UpdateAreaThresholdAlarm godoc +// @Summary 更新区域阈值告警 +// @Description 根据任务ID更新已存在的区域阈值告警规则 +// @Tags 告警管理 +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param task_id path int true "任务ID" +// @Param request body dto.UpdateAreaThresholdAlarmDTO true "更新区域阈值告警请求体" +// @Success 200 {object} controller.Response "成功更新区域阈值告警" +// @Router /api/v1/alarm/threshold/area/{task_id} [put] +func (t *ThresholdAlarmController) UpdateAreaThresholdAlarm(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "UpdateAreaThresholdAlarm") + const actionType = "更新区域阈值告警" + + taskID, err := strconv.Atoi(ctx.Param("task_id")) + if err != nil { + logger.Errorf("%s: 无效的任务ID: %s", actionType, ctx.Param("task_id")) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的任务ID", actionType, "无效的任务ID", ctx.Param("task_id")) + } + + var req dto.UpdateAreaThresholdAlarmDTO + if err := ctx.Bind(&req); err != nil { + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) + } + + if err := t.thresholdAlarmService.UpdateAreaThresholdAlarm(reqCtx, taskID, &req); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Warnf("%s: 任务不存在, ID: %d", actionType, taskID) + return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "任务未找到", actionType, "任务不存在", taskID) + } + logger.Errorf("%s: 服务层更新失败: %v, ID: %d", actionType, err, taskID) + return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新失败: "+err.Error(), actionType, "服务层更新失败", taskID) + } + + logger.Infof("%s: 成功, ID: %d", actionType, taskID) + return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "更新成功", nil, actionType, "更新成功", taskID) +} + +// DeleteAreaThresholdAlarm godoc +// @Summary 删除区域阈值告警 +// @Description 根据任务ID删除区域阈值告警规则 +// @Tags 告警管理 +// @Security BearerAuth +// @Produce json +// @Param task_id path int true "任务ID" +// @Success 200 {object} controller.Response "成功删除区域阈值告警" +// @Router /api/v1/alarm/threshold/area/{task_id} [delete] +func (t *ThresholdAlarmController) DeleteAreaThresholdAlarm(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), t.ctx, "DeleteAreaThresholdAlarm") + const actionType = "删除区域阈值告警" + + taskID, err := strconv.Atoi(ctx.Param("task_id")) + if err != nil { + logger.Errorf("%s: 无效的任务ID: %s", actionType, ctx.Param("task_id")) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的任务ID", actionType, "无效的任务ID", ctx.Param("task_id")) + } + + if err := t.thresholdAlarmService.DeleteAreaThresholdAlarm(reqCtx, taskID); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Warnf("%s: 任务不存在, ID: %d", actionType, taskID) + return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "任务未找到", actionType, "任务不存在", taskID) + } + logger.Errorf("%s: 服务层删除失败: %v, ID: %d", actionType, err, taskID) + return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除失败: "+err.Error(), actionType, "服务层删除失败", taskID) + } + + logger.Infof("%s: 成功, ID: %d", actionType, taskID) + return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "删除成功", nil, actionType, "删除成功", taskID) +} From 9dc47ec7addcf376dbb019bab6dbf5737603e34f Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 20:37:07 +0800 Subject: [PATCH 30/35] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=A0=B9=E6=8D=AE?= =?UTF-8?q?=E5=8C=BA=E5=9F=9FID=E6=88=96=E8=AE=BE=E5=A4=87ID=E6=B8=85?= =?UTF-8?q?=E7=A9=BA=E5=AF=B9=E5=BA=94=E9=98=88=E5=80=BC=E5=91=8A=E8=AD=A6?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 +- internal/app/service/device_service.go | 35 +++-- .../app/service/threshold_alarm_service.go | 131 ++++++++++++++++++ internal/infra/repository/plan_repository.go | 18 +++ 4 files changed, 176 insertions(+), 11 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index f9e25c8..3b0fe71 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -141,4 +141,5 @@ 9. 系统初始化时健康计划调整(包括增加延时任务) 10. 实现区域阈值告警任务 11. 实现区域阈值告警和设备阈值告警的增删改查 -12. 实现任务11应的八个web接口 \ No newline at end of file +12. 实现任务11应的八个web接口 +13. 实现根据区域ID或设备ID清空对应阈值告警任务 \ No newline at end of file diff --git a/internal/app/service/device_service.go b/internal/app/service/device_service.go index d00080e..7b0af75 100644 --- a/internal/app/service/device_service.go +++ b/internal/app/service/device_service.go @@ -49,11 +49,12 @@ type DeviceService interface { // deviceService 是 DeviceService 接口的具体实现。 type deviceService struct { - ctx context.Context - deviceRepo repository.DeviceRepository - areaControllerRepo repository.AreaControllerRepository - deviceTemplateRepo repository.DeviceTemplateRepository - deviceDomainSvc device.Service + ctx context.Context + deviceRepo repository.DeviceRepository + areaControllerRepo repository.AreaControllerRepository + deviceTemplateRepo repository.DeviceTemplateRepository + deviceDomainSvc device.Service + thresholdAlarmService ThresholdAlarmService } // NewDeviceService 创建一个新的 DeviceService 实例。 @@ -63,13 +64,15 @@ func NewDeviceService( areaControllerRepo repository.AreaControllerRepository, deviceTemplateRepo repository.DeviceTemplateRepository, deviceDomainSvc device.Service, + thresholdAlarmService ThresholdAlarmService, ) DeviceService { return &deviceService{ - ctx: ctx, - deviceRepo: deviceRepo, - areaControllerRepo: areaControllerRepo, - deviceTemplateRepo: deviceTemplateRepo, - deviceDomainSvc: deviceDomainSvc, + ctx: ctx, + deviceRepo: deviceRepo, + areaControllerRepo: areaControllerRepo, + deviceTemplateRepo: deviceTemplateRepo, + deviceDomainSvc: deviceDomainSvc, + thresholdAlarmService: thresholdAlarmService, } } @@ -167,6 +170,12 @@ func (s *deviceService) DeleteDevice(ctx context.Context, id uint) error { return err // 如果未找到,会返回 gorm.ErrRecordNotFound } + // TODO 这个应该用事务处理 + err = s.thresholdAlarmService.DeleteDeviceThresholdAlarmByDeviceID(serviceCtx, id) + if err != nil { + return fmt.Errorf("删除设备阈值告警失败: %w", err) + } + // 在删除前检查设备是否被任务使用 inUse, err := s.deviceRepo.IsDeviceInUse(serviceCtx, id) if err != nil { @@ -287,6 +296,12 @@ func (s *deviceService) DeleteAreaController(ctx context.Context, id uint) error return err // 如果未找到,gorm会返回 ErrRecordNotFound } + // TODO 这个应该用事务处理 + err = s.thresholdAlarmService.DeleteAreaThresholdAlarmByAreaControllerID(serviceCtx, id) + if err != nil { + return fmt.Errorf("删除区域阈值告警失败: %w", err) + } + // 2. 检查是否被使用(业务逻辑) inUse, err := s.deviceRepo.IsAreaControllerInUse(serviceCtx, id) if err != nil { diff --git a/internal/app/service/threshold_alarm_service.go b/internal/app/service/threshold_alarm_service.go index e954919..19a81ac 100644 --- a/internal/app/service/threshold_alarm_service.go +++ b/internal/app/service/threshold_alarm_service.go @@ -34,6 +34,8 @@ type ThresholdAlarmService interface { GetDeviceThresholdAlarm(ctx context.Context, taskID int) (*dto.DeviceThresholdAlarmDTO, error) // DeleteDeviceThresholdAlarm 删除一个设备阈值告警。 DeleteDeviceThresholdAlarm(ctx context.Context, taskID int, req *dto.DeleteDeviceThresholdAlarmDTO) error + // DeleteDeviceThresholdAlarmByDeviceID 实现了根据设备ID删除一个设备下所有设备阈值告警的逻辑。 + DeleteDeviceThresholdAlarmByDeviceID(ctx context.Context, deviceID uint) error // CreateAreaThresholdAlarm 创建一个区域阈值告警。 CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error @@ -43,6 +45,8 @@ type ThresholdAlarmService interface { GetAreaThresholdAlarm(ctx context.Context, taskID int) (*dto.AreaThresholdAlarmDTO, error) // DeleteAreaThresholdAlarm 实现了删除一个区域阈值告警的逻辑。 DeleteAreaThresholdAlarm(ctx context.Context, taskID int) error + // DeleteAreaThresholdAlarmByAreaControllerID 实现了根据区域ID删除一个区域下所有区域阈值告警的逻辑。 + DeleteAreaThresholdAlarmByAreaControllerID(ctx context.Context, areaControllerID uint) error } // thresholdAlarmService 是 ThresholdAlarmService 接口的具体实现。 @@ -370,6 +374,76 @@ func (s *thresholdAlarmService) DeleteDeviceThresholdAlarm(ctx context.Context, return err } +// DeleteDeviceThresholdAlarmByDeviceID 实现了根据设备ID删除一个设备下所有设备阈值告警的逻辑。 +func (s *thresholdAlarmService) DeleteDeviceThresholdAlarmByDeviceID(ctx context.Context, deviceID uint) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeleteDeviceThresholdAlarmByDeviceID") + tasks, err := s.planRepo.ListTasksByDeviceID(serviceCtx, deviceID) + if err != nil { + return fmt.Errorf("获取任务列表失败: %w", err) + } + device, err := s.deviceRepo.FindByID(serviceCtx, deviceID) + if err != nil { + return fmt.Errorf("获取设备 %d 失败: %w", deviceID, err) + } + plan, err := s.planRepo.GetSystemPlanByName(serviceCtx, models.PlanNamePeriodicSystemHealthCheck) + if err != nil { + return fmt.Errorf("获取系统计划失败: %w", err) + } + if plan == nil { + logger.Panicf("系统计划 %v 不存在", models.PlanNamePeriodicSystemHealthCheck) + } + + deleteNums := []int{} + for i, t := range plan.Tasks { + for _, dt := range tasks { + if t.ID == dt.ID && t.Type == models.TaskTypeDeviceThresholdCheck { + deleteNums = append(deleteNums, i) + break + } + } + + if t.Type == models.TaskTypeAreaCollectorThresholdCheck { + var areaParams task.AreaThresholdCheckParams + if err := t.ParseParameters(&areaParams); err != nil { + return fmt.Errorf("任务 %d: 解析参数失败: %w", t.ID, err) + } + if areaParams.AreaControllerID == device.AreaControllerID { + for ai, ae := range areaParams.ExcludeDeviceIDs { + if ae == deviceID { + areaParams.ExcludeDeviceIDs = append(areaParams.ExcludeDeviceIDs[:ai], areaParams.ExcludeDeviceIDs[ai+1:]...) + break + } + } + err = plan.Tasks[i].SaveParameters(areaParams) + if err != nil { + return fmt.Errorf("任务 %d: 保存参数失败: %w", t.ID, err) + } + } + } + } + + // 为了高效地判断一个索引是否需要被删除,先将 deleteNums 转换为一个 map。 + deleteMap := make(map[int]struct{}, len(deleteNums)) + for _, index := range deleteNums { + deleteMap[index] = struct{}{} + } + + // 创建一个新的任务切片,只包含不需要删除的任务。 + newTasks := make([]models.Task, 0, len(plan.Tasks)-len(deleteNums)) + for i, t := range plan.Tasks { + if _, found := deleteMap[i]; !found { + newTasks = append(newTasks, t) + } + } + plan.Tasks = newTasks + + // 重新排序任务并更新计划 + plan.ReorderSteps() + _, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem) + return err + +} + // CreateAreaThresholdAlarm 实现了创建一个区域阈值告警的逻辑。 func (s *thresholdAlarmService) CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreateAreaThresholdAlarm") @@ -555,3 +629,60 @@ func (s *thresholdAlarmService) DeleteAreaThresholdAlarm(ctx context.Context, ta _, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem) return err } + +// DeleteAreaThresholdAlarmByAreaControllerID 实现了根据区域ID删除一个区域下所有区域阈值告警的逻辑。 +func (s *thresholdAlarmService) DeleteAreaThresholdAlarmByAreaControllerID(ctx context.Context, areaControllerID uint) error { + serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeleteAreaThresholdAlarmByAreaControllerID") + + // 1. 获取系统健康检查计划 + plan, err := s.planRepo.GetSystemPlanByName(serviceCtx, models.PlanNamePeriodicSystemHealthCheck) + if err != nil { + return fmt.Errorf("获取系统健康检查计划失败: %w", err) + } + if plan == nil { + logger.Panicf("系统计划 %v 不存在", models.PlanNamePeriodicSystemHealthCheck) + } + + // 2. 收集所有与指定 areaControllerID 相关的区域阈值告警任务的索引 + var deleteIndices []int + for i, t := range plan.Tasks { + // 只关心区域阈值检查任务 + if t.Type != models.TaskTypeAreaCollectorThresholdCheck { + continue + } + + var params task.AreaThresholdCheckParams + if err := t.ParseParameters(¶ms); err != nil { + return fmt.Errorf("任务 %d: 解析参数失败: %w", t.ID, err) + } + + // 如果 AreaControllerID 匹配,则记录其索引以待删除 + if params.AreaControllerID == areaControllerID { + deleteIndices = append(deleteIndices, i) + } + } + + // 如果没有找到要删除的任务,则直接返回 + if len(deleteIndices) == 0 { + return nil + } + + // 3. 使用 map 和新切片的方式安全地删除多个任务 + deleteMap := make(map[int]struct{}, len(deleteIndices)) + for _, index := range deleteIndices { + deleteMap[index] = struct{}{} + } + + newTasks := make([]models.Task, 0, len(plan.Tasks)-len(deleteMap)) + for i, t := range plan.Tasks { + if _, found := deleteMap[i]; !found { + newTasks = append(newTasks, t) + } + } + plan.Tasks = newTasks + + // 4. 重新排序任务并更新计划 + plan.ReorderSteps() + _, err = s.planService.UpdatePlan(serviceCtx, plan, models.PlanTypeSystem) + return err +} diff --git a/internal/infra/repository/plan_repository.go b/internal/infra/repository/plan_repository.go index e623fc0..38fe3f4 100644 --- a/internal/infra/repository/plan_repository.go +++ b/internal/infra/repository/plan_repository.go @@ -87,6 +87,8 @@ type PlanRepository interface { StopPlanTransactionally(ctx context.Context, planID uint) error // UpdatePlanStateAfterExecution 更新计划执行后的状态(计数和状态) UpdatePlanStateAfterExecution(ctx context.Context, planID uint, newCount uint, newStatus models.PlanStatus) error + // ListTasksByDeviceID 根据设备ID获取关联任务列表 + ListTasksByDeviceID(ctx context.Context, deviceID uint) ([]*models.Task, error) } // gormPlanRepository 是 PlanRepository 的 GORM 实现 @@ -878,3 +880,19 @@ func (r *gormPlanRepository) FindTaskByID(ctx context.Context, id int) (*models. } return &task, nil } + +func (r *gormPlanRepository) ListTasksByDeviceID(ctx context.Context, deviceID uint) ([]*models.Task, error) { + repoCtx := logs.AddFuncName(ctx, r.ctx, "ListTasksByDeviceID") + tasks := []*models.Task{} + // 使用 Joins 方法来连接 tasks 表和 device_tasks 关联表, + // 然后通过 Where 子句筛选出与指定 deviceID 关联的所有任务。 + err := r.db.WithContext(repoCtx).Joins("JOIN device_tasks ON device_tasks.task_id = tasks.id"). + Where("device_tasks.device_id = ?", deviceID). + Find(&tasks).Error + + if err != nil { + return nil, err + } + + return tasks, nil +} From 30880f8c30d16937bce997f43144ba1da4652b6e Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 21:14:36 +0800 Subject: [PATCH 31/35] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=92=8C=E5=8C=BA?= =?UTF-8?q?=E5=9F=9F=E4=B8=BB=E6=8E=A7=E5=88=A0=E9=99=A4=E6=97=B6=E6=B8=85?= =?UTF-8?q?=E9=99=A4=E5=AF=B9=E5=BA=94=E5=8C=BA=E5=9F=9F=E9=98=88=E5=80=BC?= =?UTF-8?q?=E5=91=8A=E8=AD=A6=E6=88=96=E8=AE=BE=E5=A4=87=E9=98=88=E5=80=BC?= =?UTF-8?q?=E5=91=8A=E8=AD=A6=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 +- internal/app/service/device_service.go | 28 ++++---- internal/core/component_initializers.go | 23 ++++--- internal/domain/task/task.go | 12 ++++ .../repository/area_controller_repository.go | 66 +++++++++++++++++++ .../infra/repository/device_repository.go | 22 +++++-- 6 files changed, 123 insertions(+), 31 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 3b0fe71..b4ea6af 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -142,4 +142,5 @@ 10. 实现区域阈值告警任务 11. 实现区域阈值告警和设备阈值告警的增删改查 12. 实现任务11应的八个web接口 -13. 实现根据区域ID或设备ID清空对应阈值告警任务 \ No newline at end of file +13. 实现根据区域ID或设备ID清空对应阈值告警任务 +14. 设备和区域主控删除时清除对应区域阈值告警或设备阈值告警任务 \ No newline at end of file diff --git a/internal/app/service/device_service.go b/internal/app/service/device_service.go index 7b0af75..4a471c9 100644 --- a/internal/app/service/device_service.go +++ b/internal/app/service/device_service.go @@ -170,14 +170,8 @@ func (s *deviceService) DeleteDevice(ctx context.Context, id uint) error { return err // 如果未找到,会返回 gorm.ErrRecordNotFound } - // TODO 这个应该用事务处理 - err = s.thresholdAlarmService.DeleteDeviceThresholdAlarmByDeviceID(serviceCtx, id) - if err != nil { - return fmt.Errorf("删除设备阈值告警失败: %w", err) - } - // 在删除前检查设备是否被任务使用 - inUse, err := s.deviceRepo.IsDeviceInUse(serviceCtx, id) + inUse, err := s.deviceRepo.IsDeviceInUse(serviceCtx, id, []models.TaskType{models.TaskTypeDeviceThresholdCheck}) if err != nil { // 如果检查过程中发生数据库错误,则返回错误 return fmt.Errorf("检查设备使用情况失败: %w", err) @@ -187,6 +181,12 @@ func (s *deviceService) DeleteDevice(ctx context.Context, id uint) error { return ErrDeviceInUse } + // TODO 这个应该用事务处理 + err = s.thresholdAlarmService.DeleteDeviceThresholdAlarmByDeviceID(serviceCtx, id) + if err != nil { + return fmt.Errorf("删除设备阈值告警失败: %w", err) + } + // 只有在未被使用时,才执行删除操作 return s.deviceRepo.Delete(serviceCtx, id) } @@ -296,14 +296,8 @@ func (s *deviceService) DeleteAreaController(ctx context.Context, id uint) error return err // 如果未找到,gorm会返回 ErrRecordNotFound } - // TODO 这个应该用事务处理 - err = s.thresholdAlarmService.DeleteAreaThresholdAlarmByAreaControllerID(serviceCtx, id) - if err != nil { - return fmt.Errorf("删除区域阈值告警失败: %w", err) - } - // 2. 检查是否被使用(业务逻辑) - inUse, err := s.deviceRepo.IsAreaControllerInUse(serviceCtx, id) + inUse, err := s.areaControllerRepo.IsAreaControllerUsedByTasks(serviceCtx, id, []models.TaskType{models.TaskTypeAreaCollectorThresholdCheck}) if err != nil { return err // 返回数据库检查错误 } @@ -311,6 +305,12 @@ func (s *deviceService) DeleteAreaController(ctx context.Context, id uint) error return ErrAreaControllerInUse // 返回业务错误 } + // TODO 这个应该用事务处理 + err = s.thresholdAlarmService.DeleteAreaThresholdAlarmByAreaControllerID(serviceCtx, id) + if err != nil { + return fmt.Errorf("删除区域阈值告警失败: %w", err) + } + // 3. 执行删除 return s.areaControllerRepo.Delete(serviceCtx, id) } diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index f2b0560..6be7dc5 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -254,16 +254,6 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices infra.repos.pigTradeRepo, infra.repos.notificationRepo, ) - deviceService := service.NewDeviceService( - logs.AddCompName(baseCtx, "DeviceService"), - infra.repos.deviceRepo, - infra.repos.areaControllerRepo, - infra.repos.deviceTemplateRepo, - domainServices.generalDeviceService, - ) - auditService := service.NewAuditService(logs.AddCompName(baseCtx, "AuditService"), infra.repos.userActionLogRepo) - planService := service.NewPlanService(logs.AddCompName(baseCtx, "AppPlanService"), domainServices.planService) - userService := service.NewUserService(logs.AddCompName(baseCtx, "UserService"), infra.repos.userRepo, infra.tokenGenerator, domainServices.notifyService) // 初始化阈值告警服务 thresholdAlarmService := service.NewThresholdAlarmService( @@ -276,6 +266,19 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices infra.repos.deviceRepo, ) + deviceService := service.NewDeviceService( + logs.AddCompName(baseCtx, "DeviceService"), + infra.repos.deviceRepo, + infra.repos.areaControllerRepo, + infra.repos.deviceTemplateRepo, + domainServices.generalDeviceService, + thresholdAlarmService, + ) + + auditService := service.NewAuditService(logs.AddCompName(baseCtx, "AuditService"), infra.repos.userActionLogRepo) + planService := service.NewPlanService(logs.AddCompName(baseCtx, "AppPlanService"), domainServices.planService) + userService := service.NewUserService(logs.AddCompName(baseCtx, "UserService"), infra.repos.userRepo, infra.tokenGenerator, domainServices.notifyService) + return &AppServices{ pigFarmService: pigFarmService, pigBatchService: pigBatchService, diff --git a/internal/domain/task/task.go b/internal/domain/task/task.go index 7ee9f09..e8c51cf 100644 --- a/internal/domain/task/task.go +++ b/internal/domain/task/task.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "git.huangwc.com/pig/pig-farm-controller/internal/domain/alarm" "git.huangwc.com/pig/pig-farm-controller/internal/domain/device" "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify" "git.huangwc.com/pig/pig-farm-controller/internal/domain/plan" @@ -28,6 +29,7 @@ type taskFactory struct { deviceService device.Service notificationService notify.Service + alarmService alarm.AlarmService } func NewTaskFactory( @@ -37,6 +39,7 @@ func NewTaskFactory( alarmRepo repository.AlarmRepository, deviceService device.Service, notifyService notify.Service, + alarmService alarm.AlarmService, ) plan.TaskFactory { return &taskFactory{ ctx: ctx, @@ -45,6 +48,7 @@ func NewTaskFactory( alarmRepo: alarmRepo, deviceService: deviceService, notificationService: notifyService, + alarmService: alarmService, } } @@ -60,6 +64,10 @@ func (t *taskFactory) Production(ctx context.Context, claimedLog *models.TaskExe return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), claimedLog, t.deviceRepo, t.deviceService) case models.TaskTypeAlarmNotification: return NewAlarmNotificationTask(logs.AddCompName(baseCtx, CompNameAlarmNotification), claimedLog, t.notificationService, t.alarmRepo) + case models.TaskTypeDeviceThresholdCheck: + return NewDeviceThresholdCheckTask(logs.AddCompName(baseCtx, "DeviceThresholdCheckTask"), claimedLog, t.sensorDataRepo, t.alarmService) + case models.TaskTypeAreaCollectorThresholdCheck: + return NewAreaThresholdCheckTask(logs.AddCompName(baseCtx, "AreaCollectorThresholdCheckTask"), claimedLog, t.sensorDataRepo, t.deviceRepo, t.alarmService) default: // TODO 这里直接panic合适吗? 不过这个场景确实不该出现任何异常的任务类型 logger.Panicf("不支持的任务类型: %s", claimedLog.Task.Type) @@ -89,6 +97,10 @@ func (t *taskFactory) CreateTaskFromModel(ctx context.Context, taskModel *models return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), tempLog, t.deviceRepo, t.deviceService), nil case models.TaskTypeAlarmNotification: return NewAlarmNotificationTask(logs.AddCompName(baseCtx, CompNameAlarmNotification), tempLog, t.notificationService, t.alarmRepo), nil + case models.TaskTypeDeviceThresholdCheck: + return NewDeviceThresholdCheckTask(logs.AddCompName(baseCtx, "DeviceThresholdCheckTask"), tempLog, t.sensorDataRepo, t.alarmService), nil + case models.TaskTypeAreaCollectorThresholdCheck: + return NewAreaThresholdCheckTask(logs.AddCompName(baseCtx, "AreaCollectorThresholdCheckTask"), tempLog, t.sensorDataRepo, t.deviceRepo, t.alarmService), nil default: return nil, fmt.Errorf("不支持为类型 '%s' 的任务创建模型实例", taskModel.Type) } diff --git a/internal/infra/repository/area_controller_repository.go b/internal/infra/repository/area_controller_repository.go index 4c0b62f..0a0e0bf 100644 --- a/internal/infra/repository/area_controller_repository.go +++ b/internal/infra/repository/area_controller_repository.go @@ -3,6 +3,7 @@ package repository import ( "context" "fmt" + "strconv" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" @@ -18,6 +19,8 @@ type AreaControllerRepository interface { ListAll(ctx context.Context) ([]*models.AreaController, error) Update(ctx context.Context, ac *models.AreaController) error Delete(ctx context.Context, id uint) error + // IsAreaControllerUsedByTasks 检查区域主控是否被特定任务类型使用,可以忽略指定任务类型 + IsAreaControllerUsedByTasks(ctx context.Context, areaControllerID uint, ignoredTaskTypes []models.TaskType) (bool, error) } // gormAreaControllerRepository 是 AreaControllerRepository 的 GORM 实现。 @@ -84,3 +87,66 @@ func (r *gormAreaControllerRepository) FindByNetworkID(ctx context.Context, netw } return &areaController, nil } + +// IsAreaControllerUsedByTasks 检查区域主控是否被特定任务类型使用,可以忽略指定任务类型 +func (r *gormAreaControllerRepository) IsAreaControllerUsedByTasks(ctx context.Context, areaControllerID uint, ignoredTaskTypes []models.TaskType) (bool, error) { + repoCtx, logger := logs.Trace(ctx, r.ctx, "IsAreaControllerUsedByTasks") + + // 将 ignoredTaskTypes 转换为 map,以便高效查找 + ignoredMap := make(map[models.TaskType]struct{}) + for _, tt := range ignoredTaskTypes { + ignoredMap[tt] = struct{}{} + } + + areaControllerIDStr := strconv.FormatUint(uint64(areaControllerID), 10) + + // 定义所有可能与 AreaControllerID 相关的任务类型列表 + // 方便未来扩展,如果新增任务类型与区域主控关联,只需在此处添加 + relevantTaskTypes := []models.TaskType{ + models.TaskTypeAreaCollectorThresholdCheck, + // TODO: 如果未来有其他任务类型通过 parameters 关联 AreaControllerID,请在此处添加 + // 例如: models.TaskTypeAnotherAreaControllerTask, + } + + for _, taskType := range relevantTaskTypes { + // 如果当前任务类型在忽略列表中,则跳过检查 + if _, ok := ignoredMap[taskType]; ok { + continue + } + + var taskCount int64 + var query *gorm.DB + + // 根据任务类型构建不同的查询条件 + switch taskType { + case models.TaskTypeAreaCollectorThresholdCheck: + // TaskTypeAreaCollectorThresholdCheck 任务的 AreaControllerID 存储在 parameters->>'AreaControllerID' + query = r.db.WithContext(repoCtx). + Model(&models.Task{}). + Where("type = ?", models.TaskTypeAreaCollectorThresholdCheck). + Where("parameters->>'AreaControllerID' = ?", areaControllerIDStr) + // TODO: 如果未来有其他任务类型通过不同的 parameters 字段关联 AreaControllerID,请在此处添加 case + // case models.TaskTypeAnotherAreaControllerTask: + // query = r.db.WithContext(repoCtx). + // Model(&models.Task{}). + // Where("type = ?", models.TaskTypeAnotherAreaControllerTask). + // Where("parameters->>'AnotherFieldForAreaControllerID' = ?", areaControllerIDStr) + default: + // 对于未明确处理的 relevantTaskTypes,可以记录警告或直接跳过 + logger.Warnf(fmt.Sprintf("IsAreaControllerUsedByTasks: 未处理的区域主控相关任务类型: %s", taskType)) + continue + } + + if query != nil { + err := query.Count(&taskCount).Error + if err != nil { + return false, fmt.Errorf("查询区域主控任务使用情况失败 (任务类型: %s): %w", taskType, err) + } + if taskCount > 0 { + return true, nil // 发现有未被忽略的任务正在使用此区域主控 + } + } + } + + return false, nil // 没有发现任何未被忽略的任务正在使用此区域主控 +} diff --git a/internal/infra/repository/device_repository.go b/internal/infra/repository/device_repository.go index 32757e3..e19b974 100644 --- a/internal/infra/repository/device_repository.go +++ b/internal/infra/repository/device_repository.go @@ -47,8 +47,8 @@ type DeviceRepository interface { // GetDevicesByIDsTx 在指定事务中根据ID列表获取设备 GetDevicesByIDsTx(ctx context.Context, tx *gorm.DB, ids []uint) ([]models.Device, error) - // IsDeviceInUse 检查设备是否被任何任务使用 - IsDeviceInUse(ctx context.Context, deviceID uint) (bool, error) + // IsDeviceInUse 检查设备是否被任何任务使用,可以忽略指定任务类型 + IsDeviceInUse(ctx context.Context, deviceID uint, ignoredTaskTypes []models.TaskType) (bool, error) // IsAreaControllerInUse 检查区域主控是否被任何设备使用 IsAreaControllerInUse(ctx context.Context, areaControllerID uint) (bool, error) @@ -184,12 +184,22 @@ func (r *gormDeviceRepository) FindByAreaControllerAndPhysicalAddress(ctx contex return &device, nil } -// IsDeviceInUse 检查设备是否被任何任务使用 -func (r *gormDeviceRepository) IsDeviceInUse(ctx context.Context, deviceID uint) (bool, error) { +// IsDeviceInUse 检查设备是否被任何任务使用,可以忽略指定任务类型 +func (r *gormDeviceRepository) IsDeviceInUse(ctx context.Context, deviceID uint, ignoredTaskTypes []models.TaskType) (bool, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "IsDeviceInUse") var count int64 - // 直接对 device_tasks 关联表进行 COUNT 操作,性能最高 - err := r.db.WithContext(repoCtx).Model(&models.DeviceTask{}).Where("device_id = ?", deviceID).Count(&count).Error + + // 构建查询,需要 JOIN tasks 表来过滤 TaskType + query := r.db.WithContext(repoCtx). + Model(&models.DeviceTask{}). + Joins("JOIN tasks ON tasks.id = device_tasks.task_id"). + Where("device_tasks.device_id = ?", deviceID) + + if len(ignoredTaskTypes) > 0 { + query = query.Where("tasks.type NOT IN (?)", ignoredTaskTypes) + } + + err := query.Count(&count).Error if err != nil { return false, fmt.Errorf("查询设备任务关联失败: %w", err) } From 4f3148eaa26476802bd130bd807f6bb8d282e1d4 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 21:17:28 +0800 Subject: [PATCH 32/35] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/core/component_initializers.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index 6be7dc5..73a9c05 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -161,13 +161,22 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr infra.lora.comm, ) + // 告警服务 + alarmService := alarm.NewAlarmService( + logs.AddCompName(baseCtx, "AlarmService"), + infra.repos.alarmRepo, + infra.repos.unitOfWork, + ) + // 任务工厂 taskFactory := task.NewTaskFactory(logs.AddCompName(baseCtx, "TaskFactory"), infra.repos.sensorDataRepo, infra.repos.deviceRepo, infra.repos.alarmRepo, generalDeviceService, - notifyService) + notifyService, + alarmService, + ) // 计划任务管理器 analysisPlanTaskManager := plan.NewAnalysisPlanTaskManager(logs.AddCompName(baseCtx, "AnalysisPlanTaskManager"), infra.repos.planRepo, infra.repos.pendingTaskRepo, infra.repos.executionLogRepo) @@ -198,13 +207,6 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr taskFactory, ) - // 告警服务 - alarmService := alarm.NewAlarmService( - logs.AddCompName(baseCtx, "AlarmService"), - infra.repos.alarmRepo, - infra.repos.unitOfWork, - ) - return &DomainServices{ pigPenTransferManager: pigPenTransferManager, pigTradeManager: pigTradeManager, From 75306941c2fc214399e0320bf27ac8bdb68dbad6 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 21:32:18 +0800 Subject: [PATCH 33/35] =?UTF-8?q?=E5=B0=86=E6=89=80=E6=9C=89Regional?= =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=B8=BAArea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 +- docs/docs.go | 6 +-- docs/swagger.json | 6 +-- docs/swagger.yaml | 4 +- internal/app/dto/monitor_converter.go | 10 ++--- internal/app/dto/monitor_dto.go | 10 ++--- internal/app/webhook/chirp_stack.go | 38 +++++++++---------- internal/domain/device/device_service.go | 2 +- .../domain/device/general_device_service.go | 18 ++++----- internal/infra/models/sensor_data.go | 4 +- .../lora_mesh_uart_passthrough_transport.go | 20 +++++----- 11 files changed, 61 insertions(+), 60 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index b4ea6af..a3fdc40 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -143,4 +143,5 @@ 11. 实现区域阈值告警和设备阈值告警的增删改查 12. 实现任务11应的八个web接口 13. 实现根据区域ID或设备ID清空对应阈值告警任务 -14. 设备和区域主控删除时清除对应区域阈值告警或设备阈值告警任务 \ No newline at end of file +14. 设备和区域主控删除时清除对应区域阈值告警或设备阈值告警任务 +15. 将所有Regional更改为Area \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 05aa23a..61485a4 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -6816,6 +6816,9 @@ const docTemplate = `{ "dto.SensorDataDTO": { "type": "object", "properties": { + "area_controller_id": { + "type": "integer" + }, "data": { "type": "array", "items": { @@ -6825,9 +6828,6 @@ const docTemplate = `{ "device_id": { "type": "integer" }, - "regional_controller_id": { - "type": "integer" - }, "sensor_type": { "$ref": "#/definitions/models.SensorType" }, diff --git a/docs/swagger.json b/docs/swagger.json index b3cfe1a..dfa71b7 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -6808,6 +6808,9 @@ "dto.SensorDataDTO": { "type": "object", "properties": { + "area_controller_id": { + "type": "integer" + }, "data": { "type": "array", "items": { @@ -6817,9 +6820,6 @@ "device_id": { "type": "integer" }, - "regional_controller_id": { - "type": "integer" - }, "sensor_type": { "$ref": "#/definitions/models.SensorType" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c228a4f..0230576 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1369,14 +1369,14 @@ definitions: type: object dto.SensorDataDTO: properties: + area_controller_id: + type: integer data: items: type: integer type: array device_id: type: integer - regional_controller_id: - type: integer sensor_type: $ref: '#/definitions/models.SensorType' time: diff --git a/internal/app/dto/monitor_converter.go b/internal/app/dto/monitor_converter.go index f7192f0..84c7ac2 100644 --- a/internal/app/dto/monitor_converter.go +++ b/internal/app/dto/monitor_converter.go @@ -11,11 +11,11 @@ func NewListSensorDataResponse(data []models.SensorData, total int64, page, page dtos := make([]SensorDataDTO, len(data)) for i, item := range data { dtos[i] = SensorDataDTO{ - Time: item.Time, - DeviceID: item.DeviceID, - RegionalControllerID: item.RegionalControllerID, - SensorType: item.SensorType, - Data: json.RawMessage(item.Data), + Time: item.Time, + DeviceID: item.DeviceID, + AreaControllerID: item.AreaControllerID, + SensorType: item.SensorType, + Data: json.RawMessage(item.Data), } } diff --git a/internal/app/dto/monitor_dto.go b/internal/app/dto/monitor_dto.go index 0eeee8f..7129998 100644 --- a/internal/app/dto/monitor_dto.go +++ b/internal/app/dto/monitor_dto.go @@ -22,11 +22,11 @@ type ListSensorDataRequest struct { // SensorDataDTO 是用于API响应的传感器数据结构 type SensorDataDTO struct { - Time time.Time `json:"time"` - DeviceID uint `json:"device_id"` - RegionalControllerID uint `json:"regional_controller_id"` - SensorType models.SensorType `json:"sensor_type"` - Data json.RawMessage `json:"data"` + Time time.Time `json:"time"` + DeviceID uint `json:"device_id"` + AreaControllerID uint `json:"area_controller_id"` + SensorType models.SensorType `json:"sensor_type"` + Data json.RawMessage `json:"data"` } // ListSensorDataResponse 是获取传感器数据列表的响应结构 diff --git a/internal/app/webhook/chirp_stack.go b/internal/app/webhook/chirp_stack.go index 1af2d83..60e6fe4 100644 --- a/internal/app/webhook/chirp_stack.go +++ b/internal/app/webhook/chirp_stack.go @@ -162,17 +162,17 @@ func (c *ChirpStackListener) handleUpEvent(ctx context.Context, event *UpEvent) logger.Infof("开始处理 'up' 事件, DevEui: %s", event.DeviceInfo.DevEui) // 1. 查找区域主控设备 - regionalController, err := c.areaControllerRepo.FindByNetworkID(reqCtx, event.DeviceInfo.DevEui) + areaController, err := c.areaControllerRepo.FindByNetworkID(reqCtx, event.DeviceInfo.DevEui) if err != nil { logger.Errorf("处理 'up' 事件失败:无法通过 DevEui '%s' 找到区域主控设备: %v", event.DeviceInfo.DevEui, err) return } // 依赖 SelfCheck 确保区域主控有效 - if err := regionalController.SelfCheck(); err != nil { - logger.Errorf("处理 'up' 事件失败:区域主控 %v(ID: %d) 未通过自检: %v", regionalController.Name, regionalController.ID, err) + if err := areaController.SelfCheck(); err != nil { + logger.Errorf("处理 'up' 事件失败:区域主控 %v(ID: %d) 未通过自检: %v", areaController.Name, areaController.ID, err) return } - logger.Infof("找到区域主控: %s (ID: %d)", regionalController.Name, regionalController.ID) + logger.Infof("找到区域主控: %s (ID: %d)", areaController.Name, areaController.ID) // 2. 记录区域主控的信号强度 (如果存在) if len(event.RxInfo) > 0 { @@ -187,8 +187,8 @@ func (c *ChirpStackListener) handleUpEvent(ctx context.Context, event *UpEvent) } // 记录信号强度 - c.recordSensorData(reqCtx, regionalController.ID, regionalController.ID, event.Time, models.SensorTypeSignalMetrics, signalMetrics) - logger.Infof("已记录区域主控 (ID: %d) 的信号强度: RSSI=%d, SNR=%.2f", regionalController.ID, rx.Rssi, rx.Snr) + c.recordSensorData(reqCtx, areaController.ID, areaController.ID, event.Time, models.SensorTypeSignalMetrics, signalMetrics) + logger.Infof("已记录区域主控 (ID: %d) 的信号强度: RSSI=%d, SNR=%.2f", areaController.ID, rx.Rssi, rx.Snr) } else { logger.Warnf("处理 'up' 事件时未找到 RxInfo,无法记录信号数据。DevEui: %s", event.DeviceInfo.DevEui) } @@ -316,7 +316,7 @@ func (c *ChirpStackListener) handleUpEvent(ctx context.Context, event *UpEvent) } // 5.5 记录传感器数据 - c.recordSensorData(reqCtx, regionalController.ID, dev.ID, event.Time, valueDescriptor.Type, dataToRecord) + c.recordSensorData(reqCtx, areaController.ID, dev.ID, event.Time, valueDescriptor.Type, dataToRecord) logger.Infof("成功记录传感器数据: 设备ID=%d, 类型=%s, 原始值=%f, 解析值=%.2f", dev.ID, valueDescriptor.Type, rawSensorValue, parsedValue) } @@ -334,7 +334,7 @@ func (c *ChirpStackListener) handleStatusEvent(ctx context.Context, event *Statu logger.Infof("处接收到理 'status' 事件: %+v", event) // 查找区域主控设备 - regionalController, err := c.areaControllerRepo.FindByNetworkID(reqCtx, event.DeviceInfo.DevEui) + areaController, err := c.areaControllerRepo.FindByNetworkID(reqCtx, event.DeviceInfo.DevEui) if err != nil { logger.Errorf("处理 'status' 事件失败:无法通过 DevEui '%s' 找到区域主控设备: %v", event.DeviceInfo.DevEui, err) return @@ -344,8 +344,8 @@ func (c *ChirpStackListener) handleStatusEvent(ctx context.Context, event *Statu signalMetrics := models.SignalMetrics{ MarginDb: event.Margin, } - c.recordSensorData(reqCtx, regionalController.ID, regionalController.ID, event.Time, models.SensorTypeSignalMetrics, signalMetrics) - logger.Infof("已记录区域主控 (ID: %d) 的信号状态: %+v", regionalController.ID, signalMetrics) + c.recordSensorData(reqCtx, areaController.ID, areaController.ID, event.Time, models.SensorTypeSignalMetrics, signalMetrics) + logger.Infof("已记录区域主控 (ID: %d) 的信号状态: %+v", areaController.ID, signalMetrics) // 记录电量 batteryLevel := models.BatteryLevel{ @@ -353,8 +353,8 @@ func (c *ChirpStackListener) handleStatusEvent(ctx context.Context, event *Statu BatteryLevelUnavailable: event.BatteryLevelUnavailable, ExternalPower: event.ExternalPower, } - c.recordSensorData(reqCtx, regionalController.ID, regionalController.ID, event.Time, models.SensorTypeBatteryLevel, batteryLevel) - logger.Infof("已记录区域主控 (ID: %d) 的电池状态: %+v", regionalController.ID, batteryLevel) + c.recordSensorData(reqCtx, areaController.ID, areaController.ID, event.Time, models.SensorTypeBatteryLevel, batteryLevel) + logger.Infof("已记录区域主控 (ID: %d) 的电池状态: %+v", areaController.ID, batteryLevel) } // handleAckEvent 处理下行确认事件 @@ -425,11 +425,11 @@ func (c *ChirpStackListener) handleIntegrationEvent(ctx context.Context, event * } // recordSensorData 是一个通用方法,用于将传感器数据存入数据库。 -// regionalControllerID: 区域主控设备的ID +// areaControllerID: 区域主控设备的ID // sensorDeviceID: 实际产生传感器数据的普通设备的ID // sensorType: 传感器值的类型 (例如 models.SensorTypeTemperature) // data: 具体的传感器数据结构体实例 (例如 models.TemperatureData) -func (c *ChirpStackListener) recordSensorData(ctx context.Context, regionalControllerID uint, sensorDeviceID uint, eventTime time.Time, sensorType models.SensorType, data interface{}) { +func (c *ChirpStackListener) recordSensorData(ctx context.Context, areaControllerID uint, sensorDeviceID uint, eventTime time.Time, sensorType models.SensorType, data interface{}) { reqCtx, logger := logs.Trace(ctx, c.ctx, "recordSensorData") // 1. 将传入的结构体序列化为 JSON jsonData, err := json.Marshal(data) @@ -440,11 +440,11 @@ func (c *ChirpStackListener) recordSensorData(ctx context.Context, regionalContr // 2. 构建 SensorData 模型 sensorData := &models.SensorData{ - Time: eventTime, - DeviceID: sensorDeviceID, - RegionalControllerID: regionalControllerID, - SensorType: sensorType, - Data: datatypes.JSON(jsonData), + Time: eventTime, + DeviceID: sensorDeviceID, + AreaControllerID: areaControllerID, + SensorType: sensorType, + Data: datatypes.JSON(jsonData), } // 3. 调用仓库创建记录 diff --git a/internal/domain/device/device_service.go b/internal/domain/device/device_service.go index 9dd6a1b..bd892dd 100644 --- a/internal/domain/device/device_service.go +++ b/internal/domain/device/device_service.go @@ -28,7 +28,7 @@ type Service interface { Switch(ctx context.Context, device *models.Device, action DeviceAction) error // Collect 用于发起对指定区域主控下的多个设备的批量采集请求。 - Collect(ctx context.Context, regionalControllerID uint, devicesToCollect []*models.Device) error + Collect(ctx context.Context, areaControllerID uint, devicesToCollect []*models.Device) error } // 设备操作指令通用结构(最外层) diff --git a/internal/domain/device/general_device_service.go b/internal/domain/device/general_device_service.go index cfdc9d5..1a33e1c 100644 --- a/internal/domain/device/general_device_service.go +++ b/internal/domain/device/general_device_service.go @@ -133,7 +133,7 @@ func (g *GeneralDeviceService) Switch(ctx context.Context, device *models.Device } // Collect 实现了 Service 接口,用于发起对指定区域主控下的多个设备的批量采集请求。 -func (g *GeneralDeviceService) Collect(ctx context.Context, regionalControllerID uint, devicesToCollect []*models.Device) error { +func (g *GeneralDeviceService) Collect(ctx context.Context, areaControllerID uint, devicesToCollect []*models.Device) error { serviceCtx, logger := logs.Trace(ctx, g.ctx, "Collect") if len(devicesToCollect) == 0 { logger.Info("待采集设备列表为空,无需执行采集任务。") @@ -141,12 +141,12 @@ func (g *GeneralDeviceService) Collect(ctx context.Context, regionalControllerID } // 1. 从设备列表中获取预加载的区域主控,并进行校验 - regionalController := &devicesToCollect[0].AreaController - if regionalController.ID != regionalControllerID { - return fmt.Errorf("设备列表与指定的区域主控ID (%d) 不匹配", regionalControllerID) + areaController := &devicesToCollect[0].AreaController + if areaController.ID != areaControllerID { + return fmt.Errorf("设备列表与指定的区域主控ID (%d) 不匹配", areaControllerID) } - if err := regionalController.SelfCheck(); err != nil { - return fmt.Errorf("区域主控 (ID: %d) 未通过自检: %w", regionalControllerID, err) + if err := areaController.SelfCheck(); err != nil { + return fmt.Errorf("区域主控 (ID: %d) 未通过自检: %w", areaControllerID, err) } // 2. 准备采集任务列表 @@ -208,13 +208,13 @@ func (g *GeneralDeviceService) Collect(ctx context.Context, regionalControllerID } // 3. 构建并发送指令 - networkID := regionalController.NetworkID + networkID := areaController.NetworkID // 4. 创建待处理请求记录 correlationID := uuid.New().String() pendingReq := &models.PendingCollection{ CorrelationID: correlationID, - DeviceID: regionalController.ID, + DeviceID: areaController.ID, CommandMetadata: childDeviceIDs, Status: models.PendingStatusPending, CreatedAt: time.Now(), @@ -223,7 +223,7 @@ func (g *GeneralDeviceService) Collect(ctx context.Context, regionalControllerID logger.Errorf("创建待采集请求失败 (CorrelationID: %s): %v", correlationID, err) return err } - logger.Infof("成功创建待采集请求 (CorrelationID: %s, DeviceID: %d)", correlationID, regionalController.ID) + logger.Infof("成功创建待采集请求 (CorrelationID: %s, DeviceID: %d)", correlationID, areaController.ID) // 5. 构建最终的空中载荷 batchCmd := &proto.BatchCollectCommand{ diff --git a/internal/infra/models/sensor_data.go b/internal/infra/models/sensor_data.go index fe683a9..804b164 100644 --- a/internal/infra/models/sensor_data.go +++ b/internal/infra/models/sensor_data.go @@ -57,8 +57,8 @@ type SensorData struct { // DeviceID 是传感器的唯一标识符,作为复合主键的另一部分。 DeviceID uint `gorm:"primaryKey" json:"device_id"` - // RegionalControllerID 是上报此数据的区域主控的ID。 - RegionalControllerID uint `json:"regional_controller_id"` + // AreaControllerID 是上报此数据的区域主控的ID。 + AreaControllerID uint `json:"area_controller_id"` // SensorType 是传感数据的类型 SensorType SensorType `gorm:"not null;index" json:"sensor_type"` diff --git a/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go b/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go index bd51957..acebfcf 100644 --- a/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go +++ b/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go @@ -406,13 +406,13 @@ func (t *LoRaMeshUartPassthroughTransport) handleUpstreamMessage(ctx context.Con logger.Infof("成功解析采集响应 (CorrelationID: %s),包含 %d 个值。", correlationID, len(collectResp.Values)) // 3. 查找区域主控 (注意:LoRa Mesh 的 SourceAddr 对应于区域主控的 NetworkID) - regionalController, err := t.areaControllerRepo.FindByNetworkID(loraCtx, msg.SourceAddr) + areaController, err := t.areaControllerRepo.FindByNetworkID(loraCtx, msg.SourceAddr) if err != nil { logger.Errorf("处理上行消息失败:无法通过源地址 '%s' 找到区域主控设备: %v", msg.SourceAddr, err) return } - if err := regionalController.SelfCheck(); err != nil { - logger.Errorf("处理上行消息失败:区域主控 %v(ID: %d) 未通过自检: %v", regionalController.Name, regionalController.ID, err) + if err := areaController.SelfCheck(); err != nil { + logger.Errorf("处理上行消息失败:区域主控 %v(ID: %d) 未通过自检: %v", areaController.Name, areaController.ID, err) return } @@ -489,7 +489,7 @@ func (t *LoRaMeshUartPassthroughTransport) handleUpstreamMessage(ctx context.Con dataToRecord = map[string]float64{"value": parsedValue} } - t.recordSensorData(loraCtx, regionalController.ID, dev.ID, time.Now(), valueDescriptor.Type, dataToRecord) + t.recordSensorData(loraCtx, areaController.ID, dev.ID, time.Now(), valueDescriptor.Type, dataToRecord) logger.Infof("成功记录传感器数据: 设备ID=%d, 类型=%s, 原始值=%f, 解析值=%.2f", dev.ID, valueDescriptor.Type, rawSensorValue, parsedValue) } @@ -502,7 +502,7 @@ func (t *LoRaMeshUartPassthroughTransport) handleUpstreamMessage(ctx context.Con } // recordSensorData 是一个通用方法,用于将传感器数据存入数据库。 -func (t *LoRaMeshUartPassthroughTransport) recordSensorData(ctx context.Context, regionalControllerID uint, sensorDeviceID uint, eventTime time.Time, sensorType models.SensorType, data interface{}) { +func (t *LoRaMeshUartPassthroughTransport) recordSensorData(ctx context.Context, areaControllerID uint, sensorDeviceID uint, eventTime time.Time, sensorType models.SensorType, data interface{}) { loraCtx, logger := logs.Trace(ctx, t.ctx, "recordSensorData") jsonData, err := json.Marshal(data) @@ -512,11 +512,11 @@ func (t *LoRaMeshUartPassthroughTransport) recordSensorData(ctx context.Context, } sensorData := &models.SensorData{ - Time: eventTime, - DeviceID: sensorDeviceID, - RegionalControllerID: regionalControllerID, - SensorType: sensorType, - Data: datatypes.JSON(jsonData), + Time: eventTime, + DeviceID: sensorDeviceID, + AreaControllerID: areaControllerID, + SensorType: sensorType, + Data: datatypes.JSON(jsonData), } if err := t.sensorDataRepo.Create(loraCtx, sensorData); err != nil { From 3e711551e7e7383cc96fef92759f458cca93e47c Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 21:42:46 +0800 Subject: [PATCH 34/35] =?UTF-8?q?float64=E5=85=A8=E9=83=A8=E6=94=B9float32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 ++- internal/app/dto/alarm_dto.go | 12 +++++----- internal/app/dto/monitor_dto.go | 22 +++++++++---------- internal/app/dto/pig_batch_dto.go | 8 +++---- internal/app/service/pig_batch_service.go | 8 +++---- internal/app/webhook/chirp_stack.go | 4 ++-- internal/app/webhook/chirp_stack_types.go | 14 ++++++------ internal/domain/pig/pig_batch_service.go | 4 ++-- .../domain/pig/pig_batch_service_pig_trade.go | 4 ++-- .../domain/task/area_threshold_check_task.go | 2 +- internal/domain/task/delay_task.go | 2 +- .../task/device_threshold_check_task.go | 6 ++--- .../domain/task/release_feed_weight_task.go | 6 ++--- internal/infra/logs/logs.go | 2 +- internal/infra/models/device_template.go | 4 ++-- internal/infra/models/feed.go | 14 ++++++------ internal/infra/models/medication.go | 10 ++++----- internal/infra/models/pig_batch.go | 2 +- internal/infra/models/pig_trade.go | 8 +++---- internal/infra/models/sensor_data.go | 8 +++---- .../lora_mesh_uart_passthrough_transport.go | 4 ++-- 21 files changed, 74 insertions(+), 73 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index a3fdc40..3bd49d3 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -144,4 +144,5 @@ 12. 实现任务11应的八个web接口 13. 实现根据区域ID或设备ID清空对应阈值告警任务 14. 设备和区域主控删除时清除对应区域阈值告警或设备阈值告警任务 -15. 将所有Regional更改为Area \ No newline at end of file +15. 将所有Regional更改为Area +16. float64全部改float32 \ No newline at end of file diff --git a/internal/app/dto/alarm_dto.go b/internal/app/dto/alarm_dto.go index bf55fb9..2fc48ea 100644 --- a/internal/app/dto/alarm_dto.go +++ b/internal/app/dto/alarm_dto.go @@ -86,14 +86,14 @@ type ListHistoricalAlarmResponse struct { type CreateDeviceThresholdAlarmDTO struct { DeviceID uint `json:"device_id" binding:"required"` // 设备ID SensorType models.SensorType `json:"sensor_type" binding:"required"` // 传感器类型 - Thresholds float64 `json:"thresholds" binding:"required"` // 阈值 + Thresholds float32 `json:"thresholds" binding:"required"` // 阈值 Operator models.Operator `json:"operator" binding:"required"` // 操作符 (使用string类型,与前端交互更通用) Level models.SeverityLevel `json:"level,omitempty"` // 告警等级,可选,如果未提供则使用默认值 } // UpdateDeviceThresholdAlarmDTO 更新设备阈值告警的请求DTO type UpdateDeviceThresholdAlarmDTO struct { - Thresholds float64 `json:"thresholds" binding:"required"` // 新的阈值 + Thresholds float32 `json:"thresholds" binding:"required"` // 新的阈值 Operator models.Operator `json:"operator" binding:"required"` // 新的操作符 Level models.SeverityLevel `json:"level,omitempty"` // 新的告警等级,可选 } @@ -102,14 +102,14 @@ type UpdateDeviceThresholdAlarmDTO struct { type CreateAreaThresholdAlarmDTO struct { AreaControllerID uint `json:"area_controller_id" binding:"required"` // 区域主控ID SensorType models.SensorType `json:"sensor_type" binding:"required"` // 传感器类型 - Thresholds float64 `json:"thresholds" binding:"required"` // 阈值 + Thresholds float32 `json:"thresholds" binding:"required"` // 阈值 Operator models.Operator `json:"operator" binding:"required"` // 操作符 Level models.SeverityLevel `json:"level,omitempty"` // 告警等级,可选 } // UpdateAreaThresholdAlarmDTO 更新区域阈值告警的请求DTO type UpdateAreaThresholdAlarmDTO struct { - Thresholds float64 `json:"thresholds" binding:"required"` // 新的阈值 + Thresholds float32 `json:"thresholds" binding:"required"` // 新的阈值 Operator models.Operator `json:"operator" binding:"required"` // 新的操作符 Level models.SeverityLevel `json:"level,omitempty"` // 新的告警等级,可选 } @@ -124,7 +124,7 @@ type AreaThresholdAlarmDTO struct { ID int `json:"id"` AreaControllerID uint `json:"area_controller_id"` SensorType models.SensorType `json:"sensor_type"` - Thresholds float64 `json:"thresholds"` + Thresholds float32 `json:"thresholds"` Operator models.Operator `json:"operator"` Level models.SeverityLevel `json:"level"` } @@ -134,7 +134,7 @@ type DeviceThresholdAlarmDTO struct { ID int `json:"id"` DeviceID uint `json:"device_id"` SensorType models.SensorType `json:"sensor_type"` - Thresholds float64 `json:"thresholds"` + Thresholds float32 `json:"thresholds"` Operator models.Operator `json:"operator"` Level models.SeverityLevel `json:"level"` } diff --git a/internal/app/dto/monitor_dto.go b/internal/app/dto/monitor_dto.go index 7129998..406883e 100644 --- a/internal/app/dto/monitor_dto.go +++ b/internal/app/dto/monitor_dto.go @@ -227,9 +227,9 @@ type RawMaterialPurchaseDTO struct { RawMaterialID uint `json:"raw_material_id"` RawMaterial RawMaterialDTO `json:"raw_material"` Supplier string `json:"supplier"` - Amount float64 `json:"amount"` - UnitPrice float64 `json:"unit_price"` - TotalPrice float64 `json:"total_price"` + Amount float32 `json:"amount"` + UnitPrice float32 `json:"unit_price"` + TotalPrice float32 `json:"total_price"` PurchaseDate time.Time `json:"purchase_date"` CreatedAt time.Time `json:"created_at"` } @@ -258,7 +258,7 @@ type ListRawMaterialStockLogRequest struct { type RawMaterialStockLogDTO struct { ID uint `json:"id"` RawMaterialID uint `json:"raw_material_id"` - ChangeAmount float64 `json:"change_amount"` + ChangeAmount float32 `json:"change_amount"` SourceType models.StockLogSourceType `json:"source_type"` SourceID uint `json:"source_id"` HappenedAt time.Time `json:"happened_at"` @@ -304,7 +304,7 @@ type FeedUsageRecordDTO struct { Pen PenDTO `json:"pen"` FeedFormulaID uint `json:"feed_formula_id"` FeedFormula FeedFormulaDTO `json:"feed_formula"` - Amount float64 `json:"amount"` + Amount float32 `json:"amount"` RecordedAt time.Time `json:"recorded_at"` OperatorID uint `json:"operator_id"` Remarks string `json:"remarks"` @@ -343,7 +343,7 @@ type MedicationLogDTO struct { PigBatchID uint `json:"pig_batch_id"` MedicationID uint `json:"medication_id"` Medication MedicationDTO `json:"medication"` - DosageUsed float64 `json:"dosage_used"` + DosageUsed float32 `json:"dosage_used"` TargetCount int `json:"target_count"` Reason models.MedicationReasonType `json:"reason"` Description string `json:"description"` @@ -439,7 +439,7 @@ type WeighingRecordDTO struct { ID uint `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` - Weight float64 `json:"weight"` + Weight float32 `json:"weight"` WeighingBatchID uint `json:"weighing_batch_id"` PenID uint `json:"pen_id"` OperatorID uint `json:"operator_id"` @@ -552,8 +552,8 @@ type PigPurchaseDTO struct { PurchaseDate time.Time `json:"purchase_date"` Supplier string `json:"supplier"` Quantity int `json:"quantity"` - UnitPrice float64 `json:"unit_price"` - TotalPrice float64 `json:"total_price"` + UnitPrice float32 `json:"unit_price"` + TotalPrice float32 `json:"total_price"` Remarks string `json:"remarks"` OperatorID uint `json:"operator_id"` } @@ -587,8 +587,8 @@ type PigSaleDTO struct { SaleDate time.Time `json:"sale_date"` Buyer string `json:"buyer"` Quantity int `json:"quantity"` - UnitPrice float64 `json:"unit_price"` - TotalPrice float64 `json:"total_price"` + UnitPrice float32 `json:"unit_price"` + TotalPrice float32 `json:"total_price"` Remarks string `json:"remarks"` OperatorID uint `json:"operator_id"` } diff --git a/internal/app/dto/pig_batch_dto.go b/internal/app/dto/pig_batch_dto.go index 1cdfc3c..0b23c92 100644 --- a/internal/app/dto/pig_batch_dto.go +++ b/internal/app/dto/pig_batch_dto.go @@ -74,8 +74,8 @@ type MovePigsIntoPenRequest struct { type SellPigsRequest struct { PenID uint `json:"pen_id" validate:"required"` // 猪栏ID Quantity int `json:"quantity" validate:"required,min=1"` // 卖出猪只数量 - UnitPrice float64 `json:"unit_price" validate:"required,min=0"` // 单价 - TotalPrice float64 `json:"total_price" validate:"required,min=0"` // 总价 + UnitPrice float32 `json:"unit_price" validate:"required,min=0"` // 单价 + TotalPrice float32 `json:"total_price" validate:"required,min=0"` // 总价 TraderName string `json:"trader_name" validate:"required"` // 交易方名称 TradeDate time.Time `json:"trade_date" validate:"required"` // 交易日期 Remarks string `json:"remarks"` // 备注 @@ -85,8 +85,8 @@ type SellPigsRequest struct { type BuyPigsRequest struct { PenID uint `json:"pen_id" validate:"required"` // 猪栏ID Quantity int `json:"quantity" validate:"required,min=1"` // 买入猪只数量 - UnitPrice float64 `json:"unit_price" validate:"required,min=0"` // 单价 - TotalPrice float64 `json:"total_price" validate:"required,min=0"` // 总价 + UnitPrice float32 `json:"unit_price" validate:"required,min=0"` // 单价 + TotalPrice float32 `json:"total_price" validate:"required,min=0"` // 总价 TraderName string `json:"trader_name" validate:"required"` // 交易方名称 TradeDate time.Time `json:"trade_date" validate:"required"` // 交易日期 Remarks string `json:"remarks"` // 备注 diff --git a/internal/app/service/pig_batch_service.go b/internal/app/service/pig_batch_service.go index 4ee78ac..b0ca12c 100644 --- a/internal/app/service/pig_batch_service.go +++ b/internal/app/service/pig_batch_service.go @@ -25,8 +25,8 @@ type PigBatchService interface { MovePigsIntoPen(ctx context.Context, batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error // Trade Sub-service - SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error - BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error + SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error + BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error // Transfer Sub-service TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error @@ -258,7 +258,7 @@ func (s *pigBatchService) MovePigsIntoPen(ctx context.Context, batchID uint, toP } // SellPigs 委托给领域服务 -func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { +func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "SellPigs") err := s.domainService.SellPigs(serviceCtx, batchID, penID, quantity, unitPrice, tatalPrice, traderName, tradeDate, remarks, operatorID) if err != nil { @@ -269,7 +269,7 @@ func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint } // BuyPigs 委托给领域服务 -func (s *pigBatchService) BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { +func (s *pigBatchService) BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "BuyPigs") err := s.domainService.BuyPigs(serviceCtx, batchID, penID, quantity, unitPrice, tatalPrice, traderName, tradeDate, remarks, operatorID) if err != nil { diff --git a/internal/app/webhook/chirp_stack.go b/internal/app/webhook/chirp_stack.go index 60e6fe4..8714d47 100644 --- a/internal/app/webhook/chirp_stack.go +++ b/internal/app/webhook/chirp_stack.go @@ -298,7 +298,7 @@ func (c *ChirpStackListener) handleUpEvent(ctx context.Context, event *UpEvent) valueDescriptor := valueDescriptors[0] // 5.3 应用乘数和偏移量计算最终值 - parsedValue := float64(rawSensorValue)*valueDescriptor.Multiplier + valueDescriptor.Offset + parsedValue := rawSensorValue*valueDescriptor.Multiplier + valueDescriptor.Offset // 5.4 根据传感器类型构建具体的数据结构 var dataToRecord interface{} @@ -312,7 +312,7 @@ func (c *ChirpStackListener) handleUpEvent(ctx context.Context, event *UpEvent) default: // TODO 未知传感器的数据需要记录吗 logger.Warnf("未知的传感器类型 '%s',将使用通用格式记录", valueDescriptor.Type) - dataToRecord = map[string]float64{"value": parsedValue} + dataToRecord = map[string]float32{"value": parsedValue} } // 5.5 记录传感器数据 diff --git a/internal/app/webhook/chirp_stack_types.go b/internal/app/webhook/chirp_stack_types.go index 55d7de5..05247d2 100644 --- a/internal/app/webhook/chirp_stack_types.go +++ b/internal/app/webhook/chirp_stack_types.go @@ -24,9 +24,9 @@ type DeviceInfo struct { // Location 包含了地理位置信息。 type Location struct { - Latitude float64 `json:"latitude"` // 纬度 - Longitude float64 `json:"longitude"` // 经度 - Altitude float64 `json:"altitude"` // 海拔 + Latitude float32 `json:"latitude"` // 纬度 + Longitude float32 `json:"longitude"` // 经度 + Altitude float32 `json:"altitude"` // 海拔 } // --- 可复用的子结构体 --- @@ -61,7 +61,7 @@ type UplinkRxInfo struct { UplinkID uint32 `json:"uplink_id"` // 上行ID Time time.Time `json:"time"` // 接收时间 Rssi int `json:"rssi"` // 接收信号强度指示 - Snr float64 `json:"snr"` // 信噪比 + Snr float32 `json:"snr"` // 信噪比 Channel int `json:"channel"` // 接收通道 Location *Location `json:"location"` // 网关位置 Context string `json:"context"` // 上下文信息 @@ -96,9 +96,9 @@ type DownlinkTxInfo struct { // ResolvedLocation 包含了地理位置解析结果。 type ResolvedLocation struct { - Latitude float64 `json:"latitude"` // 纬度 - Longitude float64 `json:"longitude"` // 经度 - Altitude float64 `json:"altitude"` // 海拔 + Latitude float32 `json:"latitude"` // 纬度 + Longitude float32 `json:"longitude"` // 经度 + Altitude float32 `json:"altitude"` // 海拔 Source string `json:"source"` // 位置来源 Accuracy int `json:"accuracy"` // 精度 } diff --git a/internal/domain/pig/pig_batch_service.go b/internal/domain/pig/pig_batch_service.go index a3d5ea2..f103efc 100644 --- a/internal/domain/pig/pig_batch_service.go +++ b/internal/domain/pig/pig_batch_service.go @@ -67,9 +67,9 @@ type PigBatchService interface { // ---交易子服务--- // SellPigs 处理卖猪的业务逻辑。 - SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error + SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error // BuyPigs 处理买猪的业务逻辑。 - BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error + BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error // ---调栏子服务 --- TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error diff --git a/internal/domain/pig/pig_batch_service_pig_trade.go b/internal/domain/pig/pig_batch_service_pig_trade.go index 9335795..c6a14b2 100644 --- a/internal/domain/pig/pig_batch_service_pig_trade.go +++ b/internal/domain/pig/pig_batch_service_pig_trade.go @@ -13,7 +13,7 @@ import ( ) // SellPigs 处理批量销售猪的业务逻辑。 -func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { +func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "SellPigs") return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { if quantity <= 0 { @@ -85,7 +85,7 @@ func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint } // BuyPigs 处理批量购买猪的业务逻辑。 -func (s *pigBatchService) BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float64, totalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { +func (s *pigBatchService) BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, totalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "BuyPigs") return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { if quantity <= 0 { diff --git a/internal/domain/task/area_threshold_check_task.go b/internal/domain/task/area_threshold_check_task.go index f7ba56e..2a2362d 100644 --- a/internal/domain/task/area_threshold_check_task.go +++ b/internal/domain/task/area_threshold_check_task.go @@ -16,7 +16,7 @@ import ( type AreaThresholdCheckParams struct { AreaControllerID uint `json:"area_controller_id"` // 区域主控ID SensorType models.SensorType `json:"sensor_type"` // 传感器类型 - Thresholds float64 `json:"thresholds"` // 阈值 + Thresholds float32 `json:"thresholds"` // 阈值 Operator models.Operator `json:"operator"` // 操作符 Level models.SeverityLevel `json:"level"` // 告警级别 ExcludeDeviceIDs []uint `json:"exclude_device_ids"` // 排除的传感器ID diff --git a/internal/domain/task/delay_task.go b/internal/domain/task/delay_task.go index f801ce4..916fb77 100644 --- a/internal/domain/task/delay_task.go +++ b/internal/domain/task/delay_task.go @@ -11,7 +11,7 @@ import ( ) type DelayTaskParams struct { - DelayDuration float64 `json:"delay_duration"` + DelayDuration float32 `json:"delay_duration"` } // DelayTask 是一个用于模拟延迟的 Task 实现 diff --git a/internal/domain/task/device_threshold_check_task.go b/internal/domain/task/device_threshold_check_task.go index 3fbace1..3616118 100644 --- a/internal/domain/task/device_threshold_check_task.go +++ b/internal/domain/task/device_threshold_check_task.go @@ -16,7 +16,7 @@ import ( type DeviceThresholdCheckParams struct { DeviceID uint `json:"device_id"` // 设备ID SensorType models.SensorType `json:"sensor_type"` // 传感器类型 - Thresholds float64 `json:"thresholds"` // 阈值 + Thresholds float32 `json:"thresholds"` // 阈值 Operator models.Operator `json:"operator"` // 操作符 Level models.SeverityLevel `json:"level"` // 告警等级 } @@ -55,7 +55,7 @@ func (d *DeviceThresholdCheckTask) Execute(ctx context.Context) error { return fmt.Errorf("任务 %v: 获取最新传感器数据失败: %v", d.taskLog.TaskID, err) } - var currentValue float64 + var currentValue float32 var alarmCode models.AlarmCode switch d.params.SensorType { @@ -123,7 +123,7 @@ func (d *DeviceThresholdCheckTask) Execute(ctx context.Context) error { } // checkThreshold 校验当前值是否满足阈值条件 -func (d *DeviceThresholdCheckTask) checkThreshold(currentValue float64, operator models.Operator, threshold float64) bool { +func (d *DeviceThresholdCheckTask) checkThreshold(currentValue float32, operator models.Operator, threshold float32) bool { switch operator { case models.OperatorLessThan: return currentValue < threshold diff --git a/internal/domain/task/release_feed_weight_task.go b/internal/domain/task/release_feed_weight_task.go index 5e2b30c..7b90038 100644 --- a/internal/domain/task/release_feed_weight_task.go +++ b/internal/domain/task/release_feed_weight_task.go @@ -15,7 +15,7 @@ import ( // ReleaseFeedWeightTaskParams 定义了 ReleaseFeedWeightTask 的参数结构 type ReleaseFeedWeightTaskParams struct { - ReleaseWeight float64 `json:"release_weight"` // 需要释放的重量 + ReleaseWeight float32 `json:"release_weight"` // 需要释放的重量 FeedPortDeviceID uint `json:"feed_port_device_id"` // 下料口ID MixingTankDeviceID uint `json:"mixing_tank_device_id"` // 称重传感器ID } @@ -29,7 +29,7 @@ type ReleaseFeedWeightTask struct { claimedLog *models.TaskExecutionLog feedPortDevice *models.Device - releaseWeight float64 + releaseWeight float32 mixingTankDeviceID uint feedPort device.Service @@ -100,7 +100,7 @@ func (r *ReleaseFeedWeightTask) Execute(ctx context.Context) error { } // 获取当前搅拌罐重量 -func (r *ReleaseFeedWeightTask) getNowWeight(ctx context.Context) (float64, error) { +func (r *ReleaseFeedWeightTask) getNowWeight(ctx context.Context) (float32, error) { taskCtx, logger := logs.Trace(ctx, r.ctx, "getNowWeight") sensorData, err := r.sensorDataRepo.GetLatestSensorDataByDeviceIDAndSensorType(taskCtx, r.mixingTankDeviceID, models.SensorTypeWeight) if err != nil { diff --git a/internal/infra/logs/logs.go b/internal/infra/logs/logs.go index 13e8c4d..d44cf9d 100644 --- a/internal/infra/logs/logs.go +++ b/internal/infra/logs/logs.go @@ -208,7 +208,7 @@ func (g *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql fields := []interface{}{ "sql", sql, "rows", rows, - "elapsed", fmt.Sprintf("%.3fms", float64(elapsed.Nanoseconds())/1e6), + "elapsed", fmt.Sprintf("%.3fms", float32(elapsed.Nanoseconds())/1e6), } // 附加调用链信息 diff --git a/internal/infra/models/device_template.go b/internal/infra/models/device_template.go index 8960046..a8f2c7e 100644 --- a/internal/infra/models/device_template.go +++ b/internal/infra/models/device_template.go @@ -54,8 +54,8 @@ const ( // 它提供了必要的元数据,以便应用程序能够正确解释从设备读取的原始数据。 type ValueDescriptor struct { Type SensorType `json:"type"` - Multiplier float64 `json:"multiplier"` // 乘数,用于原始数据转换 - Offset float64 `json:"offset"` // 偏移量,用于原始数据转换 + Multiplier float32 `json:"multiplier"` // 乘数,用于原始数据转换 + Offset float32 `json:"offset"` // 偏移量,用于原始数据转换 } // --- 指令结构体 (Command Structs) --- diff --git a/internal/infra/models/feed.go b/internal/infra/models/feed.go index a16378f..b1414dc 100644 --- a/internal/infra/models/feed.go +++ b/internal/infra/models/feed.go @@ -16,7 +16,7 @@ type RawMaterial struct { gorm.Model Name string `gorm:"size:100;unique;not null;comment:原料名称"` Description string `gorm:"size:255;comment:描述"` - Quantity float64 `gorm:"not null;comment:库存总量, 单位: g"` + Quantity float32 `gorm:"not null;comment:库存总量, 单位: g"` } func (RawMaterial) TableName() string { @@ -29,9 +29,9 @@ type RawMaterialPurchase struct { RawMaterialID uint `gorm:"not null;index;comment:关联的原料ID"` RawMaterial RawMaterial `gorm:"foreignKey:RawMaterialID"` Supplier string `gorm:"size:100;comment:供应商"` - Amount float64 `gorm:"not null;comment:采购数量, 单位: g"` - UnitPrice float64 `gorm:"comment:单价"` - TotalPrice float64 `gorm:"comment:总价"` + Amount float32 `gorm:"not null;comment:采购数量, 单位: g"` + UnitPrice float32 `gorm:"comment:单价"` + TotalPrice float32 `gorm:"comment:总价"` PurchaseDate time.Time `gorm:"primaryKey;comment:采购日期"` CreatedAt time.Time } @@ -56,7 +56,7 @@ const ( type RawMaterialStockLog struct { gorm.Model RawMaterialID uint `gorm:"not null;index;comment:关联的原料ID"` - ChangeAmount float64 `gorm:"not null;comment:变动数量, 正数为入库, 负数为出库"` + ChangeAmount float32 `gorm:"not null;comment:变动数量, 正数为入库, 负数为出库"` SourceType StockLogSourceType `gorm:"size:50;not null;index;comment:库存变动来源类型"` SourceID uint `gorm:"not null;index;comment:来源记录的ID (如 RawMaterialPurchase.ID 或 FeedUsageRecord.ID)"` HappenedAt time.Time `gorm:"primaryKey;comment:业务发生时间"` @@ -86,7 +86,7 @@ type FeedFormulaComponent struct { FeedFormulaID uint `gorm:"not null;index;comment:外键到 FeedFormula"` RawMaterialID uint `gorm:"not null;index;comment:外键到 RawMaterial"` RawMaterial RawMaterial `gorm:"foreignKey:RawMaterialID"` - Percentage float64 `gorm:"not null;comment:该原料在配方中的百分比 (0-1.0)"` + Percentage float32 `gorm:"not null;comment:该原料在配方中的百分比 (0-1.0)"` } func (FeedFormulaComponent) TableName() string { @@ -102,7 +102,7 @@ type FeedUsageRecord struct { Pen Pen `gorm:"foreignKey:PenID"` FeedFormulaID uint `gorm:"not null;index;comment:使用的饲料配方ID"` FeedFormula FeedFormula `gorm:"foreignKey:FeedFormulaID"` - Amount float64 `gorm:"not null;comment:使用数量, 单位: g"` + Amount float32 `gorm:"not null;comment:使用数量, 单位: g"` RecordedAt time.Time `gorm:"primaryKey;comment:记录时间"` OperatorID uint `gorm:"not null;comment:操作员"` Remarks string `gorm:"comment:备注, 如 '例行喂料, 弱猪补料' 等"` diff --git a/internal/infra/models/medication.go b/internal/infra/models/medication.go index bd60718..c1bd710 100644 --- a/internal/infra/models/medication.go +++ b/internal/infra/models/medication.go @@ -48,9 +48,9 @@ type PowderInstructions struct { // 出栏前停药期 WithdrawalPeriod time.Duration `json:"withdrawal_period"` // 拌料使用计量, 每千克体重用多少克药, 单位: g/kg - BodyWeightDosageUsed float64 `json:"body_weight_dosage_used"` + BodyWeightDosageUsed float32 `json:"body_weight_dosage_used"` // 拌料使用剂量, 每升水加多少克药或每千克饲料干重加多少克药, 单位: g/kg(L) - MixDosageUsed float64 `json:"mix_dosage_used"` + MixDosageUsed float32 `json:"mix_dosage_used"` // 拌料使用方式, 兑水/拌料 MixType MixType `json:"mix_type"` } @@ -61,8 +61,8 @@ type Medication struct { Name string `gorm:"size:100;not null;comment:药品名称" json:"name"` Type MedicationType `gorm:"size:20;not null;comment:兽药类型 (粉剂, 针剂, 疫苗)" json:"type"` Category MedicationCategory `gorm:"size:30;not null;comment:兽药种类 (四环素类, 磺胺类等)" json:"category"` - DosagePerUnit float64 `gorm:"size:50;comment:一份药物的计量 (针剂计量单位为毫升, 粉剂为克)" json:"dosage_per_unit"` - ActiveIngredientConcentration float64 `gorm:"size:50;comment:有效成分含量百分比" json:"active_ingredient_concentration"` + DosagePerUnit float32 `gorm:"size:50;comment:一份药物的计量 (针剂计量单位为毫升, 粉剂为克)" json:"dosage_per_unit"` + ActiveIngredientConcentration float32 `gorm:"size:50;comment:有效成分含量百分比" json:"active_ingredient_concentration"` Manufacturer string `gorm:"size:100;comment:生产厂家" json:"manufacturer"` Instructions datatypes.JSON `gorm:"type:jsonb;comment:使用说明" json:"instructions"` } @@ -86,7 +86,7 @@ type MedicationLog struct { PigBatchID uint `gorm:"not null;index;comment:关联的猪批次ID"` MedicationID uint `gorm:"not null;index;comment:关联的药品ID"` Medication Medication `gorm:"foreignKey:MedicationID"` // 预加载药品信息 - DosageUsed float64 `gorm:"not null;comment:使用的总剂量 (单位由药品决定,如g或ml)"` + DosageUsed float32 `gorm:"not null;comment:使用的总剂量 (单位由药品决定,如g或ml)"` TargetCount int `gorm:"not null;comment:用药对象数量"` Reason MedicationReasonType `gorm:"size:20;not null;comment:用药原因"` Description string `gorm:"size:255;comment:具体描述,如'治疗呼吸道病'"` diff --git a/internal/infra/models/pig_batch.go b/internal/infra/models/pig_batch.go index 658c31b..37187c9 100644 --- a/internal/infra/models/pig_batch.go +++ b/internal/infra/models/pig_batch.go @@ -95,7 +95,7 @@ func (WeighingBatch) TableName() string { // WeighingRecord 记录了单次称重信息 type WeighingRecord struct { gorm.Model - Weight float64 `gorm:"not null;comment:单只猪重量 (kg)"` + Weight float32 `gorm:"not null;comment:单只猪重量 (kg)"` WeighingBatchID uint `gorm:"not null;index;comment:关联的批次称重ID"` PenID uint `gorm:"not null;index;comment:所在猪圈ID"` OperatorID uint `gorm:"not null;comment:操作员ID"` diff --git a/internal/infra/models/pig_trade.go b/internal/infra/models/pig_trade.go index 4d4e835..e46a716 100644 --- a/internal/infra/models/pig_trade.go +++ b/internal/infra/models/pig_trade.go @@ -13,8 +13,8 @@ type PigPurchase struct { PurchaseDate time.Time `gorm:"primaryKey;comment:采购日期"` Supplier string `gorm:"comment:供应商"` Quantity int `gorm:"not null;comment:采购数量"` - UnitPrice float64 `gorm:"not null;comment:单价"` - TotalPrice float64 `gorm:"not null;comment:总价"` + UnitPrice float32 `gorm:"not null;comment:单价"` + TotalPrice float32 `gorm:"not null;comment:总价"` Remarks string `gorm:"size:255;comment:备注"` OperatorID uint `gorm:"comment:操作员ID"` } @@ -30,8 +30,8 @@ type PigSale struct { SaleDate time.Time `gorm:"primaryKey;comment:销售日期"` Buyer string `gorm:"comment:购买方"` Quantity int `gorm:"not null;comment:销售数量"` - UnitPrice float64 `gorm:"not null;comment:单价"` - TotalPrice float64 `gorm:"not null;comment:总价"` + UnitPrice float32 `gorm:"not null;comment:单价"` + TotalPrice float32 `gorm:"not null;comment:总价"` Remarks string `gorm:"size:255;comment:备注"` OperatorID uint `gorm:"comment:操作员ID"` } diff --git a/internal/infra/models/sensor_data.go b/internal/infra/models/sensor_data.go index 804b164..a04481a 100644 --- a/internal/infra/models/sensor_data.go +++ b/internal/infra/models/sensor_data.go @@ -22,7 +22,7 @@ const ( // SignalMetrics 存储信号强度数据 type SignalMetrics struct { RssiDbm int `json:"rssi_dbm"` // 绝对信号强度(dBm),受距离、障碍物影响 - SnrDb float64 `json:"snr_db"` // 信号与噪声的相对比率(dB),由 RSSI 减去噪声地板(Noise Floor) + SnrDb float32 `json:"snr_db"` // 信号与噪声的相对比率(dB),由 RSSI 减去噪声地板(Noise Floor) SensitivityDbm int `json:"sensitivity_dbm"` // 网关的最低检测阈值(dBm) MarginDb int `json:"margin_db"` // SNR 相对于接收器灵敏度的余量, Margin = SNR - Sensitivity } @@ -36,17 +36,17 @@ type BatteryLevel struct { // TemperatureData 存储温度数据 type TemperatureData struct { - TemperatureCelsius float64 `json:"temperature_celsius"` // 温度值 (摄氏度) + TemperatureCelsius float32 `json:"temperature_celsius"` // 温度值 (摄氏度) } // HumidityData 存储湿度数据 type HumidityData struct { - HumidityPercent float64 `json:"humidity_percent"` // 湿度值 (%) + HumidityPercent float32 `json:"humidity_percent"` // 湿度值 (%) } // WeightData 存储重量数据 type WeightData struct { - WeightKilograms float64 `json:"weight_kilograms"` // 重量值 (公斤) + WeightKilograms float32 `json:"weight_kilograms"` // 重量值 (公斤) } // SensorData 存储所有类型的传感器数据,对应数据库中的 'sensor_data' 表。 diff --git a/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go b/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go index acebfcf..c719246 100644 --- a/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go +++ b/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go @@ -474,7 +474,7 @@ func (t *LoRaMeshUartPassthroughTransport) handleUpstreamMessage(ctx context.Con } valueDescriptor := valueDescriptors[0] - parsedValue := float64(rawSensorValue)*valueDescriptor.Multiplier + valueDescriptor.Offset + parsedValue := rawSensorValue*valueDescriptor.Multiplier + valueDescriptor.Offset var dataToRecord interface{} switch valueDescriptor.Type { @@ -486,7 +486,7 @@ func (t *LoRaMeshUartPassthroughTransport) handleUpstreamMessage(ctx context.Con dataToRecord = models.WeightData{WeightKilograms: parsedValue} default: logger.Warnf("未知的传感器类型 '%s',将使用通用格式记录", valueDescriptor.Type) - dataToRecord = map[string]float64{"value": parsedValue} + dataToRecord = map[string]float32{"value": parsedValue} } t.recordSensorData(loraCtx, areaController.ID, dev.ID, time.Now(), valueDescriptor.Type, dataToRecord) From ecd2d37c7087b82a0d3e888e98a0fef141a0f296 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 10 Nov 2025 22:23:31 +0800 Subject: [PATCH 35/35] =?UTF-8?q?uint/uint64=E5=85=A8=E9=83=A8=E6=94=B9?= =?UTF-8?q?=E4=B8=BAuint32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 3 +- docs/docs.go | 16 +- docs/swagger.json | 16 +- docs/swagger.yaml | 8 +- .../alarm/threshold_alarm_controller.go | 4 +- internal/app/controller/auth_utils.go | 2 +- .../controller/device/device_controller.go | 20 +-- .../management/controller_helpers.go | 22 +-- .../management/pig_batch_controller.go | 28 +-- .../management/pig_batch_health_controller.go | 12 +- .../management/pig_batch_trade_controller.go | 4 +- .../pig_batch_transfer_controller.go | 8 +- .../management/pig_farm_controller.go | 14 +- .../app/controller/plan/plan_controller.go | 20 +-- .../app/controller/user/user_controller.go | 4 +- internal/app/dto/alarm_dto.go | 24 +-- internal/app/dto/device_dto.go | 18 +- internal/app/dto/monitor_converter.go | 6 +- internal/app/dto/monitor_dto.go | 164 +++++++++--------- internal/app/dto/notification_dto.go | 6 +- internal/app/dto/pig_batch_dto.go | 42 ++--- internal/app/dto/pig_farm_dto.go | 12 +- internal/app/dto/plan_dto.go | 22 +-- internal/app/dto/user_dto.go | 6 +- internal/app/service/device_service.go | 40 ++--- internal/app/service/monitor_service.go | 2 +- internal/app/service/pig_batch_service.go | 72 ++++---- internal/app/service/pig_farm_service.go | 36 ++-- internal/app/service/plan_service.go | 20 +-- .../app/service/threshold_alarm_service.go | 22 +-- internal/app/service/user_service.go | 4 +- internal/app/webhook/chirp_stack.go | 2 +- internal/core/data_initializer.go | 2 +- internal/domain/alarm/alarm_service.go | 12 +- internal/domain/device/device_service.go | 2 +- .../domain/device/general_device_service.go | 4 +- internal/domain/notify/notify.go | 18 +- internal/domain/pig/pen_transfer_manager.go | 24 +-- internal/domain/pig/pig_batch_service.go | 42 ++--- .../domain/pig/pig_batch_service_method.go | 14 +- .../pig/pig_batch_service_pen_transfer.go | 19 +- .../domain/pig/pig_batch_service_pig_sick.go | 12 +- .../domain/pig/pig_batch_service_pig_trade.go | 4 +- internal/domain/pig/pig_sick_manager.go | 4 +- .../domain/plan/analysis_plan_task_manager.go | 22 +-- .../domain/plan/plan_execution_manager.go | 24 +-- internal/domain/plan/plan_service.go | 20 +-- internal/domain/plan/task.go | 4 +- .../domain/task/alarm_notification_task.go | 6 +- .../domain/task/area_threshold_check_task.go | 10 +- internal/domain/task/delay_task.go | 4 +- .../task/device_threshold_check_task.go | 6 +- internal/domain/task/full_collection_task.go | 6 +- .../domain/task/release_feed_weight_task.go | 10 +- internal/infra/config/config.go | 14 +- internal/infra/models/alarm.go | 18 +- internal/infra/models/device.go | 11 +- internal/infra/models/device_template.go | 3 +- internal/infra/models/execution.go | 16 +- internal/infra/models/farm_asset.go | 12 +- internal/infra/models/feed.go | 30 ++-- internal/infra/models/medication.go | 11 +- internal/infra/models/models.go | 20 ++- internal/infra/models/notify.go | 6 +- internal/infra/models/pig_batch.go | 22 ++- internal/infra/models/pig_sick.go | 10 +- internal/infra/models/pig_trade.go | 14 +- internal/infra/models/pig_transfer.go | 10 +- internal/infra/models/plan.go | 24 +-- internal/infra/models/schedule.go | 6 +- internal/infra/models/sensor_data.go | 4 +- internal/infra/models/user.go | 4 +- internal/infra/repository/alarm_repository.go | 36 ++-- .../repository/area_controller_repository.go | 12 +- .../device_command_log_repository.go | 2 +- .../infra/repository/device_repository.go | 35 ++-- .../repository/device_template_repository.go | 12 +- .../repository/execution_log_repository.go | 48 ++--- .../repository/medication_log_repository.go | 6 +- .../repository/notification_repository.go | 2 +- .../pending_collection_repository.go | 2 +- .../repository/pending_task_repository.go | 24 +-- .../repository/pig_batch_log_repository.go | 12 +- .../infra/repository/pig_batch_repository.go | 24 +-- .../infra/repository/pig_farm_repository.go | 12 +- .../infra/repository/pig_pen_repository.go | 20 +-- .../infra/repository/pig_sick_repository.go | 10 +- .../infra/repository/pig_trade_repository.go | 8 +- .../repository/pig_transfer_log_repository.go | 10 +- internal/infra/repository/plan_repository.go | 70 ++++---- .../repository/raw_material_repository.go | 12 +- .../repository/sensor_data_repository.go | 6 +- .../repository/user_action_log_repository.go | 2 +- internal/infra/repository/user_repository.go | 4 +- .../lora_mesh_uart_passthrough_transport.go | 2 +- internal/infra/utils/token/token_service.go | 6 +- 96 files changed, 775 insertions(+), 785 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 3bd49d3..d93188d 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -145,4 +145,5 @@ 13. 实现根据区域ID或设备ID清空对应阈值告警任务 14. 设备和区域主控删除时清除对应区域阈值告警或设备阈值告警任务 15. 将所有Regional更改为Area -16. float64全部改float32 \ No newline at end of file +16. float64全部改float32 +17. uint/uint64全部改为uint32 \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 61485a4..8884ef9 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1665,7 +1665,6 @@ const docTemplate = `{ }, { "enum": [ - 7, -1, 0, 1, @@ -1675,12 +1674,12 @@ const docTemplate = `{ 5, -1, 5, - 6 + 6, + 7 ], "type": "integer", "format": "int32", "x-enum-varnames": [ - "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -1690,7 +1689,8 @@ const docTemplate = `{ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel" + "InvalidLevel", + "_numLevels" ], "name": "level", "in": "query" @@ -7981,7 +7981,6 @@ const docTemplate = `{ "type": "integer", "format": "int32", "enum": [ - 7, -1, 0, 1, @@ -7991,10 +7990,10 @@ const docTemplate = `{ 5, -1, 5, - 6 + 6, + 7 ], "x-enum-varnames": [ - "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -8004,7 +8003,8 @@ const docTemplate = `{ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel" + "InvalidLevel", + "_numLevels" ] } }, diff --git a/docs/swagger.json b/docs/swagger.json index dfa71b7..aadfac4 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1657,7 +1657,6 @@ }, { "enum": [ - 7, -1, 0, 1, @@ -1667,12 +1666,12 @@ 5, -1, 5, - 6 + 6, + 7 ], "type": "integer", "format": "int32", "x-enum-varnames": [ - "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -1682,7 +1681,8 @@ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel" + "InvalidLevel", + "_numLevels" ], "name": "level", "in": "query" @@ -7973,7 +7973,6 @@ "type": "integer", "format": "int32", "enum": [ - 7, -1, 0, 1, @@ -7983,10 +7982,10 @@ 5, -1, 5, - 6 + 6, + 7 ], "x-enum-varnames": [ - "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -7996,7 +7995,8 @@ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel" + "InvalidLevel", + "_numLevels" ] } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 0230576..10e5f77 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2230,7 +2230,6 @@ definitions: - PlanTypeFilterSystem zapcore.Level: enum: - - 7 - -1 - 0 - 1 @@ -2241,10 +2240,10 @@ definitions: - -1 - 5 - 6 + - 7 format: int32 type: integer x-enum-varnames: - - _numLevels - DebugLevel - InfoLevel - WarnLevel @@ -2255,6 +2254,7 @@ definitions: - _minLevel - _maxLevel - InvalidLevel + - _numLevels info: contact: email: divano@example.com @@ -3248,7 +3248,6 @@ paths: name: end_time type: string - enum: - - 7 - -1 - 0 - 1 @@ -3259,12 +3258,12 @@ paths: - -1 - 5 - 6 + - 7 format: int32 in: query name: level type: integer x-enum-varnames: - - _numLevels - DebugLevel - InfoLevel - WarnLevel @@ -3275,6 +3274,7 @@ paths: - _minLevel - _maxLevel - InvalidLevel + - _numLevels - enum: - 邮件 - 企业微信 diff --git a/internal/app/controller/alarm/threshold_alarm_controller.go b/internal/app/controller/alarm/threshold_alarm_controller.go index fed4768..6e69afd 100644 --- a/internal/app/controller/alarm/threshold_alarm_controller.go +++ b/internal/app/controller/alarm/threshold_alarm_controller.go @@ -61,7 +61,7 @@ func (t *ThresholdAlarmController) SnoozeThresholdAlarm(ctx echo.Context) error return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) } - if err := t.thresholdAlarmService.SnoozeThresholdAlarm(reqCtx, uint(alarmID), req.DurationMinutes); err != nil { + if err := t.thresholdAlarmService.SnoozeThresholdAlarm(reqCtx, uint32(alarmID), req.DurationMinutes); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Warnf("%s: 告警不存在, ID: %d", actionType, alarmID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "告警未找到", actionType, "告警不存在", alarmID) @@ -96,7 +96,7 @@ func (t *ThresholdAlarmController) CancelSnoozeThresholdAlarm(ctx echo.Context) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的告警ID: "+alarmIDStr, actionType, "无效的告警ID", alarmIDStr) } - if err := t.thresholdAlarmService.CancelSnoozeThresholdAlarm(reqCtx, uint(alarmID)); err != nil { + if err := t.thresholdAlarmService.CancelSnoozeThresholdAlarm(reqCtx, uint32(alarmID)); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Warnf("%s: 告警不存在, ID: %d", actionType, alarmID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "告警未找到", actionType, "告警不存在", alarmID) diff --git a/internal/app/controller/auth_utils.go b/internal/app/controller/auth_utils.go index 25d28b8..2b0fe81 100644 --- a/internal/app/controller/auth_utils.go +++ b/internal/app/controller/auth_utils.go @@ -17,7 +17,7 @@ var ( // GetOperatorIDFromContext 从 echo.Context 中提取操作者ID。 // 假设操作者ID是由 AuthMiddleware 存储到 context 中的 *models.User 对象的 ID 字段。 -func GetOperatorIDFromContext(c echo.Context) (uint, error) { +func GetOperatorIDFromContext(c echo.Context) (uint32, error) { userVal := c.Get(models.ContextUserKey.String()) if userVal == nil { return 0, ErrUserNotFoundInContext diff --git a/internal/app/controller/device/device_controller.go b/internal/app/controller/device/device_controller.go index 451b22b..448fecc 100644 --- a/internal/app/controller/device/device_controller.go +++ b/internal/app/controller/device/device_controller.go @@ -84,7 +84,7 @@ func (c *Controller) GetDevice(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+deviceID, actionType, "无效的ID", deviceID) } - resp, err := c.deviceService.GetDevice(reqCtx, uint(id)) + resp, err := c.deviceService.GetDevice(reqCtx, uint32(id)) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) @@ -149,7 +149,7 @@ func (c *Controller) UpdateDevice(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+deviceID, actionType, "无效的ID", deviceID) } - resp, err := c.deviceService.UpdateDevice(reqCtx, uint(id), &req) + resp, err := c.deviceService.UpdateDevice(reqCtx, uint32(id), &req) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) @@ -184,7 +184,7 @@ func (c *Controller) DeleteDevice(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+deviceID, actionType, "无效的ID", deviceID) } - if err := c.deviceService.DeleteDevice(reqCtx, uint(id)); err != nil { + if err := c.deviceService.DeleteDevice(reqCtx, uint32(id)); err != nil { switch { case errors.Is(err, gorm.ErrRecordNotFound): logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) @@ -232,7 +232,7 @@ func (c *Controller) ManualControl(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+deviceID, actionType, "无效的ID", deviceID) } - if err := c.deviceService.ManualControl(reqCtx, uint(id), &req); err != nil { + if err := c.deviceService.ManualControl(reqCtx, uint32(id), &req); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", actionType, "设备不存在", deviceID) @@ -297,7 +297,7 @@ func (c *Controller) GetAreaController(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+acID, actionType, "无效的ID", acID) } - resp, err := c.deviceService.GetAreaController(reqCtx, uint(id)) + resp, err := c.deviceService.GetAreaController(reqCtx, uint32(id)) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID) @@ -361,7 +361,7 @@ func (c *Controller) UpdateAreaController(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+acID, actionType, "无效的ID", acID) } - resp, err := c.deviceService.UpdateAreaController(reqCtx, uint(id), &req) + resp, err := c.deviceService.UpdateAreaController(reqCtx, uint32(id), &req) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID) @@ -396,7 +396,7 @@ func (c *Controller) DeleteAreaController(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+acID, actionType, "无效的ID", acID) } - if err := c.deviceService.DeleteAreaController(reqCtx, uint(id)); err != nil { + if err := c.deviceService.DeleteAreaController(reqCtx, uint32(id)); err != nil { switch { case errors.Is(err, gorm.ErrRecordNotFound): logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID) @@ -467,7 +467,7 @@ func (c *Controller) GetDeviceTemplate(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+dtID, actionType, "无效的ID", dtID) } - resp, err := c.deviceService.GetDeviceTemplate(reqCtx, uint(id)) + resp, err := c.deviceService.GetDeviceTemplate(reqCtx, uint32(id)) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID) @@ -532,7 +532,7 @@ func (c *Controller) UpdateDeviceTemplate(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+dtID, actionType, "无效的ID", dtID) } - resp, err := c.deviceService.UpdateDeviceTemplate(reqCtx, uint(id), &req) + resp, err := c.deviceService.UpdateDeviceTemplate(reqCtx, uint32(id), &req) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID) @@ -567,7 +567,7 @@ func (c *Controller) DeleteDeviceTemplate(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+dtID, actionType, "无效的ID", dtID) } - if err := c.deviceService.DeleteDeviceTemplate(reqCtx, uint(id)); err != nil { + if err := c.deviceService.DeleteDeviceTemplate(reqCtx, uint32(id)); err != nil { switch { case errors.Is(err, gorm.ErrRecordNotFound): logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID) diff --git a/internal/app/controller/management/controller_helpers.go b/internal/app/controller/management/controller_helpers.go index 8e68435..08345ee 100644 --- a/internal/app/controller/management/controller_helpers.go +++ b/internal/app/controller/management/controller_helpers.go @@ -15,7 +15,7 @@ import ( // mapAndSendError 统一映射服务层错误并发送响应。 // 这个函数将服务层返回的错误转换为控制器层应返回的HTTP状态码和审计信息。 -func mapAndSendError(reqContext context.Context, c *PigBatchController, ctx echo.Context, action string, err error, id uint) error { +func mapAndSendError(reqContext context.Context, c *PigBatchController, ctx echo.Context, action string, err error, id uint32) error { if errors.Is(err, service.ErrPigBatchNotFound) || errors.Is(err, service.ErrPenNotFound) || errors.Is(err, service.ErrPenNotAssociatedWithBatch) { @@ -34,7 +34,7 @@ func mapAndSendError(reqContext context.Context, c *PigBatchController, ctx echo } // idExtractorFunc 定义了一个函数类型,用于从echo.Context中提取主ID。 -type idExtractorFunc func(ctx echo.Context) (uint, error) +type idExtractorFunc func(ctx echo.Context) (uint32, error) // extractOperatorAndPrimaryID 封装了从echo.Context中提取操作员ID和主ID的通用逻辑。 // 它负责处理ID提取过程中的错误,并发送相应的HTTP响应。 @@ -48,15 +48,15 @@ type idExtractorFunc func(ctx echo.Context) (uint, error) // // 返回值: // -// operatorID: uint - 提取到的操作员ID。 -// primaryID: uint - 提取到的主ID。 +// operatorID: uint32 - 提取到的操作员ID。 +// primaryID: uint32 - 提取到的主ID。 // err: error - 如果ID提取失败或发送错误响应,则返回错误。 func extractOperatorAndPrimaryID( c *PigBatchController, ctx echo.Context, action string, idExtractor idExtractorFunc, -) (operatorID uint, primaryID uint, err error) { +) (operatorID uint32, primaryID uint32, err error) { // 1. 获取操作员ID operatorID, err = controller.GetOperatorIDFromContext(ctx) if err != nil { @@ -78,7 +78,7 @@ func extractOperatorAndPrimaryID( if err != nil { return 0, 0, controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", idParam) } - primaryID = uint(parsedID) + primaryID = uint32(parsedID) } } @@ -93,7 +93,7 @@ func handleAPIRequest[Req any]( ctx echo.Context, action string, reqDTO Req, - serviceExecutor func(ctx echo.Context, operatorID uint, primaryID uint, req Req) error, + serviceExecutor func(ctx echo.Context, operatorID uint32, primaryID uint32, req Req) error, successMsg string, idExtractor idExtractorFunc, ) error { @@ -124,7 +124,7 @@ func handleNoBodyAPIRequest( c *PigBatchController, ctx echo.Context, action string, - serviceExecutor func(ctx echo.Context, operatorID uint, primaryID uint) error, + serviceExecutor func(ctx echo.Context, operatorID uint32, primaryID uint32) error, successMsg string, idExtractor idExtractorFunc, ) error { @@ -151,7 +151,7 @@ func handleAPIRequestWithResponse[Req any, Resp any]( ctx echo.Context, action string, reqDTO Req, - serviceExecutor func(ctx echo.Context, operatorID uint, primaryID uint, req Req) (Resp, error), // serviceExecutor现在返回Resp + serviceExecutor func(ctx echo.Context, operatorID uint32, primaryID uint32, req Req) (Resp, error), // serviceExecutor现在返回Resp successMsg string, idExtractor idExtractorFunc, ) error { @@ -182,7 +182,7 @@ func handleNoBodyAPIRequestWithResponse[Resp any]( c *PigBatchController, ctx echo.Context, action string, - serviceExecutor func(ctx echo.Context, operatorID uint, primaryID uint) (Resp, error), // serviceExecutor现在返回Resp + serviceExecutor func(ctx echo.Context, operatorID uint32, primaryID uint32) (Resp, error), // serviceExecutor现在返回Resp successMsg string, idExtractor idExtractorFunc, ) error { @@ -209,7 +209,7 @@ func handleQueryAPIRequestWithResponse[Query any, Resp any]( ctx echo.Context, action string, queryDTO Query, - serviceExecutor func(ctx echo.Context, operatorID uint, query Query) (Resp, error), // serviceExecutor现在接收queryDTO + serviceExecutor func(ctx echo.Context, operatorID uint32, query Query) (Resp, error), // serviceExecutor现在接收queryDTO successMsg string, ) error { // 1. 绑定查询参数 diff --git a/internal/app/controller/management/pig_batch_controller.go b/internal/app/controller/management/pig_batch_controller.go index 6a82809..a0dd7e9 100644 --- a/internal/app/controller/management/pig_batch_controller.go +++ b/internal/app/controller/management/pig_batch_controller.go @@ -43,7 +43,7 @@ func (c *PigBatchController) CreatePigBatch(ctx echo.Context) error { return handleAPIRequestWithResponse( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) { // 对于创建操作,primaryID通常不从路径中获取,而是由服务层生成 return c.service.CreatePigBatch(reqCtx, operatorID, req) }, @@ -68,7 +68,7 @@ func (c *PigBatchController) GetPigBatch(ctx echo.Context) error { return handleNoBodyAPIRequestWithResponse( reqCtx, c, ctx, action, - func(ctx echo.Context, operatorID uint, primaryID uint) (*dto.PigBatchResponseDTO, error) { + func(ctx echo.Context, operatorID uint32, primaryID uint32) (*dto.PigBatchResponseDTO, error) { return c.service.GetPigBatch(reqCtx, primaryID) }, "获取成功", @@ -95,7 +95,7 @@ func (c *PigBatchController) UpdatePigBatch(ctx echo.Context) error { return handleAPIRequestWithResponse( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) { return c.service.UpdatePigBatch(reqCtx, primaryID, req) }, "更新成功", @@ -119,7 +119,7 @@ func (c *PigBatchController) DeletePigBatch(ctx echo.Context) error { return handleNoBodyAPIRequest( reqCtx, c, ctx, action, - func(ctx echo.Context, operatorID uint, primaryID uint) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32) error { return c.service.DeletePigBatch(reqCtx, primaryID) }, "删除成功", @@ -144,7 +144,7 @@ func (c *PigBatchController) ListPigBatches(ctx echo.Context) error { return handleQueryAPIRequestWithResponse( reqCtx, c, ctx, action, &query, - func(ctx echo.Context, operatorID uint, query *dto.PigBatchQueryDTO) ([]*dto.PigBatchResponseDTO, error) { + func(ctx echo.Context, operatorID uint32, query *dto.PigBatchQueryDTO) ([]*dto.PigBatchResponseDTO, error) { return c.service.ListPigBatches(reqCtx, query.IsActive) }, "获取成功", @@ -170,7 +170,7 @@ func (c *PigBatchController) AssignEmptyPensToBatch(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.AssignEmptyPensToBatchRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.AssignEmptyPensToBatchRequest) error { return c.service.AssignEmptyPensToBatch(reqCtx, primaryID, req.PenIDs, operatorID) }, "分配成功", @@ -197,18 +197,18 @@ func (c *PigBatchController) ReclassifyPenToNewBatch(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.ReclassifyPenToNewBatchRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.ReclassifyPenToNewBatchRequest) error { // primaryID 在这里是 fromBatchID return c.service.ReclassifyPenToNewBatch(reqCtx, primaryID, req.ToBatchID, req.PenID, operatorID, req.Remarks) }, "划拨成功", - func(ctx echo.Context) (uint, error) { // 自定义ID提取器,从 ":fromBatchID" 路径参数提取 + func(ctx echo.Context) (uint32, error) { // 自定义ID提取器,从 ":fromBatchID" 路径参数提取 idParam := ctx.Param("fromBatchID") parsedID, err := strconv.ParseUint(idParam, 10, 32) if err != nil { return 0, err } - return uint(parsedID), nil + return uint32(parsedID), nil }, ) } @@ -230,23 +230,23 @@ func (c *PigBatchController) RemoveEmptyPenFromBatch(ctx echo.Context) error { return handleNoBodyAPIRequest( reqCtx, c, ctx, action, - func(ctx echo.Context, operatorID uint, primaryID uint) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32) error { // primaryID 在这里是 batchID penIDParam := ctx.Param("penID") parsedPenID, err := strconv.ParseUint(penIDParam, 10, 32) if err != nil { return err // 返回错误,因为 penID 格式无效 } - return c.service.RemoveEmptyPenFromBatch(reqCtx, primaryID, uint(parsedPenID)) + return c.service.RemoveEmptyPenFromBatch(reqCtx, primaryID, uint32(parsedPenID)) }, "移除成功", - func(ctx echo.Context) (uint, error) { // 自定义ID提取器,从 ":batchID" 路径参数提取 + func(ctx echo.Context) (uint32, error) { // 自定义ID提取器,从 ":batchID" 路径参数提取 idParam := ctx.Param("batchID") parsedID, err := strconv.ParseUint(idParam, 10, 32) if err != nil { return 0, err } - return uint(parsedID), nil + return uint32(parsedID), nil }, ) } @@ -270,7 +270,7 @@ func (c *PigBatchController) MovePigsIntoPen(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.MovePigsIntoPenRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.MovePigsIntoPenRequest) error { return c.service.MovePigsIntoPen(reqCtx, primaryID, req.ToPenID, req.Quantity, operatorID, req.Remarks) }, "移入成功", diff --git a/internal/app/controller/management/pig_batch_health_controller.go b/internal/app/controller/management/pig_batch_health_controller.go index cd82e8b..905af89 100644 --- a/internal/app/controller/management/pig_batch_health_controller.go +++ b/internal/app/controller/management/pig_batch_health_controller.go @@ -26,7 +26,7 @@ func (c *PigBatchController) RecordSickPigs(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.RecordSickPigsRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.RecordSickPigsRequest) error { return c.service.RecordSickPigs(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.TreatmentLocation, req.HappenedAt, req.Remarks) }, "记录成功", @@ -53,7 +53,7 @@ func (c *PigBatchController) RecordSickPigRecovery(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.RecordSickPigRecoveryRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.RecordSickPigRecoveryRequest) error { return c.service.RecordSickPigRecovery(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.TreatmentLocation, req.HappenedAt, req.Remarks) }, "记录成功", @@ -80,7 +80,7 @@ func (c *PigBatchController) RecordSickPigDeath(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.RecordSickPigDeathRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.RecordSickPigDeathRequest) error { return c.service.RecordSickPigDeath(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.TreatmentLocation, req.HappenedAt, req.Remarks) }, "记录成功", @@ -107,7 +107,7 @@ func (c *PigBatchController) RecordSickPigCull(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.RecordSickPigCullRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.RecordSickPigCullRequest) error { return c.service.RecordSickPigCull(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.TreatmentLocation, req.HappenedAt, req.Remarks) }, "记录成功", @@ -134,7 +134,7 @@ func (c *PigBatchController) RecordDeath(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.RecordDeathRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.RecordDeathRequest) error { return c.service.RecordDeath(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.HappenedAt, req.Remarks) }, "记录成功", @@ -161,7 +161,7 @@ func (c *PigBatchController) RecordCull(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.RecordCullRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.RecordCullRequest) error { return c.service.RecordCull(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.HappenedAt, req.Remarks) }, "记录成功", diff --git a/internal/app/controller/management/pig_batch_trade_controller.go b/internal/app/controller/management/pig_batch_trade_controller.go index 6380c40..e33602d 100644 --- a/internal/app/controller/management/pig_batch_trade_controller.go +++ b/internal/app/controller/management/pig_batch_trade_controller.go @@ -25,7 +25,7 @@ func (c *PigBatchController) SellPigs(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.SellPigsRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.SellPigsRequest) error { return c.service.SellPigs(reqCtx, primaryID, req.PenID, req.Quantity, req.UnitPrice, req.TotalPrice, req.TraderName, req.TradeDate, req.Remarks, operatorID) }, "卖猪成功", @@ -51,7 +51,7 @@ func (c *PigBatchController) BuyPigs(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.BuyPigsRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.BuyPigsRequest) error { return c.service.BuyPigs(reqCtx, primaryID, req.PenID, req.Quantity, req.UnitPrice, req.TotalPrice, req.TraderName, req.TradeDate, req.Remarks, operatorID) }, "买猪成功", diff --git a/internal/app/controller/management/pig_batch_transfer_controller.go b/internal/app/controller/management/pig_batch_transfer_controller.go index 1d8c7b4..0bb478a 100644 --- a/internal/app/controller/management/pig_batch_transfer_controller.go +++ b/internal/app/controller/management/pig_batch_transfer_controller.go @@ -28,18 +28,18 @@ func (c *PigBatchController) TransferPigsAcrossBatches(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.TransferPigsAcrossBatchesRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.TransferPigsAcrossBatchesRequest) error { // primaryID 在这里是 sourceBatchID return c.service.TransferPigsAcrossBatches(reqCtx, primaryID, req.DestBatchID, req.FromPenID, req.ToPenID, req.Quantity, operatorID, req.Remarks) }, "调栏成功", - func(ctx echo.Context) (uint, error) { // 自定义ID提取器,从 ":sourceBatchID" 路径参数提取 + func(ctx echo.Context) (uint32, error) { // 自定义ID提取器,从 ":sourceBatchID" 路径参数提取 idParam := ctx.Param("sourceBatchID") parsedID, err := strconv.ParseUint(idParam, 10, 32) if err != nil { return 0, err } - return uint(parsedID), nil + return uint32(parsedID), nil }, ) } @@ -63,7 +63,7 @@ func (c *PigBatchController) TransferPigsWithinBatch(ctx echo.Context) error { return handleAPIRequest( reqCtx, c, ctx, action, &req, - func(ctx echo.Context, operatorID uint, primaryID uint, req *dto.TransferPigsWithinBatchRequest) error { + func(ctx echo.Context, operatorID uint32, primaryID uint32, req *dto.TransferPigsWithinBatchRequest) error { // primaryID 在这里是 batchID return c.service.TransferPigsWithinBatch(reqCtx, primaryID, req.FromPenID, req.ToPenID, req.Quantity, operatorID, req.Remarks) }, diff --git a/internal/app/controller/management/pig_farm_controller.go b/internal/app/controller/management/pig_farm_controller.go index 0a6733c..69dd7c7 100644 --- a/internal/app/controller/management/pig_farm_controller.go +++ b/internal/app/controller/management/pig_farm_controller.go @@ -76,7 +76,7 @@ func (c *PigFarmController) GetPigHouse(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", ctx.Param("id")) } - house, err := c.service.GetPigHouseByID(reqCtx, uint(id)) + house, err := c.service.GetPigHouseByID(reqCtx, uint32(id)) if err != nil { if errors.Is(err, service.ErrHouseNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪舍不存在", action, "猪舍不存在", id) @@ -132,7 +132,7 @@ func (c *PigFarmController) UpdatePigHouse(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) } - house, err := c.service.UpdatePigHouse(reqCtx, uint(id), req.Name, req.Description) + house, err := c.service.UpdatePigHouse(reqCtx, uint32(id), req.Name, req.Description) if err != nil { if errors.Is(err, service.ErrHouseNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪舍不存在", action, "猪舍不存在", id) @@ -161,7 +161,7 @@ func (c *PigFarmController) DeletePigHouse(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", ctx.Param("id")) } - if err := c.service.DeletePigHouse(reqCtx, uint(id)); err != nil { + if err := c.service.DeletePigHouse(reqCtx, uint32(id)); err != nil { if errors.Is(err, service.ErrHouseNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪舍不存在", action, "猪舍不存在", id) } @@ -226,7 +226,7 @@ func (c *PigFarmController) GetPen(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", ctx.Param("id")) } - pen, err := c.service.GetPenByID(reqCtx, uint(id)) + pen, err := c.service.GetPenByID(reqCtx, uint32(id)) if err != nil { if errors.Is(err, service.ErrPenNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪栏不存在", action, "猪栏不存在", id) @@ -282,7 +282,7 @@ func (c *PigFarmController) UpdatePen(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) } - pen, err := c.service.UpdatePen(reqCtx, uint(id), req.PenNumber, req.HouseID, req.Capacity, req.Status) + pen, err := c.service.UpdatePen(reqCtx, uint32(id), req.PenNumber, req.HouseID, req.Capacity, req.Status) if err != nil { if errors.Is(err, service.ErrPenNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪栏不存在", action, "猪栏不存在", id) @@ -312,7 +312,7 @@ func (c *PigFarmController) DeletePen(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", ctx.Param("id")) } - if err := c.service.DeletePen(reqCtx, uint(id)); err != nil { + if err := c.service.DeletePen(reqCtx, uint32(id)); err != nil { if errors.Is(err, service.ErrPenNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪栏不存在", action, "猪栏不存在", id) } @@ -351,7 +351,7 @@ func (c *PigFarmController) UpdatePenStatus(ctx echo.Context) error { return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) } - pen, err := c.service.UpdatePenStatus(reqCtx, uint(id), req.Status) + pen, err := c.service.UpdatePenStatus(reqCtx, uint32(id), req.Status) if err != nil { if errors.Is(err, service.ErrPenNotFound) { return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), action, err.Error(), id) diff --git a/internal/app/controller/plan/plan_controller.go b/internal/app/controller/plan/plan_controller.go index 35fa6b5..dfce780 100644 --- a/internal/app/controller/plan/plan_controller.go +++ b/internal/app/controller/plan/plan_controller.go @@ -81,14 +81,14 @@ func (c *Controller) GetPlan(ctx echo.Context) error { const actionType = "获取计划详情" // 1. 从 URL 路径中获取 ID idStr := ctx.Param("id") - id, err := strconv.ParseUint(idStr, 10, 32) + id, err := strconv.ParseUint(idStr, 10, 64) if err != nil { logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr) } // 调用服务层获取计划详情 - resp, err := c.planService.GetPlanByID(reqCtx, uint(id)) + resp, err := c.planService.GetPlanByID(reqCtx, uint32(id)) if err != nil { logger.Errorf("%s: 服务层获取计划详情失败: %v, ID: %d", actionType, err, id) if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound @@ -147,7 +147,7 @@ func (c *Controller) UpdatePlan(ctx echo.Context) error { const actionType = "更新计划" // 1. 从 URL 路径中获取 ID idStr := ctx.Param("id") - id, err := strconv.ParseUint(idStr, 10, 32) + id, err := strconv.ParseUint(idStr, 10, 64) if err != nil { logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr) @@ -161,7 +161,7 @@ func (c *Controller) UpdatePlan(ctx echo.Context) error { } // 调用服务层更新计划 - resp, err := c.planService.UpdatePlan(reqCtx, uint(id), &req) + resp, err := c.planService.UpdatePlan(reqCtx, uint32(id), &req) if err != nil { logger.Errorf("%s: 服务层更新计划失败: %v, ID: %d", actionType, err, id) if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound @@ -191,14 +191,14 @@ func (c *Controller) DeletePlan(ctx echo.Context) error { const actionType = "删除计划" // 1. 从 URL 路径中获取 ID idStr := ctx.Param("id") - id, err := strconv.ParseUint(idStr, 10, 32) + id, err := strconv.ParseUint(idStr, 10, 64) if err != nil { logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr) } // 调用服务层删除计划 - err = c.planService.DeletePlan(reqCtx, uint(id)) + err = c.planService.DeletePlan(reqCtx, uint32(id)) if err != nil { logger.Errorf("%s: 服务层删除计划失败: %v, ID: %d", actionType, err, id) if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound @@ -228,14 +228,14 @@ func (c *Controller) StartPlan(ctx echo.Context) error { const actionType = "启动计划" // 1. 从 URL 路径中获取 ID idStr := ctx.Param("id") - id, err := strconv.ParseUint(idStr, 10, 32) + id, err := strconv.ParseUint(idStr, 10, 64) if err != nil { logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr) } // 调用服务层启动计划 - err = c.planService.StartPlan(reqCtx, uint(id)) + err = c.planService.StartPlan(reqCtx, uint32(id)) if err != nil { logger.Errorf("%s: 服务层启动计划失败: %v, ID: %d", actionType, err, id) if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound @@ -267,14 +267,14 @@ func (c *Controller) StopPlan(ctx echo.Context) error { const actionType = "停止计划" // 1. 从 URL 路径中获取 ID idStr := ctx.Param("id") - id, err := strconv.ParseUint(idStr, 10, 32) + id, err := strconv.ParseUint(idStr, 10, 64) if err != nil { logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr) } // 调用服务层停止计划 - err = c.planService.StopPlan(reqCtx, uint(id)) + err = c.planService.StopPlan(reqCtx, uint32(id)) if err != nil { logger.Errorf("%s: 服务层停止计划失败: %v, ID: %d", actionType, err, id) if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound diff --git a/internal/app/controller/user/user_controller.go b/internal/app/controller/user/user_controller.go index 1aa38c3..dada6ad 100644 --- a/internal/app/controller/user/user_controller.go +++ b/internal/app/controller/user/user_controller.go @@ -101,7 +101,7 @@ func (c *Controller) SendTestNotification(ctx echo.Context) error { const actionType = "发送测试通知" // 1. 从 URL 中获取用户 ID - userID, err := strconv.ParseUint(ctx.Param("id"), 10, 32) + userID, err := strconv.ParseUint(ctx.Param("id"), 10, 64) if err != nil { logger.Errorf("%s: 无效的用户ID格式: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的用户ID格式", actionType, "无效的用户ID格式", ctx.Param("id")) @@ -115,7 +115,7 @@ func (c *Controller) SendTestNotification(ctx echo.Context) error { } // 3. 调用服务层 - err = c.userService.SendTestNotification(reqCtx, uint(userID), &req) + err = c.userService.SendTestNotification(reqCtx, uint32(userID), &req) if err != nil { logger.Errorf("%s: 服务层调用失败: %v", actionType, err) return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "发送测试消息失败: "+err.Error(), actionType, "服务层调用失败", map[string]interface{}{"userID": userID, "type": req.Type}) diff --git a/internal/app/dto/alarm_dto.go b/internal/app/dto/alarm_dto.go index 2fc48ea..b83527a 100644 --- a/internal/app/dto/alarm_dto.go +++ b/internal/app/dto/alarm_dto.go @@ -8,7 +8,7 @@ import ( // SnoozeAlarmRequest 定义了忽略告警的请求体 type SnoozeAlarmRequest struct { - DurationMinutes uint `json:"duration_minutes" validate:"required,min=1"` // 忽略时长,单位分钟 + DurationMinutes uint32 `json:"duration_minutes" validate:"required,min=1"` // 忽略时长,单位分钟 } // ListActiveAlarmRequest 定义了获取活跃告警列表的请求参数 @@ -16,7 +16,7 @@ type ListActiveAlarmRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` SourceType *models.AlarmSourceType `json:"source_type" query:"source_type"` // 按告警来源类型过滤 - SourceID *uint `json:"source_id" query:"source_id"` // 按告警来源ID过滤 + SourceID *uint32 `json:"source_id" query:"source_id"` // 按告警来源ID过滤 Level *models.SeverityLevel `json:"level" query:"level"` // 按告警严重性等级过滤 IsIgnored *bool `json:"is_ignored" query:"is_ignored"` // 按是否被忽略过滤 TriggerTime *time.Time `json:"trigger_time" query:"trigger_time"` // 告警触发时间范围 - 开始时间 @@ -26,11 +26,11 @@ type ListActiveAlarmRequest struct { // ActiveAlarmDTO 是用于API响应的活跃告警结构 type ActiveAlarmDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` SourceType models.AlarmSourceType `json:"source_type"` - SourceID uint `json:"source_id"` + SourceID uint32 `json:"source_id"` AlarmCode models.AlarmCode `json:"alarm_code"` AlarmSummary string `json:"alarm_summary"` Level models.SeverityLevel `json:"level"` @@ -52,7 +52,7 @@ type ListHistoricalAlarmRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` SourceType *models.AlarmSourceType `json:"source_type" query:"source_type"` // 按告警来源类型过滤 - SourceID *uint `json:"source_id" query:"source_id"` // 按告警来源ID过滤 + SourceID *uint32 `json:"source_id" query:"source_id"` // 按告警来源ID过滤 Level *models.SeverityLevel `json:"level" query:"level"` // 按告警严重性等级过滤 TriggerTimeStart *time.Time `json:"trigger_time_start" query:"trigger_time_start"` // 告警触发时间范围 - 开始时间 TriggerTimeEnd *time.Time `json:"trigger_time_end" query:"trigger_time_end"` // 告警触发时间范围 - 结束时间 @@ -63,9 +63,9 @@ type ListHistoricalAlarmRequest struct { // HistoricalAlarmDTO 是用于API响应的历史告警结构 type HistoricalAlarmDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` SourceType models.AlarmSourceType `json:"source_type"` - SourceID uint `json:"source_id"` + SourceID uint32 `json:"source_id"` AlarmCode models.AlarmCode `json:"alarm_code"` AlarmSummary string `json:"alarm_summary"` Level models.SeverityLevel `json:"level"` @@ -73,7 +73,7 @@ type HistoricalAlarmDTO struct { TriggerTime time.Time `json:"trigger_time"` ResolveTime time.Time `json:"resolve_time"` ResolveMethod string `json:"resolve_method"` - ResolvedBy *uint `json:"resolved_by"` + ResolvedBy *uint32 `json:"resolved_by"` } // ListHistoricalAlarmResponse 是获取历史告警列表的响应结构 @@ -84,7 +84,7 @@ type ListHistoricalAlarmResponse struct { // CreateDeviceThresholdAlarmDTO 创建设备阈值告警的请求DTO type CreateDeviceThresholdAlarmDTO struct { - DeviceID uint `json:"device_id" binding:"required"` // 设备ID + DeviceID uint32 `json:"device_id" binding:"required"` // 设备ID SensorType models.SensorType `json:"sensor_type" binding:"required"` // 传感器类型 Thresholds float32 `json:"thresholds" binding:"required"` // 阈值 Operator models.Operator `json:"operator" binding:"required"` // 操作符 (使用string类型,与前端交互更通用) @@ -100,7 +100,7 @@ type UpdateDeviceThresholdAlarmDTO struct { // CreateAreaThresholdAlarmDTO 创建区域阈值告警的请求DTO type CreateAreaThresholdAlarmDTO struct { - AreaControllerID uint `json:"area_controller_id" binding:"required"` // 区域主控ID + AreaControllerID uint32 `json:"area_controller_id" binding:"required"` // 区域主控ID SensorType models.SensorType `json:"sensor_type" binding:"required"` // 传感器类型 Thresholds float32 `json:"thresholds" binding:"required"` // 阈值 Operator models.Operator `json:"operator" binding:"required"` // 操作符 @@ -122,7 +122,7 @@ type DeleteDeviceThresholdAlarmDTO struct { // AreaThresholdAlarmDTO 用于表示一个区域阈值告警任务的详细信息 type AreaThresholdAlarmDTO struct { ID int `json:"id"` - AreaControllerID uint `json:"area_controller_id"` + AreaControllerID uint32 `json:"area_controller_id"` SensorType models.SensorType `json:"sensor_type"` Thresholds float32 `json:"thresholds"` Operator models.Operator `json:"operator"` @@ -132,7 +132,7 @@ type AreaThresholdAlarmDTO struct { // DeviceThresholdAlarmDTO 用于表示一个设备阈值告警任务的详细信息 type DeviceThresholdAlarmDTO struct { ID int `json:"id"` - DeviceID uint `json:"device_id"` + DeviceID uint32 `json:"device_id"` SensorType models.SensorType `json:"sensor_type"` Thresholds float32 `json:"thresholds"` Operator models.Operator `json:"operator"` diff --git a/internal/app/dto/device_dto.go b/internal/app/dto/device_dto.go index 4dd4f20..5ac018b 100644 --- a/internal/app/dto/device_dto.go +++ b/internal/app/dto/device_dto.go @@ -5,8 +5,8 @@ import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" // CreateDeviceRequest 定义了创建设备时需要传入的参数 type CreateDeviceRequest struct { Name string `json:"name" validate:"required"` - DeviceTemplateID uint `json:"device_template_id" validate:"required"` - AreaControllerID uint `json:"area_controller_id" validate:"required"` + DeviceTemplateID uint32 `json:"device_template_id" validate:"required"` + AreaControllerID uint32 `json:"area_controller_id" validate:"required"` Location string `json:"location,omitempty" validate:"omitempty"` Properties map[string]interface{} `json:"properties,omitempty" validate:"omitempty"` } @@ -14,8 +14,8 @@ type CreateDeviceRequest struct { // UpdateDeviceRequest 定义了更新设备时需要传入的参数 type UpdateDeviceRequest struct { Name string `json:"name" validate:"required"` - DeviceTemplateID uint `json:"device_template_id" validate:"required"` - AreaControllerID uint `json:"area_controller_id" validate:"required"` + DeviceTemplateID uint32 `json:"device_template_id" validate:"required"` + AreaControllerID uint32 `json:"area_controller_id" validate:"required"` Location string `json:"location,omitempty" validate:"omitempty"` Properties map[string]interface{} `json:"properties,omitempty" validate:"omitempty"` } @@ -64,11 +64,11 @@ type UpdateDeviceTemplateRequest struct { // DeviceResponse 定义了返回给客户端的单个设备信息的结构 type DeviceResponse struct { - ID uint `json:"id"` + ID uint32 `json:"id"` Name string `json:"name"` - DeviceTemplateID uint `json:"device_template_id"` + DeviceTemplateID uint32 `json:"device_template_id"` DeviceTemplateName string `json:"device_template_name"` - AreaControllerID uint `json:"area_controller_id"` + AreaControllerID uint32 `json:"area_controller_id"` AreaControllerName string `json:"area_controller_name"` Location string `json:"location"` Properties map[string]interface{} `json:"properties"` @@ -78,7 +78,7 @@ type DeviceResponse struct { // AreaControllerResponse 定义了返回给客户端的单个区域主控信息的结构 type AreaControllerResponse struct { - ID uint `json:"id"` + ID uint32 `json:"id"` Name string `json:"name"` NetworkID string `json:"network_id"` Location string `json:"location"` @@ -90,7 +90,7 @@ type AreaControllerResponse struct { // DeviceTemplateResponse 定义了返回给客户端的单个设备模板信息的结构 type DeviceTemplateResponse struct { - ID uint `json:"id"` + ID uint32 `json:"id"` Name string `json:"name"` Manufacturer string `json:"manufacturer"` Description string `json:"description"` diff --git a/internal/app/dto/monitor_converter.go b/internal/app/dto/monitor_converter.go index 84c7ac2..657a4d4 100644 --- a/internal/app/dto/monitor_converter.go +++ b/internal/app/dto/monitor_converter.go @@ -54,7 +54,7 @@ func NewListDeviceCommandLogResponse(data []models.DeviceCommandLog, total int64 // NewListPlanExecutionLogResponse 从模型数据创建列表响应 DTO func NewListPlanExecutionLogResponse(planLogs []models.PlanExecutionLog, plans []models.Plan, total int64, page, pageSize int) *ListPlanExecutionLogResponse { - planId2Name := make(map[uint]string) + planId2Name := make(map[uint32]string) for _, plan := range plans { planId2Name[plan.ID] = string(plan.Name) } @@ -95,7 +95,7 @@ func NewListTaskExecutionLogResponse(data []models.TaskExecutionLog, total int64 PlanExecutionLogID: item.PlanExecutionLogID, TaskID: item.TaskID, Task: TaskDTO{ - ID: uint(item.Task.ID), + ID: uint32(item.Task.ID), Name: item.Task.Name, Description: item.Task.Description, }, @@ -373,7 +373,7 @@ func NewListWeighingRecordResponse(data []models.WeighingRecord, total int64, pa func NewListPigTransferLogResponse(data []models.PigTransferLog, total int64, page, pageSize int) *ListPigTransferLogResponse { dtos := make([]PigTransferLogDTO, len(data)) for i, item := range data { - // 注意:PigTransferLog 的 ID, CreatedAt, UpdatedAt 字段是 gorm.Model 嵌入的 + // 注意:PigTransferLog 的 ID, CreatedAt, UpdatedAt 字段是 Model 嵌入的 dtos[i] = PigTransferLogDTO{ ID: item.ID, CreatedAt: item.CreatedAt, diff --git a/internal/app/dto/monitor_dto.go b/internal/app/dto/monitor_dto.go index 406883e..6fcf4b6 100644 --- a/internal/app/dto/monitor_dto.go +++ b/internal/app/dto/monitor_dto.go @@ -13,7 +13,7 @@ import ( type ListSensorDataRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - DeviceID *uint `json:"device_id" query:"device_id"` + DeviceID *uint32 `json:"device_id" query:"device_id"` SensorType *string `json:"sensor_type" query:"sensor_type"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` @@ -23,8 +23,8 @@ type ListSensorDataRequest struct { // SensorDataDTO 是用于API响应的传感器数据结构 type SensorDataDTO struct { Time time.Time `json:"time"` - DeviceID uint `json:"device_id"` - AreaControllerID uint `json:"area_controller_id"` + DeviceID uint32 `json:"device_id"` + AreaControllerID uint32 `json:"area_controller_id"` SensorType models.SensorType `json:"sensor_type"` Data json.RawMessage `json:"data"` } @@ -41,7 +41,7 @@ type ListSensorDataResponse struct { type ListDeviceCommandLogRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - DeviceID *uint `json:"device_id" query:"device_id"` + DeviceID *uint32 `json:"device_id" query:"device_id"` ReceivedSuccess *bool `json:"received_success" query:"received_success"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` @@ -51,7 +51,7 @@ type ListDeviceCommandLogRequest struct { // DeviceCommandLogDTO 是用于API响应的设备命令日志结构 type DeviceCommandLogDTO struct { MessageID string `json:"message_id"` - DeviceID uint `json:"device_id"` + DeviceID uint32 `json:"device_id"` SentAt time.Time `json:"sent_at"` AcknowledgedAt *time.Time `json:"acknowledged_at"` ReceivedSuccess bool `json:"received_success"` @@ -69,7 +69,7 @@ type ListDeviceCommandLogResponse struct { type ListPlanExecutionLogRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - PlanID *uint `json:"plan_id" query:"plan_id"` + PlanID *uint32 `json:"plan_id" query:"plan_id"` Status *string `json:"status" query:"status"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` @@ -78,10 +78,10 @@ type ListPlanExecutionLogRequest struct { // PlanExecutionLogDTO 是用于API响应的计划执行日志结构 type PlanExecutionLogDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` - PlanID uint `json:"plan_id"` + PlanID uint32 `json:"plan_id"` PlanName string `json:"plan_name"` Status models.ExecutionStatus `json:"status"` StartedAt time.Time `json:"started_at"` @@ -101,7 +101,7 @@ type ListPlanExecutionLogResponse struct { type ListTaskExecutionLogRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - PlanExecutionLogID *uint `json:"plan_execution_log_id" query:"plan_execution_log_id"` + PlanExecutionLogID *uint32 `json:"plan_execution_log_id" query:"plan_execution_log_id"` TaskID *int `json:"task_id" query:"task_id"` Status *string `json:"status" query:"status"` StartTime *time.Time `json:"start_time" query:"start_time"` @@ -111,17 +111,17 @@ type ListTaskExecutionLogRequest struct { // TaskDTO 是用于API响应的简化版任务结构 type TaskDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` Name string `json:"name"` Description string `json:"description"` } // TaskExecutionLogDTO 是用于API响应的任务执行日志结构 type TaskExecutionLogDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` - PlanExecutionLogID uint `json:"plan_execution_log_id"` + PlanExecutionLogID uint32 `json:"plan_execution_log_id"` TaskID int `json:"task_id"` Task TaskDTO `json:"task"` // 嵌套的任务信息 Status models.ExecutionStatus `json:"status"` @@ -142,7 +142,7 @@ type ListTaskExecutionLogResponse struct { type ListPendingCollectionRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - DeviceID *uint `json:"device_id" query:"device_id"` + DeviceID *uint32 `json:"device_id" query:"device_id"` Status *string `json:"status" query:"status"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` @@ -152,7 +152,7 @@ type ListPendingCollectionRequest struct { // PendingCollectionDTO 是用于API响应的待采集请求结构 type PendingCollectionDTO struct { CorrelationID string `json:"correlation_id"` - DeviceID uint `json:"device_id"` + DeviceID uint32 `json:"device_id"` CommandMetadata models.UintArray `json:"command_metadata"` Status models.PendingCollectionStatus `json:"status"` FulfilledAt *time.Time `json:"fulfilled_at"` @@ -171,7 +171,7 @@ type ListPendingCollectionResponse struct { type ListUserActionLogRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - UserID *uint `json:"user_id" query:"user_id"` + UserID *uint32 `json:"user_id" query:"user_id"` Username *string `json:"username" query:"username"` ActionType *string `json:"action_type" query:"action_type"` Status *string `json:"status" query:"status"` @@ -182,9 +182,9 @@ type ListUserActionLogRequest struct { // UserActionLogDTO 是用于API响应的用户操作日志结构 type UserActionLogDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` Time time.Time `json:"time"` - UserID uint `json:"user_id"` + UserID uint32 `json:"user_id"` Username string `json:"username"` SourceIP string `json:"source_ip"` ActionType string `json:"action_type"` @@ -208,7 +208,7 @@ type ListUserActionLogResponse struct { type ListRawMaterialPurchaseRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - RawMaterialID *uint `json:"raw_material_id" query:"raw_material_id"` + RawMaterialID *uint32 `json:"raw_material_id" query:"raw_material_id"` Supplier *string `json:"supplier" query:"supplier"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` @@ -217,14 +217,14 @@ type ListRawMaterialPurchaseRequest struct { // RawMaterialDTO 是用于API响应的简化版原料结构 type RawMaterialDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` Name string `json:"name"` } // RawMaterialPurchaseDTO 是用于API响应的原料采购结构 type RawMaterialPurchaseDTO struct { - ID uint `json:"id"` - RawMaterialID uint `json:"raw_material_id"` + ID uint32 `json:"id"` + RawMaterialID uint32 `json:"raw_material_id"` RawMaterial RawMaterialDTO `json:"raw_material"` Supplier string `json:"supplier"` Amount float32 `json:"amount"` @@ -246,9 +246,9 @@ type ListRawMaterialPurchaseResponse struct { type ListRawMaterialStockLogRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - RawMaterialID *uint `json:"raw_material_id" query:"raw_material_id"` + RawMaterialID *uint32 `json:"raw_material_id" query:"raw_material_id"` SourceType *string `json:"source_type" query:"source_type"` - SourceID *uint `json:"source_id" query:"source_id"` + SourceID *uint32 `json:"source_id" query:"source_id"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` OrderBy string `json:"order_by" query:"order_by"` @@ -256,11 +256,11 @@ type ListRawMaterialStockLogRequest struct { // RawMaterialStockLogDTO 是用于API响应的原料库存日志结构 type RawMaterialStockLogDTO struct { - ID uint `json:"id"` - RawMaterialID uint `json:"raw_material_id"` + ID uint32 `json:"id"` + RawMaterialID uint32 `json:"raw_material_id"` ChangeAmount float32 `json:"change_amount"` SourceType models.StockLogSourceType `json:"source_type"` - SourceID uint `json:"source_id"` + SourceID uint32 `json:"source_id"` HappenedAt time.Time `json:"happened_at"` Remarks string `json:"remarks"` } @@ -277,9 +277,9 @@ type ListRawMaterialStockLogResponse struct { type ListFeedUsageRecordRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - PenID *uint `json:"pen_id" query:"pen_id"` - FeedFormulaID *uint `json:"feed_formula_id" query:"feed_formula_id"` - OperatorID *uint `json:"operator_id" query:"operator_id"` + PenID *uint32 `json:"pen_id" query:"pen_id"` + FeedFormulaID *uint32 `json:"feed_formula_id" query:"feed_formula_id"` + OperatorID *uint32 `json:"operator_id" query:"operator_id"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` OrderBy string `json:"order_by" query:"order_by"` @@ -287,26 +287,26 @@ type ListFeedUsageRecordRequest struct { // PenDTO 是用于API响应的简化版猪栏结构 type PenDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` Name string `json:"name"` } // FeedFormulaDTO 是用于API响应的简化版饲料配方结构 type FeedFormulaDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` Name string `json:"name"` } // FeedUsageRecordDTO 是用于API响应的饲料使用记录结构 type FeedUsageRecordDTO struct { - ID uint `json:"id"` - PenID uint `json:"pen_id"` + ID uint32 `json:"id"` + PenID uint32 `json:"pen_id"` Pen PenDTO `json:"pen"` - FeedFormulaID uint `json:"feed_formula_id"` + FeedFormulaID uint32 `json:"feed_formula_id"` FeedFormula FeedFormulaDTO `json:"feed_formula"` Amount float32 `json:"amount"` RecordedAt time.Time `json:"recorded_at"` - OperatorID uint `json:"operator_id"` + OperatorID uint32 `json:"operator_id"` Remarks string `json:"remarks"` } @@ -322,10 +322,10 @@ type ListFeedUsageRecordResponse struct { type ListMedicationLogRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - PigBatchID *uint `json:"pig_batch_id" query:"pig_batch_id"` - MedicationID *uint `json:"medication_id" query:"medication_id"` + PigBatchID *uint32 `json:"pig_batch_id" query:"pig_batch_id"` + MedicationID *uint32 `json:"medication_id" query:"medication_id"` Reason *string `json:"reason" query:"reason"` - OperatorID *uint `json:"operator_id" query:"operator_id"` + OperatorID *uint32 `json:"operator_id" query:"operator_id"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` OrderBy string `json:"order_by" query:"order_by"` @@ -333,21 +333,21 @@ type ListMedicationLogRequest struct { // MedicationDTO 是用于API响应的简化版药品结构 type MedicationDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` Name string `json:"name"` } // MedicationLogDTO 是用于API响应的用药记录结构 type MedicationLogDTO struct { - ID uint `json:"id"` - PigBatchID uint `json:"pig_batch_id"` - MedicationID uint `json:"medication_id"` + ID uint32 `json:"id"` + PigBatchID uint32 `json:"pig_batch_id"` + MedicationID uint32 `json:"medication_id"` Medication MedicationDTO `json:"medication"` DosageUsed float32 `json:"dosage_used"` TargetCount int `json:"target_count"` Reason models.MedicationReasonType `json:"reason"` Description string `json:"description"` - OperatorID uint `json:"operator_id"` + OperatorID uint32 `json:"operator_id"` HappenedAt time.Time `json:"happened_at"` } @@ -363,9 +363,9 @@ type ListMedicationLogResponse struct { type ListPigBatchLogRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - PigBatchID *uint `json:"pig_batch_id" query:"pig_batch_id"` + PigBatchID *uint32 `json:"pig_batch_id" query:"pig_batch_id"` ChangeType *string `json:"change_type" query:"change_type"` - OperatorID *uint `json:"operator_id" query:"operator_id"` + OperatorID *uint32 `json:"operator_id" query:"operator_id"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` OrderBy string `json:"order_by" query:"order_by"` @@ -373,16 +373,16 @@ type ListPigBatchLogRequest struct { // PigBatchLogDTO 是用于API响应的猪批次日志结构 type PigBatchLogDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` - PigBatchID uint `json:"pig_batch_id"` + PigBatchID uint32 `json:"pig_batch_id"` ChangeType models.LogChangeType `json:"change_type"` ChangeCount int `json:"change_count"` Reason string `json:"reason"` BeforeCount int `json:"before_count"` AfterCount int `json:"after_count"` - OperatorID uint `json:"operator_id"` + OperatorID uint32 `json:"operator_id"` HappenedAt time.Time `json:"happened_at"` } @@ -398,7 +398,7 @@ type ListPigBatchLogResponse struct { type ListWeighingBatchRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - PigBatchID *uint `json:"pig_batch_id" query:"pig_batch_id"` + PigBatchID *uint32 `json:"pig_batch_id" query:"pig_batch_id"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` OrderBy string `json:"order_by" query:"order_by"` @@ -406,12 +406,12 @@ type ListWeighingBatchRequest struct { // WeighingBatchDTO 是用于API响应的批次称重记录结构 type WeighingBatchDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` WeighingTime time.Time `json:"weighing_time"` Description string `json:"description"` - PigBatchID uint `json:"pig_batch_id"` + PigBatchID uint32 `json:"pig_batch_id"` } // ListWeighingBatchResponse 是获取批次称重记录列表的响应结构 @@ -426,9 +426,9 @@ type ListWeighingBatchResponse struct { type ListWeighingRecordRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - WeighingBatchID *uint `json:"weighing_batch_id" query:"weighing_batch_id"` - PenID *uint `json:"pen_id" query:"pen_id"` - OperatorID *uint `json:"operator_id" query:"operator_id"` + WeighingBatchID *uint32 `json:"weighing_batch_id" query:"weighing_batch_id"` + PenID *uint32 `json:"pen_id" query:"pen_id"` + OperatorID *uint32 `json:"operator_id" query:"operator_id"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` OrderBy string `json:"order_by" query:"order_by"` @@ -436,13 +436,13 @@ type ListWeighingRecordRequest struct { // WeighingRecordDTO 是用于API响应的单次称重记录结构 type WeighingRecordDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Weight float32 `json:"weight"` - WeighingBatchID uint `json:"weighing_batch_id"` - PenID uint `json:"pen_id"` - OperatorID uint `json:"operator_id"` + WeighingBatchID uint32 `json:"weighing_batch_id"` + PenID uint32 `json:"pen_id"` + OperatorID uint32 `json:"operator_id"` Remark string `json:"remark"` WeighingTime time.Time `json:"weighing_time"` } @@ -459,10 +459,10 @@ type ListWeighingRecordResponse struct { type ListPigTransferLogRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - PigBatchID *uint `json:"pig_batch_id" query:"pig_batch_id"` - PenID *uint `json:"pen_id" query:"pen_id"` + PigBatchID *uint32 `json:"pig_batch_id" query:"pig_batch_id"` + PenID *uint32 `json:"pen_id" query:"pen_id"` TransferType *string `json:"transfer_type" query:"transfer_type"` - OperatorID *uint `json:"operator_id" query:"operator_id"` + OperatorID *uint32 `json:"operator_id" query:"operator_id"` CorrelationID *string `json:"correlation_id" query:"correlation_id"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` @@ -471,16 +471,16 @@ type ListPigTransferLogRequest struct { // PigTransferLogDTO 是用于API响应的猪只迁移日志结构 type PigTransferLogDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` TransferTime time.Time `json:"transfer_time"` - PigBatchID uint `json:"pig_batch_id"` - PenID uint `json:"pen_id"` + PigBatchID uint32 `json:"pig_batch_id"` + PenID uint32 `json:"pen_id"` Quantity int `json:"quantity"` Type models.PigTransferType `json:"type"` CorrelationID string `json:"correlation_id"` - OperatorID uint `json:"operator_id"` + OperatorID uint32 `json:"operator_id"` Remarks string `json:"remarks"` } @@ -496,11 +496,11 @@ type ListPigTransferLogResponse struct { type ListPigSickLogRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - PigBatchID *uint `json:"pig_batch_id" query:"pig_batch_id"` - PenID *uint `json:"pen_id" query:"pen_id"` + PigBatchID *uint32 `json:"pig_batch_id" query:"pig_batch_id"` + PenID *uint32 `json:"pen_id" query:"pen_id"` Reason *string `json:"reason" query:"reason"` TreatmentLocation *string `json:"treatment_location" query:"treatment_location"` - OperatorID *uint `json:"operator_id" query:"operator_id"` + OperatorID *uint32 `json:"operator_id" query:"operator_id"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` OrderBy string `json:"order_by" query:"order_by"` @@ -508,18 +508,18 @@ type ListPigSickLogRequest struct { // PigSickLogDTO 是用于API响应的病猪日志结构 type PigSickLogDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` - PigBatchID uint `json:"pig_batch_id"` - PenID uint `json:"pen_id"` + PigBatchID uint32 `json:"pig_batch_id"` + PenID uint32 `json:"pen_id"` ChangeCount int `json:"change_count"` Reason models.PigBatchSickPigReasonType `json:"reason"` BeforeCount int `json:"before_count"` AfterCount int `json:"after_count"` Remarks string `json:"remarks"` TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location"` - OperatorID uint `json:"operator_id"` + OperatorID uint32 `json:"operator_id"` HappenedAt time.Time `json:"happened_at"` } @@ -535,9 +535,9 @@ type ListPigSickLogResponse struct { type ListPigPurchaseRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - PigBatchID *uint `json:"pig_batch_id" query:"pig_batch_id"` + PigBatchID *uint32 `json:"pig_batch_id" query:"pig_batch_id"` Supplier *string `json:"supplier" query:"supplier"` - OperatorID *uint `json:"operator_id" query:"operator_id"` + OperatorID *uint32 `json:"operator_id" query:"operator_id"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` OrderBy string `json:"order_by" query:"order_by"` @@ -545,17 +545,17 @@ type ListPigPurchaseRequest struct { // PigPurchaseDTO 是用于API响应的猪只采购记录结构 type PigPurchaseDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` - PigBatchID uint `json:"pig_batch_id"` + PigBatchID uint32 `json:"pig_batch_id"` PurchaseDate time.Time `json:"purchase_date"` Supplier string `json:"supplier"` Quantity int `json:"quantity"` UnitPrice float32 `json:"unit_price"` TotalPrice float32 `json:"total_price"` Remarks string `json:"remarks"` - OperatorID uint `json:"operator_id"` + OperatorID uint32 `json:"operator_id"` } // ListPigPurchaseResponse 是获取猪只采购记录列表的响应结构 @@ -570,9 +570,9 @@ type ListPigPurchaseResponse struct { type ListPigSaleRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - PigBatchID *uint `json:"pig_batch_id" query:"pig_batch_id"` + PigBatchID *uint32 `json:"pig_batch_id" query:"pig_batch_id"` Buyer *string `json:"buyer" query:"buyer"` - OperatorID *uint `json:"operator_id" query:"operator_id"` + OperatorID *uint32 `json:"operator_id" query:"operator_id"` StartTime *time.Time `json:"start_time" query:"start_time"` EndTime *time.Time `json:"end_time" query:"end_time"` OrderBy string `json:"order_by" query:"order_by"` @@ -580,17 +580,17 @@ type ListPigSaleRequest struct { // PigSaleDTO 是用于API响应的猪只销售记录结构 type PigSaleDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` - PigBatchID uint `json:"pig_batch_id"` + PigBatchID uint32 `json:"pig_batch_id"` SaleDate time.Time `json:"sale_date"` Buyer string `json:"buyer"` Quantity int `json:"quantity"` UnitPrice float32 `json:"unit_price"` TotalPrice float32 `json:"total_price"` Remarks string `json:"remarks"` - OperatorID uint `json:"operator_id"` + OperatorID uint32 `json:"operator_id"` } // ListPigSaleResponse 是获取猪只销售记录列表的响应结构 diff --git a/internal/app/dto/notification_dto.go b/internal/app/dto/notification_dto.go index f0d32ac..15d2b48 100644 --- a/internal/app/dto/notification_dto.go +++ b/internal/app/dto/notification_dto.go @@ -18,7 +18,7 @@ type SendTestNotificationRequest struct { type ListNotificationRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` - UserID *uint `json:"user_id" query:"user_id"` + UserID *uint32 `json:"user_id" query:"user_id"` NotifierType *models.NotifierType `json:"notifier_type" query:"notifier_type"` Status *models.NotificationStatus `json:"status" query:"status"` Level *zapcore.Level `json:"level" query:"level"` @@ -29,11 +29,11 @@ type ListNotificationRequest struct { // NotificationDTO 是用于API响应的通知结构 type NotificationDTO struct { - ID uint `json:"id"` + ID uint32 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` NotifierType models.NotifierType `json:"notifier_type"` - UserID uint `json:"user_id"` + UserID uint32 `json:"user_id"` Title string `json:"title"` Message string `json:"message"` Level models.SeverityLevel `json:"level"` diff --git a/internal/app/dto/pig_batch_dto.go b/internal/app/dto/pig_batch_dto.go index 0b23c92..2f3fbab 100644 --- a/internal/app/dto/pig_batch_dto.go +++ b/internal/app/dto/pig_batch_dto.go @@ -32,7 +32,7 @@ type PigBatchQueryDTO struct { // PigBatchResponseDTO 定义了猪批次信息的响应结构 type PigBatchResponseDTO struct { - ID uint `json:"id"` // 批次ID + ID uint32 `json:"id"` // 批次ID BatchNumber string `json:"batch_number"` // 批次编号 OriginType models.PigBatchOriginType `json:"origin_type"` // 批次来源 StartDate time.Time `json:"start_date"` // 批次开始日期 @@ -48,31 +48,31 @@ type PigBatchResponseDTO struct { // AssignEmptyPensToBatchRequest 用于为猪批次分配空栏的请求体 type AssignEmptyPensToBatchRequest struct { - PenIDs []uint `json:"pen_ids" validate:"required,min=1,dive" example:"1,2,3"` // 待分配的猪栏ID列表 + PenIDs []uint32 `json:"pen_ids" validate:"required,min=1,dive" example:"1,2,3"` // 待分配的猪栏ID列表 } // ReclassifyPenToNewBatchRequest 用于将猪栏划拨到新批次的请求体 type ReclassifyPenToNewBatchRequest struct { - ToBatchID uint `json:"to_batch_id" validate:"required"` // 目标猪批次ID - PenID uint `json:"pen_id" validate:"required"` // 待划拨的猪栏ID + ToBatchID uint32 `json:"to_batch_id" validate:"required"` // 目标猪批次ID + PenID uint32 `json:"pen_id" validate:"required"` // 待划拨的猪栏ID Remarks string `json:"remarks"` // 备注 } // RemoveEmptyPenFromBatchRequest 用于从猪批次移除空栏的请求体 type RemoveEmptyPenFromBatchRequest struct { - PenID uint `json:"pen_id" validate:"required"` // 待移除的猪栏ID + PenID uint32 `json:"pen_id" validate:"required"` // 待移除的猪栏ID } // MovePigsIntoPenRequest 用于将猪只从“虚拟库存”移入指定猪栏的请求体 type MovePigsIntoPenRequest struct { - ToPenID uint `json:"to_pen_id" validate:"required"` // 目标猪栏ID + ToPenID uint32 `json:"to_pen_id" validate:"required"` // 目标猪栏ID Quantity int `json:"quantity" validate:"required,min=1"` // 移入猪只数量 Remarks string `json:"remarks"` // 备注 } // SellPigsRequest 用于处理卖猪的请求体 type SellPigsRequest struct { - PenID uint `json:"pen_id" validate:"required"` // 猪栏ID + PenID uint32 `json:"pen_id" validate:"required"` // 猪栏ID Quantity int `json:"quantity" validate:"required,min=1"` // 卖出猪只数量 UnitPrice float32 `json:"unit_price" validate:"required,min=0"` // 单价 TotalPrice float32 `json:"total_price" validate:"required,min=0"` // 总价 @@ -83,7 +83,7 @@ type SellPigsRequest struct { // BuyPigsRequest 用于处理买猪的请求体 type BuyPigsRequest struct { - PenID uint `json:"pen_id" validate:"required"` // 猪栏ID + PenID uint32 `json:"pen_id" validate:"required"` // 猪栏ID Quantity int `json:"quantity" validate:"required,min=1"` // 买入猪只数量 UnitPrice float32 `json:"unit_price" validate:"required,min=0"` // 单价 TotalPrice float32 `json:"total_price" validate:"required,min=0"` // 总价 @@ -94,24 +94,24 @@ type BuyPigsRequest struct { // TransferPigsAcrossBatchesRequest 用于跨猪群调栏的请求体 type TransferPigsAcrossBatchesRequest struct { - DestBatchID uint `json:"dest_batch_id" validate:"required"` // 目标猪批次ID - FromPenID uint `json:"from_pen_id" validate:"required"` // 源猪栏ID - ToPenID uint `json:"to_pen_id" validate:"required"` // 目标猪栏ID - Quantity uint `json:"quantity" validate:"required,min=1"` // 调栏猪只数量 + DestBatchID uint32 `json:"dest_batch_id" validate:"required"` // 目标猪批次ID + FromPenID uint32 `json:"from_pen_id" validate:"required"` // 源猪栏ID + ToPenID uint32 `json:"to_pen_id" validate:"required"` // 目标猪栏ID + Quantity uint32 `json:"quantity" validate:"required,min=1"` // 调栏猪只数量 Remarks string `json:"remarks"` // 备注 } // TransferPigsWithinBatchRequest 用于群内调栏的请求体 type TransferPigsWithinBatchRequest struct { - FromPenID uint `json:"from_pen_id" validate:"required"` // 源猪栏ID - ToPenID uint `json:"to_pen_id" validate:"required"` // 目标猪栏ID - Quantity uint `json:"quantity" validate:"required,min=1"` // 调栏猪只数量 + FromPenID uint32 `json:"from_pen_id" validate:"required"` // 源猪栏ID + ToPenID uint32 `json:"to_pen_id" validate:"required"` // 目标猪栏ID + Quantity uint32 `json:"quantity" validate:"required,min=1"` // 调栏猪只数量 Remarks string `json:"remarks"` // 备注 } // RecordSickPigsRequest 用于记录新增病猪事件的请求体 type RecordSickPigsRequest struct { - PenID uint `json:"pen_id" validate:"required"` // 猪栏ID + PenID uint32 `json:"pen_id" validate:"required"` // 猪栏ID Quantity int `json:"quantity" validate:"required,min=1"` // 病猪数量 TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location" validate:"required"` // 治疗地点 HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间 @@ -120,7 +120,7 @@ type RecordSickPigsRequest struct { // RecordSickPigRecoveryRequest 用于记录病猪康复事件的请求体 type RecordSickPigRecoveryRequest struct { - PenID uint `json:"pen_id" validate:"required"` // 猪栏ID + PenID uint32 `json:"pen_id" validate:"required"` // 猪栏ID Quantity int `json:"quantity" validate:"required,min=1"` // 康复猪数量 TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location" validate:"required"` // 治疗地点 HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间 @@ -129,7 +129,7 @@ type RecordSickPigRecoveryRequest struct { // RecordSickPigDeathRequest 用于记录病猪死亡事件的请求体 type RecordSickPigDeathRequest struct { - PenID uint `json:"pen_id" validate:"required"` // 猪栏ID + PenID uint32 `json:"pen_id" validate:"required"` // 猪栏ID Quantity int `json:"quantity" validate:"required,min=1"` // 死亡猪数量 TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location" validate:"required"` // 治疗地点 HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间 @@ -138,7 +138,7 @@ type RecordSickPigDeathRequest struct { // RecordSickPigCullRequest 用于记录病猪淘汰事件的请求体 type RecordSickPigCullRequest struct { - PenID uint `json:"pen_id" validate:"required"` // 猪栏ID + PenID uint32 `json:"pen_id" validate:"required"` // 猪栏ID Quantity int `json:"quantity" validate:"required,min=1"` // 淘汰猪数量 TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location" validate:"required"` // 治疗地点 HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间 @@ -147,7 +147,7 @@ type RecordSickPigCullRequest struct { // RecordDeathRequest 用于记录正常猪只死亡事件的请求体 type RecordDeathRequest struct { - PenID uint `json:"pen_id" validate:"required"` // 猪栏ID + PenID uint32 `json:"pen_id" validate:"required"` // 猪栏ID Quantity int `json:"quantity" validate:"required,min=1"` // 死亡猪数量 HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间 Remarks string `json:"remarks"` // 备注 @@ -155,7 +155,7 @@ type RecordDeathRequest struct { // RecordCullRequest 用于记录正常猪只淘汰事件的请求体 type RecordCullRequest struct { - PenID uint `json:"pen_id" validate:"required"` // 猪栏ID + PenID uint32 `json:"pen_id" validate:"required"` // 猪栏ID Quantity int `json:"quantity" validate:"required,min=1"` // 淘汰猪数量 HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间 Remarks string `json:"remarks"` // 备注 diff --git a/internal/app/dto/pig_farm_dto.go b/internal/app/dto/pig_farm_dto.go index fd789bc..913e374 100644 --- a/internal/app/dto/pig_farm_dto.go +++ b/internal/app/dto/pig_farm_dto.go @@ -4,19 +4,19 @@ import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" // PigHouseResponse 定义了猪舍信息的响应结构 type PigHouseResponse struct { - ID uint `json:"id"` + ID uint32 `json:"id"` Name string `json:"name"` Description string `json:"description"` } // PenResponse 定义了猪栏信息的响应结构 type PenResponse struct { - ID uint `json:"id"` + ID uint32 `json:"id"` PenNumber string `json:"pen_number"` - HouseID uint `json:"house_id"` + HouseID uint32 `json:"house_id"` Capacity int `json:"capacity"` Status models.PenStatus `json:"status"` - PigBatchID *uint `json:"pig_batch_id,omitempty"` + PigBatchID *uint32 `json:"pig_batch_id,omitempty"` CurrentPigCount int `json:"current_pig_count"` } @@ -35,14 +35,14 @@ type UpdatePigHouseRequest struct { // CreatePenRequest 定义了创建猪栏的请求结构 type CreatePenRequest struct { PenNumber string `json:"pen_number" validate:"required"` - HouseID uint `json:"house_id" validate:"required"` + HouseID uint32 `json:"house_id" validate:"required"` Capacity int `json:"capacity" validate:"required"` } // UpdatePenRequest 定义了更新猪栏的请求结构 type UpdatePenRequest struct { PenNumber string `json:"pen_number" validate:"required"` - HouseID uint `json:"house_id" validate:"required"` + HouseID uint32 `json:"house_id" validate:"required"` Capacity int `json:"capacity" validate:"required"` Status models.PenStatus `json:"status" validate:"required,oneof=空闲 使用中 病猪栏 康复栏 清洗消毒 维修中"` // 添加oneof校验 } diff --git a/internal/app/dto/plan_dto.go b/internal/app/dto/plan_dto.go index 51b82f1..b46efdb 100644 --- a/internal/app/dto/plan_dto.go +++ b/internal/app/dto/plan_dto.go @@ -17,22 +17,22 @@ type CreatePlanRequest struct { Name string `json:"name" validate:"required" example:"猪舍温度控制计划"` Description string `json:"description" example:"根据温度自动调节风扇和加热器"` ExecutionType models.PlanExecutionType `json:"execution_type" validate:"required" example:"自动"` - ExecuteNum uint `json:"execute_num,omitempty" validate:"omitempty,min=0" example:"10"` + ExecuteNum uint32 `json:"execute_num,omitempty" validate:"omitempty,min=0" example:"10"` CronExpression string `json:"cron_expression" validate:"omitempty,cron" example:"0 0 6 * * *"` - SubPlanIDs []uint `json:"sub_plan_ids,omitempty" validate:"omitempty,dive"` + SubPlanIDs []uint32 `json:"sub_plan_ids,omitempty" validate:"omitempty,dive"` Tasks []TaskRequest `json:"tasks,omitempty" validate:"omitempty,dive"` } // PlanResponse 定义计划详情响应的结构体 type PlanResponse struct { - ID uint `json:"id" example:"1"` + ID uint32 `json:"id" example:"1"` Name string `json:"name" example:"猪舍温度控制计划"` Description string `json:"description" example:"根据温度自动调节风扇和加热器"` PlanType models.PlanType `json:"plan_type" example:"自定义任务"` ExecutionType models.PlanExecutionType `json:"execution_type" example:"自动"` Status models.PlanStatus `json:"status" example:"已启用"` - ExecuteNum uint `json:"execute_num" example:"10"` - ExecuteCount uint `json:"execute_count" example:"0"` + ExecuteNum uint32 `json:"execute_num" example:"10"` + ExecuteCount uint32 `json:"execute_count" example:"0"` CronExpression string `json:"cron_expression" example:"0 0 6 * * *"` ContentType models.PlanContentType `json:"content_type" example:"任务"` SubPlans []SubPlanResponse `json:"sub_plans,omitempty"` @@ -50,17 +50,17 @@ type UpdatePlanRequest struct { Name string `json:"name" example:"猪舍温度控制计划V2"` Description string `json:"description" example:"更新后的描述"` ExecutionType models.PlanExecutionType `json:"execution_type" validate:"required" example:"自动"` - ExecuteNum uint `json:"execute_num,omitempty" validate:"omitempty,min=0" example:"10"` + ExecuteNum uint32 `json:"execute_num,omitempty" validate:"omitempty,min=0" example:"10"` CronExpression string `json:"cron_expression" validate:"omitempty,cron" example:"0 0 6 * * *"` - SubPlanIDs []uint `json:"sub_plan_ids,omitempty" validate:"omitempty,dive"` + SubPlanIDs []uint32 `json:"sub_plan_ids,omitempty" validate:"omitempty,dive"` Tasks []TaskRequest `json:"tasks,omitempty" validate:"omitempty,dive"` } // SubPlanResponse 定义子计划响应结构体 type SubPlanResponse struct { - ID uint `json:"id" example:"1"` - ParentPlanID uint `json:"parent_plan_id" example:"1"` - ChildPlanID uint `json:"child_plan_id" example:"2"` + ID uint32 `json:"id" example:"1"` + ParentPlanID uint32 `json:"parent_plan_id" example:"1"` + ChildPlanID uint32 `json:"child_plan_id" example:"2"` ExecutionOrder int `json:"execution_order" example:"1"` ChildPlan *PlanResponse `json:"child_plan,omitempty"` } @@ -77,7 +77,7 @@ type TaskRequest struct { // TaskResponse 定义任务响应结构体 type TaskResponse struct { ID int `json:"id" example:"1"` - PlanID uint `json:"plan_id" example:"1"` + PlanID uint32 `json:"plan_id" example:"1"` Name string `json:"name" example:"打开风扇"` Description string `json:"description" example:"打开1号风扇"` ExecutionOrder int `json:"execution_order" example:"1"` diff --git a/internal/app/dto/user_dto.go b/internal/app/dto/user_dto.go index 1a758c7..17700f2 100644 --- a/internal/app/dto/user_dto.go +++ b/internal/app/dto/user_dto.go @@ -16,19 +16,19 @@ type LoginRequest struct { // CreateUserResponse 定义创建用户成功响应的结构体 type CreateUserResponse struct { Username string `json:"username" example:"newuser"` - ID uint `json:"id" example:"1"` + ID uint32 `json:"id" example:"1"` } // LoginResponse 定义登录成功响应的结构体 type LoginResponse struct { Username string `json:"username" example:"testuser"` - ID uint `json:"id" example:"1"` + ID uint32 `json:"id" example:"1"` Token string `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."` } // HistoryResponse 定义单条操作历史的响应结构体 type HistoryResponse struct { - UserID uint `json:"user_id" example:"101"` + UserID uint32 `json:"user_id" example:"101"` Username string `json:"username" example:"testuser"` ActionType string `json:"action_type" example:"更新设备"` Description string `json:"description" example:"设备更新成功"` diff --git a/internal/app/service/device_service.go b/internal/app/service/device_service.go index 4a471c9..89cc49c 100644 --- a/internal/app/service/device_service.go +++ b/internal/app/service/device_service.go @@ -28,23 +28,23 @@ var ( // DeviceService 定义了应用层的设备服务接口,用于协调设备相关的业务逻辑。 type DeviceService interface { CreateDevice(ctx context.Context, req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error) - GetDevice(ctx context.Context, id uint) (*dto.DeviceResponse, error) + GetDevice(ctx context.Context, id uint32) (*dto.DeviceResponse, error) ListDevices(ctx context.Context) ([]*dto.DeviceResponse, error) - UpdateDevice(ctx context.Context, id uint, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error) - DeleteDevice(ctx context.Context, id uint) error - ManualControl(ctx context.Context, id uint, req *dto.ManualControlDeviceRequest) error + UpdateDevice(ctx context.Context, id uint32, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error) + DeleteDevice(ctx context.Context, id uint32) error + ManualControl(ctx context.Context, id uint32, req *dto.ManualControlDeviceRequest) error CreateAreaController(ctx context.Context, req *dto.CreateAreaControllerRequest) (*dto.AreaControllerResponse, error) - GetAreaController(ctx context.Context, id uint) (*dto.AreaControllerResponse, error) + GetAreaController(ctx context.Context, id uint32) (*dto.AreaControllerResponse, error) ListAreaControllers(ctx context.Context) ([]*dto.AreaControllerResponse, error) - UpdateAreaController(ctx context.Context, id uint, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error) - DeleteAreaController(ctx context.Context, id uint) error + UpdateAreaController(ctx context.Context, id uint32, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error) + DeleteAreaController(ctx context.Context, id uint32) error CreateDeviceTemplate(ctx context.Context, req *dto.CreateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) - GetDeviceTemplate(ctx context.Context, id uint) (*dto.DeviceTemplateResponse, error) + GetDeviceTemplate(ctx context.Context, id uint32) (*dto.DeviceTemplateResponse, error) ListDeviceTemplates(ctx context.Context) ([]*dto.DeviceTemplateResponse, error) - UpdateDeviceTemplate(ctx context.Context, id uint, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) - DeleteDeviceTemplate(ctx context.Context, id uint) error + UpdateDeviceTemplate(ctx context.Context, id uint32, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) + DeleteDeviceTemplate(ctx context.Context, id uint32) error } // deviceService 是 DeviceService 接口的具体实现。 @@ -109,7 +109,7 @@ func (s *deviceService) CreateDevice(ctx context.Context, req *dto.CreateDeviceR return dto.NewDeviceResponse(createdDevice) } -func (s *deviceService) GetDevice(ctx context.Context, id uint) (*dto.DeviceResponse, error) { +func (s *deviceService) GetDevice(ctx context.Context, id uint32) (*dto.DeviceResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetDevice") device, err := s.deviceRepo.FindByID(serviceCtx, id) if err != nil { @@ -127,7 +127,7 @@ func (s *deviceService) ListDevices(ctx context.Context) ([]*dto.DeviceResponse, return dto.NewListDeviceResponse(devices) } -func (s *deviceService) UpdateDevice(ctx context.Context, id uint, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error) { +func (s *deviceService) UpdateDevice(ctx context.Context, id uint32, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateDevice") existingDevice, err := s.deviceRepo.FindByID(serviceCtx, id) if err != nil { @@ -161,7 +161,7 @@ func (s *deviceService) UpdateDevice(ctx context.Context, id uint, req *dto.Upda return dto.NewDeviceResponse(updatedDevice) } -func (s *deviceService) DeleteDevice(ctx context.Context, id uint) error { +func (s *deviceService) DeleteDevice(ctx context.Context, id uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteDevice") // 检查设备是否存在 @@ -191,7 +191,7 @@ func (s *deviceService) DeleteDevice(ctx context.Context, id uint) error { return s.deviceRepo.Delete(serviceCtx, id) } -func (s *deviceService) ManualControl(ctx context.Context, id uint, req *dto.ManualControlDeviceRequest) error { +func (s *deviceService) ManualControl(ctx context.Context, id uint32, req *dto.ManualControlDeviceRequest) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "ManualControl") dev, err := s.deviceRepo.FindByID(serviceCtx, id) if err != nil { @@ -241,7 +241,7 @@ func (s *deviceService) CreateAreaController(ctx context.Context, req *dto.Creat return dto.NewAreaControllerResponse(ac) } -func (s *deviceService) GetAreaController(ctx context.Context, id uint) (*dto.AreaControllerResponse, error) { +func (s *deviceService) GetAreaController(ctx context.Context, id uint32) (*dto.AreaControllerResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetAreaController") ac, err := s.areaControllerRepo.FindByID(serviceCtx, id) if err != nil { @@ -259,7 +259,7 @@ func (s *deviceService) ListAreaControllers(ctx context.Context) ([]*dto.AreaCon return dto.NewListAreaControllerResponse(acs) } -func (s *deviceService) UpdateAreaController(ctx context.Context, id uint, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error) { +func (s *deviceService) UpdateAreaController(ctx context.Context, id uint32, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateAreaController") existingAC, err := s.areaControllerRepo.FindByID(serviceCtx, id) if err != nil { @@ -287,7 +287,7 @@ func (s *deviceService) UpdateAreaController(ctx context.Context, id uint, req * return dto.NewAreaControllerResponse(existingAC) } -func (s *deviceService) DeleteAreaController(ctx context.Context, id uint) error { +func (s *deviceService) DeleteAreaController(ctx context.Context, id uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteAreaController") // 1. 检查是否存在 @@ -349,7 +349,7 @@ func (s *deviceService) CreateDeviceTemplate(ctx context.Context, req *dto.Creat return dto.NewDeviceTemplateResponse(deviceTemplate) } -func (s *deviceService) GetDeviceTemplate(ctx context.Context, id uint) (*dto.DeviceTemplateResponse, error) { +func (s *deviceService) GetDeviceTemplate(ctx context.Context, id uint32) (*dto.DeviceTemplateResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetDeviceTemplate") deviceTemplate, err := s.deviceTemplateRepo.FindByID(serviceCtx, id) if err != nil { @@ -367,7 +367,7 @@ func (s *deviceService) ListDeviceTemplates(ctx context.Context) ([]*dto.DeviceT return dto.NewListDeviceTemplateResponse(deviceTemplates) } -func (s *deviceService) UpdateDeviceTemplate(ctx context.Context, id uint, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) { +func (s *deviceService) UpdateDeviceTemplate(ctx context.Context, id uint32, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateDeviceTemplate") existingDeviceTemplate, err := s.deviceTemplateRepo.FindByID(serviceCtx, id) if err != nil { @@ -402,7 +402,7 @@ func (s *deviceService) UpdateDeviceTemplate(ctx context.Context, id uint, req * return dto.NewDeviceTemplateResponse(existingDeviceTemplate) } -func (s *deviceService) DeleteDeviceTemplate(ctx context.Context, id uint) error { +func (s *deviceService) DeleteDeviceTemplate(ctx context.Context, id uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteDeviceTemplate") // 1. 检查是否存在 diff --git a/internal/app/service/monitor_service.go b/internal/app/service/monitor_service.go index ea3bbab..64be63e 100644 --- a/internal/app/service/monitor_service.go +++ b/internal/app/service/monitor_service.go @@ -147,7 +147,7 @@ func (s *monitorService) ListPlanExecutionLogs(ctx context.Context, req *dto.Lis return nil, err } - planIds := make([]uint, 0, len(planLogs)) + planIds := make([]uint32, 0, len(planLogs)) for _, datum := range planLogs { has := false for _, id := range planIds { diff --git a/internal/app/service/pig_batch_service.go b/internal/app/service/pig_batch_service.go index b0ca12c..8702bbf 100644 --- a/internal/app/service/pig_batch_service.go +++ b/internal/app/service/pig_batch_service.go @@ -12,35 +12,35 @@ import ( // PigBatchService 接口定义保持不变,继续作为应用层对外的契约。 type PigBatchService interface { - CreatePigBatch(ctx context.Context, operatorID uint, dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) - GetPigBatch(ctx context.Context, id uint) (*dto.PigBatchResponseDTO, error) - UpdatePigBatch(ctx context.Context, id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) - DeletePigBatch(ctx context.Context, id uint) error + CreatePigBatch(ctx context.Context, operatorID uint32, dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) + GetPigBatch(ctx context.Context, id uint32) (*dto.PigBatchResponseDTO, error) + UpdatePigBatch(ctx context.Context, id uint32, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) + DeletePigBatch(ctx context.Context, id uint32) error ListPigBatches(ctx context.Context, isActive *bool) ([]*dto.PigBatchResponseDTO, error) // Pig Pen Management - AssignEmptyPensToBatch(ctx context.Context, batchID uint, penIDs []uint, operatorID uint) error - ReclassifyPenToNewBatch(ctx context.Context, fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error - RemoveEmptyPenFromBatch(ctx context.Context, batchID uint, penID uint) error - MovePigsIntoPen(ctx context.Context, batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error + AssignEmptyPensToBatch(ctx context.Context, batchID uint32, penIDs []uint32, operatorID uint32) error + ReclassifyPenToNewBatch(ctx context.Context, fromBatchID uint32, toBatchID uint32, penID uint32, operatorID uint32, remarks string) error + RemoveEmptyPenFromBatch(ctx context.Context, batchID uint32, penID uint32) error + MovePigsIntoPen(ctx context.Context, batchID uint32, toPenID uint32, quantity int, operatorID uint32, remarks string) error // Trade Sub-service - SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error - BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error + SellPigs(ctx context.Context, batchID uint32, penID uint32, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint32) error + BuyPigs(ctx context.Context, batchID uint32, penID uint32, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint32) error // Transfer Sub-service - TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error - TransferPigsWithinBatch(ctx context.Context, batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error + TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint32, destBatchID uint32, fromPenID uint32, toPenID uint32, quantity uint32, operatorID uint32, remarks string) error + TransferPigsWithinBatch(ctx context.Context, batchID uint32, fromPenID uint32, toPenID uint32, quantity uint32, operatorID uint32, remarks string) error // Sick Pig Management - RecordSickPigs(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error - RecordSickPigRecovery(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error - RecordSickPigDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error - RecordSickPigCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error + RecordSickPigs(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error + RecordSickPigRecovery(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error + RecordSickPigDeath(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error + RecordSickPigCull(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error // Normal Pig Management - RecordDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error - RecordCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error + RecordDeath(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, happenedAt time.Time, remarks string) error + RecordCull(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, happenedAt time.Time, remarks string) error } // pigBatchService 的实现现在依赖于领域服务接口。 @@ -79,7 +79,7 @@ func (s *pigBatchService) toPigBatchResponseDTO(batch *models.PigBatch, currentT } // CreatePigBatch 现在将请求委托给领域服务处理。 -func (s *pigBatchService) CreatePigBatch(ctx context.Context, operatorID uint, dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) { +func (s *pigBatchService) CreatePigBatch(ctx context.Context, operatorID uint32, dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) { serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreatePigBatch") // 1. DTO -> 领域模型 batch := &models.PigBatch{ @@ -102,7 +102,7 @@ func (s *pigBatchService) CreatePigBatch(ctx context.Context, operatorID uint, d } // GetPigBatch 从领域服务获取数据并转换为DTO,同时处理错误转换。 -func (s *pigBatchService) GetPigBatch(ctx context.Context, id uint) (*dto.PigBatchResponseDTO, error) { +func (s *pigBatchService) GetPigBatch(ctx context.Context, id uint32) (*dto.PigBatchResponseDTO, error) { serviceCtx, logger := logs.Trace(ctx, s.ctx, "GetPigBatch") batch, err := s.domainService.GetPigBatch(serviceCtx, id) if err != nil { @@ -123,7 +123,7 @@ func (s *pigBatchService) GetPigBatch(ctx context.Context, id uint) (*dto.PigBat } // UpdatePigBatch 协调获取、更新和保存的流程,并处理错误转换。 -func (s *pigBatchService) UpdatePigBatch(ctx context.Context, id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) { +func (s *pigBatchService) UpdatePigBatch(ctx context.Context, id uint32, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) { serviceCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePigBatch") // 1. 先获取最新的领域模型 existingBatch, err := s.domainService.GetPigBatch(serviceCtx, id) @@ -176,7 +176,7 @@ func (s *pigBatchService) UpdatePigBatch(ctx context.Context, id uint, dto *dto. } // DeletePigBatch 将删除操作委托给领域服务,并转换领域错误为应用层错误。 -func (s *pigBatchService) DeletePigBatch(ctx context.Context, id uint) error { +func (s *pigBatchService) DeletePigBatch(ctx context.Context, id uint32) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeletePigBatch") err := s.domainService.DeletePigBatch(serviceCtx, id) if err != nil { @@ -214,7 +214,7 @@ func (s *pigBatchService) ListPigBatches(ctx context.Context, isActive *bool) ([ } // AssignEmptyPensToBatch 委托给领域服务 -func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID uint, penIDs []uint, operatorID uint) error { +func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID uint32, penIDs []uint32, operatorID uint32) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "AssignEmptyPensToBatch") err := s.domainService.AssignEmptyPensToBatch(serviceCtx, batchID, penIDs, operatorID) if err != nil { @@ -225,7 +225,7 @@ func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID ui } // ReclassifyPenToNewBatch 委托给领域服务 -func (s *pigBatchService) ReclassifyPenToNewBatch(ctx context.Context, fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error { +func (s *pigBatchService) ReclassifyPenToNewBatch(ctx context.Context, fromBatchID uint32, toBatchID uint32, penID uint32, operatorID uint32, remarks string) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "ReclassifyPenToNewBatch") err := s.domainService.ReclassifyPenToNewBatch(serviceCtx, fromBatchID, toBatchID, penID, operatorID, remarks) if err != nil { @@ -236,7 +236,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(ctx context.Context, fromBatch } // RemoveEmptyPenFromBatch 委托给领域服务 -func (s *pigBatchService) RemoveEmptyPenFromBatch(ctx context.Context, batchID uint, penID uint) error { +func (s *pigBatchService) RemoveEmptyPenFromBatch(ctx context.Context, batchID uint32, penID uint32) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "RemoveEmptyPenFromBatch") err := s.domainService.RemoveEmptyPenFromBatch(serviceCtx, batchID, penID) if err != nil { @@ -247,7 +247,7 @@ func (s *pigBatchService) RemoveEmptyPenFromBatch(ctx context.Context, batchID u } // MovePigsIntoPen 委托给领域服务 -func (s *pigBatchService) MovePigsIntoPen(ctx context.Context, batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error { +func (s *pigBatchService) MovePigsIntoPen(ctx context.Context, batchID uint32, toPenID uint32, quantity int, operatorID uint32, remarks string) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "MovePigsIntoPen") err := s.domainService.MovePigsIntoPen(serviceCtx, batchID, toPenID, quantity, operatorID, remarks) if err != nil { @@ -258,7 +258,7 @@ func (s *pigBatchService) MovePigsIntoPen(ctx context.Context, batchID uint, toP } // SellPigs 委托给领域服务 -func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { +func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint32, penID uint32, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint32) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "SellPigs") err := s.domainService.SellPigs(serviceCtx, batchID, penID, quantity, unitPrice, tatalPrice, traderName, tradeDate, remarks, operatorID) if err != nil { @@ -269,7 +269,7 @@ func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint } // BuyPigs 委托给领域服务 -func (s *pigBatchService) BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { +func (s *pigBatchService) BuyPigs(ctx context.Context, batchID uint32, penID uint32, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint32) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "BuyPigs") err := s.domainService.BuyPigs(serviceCtx, batchID, penID, quantity, unitPrice, tatalPrice, traderName, tradeDate, remarks, operatorID) if err != nil { @@ -280,7 +280,7 @@ func (s *pigBatchService) BuyPigs(ctx context.Context, batchID uint, penID uint, } // TransferPigsAcrossBatches 委托给领域服务 -func (s *pigBatchService) TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error { +func (s *pigBatchService) TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint32, destBatchID uint32, fromPenID uint32, toPenID uint32, quantity uint32, operatorID uint32, remarks string) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "TransferPigsAcrossBatches") err := s.domainService.TransferPigsAcrossBatches(serviceCtx, sourceBatchID, destBatchID, fromPenID, toPenID, quantity, operatorID, remarks) if err != nil { @@ -291,7 +291,7 @@ func (s *pigBatchService) TransferPigsAcrossBatches(ctx context.Context, sourceB } // TransferPigsWithinBatch 委托给领域服务 -func (s *pigBatchService) TransferPigsWithinBatch(ctx context.Context, batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error { +func (s *pigBatchService) TransferPigsWithinBatch(ctx context.Context, batchID uint32, fromPenID uint32, toPenID uint32, quantity uint32, operatorID uint32, remarks string) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "TransferPigsWithinBatch") err := s.domainService.TransferPigsWithinBatch(serviceCtx, batchID, fromPenID, toPenID, quantity, operatorID, remarks) if err != nil { @@ -302,7 +302,7 @@ func (s *pigBatchService) TransferPigsWithinBatch(ctx context.Context, batchID u } // RecordSickPigs 委托给领域服务 -func (s *pigBatchService) RecordSickPigs(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordSickPigs(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordSickPigs") err := s.domainService.RecordSickPigs(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks) if err != nil { @@ -313,7 +313,7 @@ func (s *pigBatchService) RecordSickPigs(ctx context.Context, operatorID uint, b } // RecordSickPigRecovery 委托给领域服务 -func (s *pigBatchService) RecordSickPigRecovery(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordSickPigRecovery(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordSickPigRecovery") err := s.domainService.RecordSickPigRecovery(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks) if err != nil { @@ -324,7 +324,7 @@ func (s *pigBatchService) RecordSickPigRecovery(ctx context.Context, operatorID } // RecordSickPigDeath 委托给领域服务 -func (s *pigBatchService) RecordSickPigDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordSickPigDeath(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordSickPigDeath") err := s.domainService.RecordSickPigDeath(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks) if err != nil { @@ -335,7 +335,7 @@ func (s *pigBatchService) RecordSickPigDeath(ctx context.Context, operatorID uin } // RecordSickPigCull 委托给领域服务 -func (s *pigBatchService) RecordSickPigCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordSickPigCull(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordSickPigCull") err := s.domainService.RecordSickPigCull(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks) if err != nil { @@ -346,7 +346,7 @@ func (s *pigBatchService) RecordSickPigCull(ctx context.Context, operatorID uint } // RecordDeath 委托给领域服务 -func (s *pigBatchService) RecordDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordDeath(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, happenedAt time.Time, remarks string) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordDeath") err := s.domainService.RecordDeath(serviceCtx, operatorID, batchID, penID, quantity, happenedAt, remarks) if err != nil { @@ -357,7 +357,7 @@ func (s *pigBatchService) RecordDeath(ctx context.Context, operatorID uint, batc } // RecordCull 委托给领域服务 -func (s *pigBatchService) RecordCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordCull(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, happenedAt time.Time, remarks string) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordCull") err := s.domainService.RecordCull(serviceCtx, operatorID, batchID, penID, quantity, happenedAt, remarks) if err != nil { diff --git a/internal/app/service/pig_farm_service.go b/internal/app/service/pig_farm_service.go index 4ea98f3..ba85089 100644 --- a/internal/app/service/pig_farm_service.go +++ b/internal/app/service/pig_farm_service.go @@ -18,19 +18,19 @@ import ( type PigFarmService interface { // PigHouse methods CreatePigHouse(ctx context.Context, name, description string) (*dto.PigHouseResponse, error) - GetPigHouseByID(ctx context.Context, id uint) (*dto.PigHouseResponse, error) + GetPigHouseByID(ctx context.Context, id uint32) (*dto.PigHouseResponse, error) ListPigHouses(ctx context.Context) ([]dto.PigHouseResponse, error) - UpdatePigHouse(ctx context.Context, id uint, name, description string) (*dto.PigHouseResponse, error) - DeletePigHouse(ctx context.Context, id uint) error + UpdatePigHouse(ctx context.Context, id uint32, name, description string) (*dto.PigHouseResponse, error) + DeletePigHouse(ctx context.Context, id uint32) error // Pen methods - CreatePen(ctx context.Context, penNumber string, houseID uint, capacity int) (*dto.PenResponse, error) - GetPenByID(ctx context.Context, id uint) (*dto.PenResponse, error) + CreatePen(ctx context.Context, penNumber string, houseID uint32, capacity int) (*dto.PenResponse, error) + GetPenByID(ctx context.Context, id uint32) (*dto.PenResponse, error) ListPens(ctx context.Context) ([]*dto.PenResponse, error) - UpdatePen(ctx context.Context, id uint, penNumber string, houseID uint, capacity int, status models.PenStatus) (*dto.PenResponse, error) - DeletePen(ctx context.Context, id uint) error + UpdatePen(ctx context.Context, id uint32, penNumber string, houseID uint32, capacity int, status models.PenStatus) (*dto.PenResponse, error) + DeletePen(ctx context.Context, id uint32) error // UpdatePenStatus 更新猪栏状态 - UpdatePenStatus(ctx context.Context, id uint, newStatus models.PenStatus) (*dto.PenResponse, error) + UpdatePenStatus(ctx context.Context, id uint32, newStatus models.PenStatus) (*dto.PenResponse, error) } type pigFarmService struct { @@ -79,7 +79,7 @@ func (s *pigFarmService) CreatePigHouse(ctx context.Context, name, description s }, nil } -func (s *pigFarmService) GetPigHouseByID(ctx context.Context, id uint) (*dto.PigHouseResponse, error) { +func (s *pigFarmService) GetPigHouseByID(ctx context.Context, id uint32) (*dto.PigHouseResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigHouseByID") house, err := s.farmRepository.GetPigHouseByID(serviceCtx, id) if err != nil { @@ -109,10 +109,10 @@ func (s *pigFarmService) ListPigHouses(ctx context.Context) ([]dto.PigHouseRespo return resp, nil } -func (s *pigFarmService) UpdatePigHouse(ctx context.Context, id uint, name, description string) (*dto.PigHouseResponse, error) { +func (s *pigFarmService) UpdatePigHouse(ctx context.Context, id uint32, name, description string) (*dto.PigHouseResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigHouse") house := &models.PigHouse{ - Model: gorm.Model{ID: id}, + Model: models.Model{ID: id}, Name: name, Description: description, } @@ -135,7 +135,7 @@ func (s *pigFarmService) UpdatePigHouse(ctx context.Context, id uint, name, desc }, nil } -func (s *pigFarmService) DeletePigHouse(ctx context.Context, id uint) error { +func (s *pigFarmService) DeletePigHouse(ctx context.Context, id uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigHouse") // 业务逻辑:检查猪舍是否包含猪栏 penCount, err := s.farmRepository.CountPensInHouse(serviceCtx, id) @@ -159,7 +159,7 @@ func (s *pigFarmService) DeletePigHouse(ctx context.Context, id uint) error { // --- Pen Implementation --- -func (s *pigFarmService) CreatePen(ctx context.Context, penNumber string, houseID uint, capacity int) (*dto.PenResponse, error) { +func (s *pigFarmService) CreatePen(ctx context.Context, penNumber string, houseID uint32, capacity int) (*dto.PenResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePen") // 业务逻辑:验证所属猪舍是否存在 _, err := s.farmRepository.GetPigHouseByID(serviceCtx, houseID) @@ -189,7 +189,7 @@ func (s *pigFarmService) CreatePen(ctx context.Context, penNumber string, houseI }, nil } -func (s *pigFarmService) GetPenByID(ctx context.Context, id uint) (*dto.PenResponse, error) { +func (s *pigFarmService) GetPenByID(ctx context.Context, id uint32) (*dto.PenResponse, error) { serviceCtx, logger := logs.Trace(ctx, s.ctx, "GetPenByID") pen, err := s.penRepository.GetPenByID(serviceCtx, id) if err != nil { @@ -251,7 +251,7 @@ func (s *pigFarmService) ListPens(ctx context.Context) ([]*dto.PenResponse, erro return response, nil } -func (s *pigFarmService) UpdatePen(ctx context.Context, id uint, penNumber string, houseID uint, capacity int, status models.PenStatus) (*dto.PenResponse, error) { +func (s *pigFarmService) UpdatePen(ctx context.Context, id uint32, penNumber string, houseID uint32, capacity int, status models.PenStatus) (*dto.PenResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePen") // 业务逻辑:验证所属猪舍是否存在 _, err := s.farmRepository.GetPigHouseByID(serviceCtx, houseID) @@ -263,7 +263,7 @@ func (s *pigFarmService) UpdatePen(ctx context.Context, id uint, penNumber strin } pen := &models.Pen{ - Model: gorm.Model{ID: id}, + Model: models.Model{ID: id}, PenNumber: penNumber, HouseID: houseID, Capacity: capacity, @@ -291,7 +291,7 @@ func (s *pigFarmService) UpdatePen(ctx context.Context, id uint, penNumber strin }, nil } -func (s *pigFarmService) DeletePen(ctx context.Context, id uint) error { +func (s *pigFarmService) DeletePen(ctx context.Context, id uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePen") // 业务逻辑:检查猪栏是否被活跃批次使用 pen, err := s.penRepository.GetPenByID(serviceCtx, id) @@ -327,7 +327,7 @@ func (s *pigFarmService) DeletePen(ctx context.Context, id uint) error { } // UpdatePenStatus 更新猪栏状态 -func (s *pigFarmService) UpdatePenStatus(ctx context.Context, id uint, newStatus models.PenStatus) (*dto.PenResponse, error) { +func (s *pigFarmService) UpdatePenStatus(ctx context.Context, id uint32, newStatus models.PenStatus) (*dto.PenResponse, error) { serviceCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePenStatus") var updatedPen *models.Pen err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { diff --git a/internal/app/service/plan_service.go b/internal/app/service/plan_service.go index 8601487..3d74823 100644 --- a/internal/app/service/plan_service.go +++ b/internal/app/service/plan_service.go @@ -16,17 +16,17 @@ type PlanService interface { // CreatePlan 创建一个新的计划 CreatePlan(ctx context.Context, req *dto.CreatePlanRequest) (*dto.PlanResponse, error) // GetPlanByID 根据ID获取计划详情 - GetPlanByID(ctx context.Context, id uint) (*dto.PlanResponse, error) + GetPlanByID(ctx context.Context, id uint32) (*dto.PlanResponse, error) // ListPlans 获取计划列表,支持过滤和分页 ListPlans(ctx context.Context, query *dto.ListPlansQuery) (*dto.ListPlansResponse, error) // UpdatePlan 更新计划 - UpdatePlan(ctx context.Context, id uint, req *dto.UpdatePlanRequest) (*dto.PlanResponse, error) + UpdatePlan(ctx context.Context, id uint32, req *dto.UpdatePlanRequest) (*dto.PlanResponse, error) // DeletePlan 删除计划(软删除) - DeletePlan(ctx context.Context, id uint) error + DeletePlan(ctx context.Context, id uint32) error // StartPlan 启动计划 - StartPlan(ctx context.Context, id uint) error + StartPlan(ctx context.Context, id uint32) error // StopPlan 停止计划 - StopPlan(ctx context.Context, id uint) error + StopPlan(ctx context.Context, id uint32) error } // planService 是 PlanService 接口的实现 @@ -77,7 +77,7 @@ func (s *planService) CreatePlan(ctx context.Context, req *dto.CreatePlanRequest } // GetPlanByID 根据ID获取计划详情 -func (s *planService) GetPlanByID(ctx context.Context, id uint) (*dto.PlanResponse, error) { +func (s *planService) GetPlanByID(ctx context.Context, id uint32) (*dto.PlanResponse, error) { serviceCtx, logger := logs.Trace(ctx, s.ctx, "GetPlanByID") const actionType = "应用服务层:获取计划详情" @@ -135,7 +135,7 @@ func (s *planService) ListPlans(ctx context.Context, query *dto.ListPlansQuery) } // UpdatePlan 更新计划 -func (s *planService) UpdatePlan(ctx context.Context, id uint, req *dto.UpdatePlanRequest) (*dto.PlanResponse, error) { +func (s *planService) UpdatePlan(ctx context.Context, id uint32, req *dto.UpdatePlanRequest) (*dto.PlanResponse, error) { serviceCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePlan") const actionType = "应用服务层:更新计划" @@ -166,7 +166,7 @@ func (s *planService) UpdatePlan(ctx context.Context, id uint, req *dto.UpdatePl } // DeletePlan 删除计划(软删除) -func (s *planService) DeletePlan(ctx context.Context, id uint) error { +func (s *planService) DeletePlan(ctx context.Context, id uint32) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeletePlan") const actionType = "应用服务层:删除计划" @@ -182,7 +182,7 @@ func (s *planService) DeletePlan(ctx context.Context, id uint) error { } // StartPlan 启动计划 -func (s *planService) StartPlan(ctx context.Context, id uint) error { +func (s *planService) StartPlan(ctx context.Context, id uint32) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "StartPlan") const actionType = "应用服务层:启动计划" @@ -198,7 +198,7 @@ func (s *planService) StartPlan(ctx context.Context, id uint) error { } // StopPlan 停止计划 -func (s *planService) StopPlan(ctx context.Context, id uint) error { +func (s *planService) StopPlan(ctx context.Context, id uint32) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "StopPlan") const actionType = "应用服务层:停止计划" diff --git a/internal/app/service/threshold_alarm_service.go b/internal/app/service/threshold_alarm_service.go index 19a81ac..834a003 100644 --- a/internal/app/service/threshold_alarm_service.go +++ b/internal/app/service/threshold_alarm_service.go @@ -18,9 +18,9 @@ import ( // 该服务负责管理阈值告警任务的配置,并将其与计划进行联动。 type ThresholdAlarmService interface { // SnoozeThresholdAlarm 忽略一个阈值告警,或更新其忽略时间。 - SnoozeThresholdAlarm(ctx context.Context, alarmID uint, durationMinutes uint) error + SnoozeThresholdAlarm(ctx context.Context, alarmID uint32, durationMinutes uint32) error // CancelSnoozeThresholdAlarm 取消对一个阈值告警的忽略状态。 - CancelSnoozeThresholdAlarm(ctx context.Context, alarmID uint) error + CancelSnoozeThresholdAlarm(ctx context.Context, alarmID uint32) error // ListActiveAlarms 批量查询活跃告警。 ListActiveAlarms(ctx context.Context, req *dto.ListActiveAlarmRequest) (*dto.ListActiveAlarmResponse, error) // ListHistoricalAlarms 批量查询历史告警。 @@ -35,7 +35,7 @@ type ThresholdAlarmService interface { // DeleteDeviceThresholdAlarm 删除一个设备阈值告警。 DeleteDeviceThresholdAlarm(ctx context.Context, taskID int, req *dto.DeleteDeviceThresholdAlarmDTO) error // DeleteDeviceThresholdAlarmByDeviceID 实现了根据设备ID删除一个设备下所有设备阈值告警的逻辑。 - DeleteDeviceThresholdAlarmByDeviceID(ctx context.Context, deviceID uint) error + DeleteDeviceThresholdAlarmByDeviceID(ctx context.Context, deviceID uint32) error // CreateAreaThresholdAlarm 创建一个区域阈值告警。 CreateAreaThresholdAlarm(ctx context.Context, req *dto.CreateAreaThresholdAlarmDTO) error @@ -46,7 +46,7 @@ type ThresholdAlarmService interface { // DeleteAreaThresholdAlarm 实现了删除一个区域阈值告警的逻辑。 DeleteAreaThresholdAlarm(ctx context.Context, taskID int) error // DeleteAreaThresholdAlarmByAreaControllerID 实现了根据区域ID删除一个区域下所有区域阈值告警的逻辑。 - DeleteAreaThresholdAlarmByAreaControllerID(ctx context.Context, areaControllerID uint) error + DeleteAreaThresholdAlarmByAreaControllerID(ctx context.Context, areaControllerID uint32) error } // thresholdAlarmService 是 ThresholdAlarmService 接口的具体实现。 @@ -82,13 +82,13 @@ func NewThresholdAlarmService(ctx context.Context, } // SnoozeThresholdAlarm 实现了忽略阈值告警的逻辑。 -func (s *thresholdAlarmService) SnoozeThresholdAlarm(ctx context.Context, alarmID uint, durationMinutes uint) error { +func (s *thresholdAlarmService) SnoozeThresholdAlarm(ctx context.Context, alarmID uint32, durationMinutes uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "SnoozeThresholdAlarm") return s.alarmService.SnoozeAlarm(serviceCtx, alarmID, time.Duration(durationMinutes)*time.Minute) } // CancelSnoozeThresholdAlarm 实现了取消忽略阈值告警的逻辑。 -func (s *thresholdAlarmService) CancelSnoozeThresholdAlarm(ctx context.Context, alarmID uint) error { +func (s *thresholdAlarmService) CancelSnoozeThresholdAlarm(ctx context.Context, alarmID uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "CancelSnoozeThresholdAlarm") return s.alarmService.CancelAlarmSnooze(serviceCtx, alarmID) } @@ -169,7 +169,7 @@ func (s *thresholdAlarmService) CreateDeviceThresholdAlarm(ctx context.Context, } case models.TaskTypeAreaCollectorThresholdCheck: // 向区域阈值检查任务过滤列表中添加该设备 params := task.AreaThresholdCheckParams{ - ExcludeDeviceIDs: []uint{}, + ExcludeDeviceIDs: []uint32{}, } err = t.ParseParameters(¶ms) if err != nil { @@ -375,7 +375,7 @@ func (s *thresholdAlarmService) DeleteDeviceThresholdAlarm(ctx context.Context, } // DeleteDeviceThresholdAlarmByDeviceID 实现了根据设备ID删除一个设备下所有设备阈值告警的逻辑。 -func (s *thresholdAlarmService) DeleteDeviceThresholdAlarmByDeviceID(ctx context.Context, deviceID uint) error { +func (s *thresholdAlarmService) DeleteDeviceThresholdAlarmByDeviceID(ctx context.Context, deviceID uint32) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeleteDeviceThresholdAlarmByDeviceID") tasks, err := s.planRepo.ListTasksByDeviceID(serviceCtx, deviceID) if err != nil { @@ -462,13 +462,13 @@ func (s *thresholdAlarmService) CreateAreaThresholdAlarm(ctx context.Context, re if err != nil { return fmt.Errorf("获取区域 %d 下的设备列表失败: %w", req.AreaControllerID, err) } - devicesInAreaMap := make(map[uint]struct{}, len(devicesInArea)) + devicesInAreaMap := make(map[uint32]struct{}, len(devicesInArea)) for _, device := range devicesInArea { devicesInAreaMap[device.ID] = struct{}{} } // 3. 遍历计划,检查存在性并收集需要排除的设备ID - var excludeDeviceIDs []uint + var excludeDeviceIDs []uint32 for _, t := range plan.Tasks { switch t.Type { case models.TaskTypeAreaCollectorThresholdCheck: @@ -631,7 +631,7 @@ func (s *thresholdAlarmService) DeleteAreaThresholdAlarm(ctx context.Context, ta } // DeleteAreaThresholdAlarmByAreaControllerID 实现了根据区域ID删除一个区域下所有区域阈值告警的逻辑。 -func (s *thresholdAlarmService) DeleteAreaThresholdAlarmByAreaControllerID(ctx context.Context, areaControllerID uint) error { +func (s *thresholdAlarmService) DeleteAreaThresholdAlarmByAreaControllerID(ctx context.Context, areaControllerID uint32) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeleteAreaThresholdAlarmByAreaControllerID") // 1. 获取系统健康检查计划 diff --git a/internal/app/service/user_service.go b/internal/app/service/user_service.go index 3efabc8..b9281e9 100644 --- a/internal/app/service/user_service.go +++ b/internal/app/service/user_service.go @@ -18,7 +18,7 @@ import ( type UserService interface { CreateUser(ctx context.Context, req *dto.CreateUserRequest) (*dto.CreateUserResponse, error) Login(ctx context.Context, req *dto.LoginRequest) (*dto.LoginResponse, error) - SendTestNotification(ctx context.Context, userID uint, req *dto.SendTestNotificationRequest) error + SendTestNotification(ctx context.Context, userID uint32, req *dto.SendTestNotificationRequest) error } // userService 实现了 UserService 接口 @@ -103,7 +103,7 @@ func (s *userService) Login(ctx context.Context, req *dto.LoginRequest) (*dto.Lo } // SendTestNotification 发送测试通知 -func (s *userService) SendTestNotification(ctx context.Context, userID uint, req *dto.SendTestNotificationRequest) error { +func (s *userService) SendTestNotification(ctx context.Context, userID uint32, req *dto.SendTestNotificationRequest) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendTestNotification") err := s.notifyService.SendTestMessage(serviceCtx, userID, req.Type) if err != nil { diff --git a/internal/app/webhook/chirp_stack.go b/internal/app/webhook/chirp_stack.go index 8714d47..c3cf005 100644 --- a/internal/app/webhook/chirp_stack.go +++ b/internal/app/webhook/chirp_stack.go @@ -429,7 +429,7 @@ func (c *ChirpStackListener) handleIntegrationEvent(ctx context.Context, event * // sensorDeviceID: 实际产生传感器数据的普通设备的ID // sensorType: 传感器值的类型 (例如 models.SensorTypeTemperature) // data: 具体的传感器数据结构体实例 (例如 models.TemperatureData) -func (c *ChirpStackListener) recordSensorData(ctx context.Context, areaControllerID uint, sensorDeviceID uint, eventTime time.Time, sensorType models.SensorType, data interface{}) { +func (c *ChirpStackListener) recordSensorData(ctx context.Context, areaControllerID uint32, sensorDeviceID uint32, eventTime time.Time, sensorType models.SensorType, data interface{}) { reqCtx, logger := logs.Trace(ctx, c.ctx, "recordSensorData") // 1. 将传入的结构体序列化为 JSON jsonData, err := json.Marshal(data) diff --git a/internal/core/data_initializer.go b/internal/core/data_initializer.go index 9560aac..cfe4894 100644 --- a/internal/core/data_initializer.go +++ b/internal/core/data_initializer.go @@ -293,7 +293,7 @@ func (app *Application) cleanupStaleTasksAndLogs(ctx context.Context) error { } // 2. 收集所有受影响的唯一 PlanID - affectedPlanIDs := make(map[uint]struct{}) + affectedPlanIDs := make(map[uint32]struct{}) for _, log := range incompletePlanLogs { affectedPlanIDs[log.PlanID] = struct{}{} } diff --git a/internal/domain/alarm/alarm_service.go b/internal/domain/alarm/alarm_service.go index 661ba25..a542935 100644 --- a/internal/domain/alarm/alarm_service.go +++ b/internal/domain/alarm/alarm_service.go @@ -21,15 +21,15 @@ type AlarmService interface { // CloseAlarm 关闭一个活跃告警,将其归档到历史记录。 // 如果指定的告警当前不活跃,则不执行任何操作并返回 nil。 - CloseAlarm(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode, resolveMethod string, resolvedBy *uint) error + CloseAlarm(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint32, alarmCode models.AlarmCode, resolveMethod string, resolvedBy *uint32) error // SnoozeAlarm 忽略一个活跃告警,或更新其忽略时间。 // 如果告警不存在,将返回错误。 - SnoozeAlarm(ctx context.Context, alarmID uint, duration time.Duration) error + SnoozeAlarm(ctx context.Context, alarmID uint32, duration time.Duration) error // CancelAlarmSnooze 取消对一个告警的忽略状态。 // 如果告警不存在,或本就未被忽略,不执行任何操作并返回 nil。 - CancelAlarmSnooze(ctx context.Context, alarmID uint) error + CancelAlarmSnooze(ctx context.Context, alarmID uint32) error } // alarmService 是 AlarmService 接口的具体实现。 @@ -71,7 +71,7 @@ func (s *alarmService) CreateAlarmIfNotExists(ctx context.Context, newAlarm *mod } // CloseAlarm 实现了关闭告警并将其归档的逻辑。 -func (s *alarmService) CloseAlarm(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode, resolveMethod string, resolvedBy *uint) error { +func (s *alarmService) CloseAlarm(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint32, alarmCode models.AlarmCode, resolveMethod string, resolvedBy *uint32) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "CloseAlarm") // 1. 在事务外进行快速只读检查,避免不必要的事务开销 @@ -133,7 +133,7 @@ func (s *alarmService) CloseAlarm(ctx context.Context, sourceType models.AlarmSo } // SnoozeAlarm 忽略一个活跃告警,或更新其忽略时间。 -func (s *alarmService) SnoozeAlarm(ctx context.Context, alarmID uint, duration time.Duration) error { +func (s *alarmService) SnoozeAlarm(ctx context.Context, alarmID uint32, duration time.Duration) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "SnoozeAlarm") if duration <= 0 { @@ -156,7 +156,7 @@ func (s *alarmService) SnoozeAlarm(ctx context.Context, alarmID uint, duration t } // CancelAlarmSnooze 取消对一个告警的忽略状态。 -func (s *alarmService) CancelAlarmSnooze(ctx context.Context, alarmID uint) error { +func (s *alarmService) CancelAlarmSnooze(ctx context.Context, alarmID uint32) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "CancelAlarmSnooze") err := s.alarmRepo.UpdateIgnoreStatus(serviceCtx, alarmID, false, nil) diff --git a/internal/domain/device/device_service.go b/internal/domain/device/device_service.go index bd892dd..db445c0 100644 --- a/internal/domain/device/device_service.go +++ b/internal/domain/device/device_service.go @@ -28,7 +28,7 @@ type Service interface { Switch(ctx context.Context, device *models.Device, action DeviceAction) error // Collect 用于发起对指定区域主控下的多个设备的批量采集请求。 - Collect(ctx context.Context, areaControllerID uint, devicesToCollect []*models.Device) error + Collect(ctx context.Context, areaControllerID uint32, devicesToCollect []*models.Device) error } // 设备操作指令通用结构(最外层) diff --git a/internal/domain/device/general_device_service.go b/internal/domain/device/general_device_service.go index 1a33e1c..4364735 100644 --- a/internal/domain/device/general_device_service.go +++ b/internal/domain/device/general_device_service.go @@ -133,7 +133,7 @@ func (g *GeneralDeviceService) Switch(ctx context.Context, device *models.Device } // Collect 实现了 Service 接口,用于发起对指定区域主控下的多个设备的批量采集请求。 -func (g *GeneralDeviceService) Collect(ctx context.Context, areaControllerID uint, devicesToCollect []*models.Device) error { +func (g *GeneralDeviceService) Collect(ctx context.Context, areaControllerID uint32, devicesToCollect []*models.Device) error { serviceCtx, logger := logs.Trace(ctx, g.ctx, "Collect") if len(devicesToCollect) == 0 { logger.Info("待采集设备列表为空,无需执行采集任务。") @@ -150,7 +150,7 @@ func (g *GeneralDeviceService) Collect(ctx context.Context, areaControllerID uin } // 2. 准备采集任务列表 - var childDeviceIDs []uint + var childDeviceIDs []uint32 var collectTasks []*proto.CollectTask for _, dev := range devicesToCollect { diff --git a/internal/domain/notify/notify.go b/internal/domain/notify/notify.go index 53f1168..0c95842 100644 --- a/internal/domain/notify/notify.go +++ b/internal/domain/notify/notify.go @@ -16,13 +16,13 @@ import ( // Service 定义了通知领域的核心业务逻辑接口 type Service interface { // SendBatchAlarm 向一批用户发送告警通知。它会并发地为每个用户执行带故障转移的发送逻辑。 - SendBatchAlarm(ctx context.Context, userIDs []uint, content notify.AlarmContent) error + SendBatchAlarm(ctx context.Context, userIDs []uint32, content notify.AlarmContent) error // BroadcastAlarm 向所有用户发送告警通知。它会并发地为每个用户执行带故障转移的发送逻辑。 BroadcastAlarm(ctx context.Context, content notify.AlarmContent) error // SendTestMessage 向指定用户发送一条测试消息,用于手动验证特定通知渠道的配置。 - SendTestMessage(ctx context.Context, userID uint, notifierType models.NotifierType) error + SendTestMessage(ctx context.Context, userID uint32, notifierType models.NotifierType) error } // failoverService 是 Service 接口的实现,提供了故障转移功能 @@ -32,7 +32,7 @@ type failoverService struct { notifiers map[models.NotifierType]notify.Notifier primaryNotifier notify.Notifier failureThreshold int - failureCounters *sync.Map // 使用 sync.Map 来安全地并发读写失败计数, key: userID (uint), value: counter (int) + failureCounters *sync.Map // 使用 sync.Map 来安全地并发读写失败计数, key: userID (uint32), value: counter (int) notificationRepo repository.NotificationRepository } @@ -67,7 +67,7 @@ func NewFailoverService( } // SendBatchAlarm 实现了向多个用户并发发送告警的功能 -func (s *failoverService) SendBatchAlarm(ctx context.Context, userIDs []uint, content notify.AlarmContent) error { +func (s *failoverService) SendBatchAlarm(ctx context.Context, userIDs []uint32, content notify.AlarmContent) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendBatchAlarm") var wg sync.WaitGroup var mu sync.Mutex @@ -77,7 +77,7 @@ func (s *failoverService) SendBatchAlarm(ctx context.Context, userIDs []uint, co for _, userID := range userIDs { wg.Add(1) - go func(id uint) { + go func(id uint32) { defer wg.Done() if err := s.sendAlarmToUser(serviceCtx, id, content); err != nil { mu.Lock() @@ -108,7 +108,7 @@ func (s *failoverService) BroadcastAlarm(ctx context.Context, content notify.Ala return fmt.Errorf("广播告警失败:查找所有用户时出错: %w", err) } - var userIDs []uint + var userIDs []uint32 for _, user := range users { userIDs = append(userIDs, user.ID) } @@ -119,7 +119,7 @@ func (s *failoverService) BroadcastAlarm(ctx context.Context, content notify.Ala } // sendAlarmToUser 是为单个用户发送告警的内部方法,包含了完整的故障转移逻辑 -func (s *failoverService) sendAlarmToUser(ctx context.Context, userID uint, content notify.AlarmContent) error { +func (s *failoverService) sendAlarmToUser(ctx context.Context, userID uint32, content notify.AlarmContent) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "sendAlarmToUser") user, err := s.userRepo.FindByID(serviceCtx, userID) if err != nil { @@ -187,7 +187,7 @@ func (s *failoverService) sendAlarmToUser(ctx context.Context, userID uint, cont } // SendTestMessage 实现了手动发送测试消息的功能 -func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, notifierType models.NotifierType) error { +func (s *failoverService) SendTestMessage(ctx context.Context, userID uint32, notifierType models.NotifierType) error { serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendTestMessage") user, err := s.userRepo.FindByID(serviceCtx, userID) if err != nil { @@ -261,7 +261,7 @@ func getAddressForNotifier(notifierType models.NotifierType, contact models.Cont // err: 如果发送失败,记录的错误信息 func (s *failoverService) recordNotificationAttempt( ctx context.Context, - userID uint, + userID uint32, notifierType models.NotifierType, content notify.AlarmContent, toAddress string, diff --git a/internal/domain/pig/pen_transfer_manager.go b/internal/domain/pig/pen_transfer_manager.go index 6b522a5..266db08 100644 --- a/internal/domain/pig/pen_transfer_manager.go +++ b/internal/domain/pig/pen_transfer_manager.go @@ -19,22 +19,22 @@ type PigPenTransferManager interface { LogTransfer(ctx context.Context, tx *gorm.DB, log *models.PigTransferLog) error // GetPenByID 用于获取猪栏的详细信息,供上层服务进行业务校验。 - GetPenByID(ctx context.Context, tx *gorm.DB, penID uint) (*models.Pen, error) + GetPenByID(ctx context.Context, tx *gorm.DB, penID uint32) (*models.Pen, error) // GetPensByBatchID 获取一个猪群当前关联的所有猪栏。 - GetPensByBatchID(ctx context.Context, tx *gorm.DB, batchID uint) ([]*models.Pen, error) + GetPensByBatchID(ctx context.Context, tx *gorm.DB, batchID uint32) ([]*models.Pen, error) // UpdatePenFields 更新一个猪栏的指定字段。 - UpdatePenFields(ctx context.Context, tx *gorm.DB, penID uint, updates map[string]interface{}) error + UpdatePenFields(ctx context.Context, tx *gorm.DB, penID uint32, updates map[string]interface{}) error // GetCurrentPigsInPen 通过汇总猪只迁移日志,计算给定猪栏中的当前猪只数量。 - GetCurrentPigsInPen(ctx context.Context, tx *gorm.DB, penID uint) (int, error) + GetCurrentPigsInPen(ctx context.Context, tx *gorm.DB, penID uint32) (int, error) // GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数 - GetTotalPigsInPensForBatchTx(ctx context.Context, tx *gorm.DB, batchID uint) (int, error) + GetTotalPigsInPensForBatchTx(ctx context.Context, tx *gorm.DB, batchID uint32) (int, error) // ReleasePen 将猪栏的猪群归属移除,并将其状态标记为空闲。 - ReleasePen(ctx context.Context, tx *gorm.DB, penID uint) error + ReleasePen(ctx context.Context, tx *gorm.DB, penID uint32) error } // pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。 @@ -63,25 +63,25 @@ func (s *pigPenTransferManager) LogTransfer(ctx context.Context, tx *gorm.DB, lo } // GetPenByID 实现了获取猪栏信息的逻辑。 -func (s *pigPenTransferManager) GetPenByID(ctx context.Context, tx *gorm.DB, penID uint) (*models.Pen, error) { +func (s *pigPenTransferManager) GetPenByID(ctx context.Context, tx *gorm.DB, penID uint32) (*models.Pen, error) { managerCtx := logs.AddFuncName(ctx, s.ctx, "GetPenByID") return s.penRepo.GetPenByIDTx(managerCtx, tx, penID) } // GetPensByBatchID 实现了获取猪群关联猪栏列表的逻辑。 -func (s *pigPenTransferManager) GetPensByBatchID(ctx context.Context, tx *gorm.DB, batchID uint) ([]*models.Pen, error) { +func (s *pigPenTransferManager) GetPensByBatchID(ctx context.Context, tx *gorm.DB, batchID uint32) ([]*models.Pen, error) { managerCtx := logs.AddFuncName(ctx, s.ctx, "GetPensByBatchID") return s.penRepo.GetPensByBatchIDTx(managerCtx, tx, batchID) } // UpdatePenFields 实现了更新猪栏字段的逻辑。 -func (s *pigPenTransferManager) UpdatePenFields(ctx context.Context, tx *gorm.DB, penID uint, updates map[string]interface{}) error { +func (s *pigPenTransferManager) UpdatePenFields(ctx context.Context, tx *gorm.DB, penID uint32, updates map[string]interface{}) error { managerCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePenFields") return s.penRepo.UpdatePenFieldsTx(managerCtx, tx, penID, updates) } // GetCurrentPigsInPen 实现了计算猪栏当前猪只数量的逻辑。 -func (s *pigPenTransferManager) GetCurrentPigsInPen(ctx context.Context, tx *gorm.DB, penID uint) (int, error) { +func (s *pigPenTransferManager) GetCurrentPigsInPen(ctx context.Context, tx *gorm.DB, penID uint32) (int, error) { managerCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentPigsInPen") // 1. 通过猪栏ID查出所属猪群信息 pen, err := s.penRepo.GetPenByIDTx(managerCtx, tx, penID) @@ -137,7 +137,7 @@ func (s *pigPenTransferManager) GetCurrentPigsInPen(ctx context.Context, tx *gor // GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数 // 该方法通过遍历猪群下的每个猪栏,并调用 GetCurrentPigsInPen 来累加存栏数。 -func (s *pigPenTransferManager) GetTotalPigsInPensForBatchTx(ctx context.Context, tx *gorm.DB, batchID uint) (int, error) { +func (s *pigPenTransferManager) GetTotalPigsInPensForBatchTx(ctx context.Context, tx *gorm.DB, batchID uint32) (int, error) { managerCtx := logs.AddFuncName(ctx, s.ctx, "GetTotalPigsInPensForBatchTx") // 1. 获取该批次下所有猪栏的列表 pensInBatch, err := s.GetPensByBatchID(managerCtx, tx, batchID) @@ -160,7 +160,7 @@ func (s *pigPenTransferManager) GetTotalPigsInPensForBatchTx(ctx context.Context // ReleasePen 将猪栏的猪群归属移除,并将其状态标记为空闲。 // 此操作通常在猪栏被清空后调用。 -func (s *pigPenTransferManager) ReleasePen(ctx context.Context, tx *gorm.DB, penID uint) error { +func (s *pigPenTransferManager) ReleasePen(ctx context.Context, tx *gorm.DB, penID uint32) error { managerCtx := logs.AddFuncName(ctx, s.ctx, "ReleasePen") // 1. 获取猪栏信息 pen, err := s.penRepo.GetPenByIDTx(managerCtx, tx, penID) diff --git a/internal/domain/pig/pig_batch_service.go b/internal/domain/pig/pig_batch_service.go index f103efc..d4deb40 100644 --- a/internal/domain/pig/pig_batch_service.go +++ b/internal/domain/pig/pig_batch_service.go @@ -38,58 +38,58 @@ var ( // 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。 type PigBatchService interface { // CreatePigBatch 创建猪批次,并记录初始日志。 - CreatePigBatch(ctx context.Context, operatorID uint, batch *models.PigBatch) (*models.PigBatch, error) + CreatePigBatch(ctx context.Context, operatorID uint32, batch *models.PigBatch) (*models.PigBatch, error) // GetPigBatch 获取单个猪批次。 - GetPigBatch(ctx context.Context, id uint) (*models.PigBatch, error) + GetPigBatch(ctx context.Context, id uint32) (*models.PigBatch, error) // UpdatePigBatch 更新猪批次信息。 UpdatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, error) // DeletePigBatch 删除猪批次,包含业务规则校验。 - DeletePigBatch(ctx context.Context, id uint) error + DeletePigBatch(ctx context.Context, id uint32) error // ListPigBatches 批量查询猪批次。 ListPigBatches(ctx context.Context, isActive *bool) ([]*models.PigBatch, error) // AssignEmptyPensToBatch 为猪群分配空栏 - AssignEmptyPensToBatch(ctx context.Context, batchID uint, penIDs []uint, operatorID uint) error + AssignEmptyPensToBatch(ctx context.Context, batchID uint32, penIDs []uint32, operatorID uint32) error // MovePigsIntoPen 将猪只从“虚拟库存”移入指定猪栏 - MovePigsIntoPen(ctx context.Context, batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error + MovePigsIntoPen(ctx context.Context, batchID uint32, toPenID uint32, quantity int, operatorID uint32, remarks string) error // ReclassifyPenToNewBatch 连猪带栏,整体划拨到另一个猪群 - ReclassifyPenToNewBatch(ctx context.Context, fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error + ReclassifyPenToNewBatch(ctx context.Context, fromBatchID uint32, toBatchID uint32, penID uint32, operatorID uint32, remarks string) error // RemoveEmptyPenFromBatch 将一个猪栏移除出猪群,此方法需要在猪栏为空的情况下执行。 - RemoveEmptyPenFromBatch(ctx context.Context, batchID uint, penID uint) error + RemoveEmptyPenFromBatch(ctx context.Context, batchID uint32, penID uint32) error // GetCurrentPigQuantity 获取指定猪批次的当前猪只数量。 - GetCurrentPigQuantity(ctx context.Context, batchID uint) (int, error) + GetCurrentPigQuantity(ctx context.Context, batchID uint32) (int, error) // GetCurrentPigsInPen 获取指定猪栏的当前存栏量。 - GetCurrentPigsInPen(ctx context.Context, penID uint) (int, error) + GetCurrentPigsInPen(ctx context.Context, penID uint32) (int, error) // GetTotalPigsInPensForBatch 获取指定猪群下所有猪栏的当前总存栏数 - GetTotalPigsInPensForBatch(ctx context.Context, batchID uint) (int, error) + GetTotalPigsInPensForBatch(ctx context.Context, batchID uint32) (int, error) - UpdatePigBatchQuantity(ctx context.Context, operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error + UpdatePigBatchQuantity(ctx context.Context, operatorID uint32, batchID uint32, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error // ---交易子服务--- // SellPigs 处理卖猪的业务逻辑。 - SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error + SellPigs(ctx context.Context, batchID uint32, penID uint32, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint32) error // BuyPigs 处理买猪的业务逻辑。 - BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error + BuyPigs(ctx context.Context, batchID uint32, penID uint32, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint32) error // ---调栏子服务 --- - TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error - TransferPigsWithinBatch(ctx context.Context, batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error + TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint32, destBatchID uint32, fromPenID uint32, toPenID uint32, quantity uint32, operatorID uint32, remarks string) error + TransferPigsWithinBatch(ctx context.Context, batchID uint32, fromPenID uint32, toPenID uint32, quantity uint32, operatorID uint32, remarks string) error // --- 病猪管理相关方法 --- // RecordSickPigs 记录新增病猪事件。 - RecordSickPigs(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error + RecordSickPigs(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error // RecordSickPigRecovery 记录病猪康复事件。 - RecordSickPigRecovery(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error + RecordSickPigRecovery(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error // RecordSickPigDeath 记录病猪死亡事件。 - RecordSickPigDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error + RecordSickPigDeath(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error // RecordSickPigCull 记录病猪淘汰事件。 - RecordSickPigCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error + RecordSickPigCull(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error // --- 正常猪只管理相关方法 --- // RecordDeath 记录正常猪只死亡事件。 - RecordDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error + RecordDeath(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, happenedAt time.Time, remarks string) error // RecordCull 记录正常猪只淘汰事件。 - RecordCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error + RecordCull(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, happenedAt time.Time, remarks string) error } // pigBatchService 是 PigBatchService 接口的具体实现。 diff --git a/internal/domain/pig/pig_batch_service_method.go b/internal/domain/pig/pig_batch_service_method.go index 9f070a2..289e3c1 100644 --- a/internal/domain/pig/pig_batch_service_method.go +++ b/internal/domain/pig/pig_batch_service_method.go @@ -15,7 +15,7 @@ import ( // --- 领域服务实现 --- // CreatePigBatch 实现了创建猪批次的逻辑,并同时创建初始批次日志。 -func (s *pigBatchService) CreatePigBatch(ctx context.Context, operatorID uint, batch *models.PigBatch) (*models.PigBatch, error) { +func (s *pigBatchService) CreatePigBatch(ctx context.Context, operatorID uint32, batch *models.PigBatch) (*models.PigBatch, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePigBatch") // 业务规则可以在这里添加,例如检查批次号是否唯一等 @@ -57,7 +57,7 @@ func (s *pigBatchService) CreatePigBatch(ctx context.Context, operatorID uint, b } // GetPigBatch 实现了获取单个猪批次的逻辑。 -func (s *pigBatchService) GetPigBatch(ctx context.Context, id uint) (*models.PigBatch, error) { +func (s *pigBatchService) GetPigBatch(ctx context.Context, id uint32) (*models.PigBatch, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigBatch") batch, err := s.pigBatchRepo.GetPigBatchByID(serviceCtx, id) if err != nil { @@ -84,7 +84,7 @@ func (s *pigBatchService) UpdatePigBatch(ctx context.Context, batch *models.PigB } // DeletePigBatch 实现了删除猪批次的逻辑,并包含业务规则校验。 -func (s *pigBatchService) DeletePigBatch(ctx context.Context, id uint) error { +func (s *pigBatchService) DeletePigBatch(ctx context.Context, id uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigBatch") return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { // 1. 获取猪批次信息 @@ -135,7 +135,7 @@ func (s *pigBatchService) ListPigBatches(ctx context.Context, isActive *bool) ([ } // GetCurrentPigQuantity 实现了获取指定猪批次的当前猪只数量的逻辑。 -func (s *pigBatchService) GetCurrentPigQuantity(ctx context.Context, batchID uint) (int, error) { +func (s *pigBatchService) GetCurrentPigQuantity(ctx context.Context, batchID uint32) (int, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentPigQuantity") var getErr error var quantity int @@ -150,7 +150,7 @@ func (s *pigBatchService) GetCurrentPigQuantity(ctx context.Context, batchID uin } // getCurrentPigQuantityTx 实现了获取指定猪批次的当前猪只数量的逻辑。 -func (s *pigBatchService) getCurrentPigQuantityTx(ctx context.Context, tx *gorm.DB, batchID uint) (int, error) { +func (s *pigBatchService) getCurrentPigQuantityTx(ctx context.Context, tx *gorm.DB, batchID uint32) (int, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "getCurrentPigQuantityTx") // 1. 获取猪批次初始信息 batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID) @@ -175,14 +175,14 @@ func (s *pigBatchService) getCurrentPigQuantityTx(ctx context.Context, tx *gorm. return lastLog.AfterCount, nil } -func (s *pigBatchService) UpdatePigBatchQuantity(ctx context.Context, operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error { +func (s *pigBatchService) UpdatePigBatchQuantity(ctx context.Context, operatorID uint32, batchID uint32, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigBatchQuantity") return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { return s.updatePigBatchQuantityTx(serviceCtx, tx, operatorID, batchID, changeType, changeAmount, changeReason, happenedAt) }) } -func (s *pigBatchService) updatePigBatchQuantityTx(ctx context.Context, tx *gorm.DB, operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error { +func (s *pigBatchService) updatePigBatchQuantityTx(ctx context.Context, tx *gorm.DB, operatorID uint32, batchID uint32, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "updatePigBatchQuantityTx") lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(serviceCtx, tx, batchID) if err != nil { diff --git a/internal/domain/pig/pig_batch_service_pen_transfer.go b/internal/domain/pig/pig_batch_service_pen_transfer.go index e41f06c..292955e 100644 --- a/internal/domain/pig/pig_batch_service_pen_transfer.go +++ b/internal/domain/pig/pig_batch_service_pen_transfer.go @@ -14,7 +14,7 @@ import ( ) // executeTransferAndLog 是一个私有辅助方法,用于封装创建和记录迁移日志的通用逻辑。 -func (s *pigBatchService) executeTransferAndLog(ctx context.Context, tx *gorm.DB, fromBatchID, toBatchID, fromPenID, toPenID uint, quantity int, transferType models.PigTransferType, operatorID uint, remarks string) error { +func (s *pigBatchService) executeTransferAndLog(ctx context.Context, tx *gorm.DB, fromBatchID, toBatchID, fromPenID, toPenID uint32, quantity int, transferType models.PigTransferType, operatorID uint32, remarks string) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "executeTransferAndLog") // 通用校验:任何调出操作都不能超过源猪栏的当前存栏数 @@ -67,7 +67,7 @@ func (s *pigBatchService) executeTransferAndLog(ctx context.Context, tx *gorm.DB } // TransferPigsWithinBatch 实现了同一个猪群内部的调栏业务。 -func (s *pigBatchService) TransferPigsWithinBatch(ctx context.Context, batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error { +func (s *pigBatchService) TransferPigsWithinBatch(ctx context.Context, batchID uint32, fromPenID uint32, toPenID uint32, quantity uint32, operatorID uint32, remarks string) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "TransferPigsWithinBatch") if fromPenID == toPenID { return errors.New("源猪栏和目标猪栏不能相同") @@ -106,7 +106,7 @@ func (s *pigBatchService) TransferPigsWithinBatch(ctx context.Context, batchID u } // TransferPigsAcrossBatches 实现了跨猪群的调栏业务。 -func (s *pigBatchService) TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error { +func (s *pigBatchService) TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint32, destBatchID uint32, fromPenID uint32, toPenID uint32, quantity uint32, operatorID uint32, remarks string) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "TransferPigsAcrossBatches") if sourceBatchID == destBatchID { return errors.New("源猪群和目标猪群不能相同") @@ -167,7 +167,7 @@ func (s *pigBatchService) TransferPigsAcrossBatches(ctx context.Context, sourceB } // AssignEmptyPensToBatch 为猪群分配空栏 -func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID uint, penIDs []uint, operatorID uint) error { +func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID uint32, penIDs []uint32, operatorID uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "AssignEmptyPensToBatch") return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { // 1. 验证猪批次是否存在且活跃 @@ -204,6 +204,7 @@ func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID ui updates := map[string]interface{}{ "pig_batch_id": &batchID, "status": models.PenStatusOccupied, + "operator_id": operatorID, } if err := s.transferSvc.UpdatePenFields(serviceCtx, tx, penID, updates); err != nil { return fmt.Errorf("分配猪栏 %d 失败: %w", penID, err) @@ -215,7 +216,7 @@ func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID ui } // MovePigsIntoPen 将猪只从“虚拟库存”移入指定猪栏 -func (s *pigBatchService) MovePigsIntoPen(ctx context.Context, batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error { +func (s *pigBatchService) MovePigsIntoPen(ctx context.Context, batchID uint32, toPenID uint32, quantity int, operatorID uint32, remarks string) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "MovePigsIntoPen") if quantity <= 0 { return errors.New("迁移数量必须大于零") @@ -287,7 +288,7 @@ func (s *pigBatchService) MovePigsIntoPen(ctx context.Context, batchID uint, toP } // ReclassifyPenToNewBatch 连猪带栏,整体划拨到另一个猪群 -func (s *pigBatchService) ReclassifyPenToNewBatch(ctx context.Context, fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error { +func (s *pigBatchService) ReclassifyPenToNewBatch(ctx context.Context, fromBatchID uint32, toBatchID uint32, penID uint32, operatorID uint32, remarks string) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "ReclassifyPenToNewBatch") if fromBatchID == toBatchID { return errors.New("源猪群和目标猪群不能相同") @@ -392,7 +393,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(ctx context.Context, fromBatch }) } -func (s *pigBatchService) RemoveEmptyPenFromBatch(ctx context.Context, batchID uint, penID uint) error { +func (s *pigBatchService) RemoveEmptyPenFromBatch(ctx context.Context, batchID uint32, penID uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "RemoveEmptyPenFromBatch") return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { // 1. 检查猪批次是否存在且活跃 @@ -438,7 +439,7 @@ func (s *pigBatchService) RemoveEmptyPenFromBatch(ctx context.Context, batchID u }) } -func (s *pigBatchService) GetCurrentPigsInPen(ctx context.Context, penID uint) (int, error) { +func (s *pigBatchService) GetCurrentPigsInPen(ctx context.Context, penID uint32) (int, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentPigsInPen") var currentPigs int err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { @@ -453,7 +454,7 @@ func (s *pigBatchService) GetCurrentPigsInPen(ctx context.Context, penID uint) ( } // GetTotalPigsInPensForBatch 实现了获取指定猪群下所有猪栏的当前总存栏数的逻辑。 -func (s *pigBatchService) GetTotalPigsInPensForBatch(ctx context.Context, batchID uint) (int, error) { +func (s *pigBatchService) GetTotalPigsInPensForBatch(ctx context.Context, batchID uint32) (int, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetTotalPigsInPensForBatch") var totalPigs int err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { diff --git a/internal/domain/pig/pig_batch_service_pig_sick.go b/internal/domain/pig/pig_batch_service_pig_sick.go index 2fca6f4..db1a3bf 100644 --- a/internal/domain/pig/pig_batch_service_pig_sick.go +++ b/internal/domain/pig/pig_batch_service_pig_sick.go @@ -13,7 +13,7 @@ import ( ) // RecordSickPigs 记录新增病猪事件。 -func (s *pigBatchService) RecordSickPigs(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordSickPigs(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordSickPigs") if quantity <= 0 { return errors.New("新增病猪数量必须大于0") @@ -89,7 +89,7 @@ func (s *pigBatchService) RecordSickPigs(ctx context.Context, operatorID uint, b } // RecordSickPigRecovery 记录病猪康复事件。 -func (s *pigBatchService) RecordSickPigRecovery(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordSickPigRecovery(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordSickPigRecovery") if quantity <= 0 { return errors.New("康复猪只数量必须大于0") @@ -158,7 +158,7 @@ func (s *pigBatchService) RecordSickPigRecovery(ctx context.Context, operatorID } // RecordSickPigDeath 记录病猪死亡事件。 -func (s *pigBatchService) RecordSickPigDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordSickPigDeath(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordSickPigDeath") if quantity <= 0 { return errors.New("死亡猪只数量必须大于0") @@ -254,7 +254,7 @@ func (s *pigBatchService) RecordSickPigDeath(ctx context.Context, operatorID uin } // RecordSickPigCull 记录病猪淘汰事件。 -func (s *pigBatchService) RecordSickPigCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordSickPigCull(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordSickPigCull") if quantity <= 0 { return errors.New("淘汰猪只数量必须大于0") @@ -350,7 +350,7 @@ func (s *pigBatchService) RecordSickPigCull(ctx context.Context, operatorID uint } // RecordDeath 记录正常猪只死亡事件。 -func (s *pigBatchService) RecordDeath(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordDeath(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, happenedAt time.Time, remarks string) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordDeath") if quantity <= 0 { return errors.New("死亡猪只数量必须大于0") @@ -421,7 +421,7 @@ func (s *pigBatchService) RecordDeath(ctx context.Context, operatorID uint, batc } // RecordCull 记录正常猪只淘汰事件。 -func (s *pigBatchService) RecordCull(ctx context.Context, operatorID uint, batchID uint, penID uint, quantity int, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordCull(ctx context.Context, operatorID uint32, batchID uint32, penID uint32, quantity int, happenedAt time.Time, remarks string) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordCull") if quantity <= 0 { return errors.New("淘汰猪只数量必须大于0") diff --git a/internal/domain/pig/pig_batch_service_pig_trade.go b/internal/domain/pig/pig_batch_service_pig_trade.go index c6a14b2..0e60283 100644 --- a/internal/domain/pig/pig_batch_service_pig_trade.go +++ b/internal/domain/pig/pig_batch_service_pig_trade.go @@ -13,7 +13,7 @@ import ( ) // SellPigs 处理批量销售猪的业务逻辑。 -func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { +func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint32, penID uint32, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "SellPigs") return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { if quantity <= 0 { @@ -85,7 +85,7 @@ func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint } // BuyPigs 处理批量购买猪的业务逻辑。 -func (s *pigBatchService) BuyPigs(ctx context.Context, batchID uint, penID uint, quantity int, unitPrice float32, totalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { +func (s *pigBatchService) BuyPigs(ctx context.Context, batchID uint32, penID uint32, quantity int, unitPrice float32, totalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "BuyPigs") return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { if quantity <= 0 { diff --git a/internal/domain/pig/pig_sick_manager.go b/internal/domain/pig/pig_sick_manager.go index fd93b29..be48017 100644 --- a/internal/domain/pig/pig_sick_manager.go +++ b/internal/domain/pig/pig_sick_manager.go @@ -21,7 +21,7 @@ type SickPigManager interface { ProcessSickPigLog(ctx context.Context, tx *gorm.DB, log *models.PigSickLog) error // GetCurrentSickPigCount 获取指定批次当前患病猪只的总数 - GetCurrentSickPigCount(ctx context.Context, tx *gorm.DB, batchID uint) (int, error) + GetCurrentSickPigCount(ctx context.Context, tx *gorm.DB, batchID uint32) (int, error) } // sickPigManager 是 SickPigManager 接口的具体实现。 @@ -122,7 +122,7 @@ func (s *sickPigManager) ProcessSickPigLog(ctx context.Context, tx *gorm.DB, log return nil } -func (s *sickPigManager) GetCurrentSickPigCount(ctx context.Context, tx *gorm.DB, batchID uint) (int, error) { +func (s *sickPigManager) GetCurrentSickPigCount(ctx context.Context, tx *gorm.DB, batchID uint32) (int, error) { managerCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentSickPigCount") lastLog, err := s.sickLogRepo.GetLastLogByBatchTx(managerCtx, tx, batchID) if err != nil { diff --git a/internal/domain/plan/analysis_plan_task_manager.go b/internal/domain/plan/analysis_plan_task_manager.go index 05fb741..add3f20 100644 --- a/internal/domain/plan/analysis_plan_task_manager.go +++ b/internal/domain/plan/analysis_plan_task_manager.go @@ -18,10 +18,10 @@ type AnalysisPlanTaskManager interface { Refresh(ctx context.Context) error // CreateOrUpdateTrigger 为给定的 planID 创建其关联的触发任务。 // 如果触发器已存在,会根据计划类型更新其执行时间。 - CreateOrUpdateTrigger(ctx context.Context, planID uint) error + CreateOrUpdateTrigger(ctx context.Context, planID uint32) error // EnsureAnalysisTaskDefinition 确保计划的分析任务定义存在于 tasks 表中。 // 如果不存在,则会自动创建。此方法不涉及待执行队列。 - EnsureAnalysisTaskDefinition(ctx context.Context, planID uint) error + EnsureAnalysisTaskDefinition(ctx context.Context, planID uint32) error } // analysisPlanTaskManagerImpl 负责管理分析计划的触发器任务。 @@ -82,7 +82,7 @@ func (m *analysisPlanTaskManagerImpl) Refresh(ctx context.Context) error { // CreateOrUpdateTrigger 为给定的 planID 创建其关联的触发任务。 // 如果触发器已存在,会根据计划类型更新其执行时间。 -func (m *analysisPlanTaskManagerImpl) CreateOrUpdateTrigger(ctx context.Context, planID uint) error { +func (m *analysisPlanTaskManagerImpl) CreateOrUpdateTrigger(ctx context.Context, planID uint32) error { managerCtx, logger := logs.Trace(ctx, m.ctx, "CreateOrUpdateTrigger") m.mu.Lock() defer m.mu.Unlock() @@ -138,7 +138,7 @@ func (m *analysisPlanTaskManagerImpl) CreateOrUpdateTrigger(ctx context.Context, // EnsureAnalysisTaskDefinition 确保计划的分析任务定义存在于 tasks 表中。 // 如果不存在,则会自动创建。此方法不涉及待执行队列。 -func (m *analysisPlanTaskManagerImpl) EnsureAnalysisTaskDefinition(ctx context.Context, planID uint) error { +func (m *analysisPlanTaskManagerImpl) EnsureAnalysisTaskDefinition(ctx context.Context, planID uint32) error { managerCtx, logger := logs.Trace(ctx, m.ctx, "EnsureAnalysisTaskDefinition") m.mu.Lock() defer m.mu.Unlock() @@ -170,7 +170,7 @@ func (m *analysisPlanTaskManagerImpl) EnsureAnalysisTaskDefinition(ctx context.C // --- 内部私有方法 --- // getRefreshData 从数据库获取刷新所需的所有数据。 -func (m *analysisPlanTaskManagerImpl) getRefreshData(ctx context.Context) (runnablePlans []*models.Plan, invalidPlanIDs []uint, pendingTasks []models.PendingTask, err error) { +func (m *analysisPlanTaskManagerImpl) getRefreshData(ctx context.Context) (runnablePlans []*models.Plan, invalidPlanIDs []uint32, pendingTasks []models.PendingTask, err error) { managerCtx, logger := logs.Trace(ctx, m.ctx, "getRefreshData") runnablePlans, err = m.planRepo.FindRunnablePlans(managerCtx) if err != nil { @@ -183,7 +183,7 @@ func (m *analysisPlanTaskManagerImpl) getRefreshData(ctx context.Context) (runna logger.Errorf("获取失效计划列表失败: %v", err) return } - invalidPlanIDs = make([]uint, len(invalidPlans)) + invalidPlanIDs = make([]uint32, len(invalidPlans)) for i, p := range invalidPlans { invalidPlanIDs[i] = p.ID } @@ -197,19 +197,19 @@ func (m *analysisPlanTaskManagerImpl) getRefreshData(ctx context.Context) (runna } // cleanupInvalidTasks 清理所有与失效计划相关的待执行任务。 -func (m *analysisPlanTaskManagerImpl) cleanupInvalidTasks(ctx context.Context, invalidPlanIDs []uint, allPendingTasks []models.PendingTask) error { +func (m *analysisPlanTaskManagerImpl) cleanupInvalidTasks(ctx context.Context, invalidPlanIDs []uint32, allPendingTasks []models.PendingTask) error { managerCtx, logger := logs.Trace(ctx, m.ctx, "cleanupInvalidTasks") if len(invalidPlanIDs) == 0 { return nil // 没有需要清理的计划 } - invalidPlanIDSet := make(map[uint]struct{}, len(invalidPlanIDs)) + invalidPlanIDSet := make(map[uint32]struct{}, len(invalidPlanIDs)) for _, id := range invalidPlanIDs { invalidPlanIDSet[id] = struct{}{} } - var tasksToDeleteIDs []uint - var logsToCancelIDs []uint + var tasksToDeleteIDs []uint32 + var logsToCancelIDs []uint32 for _, pt := range allPendingTasks { if pt.Task == nil { // 防御性编程,确保 Task 被预加载 @@ -245,7 +245,7 @@ func (m *analysisPlanTaskManagerImpl) cleanupInvalidTasks(ctx context.Context, i func (m *analysisPlanTaskManagerImpl) addOrUpdateTriggers(ctx context.Context, runnablePlans []*models.Plan, allPendingTasks []models.PendingTask) error { managerCtx, logger := logs.Trace(ctx, m.ctx, "addOrUpdateTriggers") // 创建一个映射,存放所有已在队列中的计划触发器 - pendingTriggersMap := make(map[uint]models.PendingTask) + pendingTriggersMap := make(map[uint32]models.PendingTask) for _, pt := range allPendingTasks { if pt.Task != nil && pt.Task.Type == models.TaskPlanAnalysis { pendingTriggersMap[pt.Task.PlanID] = pt diff --git a/internal/domain/plan/plan_execution_manager.go b/internal/domain/plan/plan_execution_manager.go index fc76e4c..8bc9566 100644 --- a/internal/domain/plan/plan_execution_manager.go +++ b/internal/domain/plan/plan_execution_manager.go @@ -25,21 +25,21 @@ type ExecutionManager interface { // ProgressTracker 仅用于在内存中提供计划执行的并发锁 type ProgressTracker struct { mu sync.Mutex - cond *sync.Cond // 用于实现阻塞锁 - runningPlans map[uint]bool // key: planExecutionLogID, value: true (用作内存锁) + cond *sync.Cond // 用于实现阻塞锁 + runningPlans map[uint32]bool // key: planExecutionLogID, value: true (用作内存锁) } // NewProgressTracker 创建一个新的进度跟踪器 func NewProgressTracker() *ProgressTracker { t := &ProgressTracker{ - runningPlans: make(map[uint]bool), + runningPlans: make(map[uint32]bool), } t.cond = sync.NewCond(&t.mu) return t } // TryLock (非阻塞) 尝试锁定一个计划。如果计划未被锁定,则锁定并返回 true。 -func (t *ProgressTracker) TryLock(planLogID uint) bool { +func (t *ProgressTracker) TryLock(planLogID uint32) bool { t.mu.Lock() defer t.mu.Unlock() if t.runningPlans[planLogID] { @@ -50,7 +50,7 @@ func (t *ProgressTracker) TryLock(planLogID uint) bool { } // Lock (阻塞) 获取一个计划的执行锁。如果锁已被占用,则会一直等待直到锁被释放。 -func (t *ProgressTracker) Lock(planLogID uint) { +func (t *ProgressTracker) Lock(planLogID uint32) { t.mu.Lock() // 当计划正在运行时,调用 t.cond.Wait() 会原子地解锁 mu 并挂起当前协程。 // 当被唤醒时,它会重新锁定 mu 并再次检查循环条件。 @@ -63,7 +63,7 @@ func (t *ProgressTracker) Lock(planLogID uint) { } // Unlock 解锁一个计划,并唤醒所有正在等待此锁的协程。 -func (t *ProgressTracker) Unlock(planLogID uint) { +func (t *ProgressTracker) Unlock(planLogID uint32) { t.mu.Lock() defer t.mu.Unlock() delete(t.runningPlans, planLogID) @@ -72,10 +72,10 @@ func (t *ProgressTracker) Unlock(planLogID uint) { } // GetRunningPlanIDs 获取当前所有正在执行的计划ID列表 -func (t *ProgressTracker) GetRunningPlanIDs() []uint { +func (t *ProgressTracker) GetRunningPlanIDs() []uint32 { t.mu.Lock() defer t.mu.Unlock() - ids := make([]uint, 0, len(t.runningPlans)) + ids := make([]uint32, 0, len(t.runningPlans)) for id := range t.runningPlans { ids = append(ids, id) } @@ -214,7 +214,7 @@ func (s *planExecutionManagerImpl) claimAndSubmit(ctx context.Context) { } // handleRequeue 同步地、安全地将一个无法立即执行的任务放回队列。 -func (s *planExecutionManagerImpl) handleRequeue(ctx context.Context, planExecutionLogID uint, taskToRequeue *models.PendingTask) { +func (s *planExecutionManagerImpl) handleRequeue(ctx context.Context, planExecutionLogID uint32, taskToRequeue *models.PendingTask) { managerCtx, logger := logs.Trace(ctx, s.ctx, "handleRequeue") logger.Warnf("计划 %d 正在执行,任务 %d (TaskID: %d) 将等待并重新入队...", planExecutionLogID, taskToRequeue.ID, taskToRequeue.TaskID) @@ -308,7 +308,7 @@ func (s *planExecutionManagerImpl) analysisPlan(ctx context.Context, claimedLog // 创建Plan执行记录 // 从任务的 Parameters 中解析出真实的 PlanID var params struct { - PlanID uint `json:"plan_id"` + PlanID uint32 `json:"plan_id"` } if err := claimedLog.Task.ParseParameters(¶ms); err != nil { logger.Errorf("解析任务参数中的计划ID失败,日志ID: %d, 错误: %v", claimedLog.ID, err) @@ -390,7 +390,7 @@ func (s *planExecutionManagerImpl) updateTaskExecutionLogStatus(ctx context.Cont } // handlePlanTermination 集中处理计划的终止逻辑(失败或取消) -func (s *planExecutionManagerImpl) handlePlanTermination(ctx context.Context, planLogID uint, reason string) { +func (s *planExecutionManagerImpl) handlePlanTermination(ctx context.Context, planLogID uint32, reason string) { managerCtx, logger := logs.Trace(ctx, s.ctx, "handlePlanTermination") // 1. 从待执行队列中删除所有相关的子任务 if err := s.pendingTaskRepo.DeletePendingTasksByPlanLogID(managerCtx, planLogID); err != nil { @@ -434,7 +434,7 @@ func (s *planExecutionManagerImpl) handlePlanTermination(ctx context.Context, pl } // handlePlanCompletion 集中处理计划成功完成后的所有逻辑 -func (s *planExecutionManagerImpl) handlePlanCompletion(ctx context.Context, planLogID uint) { +func (s *planExecutionManagerImpl) handlePlanCompletion(ctx context.Context, planLogID uint32) { managerCtx, logger := logs.Trace(ctx, s.ctx, "handlePlanCompletion") logger.Infof("计划执行 %d 的所有任务已完成,开始处理计划完成逻辑...", planLogID) diff --git a/internal/domain/plan/plan_service.go b/internal/domain/plan/plan_service.go index ea61a9f..48c5cf7 100644 --- a/internal/domain/plan/plan_service.go +++ b/internal/domain/plan/plan_service.go @@ -41,17 +41,17 @@ type Service interface { // CreatePlan 创建一个新的计划 CreatePlan(ctx context.Context, plan *models.Plan) (*models.Plan, error) // GetPlanByID 根据ID获取计划详情 - GetPlanByID(ctx context.Context, id uint) (*models.Plan, error) + GetPlanByID(ctx context.Context, id uint32) (*models.Plan, error) // ListPlans 获取计划列表,支持过滤和分页 ListPlans(ctx context.Context, opts repository.ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error) // UpdatePlan 更新计划, wantPlanType 表示期望被修改的计划是什么类型 UpdatePlan(ctx context.Context, plan *models.Plan, wantPlanType models.PlanType) (*models.Plan, error) // DeletePlan 删除计划(软删除) - DeletePlan(ctx context.Context, id uint) error + DeletePlan(ctx context.Context, id uint32) error // StartPlan 启动计划 - StartPlan(ctx context.Context, id uint) error + StartPlan(ctx context.Context, id uint32) error // StopPlan 停止计划 - StopPlan(ctx context.Context, id uint) error + StopPlan(ctx context.Context, id uint32) error } // planServiceImpl 是 Service 接口的具体实现。 @@ -150,7 +150,7 @@ func (s *planServiceImpl) CreatePlan(ctx context.Context, planToCreate *models.P // 优化:无需查询完整的设备对象,只需构建包含ID的结构体即可建立关联 devices := make([]models.Device, len(deviceIDs)) for i, id := range deviceIDs { - devices[i] = models.Device{Model: gorm.Model{ID: id}} + devices[i] = models.Device{Model: models.Model{ID: id}} } taskModel.Devices = devices } @@ -174,7 +174,7 @@ func (s *planServiceImpl) CreatePlan(ctx context.Context, planToCreate *models.P } // GetPlanByID 根据ID获取计划详情 -func (s *planServiceImpl) GetPlanByID(ctx context.Context, id uint) (*models.Plan, error) { +func (s *planServiceImpl) GetPlanByID(ctx context.Context, id uint32) (*models.Plan, error) { planCtx, logger := logs.Trace(ctx, s.ctx, "GetPlanByID") const actionType = "领域层:获取计划详情" @@ -262,7 +262,7 @@ func (s *planServiceImpl) UpdatePlan(ctx context.Context, planToUpdate *models.P // 优化:无需查询完整的设备对象,只需构建包含ID的结构体即可建立关联 devices := make([]models.Device, len(deviceIDs)) for i, id := range deviceIDs { - devices[i] = models.Device{Model: gorm.Model{ID: id}} + devices[i] = models.Device{Model: models.Model{ID: id}} } taskModel.Devices = devices } @@ -290,7 +290,7 @@ func (s *planServiceImpl) UpdatePlan(ctx context.Context, planToUpdate *models.P } // DeletePlan 删除计划(软删除) -func (s *planServiceImpl) DeletePlan(ctx context.Context, id uint) error { +func (s *planServiceImpl) DeletePlan(ctx context.Context, id uint32) error { planCtx, logger := logs.Trace(ctx, s.ctx, "DeletePlan") const actionType = "领域层:删除计划" @@ -328,7 +328,7 @@ func (s *planServiceImpl) DeletePlan(ctx context.Context, id uint) error { } // StartPlan 启动计划 -func (s *planServiceImpl) StartPlan(ctx context.Context, id uint) error { +func (s *planServiceImpl) StartPlan(ctx context.Context, id uint32) error { planCtx, logger := logs.Trace(ctx, s.ctx, "StartPlan") const actionType = "领域层:启动计划" @@ -383,7 +383,7 @@ func (s *planServiceImpl) StartPlan(ctx context.Context, id uint) error { } // StopPlan 停止计划 -func (s *planServiceImpl) StopPlan(ctx context.Context, id uint) error { +func (s *planServiceImpl) StopPlan(ctx context.Context, id uint32) error { planCtx, logger := logs.Trace(ctx, s.ctx, "StopPlan") const actionType = "领域层:停止计划" diff --git a/internal/domain/plan/task.go b/internal/domain/plan/task.go index ba16106..9b010f4 100644 --- a/internal/domain/plan/task.go +++ b/internal/domain/plan/task.go @@ -25,8 +25,8 @@ type Task interface { // TaskDeviceIDResolver 定义了从任务配置中解析设备ID的方法 type TaskDeviceIDResolver interface { // ResolveDeviceIDs 从任务配置中解析并返回所有关联的设备ID列表 - // 返回值: uint数组,每个字符串代表一个设备ID - ResolveDeviceIDs(ctx context.Context) ([]uint, error) + // 返回值: uint32数组,每个字符串代表一个设备ID + ResolveDeviceIDs(ctx context.Context) ([]uint32, error) } // TaskFactory 是一个工厂接口,用于根据任务执行日志创建任务实例。 diff --git a/internal/domain/task/alarm_notification_task.go b/internal/domain/task/alarm_notification_task.go index 2b03a0d..df7642f 100644 --- a/internal/domain/task/alarm_notification_task.go +++ b/internal/domain/task/alarm_notification_task.go @@ -18,7 +18,7 @@ import ( // 如果用户没有指定某个等级的配置, 则默认为该等级消息只发送一次 type AlarmNotificationTaskParams struct { // NotificationIntervals 告警通知的发送间隔时间,键为告警等级,值为时间间隔(分钟) - NotificationIntervals map[models.SeverityLevel]uint `json:"notification_intervals"` + NotificationIntervals map[models.SeverityLevel]uint32 `json:"notification_intervals"` } // AlarmNotificationTask 告警通知发送任务 @@ -107,9 +107,9 @@ func (t *AlarmNotificationTask) OnFailure(ctx context.Context, executeErr error) } // ResolveDeviceIDs 从任务配置中解析并返回所有关联的设备ID列表 -func (t *AlarmNotificationTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) { +func (t *AlarmNotificationTask) ResolveDeviceIDs(ctx context.Context) ([]uint32, error) { // 告警通知任务与设备无关 - return []uint{}, nil + return []uint32{}, nil } // parseParameters 解析任务参数 diff --git a/internal/domain/task/area_threshold_check_task.go b/internal/domain/task/area_threshold_check_task.go index 2a2362d..2545796 100644 --- a/internal/domain/task/area_threshold_check_task.go +++ b/internal/domain/task/area_threshold_check_task.go @@ -14,12 +14,12 @@ import ( // AreaThresholdCheckParams 定义了区域阈值检查任务的参数 type AreaThresholdCheckParams struct { - AreaControllerID uint `json:"area_controller_id"` // 区域主控ID + AreaControllerID uint32 `json:"area_controller_id"` // 区域主控ID SensorType models.SensorType `json:"sensor_type"` // 传感器类型 Thresholds float32 `json:"thresholds"` // 阈值 Operator models.Operator `json:"operator"` // 操作符 Level models.SeverityLevel `json:"level"` // 告警级别 - ExcludeDeviceIDs []uint `json:"exclude_device_ids"` // 排除的传感器ID + ExcludeDeviceIDs []uint32 `json:"exclude_device_ids"` // 排除的传感器ID } // AreaThresholdCheckTask 是一个任务,用于检查区域阈值并触发告警, 区域主控下的所有没有独立校验任务的设备都会受到此任务的检查 @@ -62,7 +62,7 @@ func (a *AreaThresholdCheckTask) Execute(ctx context.Context) error { } // 构建忽略设备ID的map,方便快速查找 - ignoredMap := make(map[uint]struct{}) + ignoredMap := make(map[uint32]struct{}) for _, id := range a.params.ExcludeDeviceIDs { ignoredMap[id] = struct{}{} } @@ -112,7 +112,7 @@ func (a *AreaThresholdCheckTask) OnFailure(ctx context.Context, executeErr error logger.Errorf("区域阈值检测任务执行失败, 任务ID: %v: 执行失败: %v", a.taskLog.TaskID, executeErr) } -func (a *AreaThresholdCheckTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) { +func (a *AreaThresholdCheckTask) ResolveDeviceIDs(ctx context.Context) ([]uint32, error) { taskCtx := logs.AddFuncName(ctx, a.ctx, "ResolveDeviceIDs") if err := a.parseParameters(taskCtx); err != nil { return nil, err @@ -156,7 +156,7 @@ func (a *AreaThresholdCheckTask) parseParameters(ctx context.Context) error { params.Level = models.WarnLevel } if params.ExcludeDeviceIDs == nil { - params.ExcludeDeviceIDs = []uint{} + params.ExcludeDeviceIDs = []uint32{} } a.params = params diff --git a/internal/domain/task/delay_task.go b/internal/domain/task/delay_task.go index 916fb77..24bcd9a 100644 --- a/internal/domain/task/delay_task.go +++ b/internal/domain/task/delay_task.go @@ -70,6 +70,6 @@ func (d *DelayTask) OnFailure(ctx context.Context, executeErr error) { logger.Errorf("任务 %v: 执行失败: %v", d.executionTask.TaskID, executeErr) } -func (d *DelayTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) { - return []uint{}, nil +func (d *DelayTask) ResolveDeviceIDs(ctx context.Context) ([]uint32, error) { + return []uint32{}, nil } diff --git a/internal/domain/task/device_threshold_check_task.go b/internal/domain/task/device_threshold_check_task.go index 3616118..d351650 100644 --- a/internal/domain/task/device_threshold_check_task.go +++ b/internal/domain/task/device_threshold_check_task.go @@ -14,7 +14,7 @@ import ( ) type DeviceThresholdCheckParams struct { - DeviceID uint `json:"device_id"` // 设备ID + DeviceID uint32 `json:"device_id"` // 设备ID SensorType models.SensorType `json:"sensor_type"` // 传感器类型 Thresholds float32 `json:"thresholds"` // 阈值 Operator models.Operator `json:"operator"` // 操作符 @@ -188,10 +188,10 @@ func (d *DeviceThresholdCheckTask) OnFailure(ctx context.Context, executeErr err logger.Errorf("设备阈值检测任务执行失败, 任务ID: %v: 执行失败: %v", d.taskLog.TaskID, executeErr) } -func (d *DeviceThresholdCheckTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) { +func (d *DeviceThresholdCheckTask) ResolveDeviceIDs(ctx context.Context) ([]uint32, error) { taskCtx := logs.AddFuncName(ctx, d.ctx, "ResolveDeviceIDs") if err := d.parseParameters(taskCtx); err != nil { return nil, err } - return []uint{d.params.DeviceID}, nil + return []uint32{d.params.DeviceID}, nil } diff --git a/internal/domain/task/full_collection_task.go b/internal/domain/task/full_collection_task.go index b963500..9f94d7d 100644 --- a/internal/domain/task/full_collection_task.go +++ b/internal/domain/task/full_collection_task.go @@ -49,7 +49,7 @@ func (t *FullCollectionTask) Execute(ctx context.Context) error { return nil } - sensorsByController := make(map[uint][]*models.Device) + sensorsByController := make(map[uint32][]*models.Device) for _, sensor := range sensors { sensorsByController[sensor.AreaControllerID] = append(sensorsByController[sensor.AreaControllerID], sensor) } @@ -97,7 +97,7 @@ func (t *FullCollectionTask) OnFailure(ctx context.Context, executeErr error) { } // ResolveDeviceIDs 获取当前任务需要使用的设备ID列表 -func (t *FullCollectionTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) { +func (t *FullCollectionTask) ResolveDeviceIDs(ctx context.Context) ([]uint32, error) { // 全量采集任务不和任何设备绑定, 每轮采集都会重新获取全量传感器 - return []uint{}, nil + return []uint32{}, nil } diff --git a/internal/domain/task/release_feed_weight_task.go b/internal/domain/task/release_feed_weight_task.go index 7b90038..ad58d2e 100644 --- a/internal/domain/task/release_feed_weight_task.go +++ b/internal/domain/task/release_feed_weight_task.go @@ -16,8 +16,8 @@ import ( // ReleaseFeedWeightTaskParams 定义了 ReleaseFeedWeightTask 的参数结构 type ReleaseFeedWeightTaskParams struct { ReleaseWeight float32 `json:"release_weight"` // 需要释放的重量 - FeedPortDeviceID uint `json:"feed_port_device_id"` // 下料口ID - MixingTankDeviceID uint `json:"mixing_tank_device_id"` // 称重传感器ID + FeedPortDeviceID uint32 `json:"feed_port_device_id"` // 下料口ID + MixingTankDeviceID uint32 `json:"mixing_tank_device_id"` // 称重传感器ID } // ReleaseFeedWeightTask 是一个控制下料口释放指定重量的任务 @@ -30,7 +30,7 @@ type ReleaseFeedWeightTask struct { feedPortDevice *models.Device releaseWeight float32 - mixingTankDeviceID uint + mixingTankDeviceID uint32 feedPort device.Service @@ -178,10 +178,10 @@ func (r *ReleaseFeedWeightTask) OnFailure(ctx context.Context, executeErr error) logger.Errorf("善后处理完成, 日志ID:%v", r.claimedLog.ID) } -func (r *ReleaseFeedWeightTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) { +func (r *ReleaseFeedWeightTask) ResolveDeviceIDs(ctx context.Context) ([]uint32, error) { taskCtx := logs.AddFuncName(ctx, r.ctx, "ResolveDeviceIDs") if err := r.parseParameters(taskCtx); err != nil { return nil, err } - return []uint{r.feedPortDevice.ID, r.mixingTankDeviceID}, nil + return []uint32{r.feedPortDevice.ID, r.mixingTankDeviceID}, nil } diff --git a/internal/infra/config/config.go b/internal/infra/config/config.go index 7700d44..b592074 100644 --- a/internal/infra/config/config.go +++ b/internal/infra/config/config.go @@ -209,19 +209,19 @@ type CollectionConfig struct { type NotificationIntervalsConfig struct { // DebugIntervalMinutes Debug级别告警的通知间隔(分钟) - DebugIntervalMinutes uint `yaml:"debug"` + DebugIntervalMinutes uint32 `yaml:"debug"` // InfoIntervalMinutes Info级别告警的通知间隔(分钟) - InfoIntervalMinutes uint `yaml:"info"` + InfoIntervalMinutes uint32 `yaml:"info"` // WarnIntervalMinutes Warn级别告警的通知间隔(分钟) - WarnIntervalMinutes uint `yaml:"warn"` + WarnIntervalMinutes uint32 `yaml:"warn"` // ErrorIntervalMinutes Error级别告警的通知间隔(分钟) - ErrorIntervalMinutes uint `yaml:"error"` + ErrorIntervalMinutes uint32 `yaml:"error"` // DPanicIntervalMinutes DPanic级别告警的通知间隔(分钟) - DPanicIntervalMinutes uint `yaml:"dpanic"` + DPanicIntervalMinutes uint32 `yaml:"dpanic"` // PanicIntervalMinutes Panic级别告警的通知间隔(分钟) - PanicIntervalMinutes uint `yaml:"panic"` + PanicIntervalMinutes uint32 `yaml:"panic"` // FatalIntervalMinutes Fatal级别告警的通知间隔(分钟) - FatalIntervalMinutes uint `yaml:"fatal"` + FatalIntervalMinutes uint32 `yaml:"fatal"` } // AlarmNotificationConfig 告警通知配置 diff --git a/internal/infra/models/alarm.go b/internal/infra/models/alarm.go index 8306faa..4cd02a1 100644 --- a/internal/infra/models/alarm.go +++ b/internal/infra/models/alarm.go @@ -2,8 +2,6 @@ package models import ( "time" - - "gorm.io/gorm" ) // AlarmSourceType 定义了告警的来源类型 @@ -46,13 +44,13 @@ const ( ) // ActiveAlarm 活跃告警 -// 活跃告警会被更新(如忽略状态),因此保留 gorm.Model 以包含所有标准字段。 +// 活跃告警会被更新(如忽略状态),因此保留 Model 以包含所有标准字段。 type ActiveAlarm struct { - gorm.Model + Model SourceType AlarmSourceType `gorm:"type:varchar(50);not null;index:idx_alarm_uniqueness;comment:告警来源类型" json:"source_type"` // SourceID 告警来源ID,其具体含义取决于 SourceType 字段 (例如:设备ID, 区域主控ID, 猪栏ID)。 - SourceID uint `gorm:"not null;index:idx_alarm_uniqueness;comment:告警来源ID" json:"source_id"` + SourceID uint32 `gorm:"not null;index:idx_alarm_uniqueness;comment:告警来源ID" json:"source_id"` // AlarmCode 是一个机器可读的、标准化的告警类型标识。 // 它与 SourceType 和 SourceID 共同构成一个活跃告警的唯一标识。 @@ -79,15 +77,15 @@ func (ActiveAlarm) TableName() string { } // HistoricalAlarm 历史告警 -// 历史告警是不可变归档数据,我们移除 gorm.Model,并手动定义字段。 +// 历史告警是不可变归档数据,我们移除 Model,并手动定义字段。 // ID 和 TriggerTime 共同构成联合主键,以满足 TimescaleDB 超表的要求。 type HistoricalAlarm struct { // 手动定义主键,ID 仍然自增 - ID uint `gorm:"primaryKey;autoIncrement;comment:主键ID" json:"id"` + ID uint32 `gorm:"primaryKey;autoIncrement;comment:主键ID" json:"id"` SourceType AlarmSourceType `gorm:"type:varchar(50);not null;index;comment:告警来源类型" json:"source_type"` // SourceID 告警来源ID,其具体含义取决于 SourceType 字段 (例如:设备ID, 区域主控ID, 猪栏ID)。 - SourceID uint `gorm:"not null;index;comment:告警来源ID" json:"source_id"` + SourceID uint32 `gorm:"not null;index;comment:告警来源ID" json:"source_id"` // AlarmCode 是一个机器可读的、标准化的告警类型标识。 AlarmCode AlarmCode `gorm:"type:varchar(100);not null;index;comment:告警代码" json:"alarm_code"` @@ -99,8 +97,8 @@ type HistoricalAlarm struct { ResolveTime time.Time `gorm:"not null;comment:告警解决时间" json:"resolve_time"` ResolveMethod string `gorm:"comment:告警解决方式" json:"resolve_method"` - // ResolvedBy 使用指针类型 *uint 来表示可为空解决人, 当字段为空时表示系统自动解决的 - ResolvedBy *uint `gorm:"comment:告警解决人" json:"resolved_by"` + // ResolvedBy 使用指针类型 *uint32 来表示可为空解决人, 当字段为空时表示系统自动解决的 + ResolvedBy *uint32 `gorm:"comment:告警解决人" json:"resolved_by"` } // TableName 指定 HistoricalAlarm 结构体对应的数据库表名 diff --git a/internal/infra/models/device.go b/internal/infra/models/device.go index 3b15bab..aa2af91 100644 --- a/internal/infra/models/device.go +++ b/internal/infra/models/device.go @@ -6,7 +6,6 @@ import ( "strings" "gorm.io/datatypes" - "gorm.io/gorm" ) // --- Properties 结构体定义 --- @@ -19,7 +18,7 @@ type Bus485Properties struct { // AreaController 是一个LoRa转总线(如485)的通信网关 type AreaController struct { - gorm.Model + Model // Name 是主控的业务名称,例如 "1号猪舍主控" Name string `gorm:"not null;unique" json:"name"` @@ -53,20 +52,20 @@ func (AreaController) TableName() string { // Device 代表系统中的所有普通设备 type Device struct { - // gorm.Model 内嵌了标准模型字段 (ID, CreatedAt, UpdatedAt, DeletedAt) - gorm.Model + // Model 内嵌了标准模型字段 (ID, CreatedAt, UpdatedAt, DeletedAt) + Model // Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器" Name string `gorm:"not null" json:"name"` // DeviceTemplateID 是设备模板的外键 - DeviceTemplateID uint `gorm:"not null;index" json:"device_template_id"` + DeviceTemplateID uint32 `gorm:"not null;index" json:"device_template_id"` // DeviceTemplate 是设备的模板,包含了设备的通用信息 DeviceTemplate DeviceTemplate `json:"device_template"` // AreaControllerID 是区域主控的外键 - AreaControllerID uint `gorm:"not null;index" json:"area_controller_id"` + AreaControllerID uint32 `gorm:"not null;index" json:"area_controller_id"` // AreaController 是设备所属的区域主控 AreaController AreaController `json:"area_controller"` diff --git a/internal/infra/models/device_template.go b/internal/infra/models/device_template.go index a8f2c7e..0f5cb6d 100644 --- a/internal/infra/models/device_template.go +++ b/internal/infra/models/device_template.go @@ -6,7 +6,6 @@ import ( "fmt" "gorm.io/datatypes" - "gorm.io/gorm" ) // ModbusFunctionCode 定义Modbus功能码的枚举类型 @@ -106,7 +105,7 @@ func (sc *SensorCommands) SelfCheck() error { // DeviceTemplate 代表一种物理设备的类型。 type DeviceTemplate struct { - gorm.Model + Model // Name 是此模板的唯一名称, 例如 "FanModel-XYZ-2000" 或 "TempSensor-T1" Name string `gorm:"not null;unique" json:"name"` diff --git a/internal/infra/models/execution.go b/internal/infra/models/execution.go index e8804d2..fc69e13 100644 --- a/internal/infra/models/execution.go +++ b/internal/infra/models/execution.go @@ -27,12 +27,12 @@ const ( // PlanExecutionLog 记录整个计划的一次执行历史 type PlanExecutionLog struct { - ID uint `gorm:"primaryKey"` + ID uint32 `gorm:"primaryKey"` CreatedAt time.Time `gorm:"primaryKey"` // 作为联合主键方便只查询热点数据 UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` - PlanID uint `gorm:"index"` + PlanID uint32 `gorm:"index"` Status ExecutionStatus StartedAt time.Time EndedAt time.Time @@ -46,12 +46,12 @@ func (PlanExecutionLog) TableName() string { // TaskExecutionLog 记录单个任务的一次执行历史 type TaskExecutionLog struct { - ID uint `gorm:"primaryKey"` + ID uint32 `gorm:"primaryKey"` CreatedAt time.Time `gorm:"primaryKey"` // 作为联合主键方便只查询热点数据 UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` - PlanExecutionLogID uint `gorm:"index"` // 关联到某次计划执行 + PlanExecutionLogID uint32 `gorm:"index"` // 关联到某次计划执行 // TaskID 使用 int 类型以容纳特殊的负数ID,代表系统任务 TaskID int `gorm:"index"` @@ -106,7 +106,7 @@ type DeviceCommandLog struct { // DeviceID 是接收此下行任务的设备的ID。 // 对于 LoRaWAN,这通常是区域主控设备的ID。 - DeviceID uint `gorm:"not null;index" json:"device_id"` + DeviceID uint32 `gorm:"not null;index" json:"device_id"` // SentAt 记录下行任务最初发送的时间。 SentAt time.Time `gorm:"primaryKey" json:"sent_at"` @@ -133,7 +133,7 @@ type PendingCollection struct { // DeviceID 是接收此任务的设备ID // 对于 LoRaWAN,这通常是区域主控设备的ID。 - DeviceID uint `gorm:"index"` + DeviceID uint32 `gorm:"index"` // CommandMetadata 存储了此次采集任务对应的设备ID列表,顺序与设备响应值的顺序一致。 CommandMetadata UintArray `gorm:"type:bigint[]"` @@ -183,13 +183,13 @@ func (a AuditContextKey) String() string { // UserActionLog 记录用户的操作历史,用于审计 type UserActionLog struct { // 用 ID 和 Time 组成复合主键, 防止高并发时时间重复 - ID uint `gorm:"primaryKey"` + ID uint32 `gorm:"primaryKey"` // Time 是操作发生的时间,作为主键和超表的时间分区键 Time time.Time `gorm:"primaryKey" json:"time"` // --- Who (谁) --- - UserID uint `gorm:"not null" json:"user_id,omitempty"` + UserID uint32 `gorm:"not null" json:"user_id,omitempty"` Username string `json:"username,omitempty"` // 操作发生时用户名的快照 // --- Where (何地) --- diff --git a/internal/infra/models/farm_asset.go b/internal/infra/models/farm_asset.go index 7d5731c..c4723b0 100644 --- a/internal/infra/models/farm_asset.go +++ b/internal/infra/models/farm_asset.go @@ -1,16 +1,12 @@ package models -import ( - "gorm.io/gorm" -) - /* 猪场固定资产相关模型 */ // PigHouse 定义了猪舍,是猪栏的集合 type PigHouse struct { - gorm.Model + Model Name string `gorm:"size:100;not null;unique;comment:猪舍名称, 如 '育肥舍A栋'"` Description string `gorm:"size:255;comment:描述信息"` Pens []Pen `gorm:"foreignKey:HouseID"` // 一个猪舍包含多个猪栏 @@ -30,10 +26,10 @@ const ( // Pen 是猪栏的物理实体模型, 是所有空间相关数据的“锚点” type Pen struct { - gorm.Model + Model PenNumber string `gorm:"not null;comment:猪栏的唯一编号, 如 A-01"` - HouseID uint `gorm:"index;comment:所属猪舍ID"` - PigBatchID *uint `gorm:"index;comment:关联的猪批次ID"` + HouseID uint32 `gorm:"index;comment:所属猪舍ID"` + PigBatchID *uint32 `gorm:"index;comment:关联的猪批次ID"` Capacity int `gorm:"not null;comment:设计容量 (头)"` Status PenStatus `gorm:"not null;index;comment:猪栏当前状态"` } diff --git a/internal/infra/models/feed.go b/internal/infra/models/feed.go index b1414dc..472ba2d 100644 --- a/internal/infra/models/feed.go +++ b/internal/infra/models/feed.go @@ -2,8 +2,6 @@ package models import ( "time" - - "gorm.io/gorm" ) /* @@ -13,7 +11,7 @@ import ( // RawMaterial 代表饲料的原料。 // 建议:所有重量单位统一存储 (例如, 全部使用 'g'),便于计算和避免转换错误。 type RawMaterial struct { - gorm.Model + Model Name string `gorm:"size:100;unique;not null;comment:原料名称"` Description string `gorm:"size:255;comment:描述"` Quantity float32 `gorm:"not null;comment:库存总量, 单位: g"` @@ -25,8 +23,8 @@ func (RawMaterial) TableName() string { // RawMaterialPurchase 记录了原料的每一次采购。 type RawMaterialPurchase struct { - gorm.Model - RawMaterialID uint `gorm:"not null;index;comment:关联的原料ID"` + Model + RawMaterialID uint32 `gorm:"not null;index;comment:关联的原料ID"` RawMaterial RawMaterial `gorm:"foreignKey:RawMaterialID"` Supplier string `gorm:"size:100;comment:供应商"` Amount float32 `gorm:"not null;comment:采购数量, 单位: g"` @@ -54,11 +52,11 @@ const ( // RawMaterialStockLog 记录了原料库存的所有变动,提供了完整的追溯链。 type RawMaterialStockLog struct { - gorm.Model - RawMaterialID uint `gorm:"not null;index;comment:关联的原料ID"` + Model + RawMaterialID uint32 `gorm:"not null;index;comment:关联的原料ID"` ChangeAmount float32 `gorm:"not null;comment:变动数量, 正数为入库, 负数为出库"` SourceType StockLogSourceType `gorm:"size:50;not null;index;comment:库存变动来源类型"` - SourceID uint `gorm:"not null;index;comment:来源记录的ID (如 RawMaterialPurchase.ID 或 FeedUsageRecord.ID)"` + SourceID uint32 `gorm:"not null;index;comment:来源记录的ID (如 RawMaterialPurchase.ID 或 FeedUsageRecord.ID)"` HappenedAt time.Time `gorm:"primaryKey;comment:业务发生时间"` Remarks string `gorm:"comment:备注, 如主动领取的理由等"` } @@ -70,7 +68,7 @@ func (RawMaterialStockLog) TableName() string { // FeedFormula 代表饲料配方。 // 对于没有配方的外购饲料,可以将其视为一种特殊的 RawMaterial, 并为其创建一个仅包含它自己的 FeedFormula。 type FeedFormula struct { - gorm.Model + Model Name string `gorm:"size:100;unique;not null;comment:配方名称"` Description string `gorm:"size:255;comment:描述"` Components []FeedFormulaComponent `gorm:"foreignKey:FeedFormulaID"` @@ -82,9 +80,9 @@ func (FeedFormula) TableName() string { // FeedFormulaComponent 代表配方中的一种原料及其占比。 type FeedFormulaComponent struct { - gorm.Model - FeedFormulaID uint `gorm:"not null;index;comment:外键到 FeedFormula"` - RawMaterialID uint `gorm:"not null;index;comment:外键到 RawMaterial"` + Model + FeedFormulaID uint32 `gorm:"not null;index;comment:外键到 FeedFormula"` + RawMaterialID uint32 `gorm:"not null;index;comment:外键到 RawMaterial"` RawMaterial RawMaterial `gorm:"foreignKey:RawMaterialID"` Percentage float32 `gorm:"not null;comment:该原料在配方中的百分比 (0-1.0)"` } @@ -97,14 +95,14 @@ func (FeedFormulaComponent) TableName() string { // 应用层逻辑:当一条使用记录被创建时,应根据其使用的 FeedFormula, // 计算出每种 RawMaterial 的消耗量,并在 RawMaterialStockLog 中创建对应的出库记录。 type FeedUsageRecord struct { - gorm.Model - PenID uint `gorm:"not null;index;comment:关联的猪栏ID"` + Model + PenID uint32 `gorm:"not null;index;comment:关联的猪栏ID"` Pen Pen `gorm:"foreignKey:PenID"` - FeedFormulaID uint `gorm:"not null;index;comment:使用的饲料配方ID"` + FeedFormulaID uint32 `gorm:"not null;index;comment:使用的饲料配方ID"` FeedFormula FeedFormula `gorm:"foreignKey:FeedFormulaID"` Amount float32 `gorm:"not null;comment:使用数量, 单位: g"` RecordedAt time.Time `gorm:"primaryKey;comment:记录时间"` - OperatorID uint `gorm:"not null;comment:操作员"` + OperatorID uint32 `gorm:"not null;comment:操作员"` Remarks string `gorm:"comment:备注, 如 '例行喂料, 弱猪补料' 等"` } diff --git a/internal/infra/models/medication.go b/internal/infra/models/medication.go index c1bd710..052fb60 100644 --- a/internal/infra/models/medication.go +++ b/internal/infra/models/medication.go @@ -4,7 +4,6 @@ import ( "time" "gorm.io/datatypes" - "gorm.io/gorm" ) /* @@ -57,7 +56,7 @@ type PowderInstructions struct { // Medication 定义了兽药/疫苗的基本信息模型 type Medication struct { - gorm.Model + Model Name string `gorm:"size:100;not null;comment:药品名称" json:"name"` Type MedicationType `gorm:"size:20;not null;comment:兽药类型 (粉剂, 针剂, 疫苗)" json:"type"` Category MedicationCategory `gorm:"size:30;not null;comment:兽药种类 (四环素类, 磺胺类等)" json:"category"` @@ -82,15 +81,15 @@ const ( // MedicationLog 记录了对整个猪批次的用药情况 type MedicationLog struct { - gorm.Model - PigBatchID uint `gorm:"not null;index;comment:关联的猪批次ID"` - MedicationID uint `gorm:"not null;index;comment:关联的药品ID"` + Model + PigBatchID uint32 `gorm:"not null;index;comment:关联的猪批次ID"` + MedicationID uint32 `gorm:"not null;index;comment:关联的药品ID"` Medication Medication `gorm:"foreignKey:MedicationID"` // 预加载药品信息 DosageUsed float32 `gorm:"not null;comment:使用的总剂量 (单位由药品决定,如g或ml)"` TargetCount int `gorm:"not null;comment:用药对象数量"` Reason MedicationReasonType `gorm:"size:20;not null;comment:用药原因"` Description string `gorm:"size:255;comment:具体描述,如'治疗呼吸道病'"` - OperatorID uint `gorm:"comment:操作员ID"` + OperatorID uint32 `gorm:"comment:操作员ID"` HappenedAt time.Time `gorm:"primaryKey;comment:用药时间"` } diff --git a/internal/infra/models/models.go b/internal/infra/models/models.go index 9fbb914..fa35ec0 100644 --- a/internal/infra/models/models.go +++ b/internal/infra/models/models.go @@ -6,10 +6,20 @@ import ( "fmt" "strconv" "strings" + "time" "go.uber.org/zap/zapcore" + "gorm.io/gorm" ) +// Model 用于代替gorm.Model, 使用uint32以节约空间 +type Model struct { + ID uint32 `gorm:"primarykey"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` +} + // GetAllModels 返回一个包含所有数据库模型实例的切片。 // 这个函数用于在数据库初始化时自动迁移所有的表结构。 func GetAllModels() []interface{} { @@ -72,10 +82,10 @@ func GetAllModels() []interface{} { } } -// UintArray 是一个自定义类型,代表 uint 的切片。 +// UintArray 是一个自定义类型,代表 uint32 的切片。 // 它实现了 gorm.Scanner 和 driver.Valuer 接口, // 以便能与数据库的 bigint[] 类型进行原生映射。 -type UintArray []uint +type UintArray []uint32 // Value 实现了 driver.Valuer 接口。 // 它告诉 GORM 如何将 UintArray ([]) 转换为数据库能够理解的格式。 @@ -117,19 +127,19 @@ func (a *UintArray) Scan(src interface{}) error { // 去掉花括号 srcStr = strings.Trim(srcStr, "{}") if srcStr == "" { - *a = []uint{} + *a = []uint32{} return nil } // 按逗号分割 parts := strings.Split(srcStr, ",") - arr := make([]uint, len(parts)) + arr := make([]uint32, len(parts)) for i, p := range parts { val, err := strconv.ParseUint(p, 10, 64) if err != nil { return fmt.Errorf("解析 UintArray 元素失败: %w", err) } - arr[i] = uint(val) + arr[i] = uint32(val) } *a = arr diff --git a/internal/infra/models/notify.go b/internal/infra/models/notify.go index d63b5cf..2602267 100644 --- a/internal/infra/models/notify.go +++ b/internal/infra/models/notify.go @@ -2,8 +2,6 @@ package models import ( "time" - - "gorm.io/gorm" ) // NotifierType 定义了通知器的类型。 @@ -31,12 +29,12 @@ const ( // Notification 表示已发送或尝试发送的通知记录。 type Notification struct { - gorm.Model + Model // NotifierType 通知器类型 (例如:"邮件", "企业微信", "飞书", "日志") NotifierType NotifierType `gorm:"type:varchar(20);not null;index" json:"notifier_type"` // UserID 接收通知的用户ID,用于追溯通知记录到特定用户 - UserID uint `gorm:"index" json:"user_id"` // 增加 UserID 字段,并添加索引 + UserID uint32 `gorm:"index" json:"user_id"` // 增加 UserID 字段,并添加索引 // Title 通知标题 Title string `gorm:"type:varchar(255);not null" json:"title"` // Message 通知内容 diff --git a/internal/infra/models/pig_batch.go b/internal/infra/models/pig_batch.go index 37187c9..ee9ca0d 100644 --- a/internal/infra/models/pig_batch.go +++ b/internal/infra/models/pig_batch.go @@ -2,8 +2,6 @@ package models import ( "time" - - "gorm.io/gorm" ) /* @@ -32,7 +30,7 @@ const ( // PigBatch 是猪批次的核心模型,代表了一群被共同管理的猪 type PigBatch struct { - gorm.Model + Model BatchNumber string `gorm:"size:50;not null;uniqueIndex;comment:批次编号,如 2024-W25-A01"` OriginType PigBatchOriginType `gorm:"size:20;not null;comment:批次来源 (自繁, 外购)"` StartDate time.Time `gorm:"not null;comment:批次开始日期 (如转入日或购买日)"` @@ -65,14 +63,14 @@ const ( // PigBatchLog 记录了猪批次数量或状态的每一次变更 type PigBatchLog struct { - gorm.Model - PigBatchID uint `gorm:"not null;index;comment:关联的猪批次ID"` + Model + PigBatchID uint32 `gorm:"not null;index;comment:关联的猪批次ID"` ChangeType LogChangeType `gorm:"size:20;not null;comment:变更类型"` ChangeCount int `gorm:"not null;comment:数量变化,负数表示减少"` Reason string `gorm:"size:255;comment:变更原因描述"` BeforeCount int `gorm:"not null;comment:变更前总数"` AfterCount int `gorm:"not null;comment:变更后总数"` - OperatorID uint `gorm:"comment:操作员ID"` + OperatorID uint32 `gorm:"comment:操作员ID"` HappenedAt time.Time `gorm:"primaryKey;comment:事件发生时间"` } @@ -82,10 +80,10 @@ func (PigBatchLog) TableName() string { // WeighingBatch 记录了一次批次称重的信息 type WeighingBatch struct { - gorm.Model + Model WeighingTime time.Time `gorm:"primaryKey;comment:称重时间"` Description string `gorm:"size:255;comment:批次称重描述"` - PigBatchID uint `gorm:"not null;index;comment:关联的猪批次ID"` + PigBatchID uint32 `gorm:"not null;index;comment:关联的猪批次ID"` } func (WeighingBatch) TableName() string { @@ -94,11 +92,11 @@ func (WeighingBatch) TableName() string { // WeighingRecord 记录了单次称重信息 type WeighingRecord struct { - gorm.Model + Model Weight float32 `gorm:"not null;comment:单只猪重量 (kg)"` - WeighingBatchID uint `gorm:"not null;index;comment:关联的批次称重ID"` - PenID uint `gorm:"not null;index;comment:所在猪圈ID"` - OperatorID uint `gorm:"not null;comment:操作员ID"` + WeighingBatchID uint32 `gorm:"not null;index;comment:关联的批次称重ID"` + PenID uint32 `gorm:"not null;index;comment:所在猪圈ID"` + OperatorID uint32 `gorm:"not null;comment:操作员ID"` Remark string `gorm:"size:255;comment:备注"` WeighingTime time.Time `gorm:"primaryKey;comment:称重时间"` } diff --git a/internal/infra/models/pig_sick.go b/internal/infra/models/pig_sick.go index caf21a9..be85b19 100644 --- a/internal/infra/models/pig_sick.go +++ b/internal/infra/models/pig_sick.go @@ -2,8 +2,6 @@ package models import ( "time" - - "gorm.io/gorm" ) // PigBatchSickPigTreatmentLocation 定义了病猪治疗地点 @@ -29,16 +27,16 @@ const ( // PigSickLog 记录了猪批次中病猪数量的变化日志 type PigSickLog struct { - gorm.Model - PigBatchID uint `gorm:"primaryKey;comment:关联的猪批次ID"` - PenID uint `gorm:"not null;index;comment:所在猪圈ID"` + Model + PigBatchID uint32 `gorm:"primaryKey;comment:关联的猪批次ID"` + PenID uint32 `gorm:"not null;index;comment:所在猪圈ID"` ChangeCount int `gorm:"not null;comment:变化数量, 正数表示新增, 负数表示移除"` Reason PigBatchSickPigReasonType `gorm:"size:20;not null;comment:变化原因 (如: 患病, 康复, 死亡, 转入, 转出, 其他)"` BeforeCount int `gorm:"comment:变化前的数量"` AfterCount int `gorm:"comment:变化后的数量"` Remarks string `gorm:"size:255;comment:备注"` TreatmentLocation PigBatchSickPigTreatmentLocation `gorm:"size:50;comment:治疗地点"` - OperatorID uint `gorm:"comment:操作员ID"` + OperatorID uint32 `gorm:"comment:操作员ID"` HappenedAt time.Time `gorm:"primaryKey;comment:事件发生时间"` } diff --git a/internal/infra/models/pig_trade.go b/internal/infra/models/pig_trade.go index e46a716..4893c98 100644 --- a/internal/infra/models/pig_trade.go +++ b/internal/infra/models/pig_trade.go @@ -2,21 +2,19 @@ package models import ( "time" - - "gorm.io/gorm" ) // PigPurchase 记录了猪只采购信息 type PigPurchase struct { - gorm.Model - PigBatchID uint `gorm:"not null;index;comment:关联的猪批次ID"` + Model + PigBatchID uint32 `gorm:"not null;index;comment:关联的猪批次ID"` PurchaseDate time.Time `gorm:"primaryKey;comment:采购日期"` Supplier string `gorm:"comment:供应商"` Quantity int `gorm:"not null;comment:采购数量"` UnitPrice float32 `gorm:"not null;comment:单价"` TotalPrice float32 `gorm:"not null;comment:总价"` Remarks string `gorm:"size:255;comment:备注"` - OperatorID uint `gorm:"comment:操作员ID"` + OperatorID uint32 `gorm:"comment:操作员ID"` } func (PigPurchase) TableName() string { @@ -25,15 +23,15 @@ func (PigPurchase) TableName() string { // PigSale 记录了猪只销售信息 type PigSale struct { - gorm.Model - PigBatchID uint `gorm:"not null;index;comment:关联的猪批次ID"` + Model + PigBatchID uint32 `gorm:"not null;index;comment:关联的猪批次ID"` SaleDate time.Time `gorm:"primaryKey;comment:销售日期"` Buyer string `gorm:"comment:购买方"` Quantity int `gorm:"not null;comment:销售数量"` UnitPrice float32 `gorm:"not null;comment:单价"` TotalPrice float32 `gorm:"not null;comment:总价"` Remarks string `gorm:"size:255;comment:备注"` - OperatorID uint `gorm:"comment:操作员ID"` + OperatorID uint32 `gorm:"comment:操作员ID"` } func (PigSale) TableName() string { diff --git a/internal/infra/models/pig_transfer.go b/internal/infra/models/pig_transfer.go index 3fc3a04..0af3059 100644 --- a/internal/infra/models/pig_transfer.go +++ b/internal/infra/models/pig_transfer.go @@ -2,8 +2,6 @@ package models import ( "time" - - "gorm.io/gorm" ) // PigTransferType 定义了猪只迁移的类型 @@ -23,14 +21,14 @@ const ( // PigTransferLog 记录了每一次猪只数量在猪栏间的变动事件。 // 它作为事件溯源的基础,用于推算任意时间点猪栏的猪只数量。 type PigTransferLog struct { - gorm.Model + Model TransferTime time.Time `gorm:"primaryKey;comment:迁移发生时间" json:"transfer_time"` // 迁移发生时间,作为联合主键 - PigBatchID uint `gorm:"primaryKey;comment:关联的猪群ID" json:"pig_batch_id"` // 关联的猪群ID,作为联合主键 - PenID uint `gorm:"primaryKey;comment:发生变动的猪栏ID" json:"pen_id"` // 发生变动的猪栏ID,作为联合主键 + PigBatchID uint32 `gorm:"primaryKey;comment:关联的猪群ID" json:"pig_batch_id"` // 关联的猪群ID,作为联合主键 + PenID uint32 `gorm:"primaryKey;comment:发生变动的猪栏ID" json:"pen_id"` // 发生变动的猪栏ID,作为联合主键 Quantity int `gorm:"not null;comment:变动数量(正数表示增加,负数表示减少)" json:"quantity"` // 变动数量(正数表示增加,负数减少) Type PigTransferType `gorm:"not null;comment:变动类型" json:"type"` // 变动类型,使用枚举类型 CorrelationID string `gorm:"comment:用于关联一次完整操作(如一次调栏会产生两条日志)" json:"correlation_id"` // 用于关联一次完整操作 - OperatorID uint `gorm:"not null;comment:操作员ID" json:"operator_id"` // 操作员ID + OperatorID uint32 `gorm:"not null;comment:操作员ID" json:"operator_id"` // 操作员ID Remarks string `gorm:"comment:备注" json:"remarks"` } diff --git a/internal/infra/models/plan.go b/internal/infra/models/plan.go index 86efc6d..7827980 100644 --- a/internal/infra/models/plan.go +++ b/internal/infra/models/plan.go @@ -74,15 +74,15 @@ const ( // Plan 代表系统中的一个计划,可以包含子计划或任务 type Plan struct { - gorm.Model + Model Name PlanName `gorm:"not null" json:"name"` Description string `json:"description"` PlanType PlanType `gorm:"not null;index" json:"plan_type"` // 任务类型, 包括系统任务和用户自定义任务 ExecutionType PlanExecutionType `gorm:"not null;index" json:"execution_type"` Status PlanStatus `gorm:"default:'已禁用';index" json:"status"` // 计划是否被启动 - ExecuteNum uint `gorm:"default:0" json:"execute_num"` // 计划预期执行次数 - ExecuteCount uint `gorm:"default:0" json:"execute_count"` // 执行计数器 + ExecuteNum uint32 `gorm:"default:0" json:"execute_num"` // 计划预期执行次数 + ExecuteCount uint32 `gorm:"default:0" json:"execute_count"` // 执行计数器 // 针对 PlanExecutionTypeAutomatic,使用 Cron 表达式定义调度规则 CronExpression string `json:"cron_expression"` @@ -163,12 +163,12 @@ func (p *Plan) ReorderSteps() { // SubPlan 代表作为另一个计划一部分的子计划,具有执行顺序 type SubPlan struct { - gorm.Model + Model - ParentPlanID uint `gorm:"not null;index" json:"parent_plan_id"` // 父计划的ID - ChildPlanID uint `gorm:"not null;index" json:"child_plan_id"` // 子计划的ID (它本身也是一个 Plan) - ExecutionOrder int `gorm:"not null" json:"execution_order"` // 在父计划中的执行顺序 - ChildPlan *Plan `gorm:"-" json:"child_plan"` // 完整子计划数据,仅内存中 + ParentPlanID uint32 `gorm:"not null;index" json:"parent_plan_id"` // 父计划的ID + ChildPlanID uint32 `gorm:"not null;index" json:"child_plan_id"` // 子计划的ID (它本身也是一个 Plan) + ExecutionOrder int `gorm:"not null" json:"execution_order"` // 在父计划中的执行顺序 + ChildPlan *Plan `gorm:"-" json:"child_plan"` // 完整子计划数据,仅内存中 } // TableName 自定义 GORM 使用的数据库表名 @@ -184,7 +184,7 @@ type Task struct { UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` // 保持软删除功能 - PlanID uint `gorm:"not null;index" json:"plan_id"` // 此任务所属计划的ID + PlanID uint32 `gorm:"not null;index" json:"plan_id"` // 此任务所属计划的ID Name string `gorm:"not null" json:"name"` Description string `json:"description"` ExecutionOrder int `gorm:"not null" json:"execution_order"` // 在计划中的执行顺序 @@ -229,9 +229,9 @@ func (t *Task) SaveParameters(v interface{}) error { // DeviceTask 是设备和任务之间的关联模型,表示一个设备可以执行多个任务,一个任务可以被多个设备执行。 type DeviceTask struct { - gorm.Model - DeviceID uint `gorm:"not null;index"` // 设备ID - TaskID uint `gorm:"not null;index"` // 任务ID + Model + DeviceID uint32 `gorm:"not null;index"` // 设备ID + TaskID uint32 `gorm:"not null;index"` // 任务ID // 可选:如果需要存储关联的额外信息,可以在这里添加字段,例如: // Configuration datatypes.JSON `json:"configuration"` // 任务在特定设备上的配置 diff --git a/internal/infra/models/schedule.go b/internal/infra/models/schedule.go index 12a6384..98f06fc 100644 --- a/internal/infra/models/schedule.go +++ b/internal/infra/models/schedule.go @@ -7,8 +7,8 @@ import ( // PendingTask 是一个待执行任务队列, 里面会储存待执行的Task以及这个Task什么时候执行 // 它是一个纯粹的工作队列,任务被认领后即被删除。 type PendingTask struct { - // 手动填充必须字段以实现硬删除,不内嵌 gorm.Model - ID uint `gorm:"primarykey"` + // 手动填充必须字段以实现硬删除,不内嵌 Model + ID uint32 `gorm:"primarykey"` CreatedAt time.Time UpdatedAt time.Time @@ -21,7 +21,7 @@ type PendingTask struct { Task *Task `gorm:"foreignKey:TaskID"` ExecuteAt time.Time `gorm:"index"` // 任务执行时间 - TaskExecutionLogID uint `gorm:"unique;not null;index"` // 对应的执行历史记录ID + TaskExecutionLogID uint32 `gorm:"unique;not null;index"` // 对应的执行历史记录ID // 通过 TaskExecutionLogID 关联到唯一的 TaskExecutionLog 记录 // ON DELETE CASCADE 确保如果日志被删除,这个待办任务也会被自动清理 diff --git a/internal/infra/models/sensor_data.go b/internal/infra/models/sensor_data.go index a04481a..8165bcc 100644 --- a/internal/infra/models/sensor_data.go +++ b/internal/infra/models/sensor_data.go @@ -55,10 +55,10 @@ type SensorData struct { Time time.Time `gorm:"primaryKey" json:"time"` // DeviceID 是传感器的唯一标识符,作为复合主键的另一部分。 - DeviceID uint `gorm:"primaryKey" json:"device_id"` + DeviceID uint32 `gorm:"primaryKey" json:"device_id"` // AreaControllerID 是上报此数据的区域主控的ID。 - AreaControllerID uint `json:"area_controller_id"` + AreaControllerID uint32 `json:"area_controller_id"` // SensorType 是传感数据的类型 SensorType SensorType `gorm:"not null;index" json:"sensor_type"` diff --git a/internal/infra/models/user.go b/internal/infra/models/user.go index 6212136..2d66ca7 100644 --- a/internal/infra/models/user.go +++ b/internal/infra/models/user.go @@ -38,9 +38,9 @@ func (ci ContactInfo) Value() (driver.Value, error) { // User 代表系统中的用户模型 type User struct { - // gorm.Model 内嵌了 ID, CreatedAt, UpdatedAt, 和 DeletedAt + // Model 内嵌了 ID, CreatedAt, UpdatedAt, 和 DeletedAt // DeletedAt 字段的存在自动为 GORM 开启了软删除模式 - gorm.Model + Model // Username 是用户的登录名,应该是唯一的 // 修正了 gorm 标签的拼写错误 (移除了 gorm 后面的冒号) diff --git a/internal/infra/repository/alarm_repository.go b/internal/infra/repository/alarm_repository.go index f513778..7eea0be 100644 --- a/internal/infra/repository/alarm_repository.go +++ b/internal/infra/repository/alarm_repository.go @@ -15,7 +15,7 @@ import ( // ActiveAlarmListOptions 定义了查询活跃告警列表时的可选参数 type ActiveAlarmListOptions struct { SourceType *models.AlarmSourceType // 按告警来源类型过滤 - SourceID *uint // 按告警来源ID过滤 + SourceID *uint32 // 按告警来源ID过滤 Level *models.SeverityLevel // 按告警严重性等级过滤 IsIgnored *bool // 按是否被忽略过滤 TriggerTime *time.Time // 告警触发时间范围 - 开始时间 @@ -26,7 +26,7 @@ type ActiveAlarmListOptions struct { // HistoricalAlarmListOptions 定义了查询历史告警列表时的可选参数 type HistoricalAlarmListOptions struct { SourceType *models.AlarmSourceType // 按告警来源类型过滤 - SourceID *uint // 按告警来源ID过滤 + SourceID *uint32 // 按告警来源ID过滤 Level *models.SeverityLevel // 按告警严重性等级过滤 TriggerTimeStart *time.Time // 告警触发时间范围 - 开始时间 TriggerTimeEnd *time.Time // 告警触发时间范围 - 结束时间 @@ -41,19 +41,19 @@ type AlarmRepository interface { CreateActiveAlarm(ctx context.Context, alarm *models.ActiveAlarm) error // IsAlarmActiveInUse 检查具有相同来源和告警代码的告警当前是否处于活跃表中 - IsAlarmActiveInUse(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode) (bool, error) + IsAlarmActiveInUse(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint32, alarmCode models.AlarmCode) (bool, error) // GetActiveAlarmByUniqueFieldsTx 在指定事务中根据唯一业务键获取一个活跃告警 - GetActiveAlarmByUniqueFieldsTx(ctx context.Context, tx *gorm.DB, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode) (*models.ActiveAlarm, error) + GetActiveAlarmByUniqueFieldsTx(ctx context.Context, tx *gorm.DB, sourceType models.AlarmSourceType, sourceID uint32, alarmCode models.AlarmCode) (*models.ActiveAlarm, error) // CreateHistoricalAlarmTx 在指定事务中创建一条历史告警记录 CreateHistoricalAlarmTx(ctx context.Context, tx *gorm.DB, alarm *models.HistoricalAlarm) error // DeleteActiveAlarmTx 在指定事务中根据主键 ID 删除一个活跃告警 - DeleteActiveAlarmTx(ctx context.Context, tx *gorm.DB, id uint) error + DeleteActiveAlarmTx(ctx context.Context, tx *gorm.DB, id uint32) error // UpdateIgnoreStatus 更新指定告警的忽略状态 - UpdateIgnoreStatus(ctx context.Context, id uint, isIgnored bool, ignoredUntil *time.Time) error + UpdateIgnoreStatus(ctx context.Context, id uint32, isIgnored bool, ignoredUntil *time.Time) error // ListActiveAlarms 支持分页和过滤的活跃告警列表查询。 // 返回活跃告警列表、总记录数和错误。 @@ -67,16 +67,16 @@ type AlarmRepository interface { // lastNotifiedAt: 传入具体的发送时间。 // isIgnored: 告警新的忽略状态。 // ignoredUntil: 告警新的忽略截止时间 (nil 表示没有忽略截止时间/已取消忽略)。 - UpdateAlarmNotificationStatus(ctx context.Context, alarmID uint, lastNotifiedAt time.Time, isIgnored bool, ignoredUntil *time.Time) error + UpdateAlarmNotificationStatus(ctx context.Context, alarmID uint32, lastNotifiedAt time.Time, isIgnored bool, ignoredUntil *time.Time) error // <-- 下列两个方法是为了性能做出的架构妥协, 业务逻辑入侵仓库层带来的收益远大于通过业务层进行数据筛选 --> // ListAlarmsForNotification 查询满足发送告警消息条件的活跃告警列表。 // 返回活跃告警列表和错误。 // intervalByLevel: key=SeverityLevel, value=interval_in_minutes - ListAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint) ([]models.ActiveAlarm, error) + ListAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint32) ([]models.ActiveAlarm, error) // 查询满足发送告警消息条件的记录总数 - CountAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint) (int64, error) + CountAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint32) (int64, error) } // gormAlarmRepository 是 AlarmRepository 的 GORM 实现。 @@ -100,7 +100,7 @@ func (r *gormAlarmRepository) CreateActiveAlarm(ctx context.Context, alarm *mode } // IsAlarmActiveInUse 检查具有相同来源和告警代码的告警当前是否处于活跃表中 -func (r *gormAlarmRepository) IsAlarmActiveInUse(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode) (bool, error) { +func (r *gormAlarmRepository) IsAlarmActiveInUse(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint32, alarmCode models.AlarmCode) (bool, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "IsAlarmActiveInUse") var count int64 err := r.db.WithContext(repoCtx).Model(&models.ActiveAlarm{}). @@ -113,7 +113,7 @@ func (r *gormAlarmRepository) IsAlarmActiveInUse(ctx context.Context, sourceType } // GetActiveAlarmByUniqueFieldsTx 在指定事务中根据唯一业务键获取一个活跃告警 -func (r *gormAlarmRepository) GetActiveAlarmByUniqueFieldsTx(ctx context.Context, tx *gorm.DB, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode) (*models.ActiveAlarm, error) { +func (r *gormAlarmRepository) GetActiveAlarmByUniqueFieldsTx(ctx context.Context, tx *gorm.DB, sourceType models.AlarmSourceType, sourceID uint32, alarmCode models.AlarmCode) (*models.ActiveAlarm, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetActiveAlarmByUniqueFieldsTx") var alarm models.ActiveAlarm err := tx.WithContext(repoCtx). @@ -129,14 +129,14 @@ func (r *gormAlarmRepository) CreateHistoricalAlarmTx(ctx context.Context, tx *g } // DeleteActiveAlarmTx 在指定事务中根据主键 ID 删除一个活跃告警 -func (r *gormAlarmRepository) DeleteActiveAlarmTx(ctx context.Context, tx *gorm.DB, id uint) error { +func (r *gormAlarmRepository) DeleteActiveAlarmTx(ctx context.Context, tx *gorm.DB, id uint32) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "DeleteActiveAlarmTx") // 使用 Unscoped() 确保执行物理删除,而不是软删除 return tx.WithContext(repoCtx).Unscoped().Delete(&models.ActiveAlarm{}, id).Error } // UpdateIgnoreStatus 更新指定告警的忽略状态 -func (r *gormAlarmRepository) UpdateIgnoreStatus(ctx context.Context, id uint, isIgnored bool, ignoredUntil *time.Time) error { +func (r *gormAlarmRepository) UpdateIgnoreStatus(ctx context.Context, id uint32, isIgnored bool, ignoredUntil *time.Time) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateIgnoreStatus") updates := map[string]interface{}{ "is_ignored": isIgnored, @@ -266,7 +266,7 @@ func (r *gormAlarmRepository) ListHistoricalAlarms(ctx context.Context, opts His return results, total, err } -func (r *gormAlarmRepository) UpdateAlarmNotificationStatus(ctx context.Context, alarmID uint, lastNotifiedAt time.Time, isIgnored bool, ignoredUntil *time.Time) error { +func (r *gormAlarmRepository) UpdateAlarmNotificationStatus(ctx context.Context, alarmID uint32, lastNotifiedAt time.Time, isIgnored bool, ignoredUntil *time.Time) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateAlarmNotificationStatus") // 1. 内部安全地构造 map,将强类型参数转换为 GORM 需要的格式 @@ -290,7 +290,7 @@ func (r *gormAlarmRepository) UpdateAlarmNotificationStatus(ctx context.Context, } // CountAlarmsForNotification 查询满足发送告警消息条件的记录总数 -func (r *gormAlarmRepository) CountAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint) (int64, error) { +func (r *gormAlarmRepository) CountAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint32) (int64, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "CountAlarmsForNotification") var total int64 @@ -311,7 +311,7 @@ func (r *gormAlarmRepository) CountAlarmsForNotification(ctx context.Context, in } // ListAlarmsForNotification 查询满足发送告警消息条件的活跃告警列表 -func (r *gormAlarmRepository) ListAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint) ([]models.ActiveAlarm, error) { +func (r *gormAlarmRepository) ListAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint32) ([]models.ActiveAlarm, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "ListAlarmsForNotification") var results []models.ActiveAlarm @@ -332,7 +332,7 @@ func (r *gormAlarmRepository) ListAlarmsForNotification(ctx context.Context, int } // buildNotificationBaseQuery 负责组合 Group A 和 Group B 的逻辑 -func (r *gormAlarmRepository) buildNotificationBaseQuery(tx *gorm.DB, intervalByLevel map[models.SeverityLevel]uint) *gorm.DB { +func (r *gormAlarmRepository) buildNotificationBaseQuery(tx *gorm.DB, intervalByLevel map[models.SeverityLevel]uint32) *gorm.DB { // 1. 获取所有配置的 Level 列表 configuredLevels := make([]models.SeverityLevel, 0, len(intervalByLevel)) @@ -392,7 +392,7 @@ func (r *gormAlarmRepository) buildGroupAClause(tx *gorm.DB, configuredLevels [] // buildGroupBClause 构造 Group B 的 WHERE 语句和参数列表。 // 针对 Level 存在配置的告警,使用“间隔发送”逻辑。 -func (r *gormAlarmRepository) buildGroupBClause(tx *gorm.DB, intervalByLevel map[models.SeverityLevel]uint, configuredLevels []models.SeverityLevel) *gorm.DB { +func (r *gormAlarmRepository) buildGroupBClause(tx *gorm.DB, intervalByLevel map[models.SeverityLevel]uint32, configuredLevels []models.SeverityLevel) *gorm.DB { now := time.Now() // B.1. 构造 Level IN 子句 diff --git a/internal/infra/repository/area_controller_repository.go b/internal/infra/repository/area_controller_repository.go index 0a0e0bf..d9ed656 100644 --- a/internal/infra/repository/area_controller_repository.go +++ b/internal/infra/repository/area_controller_repository.go @@ -13,14 +13,14 @@ import ( // AreaControllerRepository 定义了对 AreaController 模型的数据库操作接口 type AreaControllerRepository interface { - FindByID(ctx context.Context, id uint) (*models.AreaController, error) + FindByID(ctx context.Context, id uint32) (*models.AreaController, error) FindByNetworkID(ctx context.Context, networkID string) (*models.AreaController, error) Create(ctx context.Context, ac *models.AreaController) error ListAll(ctx context.Context) ([]*models.AreaController, error) Update(ctx context.Context, ac *models.AreaController) error - Delete(ctx context.Context, id uint) error + Delete(ctx context.Context, id uint32) error // IsAreaControllerUsedByTasks 检查区域主控是否被特定任务类型使用,可以忽略指定任务类型 - IsAreaControllerUsedByTasks(ctx context.Context, areaControllerID uint, ignoredTaskTypes []models.TaskType) (bool, error) + IsAreaControllerUsedByTasks(ctx context.Context, areaControllerID uint32, ignoredTaskTypes []models.TaskType) (bool, error) } // gormAreaControllerRepository 是 AreaControllerRepository 的 GORM 实现。 @@ -60,7 +60,7 @@ func (r *gormAreaControllerRepository) Update(ctx context.Context, ac *models.Ar } // Delete 删除一个 AreaController 记录。 -func (r *gormAreaControllerRepository) Delete(ctx context.Context, id uint) error { +func (r *gormAreaControllerRepository) Delete(ctx context.Context, id uint32) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "Delete") if err := r.db.WithContext(repoCtx).Delete(&models.AreaController{}, id).Error; err != nil { return fmt.Errorf("删除区域主控失败: %w", err) @@ -69,7 +69,7 @@ func (r *gormAreaControllerRepository) Delete(ctx context.Context, id uint) erro } // FindByID 通过 ID 查找一个 AreaController。 -func (r *gormAreaControllerRepository) FindByID(ctx context.Context, id uint) (*models.AreaController, error) { +func (r *gormAreaControllerRepository) FindByID(ctx context.Context, id uint32) (*models.AreaController, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID") var areaController models.AreaController if err := r.db.WithContext(repoCtx).First(&areaController, id).Error; err != nil { @@ -89,7 +89,7 @@ func (r *gormAreaControllerRepository) FindByNetworkID(ctx context.Context, netw } // IsAreaControllerUsedByTasks 检查区域主控是否被特定任务类型使用,可以忽略指定任务类型 -func (r *gormAreaControllerRepository) IsAreaControllerUsedByTasks(ctx context.Context, areaControllerID uint, ignoredTaskTypes []models.TaskType) (bool, error) { +func (r *gormAreaControllerRepository) IsAreaControllerUsedByTasks(ctx context.Context, areaControllerID uint32, ignoredTaskTypes []models.TaskType) (bool, error) { repoCtx, logger := logs.Trace(ctx, r.ctx, "IsAreaControllerUsedByTasks") // 将 ignoredTaskTypes 转换为 map,以便高效查找 diff --git a/internal/infra/repository/device_command_log_repository.go b/internal/infra/repository/device_command_log_repository.go index b4bc0bb..e974dfd 100644 --- a/internal/infra/repository/device_command_log_repository.go +++ b/internal/infra/repository/device_command_log_repository.go @@ -12,7 +12,7 @@ import ( // DeviceCommandLogListOptions 定义了查询设备命令日志时的可选参数 type DeviceCommandLogListOptions struct { - DeviceID *uint + DeviceID *uint32 ReceivedSuccess *bool StartTime *time.Time // 基于 sent_at 字段 EndTime *time.Time // 基于 sent_at 字段 diff --git a/internal/infra/repository/device_repository.go b/internal/infra/repository/device_repository.go index e19b974..72b01d6 100644 --- a/internal/infra/repository/device_repository.go +++ b/internal/infra/repository/device_repository.go @@ -18,7 +18,7 @@ type DeviceRepository interface { Create(ctx context.Context, device *models.Device) error // FindByID 根据主键 ID 查找设备 - FindByID(ctx context.Context, id uint) (*models.Device, error) + FindByID(ctx context.Context, id uint32) (*models.Device, error) // FindByIDString 根据字符串形式的主键 ID 查找设备 FindByIDString(ctx context.Context, id string) (*models.Device, error) @@ -30,28 +30,28 @@ type DeviceRepository interface { ListAllSensors(ctx context.Context) ([]*models.Device, error) // ListByAreaControllerID 根据区域主控 ID 列出所有子设备 - ListByAreaControllerID(ctx context.Context, areaControllerID uint) ([]*models.Device, error) + ListByAreaControllerID(ctx context.Context, areaControllerID uint32) ([]*models.Device, error) // FindByDeviceTemplateID 根据设备模板ID查找所有使用该模板的设备 - FindByDeviceTemplateID(ctx context.Context, deviceTemplateID uint) ([]*models.Device, error) + FindByDeviceTemplateID(ctx context.Context, deviceTemplateID uint32) ([]*models.Device, error) // Update 更新一个已有的设备信息 Update(ctx context.Context, device *models.Device) error // Delete 根据主键 ID 删除一个设备 - Delete(ctx context.Context, id uint) error + Delete(ctx context.Context, id uint32) error // FindByAreaControllerAndPhysicalAddress 根据区域主控ID和物理地址(总线号、总线地址)查找设备 - FindByAreaControllerAndPhysicalAddress(ctx context.Context, areaControllerID uint, busNumber int, busAddress int) (*models.Device, error) + FindByAreaControllerAndPhysicalAddress(ctx context.Context, areaControllerID uint32, busNumber int, busAddress int) (*models.Device, error) // GetDevicesByIDsTx 在指定事务中根据ID列表获取设备 - GetDevicesByIDsTx(ctx context.Context, tx *gorm.DB, ids []uint) ([]models.Device, error) + GetDevicesByIDsTx(ctx context.Context, tx *gorm.DB, ids []uint32) ([]models.Device, error) // IsDeviceInUse 检查设备是否被任何任务使用,可以忽略指定任务类型 - IsDeviceInUse(ctx context.Context, deviceID uint, ignoredTaskTypes []models.TaskType) (bool, error) + IsDeviceInUse(ctx context.Context, deviceID uint32, ignoredTaskTypes []models.TaskType) (bool, error) // IsAreaControllerInUse 检查区域主控是否被任何设备使用 - IsAreaControllerInUse(ctx context.Context, areaControllerID uint) (bool, error) + IsAreaControllerInUse(ctx context.Context, areaControllerID uint32) (bool, error) } // gormDeviceRepository 是 DeviceRepository 的 GORM 实现 @@ -73,7 +73,7 @@ func (r *gormDeviceRepository) Create(ctx context.Context, device *models.Device } // FindByID 根据 ID 查找设备 -func (r *gormDeviceRepository) FindByID(ctx context.Context, id uint) (*models.Device, error) { +func (r *gormDeviceRepository) FindByID(ctx context.Context, id uint32) (*models.Device, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID") var device models.Device if err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").First(&device, id).Error; err != nil { @@ -83,7 +83,7 @@ func (r *gormDeviceRepository) FindByID(ctx context.Context, id uint) (*models.D } // GetDevicesByIDsTx 在指定事务中根据ID列表获取设备 -func (r *gormDeviceRepository) GetDevicesByIDsTx(ctx context.Context, tx *gorm.DB, ids []uint) ([]models.Device, error) { +func (r *gormDeviceRepository) GetDevicesByIDsTx(ctx context.Context, tx *gorm.DB, ids []uint32) ([]models.Device, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetDevicesByIDsTx") var devices []models.Device if len(ids) == 0 { @@ -98,14 +98,13 @@ func (r *gormDeviceRepository) GetDevicesByIDsTx(ctx context.Context, tx *gorm.D // FindByIDString 根据字符串形式的主键 ID 查找设备 func (r *gormDeviceRepository) FindByIDString(ctx context.Context, id string) (*models.Device, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByIDString") - // 将字符串ID转换为uint64 idInt, err := strconv.ParseUint(id, 10, 64) if err != nil { // 如果转换失败,说明ID格式不正确,返回一个明确的错误 return nil, fmt.Errorf("无效的设备ID格式: %w", err) } // 调用已有的 FindByID 方法 - return r.FindByID(repoCtx, uint(idInt)) + return r.FindByID(repoCtx, uint32(idInt)) } // ListAll 获取所有设备的列表 @@ -133,7 +132,7 @@ func (r *gormDeviceRepository) ListAllSensors(ctx context.Context) ([]*models.De } // ListByAreaControllerID 根据区域主控 ID 列出所有子设备 -func (r *gormDeviceRepository) ListByAreaControllerID(ctx context.Context, areaControllerID uint) ([]*models.Device, error) { +func (r *gormDeviceRepository) ListByAreaControllerID(ctx context.Context, areaControllerID uint32) ([]*models.Device, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "ListByAreaControllerID") var devices []*models.Device err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").Where("area_controller_id = ?", areaControllerID).Find(&devices).Error @@ -144,7 +143,7 @@ func (r *gormDeviceRepository) ListByAreaControllerID(ctx context.Context, areaC } // FindByDeviceTemplateID 根据设备模板ID查找所有使用该模板的设备 -func (r *gormDeviceRepository) FindByDeviceTemplateID(ctx context.Context, deviceTemplateID uint) ([]*models.Device, error) { +func (r *gormDeviceRepository) FindByDeviceTemplateID(ctx context.Context, deviceTemplateID uint32) ([]*models.Device, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByDeviceTemplateID") var devices []*models.Device err := r.db.WithContext(repoCtx).Where("device_template_id = ?", deviceTemplateID).Find(&devices).Error @@ -163,13 +162,13 @@ func (r *gormDeviceRepository) Update(ctx context.Context, device *models.Device // Delete 根据 ID 删除一个设备 // GORM 使用软删除,记录不会从数据库中物理移除,而是设置 DeletedAt 字段。 -func (r *gormDeviceRepository) Delete(ctx context.Context, id uint) error { +func (r *gormDeviceRepository) Delete(ctx context.Context, id uint32) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "Delete") return r.db.WithContext(repoCtx).Delete(&models.Device{}, id).Error } // FindByAreaControllerAndPhysicalAddress 根据区域主控ID和物理地址(总线号、总线地址)查找设备 -func (r *gormDeviceRepository) FindByAreaControllerAndPhysicalAddress(ctx context.Context, areaControllerID uint, busNumber int, busAddress int) (*models.Device, error) { +func (r *gormDeviceRepository) FindByAreaControllerAndPhysicalAddress(ctx context.Context, areaControllerID uint32, busNumber int, busAddress int) (*models.Device, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByAreaControllerAndPhysicalAddress") var device models.Device err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate"). @@ -185,7 +184,7 @@ func (r *gormDeviceRepository) FindByAreaControllerAndPhysicalAddress(ctx contex } // IsDeviceInUse 检查设备是否被任何任务使用,可以忽略指定任务类型 -func (r *gormDeviceRepository) IsDeviceInUse(ctx context.Context, deviceID uint, ignoredTaskTypes []models.TaskType) (bool, error) { +func (r *gormDeviceRepository) IsDeviceInUse(ctx context.Context, deviceID uint32, ignoredTaskTypes []models.TaskType) (bool, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "IsDeviceInUse") var count int64 @@ -207,7 +206,7 @@ func (r *gormDeviceRepository) IsDeviceInUse(ctx context.Context, deviceID uint, } // IsAreaControllerInUse 检查区域主控是否被任何设备使用 -func (r *gormDeviceRepository) IsAreaControllerInUse(ctx context.Context, areaControllerID uint) (bool, error) { +func (r *gormDeviceRepository) IsAreaControllerInUse(ctx context.Context, areaControllerID uint32) (bool, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "IsAreaControllerInUse") var count int64 if err := r.db.WithContext(repoCtx).Model(&models.Device{}).Where("area_controller_id = ?", areaControllerID).Count(&count).Error; err != nil { diff --git a/internal/infra/repository/device_template_repository.go b/internal/infra/repository/device_template_repository.go index dd9c0b1..22d3fa3 100644 --- a/internal/infra/repository/device_template_repository.go +++ b/internal/infra/repository/device_template_repository.go @@ -14,12 +14,12 @@ import ( // DeviceTemplateRepository 定义了设备模板数据访问的接口 type DeviceTemplateRepository interface { Create(ctx context.Context, deviceTemplate *models.DeviceTemplate) error - FindByID(ctx context.Context, id uint) (*models.DeviceTemplate, error) + FindByID(ctx context.Context, id uint32) (*models.DeviceTemplate, error) FindByName(ctx context.Context, name string) (*models.DeviceTemplate, error) ListAll(ctx context.Context) ([]*models.DeviceTemplate, error) Update(ctx context.Context, deviceTemplate *models.DeviceTemplate) error - Delete(ctx context.Context, id uint) error - IsInUse(ctx context.Context, id uint) (bool, error) + Delete(ctx context.Context, id uint32) error + IsInUse(ctx context.Context, id uint32) (bool, error) } // gormDeviceTemplateRepository 是 DeviceTemplateRepository 的 GORM 实现 @@ -40,7 +40,7 @@ func (r *gormDeviceTemplateRepository) Create(ctx context.Context, deviceTemplat } // FindByID 根据ID查找设备模板 -func (r *gormDeviceTemplateRepository) FindByID(ctx context.Context, id uint) (*models.DeviceTemplate, error) { +func (r *gormDeviceTemplateRepository) FindByID(ctx context.Context, id uint32) (*models.DeviceTemplate, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID") var deviceTemplate models.DeviceTemplate if err := r.db.WithContext(repoCtx).First(&deviceTemplate, id).Error; err != nil { @@ -82,7 +82,7 @@ func (r *gormDeviceTemplateRepository) Update(ctx context.Context, deviceTemplat } // IsInUse 检查设备模板是否正在被设备使用 -func (r *gormDeviceTemplateRepository) IsInUse(ctx context.Context, id uint) (bool, error) { +func (r *gormDeviceTemplateRepository) IsInUse(ctx context.Context, id uint32) (bool, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "IsInUse") var count int64 if err := r.db.WithContext(repoCtx).Model(&models.Device{}).Where("device_template_id = ?", id).Count(&count).Error; err != nil { @@ -92,7 +92,7 @@ func (r *gormDeviceTemplateRepository) IsInUse(ctx context.Context, id uint) (bo } // Delete 软删除数据库中的设备模板 -func (r *gormDeviceTemplateRepository) Delete(ctx context.Context, id uint) error { +func (r *gormDeviceTemplateRepository) Delete(ctx context.Context, id uint32) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "Delete") if err := r.db.WithContext(repoCtx).Delete(&models.DeviceTemplate{}, id).Error; err != nil { return fmt.Errorf("删除设备模板失败: %w", err) diff --git a/internal/infra/repository/execution_log_repository.go b/internal/infra/repository/execution_log_repository.go index bd4a1fb..9d58856 100644 --- a/internal/infra/repository/execution_log_repository.go +++ b/internal/infra/repository/execution_log_repository.go @@ -13,7 +13,7 @@ import ( // PlanExecutionLogListOptions 定义了查询计划执行日志时的可选参数 type PlanExecutionLogListOptions struct { - PlanID *uint + PlanID *uint32 Status *models.ExecutionStatus StartTime *time.Time // 基于 created_at 字段 EndTime *time.Time // 基于 created_at 字段 @@ -22,7 +22,7 @@ type PlanExecutionLogListOptions struct { // TaskExecutionLogListOptions 定义了查询任务执行日志时的可选参数 type TaskExecutionLogListOptions struct { - PlanExecutionLogID *uint + PlanExecutionLogID *uint32 TaskID *int Status *models.ExecutionStatus StartTime *time.Time // 基于 created_at 字段 @@ -33,26 +33,26 @@ type TaskExecutionLogListOptions struct { // ExecutionLogRepository 定义了与执行日志交互的接口。 type ExecutionLogRepository interface { // --- Existing methods --- - UpdateTaskExecutionLogStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error - UpdateTaskExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error + UpdateTaskExecutionLogStatusByIDs(ctx context.Context, logIDs []uint32, status models.ExecutionStatus) error + UpdateTaskExecutionLogStatus(ctx context.Context, logID uint32, status models.ExecutionStatus) error CreateTaskExecutionLog(ctx context.Context, log *models.TaskExecutionLog) error CreatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error UpdatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error CreateTaskExecutionLogsInBatch(ctx context.Context, logs []*models.TaskExecutionLog) error UpdateTaskExecutionLog(ctx context.Context, log *models.TaskExecutionLog) error - FindTaskExecutionLogByID(ctx context.Context, id uint) (*models.TaskExecutionLog, error) + FindTaskExecutionLogByID(ctx context.Context, id uint32) (*models.TaskExecutionLog, error) // UpdatePlanExecutionLogStatus 更新计划执行日志的状态 - UpdatePlanExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error + UpdatePlanExecutionLogStatus(ctx context.Context, logID uint32, status models.ExecutionStatus) error // UpdatePlanExecutionLogsStatusByIDs 批量更新计划执行日志的状态 - UpdatePlanExecutionLogsStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error + UpdatePlanExecutionLogsStatusByIDs(ctx context.Context, logIDs []uint32, status models.ExecutionStatus) error // FindIncompletePlanExecutionLogs 查找所有未完成的计划执行日志 FindIncompletePlanExecutionLogs(ctx context.Context) ([]models.PlanExecutionLog, error) // FindInProgressPlanExecutionLogByPlanID 根据 PlanID 查找正在进行的计划执行日志 - FindInProgressPlanExecutionLogByPlanID(ctx context.Context, planID uint) (*models.PlanExecutionLog, error) + FindInProgressPlanExecutionLogByPlanID(ctx context.Context, planID uint32) (*models.PlanExecutionLog, error) // FindIncompleteTaskExecutionLogsByPlanLogID 根据计划日志ID查找所有未完成的任务日志 - FindIncompleteTaskExecutionLogsByPlanLogID(ctx context.Context, planLogID uint) ([]models.TaskExecutionLog, error) + FindIncompleteTaskExecutionLogsByPlanLogID(ctx context.Context, planLogID uint32) ([]models.TaskExecutionLog, error) // FailAllIncompletePlanExecutionLogs 将所有状态为 ExecutionStatusStarted 和 ExecutionStatusWaiting 的计划状态都修改为 ExecutionStatusFailed FailAllIncompletePlanExecutionLogs(ctx context.Context) error @@ -60,16 +60,16 @@ type ExecutionLogRepository interface { CancelAllIncompleteTaskExecutionLogs(ctx context.Context) error // FindPlanExecutionLogByID 根据ID查找计划执行日志 - FindPlanExecutionLogByID(ctx context.Context, id uint) (*models.PlanExecutionLog, error) + FindPlanExecutionLogByID(ctx context.Context, id uint32) (*models.PlanExecutionLog, error) // CountIncompleteTasksByPlanLogID 计算一个计划执行中未完成的任务数量 - CountIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint) (int64, error) + CountIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint32) (int64, error) // FailPlanExecution 将指定的计划执行标记为失败 - FailPlanExecution(ctx context.Context, planLogID uint, errorMessage string) error + FailPlanExecution(ctx context.Context, planLogID uint32, errorMessage string) error // CancelIncompleteTasksByPlanLogID 取消一个计划执行中的所有未完成任务 - CancelIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint, reason string) error + CancelIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint32, reason string) error // --- New methods --- ListPlanExecutionLogs(ctx context.Context, opts PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error) @@ -175,7 +175,7 @@ func (r *gormExecutionLogRepository) ListTaskExecutionLogs(ctx context.Context, // --- Existing method implementations --- -func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error { +func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatusByIDs(ctx context.Context, logIDs []uint32, status models.ExecutionStatus) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateTaskExecutionLogStatusByIDs") if len(logIDs) == 0 { return nil @@ -183,7 +183,7 @@ func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatusByIDs(ctx conte return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Where("id IN ?", logIDs).Update("status", status).Error } -func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error { +func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatus(ctx context.Context, logID uint32, status models.ExecutionStatus) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateTaskExecutionLogStatus") return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Where("id = ?", logID).Update("status", status).Error } @@ -228,7 +228,7 @@ func (r *gormExecutionLogRepository) UpdateTaskExecutionLog(ctx context.Context, // FindTaskExecutionLogByID 根据 ID 查找单个任务执行日志。 // 它会预加载关联的 Task 信息。 -func (r *gormExecutionLogRepository) FindTaskExecutionLogByID(ctx context.Context, id uint) (*models.TaskExecutionLog, error) { +func (r *gormExecutionLogRepository) FindTaskExecutionLogByID(ctx context.Context, id uint32) (*models.TaskExecutionLog, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindTaskExecutionLogByID") var log models.TaskExecutionLog // 使用 Preload("Task") 来确保关联的任务信息被一并加载 @@ -240,13 +240,13 @@ func (r *gormExecutionLogRepository) FindTaskExecutionLogByID(ctx context.Contex } // UpdatePlanExecutionLogStatus 更新计划执行日志的状态 -func (r *gormExecutionLogRepository) UpdatePlanExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error { +func (r *gormExecutionLogRepository) UpdatePlanExecutionLogStatus(ctx context.Context, logID uint32, status models.ExecutionStatus) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanExecutionLogStatus") return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}).Where("id = ?", logID).Update("status", status).Error } // UpdatePlanExecutionLogsStatusByIDs 批量更新计划执行日志的状态 -func (r *gormExecutionLogRepository) UpdatePlanExecutionLogsStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error { +func (r *gormExecutionLogRepository) UpdatePlanExecutionLogsStatusByIDs(ctx context.Context, logIDs []uint32, status models.ExecutionStatus) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanExecutionLogsStatusByIDs") if len(logIDs) == 0 { return nil @@ -263,7 +263,7 @@ func (r *gormExecutionLogRepository) FindIncompletePlanExecutionLogs(ctx context } // FindInProgressPlanExecutionLogByPlanID 根据 PlanID 查找正在进行的计划执行日志 -func (r *gormExecutionLogRepository) FindInProgressPlanExecutionLogByPlanID(ctx context.Context, planID uint) (*models.PlanExecutionLog, error) { +func (r *gormExecutionLogRepository) FindInProgressPlanExecutionLogByPlanID(ctx context.Context, planID uint32) (*models.PlanExecutionLog, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindInProgressPlanExecutionLogByPlanID") var log models.PlanExecutionLog err := r.db.WithContext(repoCtx).Where("plan_id = ? AND status = ?", planID, models.ExecutionStatusStarted).First(&log).Error @@ -279,7 +279,7 @@ func (r *gormExecutionLogRepository) FindInProgressPlanExecutionLogByPlanID(ctx } // FindIncompleteTaskExecutionLogsByPlanLogID 根据计划日志ID查找所有未完成的任务日志 -func (r *gormExecutionLogRepository) FindIncompleteTaskExecutionLogsByPlanLogID(ctx context.Context, planLogID uint) ([]models.TaskExecutionLog, error) { +func (r *gormExecutionLogRepository) FindIncompleteTaskExecutionLogsByPlanLogID(ctx context.Context, planLogID uint32) ([]models.TaskExecutionLog, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindIncompleteTaskExecutionLogsByPlanLogID") var logs []models.TaskExecutionLog err := r.db.WithContext(repoCtx).Where("plan_execution_log_id = ? AND (status = ? OR status = ?)", @@ -304,7 +304,7 @@ func (r *gormExecutionLogRepository) CancelAllIncompleteTaskExecutionLogs(ctx co } // FindPlanExecutionLogByID 根据ID查找计划执行日志 -func (r *gormExecutionLogRepository) FindPlanExecutionLogByID(ctx context.Context, id uint) (*models.PlanExecutionLog, error) { +func (r *gormExecutionLogRepository) FindPlanExecutionLogByID(ctx context.Context, id uint32) (*models.PlanExecutionLog, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPlanExecutionLogByID") var log models.PlanExecutionLog err := r.db.WithContext(repoCtx).First(&log, id).Error @@ -315,7 +315,7 @@ func (r *gormExecutionLogRepository) FindPlanExecutionLogByID(ctx context.Contex } // CountIncompleteTasksByPlanLogID 计算一个计划执行中未完成的任务数量 -func (r *gormExecutionLogRepository) CountIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint) (int64, error) { +func (r *gormExecutionLogRepository) CountIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint32) (int64, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "CountIncompleteTasksByPlanLogID") var count int64 err := r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}). @@ -326,7 +326,7 @@ func (r *gormExecutionLogRepository) CountIncompleteTasksByPlanLogID(ctx context } // FailPlanExecution 将指定的计划执行标记为失败 -func (r *gormExecutionLogRepository) FailPlanExecution(ctx context.Context, planLogID uint, errorMessage string) error { +func (r *gormExecutionLogRepository) FailPlanExecution(ctx context.Context, planLogID uint32, errorMessage string) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "FailPlanExecution") return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}). Where("id = ?", planLogID). @@ -338,7 +338,7 @@ func (r *gormExecutionLogRepository) FailPlanExecution(ctx context.Context, plan } // CancelIncompleteTasksByPlanLogID 取消一个计划执行中的所有未完成任务 -func (r *gormExecutionLogRepository) CancelIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint, reason string) error { +func (r *gormExecutionLogRepository) CancelIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint32, reason string) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "CancelIncompleteTasksByPlanLogID") return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}). Where("plan_execution_log_id = ? AND status IN (?, ?)", diff --git a/internal/infra/repository/medication_log_repository.go b/internal/infra/repository/medication_log_repository.go index 6db863d..ac99400 100644 --- a/internal/infra/repository/medication_log_repository.go +++ b/internal/infra/repository/medication_log_repository.go @@ -12,10 +12,10 @@ import ( // MedicationLogListOptions 定义了查询用药记录时的可选参数 type MedicationLogListOptions struct { - PigBatchID *uint - MedicationID *uint + PigBatchID *uint32 + MedicationID *uint32 Reason *models.MedicationReasonType - OperatorID *uint + OperatorID *uint32 StartTime *time.Time EndTime *time.Time OrderBy string // 例如 "happened_at desc" diff --git a/internal/infra/repository/notification_repository.go b/internal/infra/repository/notification_repository.go index 10798fc..3f27a57 100644 --- a/internal/infra/repository/notification_repository.go +++ b/internal/infra/repository/notification_repository.go @@ -13,7 +13,7 @@ import ( // NotificationListOptions 定义了查询通知列表时的可选参数 type NotificationListOptions struct { - UserID *uint // 按用户ID过滤 + UserID *uint32 // 按用户ID过滤 NotifierType *models.NotifierType // 按通知器类型过滤 Status *models.NotificationStatus // 按通知状态过滤 (例如:"success", "failed") Level *zapcore.Level // 按通知等级过滤 (例如:"info", "warning", "error") diff --git a/internal/infra/repository/pending_collection_repository.go b/internal/infra/repository/pending_collection_repository.go index 77ab3ad..55360e8 100644 --- a/internal/infra/repository/pending_collection_repository.go +++ b/internal/infra/repository/pending_collection_repository.go @@ -12,7 +12,7 @@ import ( // PendingCollectionListOptions 定义了查询待采集请求时的可选参数 type PendingCollectionListOptions struct { - DeviceID *uint + DeviceID *uint32 Status *models.PendingCollectionStatus StartTime *time.Time // 基于 created_at 字段 EndTime *time.Time // 基于 created_at 字段 diff --git a/internal/infra/repository/pending_task_repository.go b/internal/infra/repository/pending_task_repository.go index 06824df..25f7d7e 100644 --- a/internal/infra/repository/pending_task_repository.go +++ b/internal/infra/repository/pending_task_repository.go @@ -16,27 +16,27 @@ import ( // PendingTaskRepository 定义了与待执行任务队列交互的接口。 type PendingTaskRepository interface { FindAllPendingTasks(ctx context.Context) ([]models.PendingTask, error) - FindPendingTriggerByPlanID(ctx context.Context, planID uint) (*models.PendingTask, error) - DeletePendingTasksByIDs(ctx context.Context, ids []uint) error + FindPendingTriggerByPlanID(ctx context.Context, planID uint32) (*models.PendingTask, error) + DeletePendingTasksByIDs(ctx context.Context, ids []uint32) error CreatePendingTask(ctx context.Context, task *models.PendingTask) error CreatePendingTasksInBatch(ctx context.Context, tasks []*models.PendingTask) error // UpdatePendingTaskExecuteAt 更新指定待执行任务的执行时间 - UpdatePendingTaskExecuteAt(ctx context.Context, id uint, executeAt time.Time) error + UpdatePendingTaskExecuteAt(ctx context.Context, id uint32, executeAt time.Time) error // ClearAllPendingTasks 清空所有待执行任务 ClearAllPendingTasks(ctx context.Context) error // ClaimNextAvailableTask 原子地认领下一个可用的任务。 // 它会同时返回被认领任务对应的日志对象,以及被删除的待办任务对象的内存副本。 - ClaimNextAvailableTask(ctx context.Context, excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error) + ClaimNextAvailableTask(ctx context.Context, excludePlanIDs []uint32) (*models.TaskExecutionLog, *models.PendingTask, error) // RequeueTask 安全地将一个任务重新放回队列。 RequeueTask(ctx context.Context, originalPendingTask *models.PendingTask) error // FindPendingTasksByTaskLogIDs 根据 TaskExecutionLogID 列表查找对应的待执行任务 - FindPendingTasksByTaskLogIDs(ctx context.Context, taskLogIDs []uint) ([]models.PendingTask, error) + FindPendingTasksByTaskLogIDs(ctx context.Context, taskLogIDs []uint32) ([]models.PendingTask, error) // DeletePendingTasksByPlanLogID 删除与指定计划执行日志ID相关的所有待执行任务 - DeletePendingTasksByPlanLogID(ctx context.Context, planLogID uint) error + DeletePendingTasksByPlanLogID(ctx context.Context, planLogID uint32) error } // gormPendingTaskRepository 是使用 GORM 的具体实现。 @@ -59,7 +59,7 @@ func (r *gormPendingTaskRepository) FindAllPendingTasks(ctx context.Context) ([] return tasks, err } -func (r *gormPendingTaskRepository) FindPendingTriggerByPlanID(ctx context.Context, planID uint) (*models.PendingTask, error) { +func (r *gormPendingTaskRepository) FindPendingTriggerByPlanID(ctx context.Context, planID uint32) (*models.PendingTask, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPendingTriggerByPlanID") var pendingTask models.PendingTask // 关键修改:通过 JOIN tasks 表并查询 parameters JSON 字段来查找触发器,而不是依赖 task.plan_id @@ -73,7 +73,7 @@ func (r *gormPendingTaskRepository) FindPendingTriggerByPlanID(ctx context.Conte return &pendingTask, err } -func (r *gormPendingTaskRepository) DeletePendingTasksByIDs(ctx context.Context, ids []uint) error { +func (r *gormPendingTaskRepository) DeletePendingTasksByIDs(ctx context.Context, ids []uint32) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePendingTasksByIDs") if len(ids) == 0 { return nil @@ -96,7 +96,7 @@ func (r *gormPendingTaskRepository) CreatePendingTasksInBatch(ctx context.Contex } // UpdatePendingTaskExecuteAt 更新指定待执行任务的执行时间 -func (r *gormPendingTaskRepository) UpdatePendingTaskExecuteAt(ctx context.Context, id uint, executeAt time.Time) error { +func (r *gormPendingTaskRepository) UpdatePendingTaskExecuteAt(ctx context.Context, id uint32, executeAt time.Time) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePendingTaskExecuteAt") return r.db.WithContext(repoCtx).Model(&models.PendingTask{}).Where("id = ?", id).Update("execute_at", executeAt).Error } @@ -108,7 +108,7 @@ func (r *gormPendingTaskRepository) ClearAllPendingTasks(ctx context.Context) er } // ClaimNextAvailableTask 以原子方式认领下一个可用的任务。 -func (r *gormPendingTaskRepository) ClaimNextAvailableTask(ctx context.Context, excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error) { +func (r *gormPendingTaskRepository) ClaimNextAvailableTask(ctx context.Context, excludePlanIDs []uint32) (*models.TaskExecutionLog, *models.PendingTask, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "ClaimNextAvailableTask") var log models.TaskExecutionLog var pendingTask models.PendingTask @@ -175,7 +175,7 @@ func (r *gormPendingTaskRepository) RequeueTask(ctx context.Context, originalPen } // FindPendingTasksByTaskLogIDs 根据 TaskExecutionLogID 列表查找对应的待执行任务 -func (r *gormPendingTaskRepository) FindPendingTasksByTaskLogIDs(ctx context.Context, taskLogIDs []uint) ([]models.PendingTask, error) { +func (r *gormPendingTaskRepository) FindPendingTasksByTaskLogIDs(ctx context.Context, taskLogIDs []uint32) ([]models.PendingTask, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPendingTasksByTaskLogIDs") if len(taskLogIDs) == 0 { return []models.PendingTask{}, nil @@ -186,7 +186,7 @@ func (r *gormPendingTaskRepository) FindPendingTasksByTaskLogIDs(ctx context.Con } // DeletePendingTasksByPlanLogID 删除与指定计划执行日志ID相关的所有待执行任务 -func (r *gormPendingTaskRepository) DeletePendingTasksByPlanLogID(ctx context.Context, planLogID uint) error { +func (r *gormPendingTaskRepository) DeletePendingTasksByPlanLogID(ctx context.Context, planLogID uint32) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePendingTasksByPlanLogID") // 使用子查询找到所有与 planLogID 相关的 task_execution_log_id subQuery := r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Select("id").Where("plan_execution_log_id = ?", planLogID) diff --git a/internal/infra/repository/pig_batch_log_repository.go b/internal/infra/repository/pig_batch_log_repository.go index c07b70c..54eaf4a 100644 --- a/internal/infra/repository/pig_batch_log_repository.go +++ b/internal/infra/repository/pig_batch_log_repository.go @@ -12,9 +12,9 @@ import ( // PigBatchLogListOptions 定义了查询猪批次日志时的可选参数 type PigBatchLogListOptions struct { - PigBatchID *uint + PigBatchID *uint32 ChangeType *models.LogChangeType - OperatorID *uint + OperatorID *uint32 StartTime *time.Time // 基于 happened_at 字段 EndTime *time.Time // 基于 happened_at 字段 OrderBy string // 例如 "happened_at asc" @@ -26,10 +26,10 @@ type PigBatchLogRepository interface { CreateTx(ctx context.Context, tx *gorm.DB, log *models.PigBatchLog) error // GetLogsByBatchIDAndDateRangeTx 在指定的事务中,获取指定批次在特定时间范围内的所有日志记录。 - GetLogsByBatchIDAndDateRangeTx(ctx context.Context, tx *gorm.DB, batchID uint, startDate, endDate time.Time) ([]*models.PigBatchLog, error) + GetLogsByBatchIDAndDateRangeTx(ctx context.Context, tx *gorm.DB, batchID uint32, startDate, endDate time.Time) ([]*models.PigBatchLog, error) // GetLastLogByBatchIDTx 在指定的事务中,获取某批次的最后一条日志记录。 - GetLastLogByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint) (*models.PigBatchLog, error) + GetLastLogByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint32) (*models.PigBatchLog, error) // List 支持分页和过滤的列表查询 List(ctx context.Context, opts PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error) @@ -53,7 +53,7 @@ func (r *gormPigBatchLogRepository) CreateTx(ctx context.Context, tx *gorm.DB, l } // GetLogsByBatchIDAndDateRangeTx 实现了在指定的事务中,获取指定批次在特定时间范围内的所有日志记录的逻辑。 -func (r *gormPigBatchLogRepository) GetLogsByBatchIDAndDateRangeTx(ctx context.Context, tx *gorm.DB, batchID uint, startDate, endDate time.Time) ([]*models.PigBatchLog, error) { +func (r *gormPigBatchLogRepository) GetLogsByBatchIDAndDateRangeTx(ctx context.Context, tx *gorm.DB, batchID uint32, startDate, endDate time.Time) ([]*models.PigBatchLog, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLogsByBatchIDAndDateRangeTx") var logs []*models.PigBatchLog err := tx.WithContext(repoCtx).Where("pig_batch_id = ? AND created_at >= ? AND created_at <= ?", batchID, startDate, endDate).Find(&logs).Error @@ -64,7 +64,7 @@ func (r *gormPigBatchLogRepository) GetLogsByBatchIDAndDateRangeTx(ctx context.C } // GetLastLogByBatchIDTx 实现了在指定的事务中,获取某批次的最后一条日志记录的逻辑。 -func (r *gormPigBatchLogRepository) GetLastLogByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint) (*models.PigBatchLog, error) { +func (r *gormPigBatchLogRepository) GetLastLogByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint32) (*models.PigBatchLog, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLastLogByBatchIDTx") var log models.PigBatchLog err := tx.WithContext(repoCtx).Where("pig_batch_id = ?", batchID).Order("id DESC").First(&log).Error diff --git a/internal/infra/repository/pig_batch_repository.go b/internal/infra/repository/pig_batch_repository.go index ae37ecd..40c94e0 100644 --- a/internal/infra/repository/pig_batch_repository.go +++ b/internal/infra/repository/pig_batch_repository.go @@ -14,13 +14,13 @@ import ( type PigBatchRepository interface { CreatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, error) CreatePigBatchTx(ctx context.Context, tx *gorm.DB, batch *models.PigBatch) (*models.PigBatch, error) - GetPigBatchByID(ctx context.Context, id uint) (*models.PigBatch, error) - GetPigBatchByIDTx(ctx context.Context, tx *gorm.DB, id uint) (*models.PigBatch, error) + GetPigBatchByID(ctx context.Context, id uint32) (*models.PigBatch, error) + GetPigBatchByIDTx(ctx context.Context, tx *gorm.DB, id uint32) (*models.PigBatch, error) // UpdatePigBatch 更新一个猪批次,返回更新后的批次、受影响的行数和错误 UpdatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, int64, error) // DeletePigBatch 根据ID删除一个猪批次,返回受影响的行数和错误 - DeletePigBatch(ctx context.Context, id uint) (int64, error) - DeletePigBatchTx(ctx context.Context, tx *gorm.DB, id uint) (int64, error) + DeletePigBatch(ctx context.Context, id uint32) (int64, error) + DeletePigBatchTx(ctx context.Context, tx *gorm.DB, id uint32) (int64, error) ListPigBatches(ctx context.Context, isActive *bool) ([]*models.PigBatch, error) // ListWeighingBatches 支持分页和过滤的批次称重列表查询 @@ -32,7 +32,7 @@ type PigBatchRepository interface { // WeighingBatchListOptions 定义了查询批次称重记录时的可选参数 type WeighingBatchListOptions struct { - PigBatchID *uint + PigBatchID *uint32 StartTime *time.Time // 基于 weighing_time 字段 EndTime *time.Time // 基于 weighing_time 字段 OrderBy string // 例如 "weighing_time asc" @@ -40,9 +40,9 @@ type WeighingBatchListOptions struct { // WeighingRecordListOptions 定义了查询单次称重记录时的可选参数 type WeighingRecordListOptions struct { - WeighingBatchID *uint - PenID *uint - OperatorID *uint + WeighingBatchID *uint32 + PenID *uint32 + OperatorID *uint32 StartTime *time.Time // 基于 weighing_time 字段 EndTime *time.Time // 基于 weighing_time 字段 OrderBy string // 例如 "weighing_time asc" @@ -75,7 +75,7 @@ func (r *gormPigBatchRepository) CreatePigBatchTx(ctx context.Context, tx *gorm. } // GetPigBatchByID 根据ID获取单个猪批次 -func (r *gormPigBatchRepository) GetPigBatchByID(ctx context.Context, id uint) (*models.PigBatch, error) { +func (r *gormPigBatchRepository) GetPigBatchByID(ctx context.Context, id uint32) (*models.PigBatch, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPigBatchByID") return r.GetPigBatchByIDTx(repoCtx, r.db, id) } @@ -92,12 +92,12 @@ func (r *gormPigBatchRepository) UpdatePigBatch(ctx context.Context, batch *mode } // DeletePigBatch 根据ID删除一个猪批次 (GORM 会执行软删除) -func (r *gormPigBatchRepository) DeletePigBatch(ctx context.Context, id uint) (int64, error) { +func (r *gormPigBatchRepository) DeletePigBatch(ctx context.Context, id uint32) (int64, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePigBatch") return r.DeletePigBatchTx(repoCtx, r.db, id) } -func (r *gormPigBatchRepository) DeletePigBatchTx(ctx context.Context, tx *gorm.DB, id uint) (int64, error) { +func (r *gormPigBatchRepository) DeletePigBatchTx(ctx context.Context, tx *gorm.DB, id uint32) (int64, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePigBatchTx") result := tx.WithContext(repoCtx).Delete(&models.PigBatch{}, id) if result.Error != nil { @@ -130,7 +130,7 @@ func (r *gormPigBatchRepository) ListPigBatches(ctx context.Context, isActive *b } // GetPigBatchByIDTx 在指定的事务中,通过ID获取单个猪批次 -func (r *gormPigBatchRepository) GetPigBatchByIDTx(ctx context.Context, tx *gorm.DB, id uint) (*models.PigBatch, error) { +func (r *gormPigBatchRepository) GetPigBatchByIDTx(ctx context.Context, tx *gorm.DB, id uint32) (*models.PigBatch, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPigBatchByIDTx") var batch models.PigBatch if err := tx.WithContext(repoCtx).First(&batch, id).Error; err != nil { diff --git a/internal/infra/repository/pig_farm_repository.go b/internal/infra/repository/pig_farm_repository.go index f70c75b..10068c5 100644 --- a/internal/infra/repository/pig_farm_repository.go +++ b/internal/infra/repository/pig_farm_repository.go @@ -13,13 +13,13 @@ import ( type PigFarmRepository interface { // PigHouse methods CreatePigHouse(ctx context.Context, house *models.PigHouse) error - GetPigHouseByID(ctx context.Context, id uint) (*models.PigHouse, error) + GetPigHouseByID(ctx context.Context, id uint32) (*models.PigHouse, error) ListPigHouses(ctx context.Context) ([]models.PigHouse, error) // UpdatePigHouse 更新一个猪舍,返回受影响的行数和错误 UpdatePigHouse(ctx context.Context, house *models.PigHouse) (int64, error) // DeletePigHouse 根据ID删除一个猪舍,返回受影响的行数和错误 - DeletePigHouse(ctx context.Context, id uint) (int64, error) - CountPensInHouse(ctx context.Context, houseID uint) (int64, error) + DeletePigHouse(ctx context.Context, id uint32) (int64, error) + CountPensInHouse(ctx context.Context, houseID uint32) (int64, error) } // gormPigFarmRepository 是 PigFarmRepository 的 GORM 实现 @@ -42,7 +42,7 @@ func (r *gormPigFarmRepository) CreatePigHouse(ctx context.Context, house *model } // GetPigHouseByID 根据ID获取单个猪舍 -func (r *gormPigFarmRepository) GetPigHouseByID(ctx context.Context, id uint) (*models.PigHouse, error) { +func (r *gormPigFarmRepository) GetPigHouseByID(ctx context.Context, id uint32) (*models.PigHouse, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPigHouseByID") var house models.PigHouse if err := r.db.WithContext(repoCtx).First(&house, id).Error; err != nil { @@ -72,7 +72,7 @@ func (r *gormPigFarmRepository) UpdatePigHouse(ctx context.Context, house *model } // DeletePigHouse 根据ID删除一个猪舍,返回受影响的行数和错误 -func (r *gormPigFarmRepository) DeletePigHouse(ctx context.Context, id uint) (int64, error) { +func (r *gormPigFarmRepository) DeletePigHouse(ctx context.Context, id uint32) (int64, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePigHouse") result := r.db.WithContext(repoCtx).Delete(&models.PigHouse{}, id) if result.Error != nil { @@ -82,7 +82,7 @@ func (r *gormPigFarmRepository) DeletePigHouse(ctx context.Context, id uint) (in } // CountPensInHouse 统计猪舍中的猪栏数量 -func (r *gormPigFarmRepository) CountPensInHouse(ctx context.Context, houseID uint) (int64, error) { +func (r *gormPigFarmRepository) CountPensInHouse(ctx context.Context, houseID uint32) (int64, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "CountPensInHouse") var count int64 err := r.db.WithContext(repoCtx).Model(&models.Pen{}).Where("house_id = ?", houseID).Count(&count).Error diff --git a/internal/infra/repository/pig_pen_repository.go b/internal/infra/repository/pig_pen_repository.go index ac83df9..4ec91e5 100644 --- a/internal/infra/repository/pig_pen_repository.go +++ b/internal/infra/repository/pig_pen_repository.go @@ -13,18 +13,18 @@ import ( type PigPenRepository interface { CreatePen(ctx context.Context, pen *models.Pen) error // GetPenByID 根据ID获取单个猪栏 (非事务性) - GetPenByID(ctx context.Context, id uint) (*models.Pen, error) + GetPenByID(ctx context.Context, id uint32) (*models.Pen, error) // GetPenByIDTx 根据ID获取单个猪栏 (事务性) - GetPenByIDTx(ctx context.Context, tx *gorm.DB, id uint) (*models.Pen, error) + GetPenByIDTx(ctx context.Context, tx *gorm.DB, id uint32) (*models.Pen, error) ListPens(ctx context.Context) ([]models.Pen, error) // UpdatePen 更新一个猪栏,返回受影响的行数和错误 UpdatePen(ctx context.Context, pen *models.Pen) (int64, error) // DeletePen 根据ID删除一个猪栏,返回受影响的行数和错误 - DeletePen(ctx context.Context, id uint) (int64, error) + DeletePen(ctx context.Context, id uint32) (int64, error) // GetPensByBatchIDTx 根据批次ID获取所有关联的猪栏 (事务性) - GetPensByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint) ([]*models.Pen, error) + GetPensByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint32) ([]*models.Pen, error) // UpdatePenFieldsTx 更新猪栏的指定字段 (事务性) - UpdatePenFieldsTx(ctx context.Context, tx *gorm.DB, penID uint, updates map[string]interface{}) error + UpdatePenFieldsTx(ctx context.Context, tx *gorm.DB, penID uint32, updates map[string]interface{}) error } // gormPigPenRepository 是 PigPenRepository 接口的 GORM 实现。 @@ -45,13 +45,13 @@ func (r *gormPigPenRepository) CreatePen(ctx context.Context, pen *models.Pen) e } // GetPenByID 根据ID获取单个猪栏 (非事务性) -func (r *gormPigPenRepository) GetPenByID(ctx context.Context, id uint) (*models.Pen, error) { +func (r *gormPigPenRepository) GetPenByID(ctx context.Context, id uint32) (*models.Pen, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPenByID") return r.GetPenByIDTx(repoCtx, r.db, id) // 非Tx方法直接调用Tx方法 } // GetPenByIDTx 在指定的事务中,通过ID获取单个猪栏信息。 -func (r *gormPigPenRepository) GetPenByIDTx(ctx context.Context, tx *gorm.DB, id uint) (*models.Pen, error) { +func (r *gormPigPenRepository) GetPenByIDTx(ctx context.Context, tx *gorm.DB, id uint32) (*models.Pen, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPenByIDTx") var pen models.Pen if err := tx.WithContext(repoCtx).First(&pen, id).Error; err != nil { @@ -81,7 +81,7 @@ func (r *gormPigPenRepository) UpdatePen(ctx context.Context, pen *models.Pen) ( } // DeletePen 根据ID删除一个猪栏,返回受影响的行数和错误 -func (r *gormPigPenRepository) DeletePen(ctx context.Context, id uint) (int64, error) { +func (r *gormPigPenRepository) DeletePen(ctx context.Context, id uint32) (int64, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePen") result := r.db.WithContext(repoCtx).Delete(&models.Pen{}, id) if result.Error != nil { @@ -91,7 +91,7 @@ func (r *gormPigPenRepository) DeletePen(ctx context.Context, id uint) (int64, e } // GetPensByBatchIDTx 在指定的事务中,获取一个猪群当前关联的所有猪栏。 -func (r *gormPigPenRepository) GetPensByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint) ([]*models.Pen, error) { +func (r *gormPigPenRepository) GetPensByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint32) ([]*models.Pen, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPensByBatchIDTx") var pens []*models.Pen // 注意:PigBatchID 是指针类型,需要处理 nil 值 @@ -103,7 +103,7 @@ func (r *gormPigPenRepository) GetPensByBatchIDTx(ctx context.Context, tx *gorm. } // UpdatePenFieldsTx 在指定的事务中,更新一个猪栏的指定字段。 -func (r *gormPigPenRepository) UpdatePenFieldsTx(ctx context.Context, tx *gorm.DB, penID uint, updates map[string]interface{}) error { +func (r *gormPigPenRepository) UpdatePenFieldsTx(ctx context.Context, tx *gorm.DB, penID uint32, updates map[string]interface{}) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePenFieldsTx") result := tx.WithContext(repoCtx).Model(&models.Pen{}).Where("id = ?", penID).Updates(updates) return result.Error diff --git a/internal/infra/repository/pig_sick_repository.go b/internal/infra/repository/pig_sick_repository.go index 61f63e9..8a8b569 100644 --- a/internal/infra/repository/pig_sick_repository.go +++ b/internal/infra/repository/pig_sick_repository.go @@ -13,11 +13,11 @@ import ( // PigSickLogListOptions 定义了查询病猪日志时的可选参数 type PigSickLogListOptions struct { - PigBatchID *uint - PenID *uint + PigBatchID *uint32 + PenID *uint32 Reason *models.PigBatchSickPigReasonType TreatmentLocation *models.PigBatchSickPigTreatmentLocation - OperatorID *uint + OperatorID *uint32 StartTime *time.Time // 基于 happened_at 字段 EndTime *time.Time // 基于 happened_at 字段 OrderBy string // 例如 "happened_at desc" @@ -30,7 +30,7 @@ type PigSickLogRepository interface { CreatePigSickLogTx(ctx context.Context, tx *gorm.DB, log *models.PigSickLog) error // GetLastLogByBatchTx 在事务中获取指定批次和猪栏的最新一条 PigSickLog 记录 - GetLastLogByBatchTx(ctx context.Context, tx *gorm.DB, batchID uint) (*models.PigSickLog, error) + GetLastLogByBatchTx(ctx context.Context, tx *gorm.DB, batchID uint32) (*models.PigSickLog, error) // ListPigSickLogs 支持分页和过滤的病猪日志列表查询 ListPigSickLogs(ctx context.Context, opts PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) @@ -58,7 +58,7 @@ func (r *gormPigSickLogRepository) CreatePigSickLogTx(ctx context.Context, tx *g } // GetLastLogByBatchTx 在事务中获取指定批次和猪栏的最新一条 PigSickLog 记录 -func (r *gormPigSickLogRepository) GetLastLogByBatchTx(ctx context.Context, tx *gorm.DB, batchID uint) (*models.PigSickLog, error) { +func (r *gormPigSickLogRepository) GetLastLogByBatchTx(ctx context.Context, tx *gorm.DB, batchID uint32) (*models.PigSickLog, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLastLogByBatchTx") var lastLog models.PigSickLog err := tx.WithContext(repoCtx). diff --git a/internal/infra/repository/pig_trade_repository.go b/internal/infra/repository/pig_trade_repository.go index 3e6cd25..06f85bb 100644 --- a/internal/infra/repository/pig_trade_repository.go +++ b/internal/infra/repository/pig_trade_repository.go @@ -12,9 +12,9 @@ import ( // PigPurchaseListOptions 定义了查询猪只采购记录时的可选参数 type PigPurchaseListOptions struct { - PigBatchID *uint + PigBatchID *uint32 Supplier *string - OperatorID *uint + OperatorID *uint32 StartTime *time.Time // 基于 purchase_date 字段 EndTime *time.Time // 基于 purchase_date 字段 OrderBy string // 例如 "purchase_date desc" @@ -22,9 +22,9 @@ type PigPurchaseListOptions struct { // PigSaleListOptions 定义了查询猪只销售记录时的可选参数 type PigSaleListOptions struct { - PigBatchID *uint + PigBatchID *uint32 Buyer *string - OperatorID *uint + OperatorID *uint32 StartTime *time.Time // 基于 sale_date 字段 EndTime *time.Time // 基于 sale_date 字段 OrderBy string // 例如 "sale_date desc" diff --git a/internal/infra/repository/pig_transfer_log_repository.go b/internal/infra/repository/pig_transfer_log_repository.go index ca0d5e5..2a5fc85 100644 --- a/internal/infra/repository/pig_transfer_log_repository.go +++ b/internal/infra/repository/pig_transfer_log_repository.go @@ -12,10 +12,10 @@ import ( // PigTransferLogListOptions 定义了查询猪只迁移日志时的可选参数 type PigTransferLogListOptions struct { - PigBatchID *uint - PenID *uint + PigBatchID *uint32 + PenID *uint32 TransferType *models.PigTransferType // 迁移类型 - OperatorID *uint + OperatorID *uint32 CorrelationID *string StartTime *time.Time // 基于 transfer_time 字段 EndTime *time.Time // 基于 transfer_time 字段 @@ -28,7 +28,7 @@ type PigTransferLogRepository interface { CreatePigTransferLog(ctx context.Context, tx *gorm.DB, log *models.PigTransferLog) error // GetLogsForPenSince 获取指定猪栏自特定时间点以来的所有迁移日志,按时间倒序排列。 - GetLogsForPenSince(ctx context.Context, tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error) + GetLogsForPenSince(ctx context.Context, tx *gorm.DB, penID uint32, since time.Time) ([]*models.PigTransferLog, error) // ListPigTransferLogs 支持分页和过滤的猪只迁移日志列表查询 ListPigTransferLogs(ctx context.Context, opts PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error) @@ -52,7 +52,7 @@ func (r *gormPigTransferLogRepository) CreatePigTransferLog(ctx context.Context, } // GetLogsForPenSince 实现了获取猪栏自特定时间点以来所有迁移日志的逻辑。 -func (r *gormPigTransferLogRepository) GetLogsForPenSince(ctx context.Context, tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error) { +func (r *gormPigTransferLogRepository) GetLogsForPenSince(ctx context.Context, tx *gorm.DB, penID uint32, since time.Time) ([]*models.PigTransferLog, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLogsForPenSince") var logs []*models.PigTransferLog err := tx.WithContext(repoCtx).Where("pen_id = ? AND transfer_time >= ?", penID, since).Order("transfer_time DESC").Find(&logs).Error diff --git a/internal/infra/repository/plan_repository.go b/internal/infra/repository/plan_repository.go index 38fe3f4..9f01f57 100644 --- a/internal/infra/repository/plan_repository.go +++ b/internal/infra/repository/plan_repository.go @@ -44,11 +44,11 @@ type PlanRepository interface { // ListPlans 获取计划列表,支持过滤和分页 ListPlans(ctx context.Context, opts ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error) // GetBasicPlanByID 根据ID获取计划的基本信息,不包含子计划和任务详情 - GetBasicPlanByID(ctx context.Context, id uint) (*models.Plan, error) + GetBasicPlanByID(ctx context.Context, id uint32) (*models.Plan, error) // GetPlanByID 根据ID获取计划,包含子计划和任务详情 - GetPlanByID(ctx context.Context, id uint) (*models.Plan, error) + GetPlanByID(ctx context.Context, id uint32) (*models.Plan, error) // GetPlansByIDs 根据ID列表获取计划,不包含子计划和任务详情 - GetPlansByIDs(ctx context.Context, ids []uint) ([]models.Plan, error) + GetPlansByIDs(ctx context.Context, ids []uint32) ([]models.Plan, error) // GetSystemPlanByName 根据计划名称获取系统计划,包含子计划和任务详情 GetSystemPlanByName(ctx context.Context, planName models.PlanName) (*models.Plan, error) // CreatePlan 创建一个新的计划 @@ -60,35 +60,35 @@ type PlanRepository interface { // UpdatePlan 更新计划的所有字段 UpdatePlan(ctx context.Context, plan *models.Plan) error // UpdatePlanStatus 更新指定计划的状态 - UpdatePlanStatus(ctx context.Context, id uint, status models.PlanStatus) error + UpdatePlanStatus(ctx context.Context, id uint32, status models.PlanStatus) error // UpdateExecuteCount 更新指定计划的执行计数 - UpdateExecuteCount(ctx context.Context, id uint, count uint) error + UpdateExecuteCount(ctx context.Context, id uint32, count uint32) error // DeletePlan 根据ID删除计划,同时删除其关联的任务(非子任务)或子计划关联 - DeletePlan(ctx context.Context, id uint) error + DeletePlan(ctx context.Context, id uint32) error // FlattenPlanTasks 递归展开计划,返回按执行顺序排列的所有任务列表 - FlattenPlanTasks(ctx context.Context, planID uint) ([]models.Task, error) + FlattenPlanTasks(ctx context.Context, planID uint32) ([]models.Task, error) // DeleteTask 根据ID删除任务 DeleteTask(ctx context.Context, id int) error // FindTaskByID 根据ID获取任务的基本信息 FindTaskByID(ctx context.Context, id int) (*models.Task, error) // FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task - FindPlanAnalysisTaskByParamsPlanID(ctx context.Context, paramsPlanID uint) (*models.Task, error) + FindPlanAnalysisTaskByParamsPlanID(ctx context.Context, paramsPlanID uint32) (*models.Task, error) // FindRunnablePlans 获取所有应执行的计划 FindRunnablePlans(ctx context.Context) ([]*models.Plan, error) // FindInactivePlans 获取所有已禁用或已停止的计划 FindInactivePlans(ctx context.Context) ([]*models.Plan, error) // FindPlanAnalysisTaskByPlanID 根据 PlanID 找到其关联的 'plan_analysis' 任务 - FindPlanAnalysisTaskByPlanID(ctx context.Context, planID uint) (*models.Task, error) + FindPlanAnalysisTaskByPlanID(ctx context.Context, planID uint32) (*models.Task, error) // CreatePlanAnalysisTask 创建一个 plan_analysis 类型的任务并返回它 CreatePlanAnalysisTask(ctx context.Context, plan *models.Plan) (*models.Task, error) // FindPlansWithPendingTasks 查找所有正在执行的计划 FindPlansWithPendingTasks(ctx context.Context) ([]*models.Plan, error) // StopPlanTransactionally 停止一个计划的执行,包括更新状态、移除待执行任务和更新执行日志 - StopPlanTransactionally(ctx context.Context, planID uint) error + StopPlanTransactionally(ctx context.Context, planID uint32) error // UpdatePlanStateAfterExecution 更新计划执行后的状态(计数和状态) - UpdatePlanStateAfterExecution(ctx context.Context, planID uint, newCount uint, newStatus models.PlanStatus) error + UpdatePlanStateAfterExecution(ctx context.Context, planID uint32, newCount uint32, newStatus models.PlanStatus) error // ListTasksByDeviceID 根据设备ID获取关联任务列表 - ListTasksByDeviceID(ctx context.Context, deviceID uint) ([]*models.Task, error) + ListTasksByDeviceID(ctx context.Context, deviceID uint32) ([]*models.Task, error) } // gormPlanRepository 是 PlanRepository 的 GORM 实现 @@ -140,7 +140,7 @@ func (r *gormPlanRepository) ListPlans(ctx context.Context, opts ListPlansOption } // GetBasicPlanByID 根据ID获取计划的基本信息,不包含子计划和任务详情 -func (r *gormPlanRepository) GetBasicPlanByID(ctx context.Context, id uint) (*models.Plan, error) { +func (r *gormPlanRepository) GetBasicPlanByID(ctx context.Context, id uint32) (*models.Plan, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetBasicPlanByID") var plan models.Plan // GORM 默认不会加载关联,除非使用 Preload,所以直接 First 即可满足要求 @@ -152,7 +152,7 @@ func (r *gormPlanRepository) GetBasicPlanByID(ctx context.Context, id uint) (*mo } // GetPlansByIDs 根据ID列表获取计划,不包含子计划和任务详情 -func (r *gormPlanRepository) GetPlansByIDs(ctx context.Context, ids []uint) ([]models.Plan, error) { +func (r *gormPlanRepository) GetPlansByIDs(ctx context.Context, ids []uint32) ([]models.Plan, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPlansByIDs") var plans []models.Plan if len(ids) == 0 { @@ -183,7 +183,7 @@ func (r *gormPlanRepository) GetSystemPlanByName(ctx context.Context, planName m } // GetPlanByID 根据ID获取计划,包含子计划和任务详情 -func (r *gormPlanRepository) GetPlanByID(ctx context.Context, id uint) (*models.Plan, error) { +func (r *gormPlanRepository) GetPlanByID(ctx context.Context, id uint32) (*models.Plan, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPlanByID") var plan models.Plan @@ -254,7 +254,7 @@ func (r *gormPlanRepository) CreatePlanTx(ctx context.Context, tx *gorm.DB, plan // 如果是子计划类型,验证所有子计划是否存在且ID不为0 if plan.ContentType == models.PlanContentTypeSubPlans { - childIDsToValidate := make(map[uint]bool) + childIDsToValidate := make(map[uint32]bool) for _, subPlanLink := range plan.SubPlans { if subPlanLink.ChildPlanID == 0 { return ErrSubPlanIDIsZeroOnCreate @@ -262,7 +262,7 @@ func (r *gormPlanRepository) CreatePlanTx(ctx context.Context, tx *gorm.DB, plan childIDsToValidate[subPlanLink.ChildPlanID] = true } - var ids []uint + var ids []uint32 for id := range childIDsToValidate { ids = append(ids, id) } @@ -334,8 +334,8 @@ func (r *gormPlanRepository) validatePlanTree(ctx context.Context, tx *gorm.DB, } // 2. 递归验证所有子节点,并检测循环引用 - allIDs := make(map[uint]bool) - recursionStack := make(map[uint]bool) + allIDs := make(map[uint32]bool) + recursionStack := make(map[uint32]bool) if err := validateNodeAndDetectCycles(plan, allIDs, recursionStack); err != nil { return err } @@ -346,7 +346,7 @@ func (r *gormPlanRepository) validatePlanTree(ctx context.Context, tx *gorm.DB, } // 4. 一次性数据库存在性校验 - var idsToCheck []uint + var idsToCheck []uint32 for id := range allIDs { idsToCheck = append(idsToCheck, id) } @@ -364,7 +364,7 @@ func (r *gormPlanRepository) validatePlanTree(ctx context.Context, tx *gorm.DB, } // validateNodeAndDetectCycles 递归地验证节点有效性并检测循环引用 -func validateNodeAndDetectCycles(plan *models.Plan, allIDs, recursionStack map[uint]bool) error { +func validateNodeAndDetectCycles(plan *models.Plan, allIDs, recursionStack map[uint32]bool) error { if plan == nil { return nil } @@ -469,7 +469,7 @@ func (r *gormPlanRepository) reconcileSubPlans(ctx context.Context, tx *gorm.DB, return err } - existingLinkMap := make(map[uint]bool) + existingLinkMap := make(map[uint32]bool) for _, link := range existingLinks { existingLinkMap[link.ID] = true } @@ -489,7 +489,7 @@ func (r *gormPlanRepository) reconcileSubPlans(ctx context.Context, tx *gorm.DB, } } - var linksToDelete []uint + var linksToDelete []uint32 for id := range existingLinkMap { linksToDelete = append(linksToDelete, id) } @@ -503,7 +503,7 @@ func (r *gormPlanRepository) reconcileSubPlans(ctx context.Context, tx *gorm.DB, } // DeletePlan 根据ID删除计划,同时删除其关联的任务(非子任务)或子计划关联 -func (r *gormPlanRepository) DeletePlan(ctx context.Context, id uint) error { +func (r *gormPlanRepository) DeletePlan(ctx context.Context, id uint32) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePlan") return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error { // 1. 检查该计划是否是其他计划的子计划 @@ -545,7 +545,7 @@ func (r *gormPlanRepository) DeletePlan(ctx context.Context, id uint) error { } // FlattenPlanTasks 递归展开计划,返回按执行顺序排列的所有任务列表 -func (r *gormPlanRepository) FlattenPlanTasks(ctx context.Context, planID uint) ([]models.Task, error) { +func (r *gormPlanRepository) FlattenPlanTasks(ctx context.Context, planID uint32) ([]models.Task, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FlattenPlanTasks") plan, err := r.GetPlanByID(repoCtx, planID) if err != nil { @@ -645,7 +645,7 @@ func (r *gormPlanRepository) deleteTasksTx(ctx context.Context, tx *gorm.DB, ids } // FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task -func (r *gormPlanRepository) FindPlanAnalysisTaskByParamsPlanID(ctx context.Context, paramsPlanID uint) (*models.Task, error) { +func (r *gormPlanRepository) FindPlanAnalysisTaskByParamsPlanID(ctx context.Context, paramsPlanID uint32) (*models.Task, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPlanAnalysisTaskByParamsPlanID") return r.findPlanAnalysisTask(repoCtx, r.db, paramsPlanID) } @@ -722,7 +722,7 @@ func (r *gormPlanRepository) FindInactivePlans(ctx context.Context) ([]*models.P // findPlanAnalysisTask 是一个内部使用的、更高效的查找方法 // 关键修改:通过查询 parameters JSON 字段来查找 -func (r *gormPlanRepository) findPlanAnalysisTask(ctx context.Context, tx *gorm.DB, planID uint) (*models.Task, error) { +func (r *gormPlanRepository) findPlanAnalysisTask(ctx context.Context, tx *gorm.DB, planID uint32) (*models.Task, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "findPlanAnalysisTask") var task models.Task err := tx.WithContext(repoCtx).Where("type = ? AND parameters->>'plan_id' = ?", models.TaskPlanAnalysis, fmt.Sprintf("%d", planID)).First(&task).Error @@ -734,7 +734,7 @@ func (r *gormPlanRepository) findPlanAnalysisTask(ctx context.Context, tx *gorm. // FindPlanAnalysisTaskByPlanID 是暴露给外部的公共方法 // 关键修改:通过查询 parameters JSON 字段来查找 -func (r *gormPlanRepository) FindPlanAnalysisTaskByPlanID(ctx context.Context, planID uint) (*models.Task, error) { +func (r *gormPlanRepository) FindPlanAnalysisTaskByPlanID(ctx context.Context, planID uint32) (*models.Task, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPlanAnalysisTaskByPlanID") return r.findPlanAnalysisTask(repoCtx, r.db, planID) } @@ -769,7 +769,7 @@ func (r *gormPlanRepository) FindPlansWithPendingTasks(ctx context.Context) ([]* } // StopPlanTransactionally 停止一个计划的执行,包括更新状态、移除待执行任务和更新执行日志。 -func (r *gormPlanRepository) StopPlanTransactionally(ctx context.Context, planID uint) error { +func (r *gormPlanRepository) StopPlanTransactionally(ctx context.Context, planID uint32) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "StopPlanTransactionally") return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error { // 使用事务创建新的仓库实例,确保所有操作都在同一个事务中 @@ -800,7 +800,7 @@ func (r *gormPlanRepository) StopPlanTransactionally(ctx context.Context, planID } if len(taskLogs) > 0 { - var taskLogIDs []uint + var taskLogIDs []uint32 for _, tl := range taskLogs { taskLogIDs = append(taskLogIDs, tl.ID) } @@ -817,7 +817,7 @@ func (r *gormPlanRepository) StopPlanTransactionally(ctx context.Context, planID } if len(pendingTasks) > 0 { - var pendingTaskIDs []uint + var pendingTaskIDs []uint32 for _, pt := range pendingTasks { pendingTaskIDs = append(pendingTaskIDs, pt.ID) } @@ -837,7 +837,7 @@ func (r *gormPlanRepository) StopPlanTransactionally(ctx context.Context, planID } // UpdatePlanStatus 更新指定计划的状态 -func (r *gormPlanRepository) UpdatePlanStatus(ctx context.Context, id uint, status models.PlanStatus) error { +func (r *gormPlanRepository) UpdatePlanStatus(ctx context.Context, id uint32, status models.PlanStatus) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanStatus") result := r.db.WithContext(repoCtx).Model(&models.Plan{}).Where("id = ?", id).Update("status", status) if result.Error != nil { @@ -849,7 +849,7 @@ func (r *gormPlanRepository) UpdatePlanStatus(ctx context.Context, id uint, stat return nil } -func (r *gormPlanRepository) UpdatePlanStateAfterExecution(ctx context.Context, planID uint, newCount uint, newStatus models.PlanStatus) error { +func (r *gormPlanRepository) UpdatePlanStateAfterExecution(ctx context.Context, planID uint32, newCount uint32, newStatus models.PlanStatus) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanStateAfterExecution") return r.db.WithContext(repoCtx).Model(&models.Plan{}).Where("id = ?", planID).Updates(map[string]interface{}{ "execute_count": newCount, @@ -858,7 +858,7 @@ func (r *gormPlanRepository) UpdatePlanStateAfterExecution(ctx context.Context, } // UpdateExecuteCount 更新指定计划的执行计数 -func (r *gormPlanRepository) UpdateExecuteCount(ctx context.Context, id uint, count uint) error { +func (r *gormPlanRepository) UpdateExecuteCount(ctx context.Context, id uint32, count uint32) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateExecuteCount") result := r.db.WithContext(repoCtx).Model(&models.Plan{}).Where("id = ?", id).Update("execute_count", count) if result.Error != nil { @@ -881,7 +881,7 @@ func (r *gormPlanRepository) FindTaskByID(ctx context.Context, id int) (*models. return &task, nil } -func (r *gormPlanRepository) ListTasksByDeviceID(ctx context.Context, deviceID uint) ([]*models.Task, error) { +func (r *gormPlanRepository) ListTasksByDeviceID(ctx context.Context, deviceID uint32) ([]*models.Task, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "ListTasksByDeviceID") tasks := []*models.Task{} // 使用 Joins 方法来连接 tasks 表和 device_tasks 关联表, diff --git a/internal/infra/repository/raw_material_repository.go b/internal/infra/repository/raw_material_repository.go index ba6a757..2795167 100644 --- a/internal/infra/repository/raw_material_repository.go +++ b/internal/infra/repository/raw_material_repository.go @@ -12,7 +12,7 @@ import ( // RawMaterialPurchaseListOptions 定义了查询原料采购记录时的可选参数 type RawMaterialPurchaseListOptions struct { - RawMaterialID *uint + RawMaterialID *uint32 Supplier *string StartTime *time.Time // 基于 purchase_date 字段 EndTime *time.Time // 基于 purchase_date 字段 @@ -21,9 +21,9 @@ type RawMaterialPurchaseListOptions struct { // RawMaterialStockLogListOptions 定义了查询原料库存日志时的可选参数 type RawMaterialStockLogListOptions struct { - RawMaterialID *uint + RawMaterialID *uint32 SourceType *models.StockLogSourceType - SourceID *uint + SourceID *uint32 StartTime *time.Time // 基于 happened_at 字段 EndTime *time.Time // 基于 happened_at 字段 OrderBy string // 例如 "happened_at asc" @@ -31,9 +31,9 @@ type RawMaterialStockLogListOptions struct { // FeedUsageRecordListOptions 定义了查询饲料使用记录时的可选参数 type FeedUsageRecordListOptions struct { - PenID *uint - FeedFormulaID *uint - OperatorID *uint + PenID *uint32 + FeedFormulaID *uint32 + OperatorID *uint32 StartTime *time.Time // 基于 recorded_at 字段 EndTime *time.Time // 基于 recorded_at 字段 OrderBy string // 例如 "recorded_at asc" diff --git a/internal/infra/repository/sensor_data_repository.go b/internal/infra/repository/sensor_data_repository.go index 70c8810..88f1aea 100644 --- a/internal/infra/repository/sensor_data_repository.go +++ b/internal/infra/repository/sensor_data_repository.go @@ -12,7 +12,7 @@ import ( // SensorDataListOptions 定义了查询传感器数据列表时的可选参数 type SensorDataListOptions struct { - DeviceID *uint + DeviceID *uint32 SensorType *models.SensorType StartTime *time.Time EndTime *time.Time @@ -22,7 +22,7 @@ type SensorDataListOptions struct { // SensorDataRepository 定义了与传感器数据相关的数据库操作接口。 type SensorDataRepository interface { Create(ctx context.Context, sensorData *models.SensorData) error - GetLatestSensorDataByDeviceIDAndSensorType(ctx context.Context, deviceID uint, sensorType models.SensorType) (*models.SensorData, error) + GetLatestSensorDataByDeviceIDAndSensorType(ctx context.Context, deviceID uint32, sensorType models.SensorType) (*models.SensorData, error) // List 支持分页和过滤的列表查询 List(ctx context.Context, opts SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error) } @@ -45,7 +45,7 @@ func (r *gormSensorDataRepository) Create(ctx context.Context, sensorData *model } // GetLatestSensorDataByDeviceIDAndSensorType 根据设备ID和传感器类型查询最新的传感器数据。 -func (r *gormSensorDataRepository) GetLatestSensorDataByDeviceIDAndSensorType(ctx context.Context, deviceID uint, sensorType models.SensorType) (*models.SensorData, error) { +func (r *gormSensorDataRepository) GetLatestSensorDataByDeviceIDAndSensorType(ctx context.Context, deviceID uint32, sensorType models.SensorType) (*models.SensorData, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLatestSensorDataByDeviceIDAndSensorType") var sensorData models.SensorData // 增加一个时间范围来缩小查询范围, 从而加快查找速度, 当使用时序数据库时时间范围可以让数据库忽略时间靠前的分片 diff --git a/internal/infra/repository/user_action_log_repository.go b/internal/infra/repository/user_action_log_repository.go index e0102e9..fcd4f32 100644 --- a/internal/infra/repository/user_action_log_repository.go +++ b/internal/infra/repository/user_action_log_repository.go @@ -12,7 +12,7 @@ import ( // UserActionLogListOptions 定义了查询用户操作日志时的可选参数 type UserActionLogListOptions struct { - UserID *uint + UserID *uint32 Username *string ActionType *string Status *models.AuditStatus diff --git a/internal/infra/repository/user_repository.go b/internal/infra/repository/user_repository.go index 97b8bb2..66f6c87 100644 --- a/internal/infra/repository/user_repository.go +++ b/internal/infra/repository/user_repository.go @@ -15,7 +15,7 @@ import ( type UserRepository interface { Create(ctx context.Context, user *models.User) error FindByUsername(ctx context.Context, username string) (*models.User, error) - FindByID(ctx context.Context, id uint) (*models.User, error) + FindByID(ctx context.Context, id uint32) (*models.User, error) FindUserForLogin(ctx context.Context, identifier string) (*models.User, error) FindAll(ctx context.Context) ([]*models.User, error) } @@ -66,7 +66,7 @@ func (r *gormUserRepository) FindUserForLogin(ctx context.Context, identifier st } // FindByID 根据 ID 查找用户 -func (r *gormUserRepository) FindByID(ctx context.Context, id uint) (*models.User, error) { +func (r *gormUserRepository) FindByID(ctx context.Context, id uint32) (*models.User, error) { repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID") var user models.User if err := r.db.WithContext(repoCtx).First(&user, id).Error; err != nil { diff --git a/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go b/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go index c719246..a302e48 100644 --- a/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go +++ b/internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go @@ -502,7 +502,7 @@ func (t *LoRaMeshUartPassthroughTransport) handleUpstreamMessage(ctx context.Con } // recordSensorData 是一个通用方法,用于将传感器数据存入数据库。 -func (t *LoRaMeshUartPassthroughTransport) recordSensorData(ctx context.Context, areaControllerID uint, sensorDeviceID uint, eventTime time.Time, sensorType models.SensorType, data interface{}) { +func (t *LoRaMeshUartPassthroughTransport) recordSensorData(ctx context.Context, areaControllerID uint32, sensorDeviceID uint32, eventTime time.Time, sensorType models.SensorType, data interface{}) { loraCtx, logger := logs.Trace(ctx, t.ctx, "recordSensorData") jsonData, err := json.Marshal(data) diff --git a/internal/infra/utils/token/token_service.go b/internal/infra/utils/token/token_service.go index 69dcbcc..8311e2e 100644 --- a/internal/infra/utils/token/token_service.go +++ b/internal/infra/utils/token/token_service.go @@ -9,13 +9,13 @@ import ( // Claims 定义了 JWT 的声明结构 type Claims struct { - UserID uint `json:"user_id"` + UserID uint32 `json:"user_id"` jwt.RegisteredClaims } // Generator 定义了 token 操作的接口 type Generator interface { - GenerateToken(userID uint) (string, error) + GenerateToken(userID uint32) (string, error) ParseToken(tokenString string) (*Claims, error) } @@ -30,7 +30,7 @@ func NewTokenGenerator(secret []byte) Generator { } // GenerateToken 生成一个新的 JWT token -func (s *tokenGenerator) GenerateToken(userID uint) (string, error) { +func (s *tokenGenerator) GenerateToken(userID uint32) (string, error) { nowTime := time.Now() expireTime := nowTime.Add(24 * time.Hour) // Token 有效期为 24 小时