一、介绍

业务要求:用户可以看到列表中哪些用户是在线的,能跟在线用户发送消息,并收到即时消息。

参考网上一些介绍,根据实战经验进行总结。

二、思路

1、用户登录以后,进行websocket连接;

2、首次登录,获取在线用户信息并渲染,每次接受新消息,出现消息提示,并调用相应方法(根据业务需要);

3、退出登录需要断开连接。

三、遇到的问题与解决思路

1、vue项目中如何解决在页面刷新以后,websocket关闭导致异常的问题?

把websocket封装在socket.js中,在socket.js中初始化函数,在main.js作为全局引入。每次刷新都会重新连接websocket。

2、发现全局引入以后,在注册页面、以及登录退出以后还在不停调用?

进行token判断。

3、获取websocket消息以后,对数据解析以后存在变量中,如何监听这个变量?

一开始将变量存在window中,但没有找到监听window变量的方法。又试着存在vue.prototype中,也没找到监听方法(有人说可以定义在根组件里用$root访问)。最后用vuex存变量,能成功监听了。

4、将存在vue.prototype中的值作为判断条件来渲染页面的时候,对数值监听,数值更新了,条件改变了,调用$forceUpdate(),但页面不重新渲染?

我的理解是,vue的响应式系统不追踪vue.prototype,所以,我这边是先将vue.prototype中的值赋值给本地变量。用本地变量作为判断条件进行渲染页面。使用this.$nextTick,页面能重新渲染了。

5、如何让消息提示在所有页面都能接收到?

通过bus,把$notify写在全局函数main.js中。

6、如何让用户接收到数据以后,调用某页面的方法?

在main.js中接收到消息以后,通过bus.$emit('refreshApplication'); 发送,在相应的页面的mounted中接收,并调用方法。

 bus.$on('refreshApplication', () => {
       //按照业务需要调用方法

});

四、实现

1、socket.js

import Vue from 'vue'
import Cookies from 'js-cookie'
import storeOnlineuser from "@/store/modules/onlineUser.js"
import bus from "@/api/bus.js"
import {dateFormatter} from "@/utils/validate"
let socket = null;
let lockReconnet = false; //避免重复连接

const TokenKey = 'USERTOKEN'
const wsUrl =  `${window.g.WEBSOCKETPATH}?token=${Cookies.get(TokenKey)}` ;
console.log(wsUrl)

let isReconnet = false;
let globalCallback = null, sendData = null; //把要发送给socket的数据和处理socket返回数据的回调保存起来

//export用以登录后调用 避免在登录页面也调用
export let createSocket = url => { //创建socket

    //判断有无token
    if(!Cookies.get(TokenKey)){
        return false
    }

    try {
        if ('WebSocket' in window) {
            socket = new WebSocket(url)
        } else if ('MozWebSocket' in window) {
            socket = new MozWebSocket(url)
        }
        Vue.prototype.socket = socket //需要主动关闭的话就可以直接调用this.socket.close()进行关闭,不需要的话这个可以去掉
        initSocket()
    } catch (e) {
        reconnet(url)
    }
}
let sendMsg = (data, callback) => { //发送数据,接收数据
    if (socket.readyState === 1) {
        globalCallback = callback;
        sendData = data;

        data = JSON.stringify(data);
        console.log('发送数据',data)
        socket.send(data);
    }else {
        setTimeout(() => {
            console.log(socket, '等待socket链接成功')
            sendMsg(data, callback)
        }, 3500)
        return false
    }
    socket.onmessage = ev => {
        callback && callback(ev)
    }
}
let initSocket = () => { //初始化websocket
    
    socket.onopen = () => {
        //heartCheck.reset().start() //暂时不需要做心跳检测
        if (isReconnet) {//执行全局回调函数
            console.log('websocket重新连接了')
            isReconnet = false
        }
        console.log('websocket连接成功')
    }

    socket.onmessage = (ev) => {
        console.log(ev, '连接正常')
        //存数据 得考虑会不会被覆盖的问题
       let res = JSON.parse(ev.data)
       console.log('获得消息',res)
       if(res.Type == 'Event' &&  res.Message.Data.type == '呼叫'){
           //获取到呼叫信息 发送信息 其他页面获取到以后 调用相应方法 根据业务需要写
          bus.$emit('refreshApplication'); 
          bus.$notify.info({
            title: res.Message.Data.type,
            dangerouslyUseHTMLString: true,
            message: `<strong>${res.Message.Data.sender}</strong>呼叫<strong>${res.Message.Data.receiver}</strong><br>呼叫时间:${dateFormatter(res.SendTime)}`,
            duration: 0,
            position: 'bottom-right'
          });
       }

       //Type==Chat时Message内列表为在线用户数据列表
       else if(res.Type == 'Chat'){
        Vue.prototype.onlineUserRes = res.Message // 用来页面遍历显示 也可以直接用store中的数据
        storeOnlineuser.state.onlineUser = res.Message //存储在线用户信息
       }
        //heartCheck.reset().start() //暂时不需要做心跳检测
    }

    socket.onerror = () => {
        console.log('websocket服务出错了');
        reconnet(wsUrl)
    }

    socket.onclose = () => {
        console.log('websocket服务关闭了'); 
        reconnet(wsUrl)//防止自动断开
    }
}
let reconnet = url => { //重新连接websock函数
    //判断有无token
    if(!Cookies.get(TokenKey)){
        return false
    }
    if (lockReconnet)
        return false

    isReconnet = true;
    lockReconnet = true
    setTimeout(() => {
        createSocket(url)
        lockReconnet = false
    }, 4000)
}
let heartCheck = { //心跳检测
    timeout: 60 * 1000,
    timeoutObj: null,
    serverTimeoutObj: null,
    reset() {
        clearTimeout(this.timeoutObj)
        clearTimeout(this.serverTimeoutObj)
        return this;
    },
    start() {
        let that = this;
        this.timeoutObj = setTimeout(() => {
            //发送数据,如果onmessage能接收到数据,表示连接正常,然后在onmessage里面执行reset方法清除定时器
            socket.send('heart check')
            this.serverTimeoutObj = setTimeout(() => {
                socket.close()
            }, that.timeout)
        }, this.timeout)
    }
}
 
//避免刷新以后页面没有数据
createSocket(wsUrl)

export default { sendMsg }

2、在main.js引用

//全局发送消息
import socket from '@/utils/mysocket'
Vue.prototype.sendMsg = socket.sendMsg

3、登录的时候调用

createSocket(`${window.g.WEBSOCKETPATH}?token=${getToken()}`)

4、获取实时在线人数。由于在mainJS中已经将消息存在vuex中,所以直接监听。

<template>
  <div class="app-container">
    <el-table
      :data="patList"
      max-height="580"
      :highlight-current-row="true"
    >  
       <el-table-column prop="userName" label="用户" width="120">
        <template slot-scope="scope">
           {{scope.row.userName}}
           <i class="el-icon-s-opportunity" :class="onlineUser && onlineUser.includes(scope.row.userId)  ? 'online' : 'offline'"></i>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
 
 
import integrated from "@/views/integrated/index"

export default {
  name: 'index',
  data() {
    return {
      patList:[{
        userName:'章三',
        userId:'1'
      }],
      onlineUser:[],
    }
  },
  watch:{
    '$store.state.online.onlineUser':{
      immediate:true,
      handler(val){
        this.$nextTick(()=> {
          this.onlineUser = this.onlineUserRes
        })
      }
    }
  },
  methods: {

  },
}
</script>

<style lang="scss" scoped>
.online{
  color: forestgreen;
}
.offline{
  color:gainsboro;
}
</style>

 

5、发送消息

export default {
  data() {
    return {
     callData: {
        Sender: this.$store.state.user.id,//发送者id
        Receiver: "",//接收者id
        SendTime: "",//2020-08-06T16:32:22.3224288+08:00
        Message: {
          Event: "Call",
          Data: "",//发送的消息
        },
        Type: "Event"
      },
    }
  }
call(row){
  let call = {
    sender : this.$store.state.user.userName,
    type:'呼叫',
    receiver: row.receiverName
    
  }
  //呼叫接受人
  this.callData.Receiver = row.userId
  this.callData.Message.Data = call 

   this.$message({
      type: 'success',
      message: '已呼叫'
    });
  this.getSocketData(this.callData)
    
},
getSocketData(data){
   this.sendMsg(data,ev=>{
   console.log(JSON.parse(ev.data),'发送后回调获取数据')
  })
 },
}

6、接收消息

import bus from "@/api/bus.js"

mounted(){
   //获取main.js的emit
    bus.$on('refreshApplication', () => {
       //按照业务需要调用方法
        this.fetchData()

    });
  },

7、项目退出的时候调用

//关闭websocket
 Vue.prototype.socket.onclose();

 

 

 

Logo

前往低代码交流专区

更多推荐