3292a74698
- 在AiChatServiceImpl中添加完整的API调用记录功能 - 每次调用Coze API时自动记录请求和响应信息 - 支持聊天和总结两种类型的API调用记录 - 记录详细信息包括: * 请求信息:URL、请求体、请求头、用户消息 * 响应信息:HTTP状态码、响应体、响应头 * Coze信息:Bot ID、Workflow ID、Chat ID、Conversation ID * 用户信息:用户ID、客户端IP、User Agent、会话ID * 性能指标:开始时间、结束时间、耗时、轮询次数 * 状态跟踪:调用状态、最终状态、错误信息 * 追踪信息:唯一追踪ID - 添加集成测试验证记录功能 - 支持错误处理和异常情况记录
900 lines
34 KiB
Java
900 lines
34 KiB
Java
package com.emotion.service.impl;
|
||
|
||
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;
|
||
import com.emotion.service.CozeApiCallService;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.beans.factory.annotation.Value;
|
||
import org.springframework.http.HttpEntity;
|
||
import org.springframework.http.HttpHeaders;
|
||
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聊天服务实现类
|
||
*
|
||
* @author emotion-museum
|
||
* @date 2025-07-24
|
||
*/
|
||
@Slf4j
|
||
@Service
|
||
public class AiChatServiceImpl implements AIChatService {
|
||
|
||
@Autowired
|
||
private RestTemplate restTemplate;
|
||
|
||
@Autowired
|
||
private MessageService messageService;
|
||
|
||
@Autowired
|
||
private ConversationService conversationService;
|
||
|
||
@Autowired
|
||
private CozeApiCallService cozeApiCallService;
|
||
|
||
@Value("${emotion.coze.api.token:}")
|
||
private String cozeApiToken;
|
||
|
||
@Value("${emotion.coze.api.base-url:https://api.coze.cn}")
|
||
private String cozeBaseUrl;
|
||
|
||
@Value("${emotion.coze.api.chat.path:/v3/chat}")
|
||
private String chatPath;
|
||
|
||
@Value("${emotion.coze.api.chat.talk.bot-id:}")
|
||
private String chatBotId;
|
||
|
||
@Value("${emotion.coze.api.chat.talk.workflow-id:}")
|
||
private String chatWorkflowId;
|
||
|
||
@Value("${emotion.coze.api.chat.summary.bot-id:}")
|
||
private String summaryBotId;
|
||
|
||
@Value("${emotion.coze.api.chat.summary.workflow-id:}")
|
||
private String summaryWorkflowId;
|
||
|
||
@Value("${emotion.coze.api.timeout:30000}")
|
||
private int timeout;
|
||
|
||
@Value("${emotion.coze.api.retry-count:3}")
|
||
private int retryCount;
|
||
|
||
@Value("${emotion.coze.api.retry-delay:1000}")
|
||
private int retryDelay;
|
||
|
||
private static final String DEFAULT_USER_ID = "emotion-museum-user";
|
||
|
||
// API 相关常量
|
||
private static final String CONTENT_KEY = "content";
|
||
private static final String ROLE_KEY = "role";
|
||
private static final String USER_ROLE = "user";
|
||
private static final String ASSISTANT_ROLE = "assistant";
|
||
private static final String CONTENT_TYPE_KEY = "content_type";
|
||
private static final String TEXT_TYPE = "text";
|
||
private static final String ANSWER_TYPE = "answer";
|
||
|
||
@Override
|
||
public String sendChatMessage(String conversationId, String message, String userId) {
|
||
log.info("发送聊天消息: conversationId={}, userId={}, message={}", conversationId, userId, message);
|
||
|
||
try {
|
||
// 调用Coze API
|
||
String aiReply = sendMessage(conversationId, message, userId);
|
||
|
||
// 保存用户消息
|
||
Message userMessage = new Message();
|
||
userMessage.setConversationId(conversationId);
|
||
userMessage.setCreateBy(userId);
|
||
userMessage.setContent(message);
|
||
userMessage.setType("text");
|
||
userMessage.setSender("user");
|
||
userMessage = messageService.createMessage(userMessage);
|
||
|
||
// 保存AI回复
|
||
Message aiMessage = new Message();
|
||
aiMessage.setConversationId(conversationId);
|
||
aiMessage.setCreateBy("ai");
|
||
aiMessage.setContent(aiReply);
|
||
aiMessage.setType("text");
|
||
aiMessage.setSender("ai");
|
||
aiMessage = messageService.createMessage(aiMessage);
|
||
|
||
log.info("聊天消息处理完成: userMessageId={}, aiMessageId={}",
|
||
userMessage.getId(), aiMessage.getId());
|
||
|
||
return aiReply;
|
||
|
||
} catch (Exception e) {
|
||
log.error("发送聊天消息失败", e);
|
||
return "抱歉,我暂时无法回复,请稍后再试。";
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public String generateConversationSummary(String conversationId, String userId) {
|
||
log.info("生成对话总结: conversationId={}, userId={}", conversationId, userId);
|
||
|
||
try {
|
||
// 获取对话历史
|
||
String conversationHistory = getConversationHistory(conversationId);
|
||
|
||
// 构建总结请求
|
||
String summaryPrompt = "请为以下对话生成一个简洁的总结:\n\n" + conversationHistory;
|
||
|
||
// 调用AI生成总结 - 使用专门的总结bot
|
||
String summary = sendSummaryMessage(conversationId, summaryPrompt, userId);
|
||
|
||
log.info("对话总结生成完成: conversationId={}", conversationId);
|
||
|
||
return summary;
|
||
|
||
} catch (Exception e) {
|
||
log.error("生成对话总结失败", e);
|
||
return "无法生成对话总结,请稍后再试。";
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public boolean isServiceAvailable() {
|
||
try {
|
||
// 简单的健康检查
|
||
return cozeApiToken != null && !cozeApiToken.isEmpty() &&
|
||
chatBotId != null && !chatBotId.isEmpty();
|
||
} catch (Exception e) {
|
||
log.error("检查AI服务可用性失败", e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public String getServiceStatus() {
|
||
if (isServiceAvailable()) {
|
||
return "available";
|
||
} else {
|
||
return "unavailable";
|
||
}
|
||
}
|
||
|
||
@Override
|
||
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();
|
||
headers.set("Authorization", "Bearer " + cozeApiToken);
|
||
headers.set("Content-Type", "application/json");
|
||
|
||
// 构建请求体 - 使用正确的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
|
||
String cozeApiUrl = cozeBaseUrl + chatPath;
|
||
log.info("发送Coze请求到: {}, 请求体: {}", cozeApiUrl, requestBody);
|
||
|
||
// 发送请求
|
||
ResponseEntity<String> response = restTemplate.exchange(
|
||
cozeApiUrl,
|
||
HttpMethod.POST,
|
||
request,
|
||
String.class);
|
||
|
||
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 = 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服务暂时不可用,请稍后再试。";
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public Map<String, Object> guestChat(String message, String clientIp) {
|
||
log.info("访客聊天: message={}, clientIp={}", message, clientIp);
|
||
|
||
Map<String, Object> result = new HashMap<>();
|
||
|
||
try {
|
||
// 生成访客会话ID
|
||
String guestConversationId = "guest_" + clientIp.replace(".", "_") + "_" + System.currentTimeMillis();
|
||
|
||
// 调用AI服务
|
||
String aiReply = sendMessage(guestConversationId, message, "guest");
|
||
|
||
// 保存访客消息
|
||
Message guestMessage = new Message();
|
||
guestMessage.setConversationId(guestConversationId);
|
||
guestMessage.setCreateBy("guest");
|
||
guestMessage.setContent(message);
|
||
guestMessage.setType("text");
|
||
guestMessage.setSender("guest");
|
||
guestMessage = messageService.createMessage(guestMessage);
|
||
|
||
// 保存AI回复
|
||
Message aiMessage = new Message();
|
||
aiMessage.setConversationId(guestConversationId);
|
||
aiMessage.setCreateBy("ai");
|
||
aiMessage.setContent(aiReply);
|
||
aiMessage.setType("text");
|
||
aiMessage.setSender("ai");
|
||
aiMessage = messageService.createMessage(aiMessage);
|
||
|
||
result.put("message", aiReply);
|
||
result.put("messageId", aiMessage.getId());
|
||
result.put("timestamp", System.currentTimeMillis());
|
||
result.put("error", false);
|
||
|
||
log.info("访客聊天处理完成: guestMessageId={}, aiMessageId={}",
|
||
guestMessage.getId(), aiMessage.getId());
|
||
|
||
} catch (Exception e) {
|
||
log.error("访客聊天失败", e);
|
||
result.put("message", "抱歉,服务暂时不可用,请稍后再试。");
|
||
result.put("messageId", null);
|
||
result.put("timestamp", System.currentTimeMillis());
|
||
result.put("error", true);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
@Override
|
||
public Map<String, Object> createConversation(String userId, String title) {
|
||
log.info("创建对话: userId={}, title={}", userId, title);
|
||
|
||
Map<String, Object> result = new HashMap<>();
|
||
|
||
try {
|
||
// 调用数据库服务创建对话
|
||
Conversation conversation = conversationService.createConversation(userId, title, "user");
|
||
|
||
result.put("conversationId", conversation.getId());
|
||
result.put("title", title);
|
||
result.put("userId", userId);
|
||
result.put("createTime", System.currentTimeMillis());
|
||
result.put("success", true);
|
||
|
||
log.info("对话创建成功: conversationId={}", conversation.getId());
|
||
|
||
} catch (Exception e) {
|
||
log.error("创建对话失败", e);
|
||
result.put("success", false);
|
||
result.put("error", "创建对话失败");
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
@Override
|
||
public Map<String, Object> getGuestUserInfo(String clientIp) {
|
||
log.info("获取访客用户信息: clientIp={}", clientIp);
|
||
|
||
Map<String, Object> result = new HashMap<>();
|
||
|
||
try {
|
||
// 生成访客用户信息
|
||
String guestId = "guest_" + clientIp.replace(".", "_");
|
||
String guestUsername = "访客_" + clientIp.substring(clientIp.lastIndexOf(".") + 1);
|
||
|
||
result.put("id", guestId);
|
||
result.put("username", guestUsername);
|
||
result.put("nickname", guestUsername);
|
||
result.put("type", "guest");
|
||
result.put("clientIp", clientIp);
|
||
result.put("createTime", System.currentTimeMillis());
|
||
|
||
log.info("访客用户信息获取成功: guestId={}", guestId);
|
||
|
||
} catch (Exception e) {
|
||
log.error("获取访客用户信息失败", e);
|
||
result.put("error", "获取用户信息失败");
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
@Override
|
||
public String streamChat(String conversationId, String message, String userId) {
|
||
log.info("流式聊天: conversationId={}, userId={}", conversationId, userId);
|
||
|
||
try {
|
||
// 构建流式请求
|
||
Map<String, Object> requestBody = buildCozeRequest(conversationId, message, userId);
|
||
requestBody.put("stream", true);
|
||
|
||
// 这里应该实现流式处理,暂时降级到普通聊天
|
||
return sendMessage(conversationId, message, userId);
|
||
|
||
} catch (Exception e) {
|
||
log.error("流式聊天失败", e);
|
||
return "抱歉,流式聊天暂时不可用,请稍后再试。";
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public boolean healthCheck() {
|
||
try {
|
||
// 简化健康检查 - 检查必要配置是否存在
|
||
boolean configValid = cozeApiToken != null && !cozeApiToken.trim().isEmpty() &&
|
||
chatBotId != null && !chatBotId.trim().isEmpty() &&
|
||
cozeBaseUrl != null && !cozeBaseUrl.trim().isEmpty();
|
||
|
||
if (!configValid) {
|
||
log.warn("Coze API 配置不完整");
|
||
return false;
|
||
}
|
||
|
||
// 可选:发送一个简单的测试请求
|
||
// 这里可以调用一个轻量级的API来验证连接
|
||
return true;
|
||
|
||
} catch (Exception e) {
|
||
log.error("健康检查失败: {}", e.getMessage());
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 构建Coze API请求 - 根据官方文档修正格式
|
||
*/
|
||
private Map<String, Object> buildCozeRequest(String conversationId, String userMessage, String userId) {
|
||
Map<String, Object> cozeRequest = new HashMap<>();
|
||
cozeRequest.put("bot_id", chatBotId);
|
||
|
||
// 如果有workflow_id,则添加
|
||
if (chatWorkflowId != null && !chatWorkflowId.trim().isEmpty()) {
|
||
cozeRequest.put("workflow_id", chatWorkflowId);
|
||
}
|
||
|
||
cozeRequest.put("user_id", userId != null ? userId : DEFAULT_USER_ID);
|
||
cozeRequest.put("stream", false);
|
||
|
||
// 构建消息列表 - 按照 Coze API 标准格式
|
||
java.util.List<Map<String, Object>> messages = new java.util.ArrayList<>();
|
||
|
||
// 添加当前用户消息
|
||
Map<String, Object> currentMsg = new HashMap<>();
|
||
currentMsg.put(ROLE_KEY, USER_ROLE);
|
||
currentMsg.put(CONTENT_KEY, userMessage);
|
||
currentMsg.put(CONTENT_TYPE_KEY, TEXT_TYPE);
|
||
currentMsg.put("type", "question");
|
||
messages.add(currentMsg);
|
||
|
||
cozeRequest.put("additional_messages", messages);
|
||
cozeRequest.put("parameters", new HashMap<>());
|
||
|
||
return cozeRequest;
|
||
}
|
||
|
||
/**
|
||
* 从Coze响应中提取chat_id
|
||
*/
|
||
private String extractChatIdFromResponse(JSONObject responseJson) {
|
||
try {
|
||
if (responseJson != null && responseJson.getJSONObject("data") != null) {
|
||
return responseJson.getJSONObject("data").getString("id");
|
||
}
|
||
} catch (Exception e) {
|
||
log.error("提取chat_id失败: {}", e.getMessage());
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 从Coze响应中提取conversation_id
|
||
*/
|
||
private String extractConversationIdFromResponse(JSONObject responseJson) {
|
||
try {
|
||
if (responseJson != null && responseJson.getJSONObject("data") != null) {
|
||
return responseJson.getJSONObject("data").getString("conversation_id");
|
||
}
|
||
} catch (Exception e) {
|
||
log.error("提取conversation_id失败: {}", e.getMessage());
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 等待聊天完成并获取回复内容
|
||
*/
|
||
private String waitForChatCompletion(String chatId, String conversationId) {
|
||
try {
|
||
// 最多等待30秒,每2秒轮询一次
|
||
int maxAttempts = 15;
|
||
int attempt = 0;
|
||
|
||
while (attempt < maxAttempts) {
|
||
log.info("轮询聊天状态,第{}次尝试: chatId={}, conversationId={}", attempt + 1, chatId, conversationId);
|
||
|
||
// 构建状态查询URL
|
||
String statusUrl = cozeBaseUrl + "/v3/chat/retrieve?chat_id=" + chatId + "&conversation_id=" + conversationId;
|
||
|
||
// 构建请求头
|
||
HttpHeaders headers = new HttpHeaders();
|
||
headers.set("Authorization", "Bearer " + cozeApiToken);
|
||
headers.set("Content-Type", "application/json");
|
||
|
||
HttpEntity<String> request = new HttpEntity<>(headers);
|
||
|
||
// 发送状态查询请求
|
||
ResponseEntity<String> response = restTemplate.exchange(
|
||
statusUrl,
|
||
HttpMethod.GET,
|
||
request,
|
||
String.class);
|
||
|
||
JSONObject statusResponse = JSON.parseObject(response.getBody());
|
||
log.info("轮询响应: {}", statusResponse);
|
||
|
||
if (statusResponse != null && statusResponse.getJSONObject("data") != null) {
|
||
JSONObject data = statusResponse.getJSONObject("data");
|
||
String status = data.getString("status");
|
||
log.info("聊天状态: {}", status);
|
||
|
||
if ("completed".equals(status)) {
|
||
// 聊天完成,获取消息
|
||
log.info("聊天完成,开始获取消息: chatId={}, conversationId={}", chatId, conversationId);
|
||
return getChatMessages(chatId, conversationId);
|
||
} else if ("failed".equals(status)) {
|
||
log.error("Coze聊天失败: chatId={}, conversationId={}", chatId, conversationId);
|
||
return "抱歉,AI服务暂时不可用,请稍后再试。";
|
||
}
|
||
} else {
|
||
log.warn("轮询响应为空或无data字段: {}", statusResponse);
|
||
}
|
||
|
||
// 等待2秒后重试
|
||
Thread.sleep(2000);
|
||
attempt++;
|
||
}
|
||
|
||
log.warn("Coze聊天超时: chatId={}, conversationId={}", chatId, conversationId);
|
||
return "抱歉,AI响应超时,请稍后再试。";
|
||
|
||
} catch (Exception e) {
|
||
log.error("等待Coze聊天完成失败: chatId={}, conversationId={}, error={}",
|
||
chatId, conversationId, e.getMessage(), e);
|
||
return "抱歉,AI服务出现错误,请稍后再试。";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取聊天消息
|
||
*/
|
||
private String getChatMessages(String chatId, String conversationId) {
|
||
try {
|
||
log.info("获取聊天消息: chatId={}, conversationId={}", chatId, conversationId);
|
||
|
||
// 构建消息查询URL
|
||
String messagesUrl = cozeBaseUrl + "/v3/chat/message/list?chat_id=" + chatId + "&conversation_id=" + conversationId;
|
||
|
||
// 构建请求头
|
||
HttpHeaders headers = new HttpHeaders();
|
||
headers.set("Authorization", "Bearer " + cozeApiToken);
|
||
headers.set("Content-Type", "application/json");
|
||
|
||
HttpEntity<String> request = new HttpEntity<>(headers);
|
||
|
||
// 发送消息查询请求
|
||
ResponseEntity<String> response = restTemplate.exchange(
|
||
messagesUrl,
|
||
HttpMethod.GET,
|
||
request,
|
||
String.class);
|
||
|
||
JSONObject messagesResponse = JSON.parseObject(response.getBody());
|
||
log.info("消息响应: {}", messagesResponse);
|
||
|
||
if (messagesResponse != null && messagesResponse.getJSONArray("data") != null) {
|
||
java.util.List<JSONObject> messages = messagesResponse.getJSONArray("data").toJavaList(JSONObject.class);
|
||
log.info("收到{}条消息", messages.size());
|
||
|
||
// 查找AI的回复消息(role=assistant, type=answer)
|
||
for (JSONObject message : messages) {
|
||
String role = message.getString("role");
|
||
String type = message.getString("type");
|
||
log.info("消息详情: role={}, type={}, content={}", role, type, message.getString("content"));
|
||
|
||
if (ASSISTANT_ROLE.equals(role) && ANSWER_TYPE.equals(type)) {
|
||
String content = message.getString("content");
|
||
log.info("找到AI回复: {}", content);
|
||
return content;
|
||
}
|
||
}
|
||
log.warn("未找到AI回复消息");
|
||
} else {
|
||
log.warn("消息响应为空或无data字段");
|
||
}
|
||
|
||
return "抱歉,未能获取到AI回复。";
|
||
|
||
} catch (Exception e) {
|
||
log.error("获取Coze聊天消息失败: chatId={}, conversationId={}, error={}",
|
||
chatId, conversationId, e.getMessage(), e);
|
||
return "抱歉,获取AI回复失败。";
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 发送总结消息到Coze AI
|
||
*/
|
||
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();
|
||
headers.set("Authorization", "Bearer " + cozeApiToken);
|
||
headers.set("Content-Type", "application/json");
|
||
|
||
// 构建请求体 - 使用总结专用的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
|
||
String cozeApiUrl = cozeBaseUrl + chatPath;
|
||
log.info("发送Coze总结请求到: {}, 请求体: {}", cozeApiUrl, requestBody);
|
||
|
||
// 发送请求
|
||
ResponseEntity<String> response = restTemplate.exchange(
|
||
cozeApiUrl,
|
||
HttpMethod.POST,
|
||
request,
|
||
String.class);
|
||
|
||
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 = 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总结服务暂时不可用,请稍后再试。";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 构建总结请求 - 使用专门的总结bot和workflow
|
||
*/
|
||
private Map<String, Object> buildSummaryRequest(String conversationId, String userMessage, String userId) {
|
||
Map<String, Object> cozeRequest = new HashMap<>();
|
||
cozeRequest.put("bot_id", summaryBotId != null && !summaryBotId.trim().isEmpty() ? summaryBotId : chatBotId);
|
||
|
||
// 如果有总结workflow_id,则添加
|
||
if (summaryWorkflowId != null && !summaryWorkflowId.trim().isEmpty()) {
|
||
cozeRequest.put("workflow_id", summaryWorkflowId);
|
||
}
|
||
|
||
cozeRequest.put("user_id", userId != null ? userId : DEFAULT_USER_ID);
|
||
cozeRequest.put("stream", false);
|
||
cozeRequest.put("auto_save_history", true);
|
||
|
||
// 如果有会话ID,则添加
|
||
if (conversationId != null && !conversationId.trim().isEmpty()) {
|
||
cozeRequest.put("conversation_id", conversationId);
|
||
}
|
||
|
||
// 构建消息列表 - 按照 Coze API 标准格式
|
||
java.util.List<Map<String, Object>> messages = new java.util.ArrayList<>();
|
||
|
||
// 添加当前用户消息
|
||
Map<String, Object> currentMsg = new HashMap<>();
|
||
currentMsg.put(ROLE_KEY, USER_ROLE);
|
||
currentMsg.put(CONTENT_KEY, userMessage);
|
||
currentMsg.put(CONTENT_TYPE_KEY, TEXT_TYPE);
|
||
messages.add(currentMsg);
|
||
|
||
cozeRequest.put("additional_messages", messages);
|
||
|
||
return cozeRequest;
|
||
}
|
||
|
||
/**
|
||
* 获取对话历史
|
||
*/
|
||
private String getConversationHistory(String conversationId) {
|
||
try {
|
||
// 这里应该从数据库获取对话历史
|
||
// 暂时返回空字符串
|
||
return "";
|
||
} catch (Exception e) {
|
||
log.error("获取对话历史失败", e);
|
||
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;
|
||
}
|
||
}
|
||
} |