package models import ( "encoding/json" "errors" "strings" "gorm.io/datatypes" "gorm.io/gorm" ) // 设备属性名大全 var ( // 普通开关式设备 BusNumber = "bus_number" // 总线号 BusAddress = "bus_address" // 总线地址 RelayChannel = "relay_channel" // 继电器通道号 // 区域主控 LoRaAddress = "lora_address" // 区域主控 LoRa 地址, 如果使用LoRa网关也可能是LoRa网关记录的设备ID ) // --- Properties 结构体定义 --- // LoraProperties 定义了区域主控的特有属性 type LoraProperties struct { LoraAddress string `json:"lora_address"` // LoRa 地址 } // BusProperties 定义了总线设备的特有属性 type BusProperties struct { BusNumber int `json:"bus_number"` // 485 总线号 BusAddress int `json:"bus_address"` // 485 总线地址 RelayChannel int `json:"relay_channel"` // 继电器通道号 } // AreaController 是一个LoRa转总线(如485)的通信网关 type AreaController struct { gorm.Model // Name 是主控的业务名称,例如 "1号猪舍主控" Name string `gorm:"not null;unique" json:"name"` // NetworkID 是主控在通信网络中的唯一标识,例如 LoRaWAN 的 DevEUI。 // 这是 transport 层用来寻址的关键。 NetworkID string `gorm:"not null;unique;index" json:"network_id"` // Location 描述了主控的物理安装位置。 Location string `gorm:"index" json:"location"` // Status 表示主控的在线状态等,可以后续扩展。 Status string `gorm:"default:'unknown'" json:"status"` // Properties 用于存储其他与主控相关的属性,例如硬件版本、固件版本等。 Properties datatypes.JSON `json:"properties"` } // SelfCheck 对 AreaController 的关键字段进行业务逻辑验证。 func (ac *AreaController) SelfCheck() error { if strings.TrimSpace(ac.NetworkID) == "" { return errors.New("区域主控的 NetworkID 不能为空") } return nil } // TableName 自定义 GORM 使用的数据库表名 func (AreaController) TableName() string { return "area_controllers" } // Device 代表系统中的所有普通设备 type Device struct { // gorm.Model 内嵌了标准模型字段 (ID, CreatedAt, UpdatedAt, DeletedAt) gorm.Model // Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器" Name string `gorm:"not null" json:"name"` // DeviceTemplateID 是设备模板的外键 DeviceTemplateID uint `gorm:"not null;index" json:"device_template_id"` // DeviceTemplate 是设备的模板,包含了设备的通用信息 DeviceTemplate DeviceTemplate `json:"device_template"` // AreaControllerID 是区域主控的外键 AreaControllerID uint `gorm:"not null;index" json:"area_controller_id"` // AreaController 是设备所属的区域主控 AreaController AreaController `json:"area_controller"` // Location 描述了设备的物理安装位置,例如 "1号猪舍东侧",方便运维。建立索引以优化按位置查询。 Location string `gorm:"index" json:"location"` // Properties 用于存储特定类型设备的独有属性,采用JSON格式。 // 建议在应用层为不同子类型的设备定义专用的属性结构体(如 LoraProperties, BusProperties),以保证数据一致性。 Properties datatypes.JSON `json:"properties"` } // SelfCheck 对 Device 的关键字段和属性进行业务逻辑验证。 func (d *Device) SelfCheck() error { if d.AreaControllerID == 0 { return errors.New("设备必须关联一个区域主控 (AreaControllerID不能为0)") } if d.DeviceTemplateID == 0 { return errors.New("设备必须关联一个设备模板 (DeviceTemplateID不能为0)") } // 验证 Properties 是否包含必要的总线地址信息 if d.Properties == nil { return errors.New("设备属性 (Properties) 不能为空") } var props map[string]interface{} if err := json.Unmarshal(d.Properties, &props); err != nil { return errors.New("无法解析设备属性 (Properties)") } if _, ok := props[BusAddress]; !ok { return errors.New("设备属性 (Properties) 中缺少总线地址 (bus_address)") } return nil } // TableName 自定义 GORM 使用的数据库表名 func (Device) TableName() string { return "devices" } // ParseProperties 解析 JSON 属性到一个具体的结构体中。 // 调用方需要传入一个指向目标结构体实例的指针。 // 示例: // // var props LoraProperties // if err := device.ParseProperties(&props); err != nil { ... } func (d *Device) ParseProperties(v interface{}) error { if d.Properties == nil { return errors.New("设备属性为空,无法解析") } return json.Unmarshal(d.Properties, v) }