From 33d27fdedc3653a92d9b478382efd460afd538b9 Mon Sep 17 00:00:00 2001 From: yang <2119157836@qq.com> Date: Tue, 4 Mar 2025 15:48:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20ai=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../communityCenter/ChatController.java | 12 ++-- .../src/main/resources/application-dev.yml | 2 + .../config/WebSocketConfig.java | 15 +++-- .../communityCenter/domain/ChatMessage.java | 15 +++++ .../service/CustomerService.java | 11 ---- .../communityCenter/service/HumanService.java | 15 +++++ .../service/impl/AIServiceImpl.java | 2 +- ...ServiceImpl.java => HumanServiceImpl.java} | 16 ++++-- ...WebSocketPoint.java => ChatWebSocket.java} | 43 ++++++--------- .../webSocket/HumanWebSocket.java | 55 +++++++++++++++++++ 10 files changed, 133 insertions(+), 53 deletions(-) create mode 100644 mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/domain/ChatMessage.java delete mode 100644 mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/CustomerService.java create mode 100644 mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/HumanService.java rename mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/{CustomerServiceImpl.java => HumanServiceImpl.java} (67%) rename mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/{ChatWebSocketPoint.java => ChatWebSocket.java} (63%) create mode 100644 mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/HumanWebSocket.java diff --git a/mcwl-admin/src/main/java/com/mcwl/web/controller/communityCenter/ChatController.java b/mcwl-admin/src/main/java/com/mcwl/web/controller/communityCenter/ChatController.java index 13a63b0..ff753cb 100644 --- a/mcwl-admin/src/main/java/com/mcwl/web/controller/communityCenter/ChatController.java +++ b/mcwl-admin/src/main/java/com/mcwl/web/controller/communityCenter/ChatController.java @@ -1,6 +1,6 @@ package com.mcwl.web.controller.communityCenter; -import com.mcwl.communityCenter.webSocket.ChatWebSocketPoint; +import com.mcwl.communityCenter.webSocket.ChatWebSocket; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -11,15 +11,11 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/chat") public class ChatController { - private final ChatWebSocketPoint chatWebSocketPoint; + private final ChatWebSocket chatWebSocket; @GetMapping("/switchUserMode") - public void switchUserMode(String sessionId, Boolean isCustomer) throws Exception { - if (isCustomer == null) { - chatWebSocketPoint.switchUserMode(sessionId, false); - } else { - chatWebSocketPoint.switchUserMode(sessionId, isCustomer); - } + public void switchUserMode(String sessionId) throws Exception { + chatWebSocket.switchUserMode(sessionId); } } \ No newline at end of file diff --git a/mcwl-admin/src/main/resources/application-dev.yml b/mcwl-admin/src/main/resources/application-dev.yml index e17876b..e1c8c21 100644 --- a/mcwl-admin/src/main/resources/application-dev.yml +++ b/mcwl-admin/src/main/resources/application-dev.yml @@ -154,6 +154,8 @@ mall: notifyUrl: https://53a65908.r27.cpolar.top/ali/pay/notify # 沙箱支付宝网关 gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do + # 绑定回调 + bindUrl: https://4b0ca615.r27.cpolar.top/ali/pay/callback huawei: obs: diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/config/WebSocketConfig.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/config/WebSocketConfig.java index 192b4a1..789bf1f 100644 --- a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/config/WebSocketConfig.java +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/config/WebSocketConfig.java @@ -1,6 +1,7 @@ package com.mcwl.communityCenter.config; -import com.mcwl.communityCenter.webSocket.ChatWebSocketPoint; +import com.mcwl.communityCenter.webSocket.ChatWebSocket; +import com.mcwl.communityCenter.webSocket.HumanWebSocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; @@ -13,13 +14,19 @@ public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry.addHandler(chatWebSocketPoint(), "/chat") + registry.addHandler(chatWebSocket(), "/chat") + .addHandler(humanWebSocket(), "/chat/human") .setAllowedOrigins("*"); } @Bean - public ChatWebSocketPoint chatWebSocketPoint() { - return new ChatWebSocketPoint(); + public ChatWebSocket chatWebSocket() { + return new ChatWebSocket(); + } + + @Bean + public HumanWebSocket humanWebSocket() { + return new HumanWebSocket(); } } \ No newline at end of file diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/domain/ChatMessage.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/domain/ChatMessage.java new file mode 100644 index 0000000..d770395 --- /dev/null +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/domain/ChatMessage.java @@ -0,0 +1,15 @@ +package com.mcwl.communityCenter.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.time.LocalDateTime; + +// 消息实体类 +@Data +@AllArgsConstructor +public class ChatMessage { + private String role; // "user" 或 "assistant" + private String content; + private LocalDateTime timestamp; +} \ No newline at end of file diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/CustomerService.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/CustomerService.java deleted file mode 100644 index 5115fab..0000000 --- a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/CustomerService.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.mcwl.communityCenter.service; - -import org.springframework.web.socket.WebSocketSession; - -import java.io.IOException; - -public interface CustomerService { - void transferToHuman(String userId, WebSocketSession session); - - void handleCustomerMessage(String userId, String message) throws IOException; -} diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/HumanService.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/HumanService.java new file mode 100644 index 0000000..7990ac7 --- /dev/null +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/HumanService.java @@ -0,0 +1,15 @@ +package com.mcwl.communityCenter.service; + +import com.mcwl.communityCenter.domain.ChatMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.util.List; + +public interface HumanService { + void transferToHuman(String userId, WebSocketSession session); + + void handleHumanMessage(String userId, String message) throws IOException; + + void transferToAgent(String sessionId, List history); +} diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/AIServiceImpl.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/AIServiceImpl.java index 594e430..0dadcde 100644 --- a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/AIServiceImpl.java +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/AIServiceImpl.java @@ -55,7 +55,7 @@ public class AIServiceImpl implements AIService { .bodyToFlux(String.class) // 原始数据流 .takeUntil(data -> data.contains("[DONE]")) // 遇到结束标记停止 .flatMap(json -> parseContentFromJson(json)) // 解析内容 - .onErrorResume(e -> Flux.just("服务暂时不可用"));// 错误处理 + .onErrorResume(e -> Flux.just(""));// 错误处理 } diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/CustomerServiceImpl.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/HumanServiceImpl.java similarity index 67% rename from mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/CustomerServiceImpl.java rename to mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/HumanServiceImpl.java index 49fa65c..675d4e2 100644 --- a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/CustomerServiceImpl.java +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/HumanServiceImpl.java @@ -1,17 +1,19 @@ package com.mcwl.communityCenter.service.impl; -import com.mcwl.communityCenter.service.CustomerService; +import com.mcwl.communityCenter.domain.ChatMessage; +import com.mcwl.communityCenter.service.HumanService; import org.springframework.stereotype.Service; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import java.io.IOException; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; @Service -public class CustomerServiceImpl implements CustomerService { +public class HumanServiceImpl implements HumanService { + // 已上线的客服,key为用户ID,value为WebSocketSession private static final Map activeSessions = new ConcurrentHashMap<>(); @Override @@ -21,7 +23,7 @@ public class CustomerServiceImpl implements CustomerService { } @Override - public void handleCustomerMessage(String userId, String message) throws IOException { + public void handleHumanMessage(String userId, String message) throws IOException { // 应实现消息队列或持久化存储 // 先返回固定响应 WebSocketSession session = activeSessions.get(userId); @@ -29,4 +31,10 @@ public class CustomerServiceImpl implements CustomerService { session.sendMessage(new TextMessage("[客服] 您好,当前为人工服务模式")); } } + + @Override + public void transferToAgent(String sessionId, List history) { + + + } } diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/ChatWebSocketPoint.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/ChatWebSocket.java similarity index 63% rename from mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/ChatWebSocketPoint.java rename to mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/ChatWebSocket.java index adfd9bb..76e7d14 100644 --- a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/ChatWebSocketPoint.java +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/ChatWebSocket.java @@ -1,7 +1,7 @@ package com.mcwl.communityCenter.webSocket; import com.mcwl.communityCenter.service.AIService; -import com.mcwl.communityCenter.service.CustomerService; +import com.mcwl.communityCenter.service.HumanService; import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.socket.CloseStatus; @@ -18,7 +18,7 @@ import java.util.concurrent.ConcurrentHashMap; @ServerEndpoint("/chat") @NoArgsConstructor -public class ChatWebSocketPoint extends AbstractWebSocketHandler { +public class ChatWebSocket extends AbstractWebSocketHandler { private final Map userModes = new ConcurrentHashMap<>(); // 存储会话与订阅的映射关系 @@ -27,38 +27,32 @@ public class ChatWebSocketPoint extends AbstractWebSocketHandler { @Autowired private AIService aiService; - @Autowired - private CustomerService customerService; - // 构造函数注入服务... @Override public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String userId = session.getId(); String userMessage = message.getPayload(); - // 判断当前模式 - if (userModes.getOrDefault(userId, false)) { - // 人工模式 - customerService.handleCustomerMessage(userId, userMessage); - } else { - // AI 流式响应模式 - Flux responseStream = aiService.getDeepSeekResponseStream(userMessage); - // 订阅响应流并存储 Disposable - Disposable disposable = responseStream - .doOnNext(chunk -> sendText(session, chunk)) - .doOnComplete(() -> sendText(session, "[END]")) - .doOnError(e -> sendText(session, "[ERROR] " + e.getMessage())) - .subscribe(); + // AI 流式响应模式 + Flux responseStream = aiService.getDeepSeekResponseStream(userMessage); + + // 订阅响应流并存储 Disposable + Disposable disposable = responseStream + .doOnNext(chunk -> sendText(session, chunk)) // 发送每个数据块到客户端 + .doOnComplete(() -> sendText(session, "[END]")) // 当流处理完成时,发送结束标记 + .doOnError(e -> sendText(session, "[ERROR] " + e.getMessage())) + .subscribe(); + + sessionSubscriptions.put(userId, disposable); - sessionSubscriptions.put(userId, disposable); - } } @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { super.afterConnectionEstablished(session); - userModes.put(session.getId(), false); +// userModes.put(session.getId(), false); + session.sendMessage(new TextMessage("[AI] 您好,请问有什么问题?")); } @Override @@ -66,15 +60,14 @@ public class ChatWebSocketPoint extends AbstractWebSocketHandler { // 清理订阅资源 String sessionId = session.getId(); Disposable disposable = sessionSubscriptions.remove(sessionId); - if (disposable != null && !disposable.isDisposed()) { + if (disposable != null && disposable.isDisposed()) { disposable.dispose(); - System.out.println("已清理会话资源: " + sessionId); } } // 添加模式切换方法(根据业务需求) - public void switchUserMode(String sessionId, boolean isHumanMode) { - userModes.put(sessionId, isHumanMode); + public void switchUserMode(String sessionId) { + } // 线程安全的发送方法 diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/HumanWebSocket.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/HumanWebSocket.java new file mode 100644 index 0000000..dc09eab --- /dev/null +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/HumanWebSocket.java @@ -0,0 +1,55 @@ +package com.mcwl.communityCenter.webSocket; + +import com.mcwl.common.utils.SecurityUtils; +import com.mcwl.communityCenter.service.HumanService; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.AbstractWebSocketHandler; +import reactor.core.Disposable; + +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@ServerEndpoint("/chat/human") +@NoArgsConstructor +public class HumanWebSocket extends AbstractWebSocketHandler { + + @Autowired + private HumanService humanService; + + // 构造函数注入服务... + + @Override + public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + String userId = session.getId(); + String userMessage = message.getPayload(); + + humanService.handleHumanMessage(userId, userMessage); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + super.afterConnectionEstablished(session); + humanService.transferToHuman(SecurityUtils.getUserId().toString(), session); + System.out.println("客服:" + SecurityUtils.getUsername() + " 已上线"); + } + + + // 线程安全的发送方法 + private void sendText(WebSocketSession session, String text) { + try { + if (session.isOpen()) { + synchronized (session) { // WebSocketSession 非线程安全 + session.sendMessage(new TextMessage(text)); + } + } + } catch (IOException e) { + System.out.println("WebSocket 发送失败: " + e.getMessage()); + } + } +} \ No newline at end of file