no message

This commit is contained in:
cb 2025-09-23 18:46:32 +08:00
parent a4a0e8df26
commit 4d49e11023
11 changed files with 918 additions and 70 deletions

View File

@ -1,23 +1,41 @@
package com.ruoyi.web.config; package com.ruoyi.web.config;
import org.springframework.context.annotation.Bean; import com.ruoyi.customer.service.ICustomerServiceService;
import com.ruoyi.web.websocket.CustomerServiceWebSocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter; import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import org.springframework.context.annotation.Bean;
import javax.annotation.PostConstruct;
/** /**
* WebSocket配置 * WebSocket配置类
* 用于注入服务到WebSocket端点
* *
* @author ruoyi * @author ruoyi
* @date 2024-01-01
*/ */
@Configuration @Configuration
public class WebSocketConfig { public class WebSocketConfig {
@Autowired
private ICustomerServiceService customerServiceService;
/** /**
* 注入ServerEndpointExporter * 注入ServerEndpointExporter这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
* 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/ */
@Bean @Bean
public ServerEndpointExporter serverEndpointExporter() { public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter(); return new ServerEndpointExporter();
} }
/**
* 初始化时将服务注入到WebSocket中
*/
@PostConstruct
public void init() {
CustomerServiceWebSocket customerServiceWebSocket = new CustomerServiceWebSocket();
customerServiceWebSocket.setCustomerServiceService(customerServiceService);
}
} }

View File

@ -1,6 +1,8 @@
package com.ruoyi.web.controller.customer; package com.ruoyi.web.controller.customer;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -9,7 +11,9 @@ import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
@ -22,6 +26,8 @@ import com.ruoyi.system.domain.ManualServiceSessions;
import com.ruoyi.customer.service.ICustomerServiceService; import com.ruoyi.customer.service.ICustomerServiceService;
import com.ruoyi.system.domain.ChatHistory; import com.ruoyi.system.domain.ChatHistory;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.web.controller.customer.dto.TransferToManualRequest;
import com.ruoyi.web.websocket.CustomerServiceWebSocket;
/** /**
* 客服系统Controller * 客服系统Controller
@ -79,7 +85,7 @@ public class CustomerServiceController extends BaseController
*/ */
@GetMapping("/sessions/{sessionId}") @GetMapping("/sessions/{sessionId}")
@ResponseBody @ResponseBody
public AjaxResult getSession(@PathVariable Long sessionId) public AjaxResult getSession(@PathVariable String sessionId)
{ {
try { try {
UserSessions session = customerServiceService.selectUserSessionsById(sessionId); UserSessions session = customerServiceService.selectUserSessionsById(sessionId);
@ -95,7 +101,7 @@ public class CustomerServiceController extends BaseController
*/ */
@GetMapping("/messages/{sessionId}") @GetMapping("/messages/{sessionId}")
@ResponseBody @ResponseBody
public AjaxResult getChatHistory(@PathVariable Long sessionId) public AjaxResult getChatHistory(@PathVariable String sessionId)
{ {
try { try {
List<ChatHistory> messages = customerServiceService.getChatHistoryBySessionId(sessionId); List<ChatHistory> messages = customerServiceService.getChatHistoryBySessionId(sessionId);
@ -111,19 +117,40 @@ public class CustomerServiceController extends BaseController
*/ */
@PostMapping("/send") @PostMapping("/send")
@ResponseBody @ResponseBody
public AjaxResult sendMessage(ChatHistory chatHistory) public AjaxResult sendMessage(@RequestParam String sessionId, @RequestParam String content, @RequestParam String senderType) {
{
try { try {
// 设置发送者信息 // 根据sessionId查找对应的用户ID
chatHistory.setSenderId(getUserId()); UserSessions userSession = customerServiceService.selectUserSessionsById(sessionId);
chatHistory.setSenderType("SERVICE"); if (userSession == null) {
return AjaxResult.error("会话不存在");
int result = customerServiceService.sendMessage(chatHistory);
if (result > 0) {
return AjaxResult.success("消息发送成功");
} else {
return AjaxResult.error("消息发送失败");
} }
// 创建聊天记录
ChatHistory chatHistory = new ChatHistory();
chatHistory.setSessionId(sessionId);
chatHistory.setContent(content);
chatHistory.setUserId(userSession.getUserId().toString()); // 设置为客户的userId
chatHistory.setSenderId(getUserId()); // 发送者ID为当前登录的客服ID
chatHistory.setSenderType("SERVICE"); // 发送者类型为客服
chatHistory.setMessageType("service"); // 消息类型为客服消息
chatHistory.setCreateTime(new java.util.Date());
// 保存聊天记录
customerServiceService.insertChatHistory(chatHistory);
// 通过WebSocket发送消息给客户端
// 创建JSON格式的WebSocket消息
com.alibaba.fastjson.JSONObject wsMessage = new com.alibaba.fastjson.JSONObject();
wsMessage.put("type", "message");
wsMessage.put("content", content);
// 发送JSON格式的WebSocket消息给客户端
CustomerServiceWebSocket.sendInfo(wsMessage.toJSONString(), userSession.getUserId().toString());
logger.info("[发送消息] WebSocket消息已发送 - userId: {}, sessionId: {}, content: {}, messageFormat: JSON",
userSession.getUserId(), sessionId, content);
return AjaxResult.success();
} catch (Exception e) { } catch (Exception e) {
logger.error("发送消息失败", e); logger.error("发送消息失败", e);
return AjaxResult.error("发送消息失败: " + e.getMessage()); return AjaxResult.error("发送消息失败: " + e.getMessage());
@ -135,40 +162,103 @@ public class CustomerServiceController extends BaseController
*/ */
@PostMapping("/transfer") @PostMapping("/transfer")
@ResponseBody @ResponseBody
public AjaxResult transferToManual(Long sessionId, String reason, HttpServletRequest request) public AjaxResult transferToManual(@RequestBody TransferToManualRequest request, HttpServletRequest httpRequest)
{ {
logger.info("[转人工服务] 收到转人工请求 - sessionId: {}, reason: {}", sessionId, reason); logger.info("[转人工服务] 收到转人工请求 - 原始请求: {}", request);
logger.info("[转人工服务] 请求头信息 - Authorization: {}, Content-Type: {}", logger.info("[转人工服务] 请求头信息 - Authorization: {}, Content-Type: {}",
request.getHeader("Authorization"), request.getHeader("Content-Type")); httpRequest.getHeader("Authorization"), httpRequest.getHeader("Content-Type"));
// 解析参数
Long userId = request.getUserIdAsLong();
String reason = request.getReasonAsString();
logger.info("[转人工服务] 解析后参数 - userId: {}, reason: {}", userId, reason);
logger.info("[转人工服务] 请求参数验证 - userId是否为空: {}, reason是否为空: {}",
userId == null, reason == null || reason.trim().isEmpty());
// 参数验证
if (userId == null) {
logger.error("[转人工服务] 参数验证失败 - userId为空或格式错误原始值: {}", request.getUserId());
return AjaxResult.error("用户ID不能为空或格式错误");
}
if (reason == null || reason.trim().isEmpty()) {
logger.error("[转人工服务] 参数验证失败 - reason为空");
return AjaxResult.error("转人工原因不能为空");
}
try { try {
// 获取当前用户ID // 生成或查找SessionId
// Long currentUserId = getUserId(); String sessionId = generateOrFindSessionId(userId);
// logger.info("[转人工服务] 当前用户ID: {}", currentUserId); logger.info("[转人工服务] SessionId生成结果 - userId: {}, sessionId: {}", userId, sessionId);
// 防重复提交检查是否已有待处理的转人工记录
List<ManualServiceSessions> existingRequests = customerServiceService.getPendingManualRequestsByUserId(userId);
if (existingRequests != null && !existingRequests.isEmpty()) {
logger.warn("[转人工服务] 该用户已有待处理的转人工记录 - userId: {}, 数量: {}", userId, existingRequests.size());
return AjaxResult.error("您已提交转人工请求,请等待客服处理");
}
ManualServiceSessions manualSession = new ManualServiceSessions(); ManualServiceSessions manualSession = new ManualServiceSessions();
manualSession.setUserId(userId);
// 设置会话ID为字符串类型的UUID
manualSession.setSessionId(sessionId); manualSession.setSessionId(sessionId);
manualSession.setReason(reason); manualSession.setReason(reason.trim());
manualSession.setStatus("0"); // 0-待处理 manualSession.setStatus("0"); // 0-待处理
manualSession.setCreateBy(sessionId+""); manualSession.setRequestTime(new java.util.Date());
manualSession.setCreateBy(userId.toString());
logger.info("[转人工服务] 准备插入数据库 - 转人工会话对象: {}", manualSession); logger.info("[转人工服务] 准备插入数据库 - 转人工会话对象: userId={}, sessionId={}, reason={}, status={}",
manualSession.getUserId(), manualSession.getSessionId(),
manualSession.getReason(), manualSession.getStatus());
int result = customerServiceService.createManualServiceRequest(manualSession); int result = customerServiceService.createManualServiceRequest(manualSession);
logger.info("[转人工服务] 数据库插入结果: {}", result); logger.info("[转人工服务] 数据库插入结果: {}", result);
if (result > 0) { if (result > 0) {
logger.info("[转人工服务] 转人工请求提交成功"); logger.info("[转人工服务] 转人工请求提交成功 - userId: {}, sessionId: {}", userId, sessionId);
return AjaxResult.success("转人工请求已提交"); // 返回sessionId给前端
Map<String, Object> responseData = new HashMap<>();
responseData.put("sessionId", sessionId);
responseData.put("message", "转人工请求已提交,请等待客服接听");
return AjaxResult.success(responseData);
} else { } else {
logger.error("[转人工服务] 转人工请求提交失败 - 数据库插入返回0"); logger.error("[转人工服务] 转人工请求提交失败 - 数据库插入返回0");
return AjaxResult.error("转人工请求提交失败"); return AjaxResult.error("转人工请求提交失败");
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("[转人工服务] 转人工服务异常", e); logger.error("[转人工服务] 转人工服务异常 - userId: {}, reason: {}", userId, reason, e);
return AjaxResult.error("转人工服务失败: " + e.getMessage()); return AjaxResult.error("转人工服务失败: " + e.getMessage());
} }
} }
/**
* 生成或查找SessionId
* 方案1在转人工接口中按需生成SessionId
*/
private String generateOrFindSessionId(Long userId) {
try {
// 1. 先查找用户是否有活跃的会话
String existingSessionId = customerServiceService.findActiveSessionByUserId(userId);
if (existingSessionId != null && !existingSessionId.trim().isEmpty()) {
logger.info("[SessionId生成] 找到用户活跃会话 - userId: {}, sessionId: {}", userId, existingSessionId);
return existingSessionId;
}
// 2. 如果没有活跃会话生成新的SessionId
String newSessionId = java.util.UUID.randomUUID().toString().replace("-", "");
logger.info("[SessionId生成] 生成新的SessionId - userId: {}, newSessionId: {}", userId, newSessionId);
// 3. 可选将新的SessionId保存到用户会话表中
// customerServiceService.createUserSession(userId, newSessionId);
return newSessionId;
} catch (Exception e) {
logger.error("[SessionId生成] 生成SessionId异常 - userId: {}", userId, e);
// 异常情况下返回基于userId的默认SessionId
return "session_" + userId + "_" + System.currentTimeMillis();
}
}
/** /**
* 获取转人工请求列表 * 获取转人工请求列表
@ -186,6 +276,31 @@ public class CustomerServiceController extends BaseController
} }
} }
/**
* 获取单个转人工请求详情
*/
@GetMapping("/manual-requests/{requestId}")
@ResponseBody
public AjaxResult getManualRequestById(@PathVariable Long requestId)
{
try {
logger.info("[获取转人工请求详情] 开始处理请求 - requestId: {}", requestId);
ManualServiceSessions request = customerServiceService.getManualRequestById(requestId);
if (request == null) {
logger.error("[获取转人工请求详情] 未找到转人工记录 - requestId: {}", requestId);
return AjaxResult.error("未找到转人工记录");
}
logger.info("[获取转人工请求详情] 成功获取记录 - userId: {}, sessionId: {}, status: {}",
request.getUserId(), request.getSessionId(), request.getStatus());
return AjaxResult.success(request);
} catch (Exception e) {
logger.error("[获取转人工请求详情] 获取转人工请求详情失败 - requestId: {}", requestId, e);
return AjaxResult.error("获取转人工请求详情失败: " + e.getMessage());
}
}
/** /**
* 接受转人工请求 * 接受转人工请求
*/ */
@ -194,20 +309,80 @@ public class CustomerServiceController extends BaseController
public AjaxResult acceptManualRequest(@PathVariable Long requestId) public AjaxResult acceptManualRequest(@PathVariable Long requestId)
{ {
try { try {
logger.info("[接受转人工] 开始处理请求 - requestId: {}, serviceId: {}", requestId, getUserId());
// 先获取原始记录
ManualServiceSessions originalRequest = customerServiceService.getManualRequestById(requestId);
if (originalRequest == null) {
logger.error("[接受转人工] 未找到转人工记录 - requestId: {}", requestId);
return AjaxResult.error("未找到转人工记录");
}
logger.info("[接受转人工] 原始记录 - userId: {}, sessionId: {}, status: {}",
originalRequest.getUserId(), originalRequest.getSessionId(), originalRequest.getStatus());
// 检查状态是否为待处理
if (!"0".equals(originalRequest.getStatus())) {
logger.warn("[接受转人工] 记录状态不是待处理 - status: {}", originalRequest.getStatus());
return AjaxResult.error("该请求已被处理");
}
// 更新记录状态
ManualServiceSessions manualSession = new ManualServiceSessions(); ManualServiceSessions manualSession = new ManualServiceSessions();
manualSession.setManualSessionId(requestId); // 设置主键ID manualSession.setManualSessionId(requestId); // 设置主键ID
manualSession.setStatus("1"); // 1-已接受 manualSession.setStatus("1"); // 1-已接受
manualSession.setServiceId(getUserId()); manualSession.setServiceId(getUserId());
manualSession.setAcceptTime(new java.util.Date());
manualSession.setUpdateBy(requestId+""); manualSession.setUpdateBy(requestId+"");
int result = customerServiceService.updateManualServiceRequest(manualSession); int result = customerServiceService.updateManualServiceRequest(manualSession);
logger.info("[接受转人工] 数据库更新结果: {}", result);
if (result > 0) { if (result > 0) {
// 创建或更新UserSessions记录
try {
String sessionId = originalRequest.getSessionId();
boolean sessionExists = customerServiceService.isSessionIdExists(sessionId);
UserSessions userSession = new UserSessions();
userSession.setSessionId(sessionId);
userSession.setUserId(originalRequest.getUserId());
userSession.setSessionStart(new java.util.Date());
userSession.setStatus("active");
userSession.setLastActivity(new java.util.Date());
userSession.setSessionToken(sessionId);
int sessionResult;
if (sessionExists) {
// 如果会话已存在则更新记录
sessionResult = customerServiceService.updateUserSessions(userSession);
logger.info("[接受转人工] 更新UserSessions记录结果: {}", sessionResult);
} else {
// 如果会话不存在则插入新记录
sessionResult = customerServiceService.insertUserSessions(userSession);
logger.info("[接受转人工] 创建UserSessions记录结果: {}", sessionResult);
}
} catch (Exception e) {
logger.error("[接受转人工] 处理UserSessions记录失败", e);
}
// 通知用户转人工请求被接受
if (originalRequest.getUserId() != null) {
logger.info("[接受转人工] 发送用户通知 - userId: {}", originalRequest.getUserId());
customerServiceService.notifyUserManualRequestAccepted(
originalRequest.getUserId(), requestId, getUserId());
} else {
logger.warn("[接受转人工] 原始记录中userId为空无法发送用户通知");
}
logger.info("[接受转人工] 处理完成 - 成功接受转人工请求");
return AjaxResult.success("已接受转人工请求"); return AjaxResult.success("已接受转人工请求");
} else { } else {
logger.error("[接受转人工] 数据库更新失败");
return AjaxResult.error("操作失败"); return AjaxResult.error("操作失败");
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("接受转人工请求失败", e); logger.error("[接受转人工] 处理异常", e);
return AjaxResult.error("操作失败: " + e.getMessage()); return AjaxResult.error("操作失败: " + e.getMessage());
} }
} }
@ -243,17 +418,21 @@ public class CustomerServiceController extends BaseController
*/ */
@PostMapping("/status") @PostMapping("/status")
@ResponseBody @ResponseBody
public AjaxResult updateServiceStatus(String status) public AjaxResult updateServiceStatus(@RequestParam String status)
{ {
try { try {
logger.info("[更新客服状态] 收到请求参数 - serviceId: {}, status: {}", getUserId(), status);
int result = customerServiceService.updateServiceStatus(getUserId(), status); int result = customerServiceService.updateServiceStatus(getUserId(), status);
if (result > 0) { if (result > 0) {
logger.info("[更新客服状态] 状态更新成功");
return AjaxResult.success("状态更新成功"); return AjaxResult.success("状态更新成功");
} else { } else {
logger.error("[更新客服状态] 状态更新失败 - 数据库返回0");
return AjaxResult.error("状态更新失败"); return AjaxResult.error("状态更新失败");
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("更新客服状态失败", e); logger.error("[更新客服状态] 更新客服状态异常", e);
return AjaxResult.error("状态更新失败: " + e.getMessage()); return AjaxResult.error("状态更新失败: " + e.getMessage());
} }
} }
@ -265,11 +444,45 @@ public class CustomerServiceController extends BaseController
@Log(title = "客服会话", businessType = BusinessType.EXPORT) @Log(title = "客服会话", businessType = BusinessType.EXPORT)
@PostMapping("/export") @PostMapping("/export")
@ResponseBody @ResponseBody
public AjaxResult export(UserSessions userSessions) public AjaxResult export(@RequestBody UserSessions userSessions)
{ {
List<UserSessions> list = customerServiceService.selectUserSessionsList(userSessions); try {
ExcelUtil<UserSessions> util = new ExcelUtil<UserSessions>(UserSessions.class); logger.info("[导出会话] 收到导出请求 - 查询条件: {}", userSessions);
return util.exportExcel(list, "会话数据");
List<UserSessions> list = customerServiceService.selectUserSessionsList(userSessions);
logger.info("[导出会话] 查询到数据条数: {}", list != null ? list.size() : 0);
ExcelUtil<UserSessions> util = new ExcelUtil<UserSessions>(UserSessions.class);
AjaxResult result = util.exportExcel(list, "会话数据");
logger.info("[导出会话] 导出完成");
return result;
} catch (Exception e) {
logger.error("[导出会话] 导出会话列表异常", e);
return AjaxResult.error("导出失败: " + e.getMessage());
}
}
/**
* 获取当前登录用户信息
*/
@GetMapping("/current-user")
@ResponseBody
public AjaxResult getCurrentUser()
{
try {
Long userId = getUserId();
String loginName = getLoginName();
AjaxResult result = AjaxResult.success();
result.put("userId", userId);
result.put("loginName", loginName);
return result;
} catch (Exception e) {
logger.error("获取当前用户信息失败", e);
return AjaxResult.error("获取当前用户信息失败");
}
} }
/** /**

View File

@ -0,0 +1,101 @@
package com.ruoyi.web.controller.customer.dto;
import java.util.List;
/**
* 转人工服务请求DTO
*
* @author ruoyi
*/
public class TransferToManualRequest
{
/** 用户ID */
private String userId;
/** 转人工原因 */
private Object reason;
public TransferToManualRequest()
{
}
public TransferToManualRequest(String userId, Object reason)
{
this.userId = userId;
this.reason = reason;
}
public String getUserId()
{
return userId;
}
public void setUserId(String userId)
{
this.userId = userId;
}
public Object getReason()
{
return reason;
}
public void setReason(Object reason)
{
this.reason = reason;
}
/**
* 获取转换后的用户IDLong类型
*/
public Long getUserIdAsLong()
{
if (userId == null || userId.trim().isEmpty())
{
return null;
}
try
{
return Long.parseLong(userId.trim());
}
catch (NumberFormatException e)
{
return null;
}
}
/**
* 获取转换后的原因String类型
*/
public String getReasonAsString()
{
if (reason == null)
{
return null;
}
if (reason instanceof String)
{
return (String) reason;
}
else if (reason instanceof List)
{
// 如果是消息列表转换为字符串描述
List<?> messageList = (List<?>) reason;
return "用户最近" + messageList.size() + "条消息记录";
}
else
{
return reason.toString();
}
}
@Override
public String toString()
{
return "TransferToManualRequest{" +
"userId='" + userId + '\'' +
", reason=" + reason +
'}';
}
}

View File

@ -14,9 +14,12 @@ import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.ruoyi.customer.service.ICustomerServiceService;
import com.ruoyi.system.domain.ManualServiceSessions;
/** /**
* 客服系统WebSocket服务 * 客服系统WebSocket服务
@ -29,6 +32,9 @@ public class CustomerServiceWebSocket {
private static final Logger log = LoggerFactory.getLogger(CustomerServiceWebSocket.class); private static final Logger log = LoggerFactory.getLogger(CustomerServiceWebSocket.class);
/** 客服服务 - 使用静态变量以便在WebSocket中使用 */
private static ICustomerServiceService customerServiceService;
/** 静态变量,用来记录当前在线连接数 */ /** 静态变量,用来记录当前在线连接数 */
private static final AtomicInteger onlineCount = new AtomicInteger(0); private static final AtomicInteger onlineCount = new AtomicInteger(0);
@ -41,6 +47,14 @@ public class CustomerServiceWebSocket {
/** 接收userId */ /** 接收userId */
private String userId = ""; private String userId = "";
/**
* 注入客服服务
*/
@Autowired
public void setCustomerServiceService(ICustomerServiceService customerServiceService) {
CustomerServiceWebSocket.customerServiceService = customerServiceService;
}
/** /**
* 连接建立成功调用的方法 * 连接建立成功调用的方法
*/ */
@ -74,6 +88,20 @@ public class CustomerServiceWebSocket {
webSocketMap.remove(userId); webSocketMap.remove(userId);
subOnlineCount(); subOnlineCount();
} }
// 清理该用户的待处理转人工记录
try {
if (customerServiceService != null && userId != null && !userId.isEmpty()) {
Long userIdLong = Long.valueOf(userId);
int cleanedCount = customerServiceService.cleanupPendingManualRequests(userIdLong);
if (cleanedCount > 0) {
log.info("WebSocket断开已清理用户{}的{}条待处理转人工记录", userId, cleanedCount);
}
}
} catch (Exception e) {
log.error("清理用户{}的转人工记录失败", userId, e);
}
log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount()); log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
} }
@ -94,9 +122,62 @@ public class CustomerServiceWebSocket {
String messageContent = jsonObject.getString("message"); String messageContent = jsonObject.getString("message");
String messageType = jsonObject.getString("type"); String messageType = jsonObject.getString("type");
// 传输给对应toUserId用户的websocket // 处理消息路由
if (toUserId != null && webSocketMap.containsKey(toUserId)) { if ("service".equals(toUserId)) {
// 当toUserId为'service'根据发送者userId查找对应的客服人员
log.info("收到发送给客服的消息发送者userId: {}", this.userId);
// 检查customerServiceService是否为null
if (customerServiceService == null) {
log.error("customerServiceService为null无法处理消息路由");
return;
}
log.info("customerServiceService检查通过开始处理消息路由");
try {
Long senderUserId = Long.valueOf(this.userId);
log.info("准备查询用户{}的已接受转人工会话", senderUserId);
// 查找该用户对应的已接受状态的转人工记录
ManualServiceSessions acceptedSession = customerServiceService.getAcceptedManualSessionByUserId(senderUserId);
log.info("查询已接受转人工会话完成,结果: {}", acceptedSession != null ? "找到会话" : "未找到会话");
if (acceptedSession != null) {
log.info("已接受会话详情 - manualSessionId: {}, sessionId: {}, userId: {}, serviceId: {}, status: {}",
acceptedSession.getManualSessionId(),
acceptedSession.getSessionId(),
acceptedSession.getUserId(),
acceptedSession.getServiceId(),
acceptedSession.getStatus());
if (acceptedSession.getServiceId() != null) {
String serviceUserId = acceptedSession.getServiceId().toString();
log.info("目标客服ID: {}", serviceUserId);
// 检查对应的客服是否在线
log.info("当前在线用户列表: {}", webSocketMap.keySet());
if (webSocketMap.containsKey(serviceUserId)) {
log.info("客服{}在线,准备转发消息", serviceUserId);
webSocketMap.get(serviceUserId).sendMessage(JSON.toJSONString(jsonObject));
log.info("消息已成功转发给对应的客服人员: serviceId={}", serviceUserId);
} else {
log.warn("对应的客服人员不在线: serviceId={}, 当前在线用户: {}", serviceUserId, webSocketMap.keySet());
}
} else {
log.warn("已接受会话的serviceId为null");
}
} else {
log.warn("未找到用户{}对应的已接受状态的转人工记录", this.userId);
}
} catch (NumberFormatException e) {
log.error("用户ID格式错误: {}", this.userId, e);
} catch (Exception e) {
log.error("查找对应客服人员失败,异常详情: ", e);
}
} else if (toUserId != null && webSocketMap.containsKey(toUserId)) {
// 正常的点对点消息转发
webSocketMap.get(toUserId).sendMessage(JSON.toJSONString(jsonObject)); webSocketMap.get(toUserId).sendMessage(JSON.toJSONString(jsonObject));
log.info("消息已转发给指定用户: {}", toUserId);
} else { } else {
log.error("请求的userId:" + toUserId + "不在该服务器上"); log.error("请求的userId:" + toUserId + "不在该服务器上");
} }
@ -126,11 +207,33 @@ public class CustomerServiceWebSocket {
* 发送自定义消息 * 发送自定义消息
*/ */
public static void sendInfo(String message, @PathParam("userId") String userId) throws IOException { public static void sendInfo(String message, @PathParam("userId") String userId) throws IOException {
log.info("发送消息到:" + userId + ",报文:" + message); log.info("[WebSocket调试] 开始发送消息到用户: {}, 报文: {}", userId, message);
// 打印当前在线用户列表和数量
log.info("[WebSocket调试] 当前在线用户数量: {}", webSocketMap.size());
log.info("[WebSocket调试] 当前在线用户列表: {}", webSocketMap.keySet());
// 检查目标用户是否在线
if (userId != null && webSocketMap.containsKey(userId)) { if (userId != null && webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessage(message); CustomerServiceWebSocket targetWebSocket = webSocketMap.get(userId);
// 检查WebSocket连接状态
if (targetWebSocket != null && targetWebSocket.session != null) {
log.info("[WebSocket调试] 用户{}的WebSocket连接状态: isOpen={}, id={}",
userId, targetWebSocket.session.isOpen(), targetWebSocket.session.getId());
try {
targetWebSocket.sendMessage(message);
log.info("[WebSocket调试] 消息发送成功到用户: {}", userId);
} catch (IOException e) {
log.error("[WebSocket调试] 消息发送失败到用户: {}, 错误: {}", userId, e.getMessage());
throw e;
}
} else {
log.error("[WebSocket调试] 用户{}的WebSocket连接为空或session为空", userId);
}
} else { } else {
log.error("用户" + userId + ",不在线!"); log.error("[WebSocket调试] 用户{}不在线!在线用户列表: {}", userId, webSocketMap.keySet());
} }
} }
@ -161,4 +264,20 @@ public class CustomerServiceWebSocket {
public static ConcurrentHashMap<String, CustomerServiceWebSocket> getWebSocketMap() { public static ConcurrentHashMap<String, CustomerServiceWebSocket> getWebSocketMap() {
return webSocketMap; return webSocketMap;
} }
/**
* 发送消息给指定用户
*/
public static void sendMessageToUser(String userId, String message) {
try {
if (userId != null && webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessage(message);
log.info("已发送消息给用户: userId={}", userId);
} else {
log.warn("用户{}不在线,无法发送消息", userId);
}
} catch (Exception e) {
log.error("发送消息给用户{}失败", userId, e);
}
}
} }

View File

@ -229,12 +229,12 @@
<div class="chat-input"> <div class="chat-input">
<div class="input-toolbar"> <div class="input-toolbar">
<button type="button" class="btn btn-sm btn-default" onclick="insertEmoji('😊')"> <!-- <button type="button" class="btn btn-sm btn-default" onclick="insertEmoji('😊')">
<i class="fa fa-smile-o"></i> 表情 <i class="fa fa-smile-o"></i> 表情
</button> </button>
<button type="button" class="btn btn-sm btn-default" onclick="uploadFile()"> <button type="button" class="btn btn-sm btn-default" onclick="uploadFile()">
<i class="fa fa-paperclip"></i> 文件 <i class="fa fa-paperclip"></i> 文件
</button> </button> -->
<!-- <button type="button" class="btn btn-sm btn-warning pull-right" onclick="transferToManual()"> <!-- <button type="button" class="btn btn-sm btn-warning pull-right" onclick="transferToManual()">
<i class="fa fa-user"></i> 转人工 <i class="fa fa-user"></i> 转人工
</button> --> </button> -->
@ -271,7 +271,16 @@
var wsConnection = null; var wsConnection = null;
$(document).ready(function() { $(document).ready(function() {
initWebSocket(); // 获取当前客服ID并建立WebSocket连接
getCurrentUserId(function(serviceId) {
if (serviceId) {
console.log('[DEBUG] 页面加载时获取到客服ID:', serviceId);
initWebSocket(serviceId);
} else {
console.error('[ERROR] 无法获取当前客服IDWebSocket连接失败');
}
});
loadSessionList(); loadSessionList();
loadManualRequests(); loadManualRequests();
@ -283,22 +292,76 @@
}); });
// 初始化WebSocket连接 // 初始化WebSocket连接
function initWebSocket() { function initWebSocket(serviceId) {
console.log("[DEBUG] 开始初始化WebSocket连接..."); console.log("[DEBUG] 开始初始化WebSocket连接客服ID:", serviceId);
if (typeof(WebSocket) == "undefined") { if (typeof(WebSocket) == "undefined") {
console.error("[ERROR] 您的浏览器不支持WebSocket"); console.error("[ERROR] 您的浏览器不支持WebSocket");
return; return;
} }
var wsUrl = "ws://bwy.opsoul.com/websocket/customer-service"; if (serviceId) {
createWebSocketConnection(serviceId);
} else {
console.error("[ERROR] 客服ID为空无法建立WebSocket连接");
}
}
// 创建WebSocket连接
function createWebSocketConnection(userId) {
// 根据当前页面协议动态选择WebSocket协议
var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
var wsUrl = protocol + "//bwy.opsoul.com/websocket/customer/" + userId;
console.log("[DEBUG] WebSocket连接URL:", wsUrl); console.log("[DEBUG] WebSocket连接URL:", wsUrl);
console.log("[DEBUG] 当前页面协议:", window.location.protocol, "选择WebSocket协议:", protocol);
try { try {
// 如果已有连接,先关闭
if (wsConnection && wsConnection.readyState !== WebSocket.CLOSED) {
wsConnection.close();
}
wsConnection = new WebSocket(wsUrl); wsConnection = new WebSocket(wsUrl);
console.log("[DEBUG] WebSocket对象创建成功"); console.log("[DEBUG] WebSocket对象创建成功");
} catch (e) { } catch (e) {
console.error("[ERROR] WebSocket对象创建失败:", e); console.error("[ERROR] WebSocket对象创建失败:", e);
// 添加更详细的错误信息
if (e.name === 'SecurityError') {
console.error("[ERROR] 安全错误: HTTPS页面无法连接到不安全的WebSocket端点");
console.error("[ERROR] 请确保服务器支持WSS连接或在HTTP环境下访问页面");
}
return;
}
setupWebSocketHandlers();
}
// 获取当前用户ID
function getCurrentUserId(callback) {
$.ajax({
url: ctx + "customer/service/current-user",
type: "GET",
dataType: "json",
success: function(response) {
if (response.code === 0 && response && response.userId) {
console.log("[DEBUG] 获取到当前用户ID:", response.userId);
callback(response.userId);
} else {
console.error("[ERROR] 获取用户ID失败:", response);
callback(null);
}
},
error: function(xhr, status, error) {
console.error("[ERROR] 获取用户ID请求失败:", error);
callback(null);
}
});
}
// 设置WebSocket事件处理器
function setupWebSocketHandlers() {
if (!wsConnection) {
console.error("[ERROR] WebSocket连接对象不存在");
return; return;
} }
@ -372,6 +435,40 @@
console.log('[DEBUG] 使用alert显示通知'); console.log('[DEBUG] 使用alert显示通知');
} }
break; break;
case 'MANUAL_ACCEPTED':
// 处理转人工请求被接受的通知
console.log('[SUCCESS] 转人工请求已被接受:', message);
// 刷新转人工请求列表
loadManualRequests();
// 显示成功通知
if (typeof layer !== 'undefined') {
layer.msg('您的转人工请求已被客服接受,即将为您服务');
} else {
alert('您的转人工请求已被客服接受,即将为您服务');
}
// 如果有会话ID可以切换到对应会话
if (message.sessionId && message.sessionId != currentSessionId) {
selectSession(message.sessionId);
}
break;
case 'message':
// 处理APP端发送的普通消息
console.log('[DEBUG] 处理message类型消息:', message);
console.log('[DEBUG] 消息内容:', message.message);
console.log('[DEBUG] 发送者ID:', message.fromUserId);
console.log('[DEBUG] 会话ID:', message.sessionId);
// 如果消息属于当前会话,添加到聊天区域
if (message.sessionId && message.sessionId == currentSessionId) {
appendMessage(message.message, true, message.timestamp);
console.log('[DEBUG] message类型消息已添加到当前会话');
} else {
console.log('[DEBUG] message类型消息不属于当前会话sessionId:', message.sessionId, '当前会话:', currentSessionId);
}
// 刷新会话列表以更新最新消息
loadSessionList();
break;
default: default:
console.warn('[WARN] 未知的消息类型:', message.type, '完整消息:', message); console.warn('[WARN] 未知的消息类型:', message.type, '完整消息:', message);
break; break;
@ -390,7 +487,7 @@
var avatar = session.customerName ? session.customerName.charAt(0).toUpperCase() : 'U'; var avatar = session.customerName ? session.customerName.charAt(0).toUpperCase() : 'U';
var activeClass = session.sessionId == currentSessionId ? 'active' : ''; var activeClass = session.sessionId == currentSessionId ? 'active' : '';
html += '<div class="session-item ' + activeClass + '" onclick="selectSession(' + session.sessionId + ')">'; html += '<div class="session-item ' + activeClass + '" onclick="selectSession(\'' + session.sessionId + '\')">';
html += ' <div style="display: flex; align-items: center;">'; html += ' <div style="display: flex; align-items: center;">';
html += ' <div class="session-avatar">' + avatar + '</div>'; html += ' <div class="session-avatar">' + avatar + '</div>';
html += ' <div class="session-info">'; html += ' <div class="session-info">';
@ -412,8 +509,11 @@
function selectSession(sessionId) { function selectSession(sessionId) {
currentSessionId = sessionId; currentSessionId = sessionId;
$('.session-item').removeClass('active'); $('.session-item').removeClass('active');
$('[onclick="selectSession(' + sessionId + ')"]').addClass('active'); $('[onclick="selectSession(\'' + sessionId + '\')"]').addClass('active');
console.log('[DEBUG] 选择会话:', sessionId, '不重新建立WebSocket连接');
// 直接加载聊天历史和更新会话信息不重新建立WebSocket连接
loadChatHistory(sessionId); loadChatHistory(sessionId);
updateCurrentSessionInfo(sessionId); updateCurrentSessionInfo(sessionId);
} }
@ -457,6 +557,28 @@
return; return;
} }
// 检查WebSocket连接状态
if (!wsConnection || wsConnection.readyState !== WebSocket.OPEN) {
layer.confirm('WebSocket连接已断开消息可能无法实时推送给客户。是否继续发送', {
btn: ['继续发送', '重新连接']
}, function(index) {
// 继续发送
doSendMessage(content);
layer.close(index);
}, function(index) {
// 重新连接
initWebSocket();
layer.close(index);
layer.msg('正在重新连接WebSocket...');
});
return;
}
doSendMessage(content);
}
// 执行发送消息
function doSendMessage(content) {
var messageData = { var messageData = {
sessionId: currentSessionId, sessionId: currentSessionId,
content: content, content: content,
@ -471,6 +593,8 @@
} else { } else {
layer.msg('发送失败: ' + data.msg); layer.msg('发送失败: ' + data.msg);
} }
}).fail(function() {
layer.msg('网络错误,消息发送失败');
}); });
} }
@ -579,6 +703,41 @@
$.post('/customer/service/manual-requests/' + requestId + '/accept', function(data) { $.post('/customer/service/manual-requests/' + requestId + '/accept', function(data) {
if (data.code === 0) { if (data.code === 0) {
layer.msg('已接受转人工请求'); layer.msg('已接受转人工请求');
// 获取转人工请求的详细信息包含sessionId
console.log('[DEBUG] 开始获取转人工请求详情 - requestId:', requestId);
$.get('/customer/service/manual-requests/' + requestId)
.done(function(requestData) {
console.log('[DEBUG] 获取转人工请求详情成功:', requestData);
if (requestData.code === 0 && requestData.data) {
var sessionId = requestData.data.sessionId;
if (sessionId) {
console.log('[DEBUG] 获取到sessionId:', sessionId);
// 直接切换到对应的会话不重新建立WebSocket连接
console.log('[DEBUG] 接受转人工请求,切换到会话:', sessionId);
selectSession(sessionId);
} else {
console.error('[ERROR] 转人工请求数据中缺少sessionId:', requestData.data);
layer.msg('转人工请求数据异常缺少会话ID');
}
} else {
console.error('[ERROR] 转人工请求数据格式错误:', requestData);
layer.msg('获取转人工请求详情失败:' + (requestData.msg || '数据格式错误'));
}
})
.fail(function(xhr, status, error) {
console.error('[ERROR] 获取转人工请求详情失败:', {
requestId: requestId,
status: xhr.status,
statusText: xhr.statusText,
error: error,
response: xhr.responseText
});
layer.msg('获取转人工请求详情失败,请检查网络连接或联系管理员');
});
loadManualRequests(); loadManualRequests();
loadSessionList(); loadSessionList();
} else { } else {

View File

@ -19,7 +19,7 @@ public interface ICustomerServiceService
* @param sessionId 用户会话ID * @param sessionId 用户会话ID
* @return 用户会话 * @return 用户会话
*/ */
public UserSessions selectUserSessionsById(Long sessionId); public UserSessions selectUserSessionsById(String sessionId);
/** /**
* 查询用户会话列表 * 查询用户会话列表
@ -52,13 +52,21 @@ public interface ICustomerServiceService
*/ */
public int updateUserSessions(UserSessions userSessions); public int updateUserSessions(UserSessions userSessions);
/**
* 检查会话ID是否已存在
*
* @param sessionId 会话ID
* @return 是否存在
*/
public boolean isSessionIdExists(String sessionId);
/** /**
* 批量删除用户会话 * 批量删除用户会话
* *
* @param sessionIds 需要删除的用户会话ID * @param sessionIds 需要删除的用户会话ID
* @return 结果 * @return 结果
*/ */
public int deleteUserSessionsByIds(Long[] sessionIds); public int deleteUserSessionsByIds(String[] sessionIds);
/** /**
* 删除用户会话信息 * 删除用户会话信息
@ -66,7 +74,7 @@ public interface ICustomerServiceService
* @param sessionId 用户会话ID * @param sessionId 用户会话ID
* @return 结果 * @return 结果
*/ */
public int deleteUserSessionsById(Long sessionId); public int deleteUserSessionsById(String sessionId);
/** /**
* 根据会话ID获取聊天记录 * 根据会话ID获取聊天记录
@ -74,7 +82,7 @@ public interface ICustomerServiceService
* @param sessionId 会话ID * @param sessionId 会话ID
* @return 聊天记录列表 * @return 聊天记录列表
*/ */
public List<ChatHistory> getChatHistoryBySessionId(Long sessionId); public List<ChatHistory> getChatHistoryBySessionId(String sessionId);
/** /**
* 发送消息 * 发送消息
@ -239,4 +247,61 @@ public interface ICustomerServiceService
* @return 结果 * @return 结果
*/ */
public int deleteManualServiceSessionsById(Long id); public int deleteManualServiceSessionsById(Long id);
/**
* 清理用户的待处理转人工记录
*
* @param userId 用户ID
* @return 清理数量
*/
public int cleanupPendingManualRequests(Long userId);
/**
* 根据会话ID获取待处理的转人工记录
*
* @param sessionId 会话ID
* @return 转人工记录列表
*/
public List<ManualServiceSessions> getPendingManualRequestsBySession(String sessionId);
/**
* 根据用户ID获取待处理的转人工记录
*
* @param userId 用户ID
* @return 转人工记录列表
*/
public List<ManualServiceSessions> getPendingManualRequestsByUserId(Long userId);
/**
* 根据用户ID查找活跃的会话ID
*
* @param userId 用户ID
* @return 活跃的会话ID如果没有则返回null
*/
public String findActiveSessionByUserId(Long userId);
/**
* 根据ID获取转人工记录
*
* @param requestId 记录ID
* @return 转人工记录
*/
public ManualServiceSessions getManualRequestById(Long requestId);
/**
* 通知用户转人工请求被接受
*
* @param userId 用户ID
* @param requestId 请求ID
* @param serviceId 客服ID
*/
public void notifyUserManualRequestAccepted(Long userId, Long requestId, Long serviceId);
/**
* 根据用户ID获取已接受状态的转人工记录
*
* @param userId 用户ID
* @return 已接受的转人工记录如果没有则返回null
*/
public ManualServiceSessions getAcceptedManualSessionByUserId(Long userId);
} }

View File

@ -39,7 +39,7 @@ public class CustomerServiceServiceImpl implements ICustomerServiceService
* @return 用户会话 * @return 用户会话
*/ */
@Override @Override
public UserSessions selectUserSessionsById(Long sessionId) public UserSessions selectUserSessionsById(String sessionId)
{ {
return userSessionsMapper.selectUserSessionsById(sessionId); return userSessionsMapper.selectUserSessionsById(sessionId);
} }
@ -95,6 +95,19 @@ public class CustomerServiceServiceImpl implements ICustomerServiceService
return userSessionsMapper.updateUserSessions(userSessions); return userSessionsMapper.updateUserSessions(userSessions);
} }
/**
* 检查会话ID是否已存在
*
* @param sessionId 会话ID
* @return 是否存在
*/
@Override
public boolean isSessionIdExists(String sessionId)
{
UserSessions userSession = userSessionsMapper.selectUserSessionsById(sessionId);
return userSession != null;
}
/** /**
* 删除用户会话对象 * 删除用户会话对象
* *
@ -102,7 +115,7 @@ public class CustomerServiceServiceImpl implements ICustomerServiceService
* @return 结果 * @return 结果
*/ */
@Override @Override
public int deleteUserSessionsByIds(Long[] sessionIds) public int deleteUserSessionsByIds(String[] sessionIds)
{ {
return userSessionsMapper.deleteUserSessionsByIds(sessionIds); return userSessionsMapper.deleteUserSessionsByIds(sessionIds);
} }
@ -114,7 +127,7 @@ public class CustomerServiceServiceImpl implements ICustomerServiceService
* @return 结果 * @return 结果
*/ */
@Override @Override
public int deleteUserSessionsById(Long sessionId) public int deleteUserSessionsById(String sessionId)
{ {
return userSessionsMapper.deleteUserSessionsById(sessionId); return userSessionsMapper.deleteUserSessionsById(sessionId);
} }
@ -126,7 +139,7 @@ public class CustomerServiceServiceImpl implements ICustomerServiceService
* @return 聊天记录列表 * @return 聊天记录列表
*/ */
@Override @Override
public List<ChatHistory> getChatHistoryBySessionId(Long sessionId) public List<ChatHistory> getChatHistoryBySessionId(String sessionId)
{ {
ChatHistory chatHistory = new ChatHistory(); ChatHistory chatHistory = new ChatHistory();
chatHistory.setSessionId(sessionId); chatHistory.setSessionId(sessionId);
@ -450,4 +463,162 @@ public class CustomerServiceServiceImpl implements ICustomerServiceService
{ {
return manualServiceSessionsMapper.deleteManualServiceSessionsById(id); return manualServiceSessionsMapper.deleteManualServiceSessionsById(id);
} }
/**
* 清理用户的待处理转人工记录
*
* @param userId 用户ID
* @return 清理数量
*/
@Override
public int cleanupPendingManualRequests(Long userId)
{
ManualServiceSessions condition = new ManualServiceSessions();
condition.setUserId(userId);
condition.setStatus("0"); // 待处理状态
List<ManualServiceSessions> pendingRequests = manualServiceSessionsMapper.selectManualServiceSessionsList(condition);
if (pendingRequests != null && !pendingRequests.isEmpty()) {
System.out.println("[DEBUG] 清理用户 " + userId + " 的待处理转人工记录,数量: " + pendingRequests.size());
// 将状态更新为已关闭
for (ManualServiceSessions request : pendingRequests) {
request.setStatus("3"); // 3-已关闭WebSocket断开
request.setEndTime(DateUtils.getNowDate());
request.setUpdateTime(DateUtils.getNowDate());
manualServiceSessionsMapper.updateManualServiceSessions(request);
}
return pendingRequests.size();
}
return 0;
}
/**
* 根据会话ID获取待处理的转人工记录
*
* @param sessionId 会话ID
* @return 转人工记录列表
*/
@Override
public List<ManualServiceSessions> getPendingManualRequestsBySession(String sessionId)
{
ManualServiceSessions condition = new ManualServiceSessions();
condition.setSessionId(sessionId);
condition.setStatus("0"); // 待处理状态
return manualServiceSessionsMapper.selectManualServiceSessionsList(condition);
}
/**
* 根据ID获取转人工记录
*
* @param requestId 记录ID
* @return 转人工记录
*/
@Override
public ManualServiceSessions getManualRequestById(Long requestId)
{
return manualServiceSessionsMapper.selectManualServiceSessionsById(requestId);
}
/**
* 根据用户ID获取待处理的转人工记录
*
* @param userId 用户ID
* @return 转人工记录列表
*/
@Override
public List<ManualServiceSessions> getPendingManualRequestsByUserId(Long userId)
{
ManualServiceSessions condition = new ManualServiceSessions();
condition.setUserId(userId);
condition.setStatus("0"); // 待处理状态
return manualServiceSessionsMapper.selectManualServiceSessionsList(condition);
}
/**
* 根据用户ID查找活跃的会话ID
*
* @param userId 用户ID
* @return 活跃的会话ID如果没有则返回null
*/
@Override
public String findActiveSessionByUserId(Long userId)
{
// 查找该用户最近的活跃会话状态为0-待处理或1-已接受
ManualServiceSessions condition = new ManualServiceSessions();
condition.setUserId(userId);
List<ManualServiceSessions> sessions = manualServiceSessionsMapper.selectManualServiceSessionsList(condition);
if (sessions != null && !sessions.isEmpty()) {
// 按创建时间倒序排列获取最新的活跃会话
for (ManualServiceSessions session : sessions) {
if ("0".equals(session.getStatus()) || "1".equals(session.getStatus())) {
return session.getSessionId();
}
}
}
return null;
}
/**
* 根据用户ID获取已接受状态的转人工记录
*
* @param userId 用户ID
* @return 已接受的转人工记录如果没有则返回null
*/
@Override
public ManualServiceSessions getAcceptedManualSessionByUserId(Long userId)
{
ManualServiceSessions condition = new ManualServiceSessions();
condition.setUserId(userId);
condition.setStatus("1"); // 1-已接受状态
List<ManualServiceSessions> sessions = manualServiceSessionsMapper.selectManualServiceSessionsList(condition);
if (sessions != null && !sessions.isEmpty()) {
// 返回最新的已接受会话
return sessions.get(0);
}
return null;
}
/**
* 通知用户转人工请求被接受
*
* @param userId 用户ID
* @param requestId 请求ID
* @param serviceId 客服ID
*/
@Override
public void notifyUserManualRequestAccepted(Long userId, Long requestId, Long serviceId)
{
try {
// 创建WebSocket消息
com.alibaba.fastjson.JSONObject message = new com.alibaba.fastjson.JSONObject();
message.put("type", "MANUAL_ACCEPTED");
message.put("requestId", requestId);
message.put("serviceId", serviceId);
message.put("message", "您的转人工请求已被客服接受,即将为您服务");
message.put("timestamp", System.currentTimeMillis());
System.out.println("[DEBUG] 通知用户转人工请求被接受 - userId: " + userId + ", message: " + message.toJSONString());
// 通过反射调用WebSocket发送消息方法
Class<?> webSocketClass = Class.forName("com.ruoyi.web.websocket.CustomerServiceWebSocket");
java.lang.reflect.Method sendMessageToUserMethod = webSocketClass.getMethod("sendMessageToUser", String.class, String.class);
sendMessageToUserMethod.invoke(null, userId.toString(), message.toJSONString());
System.out.println("[DEBUG] 用户通知发送成功");
} catch (Exception e) {
System.err.println("[DEBUG] 发送用户通知失败: " + e.getMessage());
e.printStackTrace();
}
}
} }

View File

@ -22,7 +22,7 @@ public class ManualServiceSessions extends BaseEntity
/** 原会话ID */ /** 原会话ID */
@Excel(name = "原会话ID") @Excel(name = "原会话ID")
private Long sessionId; private String sessionId;
/** 用户ID */ /** 用户ID */
@Excel(name = "用户ID") @Excel(name = "用户ID")
@ -81,12 +81,12 @@ public class ManualServiceSessions extends BaseEntity
{ {
return manualSessionId; return manualSessionId;
} }
public void setSessionId(Long sessionId) public void setSessionId(String sessionId)
{ {
this.sessionId = sessionId; this.sessionId = sessionId;
} }
public Long getSessionId() public String getSessionId()
{ {
return sessionId; return sessionId;
} }

View File

@ -18,7 +18,7 @@ public class UserSessions extends BaseEntity
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 会话ID */ /** 会话ID */
private Long sessionId; private String sessionId;
/** 用户ID */ /** 用户ID */
@Excel(name = "用户ID") @Excel(name = "用户ID")
@ -47,12 +47,12 @@ public class UserSessions extends BaseEntity
@Excel(name = "会话令牌") @Excel(name = "会话令牌")
private String sessionToken; private String sessionToken;
public void setSessionId(Long sessionId) public void setSessionId(String sessionId)
{ {
this.sessionId = sessionId; this.sessionId = sessionId;
} }
public Long getSessionId() public String getSessionId()
{ {
return sessionId; return sessionId;
} }

View File

@ -17,7 +17,7 @@ public interface UserSessionsMapper
* @param sessionId 用户会话ID * @param sessionId 用户会话ID
* @return 用户会话 * @return 用户会话
*/ */
public UserSessions selectUserSessionsById(Long sessionId); public UserSessions selectUserSessionsById(String sessionId);
/** /**
* 查询用户会话列表 * 查询用户会话列表
@ -49,7 +49,7 @@ public interface UserSessionsMapper
* @param sessionId 用户会话ID * @param sessionId 用户会话ID
* @return 结果 * @return 结果
*/ */
public int deleteUserSessionsById(Long sessionId); public int deleteUserSessionsById(String sessionId);
/** /**
* 批量删除用户会话 * 批量删除用户会话
@ -57,5 +57,5 @@ public interface UserSessionsMapper
* @param sessionIds 需要删除的数据ID * @param sessionIds 需要删除的数据ID
* @return 结果 * @return 结果
*/ */
public int deleteUserSessionsByIds(Long[] sessionIds); public int deleteUserSessionsByIds(String[] sessionIds);
} }

View File

@ -29,14 +29,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</where> </where>
</select> </select>
<select id="selectUserSessionsById" parameterType="Long" resultMap="UserSessionsResult"> <select id="selectUserSessionsById" parameterType="String" resultMap="UserSessionsResult">
<include refid="selectUserSessionsVo"/> <include refid="selectUserSessionsVo"/>
where session_id = #{sessionId} where session_id = #{sessionId}
</select> </select>
<insert id="insertUserSessions" parameterType="UserSessions" useGeneratedKeys="true" keyProperty="sessionId"> <insert id="insertUserSessions" parameterType="UserSessions">
insert into user_sessions insert into user_sessions
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
<if test="sessionId != null">session_id,</if>
<if test="userId != null">user_id,</if> <if test="userId != null">user_id,</if>
<if test="sessionStart != null">session_start,</if> <if test="sessionStart != null">session_start,</if>
<if test="sessionEnd != null">session_end,</if> <if test="sessionEnd != null">session_end,</if>
@ -47,6 +48,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateTime != null">update_time,</if> <if test="updateTime != null">update_time,</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="sessionId != null">#{sessionId},</if>
<if test="userId != null">#{userId},</if> <if test="userId != null">#{userId},</if>
<if test="sessionStart != null">#{sessionStart},</if> <if test="sessionStart != null">#{sessionStart},</if>
<if test="sessionEnd != null">#{sessionEnd},</if> <if test="sessionEnd != null">#{sessionEnd},</if>
@ -73,7 +75,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where session_id = #{sessionId} where session_id = #{sessionId}
</update> </update>
<delete id="deleteUserSessionsById" parameterType="Long"> <delete id="deleteUserSessionsById" parameterType="String">
delete from user_sessions where session_id = #{sessionId} delete from user_sessions where session_id = #{sessionId}
</delete> </delete>