|
@@ -38,21 +38,50 @@ public class ChatServiceImpl implements IChatService {
|
|
|
// 存储每个会话的对话内容
|
|
// 存储每个会话的对话内容
|
|
|
private final Map<String, List<Map<String, String>>> conversationMap = new ConcurrentHashMap<>();
|
|
private final Map<String, List<Map<String, String>>> conversationMap = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
- @Override
|
|
|
|
|
- public void processMessageStream(String userMessage, Long agentId, String agentGender,
|
|
|
|
|
- List<Map<String, String>> ttsVcnList, String conversationId,
|
|
|
|
|
- Boolean isGreeting, Integer requestId, String customerPhone, SseEmitter emitter) {
|
|
|
|
|
- // 如果没有传递customerPhone,尝试从数据库中查询
|
|
|
|
|
- if (customerPhone == null && conversationId != null) {
|
|
|
|
|
- TalkSessionVo session = talkSessionService.queryBySessionId(conversationId);
|
|
|
|
|
- if (session != null) {
|
|
|
|
|
- customerPhone = session.getCustomerPhone();
|
|
|
|
|
- log.info("从数据库查询到客户手机号: {}", customerPhone);
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 合成音频并通过 SSE 发送
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param text 要合成的文本
|
|
|
|
|
+ * @param agentConfig 客服配置
|
|
|
|
|
+ * @param emitter SSE 发送器
|
|
|
|
|
+ * @return 是否成功
|
|
|
|
|
+ */
|
|
|
|
|
+ private boolean synthesizeAndSendAudio(String text, TalkAgentVo agentConfig, SseEmitter emitter) {
|
|
|
|
|
+ CountDownLatch latch = new CountDownLatch(1);
|
|
|
|
|
+ ByteArrayOutputStream mergedAudioBytes = new ByteArrayOutputStream();
|
|
|
|
|
+
|
|
|
|
|
+ ttsService.synthesizeStream(text, agentConfig, (audioChunk, status) -> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ byte[] audioBytes = Base64.getDecoder().decode(audioChunk);
|
|
|
|
|
+ mergedAudioBytes.write(audioBytes);
|
|
|
|
|
+
|
|
|
|
|
+ if (status == 2) {
|
|
|
|
|
+ String mergedAudioBase64 = Base64.getEncoder().encodeToString(mergedAudioBytes.toByteArray());
|
|
|
|
|
+ Map<String, String> audioEvent = new HashMap<>();
|
|
|
|
|
+ audioEvent.put("name", "audio");
|
|
|
|
|
+ audioEvent.put("data", mergedAudioBase64);
|
|
|
|
|
+ emitter.send(SseEmitter.event().data(audioEvent));
|
|
|
|
|
+ latch.countDown();
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("发送音频失败", e);
|
|
|
|
|
+ latch.countDown();
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- String finalCustomerPhone = customerPhone;
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ return latch.await(30, TimeUnit.SECONDS);
|
|
|
|
|
+ } catch (InterruptedException e) {
|
|
|
|
|
+ log.error("等待音频合成被中断", e);
|
|
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void processMessageStream(String userMessage, Long agentId, String agentGender,
|
|
|
|
|
+ List<Map<String, String>> ttsVcnList, String conversationId,
|
|
|
|
|
+ Boolean isGreeting, Integer requestId, SseEmitter emitter) {
|
|
|
// 在主线程中获取用户ID,避免在异步线程中访问ThreadLocal
|
|
// 在主线程中获取用户ID,避免在异步线程中访问ThreadLocal
|
|
|
Long userId = LoginHelper.getUserId();
|
|
Long userId = LoginHelper.getUserId();
|
|
|
if (userId == null) {
|
|
if (userId == null) {
|
|
@@ -78,39 +107,8 @@ public class ChatServiceImpl implements IChatService {
|
|
|
|
|
|
|
|
// 如果是欢迎语,合成语音但不发送text事件(避免前端重复显示)
|
|
// 如果是欢迎语,合成语音但不发送text事件(避免前端重复显示)
|
|
|
if (Boolean.TRUE.equals(isGreeting)) {
|
|
if (Boolean.TRUE.equals(isGreeting)) {
|
|
|
- TalkAgentVo finalAgentConfig = agentConfig;
|
|
|
|
|
-
|
|
|
|
|
// 合成欢迎语语音
|
|
// 合成欢迎语语音
|
|
|
- CountDownLatch latch = new CountDownLatch(1);
|
|
|
|
|
- ByteArrayOutputStream mergedAudioBytes = new ByteArrayOutputStream();
|
|
|
|
|
-
|
|
|
|
|
- ttsService.synthesizeStream(userMessage, finalAgentConfig, (audioChunk, status) -> {
|
|
|
|
|
- try {
|
|
|
|
|
- byte[] audioBytes = Base64.getDecoder().decode(audioChunk);
|
|
|
|
|
- mergedAudioBytes.write(audioBytes);
|
|
|
|
|
-
|
|
|
|
|
- if (status == 2) {
|
|
|
|
|
- String mergedAudioBase64 = Base64.getEncoder().encodeToString(mergedAudioBytes.toByteArray());
|
|
|
|
|
- Map<String, String> audioEvent = new HashMap<>();
|
|
|
|
|
- audioEvent.put("name", "audio");
|
|
|
|
|
- audioEvent.put("data", mergedAudioBase64);
|
|
|
|
|
- emitter.send(SseEmitter.event()
|
|
|
|
|
- .data(audioEvent));
|
|
|
|
|
- latch.countDown();
|
|
|
|
|
- }
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.error("发送欢迎语音频失败", e);
|
|
|
|
|
- latch.countDown();
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 等待音频合成完成
|
|
|
|
|
- try {
|
|
|
|
|
- latch.await(30, java.util.concurrent.TimeUnit.SECONDS);
|
|
|
|
|
- } catch (InterruptedException e) {
|
|
|
|
|
- log.error("等待欢迎语音频合成被中断", e);
|
|
|
|
|
- Thread.currentThread().interrupt();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ synthesizeAndSendAudio(userMessage, agentConfig, emitter);
|
|
|
|
|
|
|
|
// 发送完成信号
|
|
// 发送完成信号
|
|
|
Map<String, String> doneEvent = new HashMap<>();
|
|
Map<String, String> doneEvent = new HashMap<>();
|
|
@@ -128,7 +126,8 @@ public class ChatServiceImpl implements IChatService {
|
|
|
// 初始化对话记录列表
|
|
// 初始化对话记录列表
|
|
|
String finalConversationId = conversationId;
|
|
String finalConversationId = conversationId;
|
|
|
if (finalConversationId != null) {
|
|
if (finalConversationId != null) {
|
|
|
- conversationMap.putIfAbsent(finalConversationId, new ArrayList<>());
|
|
|
|
|
|
|
+ // 使用 computeIfAbsent 避免竞态条件
|
|
|
|
|
+ conversationMap.computeIfAbsent(finalConversationId, k -> new ArrayList<>());
|
|
|
|
|
|
|
|
// 添加用户消息(客户)
|
|
// 添加用户消息(客户)
|
|
|
Map<String, String> userMsg = new HashMap<>();
|
|
Map<String, String> userMsg = new HashMap<>();
|
|
@@ -178,8 +177,8 @@ public class ChatServiceImpl implements IChatService {
|
|
|
|
|
|
|
|
// 发送conversationId
|
|
// 发送conversationId
|
|
|
if (newConversationId != null) {
|
|
if (newConversationId != null) {
|
|
|
- // 确保conversationMap中有对应的列表
|
|
|
|
|
- conversationMap.putIfAbsent(newConversationId, new ArrayList<>());
|
|
|
|
|
|
|
+ // 使用 computeIfAbsent 确保线程安全
|
|
|
|
|
+ conversationMap.computeIfAbsent(newConversationId, k -> new ArrayList<>());
|
|
|
|
|
|
|
|
// 更新conversationId
|
|
// 更新conversationId
|
|
|
if (finalConversationId == null) {
|
|
if (finalConversationId == null) {
|
|
@@ -217,53 +216,7 @@ public class ChatServiceImpl implements IChatService {
|
|
|
|
|
|
|
|
if (needAudio && sentence != null && !sentence.trim().isEmpty()) {
|
|
if (needAudio && sentence != null && !sentence.trim().isEmpty()) {
|
|
|
log.info("合成句子音频,长度: {}, 内容: {}", sentence.length(), sentence);
|
|
log.info("合成句子音频,长度: {}, 内容: {}", sentence.length(), sentence);
|
|
|
-
|
|
|
|
|
- // 使用 CountDownLatch 等待音频合成完成
|
|
|
|
|
- CountDownLatch latch = new CountDownLatch(1);
|
|
|
|
|
-
|
|
|
|
|
- // 用于累积同一句子的所有音频片段(字节数组)
|
|
|
|
|
- ByteArrayOutputStream mergedAudioBytes = new ByteArrayOutputStream();
|
|
|
|
|
-
|
|
|
|
|
- // 对每个句子进行 TTS 合成
|
|
|
|
|
- ttsService.synthesizeStream(sentence, finalAgentConfig, (audioChunk, status) -> {
|
|
|
|
|
- try {
|
|
|
|
|
- // 解码base64音频片段并累积到字节流
|
|
|
|
|
- byte[] audioBytes = Base64.getDecoder().decode(audioChunk);
|
|
|
|
|
- mergedAudioBytes.write(audioBytes);
|
|
|
|
|
-
|
|
|
|
|
- // 当音频合成完成时(status=2),发送合并后的音频
|
|
|
|
|
- if (status == 2) {
|
|
|
|
|
- // 将合并后的字节数组重新编码为base64
|
|
|
|
|
- String mergedAudioBase64 = Base64.getEncoder().encodeToString(mergedAudioBytes.toByteArray());
|
|
|
|
|
-
|
|
|
|
|
- Map<String, String> audioEvent = new HashMap<>();
|
|
|
|
|
- audioEvent.put("name", "audio");
|
|
|
|
|
- audioEvent.put("data", mergedAudioBase64);
|
|
|
|
|
- emitter.send(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, TimeUnit.SECONDS);
|
|
|
|
|
- if (completed) {
|
|
|
|
|
- log.info("句子音频合成等待完成");
|
|
|
|
|
- } else {
|
|
|
|
|
- log.warn("句子音频合成等待超时");
|
|
|
|
|
- }
|
|
|
|
|
- } catch (InterruptedException e) {
|
|
|
|
|
- log.error("等待音频合成被中断", e);
|
|
|
|
|
- Thread.currentThread().interrupt();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ synthesizeAndSendAudio(sentence, finalAgentConfig, emitter);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 如果是最后一个句子,发送完成事件
|
|
// 如果是最后一个句子,发送完成事件
|
|
@@ -289,9 +242,13 @@ public class ChatServiceImpl implements IChatService {
|
|
|
String conversationJson = new ObjectMapper().writeValueAsString(messages);
|
|
String conversationJson = new ObjectMapper().writeValueAsString(messages);
|
|
|
|
|
|
|
|
// 保存到数据库
|
|
// 保存到数据库
|
|
|
- talkSessionService.saveOrUpdateConversation(newConversationId, finalAgentConfig.getId(), conversationJson, finalCustomerPhone, finalUserId);
|
|
|
|
|
|
|
+ talkSessionService.saveOrUpdateConversation(newConversationId, finalAgentConfig.getId(), conversationJson, null, finalUserId);
|
|
|
|
|
|
|
|
log.info("对话内容已保存到数据库,会话ID: {}, 消息数量: {}", newConversationId, messages.size());
|
|
log.info("对话内容已保存到数据库,会话ID: {}, 消息数量: {}", newConversationId, messages.size());
|
|
|
|
|
+
|
|
|
|
|
+ // 清理内存:对话完成后从 conversationMap 中移除
|
|
|
|
|
+ conversationMap.remove(newConversationId);
|
|
|
|
|
+ log.debug("已清理会话 {} 的对话内容缓存", newConversationId);
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("保存对话内容失败", e);
|
|
log.error("保存对话内容失败", e);
|
|
|
}
|
|
}
|