后端springboot:
pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.35</version>
</dependency>
</dependencies>
entity
package com.sparrow.es.entity;
import lombok.Data;
import javax.websocket.Session;
//这里我使用了Lombok的注解,如果没有添加这个依赖 可以创建get set方法
@Data
public class WebSocketClient {
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//连接的uri
private String uri;
}
config
package com.sparrow.es.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 开启 WebSocket:使用 springboot 内嵌的tomcat容器启动 websocket
**/
@Slf4j
@Configuration
public class WebSocketConfig {
/**
* 服务器节点
*
* 如果使用独立的servlet容器,而不是直接使用springboot 的内置容器,就不要注入ServerEndPoint
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
log.info("启动 WebSocket ...");
return new ServerEndpointExporter();
}
}
service(重点)
package com.sparrow.es.config;
import com.sparrow.es.entity.WebSocketClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/**
* @desc: WebSocketService实现
* @author: M
* @since: 2022/02/18
*/
//ServerEncoder 是为了解决编码异常,如果不需要使用sendObject()方法,这个可以忽略,只写value即可
@ServerEndpoint(value = "/websocket/{userName}",encoders = {ServerEncoder.class})
@Component
public class WebSocketService {
private static final Logger log = LoggerFactory.getLogger(WebSocketService.class);
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
*/
private static ConcurrentHashMap<String, WebSocketClient> webSocketMap = new ConcurrentHashMap<>();
/**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
private Session session;
/**接收userName 用来区别不同的用户*/
private String userName="";
/**
* 连接建立成功调用的方法 可根据自己的业务需求做不同的处理*/
@OnOpen
public void onOpen(Session session, @PathParam("userName") String userName) {
this.session = session;
this.userName= userName;
WebSocketClient client = new WebSocketClient();
client.setSession(session);
client.setUri(session.getRequestURI().toString());
webSocketMap.put(userName, client);
log.info("测试连接:"+userName+",当前使用人数为:" + webSocketMap.size());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if(webSocketMap.containsKey(userName)){
webSocketMap.remove(userName);
}
log.info(userName+"测试结束,当前在测数量为:" + webSocketMap.size());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到用户消息:"+userName+",报文:"+message);
}
/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误:"+this.userName+",原因:"+error.getMessage());
error.printStackTrace();
}
/**
* 连接服务器成功后主动推送
*/
public void sendMessage(String message) throws IOException {
System.out.println("【websocket消息】广播消息:"+message);
webSocketMap.forEach((key,value) -> {
try {
value.getSession().getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
});
}
/**
* 向指定客户端发送消息(字符串形式)
* @param userName
* @param message
*/
public static void sendMessage(String userName,String message){
try {
WebSocketClient webSocketClient = webSocketMap.get(userName);
if(webSocketClient!=null){
webSocketClient.getSession().getBasicRemote().sendText(message);
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
/**
* 向指定客户端发送消息(对象的形式)
* @param userName
* @param object
*/
public static void sendMessage(String userName,Object object){
try {
WebSocketClient webSocketClient = webSocketMap.get(userName);
if(webSocketClient!=null){
webSocketClient.getSession().getBasicRemote().sendObject(object);
}
} catch (IOException | EncodeException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
Encoder(编码格式)
package com.sparrow.es.config;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
import java.util.HashMap;
/**
* @desc: WebSocket编码器
* @author: M
* @since: 2022/02/21
*/
public class ServerEncoder implements Encoder.Text<HashMap> {
private static final Logger log = LoggerFactory.getLogger(ServerEncoder.class);
/**
* 这里的参数 hashMap 要和 Encoder.Text<T>保持一致
* @param hashMap
* @return
* @throws EncodeException
*/
@Override
public String encode(HashMap hashMap) throws EncodeException {
/*
* 这里是重点,只需要返回Object序列化后的json字符串就行
* 你也可以使用gosn,fastJson来序列化。
* 这里我使用fastjson
*/
try {
return JSONObject.toJSONString(hashMap);
}catch (Exception e){
log.error("",e);
}
return null;
}
@Override
public void init(EndpointConfig endpointConfig) {
//可忽略
}
@Override
public void destroy() {
//可忽略
}
}
controller(测试使用)
package com.sparrow.es.controller;
import com.sparrow.es.config.WebSocketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.HashMap;
@RestController
public class TestController {
@Autowired
WebSocketService webSocketService;
@GetMapping("test")
public String test(){
return "success";
}
@GetMapping("/sendAll")
public String sendAll() throws IOException {
String text="你们好!这是websocket群体发送!";
webSocketService.sendMessage("text");
return text;
}
@GetMapping("/sendOne/{userName}/{msg}")
public String sendOne(@PathVariable("userName") String userName,@PathVariable("msg") String msg) {
String text=userName+" 你好! 这是websocket单人发送!";
HashMap<Object, Object> msgs = new HashMap<>();
msgs.put("test",msg);
webSocketService.sendMessage(userName,msgs);
return text;
}
}
前端VUE:
工具类直接引入
import {ref} from "vue";
import store from "../store"
let websock = ref(null);
export function initWensocket(websocketOnMessage ){
//建立websocket连接
if ("WebSocket" in window) {
//连接服务端访问的url,我这里配置在了env中,就是上面在线测试工具中的地址,下面放了实例
//let ws = "ws://后端ip:端口号/service接口名/当前连接名";
let ws = "ws://127.0.0.1:9696/websocket/"+888;
websock = new WebSocket(ws);
websock.onopen = websocketOnOpen;
websock.onerror = websocketonerror;
websock.onmessage = websocketOnMessage;
websock.onclose = websocketclose;
store.commit("websocket/saveWebSocket",websock)
} else {
alert('当前浏览器 Not support websocket')
}
}
const websocketOnOpen = () => {
console.log("链接成功");
}
const websocketclose = () => {
console.log("链接关闭")
}
const websocketonerror = () => {
console.log("建立链接失败")
}
vuex
// websocket模块(局部模块)
export default {
namespaced: true, // 开启命名空间,用于在全局引用此文件里的方法时标识这一个的文件名,解决命名冲突问题
state () {
},
// 定义mutations,用于同步修改状态
mutations: {
saveWebSocket(state,websocket){
state["websocket"] = websocket
},
},
// 定义actions,用于异步修改状态
actions: {},
// 定义一个getters
getters: {}
}
页面使用
//引入
import {initWensocket} from "@/utils/websocket";
//需要的地方引用
onMounted(() => {
initWensocket(websocketOnMessage )
})
const sendMessage = () => {
store.state.websocket.websocket.send("111")
}
const websocketOnMessage = (e) => {
console.log(e)
if (e) {
let message = typeof e.data == 'string' ? e.data : JSON.parse(e.data);//这个是收到后端主动推送的json字符串转化为对象(必须保证服务端传递的是json字符串,否则会报错)
//你的业务处理...
console.log(message)
}
}
跳转时候关闭
由于我这里的业务需求是离开当前页面就关闭链接,所以对将它在路由跳转后关闭链接
router.afterEach((to, from, next) => {
if (store.state.websocket.websocket){
store.state.websocket.websocket.close()
}
});