feat: ai调整

master
yang 2025-03-04 15:48:45 +08:00
parent 38d47a0bca
commit 33d27fdedc
10 changed files with 133 additions and 53 deletions

View File

@ -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);
}
}

View File

@ -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:

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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(""));// 错误处理
}

View File

@ -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为用户IDvalue为WebSocketSession
private static final Map<String, WebSocketSession> 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<ChatMessage> history) {
}
}

View File

@ -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<String, Boolean> 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<String> 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<String> 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) {
}
// 线程安全的发送方法

View File

@ -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());
}
}
}