Vue+WebSocket-实现多人聊天室
在前端中 WebSocket 是H5新增的对象
主要作用有:实时通讯 长连接 双向传输 后端主动推送数据
websocket实例的主要事件
前端:
直接new 一个实例
const ws = new WebSocket("ws://localhost:9100");
大部分浏览器已经支持 WebSocket 对象 协议格式为ws(不是 file http)
主要事件:
| open | 建立链接 |
| close | 断开链接 |
| error | 发生错误 |
| message | 发送消息给后端 |
后端:
使用node 来简单搞一个 本地服务、后端要依赖第三方包使用websocket 这里以ws为例
npm i ws@8.10.0
引入ws 创建服务 配置端口和前端一致
const ws = require('ws')
const wss= new ws.Server({port:9100})
主要事件:
| open | 建立链接 |
| close | 断开链接 |
| error | 发生错误 |
| connection message | 有客户端连接上 接收到客户端发送来的消息 一般 message事件在 connection里面 |
案例流程梳理
登录页面展示:
样式部分省略...
使用双向绑定获取 用户输入的昵称和 选择的头像 头像v-for渲染数据 点击时currentIndex修改为当前的索引 根据索引 增加高亮边框和 选择此数据传给下一步
<input type="text" placeholder="请输入发言昵称" v-model="nickname" id="input" /><ul class="avatar"><li v-for="(aa,index) in avatar_list":key="index":class="{curr : currentIndex===index}"@click="bianse(index)"><img :src="aa" alt=""></li>验证输入用户的长度要在1-9位
想服务器发起请求 存放昵称(禁止其他人使用)
收集数据保存到 localStorage 中 进行下一步
methods:{defind(){if (this.nickname.length < 1) {return alert("请输入昵称");}if (this.nickname.length > 9) {return alert("输入昵称过长");}this.$http.get(`/login/${this.nickname}`).then(res => {if(res.data.status === 1){localStorage.setItem("nickname", this.nickname);localStorage.setItem("avatar", this.avatar_list[this.currentIndex]);this.$router.push('/about')}else{return alert("昵称已被占用");}})},bianse(index){this.currentIndex = index}}聊天室页面
预留组件中需要的数据 进入组件和挂载元素 生命周期中进行对应的操作
进入到页面 简易判断 前一步保存到 localStorage 的数据是否存在 如果不存在自动返回登录页面重新设置 防止用户跳过登录直接进入到 聊天室 没有昵称导致的一系列错误 这一步也可以使用 导航守卫来实现
data(){return {nickname:'', // 用户的昵称message:'', // 用户发送的消息record:[], // 消息记录数组ws:null, // ws 实例 预留变量user_list:[] // 实时在线人数列表}},created() {this.nickname = localStorage.getItem("nickname")if (!this.nickname) {return this.$router.push('/');}},mounted 生命周期中 创建 WebSocket实例
添加 ws相关事件 这里只需要
open 连接上ws服务器端了 发送欢迎消息
message 接收到服务器返回来的数据 渲染到页面 聊天信息部分 并判断是新进入的用户发送的消息 还是老用户发送的消息 如果是新用户 就添加到左侧在线列表 老用户此步骤忽略
业务流程:连接上后端发送欢迎消息 =》后端接收消息 返回给每个客户端 =》 客户端接收到服务端发来的消息 渲染到 消息列表 并且根据条件 渲染左侧在线列表
mounted() {this.ws = new WebSocket("ws://localhost:9100");this.ws.addEventListener("open", () => {this.ws.send(JSON.stringify({user: this.nickname,avatar:localStorage.getItem('avatar'),dateTime: this.nowTimeFormatChinese(new Date()),message:'欢迎 ' +this.nickname + ' 来到聊天室',}));});this.ws.addEventListener("message", (e) => {const data = JSON.parse(e.data)this.record.push(data);const flag = this.user_list.filter(x => x.user === data.user)if(flag.length === 0){this.user_list.push(data);}});},点击发送按钮 组织好数据 ws.send 发送给服务端
发送消息 返回渲染之后 滚动跳滚到最新消息处
this.$refs.lists.scrollTop += 100
// window.scrollTo(0, document.body.scrollHeight);
还有格式化时间的方法
methods:{send(){if (!this.message.trim().length) {return alert("请输入内容");}this.ws.send(JSON.stringify({user: this.nickname,avatar:localStorage.getItem('avatar'),dateTime: this.nowTimeFormatChinese(new Date()),message: this.message,}));this.message = "";setTimeout(()=>{this.$refs.lists.scrollTop += 100// window.scrollTo(0, document.body.scrollHeight);},100)},padZero(n){return n > 9 ? n : "0" + n;},nowTimeFormatChinese(riqi){let hour = this.padZero(riqi.getHours()),min = this.padZero(riqi.getMinutes()),sec = this.padZero(riqi.getSeconds())return hour + "时" + min + "分" + sec + "杪";}},离开页面(销毁组件)时 清楚自己的昵称 ---左侧的在线列表 和 服务器端的命名空间
deactivated(){const index = this.user_list.findIndex(x => x.user === this.nickname)this.user_list.splice(index, 1)this.$http.get(`/loginout/${this.nickname}`)},聊天室页面完整模板(样式省略):
渲染消息列表时 判断是不是自己所发的消息 返回来的user === 自己的nickname
是的话添加 meSay 样式 右侧显示 作为区分
<template><div class="about"><ul id="list" ref="lists"><li v-for="(n,index) in record" :key="index" :class="{meSay : n.user === nickname}"> <div><div class="cow"><img :src="n.avatar" alt="" class="avatar"><p class="ppp"><span>{{n.user}}</span><br><span>{{n.dateTime}}</span></p></div><div class="nei">{{n.message}}</div> </div></li></ul><div class="bottom"><div class="people"><h3>当前在线人数:{{this.user_list.length}}</h3><div class="user_list" v-for="n in user_list" :key="n.user"><img :src="n.avatar" alt="">{{n.user}}</div></div><textarea id="message" v-model="message" @keyup.enter="send"></textarea><button id="send" @click="send">发送</button></div></div> </template>后端完整代码
ws部分比较简单 监听链接 和 接收消息的事件 出发了就遍历所有链接的客户端 把数据原封不动的发送出去
const ws = require('ws')const wss= new ws.Server({port:9100})wss.on('connection',(client)=>{ // clent 这个客户端链接了client.on('message',(msg)=>{ // 并且发来了数据const radio = msg.toString() // 数据转换格式防止乱码wss.clients.forEach(e =>{ // 遍历再原封不动发送给每个链接的客户端e.send(radio)})}) })管理昵称命名空间的端口
引入express 快速搭建本地服务器
npm i express@4
使用中间件 解决跨域问题
创建 activeUser 数组储存 已在线的用户昵称、登录时发送请求携带 nickname req.params 获取路径中的形参 判断在 activeUser 中是否存在 如果存在添加失败 不存在 添加进去 这样保证昵称不重复、 注销时发送请求 携带nickname 在activeUser 查找到 并且 删除它
// 导入 express 模块 const express = require('express') // 创建 express 的服务器实例 const app = express()// 这样也可以解决跨域问题 app.use(function(req,res,next){// 第二个 * 代表通配符 也可以指定具体的网站 http://www.wsg3096.comconst contentType = 'application/json; charset=utf-8'res.setHeader('Content-Type',contentType)res.setHeader('Access-Control-Allow-Origin','*')// 后面的也可以用通配符res.setHeader('Access-Control-Allow-Methods','OPTIONS,GET,PUT,POST,DELETE')// 设置其他的请求头res.setHeader('Access-Control-Allow-Headers','Content-Type','X-Custom-Header')next() })const activeUser = []// 登录的 API 接口 app.get('/api/login/:nickname', (req, res) => {const nickname = req.params.nicknameconst find = activeUser.find(x => x=== nickname)if(find){return res.send({status:0,msg:'用户昵称已经被占用'})}else{activeUser.push(nickname)return res.send({activeUser,status:1,msg:'成功进入聊天室队列'})} })// 注销的接口 app.get('/api/loginout/:nickname', (req, res) => {const nickname = req.params.nicknameconst index = activeUser.findIndex(x => x=== nickname)activeUser.splice(index,1)return res.send({activeUser,status:200,msg: `成功释放${nickname}的命名空间`}) })// 调用 app.listen 方法,指定端口号并启动web服务器 app.listen(7777, function () {console.log('Express server running at http://127.0.0.1:7777') })总结
以上是生活随笔为你收集整理的Vue+WebSocket-实现多人聊天室的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: linux+hdmi分辨率设置,自用li
- 下一篇: vue-cli中css引入图片打包路径问