Vue项目配置WebSocket连接 ws、wss 两种方式
1. 写作背景
项目使用的是ruoyi的前后端分离框架
- 项目需要使用到 websocket , 在本地使用 ws 连接方式是没问题 , 但是服务器上边使用的是nginx + ssl 证书 https域名访问的方式部署的
使用普通的 ws 连接是不可以成功的 需要使用 wss的方式
2. 晒出代码
2.1 前端 vue.config.js 的代码
这里target: 里边指向的都是后端server的地址 16000是我后端服务的端口 , 我这里websocket服务和普通的业务项目用的都是一个项目 所以都是16000端口
devServer:{host:'0.0.0.0',port: port,open:true,proxy:{// detail: https://cli.vuejs.org/config/#devserver-proxy// 正常的 http 请求代理[process.env.VUE_APP_BASE_API]:{target:`http://localhost:16000`,changeOrigin:true,pathRewrite:{['^'+ process.env.VUE_APP_BASE_API]:''}},// websocket ws 的代理路由配置[process.env.VUE_APP_WEBSOCKET_API]:{target:`ws://localhost:16000`,changeOrigin:true,ws:true,pathRewrite:{['^'+ process.env.VUE_APP_WEBSOCKET_API]:''}},// websocket wss 的代理路由配置[process.env.VUE_APP_WSS_WEBSOCKET_API]:{target:`wss://域名:16000`,changeOrigin:true,ws:true,pathRewrite:{['^'+ process.env.VUE_APP_WSS_WEBSOCKET_API]:''}}},disableHostCheck:true},
2.2 Vue项目路由配置代码
为什么要配置两个地址呢? , 因为在本次测试的时候使用的是普通的ws方式连接 所以为了方便切换就写了两个websocket代理路由
- .env.development 文件和 .env.production 文件都加上这两行代码即可
// WebSocket地址VUE_APP_WEBSOCKET_API='/websocket-api'// WebSocket wss 地址VUE_APP_WSS_WEBSOCKET_API='/wss-websocket-api'
3.3 服务器Nginx配置
server {
add_header X-Frame-Options ALLOWALL;
listen 8681 ssl;
server_name 域名; #需要将yourdomain.com替换成证书绑定的域名。
root ..\html;
index index.html index.htm;
ssl_certificate pem文件地址; #需要将cert-file-name.pem替换成已上传的证书文件的名称。
ssl_certificate_key文件地址; #需要将cert-file-name.key替换成已上传的证书私钥文件的名称。
ssl_session_timeout 6m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #表示使用的TLS协议的类型。
ssl_prefer_server_ciphers on;
#charset koi8-r;
access_log logs/host.access.log main;
#默认目录
location / {
root C:/xxx/dist;
index index.html;
try_files $uri $uri/ @router;
}
location @router {
rewrite ^.*$ /index.html last;
}
#vue二级目录代理
location /admin {
alias /root/www/admin;
index index.html;
try_files $uri $uri/ /index.html last;
}
location /prod-api {
rewrite ^/prod-api/(.*)$ /$1 break;
proxy_pass http://localhost:16000;
proxy_set_header Host $host;
add_header X-Frame-Options ALLOWALL;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header authorization $http_authorization;
}
# websocket wss 连接方式的路由代理配置
location /wss-websocket-api {
rewrite ^/wss-websocket-api/(.*)$ /$1 break;
proxy_pass http://localhost:16000; #通过配置端口指向部署websocker的项目
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-real-ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
3. 使用方式
3.1 前端代码
// 当前浏览器Location对象const nowLocation = window.location;// 协议 => http、httpsconst protocol = nowLocation.protocol;// hostName => ipconst hostName = nowLocation.hostname;// host => ip:portconst host = nowLocation.host;// websocket api 地址// 这个判断就是根据当前项目环境 自动确定使用 ws 还是 wss 的路由地址const websocket_pattern =(hostName =='域名')?'wss-websocket-api':'websocket-api';// websocket 请求地址前缀const webSocketApiUrl =((protocol.startsWith('https'))?'wss://':'ws://')+ host +'/'+ websocket_pattern;// 当前WebSocket的请求地址前缀,// /websocket/template-push/ 就是我后端配置的websocket端点地址letREQUEST_WEBSOCKET_URL_PREFIX= webSocketApiUrl +'/websocket/template-push/';// 当前的WwebSocket对象letCURRENT_SOCKET=null;// 当前请求WebSocket的指令代码letCURRENT_INDICATE_CODE=null;letENABLE_CONFIG={WEBSOCKET_PUSH_VIDEO_ENABLE:true,}/**
* 1. 初始化WebSocket连接对象
* @param {*} clientKey 当前客户端Key
*/functionopenWebSocket(clientKey){if(CURRENT_SOCKET!=null){CURRENT_SOCKET.close();CURRENT_SOCKET=null;}CURRENT_SOCKET=newWebSocket(REQUEST_WEBSOCKET_URL_PREFIX+ clientKey);CURRENT_SOCKET.onopen=event=>{
console.log('连接Socket');};// 从服务器接受到信息时的回调函数CURRENT_SOCKET.onmessage=event=>{
console.log('收到服务器响应 , 响应数据信息==>', event.data);};CURRENT_SOCKET.onclose=event=>{
console.log('关闭Socket连接!');};//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload=()=>{CURRENT_SOCKET.close();CURRENT_SOCKET=null;};}functiongetWebSocketConnection(){returnCURRENT_SOCKET;}
- 前端websocket向后端发送数据使用方式
let sendData ={};getWebSocketConnection().send(JSON.stringify(sendData));
3.2 后端代码
packagecom.ruoyi.web.controller.websocket;importcom.alibaba.fastjson2.JSONException;importcom.alibaba.fastjson2.JSONObject;importcom.ruoyi.common.utils.StringUtils;importcom.ruoyi.websocket.WebSocketClientIndicate;importcom.ruoyi.websocket.WebSocketRequest;importcom.ruoyi.websocket.WebSocketTemplateSession;importlombok.RequiredArgsConstructor;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.stereotype.Component;importjavax.websocket.CloseReason;importjavax.websocket.OnClose;importjavax.websocket.OnMessage;importjavax.websocket.OnOpen;importjavax.websocket.Session;importjavax.websocket.server.PathParam;importjavax.websocket.server.ServerEndpoint;importjava.io.IOException;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;importjava.util.concurrent.atomic.LongAdder;/**
* --->
*
* @author xqh , [email protected]
* @data 2023-11-02 15:46:51
*/@Component@ServerEndpoint("/websocket/template-push/{clientKey}")@RequiredArgsConstructorpublicclassWebSocketTemplateInfoPushServer{/**
* 统计在线人数 线程安全的计数器 比原子更新类 效率更高 更专业
*/privatefinalstaticLongAdderONLINE_ADDER=newLongAdder();/**
* 客户端 连接会话存储Map , 每个客户端对应一个唯一Id , 在当前端点中 唯一Id为Session Id
*/privatefinalstaticMap<String,WebSocketTemplateSession>SESSION_MAP=newConcurrentHashMap<>();/**
* 通过 clientKey 反查 sessionId , key为clientKey , value 为sessionId
*/privatefinalstaticMap<String,String>CLIENT_KEY_SESSION_STORE_MAP=newConcurrentHashMap<>();privatestaticfinalLoggerWEBSOCKET_TEMPLATE_PUSH_LOGGER=LoggerFactory.getLogger(WebSocketTemplateInfoPushServer.class);/**
* 1. 有新的连接访问当前 websocket 地址
*
* @param session 当前客户端的服务器对象 session
* @param clientCode 客户端设备唯一code码
*/@OnOpenpublicvoiddoConnectionSocket(Session session,@PathParam("clientKey")String clientCode){// 前端异常 通过抓包发送 则直接关闭当前创建的session对象if(StringUtils.isEmpty(clientCode)){try{
session.close(newCloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT,"参数不合法,已关闭当前连接!"));}catch(IOException e){WEBSOCKET_TEMPLATE_PUSH_LOGGER.error(e.getMessage());}}// 正常则建立连接 存储数据 并返回连接成功else{String sessionId = session.getId();SESSION_MAP.put(sessionId,newWebSocketTemplateSession(session,clientCode));CLIENT_KEY_SESSION_STORE_MAP.put(sessionId,clientCode);ONLINE_ADDER.increment();WEBSOCKET_TEMPLATE_PUSH_LOGGER.info("WebSocket-连接成功,此刻连接设备码为: [{}] , 此刻在线的连接数为:[{}]", clientCode,ONLINE_ADDER.sum());}}/**
* 2. 关闭当前websocket连接
* @param session
*/@OnClosepublicvoiddoCloseSocket(Session session){try{String sessionId = session.getId();WebSocketTemplateSession doCloseSession =SESSION_MAP.get(sessionId);
doCloseSession.getSession().close();// 清除当前关联的Session信息SESSION_MAP.remove(sessionId);CLIENT_KEY_SESSION_STORE_MAP.remove(sessionId);ONLINE_ADDER.decrement();WEBSOCKET_TEMPLATE_PUSH_LOGGER.info("WebSocket-连接关闭,此刻在线的连接数为:[{}]",ONLINE_ADDER.sum());}catch(IOException e){WEBSOCKET_TEMPLATE_PUSH_LOGGER.error(e.getMessage());}}/**
* 3. 接收客户端主动发送的消息数据
* @param session 当前会话
* @param jsonMessage 客户端发送的JSON数据
*/@OnMessagepublicvoidreceiveMessage(Session session ,String jsonMessage){try{// 收到前端发送的信息}catch(JSONException jsonException){WEBSOCKET_TEMPLATE_PUSH_LOGGER.error("JSON格式有误,异常信息->[{}]", jsonException.getMessage());}catch(Exception e){WEBSOCKET_TEMPLATE_PUSH_LOGGER.error("接收信息接口失败,异常信息->[{}]", e.getMessage());}}}
- WebSocketTemplateSession
@Data@AllArgsConstructorpublicclassWebSocketTemplateSession{privateSession session;privateString clientKey;}
4. 测试使用
- 本地的 ws 方式
- 服务器的 wss 方式
版权归原作者 ~奇思妙想的王多鱼 所有, 如有侵权,请联系我们删除。