1. 程式人生 > >JAVA前後端實現WebSocket訊息推送(針對性推送)

JAVA前後端實現WebSocket訊息推送(針對性推送)

1、需要新增依賴包,在pom.xml檔案中新增

javax javaee-api 7.0 provided

2、客戶端程式碼 在這裡我為了做成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、呼叫結果如圖