JAVA前後端實現WebSocket訊息推送(針對性推送)
1、需要新增依賴包,在pom.xml檔案中新增
javax javaee-api 7.0 provided2、客戶端程式碼 在這裡我為了做成httpsession登入後是同一個,所以我做成兩個頁面,一個登入跳轉頁面,一個用於連結WebSocket接收訊息
a.登入頁面
<head> <meta charset="UTF-8"> <title>WebSocket</title> <script src="js/jquery-1.8.3.min.js"></script> <script type="text/javascript"> function dl() { $.ajax({ xhrFields: { withCredentials: true }, type:"get", url:"http://localhost:8080/cloudmgr/api/login?user=ppp", }); } </script> </head> <body> <input type="button" value="登入" onclick="dl()" /> <a href="login.html">tiaozhuan</a> </body>
b.接收訊息推送頁面
<head> <meta charset="UTF-8"> <title>WebSocket</title> <script src="js/jquery-1.8.3.min.js"></script> <script type="text/javascript"> var ws = null; //判斷當前瀏覽器是否支援WebSocket if('WebSocket' in window) { ws = new WebSocket("ws://localhost:8080/cloudmgr/chat"); } else { alert('當前瀏覽器 Not support websocket') } /* *監聽三種狀態的變化js會回撥 */ ws.onopen = function(message) { // 連接回調 }; ws.onclose = function(message) { // 斷開連接回調 }; ws.onmessage = function(message) { // 訊息監聽 showMessage(message.data); }; //監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連線,防止連線還沒斷開就關閉視窗,server端會拋異常。 window.onbeforeunload = function() { ws.close(); }; //關閉連線 function closeWebSocket() { ws.close(); } //傳送訊息 function send() { var input = document.getElementById("msg"); var text = input.value; // 訊息體JSON 物件 對應JAVA 的 Msg物件 var data = { // 定點發送給其他使用者的userId toUid: "3d535429-5fcb-4490-bcf7-96fd84bb17b6", data: text } ws.send(JSON.stringify(data)); input.value = ""; } function showMessage(message) { /*var text = document.createTextNode(JSON.parse(message).data); var br = document.createElement("br") var div = document.getElementById("showChatMessage"); div.appendChild(text); div.appendChild(br);*/ var text = document.createTextNode(message); document.getElementById("showText").appendChild(text); } </script> </head> <body> <div> <style="width: 600px; height: 240px; overflow-y: auto; border: 1px solid #333;" id="show"> <div id="showChatMessage"></div> <div id="showText"/> <input type="text" size="80" id="msg" name="msg" placeholder="輸入聊天內容" /> <input type="button" value="傳送" id="sendBn" name="sendBn" onclick="send()"> </div> </body>
3、關於後端程式碼這邊事由4個檔案
一個通用msg檔案、一個用於獲取當前會話的httpsession、一個用監聽有沒有httpsession(沒有則建立)、一個用於WebSocket連結和傳送訊息
a.通用msg檔案
package com.boli.srcoin.websocket;
import java.util.Date;
/**
-
@author : admin
-
@DESC :
WebSocket訊息模型
*/ public class Msg {// 推送人ID private String fromUid;
// 定點推送人ID private String toUid;
// 定點推送單位ID private String toOrgId;
// 訊息體 private String data;
// 推送時間 private Date createDate = new Date();
// 訊息狀態 private Integer flag;
public Msg() {
}
public Msg(String fromUid, String toUid, String toOrgId, String data, Date createDate, Integer flag) { this.fromUid = fromUid; this.toUid = toUid; this.toOrgId = toOrgId; this.data = data; this.createDate = createDate; this.flag = flag; }
public String getFromUid() { return fromUid; }
public void setFromUid(String fromUid) { this.fromUid = fromUid; }
public String getToUid() { return toUid; }
public void setToUid(String toUid) { this.toUid = toUid; }
public String getToOrgId() { return toOrgId; }
public void setToOrgId(String toOrgId) { this.toOrgId = toOrgId; }
public String getData() { return data; }
public void setData(String data) { this.data = data; }
public Date getCreateDate() { return createDate; }
public void setCreateDate(Date createDate) { this.createDate = createDate; }
public Integer getFlag() { return flag; }
public void setFlag(Integer flag) { this.flag = flag; }
@Override public String toString() { return “Msg{” + “fromUid=’” + fromUid + ‘’’ + “, toUid=’” + toUid + ‘’’ + “, toOrgId=’” + toOrgId + ‘’’ + “, data=’” + data + ‘’’ + “, createDate=” + createDate + “, flag=” + flag + ‘}’; } }
b.用於在WebSocket或去httpsession
package com.boli.srcoin.websocket;
import javax.servlet.http.HttpSession; import javax.websocket.HandshakeResponse; import javax.websocket.server.HandshakeRequest; import javax.websocket.server.ServerEndpointConfig; import javax.websocket.server.ServerEndpointConfig.Configurator;
/**
-
@author : admin
-
@DESC :
講http request的session 存入websocket的session內
*/ public class HttpSessionConfigurator extends Configurator {@Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
// 獲取當前Http連線的session HttpSession httpSession = (HttpSession) request.getHttpSession(); // 將http session資訊注入websocket session sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
} } c.用於監聽有沒有httpsession,沒有則建立
package com.boli.srcoin.websocket;
import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpServletRequest;
@WebListener public class RequestListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
//將所有request請求都攜帶上httpSession
((HttpServletRequest) sre.getServletRequest()).getSession();
}
public RequestListener() {
// TODO Auto-generated constructor stub
}
public void requestDestroyed(ServletRequestEvent arg0) {
// TODO Auto-generated method stub
}
}
d.接收WebSocket連結和傳送訊息 package com.boli.srcoin.websocket;
import com.alibaba.fastjson.JSON; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger;
import javax.servlet.http.HttpSession; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap;
/**
-
@author : admin
-
@DESC :
註解{@link ServerEndpoint}宣告websocket 服務端
*/ @ServerEndpoint(value = “/chat”, configurator = HttpSessionConfigurator.class) public class WSServer {static private Logger logger = Logger.getLogger(WSServer.class);
// 線上人數 執行緒安全 private static int onlineCount = 0;
// 連線集合 userId => server 鍵值對 執行緒安全 static public final ConcurrentMap<String, WSServer> map = new ConcurrentHashMap<>();
// 與某個客戶端的連線會話,需要通過它來給客戶端傳送資料 private Session session;
// 當前會話的httpsession private HttpSession httpSession;
/**
-
@param session websocket連線sesson
-
@param config {@link com.github.websocket.configuration.HttpSessionConfigurator}
-
@DESC
註解{@link OnOpen} 宣告客戶端連線進入的方法
*/ @OnOpen public void onOpen(Session session, EndpointConfig config) {// 得到httpSession this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
// 獲取session物件 SObject(這個就是java web登入後的儲存的session物件,此處為使用者資訊,包含了userId) String user = (String) this.httpSession.getAttribute(“user”);
this.session = session; System.out.println(user+"-------"+this.session.getId());
//針對一個使用者只能有一個連結 if(map.get(user)!=null){ // 移除連線 map.remove(user); // 連線數-1 subOnlineCount(); }
// 將連線session物件存入map map.put(user, this);
// 連線數+1 addOnlineCount();
logger.info(“有新的連線,當前連線數為:” + getOnlineCount()); }
/**
-
{@link OnClose} 關閉連線
*/ @OnClose public void onClose() {
/** * 獲取當前連線資訊 {@code CommonConstant.USER_LOGIN_SESSION} 為Http session 名 */ String user = (String) this.httpSession.getAttribute("user"); // 移除連線 map.remove(user); // 連線數-1 subOnlineCount(); logger.info("有一連線斷開,當前連線數為:" + getOnlineCount());
}
/**
-
{@link OnMessage} 訊息監聽處理方法
-
@param message 訊息物件{@link com.github.websocket.msg.Msg}的JSON物件
-
@throws IOException 異常 */ @OnMessage public void onMessage(String message) throws IOException {
// 將訊息轉Msg物件 Msg msg = JSON.parseObject(message, Msg.class);
//TODO 可以對msg做些處理…
// 根據Msg訊息物件獲取定點發送人的userId WSServer _client = map.get(msg.getToUid());
// 定點發送 if (StringUtils.isNotEmpty(msg.getToUid())) { if (null != _client) { // 是否連線判斷 if (_client.session.isOpen()) // 訊息傳送 _client.session.getBasicRemote().sendText(JSON.toJSONString(msg)); } }
// 群發 if (StringUtils.isEmpty(msg.getToUid())) { // 群發已連線使用者 for (WSServer client : map.values()) { client.session.getBasicRemote().sendText(JSON.toJSONString(msg)); } }
}
/**
-
{@link OnError} websocket系統異常處理
- @param t 異常 */ @OnError public void onError(Throwable t) { logger.error(t); t.printStackTrace(); }
/**
-
系統主動推送 這是個靜態方法在web啟動後可在程式的其他合適的地方和時間呼叫,這就實現了系統的主動推送
-
@param msg 訊息物件{@link com.github.websocket.msg.Msg}的JSON物件 */ static public void pushBySys(Msg msg) {
//TODO 也可以實現定點推送 //msg傳輸的時候會帶上需要傳送訊息給誰msg.getToUid() //通過map去獲取那個使用者所在的session WSServer ws=map.get(msg.getToUid()); try { if(ws!=null){ ws.session.getBasicRemote().sendText(“123456”); } } catch (IOException e1) { e1.printStackTrace(); }
// 群發 /for (WSServer client : map.values()) { try { client.session.getBasicRemote().sendText(JSON.toJSONString(msg)); } catch (IOException e) { e.printStackTrace(); } }/ }
// 獲取連線數 private static synchronized int getOnlineCount() { return WSServer.onlineCount; }
// 增加連線數 private static synchronized void addOnlineCount() { WSServer.onlineCount++; }
// 減少連線數 private static synchronized void subOnlineCount() { WSServer.onlineCount–; }
-
}
4、在後端的呼叫,也就是登入和呼叫傳送訊息
package com.boli.srcoin.member.service.impl;
import java.util.HashMap; import java.util.Map;
import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
import com.boli.framework.system.result.StandardResult; import com.boli.framework.utils.WebUtil; import com.boli.srcoin.member.form.MemberLoginForm; import com.boli.srcoin.member.service.LoginMemberService; import com.boli.srcoin.websocket.Msg; import com.boli.srcoin.websocket.WSServer;
@Service public class LoginMemberServiceImpl implements LoginMemberService{
@Override
@Transactional(readOnly = false)
public StandardResult toLogin(MemberLoginForm loginForm) {
WebUtil.getSession().setAttribute("user", loginForm.getUser());
Map<String,Object> map=new HashMap<>();
map.put("sessionId", WebUtil.getSession().getId());
map.put("user", loginForm.getUser());
System.out.println("呼叫登入方法:"+WebUtil.getSession().getId()+loginForm.getUser());
return StandardResult.ok(map);
}
@Override
@Transactional(readOnly = false)
public StandardResult tishi() {
Msg msg=new Msg();
msg.setToUid("ppp");
WSServer.pushBySys(msg);
return StandardResult.ok();
}
}
5、呼叫結果如圖