一、實現(xiàn)目標(biāo)
- 1、在線聊天,客服聊天,聊天室
- 2、業(yè)務(wù)數(shù)據(jù)實時展示,自動更新
二、實踐步驟
以下是為了實現(xiàn):業(yè)務(wù)數(shù)據(jù)實時展示,自動更新
1. Maven引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2. 創(chuàng)建配置文件
WebSocketConfig
/**
* 開啟WebSocket支持
* @author zhengkai
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3. 創(chuàng)建服務(wù)端
@ServerEndpoint(value = "/websocket")
@Component
public class WebSocketServer {
private final Logger log = LoggerFactory.getLogger(this.getClass());
//concurrent包的線程安全Set,用來存放每個客戶端對應(yīng)的MyWebSocket對象。
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
//與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
private Session session;
/**
* 連接建立成功調(diào)用的方法
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this);
sendMessage("連接成功");
}
/**
* 連接關(guān)閉調(diào)用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this);
log.info("有一連接關(guān)閉!");
}
/**
* 收到客戶端消息后調(diào)用的方法
*
* @param queryType 客戶端發(fā)送過來的消息
*/
@OnMessage
public void onMessage(String queryType) {
log.info("收到信息:" + queryType);
//群發(fā)消息
for (WebSocketServer item : webSocketSet) {
item.sendMessage("hello");
}
}
/**
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("發(fā)生錯誤");
error.printStackTrace();
}
/**
* 實現(xiàn)服務(wù)器主動推送
*/
public void sendMessage(Object message) {
try {
this.session.getBasicRemote().sendText(JSON.toJSONString(message));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 實現(xiàn)服務(wù)器主動群發(fā)消息
*/
public static void sendInfo(Object message) {
for (WebSocketServer item : webSocketSet) {
item.sendMessage(message);
}
}
}
4. 前端發(fā)起websocket請求
<script>
var socket;
if(typeof(WebSocket) == "undefined") {
console.log("您的瀏覽器不支持WebSocket");
}else{
console.log("您的瀏覽器支持WebSocket");
//實現(xiàn)化WebSocket對象,指定要連接的服務(wù)器地址與端口 建立連接
socket = new WebSocket("ws://localhost:8080/webscoket");
//打開事件
socket.onopen = function() {
console.log("Socket 已打開");
//socket.send("這是來自客戶端的消息" + location.href + new Date());
};
//獲得消息事件
socket.onmessage = function(msg) {
console.log(msg.data);
//發(fā)現(xiàn)消息進(jìn)入 開始處理前端觸發(fā)邏輯
};
//關(guān)閉事件
socket.onclose = function() {
console.log("Socket已關(guān)閉");
};
//發(fā)生了錯誤事件
socket.onerror = function() {
alert("Socket發(fā)生了錯誤");
//此時可以嘗試刷新頁面
}
}
</script>
三、實踐中會遇到的問題
1. 前端發(fā)起websocket請求, 卻無法連接websocket
- 錯誤一:被權(quán)限攔截,例如spring security
解決方式:放開權(quán)限或允許訪問
// spring security
.antMatchers(
"/websocket"
)
.permitAll()
- 錯誤二:請求的websocket地址錯誤
@ServerEndpoint(value = "/websocket")
對應(yīng)的地址是:ws://localhost:8080/webscoket
- 錯誤三:被AOP攔截
@Pointcut("execution(public * com.xxx.xxx.controller..*(..))")
public void webLog() {
}
解決辦法
// 排除攔截
@Pointcut("execution(public * com.xxx.xxx.controller..*(..)) && !execution(public * com.xxx.xxx.controller.xxx*(..))")
public void webLog() {
}
2. 在WebSocketServer中,無法注入Bean,無法使用@Autowired
即你按如下方式直接使用@Autowired,會報錯
@ServerEndpoint(value = "/websocket")
@Component
public class WebSocketServer {
@Autowired
ChartDataService chartDataService;
}
在WebSocket中, 因 SpringBoot+WebSocket 對每個客戶端連接都會創(chuàng)建一個 WebSocketServer(@ServerEndpoint 注解對應(yīng)的) 對象,Bean 注入操作會被直接略過,因而手動注入一個全局變量(即你需要注入的bean)
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
// 在這里注入你需要的bean
@Autowired
public void setMessageService(ChartDataService chartDataService) {
WebSocketServer.chartDataService = chartDataService;
// ...
}
}
使用你注入的bean
@ServerEndpoint(value = "/websocket")
@Component
public class WebSocketServer {
public static ChartDataService chartDataService;
此方式經(jīng)實踐,有效
其他解決方式: https://stackoverflow.com/questions/29306854/serverendpoint-and-autowired
- 1.使用@Inject
@ServerEndpoint("/ws")
public class MyWebSocket {
@Inject
private ObjectMapper objectMapper;
}
- 2.使用SpringConfigurator
@ServerEndpoint(value = "/ws", configurator = SpringConfigurator.class)
- 3.使用@PostConstruct
@PostConstruct
public void init(){
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
經(jīng)過實踐,上述三種方式,對于我而言,均無效
參考鏈接:https://blog.csdn.net/moshowgame/article/details/80275084