<>后端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() } });