上篇文章中提到了最近要做一个进度条的功能,但实际比进度条要复杂,有可能用在上传文件或者是服务端处理的进度或者是聊天助手等等,所以选择了用WebSocket。以下是我封装的WebSocket工具类及使用方案,目前还没有加上断线重连功能,后续会优化
包括的功能:
带有token验证 支持心跳检测 代理配置
工具类的封装
在units文件夹下新建一个叫WebsocketService.ts的文件
import {ElMessage} from 'element-plus';
/**
* 消息状态
* @enum 1000 连接已正常关闭
* @enum 1001 终端离开或重新加载页面
* @enum 1002 协议错误,无法解析收到的数据
* @enum 1003 数据类型不符合要求
* @enum 1006 连接已关闭,无法建立连接
*/
export default class WebsocketService {
public readonly socket: any
private readonly promises: any
private isConnected: boolean | undefined // 连接状态
private message: string | undefined // 消息内容
private reconnectError: boolean | undefined// 重新连接错误
private readonly heartBeatInterval: number | undefined// 心跳消息发送时间
private heartBeatTimer: number | undefined // 心跳定时器
private baseUrl: string | undefined //服务URL
private readonly token: string | undefined
constructor(connectUrl: string, onOpenCallback?: Function | undefined, onMessageCallback?: Function | undefined, onCloseCallback?: Function | undefined, onErrorCallback?: Function | undefined) {
if (typeof WebSocket !== "undefined") {
this.promises = {};
this.isConnected = false
this.message = ""
this.reconnectError = false
this.heartBeatInterval = 50000
this.heartBeatTimer = 0
this.baseUrl = (window.location.protocol.indexOf('https') !== -1 ? 'wss' : 'ws') + '://' + window.location.host + '/ws_proxy' //注意 ws_proxy是在vue.config.js单独配的 见下文
this.token = window.sessionStorage.getItem("token") || ""
// 这里通过子协议的方式将token传递给服务端,
// 传递形式为:在请求的requestHeader中会有一个叫“Sec-Websocket-Protocol: 你的token串 ”的请求头
// 在开发者工具中查看请求时注意筛选ws的请求,而不是Fetch/XHR
this.socket = new window.WebSocket(this.baseUrl + connectUrl, [this.token])
this.socket.onopen = (e: any) => this.handleOpen(this, e, onOpenCallback);
this.socket.onmessage = (e: any) => this.handleMessage(this, e, onMessageCallback);
this.socket.onclose = (e: any) => this.handleClose(this, e, onCloseCallback);
this.socket.onerror = (e: any) => this.handleError(this, e, onErrorCallback);
} else {
ElMessage({
message: "您的浏览器不支持WebSocket",
type: 'error',
})
}
}
protected handleOpen(that: any, e: any, callBack?: Function | undefined) {
that.isConnected = true
that.heartBeatTimer = window.setInterval(() => {
const message = 'keep heartbeat'
that.sendMessage("heart_beat", message)
}, that.heartBeatInterval);
if (callBack) callBack(e)
}
protected handleMessage(that: any, e: any, callBack?: Function | undefined) {
if (callBack) callBack(e)
}
protected handleClose(that: any, e: any, callBack?: Function | undefined) {
that.isConnected = false
clearInterval(this.heartBeatTimer)
that.heartBeatTimer = 0
that.socket.close()
if (callBack) callBack(e)
}
protected close() {
this.isConnected = false
clearInterval(this.heartBeatTimer)
this.heartBeatTimer = 0
this.socket.close()
}
protected handleError(that: any, e: any, callBack: Function | undefined) {
if (this.socket.readyState !== 3) {
ElMessage({
message: "连接异常,请尝试重新连接",
type: 'error',
})
}
if (callBack) callBack(e)
}
protected sendMessage(type: string, content: any) {
this.socket.send(JSON.stringify({type: type, content: content}));
}
}
业务页面使用方案
import socket from "@/utils/WebsocketService";
const progressPathName = "/ws" //你的服务端相对路径
const createProgressConnection = () => {
progressSocket = new socket(progressPathName, undefined, onMessage, onClose, onError)
}
const onMessage = (e: any) => {
const receivedData = JSON.parse(e.data.replace(new RegExp("\\\\\"", "gm"), "\"")) //反序列化服务端发送过来的消息
const {type} = receivedData
if (type === "xxx") { //拿到对应的第一条业务功能标识比如uuid等,因为你要区分业务逻辑啊
window.sessionStorage.setItem("xxx", receivedData.xx) //将业务数据保存到本地,或者你自己的逻辑
progressSocket.socket.send(JSON.stringify({
... //发送你的业务数据给服务端
}))
} else if (type === "XXX") {
... // todo something
} else if (type === "close") {
progressSocket.socket.close()
}
}
const onError = () => {
progressSocket.socket.close() // 或者你自己的逻辑
}
const onClose = (e: any) => {
progressSocket.socket.close()
let token = window.sessionStorage.getItem("token") || ""
if (e.target && e.target.protocol === "") {
// 验证失败
ElMessage({
message: "用户验证失败,请尝试重新登陆",
type: 'error',
})
} else if (e.target && e.target.protocol === token) {
}
}
代理配置之 vue.config.js
const {defineConfig} = require('@vue/cli-service')
module.exports = defineConfig({
lintOnSave: false,
transpileDependencies: true,
devServer: {
host: '127.0.0.1',
port: 3001,
open: true,
proxy: {
'/use_proxy': {
target: process.env.VUE_APP_API_URL,
pathRewrite: {'^/use_proxy': ''},
ws: false,
changeOrigin: true
},
'/ws_proxy': {
target: process.env.VUE_APP_WS_URL,
pathRewrite: {'^/ws_proxy': ''},
ws: true,
changeOrigin: true
},
}
},
configureWebpack: {
devtool: 'source-map',
optimization: {
splitChunks: {
chunks: 'all',
},
},
},
})
代理配置之 .env
VUE_APP_API_URL=http://127.0.0.1:xxxx
VUE_APP_WS_URL=wss://xxx.xxx.com.cn //你的环境域名
// 注意:
// 1. wss是带有证书的WebSocket协议,这个结合实际来
// 2. 正式环境是需要配置Nginx的
代理配置之 nginx.conf
...
server {
listen 80;
server_name localhost;
client_max_body_size 50M;
location / {
root /usr/share/nginx/html/dist;
try_files $uri $uri/ /index.html;
}
location /use_proxy/ {
proxy_pass https://xxx.com.cn;
client_max_body_size 500M;
}
location /ws_proxy/ {
proxy_pass https://xxx.com.cn;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
...
本文原创,转载请注明出处