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

news2024/11/23 18:39:52

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 方式在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

上市公司-赫芬达尔指数(2000-2022年)(数据+2种结果)

上市公司-赫芬达尔指数&#xff08;HHI&#xff09;可衡量一个公司在市场中的相对份额或集中度。它是由每家公司在市场中份额的平方和得到的。指数值越高&#xff0c;表示该市场或行业的集中度越高&#xff0c;竞争可能相对较小&#xff1b;而指数值越低&#xff0c;则意味着该…

如何让群晖Audio Station公开共享的本地音频公网可访问?

文章目录 1. 本教程使用环境&#xff1a;2. 制作音频分享链接3. 制作永久固定音频分享链接&#xff1a; 之前文章我详细介绍了如何在公网环境下使用pc和移动端访问群晖Audio Station&#xff1a; 公网访问群晖audiostation听歌 - cpolar 极点云 群晖套件不仅能读写本地文件&a…

公众号开发实践:用PHP实现通过接口自定义微信公众号菜单

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师…

Apache DolphinScheduler如何完全设置东八区?

默认情况 为了兼容全世界不同时区&#xff0c;Apache DolphinScheduler 使用的是 UTC 0 时区&#xff0c;包括保存到数据库表中的数据时区&#xff0c;以及展示到页面上的时区。 如果我们想在页面上看到东八区时间&#xff0c;则需要在页面上手动选择上海时区&#xff0c;如下…

小程序订单中心path设置本次审核不通过,审核原因:小程序尚未发布,无法审核。

小程序尚未发布&#xff0c;无法审核。 先按照这篇文章把小程序审核通过&#xff0c;小程序版本审核未通过&#xff0c;需在开发者后台「版本管理—提交审核——小程序订单中心path」设置订单中心页path&#xff0c;请设置后再提交代码审核 小程序审核通过后&#xff0c;发布…

2023.11.09 homework

4年级数学&#xff1a;不会做就作图&#xff0c;画出来&#xff0c;简单化就容易懂 2023.11.09 homework &#xff08;2&#xff09;-CSDN博客

MySQL代码子查询续集

dept表&#xff1a; emp表&#xff1a; -- 查询每个部门的信息(包括&#xff1a;部门名&#xff0c;编号&#xff0c;地址)和人员数量 -- 1.将两张表结合起来&#xff0c;筛选出部门名&#xff0c;编号&#xff0c;地址 tips: 表.* 表示将该表所有列都显示出来&#xff…

基于SpringBoot+Vue的婚恋相亲交友系统

基于SpringBootVue的婚恋相亲交友系统~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 管理员界面 摘要 基于SpringBootVue的婚恋相亲交友系统是一个现代化的、高效的交…

【算法设计】回溯法算法设计——骑士游历问题(C++实现)

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 更多算法分析与设计知识专栏&#xff1a;算法分析&#x1f525; 给大家跳…

【19】c++11新特性 —>线程异步

什么是异步&#xff1f; async的两种方式 //方式1 async( Function&& f, Args&&... args ); //方式2 async( std::launch policy, Function&& f, Args&&... args );函数参数&#xff1a; f:任务函数 Args:传递给f的参数 policy:可调用对象f的…

小解List的使用【C++】

小解List的使用【C】 一. List1.1. 与vector的不同1.2 与vector的使用不同1.2.1 迭代器失效1.2.2. insert1.2.3 erase1.2.4 sort1.3. 其他接口 补充迭代器容器与迭代器的关系迭代器的类型 一. List 学习了STL&#xff0c;也已经到了List的内容 因为List与string以及vector比起…

【Kurbernetes资源管理】陈述式资源管理方式

陈述式 一、 理论部分1.1 管理K8s资源的基本方法1.1.1 陈述式资源管理方式1.1.2声明式资源管理方式1.1.3 GUI式资源管理方法 1.2 陈述式资源管理方式1.2.1 Kubelet工具简介1.2.2 kubectl 的基本语法1.2.3 Kubectl工具的自动补全功能 1.3 Kubernetes Service1.4 Service 的类型(…

Python教程之字典(Dictionary)操作详解

文章目录 前言一、创建字典二、访问字典里的值三、访问字典里的值四、删除字典元素五、字典键的特性六、字典内置函数&方法七、字典练习代码关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Py…

11-09 周四 CNN 卷积神经网络基础知识

11-09 周四 CNN 卷积神经网络 时间版本修改人描述2023年11月9日09:38:12V0.1宋全恒新建文档 简介 学习一下CNN&#xff0c;卷积神经网络。使用的视频课程。视觉相关的任务&#xff1a; 人脸识别 卷积网络与传统网络的区别&#xff1a; <img altimage-20231109094400591 s…

soildworks2022如何快速更改草图基准面?

1.有时候拉伸切除的时候会显示“重建模型错误”&#xff0c;原因为切除的方向有问题&#xff0c;可以通过拉切除箭头的切除方向来解决&#xff1b; 2.有时候一个面需要正面切一部分&#xff0c;背面再切一部分&#xff0c;不是很好整&#xff0c;那么就得更改草图基准面&#x…

MySQL中表的增删查改(进阶),超详细!

目录 一、数据库的约束 1、约束类型 2、NULL约束 3、UNIQUE&#xff1a;唯一约束 4、DEFAULT&#xff1a;默认值约束 5、PRIMARY KEY&#xff1a;主键约束&#xff08;主键只能定义一个&#xff0c;NOT NULL 和 UNIQUE 的结合&#xff09; 6、FOREIGN KEY&#xff1a;外键约…

在Google Kubernetes集群创建分布式Jenkins(二)

上一篇博客在Google Kubernetes集群创建分布式Jenkins(一)-CSDN博客我介绍了如何在GCP的K8S集群上部署一个分布式的Jenkins&#xff0c;并实现了一个简单的Pipeline的运行。 在实际的开发中&#xff0c;我们通常都会按照以下的CICD流程来设置Pipeline 在我司的实际实践中&…

手把手 Golang 实现静态图像与视频流人脸识别

说起人脸识别&#xff0c;大家首先想到的实现方式应该是 Python 去做相关的处理&#xff0c;因为相关的机器学习框架&#xff0c;库都已经封装得比较好了。但是我们今天讨论的实现方式换成 Golang&#xff0c;利用 Golang 去做静态图像和视频流人脸识别的相应处理。 静态图像人…

【leaflet】1. 初见

▒ 目录 ▒ &#x1f6eb; 导读需求开发环境 1️⃣ 概念概念解释特点 2️⃣ 学习路线图3️⃣ html示例&#x1f6ec; 文章小结&#x1f4d6; 参考资料 &#x1f6eb; 导读 需求 要做游戏地图了&#xff0c;看到大量产品都使用的leaflet&#xff0c;所以开始学习这个。 开发环境…