feat: 社区帖子详情页自动 AI 填充营养数据

- 在 getDetail() 中添加营养数据为空时的自动填充逻辑
- fillNutritionByAi() 方法调用 Coze 工作流 7613879935811354687
- 统一参数: input(帖子内容) + img(图片URL)
- fillNutrition() 同样添加 img 参数支持
This commit is contained in:
2026-03-08 15:13:32 +08:00
parent 314a29ea70
commit 3ba0601242

View File

@@ -32,6 +32,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import javax.annotation.Resource;
import java.util.*;
@@ -231,6 +232,28 @@ public class ToolCommunityServiceImpl implements ToolCommunityService {
post.setViewCount(post.getViewCount() + 1);
v2CommunityPostDao.updateById(post);
// 如果营养数据为空,使用 AI 自动填充
if (StrUtil.isBlank(post.getNutritionDataJson())) {
try {
log.info("帖子 {} 营养数据为空,调用 AI 自动填充", id);
Map<String, Object> nutritionData = fillNutritionByAi(post);
if (nutritionData != null && !nutritionData.isEmpty()) {
// 存库使用嵌套格式value/unit
@SuppressWarnings("unchecked")
Map<String, Object> nestedData = (Map<String, Object>) nutritionData.get("nested");
if (nestedData != null && !nestedData.isEmpty()) {
post.setNutritionDataJson(JSON.toJSONString(nestedData));
post.setUpdatedAt(new Date());
v2CommunityPostDao.updateById(post);
}
}
} catch (Exception e) {
log.warn("帖子 {} AI 营养填充失败: {}", id, e.getMessage());
// 不影响详情返回,只是记录日志
}
}
Map<String, Object> map = new HashMap<>();
map.put("id", post.getPostId());
map.put("postId", post.getPostId());
@@ -614,15 +637,35 @@ public class ToolCommunityServiceImpl implements ToolCommunityService {
if (userId.longValue() != post.getUserId()) throw new CrmebException("只能为自己的帖子填充营养数据");
StringBuilder text = new StringBuilder();
if (StrUtil.isNotBlank(post.getTitle())) text.append(post.getTitle()).append(" ");
if (StrUtil.isNotBlank(post.getTitle())) text.append(post.getTitle()).append("\n");
if (StrUtil.isNotBlank(post.getContent())) text.append(post.getContent());
if (text.length() == 0) throw new CrmebException("帖子标题和内容为空,无法估算营养");
// 获取第一张图片 URL
String firstImageUrl = null;
if (StrUtil.isNotBlank(post.getImagesJson())) {
try {
JSONArray images = JSON.parseArray(post.getImagesJson());
if (images != null && !images.isEmpty()) {
firstImageUrl = images.getString(0);
}
} catch (Exception e) {
log.warn("帖子 {} 图片 JSON 解析失败: {}", postId, e.getMessage());
}
}
// 如果没有图片数组,尝试使用封面图
if (StrUtil.isBlank(firstImageUrl) && StrUtil.isNotBlank(post.getCoverImage())) {
firstImageUrl = post.getCoverImage();
}
// 使用 Coze 工作流进行营养成分分析
CozeWorkflowRequest workflowRequest = new CozeWorkflowRequest();
workflowRequest.setWorkflowId("7613879935811354687");
Map<String, Object> params = new HashMap<>();
params.put("text", text.toString());
params.put("input", text.toString());
if (StrUtil.isNotBlank(firstImageUrl)) {
params.put("img", firstImageUrl);
}
workflowRequest.setParameters(params);
CozeBaseResponse<Object> response = toolCozeService.workflow(workflowRequest);
@@ -714,4 +757,95 @@ public class ToolCommunityServiceImpl implements ToolCommunityService {
}
}
}
/**
* AI 自动填充帖子营养数据
* 调用 Coze 工作流 7613879935811354687使用帖子图片和内容进行分析
*
* @param post 帖子实体
* @return 包含 flat 和 nested 格式的营养数据 Map
*/
private Map<String, Object> fillNutritionByAi(V2CommunityPost post) {
if (post == null) {
return null;
}
// 获取帖子内容
StringBuilder text = new StringBuilder();
if (StrUtil.isNotBlank(post.getTitle())) text.append(post.getTitle()).append("\n");
if (StrUtil.isNotBlank(post.getContent())) text.append(post.getContent());
String content = text.toString().trim();
if (StrUtil.isBlank(content)) {
log.warn("帖子 {} 标题和内容为空,无法估算营养", post.getPostId());
return null;
}
// 获取第一张图片 URL
String firstImageUrl = null;
if (StrUtil.isNotBlank(post.getImagesJson())) {
try {
JSONArray images = JSON.parseArray(post.getImagesJson());
if (images != null && !images.isEmpty()) {
firstImageUrl = images.getString(0);
}
} catch (Exception e) {
log.warn("帖子 {} 图片 JSON 解析失败: {}", post.getPostId(), e.getMessage());
}
}
// 如果没有图片数组,尝试使用封面图
if (StrUtil.isBlank(firstImageUrl) && StrUtil.isNotBlank(post.getCoverImage())) {
firstImageUrl = post.getCoverImage();
}
log.info("帖子 {} AI 营养填充,图片: {}, 内容长度: {}",
post.getPostId(),
firstImageUrl != null ? "" : "",
content.length()
);
// 调用 Coze 工作流进行营养成分分析
CozeWorkflowRequest workflowRequest = new CozeWorkflowRequest();
workflowRequest.setWorkflowId("7613879935811354687");
Map<String, Object> params = new HashMap<>();
params.put("input", content);
if (StrUtil.isNotBlank(firstImageUrl)) {
params.put("img", firstImageUrl);
}
workflowRequest.setParameters(params);
CozeBaseResponse<Object> response = toolCozeService.workflow(workflowRequest);
if (response == null || response.getData() == null) {
log.warn("帖子 {} Coze 工作流调用失败", post.getPostId());
return null;
}
// 解析工作流返回结果
Map<String, Object> workflowResult = parseWorkflowResponse(response.getData());
if (workflowResult == null || workflowResult.isEmpty()) {
log.warn("帖子 {} AI 未能估算出营养数据", post.getPostId());
return null;
}
// 提取 output 中的营养数据(嵌套格式)
@SuppressWarnings("unchecked")
Map<String, Object> output = (Map<String, Object>) workflowResult.get("output");
if (output == null || output.isEmpty()) {
log.warn("帖子 {} AI 返回数据格式错误", post.getPostId());
return null;
}
// 将嵌套格式转为扁平格式
Map<String, Object> flatNutrition = nestedToFlat(output);
// 返回两种格式的数据
Map<String, Object> result = new HashMap<>();
result.put("flat", flatNutrition);
result.put("nested", output);
log.info("帖子 {} AI 营养填充成功", post.getPostId());
return result;
}
}