草庐IT

【websocket】Node+Vue2+WebSocket实现前后端通讯

小球学前端 2023-12-08 原文

WebSocket是一个长连接,客户端可以给服务端发送消息,服务端也可以给客户端发送消息,是全双工通信。
平时的前后端分离项目,是前端请求结果获取后端服务器数据,从而渲染到页面,服务端无法主动向客户端发起消息,某些需求场景下,一旦客户端需要知道服务端的频繁状态变化,就要由客户端长轮询的多次请求服务端数据从而获得最新状态,很明显这种方法比较笨重,也比较浪费资源。WebSocket应运而生。
我们使用WebSocket模拟一下商家机器人自动回复功能。使用nodeJS启动后端,使用serve启动前端页面,先看效果:

1、NodeJS后端

const WebSocket = require('ws')
const autoData = require('./js/auto')
const ws = new WebSocket.Server({ port: 5001 })

ws.on('connection', (ws) => {
  console.log('server connection')
  ws.on('message', (msg) => {
    msg = msg.toString()
    if (msg === 'link') {
      ws.send(`RedDog旗舰店>>${autoData[msg]}`)
    } else if (Object.keys(autoData).indexOf(msg) != -1) {
      ws.send(`RedDog旗舰店>>${autoData[msg]}`)
    } else {
      ws.send(`RedDog旗舰店>>${autoData['未知']}`)
    }
  })

  ws.on('open', () => {
    console.log('open')
  })

  ws.on('close', () => {
    console.log('websocket server: 客户端已关闭连接')
  })
  ws.onerror = function () {
    console.log('websocket server: 出错了')
  }
})
auto.js(根据关键词进行回复)
module.exports = {
  "谢谢": '不客气呢萌主大人,我是您的爱宠营养师,能为您效力是我的荣幸哦',
  "好的": "好的萌主大人,还有其他可以为您效劳的嘛",
  "没有了": "好的萌主大人,那我先退下啦",
  "我不开心": '摸摸头',
  "link": '小主您好,欢迎来到小店,请问有什么可以帮您?',
  "你好": '在的萌主大人,恭迎小主大驾光临,给萌主大人请安!猜你想问【维生素,卵磷脂,驱虫,狗粮】',
  "您好": '在的萌主大人,恭迎小主大驾光临,给萌主大人请安!猜你想问【维生素,卵磷脂,驱虫,狗粮】',
  "在吗": '在的萌主大人,恭迎小主大驾光临!',
  "在么": '在的萌主大人,恭迎小主大驾光临!',
  "维生素": '狗狗体内维生素缺乏容易导致挑食厌食、体重减轻、掉毛褪色、皮肤痒、出现泪痕、不爱运动、经常生病、生长缓慢、发育不良等症状,建议狗狗及时补充维生素哈萌主大人!',
  "卵磷脂": '卵磷脂的作用有很多,分别有以下几项。首先,保护皮肤,预防皮肤病,可以有效改善狗狗皮肤干燥、瘙痒、过敏等症状。其次,可以补充营养,提高狗狗免疫力,促进骨骼的生长,对老年狗还有保养作用,延缓狗狗衰老。最后,吃卵磷脂可以帮助狗狗美毛,增加毛发的光泽度哈萌主大人!',
  "驱虫": '幼犬一般在满月左右第一次驱虫,六个月之前的幼犬,每一个月驱虫一次,六个月之后的幼犬以及成年犬,每三个月驱虫一次哈萌主大人!',
  "未知": '哎呀,回答不上来了!换下边的词问问吧【维生素,卵磷脂,驱虫,狗粮】',
  "狗粮": '一只成年的柯基犬,每天约需要吃自身体重1.5%左右的狗粮,且这些狗粮至少需要分2餐喂食,具体可以根据是情分体重增分量的哈萌主大人。',
  "柯基": '柯基犬是属于英国的品种。柯基犬性格温顺、勇敢大胆、聪明机警、对人友善、精力旺盛、活泼可爱,作为一种小型犬已经深受人们喜欢,越来越多的人选择其作为家庭伴侣犬。'
}
启动

node index.js

2、index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <link rel="stylesheet" href="./css/index.css" />
</head>

<body>
  <!-- 312321 -->
  <div id="app">
    <!-- 标题 -->
    <p class="title">PLUS专属客服</p>
    <!-- 聊天区 -->
    <div class="chatContainer" id="chatContainer">
      <!-- 导航区域 -->
      <!-- 某一个片段 -->
      <div class="contentDiv" v-for="(item, key) in dataList" :key="key">
        <p class="time">{{getFormat(key)}}</p>
        <div v-for="(itemDetail, index) in item" :key="index">
          <div class="shop content" v-if="itemDetail.type === 0">
            <img :src="itemDetail.imgSrc" />
            <div>
              <span>{{itemDetail.shopName}}</span>
              <p>{{itemDetail.content}}</p>
            </div>
          </div>
          <div class="user content" v-if="itemDetail.type === 1">
            <img :src="itemDetail.imgSrc" />
            <div>
              <span>{{itemDetail.nickName}}</span>
              <p>{{itemDetail.content}}</p>
            </div>
          </div>
        </div>
      </div>
    </div>
    <!-- 发送内容区域 -->
    <div class="sendDiv">
      <input v-model="sendMsg" placeholder="请输入您的问题" />
      <button @click="send">发送</button>
    </div>
  </div>
</body>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vant@2.9/lib/index.css" />
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vant@2.9/lib/vant.min.js"></script>
<script>
  let LinkStatus = {
    CLOSE: 0,
    SUCCESS: 1,
    ERROR: 2,
  }
  let UserType = {
    SHOP: 0,
    USER: 1,
  }

  let app = new Vue({
    el: '#app',
    data: function () {
      return {
        dataList: {},
        sendMsg: '',
        lastTimeStamp: 1664258797000,
        webSocket: null,
        linkStatus: LinkStatus.CLOSE, // 0 关闭,
      }
    },
    methods: {
      send() {
        if (this.linkStatus !== LinkStatus.SUCCESS) {
          return vant.Notify('失去链接,请刷新页面');
        }
        let time = new Date().getTime()
        let sendMsg = this.sendMsg
        this.sendMsg = ''
        let obj = {
          type: UserType.USER, //用户
          content: sendMsg,
          nickName: '小球2海绵宝宝',
          imgSrc: './img/user.jpg',
        }
        if (time - this.lastTimeStamp < 2 * 60 * 1000) {
          this.dataList[this.lastTimeStamp].push(obj)
        } else {
          this.lastTimeStamp = time
          this.$set(this.dataList, time, [obj])
        }
        this.handleScroll()
        setTimeout(() => {
          this.webSocket.send(sendMsg)
        }, 800)
      },
      initWebSocket() {
        this.webSocket = new WebSocket('ws://localhost:5001')
        this.webSocket.onopen = () => {
          this.linkStatus = LinkStatus.SUCCESS
          this.webSocket.send('link')
        }
        this.webSocket.onerror = () => {
          this.linkStatus = LinkStatus.ERROR
          console.log('连接失败...')
        }
        this.webSocket.onmessage = (event) => {
          let msg = event.data.split('>>')
          let time = new Date().getTime()
          let obj = {
            type: UserType.SHOP, //商家
            content: msg[1],
            shopName: msg[0],
            imgSrc: './img/shop.jpg',
          }
          if (time - this.lastTimeStamp < 2 * 60 * 1000) {
            this.dataList[this.lastTimeStamp].push(obj)
          } else {
            this.lastTimeStamp = time
            this.$set(this.dataList, time, [obj])
          }
          this.handleScroll()
        }
        this.webSocket.onclose = () => {
          this.linkStatus = LinkStatus.CLOSE
          console.log('连接已关闭...')
        }
      },
      handleScroll() {
        let screenHeight = window.screen.height
        let clientHeight = document.getElementById('chatContainer').clientHeight
        if(clientHeight + 150 > screenHeight){
          // 滚动
          $('#chatContainer').animate({'scrollTop': clientHeight + 150}, 300)
        }
      },
      getFormat(d) {
        var now = new Date(parseFloat(d))
        var year = now.getFullYear()
        var month = now.getMonth() + 1
        var date = now.getDate()
        if (month >= 1 && month <= 9) {
          month = '0' + month
        }
        if (date >= 0 && date <= 9) {
          date = '0' + date
        }
        var hour = now.getHours()
        var minute = now.getMinutes()
        var second = now.getSeconds()
        if (hour >= 1 && hour <= 9) {
          hour = '0' + hour
        }
        if (minute >= 0 && minute <= 9) {
          minute = '0' + minute
        }
        if (second >= 0 && second <= 9) {
          second = '0' + second
        }
        return (
          year +
          '-' +
          month +
          '-' +
          date +
          ' ' +
          hour +
          ':' +
          minute +
          ':' +
          second
        )
      },
    },
    mounted() {
      this.initWebSocket()
    },
  })
</script>
</html>
index.css
* {
  margin: 0;
  padding: 0;
}
body{
  background-color: rgb(239, 239, 239);
}
.chatContainer {
  overflow-y: scroll;
  width: 100%;
  position: fixed;
  top: 45px;
  bottom: 50px;
  margin-bottom: 10px;
}

.title{
  text-align: center;
  width: 100%;
  height:45px;
  line-height: 45px;
  border-bottom: 1px solid #d9d9d9;
  background: rgba(255, 255, 255, 0.8);
  position: fixed;
  top: 0;
  left: 0;
}
/* 聊天块 */
.contentDiv{
  padding: 20px 10px 0px 10px;
}
.contentDiv .time{
  text-align: center;
  color: #999;
  font-size: 13px;
  margin-bottom: 10px;
}
img{
  width: 40px;
  height: 40px;
  border-radius: 6px;
  margin-top: 4px;
}
.content{
  width: 80%;
  display: flex;
  margin-bottom: 16px;
}
.shop div{
  margin: 0 5px;
}
.content div span{
  font-size: 12px;
  color: #999;
}
.content div p{
  margin-top: 4px;
  font-size: 14px;
  background-color: #fff;
  padding: 10px;
  border-radius:5px;
  line-height: 23px;
  text-align: justify;
}

/* 用户话语 */
.user{
  width: 80%;
  margin-left: 20%;
  display: flex;
  flex-direction: row-reverse;
}
.user div{
  margin-right: 8px;
}
.user div span{
  display: block;
  text-align: right;
}
.sendDiv{
  margin-top: 30px;
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 60px;
  background-color: rgb(246, 246, 246);
  border-top: 1px solid #d9d9d9;
  display: flex;
  align-items: center;
  padding: 0 10px;
  box-sizing: border-box;
}
.sendDiv input{
  width: 75%;
  border-radius: 27px;
  background-color: #fff;
  height: 35px;
  line-height: 35px;
  border: 1px solid #fff;
  padding-left: 10px;
  font-size: 13px;
  margin-right: 10px;
}
.sendDiv button{
  flex: 1;
  background-color: green;
  color: #fff;
  border-radius: 24px;
  border: none;
  height: 30px;
  line-height: 30px;
  font-size: 13px;
}
启动本地服务器

注意: websocket是项目名,启动的时候注意路径
serve websocket

浏览器访问http://locahost:5000,即可看到页面,第一次连接成功以后,页面上会显示“欢迎语”,证明已经成功连接,并收到后端推送的欢迎信息,

至此:完成前后端通讯。

有关【websocket】Node+Vue2+WebSocket实现前后端通讯的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  3. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  4. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  5. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  6. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  7. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

  8. ruby - "public/protected/private"方法是如何实现的,我该如何模拟它? - 2

    在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定

  9. ruby - Faye WebSocket,关闭处理程序被触发后重新连接到套接字 - 2

    我有一个super简单的脚本,它几乎包含了FayeWebSocketGitHub页面上用于处理关闭连接的内容:ws=Faye::WebSocket::Client.new(url,nil,:headers=>headers)ws.on:opendo|event|p[:open]#sendpingcommand#sendtestcommand#ws.send({command:'test'}.to_json)endws.on:messagedo|event|#hereistheentrypointfordatacomingfromtheserver.pJSON.parse(event.d

  10. ruby - 实现k最近邻需要哪些数据? - 2

    我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项

随机推荐