feat: 使用Coze工作流自动补齐帖子营养数据
- 后端: fillNutrition()改用Coze工作流7613879935811354687分析营养成分
- 后端: 新增parseWorkflowResponse/nestedToFlat解析工作流响应
- 前端: 页面加载时自动检测并补齐不完整的营养统计
- 修复: ToolKnowledgeServiceImpl编译错误(Integer转Long)
工作流输出格式: output.{calories,protein,potassium,phosphorus}.{value,unit}
This commit is contained in:
@@ -23,6 +23,9 @@ import com.zbkj.service.service.UserService;
|
||||
import com.zbkj.service.service.UserSignService;
|
||||
import com.zbkj.service.service.tool.ToolCommunityService;
|
||||
import com.zbkj.service.service.tool.ToolNutritionFillService;
|
||||
import com.zbkj.service.service.tool.ToolCozeService;
|
||||
import com.zbkj.common.request.coze.CozeWorkflowRequest;
|
||||
import com.zbkj.common.response.CozeBaseResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -71,6 +74,9 @@ public class ToolCommunityServiceImpl implements ToolCommunityService {
|
||||
@Resource
|
||||
private ToolNutritionFillService toolNutritionFillService;
|
||||
|
||||
@Resource
|
||||
private ToolCozeService toolCozeService;
|
||||
|
||||
/**
|
||||
* 获取社区内容列表
|
||||
* @param pageParamRequest 分页参数
|
||||
@@ -593,6 +599,7 @@ public class ToolCommunityServiceImpl implements ToolCommunityService {
|
||||
|
||||
/**
|
||||
* 根据帖子内容用 AI 填充营养数据并更新帖子
|
||||
* 使用 Coze 工作流 7613879935811354687 进行营养成分分析
|
||||
* @param postId 帖子ID
|
||||
* @return 填充后的营养数据
|
||||
*/
|
||||
@@ -611,14 +618,100 @@ public class ToolCommunityServiceImpl implements ToolCommunityService {
|
||||
if (StrUtil.isNotBlank(post.getContent())) text.append(post.getContent());
|
||||
if (text.length() == 0) throw new CrmebException("帖子标题和内容为空,无法估算营养");
|
||||
|
||||
Map<String, Object> nutrition = toolNutritionFillService.fillFromText(text.toString());
|
||||
if (nutrition.isEmpty()) throw new CrmebException("AI 未能估算出营养数据");
|
||||
|
||||
// 存库使用嵌套格式(value/unit),与 v2_community_posts.nutrition_data_json 约定一致
|
||||
Map<String, Object> nested = toolNutritionFillService.flatToNestedForDb(nutrition);
|
||||
post.setNutritionDataJson(JSON.toJSONString(nested));
|
||||
// 使用 Coze 工作流进行营养成分分析
|
||||
CozeWorkflowRequest workflowRequest = new CozeWorkflowRequest();
|
||||
workflowRequest.setWorkflowId("7613879935811354687");
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("text", text.toString());
|
||||
workflowRequest.setParameters(params);
|
||||
|
||||
CozeBaseResponse<Object> response = toolCozeService.workflow(workflowRequest);
|
||||
if (response == null || response.getData() == null) {
|
||||
throw new CrmebException("Coze 工作流调用失败");
|
||||
}
|
||||
|
||||
// 解析工作流返回结果
|
||||
Map<String, Object> workflowResult = parseWorkflowResponse(response.getData());
|
||||
if (workflowResult == null || workflowResult.isEmpty()) {
|
||||
throw new CrmebException("AI 未能估算出营养数据");
|
||||
}
|
||||
|
||||
// 提取 output 中的营养数据(嵌套格式)
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> output = (Map<String, Object>) workflowResult.get("output");
|
||||
if (output == null || output.isEmpty()) {
|
||||
throw new CrmebException("AI 返回数据格式错误");
|
||||
}
|
||||
|
||||
// 将嵌套格式转为扁平格式返回给前端
|
||||
Map<String, Object> flatNutrition = nestedToFlat(output);
|
||||
|
||||
// 存库使用嵌套格式(value/unit)
|
||||
post.setNutritionDataJson(JSON.toJSONString(output));
|
||||
post.setUpdatedAt(new Date());
|
||||
v2CommunityPostDao.updateById(post);
|
||||
return nutrition;
|
||||
|
||||
return flatNutrition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Coze 工作流响应
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> parseWorkflowResponse(Object data) {
|
||||
if (data == null) return null;
|
||||
if (data instanceof Map) {
|
||||
return (Map<String, Object>) data;
|
||||
}
|
||||
// 如果是 JSON 字符串,尝试解析
|
||||
if (data instanceof String) {
|
||||
try {
|
||||
return JSON.parseObject((String) data, Map.class);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse workflow response as JSON: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将嵌套格式转为扁平格式
|
||||
* 输入: {"calories":{"value":103,"unit":"kcal"},...}
|
||||
* 输出: {"energyKcal":103,"proteinG":17.5,...}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> nestedToFlat(Map<String, Object> nested) {
|
||||
Map<String, Object> flat = new HashMap<>();
|
||||
if (nested == null) return flat;
|
||||
|
||||
// 映射关系:嵌套 key -> 扁平 key
|
||||
copyFlat(flat, nested, "calories", "energyKcal");
|
||||
copyFlat(flat, nested, "protein", "proteinG");
|
||||
copyFlat(flat, nested, "potassium", "potassiumMg");
|
||||
copyFlat(flat, nested, "phosphorus", "phosphorusMg");
|
||||
copyFlat(flat, nested, "fat", "fatG");
|
||||
copyFlat(flat, nested, "carbohydrate", "carbohydratesG");
|
||||
copyFlat(flat, nested, "sodium", "sodiumMg");
|
||||
|
||||
return flat;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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());
|
||||
} else if (value != null) {
|
||||
// 尝试转换为数字
|
||||
try {
|
||||
flat.put(flatKey, Double.parseDouble(value.toString()));
|
||||
} catch (NumberFormatException e) {
|
||||
flat.put(flatKey, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ public class ToolKnowledgeServiceImpl implements ToolKnowledgeService {
|
||||
for (String type : new String[]{"guide", "article", "nutrients", "recipe"}) {
|
||||
LambdaQueryWrapper<V2Knowledge> lqw = new LambdaQueryWrapper<>();
|
||||
lqw.eq(V2Knowledge::getType, type);
|
||||
byType.put(type, v2KnowledgeDao.selectCount(lqw));
|
||||
byType.put(type, Long.valueOf(v2KnowledgeDao.selectCount(lqw)));
|
||||
}
|
||||
stats.put("byType", byType);
|
||||
|
||||
@@ -169,7 +169,7 @@ public class ToolKnowledgeServiceImpl implements ToolKnowledgeService {
|
||||
for (String status : new String[]{"published", "draft", "deleted"}) {
|
||||
LambdaQueryWrapper<V2Knowledge> lqw = new LambdaQueryWrapper<>();
|
||||
lqw.eq(V2Knowledge::getStatus, status);
|
||||
byStatus.put(status, v2KnowledgeDao.selectCount(lqw));
|
||||
byStatus.put(status, Long.valueOf(v2KnowledgeDao.selectCount(lqw)));
|
||||
}
|
||||
stats.put("byStatus", byStatus);
|
||||
return stats;
|
||||
|
||||
Reference in New Issue
Block a user