pom 配置
<!-- WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 启用Netty https://mvnrepository.com/artifact/org.yeauty/netty-websocket-spring-boot-starter -->
<dependency>
<groupId>org.yeauty</groupId>
<artifactId>netty-websocket-spring-boot-starter</artifactId>
<version>0.7.6</version>
</dependency>
config配置 :开启WebSocket支持
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 开启WebSocket支持
* Created by yj on 2020/3/30.
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
netty-websocket 发送消息和接受消息
package com.ruoyi.framework.config.webSocket;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.yeauty.annotation.ServerEndpoint;
import javax.annotation.PostConstruct;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/**
* 注意这里的注解@ServerEndpoint是org.yeauty路径下的,别引用错了包。
* Created by yj on 2020/3/30.
*/
@ServerEndpoint(path = "/imserver",host = "8081")
@Component
public class WebSocketServer {
private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
/**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
private static int onlineCount = 0;
/**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
/**concurrent包的线程安全Set,用来存放每个客户端对应的心跳对象。*/
private static ConcurrentHashMap<String, Long> webSocketHeartCheck = new ConcurrentHashMap<String, Long>();
/**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
private Session session;
/**接收userId*/
private Long userId;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public static ConcurrentHashMap<String, WebSocketServer> getWebSocketMap() {
return webSocketMap;
}
public static void setWebSocketMap(ConcurrentHashMap<String, WebSocketServer> webSocketMap) {
WebSocketServer.webSocketMap = webSocketMap;
}
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(Session session) {
/* SysUser user = ShiroUtils.getSysUser();
String userId= String.valueOf(user.getUserId());*/
this.session = session;
Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
this.userId= userId;
if(webSocketMap.containsKey(this.userId)){
webSocketMap.remove(this.userId);
webSocketMap.put(String.valueOf(this.userId),this);
webSocketHeartCheck.put(String.valueOf(this.userId),System.currentTimeMillis());
//加入set中
}else{
webSocketMap.put(String.valueOf(this.userId),this);
webSocketHeartCheck.put(String.valueOf(this.userId),System.currentTimeMillis());
//加入set中
addOnlineCount();
//在线数加1
}
log.info("用户连接:"+ this.userId +",当前在线人数为:" + getOnlineCount());
try {
sendMessage("连接成功");
} catch (IOException e) {
log.error("用户:"+ this.userId +",网络异常!!!!!!");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if(webSocketMap.containsKey(userId)){
webSocketMap.remove(userId);
//从set中删除
subOnlineCount();
}
log.info("用户退出:"+userId+",当前在线人数为:" + getOnlineCount());
}
@PostConstruct
public void checkClientLiving(){
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.print(123);
while (true) {
// ------- ends here
try {
for (ConcurrentHashMap.Entry<String,Long> heartCheck : webSocketHeartCheck.entrySet()) {
if (System.currentTimeMillis() - heartCheck.getValue() > 600000){
webSocketMap.remove(heartCheck.getKey());
webSocketHeartCheck.remove(heartCheck.getKey());
//从set中删除
subOnlineCount();
}
}
Thread.sleep(600000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用户消息:"+userId+",报文:"+message);
//可以群发消息
//消息保存到数据库、redis
if(StringUtils.isNotBlank(message)){
try {
//解析发送的报文
JSONObject jsonObject = JSON.parseObject(message);
//追加发送人(防止串改)
jsonObject.put("fromUserId",this.userId);
String toUserId=jsonObject.getString("toUserId");
//传送给对应toUserId用户的websocket
if(StringUtils.isNotBlank(toUserId)&&webSocketMap.containsKey(toUserId)){
webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
webSocketHeartCheck.put(String.valueOf(userId),System.currentTimeMillis());
}else{
log.error("请求的userId:"+toUserId+"不在该服务器上");
//否则不在这个服务器上,发送到mysql或者redis
}
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误:"+this.userId+",原因:"+error.getMessage());
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 发送自定义模糊配备消息
* */
public static void sendInfoId(String message,@PathParam("userId") String userId) throws IOException {
log.info("发送消息到:"+userId+",报文:"+message);
if(StringUtils.isNotBlank(userId)){
String userKey = userId + "-";
for (ConcurrentHashMap.Entry<String, WebSocketServer> webMap : webSocketMap.entrySet()) {
if(webMap.getKey().indexOf(userKey)>-1){
webSocketMap.get(webMap.getKey()).sendMessage(message);
}
}
}else{
log.error("用户"+userId+",不在线!");
}
}
/**
* 发送自定义消息
* */
public static void sendUserId(String message,@PathParam("userId") String userId) throws IOException {
log.info("发送消息到:"+userId+",报文:"+message);
if(StringUtils.isNotBlank(userId)&&webSocketMap.containsKey(userId)){
webSocketMap.get(userId).sendMessage(message);
}else{
log.error("用户"+userId+",不在线!");
}
}
/**
* 群发自定义消息
*/
public static void sendInfoUser(String message) throws IOException {
for (ConcurrentHashMap.Entry<String, WebSocketServer> webMap : webSocketMap.entrySet()) {
webMap.getValue().sendMessage(message);
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
前端
var prefix = ctx + "business/announcement/";
function getAnnouncementCnt(){
var socket;
var outTradeNo = new Date().getTime() + Math.floor(Math.random()*100);
var userId = $("#userId").val()+"-"+outTradeNo
if(window.WebSocket) {
var wsUrl = "wss://127.0.0.1:443/imserver/"+userId;
console.log(wsUrl)
socket = new WebSocket(wsUrl);
//打开事件
socket.onopen = function() {
console.log("websocket已打开");
AnnouncementCnt();
//心跳检测重置
heartCheck.start();
};
socket.onmessage = function (event) {
if (event.data == '有新消息'){
console.log("123456");
AnnouncementCnt();
var contentWindow= $('iframe[data-id="/business/announcement"]')[0].contentWindow;
contentWindow.indexPublic();
contentWindow.indexSystem();
}
console.log('接收到消息');
//拿到任何消息都说明当前连接是正常的
heartCheck.start();
}
//关闭事件
socket.onclose = function() {
console.log("websocket已关闭");
reconnect(wsUrl);
};
//发生了错误事件
socket.onerror = function() {
console.log("websocket发生了错误");
reconnect(wsUrl);
}
} else {
AnnouncementCnt();
}
//避免重复连接
var lockReconnect = false;//避免重复连接
function reconnect(wsUrl) {
if(lockReconnect) {
return;
};
lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
tt && clearTimeout(tt);
tt = setTimeout(function () {
getAnnouncementCnt(wsUrl);
lockReconnect = false;
}, 4000);
}
//心跳检测
var heartCheck = {
timeout: 300000, //每隔三秒发送心跳
num: 3, //3次心跳均未响应重连
timeoutObj: null,
serverTimeoutObj: null,
start: function(){
var _this = this;
var _num = this.num;
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(function(){
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
socket.send('{"toUserId":"'+userId+'"}'); // 心跳包
_num--;
//计算答复的超时次数
if(_num === 0) {
socket.colse();
}
}, this.timeout)
}
}
}