feat: ai调整
parent
38d47a0bca
commit
33d27fdedc
|
@ -1,6 +1,6 @@
|
||||||
package com.mcwl.web.controller.communityCenter;
|
package com.mcwl.web.controller.communityCenter;
|
||||||
|
|
||||||
import com.mcwl.communityCenter.webSocket.ChatWebSocketPoint;
|
import com.mcwl.communityCenter.webSocket.ChatWebSocket;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
@ -11,15 +11,11 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
@RequestMapping("/chat")
|
@RequestMapping("/chat")
|
||||||
public class ChatController {
|
public class ChatController {
|
||||||
|
|
||||||
private final ChatWebSocketPoint chatWebSocketPoint;
|
private final ChatWebSocket chatWebSocket;
|
||||||
|
|
||||||
@GetMapping("/switchUserMode")
|
@GetMapping("/switchUserMode")
|
||||||
public void switchUserMode(String sessionId, Boolean isCustomer) throws Exception {
|
public void switchUserMode(String sessionId) throws Exception {
|
||||||
if (isCustomer == null) {
|
chatWebSocket.switchUserMode(sessionId);
|
||||||
chatWebSocketPoint.switchUserMode(sessionId, false);
|
|
||||||
} else {
|
|
||||||
chatWebSocketPoint.switchUserMode(sessionId, isCustomer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -154,6 +154,8 @@ mall:
|
||||||
notifyUrl: https://53a65908.r27.cpolar.top/ali/pay/notify
|
notifyUrl: https://53a65908.r27.cpolar.top/ali/pay/notify
|
||||||
# 沙箱支付宝网关
|
# 沙箱支付宝网关
|
||||||
gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do
|
gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do
|
||||||
|
# 绑定回调
|
||||||
|
bindUrl: https://4b0ca615.r27.cpolar.top/ali/pay/callback
|
||||||
|
|
||||||
huawei:
|
huawei:
|
||||||
obs:
|
obs:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.mcwl.communityCenter.config;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||||
|
@ -13,13 +14,19 @@ public class WebSocketConfig implements WebSocketConfigurer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||||
registry.addHandler(chatWebSocketPoint(), "/chat")
|
registry.addHandler(chatWebSocket(), "/chat")
|
||||||
|
.addHandler(humanWebSocket(), "/chat/human")
|
||||||
.setAllowedOrigins("*");
|
.setAllowedOrigins("*");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ChatWebSocketPoint chatWebSocketPoint() {
|
public ChatWebSocket chatWebSocket() {
|
||||||
return new ChatWebSocketPoint();
|
return new ChatWebSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public HumanWebSocket humanWebSocket() {
|
||||||
|
return new HumanWebSocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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<ChatMessage> history);
|
||||||
|
}
|
|
@ -55,7 +55,7 @@ public class AIServiceImpl implements AIService {
|
||||||
.bodyToFlux(String.class) // 原始数据流
|
.bodyToFlux(String.class) // 原始数据流
|
||||||
.takeUntil(data -> data.contains("[DONE]")) // 遇到结束标记停止
|
.takeUntil(data -> data.contains("[DONE]")) // 遇到结束标记停止
|
||||||
.flatMap(json -> parseContentFromJson(json)) // 解析内容
|
.flatMap(json -> parseContentFromJson(json)) // 解析内容
|
||||||
.onErrorResume(e -> Flux.just("服务暂时不可用"));// 错误处理
|
.onErrorResume(e -> Flux.just(""));// 错误处理
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
package com.mcwl.communityCenter.service.impl;
|
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.stereotype.Service;
|
||||||
import org.springframework.web.socket.TextMessage;
|
import org.springframework.web.socket.TextMessage;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class CustomerServiceImpl implements CustomerService {
|
public class HumanServiceImpl implements HumanService {
|
||||||
|
|
||||||
|
// 已上线的客服,key为用户ID,value为WebSocketSession
|
||||||
private static final Map<String, WebSocketSession> activeSessions = new ConcurrentHashMap<>();
|
private static final Map<String, WebSocketSession> activeSessions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -21,7 +23,7 @@ public class CustomerServiceImpl implements CustomerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleCustomerMessage(String userId, String message) throws IOException {
|
public void handleHumanMessage(String userId, String message) throws IOException {
|
||||||
// 应实现消息队列或持久化存储
|
// 应实现消息队列或持久化存储
|
||||||
// 先返回固定响应
|
// 先返回固定响应
|
||||||
WebSocketSession session = activeSessions.get(userId);
|
WebSocketSession session = activeSessions.get(userId);
|
||||||
|
@ -29,4 +31,10 @@ public class CustomerServiceImpl implements CustomerService {
|
||||||
session.sendMessage(new TextMessage("[客服] 您好,当前为人工服务模式"));
|
session.sendMessage(new TextMessage("[客服] 您好,当前为人工服务模式"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transferToAgent(String sessionId, List<ChatMessage> history) {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package com.mcwl.communityCenter.webSocket;
|
package com.mcwl.communityCenter.webSocket;
|
||||||
|
|
||||||
import com.mcwl.communityCenter.service.AIService;
|
import com.mcwl.communityCenter.service.AIService;
|
||||||
import com.mcwl.communityCenter.service.CustomerService;
|
import com.mcwl.communityCenter.service.HumanService;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.socket.CloseStatus;
|
import org.springframework.web.socket.CloseStatus;
|
||||||
|
@ -18,7 +18,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@ServerEndpoint("/chat")
|
@ServerEndpoint("/chat")
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class ChatWebSocketPoint extends AbstractWebSocketHandler {
|
public class ChatWebSocket extends AbstractWebSocketHandler {
|
||||||
private final Map<String, Boolean> userModes = new ConcurrentHashMap<>();
|
private final Map<String, Boolean> userModes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
// 存储会话与订阅的映射关系
|
// 存储会话与订阅的映射关系
|
||||||
|
@ -27,38 +27,32 @@ public class ChatWebSocketPoint extends AbstractWebSocketHandler {
|
||||||
@Autowired
|
@Autowired
|
||||||
private AIService aiService;
|
private AIService aiService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private CustomerService customerService;
|
|
||||||
|
|
||||||
// 构造函数注入服务...
|
// 构造函数注入服务...
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||||
String userId = session.getId();
|
String userId = session.getId();
|
||||||
String userMessage = message.getPayload();
|
String userMessage = message.getPayload();
|
||||||
// 判断当前模式
|
|
||||||
if (userModes.getOrDefault(userId, false)) {
|
|
||||||
// 人工模式
|
|
||||||
customerService.handleCustomerMessage(userId, userMessage);
|
|
||||||
} else {
|
|
||||||
// AI 流式响应模式
|
|
||||||
Flux<String> responseStream = aiService.getDeepSeekResponseStream(userMessage);
|
|
||||||
|
|
||||||
// 订阅响应流并存储 Disposable
|
// AI 流式响应模式
|
||||||
Disposable disposable = responseStream
|
Flux<String> responseStream = aiService.getDeepSeekResponseStream(userMessage);
|
||||||
.doOnNext(chunk -> sendText(session, chunk))
|
|
||||||
.doOnComplete(() -> sendText(session, "[END]"))
|
// 订阅响应流并存储 Disposable
|
||||||
.doOnError(e -> sendText(session, "[ERROR] " + e.getMessage()))
|
Disposable disposable = responseStream
|
||||||
.subscribe();
|
.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
|
@Override
|
||||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||||
super.afterConnectionEstablished(session);
|
super.afterConnectionEstablished(session);
|
||||||
userModes.put(session.getId(), false);
|
// userModes.put(session.getId(), false);
|
||||||
|
session.sendMessage(new TextMessage("[AI] 您好,请问有什么问题?"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -66,15 +60,14 @@ public class ChatWebSocketPoint extends AbstractWebSocketHandler {
|
||||||
// 清理订阅资源
|
// 清理订阅资源
|
||||||
String sessionId = session.getId();
|
String sessionId = session.getId();
|
||||||
Disposable disposable = sessionSubscriptions.remove(sessionId);
|
Disposable disposable = sessionSubscriptions.remove(sessionId);
|
||||||
if (disposable != null && !disposable.isDisposed()) {
|
if (disposable != null && disposable.isDisposed()) {
|
||||||
disposable.dispose();
|
disposable.dispose();
|
||||||
System.out.println("已清理会话资源: " + sessionId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加模式切换方法(根据业务需求)
|
// 添加模式切换方法(根据业务需求)
|
||||||
public void switchUserMode(String sessionId, boolean isHumanMode) {
|
public void switchUserMode(String sessionId) {
|
||||||
userModes.put(sessionId, isHumanMode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 线程安全的发送方法
|
// 线程安全的发送方法
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue