支持预设价格

This commit is contained in:
2025-11-26 14:35:58 +08:00
parent d7e2777c13
commit ca85671a4c
3 changed files with 430 additions and 90 deletions

View File

@@ -119,6 +119,12 @@ func SeedFromPreset(ctx context.Context, db *gorm.DB, presetDir string) error {
})
}
// rawMaterialInfo 用于临时存储解析后的原料描述和价格信息。
type rawMaterialInfo struct {
Description string
UnitPrice float32
}
// seedNutrients 先严格校验JSON源文件然后以“有则跳过”的模式播种数据。
func seedNutrients(tx *gorm.DB, jsonData []byte) error {
// 1. 严格校验JSON文件检查内部重复键
@@ -128,12 +134,16 @@ func seedNutrients(tx *gorm.DB, jsonData []byte) error {
// 2. 解析简介信息
descriptionsNode := gjson.GetBytes(jsonData, "descriptions")
rawMaterialDescriptions := make(map[string]string)
rawMaterialInfos := make(map[string]rawMaterialInfo)
nutrientDescriptions := make(map[string]string)
if descriptionsNode.Exists() {
// 解析 raw_materials 描述和价格
descriptionsNode.Get("raw_materials").ForEach(func(key, value gjson.Result) bool {
rawMaterialDescriptions[key.String()] = value.String()
rawMaterialInfos[key.String()] = rawMaterialInfo{
Description: value.Get("descriptions").String(),
UnitPrice: float32(value.Get("unit_price").Float()),
}
return true
})
descriptionsNode.Get("nutrients").ForEach(func(key, value gjson.Result) bool {
@@ -148,11 +158,16 @@ func seedNutrients(tx *gorm.DB, jsonData []byte) error {
dataNode.ForEach(func(rawMaterialKey, rawMaterialValue gjson.Result) bool {
rawMaterialName := rawMaterialKey.String()
var rawMaterial models.RawMaterial
// 将 Description 放入 Create 对象中
// 获取原料的描述和价格信息
info := rawMaterialInfos[rawMaterialName]
// 将 Description 和 ReferencePrice 放入 Create 对象中
err = tx.Where(models.RawMaterial{Name: rawMaterialName}).
FirstOrCreate(&rawMaterial, models.RawMaterial{
Name: rawMaterialName,
Description: rawMaterialDescriptions[rawMaterialName],
Name: rawMaterialName,
Description: info.Description,
ReferencePrice: info.UnitPrice,
}).Error
if err != nil {
// 返回 false 停止 ForEach 遍历
@@ -454,6 +469,88 @@ func validateAndParsePigNutrientRequirementJSON(jsonData []byte) error {
// validateAndParseNutrientJSON 严格校验JSON文件
func validateAndParseNutrientJSON(jsonData []byte) error {
descriptionsNode := gjson.GetBytes(jsonData, "descriptions")
if !descriptionsNode.Exists() {
return errors.New("JSON文件中缺少 'descriptions' 字段")
}
if !descriptionsNode.IsObject() {
return errors.New("'descriptions' 字段必须是一个JSON对象")
}
rawMaterialsNode := descriptionsNode.Get("raw_materials")
if !rawMaterialsNode.Exists() {
return errors.New("JSON文件中缺少 'descriptions.raw_materials' 字段")
}
if !rawMaterialsNode.IsObject() {
return errors.New("'descriptions.raw_materials' 字段必须是一个JSON对象")
}
// 使用 json.Decoder 严格校验 raw_materials 的结构
decoder := json.NewDecoder(bytes.NewReader([]byte(rawMaterialsNode.Raw)))
decoder.UseNumber()
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
return fmt.Errorf("'descriptions.raw_materials' 字段解析起始符失败: %v", err)
}
seenRawMaterials := make(map[string]bool)
for decoder.More() {
// 1. 解析原料名称
t, err := decoder.Token()
if err != nil {
return fmt.Errorf("解析原料名称失败: %w", err)
}
rawMaterialName := t.(string)
if seenRawMaterials[rawMaterialName] {
return fmt.Errorf("原料名称 '%s' 重复", rawMaterialName)
}
seenRawMaterials[rawMaterialName] = true
// 2. 解析该原料的描述和价格对象
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
return fmt.Errorf("期望原料 '%s' 的值是一个JSON对象", rawMaterialName)
}
for decoder.More() {
t, err := decoder.Token()
if err != nil {
return fmt.Errorf("解析原料 '%s' 内部键失败: %w", rawMaterialName, err)
}
key := t.(string)
switch key {
case "descriptions":
t, err = decoder.Token()
if err != nil {
return fmt.Errorf("解析原料 '%s' 的 'descriptions' 值失败: %w", rawMaterialName, err)
}
if _, ok := t.(string); !ok {
return fmt.Errorf("期望原料 '%s' 的 'descriptions' 值是字符串, 但实际得到的类型是 %T, 值为 '%v'", rawMaterialName, t, t)
}
case "unit_price":
t, err = decoder.Token()
if err != nil {
return fmt.Errorf("解析原料 '%s' 的 'unit_price' 值失败: %w", rawMaterialName, err)
}
if _, ok := t.(json.Number); !ok {
return fmt.Errorf("期望原料 '%s' 的 'unit_price' 值是数字, 但实际得到的类型是 %T, 值为 '%v'", rawMaterialName, t, t)
}
default:
// 忽略其他未知字段,但仍需读取其值以继续解析
if _, err := decoder.Token(); err != nil {
return fmt.Errorf("解析原料 '%s' 的未知键 '%s' 的值失败: %w", rawMaterialName, key, err)
}
}
}
// 读取原料描述和价格对象的 "}"
if t, err := decoder.Token(); err != nil || t != json.Delim('}') {
return fmt.Errorf("解析原料 '%s' 的值结束符 '}' 失败", rawMaterialName)
}
}
// 校验 data 节点
dataNode := gjson.GetBytes(jsonData, "data")
if !dataNode.Exists() {
return errors.New("JSON文件中缺少 'data' 字段")
@@ -462,14 +559,14 @@ func validateAndParseNutrientJSON(jsonData []byte) error {
return errors.New("'data' 字段必须是一个JSON对象")
}
decoder := json.NewDecoder(bytes.NewReader([]byte(dataNode.Raw)))
// 重新初始化 decoder 用于 data 节点的校验
decoder = json.NewDecoder(bytes.NewReader([]byte(dataNode.Raw)))
decoder.UseNumber()
// 读取 "{"
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
return errors.New("'data' 字段解析起始符失败")
}
seenRawMaterials := make(map[string]bool)
seenRawMaterials = make(map[string]bool) // 重置 seenRawMaterials 用于 data 节点校验
for decoder.More() {
// 1. 解析原料名称