From 6ca101727a8a973d973a68d41490922441de4223 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Thu, 20 Nov 2025 14:38:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9A=E4=B9=89=E7=8C=AA=E7=9A=84=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=92=8C=E8=90=A5=E5=85=BB=E9=9C=80=E6=B1=82=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/archive/recipe-management/index.md | 3 +- internal/infra/database/postgres.go | 55 ++++++++++++++++++++++- internal/infra/models/models.go | 6 ++- internal/infra/models/pig.go | 43 ++++++++++++++++++ internal/infra/models/pig_nutrient.go | 16 +++++++ project_structure.txt | 2 + 6 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 internal/infra/models/pig.go create mode 100644 internal/infra/models/pig_nutrient.go diff --git a/design/archive/recipe-management/index.md b/design/archive/recipe-management/index.md index 9cc4f67..60bf2fa 100644 --- a/design/archive/recipe-management/index.md +++ b/design/archive/recipe-management/index.md @@ -51,4 +51,5 @@ http://git.huangwc.com/pig/pig-farm-controller/issues/66 1. 定义原料表, 营养表, 原料营养表, 原料库存变更表 2. 迁移配置文件, 实现从json文件中读取原材料营养预设值, 并自动写入数据库 3. 定义配方领域, 实现营养元素的增删改查 -4. 实现原材料的增删改查和仓库层的原料库存记录表增查 \ No newline at end of file +4. 实现原材料的增删改查和仓库层的原料库存记录表增查 +5. 定义猪的模型和营养需求模型 \ No newline at end of file diff --git a/internal/infra/database/postgres.go b/internal/infra/database/postgres.go index 3ce4a6b..b2a84cb 100644 --- a/internal/infra/database/postgres.go +++ b/internal/infra/database/postgres.go @@ -250,9 +250,20 @@ func (ps *PostgresStorage) applyCompressionPolicies(ctx context.Context) error { // creatingIndex 用于创建gorm无法处理的索引, 如gin索引 func (ps *PostgresStorage) creatingIndex(ctx context.Context) error { - storageCtx, logger := logs.Trace(ctx, ps.ctx, "creatingIndex") + storageCtx := logs.AddFuncName(ctx, ps.ctx, "creatingIndex") // 使用 IF NOT EXISTS 保证幂等性 // 如果索引已存在,此命令不会报错 + if err := ps.creatingUniqueIndex(storageCtx); err != nil { + return err + } + if err := ps.createGinIndexes(storageCtx); err != nil { + return err + } + return nil +} + +func (ps *PostgresStorage) creatingUniqueIndex(ctx context.Context) error { + storageCtx, logger := logs.Trace(ctx, ps.ctx, "creatingUniqueIndex") // 为 raw_material_nutrients 表创建部分唯一索引,以兼容软删除 logger.Debug("正在为 raw_material_nutrients 表创建部分唯一索引") @@ -263,6 +274,47 @@ func (ps *PostgresStorage) creatingIndex(ctx context.Context) error { } logger.Debug("成功为 raw_material_nutrients 创建部分唯一索引 (或已存在)") + // 为 pig_breeds 表创建部分唯一索引,以兼容软删除 (name 唯一) + logger.Debug("正在为 pig_breeds 表创建部分唯一索引") + partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_pig_breeds_unique_name_when_not_deleted ON pig_breeds (name) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 pig_breeds 创建部分唯一索引失败", "error", err) + return fmt.Errorf("为 pig_breeds 创建部分唯一索引失败: %w", err) + } + logger.Debug("成功为 pig_breeds 创建部分唯一索引 (或已存在)") + + // 为 pig_age_stages 表创建部分唯一索引,以兼容软删除 (name 唯一) + logger.Debug("正在为 pig_age_stages 表创建部分唯一索引") + partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_pig_age_stages_unique_name_when_not_deleted ON pig_age_stages (name) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 pig_age_stages 创建部分唯一索引失败", "error", err) + return fmt.Errorf("为 pig_age_stages 创建部分唯一索引失败: %w", err) + } + logger.Debug("成功为 pig_age_stages 创建部分唯一索引 (或已存在)") + + // 为 pig_types 表创建部分唯一索引,以兼容软删除 (breed_id, age_stage_id 组合唯一) + logger.Debug("正在为 pig_types 表创建部分唯一索引") + partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_pig_types_unique_breed_age_stage_when_not_deleted ON pig_types (breed_id, age_stage_id) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 pig_types 创建部分唯一索引失败", "error", err) + return fmt.Errorf("为 pig_types 创建部分唯一索引失败: %w", err) + } + logger.Debug("成功为 pig_types 创建部分唯一索引 (或已存在)") + + // 为 pig_nutrient_requirements 表创建部分唯一索引,以兼容软删除 (pig_type_id, nutrient_id 组合唯一) + logger.Debug("正在为 pig_nutrient_requirements 表创建部分唯一索引") + partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_pig_nutrient_requirements_unique_type_nutrient_when_not_deleted ON pig_nutrient_requirements (pig_type_id, nutrient_id) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 pig_nutrient_requirements 创建部分唯一索引失败", "error", err) + return fmt.Errorf("为 pig_nutrient_requirements 创建部分唯一索引失败: %w", err) + } + logger.Debug("成功为 pig_nutrient_requirements 创建部分唯一索引 (或已存在)") + return nil +} + +func (ps *PostgresStorage) createGinIndexes(ctx context.Context) error { + storageCtx, logger := logs.Trace(ctx, ps.ctx, "createGinIndexes") + // 为 sensor_data 表的 data 字段创建 GIN 索引 logger.Debug("正在为 sensor_data 表的 data 字段创建 GIN 索引") ginSensorDataIndexSQL := "CREATE INDEX IF NOT EXISTS idx_sensor_data_data_gin ON sensor_data USING GIN (data);" @@ -280,6 +332,5 @@ func (ps *PostgresStorage) creatingIndex(ctx context.Context) error { return fmt.Errorf("为 tasks 的 parameters 字段创建 GIN 索引失败: %w", err) } logger.Debug("成功为 tasks 的 parameters 字段创建 GIN 索引 (或已存在)") - return nil } diff --git a/internal/infra/models/models.go b/internal/infra/models/models.go index 4232d42..47324ed 100644 --- a/internal/infra/models/models.go +++ b/internal/infra/models/models.go @@ -56,6 +56,10 @@ func GetAllModels() []interface{} { &WeighingRecord{}, &PigTransferLog{}, &PigSickLog{}, + &PigBreed{}, + &PigAgeStage{}, + &PigType{}, + &PigNutrientRequirement{}, // Pig Buy & Sell &PigPurchase{}, @@ -119,7 +123,7 @@ func (a *UintArray) Scan(src interface{}) error { case string: srcStr = v default: - return errors.New("无法扫描非字符串或字节类型的源到 UintArray") + return errors.New("无法将值 %v (类型 %T) 扫描为 UintArray") } // 去掉花括号 diff --git a/internal/infra/models/pig.go b/internal/infra/models/pig.go new file mode 100644 index 0000000..132c270 --- /dev/null +++ b/internal/infra/models/pig.go @@ -0,0 +1,43 @@ +package models + +// PigBreed 猪品种模型 +type PigBreed struct { + Model + Name string `gorm:"size:50;not null;comment:品种名称"` + Description string `gorm:"size:255;comment:品种描述"` +} + +func (PigBreed) TableName() string { + return "pig_breeds" +} + +// PigAgeStage 猪年龄阶段模型 +type PigAgeStage struct { + Model + Name string `gorm:"size:50;not null;comment:年龄阶段名称 (如: 仔猪, 生长猪, 育肥猪)"` + Description string `gorm:"size:255;comment:阶段描述"` +} + +func (PigAgeStage) TableName() string { + return "pig_age_stages" +} + +// PigType 猪类型模型,代表特定品种和年龄阶段的组合 +type PigType struct { + Model + BreedID uint32 `gorm:"not null;index;comment:关联的猪品种ID"` + Breed PigBreed `gorm:"foreignKey:BreedID"` + AgeStageID uint32 `gorm:"not null;index;comment:关联的猪年龄阶段ID"` + AgeStage PigAgeStage `gorm:"foreignKey:AgeStageID"` + Description string `gorm:"size:255;comment:该猪类型的描述或特点"` + DailyFeedIntake float32 `gorm:"comment:理论日均食量 (g/天)"` + DailyGainWeight float32 `gorm:"comment:理论日增重 (g/天)"` + MinDays uint32 `gorm:"comment:该猪类型在该年龄阶段的最小日龄"` + MaxDays uint32 `gorm:"comment:该猪类型在该年龄阶段的最大日龄"` + MinWeight float32 `gorm:"comment:该猪类型在该年龄阶段的最小体重 (g)"` + MaxWeight float32 `gorm:"comment:该猪类型在该年龄阶段的最大体重 (g)"` +} + +func (PigType) TableName() string { + return "pig_types" +} diff --git a/internal/infra/models/pig_nutrient.go b/internal/infra/models/pig_nutrient.go new file mode 100644 index 0000000..2552115 --- /dev/null +++ b/internal/infra/models/pig_nutrient.go @@ -0,0 +1,16 @@ +package models + +// PigNutrientRequirement 猪营养需求模型 +type PigNutrientRequirement struct { + Model + PigTypeID uint32 `gorm:"not null;index;comment:关联的猪类型ID"` + PigType PigType `gorm:"foreignKey:PigTypeID"` + NutrientID uint32 `gorm:"not null;index;comment:关联的营养素ID"` + Nutrient Nutrient `gorm:"foreignKey:NutrientID"` + MinRequirement float32 `gorm:"not null;comment:最低营养需求量"` + MaxRequirement float32 `gorm:"not null;comment:最高营养需求量"` +} + +func (PigNutrientRequirement) TableName() string { + return "pig_nutrient_requirements" +} diff --git a/project_structure.txt b/project_structure.txt index 73cbd99..dbd7129 100644 --- a/project_structure.txt +++ b/project_structure.txt @@ -129,7 +129,9 @@ internal/infra/models/farm_asset.go internal/infra/models/medication.go internal/infra/models/models.go internal/infra/models/notify.go +internal/infra/models/pig.go internal/infra/models/pig_batch.go +internal/infra/models/pig_nutrient.go internal/infra/models/pig_sick.go internal/infra/models/pig_trade.go internal/infra/models/pig_transfer.go