diff --git a/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java index ad4f8a6..4cd0560 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java @@ -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 requestBody = buildCozeRequest(conversationId, userMessage, userId); + // 更新API调用记录的请求信息 + updateApiCallRequest(apiCall, cozeBaseUrl + chatPath, requestBody, headers); + HttpEntity> 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 requestBody = buildSummaryRequest(conversationId, userMessage, userId); + // 更新API调用记录的请求信息 + updateApiCallRequest(apiCall, cozeBaseUrl + chatPath, requestBody, headers); + HttpEntity> 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 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 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; + } + } } \ No newline at end of file diff --git a/backend-single/src/test/java/com/emotion/service/CozeApiCallIntegrationTest.java b/backend-single/src/test/java/com/emotion/service/CozeApiCallIntegrationTest.java new file mode 100644 index 0000000..6fdc0ac --- /dev/null +++ b/backend-single/src/test/java/com/emotion/service/CozeApiCallIntegrationTest.java @@ -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 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 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 chatCalls = cozeApiCallService.getByRequestType("chat"); + List 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调用统计功能测试完成"); + } +}