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--;
}
}
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、调用结果如图
附带源码链接:http://download.youkuaiyun.com/download/qq_31151929/10207702