越写悦快乐之Vue项目如何集成WebSocket

SockJS - 图片来自我的手机

作为一名有追求的码农来说,不管是前端、后端、抑或是测试、运维,都需要保持一定的好奇心才可以,那么大家都知道Vue作为前端框架的集大成者,拥有着易用、灵活和高效的优点,那么对于在Vue项目中集成WebSocket,大家有没有实践过呢?今天我为大家分享一下如何在Vue项目中集成WebSocket。

开发环境

  • Window 10.0.17763
  • Node 10.18.0
  • Visual Studio Code 1.48.2
  • Vue 2.6.12

特别说明

本项目使用SockJS-ClientSTOMP.js来支持WebSocket后端(Node.js、Java等),在支持WebSocket的浏览器中可以直接进行连接、消息收发和端口等操作(详细内容请参考相关文档)。

接入步骤

添加依赖

我们在项目的入库页面(index.html)中通过CDN的方式引入SockJS-ClientSTOMP.js,文件内容如下。

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="renderer" content="webkit" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
    />
    <title>信息管理系统</title>
  </head>
  <body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.0/sockjs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
    <div id="app"></div>
  </body>
</html>

在Vue文件中连接和关闭WebSocket

既然我们在项目的入口页面中引入了WebSocket相关库,那么挂载在app下的Vue组件均可以使用STOMPSockJS,此时我们在Vue的生命周期函数mounted、和beforeDestroy中分别调用WebSocket的初始化和销毁函数,在data中声明WebSocket连接的HostTopic等相关变量,下面给出Vue文件的内容如下。

<template>
<div class="app-container">
    <el-row>
        <el-col>
            <search @getTableData="getTableData" @deleteSelected="remove"></search>
        </el-col>
    </el-row>
    <el-row>
        <el-col>
            <Table @search="search" :data="gridData" :table="table" :pageParams="pageParams" :pageSizes="[10, 20, 50]" @getSelected="getSelected"></Table>
        </el-col>
        <el-button @click="sendMessage">发送消息</el-button>
    </el-row>
</div>
</template>

<script>
import {
    list,
    save
} from "@/api/dashboard/enterprise/myMessage";

import Table from "@/components/table";

import to from "@/to";

import {
    getToken
} from "@/utils/auth";

import search from "./search";

import {
    mapState,
    mapActions
} from "vuex";

const moment = require("moment");

export default {
    name: "MesageList",
    components: {
        Table,
        search
    },
    data: function () {
        return {
            table: {
                // 行定义
                columns: [{
                        prop: "content",
                        label: "消息内容",
                        width: ""
                    },
                    {
                        prop: "sender",
                        label: "发送者",
                        width: ""
                    },
                    {
                        prop: "typeName",
                        label: "消息类型",
                        width: ""
                    }
                ],
                total: 0
            },
            wsHost: "http://localhost:8080/websocket",
            wsTopic: "/topic/greeting",
            websocket: null,
            stompClient: null,
            isConnected: false,
            message: {},
            messages: [],
            headers: {
                Authorization: getToken()
            }
        };
    },
    computed: {
        ...mapState("dashboard/message/index", [
            "editDialogVisible",
            "editDialogTitle",
            "gridData",
            "pageParams",
            "checkboxSelected",
            "params",
            "total",
            "form"
        ])
    },
    mounted() {
        this._initSockJs();
    },
    beforeDestroy() {
        this._destroySockJs();
    },
    methods: {
        ...mapActions("dashboard/message/index", ["setStateData"]),
        // 操作回调
        action(obj) {
            if (obj.methods === "delete") {
                this.setStateData({
                    checkboxSelected: [obj.row]
                });
                this.remove();
            }
        },
        // 删除
        remove() {
            if (this.checkboxSelected && this.checkboxSelected.length > 0) {
                this.$confirm("此操作将永久删除, 是否继续?", "提示", {
                        confirmButtonText: "确定",
                        cancelButtonText: "取消",
                        type: "warning"
                    })
                    .then(async () => {
                        let [err, res] = await to(deleteByIds(this.checkboxSelected));
                        if (err) {
                            throw new Error(err.message);
                        }
                        this.$message({
                            type: "success",
                            message: "删除成功"
                        });
                        this.getTableData();
                    })
                    .catch(() => {
                        this.$message({
                            type: "info",
                            message: "已取消删除"
                        });
                    });
            }
        },
        // 查询
        search(pageParams) {
            this.setStateData({
                pageParams: pageParams
            });
            this.getTableData();
        },
        // 选中回调
        getSelected(multipleSelection) {
            this.setStateData({
                checkboxSelected: multipleSelection
            });
        },
        // 获取数据
        async getTableData() {
            let response = await list(this.params, this.pageParams);
            let gridData = response.data.list;
            let total = response.data.total;
            this.setStateData({
                gridData: gridData
            });
            this.table.total = total;
        },
        _initSockJs() {
            this.getTableData();
            this.socket = new SockJS(this.wsHost);
            this.stompClient = Stomp.over(this.socket);
            // 订阅
            this.stompClient.connect(this.headers, frame => {
                console.log("WebSocket连接成功", frame);
                this.isConnected = true;
                // 广播
                this.stompClient.subscribe(this.wsTopic, response => {
                    console.log("/websocket/message", JSON.parse(response.body));
                    this.messages.push(JSON.parse(response.body));
                    this.setStateData({
                        gridData: this.messages
                    });
                });
                // 一对一
                this.stompClient.subscribe("/user/topic/greeting", response => {
                    console.log(
                        "/user/topic/greeting/message",
                        JSON.parse(response.body)
                    );
                    this.messages.push(JSON.parse(response.body));
                    this.setStateData({
                        gridData: this.messages
                    });
                });
                // 广播
                this.stompClient.subscribe("/topic/greeting", response => {
                    console.log("/topic/greeting/message", JSON.parse(response.body));
                    this.messages.push(JSON.parse(response.body));
                    this.setStateData({
                        gridData: this.messages
                    });
                });
            });
        },
        _destroySockJs() {
            if (this.stompClient != null) {
                this.stompClient.disconnect();
                this.socket.onclose;
                this.socket.close();
                this.stompClient = {};
                this.socket = {};
                this.isConnected = false;
            }
            console.log("WebSocket断开成功");
        },
        async sendMessage() {
            this.message = {
                sender: this.$store.getters.introduction,
                type: "3",
                content: "消息内容",
                receiver: "*",
                sendingDate: moment(new Date()).format("YYYY-MM-DD HH:mm:ss")
            };

            let [err, res] = await to(save(this.message));
            if (err) {
                throw new Error(err.message);
            }

            this.stompClient.send(this.wsTopic, {}, JSON.stringify(this.message));

            this.$message({
                showClose: true,
                message: "消息发送成功",
                type: "success"
            });
        }
    }
};
</script>

sendMessage函数用于测试消息发送,发送完消息后持久化消息并通过订阅的Topic即时收取消息,收到消息后保存到表格中。

getToken函数是为了获取登录认证后保存的个人信息。

验证连接结果

我们启动前后端的项目,然后定位到该页面,打开开发者工具并切换到控制台,控制台会显示以下信息。

控制台结果 - 图片来自我的手机

通过以上的截图我们可以得知WebSocket连接的后端地址,心跳检测的输出和stompClient的认证字段,那么急于此我们可以构建不同的应用场景,比如聊天、消息收发、任务提醒等功能,再配合其他功能来满足客户的诉求,我相信WebSocket的接入会让你的应用更好用。

个人收获及感想

我们的文章今天为大家介绍了Vue项目中接入WebSocket的方法,通过连接、收发消息和断开等方法来接入WebSocket后端,接入之后就可以愉快地集成其他的业务啦。而在现在的复杂多变的产品迭代周期中,WebSocket不可或缺,并且会不断发展和变化,相信过不了多久,我们的产品也会慢慢完善,并趋于完美。让我们一起持续打造能满足客户诉求的好产品,持续为客户做好服务,成为更好的团队和更好的自己。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354