运维相关(一) - Vue项目配置WebSocket连接{ws、wss 连接方式}

Vue项目配置WebSocket连接 ws、wss 两种方式

    • 1. 写作背景
    • 2. 晒出代码
      • 2.1 前端 vue.config.js 的代码
      • 2.2 Vue项目路由配置代码
      • 3.3 服务器Nginx配置
    • 3. 使用方式
      • 3.1 前端代码
      • 3.2 后端代码
    • 4. 测试使用

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、https
const protocol = nowLocation.protocol;
// hostName => ip
const hostName = nowLocation.hostname;
// host => ip:port
const 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端点地址
let REQUEST_WEBSOCKET_URL_PREFIX = webSocketApiUrl + '/websocket/template-push/';
// 当前的WwebSocket对象
let CURRENT_SOCKET = null;
// 当前请求WebSocket的指令代码
let CURRENT_INDICATE_CODE = null;


let ENABLE_CONFIG = {

  WEBSOCKET_PUSH_VIDEO_ENABLE: true,
}

/**
 * 1. 初始化WebSocket连接对象
 * @param {*} clientKey 当前客户端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');
	};

	// 从服务器接受到信息时的回调函数
	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;
	};
}

function getWebSocketConnection() {
	return CURRENT_SOCKET;
}
  • 前端websocket向后端发送数据使用方式
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 {

    /**
     * 统计在线人数 线程安全的计数器 比原子更新类 效率更高 更专业
     */
    private final static LongAdder ONLINE_ADDER = new LongAdder();
    /**
     * 客户端 连接会话存储Map , 每个客户端对应一个唯一Id , 在当前端点中 唯一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. 有新的连接访问当前 websocket 地址
     *
     * @param session      当前客户端的服务器对象 session
     * @param clientCode   客户端设备唯一code码
     */
    @OnOpen
    public void doConnectionSocket(Session session, @PathParam("clientKey") String clientCode) {

        // 前端异常 通过抓包发送 则直接关闭当前创建的session对象
        if (StringUtils.isEmpty(clientCode)) {
            try {
                session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "参数不合法,已关闭当前连接!"));
            } catch (IOException e) {
                WEBSOCKET_TEMPLATE_PUSH_LOGGER.error(e.getMessage());
            }
        }
        // 正常则建立连接 存储数据 并返回连接成功
        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-连接成功,此刻连接设备码为: [{}] , 此刻在线的连接数为:[{}]", clientCode, ONLINE_ADDER.sum());
        }
    }

    /**
     * 2. 关闭当前websocket连接
     * @param session
     */
    @OnClose
    public void doCloseSocket(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数据
     */
    @OnMessage
    public void receiveMessage(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
@AllArgsConstructor
public class WebSocketTemplateSession {

    private Session session;

    private String clientKey;

}

4. 测试使用

  • 本地的 ws 方式
    在这里插入图片描述

  • 服务器的 wss 方式在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/126198.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构与算法—双链表

前言 前面有很详细的讲过线性表(顺序表和链表)&#xff0c;当时讲的链表以单链表为主&#xff0c;但在实际应用中双链表有很多应用场景&#xff0c;例如大家熟知的LinkedList。 双链表与单链表区别 单链表和双链表都是线性表的链式实现&#xff0c;它们的主要区别在于节点结构…

lua 时间差功能概略

简介 在进行程序设计过程中&#xff0c;经常需要对某些函数、某些程序片断从开始运行到运行结束所耗费的时间进行一些量化。这种量化实际上就是计算时间差。 获取函数耗时情景如下&#xff1a; function time_used() --开始计时-- do something at here. --结束计时--时间差&…

交易所开发搭建

在当今的数字货币市场中&#xff0c;交易所开发搭建已经成为了一个重要的领域。交易所是数字货币交易的主要场所&#xff0c;为投资者提供了安全、可靠、高效的交易服本务文。将详细介绍交易所开发搭建的整个流程&#xff0c;包括需求分析、设计、技术选型、开发、测试和上线等…

【2】Spring Boot 3 项目搭建

目录 【2】Spring Boot 3 初始项目搭建项目生成1. 使用IDEA商业版创建2. 使用官方start脚手架创建 配置与启动Git版本控制 个人主页: 【⭐️个人主页】 需要您的【&#x1f496; 点赞关注】支持 &#x1f4af; 【2】Spring Boot 3 初始项目搭建 项目生成 1. 使用IDEA商业版创…

【Element】隐藏 el-table 展开行的箭头

需求 点击行展开行&#xff0c;隐藏箭头 方法 首先需求是点击行显示展开行 row-click"rowClick"const rowClick (row: any, column: any, event: any) > {console.log(row, column, event)if (multipleTable.value) {multipleTable.value.toggleRowExpansio…

PostgreSQL 技术内幕(十一)位图扫描

扫描算子在上层计算和底层存储之间&#xff0c;向下扫描底层存储的数据&#xff0c;向上作为计算的输入源&#xff0c;在SQL的执行层中&#xff0c;起着关键的作用。顺序、索引、位图等不同类型的扫描算子适配不同的数据分布场景。然而&#xff0c;扫描算子背后的实现原理是怎样…

投资自己,成就未来——人大女王金融硕士助力您成为金融领域的佼佼者

在这个日新月异的时代&#xff0c;金融行业的发展日益繁荣&#xff0c;对于金融人才的需求也越来越大。为了应对这一挑战&#xff0c;越来越多的人选择投身金融领域&#xff0c;提升自己的专业素养。而中国人民大学女王金融硕士项目&#xff0c;正是为了满足这一需求而设立的&a…

JVM在线分析-解决问题的工具一(jinfo,jmap,jstack)

1. jinfo (base) PS C:\Users\zishi\Desktop> jinfo Usage:jinfo <option> <pid>(to connect to a running process)where <option> is one of:-flag <name> to print the value of the named VM flag #输出对应名称的参数-flag [|-]<n…

Pandas数据预处理Pandas合并数据集在线闯关_头歌实践教学平台

Pandas数据预处理合并数据集 第1关 Concat与Append操作第2关 合并与连接第3关 案例&#xff1a;美国各州的统计数据 第1关 Concat与Append操作 任务描述 本关任务&#xff1a;使用read_csv()读取两个csv文件中的数据&#xff0c;将两个数据集合并&#xff0c;将索引设为Ladder…

element ui:常用的组件使用情况记录

前言 将element ui使用过程中一些常用的组件使用情况记录如下 组件 el-tree树组件 树父子节点成一列显示 没有进行设置之前显示效果 设置之后显示效果 ​​​​ 主要代码如下 <el-treeicon-class"none"expand-on-click-node"false"style"…

震裕科技-300953 三季报分析(20231108)

震裕科技-300953 基本情况 公司名称&#xff1a;宁波震裕科技股份有限公司 A股简称&#xff1a;震裕科技 成立日期&#xff1a;1994-10-18 上市日期&#xff1a;2021-03-18 所属行业&#xff1a;专用设备制造业 周期性&#xff1a;0 主营业务&#xff1a;精密级进冲压模具及下游…

Word通过Adobe打印PDF时总是报错,打开记事本

Word文档打印&#xff0c;选择Adobe作为打印机&#xff0c;打印过程中总是报错&#xff0c;不断打开记事本&#xff0c;提示打印出错&#xff0c;错误信息如下&#xff1a; %%[ ProductName: Distiller ]%% %%[Page: 1]%% %%[Page: 2]%% %%[ Error: invalidfont; OffendingCom…

Scala中编写多线程爬虫程序并做可视化处理

在Scala中编写一个爬虫程序来爬取店铺商品并进行可视化处理&#xff0c;需要使用Selenium和Jsoup库来操作网页。在这个例子中&#xff0c;我们将使用多线程来提高爬取速度。 1、首先&#xff0c;我们需要引入所需的库&#xff1a; import org.openqa.selenium.By import org.o…

【Unity之UI编程】在Unity中如何打图集,来降低DrowCall

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;UI_…

Mysql 不同存储引擎数据文件的形式详解

目录 MyISAM MERGE InnoDB Memory Archive CSV BLACKHOLE MySQL 中的每一个数据表在磁盘上至少被表示为一个文件&#xff0c;即存放着该数据表结构定义的 .frm 文件。不同的存储引擎还有其它用来存放数据和索引信息的文件。 从 MySQL 8.0 版本开始&#xff0c;frm 表结构…

[HCTF 2018]WarmUp全网最详细解释

查看源码找到提示 访问source.php 代码审计&#xff1a; class emmm{public static function checkFile(&$page){$whitelist ["source">"source.php","hint">"hint.php"]; 定义了一个名为emmm的类&#xff0c;在该类中有…

Linux之IPC通信共享内存与消息队列、管道、信号量、socket内存拷贝实例总结(六十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

射频功率放大器应用中GaN HEMT的表面电势模型

标题&#xff1a;A surface-potential based model for GaN HEMTs in RF power amplifier applications 来源&#xff1a;IEEE IEDM 2010 本文中的任何第一人称都为论文的直译 摘要&#xff1a;我们提出了第一个基于表面电位的射频GaN HEMTs紧凑模型&#xff0c;并将我们的工…

ChatGPT如何管理对话历史?

问题 由于现在开始大量使用ChatGPT对话功能&#xff0c;认识到他在提供启发方面具有一定价值。比如昨天我问他关于一个微习惯的想法&#xff0c;回答的内容还是很实在&#xff0c;而且能够通过他的表达理解自己的问题涉及到的领域是什么。 此外&#xff0c;ChatGPT能够总结对话…