实现Coze API调用记录功能

- 在AiChatServiceImpl中添加完整的API调用记录功能
- 每次调用Coze API时自动记录请求和响应信息
- 支持聊天和总结两种类型的API调用记录
- 记录详细信息包括:
  * 请求信息:URL、请求体、请求头、用户消息
  * 响应信息:HTTP状态码、响应体、响应头
  * Coze信息:Bot ID、Workflow ID、Chat ID、Conversation ID
  * 用户信息:用户ID、客户端IP、User Agent、会话ID
  * 性能指标:开始时间、结束时间、耗时、轮询次数
  * 状态跟踪:调用状态、最终状态、错误信息
  * 追踪信息:唯一追踪ID
- 添加集成测试验证记录功能
- 支持错误处理和异常情况记录
This commit is contained in:
2025-07-25 00:39:51 +08:00
parent c5ca1651db
commit 3292a74698
2 changed files with 425 additions and 2 deletions
@@ -4,6 +4,7 @@ import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.emotion.entity.Message;
import com.emotion.entity.Conversation;
import com.emotion.entity.CozeApiCall;
import com.emotion.service.AIChatService;
import com.emotion.service.MessageService;
import com.emotion.service.ConversationService;
@@ -17,9 +18,15 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* AI聊天服务实现类
@@ -170,6 +177,9 @@ public class AiChatServiceImpl implements AIChatService {
public String sendMessage(String conversationId, String userMessage, String userId) {
log.info("发送消息到Coze AI: conversationId={}, userId={}", conversationId, userId);
// 创建API调用记录
CozeApiCall apiCall = createApiCallRecord(conversationId, userMessage, userId, "chat");
try {
// 构建请求头
HttpHeaders headers = new HttpHeaders();
@@ -179,6 +189,9 @@ public class AiChatServiceImpl implements AIChatService {
// 构建请求体 - 使用正确的Coze API格式
Map<String, Object> requestBody = buildCozeRequest(conversationId, userMessage, userId);
// 更新API调用记录的请求信息
updateApiCallRequest(apiCall, cozeBaseUrl + chatPath, requestBody, headers);
HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
// 构建完整的API URL
@@ -194,23 +207,35 @@ public class AiChatServiceImpl implements AIChatService {
log.info("收到Coze初始响应: {}", response.getBody());
// 更新API调用记录的响应信息
updateApiCallResponse(apiCall, response);
// 解析响应获取chat_id和conversation_id
JSONObject responseJson = JSON.parseObject(response.getBody());
String chatId = extractChatIdFromResponse(responseJson);
String cozeConversationId = extractConversationIdFromResponse(responseJson);
if (chatId != null && cozeConversationId != null) {
// 更新API调用记录的Coze ID信息
updateApiCallCozeIds(apiCall, chatId, cozeConversationId);
// 轮询聊天状态直到完成并获取回复内容
String aiReply = waitForChatCompletion(chatId, cozeConversationId);
String aiReply = waitForChatCompletionWithTracking(chatId, cozeConversationId, apiCall);
log.info("Coze AI响应成功: reply={}", aiReply);
// 更新API调用记录的最终结果
updateApiCallSuccess(apiCall, aiReply);
return aiReply;
} else {
log.error("无法从Coze响应中获取chat_id或conversation_id");
updateApiCallError(apiCall, "INVALID_RESPONSE", "无法从Coze响应中获取chat_id或conversation_id");
return "抱歉,AI服务响应异常,请稍后再试。";
}
} catch (Exception e) {
log.error("发送消息到Coze AI失败", e);
updateApiCallError(apiCall, "REQUEST_FAILED", e.getMessage());
return "抱歉,AI服务暂时不可用,请稍后再试。";
}
}
@@ -550,6 +575,9 @@ public class AiChatServiceImpl implements AIChatService {
private String sendSummaryMessage(String conversationId, String userMessage, String userId) {
log.info("发送总结消息到Coze AI: conversationId={}, userId={}", conversationId, userId);
// 创建API调用记录
CozeApiCall apiCall = createSummaryApiCallRecord(conversationId, userMessage, userId, "summary");
try {
// 构建请求头
HttpHeaders headers = new HttpHeaders();
@@ -559,6 +587,9 @@ public class AiChatServiceImpl implements AIChatService {
// 构建请求体 - 使用总结专用的bot和workflow
Map<String, Object> requestBody = buildSummaryRequest(conversationId, userMessage, userId);
// 更新API调用记录的请求信息
updateApiCallRequest(apiCall, cozeBaseUrl + chatPath, requestBody, headers);
HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
// 构建完整的API URL
@@ -574,23 +605,35 @@ public class AiChatServiceImpl implements AIChatService {
log.info("收到Coze总结初始响应: {}", response.getBody());
// 更新API调用记录的响应信息
updateApiCallResponse(apiCall, response);
// 解析响应获取chat_id和conversation_id
JSONObject responseJson = JSON.parseObject(response.getBody());
String chatId = extractChatIdFromResponse(responseJson);
String cozeConversationId = extractConversationIdFromResponse(responseJson);
if (chatId != null && cozeConversationId != null) {
// 更新API调用记录的Coze ID信息
updateApiCallCozeIds(apiCall, chatId, cozeConversationId);
// 轮询聊天状态直到完成并获取回复内容
String aiReply = waitForChatCompletion(chatId, cozeConversationId);
String aiReply = waitForChatCompletionWithTracking(chatId, cozeConversationId, apiCall);
log.info("Coze AI总结响应成功: reply={}", aiReply);
// 更新API调用记录的最终结果
updateApiCallSuccess(apiCall, aiReply);
return aiReply;
} else {
log.error("无法从Coze总结响应中获取chat_id或conversation_id");
updateApiCallError(apiCall, "INVALID_RESPONSE", "无法从Coze总结响应中获取chat_id或conversation_id");
return "抱歉,AI总结服务响应异常,请稍后再试。";
}
} catch (Exception e) {
log.error("发送总结消息到Coze AI失败", e);
updateApiCallError(apiCall, "REQUEST_FAILED", e.getMessage());
return "抱歉,AI总结服务暂时不可用,请稍后再试。";
}
}
@@ -644,4 +687,214 @@ public class AiChatServiceImpl implements AIChatService {
return "";
}
}
/**
* 创建API调用记录
*/
private CozeApiCall createApiCallRecord(String conversationId, String userMessage, String userId, String requestType) {
CozeApiCall apiCall = CozeApiCall.builder()
.conversationId(conversationId)
.userId(userId)
.requestType(requestType)
.userMessage(userMessage)
.userMessageType("text")
.botId(chatBotId)
.workflowId(chatWorkflowId)
.status("pending")
.startTime(LocalDateTime.now())
.traceId(UUID.randomUUID().toString())
.build();
// 获取客户端信息
try {
HttpServletRequest request = getCurrentRequest();
if (request != null) {
apiCall.setClientIp(getClientIp(request));
apiCall.setUserAgent(request.getHeader("User-Agent"));
apiCall.setSessionId(request.getSession().getId());
}
} catch (Exception e) {
log.warn("获取客户端信息失败: {}", e.getMessage());
}
// 保存API调用记录
cozeApiCallService.save(apiCall);
log.info("创建API调用记录: id={}, traceId={}", apiCall.getId(), apiCall.getTraceId());
return apiCall;
}
/**
* 创建总结API调用记录
*/
private CozeApiCall createSummaryApiCallRecord(String conversationId, String userMessage, String userId, String requestType) {
CozeApiCall apiCall = CozeApiCall.builder()
.conversationId(conversationId)
.userId(userId)
.requestType(requestType)
.userMessage(userMessage)
.userMessageType("text")
.botId(summaryBotId)
.workflowId(summaryWorkflowId)
.status("pending")
.startTime(LocalDateTime.now())
.traceId(UUID.randomUUID().toString())
.build();
// 获取客户端信息
try {
HttpServletRequest request = getCurrentRequest();
if (request != null) {
apiCall.setClientIp(getClientIp(request));
apiCall.setUserAgent(request.getHeader("User-Agent"));
apiCall.setSessionId(request.getSession().getId());
}
} catch (Exception e) {
log.warn("获取客户端信息失败: {}", e.getMessage());
}
// 保存API调用记录
cozeApiCallService.save(apiCall);
log.info("创建总结API调用记录: id={}, traceId={}", apiCall.getId(), apiCall.getTraceId());
return apiCall;
}
/**
* 更新API调用记录的请求信息
*/
private void updateApiCallRequest(CozeApiCall apiCall, String requestUrl, Map<String, Object> requestBody, HttpHeaders headers) {
try {
apiCall.setRequestUrl(requestUrl);
apiCall.setRequestBody(JSON.toJSONString(requestBody));
apiCall.setRequestHeaders(JSON.toJSONString(headers.toSingleValueMap()));
cozeApiCallService.updateById(apiCall);
} catch (Exception e) {
log.error("更新API调用记录请求信息失败: {}", e.getMessage(), e);
}
}
/**
* 更新API调用记录的响应信息
*/
private void updateApiCallResponse(CozeApiCall apiCall, ResponseEntity<String> response) {
try {
apiCall.setResponseStatus(response.getStatusCodeValue());
apiCall.setResponseBody(response.getBody());
apiCall.setResponseHeaders(JSON.toJSONString(response.getHeaders().toSingleValueMap()));
cozeApiCallService.updateById(apiCall);
} catch (Exception e) {
log.error("更新API调用记录响应信息失败: {}", e.getMessage(), e);
}
}
/**
* 更新API调用记录的Coze ID信息
*/
private void updateApiCallCozeIds(CozeApiCall apiCall, String cozeChatId, String cozeConversationId) {
try {
apiCall.setCozeChatId(cozeChatId);
apiCall.setCozeConversationId(cozeConversationId);
cozeApiCallService.updateById(apiCall);
} catch (Exception e) {
log.error("更新API调用记录Coze ID信息失败: {}", e.getMessage(), e);
}
}
/**
* 更新API调用记录的成功结果
*/
private void updateApiCallSuccess(CozeApiCall apiCall, String aiReply) {
try {
LocalDateTime endTime = LocalDateTime.now();
long durationMs = java.time.Duration.between(apiCall.getStartTime(), endTime).toMillis();
apiCall.setAiReply(aiReply);
apiCall.setAiReplyType("text");
apiCall.setStatus("success");
apiCall.setFinalStatus("completed");
apiCall.setEndTime(endTime);
apiCall.setDurationMs((int) durationMs);
cozeApiCallService.updateById(apiCall);
log.info("API调用成功: id={}, duration={}ms", apiCall.getId(), durationMs);
} catch (Exception e) {
log.error("更新API调用记录成功结果失败: {}", e.getMessage(), e);
}
}
/**
* 更新API调用记录的错误信息
*/
private void updateApiCallError(CozeApiCall apiCall, String errorCode, String errorMessage) {
try {
LocalDateTime endTime = LocalDateTime.now();
long durationMs = java.time.Duration.between(apiCall.getStartTime(), endTime).toMillis();
apiCall.setErrorCode(errorCode);
apiCall.setErrorMessage(errorMessage);
apiCall.setStatus("failed");
apiCall.setFinalStatus("failed");
apiCall.setEndTime(endTime);
apiCall.setDurationMs((int) durationMs);
cozeApiCallService.updateById(apiCall);
log.error("API调用失败: id={}, errorCode={}, errorMessage={}", apiCall.getId(), errorCode, errorMessage);
} catch (Exception e) {
log.error("更新API调用记录错误信息失败: {}", e.getMessage(), e);
}
}
/**
* 获取当前HTTP请求
*/
private HttpServletRequest getCurrentRequest() {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return attributes != null ? attributes.getRequest() : null;
} catch (Exception e) {
return null;
}
}
/**
* 获取客户端IP地址
*/
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
/**
* 带跟踪的轮询聊天完成状态
*/
private String waitForChatCompletionWithTracking(String chatId, String cozeConversationId, CozeApiCall apiCall) {
try {
// 更新轮询开始时间
apiCall.setPollStartTime(LocalDateTime.now());
apiCall.setPollCount(0);
cozeApiCallService.updateById(apiCall);
// 调用原有的轮询方法
String result = waitForChatCompletion(chatId, cozeConversationId);
// 更新轮询结束时间
apiCall.setPollEndTime(LocalDateTime.now());
cozeApiCallService.updateById(apiCall);
return result;
} catch (Exception e) {
log.error("轮询聊天完成状态失败", e);
throw e;
}
}
}
@@ -0,0 +1,170 @@
package com.emotion.service;
import com.emotion.entity.CozeApiCall;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import java.util.List;
/**
* Coze API调用记录集成测试
*
* @author emotion-museum
* @date 2025-07-25
*/
@Slf4j
@SpringBootTest
@ActiveProfiles("test")
public class CozeApiCallIntegrationTest {
@Autowired
private AIChatService aiChatService;
@Autowired
private CozeApiCallService cozeApiCallService;
@Test
public void testCozeApiCallRecording() {
log.info("开始测试Coze API调用记录功能");
// 获取测试前的记录数量
long beforeCount = cozeApiCallService.count();
log.info("测试前API调用记录数量: {}", beforeCount);
// 执行AI聊天,这应该会创建API调用记录
String testConversationId = "test_conversation_" + System.currentTimeMillis();
String testUserId = "test_user_" + System.currentTimeMillis();
String testMessage = "你好,这是一个测试消息";
try {
String aiReply = aiChatService.sendMessage(testConversationId, testMessage, testUserId);
log.info("AI回复: {}", aiReply);
// 等待一下确保记录已保存
Thread.sleep(2000);
// 检查是否创建了新的API调用记录
long afterCount = cozeApiCallService.count();
log.info("测试后API调用记录数量: {}", afterCount);
// 验证记录数量增加
if (afterCount > beforeCount) {
log.info("✅ API调用记录功能正常工作,新增了 {} 条记录", afterCount - beforeCount);
// 查询最新的API调用记录
List<CozeApiCall> recentCalls = cozeApiCallService.getByUserId(testUserId);
if (!recentCalls.isEmpty()) {
CozeApiCall latestCall = recentCalls.get(0);
log.info("最新API调用记录详情:");
log.info(" - ID: {}", latestCall.getId());
log.info(" - 对话ID: {}", latestCall.getConversationId());
log.info(" - 用户ID: {}", latestCall.getUserId());
log.info(" - 请求类型: {}", latestCall.getRequestType());
log.info(" - 用户消息: {}", latestCall.getUserMessage());
log.info(" - AI回复: {}", latestCall.getAiReply());
log.info(" - 状态: {}", latestCall.getStatus());
log.info(" - 最终状态: {}", latestCall.getFinalStatus());
log.info(" - 开始时间: {}", latestCall.getStartTime());
log.info(" - 结束时间: {}", latestCall.getEndTime());
log.info(" - 耗时: {} ms", latestCall.getDurationMs());
log.info(" - 追踪ID: {}", latestCall.getTraceId());
} else {
log.warn("⚠️ 未找到对应用户的API调用记录");
}
} else {
log.error("❌ API调用记录功能异常,记录数量未增加");
}
} catch (Exception e) {
log.error("测试过程中发生异常", e);
}
log.info("Coze API调用记录功能测试完成");
}
@Test
public void testConversationSummaryRecording() {
log.info("开始测试对话总结API调用记录功能");
// 获取测试前的记录数量
long beforeCount = cozeApiCallService.count();
log.info("测试前API调用记录数量: {}", beforeCount);
// 执行对话总结,这应该会创建API调用记录
String testConversationId = "summary_test_conversation_" + System.currentTimeMillis();
String testUserId = "summary_test_user_" + System.currentTimeMillis();
try {
String summary = aiChatService.generateConversationSummary(testConversationId, testUserId);
log.info("对话总结: {}", summary);
// 等待一下确保记录已保存
Thread.sleep(2000);
// 检查是否创建了新的API调用记录
long afterCount = cozeApiCallService.count();
log.info("测试后API调用记录数量: {}", afterCount);
// 验证记录数量增加
if (afterCount > beforeCount) {
log.info("✅ 对话总结API调用记录功能正常工作,新增了 {} 条记录", afterCount - beforeCount);
// 查询总结类型的API调用记录
List<CozeApiCall> summaryCalls = cozeApiCallService.getByRequestType("summary");
if (!summaryCalls.isEmpty()) {
CozeApiCall latestSummaryCall = summaryCalls.get(0);
log.info("最新总结API调用记录详情:");
log.info(" - ID: {}", latestSummaryCall.getId());
log.info(" - 请求类型: {}", latestSummaryCall.getRequestType());
log.info(" - Bot ID: {}", latestSummaryCall.getBotId());
log.info(" - Workflow ID: {}", latestSummaryCall.getWorkflowId());
log.info(" - 状态: {}", latestSummaryCall.getStatus());
} else {
log.warn("⚠️ 未找到总结类型的API调用记录");
}
} else {
log.error("❌ 对话总结API调用记录功能异常,记录数量未增加");
}
} catch (Exception e) {
log.error("测试过程中发生异常", e);
}
log.info("对话总结API调用记录功能测试完成");
}
@Test
public void testApiCallStatistics() {
log.info("开始测试API调用统计功能");
try {
// 统计总调用次数
long totalCalls = cozeApiCallService.count();
log.info("总API调用次数: {}", totalCalls);
// 统计成功调用次数
long successCalls = cozeApiCallService.countByStatus("success");
log.info("成功API调用次数: {}", successCalls);
// 统计失败调用次数
long failedCalls = cozeApiCallService.countByStatus("failed");
log.info("失败API调用次数: {}", failedCalls);
// 统计不同请求类型的调用次数
List<CozeApiCall> chatCalls = cozeApiCallService.getByRequestType("chat");
List<CozeApiCall> summaryCalls = cozeApiCallService.getByRequestType("summary");
log.info("聊天类型API调用次数: {}", chatCalls.size());
log.info("总结类型API调用次数: {}", summaryCalls.size());
log.info("✅ API调用统计功能正常工作");
} catch (Exception e) {
log.error("统计测试过程中发生异常", e);
}
log.info("API调用统计功能测试完成");
}
}