package models import ( "database/sql/driver" "errors" "fmt" "strconv" "strings" "time" "go.uber.org/zap/zapcore" "gorm.io/gorm" ) // Model 用于代替gorm.Model, 使用uint32以节约空间 type Model struct { ID uint32 `gorm:"primarykey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` } // GetAllModels 返回一个包含所有数据库模型实例的切片。 // 这个函数用于在数据库初始化时自动迁移所有的表结构。 func GetAllModels() []interface{} { return []interface{}{ // Core Models &User{}, &UserActionLog{}, // Device Models &Device{}, &AreaController{}, &DeviceTemplate{}, &SensorData{}, &DeviceCommandLog{}, &DeviceTask{}, // Plan & Task Models &Plan{}, &SubPlan{}, &Task{}, &PlanExecutionLog{}, &TaskExecutionLog{}, &PendingTask{}, &PendingCollection{}, // Farm Asset Models &PigHouse{}, &Pen{}, // Pig & Batch Models &PigBatch{}, &PigBatchLog{}, &WeighingBatch{}, &WeighingRecord{}, &PigTransferLog{}, &PigSickLog{}, // Pig Buy & Sell &PigPurchase{}, &PigSale{}, // Feed Models &RawMaterial{}, &Nutrient{}, &RawMaterialNutrient{}, &RawMaterialStockLog{}, // Medication Models &Medication{}, &MedicationLog{}, // Alarm Models &ActiveAlarm{}, &HistoricalAlarm{}, // Notification Models &Notification{}, } } // UintArray 是一个自定义类型,代表 uint32 的切片。 // 它实现了 gorm.Scanner 和 driver.Valuer 接口, // 以便能与数据库的 bigint[] 类型进行原生映射。 type UintArray []uint32 // Value 实现了 driver.Valuer 接口。 // 它告诉 GORM 如何将 UintArray ([]) 转换为数据库能够理解的格式。 func (a UintArray) Value() (driver.Value, error) { if a == nil { return "{}", nil } 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: return errors.New("无法扫描非字符串或字节类型的源到 UintArray") } // 去掉花括号 srcStr = strings.Trim(srcStr, "{}") if srcStr == "" { *a = []uint32{} return nil } // 按逗号分割 parts := strings.Split(srcStr, ",") arr := make([]uint32, len(parts)) for i, p := range parts { val, err := strconv.ParseUint(p, 10, 64) if err != nil { return fmt.Errorf("解析 UintArray 元素失败: %w", err) } arr[i] = uint32(val) } *a = arr return nil } // SeverityLevel 定义了系统中告警、通知、日志的统一级别枚举。 // 它以中文形式存储在数据库中,提高了可读性。 type SeverityLevel string const ( // DebugLevel 调试级别,用于开发和诊断问题。 DebugLevel SeverityLevel = "debug" // InfoLevel 信息级别,用于记录常规操作。 InfoLevel SeverityLevel = "info" // WarnLevel 警告级别,表示出现潜在问题,需要关注。 WarnLevel SeverityLevel = "warn" // ErrorLevel 错误级别,表示发生了需要处理的错误。 ErrorLevel SeverityLevel = "error" // DPanicLevel 开发时崩溃级别,在开发模式下会触发 panic。 DPanicLevel SeverityLevel = "dpanic" // PanicLevel 崩溃级别,记录日志后会立即触发 panic。 PanicLevel SeverityLevel = "panic" // FatalLevel 致命级别,记录日志后会调用 os.Exit(1) 退出程序。 FatalLevel SeverityLevel = "fatal" ) // ToZapLevel 将我们的自定义级别转换为 zapcore.Level,以便与日志记录器兼容。 func (al SeverityLevel) ToZapLevel() zapcore.Level { switch al { case DebugLevel: return zapcore.DebugLevel case InfoLevel: return zapcore.InfoLevel case WarnLevel: return zapcore.WarnLevel case ErrorLevel: return zapcore.ErrorLevel case DPanicLevel: return zapcore.DPanicLevel case PanicLevel: return zapcore.PanicLevel case FatalLevel: return zapcore.FatalLevel default: // 默认情况下返回 Info 级别,保证程序健壮性 return zapcore.InfoLevel } } // Scan 实现了 sql.Scanner 接口,GORM 在从数据库读取数据时会调用此方法。 func (al *SeverityLevel) Scan(value interface{}) error { bytes, ok := value.([]byte) if !ok { // 尝试处理其他可能的类型,例如字符串 s, ok := value.(string) if !ok { return fmt.Errorf("无法将值 %v (类型 %T) 扫描为 SeverityLevel", value, value) } *al = SeverityLevel(s) return nil } *al = SeverityLevel(bytes) return nil } // Value 实现了 driver.Valuer 接口,GORM 在将数据写入数据库时会调用此方法。 func (al SeverityLevel) Value() (driver.Value, error) { return string(al), nil }