前端js
根目录创建一个js文件
import Vue from 'vue'
//创建websocket对象
import store from '@/store/index'
let webSocket = null;
//创建webSocket连接
let createWebSocket = () => {
try {
let url = window.location.href
const ip = url.split('//')[1].split('/')[0]
let wsUri = "ws://scmallfuwuqi.cn.utools.club/ucar/websocket/"+store.state.user.username //ws://localhost:8080/contenxt-path/url/..
//其实只需要定义一个变量存放后端提供的WebSocket接口地址,然后
console.log(wsUri, 'wsUri')
webSocket = new WebSocket(wsUri);
console.log(webSocket, 'webSocket')
//初始化websocket连接
initWebsocket();
} catch (e) {
console.log('尝试创建连接失败');
//如果无法连接上webSocket 那么重新连接!可能会因为服务器重新部署,或者短暂断网等导致无法创建连接
reConnect();
}
};
//连接标识 避免重复连接
let isConnect = false;
//断线重连后,延迟5秒重新创建WebSocket连接 rec用来存储延迟请求的代码
let rec;
//定义重连函数
let reConnect = () => {
console.log('尝试重新连接');
//如果已经连上就不在重连了
if (isConnect) return;
rec && clearTimeout(rec);
// 延迟5秒重连 避免过多次过频繁请求重连
rec = setTimeout(function () {
createWebSocket();
}, 5000);
};
//初始化webSocket连接
let initWebsocket = () => {
//WebSocket连接建立之后会调用onopen方法
webSocket.onopen = function (e) {
console.log('初始化,open');
//连接建立后修改标识
isConnect = true;
// 建立连接后开始心跳
// 因为nginx一般会设置例如60s没有传输数据就断开连接 所以要定时发送数据
heartCheck.start();
};
//当websocket收到服务器发送的信息之后 会调用onmessage方法 getMsg用来封装获取到服务器的消息进行处理,下面会说明
webSocket.onmessage = function (e) {
getMsg(e);
console.log(e)
//获取消息后 重置心跳
heartCheck.reset();
};
//当websocket因为各种原因(正常或者异常)关闭之后,会调用onclose方法
webSocket.onclose = function (e) {
console.log('close', '关闭');
//连接断开后修改标识
isConnect = false;
// reConnect();
};
//当websocket因为异常原因(比如服务器部署、断网等)关闭之后,会调用onerror方法
//在onerror中需要调用reConnect方法重连服务器
webSocket.onerror = function (e) {
console.log('close', '出错了');
console.log('error');
//连接断开后修改标识
isConnect = false;
//连接错误 需要重连
reConnect();
};
};
//心跳发送/返回的信息
//服务器和客户端收到的信息内容如果如下 就识别为心跳信息 不要做业务处理
let checkMsg = 'heartbeat';
//心跳设置
var heartCheck = {
//每段时间发送一次心跳包 这里设置为20s
timeout: 200000,
//延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象)
timeoutObj: null,
//一段时间后发送心跳包
start: function () {
this.timeoutObj = setTimeout(function () {
console.log('心跳1', Date.now())
if (isConnect) webSocket.send(checkMsg);
console.log('心跳2', Date.now())
}, this.timeout);
},
// 接收到服务器的信息之后要重置心跳发送的方法
reset: function () {
clearTimeout(this.timeoutObj);
this.start();
},
};
//获得消息之后 区别是心跳还是业务信息 如果是业务信息特殊处理(这里就用Element的notify才处理提醒)
let getMsg = (e) => {
console.log('message');
console.log(e.data);
if(JSON.parse(e.data)!=='保持心跳'){
store.commit('updateMessage', JSON.parse(e.data))
}
};
//关闭连接
let closeWebSocket = () => {
webSocket.close();
};
export default {
createWebSocket: createWebSocket,
closeWebSocket: closeWebSocket,
}
如果果断来了消息会放在Vuex中
然后有个难题,我们怎么去监听state中的message这个对象呢?
在我们的组件中添加计算属性,然后给这个计算属性配上侦听器
computed: {
getUserIcons() {
return this.$store.state.message;//在Vuex中拿值
},
},
watch: {
getUserIcons(val) {
this.messages.push(val);//messages是该组件在Data中一个数组绑定了组件上一个数组
console.log(val, "触发了监听器");
console.log(this.messages);
},
deep: true,
},
ok在组件中初始化
在组件中调用
<template>
{{messages}}
</template>
<script>
import websocket from "../../websocket/websocket";
export default {
data(){
return{
messages:[]
}
},
methods:{
initWebSocket() {
if (!this.$store.state.user) return;
websocket.createWebSocket();
},
},
created(){
this.initWebSocket();
},
computed: {
getUserIcons() {
return this.$store.state.message;//在Vuex中拿值
},
},
watch: {
getUserIcons(val) {
this.messages.push(val);//messages是该组件在Data中一个数组绑定了组件上一个数组
console.log(val, "触发了监听器");
console.log(this.messages);
},
deep: true,
},
}
</script>
<style scoped>
</style>
前端就完成了
Springboot
首先确保你的跨域过滤器对上面ws://xxxxxx/xx是不做拦截的!放过这个请求地址
首先在你项目的commons中创建一个类叫WebSocketStock,用于存放目前连接着的websocketClient
import com.etoak.ucar.service.impl.WebSocket;
import lombok.Data;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
@Data
public class WebSocketStock {
private static HashMap<String, WebSocket> stock =new HashMap<>();
public static void addToStock(String name,WebSocket webSocket){
stock.put(name,webSocket);
}
public static HashMap<String,WebSocket> getStock(){
return stock;
}
}
在你的service/impl中创建以下WebSocket核心类
import com.etoak.commons.Stock.WebSocketStock;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
@Component
@ServerEndpoint("/websocket/{userName}")
@Slf4j
@Data
public class WebSocket {
private Session session;
public WebSocket(){
}
@OnOpen
public void onOpen(@PathParam("userName")String userName,Session session){
this.session =session;
WebSocketStock.addToStock(userName,this);
System.out.println(WebSocketStock.getStock().toString());
log.info("【websocket消息】有新的连接,总数:{}",WebSocketStock.getStock().size());
}
@OnClose
public void onClose(){
// Collection c =stock.getStock().values();
// c.remove(this);
Collection<WebSocket> values = WebSocketStock.getStock().values();
try{ values.remove(this);}catch (Exception e){e.printStackTrace();}
log.info("【websocket消息】连接断开,总数:{}",WebSocketStock.getStock().size());
}
@OnMessage
public void onMessage(String message){
log.info("【websocket消息】收到客户端发来的消息:{}",message);
}
public void sendMessage(String userName,String message){
Set<Map.Entry<String, WebSocket>> entries = WebSocketStock.getStock().entrySet();
for (Map.Entry<String, WebSocket> entry : entries) {
if(entry.getKey().equals(userName)){
try {
log.info("【websocket消息】发送消息:{}",message);
entry.getValue().getSession().getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
另外在WebSocket中如果超过一分钟没有通信会自动断开,所以在你的config中建立SaticScheduleTask
import com.etoak.commons.Stock.WebSocketStock;
import com.etoak.ucar.service.impl.WebSocket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
@Slf4j
public class SaticScheduleTask {
//3.添加定时任务
//@Scheduled(cron = "0/5 * * * * ?")
//或直接指定时间间隔,例如:5秒
//每隔30秒发一条消息
@Scheduled(fixedRate=30*1000)
private void configureTasks() throws Exception{
log.info("保持心跳");
HashMap<String, WebSocket> stockStock = WebSocketStock.getStock();
for (Map.Entry<String, WebSocket> stringWebSocketEntry : stockStock.entrySet()) {
stringWebSocketEntry.getValue().sendMessage(stringWebSocketEntry.getKey(),"保持心跳");
}
}
}
后端工程启动 测试 首先启动前端项目 确保成功执行initWebsocket()
public static void main(String[] args){
HashMap<String, WebSocket> stockStock = WebSocketStock.getStock();
for (Map.Entry<String, WebSocket> stringWebSocketEntry : stockStock.entrySet()) {
stringWebSocketEntry.getValue().sendMessage(stringWebSocketEntry.getKey(),"这是一条测试数据");
}
}
运行看看前端有没有这条消息吧!