diff --git a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCommunityServiceImpl.java b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCommunityServiceImpl.java index cfc4c57..6acfd21 100644 --- a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCommunityServiceImpl.java +++ b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCommunityServiceImpl.java @@ -680,8 +680,14 @@ public class ToolCommunityServiceImpl implements ToolCommunityService { } // 提取 output 中的营养数据(嵌套格式) + Object outputObj = workflowResult.get("output"); + if (outputObj == null) { + throw new CrmebException("AI 返回数据格式错误"); + } @SuppressWarnings("unchecked") - Map output = (Map) workflowResult.get("output"); + Map output = (outputObj instanceof Map) + ? (Map) outputObj + : JSON.parseObject(outputObj.toString(), Map.class); if (output == null || output.isEmpty()) { throw new CrmebException("AI 返回数据格式错误"); } @@ -720,31 +726,22 @@ public class ToolCommunityServiceImpl implements ToolCommunityService { if (dataMap == null) return null; - // 处理 Coze 工作流返回的 output 字段(可能是双编码的 JSON 字符串,可能包含额外的文本) + // 处理 Coze 工作流返回的 output 字段 + // Coze 返回的 output 可能是双编码 JSON 字符串: + // - 外层 JSON parse 后得到 String,如 "\"{\n \"calories\":...}\"" + // - 其中 \" 和 \n 是字面转义字符,需再做一次 JSON 字符串解码 Object outputField = dataMap.get("output"); - if (outputField != null) { - try { - String outputStr = outputField instanceof String - ? (String) outputField - : outputField.toString(); - - // 提取 JSON 对象部分(从 { 开始到 } 结束) - int jsonStart = outputStr.indexOf('{'); - int jsonEnd = outputStr.lastIndexOf('}'); - - if (jsonStart >= 0 && jsonEnd > jsonStart) { - String jsonPart = outputStr.substring(jsonStart, jsonEnd + 1); - // 解析 JSON 对象 - Map output = JSON.parseObject(jsonPart, Map.class); - result.put("output", output); - } else { - // 如果找不到有效的 JSON,尝试直接解析 - Map output = JSON.parseObject(outputStr, Map.class); - result.put("output", output); - } - } catch (Exception e) { - log.warn("Failed to parse workflow output field: {}", e.getMessage()); - result.put("output", outputField); + if (outputField instanceof Map) { + // FastJSON 自动解析时已是 Map,直接使用 + result.put("output", outputField); + } else if (outputField != null) { + String outputStr = outputField.toString(); + Map outputMap = extractOutputMap(outputStr); + if (outputMap != null) { + result.put("output", outputMap); + } else { + log.warn("Failed to extract nutrition map from output. First 200 chars: {}", + outputStr.substring(0, Math.min(200, outputStr.length()))); } } @@ -757,7 +754,51 @@ public class ToolCommunityServiceImpl implements ToolCommunityService { return result; } - + + /** + * 从 Coze output 字符串中提取营养数据 Map。 + * + * Coze 返回的 output 格式可能有两种: + * 1. 直接的 JSON 字符串(已解码),如 {@code {"calories":{...},...}} + * 2. JSON 编码的字符串(含转义序列),如 {@code "{\n \"calories\":{...}...注:..."} + * 此时需先将其作为 JSON 字符串再解析一次,得到真正的 JSON 对象文本, + * 再提取 {@code {...}} 部分进行解析。 + */ + @SuppressWarnings("unchecked") + private Map extractOutputMap(String outputStr) { + if (outputStr == null || outputStr.isEmpty()) return null; + + // 策略 1:若以 " 开头,先做 JSON 字符串解码(处理 \" \n 等转义字符) + if (outputStr.startsWith("\"")) { + try { + String jsonStr = outputStr.endsWith("\"") ? outputStr : outputStr + "\""; + String decoded = JSON.parseObject(jsonStr, String.class); + Map m = extractJsonObject(decoded); + if (m != null) return m; + } catch (Exception e1) { + log.debug("extractOutputMap strategy1 failed: {}", e1.getMessage()); + } + } + + // 策略 2:直接从字符串中提取 {...} 部分并解析 + return extractJsonObject(outputStr); + } + + /** 从字符串中截取首个 { 到末尾 } 的子串,并用 FastJSON 解析为 Map。 */ + @SuppressWarnings("unchecked") + private Map extractJsonObject(String str) { + if (str == null) return null; + int start = str.indexOf('{'); + int end = str.lastIndexOf('}'); + if (start < 0 || end <= start) return null; + try { + return JSON.parseObject(str.substring(start, end + 1), Map.class); + } catch (Exception e) { + log.debug("extractJsonObject failed: {}", e.getMessage()); + return null; + } + } + /** * 将嵌套格式转为扁平格式 * 输入: {"calories":{"value":103,"unit":"kcal"},...}