2 changed files with 261 additions and 0 deletions
@ -0,0 +1,225 @@ |
|||||
|
package net.lab1024.sa.base.config; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSON; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.web.socket.CloseStatus; |
||||
|
import org.springframework.web.socket.TextMessage; |
||||
|
import org.springframework.web.socket.WebSocketSession; |
||||
|
import org.springframework.web.socket.handler.TextWebSocketHandler; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
|
||||
|
/** |
||||
|
* 电话WebSocket处理器 |
||||
|
* |
||||
|
* @Author SmartFlow |
||||
|
* @Date 2026-06-09 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class PhoneWebSocketHandler extends TextWebSocketHandler { |
||||
|
|
||||
|
/** |
||||
|
* 存储所有在线的WebSocket会话,key为坐席ID,value为会话 |
||||
|
*/ |
||||
|
private static final ConcurrentHashMap<String, WebSocketSession> SESSION_MAP = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
@Override |
||||
|
public void afterConnectionEstablished(WebSocketSession session) throws Exception { |
||||
|
String agentId = getAgentId(session); |
||||
|
if (agentId != null) { |
||||
|
SESSION_MAP.put(agentId, session); |
||||
|
log.info("坐席 {} WebSocket连接已建立,当前在线坐席数: {}", agentId, SESSION_MAP.size()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { |
||||
|
String agentId = getAgentId(session); |
||||
|
if (agentId != null) { |
||||
|
SESSION_MAP.remove(agentId); |
||||
|
log.info("坐席 {} WebSocket连接已关闭,当前在线坐席数: {}", agentId, SESSION_MAP.size()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { |
||||
|
String payload = message.getPayload(); |
||||
|
log.info("收到WebSocket消息: {}", payload); |
||||
|
|
||||
|
// 可以在这里处理客户端发送的消息
|
||||
|
// 例如:坐席状态更新、接听/挂断操作等
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { |
||||
|
log.error("WebSocket传输异常", exception); |
||||
|
String agentId = getAgentId(session); |
||||
|
if (agentId != null) { |
||||
|
SESSION_MAP.remove(agentId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 向指定坐席发送消息 |
||||
|
* |
||||
|
* @param agentId 坐席ID |
||||
|
* @param message 消息内容 |
||||
|
*/ |
||||
|
public void sendMessageToAgent(String agentId, String message) { |
||||
|
WebSocketSession session = SESSION_MAP.get(agentId); |
||||
|
if (session != null && session.isOpen()) { |
||||
|
try { |
||||
|
session.sendMessage(new TextMessage(message)); |
||||
|
log.info("向坐席 {} 发送消息成功", agentId); |
||||
|
} catch (IOException e) { |
||||
|
log.error("向坐席 {} 发送消息失败", agentId, e); |
||||
|
} |
||||
|
} else { |
||||
|
log.warn("坐席 {} 不在线或会话已关闭", agentId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 向所有在线坐席广播消息 |
||||
|
* |
||||
|
* @param message 消息内容 |
||||
|
*/ |
||||
|
public void broadcastToAll(String message) { |
||||
|
SESSION_MAP.forEach((agentId, session) -> { |
||||
|
if (session.isOpen()) { |
||||
|
try { |
||||
|
session.sendMessage(new TextMessage(message)); |
||||
|
} catch (IOException e) { |
||||
|
log.error("向坐席 {} 广播消息失败", agentId, e); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 发送来电弹屏消息 |
||||
|
* |
||||
|
* @param agentId 坐席ID |
||||
|
* @param callerInfo 来电信息 |
||||
|
*/ |
||||
|
public void sendIncomingCall(String agentId, Object callerInfo) { |
||||
|
String message = JSON.toJSONString(new IncomingCallMessage( |
||||
|
"INCOMING_CALL", |
||||
|
callerInfo, |
||||
|
System.currentTimeMillis() |
||||
|
)); |
||||
|
sendMessageToAgent(agentId, message); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 发送挂断消息 |
||||
|
* |
||||
|
* @param agentId 坐席ID |
||||
|
* @param callId 通话ID |
||||
|
*/ |
||||
|
public void sendCallEnded(String agentId, String callId) { |
||||
|
String message = JSON.toJSONString(new CallEndedMessage( |
||||
|
"CALL_ENDED", |
||||
|
callId, |
||||
|
System.currentTimeMillis() |
||||
|
)); |
||||
|
sendMessageToAgent(agentId, message); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 从会话中获取坐席ID |
||||
|
*/ |
||||
|
private String getAgentId(WebSocketSession session) { |
||||
|
String query = session.getUri() != null ? session.getUri().getQuery() : null; |
||||
|
if (query != null) { |
||||
|
String[] params = query.split("&"); |
||||
|
for (String param : params) { |
||||
|
if (param.startsWith("agentId=")) { |
||||
|
return param.substring("agentId=".length()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 来电消息 |
||||
|
*/ |
||||
|
public static class IncomingCallMessage { |
||||
|
private String type; |
||||
|
private Object data; |
||||
|
private long timestamp; |
||||
|
|
||||
|
public IncomingCallMessage(String type, Object data, long timestamp) { |
||||
|
this.type = type; |
||||
|
this.data = data; |
||||
|
this.timestamp = timestamp; |
||||
|
} |
||||
|
|
||||
|
public String getType() { |
||||
|
return type; |
||||
|
} |
||||
|
|
||||
|
public void setType(String type) { |
||||
|
this.type = type; |
||||
|
} |
||||
|
|
||||
|
public Object getData() { |
||||
|
return data; |
||||
|
} |
||||
|
|
||||
|
public void setData(Object data) { |
||||
|
this.data = data; |
||||
|
} |
||||
|
|
||||
|
public long getTimestamp() { |
||||
|
return timestamp; |
||||
|
} |
||||
|
|
||||
|
public void setTimestamp(long timestamp) { |
||||
|
this.timestamp = timestamp; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 通话结束消息 |
||||
|
*/ |
||||
|
public static class CallEndedMessage { |
||||
|
private String type; |
||||
|
private String callId; |
||||
|
private long timestamp; |
||||
|
|
||||
|
public CallEndedMessage(String type, String callId, long timestamp) { |
||||
|
this.type = type; |
||||
|
this.callId = callId; |
||||
|
this.timestamp = timestamp; |
||||
|
} |
||||
|
|
||||
|
public String getType() { |
||||
|
return type; |
||||
|
} |
||||
|
|
||||
|
public void setType(String type) { |
||||
|
this.type = type; |
||||
|
} |
||||
|
|
||||
|
public String getCallId() { |
||||
|
return callId; |
||||
|
} |
||||
|
|
||||
|
public void setCallId(String callId) { |
||||
|
this.callId = callId; |
||||
|
} |
||||
|
|
||||
|
public long getTimestamp() { |
||||
|
return timestamp; |
||||
|
} |
||||
|
|
||||
|
public void setTimestamp(long timestamp) { |
||||
|
this.timestamp = timestamp; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
package net.lab1024.sa.base.config; |
||||
|
|
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.web.socket.config.annotation.EnableWebSocket; |
||||
|
import org.springframework.web.socket.config.annotation.WebSocketConfigurer; |
||||
|
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; |
||||
|
import org.springframework.web.socket.server.standard.ServerEndpointExporter; |
||||
|
|
||||
|
/** |
||||
|
* WebSocket配置类 |
||||
|
* |
||||
|
* @Author SmartFlow |
||||
|
* @Date 2026-06-09 |
||||
|
*/ |
||||
|
@Configuration |
||||
|
@EnableWebSocket |
||||
|
public class WebSocketConfig implements WebSocketConfigurer { |
||||
|
|
||||
|
@Bean |
||||
|
public ServerEndpointExporter serverEndpointExporter() { |
||||
|
return new ServerEndpointExporter(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { |
||||
|
// 注册WebSocket处理器,允许跨域访问
|
||||
|
registry.addHandler(phoneWebSocketHandler(), "/websocket/phone") |
||||
|
.setAllowedOrigins("*"); |
||||
|
} |
||||
|
|
||||
|
@Bean |
||||
|
public PhoneWebSocketHandler phoneWebSocketHandler() { |
||||
|
return new PhoneWebSocketHandler(); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue