Files
2025-11-26 22:51:58 +08:00

127 lines
4.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package database
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/database/seeder"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"github.com/tidwall/gjson"
"gorm.io/gorm"
)
// SeederFunc 定义了处理一种特定类型预设数据文件的函数签名。
type SeederFunc func(ctx context.Context, tx *gorm.DB, jsonData []byte) error
// isTableEmpty 检查给定模型对应的数据库表是否为空。
func isTableEmpty(tx *gorm.DB, model interface{}) (bool, error) {
var count int64
if err := tx.Model(model).Count(&count).Error; err != nil {
return false, fmt.Errorf("查询表记录数失败: %w", err)
}
return count == 0, nil
}
// SeedFromPreset 是一个通用的数据播种函数。
// 它会读取指定目录下的所有 .json 文件,并根据文件内容中的 "type" 字段进行分发。
// 同时,它会校验所有必需的预设类型是否都已成功加载。
func SeedFromPreset(ctx context.Context, db *gorm.DB, presetDir string) error {
seedCtx, logger := logs.Trace(ctx, ctx, "SeedFromPreset")
// 定义必须存在的预设数据类型及其处理顺序
// 确保 "nutrient" 在 "pig_nutrient_requirements" 之前处理,因为后者依赖于前者。
processingOrder := []string{"nutrient", "pig_nutrient_requirements"}
requiredTypes := make(map[string]bool)
for _, t := range processingOrder {
requiredTypes[t] = true
}
processedTypes := make(map[string]bool)
typeToFileMap := make(map[string]string) // 用于检测重复的 type并存储每个 type 对应的文件路径
groupedFiles := make(map[string][][]byte) // 按 type 分组存储 jsonData
files, err := os.ReadDir(presetDir)
if err != nil {
return fmt.Errorf("读取预设数据目录 '%s' 失败: %w", presetDir, err)
}
// 第一阶段:读取所有文件并按 type 分组
for _, file := range files {
if filepath.Ext(file.Name()) != ".json" {
continue
}
filePath := filepath.Join(presetDir, file.Name())
jsonData, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("读取文件 '%s' 失败: %w", filePath, err)
}
dataType := gjson.GetBytes(jsonData, "type")
if !dataType.Exists() {
logger.Warnf("警告: 文件 '%s' 中缺少 'type' 字段,已跳过", filePath)
continue
}
dataTypeStr := dataType.String()
// 检查是否存在重复的 type
if existingFile, found := typeToFileMap[dataTypeStr]; found {
return fmt.Errorf("预设数据校验失败: type '%s' 在文件 '%s' 和 '%s' 中重复定义", dataTypeStr, existingFile, filePath)
}
typeToFileMap[dataTypeStr] = filePath // 记录该 type 对应的文件路径
groupedFiles[dataTypeStr] = append(groupedFiles[dataTypeStr], jsonData)
}
// 第二阶段:按照预定义顺序处理分组后的数据
return db.Transaction(func(tx *gorm.DB) error {
for _, dataTypeStr := range processingOrder {
jsonDatas, ok := groupedFiles[dataTypeStr]
if !ok {
// 如果是必需类型但没有找到文件,则报错
if requiredTypes[dataTypeStr] {
return fmt.Errorf("预设数据校验失败: 缺少必需的预设文件类型: '%s'", dataTypeStr)
}
continue // 非必需类型,跳过
}
var seederFunc SeederFunc
switch dataTypeStr {
case "nutrient":
seederFunc = seeder.SeedNutrients
case "pig_nutrient_requirements":
seederFunc = seeder.SeedPigNutrientRequirements
default:
logger.Warnf("警告: 存在未知的 type: '%s',已跳过", dataTypeStr)
continue
}
for _, jsonData := range jsonDatas {
// 获取原始文件路径用于错误报告
originalFilePath := typeToFileMap[dataTypeStr]
if err := seederFunc(seedCtx, tx, jsonData); err != nil {
return fmt.Errorf("处理文件 (type: %s, path: %s) 时发生错误: %w", dataTypeStr, originalFilePath, err)
}
}
processedTypes[dataTypeStr] = true
}
// 校验所有必需的类型是否都已处理
var missingTypes []string
for reqType := range requiredTypes {
if !processedTypes[reqType] {
missingTypes = append(missingTypes, reqType)
}
}
if len(missingTypes) > 0 {
return fmt.Errorf("预设数据校验失败: 缺少必需的预设文件类型: [%s]", strings.Join(missingTypes, ", "))
}
return nil // 提交事务
})
}