Nodejs + WebSocket + Vue 一對一、一對多聊天室 - 第三章

星空之火@田興發表於2020-11-03

前言

如果你看到這篇文章,還沒有了解前面2篇文章的同學,可以先去了解一波,這樣上手更快。
推薦文章:

這篇文章都是前面文章的加強版,功能升級。給大家提供一個循序漸進的學習過程,一步一步的來。

在第二篇文章結束時,我們就已經可以一對多的聊天了,就是多人群聊。這次,我們進行擴充套件來實現一對一、一對多功能。

WebSocket客戶端UI介面更改

有了一對一,一對多,我們就需要對直接的介面做出調整了。左邊顯示聊天人員列表,右邊是具體訊息列表。

在這裡插入圖片描述


<div class="web-im">
  <div class="dis-flex">
    <div class="user-list">
      <div class="user" @click="triggerGroup">1</div>
      <div class="user" @click="triggerPersonal(item)" v-if="item.uid!=uid" v-for="item in users">{{item.nickname}}</div>
    </div>
    <div class="msg-content">
      <div class="header im-title">{{title}}</div>
        <div class="content im-record">
          <div class="li" :class="{user: item.uid == uid}" v-for="item in currentMessage">
            <template v-if="item.type===1">
              <p class="join-tips">{{item.msg}}</p>
            </template>
            <template v-else>
              <div class="img">{{item.nickname}}</div>
              <p class="message-box">{{item.msg}}</p>
            </template>
          </div>
        </div>
        <div class="footer im-input">
          <input type="text" v-model="msg" placeholder="請輸入內容">
          <button @click="send">傳送</button>
        </div>
    </div>
  </div>
</div>

這裡我們就寫死了一個群,叫群1,預設是所用使用者進去群聊。

WebSocket服務端

var ws = require("nodejs-websocket");
var moment = require('moment');

console.log("開始建立連線...")

let users = [];
let conns = {};

function boardcast(obj) {
  // bridge用來實現一對一的主要引數
  if(obj.bridge && obj.bridge.length){
    obj.bridge.forEach(item=>{
      conns[item].sendText(JSON.stringify(obj));
    })
    return;
  }
  server.connections.forEach((conn, index) => {
      conn.sendText(JSON.stringify(obj));
  })
}

var server = ws.createServer(function(conn){
  conn.on("text", function (obj) {
    obj = JSON.parse(obj);
    // 將所有uid對應的連線conn存到一個物件裡面
    conns[''+obj.uid+''] = conn;
    if(obj.type===1){
      let isuser = users.some(item=>{
        return item.uid === obj.uid
      })
      if(!isuser){
        users.push({
          nickname: obj.nickname,
          uid: obj.uid
        });
      }
      boardcast({
        type: 1,
        date: moment().format('YYYY-MM-DD HH:mm:ss'),
        msg: obj.nickname+'加入聊天室',
        users: users,
        uid: obj.uid,
        nickname: obj.nickname,
        // 增加引數
        bridge: obj.bridge
      });
    } else {
      boardcast({
        type: 2,
        date: moment().format('YYYY-MM-DD HH:mm:ss'),
        msg: obj.msg,
        uid: obj.uid,
        nickname: obj.nickname,
        // 增加引數
        bridge: obj.bridge
      });
    }
  })
  conn.on("close", function (code, reason) {
    console.log("關閉連線")
  });
  conn.on("error", function (code, reason) {
    console.log("異常關閉")
  });
}).listen(8001)
console.log("WebSocket建立完畢")

主體結構還是和第二章型別,不同的是:

1、每次將uid對應的conn儲存到一個物件conns上
2、根據客戶端傳入的引數bridge來判斷,是群發還是一對一傳送
3、群發還是第二章邏輯即可

server.connections.forEach((conn, index) => {
  conn.sendText(JSON.stringify(obj));
})

4、一對一傳送,bridge裡面是一對一的兩個使用者uid,這樣就可以在conns物件上找到uid對應的連線conn,並用conn傳送資訊即可

if(obj.bridge && obj.bridge.length){
  obj.bridge.forEach(item=>{
    conns[item].sendText(JSON.stringify(obj));
  })
  return;
}

WebSocket客戶端

export default {
  ...
  data(){
    return {
      title: '群聊',
      uid: '',
      nickname: '',
      socket: '',
      msg: '',
      // 當前使用者所有訊息
      messageList: [],
      users: [],
      bridge: []
    }
  },
  mounted() {
    ...
  },
  computed: {
    // 當前對話渲染的msg列表
    currentMessage() {
      let vm = this;
      // 篩選只有bridge相同的對話,展示出來
      // 陣列比較,先轉成字串
      let data = vm.messageList.filter(item=>{
        return item.bridge.sort().join(',') == vm.bridge.sort().join(',')
      })
      return data;
    }
  },
  methods: {
    // 切換到群聊
    triggerGroup() {
      this.bridge = [];
      this.title = '群聊';
    },
    // 切到具體個人
    triggerPersonal(item) {
      if(this.uid === item.uid){
        return;
      }
      // 將當前使用者uid,和需要對話的uid放入bridge
      this.bridge = [this.uid, item.uid];
      this.title = '和' + item.nickname + '聊天';
    },
    send(){
      if(!this.msg){
        return
      }
      this.sendMessage(2, this.msg)
    },
    sendMessage(type, msg){
      this.socket.send(JSON.stringify({
        uid: this.uid,
        type: type,
        nickname: this.nickname,
        msg: msg,
        // 增加bridge引數
        bridge: this.bridge
      }));
      this.msg = '';
    },
    conWebSocket(){
      let vm = this;
      if(window.WebSocket){
        vm.socket = new WebSocket('ws://localhost:8001');
        let socket = vm.socket;

        socket.onopen = function(e){
          console.log("連線伺服器成功");
          if(!vm.uid){
            ...
          }
          // 這裡將sendMessage方法if外面
          vm.sendMessage(1)
        }
        socket.onclose = function(e){
          console.log("伺服器關閉");
        }
        socket.onerror = function(){
          console.log("連線出錯");
        }
        // 接收伺服器的訊息
        socket.onmessage = function(e){
          let message = JSON.parse(e.data);
          vm.messageList.push(message);
          if(message.users) {
            vm.users = message.users;
          }
        }   
      }
    },
    login(){
      ...
    }
  }
}

上方…的程式碼區域都是和第二篇文章一樣的地方,所有就省略了。

1、預設是群發,即bridge是空陣列,向所有使用者傳送訊息
2、點選使用者列表,賦予bridge當前使用者uid,和需要對話的uid。
3、在第二篇文章中,渲染的訊息列表是messageList。現在不是,是通過計算屬性computed,只需要bridge相等的訊息,得出currentMessage當前對話的訊息列表
4、因為所有訊息都是通過後臺socket返回,也不需要考慮傳送者/接收者是誰,判斷bridge是否相等,可以用sort()方法排序並轉換成字串後進行對比。

快速預覽效果

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
總結
一對一和一對多的核心,就是知道是那個使用者與那個使用者對話。當前目前一對多是不用考慮,因為是寫死的,所有使用者,後面來做不同群,隨意加群聊天,就需要考慮了。也就是服務端的conn不要弄錯,不然收不到訊息也接收不到訊息。

相關文章