1. 寫作背景
項(xiàng)目使用的是ruoyi的前后端分離框架
- 項(xiàng)目需要使用到 websocket , 在本地使用 ws 連接方式是沒問題 , 但是服務(wù)器上邊使用的是nginx + ssl 證書 https域名訪問的方式部署的
使用普通的 ws 連接是不可以成功的 需要使用 wss的方式
2. 曬出代碼
2.1 前端 vue.config.js 的代碼
-
這里target: 里邊指向的都是后端server的地址 16000是我后端服務(wù)的端口 , 我這里websocket服務(wù)和普通的業(yè)務(wù)項(xiàng)目用的都是一個(gè)項(xiàng)目 所以都是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項(xiàng)目路由配置代碼
-
為什么要配置兩個(gè)地址呢? , 因?yàn)樵诒敬螠y試的時(shí)候使用的是普通的ws方式連接 所以為了方便切換就寫了兩個(gè)websocket代理路由
- .env.development 文件和 .env.production 文件都加上這兩行代碼即可
// WebSocket地址
VUE_APP_WEBSOCKET_API = '/websocket-api'
// WebSocket wss 地址
VUE_APP_WSS_WEBSOCKET_API = '/wss-websocket-api'
3.3 服務(wù)器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協(xié)議的類型。
ssl_prefer_server_ciphers on;
#charset koi8-r;
access_log logs/host.access.log main;
#默認(rèn)目錄
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的項(xiàng)目
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 前端代碼
// 當(dāng)前瀏覽器Location對象
const nowLocation = window.location;
// 協(xié)議 => http、https
const protocol = nowLocation.protocol;
// hostName => ip
const hostName = nowLocation.hostname;
// host => ip:port
const host = nowLocation.host;
// websocket api 地址
// 這個(gè)判斷就是根據(jù)當(dāng)前項(xiàng)目環(huán)境 自動確定使用 ws 還是 wss 的路由地址
const websocket_pattern = (hostName == '域名') ? 'wss-websocket-api' : 'websocket-api';
// websocket 請求地址前綴
const webSocketApiUrl = ((protocol.startsWith('https')) ? 'wss://' : 'ws://') + host + '/' + websocket_pattern;
// 當(dāng)前WebSocket的請求地址前綴,
// /websocket/template-push/ 就是我后端配置的websocket端點(diǎn)地址
let REQUEST_WEBSOCKET_URL_PREFIX = webSocketApiUrl + '/websocket/template-push/';
// 當(dāng)前的WwebSocket對象
let CURRENT_SOCKET = null;
// 當(dāng)前請求WebSocket的指令代碼
let CURRENT_INDICATE_CODE = null;
let ENABLE_CONFIG = {
WEBSOCKET_PUSH_VIDEO_ENABLE: true,
}
/**
* 1. 初始化WebSocket連接對象
* @param {*} clientKey 當(dāng)前客戶端Key
*/
function openWebSocket(clientKey) {
if (CURRENT_SOCKET != null) {
CURRENT_SOCKET.close();
CURRENT_SOCKET = null;
}
CURRENT_SOCKET = new WebSocket(REQUEST_WEBSOCKET_URL_PREFIX + clientKey);
CURRENT_SOCKET.onopen = event => {
console.log('連接Socket');
};
// 從服務(wù)器接受到信息時(shí)的回調(diào)函數(shù)
CURRENT_SOCKET.onmessage = event => {
console.log('收到服務(wù)器響應(yīng) , 響應(yīng)數(shù)據(jù)信息==>' , event.data);
};
CURRENT_SOCKET.onclose = event => {
console.log('關(guān)閉Socket連接!');
};
//監(jiān)聽窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時(shí),主動去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口,server端會拋異常。
window.onbeforeunload = () => {
CURRENT_SOCKET.close();
CURRENT_SOCKET = null;
};
}
function getWebSocketConnection() {
return CURRENT_SOCKET;
}
- 前端websocket向后端發(fā)送數(shù)據(jù)使用方式
let sendData = {};
getWebSocketConnection().send(JSON.stringify(sendData));
3.2 后端代碼
package com.ruoyi.web.controller.websocket;
import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.websocket.WebSocketClientIndicate;
import com.ruoyi.websocket.WebSocketRequest;
import com.ruoyi.websocket.WebSocketTemplateSession;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.CloseReason;
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.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;
/**
* --->
*
* @author xqh , 987072248@qq.com
* @data 2023-11-02 15:46:51
*/
@Component
@ServerEndpoint("/websocket/template-push/{clientKey}")
@RequiredArgsConstructor
public class WebSocketTemplateInfoPushServer {
/**
* 統(tǒng)計(jì)在線人數(shù) 線程安全的計(jì)數(shù)器 比原子更新類 效率更高 更專業(yè)
*/
private final static LongAdder ONLINE_ADDER = new LongAdder();
/**
* 客戶端 連接會話存儲Map , 每個(gè)客戶端對應(yīng)一個(gè)唯一Id , 在當(dāng)前端點(diǎn)中 唯一Id為Session Id
*/
private final static Map<String, WebSocketTemplateSession> SESSION_MAP = new ConcurrentHashMap<>();
/**
* 通過 clientKey 反查 sessionId , key為clientKey , value 為sessionId
*/
private final static Map<String, String> CLIENT_KEY_SESSION_STORE_MAP = new ConcurrentHashMap<>();
private static final Logger WEBSOCKET_TEMPLATE_PUSH_LOGGER = LoggerFactory.getLogger(WebSocketTemplateInfoPushServer.class);
/**
* 1. 有新的連接訪問當(dāng)前 websocket 地址
*
* @param session 當(dāng)前客戶端的服務(wù)器對象 session
* @param clientCode 客戶端設(shè)備唯一code碼
*/
@OnOpen
public void doConnectionSocket(Session session, @PathParam("clientKey") String clientCode) {
// 前端異常 通過抓包發(fā)送 則直接關(guān)閉當(dāng)前創(chuàng)建的session對象
if (StringUtils.isEmpty(clientCode)) {
try {
session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "參數(shù)不合法,已關(guān)閉當(dāng)前連接!"));
} catch (IOException e) {
WEBSOCKET_TEMPLATE_PUSH_LOGGER.error(e.getMessage());
}
}
// 正常則建立連接 存儲數(shù)據(jù) 并返回連接成功
else {
String sessionId = session.getId();
SESSION_MAP.put(sessionId, new WebSocketTemplateSession(session,clientCode));
CLIENT_KEY_SESSION_STORE_MAP.put(sessionId,clientCode);
ONLINE_ADDER.increment();
WEBSOCKET_TEMPLATE_PUSH_LOGGER.info("WebSocket-連接成功,此刻連接設(shè)備碼為: [{}] , 此刻在線的連接數(shù)為:[{}]", clientCode, ONLINE_ADDER.sum());
}
}
/**
* 2. 關(guān)閉當(dāng)前websocket連接
* @param session
*/
@OnClose
public void doCloseSocket(Session session) {
try {
String sessionId = session.getId();
WebSocketTemplateSession doCloseSession = SESSION_MAP.get(sessionId);
doCloseSession.getSession().close();
// 清除當(dāng)前關(guān)聯(lián)的Session信息
SESSION_MAP.remove(sessionId);
CLIENT_KEY_SESSION_STORE_MAP.remove(sessionId);
ONLINE_ADDER.decrement();
WEBSOCKET_TEMPLATE_PUSH_LOGGER.info("WebSocket-連接關(guān)閉,此刻在線的連接數(shù)為:[{}]", ONLINE_ADDER.sum());
} catch (IOException e) {
WEBSOCKET_TEMPLATE_PUSH_LOGGER.error(e.getMessage());
}
}
/**
* 3. 接收客戶端主動發(fā)送的消息數(shù)據(jù)
* @param session 當(dāng)前會話
* @param jsonMessage 客戶端發(fā)送的JSON數(shù)據(jù)
*/
@OnMessage
public void receiveMessage(Session session , String jsonMessage){
try {
// 收到前端發(fā)送的信息
} catch (JSONException jsonException){
WEBSOCKET_TEMPLATE_PUSH_LOGGER.error("JSON格式有誤,異常信息->[{}]" , jsonException.getMessage());
} catch (Exception e){
WEBSOCKET_TEMPLATE_PUSH_LOGGER.error("接收信息接口失敗,異常信息->[{}]" , e.getMessage());
}
}
}
- WebSocketTemplateSession
@Data
@AllArgsConstructor
public class WebSocketTemplateSession {
private Session session;
private String clientKey;
}
4. 測試使用
-
本地的 ws 方式
文章來源:http://www.zghlxwxcb.cn/news/detail-790443.html
-
服務(wù)器的 wss 方式
文章來源地址http://www.zghlxwxcb.cn/news/detail-790443.html
到了這里,關(guān)于運(yùn)維相關(guān)(一) - Vue項(xiàng)目配置WebSocket連接{ws、wss 連接方式}的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!