对于一个大厅实时聊天我们都看惯不惯了,花了一天的时间结合nodejs-websocket和vue做了一个实时私聊的插件,当然代码方面有待改善。

 现在简要讲解一下我的思路

一、规范传输

我的项目经验不多,但我觉得每个项目都要现设立一些规则,如果随心所欲的可能会导致自己跳到自己的坑。

我们先设定项目的json传输规定

//获取uid
{type:'welcome',my_id:'我的uid'}

//获取成员
{type:'member',members:['成员1','成员1']}

//对话信息
{type:'chat',from:'来自uid',to:'发给uid',msg:'对话信息'}

二、服务器业务逻辑

// npm i nodejs-websocket
let ws = require("nodejs-websocket")

//str转json
let json = str => {
    return (new Function('return '+str))()
}
//json转str
let str = json =>{
    return JSON.stringify(json)
}

// 实时聊天人数数组,通过随机数生成
let conns      = [],
    message_welcome    = {},
    message_member     = {},
    message_between    = {},
    heart_beat = 9999, //要求每个连接每9999秒心跳一次,否则断线,建议调到50

let server = ws.createServer(function (conn) {
    //根据时间戳生成用户id uid
    let uid = str((new Date()).getTime()).slice(-6)

    //计算心跳时间
    conn.heart_time = 0
    let timer = setInterval(()=>{
        if (conn.heart_time > heart_beat) {
            clearInterval(timer);
            conn.close()
        }
        conn.heart_time++
    },1000)
    
    //保存用户id在全局数组conns中方便我们处理聊天对象信息
    conns[uid] = conn
    message_welcome = {'my_id':uid,type:'welcome'}
    conn.sendText(str(message_welcome))

    //如果有新的人员加入,广播数据给全部人
    message_member = {'members':Object.keys(conns),type:'member'}
    for(var i in conns){
        conns[i].sendText(str(message_member))
    }
    
    //接受到发过来的信息
    conn.on("text", function (text) {
        //重置心跳
        conn.heart_time = 0
        //判断发给谁
        console.log(text)
        let data = json(text),
        to = data['to'],
        from = uid,
        msg = data['msg']
        //存在发送的对象
        console.log(str(Object.keys(conns)),to)
        if (Object.keys(conns).indexOf(to) != -1) {
            message_between = {type:'chat','from':from,'to':to,'msg':msg}
            console.log(str(message_between))
            //发给别人
            conns[to].sendText(str(message_between))
            //发给自己
            conns[from].sendText(str(message_between))
        }
    })   
    //断开连接的回调
    conn.on("close", function (code, reason) {
        //删除成员信息
        delete conns[uid]
        //广播
        message = {'members':Object.keys(conns),type:'member'}
        for(var i in conns){
            conns[i].sendText(str(message_member))
        }
    })  

    //处理错误事件信息
    conn.on('error', function (err) {
        //异常conn就直接删除
        conn.close()
        delete conns[uid]
    })
}).listen(8001);//8001端口

三、vuex管理websocket

因为websocket在项目中通常一个足以。所以直接扔给vuex比较稳。

我没有用onclose判断websocket状态,我改用WebSocket.readyState    (0:正在连接,1:已连接,2:关闭中,3:已关闭)

因为这样的状态更深刻体现webscoket的状态。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
// str转json
let json = str => {
    return (new Function('return '+str))()
}
//json转str
let str = json =>{
    return JSON.stringify(json)
}
export default new Vuex.Store({
  state: {
    WS:false,//websocket对象
    WS_URL:'ws://127.0.0.1:8001',
    WS_RECONNECT:false,//自动重连
    // HEART_BEAT:5,//每50秒心跳一次
    HEART_MSG:'hello',
  },
  mutations: {
    SOCKET_INIT(state,config={}){
      if (!state.WS) {
        state.WS = new WebSocket(state.WS_URL)
      }
      for(var i in config){
        state[i] = config[i]
      }
    },
    SOCKET_MESSAGE(state,func){
      return new Promise((resolve,reject)=>{
        state.WS.onmessage=(data)=>{
          // data = (new Function('return '+data))()
          resolve(func(json(data.data)))
        }
        //这个有问题,虽然很方便,但还是搁置
        // state.WS.addEventListener('message',data=>{resolve(func(data))})
      })
    },
    SOCKET_SEND(msg){
    	state.WS.send(str(msg))
    },
    SOCKET_HEART(state){
      //设置心跳
      if (state.HEART_BEAT > 0) {
        let timer = setInterval(()=>{
          state.WS.send(state.HEART_MSG)
          //如果已经关闭就停止
          if([2,3].indexOf(state.WS.readyState) !== -1){
            clearInterval(timer)
          }
        },parseInt(state.HEART_BEAT)*1000)
      }
    }
  },
  getters:{

  },
  actions: {
    SOCKET_INIT({state,commit},func){
      return new Promise((resolve,reject)=>{
        commit('SOCKET_INIT')
        state.WS.onerror = function (msg) {
          reject(msg)
        }

        let timer = setInterval(()=>{
          //不是正在连接状态
          if(state.WS.readyState!==0){
            clearInterval(timer)
            //正在关闭,关闭
            if([2,3].indexOf(state.WS.readyState) !== -1){
              state.WS = false
            }
            //连接
            if(state.WS.readyState===1){
              resolve(state.WS)
              commit('SOCKET_HEART')
              commit('SOCKET_MESSAGE',func)
            }
          }
        },100)
      })
    },
  }
})

四、调用webscoket

import { mapActions,mapState } from 'vuex'

export default {
  name: 'vue-chat',
  computed:{
      //websocket实例
      ...mapState({'WS':'WS'}),
  },
  methods:{
     ...mapActions({socket:'SOCKET_INIT'}),
     sendMsg(str){
        this.WS.send(str)     
     },
     handle_message(data){
        //因为我在vuex中直接将其数据变为json,所以直接可以用
        console.log(data)
     }
  }
  //一般直接放在加载完成的时候
  mounted(){   
    let that = this
    //接受信息的时候
    this.socket((data)=>{
      that.handle_message(data)
    })
  }
}

git 代码:https://github.com/JeasonLaung/vue-chat

如果你觉得好,帮我star一个,谢谢大家

Logo

前往低代码交流专区

更多推荐