最近遇到一個需求,首頁的待辦任務要求實時刷新。剛開始的時候再前端寫了一個定時器輪訓查詢。但是過了一段時間之後覺得太LOW逼了,正好想到了websocket,準備試驗一下,廢話不說,上代碼:
(注:這裏使用maven方式添加 手動添加的同學請自行下載相應jar包放到lib目錄,本文使用的版本爲4.3.5.RELEASE)
<!-- spring websocket-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
在spring配置文件中添加如下代碼
<!-- websocket相關掃描,主要掃描:WebSocketConfig 如果前面配置能掃描到此類則可以不加 -->
<context:component-scan base-package="org.zwc.ssm" />
package com.mlkj.common.websocket;
import com.mlkj.common.config.Global;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* Component註解告訴SpringMVC該類是一個SpringIOC容器下管理的類
* 其實@Controller, @Service, @Repository是@Component的細化
*/
@Component
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
@Autowired
WebSocketHandler handler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
//添加websocket處理器,添加握手攔截器
webSocketHandlerRegistry.addHandler(handler, Global.getAdminPath()+"/ws").addInterceptors(new HandShakeInterceptor());
//添加websocket處理器,添加握手攔截器
webSocketHandlerRegistry.addHandler(handler, Global.getAdminPath()+"/ws/sockjs").addInterceptors(new HandShakeInterceptor()).withSockJS();
}
}
首先定義一個WebSocketConfig 繼承 WebMvcConfigurerAdapter 以及實現 WebSocketConfigurer接口 ,使用註解@EnableWebSocket 表示這是一個webSocket
2 定義 WebSocketHandler
package com.mlkj.common.websocket;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mlkj.common.result.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@Component
public class WebSocketHandler implements org.springframework.web.socket.WebSocketHandler {
//當MyWebSocketHandler類被加載時就會創建該Map,隨類而生
public static final Map<String, WebSocketSession> userSocketSessionMap;
static {
userSocketSessionMap = new HashMap<String, WebSocketSession>();
}
//握手實現連接後
@Override
public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
String uid = (String) webSocketSession.getAttributes().get("uid");
userSocketSessionMap.putIfAbsent(uid, webSocketSession);
}
//發送信息前的處理
@Override
public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
if(webSocketMessage.getPayloadLength()==0)return;
//TODO 得到Socket通道中的數據並轉化爲Message對象
//TODO 將信息保存至數據庫
//發送Socket信息
sendMessageToUser("1", new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(new Result())));
}
@Override
public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
}
/**
* 在此刷新頁面就相當於斷開WebSocket連接,原本在靜態變量userSocketSessionMap中的
* WebSocketSession會變成關閉狀態(close),但是刷新後的第二次連接服務器創建的
* 新WebSocketSession(open狀態)又不會加入到userSocketSessionMap中,所以這樣就無法發送消息
* 因此應當在關閉連接這個切面增加去除userSocketSessionMap中當前處於close狀態的WebSocketSession,
* 讓新創建的WebSocketSession(open狀態)可以加入到userSocketSessionMap中
* @param webSocketSession
* @param closeStatus
* @throws Exception
*/
@Override
public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
System.out.println("WebSocket:"+webSocketSession.getAttributes().get("uid")+"close connection");
Iterator<Map.Entry<String,WebSocketSession>> iterator = userSocketSessionMap.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<String,WebSocketSession> entry = iterator.next();
if(entry.getValue().getAttributes().get("uid")==webSocketSession.getAttributes().get("uid")){
userSocketSessionMap.remove(webSocketSession.getAttributes().get("uid"));
System.out.println("WebSocket in staticMap:" + webSocketSession.getAttributes().get("uid") + "removed");
}
}
}
@Override
public boolean supportsPartialMessages() {
return false;
}
//發送信息的實現
public void sendMessageToUser(String uid, TextMessage message) throws IOException {
WebSocketSession session = userSocketSessionMap.get(uid);
if (session != null && session.isOpen()) {
session.sendMessage(message);
}
}
}
定義 HandShakeInterceptor 握手攔截器
package com.mlkj.common.websocket;
import com.mlkj.common.utils.StringUtils;
import com.mlkj.modules.sys.utils.UserUtils;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import javax.servlet.http.HttpSession;
import java.util.Map;
/**
* websocket握手攔截器
* 攔截握手前,握手後的兩個切面
*/
public class HandShakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
System.out.println("Websocket:用戶[ID:" + ((ServletServerHttpRequest) serverHttpRequest).getServletRequest().getSession(false).getAttribute("id") + "]已經建立連接");
if (serverHttpRequest instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) serverHttpRequest;
HttpSession session = servletRequest.getServletRequest().getSession(false);
// 標記用戶
String id = UserUtils.getUser().getLoginName();
if (StringUtils.isNotBlank(id)) {
map.put("uid", id);//爲服務器創建WebSocketSession做準備
System.out.println("用戶id:" + id + " 被加入");
} else {
System.out.println("user爲空");
return false;
}
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
}
}
每次用戶請求的時候通過後臺獲取到用戶的id,將id放入到map中,帶到WebSocketHandler.userSocketSessionMap 中存儲起來
前端代碼:
<script type="text/javascript">
var webSocket;
$(function() {
websocketRefresh()
});
function websocketRefresh(){
if('WebSocket' in window) {
try{
var ip = '${pageContext.request.localAddr}';
var reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
if( reg.test(ip)){
console.log("此瀏覽器支持websocket");
webSocket = new WebSocket("ws://"+ip+":${pageContext.request.localPort}${ctx}/ws");
}else{
/*alert("ip地址"+ip+"不合法,請勿使用127.0.0.1或者http://localhost");*/
}
}catch (e) {
alert("websocket鏈接異常");
}
} else if('MozWebSocket' in window) {
alert("此瀏覽器只支持MozWebSocket");
} else {
alert("此瀏覽器只支持SockJS");
}
webSocket.onopen = function(event){
console.log("連接成功");
console.log(event);
};
webSocket.onerror = function(event){
console.log("連接失敗");
console.log(event);
};
webSocket.onclose = function(event){
console.log("Socket連接斷開");
console.log(event);
};
webSocket.onmessage = function(event){
//接受來自服務器的消息
//...
console.log(webSocket.onmessage);
console.log(event);
var actLsit = JSON.parse(event.data);
console.log(actLsit);
apendTask(actLsit.body.data.list);
}
/*websocket.onopen = function(evnt) {
console.log("鏈接服務器成功")
};
websocket.onmessage = function(evnt) {
var actLsit = JSON.parse(evnt.data);
console.log(actLsit.body.data);
apendTask(actLsit.body.data);
};
websocket.onerror = function(evnt) {};
websocket.onclose = function(evnt) {
if(i<10){
console.log("與服務器斷開了鏈接,嘗試連接"+i);
i++;
websocketRefresh();
}else{
alert("與服務器斷開連接,請刷新活重新登錄");
i=0;
}
}*/
}
function apendTask(acts){
$("#act").empty();
console.log(acts);
var actsLength = acts.length;
if(actsLength>10){
actsLength =10;
}
var st = "";
for (var i = 0; i <actsLength ; i++) {
var vars = acts[i].vars;
st += '<div class="search-result search-hover" style="padding: 0 20px;">';
st += ' <div class="row">';
st += ' <div class="col-sm-5">';
st += ' <h4 style="margin: 10px 0;line-height: normal;">';
st += ' <a href="${ctx}/act/task/form?taskId=' + acts[i].taskId + '&taskName=' + acts[i].taskName + '&taskDefKey=' + acts[i].taskDefKey + '&procInsId=' + acts[i].procInsId + '&procDefId=' + acts[i].procDefId + '&status=' + acts[i].status + '">';
st += vars.map.title == null ? acts[i].taskId : vars.map.title;
st += ' </a>';
st += ' </h4>';
st += ' </div>';
st += ' <div class="col-sm-2 text-right" style="text-align: center;">';
st += ' <p class="forum-sub-title" style="font-size: 14px;margin: 10px 0;color: #181818;">';
st += acts[i].taskName;
st += ' </p>';
st += ' </div>';
st += ' <div class="col-sm-5" style="text-align: right;margin: 10px 0; color: #8b9298;">';
st += ' <div class="forum-sub-title">接收時間:' + acts[i].taskCreateDate + '</div>';
st += ' </div>';
st += ' </div>';
st += '</div>';
}
$("#act").append(st);
}
</script>
我的業務邏輯爲每隔20秒從後臺查詢數據並推送到前端,具體的推送環節我使用了定時器 如下代碼
package com.mlkj.common.schedule;
import com.google.gson.GsonBuilder;
import com.mlkj.common.json.AjaxJson;
import com.mlkj.common.persistence.Page;
import com.mlkj.modules.act.entity.Act;
import com.mlkj.modules.act.service.ActTaskService;
import com.mlkj.common.websocket.WebSocketHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.Map;
/**
* 待辦任務定時器
*/
@Service
@Lazy(false)
public class actTaskTodoSchedule {
@Autowired
private WebSocketHandler handler;
@Autowired
private ActTaskService actTaskService;
/**
* 每隔20秒獲取所有連接websocket的用戶,推送當前最新的代辦列表
*/
@Scheduled(cron = "*/20 * * * * ? ")
public void act() {
Map<String, WebSocketSession> stringWebSocketSessionMap = WebSocketHandler.userSocketSessionMap;
for (String st : stringWebSocketSessionMap.keySet()) {
try {
Page<Act> list = actTaskService.todoList(new Act(), st, new Page<Act>(0, 8));
AjaxJson ajaxJson = new AjaxJson();
ajaxJson.put("data",list);
handler.sendMessageToUser(st, new TextMessage(ajaxJson.getJsonStr()));
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
本人小白,剛剛接觸,如有不對敬請指正
參考代碼 https://blog.csdn.net/zmx729618/article/details/78584633