简易在线聊天室的设计与实现

长风风 2022-01-17 02:37:05

项目介绍

本文设计了一种基于B/S架构的简易在线聊天室,运用前后端分离和的多层架构思想将应用分为用户端、服务端、数据库三部分,其中用户端(前端)主要选用Vite+Vue3+TypeScript+Quasar等技术,服务端(后端)主要选用Express框架,数据库选用MongoDB,前端和后端通过Socket.IO进行交互,后端和数据库通过Mongoose框架进行交互。

典型的三层架构

 

技术介绍

Vue全家桶

VueConf CN 2021公布的活跃数据

 

Vue是当前备受前端开发者喜爱的框架,有着庞大的生态支持,其生态被戏称为“Vue全家桶”,包括Vite、Vue Router、Vuex等工具,极大地提高了开发效率。

Vue

介绍 | Vue.js (vuejs.org)

Vue 是一套用于构建用户界面的渐进式框架,易于上手、便于集成,能够为复杂的单页应用提供驱动。

Vite

为什么选 Vite {#why-vite} | Vite中文网 (vitejs.cn)

Vite 是一个面向现代浏览器的更轻、 更快的 web 应用开发工具,解决开发阶段webpack冷启动时间长、热更新速度慢等问题。Vite 保证了只有在真正使用到某个模块的时候,浏览器才会请求并且解析这个模块,最大程度做到按需加载。

传统的bundle模式,加载了并未使用的route和module
基于ESM的模式,不加载并未使用的route和module

 

Vue Router

 

起步 | Vue Router (vuejs.org)

Vue Router是Vue官方路由管理器,与Vue深度集成,让构建单页面应用变得易如反掌。Vue Router有两种实现方式:Hash路由和History路由,其原理如下所示。

Hash路由的原理
History路由的原理

 

Quasar

Why Quasar? | Quasar Framework

Quasar是基于Vue.js的开源框架,允许开发人员快速创建多种类型的响应式网站和应用,具有全平台支持的优点,可以只编写一次代码即可部署为网站应用、移动应用或Electron应用。

Express

https://expressjs.com/

Express 是一个简洁灵活的 node.js Web应用框架,提供了一系列强大特性和丰富的 HTTP 工具帮助创建各种 Web 应用。使用 Express 可以快速地搭建一个完整功能的网站。

Express的核心是对中间件的使用,开发者可以使用框架提供的中间件或是自行编写中间件来完成对HTTP请求的处理。

Express框架对HTTP请求的处理过程

 

WebSocket和Socket.IO

WebSocket - Web API 接口参考 | MDN (mozilla.org)

Get started | Socket.IO

在HTTP协议中,服务器是被动的,只有收到客户端的请求服务器才会向客户端发送数据。为了解决服务器向客户端发送数据的问题,HTML5提供了服务器和客户端进行全双工通讯的技术WebSocket。WebSocket协议基于TCP,与HTTP兼容,WebSocket建立连接时需要借助HTTP协议,此后进行全双工通信与HTTP无关。

WebSocket的原理

 

Socket.IO是封装了WebSocket的JavaScript框架,不仅支持WebSocket,还支持多种轮询及其他的通信方式,在环境不支持WebSocket时自动选择最佳的方式实现网络通信。

Socket.IO的通信方式

 

MongoDB

MongoDB Atlas Database | Multi-Cloud Database Service | MongoDB

MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富、最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

MongoDB的数据结构

 

Mongoose

Mongoose v6.1.6: Getting Started (mongoosejs.com)

Mongoose是node.js提供用于连接MongoDB的库,是在node.js异步环境下对mongodb进行便捷操作的对象模型工具。Mongoose有以下特点:

  1. 通过关系型数据库的思想来设计非关系型数据库
  2. 基于mongoDB驱动,简化操作
  3. 用来操作mongoDB数据库更灵活、更安全

 

系统设计

概要设计

在线聊天室的用例图

 

详细设计

登陆用例的顺序图

 

 

聊天用例的顺序图

 

登出用例的顺序图

 

系统实现

前后端Socket初始化

Vue

// io.ts
import io from 'socket.io-client'
export default {
  // 编写socket插件
  install: (app, { connection, options }) => {
    const socket = io(connection, options)
    app.config.globalProperties.$socket = socket
    app.provide("socket", socket)
  },
}

// main.ts
import SocketIO from './plugin/io'
// 使用socket插件
myApp.use(SocketIO, {
  connection: 'ws://localhost:3000'
})

Express

const express = require('express')
const app = express()
const http = require('http')
const server = http.createServer(app)
const io = require('socket.io')(server, {
  // 解决跨域问题
  cors: {
    origin: '*'
  }
})

const port = process.env.PORT || '3000'
server.listen(port)

登录功能

登录界面截图

 

使用socket将用户输入传递给服务器

const submit = () => {
  // 将用户名发送给后端
  socket.emit('login', nickname.value)
  // 省略无关代码
}

服务器对用户输入进行处理

socket.on('login', socket => {
  const nickname = socket
  if (user.indexOf(nickname) === -1) {
    // 用户名不重复
    users.push(nickname)
    io.emit('login result', users) // 允许用户登录
    io.emit('update users', users) // 更新前端在线用户列表
  } else {
    // 用户名重复
    io.emit('login result', false) // 禁止用户登录
  }
})

客户端根据处理结果进行操作

const router = useRouter() // 用于路由
const $q = useQuasar() // 用于提示

const submit = () => {
  // 省略无关代码
  socket.on('login result', socket => {
    if (socket) {
      // 允许登录,跳转到聊天界面
      router.push({
        name: 'Chatroom',
        params: socket
      })
    } else {
      // 禁止登录,让用户重新输入
      $q.notify({
        type: 'warning',
        message: '有同名用户在线,请选择其他昵称'
      })
    }
  })
}

聊天页面根据处理结果进行更新

<!-- 用户列表组件 -->
<user-list :users="users" />
import UserList from './UserList.vue'

// 根据客户端传递的用户列表更新用户的用户列表
socket.on('update users', socket => {
  users.value = socket
})

聊天功能

聊天界面截图

 

使用socket将用户输入传递给服务器

import {format} from 'date-fns' // 用户格式化日期

const send = () => {
  // 初始化message
  const message = {}
  message.name = nickname.value
  message.text = text.value
  message.stamp = format(new Date(), 'yyyy/MM/dd HH:mm')
  
  socket.emit('send', message) // 将message发送给服务器
}

服务器接收用户输入广播给所有用户

// mongo.js
const saveMessage = (message) => {
  // Message是Mongoose模式
  const msg = new Message({
    name: message.name,
    text: message.text,
    stamp: message.stamp
  })
  msg
    .save() // 调用Message的save方法将数据存入MongoDB中
    .then(res => console.log('successfully saved')
}

// index.js
import saveMessage = require("./api/mongo") // 用于保存消息

socket.on('send', socket => {
  saveMessage(socket) // 将消息存入数据库
  io.emit('receive', socket) // 向用户广播消息
})

接受消息的用户更新自己的消息列表

socket.on('receive', socket => {
  messages.value.push(socket)
})

登出功能

登出功能截图

 

用户登出时通知服务器

onBeforeUnmount(() => {
  socket.emit('bye', nickname.value) // 将用户昵称传递给服务器告知该用户离线
})

服务器接收登出用户名进行处理

socket.on('bye', socket => {
  const index = users.findIndex(user => user === socket) // 根据用户名找到用户在数组中的下表
  if (index !== -1) {
    // 用户在数组中
    users.splice(index, 1)
  }
  io.emit('update users', users) // 通知用户更新在线列表,过程与登录时相同
})

作者:NP147

...全文
397 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

571

社区成员

发帖
与我相关
我的任务
社区描述
软件工程教学新范式,强化专项技能训练+基于项目的学习PBL。Git仓库:https://gitee.com/mengning997/se
软件工程 高校
社区管理员
  • 码农孟宁
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧