Aaaaaaaty / blog

趁还能折腾的时候多读书——前端何时是个头
1.78k stars 272 forks source link

基于Vue、Nodejs、Socket.io的聊天应用 #2

Open Aaaaaaaty opened 7 years ago

Aaaaaaaty commented 7 years ago

image.png

写在最前

一直想实现一个聊天应用demo,尝试一下Socket.IO这个框架,同时看到网上的教程很多关于使用node开发聊天应用的demo都是聊天室形式的,也就是群聊,很少有实现私聊的。所以想自己实现一次。另外还尝试了在上传头像的时候接入腾讯云的万象优图API来上传下载注册用户的头像,事实证明确实蛮好用的=。=,只不过那个部署接口的服务器不知道什么时候就会没钱租了,所以可能并不能跑通上传功能T T。github地址

一、技术简介

Vue

Vue.js是一款在2014年2月开源的前端MVVM开发框架,其内容核心为为开发者提供简洁的API,并且通过实现高效的数据绑定来构建一个可复用的组件系统。

Nodejs

Node.js是一个基于Chrome JavaScript运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞I/O 模型而得以轻量和高效,非常适合在分布式设备上运行数据密集型的实时应用。

MongoDB

MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。

Socket.IO

Socket.IO是一个完全由JavaScript实现、基于Node.js、支持WebSocket的协议用于实时通信、跨平台的开源框架。 Socket.IO除了支持WebSocket通讯协议外,还支持许多种轮询(Polling)机制以及其它实时通信方式,并封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。 Socket.IO会根据浏览器对通讯机制的支持情况,选择最佳方式实现网络实时通信。

二、功能

// client.js 前端部署socket.io
import io from 'socket.io-client'
const CHAT={
  ...
  init:function(username){
    //连接websocket后端服务器
    this.socket = io.connect('http://127.0.0.1:3000',{'force new connection': true})
    this.socket.on('open', function() {
      console.log('已连接')
    })
    this.socket.emit('addUser', username)
  }
}
export default CHAT
// server/bin/www 后端部署socket.io
var io = require('socket.io')(server)
io.on('connection', function(socket){
  ...
  socket.emit('open')
  ...
})
socket.on("disconnect", function () {
  console.log("客户端断开连接.")
  delete '某一个对应socket对象' 
  //每次都要删除该socket连接 否则断开重连还是这个socket但是client端socket已经改变
})

部署之后前后端均可使用emit和on来发送和监听自定义事件。socket.io文档

  1. 前端区分渲染私聊群聊
// src/component/talk.vue
<div v-for="msgObj in CHAT.msgArr" track-by="$index">
    <div  class="talk-space self-talk" 
        v-if="CHAT.msgArr[$index].fromUser == username && CHAT.msgArr[$index].toUser == $route.query.username" 
        track-by="$index">
    <div class="talk-content">
      <div class="talk-word talk-word-self">{{ msgObj.msg }}</div><i class="swip"></i>
    </div>
    </div>
    <div v-else></div>
    <div  class="talk-space user-talk" 
        v-if="CHAT.msgArr[$index].toUser == username && CHAT.msgArr[$index].fromUser == $route.query.username" 
        track-by="$index">
    <div class="talk-content">
      <div v-if="CHAT.msgArr[$index].fromUser =='群聊'" class="talk-all">{{ msgObj.trueFrom }}</div>
      <div class="talk-word talk-word-user">
        {{ msgObj.msg }}
        <i class="swip-user"></i>
      </div>
    </div>
</div>

可以看到在CHAT.msgArr维护了一个公共消息队列,这个队列里面有群聊也有私聊的消息。同时每一个数组对象里面都含有fromUSer,toUser方法,来作为记录发送消息的人和接受消息的人的区分。再配合路由中携带的用户名来判断该消息应该渲染在界面的左侧还是右侧。同时由于群聊中虽然用户所面对的聊天对象是“群聊”,但是在渲染左侧“群聊”发送的消息时,仍然应该渲染出真实的用户名即msgObj.trueFrom字段。

3.后端通讯逻辑

// server/bin/www
io.on('connection', function(socket){
  var toUser = {}
  var fromUser = {}
  var msg = ''
  socket.emit('open')
  socket.on('addUser', function(username) {
    if(!onlineUsers.hasOwnProperty(username)) {
        onlineUsers[username] = socket
        onlineCount = onlineCount + 1
    }
    user = username
    console.log(onlineUsers[username].id) //建立连接后 用户点击不同通讯录都是建立同样的socket对象
    console.log('在线人数:',onlineCount)
      socket.on('sendMsg', function(obj) {
        toUser = obj.toUser
        fromUser = obj.fromUser
        msg = obj.msg
        time = obj.time
        if (toUser == '群聊') { //判断为群聊模式
            obj.fromUser = '群聊'
            obj.toUser = user
            obj.trueFrom = fromUser //携带真实发送方
          for (user in onlineUsers) { //遍历所有对象,区分自己和其他用户
            if( user != fromUser ) { //接收方
              onlineUsers[user].emit('to' + user, obj)
            } else { //发送方
              obj.toUser = '群聊'
              obj.fromUser = user
              onlineUsers[fromUser].emit('to' + fromUser, obj)
            }
          }
        } 
        else if(toUser in onlineUsers) { //私聊模式
          onlineUsers[toUser].emit('to' + toUser, obj)
          onlineUsers[fromUser].emit('to' + fromUser, obj)
        } else {
          console.log(toUser + '不在线')
          onlineUsers[fromUser].emit('to' + fromUser, obj)
        }
      })
    socket.on("disconnect", function () {
      console.log("客户端断开连接.")
      //遇到的坑 每次都要删除该socket连接 否则断开重连还是这个socket但是client端socket已经改变
      delete onlineUsers[fromUser] 
    })
  })
})

效果如下图:

最后

这是一个最最最基本的聊天demo实现,也是我第一次自己写一个小分享。ui方面借用了网页版wx的部分样式。同时代码中仍然存在一些“不可预知”的bug,比如聊天消息显示有时候会出问题但是并没有好的方法来排查,主要是第一次使用vue来做前端框架,里面的html和js分离我还是不能很习惯,在debug方面做得还不够好,毕竟用了很久的react..嗯这都不是理由,所以欢迎交流心得,bug可提issues,虽然我可能... 最后po一个github地址:https://github.com/Aaaaaaaty/vue-im 博客地址:https://github.com/Aaaaaaaty/Blog