571
社区成员
发帖
与我相关
我的任务
分享本文设计了一种基于B/S架构的简易在线聊天室,运用前后端分离和的多层架构思想将应用分为用户端、服务端、数据库三部分,其中用户端(前端)主要选用Vite+Vue3+TypeScript+Quasar等技术,服务端(后端)主要选用Express框架,数据库选用MongoDB,前端和后端通过Socket.IO进行交互,后端和数据库通过Mongoose框架进行交互。
Vue是当前备受前端开发者喜爱的框架,有着庞大的生态支持,其生态被戏称为“Vue全家桶”,包括Vite、Vue Router、Vuex等工具,极大地提高了开发效率。
Vue
Vue 是一套用于构建用户界面的渐进式框架,易于上手、便于集成,能够为复杂的单页应用提供驱动。
Vite
为什么选 Vite {#why-vite} | Vite中文网 (vitejs.cn)
Vite 是一个面向现代浏览器的更轻、 更快的 web 应用开发工具,解决开发阶段webpack冷启动时间长、热更新速度慢等问题。Vite 保证了只有在真正使用到某个模块的时候,浏览器才会请求并且解析这个模块,最大程度做到按需加载。
Vue Router
Vue Router是Vue官方路由管理器,与Vue深度集成,让构建单页面应用变得易如反掌。Vue Router有两种实现方式:Hash路由和History路由,其原理如下所示。
Quasar
Why Quasar? | Quasar Framework
Quasar是基于Vue.js的开源框架,允许开发人员快速创建多种类型的响应式网站和应用,具有全平台支持的优点,可以只编写一次代码即可部署为网站应用、移动应用或Electron应用。
Express 是一个简洁灵活的 node.js Web应用框架,提供了一系列强大特性和丰富的 HTTP 工具帮助创建各种 Web 应用。使用 Express 可以快速地搭建一个完整功能的网站。
Express的核心是对中间件的使用,开发者可以使用框架提供的中间件或是自行编写中间件来完成对HTTP请求的处理。
WebSocket - Web API 接口参考 | MDN (mozilla.org)
在HTTP协议中,服务器是被动的,只有收到客户端的请求服务器才会向客户端发送数据。为了解决服务器向客户端发送数据的问题,HTML5提供了服务器和客户端进行全双工通讯的技术WebSocket。WebSocket协议基于TCP,与HTTP兼容,WebSocket建立连接时需要借助HTTP协议,此后进行全双工通信与HTTP无关。
Socket.IO是封装了WebSocket的JavaScript框架,不仅支持WebSocket,还支持多种轮询及其他的通信方式,在环境不支持WebSocket时自动选择最佳的方式实现网络通信。
MongoDB Atlas Database | Multi-Cloud Database Service | MongoDB
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富、最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
Mongoose
Mongoose v6.1.6: Getting Started (mongoosejs.com)
Mongoose是node.js提供用于连接MongoDB的库,是在node.js异步环境下对mongodb进行便捷操作的对象模型工具。Mongoose有以下特点:
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