错题分析接口实现全流程
一、整体流程介绍
@PostMapping(value = "/choice") 定义一个 POST 接口,接收一个 JSON 格式的 AnalysisRequest 请求体,返回一个 Map<String, Object> 类型的响应(包含分析结果或错误信息)。
核心处理流程:
- 日志记录 + 参数校验
- 构造 AI 请求消息(System + User Prompt)
- 调用 AI 接口分析错题
- 解析 AI 的 JSON 响应
- 封装为结构化数据对象 AnalysisResponse
- 记录日志、返回响应
二、接口入口解析
接口入口:analyzeChoiceQuestions()
核心流程:
构造提示词(Prompt):
String systemPrompt = buildSystemPrompt(request);
String userMessage = buildUserMessage(request);
调用 AI 接口分析错题:
String aiResponse = analyzeWithAISync(systemPrompt, userMessage, request);
解析 AI 返回结果:
AnalysisResponse analysisResponse = parseAIResponse(analysisId, request, aiResponse);
构造返回数据结构:
result.put("data", analysisResponse);
result.put("analysis", analysisResponse.getOverallAnalysis());
三、核心方法细讲
1.AI 调用:analyzeWithAISync()
方法签名:
private String analyzeWithAISync(String systemPrompt, String userMessage, AnalysisRequest request)throws IOException, InterruptedException
入参:
systemPrompt:系统指令(告诉 AI 它的角色、职责)
userMessage:用户输入(具体要分析的内容)
request:分析请求体(虽然这里没有直接使用,但可能用于日志、回溯等)
返回值:AI 回复的正文 content 字符串
构造消息结构:
List<Map<String, String>> messages = new ArrayList<>();
创建一个用于存储对话的消息列表,每条消息是一个 Map(角色+内容)。
使用 OkHttp 客户端发起 HTTP 请求:
OkHttpClient client = new OkHttpClient.Builder().build();
发送 POST
请求,模型使用的是:
params.put("model", "deepseek-chat");
携带两种类型的 Prompt:
System Prompt:指导 AI 扮演教学分析师
Map<String, String> systemMessage = new HashMap<>();
systemMessage.put("role", "system");
systemMessage.put("content", systemPrompt);
messages.add(systemMessage);
User Prompt:具体的错题信息、题干、选项、用户作答等
Map<String, String> userMsg = new HashMap<>();
userMsg.put("role", "user");
userMsg.put("content", userMessage);
messages.add(userMsg);
最终效果:
[{"role": "system", "content": "你是一个老师..."},{"role": "user", "content": "以下是我的答题..." }
]
构造请求参数(组装 API 的 payload)
Map<String, Object> params = new HashMap<>();
params.put("model", "deepseek-chat");
params.put("stream", false);
params.put("messages", messages);
params.put("temperature", 0.7);
model:指定使用的模型(比如 deepseek-chat)
stream:是否启用流式返回(false 表示同步一次性拿到结果)
messages:传入的对话历史(包括 system 和 user)
temperature:控制 AI 输出的“随机程度”,数值越高越发散。
String jsonParams = JsonUtils.convertObj2Json(params);
使用工具类将 Map 转换为 JSON 字符串,便于发送。
构建 HTTP 请求
Request.Builder builder = new Request.Builder().url(AI_URL);
builder.addHeader("Authorization", "Bearer " + apiPassword);
builder.addHeader("Content-Type", "application/json");
指定 URL(AI 的接口地址)
添加鉴权(Bearer Token)
设置请求头为 JSON 格式
okhttp3.RequestBody body = okhttp3.RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),jsonParams
);
Request httpRequest = builder.post(body).build();
设置请求体为 JSON
构造 POST 请求
创建 OkHttp 客户端并发起同步请求
OkHttpClient client = new OkHttpClient.Builder().connectTimeout(timeout, TimeUnit.SECONDS).writeTimeout(timeout, TimeUnit.SECONDS).readTimeout(timeout, TimeUnit.SECONDS).build();
配置超时时间(连接、写入、读取)
从 AI 响应中提取 "content"
Map<String, Object> responseMap = JsonUtils.convertJson2Obj(responseBody, Map.class);
将 JSON 字符串反序列化为 Map 结构
if (responseMap != null && responseMap.containsKey("choices")) {List<Map<String, Object>> choices = (List<Map<String, Object>>) responseMap.get("choices");if (!choices.isEmpty()) {Map<String, Object> firstChoice = choices.get(0);if (firstChoice.containsKey("message")) {Map<String, Object> message = (Map<String, Object>) firstChoice.get("message");if (message.containsKey("content")) {return (String) message.get("content");}}}
}
DeepSeek 通用响应结构如下:
{"choices": [{"message": {"role": "assistant","content": "分析结果内容..."}}]
}
上述逻辑层层检查字段是否存在,最终提取 message.content 字段作为 AI 的回答文本。
2.解析AI返回:parseAIResponse()
方法定义:
private AnalysisResponse parseAIResponse(Long analysisId, AnalysisRequest request, String aiResponse)
analysisId:本次分析的唯一 ID。
request:用户发起请求时的详细信息,包含题目、正确率、难度等。
aiResponse:AI 模型返回的原始字符串,预期为 JSON 格式。
返回值为构建好的 AnalysisResponse 对象。
流程:
创建返回对象并设置分析ID:
AnalysisResponse response = new AnalysisResponse();
response.setAnalysisId(analysisId);
这里创建一个空的响应对象 response
,并先设置分析 ID。
清理 AI 响应内容:
String cleanedResponse = cleanAIResponse(aiResponse);
logger.debug("清理后的AI响应: {}", cleanedResponse);
AI 可能返回带有 Markdown 代码块标记(如 json ... ),或者格式不规范,因此调用 cleanAIResponse 做预处理。
这一步是为了解决 AI 返回不是纯净 JSON 的问题,提高解析成功率。
解析 JSON 内容:
Map<String, Object> parsedResponse = JsonUtils.convertJson2Obj(cleanedResponse, Map.class);
将清理后的字符串解析成 Map<String, Object>
结构,然后使用工具类 JsonUtils
将 JSON 转换为 Java Map 便于访问字段。
成功解析 JSON 的情况:
如果 parsedResponse
不为 null,则逐个提取字段:
提取整体分析总结:
response.setOverallAnalysis((String) parsedResponse.get("overallAnalysis"));
response.setLearningPlan((String) parsedResponse.get("learningPlan"));
提取每道题的分析信息:
if (parsedResponse.containsKey("questionAnalyses")) {List<Map<String, Object>> questionAnalyses =(List<Map<String, Object>>) parsedResponse.get("questionAnalyses");List<QuestionAnalysis> analyses = new ArrayList<>();for (Map<String, Object> qa : questionAnalyses) {QuestionAnalysis analysis = new QuestionAnalysis();analysis.setQuestion((String) qa.get("question"));analysis.setErrorReason((String) qa.get("errorReason"));analysis.setCorrectAnswerExplanation((String) qa.get("correctAnswerExplanation"));analysis.setImprovementSuggestion((String) qa.get("improvementSuggestion"));analyses.add(analysis);}response.setQuestionAnalyses(analyses);
}
道错题的信息被封装在 questionAnalyses 数组中,每条记录被解析为 QuestionAnalysis
对象,添加到结果中。
提取薄弱知识点:
if (parsedResponse.containsKey("weaknessPoints")) {List<String> weaknessPoints = (List<String>) parsedResponse.get("weaknessPoints");response.setWeaknessPoints(weaknessPoints != null ? weaknessPoints : new ArrayList<>());
}
以这个解析为例,进行详细分析逻辑过程:
if (parsedResponse.containsKey("weaknessPoints")) {
作用:判断 AI 返回的 JSON 数据中是否包含 "weaknessPoints" 这个字段。
parsedResponse 是一个 Map<String, Object> 类型的数据结构。
"weaknessPoints" 是 AI 分析输出中标记“知识薄弱点”的字段。
如果不包含,就什么都不做;如果包含,就进入下一步。
List<String> weaknessPoints = (List<String>) parsedResponse.get("weaknessPoints");
从 Map 中取出 "weaknessPoints" 对应的值,并尝试将其强制转换为 List<String> 类型。
假设 AI 返回的结构是类似下面这种 JSON:
{"weaknessPoints": ["函数定义与调用","循环结构的控制","语法错误识别"]
}
那么这里 parsedResponse.get("weaknessPoints") 的结果应该是一个 List,其中每个元素是一个 String。强制转换是必要的,因为从 Map<String, Object> 拿出来的是 Object 类型。
response.setWeaknessPoints(weaknessPoints != null ? weaknessPoints : new ArrayList<>());
目的:设置最终的结果对象 response 的 weaknessPoints 字段。
这个三目运算符是一个空值保护机制:
如果 AI 的 JSON 中 "weaknessPoints" 字段存在但为 null,就不会直接传 null 进去;
而是使用一个新的空 List<String>,防止后续出现 NullPointerException。
换句话说:无论 AI 有没有真正提供内容,最终结果中的 weaknessPoints 字段一定不会是 null,而是一个 List(可能为空)。
示例:
假设输入为:
{"overallAnalysis": "...","weaknessPoints": ["集合运算", "图形几何", "函数表达式"]
}
执行完这一段后:
response.getWeaknessPoints()
返回值为:
["集合运算", "图形几何", "函数表达式"]
提取学习建议:
if (parsedResponse.containsKey("studySuggestions")) {List<String> studySuggestions = (List<String>) parsedResponse.get("studySuggestions");response.setStudySuggestions(studySuggestions != null ? studySuggestions : new ArrayList<>());
}
如果 JSON 解析失败,则降级为文本处理:
parseTextResponse(response, aiResponse, request);
这是一个降级策略,保证系统即使遇到错误数据也能返回“最起码的结果”。