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