package seeder import ( "bytes" "context" "encoding/json" "errors" "fmt" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "github.com/tidwall/gjson" "gorm.io/gorm" ) // SeedPigNutrientRequirements 先严格校验JSON源文件,然后以“有则跳过”的模式播种数据。 func SeedPigNutrientRequirements(ctx context.Context, tx *gorm.DB, jsonData []byte) error { logger := logs.GetLogger(ctx) // 检查 PigBreed 表是否为空,如果非空则跳过播种 isEmpty, err := isTableEmpty(tx, &models.PigBreed{}) if err != nil { return fmt.Errorf("检查 PigBreed 表是否为空失败: %w", err) } if !isEmpty { logger.Info("已存在猪种数据, 跳过数据播种") return nil } // 1. 严格校验JSON文件,检查内部重复键 if err := validateAndParsePigNutrientRequirementJSON(jsonData); err != nil { return fmt.Errorf("JSON源文件校验失败: %w", err) } // 2. 解析简介信息 descriptionsNode := gjson.GetBytes(jsonData, "descriptions") pigBreedDescriptions := make(map[string]models.PigBreed) pigAgeStageDescriptions := make(map[string]models.PigAgeStage) pigTypeDescriptions := make(map[string]map[string]models.PigType) if descriptionsNode.Exists() { // 解析 pig_breeds 描述 descriptionsNode.Get("pig_breeds").ForEach(func(key, value gjson.Result) bool { var pb models.PigBreed pb.Name = key.String() pb.Description = value.Get("description").String() pb.ParentInfo = value.Get("parent_info").String() pb.AppearanceFeatures = value.Get("appearance_features").String() pb.BreedAdvantages = value.Get("breed_advantages").String() pb.BreedDisadvantages = value.Get("breed_disadvantages").String() pigBreedDescriptions[key.String()] = pb return true }) // 解析 pig_age_stages 描述 descriptionsNode.Get("pig_age_stages").ForEach(func(key, value gjson.Result) bool { var pas models.PigAgeStage pas.Name = key.String() pas.Description = value.String() pigAgeStageDescriptions[key.String()] = pas return true }) // 解析 pig_breed_age_stages (PigType) 描述 descriptionsNode.Get("pig_breed_age_stages").ForEach(func(breedKey, breedValue gjson.Result) bool { if _, ok := pigTypeDescriptions[breedKey.String()]; !ok { pigTypeDescriptions[breedKey.String()] = make(map[string]models.PigType) } breedValue.ForEach(func(ageStageKey, ageStageValue gjson.Result) bool { var pt models.PigType pt.Description = ageStageValue.Get("description").String() pt.DailyFeedIntake = float32(ageStageValue.Get("daily_feed_intake").Float()) pt.DailyGainWeight = float32(ageStageValue.Get("daily_gain_weight").Float()) pt.MinDays = uint32(ageStageValue.Get("min_days").Uint()) pt.MaxDays = uint32(ageStageValue.Get("max_days").Uint()) pt.MinWeight = float32(ageStageValue.Get("min_weight").Float()) pt.MaxWeight = float32(ageStageValue.Get("max_weight").Float()) pigTypeDescriptions[breedKey.String()][ageStageKey.String()] = pt return true }) return true }) } // 3. 将通过校验的、干净的数据写入数据库 dataNode := gjson.GetBytes(jsonData, "data") dataNode.ForEach(func(breedKey, breedValue gjson.Result) bool { breedName := breedKey.String() var pigBreed models.PigBreed // 查找或创建 PigBreed pbDesc := pigBreedDescriptions[breedName] err = tx.Where(models.PigBreed{Name: breedName}). FirstOrCreate(&pigBreed, models.PigBreed{ Name: breedName, Description: pbDesc.Description, ParentInfo: pbDesc.ParentInfo, AppearanceFeatures: pbDesc.AppearanceFeatures, BreedAdvantages: pbDesc.BreedAdvantages, BreedDisadvantages: pbDesc.BreedDisadvantages, }).Error if err != nil { return false } breedValue.ForEach(func(ageStageKey, ageStageValue gjson.Result) bool { ageStageName := ageStageKey.String() var pigAgeStage models.PigAgeStage // 查找或创建 PigAgeStage pasDesc := pigAgeStageDescriptions[ageStageName] err = tx.Where(models.PigAgeStage{Name: ageStageName}). FirstOrCreate(&pigAgeStage, models.PigAgeStage{ Name: ageStageName, Description: pasDesc.Description, }).Error if err != nil { return false } var pigType models.PigType // 查找或创建 PigType ptDesc := pigTypeDescriptions[breedName][ageStageName] err = tx.Where(models.PigType{BreedID: pigBreed.ID, AgeStageID: pigAgeStage.ID}). FirstOrCreate(&pigType, models.PigType{ BreedID: pigBreed.ID, AgeStageID: pigAgeStage.ID, Description: ptDesc.Description, DailyFeedIntake: ptDesc.DailyFeedIntake, DailyGainWeight: ptDesc.DailyGainWeight, MinDays: ptDesc.MinDays, MaxDays: ptDesc.MaxDays, MinWeight: ptDesc.MinWeight, MaxWeight: ptDesc.MaxWeight, }).Error if err != nil { return false } ageStageValue.ForEach(func(nutrientKey, nutrientValue gjson.Result) bool { nutrientName := nutrientKey.String() minReq := float32(nutrientValue.Get("min_requirement").Float()) maxReq := float32(nutrientValue.Get("max_requirement").Float()) var nutrient models.Nutrient // 查找或创建 Nutrient (这里假设 Nutrient 已经在 SeedNutrients 中处理,但为了健壮性,再次 FirstOrCreate) err = tx.Where(models.Nutrient{Name: nutrientName}). FirstOrCreate(&nutrient, models.Nutrient{ Name: nutrientName, // Description 字段在 nutrient seeder 中处理,这里不设置 }).Error if err != nil { return false } linkData := models.PigNutrientRequirement{ PigTypeID: pigType.ID, NutrientID: nutrient.ID, MinRequirement: minReq, MaxRequirement: maxReq, } // 使用 FirstOrCreate 确保关联的唯一性 err = tx.Where(models.PigNutrientRequirement{ PigTypeID: pigType.ID, NutrientID: nutrient.ID, }).FirstOrCreate(&linkData, linkData).Error if err != nil { return false } return true }) return err == nil // 如果内部遍历有错误,则停止外部遍历 }) return err == nil // 如果内部遍历有错误,则停止外部遍历 }) return err // 返回捕获到的错误 } // validateAndParsePigNutrientRequirementJSON 严格校验猪营养需求JSON文件 func validateAndParsePigNutrientRequirementJSON(jsonData []byte) error { dataNode := gjson.GetBytes(jsonData, "data") if !dataNode.Exists() { return errors.New("JSON文件中缺少 'data' 字段") } if !dataNode.IsObject() { return errors.New("'data' 字段必须是一个JSON对象") } decoder := json.NewDecoder(bytes.NewReader([]byte(dataNode.Raw))) decoder.UseNumber() if t, err := decoder.Token(); err != nil || t != json.Delim('{') { return fmt.Errorf("'data' 字段解析起始符失败: %v", err) } seenBreeds := make(map[string]bool) for decoder.More() { // 解析 PigBreed 名称 t, err := decoder.Token() if err != nil { return fmt.Errorf("解析猪品种名称失败: %w", err) } breedName := t.(string) if seenBreeds[breedName] { return fmt.Errorf("猪品种名称 '%s' 重复", breedName) } seenBreeds[breedName] = true // 解析该品种的年龄阶段对象 if t, err := decoder.Token(); err != nil || t != json.Delim('{') { return fmt.Errorf("期望猪品种 '%s' 的值是一个JSON对象", breedName) } seenAgeStages := make(map[string]bool) for decoder.More() { // 解析 PigAgeStage 名称 t, err := decoder.Token() if err != nil { return fmt.Errorf("在猪品种 '%s' 中解析年龄阶段名称失败: %w", breedName, err) } ageStageName := t.(string) if seenAgeStages[ageStageName] { return fmt.Errorf("在猪品种 '%s' 中, 年龄阶段名称 '%s' 重复", breedName, ageStageName) } seenAgeStages[ageStageName] = true // 解析该年龄阶段的营养成分对象 if t, err := decoder.Token(); err != nil || t != json.Delim('{') { return fmt.Errorf("期望年龄阶段 '%s' 的值是一个JSON对象", ageStageName) } seenNutrients := make(map[string]bool) for decoder.More() { // 解析 Nutrient 名称 t, err := decoder.Token() if err != nil { return fmt.Errorf("在年龄阶段 '%s' 中解析营养素名称失败: %w", ageStageName, err) } nutrientName := t.(string) if seenNutrients[nutrientName] { return fmt.Errorf("在年龄阶段 '%s' 中, 营养素名称 '%s' 重复", ageStageName, nutrientName) } seenNutrients[nutrientName] = true // 解析 min_requirement 和 max_requirement 对象 if t, err := decoder.Token(); err != nil || t != json.Delim('{') { return fmt.Errorf("期望营养素 '%s' 的值是一个JSON对象", nutrientName) } for decoder.More() { t, err := decoder.Token() if err != nil { return fmt.Errorf("解析营养素 '%s' 的需求键失败: %w", nutrientName, err) } // key := t.(string) // 校验时不需要使用 key 的值 t, err = decoder.Token() if err != nil { return fmt.Errorf("解析营养素 '%s' 的需求值失败: %w", nutrientName, err) } if _, ok := t.(json.Number); !ok { return fmt.Errorf("期望营养素 '%s' 的需求值是数字, 但实际得到的类型是 %T, 值为 '%v'", nutrientName, t, t) } } if t, err := decoder.Token(); err != nil || t != json.Delim('}') { return fmt.Errorf("解析营养素 '%s' 的值结束符 '}' 失败", nutrientName) } } if t, err := decoder.Token(); err != nil || t != json.Delim('}') { return fmt.Errorf("解析年龄阶段 '%s' 的值结束符 '}' 失败", ageStageName) } } if t, err := decoder.Token(); err != nil || t != json.Delim('}') { return fmt.Errorf("解析猪品种 '%s' 的值结束符 '}' 失败", breedName) } } return nil }