springboot+Vue实现webSocket

1、依赖版本

springboot:2.2.5.RELEASE (springboot官网
Vue:2.11.0 (element ui官网)

springboot pom中依赖 websocket

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-websocket</artifactId>
      <scope>provided</scope>
</dependency>

2、后端实现WebSocketServer

1):WebSocketServer实现类
@ServerEndpoint("/socket")//配置服务端点,类似mapping地址
@Component
public class WebSocketServer {
	
	private static final Log log = Logs.getLog(WebSocketServer.class);
	/**用于处理链接进来的用户信息 -- 自定义*/
	private StringBuffer userIdentifiy = new StringBuffer();
			
    /**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
    private Session session;
    
    /**接收userId*/
    private String userId = "";
    
    /** 以缓冲的传入二进制消息的最大长度,可以缓冲的传入文本消息的最大长度*/
    @Value("${websocket.max-binary-message-buffer-size:2000}")
    private int maxBinaryMessageBufferSize;
    //自定义的接口类,用于实现其他处理(业务之类的)
    public static WebSocketService webSocketService;

    // 给类的 service 注入
    @Autowired
    public void setWebSocketService(WebSocketService webSocketService) {
    	WebSocketServer.webSocketService = webSocketService;
    }
    
    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session) {
    	//用于标识用户是否可以成功链接
    	boolean allowConnection = false;
    	// 获取请求参数
    	String paramInfo = null;
		try {
			// 参数以 Content-Type为 x-www-form-urlencoded 传输
			if(StringUtils.isNotBlank(session.getQueryString())) {
				paramInfo = new String(session.getQueryString().getBytes("iso-8859-1"), "utf-8");
			}
		} catch (UnsupportedEncodingException e1) {
			e1.printStackTrace();
			return;
		}
    	if(StringUtils.isNotBlank(paramInfo)) {
    		// 处理参数转为JSONObject
    		JSONObject userCache = urlParamSplit(paramInfo);
    		// token 判断必传、判断调用用途不能为空
    		if(userCache.containsKey("token") && 
    				!VerificationUtils.checkJsonKeyValueIsNull(userCache, WebSocketConstant.CONNECT_PURPOSE)) {
    			// 获取用户token信息,根据自己系统配置
    			UserInfo cache_user = xxx.getUserAuthInfo(userCache.getString("token"));
    			if(cache_user != null) {
        			// 用户编码
        			userCache.put(WebSocketConstant.CONNECT_USERCODE, cache_user.getUserCode());
        			this.session = session;
        	        int maxSize = maxBinaryMessageBufferSize * 1024;// 2000K
        	        // 可以缓冲的传入二进制消息的最大长度
        	        session.setMaxBinaryMessageBufferSize(maxSize);
        	        // 可以缓冲的传入文本消息的最大长度
        	        session.setMaxTextMessageBufferSize(maxSize);
        	        // 处理用户信息
        	        userIdentifiy.setLength(0);
        	        userIdentifiy.append(cache_user.getUserCode());// 用户编码
        	        userIdentifiy.append("--");
        	        // 其他需要的按需处理userIdentifiy.append(userCache.getString(WebSocketConstant.CONNECT_PURPOSE));// 用途
        	        userIdentifiy.append(userCache.getString(WebSocketConstant.CONNECT_CLIENT_ID));// 客户端识别app/pc
        	        this.userId = userIdentifiy.toString();
        	        // 加入关系
        	        WebSocketConstant.webSocketMap.put(userId,this);
        	        WebSocketConstant.webSocketTokenMap.put(userId, userCache);
    	            // 处理业务需要
        	        webSocketService.dealBusiness(this.userId, userCache.getString(WebSocketConstant.CONNECT_PURPOSE), userCache);
        	        try {
        	        	JSONObject returnObj = new JSONObject();
        	        	returnObj.put(WebSocketConstant.CONNECT_PURPOSE, WebSocketConstant.WEBSOCKET_CONNECTED);
        	            sendMessage(returnObj.toJSONString());
        	        } catch (IOException e) {
        	            log.error("用户:" + userId + ",网络异常!");
        	        }
        	        allowConnection = true;
    			}
    		}
    	} 
    	if(!allowConnection) {
    		try {
				session.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
    	}
    }
    
    /**
     * @param urlparam 带分隔的url参数
     * @return
     */
    public static JSONObject urlParamSplit(String urlparam){
    	JSONObject obj = new JSONObject();
    	String[] param =  urlparam.split("&");
    	for(String keyvalue: param){
    		String[] pair = keyvalue.split("=");
    		if(pair.length==2){
    			obj.put(pair[0], pair[1]);
    		}
    	}
    	return obj;
    }
    
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if(WebSocketConstant.webSocketMap.containsKey(userId)){
        	// 业务操作
        	webSocketService.removeBusinessRecord(userId, WebSocketConstant.webSocketTokenMap.get(userId));
        	WebSocketConstant.webSocketMap.remove(userId);
        	WebSocketConstant.webSocketTokenMap.remove(userId);// 用户与token关系
        }
    }

    /**
     * 收到客户端消息后调用的方法
     * 
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
    	try {
			session.getBasicRemote().sendText("接收到消息:"+ message);
		} catch (IOException 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);
    }


    /**
     * -推送消息给某个用户
     * 
     * @param message
     * @param userId
     * @throws IOException
     */
    public static void sendInfo(String message, String userId) throws IOException {
        if(StringUtils.isNotBlank(userId) && WebSocketConstant.webSocketMap.containsKey(userId)){
        	WebSocketConstant.webSocketMap.get(userId).sendMessage(message);
        }else{
            log.error("用户"+userId+",不在线!");
        }
    }
    
}
2):自定义 WebSocket 常量
/**
 * WebSocket 常量定义
 * @author chenwx
 *
 */
public class WebSocketConstant {
	
	/** 存放每个客户端对应的WebSocket对象。 */
	public static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
    
    /** 存放每个客户端对应的userInfo对象。*/
	public static ConcurrentHashMap<String, JSONObject> webSocketTokenMap = new ConcurrentHashMap<>();
	
	/** webSocket连接成功 */
	public static final String WEBSOCKET_CONNECTED = "connected";
	
	/** 缓存用户编码 */
	public static final String CONNECT_USERCODE = "userCode";
	
	/** 缓存供应商编码 */
	public static final String CONNECT_BIZCODE = "bizCode";
	
	/** 用途 */
	public static final String CONNECT_PURPOSE = "purpose";
	
	/** 返回参数 */
	public static final String CONNECT_RETURN_OBJECT = "result";
	
	/** 连接客户端类型 app/pc */
	public static final String CONNECT_CLIENT_TYPE = "clientId";
	
}

3、前端实现

1、创建一个WebSocket:

//判断是ip还是域名
let isIp = self.ipVerify(document.location.hostname);
// 域名访问需要用wss
let replaceCode = 'wss'; 
if (isIp) {
	replaceCode = 'ws';
}
var socketUrl = this.webSocketUrl()
	.replace('https', replaceCode)
	.replace('http', replaceCode);
let websocket = new WebSocket(url);
//几个常用事件方法
websocket.onopen = function(event) { 
    //连接打开
}; 
websocket.onclose = function(event) { 
    //连接关闭
}; 
websocket.onmessage = function(event) { 
    //接收到消息
}; 
websocket.onerror = function(event) { 
    //发生错误
}; 

webSocketUrl() {
	let baseUrl = this.baseUrl();
	//本地部署的服务名称,用于websocket不在自己部署的服务中时,通过替换服务名称,直接连到对应的服务去
	let serviceName = '/test/';
	//websocket所在的服务名称
	let socketServiceName = '/main-socket/';
	return baseUrl.replace(serviceName,socketServiceName);
}

4、部署注意点

项目部署时,需要在nginx上配置路由代理转发,将socket的请求转发到对应的服务中去。
以 http://127.0.0.1:8002/gateway/test/socket?xx=sss为例,
图例为同一服务器统一部署,根据自身部署策略修整。
在这里插入图片描述
解决可能发生的以下错误:
WebSocket connection to ‘xxx’ failed: Error during WebSocket handshake: Unexpected response code 200

### 使用 Spring BootVue 实现 WebSocket 功能集成 #### 添加依赖项 为了实现基于 Spring BootWebSocket 服务,首先需要引入必要的 Maven 或 Gradle 依赖。以下是 Maven 中的配置: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 上述依赖允许开发者构建支持 WebSocket 协议的服务端应用[^1]。 #### 创建 WebSocket 配置类 通过定义一个 `WebSocketConfig` 类来启用并配置 WebSocket 支持。该类需继承自 `AbstractWebSocketMessageBrokerConfigurer` 并重写相关方法: ```java @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); // 定义广播路径前缀 config.setApplicationDestinationPrefixes("/app"); // 应用程序消息路径前缀 } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").withSockJS(); // 注册 WebSocket 终端点 } } ``` 此代码片段实现了 STOMP over WebSocket 的基本设置,其中 `/ws` 是客户端连接到服务器的终端点地址[^2]。 #### 处理消息控制器 创建一个控制器用于接收和响应来自前端的消息。例如: ```java @Controller public class ChatController { @MessageMapping("/chat") @SendTo("/topic/messages") public Message sendMessage(Message message) throws Exception { Thread.sleep(100); // 模拟延迟处理时间 return new Message(message.getFrom(), message.getText()); } } // 数据传输对象 (DTO) class Message { private String from; private String text; // 构造函数、getter 和 setter 方法省略... } ``` 以上逻辑表明当接收到发往 `/chat` 路径的消息时会自动将其转发至订阅者列表中的所有客户机上显示出来[^3]。 #### 前端 Vue.js 实现Vue.js 方面,则可以通过 sockjs-client 及 stomp-js 这两个库完成与后台交互的部分工作流程如下所示: 安装所需 npm 包: ```bash npm install --save sockjs-client@latest stompjs@latest ``` 编写组件代码样例: ```javascript <script setup lang="ts"> import SockJS from 'sockjs-client'; import * as Stomp from 'stompjs'; let wsClient = null; onMounted(() => { const socket = new SockJS('http://localhost:8080/ws'); wsClient = Stomp.over(socket); wsClient.connect({}, frame => { console.log(`Connected: ${frame}`); wsClient.subscribe('/topic/messages', response => { console.log(response.body); }); }); }); const sendMessage = () => { if (!wsClient || !wsClient.connected) return alert("Not connected!"); let msgContent = document.getElementById("messageInput").value.trim(); if(msgContent.length === 0){ alert("Please enter a valid message."); return ; } wsClient.send("/app/chat", {}, JSON.stringify({from:"User",text:msgContent})); }; </script> <template> <div id="container"> <input type="text" placeholder="Type your message here..." id="messageInput"/> <button v-on:click="sendMessage">Send</button> </div> </template> ``` 上面展示了如何利用 JavaScript 封装好的 API 接口去建立持久化双向通信链路以及监听特定主题下的更新通知事件[^4][^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值