JAVA前后端实现WebSocket消息推送(针对性推送)

1、需要添加依赖包,在pom.xml文件中添加

<dependency>
	<groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
	<version>7.0</version>
	<scope>provided</scope>
</dependency>




2、客户端代码

在这里我为了做成httpsession登录后是同一个,所以我做成两个页面,一个登录跳转页面,一个用于链接WebSocket接收消息

a.登录页面


<!DOCTYPE html>
<html>

	<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="登录" οnclick="dl()" />
		<a href="login.html">tiaozhuan</a>
	</body>

</html>


b.接收消息推送页面


<!DOCTYPE html>
<html>

	<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" οnclick="send()">
		</div>
	</body>

</html>



3、关于后端代码这边事由4个文件

一个通用msg文件、一个用于获取当前会话的httpsession、一个用监听有没有httpsession(没有则创建)、一个用于WebSocket链接和发送消息

a.通用msg文件


package com.boli.srcoin.websocket;

import java.util.Date;

/**
 * @author : admin</br>
 * @DESC : <p>WebSocket消息模型</p></br>
 */
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</br>
 * @DESC : <p>讲http request的session 存入websocket的session内</p></br>
 */
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</br>
 * @DESC : <p>注解{@link ServerEndpoint}声明websocket 服务端</p></br>
 */
@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 <p>注解{@link OnOpen} 声明客户端连接进入的方法</p>
     */
    @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());
    }


    /**
     * <p>{@link OnClose} 关闭连接</p>
     */
    @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());
    }

    /**
     * <p>{@link OnMessage} 消息监听处理方法</p>
     *
     * @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));
            }
        }

    }

    /**
     * <p>{@link OnError} websocket系统异常处理</p>
     *
     * @param t 异常
     */
    @OnError
    public void onError(Throwable t) {
        logger.error(t);
        t.printStackTrace();
    }

    /**
     * <p>系统主动推送 这是个静态方法在web启动后可在程序的其他合适的地方和时间调用,这就实现了系统的主动推送</p>
     *
     * @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、调用结果如图

app推送通知 java uni javaweb消息推送_app推送通知 java uni

app推送通知 java uni javaweb消息推送_JSON_02

app推送通知 java uni javaweb消息推送_JSON_03