From da934a9bbb2d9c639c08ed2f4c8fbf8a540b9dff Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Thu, 20 Nov 2025 17:37:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BD=AF=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=92=8C=E5=94=AF=E4=B8=80=E7=B4=A2=E5=BC=95=E5=90=8C=E6=97=B6?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/infra/database/postgres.go | 72 ++++++++++++++++++++++++ internal/infra/models/device.go | 4 +- internal/infra/models/device_template.go | 2 +- internal/infra/models/farm_asset.go | 2 +- internal/infra/models/pig_batch.go | 2 +- internal/infra/models/raw_material.go | 4 +- internal/infra/models/user.go | 2 +- 7 files changed, 80 insertions(+), 8 deletions(-) diff --git a/internal/infra/database/postgres.go b/internal/infra/database/postgres.go index b2a84cb..6babcd7 100644 --- a/internal/infra/database/postgres.go +++ b/internal/infra/database/postgres.go @@ -309,6 +309,78 @@ func (ps *PostgresStorage) creatingUniqueIndex(ctx context.Context) error { return fmt.Errorf("为 pig_nutrient_requirements 创建部分唯一索引失败: %w", err) } logger.Debug("成功为 pig_nutrient_requirements 创建部分唯一索引 (或已存在)") + + // 为 users 表创建部分唯一索引 + logger.Debug("正在为 users 表创建部分唯一索引") + partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_users_unique_username_when_not_deleted ON users (username) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 users 创建部分唯一索引失败", "error", err) + return fmt.Errorf("为 users 创建部分唯一索引失败: %w", err) + } + logger.Debug("成功为 users 创建部分唯一索引 (或已存在)") + + // 为 area_controllers 表创建部分唯一索引 (Name) + logger.Debug("正在为 area_controllers 表创建部分唯一索引 (Name)") + partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_area_controllers_unique_name_when_not_deleted ON area_controllers (name) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 area_controllers 创建部分唯一索引 (Name) 失败", "error", err) + return fmt.Errorf("为 area_controllers 创建部分唯一索引 (Name) 失败: %w", err) + } + logger.Debug("成功为 area_controllers 创建部分唯一索引 (Name) (或已存在)") + + // 为 area_controllers 表创建部分唯一索引 (NetworkID) + logger.Debug("正在为 area_controllers 表创建部分唯一索引 (NetworkID)") + partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_area_controllers_unique_network_id_when_not_deleted ON area_controllers (network_id) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 area_controllers 创建部分唯一索引 (NetworkID) 失败", "error", err) + return fmt.Errorf("为 area_controllers 创建部分唯一索引 (NetworkID) 失败: %w", err) + } + logger.Debug("成功为 area_controllers 创建部分唯一索引 (NetworkID) (或已存在)") + + // 为 device_templates 表创建部分唯一索引 + logger.Debug("正在为 device_templates 表创建部分唯一索引") + partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_device_templates_unique_name_when_not_deleted ON device_templates (name) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 device_templates 创建部分唯一索引失败", "error", err) + return fmt.Errorf("为 device_templates 创建部分唯一索引失败: %w", err) + } + logger.Debug("成功为 device_templates 创建部分唯一索引 (或已存在)") + + // 为 pig_batches 表创建部分唯一索引 + logger.Debug("正在为 pig_batches 表创建部分唯一索引") + partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_pig_batches_unique_batch_number_when_not_deleted ON pig_batches (batch_number) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 pig_batches 创建部分唯一索引失败", "error", err) + return fmt.Errorf("为 pig_batches 创建部分唯一索引失败: %w", err) + } + logger.Debug("成功为 pig_batches 创建部分唯一索引 (或已存在)") + + // 为 pig_houses 表创建部分唯一索引 + logger.Debug("正在为 pig_houses 表创建部分唯一索引") + partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_pig_houses_unique_name_when_not_deleted ON pig_houses (name) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 pig_houses 创建部分唯一索引失败", "error", err) + return fmt.Errorf("为 pig_houses 创建部分唯一索引失败: %w", err) + } + logger.Debug("成功为 pig_houses 创建部分唯一索引 (或已存在)") + + // 为 raw_materials 表创建部分唯一索引 + logger.Debug("正在为 raw_materials 表创建部分唯一索引") + partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_raw_materials_unique_name_when_not_deleted ON raw_materials (name) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 raw_materials 创建部分唯一索引失败", "error", err) + return fmt.Errorf("为 raw_materials 创建部分唯一索引失败: %w", err) + } + logger.Debug("成功为 raw_materials 创建部分唯一索引 (或已存在)") + + // 为 nutrients 表创建部分唯一索引 + logger.Debug("正在为 nutrients 表创建部分唯一索引") + partialIndexSQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_nutrients_unique_name_when_not_deleted ON nutrients (name) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 nutrients 创建部分唯一索引失败", "error", err) + return fmt.Errorf("为 nutrients 创建部分唯一索引失败: %w", err) + } + logger.Debug("成功为 nutrients 创建部分唯一索引 (或已存在)") return nil } diff --git a/internal/infra/models/device.go b/internal/infra/models/device.go index aa2af91..6801110 100644 --- a/internal/infra/models/device.go +++ b/internal/infra/models/device.go @@ -21,11 +21,11 @@ type AreaController struct { Model // Name 是主控的业务名称,例如 "1号猪舍主控" - Name string `gorm:"not null;unique" json:"name"` + Name string `gorm:"not null" json:"name"` // NetworkID 是主控在通信网络中的唯一标识,例如 LoRaWAN 的 DevEUI。 // 这是 transport 层用来寻址的关键。 - NetworkID string `gorm:"not null;unique;index" json:"network_id"` + NetworkID string `gorm:"not null;index" json:"network_id"` // Location 描述了主控的物理安装位置。 Location string `gorm:"index" json:"location"` diff --git a/internal/infra/models/device_template.go b/internal/infra/models/device_template.go index 0f5cb6d..c6fc759 100644 --- a/internal/infra/models/device_template.go +++ b/internal/infra/models/device_template.go @@ -108,7 +108,7 @@ type DeviceTemplate struct { Model // Name 是此模板的唯一名称, 例如 "FanModel-XYZ-2000" 或 "TempSensor-T1" - Name string `gorm:"not null;unique" json:"name"` + Name string `gorm:"not null" json:"name"` // Manufacturer 是设备的制造商。 Manufacturer string `json:"manufacturer"` diff --git a/internal/infra/models/farm_asset.go b/internal/infra/models/farm_asset.go index c4723b0..84471d2 100644 --- a/internal/infra/models/farm_asset.go +++ b/internal/infra/models/farm_asset.go @@ -7,7 +7,7 @@ package models // PigHouse 定义了猪舍,是猪栏的集合 type PigHouse struct { Model - Name string `gorm:"size:100;not null;unique;comment:猪舍名称, 如 '育肥舍A栋'"` + Name string `gorm:"size:100;not null;comment:猪舍名称, 如 '育肥舍A栋'"` Description string `gorm:"size:255;comment:描述信息"` Pens []Pen `gorm:"foreignKey:HouseID"` // 一个猪舍包含多个猪栏 } diff --git a/internal/infra/models/pig_batch.go b/internal/infra/models/pig_batch.go index ee9ca0d..b08f9d6 100644 --- a/internal/infra/models/pig_batch.go +++ b/internal/infra/models/pig_batch.go @@ -31,7 +31,7 @@ const ( // PigBatch 是猪批次的核心模型,代表了一群被共同管理的猪 type PigBatch struct { Model - BatchNumber string `gorm:"size:50;not null;uniqueIndex;comment:批次编号,如 2024-W25-A01"` + BatchNumber string `gorm:"size:50;not null;comment:批次编号,如 2024-W25-A01"` OriginType PigBatchOriginType `gorm:"size:20;not null;comment:批次来源 (自繁, 外购)"` StartDate time.Time `gorm:"not null;comment:批次开始日期 (如转入日或购买日)"` EndDate time.Time `gorm:"not null;comment:批次结束日期 (全部淘汰或售出)"` diff --git a/internal/infra/models/raw_material.go b/internal/infra/models/raw_material.go index 703f6b2..18baa54 100644 --- a/internal/infra/models/raw_material.go +++ b/internal/infra/models/raw_material.go @@ -21,7 +21,7 @@ const ( // RawMaterial 代表一种原料的静态定义,是系统中的原料字典。 type RawMaterial struct { Model - Name string `gorm:"size:100;unique;not null;comment:原料名称"` + Name string `gorm:"size:100;not null;comment:原料名称"` Description string `gorm:"size:255;comment:描述"` // RawMaterialNutrients 关联此原料的所有营养素含量信息 RawMaterialNutrients []RawMaterialNutrient `gorm:"foreignKey:RawMaterialID"` @@ -36,7 +36,7 @@ func (RawMaterial) TableName() string { // 约定:宏量营养素(粗蛋白等)单位为百分比(%),微量元素(氨基酸等)单位为毫克/千克(mg/kg)。 type Nutrient struct { Model - Name string `gorm:"size:100;unique;not null;comment:营养素名称"` + Name string `gorm:"size:100;not null;comment:营养素名称"` Description string `gorm:"size:255;comment:描述"` // RawMaterialNutrients 记录营养在哪些原料中存在且比例是多少 RawMaterialNutrients []RawMaterialNutrient `gorm:"foreignKey:NutrientID"` diff --git a/internal/infra/models/user.go b/internal/infra/models/user.go index 2d66ca7..9d33e40 100644 --- a/internal/infra/models/user.go +++ b/internal/infra/models/user.go @@ -44,7 +44,7 @@ type User struct { // Username 是用户的登录名,应该是唯一的 // 修正了 gorm 标签的拼写错误 (移除了 gorm 后面的冒号) - Username string `gorm:"unique;not null" json:"username"` + Username string `gorm:"not null" json:"username"` // Password 存储的是加密后的密码哈希,而不是明文 // json:"-" 标签确保此字段在序列化为 JSON 时被忽略,防止密码泄露