From aed665b6b007c970a9492f7c6e6e9af7e872977a Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sat, 27 Sep 2025 00:58:22 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E6=9E=90device=E5=92=8Ctask=E7=9A=84?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=85=A8=E9=83=A8=E7=94=A8=E5=86=85=E7=BD=AE?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/device/device_controller.go | 2 +- internal/app/controller/plan/converter.go | 2 +- internal/app/service/task/delay_task.go | 3 +-- .../service/task/release_feed_weight_task.go | 2 +- internal/app/service/task/scheduler.go | 3 +-- internal/infra/models/device.go | 21 ++++++++++++------- internal/infra/models/plan.go | 15 +++++++++++++ internal/infra/utils/validation.go | 13 ++++++++++++ 8 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 internal/infra/utils/validation.go diff --git a/internal/app/controller/device/device_controller.go b/internal/app/controller/device/device_controller.go index 3eac4b5..a40c4d4 100644 --- a/internal/app/controller/device/device_controller.go +++ b/internal/app/controller/device/device_controller.go @@ -77,7 +77,7 @@ func newDeviceResponse(device *models.Device) (*DeviceResponse, error) { var props map[string]interface{} if len(device.Properties) > 0 && string(device.Properties) != "null" { - if err := json.Unmarshal(device.Properties, &props); err != nil { + if err := device.ParseProperties(&props); err != nil { return nil, fmt.Errorf("解析设备属性失败 (ID: %d): %w", device.ID, err) } } diff --git a/internal/app/controller/plan/converter.go b/internal/app/controller/plan/converter.go index 1727c14..57dbf96 100644 --- a/internal/app/controller/plan/converter.go +++ b/internal/app/controller/plan/converter.go @@ -189,7 +189,7 @@ func TaskToResponse(task *models.Task) (TaskResponse, error) { var params map[string]interface{} if len(task.Parameters) > 0 && string(task.Parameters) != "null" { - if err := json.Unmarshal(task.Parameters, ¶ms); err != nil { + if err := task.ParseParameters(¶ms); err != nil { return TaskResponse{}, fmt.Errorf("parsing task parameters failed (ID: %d): %w", task.ID, err) } } diff --git a/internal/app/service/task/delay_task.go b/internal/app/service/task/delay_task.go index 5d96a36..dcfdb41 100644 --- a/internal/app/service/task/delay_task.go +++ b/internal/app/service/task/delay_task.go @@ -1,7 +1,6 @@ package task import ( - "encoding/json" "fmt" "time" @@ -46,7 +45,7 @@ func (d *DelayTask) parseParameters() error { } var params DelayTaskParams - err := json.Unmarshal(d.executionTask.Task.Parameters, ¶ms) + err := d.executionTask.Task.ParseParameters(¶ms) if err != nil { d.logger.Errorf("任务 %v: 解析参数失败: %v", d.executionTask.TaskID, err) return fmt.Errorf("任务 %v: 解析参数失败: %v", d.executionTask.TaskID, err) diff --git a/internal/app/service/task/release_feed_weight_task.go b/internal/app/service/task/release_feed_weight_task.go index 7d13685..6541206 100644 --- a/internal/app/service/task/release_feed_weight_task.go +++ b/internal/app/service/task/release_feed_weight_task.go @@ -122,7 +122,7 @@ func (r *ReleaseFeedWeightTask) parseParameters() error { } var params ReleaseFeedWeightTaskParams - err := json.Unmarshal(r.claimedLog.Task.Parameters, ¶ms) + err := r.claimedLog.Task.ParseParameters(¶ms) if err != nil { r.logger.Errorf("任务 %v: 解析参数失败: %v", r.claimedLog.TaskID, err) return fmt.Errorf("任务 %v: 解析参数失败: %v", r.claimedLog.TaskID, err) diff --git a/internal/app/service/task/scheduler.go b/internal/app/service/task/scheduler.go index 6c42be6..d60d6c4 100644 --- a/internal/app/service/task/scheduler.go +++ b/internal/app/service/task/scheduler.go @@ -1,7 +1,6 @@ package task import ( - "encoding/json" "errors" "sync" "time" @@ -305,7 +304,7 @@ func (s *Scheduler) analysisPlan(claimedLog *models.TaskExecutionLog) error { var params struct { PlanID uint `json:"plan_id"` } - if err := json.Unmarshal(claimedLog.Task.Parameters, ¶ms); err != nil { + if err := claimedLog.Task.ParseParameters(¶ms); err != nil { s.logger.Errorf("解析任务参数中的计划ID失败,日志ID: %d, 错误: %v", claimedLog.ID, err) return err } diff --git a/internal/infra/models/device.go b/internal/infra/models/device.go index 90d0b29..5eee658 100644 --- a/internal/infra/models/device.go +++ b/internal/infra/models/device.go @@ -6,6 +6,8 @@ import ( "gorm.io/datatypes" "gorm.io/gorm" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/utils" ) // DeviceType 定义了设备的高级类别 @@ -139,20 +141,23 @@ func (d *Device) SelfCheck() bool { return false } - // 检查通用属性是否存在 - has := func(key string) bool { - _, ok := props[key] - return ok + // hasPureNumeric 检查一个key是否存在于map中,并且其值是纯数字(整数或可解析为整数的字符串) + hasPureNumeric := func(key string) bool { + val, ok := props[key] + if !ok { + return false // Key不存在 + } + return utils.IsPureNumeric(val) } // 根据子类型进行具体校验 switch d.SubType { - // 所有传感器类型都必须有 Command 和总线信息 + // 所有传感器类型都必须有 Command 和总线信息,且总线信息为纯数字 case SubTypeSensorTemp, SubTypeSensorHumidity, SubTypeSensorWeight, SubTypeSensorAmmonia: - return d.Command != "" && has(BusNumber) && has(BusAddress) - // 所有开关类型都必须有继电器和总线信息 + return d.Command != "" && hasPureNumeric(BusNumber) && hasPureNumeric(BusAddress) + // 所有开关类型都必须有继电器和总线信息,且都为纯数字 case SubTypeFan, SubTypeWaterCurtain, SubTypeValveFeed: - return has(BusNumber) && has(BusAddress) && has(RelayChannel) + return hasPureNumeric(BusNumber) && hasPureNumeric(BusAddress) && hasPureNumeric(RelayChannel) // 如果是未知的子类型,或者没有子类型,则认为自检失败 default: return false diff --git a/internal/infra/models/plan.go b/internal/infra/models/plan.go index bbb2315..bf2ce42 100644 --- a/internal/infra/models/plan.go +++ b/internal/infra/models/plan.go @@ -1,6 +1,8 @@ package models import ( + "encoding/json" + "errors" "fmt" "sort" "time" @@ -172,3 +174,16 @@ type Task struct { func (Task) TableName() string { return "tasks" } + +// ParseParameters 解析 JSON 属性到一个具体的结构体中。 +// 调用方需要传入一个指向目标结构体实例的指针。 +// 示例: +// +// var param LoraParameters +// if err := task.ParseParameters(¶m); err != nil { ... } +func (t Task) ParseParameters(v interface{}) error { + if t.Parameters == nil { + return errors.New("设备属性为空,无法解析") + } + return json.Unmarshal(t.Parameters, v) +} diff --git a/internal/infra/utils/validation.go b/internal/infra/utils/validation.go new file mode 100644 index 0000000..bab71e2 --- /dev/null +++ b/internal/infra/utils/validation.go @@ -0,0 +1,13 @@ +package utils + +import ( + "fmt" + "strconv" +) + +// IsPureNumeric 检查值是否纯数字(整数或可转换为整数的字符串)。 +func IsPureNumeric(val interface{}) bool { + v := fmt.Sprintf("%v", val) + _, err := strconv.Atoi(v) + return err == nil +}