feat: 支持 nutrition_data_json 嵌套格式存储(带 value/unit)

- 新增 flatToNestedForDb() 方法:将扁平格式转换为带 value/unit 的嵌套格式
- 新增 nestedToFlat() 方法:将嵌套格式转换回扁平格式
- 修改 fillNutrition():AI 生成的扁平结果转为嵌套格式后存入数据库
- 保持接口返回格式不变(仍为扁平格式)

支持字段映射:
- energyKcal → calories (kcal)
- proteinG → protein (g)
- potassiumMg → potassium (mg)
- phosphorusMg → phosphorus (mg)
- fatG → fat (g)
- carbohydratesG → carbohydrates (g)
- sodiumG → sodium (g)

由 Cursor CLI 实现
This commit is contained in:
2026-03-05 19:36:36 +08:00
parent 3dc1a24706
commit d62f934d7f
3 changed files with 69 additions and 2 deletions

View File

@@ -614,7 +614,9 @@ public class ToolCommunityServiceImpl implements ToolCommunityService {
Map<String, Object> nutrition = toolNutritionFillService.fillFromText(text.toString());
if (nutrition.isEmpty()) throw new CrmebException("AI 未能估算出营养数据");
post.setNutritionDataJson(JSON.toJSONString(nutrition));
// 存库使用嵌套格式value/unit与 v2_community_posts.nutrition_data_json 约定一致
Map<String, Object> nested = toolNutritionFillService.flatToNestedForDb(nutrition);
post.setNutritionDataJson(JSON.toJSONString(nested));
post.setUpdatedAt(new Date());
v2CommunityPostDao.updateById(post);
return nutrition;

View File

@@ -97,4 +97,53 @@ public class ToolNutritionFillServiceImpl implements ToolNutritionFillService {
}
return null;
}
@Override
public Map<String, Object> flatToNestedForDb(Map<String, Object> flat) {
if (flat == null || flat.isEmpty()) return new HashMap<>();
Map<String, Object> nested = new HashMap<>();
putNested(nested, flat, "energyKcal", "calories", "kcal");
putNested(nested, flat, "proteinG", "protein", "g");
putNested(nested, flat, "fatG", "fat", "g");
putNested(nested, flat, "carbohydratesG", "carbohydrates", "g");
putNested(nested, flat, "sodiumG", "sodium", "g");
putNested(nested, flat, "potassiumMg", "potassium", "mg");
putNested(nested, flat, "phosphorusMg", "phosphorus", "mg");
return nested;
}
private void putNested(Map<String, Object> nested, Map<String, Object> flat,
String flatKey, String nestedKey, String unit) {
Object v = flat.get(flatKey);
if (v == null) return;
if (v instanceof Number) {
Map<String, Object> entry = new HashMap<>();
entry.put("value", ((Number) v).doubleValue());
entry.put("unit", unit);
nested.put(nestedKey, entry);
}
}
@Override
@SuppressWarnings("unchecked")
public Map<String, Object> nestedToFlat(Map<String, Object> nested) {
if (nested == null || nested.isEmpty()) return new HashMap<>();
Map<String, Object> flat = new HashMap<>();
copyFlat(flat, nested, "calories", "energyKcal");
copyFlat(flat, nested, "protein", "proteinG");
copyFlat(flat, nested, "fat", "fatG");
copyFlat(flat, nested, "carbohydrates", "carbohydratesG");
copyFlat(flat, nested, "sodium", "sodiumG");
copyFlat(flat, nested, "potassium", "potassiumMg");
copyFlat(flat, nested, "phosphorus", "phosphorusMg");
return flat;
}
private void copyFlat(Map<String, Object> flat, Map<String, Object> nested,
String nestedKey, String flatKey) {
Object entry = nested.get(nestedKey);
if (!(entry instanceof Map)) return;
Object value = ((Map<?, ?>) entry).get("value");
if (value instanceof Number) flat.put(flatKey, ((Number) value).doubleValue());
}
}

View File

@@ -11,10 +11,26 @@ import java.util.Map;
public interface ToolNutritionFillService {
/**
* 根据一段饮食/菜品描述文本,调用 AI 估算营养数据并返回结构化结果
* 根据一段饮食/菜品描述文本,调用 AI 估算营养数据并返回结构化结果(扁平格式,供 AI/接口使用)
*
* @param text 饮食描述(如「今天中午吃了一碗米饭、一份青椒肉丝、一碗紫菜蛋花汤」)
* @return 包含 energyKcal, proteinG, potassiumMg, phosphorusMg 的 Map估算不出时为 null
*/
Map<String, Object> fillFromText(String text);
/**
* 将 AI 返回的扁平格式转为 v2_community_posts.nutrition_data_json 所需的嵌套格式(带 value/unit
*
* @param flat 扁平格式key 为 energyKcal, proteinG, potassiumMg, phosphorusMg 等
* @return 嵌套格式,如 {"calories":{"value":1850,"unit":"kcal"},"protein":{"value":65,"unit":"g"},...}
*/
Map<String, Object> flatToNestedForDb(Map<String, Object> flat);
/**
* 将数据库中的嵌套格式value/unit转为扁平格式便于前端或 AI 使用
*
* @param nested 嵌套格式,如 {"calories":{"value":1850,"unit":"kcal"},...}
* @return 扁平格式,如 {"energyKcal":1850,"proteinG":65,...}
*/
Map<String, Object> nestedToFlat(Map<String, Object> nested);
}