issue_62 #65
@@ -113,3 +113,14 @@ notify:
|
|||||||
# 定时采集配置
|
# 定时采集配置
|
||||||
collection:
|
collection:
|
||||||
interval: 1 # 采集间隔 (分钟)
|
interval: 1 # 采集间隔 (分钟)
|
||||||
|
|
||||||
|
# 告警通知配置
|
||||||
|
alarm_notification:
|
||||||
|
notification_intervals: # 告警通知间隔(分钟)
|
||||||
|
debug: 1
|
||||||
|
info: 1
|
||||||
|
warn: 1
|
||||||
|
error: 1
|
||||||
|
dpanic: 1
|
||||||
|
panic: 1
|
||||||
|
fatal: 1
|
||||||
|
|||||||
11
config.yml
11
config.yml
@@ -91,3 +91,14 @@ lora_mesh:
|
|||||||
# 定时采集配置
|
# 定时采集配置
|
||||||
collection:
|
collection:
|
||||||
interval: 1 # 采集间隔 (分钟)
|
interval: 1 # 采集间隔 (分钟)
|
||||||
|
|
||||||
|
# 告警通知配置
|
||||||
|
alarm_notification:
|
||||||
|
notification_intervals: # 告警通知间隔 (分钟)
|
||||||
|
debug: 1
|
||||||
|
info: 1
|
||||||
|
warn: 1
|
||||||
|
error: 1
|
||||||
|
dpanic: 1
|
||||||
|
panic: 1
|
||||||
|
fatal: 1
|
||||||
|
|||||||
149
design/exceeding-threshold-alarm/index.md
Normal file
149
design/exceeding-threshold-alarm/index.md
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# 需求
|
||||||
|
|
||||||
|
实现采集数据超过阈值报警
|
||||||
|
|
||||||
|
## issue
|
||||||
|
|
||||||
|
[实现采集数据超过阈值报警](http://git.huangwc.com/pig/pig-farm-controller/issues/62)
|
||||||
|
|
||||||
|
# 方案
|
||||||
|
|
||||||
|
1. **架构核心**: 新增一个 **告警领域服务**,作为告警系统的核心大脑,负责告警事件的生命周期管理。
|
||||||
|
2. **任务分离**:
|
||||||
|
* 新增 **阈值告警任务** (分为区域主控和普通设备两种),仅负责检测数据并将结果报告给领域服务。
|
||||||
|
* 新增 **告警通知发送任务**,作为一个独立的、系统预定义的定时任务,负责调用领域服务,获取并发送所有待处理的通知。
|
||||||
|
3. **计划调度**:
|
||||||
|
* 修改现有 "定时全量数据采集" 计划, 更名为 "周期性系统健康检查"。此计划包含固定的 **全量采集任务** 和由用户动态配置的
|
||||||
|
**阈值告警任务**。
|
||||||
|
* 新增一个独立的 "告警通知发送" 计划,用于定时执行固定的 **告警通知发送任务**。
|
||||||
|
4. **数据与接口**:
|
||||||
|
* 新增独立的告警记录表(建议采用“活跃告警表 + 历史告警超表”的模式)。
|
||||||
|
* 新增相应的告警配置管理接口。
|
||||||
|
|
||||||
|
## 方案细节
|
||||||
|
|
||||||
|
### 架构与职责划分
|
||||||
|
|
||||||
|
1. **告警领域服务 (`internal/domain/alarm/`) - 管理器**
|
||||||
|
* **职责**: 作为告警系统的核心大脑,负责处理告警事件的完整生命周期。
|
||||||
|
* **功能**:
|
||||||
|
* 接收来自检测任务的状态报告(包含设备ID、传感器类型、当前是否异常等信息)。
|
||||||
|
* 根据报告和数据库中的告警记录,决策是创建新告警、更新为已解决、还是因被忽略而跳过。
|
||||||
|
* 管理“手动忽略” (`Ignored`) 状态和忽略到期时间 (`ignored_until`)。
|
||||||
|
* 实现可配置的“重复通知”策略(`re_notification_interval`),决定何时对持续存在的告警再次发送通知。
|
||||||
|
* 提供接口供 `告警通知发送任务` 调用,以获取所有待处理的通知。
|
||||||
|
|
||||||
|
2. **阈值告警任务 (`internal/domain/task/`) - 检测器**
|
||||||
|
* **职责**: 职责纯粹,仅负责执行检测并将结果报告给告警领域服务。
|
||||||
|
* **逻辑**: 从传感器数据表读取最新数据 -> 与自身配置的阈值进行比对 -> 无论结果如何,都调用 `告警领域服务.ReportStatus()`
|
||||||
|
报告当前状态(正常或异常)。
|
||||||
|
* **无状态**: 任务本身不关心告警是否已存在或被忽略,它只负责“状态同步”。
|
||||||
|
|
||||||
|
3. **告警通知发送任务 (`internal/domain/task/`) - 发送器**
|
||||||
|
* **职责**: 作为一个独立的定时任务,解耦通知发送与告警检测。
|
||||||
|
* **逻辑**: 调用 `告警领域服务.GetAndProcessPendingNotifications()` -> 获取待发送通知列表 -> 调用 `通知领域服务`
|
||||||
|
逐一发送。
|
||||||
|
* **优势**: 统一管理定时任务,实现资源控制,提高系统稳定性和可扩展性。
|
||||||
|
|
||||||
|
### 计划与任务调度
|
||||||
|
|
||||||
|
1. **"周期性系统健康检查" 计划**
|
||||||
|
* **任务构成**:
|
||||||
|
* **全量数据采集任务 (ExecutionOrder: 1)**: 系统预定义,必须是第一个执行的任务,为后续的告警检测提供最新的数据基础。
|
||||||
|
* **阈值告警任务 (ExecutionOrder: 2, 3...)**: 由用户通过API动态配置和管理,`告警配置服务` 负责将其增删改到此计划中。
|
||||||
|
|
||||||
|
2. **"告警通知发送" 计划**
|
||||||
|
* **任务构成**: 包含一个系统预定义的 `告警通知发送任务`。
|
||||||
|
* **调度**: 可配置独立的执行频率(如每分钟一次),与健康检查计划解耦。
|
||||||
|
|
||||||
|
3. **系统初始化 (`data_initializer.go`)**
|
||||||
|
* **职责**: 只负责创建和维护系统预定义的、固定的计划和任务。
|
||||||
|
* **操作**:
|
||||||
|
* 确保 "周期性系统健康检查" 计划存在,并包含 `全量数据采集任务`。
|
||||||
|
* 确保 "告警通知发送" 计划存在,并包含 `告警通知发送任务`。
|
||||||
|
* **注意**: 初始化逻辑 **不会** 也 **不应该** 触及用户动态配置的阈值告警任务。
|
||||||
|
|
||||||
|
### 阈值告警任务 (用户可配置的任务类型)
|
||||||
|
|
||||||
|
1. **任务类型**: 提供两种可供用户配置的阈值告警任务类型,分别对应 **区域主控** 和 **普通设备** 告警。
|
||||||
|
2. **参数结构**:
|
||||||
|
* **通用参数**: 任务参数将包含 `Thresholds` (阈值) 和 `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. **接口职责**: 接口负责接收前端请求,调用应用服务层的阈值告警服务来完成实际的业务逻辑。
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
|
||||||
|
1. 是否要加一个延时操作, 因为采集是异步的, 采集任务结束时不一定能拿到最新数据, 所以需要一个延时操作等待区域主控上传
|
||||||
|
2. 统一一下区域主控的命名, 目前有AreaController和RegionalController, 不排除还有别的
|
||||||
|
3. 将数据类型转为float32, 节约空间, float64精度有些浪费, float32小数点后6-7位足够了
|
||||||
|
|
||||||
|
# 实现记录
|
||||||
|
|
||||||
|
1. 定义告警表和告警历史表
|
||||||
|
2. 重构部分枚举, 让models包不依赖其他项目中的包
|
||||||
|
3. 创建仓库层对象(不包含方法)
|
||||||
|
4. 实现告警发送任务
|
||||||
|
5. 实现告警通知发送计划/全量采集计划改名
|
||||||
|
6. 实现设备阈值检查任务
|
||||||
|
7. 实现忽略告警和取消忽略告警接口及功能
|
||||||
|
8. 实现列表查询活跃告警和历史告警
|
||||||
|
9. 系统初始化时健康计划调整(包括增加延时任务)
|
||||||
|
10. 实现区域阈值告警任务
|
||||||
|
11. 实现区域阈值告警和设备阈值告警的增删改查
|
||||||
|
12. 实现任务11应的八个web接口
|
||||||
|
13. 实现根据区域ID或设备ID清空对应阈值告警任务
|
||||||
|
14. 设备和区域主控删除时清除对应区域阈值告警或设备阈值告警任务
|
||||||
|
15. 将所有Regional更改为Area
|
||||||
|
16. float64全部改float32
|
||||||
|
17. uint/uint64全部改为uint32
|
||||||
1146
docs/docs.go
1146
docs/docs.go
File diff suppressed because it is too large
Load Diff
1146
docs/swagger.json
1146
docs/swagger.json
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,35 @@ definitions:
|
|||||||
- CodeConflict
|
- CodeConflict
|
||||||
- CodeInternalError
|
- CodeInternalError
|
||||||
- CodeServiceUnavailable
|
- 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:
|
dto.AreaControllerResponse:
|
||||||
properties:
|
properties:
|
||||||
created_at:
|
created_at:
|
||||||
@@ -73,6 +102,21 @@ definitions:
|
|||||||
updated_at:
|
updated_at:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
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:
|
dto.AssignEmptyPensToBatchRequest:
|
||||||
properties:
|
properties:
|
||||||
pen_ids:
|
pen_ids:
|
||||||
@@ -137,6 +181,32 @@ definitions:
|
|||||||
- name
|
- name
|
||||||
- network_id
|
- network_id
|
||||||
type: object
|
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:
|
dto.CreateDeviceRequest:
|
||||||
properties:
|
properties:
|
||||||
area_controller_id:
|
area_controller_id:
|
||||||
@@ -177,6 +247,32 @@ definitions:
|
|||||||
- commands
|
- commands
|
||||||
- name
|
- name
|
||||||
type: object
|
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:
|
dto.CreatePenRequest:
|
||||||
properties:
|
properties:
|
||||||
capacity:
|
capacity:
|
||||||
@@ -251,6 +347,15 @@ definitions:
|
|||||||
example: newuser
|
example: newuser
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
dto.DeleteDeviceThresholdAlarmDTO:
|
||||||
|
properties:
|
||||||
|
sensor_type:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/models.SensorType'
|
||||||
|
description: 传感器类型
|
||||||
|
required:
|
||||||
|
- sensor_type
|
||||||
|
type: object
|
||||||
dto.DeviceCommandLogDTO:
|
dto.DeviceCommandLogDTO:
|
||||||
properties:
|
properties:
|
||||||
acknowledged_at:
|
acknowledged_at:
|
||||||
@@ -312,6 +417,21 @@ definitions:
|
|||||||
$ref: '#/definitions/models.ValueDescriptor'
|
$ref: '#/definitions/models.ValueDescriptor'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
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:
|
dto.FeedFormulaDTO:
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
@@ -340,6 +460,40 @@ definitions:
|
|||||||
remarks:
|
remarks:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
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:
|
dto.ListDeviceCommandLogResponse:
|
||||||
properties:
|
properties:
|
||||||
list:
|
list:
|
||||||
@@ -358,6 +512,15 @@ definitions:
|
|||||||
pagination:
|
pagination:
|
||||||
$ref: '#/definitions/dto.PaginationDTO'
|
$ref: '#/definitions/dto.PaginationDTO'
|
||||||
type: object
|
type: object
|
||||||
|
dto.ListHistoricalAlarmResponse:
|
||||||
|
properties:
|
||||||
|
list:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/dto.HistoricalAlarmDTO'
|
||||||
|
type: array
|
||||||
|
pagination:
|
||||||
|
$ref: '#/definitions/dto.PaginationDTO'
|
||||||
|
type: object
|
||||||
dto.ListMedicationLogResponse:
|
dto.ListMedicationLogResponse:
|
||||||
properties:
|
properties:
|
||||||
list:
|
list:
|
||||||
@@ -600,11 +763,11 @@ definitions:
|
|||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
level:
|
level:
|
||||||
$ref: '#/definitions/zapcore.Level'
|
$ref: '#/definitions/models.SeverityLevel'
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
notifier_type:
|
notifier_type:
|
||||||
$ref: '#/definitions/notify.NotifierType'
|
$ref: '#/definitions/models.NotifierType'
|
||||||
status:
|
status:
|
||||||
$ref: '#/definitions/models.NotificationStatus'
|
$ref: '#/definitions/models.NotificationStatus'
|
||||||
title:
|
title:
|
||||||
@@ -1199,26 +1362,35 @@ definitions:
|
|||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/definitions/notify.NotifierType'
|
- $ref: '#/definitions/models.NotifierType'
|
||||||
description: Type 指定要测试的通知渠道
|
description: Type 指定要测试的通知渠道
|
||||||
required:
|
required:
|
||||||
- type
|
- type
|
||||||
type: object
|
type: object
|
||||||
dto.SensorDataDTO:
|
dto.SensorDataDTO:
|
||||||
properties:
|
properties:
|
||||||
|
area_controller_id:
|
||||||
|
type: integer
|
||||||
data:
|
data:
|
||||||
items:
|
items:
|
||||||
type: integer
|
type: integer
|
||||||
type: array
|
type: array
|
||||||
device_id:
|
device_id:
|
||||||
type: integer
|
type: integer
|
||||||
regional_controller_id:
|
|
||||||
type: integer
|
|
||||||
sensor_type:
|
sensor_type:
|
||||||
$ref: '#/definitions/models.SensorType'
|
$ref: '#/definitions/models.SensorType'
|
||||||
time:
|
time:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
dto.SnoozeAlarmRequest:
|
||||||
|
properties:
|
||||||
|
duration_minutes:
|
||||||
|
description: 忽略时长,单位分钟
|
||||||
|
minimum: 1
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- duration_minutes
|
||||||
|
type: object
|
||||||
dto.SubPlanResponse:
|
dto.SubPlanResponse:
|
||||||
properties:
|
properties:
|
||||||
child_plan:
|
child_plan:
|
||||||
@@ -1373,6 +1545,23 @@ definitions:
|
|||||||
- name
|
- name
|
||||||
- network_id
|
- network_id
|
||||||
type: object
|
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:
|
dto.UpdateDeviceRequest:
|
||||||
properties:
|
properties:
|
||||||
area_controller_id:
|
area_controller_id:
|
||||||
@@ -1413,6 +1602,23 @@ definitions:
|
|||||||
- commands
|
- commands
|
||||||
- name
|
- name
|
||||||
type: object
|
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:
|
dto.UpdatePenRequest:
|
||||||
properties:
|
properties:
|
||||||
capacity:
|
capacity:
|
||||||
@@ -1558,6 +1764,34 @@ definitions:
|
|||||||
weight:
|
weight:
|
||||||
type: number
|
type: number
|
||||||
type: object
|
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:
|
models.AuditStatus:
|
||||||
enum:
|
enum:
|
||||||
- 成功
|
- 成功
|
||||||
@@ -1646,6 +1880,34 @@ definitions:
|
|||||||
- NotificationStatusSuccess
|
- NotificationStatusSuccess
|
||||||
- NotificationStatusFailed
|
- NotificationStatusFailed
|
||||||
- NotificationStatusSkipped
|
- NotificationStatusSkipped
|
||||||
|
models.NotifierType:
|
||||||
|
enum:
|
||||||
|
- 邮件
|
||||||
|
- 企业微信
|
||||||
|
- 飞书
|
||||||
|
- 日志
|
||||||
|
type: string
|
||||||
|
x-enum-varnames:
|
||||||
|
- NotifierTypeSMTP
|
||||||
|
- NotifierTypeWeChat
|
||||||
|
- NotifierTypeLark
|
||||||
|
- NotifierTypeLog
|
||||||
|
models.Operator:
|
||||||
|
enum:
|
||||||
|
- <
|
||||||
|
- <=
|
||||||
|
- '>'
|
||||||
|
- '>='
|
||||||
|
- =
|
||||||
|
- '!='
|
||||||
|
type: string
|
||||||
|
x-enum-varnames:
|
||||||
|
- OperatorLessThan
|
||||||
|
- OperatorLessThanOrEqualTo
|
||||||
|
- OperatorGreaterThan
|
||||||
|
- OperatorGreaterThanOrEqualTo
|
||||||
|
- OperatorEqualTo
|
||||||
|
- OperatorNotEqualTo
|
||||||
models.PenStatus:
|
models.PenStatus:
|
||||||
enum:
|
enum:
|
||||||
- 空闲
|
- 空闲
|
||||||
@@ -1877,6 +2139,24 @@ definitions:
|
|||||||
- SensorTypeTemperature
|
- SensorTypeTemperature
|
||||||
- SensorTypeHumidity
|
- SensorTypeHumidity
|
||||||
- SensorTypeWeight
|
- 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:
|
models.StockLogSourceType:
|
||||||
enum:
|
enum:
|
||||||
- 采购入库
|
- 采购入库
|
||||||
@@ -1899,9 +2179,15 @@ definitions:
|
|||||||
- 等待
|
- 等待
|
||||||
- 下料
|
- 下料
|
||||||
- 全量采集
|
- 全量采集
|
||||||
|
- 告警通知
|
||||||
|
- 设备阈值检查
|
||||||
|
- 区域阈值检查
|
||||||
type: string
|
type: string
|
||||||
x-enum-comments:
|
x-enum-comments:
|
||||||
TaskPlanAnalysis: 解析Plan的Task列表并添加到待执行队列的特殊任务
|
TaskPlanAnalysis: 解析Plan的Task列表并添加到待执行队列的特殊任务
|
||||||
|
TaskTypeAlarmNotification: 告警通知任务
|
||||||
|
TaskTypeAreaCollectorThresholdCheck: 区域阈值检查任务
|
||||||
|
TaskTypeDeviceThresholdCheck: 设备阈值检查任务
|
||||||
TaskTypeFullCollection: 新增的全量采集任务
|
TaskTypeFullCollection: 新增的全量采集任务
|
||||||
TaskTypeReleaseFeedWeight: 下料口释放指定重量任务
|
TaskTypeReleaseFeedWeight: 下料口释放指定重量任务
|
||||||
TaskTypeWaiting: 等待任务
|
TaskTypeWaiting: 等待任务
|
||||||
@@ -1910,11 +2196,17 @@ definitions:
|
|||||||
- 等待任务
|
- 等待任务
|
||||||
- 下料口释放指定重量任务
|
- 下料口释放指定重量任务
|
||||||
- 新增的全量采集任务
|
- 新增的全量采集任务
|
||||||
|
- 告警通知任务
|
||||||
|
- 设备阈值检查任务
|
||||||
|
- 区域阈值检查任务
|
||||||
x-enum-varnames:
|
x-enum-varnames:
|
||||||
- TaskPlanAnalysis
|
- TaskPlanAnalysis
|
||||||
- TaskTypeWaiting
|
- TaskTypeWaiting
|
||||||
- TaskTypeReleaseFeedWeight
|
- TaskTypeReleaseFeedWeight
|
||||||
- TaskTypeFullCollection
|
- TaskTypeFullCollection
|
||||||
|
- TaskTypeAlarmNotification
|
||||||
|
- TaskTypeDeviceThresholdCheck
|
||||||
|
- TaskTypeAreaCollectorThresholdCheck
|
||||||
models.ValueDescriptor:
|
models.ValueDescriptor:
|
||||||
properties:
|
properties:
|
||||||
multiplier:
|
multiplier:
|
||||||
@@ -1926,18 +2218,6 @@ definitions:
|
|||||||
type:
|
type:
|
||||||
$ref: '#/definitions/models.SensorType'
|
$ref: '#/definitions/models.SensorType'
|
||||||
type: object
|
type: object
|
||||||
notify.NotifierType:
|
|
||||||
enum:
|
|
||||||
- 邮件
|
|
||||||
- 企业微信
|
|
||||||
- 飞书
|
|
||||||
- 日志
|
|
||||||
type: string
|
|
||||||
x-enum-varnames:
|
|
||||||
- NotifierTypeSMTP
|
|
||||||
- NotifierTypeWeChat
|
|
||||||
- NotifierTypeLark
|
|
||||||
- NotifierTypeLog
|
|
||||||
repository.PlanTypeFilter:
|
repository.PlanTypeFilter:
|
||||||
enum:
|
enum:
|
||||||
- 所有任务
|
- 所有任务
|
||||||
@@ -1950,7 +2230,6 @@ definitions:
|
|||||||
- PlanTypeFilterSystem
|
- PlanTypeFilterSystem
|
||||||
zapcore.Level:
|
zapcore.Level:
|
||||||
enum:
|
enum:
|
||||||
- 7
|
|
||||||
- -1
|
- -1
|
||||||
- 0
|
- 0
|
||||||
- 1
|
- 1
|
||||||
@@ -1961,10 +2240,10 @@ definitions:
|
|||||||
- -1
|
- -1
|
||||||
- 5
|
- 5
|
||||||
- 6
|
- 6
|
||||||
|
- 7
|
||||||
format: int32
|
format: int32
|
||||||
type: integer
|
type: integer
|
||||||
x-enum-varnames:
|
x-enum-varnames:
|
||||||
- _numLevels
|
|
||||||
- DebugLevel
|
- DebugLevel
|
||||||
- InfoLevel
|
- InfoLevel
|
||||||
- WarnLevel
|
- WarnLevel
|
||||||
@@ -1975,6 +2254,7 @@ definitions:
|
|||||||
- _minLevel
|
- _minLevel
|
||||||
- _maxLevel
|
- _maxLevel
|
||||||
- InvalidLevel
|
- InvalidLevel
|
||||||
|
- _numLevels
|
||||||
info:
|
info:
|
||||||
contact:
|
contact:
|
||||||
email: divano@example.com
|
email: divano@example.com
|
||||||
@@ -1987,6 +2267,428 @@ info:
|
|||||||
title: 猪场管理系统 API
|
title: 猪场管理系统 API
|
||||||
version: "1.0"
|
version: "1.0"
|
||||||
paths:
|
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/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:
|
||||||
|
- 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:
|
/api/v1/area-controllers:
|
||||||
get:
|
get:
|
||||||
description: 获取系统中所有区域主控的列表
|
description: 获取系统中所有区域主控的列表
|
||||||
@@ -2546,7 +3248,6 @@ paths:
|
|||||||
name: end_time
|
name: end_time
|
||||||
type: string
|
type: string
|
||||||
- enum:
|
- enum:
|
||||||
- 7
|
|
||||||
- -1
|
- -1
|
||||||
- 0
|
- 0
|
||||||
- 1
|
- 1
|
||||||
@@ -2557,12 +3258,12 @@ paths:
|
|||||||
- -1
|
- -1
|
||||||
- 5
|
- 5
|
||||||
- 6
|
- 6
|
||||||
|
- 7
|
||||||
format: int32
|
format: int32
|
||||||
in: query
|
in: query
|
||||||
name: level
|
name: level
|
||||||
type: integer
|
type: integer
|
||||||
x-enum-varnames:
|
x-enum-varnames:
|
||||||
- _numLevels
|
|
||||||
- DebugLevel
|
- DebugLevel
|
||||||
- InfoLevel
|
- InfoLevel
|
||||||
- WarnLevel
|
- WarnLevel
|
||||||
@@ -2573,6 +3274,7 @@ paths:
|
|||||||
- _minLevel
|
- _minLevel
|
||||||
- _maxLevel
|
- _maxLevel
|
||||||
- InvalidLevel
|
- InvalidLevel
|
||||||
|
- _numLevels
|
||||||
- enum:
|
- enum:
|
||||||
- 邮件
|
- 邮件
|
||||||
- 企业微信
|
- 企业微信
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "git.huangwc.com/pig/pig-farm-controller/docs" // 引入 swag 生成的 docs
|
_ "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/device"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/health"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/health"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/management"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/management"
|
||||||
@@ -53,6 +54,7 @@ type API struct {
|
|||||||
pigBatchController *management.PigBatchController // 猪群控制器实例
|
pigBatchController *management.PigBatchController // 猪群控制器实例
|
||||||
monitorController *monitor.Controller // 数据监控控制器实例
|
monitorController *monitor.Controller // 数据监控控制器实例
|
||||||
healthController *health.Controller // 健康检查控制器实例
|
healthController *health.Controller // 健康检查控制器实例
|
||||||
|
alarmController *alarm.ThresholdAlarmController // 阈值告警控制器
|
||||||
listenHandler webhook.ListenHandler // 设备上行事件监听器
|
listenHandler webhook.ListenHandler // 设备上行事件监听器
|
||||||
analysisTaskManager *domain_plan.AnalysisPlanTaskManager // 计划触发器管理器实例
|
analysisTaskManager *domain_plan.AnalysisPlanTaskManager // 计划触发器管理器实例
|
||||||
}
|
}
|
||||||
@@ -69,6 +71,7 @@ func NewAPI(cfg config.ServerConfig,
|
|||||||
planService service.PlanService,
|
planService service.PlanService,
|
||||||
userService service.UserService,
|
userService service.UserService,
|
||||||
auditService service.AuditService,
|
auditService service.AuditService,
|
||||||
|
alarmService service.ThresholdAlarmService,
|
||||||
tokenGenerator token.Generator,
|
tokenGenerator token.Generator,
|
||||||
listenHandler webhook.ListenHandler,
|
listenHandler webhook.ListenHandler,
|
||||||
) *API {
|
) *API {
|
||||||
@@ -106,6 +109,8 @@ func NewAPI(cfg config.ServerConfig,
|
|||||||
monitorController: monitor.NewController(logs.AddCompName(baseCtx, "MonitorController"), monitorService),
|
monitorController: monitor.NewController(logs.AddCompName(baseCtx, "MonitorController"), monitorService),
|
||||||
// 在 NewAPI 中初始化健康检查控制器
|
// 在 NewAPI 中初始化健康检查控制器
|
||||||
healthController: health.NewController(logs.AddCompName(baseCtx, "HealthController")),
|
healthController: health.NewController(logs.AddCompName(baseCtx, "HealthController")),
|
||||||
|
// 在 NewAPI 中初始化阈
|
||||||
|
alarmController: alarm.NewThresholdAlarmController(logs.AddCompName(baseCtx, "ThresholdAlarmController"), alarmService),
|
||||||
}
|
}
|
||||||
|
|
||||||
api.setupRoutes() // 设置所有路由
|
api.setupRoutes() // 设置所有路由
|
||||||
|
|||||||
@@ -187,6 +187,32 @@ func (a *API) setupRoutes() {
|
|||||||
monitorGroup.GET("/notifications", a.monitorController.ListNotifications)
|
monitorGroup.GET("/notifications", a.monitorController.ListNotifications)
|
||||||
}
|
}
|
||||||
logger.Debug("数据监控相关接口注册成功 (需要认证和审计)")
|
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) // 取消忽略阈值告警
|
||||||
|
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("告警相关接口注册成功 (需要认证和审计)")
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("所有接口注册成功")
|
logger.Debug("所有接口注册成功")
|
||||||
|
|||||||
456
internal/app/controller/alarm/threshold_alarm_controller.go
Normal file
456
internal/app/controller/alarm/threshold_alarm_controller.go
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
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"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||||
|
|
||||||
|
"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, 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)
|
||||||
|
}
|
||||||
|
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, 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)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ var (
|
|||||||
|
|
||||||
// GetOperatorIDFromContext 从 echo.Context 中提取操作者ID。
|
// GetOperatorIDFromContext 从 echo.Context 中提取操作者ID。
|
||||||
// 假设操作者ID是由 AuthMiddleware 存储到 context 中的 *models.User 对象的 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())
|
userVal := c.Get(models.ContextUserKey.String())
|
||||||
if userVal == nil {
|
if userVal == nil {
|
||||||
return 0, ErrUserNotFoundInContext
|
return 0, ErrUserNotFoundInContext
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ func (c *Controller) GetDevice(ctx echo.Context) error {
|
|||||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID: "+deviceID, actionType, "无效的ID", deviceID)
|
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 err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
|
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)
|
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 err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
|
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)
|
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 {
|
switch {
|
||||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||||
logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
|
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)
|
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) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
|
logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", 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)
|
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 err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID)
|
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)
|
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 err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID)
|
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)
|
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 {
|
switch {
|
||||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||||
logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID)
|
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)
|
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 err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID)
|
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)
|
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 err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID)
|
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)
|
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 {
|
switch {
|
||||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||||
logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID)
|
logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
// mapAndSendError 统一映射服务层错误并发送响应。
|
// mapAndSendError 统一映射服务层错误并发送响应。
|
||||||
// 这个函数将服务层返回的错误转换为控制器层应返回的HTTP状态码和审计信息。
|
// 这个函数将服务层返回的错误转换为控制器层应返回的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) ||
|
if errors.Is(err, service.ErrPigBatchNotFound) ||
|
||||||
errors.Is(err, service.ErrPenNotFound) ||
|
errors.Is(err, service.ErrPenNotFound) ||
|
||||||
errors.Is(err, service.ErrPenNotAssociatedWithBatch) {
|
errors.Is(err, service.ErrPenNotAssociatedWithBatch) {
|
||||||
@@ -34,7 +34,7 @@ func mapAndSendError(reqContext context.Context, c *PigBatchController, ctx echo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// idExtractorFunc 定义了一个函数类型,用于从echo.Context中提取主ID。
|
// 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的通用逻辑。
|
// extractOperatorAndPrimaryID 封装了从echo.Context中提取操作员ID和主ID的通用逻辑。
|
||||||
// 它负责处理ID提取过程中的错误,并发送相应的HTTP响应。
|
// 它负责处理ID提取过程中的错误,并发送相应的HTTP响应。
|
||||||
@@ -48,15 +48,15 @@ type idExtractorFunc func(ctx echo.Context) (uint, error)
|
|||||||
//
|
//
|
||||||
// 返回值:
|
// 返回值:
|
||||||
//
|
//
|
||||||
// operatorID: uint - 提取到的操作员ID。
|
// operatorID: uint32 - 提取到的操作员ID。
|
||||||
// primaryID: uint - 提取到的主ID。
|
// primaryID: uint32 - 提取到的主ID。
|
||||||
// err: error - 如果ID提取失败或发送错误响应,则返回错误。
|
// err: error - 如果ID提取失败或发送错误响应,则返回错误。
|
||||||
func extractOperatorAndPrimaryID(
|
func extractOperatorAndPrimaryID(
|
||||||
c *PigBatchController,
|
c *PigBatchController,
|
||||||
ctx echo.Context,
|
ctx echo.Context,
|
||||||
action string,
|
action string,
|
||||||
idExtractor idExtractorFunc,
|
idExtractor idExtractorFunc,
|
||||||
) (operatorID uint, primaryID uint, err error) {
|
) (operatorID uint32, primaryID uint32, err error) {
|
||||||
// 1. 获取操作员ID
|
// 1. 获取操作员ID
|
||||||
operatorID, err = controller.GetOperatorIDFromContext(ctx)
|
operatorID, err = controller.GetOperatorIDFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -78,7 +78,7 @@ func extractOperatorAndPrimaryID(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", idParam)
|
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,
|
ctx echo.Context,
|
||||||
action string,
|
action string,
|
||||||
reqDTO Req,
|
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,
|
successMsg string,
|
||||||
idExtractor idExtractorFunc,
|
idExtractor idExtractorFunc,
|
||||||
) error {
|
) error {
|
||||||
@@ -124,7 +124,7 @@ func handleNoBodyAPIRequest(
|
|||||||
c *PigBatchController,
|
c *PigBatchController,
|
||||||
ctx echo.Context,
|
ctx echo.Context,
|
||||||
action string,
|
action string,
|
||||||
serviceExecutor func(ctx echo.Context, operatorID uint, primaryID uint) error,
|
serviceExecutor func(ctx echo.Context, operatorID uint32, primaryID uint32) error,
|
||||||
successMsg string,
|
successMsg string,
|
||||||
idExtractor idExtractorFunc,
|
idExtractor idExtractorFunc,
|
||||||
) error {
|
) error {
|
||||||
@@ -151,7 +151,7 @@ func handleAPIRequestWithResponse[Req any, Resp any](
|
|||||||
ctx echo.Context,
|
ctx echo.Context,
|
||||||
action string,
|
action string,
|
||||||
reqDTO Req,
|
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,
|
successMsg string,
|
||||||
idExtractor idExtractorFunc,
|
idExtractor idExtractorFunc,
|
||||||
) error {
|
) error {
|
||||||
@@ -182,7 +182,7 @@ func handleNoBodyAPIRequestWithResponse[Resp any](
|
|||||||
c *PigBatchController,
|
c *PigBatchController,
|
||||||
ctx echo.Context,
|
ctx echo.Context,
|
||||||
action string,
|
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,
|
successMsg string,
|
||||||
idExtractor idExtractorFunc,
|
idExtractor idExtractorFunc,
|
||||||
) error {
|
) error {
|
||||||
@@ -209,7 +209,7 @@ func handleQueryAPIRequestWithResponse[Query any, Resp any](
|
|||||||
ctx echo.Context,
|
ctx echo.Context,
|
||||||
action string,
|
action string,
|
||||||
queryDTO Query,
|
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,
|
successMsg string,
|
||||||
) error {
|
) error {
|
||||||
// 1. 绑定查询参数
|
// 1. 绑定查询参数
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func (c *PigBatchController) CreatePigBatch(ctx echo.Context) error {
|
|||||||
|
|
||||||
return handleAPIRequestWithResponse(
|
return handleAPIRequestWithResponse(
|
||||||
reqCtx, c, ctx, action, &req,
|
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通常不从路径中获取,而是由服务层生成
|
// 对于创建操作,primaryID通常不从路径中获取,而是由服务层生成
|
||||||
return c.service.CreatePigBatch(reqCtx, operatorID, req)
|
return c.service.CreatePigBatch(reqCtx, operatorID, req)
|
||||||
},
|
},
|
||||||
@@ -68,7 +68,7 @@ func (c *PigBatchController) GetPigBatch(ctx echo.Context) error {
|
|||||||
|
|
||||||
return handleNoBodyAPIRequestWithResponse(
|
return handleNoBodyAPIRequestWithResponse(
|
||||||
reqCtx, c, ctx, action,
|
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)
|
return c.service.GetPigBatch(reqCtx, primaryID)
|
||||||
},
|
},
|
||||||
"获取成功",
|
"获取成功",
|
||||||
@@ -95,7 +95,7 @@ func (c *PigBatchController) UpdatePigBatch(ctx echo.Context) error {
|
|||||||
|
|
||||||
return handleAPIRequestWithResponse(
|
return handleAPIRequestWithResponse(
|
||||||
reqCtx, c, ctx, action, &req,
|
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)
|
return c.service.UpdatePigBatch(reqCtx, primaryID, req)
|
||||||
},
|
},
|
||||||
"更新成功",
|
"更新成功",
|
||||||
@@ -119,7 +119,7 @@ func (c *PigBatchController) DeletePigBatch(ctx echo.Context) error {
|
|||||||
|
|
||||||
return handleNoBodyAPIRequest(
|
return handleNoBodyAPIRequest(
|
||||||
reqCtx, c, ctx, action,
|
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)
|
return c.service.DeletePigBatch(reqCtx, primaryID)
|
||||||
},
|
},
|
||||||
"删除成功",
|
"删除成功",
|
||||||
@@ -144,7 +144,7 @@ func (c *PigBatchController) ListPigBatches(ctx echo.Context) error {
|
|||||||
|
|
||||||
return handleQueryAPIRequestWithResponse(
|
return handleQueryAPIRequestWithResponse(
|
||||||
reqCtx, c, ctx, action, &query,
|
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)
|
return c.service.ListPigBatches(reqCtx, query.IsActive)
|
||||||
},
|
},
|
||||||
"获取成功",
|
"获取成功",
|
||||||
@@ -170,7 +170,7 @@ func (c *PigBatchController) AssignEmptyPensToBatch(ctx echo.Context) error {
|
|||||||
|
|
||||||
return handleAPIRequest(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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)
|
return c.service.AssignEmptyPensToBatch(reqCtx, primaryID, req.PenIDs, operatorID)
|
||||||
},
|
},
|
||||||
"分配成功",
|
"分配成功",
|
||||||
@@ -197,18 +197,18 @@ func (c *PigBatchController) ReclassifyPenToNewBatch(ctx echo.Context) error {
|
|||||||
|
|
||||||
return handleAPIRequest(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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
|
// primaryID 在这里是 fromBatchID
|
||||||
return c.service.ReclassifyPenToNewBatch(reqCtx, primaryID, req.ToBatchID, req.PenID, operatorID, req.Remarks)
|
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")
|
idParam := ctx.Param("fromBatchID")
|
||||||
parsedID, err := strconv.ParseUint(idParam, 10, 32)
|
parsedID, err := strconv.ParseUint(idParam, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
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(
|
return handleNoBodyAPIRequest(
|
||||||
reqCtx, c, ctx, action,
|
reqCtx, c, ctx, action,
|
||||||
func(ctx echo.Context, operatorID uint, primaryID uint) error {
|
func(ctx echo.Context, operatorID uint32, primaryID uint32) error {
|
||||||
// primaryID 在这里是 batchID
|
// primaryID 在这里是 batchID
|
||||||
penIDParam := ctx.Param("penID")
|
penIDParam := ctx.Param("penID")
|
||||||
parsedPenID, err := strconv.ParseUint(penIDParam, 10, 32)
|
parsedPenID, err := strconv.ParseUint(penIDParam, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err // 返回错误,因为 penID 格式无效
|
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")
|
idParam := ctx.Param("batchID")
|
||||||
parsedID, err := strconv.ParseUint(idParam, 10, 32)
|
parsedID, err := strconv.ParseUint(idParam, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
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(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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)
|
return c.service.MovePigsIntoPen(reqCtx, primaryID, req.ToPenID, req.Quantity, operatorID, req.Remarks)
|
||||||
},
|
},
|
||||||
"移入成功",
|
"移入成功",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func (c *PigBatchController) RecordSickPigs(ctx echo.Context) error {
|
|||||||
|
|
||||||
return handleAPIRequest(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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)
|
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(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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)
|
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(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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)
|
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(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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)
|
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(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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)
|
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(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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)
|
return c.service.RecordCull(reqCtx, operatorID, primaryID, req.PenID, req.Quantity, req.HappenedAt, req.Remarks)
|
||||||
},
|
},
|
||||||
"记录成功",
|
"记录成功",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func (c *PigBatchController) SellPigs(ctx echo.Context) error {
|
|||||||
|
|
||||||
return handleAPIRequest(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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)
|
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(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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)
|
return c.service.BuyPigs(reqCtx, primaryID, req.PenID, req.Quantity, req.UnitPrice, req.TotalPrice, req.TraderName, req.TradeDate, req.Remarks, operatorID)
|
||||||
},
|
},
|
||||||
"买猪成功",
|
"买猪成功",
|
||||||
|
|||||||
@@ -28,18 +28,18 @@ func (c *PigBatchController) TransferPigsAcrossBatches(ctx echo.Context) error {
|
|||||||
|
|
||||||
return handleAPIRequest(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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
|
// primaryID 在这里是 sourceBatchID
|
||||||
return c.service.TransferPigsAcrossBatches(reqCtx, primaryID, req.DestBatchID, req.FromPenID, req.ToPenID, req.Quantity, operatorID, req.Remarks)
|
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")
|
idParam := ctx.Param("sourceBatchID")
|
||||||
parsedID, err := strconv.ParseUint(idParam, 10, 32)
|
parsedID, err := strconv.ParseUint(idParam, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
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(
|
return handleAPIRequest(
|
||||||
reqCtx, c, ctx, action, &req,
|
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
|
// primaryID 在这里是 batchID
|
||||||
return c.service.TransferPigsWithinBatch(reqCtx, primaryID, req.FromPenID, req.ToPenID, req.Quantity, operatorID, req.Remarks)
|
return c.service.TransferPigsWithinBatch(reqCtx, primaryID, req.FromPenID, req.ToPenID, req.Quantity, operatorID, req.Remarks)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ func (c *PigFarmController) GetPigHouse(ctx echo.Context) error {
|
|||||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", action, "ID格式错误", ctx.Param("id"))
|
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 err != nil {
|
||||||
if errors.Is(err, service.ErrHouseNotFound) {
|
if errors.Is(err, service.ErrHouseNotFound) {
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪舍不存在", action, "猪舍不存在", id)
|
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)
|
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 err != nil {
|
||||||
if errors.Is(err, service.ErrHouseNotFound) {
|
if errors.Is(err, service.ErrHouseNotFound) {
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪舍不存在", action, "猪舍不存在", id)
|
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"))
|
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) {
|
if errors.Is(err, service.ErrHouseNotFound) {
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪舍不存在", action, "猪舍不存在", id)
|
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"))
|
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 err != nil {
|
||||||
if errors.Is(err, service.ErrPenNotFound) {
|
if errors.Is(err, service.ErrPenNotFound) {
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪栏不存在", action, "猪栏不存在", id)
|
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)
|
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 err != nil {
|
||||||
if errors.Is(err, service.ErrPenNotFound) {
|
if errors.Is(err, service.ErrPenNotFound) {
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪栏不存在", action, "猪栏不存在", id)
|
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"))
|
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) {
|
if errors.Is(err, service.ErrPenNotFound) {
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "猪栏不存在", action, "猪栏不存在", id)
|
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)
|
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 err != nil {
|
||||||
if errors.Is(err, service.ErrPenNotFound) {
|
if errors.Is(err, service.ErrPenNotFound) {
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), action, err.Error(), id)
|
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), action, err.Error(), id)
|
||||||
|
|||||||
@@ -81,14 +81,14 @@ func (c *Controller) GetPlan(ctx echo.Context) error {
|
|||||||
const actionType = "获取计划详情"
|
const actionType = "获取计划详情"
|
||||||
// 1. 从 URL 路径中获取 ID
|
// 1. 从 URL 路径中获取 ID
|
||||||
idStr := ctx.Param("id")
|
idStr := ctx.Param("id")
|
||||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr)
|
logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr)
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", 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 {
|
if err != nil {
|
||||||
logger.Errorf("%s: 服务层获取计划详情失败: %v, ID: %d", actionType, err, id)
|
logger.Errorf("%s: 服务层获取计划详情失败: %v, ID: %d", actionType, err, id)
|
||||||
if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound
|
if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound
|
||||||
@@ -147,7 +147,7 @@ func (c *Controller) UpdatePlan(ctx echo.Context) error {
|
|||||||
const actionType = "更新计划"
|
const actionType = "更新计划"
|
||||||
// 1. 从 URL 路径中获取 ID
|
// 1. 从 URL 路径中获取 ID
|
||||||
idStr := ctx.Param("id")
|
idStr := ctx.Param("id")
|
||||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr)
|
logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr)
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", 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 {
|
if err != nil {
|
||||||
logger.Errorf("%s: 服务层更新计划失败: %v, ID: %d", actionType, err, id)
|
logger.Errorf("%s: 服务层更新计划失败: %v, ID: %d", actionType, err, id)
|
||||||
if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound
|
if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound
|
||||||
@@ -191,14 +191,14 @@ func (c *Controller) DeletePlan(ctx echo.Context) error {
|
|||||||
const actionType = "删除计划"
|
const actionType = "删除计划"
|
||||||
// 1. 从 URL 路径中获取 ID
|
// 1. 从 URL 路径中获取 ID
|
||||||
idStr := ctx.Param("id")
|
idStr := ctx.Param("id")
|
||||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr)
|
logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr)
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", 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 {
|
if err != nil {
|
||||||
logger.Errorf("%s: 服务层删除计划失败: %v, ID: %d", actionType, err, id)
|
logger.Errorf("%s: 服务层删除计划失败: %v, ID: %d", actionType, err, id)
|
||||||
if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound
|
if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound
|
||||||
@@ -228,14 +228,14 @@ func (c *Controller) StartPlan(ctx echo.Context) error {
|
|||||||
const actionType = "启动计划"
|
const actionType = "启动计划"
|
||||||
// 1. 从 URL 路径中获取 ID
|
// 1. 从 URL 路径中获取 ID
|
||||||
idStr := ctx.Param("id")
|
idStr := ctx.Param("id")
|
||||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr)
|
logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr)
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", 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 {
|
if err != nil {
|
||||||
logger.Errorf("%s: 服务层启动计划失败: %v, ID: %d", actionType, err, id)
|
logger.Errorf("%s: 服务层启动计划失败: %v, ID: %d", actionType, err, id)
|
||||||
if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound
|
if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound
|
||||||
@@ -267,14 +267,14 @@ func (c *Controller) StopPlan(ctx echo.Context) error {
|
|||||||
const actionType = "停止计划"
|
const actionType = "停止计划"
|
||||||
// 1. 从 URL 路径中获取 ID
|
// 1. 从 URL 路径中获取 ID
|
||||||
idStr := ctx.Param("id")
|
idStr := ctx.Param("id")
|
||||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr)
|
logger.Errorf("%s: 计划ID格式错误: %v, ID: %s", actionType, err, idStr)
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", 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 {
|
if err != nil {
|
||||||
logger.Errorf("%s: 服务层停止计划失败: %v, ID: %d", actionType, err, id)
|
logger.Errorf("%s: 服务层停止计划失败: %v, ID: %d", actionType, err, id)
|
||||||
if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound
|
if errors.Is(err, plan.ErrPlanNotFound) { // 修改为 plan.ErrPlanNotFound
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ func (c *Controller) SendTestNotification(ctx echo.Context) error {
|
|||||||
const actionType = "发送测试通知"
|
const actionType = "发送测试通知"
|
||||||
|
|
||||||
// 1. 从 URL 中获取用户 ID
|
// 1. 从 URL 中获取用户 ID
|
||||||
userID, err := strconv.ParseUint(ctx.Param("id"), 10, 32)
|
userID, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("%s: 无效的用户ID格式: %v", actionType, err)
|
logger.Errorf("%s: 无效的用户ID格式: %v", actionType, err)
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的用户ID格式", actionType, "无效的用户ID格式", ctx.Param("id"))
|
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. 调用服务层
|
// 3. 调用服务层
|
||||||
err = c.userService.SendTestNotification(reqCtx, uint(userID), &req)
|
err = c.userService.SendTestNotification(reqCtx, uint32(userID), &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("%s: 服务层调用失败: %v", actionType, err)
|
logger.Errorf("%s: 服务层调用失败: %v", actionType, err)
|
||||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "发送测试消息失败: "+err.Error(), actionType, "服务层调用失败", map[string]interface{}{"userID": userID, "type": req.Type})
|
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "发送测试消息失败: "+err.Error(), actionType, "服务层调用失败", map[string]interface{}{"userID": userID, "type": req.Type})
|
||||||
|
|||||||
65
internal/app/dto/alarm_converter.go
Normal file
65
internal/app/dto/alarm_converter.go
Normal file
@@ -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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
140
internal/app/dto/alarm_dto.go
Normal file
140
internal/app/dto/alarm_dto.go
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SnoozeAlarmRequest 定义了忽略告警的请求体
|
||||||
|
type SnoozeAlarmRequest struct {
|
||||||
|
DurationMinutes uint32 `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 *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"` // 告警触发时间范围 - 开始时间
|
||||||
|
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 uint32 `json:"id"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
SourceType models.AlarmSourceType `json:"source_type"`
|
||||||
|
SourceID uint32 `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 *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"` // 告警触发时间范围 - 结束时间
|
||||||
|
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 uint32 `json:"id"`
|
||||||
|
SourceType models.AlarmSourceType `json:"source_type"`
|
||||||
|
SourceID uint32 `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 *uint32 `json:"resolved_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListHistoricalAlarmResponse 是获取历史告警列表的响应结构
|
||||||
|
type ListHistoricalAlarmResponse struct {
|
||||||
|
List []HistoricalAlarmDTO `json:"list"`
|
||||||
|
Pagination PaginationDTO `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDeviceThresholdAlarmDTO 创建设备阈值告警的请求DTO
|
||||||
|
type CreateDeviceThresholdAlarmDTO struct {
|
||||||
|
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类型,与前端交互更通用)
|
||||||
|
Level models.SeverityLevel `json:"level,omitempty"` // 告警等级,可选,如果未提供则使用默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDeviceThresholdAlarmDTO 更新设备阈值告警的请求DTO
|
||||||
|
type UpdateDeviceThresholdAlarmDTO struct {
|
||||||
|
Thresholds float32 `json:"thresholds" binding:"required"` // 新的阈值
|
||||||
|
Operator models.Operator `json:"operator" binding:"required"` // 新的操作符
|
||||||
|
Level models.SeverityLevel `json:"level,omitempty"` // 新的告警等级,可选
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAreaThresholdAlarmDTO 创建区域阈值告警的请求DTO
|
||||||
|
type CreateAreaThresholdAlarmDTO struct {
|
||||||
|
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"` // 操作符
|
||||||
|
Level models.SeverityLevel `json:"level,omitempty"` // 告警等级,可选
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAreaThresholdAlarmDTO 更新区域阈值告警的请求DTO
|
||||||
|
type UpdateAreaThresholdAlarmDTO struct {
|
||||||
|
Thresholds float32 `json:"thresholds" binding:"required"` // 新的阈值
|
||||||
|
Operator models.Operator `json:"operator" binding:"required"` // 新的操作符
|
||||||
|
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"`
|
||||||
|
AreaControllerID uint32 `json:"area_controller_id"`
|
||||||
|
SensorType models.SensorType `json:"sensor_type"`
|
||||||
|
Thresholds float32 `json:"thresholds"`
|
||||||
|
Operator models.Operator `json:"operator"`
|
||||||
|
Level models.SeverityLevel `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceThresholdAlarmDTO 用于表示一个设备阈值告警任务的详细信息
|
||||||
|
type DeviceThresholdAlarmDTO struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
DeviceID uint32 `json:"device_id"`
|
||||||
|
SensorType models.SensorType `json:"sensor_type"`
|
||||||
|
Thresholds float32 `json:"thresholds"`
|
||||||
|
Operator models.Operator `json:"operator"`
|
||||||
|
Level models.SeverityLevel `json:"level"`
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@ import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
|||||||
// CreateDeviceRequest 定义了创建设备时需要传入的参数
|
// CreateDeviceRequest 定义了创建设备时需要传入的参数
|
||||||
type CreateDeviceRequest struct {
|
type CreateDeviceRequest struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
DeviceTemplateID uint `json:"device_template_id" validate:"required"`
|
DeviceTemplateID uint32 `json:"device_template_id" validate:"required"`
|
||||||
AreaControllerID uint `json:"area_controller_id" validate:"required"`
|
AreaControllerID uint32 `json:"area_controller_id" validate:"required"`
|
||||||
Location string `json:"location,omitempty" validate:"omitempty"`
|
Location string `json:"location,omitempty" validate:"omitempty"`
|
||||||
Properties map[string]interface{} `json:"properties,omitempty" validate:"omitempty"`
|
Properties map[string]interface{} `json:"properties,omitempty" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
@@ -14,8 +14,8 @@ type CreateDeviceRequest struct {
|
|||||||
// UpdateDeviceRequest 定义了更新设备时需要传入的参数
|
// UpdateDeviceRequest 定义了更新设备时需要传入的参数
|
||||||
type UpdateDeviceRequest struct {
|
type UpdateDeviceRequest struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
DeviceTemplateID uint `json:"device_template_id" validate:"required"`
|
DeviceTemplateID uint32 `json:"device_template_id" validate:"required"`
|
||||||
AreaControllerID uint `json:"area_controller_id" validate:"required"`
|
AreaControllerID uint32 `json:"area_controller_id" validate:"required"`
|
||||||
Location string `json:"location,omitempty" validate:"omitempty"`
|
Location string `json:"location,omitempty" validate:"omitempty"`
|
||||||
Properties map[string]interface{} `json:"properties,omitempty" validate:"omitempty"`
|
Properties map[string]interface{} `json:"properties,omitempty" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
@@ -64,11 +64,11 @@ type UpdateDeviceTemplateRequest struct {
|
|||||||
|
|
||||||
// DeviceResponse 定义了返回给客户端的单个设备信息的结构
|
// DeviceResponse 定义了返回给客户端的单个设备信息的结构
|
||||||
type DeviceResponse struct {
|
type DeviceResponse struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
DeviceTemplateID uint `json:"device_template_id"`
|
DeviceTemplateID uint32 `json:"device_template_id"`
|
||||||
DeviceTemplateName string `json:"device_template_name"`
|
DeviceTemplateName string `json:"device_template_name"`
|
||||||
AreaControllerID uint `json:"area_controller_id"`
|
AreaControllerID uint32 `json:"area_controller_id"`
|
||||||
AreaControllerName string `json:"area_controller_name"`
|
AreaControllerName string `json:"area_controller_name"`
|
||||||
Location string `json:"location"`
|
Location string `json:"location"`
|
||||||
Properties map[string]interface{} `json:"properties"`
|
Properties map[string]interface{} `json:"properties"`
|
||||||
@@ -78,7 +78,7 @@ type DeviceResponse struct {
|
|||||||
|
|
||||||
// AreaControllerResponse 定义了返回给客户端的单个区域主控信息的结构
|
// AreaControllerResponse 定义了返回给客户端的单个区域主控信息的结构
|
||||||
type AreaControllerResponse struct {
|
type AreaControllerResponse struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
NetworkID string `json:"network_id"`
|
NetworkID string `json:"network_id"`
|
||||||
Location string `json:"location"`
|
Location string `json:"location"`
|
||||||
@@ -90,7 +90,7 @@ type AreaControllerResponse struct {
|
|||||||
|
|
||||||
// DeviceTemplateResponse 定义了返回给客户端的单个设备模板信息的结构
|
// DeviceTemplateResponse 定义了返回给客户端的单个设备模板信息的结构
|
||||||
type DeviceTemplateResponse struct {
|
type DeviceTemplateResponse struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Manufacturer string `json:"manufacturer"`
|
Manufacturer string `json:"manufacturer"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
|||||||
10
internal/app/dto/dto.go
Normal file
10
internal/app/dto/dto.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
// --- General ---
|
||||||
|
|
||||||
|
// PaginationDTO 定义了分页信息的标准结构
|
||||||
|
type PaginationDTO struct {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
PageSize int `json:"page_size"`
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ func NewListSensorDataResponse(data []models.SensorData, total int64, page, page
|
|||||||
dtos[i] = SensorDataDTO{
|
dtos[i] = SensorDataDTO{
|
||||||
Time: item.Time,
|
Time: item.Time,
|
||||||
DeviceID: item.DeviceID,
|
DeviceID: item.DeviceID,
|
||||||
RegionalControllerID: item.RegionalControllerID,
|
AreaControllerID: item.AreaControllerID,
|
||||||
SensorType: item.SensorType,
|
SensorType: item.SensorType,
|
||||||
Data: json.RawMessage(item.Data),
|
Data: json.RawMessage(item.Data),
|
||||||
}
|
}
|
||||||
@@ -54,9 +54,9 @@ func NewListDeviceCommandLogResponse(data []models.DeviceCommandLog, total int64
|
|||||||
|
|
||||||
// NewListPlanExecutionLogResponse 从模型数据创建列表响应 DTO
|
// NewListPlanExecutionLogResponse 从模型数据创建列表响应 DTO
|
||||||
func NewListPlanExecutionLogResponse(planLogs []models.PlanExecutionLog, plans []models.Plan, total int64, page, pageSize int) *ListPlanExecutionLogResponse {
|
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 {
|
for _, plan := range plans {
|
||||||
planId2Name[plan.ID] = plan.Name
|
planId2Name[plan.ID] = string(plan.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
dtos := make([]PlanExecutionLogDTO, len(planLogs))
|
dtos := make([]PlanExecutionLogDTO, len(planLogs))
|
||||||
@@ -95,7 +95,7 @@ func NewListTaskExecutionLogResponse(data []models.TaskExecutionLog, total int64
|
|||||||
PlanExecutionLogID: item.PlanExecutionLogID,
|
PlanExecutionLogID: item.PlanExecutionLogID,
|
||||||
TaskID: item.TaskID,
|
TaskID: item.TaskID,
|
||||||
Task: TaskDTO{
|
Task: TaskDTO{
|
||||||
ID: uint(item.Task.ID),
|
ID: uint32(item.Task.ID),
|
||||||
Name: item.Task.Name,
|
Name: item.Task.Name,
|
||||||
Description: item.Task.Description,
|
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 {
|
func NewListPigTransferLogResponse(data []models.PigTransferLog, total int64, page, pageSize int) *ListPigTransferLogResponse {
|
||||||
dtos := make([]PigTransferLogDTO, len(data))
|
dtos := make([]PigTransferLogDTO, len(data))
|
||||||
for i, item := range data {
|
for i, item := range data {
|
||||||
// 注意:PigTransferLog 的 ID, CreatedAt, UpdatedAt 字段是 gorm.Model 嵌入的
|
// 注意:PigTransferLog 的 ID, CreatedAt, UpdatedAt 字段是 Model 嵌入的
|
||||||
dtos[i] = PigTransferLogDTO{
|
dtos[i] = PigTransferLogDTO{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
CreatedAt: item.CreatedAt,
|
CreatedAt: item.CreatedAt,
|
||||||
|
|||||||
@@ -7,22 +7,13 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
"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 ---
|
// --- SensorData ---
|
||||||
|
|
||||||
// ListSensorDataRequest 定义了获取传感器数据列表的请求参数
|
// ListSensorDataRequest 定义了获取传感器数据列表的请求参数
|
||||||
type ListSensorDataRequest struct {
|
type ListSensorDataRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
SensorType *string `json:"sensor_type" query:"sensor_type"`
|
||||||
StartTime *time.Time `json:"start_time" query:"start_time"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
@@ -32,8 +23,8 @@ type ListSensorDataRequest struct {
|
|||||||
// SensorDataDTO 是用于API响应的传感器数据结构
|
// SensorDataDTO 是用于API响应的传感器数据结构
|
||||||
type SensorDataDTO struct {
|
type SensorDataDTO struct {
|
||||||
Time time.Time `json:"time"`
|
Time time.Time `json:"time"`
|
||||||
DeviceID uint `json:"device_id"`
|
DeviceID uint32 `json:"device_id"`
|
||||||
RegionalControllerID uint `json:"regional_controller_id"`
|
AreaControllerID uint32 `json:"area_controller_id"`
|
||||||
SensorType models.SensorType `json:"sensor_type"`
|
SensorType models.SensorType `json:"sensor_type"`
|
||||||
Data json.RawMessage `json:"data"`
|
Data json.RawMessage `json:"data"`
|
||||||
}
|
}
|
||||||
@@ -50,7 +41,7 @@ type ListSensorDataResponse struct {
|
|||||||
type ListDeviceCommandLogRequest struct {
|
type ListDeviceCommandLogRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
ReceivedSuccess *bool `json:"received_success" query:"received_success"`
|
||||||
StartTime *time.Time `json:"start_time" query:"start_time"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
@@ -60,7 +51,7 @@ type ListDeviceCommandLogRequest struct {
|
|||||||
// DeviceCommandLogDTO 是用于API响应的设备命令日志结构
|
// DeviceCommandLogDTO 是用于API响应的设备命令日志结构
|
||||||
type DeviceCommandLogDTO struct {
|
type DeviceCommandLogDTO struct {
|
||||||
MessageID string `json:"message_id"`
|
MessageID string `json:"message_id"`
|
||||||
DeviceID uint `json:"device_id"`
|
DeviceID uint32 `json:"device_id"`
|
||||||
SentAt time.Time `json:"sent_at"`
|
SentAt time.Time `json:"sent_at"`
|
||||||
AcknowledgedAt *time.Time `json:"acknowledged_at"`
|
AcknowledgedAt *time.Time `json:"acknowledged_at"`
|
||||||
ReceivedSuccess bool `json:"received_success"`
|
ReceivedSuccess bool `json:"received_success"`
|
||||||
@@ -78,7 +69,7 @@ type ListDeviceCommandLogResponse struct {
|
|||||||
type ListPlanExecutionLogRequest struct {
|
type ListPlanExecutionLogRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
Status *string `json:"status" query:"status"`
|
||||||
StartTime *time.Time `json:"start_time" query:"start_time"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
@@ -87,10 +78,10 @@ type ListPlanExecutionLogRequest struct {
|
|||||||
|
|
||||||
// PlanExecutionLogDTO 是用于API响应的计划执行日志结构
|
// PlanExecutionLogDTO 是用于API响应的计划执行日志结构
|
||||||
type PlanExecutionLogDTO struct {
|
type PlanExecutionLogDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
PlanID uint `json:"plan_id"`
|
PlanID uint32 `json:"plan_id"`
|
||||||
PlanName string `json:"plan_name"`
|
PlanName string `json:"plan_name"`
|
||||||
Status models.ExecutionStatus `json:"status"`
|
Status models.ExecutionStatus `json:"status"`
|
||||||
StartedAt time.Time `json:"started_at"`
|
StartedAt time.Time `json:"started_at"`
|
||||||
@@ -110,7 +101,7 @@ type ListPlanExecutionLogResponse struct {
|
|||||||
type ListTaskExecutionLogRequest struct {
|
type ListTaskExecutionLogRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
TaskID *int `json:"task_id" query:"task_id"`
|
||||||
Status *string `json:"status" query:"status"`
|
Status *string `json:"status" query:"status"`
|
||||||
StartTime *time.Time `json:"start_time" query:"start_time"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
@@ -120,17 +111,17 @@ type ListTaskExecutionLogRequest struct {
|
|||||||
|
|
||||||
// TaskDTO 是用于API响应的简化版任务结构
|
// TaskDTO 是用于API响应的简化版任务结构
|
||||||
type TaskDTO struct {
|
type TaskDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaskExecutionLogDTO 是用于API响应的任务执行日志结构
|
// TaskExecutionLogDTO 是用于API响应的任务执行日志结构
|
||||||
type TaskExecutionLogDTO struct {
|
type TaskExecutionLogDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_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"`
|
TaskID int `json:"task_id"`
|
||||||
Task TaskDTO `json:"task"` // 嵌套的任务信息
|
Task TaskDTO `json:"task"` // 嵌套的任务信息
|
||||||
Status models.ExecutionStatus `json:"status"`
|
Status models.ExecutionStatus `json:"status"`
|
||||||
@@ -151,7 +142,7 @@ type ListTaskExecutionLogResponse struct {
|
|||||||
type ListPendingCollectionRequest struct {
|
type ListPendingCollectionRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
Status *string `json:"status" query:"status"`
|
||||||
StartTime *time.Time `json:"start_time" query:"start_time"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
@@ -161,7 +152,7 @@ type ListPendingCollectionRequest struct {
|
|||||||
// PendingCollectionDTO 是用于API响应的待采集请求结构
|
// PendingCollectionDTO 是用于API响应的待采集请求结构
|
||||||
type PendingCollectionDTO struct {
|
type PendingCollectionDTO struct {
|
||||||
CorrelationID string `json:"correlation_id"`
|
CorrelationID string `json:"correlation_id"`
|
||||||
DeviceID uint `json:"device_id"`
|
DeviceID uint32 `json:"device_id"`
|
||||||
CommandMetadata models.UintArray `json:"command_metadata"`
|
CommandMetadata models.UintArray `json:"command_metadata"`
|
||||||
Status models.PendingCollectionStatus `json:"status"`
|
Status models.PendingCollectionStatus `json:"status"`
|
||||||
FulfilledAt *time.Time `json:"fulfilled_at"`
|
FulfilledAt *time.Time `json:"fulfilled_at"`
|
||||||
@@ -180,7 +171,7 @@ type ListPendingCollectionResponse struct {
|
|||||||
type ListUserActionLogRequest struct {
|
type ListUserActionLogRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
Username *string `json:"username" query:"username"`
|
||||||
ActionType *string `json:"action_type" query:"action_type"`
|
ActionType *string `json:"action_type" query:"action_type"`
|
||||||
Status *string `json:"status" query:"status"`
|
Status *string `json:"status" query:"status"`
|
||||||
@@ -191,9 +182,9 @@ type ListUserActionLogRequest struct {
|
|||||||
|
|
||||||
// UserActionLogDTO 是用于API响应的用户操作日志结构
|
// UserActionLogDTO 是用于API响应的用户操作日志结构
|
||||||
type UserActionLogDTO struct {
|
type UserActionLogDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Time time.Time `json:"time"`
|
Time time.Time `json:"time"`
|
||||||
UserID uint `json:"user_id"`
|
UserID uint32 `json:"user_id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
SourceIP string `json:"source_ip"`
|
SourceIP string `json:"source_ip"`
|
||||||
ActionType string `json:"action_type"`
|
ActionType string `json:"action_type"`
|
||||||
@@ -217,7 +208,7 @@ type ListUserActionLogResponse struct {
|
|||||||
type ListRawMaterialPurchaseRequest struct {
|
type ListRawMaterialPurchaseRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
Supplier *string `json:"supplier" query:"supplier"`
|
||||||
StartTime *time.Time `json:"start_time" query:"start_time"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
@@ -226,19 +217,19 @@ type ListRawMaterialPurchaseRequest struct {
|
|||||||
|
|
||||||
// RawMaterialDTO 是用于API响应的简化版原料结构
|
// RawMaterialDTO 是用于API响应的简化版原料结构
|
||||||
type RawMaterialDTO struct {
|
type RawMaterialDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawMaterialPurchaseDTO 是用于API响应的原料采购结构
|
// RawMaterialPurchaseDTO 是用于API响应的原料采购结构
|
||||||
type RawMaterialPurchaseDTO struct {
|
type RawMaterialPurchaseDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
RawMaterialID uint `json:"raw_material_id"`
|
RawMaterialID uint32 `json:"raw_material_id"`
|
||||||
RawMaterial RawMaterialDTO `json:"raw_material"`
|
RawMaterial RawMaterialDTO `json:"raw_material"`
|
||||||
Supplier string `json:"supplier"`
|
Supplier string `json:"supplier"`
|
||||||
Amount float64 `json:"amount"`
|
Amount float32 `json:"amount"`
|
||||||
UnitPrice float64 `json:"unit_price"`
|
UnitPrice float32 `json:"unit_price"`
|
||||||
TotalPrice float64 `json:"total_price"`
|
TotalPrice float32 `json:"total_price"`
|
||||||
PurchaseDate time.Time `json:"purchase_date"`
|
PurchaseDate time.Time `json:"purchase_date"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
@@ -255,9 +246,9 @@ type ListRawMaterialPurchaseResponse struct {
|
|||||||
type ListRawMaterialStockLogRequest struct {
|
type ListRawMaterialStockLogRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
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"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
OrderBy string `json:"order_by" query:"order_by"`
|
OrderBy string `json:"order_by" query:"order_by"`
|
||||||
@@ -265,11 +256,11 @@ type ListRawMaterialStockLogRequest struct {
|
|||||||
|
|
||||||
// RawMaterialStockLogDTO 是用于API响应的原料库存日志结构
|
// RawMaterialStockLogDTO 是用于API响应的原料库存日志结构
|
||||||
type RawMaterialStockLogDTO struct {
|
type RawMaterialStockLogDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
RawMaterialID uint `json:"raw_material_id"`
|
RawMaterialID uint32 `json:"raw_material_id"`
|
||||||
ChangeAmount float64 `json:"change_amount"`
|
ChangeAmount float32 `json:"change_amount"`
|
||||||
SourceType models.StockLogSourceType `json:"source_type"`
|
SourceType models.StockLogSourceType `json:"source_type"`
|
||||||
SourceID uint `json:"source_id"`
|
SourceID uint32 `json:"source_id"`
|
||||||
HappenedAt time.Time `json:"happened_at"`
|
HappenedAt time.Time `json:"happened_at"`
|
||||||
Remarks string `json:"remarks"`
|
Remarks string `json:"remarks"`
|
||||||
}
|
}
|
||||||
@@ -286,9 +277,9 @@ type ListRawMaterialStockLogResponse struct {
|
|||||||
type ListFeedUsageRecordRequest struct {
|
type ListFeedUsageRecordRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
PageSize int `json:"page_size" query:"page_size"`
|
||||||
PenID *uint `json:"pen_id" query:"pen_id"`
|
PenID *uint32 `json:"pen_id" query:"pen_id"`
|
||||||
FeedFormulaID *uint `json:"feed_formula_id" query:"feed_formula_id"`
|
FeedFormulaID *uint32 `json:"feed_formula_id" query:"feed_formula_id"`
|
||||||
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"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
OrderBy string `json:"order_by" query:"order_by"`
|
OrderBy string `json:"order_by" query:"order_by"`
|
||||||
@@ -296,26 +287,26 @@ type ListFeedUsageRecordRequest struct {
|
|||||||
|
|
||||||
// PenDTO 是用于API响应的简化版猪栏结构
|
// PenDTO 是用于API响应的简化版猪栏结构
|
||||||
type PenDTO struct {
|
type PenDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeedFormulaDTO 是用于API响应的简化版饲料配方结构
|
// FeedFormulaDTO 是用于API响应的简化版饲料配方结构
|
||||||
type FeedFormulaDTO struct {
|
type FeedFormulaDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeedUsageRecordDTO 是用于API响应的饲料使用记录结构
|
// FeedUsageRecordDTO 是用于API响应的饲料使用记录结构
|
||||||
type FeedUsageRecordDTO struct {
|
type FeedUsageRecordDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
PenID uint `json:"pen_id"`
|
PenID uint32 `json:"pen_id"`
|
||||||
Pen PenDTO `json:"pen"`
|
Pen PenDTO `json:"pen"`
|
||||||
FeedFormulaID uint `json:"feed_formula_id"`
|
FeedFormulaID uint32 `json:"feed_formula_id"`
|
||||||
FeedFormula FeedFormulaDTO `json:"feed_formula"`
|
FeedFormula FeedFormulaDTO `json:"feed_formula"`
|
||||||
Amount float64 `json:"amount"`
|
Amount float32 `json:"amount"`
|
||||||
RecordedAt time.Time `json:"recorded_at"`
|
RecordedAt time.Time `json:"recorded_at"`
|
||||||
OperatorID uint `json:"operator_id"`
|
OperatorID uint32 `json:"operator_id"`
|
||||||
Remarks string `json:"remarks"`
|
Remarks string `json:"remarks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,10 +322,10 @@ type ListFeedUsageRecordResponse struct {
|
|||||||
type ListMedicationLogRequest struct {
|
type ListMedicationLogRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
||||||
MedicationID *uint `json:"medication_id" query:"medication_id"`
|
MedicationID *uint32 `json:"medication_id" query:"medication_id"`
|
||||||
Reason *string `json:"reason" query:"reason"`
|
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"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
OrderBy string `json:"order_by" query:"order_by"`
|
OrderBy string `json:"order_by" query:"order_by"`
|
||||||
@@ -342,21 +333,21 @@ type ListMedicationLogRequest struct {
|
|||||||
|
|
||||||
// MedicationDTO 是用于API响应的简化版药品结构
|
// MedicationDTO 是用于API响应的简化版药品结构
|
||||||
type MedicationDTO struct {
|
type MedicationDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MedicationLogDTO 是用于API响应的用药记录结构
|
// MedicationLogDTO 是用于API响应的用药记录结构
|
||||||
type MedicationLogDTO struct {
|
type MedicationLogDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
PigBatchID uint `json:"pig_batch_id"`
|
PigBatchID uint32 `json:"pig_batch_id"`
|
||||||
MedicationID uint `json:"medication_id"`
|
MedicationID uint32 `json:"medication_id"`
|
||||||
Medication MedicationDTO `json:"medication"`
|
Medication MedicationDTO `json:"medication"`
|
||||||
DosageUsed float64 `json:"dosage_used"`
|
DosageUsed float32 `json:"dosage_used"`
|
||||||
TargetCount int `json:"target_count"`
|
TargetCount int `json:"target_count"`
|
||||||
Reason models.MedicationReasonType `json:"reason"`
|
Reason models.MedicationReasonType `json:"reason"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
OperatorID uint `json:"operator_id"`
|
OperatorID uint32 `json:"operator_id"`
|
||||||
HappenedAt time.Time `json:"happened_at"`
|
HappenedAt time.Time `json:"happened_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,9 +363,9 @@ type ListMedicationLogResponse struct {
|
|||||||
type ListPigBatchLogRequest struct {
|
type ListPigBatchLogRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
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"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
OrderBy string `json:"order_by" query:"order_by"`
|
OrderBy string `json:"order_by" query:"order_by"`
|
||||||
@@ -382,16 +373,16 @@ type ListPigBatchLogRequest struct {
|
|||||||
|
|
||||||
// PigBatchLogDTO 是用于API响应的猪批次日志结构
|
// PigBatchLogDTO 是用于API响应的猪批次日志结构
|
||||||
type PigBatchLogDTO struct {
|
type PigBatchLogDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_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"`
|
ChangeType models.LogChangeType `json:"change_type"`
|
||||||
ChangeCount int `json:"change_count"`
|
ChangeCount int `json:"change_count"`
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
BeforeCount int `json:"before_count"`
|
BeforeCount int `json:"before_count"`
|
||||||
AfterCount int `json:"after_count"`
|
AfterCount int `json:"after_count"`
|
||||||
OperatorID uint `json:"operator_id"`
|
OperatorID uint32 `json:"operator_id"`
|
||||||
HappenedAt time.Time `json:"happened_at"`
|
HappenedAt time.Time `json:"happened_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +398,7 @@ type ListPigBatchLogResponse struct {
|
|||||||
type ListWeighingBatchRequest struct {
|
type ListWeighingBatchRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
OrderBy string `json:"order_by" query:"order_by"`
|
OrderBy string `json:"order_by" query:"order_by"`
|
||||||
@@ -415,12 +406,12 @@ type ListWeighingBatchRequest struct {
|
|||||||
|
|
||||||
// WeighingBatchDTO 是用于API响应的批次称重记录结构
|
// WeighingBatchDTO 是用于API响应的批次称重记录结构
|
||||||
type WeighingBatchDTO struct {
|
type WeighingBatchDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
WeighingTime time.Time `json:"weighing_time"`
|
WeighingTime time.Time `json:"weighing_time"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
PigBatchID uint `json:"pig_batch_id"`
|
PigBatchID uint32 `json:"pig_batch_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListWeighingBatchResponse 是获取批次称重记录列表的响应结构
|
// ListWeighingBatchResponse 是获取批次称重记录列表的响应结构
|
||||||
@@ -435,9 +426,9 @@ type ListWeighingBatchResponse struct {
|
|||||||
type ListWeighingRecordRequest struct {
|
type ListWeighingRecordRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
PageSize int `json:"page_size" query:"page_size"`
|
||||||
WeighingBatchID *uint `json:"weighing_batch_id" query:"weighing_batch_id"`
|
WeighingBatchID *uint32 `json:"weighing_batch_id" query:"weighing_batch_id"`
|
||||||
PenID *uint `json:"pen_id" query:"pen_id"`
|
PenID *uint32 `json:"pen_id" query:"pen_id"`
|
||||||
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"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
OrderBy string `json:"order_by" query:"order_by"`
|
OrderBy string `json:"order_by" query:"order_by"`
|
||||||
@@ -445,13 +436,13 @@ type ListWeighingRecordRequest struct {
|
|||||||
|
|
||||||
// WeighingRecordDTO 是用于API响应的单次称重记录结构
|
// WeighingRecordDTO 是用于API响应的单次称重记录结构
|
||||||
type WeighingRecordDTO struct {
|
type WeighingRecordDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
Weight float64 `json:"weight"`
|
Weight float32 `json:"weight"`
|
||||||
WeighingBatchID uint `json:"weighing_batch_id"`
|
WeighingBatchID uint32 `json:"weighing_batch_id"`
|
||||||
PenID uint `json:"pen_id"`
|
PenID uint32 `json:"pen_id"`
|
||||||
OperatorID uint `json:"operator_id"`
|
OperatorID uint32 `json:"operator_id"`
|
||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
WeighingTime time.Time `json:"weighing_time"`
|
WeighingTime time.Time `json:"weighing_time"`
|
||||||
}
|
}
|
||||||
@@ -468,10 +459,10 @@ type ListWeighingRecordResponse struct {
|
|||||||
type ListPigTransferLogRequest struct {
|
type ListPigTransferLogRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
||||||
PenID *uint `json:"pen_id" query:"pen_id"`
|
PenID *uint32 `json:"pen_id" query:"pen_id"`
|
||||||
TransferType *string `json:"transfer_type" query:"transfer_type"`
|
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"`
|
CorrelationID *string `json:"correlation_id" query:"correlation_id"`
|
||||||
StartTime *time.Time `json:"start_time" query:"start_time"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
@@ -480,16 +471,16 @@ type ListPigTransferLogRequest struct {
|
|||||||
|
|
||||||
// PigTransferLogDTO 是用于API响应的猪只迁移日志结构
|
// PigTransferLogDTO 是用于API响应的猪只迁移日志结构
|
||||||
type PigTransferLogDTO struct {
|
type PigTransferLogDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
TransferTime time.Time `json:"transfer_time"`
|
TransferTime time.Time `json:"transfer_time"`
|
||||||
PigBatchID uint `json:"pig_batch_id"`
|
PigBatchID uint32 `json:"pig_batch_id"`
|
||||||
PenID uint `json:"pen_id"`
|
PenID uint32 `json:"pen_id"`
|
||||||
Quantity int `json:"quantity"`
|
Quantity int `json:"quantity"`
|
||||||
Type models.PigTransferType `json:"type"`
|
Type models.PigTransferType `json:"type"`
|
||||||
CorrelationID string `json:"correlation_id"`
|
CorrelationID string `json:"correlation_id"`
|
||||||
OperatorID uint `json:"operator_id"`
|
OperatorID uint32 `json:"operator_id"`
|
||||||
Remarks string `json:"remarks"`
|
Remarks string `json:"remarks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,11 +496,11 @@ type ListPigTransferLogResponse struct {
|
|||||||
type ListPigSickLogRequest struct {
|
type ListPigSickLogRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
||||||
PenID *uint `json:"pen_id" query:"pen_id"`
|
PenID *uint32 `json:"pen_id" query:"pen_id"`
|
||||||
Reason *string `json:"reason" query:"reason"`
|
Reason *string `json:"reason" query:"reason"`
|
||||||
TreatmentLocation *string `json:"treatment_location" query:"treatment_location"`
|
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"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
OrderBy string `json:"order_by" query:"order_by"`
|
OrderBy string `json:"order_by" query:"order_by"`
|
||||||
@@ -517,18 +508,18 @@ type ListPigSickLogRequest struct {
|
|||||||
|
|
||||||
// PigSickLogDTO 是用于API响应的病猪日志结构
|
// PigSickLogDTO 是用于API响应的病猪日志结构
|
||||||
type PigSickLogDTO struct {
|
type PigSickLogDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
PigBatchID uint `json:"pig_batch_id"`
|
PigBatchID uint32 `json:"pig_batch_id"`
|
||||||
PenID uint `json:"pen_id"`
|
PenID uint32 `json:"pen_id"`
|
||||||
ChangeCount int `json:"change_count"`
|
ChangeCount int `json:"change_count"`
|
||||||
Reason models.PigBatchSickPigReasonType `json:"reason"`
|
Reason models.PigBatchSickPigReasonType `json:"reason"`
|
||||||
BeforeCount int `json:"before_count"`
|
BeforeCount int `json:"before_count"`
|
||||||
AfterCount int `json:"after_count"`
|
AfterCount int `json:"after_count"`
|
||||||
Remarks string `json:"remarks"`
|
Remarks string `json:"remarks"`
|
||||||
TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location"`
|
TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location"`
|
||||||
OperatorID uint `json:"operator_id"`
|
OperatorID uint32 `json:"operator_id"`
|
||||||
HappenedAt time.Time `json:"happened_at"`
|
HappenedAt time.Time `json:"happened_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,9 +535,9 @@ type ListPigSickLogResponse struct {
|
|||||||
type ListPigPurchaseRequest struct {
|
type ListPigPurchaseRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
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"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
OrderBy string `json:"order_by" query:"order_by"`
|
OrderBy string `json:"order_by" query:"order_by"`
|
||||||
@@ -554,17 +545,17 @@ type ListPigPurchaseRequest struct {
|
|||||||
|
|
||||||
// PigPurchaseDTO 是用于API响应的猪只采购记录结构
|
// PigPurchaseDTO 是用于API响应的猪只采购记录结构
|
||||||
type PigPurchaseDTO struct {
|
type PigPurchaseDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_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"`
|
PurchaseDate time.Time `json:"purchase_date"`
|
||||||
Supplier string `json:"supplier"`
|
Supplier string `json:"supplier"`
|
||||||
Quantity int `json:"quantity"`
|
Quantity int `json:"quantity"`
|
||||||
UnitPrice float64 `json:"unit_price"`
|
UnitPrice float32 `json:"unit_price"`
|
||||||
TotalPrice float64 `json:"total_price"`
|
TotalPrice float32 `json:"total_price"`
|
||||||
Remarks string `json:"remarks"`
|
Remarks string `json:"remarks"`
|
||||||
OperatorID uint `json:"operator_id"`
|
OperatorID uint32 `json:"operator_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPigPurchaseResponse 是获取猪只采购记录列表的响应结构
|
// ListPigPurchaseResponse 是获取猪只采购记录列表的响应结构
|
||||||
@@ -579,9 +570,9 @@ type ListPigPurchaseResponse struct {
|
|||||||
type ListPigSaleRequest struct {
|
type ListPigSaleRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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"`
|
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"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
EndTime *time.Time `json:"end_time" query:"end_time"`
|
EndTime *time.Time `json:"end_time" query:"end_time"`
|
||||||
OrderBy string `json:"order_by" query:"order_by"`
|
OrderBy string `json:"order_by" query:"order_by"`
|
||||||
@@ -589,17 +580,17 @@ type ListPigSaleRequest struct {
|
|||||||
|
|
||||||
// PigSaleDTO 是用于API响应的猪只销售记录结构
|
// PigSaleDTO 是用于API响应的猪只销售记录结构
|
||||||
type PigSaleDTO struct {
|
type PigSaleDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_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"`
|
SaleDate time.Time `json:"sale_date"`
|
||||||
Buyer string `json:"buyer"`
|
Buyer string `json:"buyer"`
|
||||||
Quantity int `json:"quantity"`
|
Quantity int `json:"quantity"`
|
||||||
UnitPrice float64 `json:"unit_price"`
|
UnitPrice float32 `json:"unit_price"`
|
||||||
TotalPrice float64 `json:"total_price"`
|
TotalPrice float32 `json:"total_price"`
|
||||||
Remarks string `json:"remarks"`
|
Remarks string `json:"remarks"`
|
||||||
OperatorID uint `json:"operator_id"`
|
OperatorID uint32 `json:"operator_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPigSaleResponse 是获取猪只销售记录列表的响应结构
|
// ListPigSaleResponse 是获取猪只销售记录列表的响应结构
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package dto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
|
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewListNotificationResponse 从模型数据创建通知列表响应 DTO
|
// NewListNotificationResponse 从模型数据创建通知列表响应 DTO
|
||||||
@@ -18,7 +16,7 @@ func NewListNotificationResponse(data []models.Notification, total int64, page,
|
|||||||
UserID: item.UserID,
|
UserID: item.UserID,
|
||||||
Title: item.Title,
|
Title: item.Title,
|
||||||
Message: item.Message,
|
Message: item.Message,
|
||||||
Level: zapcore.Level(item.Level),
|
Level: item.Level,
|
||||||
AlarmTimestamp: item.AlarmTimestamp,
|
AlarmTimestamp: item.AlarmTimestamp,
|
||||||
ToAddress: item.ToAddress,
|
ToAddress: item.ToAddress,
|
||||||
Status: item.Status,
|
Status: item.Status,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
"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"
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
@@ -12,15 +11,15 @@ import (
|
|||||||
// SendTestNotificationRequest 定义了发送测试通知请求的 JSON 结构
|
// SendTestNotificationRequest 定义了发送测试通知请求的 JSON 结构
|
||||||
type SendTestNotificationRequest struct {
|
type SendTestNotificationRequest struct {
|
||||||
// Type 指定要测试的通知渠道
|
// Type 指定要测试的通知渠道
|
||||||
Type notify.NotifierType `json:"type" validate:"required"`
|
Type models.NotifierType `json:"type" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListNotificationRequest 定义了获取通知列表的请求参数
|
// ListNotificationRequest 定义了获取通知列表的请求参数
|
||||||
type ListNotificationRequest struct {
|
type ListNotificationRequest struct {
|
||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
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 *notify.NotifierType `json:"notifier_type" query:"notifier_type"`
|
NotifierType *models.NotifierType `json:"notifier_type" query:"notifier_type"`
|
||||||
Status *models.NotificationStatus `json:"status" query:"status"`
|
Status *models.NotificationStatus `json:"status" query:"status"`
|
||||||
Level *zapcore.Level `json:"level" query:"level"`
|
Level *zapcore.Level `json:"level" query:"level"`
|
||||||
StartTime *time.Time `json:"start_time" query:"start_time"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
@@ -30,14 +29,14 @@ type ListNotificationRequest struct {
|
|||||||
|
|
||||||
// NotificationDTO 是用于API响应的通知结构
|
// NotificationDTO 是用于API响应的通知结构
|
||||||
type NotificationDTO struct {
|
type NotificationDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
NotifierType notify.NotifierType `json:"notifier_type"`
|
NotifierType models.NotifierType `json:"notifier_type"`
|
||||||
UserID uint `json:"user_id"`
|
UserID uint32 `json:"user_id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Level zapcore.Level `json:"level"`
|
Level models.SeverityLevel `json:"level"`
|
||||||
AlarmTimestamp time.Time `json:"alarm_timestamp"`
|
AlarmTimestamp time.Time `json:"alarm_timestamp"`
|
||||||
ToAddress string `json:"to_address"`
|
ToAddress string `json:"to_address"`
|
||||||
Status models.NotificationStatus `json:"status"`
|
Status models.NotificationStatus `json:"status"`
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ type PigBatchQueryDTO struct {
|
|||||||
|
|
||||||
// PigBatchResponseDTO 定义了猪批次信息的响应结构
|
// PigBatchResponseDTO 定义了猪批次信息的响应结构
|
||||||
type PigBatchResponseDTO struct {
|
type PigBatchResponseDTO struct {
|
||||||
ID uint `json:"id"` // 批次ID
|
ID uint32 `json:"id"` // 批次ID
|
||||||
BatchNumber string `json:"batch_number"` // 批次编号
|
BatchNumber string `json:"batch_number"` // 批次编号
|
||||||
OriginType models.PigBatchOriginType `json:"origin_type"` // 批次来源
|
OriginType models.PigBatchOriginType `json:"origin_type"` // 批次来源
|
||||||
StartDate time.Time `json:"start_date"` // 批次开始日期
|
StartDate time.Time `json:"start_date"` // 批次开始日期
|
||||||
@@ -48,34 +48,34 @@ type PigBatchResponseDTO struct {
|
|||||||
|
|
||||||
// AssignEmptyPensToBatchRequest 用于为猪批次分配空栏的请求体
|
// AssignEmptyPensToBatchRequest 用于为猪批次分配空栏的请求体
|
||||||
type AssignEmptyPensToBatchRequest struct {
|
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 用于将猪栏划拨到新批次的请求体
|
// ReclassifyPenToNewBatchRequest 用于将猪栏划拨到新批次的请求体
|
||||||
type ReclassifyPenToNewBatchRequest struct {
|
type ReclassifyPenToNewBatchRequest struct {
|
||||||
ToBatchID uint `json:"to_batch_id" validate:"required"` // 目标猪批次ID
|
ToBatchID uint32 `json:"to_batch_id" validate:"required"` // 目标猪批次ID
|
||||||
PenID uint `json:"pen_id" validate:"required"` // 待划拨的猪栏ID
|
PenID uint32 `json:"pen_id" validate:"required"` // 待划拨的猪栏ID
|
||||||
Remarks string `json:"remarks"` // 备注
|
Remarks string `json:"remarks"` // 备注
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveEmptyPenFromBatchRequest 用于从猪批次移除空栏的请求体
|
// RemoveEmptyPenFromBatchRequest 用于从猪批次移除空栏的请求体
|
||||||
type RemoveEmptyPenFromBatchRequest struct {
|
type RemoveEmptyPenFromBatchRequest struct {
|
||||||
PenID uint `json:"pen_id" validate:"required"` // 待移除的猪栏ID
|
PenID uint32 `json:"pen_id" validate:"required"` // 待移除的猪栏ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// MovePigsIntoPenRequest 用于将猪只从“虚拟库存”移入指定猪栏的请求体
|
// MovePigsIntoPenRequest 用于将猪只从“虚拟库存”移入指定猪栏的请求体
|
||||||
type MovePigsIntoPenRequest struct {
|
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"` // 移入猪只数量
|
Quantity int `json:"quantity" validate:"required,min=1"` // 移入猪只数量
|
||||||
Remarks string `json:"remarks"` // 备注
|
Remarks string `json:"remarks"` // 备注
|
||||||
}
|
}
|
||||||
|
|
||||||
// SellPigsRequest 用于处理卖猪的请求体
|
// SellPigsRequest 用于处理卖猪的请求体
|
||||||
type SellPigsRequest struct {
|
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"` // 卖出猪只数量
|
Quantity int `json:"quantity" validate:"required,min=1"` // 卖出猪只数量
|
||||||
UnitPrice float64 `json:"unit_price" validate:"required,min=0"` // 单价
|
UnitPrice float32 `json:"unit_price" validate:"required,min=0"` // 单价
|
||||||
TotalPrice float64 `json:"total_price" validate:"required,min=0"` // 总价
|
TotalPrice float32 `json:"total_price" validate:"required,min=0"` // 总价
|
||||||
TraderName string `json:"trader_name" validate:"required"` // 交易方名称
|
TraderName string `json:"trader_name" validate:"required"` // 交易方名称
|
||||||
TradeDate time.Time `json:"trade_date" validate:"required"` // 交易日期
|
TradeDate time.Time `json:"trade_date" validate:"required"` // 交易日期
|
||||||
Remarks string `json:"remarks"` // 备注
|
Remarks string `json:"remarks"` // 备注
|
||||||
@@ -83,10 +83,10 @@ type SellPigsRequest struct {
|
|||||||
|
|
||||||
// BuyPigsRequest 用于处理买猪的请求体
|
// BuyPigsRequest 用于处理买猪的请求体
|
||||||
type BuyPigsRequest struct {
|
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"` // 买入猪只数量
|
Quantity int `json:"quantity" validate:"required,min=1"` // 买入猪只数量
|
||||||
UnitPrice float64 `json:"unit_price" validate:"required,min=0"` // 单价
|
UnitPrice float32 `json:"unit_price" validate:"required,min=0"` // 单价
|
||||||
TotalPrice float64 `json:"total_price" validate:"required,min=0"` // 总价
|
TotalPrice float32 `json:"total_price" validate:"required,min=0"` // 总价
|
||||||
TraderName string `json:"trader_name" validate:"required"` // 交易方名称
|
TraderName string `json:"trader_name" validate:"required"` // 交易方名称
|
||||||
TradeDate time.Time `json:"trade_date" validate:"required"` // 交易日期
|
TradeDate time.Time `json:"trade_date" validate:"required"` // 交易日期
|
||||||
Remarks string `json:"remarks"` // 备注
|
Remarks string `json:"remarks"` // 备注
|
||||||
@@ -94,24 +94,24 @@ type BuyPigsRequest struct {
|
|||||||
|
|
||||||
// TransferPigsAcrossBatchesRequest 用于跨猪群调栏的请求体
|
// TransferPigsAcrossBatchesRequest 用于跨猪群调栏的请求体
|
||||||
type TransferPigsAcrossBatchesRequest struct {
|
type TransferPigsAcrossBatchesRequest struct {
|
||||||
DestBatchID uint `json:"dest_batch_id" validate:"required"` // 目标猪批次ID
|
DestBatchID uint32 `json:"dest_batch_id" validate:"required"` // 目标猪批次ID
|
||||||
FromPenID uint `json:"from_pen_id" validate:"required"` // 源猪栏ID
|
FromPenID uint32 `json:"from_pen_id" validate:"required"` // 源猪栏ID
|
||||||
ToPenID uint `json:"to_pen_id" validate:"required"` // 目标猪栏ID
|
ToPenID uint32 `json:"to_pen_id" validate:"required"` // 目标猪栏ID
|
||||||
Quantity uint `json:"quantity" validate:"required,min=1"` // 调栏猪只数量
|
Quantity uint32 `json:"quantity" validate:"required,min=1"` // 调栏猪只数量
|
||||||
Remarks string `json:"remarks"` // 备注
|
Remarks string `json:"remarks"` // 备注
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransferPigsWithinBatchRequest 用于群内调栏的请求体
|
// TransferPigsWithinBatchRequest 用于群内调栏的请求体
|
||||||
type TransferPigsWithinBatchRequest struct {
|
type TransferPigsWithinBatchRequest struct {
|
||||||
FromPenID uint `json:"from_pen_id" validate:"required"` // 源猪栏ID
|
FromPenID uint32 `json:"from_pen_id" validate:"required"` // 源猪栏ID
|
||||||
ToPenID uint `json:"to_pen_id" validate:"required"` // 目标猪栏ID
|
ToPenID uint32 `json:"to_pen_id" validate:"required"` // 目标猪栏ID
|
||||||
Quantity uint `json:"quantity" validate:"required,min=1"` // 调栏猪只数量
|
Quantity uint32 `json:"quantity" validate:"required,min=1"` // 调栏猪只数量
|
||||||
Remarks string `json:"remarks"` // 备注
|
Remarks string `json:"remarks"` // 备注
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordSickPigsRequest 用于记录新增病猪事件的请求体
|
// RecordSickPigsRequest 用于记录新增病猪事件的请求体
|
||||||
type RecordSickPigsRequest struct {
|
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"` // 病猪数量
|
Quantity int `json:"quantity" validate:"required,min=1"` // 病猪数量
|
||||||
TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location" validate:"required"` // 治疗地点
|
TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location" validate:"required"` // 治疗地点
|
||||||
HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间
|
HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间
|
||||||
@@ -120,7 +120,7 @@ type RecordSickPigsRequest struct {
|
|||||||
|
|
||||||
// RecordSickPigRecoveryRequest 用于记录病猪康复事件的请求体
|
// RecordSickPigRecoveryRequest 用于记录病猪康复事件的请求体
|
||||||
type RecordSickPigRecoveryRequest struct {
|
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"` // 康复猪数量
|
Quantity int `json:"quantity" validate:"required,min=1"` // 康复猪数量
|
||||||
TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location" validate:"required"` // 治疗地点
|
TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location" validate:"required"` // 治疗地点
|
||||||
HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间
|
HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间
|
||||||
@@ -129,7 +129,7 @@ type RecordSickPigRecoveryRequest struct {
|
|||||||
|
|
||||||
// RecordSickPigDeathRequest 用于记录病猪死亡事件的请求体
|
// RecordSickPigDeathRequest 用于记录病猪死亡事件的请求体
|
||||||
type RecordSickPigDeathRequest struct {
|
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"` // 死亡猪数量
|
Quantity int `json:"quantity" validate:"required,min=1"` // 死亡猪数量
|
||||||
TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location" validate:"required"` // 治疗地点
|
TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location" validate:"required"` // 治疗地点
|
||||||
HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间
|
HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间
|
||||||
@@ -138,7 +138,7 @@ type RecordSickPigDeathRequest struct {
|
|||||||
|
|
||||||
// RecordSickPigCullRequest 用于记录病猪淘汰事件的请求体
|
// RecordSickPigCullRequest 用于记录病猪淘汰事件的请求体
|
||||||
type RecordSickPigCullRequest struct {
|
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"` // 淘汰猪数量
|
Quantity int `json:"quantity" validate:"required,min=1"` // 淘汰猪数量
|
||||||
TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location" validate:"required"` // 治疗地点
|
TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location" validate:"required"` // 治疗地点
|
||||||
HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间
|
HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间
|
||||||
@@ -147,7 +147,7 @@ type RecordSickPigCullRequest struct {
|
|||||||
|
|
||||||
// RecordDeathRequest 用于记录正常猪只死亡事件的请求体
|
// RecordDeathRequest 用于记录正常猪只死亡事件的请求体
|
||||||
type RecordDeathRequest struct {
|
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"` // 死亡猪数量
|
Quantity int `json:"quantity" validate:"required,min=1"` // 死亡猪数量
|
||||||
HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间
|
HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间
|
||||||
Remarks string `json:"remarks"` // 备注
|
Remarks string `json:"remarks"` // 备注
|
||||||
@@ -155,7 +155,7 @@ type RecordDeathRequest struct {
|
|||||||
|
|
||||||
// RecordCullRequest 用于记录正常猪只淘汰事件的请求体
|
// RecordCullRequest 用于记录正常猪只淘汰事件的请求体
|
||||||
type RecordCullRequest struct {
|
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"` // 淘汰猪数量
|
Quantity int `json:"quantity" validate:"required,min=1"` // 淘汰猪数量
|
||||||
HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间
|
HappenedAt time.Time `json:"happened_at" validate:"required"` // 发生时间
|
||||||
Remarks string `json:"remarks"` // 备注
|
Remarks string `json:"remarks"` // 备注
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
|||||||
|
|
||||||
// PigHouseResponse 定义了猪舍信息的响应结构
|
// PigHouseResponse 定义了猪舍信息的响应结构
|
||||||
type PigHouseResponse struct {
|
type PigHouseResponse struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PenResponse 定义了猪栏信息的响应结构
|
// PenResponse 定义了猪栏信息的响应结构
|
||||||
type PenResponse struct {
|
type PenResponse struct {
|
||||||
ID uint `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
PenNumber string `json:"pen_number"`
|
PenNumber string `json:"pen_number"`
|
||||||
HouseID uint `json:"house_id"`
|
HouseID uint32 `json:"house_id"`
|
||||||
Capacity int `json:"capacity"`
|
Capacity int `json:"capacity"`
|
||||||
Status models.PenStatus `json:"status"`
|
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"`
|
CurrentPigCount int `json:"current_pig_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,14 +35,14 @@ type UpdatePigHouseRequest struct {
|
|||||||
// CreatePenRequest 定义了创建猪栏的请求结构
|
// CreatePenRequest 定义了创建猪栏的请求结构
|
||||||
type CreatePenRequest struct {
|
type CreatePenRequest struct {
|
||||||
PenNumber string `json:"pen_number" validate:"required"`
|
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"`
|
Capacity int `json:"capacity" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePenRequest 定义了更新猪栏的请求结构
|
// UpdatePenRequest 定义了更新猪栏的请求结构
|
||||||
type UpdatePenRequest struct {
|
type UpdatePenRequest struct {
|
||||||
PenNumber string `json:"pen_number" validate:"required"`
|
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"`
|
Capacity int `json:"capacity" validate:"required"`
|
||||||
Status models.PenStatus `json:"status" validate:"required,oneof=空闲 使用中 病猪栏 康复栏 清洗消毒 维修中"` // 添加oneof校验
|
Status models.PenStatus `json:"status" validate:"required,oneof=空闲 使用中 病猪栏 康复栏 清洗消毒 维修中"` // 添加oneof校验
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func NewPlanToResponse(plan *models.Plan) (*PlanResponse, error) {
|
|||||||
|
|
||||||
response := &PlanResponse{
|
response := &PlanResponse{
|
||||||
ID: plan.ID,
|
ID: plan.ID,
|
||||||
Name: plan.Name,
|
Name: string(plan.Name),
|
||||||
Description: plan.Description,
|
Description: plan.Description,
|
||||||
PlanType: plan.PlanType,
|
PlanType: plan.PlanType,
|
||||||
ExecutionType: plan.ExecutionType,
|
ExecutionType: plan.ExecutionType,
|
||||||
@@ -60,7 +60,7 @@ func NewPlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
plan := &models.Plan{
|
plan := &models.Plan{
|
||||||
Name: req.Name,
|
Name: models.PlanName(req.Name),
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
ExecutionType: req.ExecutionType,
|
ExecutionType: req.ExecutionType,
|
||||||
ExecuteNum: req.ExecuteNum,
|
ExecuteNum: req.ExecuteNum,
|
||||||
@@ -103,7 +103,7 @@ func NewPlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
plan := &models.Plan{
|
plan := &models.Plan{
|
||||||
Name: req.Name,
|
Name: models.PlanName(req.Name),
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
ExecutionType: req.ExecutionType,
|
ExecutionType: req.ExecutionType,
|
||||||
ExecuteNum: req.ExecuteNum,
|
ExecuteNum: req.ExecuteNum,
|
||||||
|
|||||||
@@ -17,22 +17,22 @@ type CreatePlanRequest struct {
|
|||||||
Name string `json:"name" validate:"required" example:"猪舍温度控制计划"`
|
Name string `json:"name" validate:"required" example:"猪舍温度控制计划"`
|
||||||
Description string `json:"description" example:"根据温度自动调节风扇和加热器"`
|
Description string `json:"description" example:"根据温度自动调节风扇和加热器"`
|
||||||
ExecutionType models.PlanExecutionType `json:"execution_type" validate:"required" 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 * * *"`
|
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"`
|
Tasks []TaskRequest `json:"tasks,omitempty" validate:"omitempty,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlanResponse 定义计划详情响应的结构体
|
// PlanResponse 定义计划详情响应的结构体
|
||||||
type PlanResponse struct {
|
type PlanResponse struct {
|
||||||
ID uint `json:"id" example:"1"`
|
ID uint32 `json:"id" example:"1"`
|
||||||
Name string `json:"name" example:"猪舍温度控制计划"`
|
Name string `json:"name" example:"猪舍温度控制计划"`
|
||||||
Description string `json:"description" example:"根据温度自动调节风扇和加热器"`
|
Description string `json:"description" example:"根据温度自动调节风扇和加热器"`
|
||||||
PlanType models.PlanType `json:"plan_type" example:"自定义任务"`
|
PlanType models.PlanType `json:"plan_type" example:"自定义任务"`
|
||||||
ExecutionType models.PlanExecutionType `json:"execution_type" example:"自动"`
|
ExecutionType models.PlanExecutionType `json:"execution_type" example:"自动"`
|
||||||
Status models.PlanStatus `json:"status" example:"已启用"`
|
Status models.PlanStatus `json:"status" example:"已启用"`
|
||||||
ExecuteNum uint `json:"execute_num" example:"10"`
|
ExecuteNum uint32 `json:"execute_num" example:"10"`
|
||||||
ExecuteCount uint `json:"execute_count" example:"0"`
|
ExecuteCount uint32 `json:"execute_count" example:"0"`
|
||||||
CronExpression string `json:"cron_expression" example:"0 0 6 * * *"`
|
CronExpression string `json:"cron_expression" example:"0 0 6 * * *"`
|
||||||
ContentType models.PlanContentType `json:"content_type" example:"任务"`
|
ContentType models.PlanContentType `json:"content_type" example:"任务"`
|
||||||
SubPlans []SubPlanResponse `json:"sub_plans,omitempty"`
|
SubPlans []SubPlanResponse `json:"sub_plans,omitempty"`
|
||||||
@@ -50,17 +50,17 @@ type UpdatePlanRequest struct {
|
|||||||
Name string `json:"name" example:"猪舍温度控制计划V2"`
|
Name string `json:"name" example:"猪舍温度控制计划V2"`
|
||||||
Description string `json:"description" example:"更新后的描述"`
|
Description string `json:"description" example:"更新后的描述"`
|
||||||
ExecutionType models.PlanExecutionType `json:"execution_type" validate:"required" 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 * * *"`
|
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"`
|
Tasks []TaskRequest `json:"tasks,omitempty" validate:"omitempty,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubPlanResponse 定义子计划响应结构体
|
// SubPlanResponse 定义子计划响应结构体
|
||||||
type SubPlanResponse struct {
|
type SubPlanResponse struct {
|
||||||
ID uint `json:"id" example:"1"`
|
ID uint32 `json:"id" example:"1"`
|
||||||
ParentPlanID uint `json:"parent_plan_id" example:"1"`
|
ParentPlanID uint32 `json:"parent_plan_id" example:"1"`
|
||||||
ChildPlanID uint `json:"child_plan_id" example:"2"`
|
ChildPlanID uint32 `json:"child_plan_id" example:"2"`
|
||||||
ExecutionOrder int `json:"execution_order" example:"1"`
|
ExecutionOrder int `json:"execution_order" example:"1"`
|
||||||
ChildPlan *PlanResponse `json:"child_plan,omitempty"`
|
ChildPlan *PlanResponse `json:"child_plan,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ type TaskRequest struct {
|
|||||||
// TaskResponse 定义任务响应结构体
|
// TaskResponse 定义任务响应结构体
|
||||||
type TaskResponse struct {
|
type TaskResponse struct {
|
||||||
ID int `json:"id" example:"1"`
|
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:"打开风扇"`
|
Name string `json:"name" example:"打开风扇"`
|
||||||
Description string `json:"description" example:"打开1号风扇"`
|
Description string `json:"description" example:"打开1号风扇"`
|
||||||
ExecutionOrder int `json:"execution_order" example:"1"`
|
ExecutionOrder int `json:"execution_order" example:"1"`
|
||||||
|
|||||||
@@ -16,19 +16,19 @@ type LoginRequest struct {
|
|||||||
// CreateUserResponse 定义创建用户成功响应的结构体
|
// CreateUserResponse 定义创建用户成功响应的结构体
|
||||||
type CreateUserResponse struct {
|
type CreateUserResponse struct {
|
||||||
Username string `json:"username" example:"newuser"`
|
Username string `json:"username" example:"newuser"`
|
||||||
ID uint `json:"id" example:"1"`
|
ID uint32 `json:"id" example:"1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginResponse 定义登录成功响应的结构体
|
// LoginResponse 定义登录成功响应的结构体
|
||||||
type LoginResponse struct {
|
type LoginResponse struct {
|
||||||
Username string `json:"username" example:"testuser"`
|
Username string `json:"username" example:"testuser"`
|
||||||
ID uint `json:"id" example:"1"`
|
ID uint32 `json:"id" example:"1"`
|
||||||
Token string `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."`
|
Token string `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistoryResponse 定义单条操作历史的响应结构体
|
// HistoryResponse 定义单条操作历史的响应结构体
|
||||||
type HistoryResponse struct {
|
type HistoryResponse struct {
|
||||||
UserID uint `json:"user_id" example:"101"`
|
UserID uint32 `json:"user_id" example:"101"`
|
||||||
Username string `json:"username" example:"testuser"`
|
Username string `json:"username" example:"testuser"`
|
||||||
ActionType string `json:"action_type" example:"更新设备"`
|
ActionType string `json:"action_type" example:"更新设备"`
|
||||||
Description string `json:"description" example:"设备更新成功"`
|
Description string `json:"description" example:"设备更新成功"`
|
||||||
|
|||||||
@@ -28,23 +28,23 @@ var (
|
|||||||
// DeviceService 定义了应用层的设备服务接口,用于协调设备相关的业务逻辑。
|
// DeviceService 定义了应用层的设备服务接口,用于协调设备相关的业务逻辑。
|
||||||
type DeviceService interface {
|
type DeviceService interface {
|
||||||
CreateDevice(ctx context.Context, req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error)
|
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)
|
ListDevices(ctx context.Context) ([]*dto.DeviceResponse, error)
|
||||||
UpdateDevice(ctx context.Context, id uint, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error)
|
UpdateDevice(ctx context.Context, id uint32, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error)
|
||||||
DeleteDevice(ctx context.Context, id uint) error
|
DeleteDevice(ctx context.Context, id uint32) error
|
||||||
ManualControl(ctx context.Context, id uint, req *dto.ManualControlDeviceRequest) error
|
ManualControl(ctx context.Context, id uint32, req *dto.ManualControlDeviceRequest) error
|
||||||
|
|
||||||
CreateAreaController(ctx context.Context, req *dto.CreateAreaControllerRequest) (*dto.AreaControllerResponse, 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)
|
ListAreaControllers(ctx context.Context) ([]*dto.AreaControllerResponse, error)
|
||||||
UpdateAreaController(ctx context.Context, id uint, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error)
|
UpdateAreaController(ctx context.Context, id uint32, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error)
|
||||||
DeleteAreaController(ctx context.Context, id uint) error
|
DeleteAreaController(ctx context.Context, id uint32) error
|
||||||
|
|
||||||
CreateDeviceTemplate(ctx context.Context, req *dto.CreateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, 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)
|
ListDeviceTemplates(ctx context.Context) ([]*dto.DeviceTemplateResponse, error)
|
||||||
UpdateDeviceTemplate(ctx context.Context, id uint, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error)
|
UpdateDeviceTemplate(ctx context.Context, id uint32, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error)
|
||||||
DeleteDeviceTemplate(ctx context.Context, id uint) error
|
DeleteDeviceTemplate(ctx context.Context, id uint32) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// deviceService 是 DeviceService 接口的具体实现。
|
// deviceService 是 DeviceService 接口的具体实现。
|
||||||
@@ -54,6 +54,7 @@ type deviceService struct {
|
|||||||
areaControllerRepo repository.AreaControllerRepository
|
areaControllerRepo repository.AreaControllerRepository
|
||||||
deviceTemplateRepo repository.DeviceTemplateRepository
|
deviceTemplateRepo repository.DeviceTemplateRepository
|
||||||
deviceDomainSvc device.Service
|
deviceDomainSvc device.Service
|
||||||
|
thresholdAlarmService ThresholdAlarmService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDeviceService 创建一个新的 DeviceService 实例。
|
// NewDeviceService 创建一个新的 DeviceService 实例。
|
||||||
@@ -63,6 +64,7 @@ func NewDeviceService(
|
|||||||
areaControllerRepo repository.AreaControllerRepository,
|
areaControllerRepo repository.AreaControllerRepository,
|
||||||
deviceTemplateRepo repository.DeviceTemplateRepository,
|
deviceTemplateRepo repository.DeviceTemplateRepository,
|
||||||
deviceDomainSvc device.Service,
|
deviceDomainSvc device.Service,
|
||||||
|
thresholdAlarmService ThresholdAlarmService,
|
||||||
) DeviceService {
|
) DeviceService {
|
||||||
return &deviceService{
|
return &deviceService{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -70,6 +72,7 @@ func NewDeviceService(
|
|||||||
areaControllerRepo: areaControllerRepo,
|
areaControllerRepo: areaControllerRepo,
|
||||||
deviceTemplateRepo: deviceTemplateRepo,
|
deviceTemplateRepo: deviceTemplateRepo,
|
||||||
deviceDomainSvc: deviceDomainSvc,
|
deviceDomainSvc: deviceDomainSvc,
|
||||||
|
thresholdAlarmService: thresholdAlarmService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +109,7 @@ func (s *deviceService) CreateDevice(ctx context.Context, req *dto.CreateDeviceR
|
|||||||
return dto.NewDeviceResponse(createdDevice)
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetDevice")
|
||||||
device, err := s.deviceRepo.FindByID(serviceCtx, id)
|
device, err := s.deviceRepo.FindByID(serviceCtx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -124,7 +127,7 @@ func (s *deviceService) ListDevices(ctx context.Context) ([]*dto.DeviceResponse,
|
|||||||
return dto.NewListDeviceResponse(devices)
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateDevice")
|
||||||
existingDevice, err := s.deviceRepo.FindByID(serviceCtx, id)
|
existingDevice, err := s.deviceRepo.FindByID(serviceCtx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -158,7 +161,7 @@ func (s *deviceService) UpdateDevice(ctx context.Context, id uint, req *dto.Upda
|
|||||||
return dto.NewDeviceResponse(updatedDevice)
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteDevice")
|
||||||
|
|
||||||
// 检查设备是否存在
|
// 检查设备是否存在
|
||||||
@@ -168,7 +171,7 @@ func (s *deviceService) DeleteDevice(ctx context.Context, id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 在删除前检查设备是否被任务使用
|
// 在删除前检查设备是否被任务使用
|
||||||
inUse, err := s.deviceRepo.IsDeviceInUse(serviceCtx, id)
|
inUse, err := s.deviceRepo.IsDeviceInUse(serviceCtx, id, []models.TaskType{models.TaskTypeDeviceThresholdCheck})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 如果检查过程中发生数据库错误,则返回错误
|
// 如果检查过程中发生数据库错误,则返回错误
|
||||||
return fmt.Errorf("检查设备使用情况失败: %w", err)
|
return fmt.Errorf("检查设备使用情况失败: %w", err)
|
||||||
@@ -178,11 +181,17 @@ func (s *deviceService) DeleteDevice(ctx context.Context, id uint) error {
|
|||||||
return ErrDeviceInUse
|
return ErrDeviceInUse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO 这个应该用事务处理
|
||||||
|
err = s.thresholdAlarmService.DeleteDeviceThresholdAlarmByDeviceID(serviceCtx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("删除设备阈值告警失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 只有在未被使用时,才执行删除操作
|
// 只有在未被使用时,才执行删除操作
|
||||||
return s.deviceRepo.Delete(serviceCtx, id)
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "ManualControl")
|
||||||
dev, err := s.deviceRepo.FindByID(serviceCtx, id)
|
dev, err := s.deviceRepo.FindByID(serviceCtx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -232,7 +241,7 @@ func (s *deviceService) CreateAreaController(ctx context.Context, req *dto.Creat
|
|||||||
return dto.NewAreaControllerResponse(ac)
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetAreaController")
|
||||||
ac, err := s.areaControllerRepo.FindByID(serviceCtx, id)
|
ac, err := s.areaControllerRepo.FindByID(serviceCtx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -250,7 +259,7 @@ func (s *deviceService) ListAreaControllers(ctx context.Context) ([]*dto.AreaCon
|
|||||||
return dto.NewListAreaControllerResponse(acs)
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateAreaController")
|
||||||
existingAC, err := s.areaControllerRepo.FindByID(serviceCtx, id)
|
existingAC, err := s.areaControllerRepo.FindByID(serviceCtx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -278,7 +287,7 @@ func (s *deviceService) UpdateAreaController(ctx context.Context, id uint, req *
|
|||||||
return dto.NewAreaControllerResponse(existingAC)
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteAreaController")
|
||||||
|
|
||||||
// 1. 检查是否存在
|
// 1. 检查是否存在
|
||||||
@@ -288,7 +297,7 @@ func (s *deviceService) DeleteAreaController(ctx context.Context, id uint) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 检查是否被使用(业务逻辑)
|
// 2. 检查是否被使用(业务逻辑)
|
||||||
inUse, err := s.deviceRepo.IsAreaControllerInUse(serviceCtx, id)
|
inUse, err := s.areaControllerRepo.IsAreaControllerUsedByTasks(serviceCtx, id, []models.TaskType{models.TaskTypeAreaCollectorThresholdCheck})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err // 返回数据库检查错误
|
return err // 返回数据库检查错误
|
||||||
}
|
}
|
||||||
@@ -296,6 +305,12 @@ func (s *deviceService) DeleteAreaController(ctx context.Context, id uint) error
|
|||||||
return ErrAreaControllerInUse // 返回业务错误
|
return ErrAreaControllerInUse // 返回业务错误
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO 这个应该用事务处理
|
||||||
|
err = s.thresholdAlarmService.DeleteAreaThresholdAlarmByAreaControllerID(serviceCtx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("删除区域阈值告警失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 执行删除
|
// 3. 执行删除
|
||||||
return s.areaControllerRepo.Delete(serviceCtx, id)
|
return s.areaControllerRepo.Delete(serviceCtx, id)
|
||||||
}
|
}
|
||||||
@@ -334,7 +349,7 @@ func (s *deviceService) CreateDeviceTemplate(ctx context.Context, req *dto.Creat
|
|||||||
return dto.NewDeviceTemplateResponse(deviceTemplate)
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetDeviceTemplate")
|
||||||
deviceTemplate, err := s.deviceTemplateRepo.FindByID(serviceCtx, id)
|
deviceTemplate, err := s.deviceTemplateRepo.FindByID(serviceCtx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -352,7 +367,7 @@ func (s *deviceService) ListDeviceTemplates(ctx context.Context) ([]*dto.DeviceT
|
|||||||
return dto.NewListDeviceTemplateResponse(deviceTemplates)
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateDeviceTemplate")
|
||||||
existingDeviceTemplate, err := s.deviceTemplateRepo.FindByID(serviceCtx, id)
|
existingDeviceTemplate, err := s.deviceTemplateRepo.FindByID(serviceCtx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -387,7 +402,7 @@ func (s *deviceService) UpdateDeviceTemplate(ctx context.Context, id uint, req *
|
|||||||
return dto.NewDeviceTemplateResponse(existingDeviceTemplate)
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteDeviceTemplate")
|
||||||
|
|
||||||
// 1. 检查是否存在
|
// 1. 检查是否存在
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ func (s *monitorService) ListPlanExecutionLogs(ctx context.Context, req *dto.Lis
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
planIds := make([]uint, 0, len(planLogs))
|
planIds := make([]uint32, 0, len(planLogs))
|
||||||
for _, datum := range planLogs {
|
for _, datum := range planLogs {
|
||||||
has := false
|
has := false
|
||||||
for _, id := range planIds {
|
for _, id := range planIds {
|
||||||
|
|||||||
@@ -12,35 +12,35 @@ import (
|
|||||||
|
|
||||||
// PigBatchService 接口定义保持不变,继续作为应用层对外的契约。
|
// PigBatchService 接口定义保持不变,继续作为应用层对外的契约。
|
||||||
type PigBatchService interface {
|
type PigBatchService interface {
|
||||||
CreatePigBatch(ctx context.Context, operatorID uint, dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error)
|
CreatePigBatch(ctx context.Context, operatorID uint32, dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error)
|
||||||
GetPigBatch(ctx context.Context, id uint) (*dto.PigBatchResponseDTO, error)
|
GetPigBatch(ctx context.Context, id uint32) (*dto.PigBatchResponseDTO, error)
|
||||||
UpdatePigBatch(ctx context.Context, id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error)
|
UpdatePigBatch(ctx context.Context, id uint32, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error)
|
||||||
DeletePigBatch(ctx context.Context, id uint) error
|
DeletePigBatch(ctx context.Context, id uint32) error
|
||||||
ListPigBatches(ctx context.Context, isActive *bool) ([]*dto.PigBatchResponseDTO, error)
|
ListPigBatches(ctx context.Context, isActive *bool) ([]*dto.PigBatchResponseDTO, error)
|
||||||
|
|
||||||
// Pig Pen Management
|
// Pig Pen Management
|
||||||
AssignEmptyPensToBatch(ctx context.Context, batchID uint, penIDs []uint, operatorID uint) error
|
AssignEmptyPensToBatch(ctx context.Context, batchID uint32, penIDs []uint32, operatorID uint32) error
|
||||||
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(ctx context.Context, batchID uint, penID uint) error
|
RemoveEmptyPenFromBatch(ctx context.Context, batchID uint32, penID uint32) error
|
||||||
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
|
||||||
|
|
||||||
// Trade Sub-service
|
// 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
|
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 uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, 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
|
||||||
|
|
||||||
// Transfer Sub-service
|
// Transfer Sub-service
|
||||||
TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint, destBatchID 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 uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error
|
TransferPigsWithinBatch(ctx context.Context, batchID uint32, fromPenID uint32, toPenID uint32, quantity uint32, operatorID uint32, remarks string) error
|
||||||
|
|
||||||
// Sick Pig Management
|
// Sick Pig Management
|
||||||
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(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(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(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
|
||||||
|
|
||||||
// Normal Pig Management
|
// Normal Pig Management
|
||||||
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(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 的实现现在依赖于领域服务接口。
|
||||||
@@ -79,7 +79,7 @@ func (s *pigBatchService) toPigBatchResponseDTO(batch *models.PigBatch, currentT
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreatePigBatch 现在将请求委托给领域服务处理。
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreatePigBatch")
|
||||||
// 1. DTO -> 领域模型
|
// 1. DTO -> 领域模型
|
||||||
batch := &models.PigBatch{
|
batch := &models.PigBatch{
|
||||||
@@ -102,7 +102,7 @@ func (s *pigBatchService) CreatePigBatch(ctx context.Context, operatorID uint, d
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPigBatch 从领域服务获取数据并转换为DTO,同时处理错误转换。
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "GetPigBatch")
|
||||||
batch, err := s.domainService.GetPigBatch(serviceCtx, id)
|
batch, err := s.domainService.GetPigBatch(serviceCtx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -123,7 +123,7 @@ func (s *pigBatchService) GetPigBatch(ctx context.Context, id uint) (*dto.PigBat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePigBatch 协调获取、更新和保存的流程,并处理错误转换。
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePigBatch")
|
||||||
// 1. 先获取最新的领域模型
|
// 1. 先获取最新的领域模型
|
||||||
existingBatch, err := s.domainService.GetPigBatch(serviceCtx, id)
|
existingBatch, err := s.domainService.GetPigBatch(serviceCtx, id)
|
||||||
@@ -176,7 +176,7 @@ func (s *pigBatchService) UpdatePigBatch(ctx context.Context, id uint, dto *dto.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeletePigBatch 将删除操作委托给领域服务,并转换领域错误为应用层错误。
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeletePigBatch")
|
||||||
err := s.domainService.DeletePigBatch(serviceCtx, id)
|
err := s.domainService.DeletePigBatch(serviceCtx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -214,7 +214,7 @@ func (s *pigBatchService) ListPigBatches(ctx context.Context, isActive *bool) ([
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AssignEmptyPensToBatch 委托给领域服务
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "AssignEmptyPensToBatch")
|
||||||
err := s.domainService.AssignEmptyPensToBatch(serviceCtx, batchID, penIDs, operatorID)
|
err := s.domainService.AssignEmptyPensToBatch(serviceCtx, batchID, penIDs, operatorID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -225,7 +225,7 @@ func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID ui
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReclassifyPenToNewBatch 委托给领域服务
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "ReclassifyPenToNewBatch")
|
||||||
err := s.domainService.ReclassifyPenToNewBatch(serviceCtx, fromBatchID, toBatchID, penID, operatorID, remarks)
|
err := s.domainService.ReclassifyPenToNewBatch(serviceCtx, fromBatchID, toBatchID, penID, operatorID, remarks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -236,7 +236,7 @@ func (s *pigBatchService) ReclassifyPenToNewBatch(ctx context.Context, fromBatch
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RemoveEmptyPenFromBatch 委托给领域服务
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "RemoveEmptyPenFromBatch")
|
||||||
err := s.domainService.RemoveEmptyPenFromBatch(serviceCtx, batchID, penID)
|
err := s.domainService.RemoveEmptyPenFromBatch(serviceCtx, batchID, penID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -247,7 +247,7 @@ func (s *pigBatchService) RemoveEmptyPenFromBatch(ctx context.Context, batchID u
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MovePigsIntoPen 委托给领域服务
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "MovePigsIntoPen")
|
||||||
err := s.domainService.MovePigsIntoPen(serviceCtx, batchID, toPenID, quantity, operatorID, remarks)
|
err := s.domainService.MovePigsIntoPen(serviceCtx, batchID, toPenID, quantity, operatorID, remarks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -258,7 +258,7 @@ func (s *pigBatchService) MovePigsIntoPen(ctx context.Context, batchID uint, toP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SellPigs 委托给领域服务
|
// 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 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "SellPigs")
|
||||||
err := s.domainService.SellPigs(serviceCtx, batchID, penID, quantity, unitPrice, tatalPrice, traderName, tradeDate, remarks, operatorID)
|
err := s.domainService.SellPigs(serviceCtx, batchID, penID, quantity, unitPrice, tatalPrice, traderName, tradeDate, remarks, operatorID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -269,7 +269,7 @@ func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BuyPigs 委托给领域服务
|
// 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 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "BuyPigs")
|
||||||
err := s.domainService.BuyPigs(serviceCtx, batchID, penID, quantity, unitPrice, tatalPrice, traderName, tradeDate, remarks, operatorID)
|
err := s.domainService.BuyPigs(serviceCtx, batchID, penID, quantity, unitPrice, tatalPrice, traderName, tradeDate, remarks, operatorID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -280,7 +280,7 @@ func (s *pigBatchService) BuyPigs(ctx context.Context, batchID uint, penID uint,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TransferPigsAcrossBatches 委托给领域服务
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "TransferPigsAcrossBatches")
|
||||||
err := s.domainService.TransferPigsAcrossBatches(serviceCtx, sourceBatchID, destBatchID, fromPenID, toPenID, quantity, operatorID, remarks)
|
err := s.domainService.TransferPigsAcrossBatches(serviceCtx, sourceBatchID, destBatchID, fromPenID, toPenID, quantity, operatorID, remarks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -291,7 +291,7 @@ func (s *pigBatchService) TransferPigsAcrossBatches(ctx context.Context, sourceB
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TransferPigsWithinBatch 委托给领域服务
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "TransferPigsWithinBatch")
|
||||||
err := s.domainService.TransferPigsWithinBatch(serviceCtx, batchID, fromPenID, toPenID, quantity, operatorID, remarks)
|
err := s.domainService.TransferPigsWithinBatch(serviceCtx, batchID, fromPenID, toPenID, quantity, operatorID, remarks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -302,7 +302,7 @@ func (s *pigBatchService) TransferPigsWithinBatch(ctx context.Context, batchID u
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordSickPigs 委托给领域服务
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordSickPigs")
|
||||||
err := s.domainService.RecordSickPigs(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks)
|
err := s.domainService.RecordSickPigs(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -313,7 +313,7 @@ func (s *pigBatchService) RecordSickPigs(ctx context.Context, operatorID uint, b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordSickPigRecovery 委托给领域服务
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordSickPigRecovery")
|
||||||
err := s.domainService.RecordSickPigRecovery(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks)
|
err := s.domainService.RecordSickPigRecovery(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -324,7 +324,7 @@ func (s *pigBatchService) RecordSickPigRecovery(ctx context.Context, operatorID
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordSickPigDeath 委托给领域服务
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordSickPigDeath")
|
||||||
err := s.domainService.RecordSickPigDeath(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks)
|
err := s.domainService.RecordSickPigDeath(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -335,7 +335,7 @@ func (s *pigBatchService) RecordSickPigDeath(ctx context.Context, operatorID uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordSickPigCull 委托给领域服务
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordSickPigCull")
|
||||||
err := s.domainService.RecordSickPigCull(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks)
|
err := s.domainService.RecordSickPigCull(serviceCtx, operatorID, batchID, penID, quantity, treatmentLocation, happenedAt, remarks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -346,7 +346,7 @@ func (s *pigBatchService) RecordSickPigCull(ctx context.Context, operatorID uint
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordDeath 委托给领域服务
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordDeath")
|
||||||
err := s.domainService.RecordDeath(serviceCtx, operatorID, batchID, penID, quantity, happenedAt, remarks)
|
err := s.domainService.RecordDeath(serviceCtx, operatorID, batchID, penID, quantity, happenedAt, remarks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -357,7 +357,7 @@ func (s *pigBatchService) RecordDeath(ctx context.Context, operatorID uint, batc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordCull 委托给领域服务
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "RecordCull")
|
||||||
err := s.domainService.RecordCull(serviceCtx, operatorID, batchID, penID, quantity, happenedAt, remarks)
|
err := s.domainService.RecordCull(serviceCtx, operatorID, batchID, penID, quantity, happenedAt, remarks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -18,19 +18,19 @@ import (
|
|||||||
type PigFarmService interface {
|
type PigFarmService interface {
|
||||||
// PigHouse methods
|
// PigHouse methods
|
||||||
CreatePigHouse(ctx context.Context, name, description string) (*dto.PigHouseResponse, error)
|
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)
|
ListPigHouses(ctx context.Context) ([]dto.PigHouseResponse, error)
|
||||||
UpdatePigHouse(ctx context.Context, id uint, name, description string) (*dto.PigHouseResponse, error)
|
UpdatePigHouse(ctx context.Context, id uint32, name, description string) (*dto.PigHouseResponse, error)
|
||||||
DeletePigHouse(ctx context.Context, id uint) error
|
DeletePigHouse(ctx context.Context, id uint32) error
|
||||||
|
|
||||||
// Pen methods
|
// Pen methods
|
||||||
CreatePen(ctx context.Context, penNumber string, houseID uint, capacity int) (*dto.PenResponse, error)
|
CreatePen(ctx context.Context, penNumber string, houseID uint32, capacity int) (*dto.PenResponse, error)
|
||||||
GetPenByID(ctx context.Context, id uint) (*dto.PenResponse, error)
|
GetPenByID(ctx context.Context, id uint32) (*dto.PenResponse, error)
|
||||||
ListPens(ctx context.Context) ([]*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)
|
UpdatePen(ctx context.Context, id uint32, penNumber string, houseID uint32, capacity int, status models.PenStatus) (*dto.PenResponse, error)
|
||||||
DeletePen(ctx context.Context, id uint) error
|
DeletePen(ctx context.Context, id uint32) error
|
||||||
// UpdatePenStatus 更新猪栏状态
|
// 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 {
|
type pigFarmService struct {
|
||||||
@@ -79,7 +79,7 @@ func (s *pigFarmService) CreatePigHouse(ctx context.Context, name, description s
|
|||||||
}, nil
|
}, 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigHouseByID")
|
||||||
house, err := s.farmRepository.GetPigHouseByID(serviceCtx, id)
|
house, err := s.farmRepository.GetPigHouseByID(serviceCtx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -109,10 +109,10 @@ func (s *pigFarmService) ListPigHouses(ctx context.Context) ([]dto.PigHouseRespo
|
|||||||
return resp, nil
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigHouse")
|
||||||
house := &models.PigHouse{
|
house := &models.PigHouse{
|
||||||
Model: gorm.Model{ID: id},
|
Model: models.Model{ID: id},
|
||||||
Name: name,
|
Name: name,
|
||||||
Description: description,
|
Description: description,
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ func (s *pigFarmService) UpdatePigHouse(ctx context.Context, id uint, name, desc
|
|||||||
}, nil
|
}, 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigHouse")
|
||||||
// 业务逻辑:检查猪舍是否包含猪栏
|
// 业务逻辑:检查猪舍是否包含猪栏
|
||||||
penCount, err := s.farmRepository.CountPensInHouse(serviceCtx, id)
|
penCount, err := s.farmRepository.CountPensInHouse(serviceCtx, id)
|
||||||
@@ -159,7 +159,7 @@ func (s *pigFarmService) DeletePigHouse(ctx context.Context, id uint) error {
|
|||||||
|
|
||||||
// --- Pen Implementation ---
|
// --- 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePen")
|
||||||
// 业务逻辑:验证所属猪舍是否存在
|
// 业务逻辑:验证所属猪舍是否存在
|
||||||
_, err := s.farmRepository.GetPigHouseByID(serviceCtx, houseID)
|
_, err := s.farmRepository.GetPigHouseByID(serviceCtx, houseID)
|
||||||
@@ -189,7 +189,7 @@ func (s *pigFarmService) CreatePen(ctx context.Context, penNumber string, houseI
|
|||||||
}, nil
|
}, 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "GetPenByID")
|
||||||
pen, err := s.penRepository.GetPenByID(serviceCtx, id)
|
pen, err := s.penRepository.GetPenByID(serviceCtx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -251,7 +251,7 @@ func (s *pigFarmService) ListPens(ctx context.Context) ([]*dto.PenResponse, erro
|
|||||||
return response, nil
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePen")
|
||||||
// 业务逻辑:验证所属猪舍是否存在
|
// 业务逻辑:验证所属猪舍是否存在
|
||||||
_, err := s.farmRepository.GetPigHouseByID(serviceCtx, houseID)
|
_, err := s.farmRepository.GetPigHouseByID(serviceCtx, houseID)
|
||||||
@@ -263,7 +263,7 @@ func (s *pigFarmService) UpdatePen(ctx context.Context, id uint, penNumber strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
pen := &models.Pen{
|
pen := &models.Pen{
|
||||||
Model: gorm.Model{ID: id},
|
Model: models.Model{ID: id},
|
||||||
PenNumber: penNumber,
|
PenNumber: penNumber,
|
||||||
HouseID: houseID,
|
HouseID: houseID,
|
||||||
Capacity: capacity,
|
Capacity: capacity,
|
||||||
@@ -291,7 +291,7 @@ func (s *pigFarmService) UpdatePen(ctx context.Context, id uint, penNumber strin
|
|||||||
}, nil
|
}, 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePen")
|
||||||
// 业务逻辑:检查猪栏是否被活跃批次使用
|
// 业务逻辑:检查猪栏是否被活跃批次使用
|
||||||
pen, err := s.penRepository.GetPenByID(serviceCtx, id)
|
pen, err := s.penRepository.GetPenByID(serviceCtx, id)
|
||||||
@@ -327,7 +327,7 @@ func (s *pigFarmService) DeletePen(ctx context.Context, id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePenStatus 更新猪栏状态
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePenStatus")
|
||||||
var updatedPen *models.Pen
|
var updatedPen *models.Pen
|
||||||
err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
"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/domain/plan"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"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"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,17 +16,17 @@ type PlanService interface {
|
|||||||
// CreatePlan 创建一个新的计划
|
// CreatePlan 创建一个新的计划
|
||||||
CreatePlan(ctx context.Context, req *dto.CreatePlanRequest) (*dto.PlanResponse, error)
|
CreatePlan(ctx context.Context, req *dto.CreatePlanRequest) (*dto.PlanResponse, error)
|
||||||
// GetPlanByID 根据ID获取计划详情
|
// GetPlanByID 根据ID获取计划详情
|
||||||
GetPlanByID(ctx context.Context, id uint) (*dto.PlanResponse, error)
|
GetPlanByID(ctx context.Context, id uint32) (*dto.PlanResponse, error)
|
||||||
// ListPlans 获取计划列表,支持过滤和分页
|
// ListPlans 获取计划列表,支持过滤和分页
|
||||||
ListPlans(ctx context.Context, query *dto.ListPlansQuery) (*dto.ListPlansResponse, error)
|
ListPlans(ctx context.Context, query *dto.ListPlansQuery) (*dto.ListPlansResponse, error)
|
||||||
// UpdatePlan 更新计划
|
// 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 删除计划(软删除)
|
||||||
DeletePlan(ctx context.Context, id uint) error
|
DeletePlan(ctx context.Context, id uint32) error
|
||||||
// StartPlan 启动计划
|
// StartPlan 启动计划
|
||||||
StartPlan(ctx context.Context, id uint) error
|
StartPlan(ctx context.Context, id uint32) error
|
||||||
// StopPlan 停止计划
|
// StopPlan 停止计划
|
||||||
StopPlan(ctx context.Context, id uint) error
|
StopPlan(ctx context.Context, id uint32) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// planService 是 PlanService 接口的实现
|
// planService 是 PlanService 接口的实现
|
||||||
@@ -76,7 +77,7 @@ func (s *planService) CreatePlan(ctx context.Context, req *dto.CreatePlanRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPlanByID 根据ID获取计划详情
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "GetPlanByID")
|
||||||
const actionType = "应用服务层:获取计划详情"
|
const actionType = "应用服务层:获取计划详情"
|
||||||
|
|
||||||
@@ -134,7 +135,7 @@ func (s *planService) ListPlans(ctx context.Context, query *dto.ListPlansQuery)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePlan 更新计划
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePlan")
|
||||||
const actionType = "应用服务层:更新计划"
|
const actionType = "应用服务层:更新计划"
|
||||||
|
|
||||||
@@ -147,7 +148,7 @@ func (s *planService) UpdatePlan(ctx context.Context, id uint, req *dto.UpdatePl
|
|||||||
planToUpdate.ID = id // 确保ID被设置
|
planToUpdate.ID = id // 确保ID被设置
|
||||||
|
|
||||||
// 调用领域服务更新计划
|
// 调用领域服务更新计划
|
||||||
updatedPlan, err := s.domainPlanService.UpdatePlan(serviceCtx, planToUpdate)
|
updatedPlan, err := s.domainPlanService.UpdatePlan(serviceCtx, planToUpdate, models.PlanTypeCustom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("%s: 领域服务更新计划失败: %v, ID: %d", actionType, err, id)
|
logger.Errorf("%s: 领域服务更新计划失败: %v, ID: %d", actionType, err, id)
|
||||||
return nil, err // 直接返回领域层错误
|
return nil, err // 直接返回领域层错误
|
||||||
@@ -165,7 +166,7 @@ func (s *planService) UpdatePlan(ctx context.Context, id uint, req *dto.UpdatePl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeletePlan 删除计划(软删除)
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "DeletePlan")
|
||||||
const actionType = "应用服务层:删除计划"
|
const actionType = "应用服务层:删除计划"
|
||||||
|
|
||||||
@@ -181,7 +182,7 @@ func (s *planService) DeletePlan(ctx context.Context, id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartPlan 启动计划
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "StartPlan")
|
||||||
const actionType = "应用服务层:启动计划"
|
const actionType = "应用服务层:启动计划"
|
||||||
|
|
||||||
@@ -197,7 +198,7 @@ func (s *planService) StartPlan(ctx context.Context, id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StopPlan 停止计划
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "StopPlan")
|
||||||
const actionType = "应用服务层:停止计划"
|
const actionType = "应用服务层:停止计划"
|
||||||
|
|
||||||
|
|||||||
688
internal/app/service/threshold_alarm_service.go
Normal file
688
internal/app/service/threshold_alarm_service.go
Normal file
@@ -0,0 +1,688 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ThresholdAlarmService 定义了阈值告警配置服务的接口。
|
||||||
|
// 该服务负责管理阈值告警任务的配置,并将其与计划进行联动。
|
||||||
|
type ThresholdAlarmService interface {
|
||||||
|
// SnoozeThresholdAlarm 忽略一个阈值告警,或更新其忽略时间。
|
||||||
|
SnoozeThresholdAlarm(ctx context.Context, alarmID uint32, durationMinutes uint32) error
|
||||||
|
// CancelSnoozeThresholdAlarm 取消对一个阈值告警的忽略状态。
|
||||||
|
CancelSnoozeThresholdAlarm(ctx context.Context, alarmID uint32) error
|
||||||
|
// ListActiveAlarms 批量查询活跃告警。
|
||||||
|
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)
|
||||||
|
// DeleteDeviceThresholdAlarm 删除一个设备阈值告警。
|
||||||
|
DeleteDeviceThresholdAlarm(ctx context.Context, taskID int, req *dto.DeleteDeviceThresholdAlarmDTO) error
|
||||||
|
// DeleteDeviceThresholdAlarmByDeviceID 实现了根据设备ID删除一个设备下所有设备阈值告警的逻辑。
|
||||||
|
DeleteDeviceThresholdAlarmByDeviceID(ctx context.Context, deviceID uint32) 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
|
||||||
|
// DeleteAreaThresholdAlarmByAreaControllerID 实现了根据区域ID删除一个区域下所有区域阈值告警的逻辑。
|
||||||
|
DeleteAreaThresholdAlarmByAreaControllerID(ctx context.Context, areaControllerID uint32) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// thresholdAlarmService 是 ThresholdAlarmService 接口的具体实现。
|
||||||
|
type thresholdAlarmService struct {
|
||||||
|
ctx context.Context
|
||||||
|
alarmService domainAlarm.AlarmService
|
||||||
|
planService plan.Service
|
||||||
|
|
||||||
|
alarmRepo repository.AlarmRepository
|
||||||
|
planRepo repository.PlanRepository
|
||||||
|
areaRepo repository.AreaControllerRepository
|
||||||
|
deviceRepo repository.DeviceRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewThresholdAlarmService 创建一个新的 ThresholdAlarmService 实例。
|
||||||
|
func NewThresholdAlarmService(ctx context.Context,
|
||||||
|
alarmService domainAlarm.AlarmService,
|
||||||
|
planService plan.Service,
|
||||||
|
alarmRepo repository.AlarmRepository,
|
||||||
|
planRepo repository.PlanRepository,
|
||||||
|
areaRepo repository.AreaControllerRepository,
|
||||||
|
deviceRepo repository.DeviceRepository,
|
||||||
|
) ThresholdAlarmService {
|
||||||
|
return &thresholdAlarmService{
|
||||||
|
ctx: ctx,
|
||||||
|
alarmService: alarmService,
|
||||||
|
planService: planService,
|
||||||
|
alarmRepo: alarmRepo,
|
||||||
|
planRepo: planRepo,
|
||||||
|
areaRepo: areaRepo,
|
||||||
|
deviceRepo: deviceRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnoozeThresholdAlarm 实现了忽略阈值告警的逻辑。
|
||||||
|
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 uint32) error {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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: []uint32{},
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDeviceThresholdAlarmByDeviceID 实现了根据设备ID删除一个设备下所有设备阈值告警的逻辑。
|
||||||
|
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 {
|
||||||
|
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")
|
||||||
|
|
||||||
|
// 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[uint32]struct{}, len(devicesInArea))
|
||||||
|
for _, device := range devicesInArea {
|
||||||
|
devicesInAreaMap[device.ID] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 遍历计划,检查存在性并收集需要排除的设备ID
|
||||||
|
var excludeDeviceIDs []uint32
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAreaThresholdAlarmByAreaControllerID 实现了根据区域ID删除一个区域下所有区域阈值告警的逻辑。
|
||||||
|
func (s *thresholdAlarmService) DeleteAreaThresholdAlarmByAreaControllerID(ctx context.Context, areaControllerID uint32) 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
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
type UserService interface {
|
type UserService interface {
|
||||||
CreateUser(ctx context.Context, req *dto.CreateUserRequest) (*dto.CreateUserResponse, error)
|
CreateUser(ctx context.Context, req *dto.CreateUserRequest) (*dto.CreateUserResponse, error)
|
||||||
Login(ctx context.Context, req *dto.LoginRequest) (*dto.LoginResponse, 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 接口
|
// userService 实现了 UserService 接口
|
||||||
@@ -103,7 +103,7 @@ func (s *userService) Login(ctx context.Context, req *dto.LoginRequest) (*dto.Lo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendTestNotification 发送测试通知
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendTestNotification")
|
||||||
err := s.notifyService.SendTestMessage(serviceCtx, userID, req.Type)
|
err := s.notifyService.SendTestMessage(serviceCtx, userID, req.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -162,17 +162,17 @@ func (c *ChirpStackListener) handleUpEvent(ctx context.Context, event *UpEvent)
|
|||||||
logger.Infof("开始处理 'up' 事件, DevEui: %s", event.DeviceInfo.DevEui)
|
logger.Infof("开始处理 'up' 事件, DevEui: %s", event.DeviceInfo.DevEui)
|
||||||
|
|
||||||
// 1. 查找区域主控设备
|
// 1. 查找区域主控设备
|
||||||
regionalController, err := c.areaControllerRepo.FindByNetworkID(reqCtx, event.DeviceInfo.DevEui)
|
areaController, err := c.areaControllerRepo.FindByNetworkID(reqCtx, event.DeviceInfo.DevEui)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("处理 'up' 事件失败:无法通过 DevEui '%s' 找到区域主控设备: %v", event.DeviceInfo.DevEui, err)
|
logger.Errorf("处理 'up' 事件失败:无法通过 DevEui '%s' 找到区域主控设备: %v", event.DeviceInfo.DevEui, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 依赖 SelfCheck 确保区域主控有效
|
// 依赖 SelfCheck 确保区域主控有效
|
||||||
if err := regionalController.SelfCheck(); err != nil {
|
if err := areaController.SelfCheck(); err != nil {
|
||||||
logger.Errorf("处理 'up' 事件失败:区域主控 %v(ID: %d) 未通过自检: %v", regionalController.Name, regionalController.ID, err)
|
logger.Errorf("处理 'up' 事件失败:区域主控 %v(ID: %d) 未通过自检: %v", areaController.Name, areaController.ID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Infof("找到区域主控: %s (ID: %d)", regionalController.Name, regionalController.ID)
|
logger.Infof("找到区域主控: %s (ID: %d)", areaController.Name, areaController.ID)
|
||||||
|
|
||||||
// 2. 记录区域主控的信号强度 (如果存在)
|
// 2. 记录区域主控的信号强度 (如果存在)
|
||||||
if len(event.RxInfo) > 0 {
|
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)
|
c.recordSensorData(reqCtx, areaController.ID, areaController.ID, event.Time, models.SensorTypeSignalMetrics, signalMetrics)
|
||||||
logger.Infof("已记录区域主控 (ID: %d) 的信号强度: RSSI=%d, SNR=%.2f", regionalController.ID, rx.Rssi, rx.Snr)
|
logger.Infof("已记录区域主控 (ID: %d) 的信号强度: RSSI=%d, SNR=%.2f", areaController.ID, rx.Rssi, rx.Snr)
|
||||||
} else {
|
} else {
|
||||||
logger.Warnf("处理 'up' 事件时未找到 RxInfo,无法记录信号数据。DevEui: %s", event.DeviceInfo.DevEui)
|
logger.Warnf("处理 'up' 事件时未找到 RxInfo,无法记录信号数据。DevEui: %s", event.DeviceInfo.DevEui)
|
||||||
}
|
}
|
||||||
@@ -298,7 +298,7 @@ func (c *ChirpStackListener) handleUpEvent(ctx context.Context, event *UpEvent)
|
|||||||
valueDescriptor := valueDescriptors[0]
|
valueDescriptor := valueDescriptors[0]
|
||||||
|
|
||||||
// 5.3 应用乘数和偏移量计算最终值
|
// 5.3 应用乘数和偏移量计算最终值
|
||||||
parsedValue := float64(rawSensorValue)*valueDescriptor.Multiplier + valueDescriptor.Offset
|
parsedValue := rawSensorValue*valueDescriptor.Multiplier + valueDescriptor.Offset
|
||||||
|
|
||||||
// 5.4 根据传感器类型构建具体的数据结构
|
// 5.4 根据传感器类型构建具体的数据结构
|
||||||
var dataToRecord interface{}
|
var dataToRecord interface{}
|
||||||
@@ -312,11 +312,11 @@ func (c *ChirpStackListener) handleUpEvent(ctx context.Context, event *UpEvent)
|
|||||||
default:
|
default:
|
||||||
// TODO 未知传感器的数据需要记录吗
|
// TODO 未知传感器的数据需要记录吗
|
||||||
logger.Warnf("未知的传感器类型 '%s',将使用通用格式记录", valueDescriptor.Type)
|
logger.Warnf("未知的传感器类型 '%s',将使用通用格式记录", valueDescriptor.Type)
|
||||||
dataToRecord = map[string]float64{"value": parsedValue}
|
dataToRecord = map[string]float32{"value": parsedValue}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5.5 记录传感器数据
|
// 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)
|
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)
|
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 {
|
if err != nil {
|
||||||
logger.Errorf("处理 'status' 事件失败:无法通过 DevEui '%s' 找到区域主控设备: %v", event.DeviceInfo.DevEui, err)
|
logger.Errorf("处理 'status' 事件失败:无法通过 DevEui '%s' 找到区域主控设备: %v", event.DeviceInfo.DevEui, err)
|
||||||
return
|
return
|
||||||
@@ -344,8 +344,8 @@ func (c *ChirpStackListener) handleStatusEvent(ctx context.Context, event *Statu
|
|||||||
signalMetrics := models.SignalMetrics{
|
signalMetrics := models.SignalMetrics{
|
||||||
MarginDb: event.Margin,
|
MarginDb: event.Margin,
|
||||||
}
|
}
|
||||||
c.recordSensorData(reqCtx, regionalController.ID, regionalController.ID, event.Time, models.SensorTypeSignalMetrics, signalMetrics)
|
c.recordSensorData(reqCtx, areaController.ID, areaController.ID, event.Time, models.SensorTypeSignalMetrics, signalMetrics)
|
||||||
logger.Infof("已记录区域主控 (ID: %d) 的信号状态: %+v", regionalController.ID, signalMetrics)
|
logger.Infof("已记录区域主控 (ID: %d) 的信号状态: %+v", areaController.ID, signalMetrics)
|
||||||
|
|
||||||
// 记录电量
|
// 记录电量
|
||||||
batteryLevel := models.BatteryLevel{
|
batteryLevel := models.BatteryLevel{
|
||||||
@@ -353,8 +353,8 @@ func (c *ChirpStackListener) handleStatusEvent(ctx context.Context, event *Statu
|
|||||||
BatteryLevelUnavailable: event.BatteryLevelUnavailable,
|
BatteryLevelUnavailable: event.BatteryLevelUnavailable,
|
||||||
ExternalPower: event.ExternalPower,
|
ExternalPower: event.ExternalPower,
|
||||||
}
|
}
|
||||||
c.recordSensorData(reqCtx, regionalController.ID, regionalController.ID, event.Time, models.SensorTypeBatteryLevel, batteryLevel)
|
c.recordSensorData(reqCtx, areaController.ID, areaController.ID, event.Time, models.SensorTypeBatteryLevel, batteryLevel)
|
||||||
logger.Infof("已记录区域主控 (ID: %d) 的电池状态: %+v", regionalController.ID, batteryLevel)
|
logger.Infof("已记录区域主控 (ID: %d) 的电池状态: %+v", areaController.ID, batteryLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleAckEvent 处理下行确认事件
|
// handleAckEvent 处理下行确认事件
|
||||||
@@ -425,11 +425,11 @@ func (c *ChirpStackListener) handleIntegrationEvent(ctx context.Context, event *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// recordSensorData 是一个通用方法,用于将传感器数据存入数据库。
|
// recordSensorData 是一个通用方法,用于将传感器数据存入数据库。
|
||||||
// regionalControllerID: 区域主控设备的ID
|
// areaControllerID: 区域主控设备的ID
|
||||||
// sensorDeviceID: 实际产生传感器数据的普通设备的ID
|
// sensorDeviceID: 实际产生传感器数据的普通设备的ID
|
||||||
// sensorType: 传感器值的类型 (例如 models.SensorTypeTemperature)
|
// sensorType: 传感器值的类型 (例如 models.SensorTypeTemperature)
|
||||||
// data: 具体的传感器数据结构体实例 (例如 models.TemperatureData)
|
// 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 uint32, sensorDeviceID uint32, eventTime time.Time, sensorType models.SensorType, data interface{}) {
|
||||||
reqCtx, logger := logs.Trace(ctx, c.ctx, "recordSensorData")
|
reqCtx, logger := logs.Trace(ctx, c.ctx, "recordSensorData")
|
||||||
// 1. 将传入的结构体序列化为 JSON
|
// 1. 将传入的结构体序列化为 JSON
|
||||||
jsonData, err := json.Marshal(data)
|
jsonData, err := json.Marshal(data)
|
||||||
@@ -442,7 +442,7 @@ func (c *ChirpStackListener) recordSensorData(ctx context.Context, regionalContr
|
|||||||
sensorData := &models.SensorData{
|
sensorData := &models.SensorData{
|
||||||
Time: eventTime,
|
Time: eventTime,
|
||||||
DeviceID: sensorDeviceID,
|
DeviceID: sensorDeviceID,
|
||||||
RegionalControllerID: regionalControllerID,
|
AreaControllerID: areaControllerID,
|
||||||
SensorType: sensorType,
|
SensorType: sensorType,
|
||||||
Data: datatypes.JSON(jsonData),
|
Data: datatypes.JSON(jsonData),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ type DeviceInfo struct {
|
|||||||
|
|
||||||
// Location 包含了地理位置信息。
|
// Location 包含了地理位置信息。
|
||||||
type Location struct {
|
type Location struct {
|
||||||
Latitude float64 `json:"latitude"` // 纬度
|
Latitude float32 `json:"latitude"` // 纬度
|
||||||
Longitude float64 `json:"longitude"` // 经度
|
Longitude float32 `json:"longitude"` // 经度
|
||||||
Altitude float64 `json:"altitude"` // 海拔
|
Altitude float32 `json:"altitude"` // 海拔
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 可复用的子结构体 ---
|
// --- 可复用的子结构体 ---
|
||||||
@@ -61,7 +61,7 @@ type UplinkRxInfo struct {
|
|||||||
UplinkID uint32 `json:"uplink_id"` // 上行ID
|
UplinkID uint32 `json:"uplink_id"` // 上行ID
|
||||||
Time time.Time `json:"time"` // 接收时间
|
Time time.Time `json:"time"` // 接收时间
|
||||||
Rssi int `json:"rssi"` // 接收信号强度指示
|
Rssi int `json:"rssi"` // 接收信号强度指示
|
||||||
Snr float64 `json:"snr"` // 信噪比
|
Snr float32 `json:"snr"` // 信噪比
|
||||||
Channel int `json:"channel"` // 接收通道
|
Channel int `json:"channel"` // 接收通道
|
||||||
Location *Location `json:"location"` // 网关位置
|
Location *Location `json:"location"` // 网关位置
|
||||||
Context string `json:"context"` // 上下文信息
|
Context string `json:"context"` // 上下文信息
|
||||||
@@ -96,9 +96,9 @@ type DownlinkTxInfo struct {
|
|||||||
|
|
||||||
// ResolvedLocation 包含了地理位置解析结果。
|
// ResolvedLocation 包含了地理位置解析结果。
|
||||||
type ResolvedLocation struct {
|
type ResolvedLocation struct {
|
||||||
Latitude float64 `json:"latitude"` // 纬度
|
Latitude float32 `json:"latitude"` // 纬度
|
||||||
Longitude float64 `json:"longitude"` // 经度
|
Longitude float32 `json:"longitude"` // 经度
|
||||||
Altitude float64 `json:"altitude"` // 海拔
|
Altitude float32 `json:"altitude"` // 海拔
|
||||||
Source string `json:"source"` // 位置来源
|
Source string `json:"source"` // 位置来源
|
||||||
Accuracy int `json:"accuracy"` // 精度
|
Accuracy int `json:"accuracy"` // 精度
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ func NewApplication(configPath string) (*Application, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("初始化基础设施失败: %w", err)
|
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)
|
appServices := initAppServices(ctx, infra, domain)
|
||||||
|
|
||||||
// 3. 初始化 API 入口点
|
// 3. 初始化 API 入口点
|
||||||
@@ -58,6 +61,7 @@ func NewApplication(configPath string) (*Application, error) {
|
|||||||
appServices.planService,
|
appServices.planService,
|
||||||
appServices.userService,
|
appServices.userService,
|
||||||
appServices.auditService,
|
appServices.auditService,
|
||||||
|
appServices.thresholdAlarmService,
|
||||||
infra.tokenGenerator,
|
infra.tokenGenerator,
|
||||||
infra.lora.listenHandler,
|
infra.lora.listenHandler,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/service"
|
"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/app/webhook"
|
||||||
|
"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/device"
|
||||||
domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/pig"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/pig"
|
||||||
@@ -21,6 +22,7 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport"
|
"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/transport/lora"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/token"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/token"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,7 +31,6 @@ type Infrastructure struct {
|
|||||||
storage database.Storage
|
storage database.Storage
|
||||||
repos *Repositories
|
repos *Repositories
|
||||||
lora *LoraComponents
|
lora *LoraComponents
|
||||||
notifyService domain_notify.Service
|
|
||||||
tokenGenerator token.Generator
|
tokenGenerator token.Generator
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,18 +48,12 @@ func initInfrastructure(ctx context.Context, cfg *config.Config) (*Infrastructur
|
|||||||
return nil, err
|
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))
|
tokenGenerator := token.NewTokenGenerator([]byte(cfg.App.JWTSecret))
|
||||||
|
|
||||||
return &Infrastructure{
|
return &Infrastructure{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
repos: repos,
|
repos: repos,
|
||||||
lora: lora,
|
lora: lora,
|
||||||
notifyService: notifyService,
|
|
||||||
tokenGenerator: tokenGenerator,
|
tokenGenerator: tokenGenerator,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -86,6 +81,7 @@ type Repositories struct {
|
|||||||
medicationLogRepo repository.MedicationLogRepository
|
medicationLogRepo repository.MedicationLogRepository
|
||||||
rawMaterialRepo repository.RawMaterialRepository
|
rawMaterialRepo repository.RawMaterialRepository
|
||||||
notificationRepo repository.NotificationRepository
|
notificationRepo repository.NotificationRepository
|
||||||
|
alarmRepo repository.AlarmRepository
|
||||||
unitOfWork repository.UnitOfWork
|
unitOfWork repository.UnitOfWork
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +110,7 @@ func initRepositories(ctx context.Context, db *gorm.DB) *Repositories {
|
|||||||
medicationLogRepo: repository.NewGormMedicationLogRepository(logs.AddCompName(baseCtx, "MedicationLogRepo"), db),
|
medicationLogRepo: repository.NewGormMedicationLogRepository(logs.AddCompName(baseCtx, "MedicationLogRepo"), db),
|
||||||
rawMaterialRepo: repository.NewGormRawMaterialRepository(logs.AddCompName(baseCtx, "RawMaterialRepo"), db),
|
rawMaterialRepo: repository.NewGormRawMaterialRepository(logs.AddCompName(baseCtx, "RawMaterialRepo"), db),
|
||||||
notificationRepo: repository.NewGormNotificationRepository(logs.AddCompName(baseCtx, "NotificationRepo"), 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),
|
unitOfWork: repository.NewGormUnitOfWork(logs.AddCompName(baseCtx, "UnitOfWork"), db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,12 +126,19 @@ type DomainServices struct {
|
|||||||
planExecutionManager plan.ExecutionManager
|
planExecutionManager plan.ExecutionManager
|
||||||
analysisPlanTaskManager plan.AnalysisPlanTaskManager
|
analysisPlanTaskManager plan.AnalysisPlanTaskManager
|
||||||
planService plan.Service
|
planService plan.Service
|
||||||
|
notifyService domain_notify.Service
|
||||||
|
alarmService alarm.AlarmService
|
||||||
}
|
}
|
||||||
|
|
||||||
// initDomainServices 初始化所有的领域服务。
|
// 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()
|
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)
|
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)
|
pigTradeManager := pig.NewPigTradeManager(logs.AddCompName(baseCtx, "PigTradeManager"), infra.repos.pigTradeRepo)
|
||||||
@@ -157,8 +161,22 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr
|
|||||||
infra.lora.comm,
|
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, generalDeviceService)
|
taskFactory := task.NewTaskFactory(logs.AddCompName(baseCtx, "TaskFactory"),
|
||||||
|
infra.repos.sensorDataRepo,
|
||||||
|
infra.repos.deviceRepo,
|
||||||
|
infra.repos.alarmRepo,
|
||||||
|
generalDeviceService,
|
||||||
|
notifyService,
|
||||||
|
alarmService,
|
||||||
|
)
|
||||||
|
|
||||||
// 计划任务管理器
|
// 计划任务管理器
|
||||||
analysisPlanTaskManager := plan.NewAnalysisPlanTaskManager(logs.AddCompName(baseCtx, "AnalysisPlanTaskManager"), infra.repos.planRepo, infra.repos.pendingTaskRepo, infra.repos.executionLogRepo)
|
analysisPlanTaskManager := plan.NewAnalysisPlanTaskManager(logs.AddCompName(baseCtx, "AnalysisPlanTaskManager"), infra.repos.planRepo, infra.repos.pendingTaskRepo, infra.repos.executionLogRepo)
|
||||||
@@ -199,7 +217,9 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr
|
|||||||
taskFactory: taskFactory,
|
taskFactory: taskFactory,
|
||||||
planExecutionManager: planExecutionManager,
|
planExecutionManager: planExecutionManager,
|
||||||
planService: planService,
|
planService: planService,
|
||||||
}
|
notifyService: notifyService,
|
||||||
|
alarmService: alarmService,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppServices 聚合了所有的应用服务实例。
|
// AppServices 聚合了所有的应用服务实例。
|
||||||
@@ -211,6 +231,7 @@ type AppServices struct {
|
|||||||
planService service.PlanService
|
planService service.PlanService
|
||||||
userService service.UserService
|
userService service.UserService
|
||||||
auditService service.AuditService
|
auditService service.AuditService
|
||||||
|
thresholdAlarmService service.ThresholdAlarmService
|
||||||
}
|
}
|
||||||
|
|
||||||
// initAppServices 初始化所有的应用服务。
|
// initAppServices 初始化所有的应用服务。
|
||||||
@@ -235,16 +256,30 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices
|
|||||||
infra.repos.pigTradeRepo,
|
infra.repos.pigTradeRepo,
|
||||||
infra.repos.notificationRepo,
|
infra.repos.notificationRepo,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 初始化阈值告警服务
|
||||||
|
thresholdAlarmService := service.NewThresholdAlarmService(
|
||||||
|
logs.AddCompName(baseCtx, "ThresholdAlarmService"),
|
||||||
|
domainServices.alarmService,
|
||||||
|
domainServices.planService,
|
||||||
|
infra.repos.alarmRepo,
|
||||||
|
infra.repos.planRepo,
|
||||||
|
infra.repos.areaControllerRepo,
|
||||||
|
infra.repos.deviceRepo,
|
||||||
|
)
|
||||||
|
|
||||||
deviceService := service.NewDeviceService(
|
deviceService := service.NewDeviceService(
|
||||||
logs.AddCompName(baseCtx, "DeviceService"),
|
logs.AddCompName(baseCtx, "DeviceService"),
|
||||||
infra.repos.deviceRepo,
|
infra.repos.deviceRepo,
|
||||||
infra.repos.areaControllerRepo,
|
infra.repos.areaControllerRepo,
|
||||||
infra.repos.deviceTemplateRepo,
|
infra.repos.deviceTemplateRepo,
|
||||||
domainServices.generalDeviceService,
|
domainServices.generalDeviceService,
|
||||||
|
thresholdAlarmService,
|
||||||
)
|
)
|
||||||
|
|
||||||
auditService := service.NewAuditService(logs.AddCompName(baseCtx, "AuditService"), infra.repos.userActionLogRepo)
|
auditService := service.NewAuditService(logs.AddCompName(baseCtx, "AuditService"), infra.repos.userActionLogRepo)
|
||||||
planService := service.NewPlanService(logs.AddCompName(baseCtx, "AppPlanService"), domainServices.planService)
|
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{
|
return &AppServices{
|
||||||
pigFarmService: pigFarmService,
|
pigFarmService: pigFarmService,
|
||||||
@@ -254,6 +289,7 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices
|
|||||||
auditService: auditService,
|
auditService: auditService,
|
||||||
planService: planService,
|
planService: planService,
|
||||||
userService: userService,
|
userService: userService,
|
||||||
|
thresholdAlarmService: thresholdAlarmService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +389,7 @@ func initNotifyService(
|
|||||||
|
|
||||||
// 3. 动态确定首选通知器
|
// 3. 动态确定首选通知器
|
||||||
var primaryNotifier notify.Notifier
|
var primaryNotifier notify.Notifier
|
||||||
primaryNotifierType := notify.NotifierType(cfg.Primary)
|
primaryNotifierType := models.NotifierType(cfg.Primary)
|
||||||
|
|
||||||
// 检查用户指定的主渠道是否已启用
|
// 检查用户指定的主渠道是否已启用
|
||||||
for _, n := range availableNotifiers {
|
for _, n := range availableNotifiers {
|
||||||
|
|||||||
@@ -4,16 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"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/logs"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// PlanNameTimedFullDataCollection 是定时全量数据采集计划的名称
|
|
||||||
PlanNameTimedFullDataCollection = "定时全量数据采集"
|
|
||||||
)
|
|
||||||
|
|
||||||
// initializeState 在应用启动时准备其初始数据状态。
|
// initializeState 在应用启动时准备其初始数据状态。
|
||||||
// 它遵循一个严格的顺序:清理 -> 更新 -> 刷新,以确保数据的一致性和正确性。
|
// 它遵循一个严格的顺序:清理 -> 更新 -> 刷新,以确保数据的一致性和正确性。
|
||||||
func (app *Application) initializeState(ctx context.Context) error {
|
func (app *Application) initializeState(ctx context.Context) error {
|
||||||
@@ -48,13 +44,11 @@ func (app *Application) initializeState(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initializeSystemPlans 确保预定义的系统计划在数据库中存在并保持最新。
|
// initializeSystemPlans 确保预定义的系统计划在数据库中存在并保持最新。
|
||||||
|
// 它通过调用各个独立的计划初始化方法来完成此操作。
|
||||||
func (app *Application) initializeSystemPlans(ctx context.Context) error {
|
func (app *Application) initializeSystemPlans(ctx context.Context) error {
|
||||||
appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializeSystemPlans")
|
appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializeSystemPlans")
|
||||||
logger.Info("开始检查并更新预定义的系统计划...")
|
logger.Info("开始检查并更新预定义的系统计划...")
|
||||||
|
|
||||||
// 动态构建预定义计划列表
|
|
||||||
predefinedSystemPlans := app.getPredefinedSystemPlans()
|
|
||||||
|
|
||||||
// 1. 获取所有已存在的系统计划
|
// 1. 获取所有已存在的系统计划
|
||||||
existingPlans, _, err := app.Infra.repos.planRepo.ListPlans(appCtx, repository.ListPlansOptions{
|
existingPlans, _, err := app.Infra.repos.planRepo.ListPlans(appCtx, repository.ListPlansOptions{
|
||||||
PlanType: repository.PlanTypeFilterSystem,
|
PlanType: repository.PlanTypeFilterSystem,
|
||||||
@@ -64,14 +58,86 @@ func (app *Application) initializeSystemPlans(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 为了方便查找, 将现有计划名放入一个 map
|
// 2. 为了方便查找, 将现有计划名放入一个 map
|
||||||
existingPlanMap := make(map[string]*models.Plan)
|
existingPlanMap := make(map[models.PlanName]*models.Plan)
|
||||||
for i := range existingPlans {
|
for i := range existingPlans {
|
||||||
existingPlanMap[existingPlans[i].Name] = &existingPlans[i]
|
existingPlanMap[existingPlans[i].Name] = &existingPlans[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 遍历预定义的计划列表
|
// 3. 调用独立的初始化方法来处理每个系统计划
|
||||||
for i := range predefinedSystemPlans {
|
if err := app.initializePeriodicSystemHealthCheckPlan(appCtx, existingPlanMap); err != nil {
|
||||||
predefinedPlan := &predefinedSystemPlans[i] // 获取可修改的指针
|
return err // 如果任何一个计划初始化失败,则立即返回错误
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.initializeAlarmNotificationPlan(appCtx, existingPlanMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("预定义系统计划检查完成。")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializePeriodicSystemHealthCheckPlan 负责初始化 "周期性系统健康检查" 计划。
|
||||||
|
// 它会根据当前配置动态构建计划,并决定是创建新计划还是更新现有计划。
|
||||||
|
func (app *Application) initializePeriodicSystemHealthCheckPlan(ctx context.Context, existingPlanMap map[models.PlanName]*models.Plan) error {
|
||||||
|
appCtx, logger := logs.Trace(ctx, app.Ctx, "initializePeriodicSystemHealthCheckPlan")
|
||||||
|
|
||||||
|
// 根据配置创建定时全量采集计划
|
||||||
|
interval := app.Config.Collection.Interval
|
||||||
|
if interval <= 0 {
|
||||||
|
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[models.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: models.PlanNamePeriodicSystemHealthCheck,
|
||||||
|
Description: fmt.Sprintf("这是一个系统预定义的计划, 每 %d 分钟自动触发一次全量数据采集, 并进行阈值校验告警。", app.Config.Collection.Interval),
|
||||||
|
PlanType: models.PlanTypeSystem,
|
||||||
|
ExecutionType: models.PlanExecutionTypeAutomatic,
|
||||||
|
CronExpression: cronExpression,
|
||||||
|
Status: models.PlanStatusEnabled,
|
||||||
|
ContentType: models.PlanContentTypeTasks,
|
||||||
|
Tasks: newTasks,
|
||||||
|
}
|
||||||
|
|
||||||
if foundExistingPlan, ok := existingPlanMap[predefinedPlan.Name]; ok {
|
if foundExistingPlan, ok := existingPlanMap[predefinedPlan.Name]; ok {
|
||||||
// 如果计划存在,则进行无差别更新
|
// 如果计划存在,则进行无差别更新
|
||||||
@@ -104,40 +170,58 @@ func (app *Application) initializeSystemPlans(ctx context.Context) error {
|
|||||||
logger.Infof("成功创建预定义计划 '%s'。", predefinedPlan.Name)
|
logger.Infof("成功创建预定义计划 '%s'。", predefinedPlan.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("预定义系统计划检查完成。")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPredefinedSystemPlans 返回一个基于当前配置的预定义系统计划列表。
|
// initializeAlarmNotificationPlan 负责初始化 "告警通知发送" 计划。
|
||||||
func (app *Application) getPredefinedSystemPlans() []models.Plan {
|
// 它确保系统中存在一个每分钟执行的、用于发送告警通知的预定义计划。
|
||||||
|
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{
|
||||||
interval := app.Config.Collection.Interval
|
Name: models.PlanNameAlarmNotification,
|
||||||
if interval <= 0 {
|
Description: "这是一个系统预定义的计划, 每分钟自动触发一次告警通知发送。",
|
||||||
interval = 1 // 确保间隔至少为1分钟
|
|
||||||
}
|
|
||||||
cronExpression := fmt.Sprintf("*/%d * * * *", interval)
|
|
||||||
timedCollectionPlan := models.Plan{
|
|
||||||
Name: PlanNameTimedFullDataCollection,
|
|
||||||
Description: fmt.Sprintf("这是一个系统预定义的计划, 每 %d 分钟自动触发一次全量数据采集。", app.Config.Collection.Interval),
|
|
||||||
PlanType: models.PlanTypeSystem,
|
PlanType: models.PlanTypeSystem,
|
||||||
ExecutionType: models.PlanExecutionTypeAutomatic,
|
ExecutionType: models.PlanExecutionTypeAutomatic,
|
||||||
CronExpression: cronExpression,
|
CronExpression: "*/1 * * * *", // 每分钟执行一次
|
||||||
Status: models.PlanStatusEnabled,
|
Status: models.PlanStatusEnabled,
|
||||||
ContentType: models.PlanContentTypeTasks,
|
ContentType: models.PlanContentTypeTasks,
|
||||||
Tasks: []models.Task{
|
Tasks: []models.Task{
|
||||||
{
|
{
|
||||||
Name: "全量采集",
|
Name: "告警通知发送",
|
||||||
Description: "触发一次全量数据采集",
|
Description: "发送所有待处理的告警通知",
|
||||||
ExecutionOrder: 1,
|
ExecutionOrder: 1,
|
||||||
Type: models.TaskTypeFullCollection,
|
Type: models.TaskTypeAlarmNotification,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return []models.Plan{timedCollectionPlan}
|
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 在应用启动时处理所有未完成的采集请求。
|
// initializePendingCollections 在应用启动时处理所有未完成的采集请求。
|
||||||
@@ -209,7 +293,7 @@ func (app *Application) cleanupStaleTasksAndLogs(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 收集所有受影响的唯一 PlanID
|
// 2. 收集所有受影响的唯一 PlanID
|
||||||
affectedPlanIDs := make(map[uint]struct{})
|
affectedPlanIDs := make(map[uint32]struct{})
|
||||||
for _, log := range incompletePlanLogs {
|
for _, log := range incompletePlanLogs {
|
||||||
affectedPlanIDs[log.PlanID] = struct{}{}
|
affectedPlanIDs[log.PlanID] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|||||||
175
internal/domain/alarm/alarm_service.go
Normal file
175
internal/domain/alarm/alarm_service.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package alarm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"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 uint32, alarmCode models.AlarmCode, resolveMethod string, resolvedBy *uint32) error
|
||||||
|
|
||||||
|
// SnoozeAlarm 忽略一个活跃告警,或更新其忽略时间。
|
||||||
|
// 如果告警不存在,将返回错误。
|
||||||
|
SnoozeAlarm(ctx context.Context, alarmID uint32, duration time.Duration) error
|
||||||
|
|
||||||
|
// CancelAlarmSnooze 取消对一个告警的忽略状态。
|
||||||
|
// 如果告警不存在,或本就未被忽略,不执行任何操作并返回 nil。
|
||||||
|
CancelAlarmSnooze(ctx context.Context, alarmID uint32) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// alarmService 是 AlarmService 接口的具体实现。
|
||||||
|
type alarmService struct {
|
||||||
|
ctx context.Context
|
||||||
|
alarmRepo repository.AlarmRepository
|
||||||
|
uow repository.UnitOfWork
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAlarmService 创建一个新的 AlarmService 实例。
|
||||||
|
func NewAlarmService(ctx context.Context, alarmRepo repository.AlarmRepository, uow repository.UnitOfWork) AlarmService {
|
||||||
|
return &alarmService{
|
||||||
|
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 uint32, alarmCode models.AlarmCode, resolveMethod string, resolvedBy *uint32) 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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnoozeAlarm 忽略一个活跃告警,或更新其忽略时间。
|
||||||
|
func (s *alarmService) SnoozeAlarm(ctx context.Context, alarmID uint32, 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 uint32) 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
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ type Service interface {
|
|||||||
Switch(ctx context.Context, device *models.Device, action DeviceAction) error
|
Switch(ctx context.Context, device *models.Device, action DeviceAction) error
|
||||||
|
|
||||||
// Collect 用于发起对指定区域主控下的多个设备的批量采集请求。
|
// Collect 用于发起对指定区域主控下的多个设备的批量采集请求。
|
||||||
Collect(ctx context.Context, regionalControllerID uint, devicesToCollect []*models.Device) error
|
Collect(ctx context.Context, areaControllerID uint32, devicesToCollect []*models.Device) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设备操作指令通用结构(最外层)
|
// 设备操作指令通用结构(最外层)
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ func (g *GeneralDeviceService) Switch(ctx context.Context, device *models.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Collect 实现了 Service 接口,用于发起对指定区域主控下的多个设备的批量采集请求。
|
// Collect 实现了 Service 接口,用于发起对指定区域主控下的多个设备的批量采集请求。
|
||||||
func (g *GeneralDeviceService) Collect(ctx context.Context, regionalControllerID 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")
|
serviceCtx, logger := logs.Trace(ctx, g.ctx, "Collect")
|
||||||
if len(devicesToCollect) == 0 {
|
if len(devicesToCollect) == 0 {
|
||||||
logger.Info("待采集设备列表为空,无需执行采集任务。")
|
logger.Info("待采集设备列表为空,无需执行采集任务。")
|
||||||
@@ -141,16 +141,16 @@ func (g *GeneralDeviceService) Collect(ctx context.Context, regionalControllerID
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. 从设备列表中获取预加载的区域主控,并进行校验
|
// 1. 从设备列表中获取预加载的区域主控,并进行校验
|
||||||
regionalController := &devicesToCollect[0].AreaController
|
areaController := &devicesToCollect[0].AreaController
|
||||||
if regionalController.ID != regionalControllerID {
|
if areaController.ID != areaControllerID {
|
||||||
return fmt.Errorf("设备列表与指定的区域主控ID (%d) 不匹配", regionalControllerID)
|
return fmt.Errorf("设备列表与指定的区域主控ID (%d) 不匹配", areaControllerID)
|
||||||
}
|
}
|
||||||
if err := regionalController.SelfCheck(); err != nil {
|
if err := areaController.SelfCheck(); err != nil {
|
||||||
return fmt.Errorf("区域主控 (ID: %d) 未通过自检: %w", regionalControllerID, err)
|
return fmt.Errorf("区域主控 (ID: %d) 未通过自检: %w", areaControllerID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 准备采集任务列表
|
// 2. 准备采集任务列表
|
||||||
var childDeviceIDs []uint
|
var childDeviceIDs []uint32
|
||||||
var collectTasks []*proto.CollectTask
|
var collectTasks []*proto.CollectTask
|
||||||
|
|
||||||
for _, dev := range devicesToCollect {
|
for _, dev := range devicesToCollect {
|
||||||
@@ -208,13 +208,13 @@ func (g *GeneralDeviceService) Collect(ctx context.Context, regionalControllerID
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 构建并发送指令
|
// 3. 构建并发送指令
|
||||||
networkID := regionalController.NetworkID
|
networkID := areaController.NetworkID
|
||||||
|
|
||||||
// 4. 创建待处理请求记录
|
// 4. 创建待处理请求记录
|
||||||
correlationID := uuid.New().String()
|
correlationID := uuid.New().String()
|
||||||
pendingReq := &models.PendingCollection{
|
pendingReq := &models.PendingCollection{
|
||||||
CorrelationID: correlationID,
|
CorrelationID: correlationID,
|
||||||
DeviceID: regionalController.ID,
|
DeviceID: areaController.ID,
|
||||||
CommandMetadata: childDeviceIDs,
|
CommandMetadata: childDeviceIDs,
|
||||||
Status: models.PendingStatusPending,
|
Status: models.PendingStatusPending,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
@@ -223,7 +223,7 @@ func (g *GeneralDeviceService) Collect(ctx context.Context, regionalControllerID
|
|||||||
logger.Errorf("创建待采集请求失败 (CorrelationID: %s): %v", correlationID, err)
|
logger.Errorf("创建待采集请求失败 (CorrelationID: %s): %v", correlationID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Infof("成功创建待采集请求 (CorrelationID: %s, DeviceID: %d)", correlationID, regionalController.ID)
|
logger.Infof("成功创建待采集请求 (CorrelationID: %s, DeviceID: %d)", correlationID, areaController.ID)
|
||||||
|
|
||||||
// 5. 构建最终的空中载荷
|
// 5. 构建最终的空中载荷
|
||||||
batchCmd := &proto.BatchCollectCommand{
|
batchCmd := &proto.BatchCollectCommand{
|
||||||
|
|||||||
@@ -11,30 +11,28 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
"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/notify"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service 定义了通知领域的核心业务逻辑接口
|
// Service 定义了通知领域的核心业务逻辑接口
|
||||||
type Service interface {
|
type Service interface {
|
||||||
// SendBatchAlarm 向一批用户发送告警通知。它会并发地为每个用户执行带故障转移的发送逻辑。
|
// SendBatchAlarm 向一批用户发送告警通知。它会并发地为每个用户执行带故障转移的发送逻辑。
|
||||||
SendBatchAlarm(ctx context.Context, userIDs []uint, content notify.AlarmContent) error
|
SendBatchAlarm(ctx context.Context, userIDs []uint32, content notify.AlarmContent) error
|
||||||
|
|
||||||
// BroadcastAlarm 向所有用户发送告警通知。它会并发地为每个用户执行带故障转移的发送逻辑。
|
// BroadcastAlarm 向所有用户发送告警通知。它会并发地为每个用户执行带故障转移的发送逻辑。
|
||||||
BroadcastAlarm(ctx context.Context, content notify.AlarmContent) error
|
BroadcastAlarm(ctx context.Context, content notify.AlarmContent) error
|
||||||
|
|
||||||
// SendTestMessage 向指定用户发送一条测试消息,用于手动验证特定通知渠道的配置。
|
// SendTestMessage 向指定用户发送一条测试消息,用于手动验证特定通知渠道的配置。
|
||||||
SendTestMessage(ctx context.Context, userID uint, notifierType notify.NotifierType) error
|
SendTestMessage(ctx context.Context, userID uint32, notifierType models.NotifierType) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// failoverService 是 Service 接口的实现,提供了故障转移功能
|
// failoverService 是 Service 接口的实现,提供了故障转移功能
|
||||||
type failoverService struct {
|
type failoverService struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
userRepo repository.UserRepository
|
userRepo repository.UserRepository
|
||||||
notifiers map[notify.NotifierType]notify.Notifier
|
notifiers map[models.NotifierType]notify.Notifier
|
||||||
primaryNotifier notify.Notifier
|
primaryNotifier notify.Notifier
|
||||||
failureThreshold int
|
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
|
notificationRepo repository.NotificationRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,11 +41,11 @@ func NewFailoverService(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userRepo repository.UserRepository,
|
userRepo repository.UserRepository,
|
||||||
notifiers []notify.Notifier,
|
notifiers []notify.Notifier,
|
||||||
primaryNotifierType notify.NotifierType,
|
primaryNotifierType models.NotifierType,
|
||||||
failureThreshold int,
|
failureThreshold int,
|
||||||
notificationRepo repository.NotificationRepository,
|
notificationRepo repository.NotificationRepository,
|
||||||
) (Service, error) {
|
) (Service, error) {
|
||||||
notifierMap := make(map[notify.NotifierType]notify.Notifier)
|
notifierMap := make(map[models.NotifierType]notify.Notifier)
|
||||||
for _, n := range notifiers {
|
for _, n := range notifiers {
|
||||||
notifierMap[n.Type()] = n
|
notifierMap[n.Type()] = n
|
||||||
}
|
}
|
||||||
@@ -69,7 +67,7 @@ func NewFailoverService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendBatchAlarm 实现了向多个用户并发发送告警的功能
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendBatchAlarm")
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
@@ -79,7 +77,7 @@ func (s *failoverService) SendBatchAlarm(ctx context.Context, userIDs []uint, co
|
|||||||
|
|
||||||
for _, userID := range userIDs {
|
for _, userID := range userIDs {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(id uint) {
|
go func(id uint32) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err := s.sendAlarmToUser(serviceCtx, id, content); err != nil {
|
if err := s.sendAlarmToUser(serviceCtx, id, content); err != nil {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
@@ -110,7 +108,7 @@ func (s *failoverService) BroadcastAlarm(ctx context.Context, content notify.Ala
|
|||||||
return fmt.Errorf("广播告警失败:查找所有用户时出错: %w", err)
|
return fmt.Errorf("广播告警失败:查找所有用户时出错: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var userIDs []uint
|
var userIDs []uint32
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
userIDs = append(userIDs, user.ID)
|
userIDs = append(userIDs, user.ID)
|
||||||
}
|
}
|
||||||
@@ -121,7 +119,7 @@ func (s *failoverService) BroadcastAlarm(ctx context.Context, content notify.Ala
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sendAlarmToUser 是为单个用户发送告警的内部方法,包含了完整的故障转移逻辑
|
// 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")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "sendAlarmToUser")
|
||||||
user, err := s.userRepo.FindByID(serviceCtx, userID)
|
user, err := s.userRepo.FindByID(serviceCtx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -189,7 +187,7 @@ func (s *failoverService) sendAlarmToUser(ctx context.Context, userID uint, cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendTestMessage 实现了手动发送测试消息的功能
|
// SendTestMessage 实现了手动发送测试消息的功能
|
||||||
func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, notifierType notify.NotifierType) error {
|
func (s *failoverService) SendTestMessage(ctx context.Context, userID uint32, notifierType models.NotifierType) error {
|
||||||
serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendTestMessage")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendTestMessage")
|
||||||
user, err := s.userRepo.FindByID(serviceCtx, userID)
|
user, err := s.userRepo.FindByID(serviceCtx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -210,7 +208,7 @@ func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, noti
|
|||||||
s.recordNotificationAttempt(serviceCtx, userID, notifierType, notify.AlarmContent{
|
s.recordNotificationAttempt(serviceCtx, userID, notifierType, notify.AlarmContent{
|
||||||
Title: "通知服务测试",
|
Title: "通知服务测试",
|
||||||
Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType),
|
Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType),
|
||||||
Level: zap.InfoLevel,
|
Level: models.InfoLevel,
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
}, "", models.NotificationStatusFailed, fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType))
|
}, "", models.NotificationStatusFailed, fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType))
|
||||||
return 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{
|
testContent := notify.AlarmContent{
|
||||||
Title: "通知服务测试",
|
Title: "通知服务测试",
|
||||||
Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType),
|
Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType),
|
||||||
Level: zap.InfoLevel,
|
Level: models.InfoLevel,
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,15 +237,15 @@ func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, noti
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getAddressForNotifier 是一个辅助函数,根据通知器类型从 ContactInfo 中获取对应的地址
|
// getAddressForNotifier 是一个辅助函数,根据通知器类型从 ContactInfo 中获取对应的地址
|
||||||
func getAddressForNotifier(notifierType notify.NotifierType, contact models.ContactInfo) string {
|
func getAddressForNotifier(notifierType models.NotifierType, contact models.ContactInfo) string {
|
||||||
switch notifierType {
|
switch notifierType {
|
||||||
case notify.NotifierTypeSMTP:
|
case models.NotifierTypeSMTP:
|
||||||
return contact.Email
|
return contact.Email
|
||||||
case notify.NotifierTypeWeChat:
|
case models.NotifierTypeWeChat:
|
||||||
return contact.WeChat
|
return contact.WeChat
|
||||||
case notify.NotifierTypeLark:
|
case models.NotifierTypeLark:
|
||||||
return contact.Feishu
|
return contact.Feishu
|
||||||
case notify.NotifierTypeLog:
|
case models.NotifierTypeLog:
|
||||||
return "log" // LogNotifier不需要具体的地址,但为了函数签名一致性,返回一个无意义的非空字符串以绕过配置存在检查
|
return "log" // LogNotifier不需要具体的地址,但为了函数签名一致性,返回一个无意义的非空字符串以绕过配置存在检查
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
@@ -263,8 +261,8 @@ func getAddressForNotifier(notifierType notify.NotifierType, contact models.Cont
|
|||||||
// err: 如果发送失败,记录的错误信息
|
// err: 如果发送失败,记录的错误信息
|
||||||
func (s *failoverService) recordNotificationAttempt(
|
func (s *failoverService) recordNotificationAttempt(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID uint,
|
userID uint32,
|
||||||
notifierType notify.NotifierType,
|
notifierType models.NotifierType,
|
||||||
content notify.AlarmContent,
|
content notify.AlarmContent,
|
||||||
toAddress string,
|
toAddress string,
|
||||||
status models.NotificationStatus,
|
status models.NotificationStatus,
|
||||||
@@ -281,7 +279,7 @@ func (s *failoverService) recordNotificationAttempt(
|
|||||||
UserID: userID,
|
UserID: userID,
|
||||||
Title: content.Title,
|
Title: content.Title,
|
||||||
Message: content.Message,
|
Message: content.Message,
|
||||||
Level: models.LogLevel(content.Level),
|
Level: content.Level,
|
||||||
AlarmTimestamp: content.Timestamp,
|
AlarmTimestamp: content.Timestamp,
|
||||||
ToAddress: toAddress,
|
ToAddress: toAddress,
|
||||||
Status: status,
|
Status: status,
|
||||||
|
|||||||
@@ -19,22 +19,22 @@ type PigPenTransferManager interface {
|
|||||||
LogTransfer(ctx context.Context, tx *gorm.DB, log *models.PigTransferLog) error
|
LogTransfer(ctx context.Context, tx *gorm.DB, log *models.PigTransferLog) error
|
||||||
|
|
||||||
// GetPenByID 用于获取猪栏的详细信息,供上层服务进行业务校验。
|
// 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 获取一个猪群当前关联的所有猪栏。
|
||||||
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 更新一个猪栏的指定字段。
|
||||||
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 通过汇总猪只迁移日志,计算给定猪栏中的当前猪只数量。
|
||||||
GetCurrentPigsInPen(ctx context.Context, tx *gorm.DB, penID uint) (int, error)
|
GetCurrentPigsInPen(ctx context.Context, tx *gorm.DB, penID uint32) (int, error)
|
||||||
|
|
||||||
// GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数
|
// 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 将猪栏的猪群归属移除,并将其状态标记为空闲。
|
||||||
ReleasePen(ctx context.Context, tx *gorm.DB, penID uint) error
|
ReleasePen(ctx context.Context, tx *gorm.DB, penID uint32) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。
|
// pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。
|
||||||
@@ -63,25 +63,25 @@ func (s *pigPenTransferManager) LogTransfer(ctx context.Context, tx *gorm.DB, lo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPenByID 实现了获取猪栏信息的逻辑。
|
// 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")
|
managerCtx := logs.AddFuncName(ctx, s.ctx, "GetPenByID")
|
||||||
return s.penRepo.GetPenByIDTx(managerCtx, tx, penID)
|
return s.penRepo.GetPenByIDTx(managerCtx, tx, penID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPensByBatchID 实现了获取猪群关联猪栏列表的逻辑。
|
// 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")
|
managerCtx := logs.AddFuncName(ctx, s.ctx, "GetPensByBatchID")
|
||||||
return s.penRepo.GetPensByBatchIDTx(managerCtx, tx, batchID)
|
return s.penRepo.GetPensByBatchIDTx(managerCtx, tx, batchID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePenFields 实现了更新猪栏字段的逻辑。
|
// 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")
|
managerCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePenFields")
|
||||||
return s.penRepo.UpdatePenFieldsTx(managerCtx, tx, penID, updates)
|
return s.penRepo.UpdatePenFieldsTx(managerCtx, tx, penID, updates)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentPigsInPen 实现了计算猪栏当前猪只数量的逻辑。
|
// 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")
|
managerCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentPigsInPen")
|
||||||
// 1. 通过猪栏ID查出所属猪群信息
|
// 1. 通过猪栏ID查出所属猪群信息
|
||||||
pen, err := s.penRepo.GetPenByIDTx(managerCtx, tx, penID)
|
pen, err := s.penRepo.GetPenByIDTx(managerCtx, tx, penID)
|
||||||
@@ -137,7 +137,7 @@ func (s *pigPenTransferManager) GetCurrentPigsInPen(ctx context.Context, tx *gor
|
|||||||
|
|
||||||
// GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数
|
// GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数
|
||||||
// 该方法通过遍历猪群下的每个猪栏,并调用 GetCurrentPigsInPen 来累加存栏数。
|
// 该方法通过遍历猪群下的每个猪栏,并调用 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")
|
managerCtx := logs.AddFuncName(ctx, s.ctx, "GetTotalPigsInPensForBatchTx")
|
||||||
// 1. 获取该批次下所有猪栏的列表
|
// 1. 获取该批次下所有猪栏的列表
|
||||||
pensInBatch, err := s.GetPensByBatchID(managerCtx, tx, batchID)
|
pensInBatch, err := s.GetPensByBatchID(managerCtx, tx, batchID)
|
||||||
@@ -160,7 +160,7 @@ func (s *pigPenTransferManager) GetTotalPigsInPensForBatchTx(ctx context.Context
|
|||||||
|
|
||||||
// ReleasePen 将猪栏的猪群归属移除,并将其状态标记为空闲。
|
// 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")
|
managerCtx := logs.AddFuncName(ctx, s.ctx, "ReleasePen")
|
||||||
// 1. 获取猪栏信息
|
// 1. 获取猪栏信息
|
||||||
pen, err := s.penRepo.GetPenByIDTx(managerCtx, tx, penID)
|
pen, err := s.penRepo.GetPenByIDTx(managerCtx, tx, penID)
|
||||||
|
|||||||
@@ -38,58 +38,58 @@ var (
|
|||||||
// 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。
|
// 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。
|
||||||
type PigBatchService interface {
|
type PigBatchService interface {
|
||||||
// CreatePigBatch 创建猪批次,并记录初始日志。
|
// 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 获取单个猪批次。
|
||||||
GetPigBatch(ctx context.Context, id uint) (*models.PigBatch, error)
|
GetPigBatch(ctx context.Context, id uint32) (*models.PigBatch, error)
|
||||||
// UpdatePigBatch 更新猪批次信息。
|
// UpdatePigBatch 更新猪批次信息。
|
||||||
UpdatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, error)
|
UpdatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, error)
|
||||||
// DeletePigBatch 删除猪批次,包含业务规则校验。
|
// DeletePigBatch 删除猪批次,包含业务规则校验。
|
||||||
DeletePigBatch(ctx context.Context, id uint) error
|
DeletePigBatch(ctx context.Context, id uint32) error
|
||||||
// ListPigBatches 批量查询猪批次。
|
// ListPigBatches 批量查询猪批次。
|
||||||
ListPigBatches(ctx context.Context, isActive *bool) ([]*models.PigBatch, error)
|
ListPigBatches(ctx context.Context, isActive *bool) ([]*models.PigBatch, error)
|
||||||
// AssignEmptyPensToBatch 为猪群分配空栏
|
// 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 将猪只从“虚拟库存”移入指定猪栏
|
||||||
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 连猪带栏,整体划拨到另一个猪群
|
||||||
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 将一个猪栏移除出猪群,此方法需要在猪栏为空的情况下执行。
|
||||||
RemoveEmptyPenFromBatch(ctx context.Context, batchID uint, penID uint) error
|
RemoveEmptyPenFromBatch(ctx context.Context, batchID uint32, penID uint32) error
|
||||||
|
|
||||||
// GetCurrentPigQuantity 获取指定猪批次的当前猪只数量。
|
// GetCurrentPigQuantity 获取指定猪批次的当前猪只数量。
|
||||||
GetCurrentPigQuantity(ctx context.Context, batchID uint) (int, error)
|
GetCurrentPigQuantity(ctx context.Context, batchID uint32) (int, error)
|
||||||
// GetCurrentPigsInPen 获取指定猪栏的当前存栏量。
|
// GetCurrentPigsInPen 获取指定猪栏的当前存栏量。
|
||||||
GetCurrentPigsInPen(ctx context.Context, penID uint) (int, error)
|
GetCurrentPigsInPen(ctx context.Context, penID uint32) (int, error)
|
||||||
// GetTotalPigsInPensForBatch 获取指定猪群下所有猪栏的当前总存栏数
|
// 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 处理卖猪的业务逻辑。
|
||||||
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 uint32, penID uint32, quantity int, unitPrice float32, tatalPrice float32, traderName string, tradeDate time.Time, remarks string, operatorID uint32) error
|
||||||
// BuyPigs 处理买猪的业务逻辑。
|
// 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 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
|
TransferPigsAcrossBatches(ctx context.Context, sourceBatchID uint32, destBatchID uint32, fromPenID uint32, toPenID uint32, quantity uint32, operatorID uint32, remarks string) error
|
||||||
TransferPigsWithinBatch(ctx context.Context, batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error
|
TransferPigsWithinBatch(ctx context.Context, batchID uint32, fromPenID uint32, toPenID uint32, quantity uint32, operatorID uint32, remarks string) error
|
||||||
|
|
||||||
// --- 病猪管理相关方法 ---
|
// --- 病猪管理相关方法 ---
|
||||||
// RecordSickPigs 记录新增病猪事件。
|
// 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 记录病猪康复事件。
|
||||||
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 记录病猪死亡事件。
|
||||||
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 记录病猪淘汰事件。
|
||||||
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 记录正常猪只死亡事件。
|
||||||
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 记录正常猪只淘汰事件。
|
||||||
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 接口的具体实现。
|
// pigBatchService 是 PigBatchService 接口的具体实现。
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
// --- 领域服务实现 ---
|
// --- 领域服务实现 ---
|
||||||
|
|
||||||
// CreatePigBatch 实现了创建猪批次的逻辑,并同时创建初始批次日志。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePigBatch")
|
||||||
// 业务规则可以在这里添加,例如检查批次号是否唯一等
|
// 业务规则可以在这里添加,例如检查批次号是否唯一等
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ func (s *pigBatchService) CreatePigBatch(ctx context.Context, operatorID uint, b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPigBatch 实现了获取单个猪批次的逻辑。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigBatch")
|
||||||
batch, err := s.pigBatchRepo.GetPigBatchByID(serviceCtx, id)
|
batch, err := s.pigBatchRepo.GetPigBatchByID(serviceCtx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -84,7 +84,7 @@ func (s *pigBatchService) UpdatePigBatch(ctx context.Context, batch *models.PigB
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeletePigBatch 实现了删除猪批次的逻辑,并包含业务规则校验。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigBatch")
|
||||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||||
// 1. 获取猪批次信息
|
// 1. 获取猪批次信息
|
||||||
@@ -135,7 +135,7 @@ func (s *pigBatchService) ListPigBatches(ctx context.Context, isActive *bool) ([
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentPigQuantity 实现了获取指定猪批次的当前猪只数量的逻辑。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentPigQuantity")
|
||||||
var getErr error
|
var getErr error
|
||||||
var quantity int
|
var quantity int
|
||||||
@@ -150,7 +150,7 @@ func (s *pigBatchService) GetCurrentPigQuantity(ctx context.Context, batchID uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getCurrentPigQuantityTx 实现了获取指定猪批次的当前猪只数量的逻辑。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "getCurrentPigQuantityTx")
|
||||||
// 1. 获取猪批次初始信息
|
// 1. 获取猪批次初始信息
|
||||||
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(serviceCtx, tx, batchID)
|
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
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigBatchQuantity")
|
||||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||||
return s.updatePigBatchQuantityTx(serviceCtx, tx, operatorID, batchID, changeType, changeAmount, changeReason, happenedAt)
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "updatePigBatchQuantityTx")
|
||||||
lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(serviceCtx, tx, batchID)
|
lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(serviceCtx, tx, batchID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// executeTransferAndLog 是一个私有辅助方法,用于封装创建和记录迁移日志的通用逻辑。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "executeTransferAndLog")
|
||||||
|
|
||||||
// 通用校验:任何调出操作都不能超过源猪栏的当前存栏数
|
// 通用校验:任何调出操作都不能超过源猪栏的当前存栏数
|
||||||
@@ -67,7 +67,7 @@ func (s *pigBatchService) executeTransferAndLog(ctx context.Context, tx *gorm.DB
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TransferPigsWithinBatch 实现了同一个猪群内部的调栏业务。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "TransferPigsWithinBatch")
|
||||||
if fromPenID == toPenID {
|
if fromPenID == toPenID {
|
||||||
return errors.New("源猪栏和目标猪栏不能相同")
|
return errors.New("源猪栏和目标猪栏不能相同")
|
||||||
@@ -106,7 +106,7 @@ func (s *pigBatchService) TransferPigsWithinBatch(ctx context.Context, batchID u
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TransferPigsAcrossBatches 实现了跨猪群的调栏业务。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "TransferPigsAcrossBatches")
|
||||||
if sourceBatchID == destBatchID {
|
if sourceBatchID == destBatchID {
|
||||||
return errors.New("源猪群和目标猪群不能相同")
|
return errors.New("源猪群和目标猪群不能相同")
|
||||||
@@ -167,7 +167,7 @@ func (s *pigBatchService) TransferPigsAcrossBatches(ctx context.Context, sourceB
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AssignEmptyPensToBatch 为猪群分配空栏
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "AssignEmptyPensToBatch")
|
||||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||||
// 1. 验证猪批次是否存在且活跃
|
// 1. 验证猪批次是否存在且活跃
|
||||||
@@ -204,6 +204,7 @@ func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID ui
|
|||||||
updates := map[string]interface{}{
|
updates := map[string]interface{}{
|
||||||
"pig_batch_id": &batchID,
|
"pig_batch_id": &batchID,
|
||||||
"status": models.PenStatusOccupied,
|
"status": models.PenStatusOccupied,
|
||||||
|
"operator_id": operatorID,
|
||||||
}
|
}
|
||||||
if err := s.transferSvc.UpdatePenFields(serviceCtx, tx, penID, updates); err != nil {
|
if err := s.transferSvc.UpdatePenFields(serviceCtx, tx, penID, updates); err != nil {
|
||||||
return fmt.Errorf("分配猪栏 %d 失败: %w", penID, err)
|
return fmt.Errorf("分配猪栏 %d 失败: %w", penID, err)
|
||||||
@@ -215,7 +216,7 @@ func (s *pigBatchService) AssignEmptyPensToBatch(ctx context.Context, batchID ui
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MovePigsIntoPen 将猪只从“虚拟库存”移入指定猪栏
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "MovePigsIntoPen")
|
||||||
if quantity <= 0 {
|
if quantity <= 0 {
|
||||||
return errors.New("迁移数量必须大于零")
|
return errors.New("迁移数量必须大于零")
|
||||||
@@ -287,7 +288,7 @@ func (s *pigBatchService) MovePigsIntoPen(ctx context.Context, batchID uint, toP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReclassifyPenToNewBatch 连猪带栏,整体划拨到另一个猪群
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "ReclassifyPenToNewBatch")
|
||||||
if fromBatchID == toBatchID {
|
if fromBatchID == toBatchID {
|
||||||
return errors.New("源猪群和目标猪群不能相同")
|
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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RemoveEmptyPenFromBatch")
|
||||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||||
// 1. 检查猪批次是否存在且活跃
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentPigsInPen")
|
||||||
var currentPigs int
|
var currentPigs int
|
||||||
err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||||
@@ -453,7 +454,7 @@ func (s *pigBatchService) GetCurrentPigsInPen(ctx context.Context, penID uint) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetTotalPigsInPensForBatch 实现了获取指定猪群下所有猪栏的当前总存栏数的逻辑。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetTotalPigsInPensForBatch")
|
||||||
var totalPigs int
|
var totalPigs int
|
||||||
err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RecordSickPigs 记录新增病猪事件。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordSickPigs")
|
||||||
if quantity <= 0 {
|
if quantity <= 0 {
|
||||||
return errors.New("新增病猪数量必须大于0")
|
return errors.New("新增病猪数量必须大于0")
|
||||||
@@ -89,7 +89,7 @@ func (s *pigBatchService) RecordSickPigs(ctx context.Context, operatorID uint, b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordSickPigRecovery 记录病猪康复事件。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordSickPigRecovery")
|
||||||
if quantity <= 0 {
|
if quantity <= 0 {
|
||||||
return errors.New("康复猪只数量必须大于0")
|
return errors.New("康复猪只数量必须大于0")
|
||||||
@@ -158,7 +158,7 @@ func (s *pigBatchService) RecordSickPigRecovery(ctx context.Context, operatorID
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordSickPigDeath 记录病猪死亡事件。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordSickPigDeath")
|
||||||
if quantity <= 0 {
|
if quantity <= 0 {
|
||||||
return errors.New("死亡猪只数量必须大于0")
|
return errors.New("死亡猪只数量必须大于0")
|
||||||
@@ -254,7 +254,7 @@ func (s *pigBatchService) RecordSickPigDeath(ctx context.Context, operatorID uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordSickPigCull 记录病猪淘汰事件。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordSickPigCull")
|
||||||
if quantity <= 0 {
|
if quantity <= 0 {
|
||||||
return errors.New("淘汰猪只数量必须大于0")
|
return errors.New("淘汰猪只数量必须大于0")
|
||||||
@@ -350,7 +350,7 @@ func (s *pigBatchService) RecordSickPigCull(ctx context.Context, operatorID uint
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordDeath 记录正常猪只死亡事件。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordDeath")
|
||||||
if quantity <= 0 {
|
if quantity <= 0 {
|
||||||
return errors.New("死亡猪只数量必须大于0")
|
return errors.New("死亡猪只数量必须大于0")
|
||||||
@@ -421,7 +421,7 @@ func (s *pigBatchService) RecordDeath(ctx context.Context, operatorID uint, batc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordCull 记录正常猪只淘汰事件。
|
// 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "RecordCull")
|
||||||
if quantity <= 0 {
|
if quantity <= 0 {
|
||||||
return errors.New("淘汰猪只数量必须大于0")
|
return errors.New("淘汰猪只数量必须大于0")
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SellPigs 处理批量销售猪的业务逻辑。
|
// 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 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "SellPigs")
|
||||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||||
if quantity <= 0 {
|
if quantity <= 0 {
|
||||||
@@ -85,7 +85,7 @@ func (s *pigBatchService) SellPigs(ctx context.Context, batchID uint, penID uint
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BuyPigs 处理批量购买猪的业务逻辑。
|
// 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 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")
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "BuyPigs")
|
||||||
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||||
if quantity <= 0 {
|
if quantity <= 0 {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type SickPigManager interface {
|
|||||||
ProcessSickPigLog(ctx context.Context, tx *gorm.DB, log *models.PigSickLog) error
|
ProcessSickPigLog(ctx context.Context, tx *gorm.DB, log *models.PigSickLog) error
|
||||||
|
|
||||||
// GetCurrentSickPigCount 获取指定批次当前患病猪只的总数
|
// 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 接口的具体实现。
|
// sickPigManager 是 SickPigManager 接口的具体实现。
|
||||||
@@ -122,7 +122,7 @@ func (s *sickPigManager) ProcessSickPigLog(ctx context.Context, tx *gorm.DB, log
|
|||||||
return nil
|
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")
|
managerCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentSickPigCount")
|
||||||
lastLog, err := s.sickLogRepo.GetLastLogByBatchTx(managerCtx, tx, batchID)
|
lastLog, err := s.sickLogRepo.GetLastLogByBatchTx(managerCtx, tx, batchID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ type AnalysisPlanTaskManager interface {
|
|||||||
Refresh(ctx context.Context) error
|
Refresh(ctx context.Context) error
|
||||||
// CreateOrUpdateTrigger 为给定的 planID 创建其关联的触发任务。
|
// CreateOrUpdateTrigger 为给定的 planID 创建其关联的触发任务。
|
||||||
// 如果触发器已存在,会根据计划类型更新其执行时间。
|
// 如果触发器已存在,会根据计划类型更新其执行时间。
|
||||||
CreateOrUpdateTrigger(ctx context.Context, planID uint) error
|
CreateOrUpdateTrigger(ctx context.Context, planID uint32) error
|
||||||
// EnsureAnalysisTaskDefinition 确保计划的分析任务定义存在于 tasks 表中。
|
// EnsureAnalysisTaskDefinition 确保计划的分析任务定义存在于 tasks 表中。
|
||||||
// 如果不存在,则会自动创建。此方法不涉及待执行队列。
|
// 如果不存在,则会自动创建。此方法不涉及待执行队列。
|
||||||
EnsureAnalysisTaskDefinition(ctx context.Context, planID uint) error
|
EnsureAnalysisTaskDefinition(ctx context.Context, planID uint32) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// analysisPlanTaskManagerImpl 负责管理分析计划的触发器任务。
|
// analysisPlanTaskManagerImpl 负责管理分析计划的触发器任务。
|
||||||
@@ -82,7 +82,7 @@ func (m *analysisPlanTaskManagerImpl) Refresh(ctx context.Context) error {
|
|||||||
|
|
||||||
// CreateOrUpdateTrigger 为给定的 planID 创建其关联的触发任务。
|
// 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")
|
managerCtx, logger := logs.Trace(ctx, m.ctx, "CreateOrUpdateTrigger")
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
@@ -138,7 +138,7 @@ func (m *analysisPlanTaskManagerImpl) CreateOrUpdateTrigger(ctx context.Context,
|
|||||||
|
|
||||||
// EnsureAnalysisTaskDefinition 确保计划的分析任务定义存在于 tasks 表中。
|
// 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")
|
managerCtx, logger := logs.Trace(ctx, m.ctx, "EnsureAnalysisTaskDefinition")
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
@@ -170,7 +170,7 @@ func (m *analysisPlanTaskManagerImpl) EnsureAnalysisTaskDefinition(ctx context.C
|
|||||||
// --- 内部私有方法 ---
|
// --- 内部私有方法 ---
|
||||||
|
|
||||||
// getRefreshData 从数据库获取刷新所需的所有数据。
|
// 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")
|
managerCtx, logger := logs.Trace(ctx, m.ctx, "getRefreshData")
|
||||||
runnablePlans, err = m.planRepo.FindRunnablePlans(managerCtx)
|
runnablePlans, err = m.planRepo.FindRunnablePlans(managerCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -183,7 +183,7 @@ func (m *analysisPlanTaskManagerImpl) getRefreshData(ctx context.Context) (runna
|
|||||||
logger.Errorf("获取失效计划列表失败: %v", err)
|
logger.Errorf("获取失效计划列表失败: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
invalidPlanIDs = make([]uint, len(invalidPlans))
|
invalidPlanIDs = make([]uint32, len(invalidPlans))
|
||||||
for i, p := range invalidPlans {
|
for i, p := range invalidPlans {
|
||||||
invalidPlanIDs[i] = p.ID
|
invalidPlanIDs[i] = p.ID
|
||||||
}
|
}
|
||||||
@@ -197,19 +197,19 @@ func (m *analysisPlanTaskManagerImpl) getRefreshData(ctx context.Context) (runna
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cleanupInvalidTasks 清理所有与失效计划相关的待执行任务。
|
// 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")
|
managerCtx, logger := logs.Trace(ctx, m.ctx, "cleanupInvalidTasks")
|
||||||
if len(invalidPlanIDs) == 0 {
|
if len(invalidPlanIDs) == 0 {
|
||||||
return nil // 没有需要清理的计划
|
return nil // 没有需要清理的计划
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidPlanIDSet := make(map[uint]struct{}, len(invalidPlanIDs))
|
invalidPlanIDSet := make(map[uint32]struct{}, len(invalidPlanIDs))
|
||||||
for _, id := range invalidPlanIDs {
|
for _, id := range invalidPlanIDs {
|
||||||
invalidPlanIDSet[id] = struct{}{}
|
invalidPlanIDSet[id] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tasksToDeleteIDs []uint
|
var tasksToDeleteIDs []uint32
|
||||||
var logsToCancelIDs []uint
|
var logsToCancelIDs []uint32
|
||||||
|
|
||||||
for _, pt := range allPendingTasks {
|
for _, pt := range allPendingTasks {
|
||||||
if pt.Task == nil { // 防御性编程,确保 Task 被预加载
|
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 {
|
func (m *analysisPlanTaskManagerImpl) addOrUpdateTriggers(ctx context.Context, runnablePlans []*models.Plan, allPendingTasks []models.PendingTask) error {
|
||||||
managerCtx, logger := logs.Trace(ctx, m.ctx, "addOrUpdateTriggers")
|
managerCtx, logger := logs.Trace(ctx, m.ctx, "addOrUpdateTriggers")
|
||||||
// 创建一个映射,存放所有已在队列中的计划触发器
|
// 创建一个映射,存放所有已在队列中的计划触发器
|
||||||
pendingTriggersMap := make(map[uint]models.PendingTask)
|
pendingTriggersMap := make(map[uint32]models.PendingTask)
|
||||||
for _, pt := range allPendingTasks {
|
for _, pt := range allPendingTasks {
|
||||||
if pt.Task != nil && pt.Task.Type == models.TaskPlanAnalysis {
|
if pt.Task != nil && pt.Task.Type == models.TaskPlanAnalysis {
|
||||||
pendingTriggersMap[pt.Task.PlanID] = pt
|
pendingTriggersMap[pt.Task.PlanID] = pt
|
||||||
|
|||||||
@@ -26,20 +26,20 @@ type ExecutionManager interface {
|
|||||||
type ProgressTracker struct {
|
type ProgressTracker struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
cond *sync.Cond // 用于实现阻塞锁
|
cond *sync.Cond // 用于实现阻塞锁
|
||||||
runningPlans map[uint]bool // key: planExecutionLogID, value: true (用作内存锁)
|
runningPlans map[uint32]bool // key: planExecutionLogID, value: true (用作内存锁)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProgressTracker 创建一个新的进度跟踪器
|
// NewProgressTracker 创建一个新的进度跟踪器
|
||||||
func NewProgressTracker() *ProgressTracker {
|
func NewProgressTracker() *ProgressTracker {
|
||||||
t := &ProgressTracker{
|
t := &ProgressTracker{
|
||||||
runningPlans: make(map[uint]bool),
|
runningPlans: make(map[uint32]bool),
|
||||||
}
|
}
|
||||||
t.cond = sync.NewCond(&t.mu)
|
t.cond = sync.NewCond(&t.mu)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// TryLock (非阻塞) 尝试锁定一个计划。如果计划未被锁定,则锁定并返回 true。
|
// TryLock (非阻塞) 尝试锁定一个计划。如果计划未被锁定,则锁定并返回 true。
|
||||||
func (t *ProgressTracker) TryLock(planLogID uint) bool {
|
func (t *ProgressTracker) TryLock(planLogID uint32) bool {
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
if t.runningPlans[planLogID] {
|
if t.runningPlans[planLogID] {
|
||||||
@@ -50,7 +50,7 @@ func (t *ProgressTracker) TryLock(planLogID uint) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lock (阻塞) 获取一个计划的执行锁。如果锁已被占用,则会一直等待直到锁被释放。
|
// Lock (阻塞) 获取一个计划的执行锁。如果锁已被占用,则会一直等待直到锁被释放。
|
||||||
func (t *ProgressTracker) Lock(planLogID uint) {
|
func (t *ProgressTracker) Lock(planLogID uint32) {
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
// 当计划正在运行时,调用 t.cond.Wait() 会原子地解锁 mu 并挂起当前协程。
|
// 当计划正在运行时,调用 t.cond.Wait() 会原子地解锁 mu 并挂起当前协程。
|
||||||
// 当被唤醒时,它会重新锁定 mu 并再次检查循环条件。
|
// 当被唤醒时,它会重新锁定 mu 并再次检查循环条件。
|
||||||
@@ -63,7 +63,7 @@ func (t *ProgressTracker) Lock(planLogID uint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unlock 解锁一个计划,并唤醒所有正在等待此锁的协程。
|
// Unlock 解锁一个计划,并唤醒所有正在等待此锁的协程。
|
||||||
func (t *ProgressTracker) Unlock(planLogID uint) {
|
func (t *ProgressTracker) Unlock(planLogID uint32) {
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
delete(t.runningPlans, planLogID)
|
delete(t.runningPlans, planLogID)
|
||||||
@@ -72,10 +72,10 @@ func (t *ProgressTracker) Unlock(planLogID uint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRunningPlanIDs 获取当前所有正在执行的计划ID列表
|
// GetRunningPlanIDs 获取当前所有正在执行的计划ID列表
|
||||||
func (t *ProgressTracker) GetRunningPlanIDs() []uint {
|
func (t *ProgressTracker) GetRunningPlanIDs() []uint32 {
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
ids := make([]uint, 0, len(t.runningPlans))
|
ids := make([]uint32, 0, len(t.runningPlans))
|
||||||
for id := range t.runningPlans {
|
for id := range t.runningPlans {
|
||||||
ids = append(ids, id)
|
ids = append(ids, id)
|
||||||
}
|
}
|
||||||
@@ -214,7 +214,7 @@ func (s *planExecutionManagerImpl) claimAndSubmit(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleRequeue 同步地、安全地将一个无法立即执行的任务放回队列。
|
// 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")
|
managerCtx, logger := logs.Trace(ctx, s.ctx, "handleRequeue")
|
||||||
logger.Warnf("计划 %d 正在执行,任务 %d (TaskID: %d) 将等待并重新入队...", planExecutionLogID, taskToRequeue.ID, taskToRequeue.TaskID)
|
logger.Warnf("计划 %d 正在执行,任务 %d (TaskID: %d) 将等待并重新入队...", planExecutionLogID, taskToRequeue.ID, taskToRequeue.TaskID)
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ func (s *planExecutionManagerImpl) analysisPlan(ctx context.Context, claimedLog
|
|||||||
// 创建Plan执行记录
|
// 创建Plan执行记录
|
||||||
// 从任务的 Parameters 中解析出真实的 PlanID
|
// 从任务的 Parameters 中解析出真实的 PlanID
|
||||||
var params struct {
|
var params struct {
|
||||||
PlanID uint `json:"plan_id"`
|
PlanID uint32 `json:"plan_id"`
|
||||||
}
|
}
|
||||||
if err := claimedLog.Task.ParseParameters(¶ms); err != nil {
|
if err := claimedLog.Task.ParseParameters(¶ms); err != nil {
|
||||||
logger.Errorf("解析任务参数中的计划ID失败,日志ID: %d, 错误: %v", claimedLog.ID, err)
|
logger.Errorf("解析任务参数中的计划ID失败,日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||||
@@ -390,7 +390,7 @@ func (s *planExecutionManagerImpl) updateTaskExecutionLogStatus(ctx context.Cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handlePlanTermination 集中处理计划的终止逻辑(失败或取消)
|
// 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")
|
managerCtx, logger := logs.Trace(ctx, s.ctx, "handlePlanTermination")
|
||||||
// 1. 从待执行队列中删除所有相关的子任务
|
// 1. 从待执行队列中删除所有相关的子任务
|
||||||
if err := s.pendingTaskRepo.DeletePendingTasksByPlanLogID(managerCtx, planLogID); err != nil {
|
if err := s.pendingTaskRepo.DeletePendingTasksByPlanLogID(managerCtx, planLogID); err != nil {
|
||||||
@@ -434,7 +434,7 @@ func (s *planExecutionManagerImpl) handlePlanTermination(ctx context.Context, pl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handlePlanCompletion 集中处理计划成功完成后的所有逻辑
|
// 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")
|
managerCtx, logger := logs.Trace(ctx, s.ctx, "handlePlanCompletion")
|
||||||
logger.Infof("计划执行 %d 的所有任务已完成,开始处理计划完成逻辑...", planLogID)
|
logger.Infof("计划执行 %d 的所有任务已完成,开始处理计划完成逻辑...", planLogID)
|
||||||
|
|
||||||
|
|||||||
@@ -41,17 +41,17 @@ type Service interface {
|
|||||||
// CreatePlan 创建一个新的计划
|
// CreatePlan 创建一个新的计划
|
||||||
CreatePlan(ctx context.Context, plan *models.Plan) (*models.Plan, error)
|
CreatePlan(ctx context.Context, plan *models.Plan) (*models.Plan, error)
|
||||||
// GetPlanByID 根据ID获取计划详情
|
// GetPlanByID 根据ID获取计划详情
|
||||||
GetPlanByID(ctx context.Context, id uint) (*models.Plan, error)
|
GetPlanByID(ctx context.Context, id uint32) (*models.Plan, error)
|
||||||
// ListPlans 获取计划列表,支持过滤和分页
|
// ListPlans 获取计划列表,支持过滤和分页
|
||||||
ListPlans(ctx context.Context, opts repository.ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error)
|
ListPlans(ctx context.Context, opts repository.ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error)
|
||||||
// UpdatePlan 更新计划
|
// UpdatePlan 更新计划, wantPlanType 表示期望被修改的计划是什么类型
|
||||||
UpdatePlan(ctx context.Context, plan *models.Plan) (*models.Plan, error)
|
UpdatePlan(ctx context.Context, plan *models.Plan, wantPlanType models.PlanType) (*models.Plan, error)
|
||||||
// DeletePlan 删除计划(软删除)
|
// DeletePlan 删除计划(软删除)
|
||||||
DeletePlan(ctx context.Context, id uint) error
|
DeletePlan(ctx context.Context, id uint32) error
|
||||||
// StartPlan 启动计划
|
// StartPlan 启动计划
|
||||||
StartPlan(ctx context.Context, id uint) error
|
StartPlan(ctx context.Context, id uint32) error
|
||||||
// StopPlan 停止计划
|
// StopPlan 停止计划
|
||||||
StopPlan(ctx context.Context, id uint) error
|
StopPlan(ctx context.Context, id uint32) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// planServiceImpl 是 Service 接口的具体实现。
|
// planServiceImpl 是 Service 接口的具体实现。
|
||||||
@@ -150,7 +150,7 @@ func (s *planServiceImpl) CreatePlan(ctx context.Context, planToCreate *models.P
|
|||||||
// 优化:无需查询完整的设备对象,只需构建包含ID的结构体即可建立关联
|
// 优化:无需查询完整的设备对象,只需构建包含ID的结构体即可建立关联
|
||||||
devices := make([]models.Device, len(deviceIDs))
|
devices := make([]models.Device, len(deviceIDs))
|
||||||
for i, id := range 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
|
taskModel.Devices = devices
|
||||||
}
|
}
|
||||||
@@ -174,7 +174,7 @@ func (s *planServiceImpl) CreatePlan(ctx context.Context, planToCreate *models.P
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPlanByID 根据ID获取计划详情
|
// 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")
|
planCtx, logger := logs.Trace(ctx, s.ctx, "GetPlanByID")
|
||||||
const actionType = "领域层:获取计划详情"
|
const actionType = "领域层:获取计划详情"
|
||||||
|
|
||||||
@@ -207,8 +207,8 @@ func (s *planServiceImpl) ListPlans(ctx context.Context, opts repository.ListPla
|
|||||||
return plans, total, nil
|
return plans, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePlan 更新计划
|
// UpdatePlan 更新计划, wantPlanType 表示期望被修改的计划是什么类型
|
||||||
func (s *planServiceImpl) UpdatePlan(ctx context.Context, planToUpdate *models.Plan) (*models.Plan, error) {
|
func (s *planServiceImpl) UpdatePlan(ctx context.Context, planToUpdate *models.Plan, wantPlanType models.PlanType) (*models.Plan, error) {
|
||||||
planCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePlan")
|
planCtx, logger := logs.Trace(ctx, s.ctx, "UpdatePlan")
|
||||||
const actionType = "领域层:更新计划"
|
const actionType = "领域层:更新计划"
|
||||||
|
|
||||||
@@ -222,9 +222,8 @@ func (s *planServiceImpl) UpdatePlan(ctx context.Context, planToUpdate *models.P
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 系统计划不允许修改
|
if existingPlan.PlanType != wantPlanType {
|
||||||
if existingPlan.PlanType == models.PlanTypeSystem {
|
logger.Warnf("%s: 禁止修改 %v 类型计划, ID: %d", actionType, wantPlanType, planToUpdate.ID)
|
||||||
logger.Warnf("%s: 尝试修改系统计划, ID: %d", actionType, planToUpdate.ID)
|
|
||||||
return nil, ErrPlanCannotBeModified
|
return nil, ErrPlanCannotBeModified
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +262,7 @@ func (s *planServiceImpl) UpdatePlan(ctx context.Context, planToUpdate *models.P
|
|||||||
// 优化:无需查询完整的设备对象,只需构建包含ID的结构体即可建立关联
|
// 优化:无需查询完整的设备对象,只需构建包含ID的结构体即可建立关联
|
||||||
devices := make([]models.Device, len(deviceIDs))
|
devices := make([]models.Device, len(deviceIDs))
|
||||||
for i, id := range 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
|
taskModel.Devices = devices
|
||||||
}
|
}
|
||||||
@@ -291,7 +290,7 @@ func (s *planServiceImpl) UpdatePlan(ctx context.Context, planToUpdate *models.P
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeletePlan 删除计划(软删除)
|
// 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")
|
planCtx, logger := logs.Trace(ctx, s.ctx, "DeletePlan")
|
||||||
const actionType = "领域层:删除计划"
|
const actionType = "领域层:删除计划"
|
||||||
|
|
||||||
@@ -329,7 +328,7 @@ func (s *planServiceImpl) DeletePlan(ctx context.Context, id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartPlan 启动计划
|
// 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")
|
planCtx, logger := logs.Trace(ctx, s.ctx, "StartPlan")
|
||||||
const actionType = "领域层:启动计划"
|
const actionType = "领域层:启动计划"
|
||||||
|
|
||||||
@@ -384,7 +383,7 @@ func (s *planServiceImpl) StartPlan(ctx context.Context, id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StopPlan 停止计划
|
// 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")
|
planCtx, logger := logs.Trace(ctx, s.ctx, "StopPlan")
|
||||||
const actionType = "领域层:停止计划"
|
const actionType = "领域层:停止计划"
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ type Task interface {
|
|||||||
// TaskDeviceIDResolver 定义了从任务配置中解析设备ID的方法
|
// TaskDeviceIDResolver 定义了从任务配置中解析设备ID的方法
|
||||||
type TaskDeviceIDResolver interface {
|
type TaskDeviceIDResolver interface {
|
||||||
// ResolveDeviceIDs 从任务配置中解析并返回所有关联的设备ID列表
|
// ResolveDeviceIDs 从任务配置中解析并返回所有关联的设备ID列表
|
||||||
// 返回值: uint数组,每个字符串代表一个设备ID
|
// 返回值: uint32数组,每个字符串代表一个设备ID
|
||||||
ResolveDeviceIDs(ctx context.Context) ([]uint, error)
|
ResolveDeviceIDs(ctx context.Context) ([]uint32, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaskFactory 是一个工厂接口,用于根据任务执行日志创建任务实例。
|
// TaskFactory 是一个工厂接口,用于根据任务执行日志创建任务实例。
|
||||||
|
|||||||
138
internal/domain/task/alarm_notification_task.go
Normal file
138
internal/domain/task/alarm_notification_task.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"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 的参数结构
|
||||||
|
// 如果用户没有指定某个等级的配置, 则默认为该等级消息只发送一次
|
||||||
|
type AlarmNotificationTaskParams struct {
|
||||||
|
// NotificationIntervals 告警通知的发送间隔时间,键为告警等级,值为时间间隔(分钟)
|
||||||
|
NotificationIntervals map[models.SeverityLevel]uint32 `json:"notification_intervals"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlarmNotificationTask 告警通知发送任务
|
||||||
|
type AlarmNotificationTask struct {
|
||||||
|
ctx context.Context
|
||||||
|
taskLog *models.TaskExecutionLog
|
||||||
|
|
||||||
|
// alarmNotificationTaskParams 是任务配置
|
||||||
|
alarmNotificationTaskParams AlarmNotificationTaskParams
|
||||||
|
|
||||||
|
onceParse sync.Once // 保证解析参数只执行一次
|
||||||
|
|
||||||
|
notificationService notify_domain.Service
|
||||||
|
alarmRepository repository.AlarmRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAlarmNotificationTask 创建一个新的告警通知发送任务实例
|
||||||
|
func NewAlarmNotificationTask(ctx context.Context, taskLog *models.TaskExecutionLog, service notify_domain.Service, alarmRepository repository.AlarmRepository) plan.Task {
|
||||||
|
return &AlarmNotificationTask{
|
||||||
|
ctx: ctx,
|
||||||
|
taskLog: taskLog,
|
||||||
|
alarmRepository: alarmRepository,
|
||||||
|
notificationService: service,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute 执行告警通知发送任务
|
||||||
|
func (t *AlarmNotificationTask) Execute(ctx context.Context) error {
|
||||||
|
taskCtx, logger := logs.Trace(ctx, t.ctx, "Execute")
|
||||||
|
logger.Infof("开始执行告警通知发送任务, 任务ID: %d", t.taskLog.TaskID)
|
||||||
|
|
||||||
|
if err := t.parseParameters(taskCtx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取是否有待发送告警通知, 用于优化性能
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveDeviceIDs 从任务配置中解析并返回所有关联的设备ID列表
|
||||||
|
func (t *AlarmNotificationTask) ResolveDeviceIDs(ctx context.Context) ([]uint32, error) {
|
||||||
|
// 告警通知任务与设备无关
|
||||||
|
return []uint32{}, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
t.alarmNotificationTaskParams = params
|
||||||
|
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
166
internal/domain/task/area_threshold_check_task.go
Normal file
166
internal/domain/task/area_threshold_check_task.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
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 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 []uint32 `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[uint32]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,
|
||||||
|
Level: a.params.Level,
|
||||||
|
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) ([]uint32, 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.Level == "" {
|
||||||
|
params.Level = models.WarnLevel
|
||||||
|
}
|
||||||
|
if params.ExcludeDeviceIDs == nil {
|
||||||
|
params.ExcludeDeviceIDs = []uint32{}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.params = params
|
||||||
|
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DelayTaskParams struct {
|
type DelayTaskParams struct {
|
||||||
DelayDuration float64 `json:"delay_duration"`
|
DelayDuration float32 `json:"delay_duration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DelayTask 是一个用于模拟延迟的 Task 实现
|
// DelayTask 是一个用于模拟延迟的 Task 实现
|
||||||
@@ -70,6 +70,6 @@ func (d *DelayTask) OnFailure(ctx context.Context, executeErr error) {
|
|||||||
logger.Errorf("任务 %v: 执行失败: %v", d.executionTask.TaskID, executeErr)
|
logger.Errorf("任务 %v: 执行失败: %v", d.executionTask.TaskID, executeErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DelayTask) ResolveDeviceIDs(ctx context.Context) ([]uint, error) {
|
func (d *DelayTask) ResolveDeviceIDs(ctx context.Context) ([]uint32, error) {
|
||||||
return []uint{}, nil
|
return []uint32{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
197
internal/domain/task/device_threshold_check_task.go
Normal file
197
internal/domain/task/device_threshold_check_task.go
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
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 DeviceThresholdCheckParams struct {
|
||||||
|
DeviceID uint32 `json:"device_id"` // 设备ID
|
||||||
|
SensorType models.SensorType `json:"sensor_type"` // 传感器类型
|
||||||
|
Thresholds float32 `json:"thresholds"` // 阈值
|
||||||
|
Operator models.Operator `json:"operator"` // 操作符
|
||||||
|
Level models.SeverityLevel `json:"level"` // 告警等级
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceThresholdCheckTask 是一个任务,用于检查设备传感器数据是否满足阈值条件。
|
||||||
|
type DeviceThresholdCheckTask struct {
|
||||||
|
ctx context.Context
|
||||||
|
onceParse sync.Once
|
||||||
|
|
||||||
|
taskLog *models.TaskExecutionLog
|
||||||
|
params DeviceThresholdCheckParams
|
||||||
|
|
||||||
|
sensorDataRepo repository.SensorDataRepository
|
||||||
|
alarmService alarm.AlarmService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeviceThresholdCheckTask(ctx context.Context, taskLog *models.TaskExecutionLog, sensorDataRepo repository.SensorDataRepository, alarmService alarm.AlarmService) plan.Task {
|
||||||
|
return &DeviceThresholdCheckTask{
|
||||||
|
ctx: ctx,
|
||||||
|
taskLog: taskLog,
|
||||||
|
sensorDataRepo: sensorDataRepo,
|
||||||
|
alarmService: alarmService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DeviceThresholdCheckTask) Execute(ctx context.Context) error {
|
||||||
|
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 float32
|
||||||
|
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: d.params.Level,
|
||||||
|
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 float32, operator models.Operator, threshold float32) bool {
|
||||||
|
switch operator {
|
||||||
|
case models.OperatorLessThan:
|
||||||
|
return currentValue < threshold
|
||||||
|
case models.OperatorLessThanOrEqualTo:
|
||||||
|
return currentValue <= threshold
|
||||||
|
case models.OperatorGreaterThan:
|
||||||
|
return currentValue > threshold
|
||||||
|
case models.OperatorGreaterThanOrEqualTo:
|
||||||
|
return currentValue >= threshold
|
||||||
|
case models.OperatorEqualTo:
|
||||||
|
return currentValue == threshold
|
||||||
|
case models.OperatorNotEqualTo:
|
||||||
|
return currentValue != threshold
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if params.Level == "" {
|
||||||
|
params.Level = models.WarnLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
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) ([]uint32, error) {
|
||||||
|
taskCtx := logs.AddFuncName(ctx, d.ctx, "ResolveDeviceIDs")
|
||||||
|
if err := d.parseParameters(taskCtx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []uint32{d.params.DeviceID}, nil
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ func (t *FullCollectionTask) Execute(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sensorsByController := make(map[uint][]*models.Device)
|
sensorsByController := make(map[uint32][]*models.Device)
|
||||||
for _, sensor := range sensors {
|
for _, sensor := range sensors {
|
||||||
sensorsByController[sensor.AreaControllerID] = append(sensorsByController[sensor.AreaControllerID], sensor)
|
sensorsByController[sensor.AreaControllerID] = append(sensorsByController[sensor.AreaControllerID], sensor)
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ func (t *FullCollectionTask) OnFailure(ctx context.Context, executeErr error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResolveDeviceIDs 获取当前任务需要使用的设备ID列表
|
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package task
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -16,9 +15,9 @@ import (
|
|||||||
|
|
||||||
// ReleaseFeedWeightTaskParams 定义了 ReleaseFeedWeightTask 的参数结构
|
// ReleaseFeedWeightTaskParams 定义了 ReleaseFeedWeightTask 的参数结构
|
||||||
type ReleaseFeedWeightTaskParams struct {
|
type ReleaseFeedWeightTaskParams struct {
|
||||||
ReleaseWeight float64 `json:"release_weight"` // 需要释放的重量
|
ReleaseWeight float32 `json:"release_weight"` // 需要释放的重量
|
||||||
FeedPortDeviceID uint `json:"feed_port_device_id"` // 下料口ID
|
FeedPortDeviceID uint32 `json:"feed_port_device_id"` // 下料口ID
|
||||||
MixingTankDeviceID uint `json:"mixing_tank_device_id"` // 称重传感器ID
|
MixingTankDeviceID uint32 `json:"mixing_tank_device_id"` // 称重传感器ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReleaseFeedWeightTask 是一个控制下料口释放指定重量的任务
|
// ReleaseFeedWeightTask 是一个控制下料口释放指定重量的任务
|
||||||
@@ -30,8 +29,8 @@ type ReleaseFeedWeightTask struct {
|
|||||||
claimedLog *models.TaskExecutionLog
|
claimedLog *models.TaskExecutionLog
|
||||||
|
|
||||||
feedPortDevice *models.Device
|
feedPortDevice *models.Device
|
||||||
releaseWeight float64
|
releaseWeight float32
|
||||||
mixingTankDeviceID uint
|
mixingTankDeviceID uint32
|
||||||
|
|
||||||
feedPort device.Service
|
feedPort device.Service
|
||||||
|
|
||||||
@@ -101,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")
|
taskCtx, logger := logs.Trace(ctx, r.ctx, "getNowWeight")
|
||||||
sensorData, err := r.sensorDataRepo.GetLatestSensorDataByDeviceIDAndSensorType(taskCtx, r.mixingTankDeviceID, models.SensorTypeWeight)
|
sensorData, err := r.sensorDataRepo.GetLatestSensorDataByDeviceIDAndSensorType(taskCtx, r.mixingTankDeviceID, models.SensorTypeWeight)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -114,7 +113,7 @@ func (r *ReleaseFeedWeightTask) getNowWeight(ctx context.Context) (float64, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
wg := &models.WeightData{}
|
wg := &models.WeightData{}
|
||||||
err = json.Unmarshal(sensorData.Data, wg)
|
err = sensorData.ParseData(wg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("反序列化设备 %v 最新传感器数据失败: %v , 日志ID: %v", r.mixingTankDeviceID, err, r.claimedLog.ID)
|
logger.Errorf("反序列化设备 %v 最新传感器数据失败: %v , 日志ID: %v", r.mixingTankDeviceID, err, r.claimedLog.ID)
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -179,10 +178,10 @@ func (r *ReleaseFeedWeightTask) OnFailure(ctx context.Context, executeErr error)
|
|||||||
logger.Errorf("善后处理完成, 日志ID:%v", r.claimedLog.ID)
|
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")
|
taskCtx := logs.AddFuncName(ctx, r.ctx, "ResolveDeviceIDs")
|
||||||
if err := r.parseParameters(taskCtx); err != nil {
|
if err := r.parseParameters(taskCtx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return []uint{r.feedPortDevice.ID, r.mixingTankDeviceID}, nil
|
return []uint32{r.feedPortDevice.ID, r.mixingTankDeviceID}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"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/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/domain/plan"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"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/models"
|
||||||
@@ -15,26 +17,38 @@ const (
|
|||||||
CompNameDelayTask = "DelayTask"
|
CompNameDelayTask = "DelayTask"
|
||||||
CompNameReleaseFeedWeight = "ReleaseFeedWeightTask"
|
CompNameReleaseFeedWeight = "ReleaseFeedWeightTask"
|
||||||
CompNameFullCollectionTask = "FullCollectionTask"
|
CompNameFullCollectionTask = "FullCollectionTask"
|
||||||
|
CompNameAlarmNotification = "AlarmNotificationTask"
|
||||||
)
|
)
|
||||||
|
|
||||||
type taskFactory struct {
|
type taskFactory struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
sensorDataRepo repository.SensorDataRepository
|
sensorDataRepo repository.SensorDataRepository
|
||||||
deviceRepo repository.DeviceRepository
|
deviceRepo repository.DeviceRepository
|
||||||
|
alarmRepo repository.AlarmRepository
|
||||||
|
|
||||||
deviceService device.Service
|
deviceService device.Service
|
||||||
|
notificationService notify.Service
|
||||||
|
alarmService alarm.AlarmService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTaskFactory(
|
func NewTaskFactory(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
sensorDataRepo repository.SensorDataRepository,
|
sensorDataRepo repository.SensorDataRepository,
|
||||||
deviceRepo repository.DeviceRepository,
|
deviceRepo repository.DeviceRepository,
|
||||||
|
alarmRepo repository.AlarmRepository,
|
||||||
deviceService device.Service,
|
deviceService device.Service,
|
||||||
|
notifyService notify.Service,
|
||||||
|
alarmService alarm.AlarmService,
|
||||||
) plan.TaskFactory {
|
) plan.TaskFactory {
|
||||||
return &taskFactory{
|
return &taskFactory{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
sensorDataRepo: sensorDataRepo,
|
sensorDataRepo: sensorDataRepo,
|
||||||
deviceRepo: deviceRepo,
|
deviceRepo: deviceRepo,
|
||||||
|
alarmRepo: alarmRepo,
|
||||||
deviceService: deviceService,
|
deviceService: deviceService,
|
||||||
|
notificationService: notifyService,
|
||||||
|
alarmService: alarmService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +62,12 @@ func (t *taskFactory) Production(ctx context.Context, claimedLog *models.TaskExe
|
|||||||
return NewReleaseFeedWeightTask(logs.AddCompName(baseCtx, CompNameReleaseFeedWeight), claimedLog, t.sensorDataRepo, t.deviceRepo, t.deviceService)
|
return NewReleaseFeedWeightTask(logs.AddCompName(baseCtx, CompNameReleaseFeedWeight), claimedLog, t.sensorDataRepo, t.deviceRepo, t.deviceService)
|
||||||
case models.TaskTypeFullCollection:
|
case models.TaskTypeFullCollection:
|
||||||
return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), claimedLog, t.deviceRepo, t.deviceService)
|
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:
|
default:
|
||||||
// TODO 这里直接panic合适吗? 不过这个场景确实不该出现任何异常的任务类型
|
// TODO 这里直接panic合适吗? 不过这个场景确实不该出现任何异常的任务类型
|
||||||
logger.Panicf("不支持的任务类型: %s", claimedLog.Task.Type)
|
logger.Panicf("不支持的任务类型: %s", claimedLog.Task.Type)
|
||||||
@@ -75,6 +95,12 @@ func (t *taskFactory) CreateTaskFromModel(ctx context.Context, taskModel *models
|
|||||||
), nil
|
), nil
|
||||||
case models.TaskTypeFullCollection:
|
case models.TaskTypeFullCollection:
|
||||||
return NewFullCollectionTask(logs.AddCompName(baseCtx, CompNameFullCollectionTask), tempLog, t.deviceRepo, t.deviceService), nil
|
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:
|
default:
|
||||||
return nil, fmt.Errorf("不支持为类型 '%s' 的任务创建模型实例", taskModel.Type)
|
return nil, fmt.Errorf("不支持为类型 '%s' 的任务创建模型实例", taskModel.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ type Config struct {
|
|||||||
|
|
||||||
// Collection 定时采集配置
|
// Collection 定时采集配置
|
||||||
Collection CollectionConfig `yaml:"collection"`
|
Collection CollectionConfig `yaml:"collection"`
|
||||||
|
|
||||||
|
// AlarmNotification 告警通知配置
|
||||||
|
AlarmNotification AlarmNotificationConfig `yaml:"alarm_notification"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppConfig 代表应用基础配置
|
// AppConfig 代表应用基础配置
|
||||||
@@ -204,6 +207,29 @@ type CollectionConfig struct {
|
|||||||
Interval int `yaml:"interval"`
|
Interval int `yaml:"interval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NotificationIntervalsConfig struct {
|
||||||
|
// DebugIntervalMinutes Debug级别告警的通知间隔(分钟)
|
||||||
|
DebugIntervalMinutes uint32 `yaml:"debug"`
|
||||||
|
// InfoIntervalMinutes Info级别告警的通知间隔(分钟)
|
||||||
|
InfoIntervalMinutes uint32 `yaml:"info"`
|
||||||
|
// WarnIntervalMinutes Warn级别告警的通知间隔(分钟)
|
||||||
|
WarnIntervalMinutes uint32 `yaml:"warn"`
|
||||||
|
// ErrorIntervalMinutes Error级别告警的通知间隔(分钟)
|
||||||
|
ErrorIntervalMinutes uint32 `yaml:"error"`
|
||||||
|
// DPanicIntervalMinutes DPanic级别告警的通知间隔(分钟)
|
||||||
|
DPanicIntervalMinutes uint32 `yaml:"dpanic"`
|
||||||
|
// PanicIntervalMinutes Panic级别告警的通知间隔(分钟)
|
||||||
|
PanicIntervalMinutes uint32 `yaml:"panic"`
|
||||||
|
// FatalIntervalMinutes Fatal级别告警的通知间隔(分钟)
|
||||||
|
FatalIntervalMinutes uint32 `yaml:"fatal"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlarmNotificationConfig 告警通知配置
|
||||||
|
type AlarmNotificationConfig struct {
|
||||||
|
// NotificationIntervals 告警通知的发送间隔时间,键为告警等级,值为时间间隔(秒)
|
||||||
|
NotificationIntervals NotificationIntervalsConfig `yaml:"notification_intervals"`
|
||||||
|
}
|
||||||
|
|
||||||
// NewConfig 创建并返回一个新的配置实例
|
// NewConfig 创建并返回一个新的配置实例
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
// 默认值可以在这里设置,但我们优先使用配置文件中的值
|
// 默认值可以在这里设置,但我们优先使用配置文件中的值
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ func (ps *PostgresStorage) creatingHyperTable(ctx context.Context) error {
|
|||||||
{models.PigPurchase{}, "purchase_date"},
|
{models.PigPurchase{}, "purchase_date"},
|
||||||
{models.PigSale{}, "sale_date"},
|
{models.PigSale{}, "sale_date"},
|
||||||
{models.Notification{}, "alarm_timestamp"},
|
{models.Notification{}, "alarm_timestamp"},
|
||||||
|
{models.HistoricalAlarm{}, "trigger_time"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, table := range tablesToConvert {
|
for _, table := range tablesToConvert {
|
||||||
@@ -221,6 +222,7 @@ func (ps *PostgresStorage) applyCompressionPolicies(ctx context.Context) error {
|
|||||||
{models.PigPurchase{}, "pig_batch_id"},
|
{models.PigPurchase{}, "pig_batch_id"},
|
||||||
{models.PigSale{}, "pig_batch_id"},
|
{models.PigSale{}, "pig_batch_id"},
|
||||||
{models.Notification{}, "user_id"},
|
{models.Notification{}, "user_id"},
|
||||||
|
{models.HistoricalAlarm{}, "source_id"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, policy := range policies {
|
for _, policy := range policies {
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ func (g *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql
|
|||||||
fields := []interface{}{
|
fields := []interface{}{
|
||||||
"sql", sql,
|
"sql", sql,
|
||||||
"rows", rows,
|
"rows", rows,
|
||||||
"elapsed", fmt.Sprintf("%.3fms", float64(elapsed.Nanoseconds())/1e6),
|
"elapsed", fmt.Sprintf("%.3fms", float32(elapsed.Nanoseconds())/1e6),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 附加调用链信息
|
// 附加调用链信息
|
||||||
|
|||||||
107
internal/infra/models/alarm.go
Normal file
107
internal/infra/models/alarm.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AlarmSourceType 定义了告警的来源类型
|
||||||
|
type AlarmSourceType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AlarmSourceTypeDevice AlarmSourceType = "普通设备"
|
||||||
|
AlarmSourceTypeAreaController AlarmSourceType = "区域主控"
|
||||||
|
AlarmSourceTypeSystem AlarmSourceType = "系统"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AlarmCode 定义了标准化的告警类型标识
|
||||||
|
type AlarmCode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// --- 设备相关告警 ---
|
||||||
|
AlarmCodeTemperature AlarmCode = "温度阈值"
|
||||||
|
AlarmCodeHumidity AlarmCode = "湿度阈值"
|
||||||
|
AlarmCodeWeight AlarmCode = "重量阈值"
|
||||||
|
AlarmCodeBatteryLevel AlarmCode = "电池电量阈值"
|
||||||
|
AlarmCodeSignalMetrics AlarmCode = "信号强度阈值"
|
||||||
|
AlarmCodeDeviceOffline AlarmCode = "设备离线"
|
||||||
|
|
||||||
|
// --- 区域主控相关告警 ---
|
||||||
|
AlarmCodeAreaControllerOffline AlarmCode = "区域主控离线"
|
||||||
|
|
||||||
|
// --- 系统相关告警 ---
|
||||||
|
// (可在此处预留或添加)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Operator string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OperatorLessThan Operator = "<"
|
||||||
|
OperatorLessThanOrEqualTo Operator = "<="
|
||||||
|
OperatorGreaterThan Operator = ">"
|
||||||
|
OperatorGreaterThanOrEqualTo Operator = ">="
|
||||||
|
OperatorEqualTo Operator = "="
|
||||||
|
OperatorNotEqualTo Operator = "!="
|
||||||
|
)
|
||||||
|
|
||||||
|
// ActiveAlarm 活跃告警
|
||||||
|
// 活跃告警会被更新(如忽略状态),因此保留 Model 以包含所有标准字段。
|
||||||
|
type ActiveAlarm struct {
|
||||||
|
Model
|
||||||
|
|
||||||
|
SourceType AlarmSourceType `gorm:"type:varchar(50);not null;index:idx_alarm_uniqueness;comment:告警来源类型" json:"source_type"`
|
||||||
|
// SourceID 告警来源ID,其具体含义取决于 SourceType 字段 (例如:设备ID, 区域主控ID, 猪栏ID)。
|
||||||
|
SourceID uint32 `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;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 字段加入到专为通知查询优化的复合索引中
|
||||||
|
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:"index:idx_notification_query;comment:上次发送通知时间" json:"last_notified_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定 ActiveAlarm 结构体对应的数据库表名
|
||||||
|
func (ActiveAlarm) TableName() string {
|
||||||
|
return "active_alarms"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HistoricalAlarm 历史告警
|
||||||
|
// 历史告警是不可变归档数据,我们移除 Model,并手动定义字段。
|
||||||
|
// ID 和 TriggerTime 共同构成联合主键,以满足 TimescaleDB 超表的要求。
|
||||||
|
type HistoricalAlarm struct {
|
||||||
|
// 手动定义主键,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 uint32 `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"`
|
||||||
|
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"`
|
||||||
|
|
||||||
|
// ResolvedBy 使用指针类型 *uint32 来表示可为空解决人, 当字段为空时表示系统自动解决的
|
||||||
|
ResolvedBy *uint32 `gorm:"comment:告警解决人" json:"resolved_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定 HistoricalAlarm 结构体对应的数据库表名
|
||||||
|
func (HistoricalAlarm) TableName() string {
|
||||||
|
return "historical_alarms"
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- Properties 结构体定义 ---
|
// --- Properties 结构体定义 ---
|
||||||
@@ -19,7 +18,7 @@ type Bus485Properties struct {
|
|||||||
|
|
||||||
// AreaController 是一个LoRa转总线(如485)的通信网关
|
// AreaController 是一个LoRa转总线(如485)的通信网关
|
||||||
type AreaController struct {
|
type AreaController struct {
|
||||||
gorm.Model
|
Model
|
||||||
|
|
||||||
// Name 是主控的业务名称,例如 "1号猪舍主控"
|
// Name 是主控的业务名称,例如 "1号猪舍主控"
|
||||||
Name string `gorm:"not null;unique" json:"name"`
|
Name string `gorm:"not null;unique" json:"name"`
|
||||||
@@ -53,20 +52,20 @@ func (AreaController) TableName() string {
|
|||||||
|
|
||||||
// Device 代表系统中的所有普通设备
|
// Device 代表系统中的所有普通设备
|
||||||
type Device struct {
|
type Device struct {
|
||||||
// gorm.Model 内嵌了标准模型字段 (ID, CreatedAt, UpdatedAt, DeletedAt)
|
// Model 内嵌了标准模型字段 (ID, CreatedAt, UpdatedAt, DeletedAt)
|
||||||
gorm.Model
|
Model
|
||||||
|
|
||||||
// Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器"
|
// Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器"
|
||||||
Name string `gorm:"not null" json:"name"`
|
Name string `gorm:"not null" json:"name"`
|
||||||
|
|
||||||
// DeviceTemplateID 是设备模板的外键
|
// DeviceTemplateID 是设备模板的外键
|
||||||
DeviceTemplateID uint `gorm:"not null;index" json:"device_template_id"`
|
DeviceTemplateID uint32 `gorm:"not null;index" json:"device_template_id"`
|
||||||
|
|
||||||
// DeviceTemplate 是设备的模板,包含了设备的通用信息
|
// DeviceTemplate 是设备的模板,包含了设备的通用信息
|
||||||
DeviceTemplate DeviceTemplate `json:"device_template"`
|
DeviceTemplate DeviceTemplate `json:"device_template"`
|
||||||
|
|
||||||
// AreaControllerID 是区域主控的外键
|
// AreaControllerID 是区域主控的外键
|
||||||
AreaControllerID uint `gorm:"not null;index" json:"area_controller_id"`
|
AreaControllerID uint32 `gorm:"not null;index" json:"area_controller_id"`
|
||||||
|
|
||||||
// AreaController 是设备所属的区域主控
|
// AreaController 是设备所属的区域主控
|
||||||
AreaController AreaController `json:"area_controller"`
|
AreaController AreaController `json:"area_controller"`
|
||||||
|
|||||||
@@ -5,10 +5,38 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/command_generater"
|
|
||||||
|
|
||||||
"gorm.io/datatypes"
|
"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 定义了设备模板的宽泛类别
|
// DeviceCategory 定义了设备模板的宽泛类别
|
||||||
@@ -25,8 +53,8 @@ const (
|
|||||||
// 它提供了必要的元数据,以便应用程序能够正确解释从设备读取的原始数据。
|
// 它提供了必要的元数据,以便应用程序能够正确解释从设备读取的原始数据。
|
||||||
type ValueDescriptor struct {
|
type ValueDescriptor struct {
|
||||||
Type SensorType `json:"type"`
|
Type SensorType `json:"type"`
|
||||||
Multiplier float64 `json:"multiplier"` // 乘数,用于原始数据转换
|
Multiplier float32 `json:"multiplier"` // 乘数,用于原始数据转换
|
||||||
Offset float64 `json:"offset"` // 偏移量,用于原始数据转换
|
Offset float32 `json:"offset"` // 偏移量,用于原始数据转换
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 指令结构体 (Command Structs) ---
|
// --- 指令结构体 (Command Structs) ---
|
||||||
@@ -51,7 +79,7 @@ func (sc *SwitchCommands) SelfCheck() error {
|
|||||||
// SensorCommands 定义了传感器读取指令所需的Modbus参数
|
// SensorCommands 定义了传感器读取指令所需的Modbus参数
|
||||||
type SensorCommands struct {
|
type SensorCommands struct {
|
||||||
// ModbusFunctionCode 记录Modbus功能码,例如 ReadHoldingRegisters。(一般是第二字节)
|
// ModbusFunctionCode 记录Modbus功能码,例如 ReadHoldingRegisters。(一般是第二字节)
|
||||||
ModbusFunctionCode command_generater.ModbusFunctionCode `json:"modbus_function_code"`
|
ModbusFunctionCode ModbusFunctionCode `json:"modbus_function_code"`
|
||||||
// ModbusStartAddress 记录Modbus寄存器的起始地址,用于生成指令。(一般是第三到四字节)
|
// ModbusStartAddress 记录Modbus寄存器的起始地址,用于生成指令。(一般是第三到四字节)
|
||||||
ModbusStartAddress uint16 `json:"modbus_start_address"`
|
ModbusStartAddress uint16 `json:"modbus_start_address"`
|
||||||
// ModbusQuantity 记录Modbus寄存器的数量,用于生成指令。(一般是五到六字节)
|
// ModbusQuantity 记录Modbus寄存器的数量,用于生成指令。(一般是五到六字节)
|
||||||
@@ -62,7 +90,7 @@ type SensorCommands struct {
|
|||||||
func (sc *SensorCommands) SelfCheck() error {
|
func (sc *SensorCommands) SelfCheck() error {
|
||||||
// 校验ModbusFunctionCode是否为读取类型
|
// 校验ModbusFunctionCode是否为读取类型
|
||||||
switch sc.ModbusFunctionCode {
|
switch sc.ModbusFunctionCode {
|
||||||
case command_generater.ReadCoils, command_generater.ReadDiscreteInputs, command_generater.ReadHoldingRegisters, command_generater.ReadInputRegisters:
|
case ReadCoils, ReadDiscreteInputs, ReadHoldingRegisters, ReadInputRegisters:
|
||||||
// 支持的读取功能码
|
// 支持的读取功能码
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("'sensor' 指令集 ModbusFunctionCode %X 无效或不是读取类型", sc.ModbusFunctionCode)
|
return fmt.Errorf("'sensor' 指令集 ModbusFunctionCode %X 无效或不是读取类型", sc.ModbusFunctionCode)
|
||||||
@@ -77,7 +105,7 @@ func (sc *SensorCommands) SelfCheck() error {
|
|||||||
|
|
||||||
// DeviceTemplate 代表一种物理设备的类型。
|
// DeviceTemplate 代表一种物理设备的类型。
|
||||||
type DeviceTemplate struct {
|
type DeviceTemplate struct {
|
||||||
gorm.Model
|
Model
|
||||||
|
|
||||||
// Name 是此模板的唯一名称, 例如 "FanModel-XYZ-2000" 或 "TempSensor-T1"
|
// Name 是此模板的唯一名称, 例如 "FanModel-XYZ-2000" 或 "TempSensor-T1"
|
||||||
Name string `gorm:"not null;unique" json:"name"`
|
Name string `gorm:"not null;unique" json:"name"`
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ const (
|
|||||||
// PlanExecutionLog 记录整个计划的一次执行历史
|
// PlanExecutionLog 记录整个计划的一次执行历史
|
||||||
|
|
||||||
type PlanExecutionLog struct {
|
type PlanExecutionLog struct {
|
||||||
ID uint `gorm:"primaryKey"`
|
ID uint32 `gorm:"primaryKey"`
|
||||||
CreatedAt time.Time `gorm:"primaryKey"` // 作为联合主键方便只查询热点数据
|
CreatedAt time.Time `gorm:"primaryKey"` // 作为联合主键方便只查询热点数据
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
|
||||||
PlanID uint `gorm:"index"`
|
PlanID uint32 `gorm:"index"`
|
||||||
Status ExecutionStatus
|
Status ExecutionStatus
|
||||||
StartedAt time.Time
|
StartedAt time.Time
|
||||||
EndedAt time.Time
|
EndedAt time.Time
|
||||||
@@ -46,12 +46,12 @@ func (PlanExecutionLog) TableName() string {
|
|||||||
|
|
||||||
// TaskExecutionLog 记录单个任务的一次执行历史
|
// TaskExecutionLog 记录单个任务的一次执行历史
|
||||||
type TaskExecutionLog struct {
|
type TaskExecutionLog struct {
|
||||||
ID uint `gorm:"primaryKey"`
|
ID uint32 `gorm:"primaryKey"`
|
||||||
CreatedAt time.Time `gorm:"primaryKey"` // 作为联合主键方便只查询热点数据
|
CreatedAt time.Time `gorm:"primaryKey"` // 作为联合主键方便只查询热点数据
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
|
||||||
PlanExecutionLogID uint `gorm:"index"` // 关联到某次计划执行
|
PlanExecutionLogID uint32 `gorm:"index"` // 关联到某次计划执行
|
||||||
|
|
||||||
// TaskID 使用 int 类型以容纳特殊的负数ID,代表系统任务
|
// TaskID 使用 int 类型以容纳特殊的负数ID,代表系统任务
|
||||||
TaskID int `gorm:"index"`
|
TaskID int `gorm:"index"`
|
||||||
@@ -106,7 +106,7 @@ type DeviceCommandLog struct {
|
|||||||
|
|
||||||
// DeviceID 是接收此下行任务的设备的ID。
|
// DeviceID 是接收此下行任务的设备的ID。
|
||||||
// 对于 LoRaWAN,这通常是区域主控设备的ID。
|
// 对于 LoRaWAN,这通常是区域主控设备的ID。
|
||||||
DeviceID uint `gorm:"not null;index" json:"device_id"`
|
DeviceID uint32 `gorm:"not null;index" json:"device_id"`
|
||||||
|
|
||||||
// SentAt 记录下行任务最初发送的时间。
|
// SentAt 记录下行任务最初发送的时间。
|
||||||
SentAt time.Time `gorm:"primaryKey" json:"sent_at"`
|
SentAt time.Time `gorm:"primaryKey" json:"sent_at"`
|
||||||
@@ -133,7 +133,7 @@ type PendingCollection struct {
|
|||||||
|
|
||||||
// DeviceID 是接收此任务的设备ID
|
// DeviceID 是接收此任务的设备ID
|
||||||
// 对于 LoRaWAN,这通常是区域主控设备的ID。
|
// 对于 LoRaWAN,这通常是区域主控设备的ID。
|
||||||
DeviceID uint `gorm:"index"`
|
DeviceID uint32 `gorm:"index"`
|
||||||
|
|
||||||
// CommandMetadata 存储了此次采集任务对应的设备ID列表,顺序与设备响应值的顺序一致。
|
// CommandMetadata 存储了此次采集任务对应的设备ID列表,顺序与设备响应值的顺序一致。
|
||||||
CommandMetadata UintArray `gorm:"type:bigint[]"`
|
CommandMetadata UintArray `gorm:"type:bigint[]"`
|
||||||
@@ -154,7 +154,6 @@ func (PendingCollection) TableName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- 用户审计日志 ---
|
// --- 用户审计日志 ---
|
||||||
// TODO 这些变量放这个包合适吗?
|
|
||||||
|
|
||||||
// --- 审计日志状态常量 ---
|
// --- 审计日志状态常量 ---
|
||||||
type AuditStatus string
|
type AuditStatus string
|
||||||
@@ -184,13 +183,13 @@ func (a AuditContextKey) String() string {
|
|||||||
// UserActionLog 记录用户的操作历史,用于审计
|
// UserActionLog 记录用户的操作历史,用于审计
|
||||||
type UserActionLog struct {
|
type UserActionLog struct {
|
||||||
// 用 ID 和 Time 组成复合主键, 防止高并发时时间重复
|
// 用 ID 和 Time 组成复合主键, 防止高并发时时间重复
|
||||||
ID uint `gorm:"primaryKey"`
|
ID uint32 `gorm:"primaryKey"`
|
||||||
|
|
||||||
// Time 是操作发生的时间,作为主键和超表的时间分区键
|
// Time 是操作发生的时间,作为主键和超表的时间分区键
|
||||||
Time time.Time `gorm:"primaryKey" json:"time"`
|
Time time.Time `gorm:"primaryKey" json:"time"`
|
||||||
|
|
||||||
// --- Who (谁) ---
|
// --- Who (谁) ---
|
||||||
UserID uint `gorm:"not null" json:"user_id,omitempty"`
|
UserID uint32 `gorm:"not null" json:"user_id,omitempty"`
|
||||||
Username string `json:"username,omitempty"` // 操作发生时用户名的快照
|
Username string `json:"username,omitempty"` // 操作发生时用户名的快照
|
||||||
|
|
||||||
// --- Where (何地) ---
|
// --- Where (何地) ---
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
猪场固定资产相关模型
|
猪场固定资产相关模型
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// PigHouse 定义了猪舍,是猪栏的集合
|
// PigHouse 定义了猪舍,是猪栏的集合
|
||||||
type PigHouse struct {
|
type PigHouse struct {
|
||||||
gorm.Model
|
Model
|
||||||
Name string `gorm:"size:100;not null;unique;comment:猪舍名称, 如 '育肥舍A栋'"`
|
Name string `gorm:"size:100;not null;unique;comment:猪舍名称, 如 '育肥舍A栋'"`
|
||||||
Description string `gorm:"size:255;comment:描述信息"`
|
Description string `gorm:"size:255;comment:描述信息"`
|
||||||
Pens []Pen `gorm:"foreignKey:HouseID"` // 一个猪舍包含多个猪栏
|
Pens []Pen `gorm:"foreignKey:HouseID"` // 一个猪舍包含多个猪栏
|
||||||
@@ -30,10 +26,10 @@ const (
|
|||||||
|
|
||||||
// Pen 是猪栏的物理实体模型, 是所有空间相关数据的“锚点”
|
// Pen 是猪栏的物理实体模型, 是所有空间相关数据的“锚点”
|
||||||
type Pen struct {
|
type Pen struct {
|
||||||
gorm.Model
|
Model
|
||||||
PenNumber string `gorm:"not null;comment:猪栏的唯一编号, 如 A-01"`
|
PenNumber string `gorm:"not null;comment:猪栏的唯一编号, 如 A-01"`
|
||||||
HouseID uint `gorm:"index;comment:所属猪舍ID"`
|
HouseID uint32 `gorm:"index;comment:所属猪舍ID"`
|
||||||
PigBatchID *uint `gorm:"index;comment:关联的猪批次ID"`
|
PigBatchID *uint32 `gorm:"index;comment:关联的猪批次ID"`
|
||||||
Capacity int `gorm:"not null;comment:设计容量 (头)"`
|
Capacity int `gorm:"not null;comment:设计容量 (头)"`
|
||||||
Status PenStatus `gorm:"not null;index;comment:猪栏当前状态"`
|
Status PenStatus `gorm:"not null;index;comment:猪栏当前状态"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -13,10 +11,10 @@ import (
|
|||||||
// RawMaterial 代表饲料的原料。
|
// RawMaterial 代表饲料的原料。
|
||||||
// 建议:所有重量单位统一存储 (例如, 全部使用 'g'),便于计算和避免转换错误。
|
// 建议:所有重量单位统一存储 (例如, 全部使用 'g'),便于计算和避免转换错误。
|
||||||
type RawMaterial struct {
|
type RawMaterial struct {
|
||||||
gorm.Model
|
Model
|
||||||
Name string `gorm:"size:100;unique;not null;comment:原料名称"`
|
Name string `gorm:"size:100;unique;not null;comment:原料名称"`
|
||||||
Description string `gorm:"size:255;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 {
|
func (RawMaterial) TableName() string {
|
||||||
@@ -25,13 +23,13 @@ func (RawMaterial) TableName() string {
|
|||||||
|
|
||||||
// RawMaterialPurchase 记录了原料的每一次采购。
|
// RawMaterialPurchase 记录了原料的每一次采购。
|
||||||
type RawMaterialPurchase struct {
|
type RawMaterialPurchase struct {
|
||||||
gorm.Model
|
Model
|
||||||
RawMaterialID uint `gorm:"not null;index;comment:关联的原料ID"`
|
RawMaterialID uint32 `gorm:"not null;index;comment:关联的原料ID"`
|
||||||
RawMaterial RawMaterial `gorm:"foreignKey:RawMaterialID"`
|
RawMaterial RawMaterial `gorm:"foreignKey:RawMaterialID"`
|
||||||
Supplier string `gorm:"size:100;comment:供应商"`
|
Supplier string `gorm:"size:100;comment:供应商"`
|
||||||
Amount float64 `gorm:"not null;comment:采购数量, 单位: g"`
|
Amount float32 `gorm:"not null;comment:采购数量, 单位: g"`
|
||||||
UnitPrice float64 `gorm:"comment:单价"`
|
UnitPrice float32 `gorm:"comment:单价"`
|
||||||
TotalPrice float64 `gorm:"comment:总价"`
|
TotalPrice float32 `gorm:"comment:总价"`
|
||||||
PurchaseDate time.Time `gorm:"primaryKey;comment:采购日期"`
|
PurchaseDate time.Time `gorm:"primaryKey;comment:采购日期"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
}
|
}
|
||||||
@@ -54,11 +52,11 @@ const (
|
|||||||
|
|
||||||
// RawMaterialStockLog 记录了原料库存的所有变动,提供了完整的追溯链。
|
// RawMaterialStockLog 记录了原料库存的所有变动,提供了完整的追溯链。
|
||||||
type RawMaterialStockLog struct {
|
type RawMaterialStockLog struct {
|
||||||
gorm.Model
|
Model
|
||||||
RawMaterialID uint `gorm:"not null;index;comment:关联的原料ID"`
|
RawMaterialID uint32 `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:库存变动来源类型"`
|
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:业务发生时间"`
|
HappenedAt time.Time `gorm:"primaryKey;comment:业务发生时间"`
|
||||||
Remarks string `gorm:"comment:备注, 如主动领取的理由等"`
|
Remarks string `gorm:"comment:备注, 如主动领取的理由等"`
|
||||||
}
|
}
|
||||||
@@ -70,7 +68,7 @@ func (RawMaterialStockLog) TableName() string {
|
|||||||
// FeedFormula 代表饲料配方。
|
// FeedFormula 代表饲料配方。
|
||||||
// 对于没有配方的外购饲料,可以将其视为一种特殊的 RawMaterial, 并为其创建一个仅包含它自己的 FeedFormula。
|
// 对于没有配方的外购饲料,可以将其视为一种特殊的 RawMaterial, 并为其创建一个仅包含它自己的 FeedFormula。
|
||||||
type FeedFormula struct {
|
type FeedFormula struct {
|
||||||
gorm.Model
|
Model
|
||||||
Name string `gorm:"size:100;unique;not null;comment:配方名称"`
|
Name string `gorm:"size:100;unique;not null;comment:配方名称"`
|
||||||
Description string `gorm:"size:255;comment:描述"`
|
Description string `gorm:"size:255;comment:描述"`
|
||||||
Components []FeedFormulaComponent `gorm:"foreignKey:FeedFormulaID"`
|
Components []FeedFormulaComponent `gorm:"foreignKey:FeedFormulaID"`
|
||||||
@@ -82,11 +80,11 @@ func (FeedFormula) TableName() string {
|
|||||||
|
|
||||||
// FeedFormulaComponent 代表配方中的一种原料及其占比。
|
// FeedFormulaComponent 代表配方中的一种原料及其占比。
|
||||||
type FeedFormulaComponent struct {
|
type FeedFormulaComponent struct {
|
||||||
gorm.Model
|
Model
|
||||||
FeedFormulaID uint `gorm:"not null;index;comment:外键到 FeedFormula"`
|
FeedFormulaID uint32 `gorm:"not null;index;comment:外键到 FeedFormula"`
|
||||||
RawMaterialID uint `gorm:"not null;index;comment:外键到 RawMaterial"`
|
RawMaterialID uint32 `gorm:"not null;index;comment:外键到 RawMaterial"`
|
||||||
RawMaterial RawMaterial `gorm:"foreignKey:RawMaterialID"`
|
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 {
|
func (FeedFormulaComponent) TableName() string {
|
||||||
@@ -97,14 +95,14 @@ func (FeedFormulaComponent) TableName() string {
|
|||||||
// 应用层逻辑:当一条使用记录被创建时,应根据其使用的 FeedFormula,
|
// 应用层逻辑:当一条使用记录被创建时,应根据其使用的 FeedFormula,
|
||||||
// 计算出每种 RawMaterial 的消耗量,并在 RawMaterialStockLog 中创建对应的出库记录。
|
// 计算出每种 RawMaterial 的消耗量,并在 RawMaterialStockLog 中创建对应的出库记录。
|
||||||
type FeedUsageRecord struct {
|
type FeedUsageRecord struct {
|
||||||
gorm.Model
|
Model
|
||||||
PenID uint `gorm:"not null;index;comment:关联的猪栏ID"`
|
PenID uint32 `gorm:"not null;index;comment:关联的猪栏ID"`
|
||||||
Pen Pen `gorm:"foreignKey:PenID"`
|
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"`
|
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:记录时间"`
|
RecordedAt time.Time `gorm:"primaryKey;comment:记录时间"`
|
||||||
OperatorID uint `gorm:"not null;comment:操作员"`
|
OperatorID uint32 `gorm:"not null;comment:操作员"`
|
||||||
Remarks string `gorm:"comment:备注, 如 '例行喂料, 弱猪补料' 等"`
|
Remarks string `gorm:"comment:备注, 如 '例行喂料, 弱猪补料' 等"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -48,21 +47,21 @@ type PowderInstructions struct {
|
|||||||
// 出栏前停药期
|
// 出栏前停药期
|
||||||
WithdrawalPeriod time.Duration `json:"withdrawal_period"`
|
WithdrawalPeriod time.Duration `json:"withdrawal_period"`
|
||||||
// 拌料使用计量, 每千克体重用多少克药, 单位: g/kg
|
// 拌料使用计量, 每千克体重用多少克药, 单位: g/kg
|
||||||
BodyWeightDosageUsed float64 `json:"body_weight_dosage_used"`
|
BodyWeightDosageUsed float32 `json:"body_weight_dosage_used"`
|
||||||
// 拌料使用剂量, 每升水加多少克药或每千克饲料干重加多少克药, 单位: g/kg(L)
|
// 拌料使用剂量, 每升水加多少克药或每千克饲料干重加多少克药, 单位: g/kg(L)
|
||||||
MixDosageUsed float64 `json:"mix_dosage_used"`
|
MixDosageUsed float32 `json:"mix_dosage_used"`
|
||||||
// 拌料使用方式, 兑水/拌料
|
// 拌料使用方式, 兑水/拌料
|
||||||
MixType MixType `json:"mix_type"`
|
MixType MixType `json:"mix_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Medication 定义了兽药/疫苗的基本信息模型
|
// Medication 定义了兽药/疫苗的基本信息模型
|
||||||
type Medication struct {
|
type Medication struct {
|
||||||
gorm.Model
|
Model
|
||||||
Name string `gorm:"size:100;not null;comment:药品名称" json:"name"`
|
Name string `gorm:"size:100;not null;comment:药品名称" json:"name"`
|
||||||
Type MedicationType `gorm:"size:20;not null;comment:兽药类型 (粉剂, 针剂, 疫苗)" json:"type"`
|
Type MedicationType `gorm:"size:20;not null;comment:兽药类型 (粉剂, 针剂, 疫苗)" json:"type"`
|
||||||
Category MedicationCategory `gorm:"size:30;not null;comment:兽药种类 (四环素类, 磺胺类等)" json:"category"`
|
Category MedicationCategory `gorm:"size:30;not null;comment:兽药种类 (四环素类, 磺胺类等)" json:"category"`
|
||||||
DosagePerUnit float64 `gorm:"size:50;comment:一份药物的计量 (针剂计量单位为毫升, 粉剂为克)" json:"dosage_per_unit"`
|
DosagePerUnit float32 `gorm:"size:50;comment:一份药物的计量 (针剂计量单位为毫升, 粉剂为克)" json:"dosage_per_unit"`
|
||||||
ActiveIngredientConcentration float64 `gorm:"size:50;comment:有效成分含量百分比" json:"active_ingredient_concentration"`
|
ActiveIngredientConcentration float32 `gorm:"size:50;comment:有效成分含量百分比" json:"active_ingredient_concentration"`
|
||||||
Manufacturer string `gorm:"size:100;comment:生产厂家" json:"manufacturer"`
|
Manufacturer string `gorm:"size:100;comment:生产厂家" json:"manufacturer"`
|
||||||
Instructions datatypes.JSON `gorm:"type:jsonb;comment:使用说明" json:"instructions"`
|
Instructions datatypes.JSON `gorm:"type:jsonb;comment:使用说明" json:"instructions"`
|
||||||
}
|
}
|
||||||
@@ -82,15 +81,15 @@ const (
|
|||||||
|
|
||||||
// MedicationLog 记录了对整个猪批次的用药情况
|
// MedicationLog 记录了对整个猪批次的用药情况
|
||||||
type MedicationLog struct {
|
type MedicationLog struct {
|
||||||
gorm.Model
|
Model
|
||||||
PigBatchID uint `gorm:"not null;index;comment:关联的猪批次ID"`
|
PigBatchID uint32 `gorm:"not null;index;comment:关联的猪批次ID"`
|
||||||
MedicationID uint `gorm:"not null;index;comment:关联的药品ID"`
|
MedicationID uint32 `gorm:"not null;index;comment:关联的药品ID"`
|
||||||
Medication Medication `gorm:"foreignKey:MedicationID"` // 预加载药品信息
|
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:用药对象数量"`
|
TargetCount int `gorm:"not null;comment:用药对象数量"`
|
||||||
Reason MedicationReasonType `gorm:"size:20;not null;comment:用药原因"`
|
Reason MedicationReasonType `gorm:"size:20;not null;comment:用药原因"`
|
||||||
Description string `gorm:"size:255;comment:具体描述,如'治疗呼吸道病'"`
|
Description string `gorm:"size:255;comment:具体描述,如'治疗呼吸道病'"`
|
||||||
OperatorID uint `gorm:"comment:操作员ID"`
|
OperatorID uint32 `gorm:"comment:操作员ID"`
|
||||||
HappenedAt time.Time `gorm:"primaryKey;comment:用药时间"`
|
HappenedAt time.Time `gorm:"primaryKey;comment:用药时间"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,20 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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 返回一个包含所有数据库模型实例的切片。
|
// GetAllModels 返回一个包含所有数据库模型实例的切片。
|
||||||
// 这个函数用于在数据库初始化时自动迁移所有的表结构。
|
// 这个函数用于在数据库初始化时自动迁移所有的表结构。
|
||||||
func GetAllModels() []interface{} {
|
func GetAllModels() []interface{} {
|
||||||
@@ -61,15 +73,19 @@ func GetAllModels() []interface{} {
|
|||||||
&Medication{},
|
&Medication{},
|
||||||
&MedicationLog{},
|
&MedicationLog{},
|
||||||
|
|
||||||
|
// Alarm Models
|
||||||
|
&ActiveAlarm{},
|
||||||
|
&HistoricalAlarm{},
|
||||||
|
|
||||||
// Notification Models
|
// Notification Models
|
||||||
&Notification{},
|
&Notification{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UintArray 是一个自定义类型,代表 uint 的切片。
|
// UintArray 是一个自定义类型,代表 uint32 的切片。
|
||||||
// 它实现了 gorm.Scanner 和 driver.Valuer 接口,
|
// 它实现了 gorm.Scanner 和 driver.Valuer 接口,
|
||||||
// 以便能与数据库的 bigint[] 类型进行原生映射。
|
// 以便能与数据库的 bigint[] 类型进行原生映射。
|
||||||
type UintArray []uint
|
type UintArray []uint32
|
||||||
|
|
||||||
// Value 实现了 driver.Valuer 接口。
|
// Value 实现了 driver.Valuer 接口。
|
||||||
// 它告诉 GORM 如何将 UintArray ([]) 转换为数据库能够理解的格式。
|
// 它告诉 GORM 如何将 UintArray ([]) 转换为数据库能够理解的格式。
|
||||||
@@ -111,21 +127,86 @@ func (a *UintArray) Scan(src interface{}) error {
|
|||||||
// 去掉花括号
|
// 去掉花括号
|
||||||
srcStr = strings.Trim(srcStr, "{}")
|
srcStr = strings.Trim(srcStr, "{}")
|
||||||
if srcStr == "" {
|
if srcStr == "" {
|
||||||
*a = []uint{}
|
*a = []uint32{}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按逗号分割
|
// 按逗号分割
|
||||||
parts := strings.Split(srcStr, ",")
|
parts := strings.Split(srcStr, ",")
|
||||||
arr := make([]uint, len(parts))
|
arr := make([]uint32, len(parts))
|
||||||
for i, p := range parts {
|
for i, p := range parts {
|
||||||
val, err := strconv.ParseUint(p, 10, 64)
|
val, err := strconv.ParseUint(p, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("解析 UintArray 元素失败: %w", err)
|
return fmt.Errorf("解析 UintArray 元素失败: %w", err)
|
||||||
}
|
}
|
||||||
arr[i] = uint(val)
|
arr[i] = uint32(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
*a = arr
|
*a = arr
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
|
||||||
"errors"
|
|
||||||
"time"
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/notify"
|
// NotifierType 定义了通知器的类型。
|
||||||
"go.uber.org/zap/zapcore"
|
type NotifierType string
|
||||||
"gorm.io/gorm"
|
|
||||||
|
const (
|
||||||
|
// NotifierTypeSMTP 表示 SMTP 邮件通知器。
|
||||||
|
NotifierTypeSMTP NotifierType = "邮件"
|
||||||
|
// NotifierTypeWeChat 表示企业微信通知器。
|
||||||
|
NotifierTypeWeChat NotifierType = "企业微信"
|
||||||
|
// NotifierTypeLark 表示飞书通知器。
|
||||||
|
NotifierTypeLark NotifierType = "飞书"
|
||||||
|
// NotifierTypeLog 表示日志通知器,作为最终的告警记录渠道。
|
||||||
|
NotifierTypeLog NotifierType = "日志"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NotificationStatus 定义了通知发送尝试的状态枚举。
|
// NotificationStatus 定义了通知发送尝试的状态枚举。
|
||||||
@@ -19,48 +27,20 @@ const (
|
|||||||
NotificationStatusSkipped NotificationStatus = "已跳过" // 通知因某些原因被跳过(例如:用户未配置联系方式)
|
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 表示已发送或尝试发送的通知记录。
|
// Notification 表示已发送或尝试发送的通知记录。
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
gorm.Model
|
Model
|
||||||
|
|
||||||
// NotifierType 通知器类型 (例如:"邮件", "企业微信", "飞书", "日志")
|
// 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 接收通知的用户ID,用于追溯通知记录到特定用户
|
||||||
UserID uint `gorm:"index" json:"user_id"` // 增加 UserID 字段,并添加索引
|
UserID uint32 `gorm:"index" json:"user_id"` // 增加 UserID 字段,并添加索引
|
||||||
// Title 通知标题
|
// Title 通知标题
|
||||||
Title string `gorm:"type:varchar(255);not null" json:"title"`
|
Title string `gorm:"type:varchar(255);not null" json:"title"`
|
||||||
// Message 通知内容
|
// Message 通知内容
|
||||||
Message string `gorm:"type:text;not null" json:"message"`
|
Message string `gorm:"type:text;not null" json:"message"`
|
||||||
// Level 通知级别 (例如:INFO, WARN, ERROR)
|
// 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 通知内容生成时的时间戳,与 ID 构成复合主键
|
||||||
AlarmTimestamp time.Time `gorm:"primaryKey;not null" json:"alarm_timestamp"`
|
AlarmTimestamp time.Time `gorm:"primaryKey;not null" json:"alarm_timestamp"`
|
||||||
// ToAddress 接收地址 (例如:邮箱地址, 企业微信ID, 日志标识符)
|
// ToAddress 接收地址 (例如:邮箱地址, 企业微信ID, 日志标识符)
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -32,7 +30,7 @@ const (
|
|||||||
|
|
||||||
// PigBatch 是猪批次的核心模型,代表了一群被共同管理的猪
|
// PigBatch 是猪批次的核心模型,代表了一群被共同管理的猪
|
||||||
type PigBatch struct {
|
type PigBatch struct {
|
||||||
gorm.Model
|
Model
|
||||||
BatchNumber string `gorm:"size:50;not null;uniqueIndex;comment:批次编号,如 2024-W25-A01"`
|
BatchNumber string `gorm:"size:50;not null;uniqueIndex;comment:批次编号,如 2024-W25-A01"`
|
||||||
OriginType PigBatchOriginType `gorm:"size:20;not null;comment:批次来源 (自繁, 外购)"`
|
OriginType PigBatchOriginType `gorm:"size:20;not null;comment:批次来源 (自繁, 外购)"`
|
||||||
StartDate time.Time `gorm:"not null;comment:批次开始日期 (如转入日或购买日)"`
|
StartDate time.Time `gorm:"not null;comment:批次开始日期 (如转入日或购买日)"`
|
||||||
@@ -65,14 +63,14 @@ const (
|
|||||||
|
|
||||||
// PigBatchLog 记录了猪批次数量或状态的每一次变更
|
// PigBatchLog 记录了猪批次数量或状态的每一次变更
|
||||||
type PigBatchLog struct {
|
type PigBatchLog struct {
|
||||||
gorm.Model
|
Model
|
||||||
PigBatchID uint `gorm:"not null;index;comment:关联的猪批次ID"`
|
PigBatchID uint32 `gorm:"not null;index;comment:关联的猪批次ID"`
|
||||||
ChangeType LogChangeType `gorm:"size:20;not null;comment:变更类型"`
|
ChangeType LogChangeType `gorm:"size:20;not null;comment:变更类型"`
|
||||||
ChangeCount int `gorm:"not null;comment:数量变化,负数表示减少"`
|
ChangeCount int `gorm:"not null;comment:数量变化,负数表示减少"`
|
||||||
Reason string `gorm:"size:255;comment:变更原因描述"`
|
Reason string `gorm:"size:255;comment:变更原因描述"`
|
||||||
BeforeCount int `gorm:"not null;comment:变更前总数"`
|
BeforeCount int `gorm:"not null;comment:变更前总数"`
|
||||||
AfterCount 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:事件发生时间"`
|
HappenedAt time.Time `gorm:"primaryKey;comment:事件发生时间"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,10 +80,10 @@ func (PigBatchLog) TableName() string {
|
|||||||
|
|
||||||
// WeighingBatch 记录了一次批次称重的信息
|
// WeighingBatch 记录了一次批次称重的信息
|
||||||
type WeighingBatch struct {
|
type WeighingBatch struct {
|
||||||
gorm.Model
|
Model
|
||||||
WeighingTime time.Time `gorm:"primaryKey;comment:称重时间"`
|
WeighingTime time.Time `gorm:"primaryKey;comment:称重时间"`
|
||||||
Description string `gorm:"size:255;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 {
|
func (WeighingBatch) TableName() string {
|
||||||
@@ -94,11 +92,11 @@ func (WeighingBatch) TableName() string {
|
|||||||
|
|
||||||
// WeighingRecord 记录了单次称重信息
|
// WeighingRecord 记录了单次称重信息
|
||||||
type WeighingRecord struct {
|
type WeighingRecord struct {
|
||||||
gorm.Model
|
Model
|
||||||
Weight float64 `gorm:"not null;comment:单只猪重量 (kg)"`
|
Weight float32 `gorm:"not null;comment:单只猪重量 (kg)"`
|
||||||
WeighingBatchID uint `gorm:"not null;index;comment:关联的批次称重ID"`
|
WeighingBatchID uint32 `gorm:"not null;index;comment:关联的批次称重ID"`
|
||||||
PenID uint `gorm:"not null;index;comment:所在猪圈ID"`
|
PenID uint32 `gorm:"not null;index;comment:所在猪圈ID"`
|
||||||
OperatorID uint `gorm:"not null;comment:操作员ID"`
|
OperatorID uint32 `gorm:"not null;comment:操作员ID"`
|
||||||
Remark string `gorm:"size:255;comment:备注"`
|
Remark string `gorm:"size:255;comment:备注"`
|
||||||
WeighingTime time.Time `gorm:"primaryKey;comment:称重时间"`
|
WeighingTime time.Time `gorm:"primaryKey;comment:称重时间"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PigBatchSickPigTreatmentLocation 定义了病猪治疗地点
|
// PigBatchSickPigTreatmentLocation 定义了病猪治疗地点
|
||||||
@@ -29,16 +27,16 @@ const (
|
|||||||
|
|
||||||
// PigSickLog 记录了猪批次中病猪数量的变化日志
|
// PigSickLog 记录了猪批次中病猪数量的变化日志
|
||||||
type PigSickLog struct {
|
type PigSickLog struct {
|
||||||
gorm.Model
|
Model
|
||||||
PigBatchID uint `gorm:"primaryKey;comment:关联的猪批次ID"`
|
PigBatchID uint32 `gorm:"primaryKey;comment:关联的猪批次ID"`
|
||||||
PenID uint `gorm:"not null;index;comment:所在猪圈ID"`
|
PenID uint32 `gorm:"not null;index;comment:所在猪圈ID"`
|
||||||
ChangeCount int `gorm:"not null;comment:变化数量, 正数表示新增, 负数表示移除"`
|
ChangeCount int `gorm:"not null;comment:变化数量, 正数表示新增, 负数表示移除"`
|
||||||
Reason PigBatchSickPigReasonType `gorm:"size:20;not null;comment:变化原因 (如: 患病, 康复, 死亡, 转入, 转出, 其他)"`
|
Reason PigBatchSickPigReasonType `gorm:"size:20;not null;comment:变化原因 (如: 患病, 康复, 死亡, 转入, 转出, 其他)"`
|
||||||
BeforeCount int `gorm:"comment:变化前的数量"`
|
BeforeCount int `gorm:"comment:变化前的数量"`
|
||||||
AfterCount int `gorm:"comment:变化后的数量"`
|
AfterCount int `gorm:"comment:变化后的数量"`
|
||||||
Remarks string `gorm:"size:255;comment:备注"`
|
Remarks string `gorm:"size:255;comment:备注"`
|
||||||
TreatmentLocation PigBatchSickPigTreatmentLocation `gorm:"size:50;comment:治疗地点"`
|
TreatmentLocation PigBatchSickPigTreatmentLocation `gorm:"size:50;comment:治疗地点"`
|
||||||
OperatorID uint `gorm:"comment:操作员ID"`
|
OperatorID uint32 `gorm:"comment:操作员ID"`
|
||||||
HappenedAt time.Time `gorm:"primaryKey;comment:事件发生时间"`
|
HappenedAt time.Time `gorm:"primaryKey;comment:事件发生时间"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,21 +2,19 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PigPurchase 记录了猪只采购信息
|
// PigPurchase 记录了猪只采购信息
|
||||||
type PigPurchase struct {
|
type PigPurchase struct {
|
||||||
gorm.Model
|
Model
|
||||||
PigBatchID uint `gorm:"not null;index;comment:关联的猪批次ID"`
|
PigBatchID uint32 `gorm:"not null;index;comment:关联的猪批次ID"`
|
||||||
PurchaseDate time.Time `gorm:"primaryKey;comment:采购日期"`
|
PurchaseDate time.Time `gorm:"primaryKey;comment:采购日期"`
|
||||||
Supplier string `gorm:"comment:供应商"`
|
Supplier string `gorm:"comment:供应商"`
|
||||||
Quantity int `gorm:"not null;comment:采购数量"`
|
Quantity int `gorm:"not null;comment:采购数量"`
|
||||||
UnitPrice float64 `gorm:"not null;comment:单价"`
|
UnitPrice float32 `gorm:"not null;comment:单价"`
|
||||||
TotalPrice float64 `gorm:"not null;comment:总价"`
|
TotalPrice float32 `gorm:"not null;comment:总价"`
|
||||||
Remarks string `gorm:"size:255;comment:备注"`
|
Remarks string `gorm:"size:255;comment:备注"`
|
||||||
OperatorID uint `gorm:"comment:操作员ID"`
|
OperatorID uint32 `gorm:"comment:操作员ID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (PigPurchase) TableName() string {
|
func (PigPurchase) TableName() string {
|
||||||
@@ -25,15 +23,15 @@ func (PigPurchase) TableName() string {
|
|||||||
|
|
||||||
// PigSale 记录了猪只销售信息
|
// PigSale 记录了猪只销售信息
|
||||||
type PigSale struct {
|
type PigSale struct {
|
||||||
gorm.Model
|
Model
|
||||||
PigBatchID uint `gorm:"not null;index;comment:关联的猪批次ID"`
|
PigBatchID uint32 `gorm:"not null;index;comment:关联的猪批次ID"`
|
||||||
SaleDate time.Time `gorm:"primaryKey;comment:销售日期"`
|
SaleDate time.Time `gorm:"primaryKey;comment:销售日期"`
|
||||||
Buyer string `gorm:"comment:购买方"`
|
Buyer string `gorm:"comment:购买方"`
|
||||||
Quantity int `gorm:"not null;comment:销售数量"`
|
Quantity int `gorm:"not null;comment:销售数量"`
|
||||||
UnitPrice float64 `gorm:"not null;comment:单价"`
|
UnitPrice float32 `gorm:"not null;comment:单价"`
|
||||||
TotalPrice float64 `gorm:"not null;comment:总价"`
|
TotalPrice float32 `gorm:"not null;comment:总价"`
|
||||||
Remarks string `gorm:"size:255;comment:备注"`
|
Remarks string `gorm:"size:255;comment:备注"`
|
||||||
OperatorID uint `gorm:"comment:操作员ID"`
|
OperatorID uint32 `gorm:"comment:操作员ID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (PigSale) TableName() string {
|
func (PigSale) TableName() string {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PigTransferType 定义了猪只迁移的类型
|
// PigTransferType 定义了猪只迁移的类型
|
||||||
@@ -23,14 +21,14 @@ const (
|
|||||||
// PigTransferLog 记录了每一次猪只数量在猪栏间的变动事件。
|
// PigTransferLog 记录了每一次猪只数量在猪栏间的变动事件。
|
||||||
// 它作为事件溯源的基础,用于推算任意时间点猪栏的猪只数量。
|
// 它作为事件溯源的基础,用于推算任意时间点猪栏的猪只数量。
|
||||||
type PigTransferLog struct {
|
type PigTransferLog struct {
|
||||||
gorm.Model
|
Model
|
||||||
TransferTime time.Time `gorm:"primaryKey;comment:迁移发生时间" json:"transfer_time"` // 迁移发生时间,作为联合主键
|
TransferTime time.Time `gorm:"primaryKey;comment:迁移发生时间" json:"transfer_time"` // 迁移发生时间,作为联合主键
|
||||||
PigBatchID uint `gorm:"primaryKey;comment:关联的猪群ID" json:"pig_batch_id"` // 关联的猪群ID,作为联合主键
|
PigBatchID uint32 `gorm:"primaryKey;comment:关联的猪群ID" json:"pig_batch_id"` // 关联的猪群ID,作为联合主键
|
||||||
PenID uint `gorm:"primaryKey;comment:发生变动的猪栏ID" json:"pen_id"` // 发生变动的猪栏ID,作为联合主键
|
PenID uint32 `gorm:"primaryKey;comment:发生变动的猪栏ID" json:"pen_id"` // 发生变动的猪栏ID,作为联合主键
|
||||||
Quantity int `gorm:"not null;comment:变动数量(正数表示增加,负数表示减少)" json:"quantity"` // 变动数量(正数表示增加,负数减少)
|
Quantity int `gorm:"not null;comment:变动数量(正数表示增加,负数表示减少)" json:"quantity"` // 变动数量(正数表示增加,负数减少)
|
||||||
Type PigTransferType `gorm:"not null;comment:变动类型" json:"type"` // 变动类型,使用枚举类型
|
Type PigTransferType `gorm:"not null;comment:变动类型" json:"type"` // 变动类型,使用枚举类型
|
||||||
CorrelationID string `gorm:"comment:用于关联一次完整操作(如一次调栏会产生两条日志)" json:"correlation_id"` // 用于关联一次完整操作
|
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"`
|
Remarks string `gorm:"comment:备注" json:"remarks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,15 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type PlanName string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PlanNamePeriodicSystemHealthCheck 是周期性系统健康检查计划的名称
|
||||||
|
PlanNamePeriodicSystemHealthCheck PlanName = "周期性系统健康检查"
|
||||||
|
// PlanNameAlarmNotification 是告警通知发送计划的名称
|
||||||
|
PlanNameAlarmNotification PlanName = "告警通知发送"
|
||||||
|
)
|
||||||
|
|
||||||
// PlanExecutionType 定义了计划的执行类型
|
// PlanExecutionType 定义了计划的执行类型
|
||||||
type PlanExecutionType string
|
type PlanExecutionType string
|
||||||
|
|
||||||
@@ -35,6 +44,9 @@ const (
|
|||||||
TaskTypeWaiting TaskType = "等待" // 等待任务
|
TaskTypeWaiting TaskType = "等待" // 等待任务
|
||||||
TaskTypeReleaseFeedWeight TaskType = "下料" // 下料口释放指定重量任务
|
TaskTypeReleaseFeedWeight TaskType = "下料" // 下料口释放指定重量任务
|
||||||
TaskTypeFullCollection TaskType = "全量采集" // 新增的全量采集任务
|
TaskTypeFullCollection TaskType = "全量采集" // 新增的全量采集任务
|
||||||
|
TaskTypeAlarmNotification TaskType = "告警通知" // 告警通知任务
|
||||||
|
TaskTypeDeviceThresholdCheck TaskType = "设备阈值检查" // 设备阈值检查任务
|
||||||
|
TaskTypeAreaCollectorThresholdCheck TaskType = "区域阈值检查" // 区域阈值检查任务
|
||||||
)
|
)
|
||||||
|
|
||||||
// -- Task Parameters --
|
// -- Task Parameters --
|
||||||
@@ -62,15 +74,15 @@ const (
|
|||||||
|
|
||||||
// Plan 代表系统中的一个计划,可以包含子计划或任务
|
// Plan 代表系统中的一个计划,可以包含子计划或任务
|
||||||
type Plan struct {
|
type Plan struct {
|
||||||
gorm.Model
|
Model
|
||||||
|
|
||||||
Name string `gorm:"not null" json:"name"`
|
Name PlanName `gorm:"not null" json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
PlanType PlanType `gorm:"not null;index" json:"plan_type"` // 任务类型, 包括系统任务和用户自定义任务
|
PlanType PlanType `gorm:"not null;index" json:"plan_type"` // 任务类型, 包括系统任务和用户自定义任务
|
||||||
ExecutionType PlanExecutionType `gorm:"not null;index" json:"execution_type"`
|
ExecutionType PlanExecutionType `gorm:"not null;index" json:"execution_type"`
|
||||||
Status PlanStatus `gorm:"default:'已禁用';index" json:"status"` // 计划是否被启动
|
Status PlanStatus `gorm:"default:'已禁用';index" json:"status"` // 计划是否被启动
|
||||||
ExecuteNum uint `gorm:"default:0" json:"execute_num"` // 计划预期执行次数
|
ExecuteNum uint32 `gorm:"default:0" json:"execute_num"` // 计划预期执行次数
|
||||||
ExecuteCount uint `gorm:"default:0" json:"execute_count"` // 执行计数器
|
ExecuteCount uint32 `gorm:"default:0" json:"execute_count"` // 执行计数器
|
||||||
|
|
||||||
// 针对 PlanExecutionTypeAutomatic,使用 Cron 表达式定义调度规则
|
// 针对 PlanExecutionTypeAutomatic,使用 Cron 表达式定义调度规则
|
||||||
CronExpression string `json:"cron_expression"`
|
CronExpression string `json:"cron_expression"`
|
||||||
@@ -151,10 +163,10 @@ func (p *Plan) ReorderSteps() {
|
|||||||
|
|
||||||
// SubPlan 代表作为另一个计划一部分的子计划,具有执行顺序
|
// SubPlan 代表作为另一个计划一部分的子计划,具有执行顺序
|
||||||
type SubPlan struct {
|
type SubPlan struct {
|
||||||
gorm.Model
|
Model
|
||||||
|
|
||||||
ParentPlanID uint `gorm:"not null;index" json:"parent_plan_id"` // 父计划的ID
|
ParentPlanID uint32 `gorm:"not null;index" json:"parent_plan_id"` // 父计划的ID
|
||||||
ChildPlanID uint `gorm:"not null;index" json:"child_plan_id"` // 子计划的ID (它本身也是一个 Plan)
|
ChildPlanID uint32 `gorm:"not null;index" json:"child_plan_id"` // 子计划的ID (它本身也是一个 Plan)
|
||||||
ExecutionOrder int `gorm:"not null" json:"execution_order"` // 在父计划中的执行顺序
|
ExecutionOrder int `gorm:"not null" json:"execution_order"` // 在父计划中的执行顺序
|
||||||
ChildPlan *Plan `gorm:"-" json:"child_plan"` // 完整子计划数据,仅内存中
|
ChildPlan *Plan `gorm:"-" json:"child_plan"` // 完整子计划数据,仅内存中
|
||||||
}
|
}
|
||||||
@@ -172,7 +184,7 @@ type Task struct {
|
|||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"` // 保持软删除功能
|
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"`
|
Name string `gorm:"not null" json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
ExecutionOrder int `gorm:"not null" json:"execution_order"` // 在计划中的执行顺序
|
ExecutionOrder int `gorm:"not null" json:"execution_order"` // 在计划中的执行顺序
|
||||||
@@ -201,11 +213,25 @@ func (t Task) ParseParameters(v interface{}) error {
|
|||||||
return json.Unmarshal(t.Parameters, v)
|
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 是设备和任务之间的关联模型,表示一个设备可以执行多个任务,一个任务可以被多个设备执行。
|
// DeviceTask 是设备和任务之间的关联模型,表示一个设备可以执行多个任务,一个任务可以被多个设备执行。
|
||||||
type DeviceTask struct {
|
type DeviceTask struct {
|
||||||
gorm.Model
|
Model
|
||||||
DeviceID uint `gorm:"not null;index"` // 设备ID
|
DeviceID uint32 `gorm:"not null;index"` // 设备ID
|
||||||
TaskID uint `gorm:"not null;index"` // 任务ID
|
TaskID uint32 `gorm:"not null;index"` // 任务ID
|
||||||
|
|
||||||
// 可选:如果需要存储关联的额外信息,可以在这里添加字段,例如:
|
// 可选:如果需要存储关联的额外信息,可以在这里添加字段,例如:
|
||||||
// Configuration datatypes.JSON `json:"configuration"` // 任务在特定设备上的配置
|
// Configuration datatypes.JSON `json:"configuration"` // 任务在特定设备上的配置
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
// PendingTask 是一个待执行任务队列, 里面会储存待执行的Task以及这个Task什么时候执行
|
// PendingTask 是一个待执行任务队列, 里面会储存待执行的Task以及这个Task什么时候执行
|
||||||
// 它是一个纯粹的工作队列,任务被认领后即被删除。
|
// 它是一个纯粹的工作队列,任务被认领后即被删除。
|
||||||
type PendingTask struct {
|
type PendingTask struct {
|
||||||
// 手动填充必须字段以实现硬删除,不内嵌 gorm.Model
|
// 手动填充必须字段以实现硬删除,不内嵌 Model
|
||||||
ID uint `gorm:"primarykey"`
|
ID uint32 `gorm:"primarykey"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ type PendingTask struct {
|
|||||||
Task *Task `gorm:"foreignKey:TaskID"`
|
Task *Task `gorm:"foreignKey:TaskID"`
|
||||||
|
|
||||||
ExecuteAt time.Time `gorm:"index"` // 任务执行时间
|
ExecuteAt time.Time `gorm:"index"` // 任务执行时间
|
||||||
TaskExecutionLogID uint `gorm:"unique;not null;index"` // 对应的执行历史记录ID
|
TaskExecutionLogID uint32 `gorm:"unique;not null;index"` // 对应的执行历史记录ID
|
||||||
|
|
||||||
// 通过 TaskExecutionLogID 关联到唯一的 TaskExecutionLog 记录
|
// 通过 TaskExecutionLogID 关联到唯一的 TaskExecutionLog 记录
|
||||||
// ON DELETE CASCADE 确保如果日志被删除,这个待办任务也会被自动清理
|
// ON DELETE CASCADE 确保如果日志被删除,这个待办任务也会被自动清理
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
@@ -20,7 +22,7 @@ const (
|
|||||||
// SignalMetrics 存储信号强度数据
|
// SignalMetrics 存储信号强度数据
|
||||||
type SignalMetrics struct {
|
type SignalMetrics struct {
|
||||||
RssiDbm int `json:"rssi_dbm"` // 绝对信号强度(dBm),受距离、障碍物影响
|
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)
|
SensitivityDbm int `json:"sensitivity_dbm"` // 网关的最低检测阈值(dBm)
|
||||||
MarginDb int `json:"margin_db"` // SNR 相对于接收器灵敏度的余量, Margin = SNR - Sensitivity
|
MarginDb int `json:"margin_db"` // SNR 相对于接收器灵敏度的余量, Margin = SNR - Sensitivity
|
||||||
}
|
}
|
||||||
@@ -34,17 +36,17 @@ type BatteryLevel struct {
|
|||||||
|
|
||||||
// TemperatureData 存储温度数据
|
// TemperatureData 存储温度数据
|
||||||
type TemperatureData struct {
|
type TemperatureData struct {
|
||||||
TemperatureCelsius float64 `json:"temperature_celsius"` // 温度值 (摄氏度)
|
TemperatureCelsius float32 `json:"temperature_celsius"` // 温度值 (摄氏度)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HumidityData 存储湿度数据
|
// HumidityData 存储湿度数据
|
||||||
type HumidityData struct {
|
type HumidityData struct {
|
||||||
HumidityPercent float64 `json:"humidity_percent"` // 湿度值 (%)
|
HumidityPercent float32 `json:"humidity_percent"` // 湿度值 (%)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WeightData 存储重量数据
|
// WeightData 存储重量数据
|
||||||
type WeightData struct {
|
type WeightData struct {
|
||||||
WeightKilograms float64 `json:"weight_kilograms"` // 重量值 (公斤)
|
WeightKilograms float32 `json:"weight_kilograms"` // 重量值 (公斤)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SensorData 存储所有类型的传感器数据,对应数据库中的 'sensor_data' 表。
|
// SensorData 存储所有类型的传感器数据,对应数据库中的 'sensor_data' 表。
|
||||||
@@ -53,10 +55,10 @@ type SensorData struct {
|
|||||||
Time time.Time `gorm:"primaryKey" json:"time"`
|
Time time.Time `gorm:"primaryKey" json:"time"`
|
||||||
|
|
||||||
// DeviceID 是传感器的唯一标识符,作为复合主键的另一部分。
|
// DeviceID 是传感器的唯一标识符,作为复合主键的另一部分。
|
||||||
DeviceID uint `gorm:"primaryKey" json:"device_id"`
|
DeviceID uint32 `gorm:"primaryKey" json:"device_id"`
|
||||||
|
|
||||||
// RegionalControllerID 是上报此数据的区域主控的ID。
|
// AreaControllerID 是上报此数据的区域主控的ID。
|
||||||
RegionalControllerID uint `json:"regional_controller_id"`
|
AreaControllerID uint32 `json:"area_controller_id"`
|
||||||
|
|
||||||
// SensorType 是传感数据的类型
|
// SensorType 是传感数据的类型
|
||||||
SensorType SensorType `gorm:"not null;index" json:"sensor_type"`
|
SensorType SensorType `gorm:"not null;index" json:"sensor_type"`
|
||||||
@@ -68,3 +70,12 @@ type SensorData struct {
|
|||||||
func (SensorData) TableName() string {
|
func (SensorData) TableName() string {
|
||||||
return "sensor_data"
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ func (ci ContactInfo) Value() (driver.Value, error) {
|
|||||||
|
|
||||||
// User 代表系统中的用户模型
|
// User 代表系统中的用户模型
|
||||||
type User struct {
|
type User struct {
|
||||||
// gorm.Model 内嵌了 ID, CreatedAt, UpdatedAt, 和 DeletedAt
|
// Model 内嵌了 ID, CreatedAt, UpdatedAt, 和 DeletedAt
|
||||||
// DeletedAt 字段的存在自动为 GORM 开启了软删除模式
|
// DeletedAt 字段的存在自动为 GORM 开启了软删除模式
|
||||||
gorm.Model
|
Model
|
||||||
|
|
||||||
// Username 是用户的登录名,应该是唯一的
|
// Username 是用户的登录名,应该是唯一的
|
||||||
// 修正了 gorm 标签的拼写错误 (移除了 gorm 后面的冒号)
|
// 修正了 gorm 标签的拼写错误 (移除了 gorm 后面的冒号)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -65,7 +66,7 @@ func (l *larkNotifier) Send(ctx context.Context, content AlarmContent, toAddr st
|
|||||||
"tag": "lark_md",
|
"tag": "lark_md",
|
||||||
"content": fmt.Sprintf("## %s\n**级别**: %s\n**时间**: %s\n\n%s",
|
"content": fmt.Sprintf("## %s\n**级别**: %s\n**时间**: %s\n\n%s",
|
||||||
content.Title,
|
content.Title,
|
||||||
content.Level.String(),
|
content.Level,
|
||||||
content.Timestamp.Format(DefaultTimeFormat),
|
content.Timestamp.Format(DefaultTimeFormat),
|
||||||
content.Message,
|
content.Message,
|
||||||
),
|
),
|
||||||
@@ -171,8 +172,8 @@ func (l *larkNotifier) getAccessToken(ctx context.Context) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Type 返回通知器的类型
|
// Type 返回通知器的类型
|
||||||
func (l *larkNotifier) Type() NotifierType {
|
func (l *larkNotifier) Type() models.NotifierType {
|
||||||
return NotifierTypeLark
|
return models.NotifierTypeLark
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- API 数据结构 ---
|
// --- API 数据结构 ---
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// logNotifier 实现了 Notifier 接口,用于将告警信息记录到日志中。
|
// logNotifier 实现了 Notifier 接口,用于将告警信息记录到日志中。
|
||||||
@@ -24,10 +25,10 @@ func NewLogNotifier(ctx context.Context) Notifier {
|
|||||||
func (l *logNotifier) Send(ctx context.Context, content AlarmContent, toAddr string) error {
|
func (l *logNotifier) Send(ctx context.Context, content AlarmContent, toAddr string) error {
|
||||||
logger := logs.TraceLogger(ctx, l.ctx, "Send")
|
logger := logs.TraceLogger(ctx, l.ctx, "Send")
|
||||||
logger.Infow("告警已记录到日志",
|
logger.Infow("告警已记录到日志",
|
||||||
"notifierType", NotifierTypeLog,
|
"notifierType", models.NotifierTypeLog,
|
||||||
"title", content.Title,
|
"title", content.Title,
|
||||||
"message", content.Message,
|
"message", content.Message,
|
||||||
"level", content.Level.String(),
|
"level", content.Level,
|
||||||
"timestamp", content.Timestamp.Format(DefaultTimeFormat),
|
"timestamp", content.Timestamp.Format(DefaultTimeFormat),
|
||||||
"toAddr", toAddr,
|
"toAddr", toAddr,
|
||||||
)
|
)
|
||||||
@@ -35,6 +36,6 @@ func (l *logNotifier) Send(ctx context.Context, content AlarmContent, toAddr str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Type 返回通知器的类型。
|
// Type 返回通知器的类型。
|
||||||
func (l *logNotifier) Type() NotifierType {
|
func (l *logNotifier) Type() models.NotifierType {
|
||||||
return NotifierTypeLog
|
return models.NotifierTypeLog
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap/zapcore"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultTimeFormat 定义了所有通知中统一使用的时间格式。
|
// DefaultTimeFormat 定义了所有通知中统一使用的时间格式。
|
||||||
const DefaultTimeFormat = "2006-01-02 15:04:05"
|
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 定义了通知的内容
|
// AlarmContent 定义了通知的内容
|
||||||
type AlarmContent struct {
|
type AlarmContent struct {
|
||||||
// 通知标题
|
// 通知标题
|
||||||
@@ -31,7 +17,7 @@ type AlarmContent struct {
|
|||||||
// 通知信息
|
// 通知信息
|
||||||
Message string
|
Message string
|
||||||
// 通知级别
|
// 通知级别
|
||||||
Level zapcore.Level
|
Level models.SeverityLevel
|
||||||
// 通知时间
|
// 通知时间
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
}
|
}
|
||||||
@@ -41,5 +27,5 @@ type Notifier interface {
|
|||||||
// Send 发送通知
|
// Send 发送通知
|
||||||
Send(ctx context.Context, content AlarmContent, toAddr string) error
|
Send(ctx context.Context, content AlarmContent, toAddr string) error
|
||||||
// Type 返回通知器的类型
|
// Type 返回通知器的类型
|
||||||
Type() NotifierType
|
Type() models.NotifierType
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// smtpNotifier 实现了 Notifier 接口,用于通过 SMTP 发送邮件通知。
|
// 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",
|
body := fmt.Sprintf("级别: %s\n时间: %s\n\n%s",
|
||||||
content.Level.String(),
|
content.Level,
|
||||||
content.Timestamp.Format(DefaultTimeFormat),
|
content.Timestamp.Format(DefaultTimeFormat),
|
||||||
content.Message,
|
content.Message,
|
||||||
)
|
)
|
||||||
@@ -71,6 +73,6 @@ func (s *smtpNotifier) Send(ctx context.Context, content AlarmContent, toAddr st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Type 返回通知器的类型
|
// Type 返回通知器的类型
|
||||||
func (s *smtpNotifier) Type() NotifierType {
|
func (s *smtpNotifier) Type() models.NotifierType {
|
||||||
return NotifierTypeSMTP
|
return models.NotifierTypeSMTP
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -55,7 +57,7 @@ func (w *wechatNotifier) Send(ctx context.Context, content AlarmContent, toAddr
|
|||||||
// 2. 构建 markdown 内容
|
// 2. 构建 markdown 内容
|
||||||
markdownContent := fmt.Sprintf("## %s\n> 级别: <font color=\"warning\">%s</font>\n> 时间: %s\n\n%s",
|
markdownContent := fmt.Sprintf("## %s\n> 级别: <font color=\"warning\">%s</font>\n> 时间: %s\n\n%s",
|
||||||
content.Title,
|
content.Title,
|
||||||
content.Level.String(),
|
content.Level,
|
||||||
content.Timestamp.Format(DefaultTimeFormat),
|
content.Timestamp.Format(DefaultTimeFormat),
|
||||||
content.Message,
|
content.Message,
|
||||||
)
|
)
|
||||||
@@ -142,8 +144,8 @@ func (w *wechatNotifier) getAccessToken() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Type 返回通知器的类型
|
// Type 返回通知器的类型
|
||||||
func (w *wechatNotifier) Type() NotifierType {
|
func (w *wechatNotifier) Type() models.NotifierType {
|
||||||
return NotifierTypeWeChat
|
return models.NotifierTypeWeChat
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- API 数据结构 ---
|
// --- API 数据结构 ---
|
||||||
|
|||||||
430
internal/infra/repository/alarm_repository.go
Normal file
430
internal/infra/repository/alarm_repository.go
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"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 *uint32 // 按告警来源ID过滤
|
||||||
|
Level *models.SeverityLevel // 按告警严重性等级过滤
|
||||||
|
IsIgnored *bool // 按是否被忽略过滤
|
||||||
|
TriggerTime *time.Time // 告警触发时间范围 - 开始时间
|
||||||
|
EndTime *time.Time // 告警触发时间范围 - 结束时间
|
||||||
|
OrderBy string // 排序字段,例如 "trigger_time DESC"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HistoricalAlarmListOptions 定义了查询历史告警列表时的可选参数
|
||||||
|
type HistoricalAlarmListOptions struct {
|
||||||
|
SourceType *models.AlarmSourceType // 按告警来源类型过滤
|
||||||
|
SourceID *uint32 // 按告警来源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 创建一条新的活跃告警记录
|
||||||
|
CreateActiveAlarm(ctx context.Context, alarm *models.ActiveAlarm) error
|
||||||
|
|
||||||
|
// IsAlarmActiveInUse 检查具有相同来源和告警代码的告警当前是否处于活跃表中
|
||||||
|
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 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 uint32) error
|
||||||
|
|
||||||
|
// UpdateIgnoreStatus 更新指定告警的忽略状态
|
||||||
|
UpdateIgnoreStatus(ctx context.Context, id uint32, isIgnored bool, ignoredUntil *time.Time) error
|
||||||
|
|
||||||
|
// ListActiveAlarms 支持分页和过滤的活跃告警列表查询。
|
||||||
|
// 返回活跃告警列表、总记录数和错误。
|
||||||
|
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: 告警新的忽略状态。
|
||||||
|
// ignoredUntil: 告警新的忽略截止时间 (nil 表示没有忽略截止时间/已取消忽略)。
|
||||||
|
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]uint32) ([]models.ActiveAlarm, error)
|
||||||
|
// 查询满足发送告警消息条件的记录总数
|
||||||
|
CountAlarmsForNotification(ctx context.Context, intervalByLevel map[models.SeverityLevel]uint32) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAlarmActiveInUse 检查具有相同来源和告警代码的告警当前是否处于活跃表中
|
||||||
|
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{}).
|
||||||
|
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 uint32, 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 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 uint32, 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")
|
||||||
|
// --- 校验分页参数 ---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 uint32, 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]uint32) (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]uint32) ([]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]uint32) *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]uint32, 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)
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"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/models"
|
||||||
@@ -12,12 +13,14 @@ import (
|
|||||||
|
|
||||||
// AreaControllerRepository 定义了对 AreaController 模型的数据库操作接口
|
// AreaControllerRepository 定义了对 AreaController 模型的数据库操作接口
|
||||||
type AreaControllerRepository interface {
|
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)
|
FindByNetworkID(ctx context.Context, networkID string) (*models.AreaController, error)
|
||||||
Create(ctx context.Context, ac *models.AreaController) error
|
Create(ctx context.Context, ac *models.AreaController) error
|
||||||
ListAll(ctx context.Context) ([]*models.AreaController, error)
|
ListAll(ctx context.Context) ([]*models.AreaController, error)
|
||||||
Update(ctx context.Context, ac *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 uint32, ignoredTaskTypes []models.TaskType) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gormAreaControllerRepository 是 AreaControllerRepository 的 GORM 实现。
|
// gormAreaControllerRepository 是 AreaControllerRepository 的 GORM 实现。
|
||||||
@@ -57,7 +60,7 @@ func (r *gormAreaControllerRepository) Update(ctx context.Context, ac *models.Ar
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete 删除一个 AreaController 记录。
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "Delete")
|
||||||
if err := r.db.WithContext(repoCtx).Delete(&models.AreaController{}, id).Error; err != nil {
|
if err := r.db.WithContext(repoCtx).Delete(&models.AreaController{}, id).Error; err != nil {
|
||||||
return fmt.Errorf("删除区域主控失败: %w", err)
|
return fmt.Errorf("删除区域主控失败: %w", err)
|
||||||
@@ -66,7 +69,7 @@ func (r *gormAreaControllerRepository) Delete(ctx context.Context, id uint) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindByID 通过 ID 查找一个 AreaController。
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID")
|
||||||
var areaController models.AreaController
|
var areaController models.AreaController
|
||||||
if err := r.db.WithContext(repoCtx).First(&areaController, id).Error; err != nil {
|
if err := r.db.WithContext(repoCtx).First(&areaController, id).Error; err != nil {
|
||||||
@@ -84,3 +87,66 @@ func (r *gormAreaControllerRepository) FindByNetworkID(ctx context.Context, netw
|
|||||||
}
|
}
|
||||||
return &areaController, nil
|
return &areaController, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsAreaControllerUsedByTasks 检查区域主控是否被特定任务类型使用,可以忽略指定任务类型
|
||||||
|
func (r *gormAreaControllerRepository) IsAreaControllerUsedByTasks(ctx context.Context, areaControllerID uint32, 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 // 没有发现任何未被忽略的任务正在使用此区域主控
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// DeviceCommandLogListOptions 定义了查询设备命令日志时的可选参数
|
// DeviceCommandLogListOptions 定义了查询设备命令日志时的可选参数
|
||||||
type DeviceCommandLogListOptions struct {
|
type DeviceCommandLogListOptions struct {
|
||||||
DeviceID *uint
|
DeviceID *uint32
|
||||||
ReceivedSuccess *bool
|
ReceivedSuccess *bool
|
||||||
StartTime *time.Time // 基于 sent_at 字段
|
StartTime *time.Time // 基于 sent_at 字段
|
||||||
EndTime *time.Time // 基于 sent_at 字段
|
EndTime *time.Time // 基于 sent_at 字段
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ type DeviceRepository interface {
|
|||||||
Create(ctx context.Context, device *models.Device) error
|
Create(ctx context.Context, device *models.Device) error
|
||||||
|
|
||||||
// FindByID 根据主键 ID 查找设备
|
// FindByID 根据主键 ID 查找设备
|
||||||
FindByID(ctx context.Context, id uint) (*models.Device, error)
|
FindByID(ctx context.Context, id uint32) (*models.Device, error)
|
||||||
|
|
||||||
// FindByIDString 根据字符串形式的主键 ID 查找设备
|
// FindByIDString 根据字符串形式的主键 ID 查找设备
|
||||||
FindByIDString(ctx context.Context, id string) (*models.Device, error)
|
FindByIDString(ctx context.Context, id string) (*models.Device, error)
|
||||||
@@ -30,28 +30,28 @@ type DeviceRepository interface {
|
|||||||
ListAllSensors(ctx context.Context) ([]*models.Device, error)
|
ListAllSensors(ctx context.Context) ([]*models.Device, error)
|
||||||
|
|
||||||
// ListByAreaControllerID 根据区域主控 ID 列出所有子设备
|
// ListByAreaControllerID 根据区域主控 ID 列出所有子设备
|
||||||
ListByAreaControllerID(ctx context.Context, areaControllerID uint) ([]*models.Device, error)
|
ListByAreaControllerID(ctx context.Context, areaControllerID uint32) ([]*models.Device, error)
|
||||||
|
|
||||||
// FindByDeviceTemplateID 根据设备模板ID查找所有使用该模板的设备
|
// FindByDeviceTemplateID 根据设备模板ID查找所有使用该模板的设备
|
||||||
FindByDeviceTemplateID(ctx context.Context, deviceTemplateID uint) ([]*models.Device, error)
|
FindByDeviceTemplateID(ctx context.Context, deviceTemplateID uint32) ([]*models.Device, error)
|
||||||
|
|
||||||
// Update 更新一个已有的设备信息
|
// Update 更新一个已有的设备信息
|
||||||
Update(ctx context.Context, device *models.Device) error
|
Update(ctx context.Context, device *models.Device) error
|
||||||
|
|
||||||
// Delete 根据主键 ID 删除一个设备
|
// Delete 根据主键 ID 删除一个设备
|
||||||
Delete(ctx context.Context, id uint) error
|
Delete(ctx context.Context, id uint32) error
|
||||||
|
|
||||||
// FindByAreaControllerAndPhysicalAddress 根据区域主控ID和物理地址(总线号、总线地址)查找设备
|
// 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 在指定事务中根据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 检查设备是否被任何任务使用,可以忽略指定任务类型
|
||||||
IsDeviceInUse(ctx context.Context, deviceID uint) (bool, error)
|
IsDeviceInUse(ctx context.Context, deviceID uint32, ignoredTaskTypes []models.TaskType) (bool, error)
|
||||||
|
|
||||||
// IsAreaControllerInUse 检查区域主控是否被任何设备使用
|
// IsAreaControllerInUse 检查区域主控是否被任何设备使用
|
||||||
IsAreaControllerInUse(ctx context.Context, areaControllerID uint) (bool, error)
|
IsAreaControllerInUse(ctx context.Context, areaControllerID uint32) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gormDeviceRepository 是 DeviceRepository 的 GORM 实现
|
// gormDeviceRepository 是 DeviceRepository 的 GORM 实现
|
||||||
@@ -73,7 +73,7 @@ func (r *gormDeviceRepository) Create(ctx context.Context, device *models.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindByID 根据 ID 查找设备
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID")
|
||||||
var device models.Device
|
var device models.Device
|
||||||
if err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").First(&device, id).Error; err != nil {
|
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列表获取设备
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetDevicesByIDsTx")
|
||||||
var devices []models.Device
|
var devices []models.Device
|
||||||
if len(ids) == 0 {
|
if len(ids) == 0 {
|
||||||
@@ -98,14 +98,13 @@ func (r *gormDeviceRepository) GetDevicesByIDsTx(ctx context.Context, tx *gorm.D
|
|||||||
// FindByIDString 根据字符串形式的主键 ID 查找设备
|
// FindByIDString 根据字符串形式的主键 ID 查找设备
|
||||||
func (r *gormDeviceRepository) FindByIDString(ctx context.Context, id string) (*models.Device, error) {
|
func (r *gormDeviceRepository) FindByIDString(ctx context.Context, id string) (*models.Device, error) {
|
||||||
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByIDString")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByIDString")
|
||||||
// 将字符串ID转换为uint64
|
|
||||||
idInt, err := strconv.ParseUint(id, 10, 64)
|
idInt, err := strconv.ParseUint(id, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 如果转换失败,说明ID格式不正确,返回一个明确的错误
|
// 如果转换失败,说明ID格式不正确,返回一个明确的错误
|
||||||
return nil, fmt.Errorf("无效的设备ID格式: %w", err)
|
return nil, fmt.Errorf("无效的设备ID格式: %w", err)
|
||||||
}
|
}
|
||||||
// 调用已有的 FindByID 方法
|
// 调用已有的 FindByID 方法
|
||||||
return r.FindByID(repoCtx, uint(idInt))
|
return r.FindByID(repoCtx, uint32(idInt))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAll 获取所有设备的列表
|
// ListAll 获取所有设备的列表
|
||||||
@@ -133,7 +132,7 @@ func (r *gormDeviceRepository) ListAllSensors(ctx context.Context) ([]*models.De
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListByAreaControllerID 根据区域主控 ID 列出所有子设备
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListByAreaControllerID")
|
||||||
var devices []*models.Device
|
var devices []*models.Device
|
||||||
err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").Where("area_controller_id = ?", areaControllerID).Find(&devices).Error
|
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查找所有使用该模板的设备
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByDeviceTemplateID")
|
||||||
var devices []*models.Device
|
var devices []*models.Device
|
||||||
err := r.db.WithContext(repoCtx).Where("device_template_id = ?", deviceTemplateID).Find(&devices).Error
|
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 删除一个设备
|
// Delete 根据 ID 删除一个设备
|
||||||
// GORM 使用软删除,记录不会从数据库中物理移除,而是设置 DeletedAt 字段。
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "Delete")
|
||||||
return r.db.WithContext(repoCtx).Delete(&models.Device{}, id).Error
|
return r.db.WithContext(repoCtx).Delete(&models.Device{}, id).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindByAreaControllerAndPhysicalAddress 根据区域主控ID和物理地址(总线号、总线地址)查找设备
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByAreaControllerAndPhysicalAddress")
|
||||||
var device models.Device
|
var device models.Device
|
||||||
err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").
|
err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").
|
||||||
@@ -184,12 +183,22 @@ func (r *gormDeviceRepository) FindByAreaControllerAndPhysicalAddress(ctx contex
|
|||||||
return &device, nil
|
return &device, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDeviceInUse 检查设备是否被任何任务使用
|
// IsDeviceInUse 检查设备是否被任何任务使用,可以忽略指定任务类型
|
||||||
func (r *gormDeviceRepository) IsDeviceInUse(ctx context.Context, deviceID uint) (bool, error) {
|
func (r *gormDeviceRepository) IsDeviceInUse(ctx context.Context, deviceID uint32, ignoredTaskTypes []models.TaskType) (bool, error) {
|
||||||
repoCtx := logs.AddFuncName(ctx, r.ctx, "IsDeviceInUse")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "IsDeviceInUse")
|
||||||
var count int64
|
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 {
|
if err != nil {
|
||||||
return false, fmt.Errorf("查询设备任务关联失败: %w", err)
|
return false, fmt.Errorf("查询设备任务关联失败: %w", err)
|
||||||
}
|
}
|
||||||
@@ -197,7 +206,7 @@ func (r *gormDeviceRepository) IsDeviceInUse(ctx context.Context, deviceID uint)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsAreaControllerInUse 检查区域主控是否被任何设备使用
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "IsAreaControllerInUse")
|
||||||
var count int64
|
var count int64
|
||||||
if err := r.db.WithContext(repoCtx).Model(&models.Device{}).Where("area_controller_id = ?", areaControllerID).Count(&count).Error; err != nil {
|
if err := r.db.WithContext(repoCtx).Model(&models.Device{}).Where("area_controller_id = ?", areaControllerID).Count(&count).Error; err != nil {
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import (
|
|||||||
// DeviceTemplateRepository 定义了设备模板数据访问的接口
|
// DeviceTemplateRepository 定义了设备模板数据访问的接口
|
||||||
type DeviceTemplateRepository interface {
|
type DeviceTemplateRepository interface {
|
||||||
Create(ctx context.Context, deviceTemplate *models.DeviceTemplate) error
|
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)
|
FindByName(ctx context.Context, name string) (*models.DeviceTemplate, error)
|
||||||
ListAll(ctx context.Context) ([]*models.DeviceTemplate, error)
|
ListAll(ctx context.Context) ([]*models.DeviceTemplate, error)
|
||||||
Update(ctx context.Context, deviceTemplate *models.DeviceTemplate) error
|
Update(ctx context.Context, deviceTemplate *models.DeviceTemplate) error
|
||||||
Delete(ctx context.Context, id uint) error
|
Delete(ctx context.Context, id uint32) error
|
||||||
IsInUse(ctx context.Context, id uint) (bool, error)
|
IsInUse(ctx context.Context, id uint32) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gormDeviceTemplateRepository 是 DeviceTemplateRepository 的 GORM 实现
|
// gormDeviceTemplateRepository 是 DeviceTemplateRepository 的 GORM 实现
|
||||||
@@ -40,7 +40,7 @@ func (r *gormDeviceTemplateRepository) Create(ctx context.Context, deviceTemplat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindByID 根据ID查找设备模板
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID")
|
||||||
var deviceTemplate models.DeviceTemplate
|
var deviceTemplate models.DeviceTemplate
|
||||||
if err := r.db.WithContext(repoCtx).First(&deviceTemplate, id).Error; err != nil {
|
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 检查设备模板是否正在被设备使用
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "IsInUse")
|
||||||
var count int64
|
var count int64
|
||||||
if err := r.db.WithContext(repoCtx).Model(&models.Device{}).Where("device_template_id = ?", id).Count(&count).Error; err != nil {
|
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 软删除数据库中的设备模板
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "Delete")
|
||||||
if err := r.db.WithContext(repoCtx).Delete(&models.DeviceTemplate{}, id).Error; err != nil {
|
if err := r.db.WithContext(repoCtx).Delete(&models.DeviceTemplate{}, id).Error; err != nil {
|
||||||
return fmt.Errorf("删除设备模板失败: %w", err)
|
return fmt.Errorf("删除设备模板失败: %w", err)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
// PlanExecutionLogListOptions 定义了查询计划执行日志时的可选参数
|
// PlanExecutionLogListOptions 定义了查询计划执行日志时的可选参数
|
||||||
type PlanExecutionLogListOptions struct {
|
type PlanExecutionLogListOptions struct {
|
||||||
PlanID *uint
|
PlanID *uint32
|
||||||
Status *models.ExecutionStatus
|
Status *models.ExecutionStatus
|
||||||
StartTime *time.Time // 基于 created_at 字段
|
StartTime *time.Time // 基于 created_at 字段
|
||||||
EndTime *time.Time // 基于 created_at 字段
|
EndTime *time.Time // 基于 created_at 字段
|
||||||
@@ -22,7 +22,7 @@ type PlanExecutionLogListOptions struct {
|
|||||||
|
|
||||||
// TaskExecutionLogListOptions 定义了查询任务执行日志时的可选参数
|
// TaskExecutionLogListOptions 定义了查询任务执行日志时的可选参数
|
||||||
type TaskExecutionLogListOptions struct {
|
type TaskExecutionLogListOptions struct {
|
||||||
PlanExecutionLogID *uint
|
PlanExecutionLogID *uint32
|
||||||
TaskID *int
|
TaskID *int
|
||||||
Status *models.ExecutionStatus
|
Status *models.ExecutionStatus
|
||||||
StartTime *time.Time // 基于 created_at 字段
|
StartTime *time.Time // 基于 created_at 字段
|
||||||
@@ -33,26 +33,26 @@ type TaskExecutionLogListOptions struct {
|
|||||||
// ExecutionLogRepository 定义了与执行日志交互的接口。
|
// ExecutionLogRepository 定义了与执行日志交互的接口。
|
||||||
type ExecutionLogRepository interface {
|
type ExecutionLogRepository interface {
|
||||||
// --- Existing methods ---
|
// --- Existing methods ---
|
||||||
UpdateTaskExecutionLogStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error
|
UpdateTaskExecutionLogStatusByIDs(ctx context.Context, logIDs []uint32, status models.ExecutionStatus) error
|
||||||
UpdateTaskExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error
|
UpdateTaskExecutionLogStatus(ctx context.Context, logID uint32, status models.ExecutionStatus) error
|
||||||
CreateTaskExecutionLog(ctx context.Context, log *models.TaskExecutionLog) error
|
CreateTaskExecutionLog(ctx context.Context, log *models.TaskExecutionLog) error
|
||||||
CreatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error
|
CreatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error
|
||||||
UpdatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error
|
UpdatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error
|
||||||
CreateTaskExecutionLogsInBatch(ctx context.Context, logs []*models.TaskExecutionLog) error
|
CreateTaskExecutionLogsInBatch(ctx context.Context, logs []*models.TaskExecutionLog) error
|
||||||
UpdateTaskExecutionLog(ctx context.Context, log *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 更新计划执行日志的状态
|
||||||
UpdatePlanExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error
|
UpdatePlanExecutionLogStatus(ctx context.Context, logID uint32, status models.ExecutionStatus) error
|
||||||
|
|
||||||
// UpdatePlanExecutionLogsStatusByIDs 批量更新计划执行日志的状态
|
// UpdatePlanExecutionLogsStatusByIDs 批量更新计划执行日志的状态
|
||||||
UpdatePlanExecutionLogsStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error
|
UpdatePlanExecutionLogsStatusByIDs(ctx context.Context, logIDs []uint32, status models.ExecutionStatus) error
|
||||||
|
|
||||||
// FindIncompletePlanExecutionLogs 查找所有未完成的计划执行日志
|
// FindIncompletePlanExecutionLogs 查找所有未完成的计划执行日志
|
||||||
FindIncompletePlanExecutionLogs(ctx context.Context) ([]models.PlanExecutionLog, error)
|
FindIncompletePlanExecutionLogs(ctx context.Context) ([]models.PlanExecutionLog, error)
|
||||||
// FindInProgressPlanExecutionLogByPlanID 根据 PlanID 查找正在进行的计划执行日志
|
// FindInProgressPlanExecutionLogByPlanID 根据 PlanID 查找正在进行的计划执行日志
|
||||||
FindInProgressPlanExecutionLogByPlanID(ctx context.Context, planID uint) (*models.PlanExecutionLog, error)
|
FindInProgressPlanExecutionLogByPlanID(ctx context.Context, planID uint32) (*models.PlanExecutionLog, error)
|
||||||
// FindIncompleteTaskExecutionLogsByPlanLogID 根据计划日志ID查找所有未完成的任务日志
|
// 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 将所有状态为 ExecutionStatusStarted 和 ExecutionStatusWaiting 的计划状态都修改为 ExecutionStatusFailed
|
||||||
FailAllIncompletePlanExecutionLogs(ctx context.Context) error
|
FailAllIncompletePlanExecutionLogs(ctx context.Context) error
|
||||||
@@ -60,16 +60,16 @@ type ExecutionLogRepository interface {
|
|||||||
CancelAllIncompleteTaskExecutionLogs(ctx context.Context) error
|
CancelAllIncompleteTaskExecutionLogs(ctx context.Context) error
|
||||||
|
|
||||||
// FindPlanExecutionLogByID 根据ID查找计划执行日志
|
// FindPlanExecutionLogByID 根据ID查找计划执行日志
|
||||||
FindPlanExecutionLogByID(ctx context.Context, id uint) (*models.PlanExecutionLog, error)
|
FindPlanExecutionLogByID(ctx context.Context, id uint32) (*models.PlanExecutionLog, error)
|
||||||
|
|
||||||
// CountIncompleteTasksByPlanLogID 计算一个计划执行中未完成的任务数量
|
// CountIncompleteTasksByPlanLogID 计算一个计划执行中未完成的任务数量
|
||||||
CountIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint) (int64, error)
|
CountIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint32) (int64, error)
|
||||||
|
|
||||||
// FailPlanExecution 将指定的计划执行标记为失败
|
// FailPlanExecution 将指定的计划执行标记为失败
|
||||||
FailPlanExecution(ctx context.Context, planLogID uint, errorMessage string) error
|
FailPlanExecution(ctx context.Context, planLogID uint32, errorMessage string) error
|
||||||
|
|
||||||
// CancelIncompleteTasksByPlanLogID 取消一个计划执行中的所有未完成任务
|
// CancelIncompleteTasksByPlanLogID 取消一个计划执行中的所有未完成任务
|
||||||
CancelIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint, reason string) error
|
CancelIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint32, reason string) error
|
||||||
|
|
||||||
// --- New methods ---
|
// --- New methods ---
|
||||||
ListPlanExecutionLogs(ctx context.Context, opts PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error)
|
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 ---
|
// --- 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateTaskExecutionLogStatusByIDs")
|
||||||
if len(logIDs) == 0 {
|
if len(logIDs) == 0 {
|
||||||
return nil
|
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
|
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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateTaskExecutionLogStatus")
|
||||||
return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Where("id = ?", logID).Update("status", status).Error
|
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 查找单个任务执行日志。
|
// FindTaskExecutionLogByID 根据 ID 查找单个任务执行日志。
|
||||||
// 它会预加载关联的 Task 信息。
|
// 它会预加载关联的 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindTaskExecutionLogByID")
|
||||||
var log models.TaskExecutionLog
|
var log models.TaskExecutionLog
|
||||||
// 使用 Preload("Task") 来确保关联的任务信息被一并加载
|
// 使用 Preload("Task") 来确保关联的任务信息被一并加载
|
||||||
@@ -240,13 +240,13 @@ func (r *gormExecutionLogRepository) FindTaskExecutionLogByID(ctx context.Contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePlanExecutionLogStatus 更新计划执行日志的状态
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanExecutionLogStatus")
|
||||||
return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}).Where("id = ?", logID).Update("status", status).Error
|
return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}).Where("id = ?", logID).Update("status", status).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePlanExecutionLogsStatusByIDs 批量更新计划执行日志的状态
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanExecutionLogsStatusByIDs")
|
||||||
if len(logIDs) == 0 {
|
if len(logIDs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -263,7 +263,7 @@ func (r *gormExecutionLogRepository) FindIncompletePlanExecutionLogs(ctx context
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindInProgressPlanExecutionLogByPlanID 根据 PlanID 查找正在进行的计划执行日志
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindInProgressPlanExecutionLogByPlanID")
|
||||||
var log models.PlanExecutionLog
|
var log models.PlanExecutionLog
|
||||||
err := r.db.WithContext(repoCtx).Where("plan_id = ? AND status = ?", planID, models.ExecutionStatusStarted).First(&log).Error
|
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查找所有未完成的任务日志
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindIncompleteTaskExecutionLogsByPlanLogID")
|
||||||
var logs []models.TaskExecutionLog
|
var logs []models.TaskExecutionLog
|
||||||
err := r.db.WithContext(repoCtx).Where("plan_execution_log_id = ? AND (status = ? OR status = ?)",
|
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查找计划执行日志
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPlanExecutionLogByID")
|
||||||
var log models.PlanExecutionLog
|
var log models.PlanExecutionLog
|
||||||
err := r.db.WithContext(repoCtx).First(&log, id).Error
|
err := r.db.WithContext(repoCtx).First(&log, id).Error
|
||||||
@@ -315,7 +315,7 @@ func (r *gormExecutionLogRepository) FindPlanExecutionLogByID(ctx context.Contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CountIncompleteTasksByPlanLogID 计算一个计划执行中未完成的任务数量
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "CountIncompleteTasksByPlanLogID")
|
||||||
var count int64
|
var count int64
|
||||||
err := r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).
|
err := r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).
|
||||||
@@ -326,7 +326,7 @@ func (r *gormExecutionLogRepository) CountIncompleteTasksByPlanLogID(ctx context
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FailPlanExecution 将指定的计划执行标记为失败
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "FailPlanExecution")
|
||||||
return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}).
|
return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}).
|
||||||
Where("id = ?", planLogID).
|
Where("id = ?", planLogID).
|
||||||
@@ -338,7 +338,7 @@ func (r *gormExecutionLogRepository) FailPlanExecution(ctx context.Context, plan
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CancelIncompleteTasksByPlanLogID 取消一个计划执行中的所有未完成任务
|
// 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")
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "CancelIncompleteTasksByPlanLogID")
|
||||||
return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).
|
return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).
|
||||||
Where("plan_execution_log_id = ? AND status IN (?, ?)",
|
Where("plan_execution_log_id = ? AND status IN (?, ?)",
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import (
|
|||||||
|
|
||||||
// MedicationLogListOptions 定义了查询用药记录时的可选参数
|
// MedicationLogListOptions 定义了查询用药记录时的可选参数
|
||||||
type MedicationLogListOptions struct {
|
type MedicationLogListOptions struct {
|
||||||
PigBatchID *uint
|
PigBatchID *uint32
|
||||||
MedicationID *uint
|
MedicationID *uint32
|
||||||
Reason *models.MedicationReasonType
|
Reason *models.MedicationReasonType
|
||||||
OperatorID *uint
|
OperatorID *uint32
|
||||||
StartTime *time.Time
|
StartTime *time.Time
|
||||||
EndTime *time.Time
|
EndTime *time.Time
|
||||||
OrderBy string // 例如 "happened_at desc"
|
OrderBy string // 例如 "happened_at desc"
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"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/models"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/notify"
|
|
||||||
|
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -14,8 +13,8 @@ import (
|
|||||||
|
|
||||||
// NotificationListOptions 定义了查询通知列表时的可选参数
|
// NotificationListOptions 定义了查询通知列表时的可选参数
|
||||||
type NotificationListOptions struct {
|
type NotificationListOptions struct {
|
||||||
UserID *uint // 按用户ID过滤
|
UserID *uint32 // 按用户ID过滤
|
||||||
NotifierType *notify.NotifierType // 按通知器类型过滤
|
NotifierType *models.NotifierType // 按通知器类型过滤
|
||||||
Status *models.NotificationStatus // 按通知状态过滤 (例如:"success", "failed")
|
Status *models.NotificationStatus // 按通知状态过滤 (例如:"success", "failed")
|
||||||
Level *zapcore.Level // 按通知等级过滤 (例如:"info", "warning", "error")
|
Level *zapcore.Level // 按通知等级过滤 (例如:"info", "warning", "error")
|
||||||
StartTime *time.Time // 通知内容生成时间范围 - 开始时间 (对应 AlarmTimestamp)
|
StartTime *time.Time // 通知内容生成时间范围 - 开始时间 (对应 AlarmTimestamp)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// PendingCollectionListOptions 定义了查询待采集请求时的可选参数
|
// PendingCollectionListOptions 定义了查询待采集请求时的可选参数
|
||||||
type PendingCollectionListOptions struct {
|
type PendingCollectionListOptions struct {
|
||||||
DeviceID *uint
|
DeviceID *uint32
|
||||||
Status *models.PendingCollectionStatus
|
Status *models.PendingCollectionStatus
|
||||||
StartTime *time.Time // 基于 created_at 字段
|
StartTime *time.Time // 基于 created_at 字段
|
||||||
EndTime *time.Time // 基于 created_at 字段
|
EndTime *time.Time // 基于 created_at 字段
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user