package models import ( "encoding/json" "errors" "strings" "time" "gorm.io/gorm" ) type ProductDefinition struct { ID uint `gorm:"primaryKey" json:"id"` Code string `gorm:"size:64;not null;uniqueIndex" json:"code"` Name string `gorm:"size:255;not null" json:"name"` Category string `gorm:"size:64;not null;default:'device';index" json:"category"` Description string `gorm:"size:1024" json:"description"` ParameterSchema string `gorm:"type:text" json:"parameterSchema"` TrackSerialNumber bool `gorm:"not null;default:false" json:"trackSerialNumber"` IsActive bool `gorm:"not null;default:true;index" json:"isActive"` Sort int `gorm:"not null;default:0;index" json:"sort"` CreatedAt int64 `gorm:"not null" json:"created_at"` UpdatedAt int64 `gorm:"not null" json:"updated_at"` } func (ProductDefinition) TableName() string { return "product_definitions" } func (p *ProductDefinition) BeforeCreate(tx *gorm.DB) (err error) { now := time.Now().UnixMilli() p.Code = normalizeProductCodeValue(p.Code) p.Name = strings.TrimSpace(p.Name) p.Category = normalizeProductCategoryValue(p.Category) p.Description = strings.TrimSpace(p.Description) p.ParameterSchema = normalizeJSONTextValue(p.ParameterSchema) p.CreatedAt = now p.UpdatedAt = now return nil } func (p *ProductDefinition) BeforeUpdate(tx *gorm.DB) (err error) { p.Code = normalizeProductCodeValue(p.Code) p.Name = strings.TrimSpace(p.Name) p.Category = normalizeProductCategoryValue(p.Category) p.Description = strings.TrimSpace(p.Description) p.ParameterSchema = normalizeJSONTextValue(p.ParameterSchema) p.UpdatedAt = time.Now().UnixMilli() return nil } func normalizeProductCodeValue(code string) string { return strings.TrimSpace(strings.ToLower(code)) } func normalizeProductCategoryValue(category string) string { value := strings.TrimSpace(strings.ToLower(category)) if value == "" { return "device" } return value } func normalizeJSONTextValue(value string) string { trimmed := strings.TrimSpace(value) if trimmed == "" { return "" } if !json.Valid([]byte(trimmed)) { return "" } return trimmed } type ProductParameterSchemaField struct { Key string `json:"key"` Label string `json:"label"` Type string `json:"type"` Required bool `json:"required"` Options []string `json:"options,omitempty"` } func mustJSON(value interface{}) string { bytes, err := json.Marshal(value) if err != nil { return "[]" } return string(bytes) } func EnsureDefaultProductDefinitions(db *gorm.DB) error { defaults := []ProductDefinition{ { Code: "charging_case_large", Name: "充电收纳箱(20位)", Category: "container", Description: "可收纳20个手环的大充电收纳箱", ParameterSchema: mustJSON([]ProductParameterSchemaField{{Key: "capacity", Label: "容量", Type: "number", Required: false}, {Key: "powerAdapter", Label: "适配器型号", Type: "string", Required: false}}), TrackSerialNumber: true, IsActive: true, Sort: 10, }, { Code: "charging_case_small", Name: "小充电收纳箱(10位)", Category: "container", Description: "可收纳10个手环的小充电收纳箱", ParameterSchema: mustJSON([]ProductParameterSchemaField{{Key: "capacity", Label: "容量", Type: "number", Required: false}, {Key: "powerAdapter", Label: "适配器型号", Type: "string", Required: false}}), TrackSerialNumber: true, IsActive: true, Sort: 20, }, { Code: "collection_band", Name: "采集手环", Category: "wearable", Description: "用于智能心率采集、50米往返跑等项目的采集手环", ParameterSchema: mustJSON([]ProductParameterSchemaField{{Key: "bandSize", Label: "尺码", Type: "string", Required: false}, {Key: "firmwareVersion", Label: "固件版本", Type: "string", Required: false}}), TrackSerialNumber: true, IsActive: true, Sort: 30, }, { Code: "control_tablet", Name: "控制平板", Category: "tablet", Description: "项目控制与操作使用的平板设备", ParameterSchema: mustJSON([]ProductParameterSchemaField{{Key: "screenSize", Label: "屏幕尺寸", Type: "string", Required: false}, {Key: "osVersion", Label: "系统版本", Type: "string", Required: false}}), TrackSerialNumber: true, IsActive: true, Sort: 40, }, { Code: "collection_gateway", Name: "采集网关", Category: "gateway", Description: "用于设备数据采集与上传的网关", ParameterSchema: mustJSON([]ProductParameterSchemaField{{Key: "mac", Label: "MAC地址", Type: "string", Required: true}, {Key: "location", Label: "安装位置", Type: "string", Required: false}}), TrackSerialNumber: true, IsActive: true, Sort: 50, }, { Code: "display_stand_screen", Name: "闺蜜机", Category: "display", Description: "用于投屏展示的移动显示设备", ParameterSchema: mustJSON([]ProductParameterSchemaField{{Key: "screenSize", Label: "屏幕尺寸", Type: "string", Required: false}, {Key: "resolution", Label: "分辨率", Type: "string", Required: false}}), TrackSerialNumber: true, IsActive: true, Sort: 60, }, { Code: "led_light_strip", Name: "LED灯带", Category: "accessory", Description: "心肺耐力测试配套的LED灯带", ParameterSchema: mustJSON([]ProductParameterSchemaField{{Key: "length", Label: "长度", Type: "string", Required: false}, {Key: "colorMode", Label: "颜色模式", Type: "string", Required: false}}), TrackSerialNumber: true, IsActive: true, Sort: 70, }, } for _, item := range defaults { var existing ProductDefinition err := db.Where("code = ?", item.Code).First(&existing).Error switch { case err == nil: existing.Name = item.Name existing.Category = item.Category existing.Description = item.Description existing.ParameterSchema = item.ParameterSchema existing.TrackSerialNumber = item.TrackSerialNumber existing.IsActive = item.IsActive existing.Sort = item.Sort if err := db.Save(&existing).Error; err != nil { return err } case errors.Is(err, gorm.ErrRecordNotFound): if err := db.Create(&item).Error; err != nil { return err } default: return err } } return nil }