no message

This commit is contained in:
cb 2025-09-23 12:22:32 +08:00
parent a13c5a0ff1
commit a4a0e8df26
8 changed files with 296 additions and 61 deletions

View File

@ -1,6 +1,7 @@
package com.ruoyi.web.controller.customer;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@ -134,23 +135,37 @@ public class CustomerServiceController extends BaseController
*/
@PostMapping("/transfer")
@ResponseBody
public AjaxResult transferToManual(Long sessionId, String reason)
public AjaxResult transferToManual(Long sessionId, String reason, HttpServletRequest request)
{
logger.info("[转人工服务] 收到转人工请求 - sessionId: {}, reason: {}", sessionId, reason);
logger.info("[转人工服务] 请求头信息 - Authorization: {}, Content-Type: {}",
request.getHeader("Authorization"), request.getHeader("Content-Type"));
try {
// 获取当前用户ID
// Long currentUserId = getUserId();
// logger.info("[转人工服务] 当前用户ID: {}", currentUserId);
ManualServiceSessions manualSession = new ManualServiceSessions();
manualSession.setSessionId(sessionId);
manualSession.setReason(reason);
manualSession.setStatus("PENDING");
manualSession.setCreateBy(getLoginName());
manualSession.setStatus("0"); // 0-待处理
manualSession.setCreateBy(sessionId+"");
logger.info("[转人工服务] 准备插入数据库 - 转人工会话对象: {}", manualSession);
int result = customerServiceService.createManualServiceRequest(manualSession);
logger.info("[转人工服务] 数据库插入结果: {}", result);
if (result > 0) {
logger.info("[转人工服务] 转人工请求提交成功");
return AjaxResult.success("转人工请求已提交");
} else {
logger.error("[转人工服务] 转人工请求提交失败 - 数据库插入返回0");
return AjaxResult.error("转人工请求提交失败");
}
} catch (Exception e) {
logger.error("转人工服务失败", e);
logger.error("[转人工服务] 转人工服务异常", e);
return AjaxResult.error("转人工服务失败: " + e.getMessage());
}
}
@ -180,10 +195,10 @@ public class CustomerServiceController extends BaseController
{
try {
ManualServiceSessions manualSession = new ManualServiceSessions();
manualSession.setUserId(requestId);
manualSession.setStatus("ACCEPTED");
manualSession.setManualSessionId(requestId); // 设置主键ID
manualSession.setStatus("1"); // 1-已接受
manualSession.setServiceId(getUserId());
manualSession.setUpdateBy(getLoginName());
manualSession.setUpdateBy(requestId+"");
int result = customerServiceService.updateManualServiceRequest(manualSession);
if (result > 0) {
@ -206,10 +221,10 @@ public class CustomerServiceController extends BaseController
{
try {
ManualServiceSessions manualSession = new ManualServiceSessions();
manualSession.setUserId(requestId);
manualSession.setStatus("REJECTED");
manualSession.setManualSessionId(requestId); // 设置主键ID
manualSession.setStatus("2"); // 2-已拒绝
manualSession.setServiceId(getUserId());
manualSession.setUpdateBy(getLoginName());
manualSession.setUpdateBy(requestId+"");
int result = customerServiceService.updateManualServiceRequest(manualSession);
if (result > 0) {

View File

@ -284,50 +284,100 @@
// 初始化WebSocket连接
function initWebSocket() {
console.log("[DEBUG] 开始初始化WebSocket连接...");
if (typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
console.error("[ERROR] 您的浏览器不支持WebSocket");
return;
}
var wsUrl = "ws://" + window.location.host + "/websocket/customer-service";
wsConnection = new WebSocket(wsUrl);
var wsUrl = "ws://bwy.opsoul.com/websocket/customer-service";
console.log("[DEBUG] WebSocket连接URL:", wsUrl);
try {
wsConnection = new WebSocket(wsUrl);
console.log("[DEBUG] WebSocket对象创建成功");
} catch (e) {
console.error("[ERROR] WebSocket对象创建失败:", e);
return;
}
wsConnection.onopen = function() {
console.log("WebSocket连接已建立");
console.log("[SUCCESS] WebSocket连接已建立");
console.log("[DEBUG] WebSocket readyState:", wsConnection.readyState);
};
wsConnection.onmessage = function(event) {
var message = JSON.parse(event.data);
handleWebSocketMessage(message);
console.log("[DEBUG] 收到WebSocket原始消息:", event.data);
try {
var message = JSON.parse(event.data);
console.log("[DEBUG] 解析后的消息对象:", message);
handleWebSocketMessage(message);
} catch (e) {
console.error("[ERROR] 解析WebSocket消息失败:", e, "原始数据:", event.data);
}
};
wsConnection.onclose = function() {
console.log("WebSocket连接已关闭");
wsConnection.onclose = function(event) {
console.log("[WARN] WebSocket连接已关闭", {
code: event.code,
reason: event.reason,
wasClean: event.wasClean
});
// 尝试重连
console.log("[DEBUG] 5秒后尝试重连WebSocket...");
setTimeout(initWebSocket, 5000);
};
wsConnection.onerror = function(error) {
console.log("WebSocket连接错误:", error);
console.error("[ERROR] WebSocket连接错误:", error);
console.log("[DEBUG] WebSocket readyState:", wsConnection ? wsConnection.readyState : 'undefined');
};
}
// 处理WebSocket消息
function handleWebSocketMessage(message) {
console.log('[DEBUG] 开始处理WebSocket消息:', message);
console.log('[DEBUG] 消息类型:', message.type);
switch(message.type) {
case 'NEW_MESSAGE':
// 处理新消息
console.log('[DEBUG] 处理新消息:', message);
if (message.sessionId == currentSessionId) {
appendMessage(message.content, false, message.timestamp);
console.log('[DEBUG] 新消息已添加到当前会话');
} else {
console.log('[DEBUG] 新消息不属于当前会话sessionId:', message.sessionId, '当前会话:', currentSessionId);
}
loadSessionList(); // 刷新会话列表
break;
case 'SESSION_UPDATE':
// 处理会话更新
console.log('[DEBUG] 处理会话更新:', message);
loadSessionList();
break;
case 'MANUAL_REQUEST':
// 处理转人工请求
console.log('[SUCCESS] 收到转人工请求通知:', message);
console.log('[DEBUG] 开始刷新转人工请求列表...');
loadManualRequests();
// 显示通知
console.log('[DEBUG] 显示转人工请求通知...');
if (typeof $.modal !== 'undefined') {
$.modal.msg('收到新的转人工请求');
console.log('[DEBUG] 使用$.modal显示通知');
} else {
alert('收到新的转人工请求');
console.log('[DEBUG] 使用alert显示通知');
}
break;
default:
console.warn('[WARN] 未知的消息类型:', message.type, '完整消息:', message);
break;
}
console.log('[DEBUG] WebSocket消息处理完成');
}
// 加载会话列表
@ -474,28 +524,54 @@
// 加载转人工请求列表
function loadManualRequests() {
$.get('/customer/service/manual-requests', function(data) {
var html = '';
if (data.code === 0 && data.data) {
data.data.forEach(function(request) {
html += '<div class="manual-request-item">';
html += ' <div class="request-user">' + (request.customerName || '访客' + request.sessionId) + '</div>';
html += ' <div class="request-reason">' + (request.reason || '无') + '</div>';
html += ' <div class="request-time">' + formatTime(request.createTime) + '</div>';
html += ' <div class="request-actions">';
html += ' <button class="btn btn-success btn-sm" onclick="acceptManualRequest(' + request.id + ')">';
html += ' <i class="fa fa-check"></i> 接受';
html += ' </button>';
html += ' <button class="btn btn-default btn-sm" onclick="rejectManualRequest(' + request.id + ')">';
html += ' <i class="fa fa-times"></i> 拒绝';
html += ' </button>';
html += ' </div>';
html += '</div>';
console.log('开始加载转人工请求列表...');
$.get('/customer/service/manual-requests')
.done(function(data) {
console.log('转人工请求API响应:', data);
if (data.code === 0) {
var html = '';
var requests = data.data || [];
console.log('转人工请求数量:', requests.length);
if (requests.length === 0) {
html = '<div class="empty-state">暂无转人工请求</div>';
} else {
requests.forEach(function(request, index) {
console.log('处理转人工请求 ' + (index + 1) + ':', request);
html += '<div class="manual-request-item" data-id="' + request.manualSessionId + '">';
html += ' <div class="request-info">';
html += ' <div class="session-id">会话ID: ' + request.sessionId + '</div>';
html += ' <div class="reason">原因: ' + (request.reason || '无') + '</div>';
html += ' <div class="time">' + formatTime(request.createTime) + '</div>';
html += ' </div>';
html += ' <div class="request-actions">';
html += ' <button class="btn btn-success btn-sm" onclick="acceptManualRequest(' + request.manualSessionId + ')">接受</button>';
html += ' <button class="btn btn-danger btn-sm" onclick="rejectManualRequest(' + request.manualSessionId + ')">拒绝</button>';
html += ' </div>';
html += '</div>';
});
}
$('#manualRequestContainer').html(html);
$('#manualRequestCount').text(requests.length);
console.log('转人工请求列表渲染完成,容器内容:', $('#manualRequestContainer').html());
} else {
console.error('转人工请求API返回错误:', data.msg);
$('#manualRequestContainer').html('<div class="error-state">加载失败: ' + data.msg + '</div>');
}
})
.fail(function(xhr, status, error) {
console.error('转人工请求API调用失败:', {
status: xhr.status,
statusText: xhr.statusText,
error: error,
response: xhr.responseText
});
$('#manualRequestCount').text(data.data.length);
}
$('#manualRequestContainer').html(html);
});
$('#manualRequestContainer').html('<div class="error-state">网络错误,请稍后重试</div>');
});
}
// 接受转人工请求

View File

@ -306,8 +306,13 @@ public class ShiroConfig
filterChainDefinitionMap.put("/system/material/app/**", "anon");
// 客服回复接口
filterChainDefinitionMap.put("/system/customerServiceReply/app/**", "anon");
filterChainDefinitionMap.put("/system/chatHistory/app/**", "anon");
// 聊天记录接口
filterChainDefinitionMap.put("/system/chatHistory/**", "anon");
// 客服相关接口
filterChainDefinitionMap.put("/customer/service/**", "anon");
// WebSocket连接
filterChainDefinitionMap.put("/websocket/**", "anon");
// 退出 logout地址shiro去清除session
filterChainDefinitionMap.put("/logout", "logout");

View File

@ -155,9 +155,68 @@ public class CustomerServiceServiceImpl implements ICustomerServiceService
@Override
public int createManualServiceRequest(ManualServiceSessions manualServiceSessions)
{
System.out.println("[DEBUG] 开始创建转人工服务请求 - sessionId: " + manualServiceSessions.getSessionId() + ", reason: " + manualServiceSessions.getReason());
manualServiceSessions.setCreateTime(DateUtils.getNowDate());
manualServiceSessions.setStatus("pending");
return manualServiceSessionsMapper.insertManualServiceSessions(manualServiceSessions);
manualServiceSessions.setStatus("0"); // 0-待处理
System.out.println("[DEBUG] 准备插入数据库 - 数据: " + manualServiceSessions.toString());
int result = manualServiceSessionsMapper.insertManualServiceSessions(manualServiceSessions);
System.out.println("[DEBUG] 数据库插入结果: " + result);
// 如果插入成功通知所有在线客服
if (result > 0) {
try {
System.out.println("[DEBUG] 数据库插入成功开始发送WebSocket通知");
// 创建WebSocket消息
com.alibaba.fastjson.JSONObject message = new com.alibaba.fastjson.JSONObject();
message.put("type", "MANUAL_REQUEST");
message.put("sessionId", manualServiceSessions.getSessionId());
message.put("reason", manualServiceSessions.getReason());
message.put("timestamp", System.currentTimeMillis());
System.out.println("[DEBUG] WebSocket消息内容: " + message.toJSONString());
// 通过反射获取WebSocket类并发送广播消息
Class<?> webSocketClass = Class.forName("com.ruoyi.web.websocket.CustomerServiceWebSocket");
java.lang.reflect.Method getWebSocketMapMethod = webSocketClass.getMethod("getWebSocketMap");
@SuppressWarnings("unchecked")
java.util.concurrent.ConcurrentHashMap<String, Object> webSocketMap =
(java.util.concurrent.ConcurrentHashMap<String, Object>) getWebSocketMapMethod.invoke(null);
System.out.println("[DEBUG] 当前在线WebSocket连接数: " + webSocketMap.size());
// 向所有在线客服发送通知
int successCount = 0;
int failCount = 0;
for (String key : webSocketMap.keySet()) {
Object webSocket = webSocketMap.get(key);
try {
System.out.println("[DEBUG] 向WebSocket连接发送消息: " + key);
java.lang.reflect.Method sendMessageMethod = webSocket.getClass().getMethod("sendMessage", String.class);
sendMessageMethod.invoke(webSocket, message.toJSONString());
successCount++;
System.out.println("[DEBUG] 消息发送成功: " + key);
} catch (Exception e) {
failCount++;
System.err.println("[DEBUG] 向WebSocket连接发送消息失败: " + key + ", 错误: " + e.getMessage());
}
}
System.out.println("[DEBUG] WebSocket通知发送完成 - 成功: " + successCount + ", 失败: " + failCount);
} catch (Exception e) {
// 记录日志但不影响主要业务流程
System.err.println("[DEBUG] 发送WebSocket通知失败: " + e.getMessage());
e.printStackTrace();
}
} else {
System.err.println("[DEBUG] 数据库插入失败不发送WebSocket通知");
}
System.out.println("[DEBUG] 创建转人工服务请求完成 - 返回结果: " + result);
return result;
}
/**
@ -182,7 +241,7 @@ public class CustomerServiceServiceImpl implements ICustomerServiceService
public List<ManualServiceSessions> getPendingManualRequests()
{
ManualServiceSessions manualServiceSessions = new ManualServiceSessions();
manualServiceSessions.setStatus("pending");
manualServiceSessions.setStatus("0"); // 0-待处理
return manualServiceSessionsMapper.selectManualServiceSessionsList(manualServiceSessions);
}

View File

@ -32,6 +32,15 @@ public class ManualServiceSessions extends BaseEntity
@Excel(name = "客服ID")
private Long serviceId;
/** 客服人员 */
@Excel(name = "客服人员")
private String serviceStaff;
/** 开始时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "开始时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
/** 转人工请求时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "转人工请求时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
@ -99,6 +108,24 @@ public class ManualServiceSessions extends BaseEntity
{
return serviceId;
}
public void setServiceStaff(String serviceStaff)
{
this.serviceStaff = serviceStaff;
}
public String getServiceStaff()
{
return serviceStaff;
}
public void setStartTime(Date startTime)
{
this.startTime = startTime;
}
public Date getStartTime()
{
return startTime;
}
public void setRequestTime(Date requestTime)
{
this.requestTime = requestTime;
@ -170,6 +197,8 @@ public class ManualServiceSessions extends BaseEntity
.append("sessionId", getSessionId())
.append("userId", getUserId())
.append("serviceId", getServiceId())
.append("serviceStaff", getServiceStaff())
.append("startTime", getStartTime())
.append("requestTime", getRequestTime())
.append("acceptTime", getAcceptTime())
.append("endTime", getEndTime())

View File

@ -43,6 +43,10 @@ public class UserSessions extends BaseEntity
@Excel(name = "最后活动时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date lastActivity;
/** 会话令牌 */
@Excel(name = "会话令牌")
private String sessionToken;
public void setSessionId(Long sessionId)
{
this.sessionId = sessionId;
@ -98,6 +102,16 @@ public class UserSessions extends BaseEntity
return lastActivity;
}
public void setSessionToken(String sessionToken)
{
this.sessionToken = sessionToken;
}
public String getSessionToken()
{
return sessionToken;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@ -107,6 +121,7 @@ public class UserSessions extends BaseEntity
.append("sessionEnd", getSessionEnd())
.append("status", getStatus())
.append("lastActivity", getLastActivity())
.append("sessionToken", getSessionToken())
.append("createTime", getCreateTime())
.append("updateTime", getUpdateTime())
.toString();

View File

@ -5,19 +5,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<mapper namespace="com.ruoyi.system.mapper.ManualServiceSessionsMapper">
<resultMap type="ManualServiceSessions" id="ManualServiceSessionsResult">
<result property="id" column="id" />
<result property="manualSessionId" column="id" />
<result property="sessionId" column="session_id" />
<result property="userId" column="user_id" />
<result property="serviceId" column="service_id" />
<result property="serviceStaff" column="service_staff" />
<result property="startTime" column="start_time" />
<result property="endTime" column="end_time" />
<result property="status" column="status" />
<result property="requestTime" column="request_time" />
<result property="acceptTime" column="accept_time" />
<result property="reason" column="reason" />
<result property="rating" column="rating" />
<result property="feedback" column="feedback" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
</resultMap>
<sql id="selectManualServiceSessionsVo">
select id, session_id, user_id, service_staff, start_time, end_time, status, create_time, update_time from manual_service_sessions
select id, session_id, user_id, service_id, service_staff, start_time, end_time, status, request_time, accept_time, reason, rating, feedback, create_time, update_time from manual_service_sessions
</sql>
<select id="selectManualServiceSessionsList" parameterType="ManualServiceSessions" resultMap="ManualServiceSessionsResult">
@ -35,25 +41,37 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where id = #{id}
</select>
<insert id="insertManualServiceSessions" parameterType="ManualServiceSessions" useGeneratedKeys="true" keyProperty="id">
<insert id="insertManualServiceSessions" parameterType="ManualServiceSessions" useGeneratedKeys="true" keyProperty="manualSessionId">
insert into manual_service_sessions
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="sessionId != null">session_id,</if>
<if test="userId != null">user_id,</if>
<if test="serviceId != null">service_id,</if>
<if test="serviceStaff != null">service_staff,</if>
<if test="startTime != null">start_time,</if>
<if test="endTime != null">end_time,</if>
<if test="status != null">status,</if>
<if test="requestTime != null">request_time,</if>
<if test="acceptTime != null">accept_time,</if>
<if test="reason != null">reason,</if>
<if test="rating != null">rating,</if>
<if test="feedback != null">feedback,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="sessionId != null">#{sessionId},</if>
<if test="userId != null">#{userId},</if>
<if test="serviceId != null">#{serviceId},</if>
<if test="serviceStaff != null">#{serviceStaff},</if>
<if test="startTime != null">#{startTime},</if>
<if test="endTime != null">#{endTime},</if>
<if test="status != null">#{status},</if>
<if test="requestTime != null">#{requestTime},</if>
<if test="acceptTime != null">#{acceptTime},</if>
<if test="reason != null">#{reason},</if>
<if test="rating != null">#{rating},</if>
<if test="feedback != null">#{feedback},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
@ -64,24 +82,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<trim prefix="SET" suffixOverrides=",">
<if test="sessionId != null">session_id = #{sessionId},</if>
<if test="userId != null">user_id = #{userId},</if>
<if test="serviceId != null">service_id = #{serviceId},</if>
<if test="serviceStaff != null">service_staff = #{serviceStaff},</if>
<if test="startTime != null">start_time = #{startTime},</if>
<if test="endTime != null">end_time = #{endTime},</if>
<if test="status != null">status = #{status},</if>
<if test="requestTime != null">request_time = #{requestTime},</if>
<if test="acceptTime != null">accept_time = #{acceptTime},</if>
<if test="reason != null">reason = #{reason},</if>
<if test="rating != null">rating = #{rating},</if>
<if test="feedback != null">feedback = #{feedback},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
where id = #{id}
where id = #{manualSessionId}
</update>
<delete id="deleteManualServiceSessionsById" parameterType="Long">
delete from manual_service_sessions where id = #{id}
delete from manual_service_sessions where id = #{manualSessionId}
</delete>
<delete id="deleteManualServiceSessionsByIds" parameterType="String">
delete from manual_service_sessions where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
<foreach item="manualSessionId" collection="array" open="(" separator="," close=")">
#{manualSessionId}
</foreach>
</delete>

View File

@ -6,15 +6,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="UserSessions" id="UserSessionsResult">
<result property="sessionId" column="session_id" />
<result property="userId" column="user_id" />
<result property="sessionToken" column="session_token" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="status" column="status" />
<result property="userId" column="user_id" />
<result property="sessionStart" column="session_start" />
<result property="sessionEnd" column="session_end" />
<result property="lastActivity" column="last_activity" />
<result property="sessionToken" column="session_token" />
<result property="status" column="status" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
</resultMap>
<sql id="selectUserSessionsVo">
select session_id, user_id, session_token, create_time, update_time, status from user_sessions
select session_id, user_id, session_start, session_end, last_activity, session_token, status, create_time, update_time from user_sessions
</sql>
<select id="selectUserSessionsList" parameterType="UserSessions" resultMap="UserSessionsResult">
@ -35,17 +38,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
insert into user_sessions
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,</if>
<if test="sessionStart != null">session_start,</if>
<if test="sessionEnd != null">session_end,</if>
<if test="lastActivity != null">last_activity,</if>
<if test="sessionToken != null">session_token,</if>
<if test="status != null">status,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
<if test="status != null">status,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},</if>
<if test="sessionStart != null">#{sessionStart},</if>
<if test="sessionEnd != null">#{sessionEnd},</if>
<if test="lastActivity != null">#{lastActivity},</if>
<if test="sessionToken != null">#{sessionToken},</if>
<if test="status != null">#{status},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="status != null">#{status},</if>
</trim>
</insert>
@ -53,10 +62,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
update user_sessions
<trim prefix="SET" suffixOverrides=",">
<if test="userId != null">user_id = #{userId},</if>
<if test="sessionStart != null">session_start = #{sessionStart},</if>
<if test="sessionEnd != null">session_end = #{sessionEnd},</if>
<if test="lastActivity != null">last_activity = #{lastActivity},</if>
<if test="sessionToken != null">session_token = #{sessionToken},</if>
<if test="status != null">status = #{status},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="status != null">status = #{status},</if>
</trim>
where session_id = #{sessionId}
</update>