|
|
@@ -167,4 +167,167 @@ public class ChatServiceImpl implements IChatService {
|
|
|
return null;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void processMessageStream(String userMessage, Long agentId, String agentGender,
|
|
|
+ List<Map<String, String>> ttsVcnList, String conversationId,
|
|
|
+ Boolean isGreeting, Integer requestId, org.springframework.web.servlet.mvc.method.annotation.SseEmitter emitter) {
|
|
|
+ // 在主线程中获取用户ID,避免在异步线程中访问ThreadLocal
|
|
|
+ Long userId = 0L;
|
|
|
+ try {
|
|
|
+ userId = StpUtil.getLoginIdAsLong();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("获取登录用户ID失败,使用默认值", e);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新最新请求ID
|
|
|
+ if (requestId != null) {
|
|
|
+ latestRequestIdMap.put(userId, requestId);
|
|
|
+ log.info("流式请求 - 更新用户 {} 的最新请求ID为: {}", userId, requestId);
|
|
|
+ }
|
|
|
+
|
|
|
+ Long finalUserId = userId;
|
|
|
+ CompletableFuture.runAsync(() -> {
|
|
|
+ try {
|
|
|
+
|
|
|
+ // 获取客服配置
|
|
|
+ TalkAgentVo agentConfig = null;
|
|
|
+ if (agentId != null) {
|
|
|
+ agentConfig = talkAgentService.queryById(agentId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是欢迎语,直接发送
|
|
|
+ if (Boolean.TRUE.equals(isGreeting)) {
|
|
|
+ Map<String, String> textEvent = new HashMap<>();
|
|
|
+ textEvent.put("name", "text");
|
|
|
+ textEvent.put("data", userMessage);
|
|
|
+ emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event()
|
|
|
+ .data(textEvent));
|
|
|
+
|
|
|
+ Map<String, String> doneEvent = new HashMap<>();
|
|
|
+ doneEvent.put("name", "done");
|
|
|
+ doneEvent.put("data", "");
|
|
|
+ emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event()
|
|
|
+ .data(doneEvent));
|
|
|
+ emitter.complete();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ TalkAgentVo finalAgentConfig = agentConfig;
|
|
|
+ Integer finalRequestId = requestId;
|
|
|
+
|
|
|
+ difyService.callWorkflowStream(userMessage, agentGender, ttsVcnList, agentConfig, finalUserId, conversationId,
|
|
|
+ (textChunk) -> {
|
|
|
+ try {
|
|
|
+ // 构建 JSON 格式的事件数据
|
|
|
+ Map<String, String> eventData = new HashMap<>();
|
|
|
+ eventData.put("name", "text");
|
|
|
+ eventData.put("data", textChunk);
|
|
|
+ emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event()
|
|
|
+ .data(eventData));
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送文本失败", e);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ (sentence, newConversationId, isComplete) -> {
|
|
|
+ try {
|
|
|
+ log.info("句子回调 - 句子: {}, isComplete: {}", sentence != null ? sentence : "(null)", isComplete);
|
|
|
+
|
|
|
+ // 发送conversationId
|
|
|
+ if (newConversationId != null) {
|
|
|
+ Map<String, String> conversationEvent = new HashMap<>();
|
|
|
+ conversationEvent.put("name", "conversationId");
|
|
|
+ conversationEvent.put("data", newConversationId);
|
|
|
+ emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event()
|
|
|
+ .data(conversationEvent));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否需要合成音频(只为最新请求合成音频)
|
|
|
+ boolean needAudio = true;
|
|
|
+ if (finalRequestId != null) {
|
|
|
+ Integer latestRequestId = latestRequestIdMap.get(finalUserId);
|
|
|
+ if (latestRequestId != null && !latestRequestId.equals(finalRequestId)) {
|
|
|
+ needAudio = false;
|
|
|
+ log.info("流式请求ID {} 不是最新请求(最新为 {}),跳过音频合成", finalRequestId, latestRequestId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (needAudio && sentence != null && !sentence.trim().isEmpty()) {
|
|
|
+ log.info("合成句子音频,长度: {}, 内容: {}", sentence.length(), sentence);
|
|
|
+
|
|
|
+ // 使用 CountDownLatch 等待音频合成完成
|
|
|
+ java.util.concurrent.CountDownLatch latch = new java.util.concurrent.CountDownLatch(1);
|
|
|
+
|
|
|
+ // 用于累积同一句子的所有音频片段(字节数组)
|
|
|
+ java.io.ByteArrayOutputStream mergedAudioBytes = new java.io.ByteArrayOutputStream();
|
|
|
+
|
|
|
+ // 对每个句子进行 TTS 合成
|
|
|
+ ttsService.synthesizeStream(sentence, finalAgentConfig, (audioChunk, status) -> {
|
|
|
+ try {
|
|
|
+ // 解码base64音频片段并累积到字节流
|
|
|
+ byte[] audioBytes = java.util.Base64.getDecoder().decode(audioChunk);
|
|
|
+ mergedAudioBytes.write(audioBytes);
|
|
|
+
|
|
|
+ // 当音频合成完成时(status=2),发送合并后的音频
|
|
|
+ if (status == 2) {
|
|
|
+ // 将合并后的字节数组重新编码为base64
|
|
|
+ String mergedAudioBase64 = java.util.Base64.getEncoder().encodeToString(mergedAudioBytes.toByteArray());
|
|
|
+
|
|
|
+ Map<String, String> audioEvent = new HashMap<>();
|
|
|
+ audioEvent.put("name", "audio");
|
|
|
+ audioEvent.put("data", mergedAudioBase64);
|
|
|
+ emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event()
|
|
|
+ .data(audioEvent));
|
|
|
+
|
|
|
+ log.info("句子音频合成完成,合并后长度: {}, 释放锁", mergedAudioBase64.length());
|
|
|
+ latch.countDown();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送音频失败", e);
|
|
|
+ latch.countDown(); // 出错时也要释放锁
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 等待当前句子的音频合成完成
|
|
|
+ try {
|
|
|
+ log.info("等待句子音频合成完成...");
|
|
|
+ boolean completed = latch.await(30, java.util.concurrent.TimeUnit.SECONDS);
|
|
|
+ if (completed) {
|
|
|
+ log.info("句子音频合成等待完成");
|
|
|
+ } else {
|
|
|
+ log.warn("句子音频合成等待超时");
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ log.error("等待音频合成被中断", e);
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是最后一个句子,发送完成事件
|
|
|
+ if (isComplete) {
|
|
|
+ log.info("收到完成标志,准备发送done事件");
|
|
|
+ Map<String, String> doneEvent = new HashMap<>();
|
|
|
+ doneEvent.put("name", "done");
|
|
|
+ doneEvent.put("data", "");
|
|
|
+ emitter.send(org.springframework.web.servlet.mvc.method.annotation.SseEmitter.event()
|
|
|
+ .data(doneEvent));
|
|
|
+ log.info("done事件已发送,关闭SSE连接");
|
|
|
+ emitter.complete();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理句子回调失败", e);
|
|
|
+ emitter.completeWithError(e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("流式处理失败", e);
|
|
|
+ try {
|
|
|
+ emitter.completeWithError(e);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ log.error("发送错误失败", ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|