2025-09-13 15:14:08 +08:00
|
|
|
|
package models
|
|
|
|
|
|
|
2025-09-26 22:50:08 +08:00
|
|
|
|
import (
|
|
|
|
|
|
"database/sql/driver"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"strconv"
|
|
|
|
|
|
"strings"
|
2025-11-10 22:23:31 +08:00
|
|
|
|
"time"
|
2025-11-07 21:39:24 +08:00
|
|
|
|
|
|
|
|
|
|
"go.uber.org/zap/zapcore"
|
2025-11-10 22:23:31 +08:00
|
|
|
|
"gorm.io/gorm"
|
2025-09-26 22:50:08 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-02 15:51:37 +08:00
|
|
|
|
type AIModel string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
2025-12-02 16:34:14 +08:00
|
|
|
|
AI_MODEL_NONE AIModel = "None"
|
2025-12-02 15:51:37 +08:00
|
|
|
|
AI_MODEL_GEMINI AIModel = "Gemini"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-11-10 22:23:31 +08:00
|
|
|
|
// Model 用于代替gorm.Model, 使用uint32以节约空间
|
|
|
|
|
|
type Model struct {
|
|
|
|
|
|
ID uint32 `gorm:"primarykey"`
|
|
|
|
|
|
CreatedAt time.Time
|
|
|
|
|
|
UpdatedAt time.Time
|
|
|
|
|
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-13 15:14:08 +08:00
|
|
|
|
// GetAllModels 返回一个包含所有数据库模型实例的切片。
|
|
|
|
|
|
// 这个函数用于在数据库初始化时自动迁移所有的表结构。
|
|
|
|
|
|
func GetAllModels() []interface{} {
|
|
|
|
|
|
return []interface{}{
|
2025-10-03 18:27:53 +08:00
|
|
|
|
// Core Models
|
2025-09-13 15:14:08 +08:00
|
|
|
|
&User{},
|
2025-10-03 18:27:53 +08:00
|
|
|
|
&UserActionLog{},
|
|
|
|
|
|
|
|
|
|
|
|
// Device Models
|
2025-09-13 15:14:08 +08:00
|
|
|
|
&Device{},
|
2025-10-03 18:27:53 +08:00
|
|
|
|
&AreaController{},
|
|
|
|
|
|
&DeviceTemplate{},
|
|
|
|
|
|
&SensorData{},
|
|
|
|
|
|
&DeviceCommandLog{},
|
2025-11-03 16:29:57 +08:00
|
|
|
|
&DeviceTask{},
|
2025-10-03 18:27:53 +08:00
|
|
|
|
|
|
|
|
|
|
// Plan & Task Models
|
2025-09-13 15:14:08 +08:00
|
|
|
|
&Plan{},
|
|
|
|
|
|
&SubPlan{},
|
|
|
|
|
|
&Task{},
|
2025-09-17 16:17:36 +08:00
|
|
|
|
&PlanExecutionLog{},
|
|
|
|
|
|
&TaskExecutionLog{},
|
|
|
|
|
|
&PendingTask{},
|
2025-09-26 22:50:08 +08:00
|
|
|
|
&PendingCollection{},
|
2025-10-03 18:27:53 +08:00
|
|
|
|
|
|
|
|
|
|
// Farm Asset Models
|
|
|
|
|
|
&PigHouse{},
|
|
|
|
|
|
&Pen{},
|
|
|
|
|
|
|
|
|
|
|
|
// Pig & Batch Models
|
|
|
|
|
|
&PigBatch{},
|
|
|
|
|
|
&PigBatchLog{},
|
2025-10-03 20:32:34 +08:00
|
|
|
|
&WeighingBatch{},
|
|
|
|
|
|
&WeighingRecord{},
|
2025-10-05 21:20:22 +08:00
|
|
|
|
&PigTransferLog{},
|
2025-10-06 15:35:20 +08:00
|
|
|
|
&PigSickLog{},
|
2025-11-20 14:38:36 +08:00
|
|
|
|
&PigBreed{},
|
|
|
|
|
|
&PigAgeStage{},
|
|
|
|
|
|
&PigType{},
|
|
|
|
|
|
&PigNutrientRequirement{},
|
2025-10-03 18:27:53 +08:00
|
|
|
|
|
2025-10-05 22:09:25 +08:00
|
|
|
|
// Pig Buy & Sell
|
|
|
|
|
|
&PigPurchase{},
|
|
|
|
|
|
&PigSale{},
|
|
|
|
|
|
|
2025-10-03 18:27:53 +08:00
|
|
|
|
// Feed Models
|
|
|
|
|
|
&RawMaterial{},
|
2025-11-18 22:22:31 +08:00
|
|
|
|
&Nutrient{},
|
|
|
|
|
|
&RawMaterialNutrient{},
|
2025-10-03 18:27:53 +08:00
|
|
|
|
&RawMaterialStockLog{},
|
2025-11-22 20:52:15 +08:00
|
|
|
|
&Recipe{},
|
|
|
|
|
|
&RecipeIngredient{},
|
2025-10-03 18:27:53 +08:00
|
|
|
|
|
|
|
|
|
|
// Medication Models
|
|
|
|
|
|
&Medication{},
|
2025-10-06 15:35:20 +08:00
|
|
|
|
&MedicationLog{},
|
2025-10-25 13:28:19 +08:00
|
|
|
|
|
2025-11-07 22:19:55 +08:00
|
|
|
|
// Alarm Models
|
|
|
|
|
|
&ActiveAlarm{},
|
|
|
|
|
|
&HistoricalAlarm{},
|
|
|
|
|
|
|
2025-10-25 13:28:19 +08:00
|
|
|
|
// Notification Models
|
|
|
|
|
|
&Notification{},
|
2025-09-26 22:50:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-10 22:23:31 +08:00
|
|
|
|
// UintArray 是一个自定义类型,代表 uint32 的切片。
|
2025-09-26 22:50:08 +08:00
|
|
|
|
// 它实现了 gorm.Scanner 和 driver.Valuer 接口,
|
|
|
|
|
|
// 以便能与数据库的 bigint[] 类型进行原生映射。
|
2025-11-10 22:23:31 +08:00
|
|
|
|
type UintArray []uint32
|
2025-09-26 22:50:08 +08:00
|
|
|
|
|
|
|
|
|
|
// Value 实现了 driver.Valuer 接口。
|
|
|
|
|
|
// 它告诉 GORM 如何将 UintArray ([]) 转换为数据库能够理解的格式。
|
|
|
|
|
|
func (a UintArray) Value() (driver.Value, error) {
|
|
|
|
|
|
if a == nil {
|
|
|
|
|
|
return "{}", nil
|
2025-09-13 15:14:08 +08:00
|
|
|
|
}
|
2025-09-26 22:50:08 +08:00
|
|
|
|
|
|
|
|
|
|
var b strings.Builder
|
|
|
|
|
|
b.WriteString("{")
|
|
|
|
|
|
for i, v := range a {
|
|
|
|
|
|
if i > 0 {
|
|
|
|
|
|
b.WriteString(",")
|
|
|
|
|
|
}
|
|
|
|
|
|
b.WriteString(strconv.FormatUint(uint64(v), 10))
|
|
|
|
|
|
}
|
|
|
|
|
|
b.WriteString("}")
|
|
|
|
|
|
return b.String(), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Scan 实现了 gorm.Scanner 接口。
|
|
|
|
|
|
// 它告诉 GORM 如何将从数据库读取的数据转换为我们的 UintArray ([])。
|
|
|
|
|
|
func (a *UintArray) Scan(src interface{}) error {
|
|
|
|
|
|
if src == nil {
|
|
|
|
|
|
*a = nil
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var srcStr string
|
|
|
|
|
|
switch v := src.(type) {
|
|
|
|
|
|
case []byte:
|
|
|
|
|
|
srcStr = string(v)
|
|
|
|
|
|
case string:
|
|
|
|
|
|
srcStr = v
|
|
|
|
|
|
default:
|
2025-11-20 14:38:36 +08:00
|
|
|
|
return errors.New("无法将值 %v (类型 %T) 扫描为 UintArray")
|
2025-09-26 22:50:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 去掉花括号
|
|
|
|
|
|
srcStr = strings.Trim(srcStr, "{}")
|
|
|
|
|
|
if srcStr == "" {
|
2025-11-10 22:23:31 +08:00
|
|
|
|
*a = []uint32{}
|
2025-09-26 22:50:08 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 按逗号分割
|
|
|
|
|
|
parts := strings.Split(srcStr, ",")
|
2025-11-10 22:23:31 +08:00
|
|
|
|
arr := make([]uint32, len(parts))
|
2025-09-26 22:50:08 +08:00
|
|
|
|
for i, p := range parts {
|
|
|
|
|
|
val, err := strconv.ParseUint(p, 10, 64)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("解析 UintArray 元素失败: %w", err)
|
|
|
|
|
|
}
|
2025-11-10 22:23:31 +08:00
|
|
|
|
arr[i] = uint32(val)
|
2025-09-26 22:50:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
*a = arr
|
|
|
|
|
|
return nil
|
2025-09-13 15:14:08 +08:00
|
|
|
|
}
|
2025-11-07 21:39:24 +08:00
|
|
|
|
|
|
|
|
|
|
// SeverityLevel 定义了系统中告警、通知、日志的统一级别枚举。
|
|
|
|
|
|
// 它以中文形式存储在数据库中,提高了可读性。
|
|
|
|
|
|
type SeverityLevel string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
// DebugLevel 调试级别,用于开发和诊断问题。
|
2025-11-18 15:46:32 +08:00
|
|
|
|
DebugLevel SeverityLevel = "debug"
|
2025-11-07 21:39:24 +08:00
|
|
|
|
// InfoLevel 信息级别,用于记录常规操作。
|
2025-11-18 15:46:32 +08:00
|
|
|
|
InfoLevel SeverityLevel = "info"
|
2025-11-07 21:39:24 +08:00
|
|
|
|
// WarnLevel 警告级别,表示出现潜在问题,需要关注。
|
2025-11-18 15:46:32 +08:00
|
|
|
|
WarnLevel SeverityLevel = "warn"
|
2025-11-07 21:39:24 +08:00
|
|
|
|
// ErrorLevel 错误级别,表示发生了需要处理的错误。
|
2025-11-18 15:46:32 +08:00
|
|
|
|
ErrorLevel SeverityLevel = "error"
|
2025-11-07 21:39:24 +08:00
|
|
|
|
// DPanicLevel 开发时崩溃级别,在开发模式下会触发 panic。
|
2025-11-18 15:46:32 +08:00
|
|
|
|
DPanicLevel SeverityLevel = "dpanic"
|
2025-11-07 21:39:24 +08:00
|
|
|
|
// PanicLevel 崩溃级别,记录日志后会立即触发 panic。
|
2025-11-18 15:46:32 +08:00
|
|
|
|
PanicLevel SeverityLevel = "panic"
|
2025-11-07 21:39:24 +08:00
|
|
|
|
// FatalLevel 致命级别,记录日志后会调用 os.Exit(1) 退出程序。
|
2025-11-18 15:46:32 +08:00
|
|
|
|
FatalLevel SeverityLevel = "fatal"
|
2025-11-07 21:39:24 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
}
|