fix: 修复 fillNutrition 中 ClassCastException 及 parseWorkflowResponse 解析失败
- 新增 extractOutputMap() 支持两种策略:
Strategy 1:output 以 '"' 开头时,先用 JSON.parseObject(str, String.class)
解码 \" \n 等转义序列,再提取内部 {...}
Strategy 2:直接 indexOf('{') / lastIndexOf('}') 提取 JSON 对象
- 新增 extractJsonObject() 辅助方法统一提取 {...} 片段
- 修复 line 684 unsafe cast:改为 instanceof Map 检查,避免 ClassCastException
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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<String, Object> output = (Map<String, Object>) workflowResult.get("output");
|
||||
Map<String, Object> output = (outputObj instanceof Map)
|
||||
? (Map<String, Object>) 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<String, Object> output = JSON.parseObject(jsonPart, Map.class);
|
||||
result.put("output", output);
|
||||
} else {
|
||||
// 如果找不到有效的 JSON,尝试直接解析
|
||||
Map<String, Object> output = JSON.parseObject(outputStr, Map.class);
|
||||
result.put("output", output);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse workflow output field: {}", e.getMessage());
|
||||
if (outputField instanceof Map) {
|
||||
// FastJSON 自动解析时已是 Map,直接使用
|
||||
result.put("output", outputField);
|
||||
} else if (outputField != null) {
|
||||
String outputStr = outputField.toString();
|
||||
Map<String, Object> 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())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,6 +755,50 @@ 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<String, Object> 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<String, Object> 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<String, Object> 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"},...}
|
||||
|
||||
Reference in New Issue
Block a user