Vue+WebSocket-实现多人聊天室
在前端中 WebSocket 是H5新增的對(duì)象
主要作用有:實(shí)時(shí)通訊 ?長(zhǎng)連接 ?雙向傳輸 ?后端主動(dòng)推送數(shù)據(jù)
websocket實(shí)例的主要事件
前端:
直接new 一個(gè)實(shí)例
const ws = new WebSocket("ws://localhost:9100");
大部分瀏覽器已經(jīng)支持 WebSocket 對(duì)象? 協(xié)議格式為ws(不是 file http)
主要事件:
| open | 建立鏈接 |
| close | 斷開(kāi)鏈接 |
| error | 發(fā)生錯(cuò)誤 |
| message | 發(fā)送消息給后端 |
后端:
使用node 來(lái)簡(jiǎn)單搞一個(gè) 本地服務(wù)、后端要依賴第三方包使用websocket 這里以ws為例
npm i ws@8.10.0
引入ws 創(chuàng)建服務(wù) 配置端口和前端一致
const ws = require('ws')
const wss= new ws.Server({port:9100})
主要事件:
| open | 建立鏈接 |
| close | 斷開(kāi)鏈接 |
| error | 發(fā)生錯(cuò)誤 |
| connection message | 有客戶端連接上? 接收到客戶端發(fā)送來(lái)的消息 一般 message事件在 connection里面 |
案例流程梳理
登錄頁(yè)面展示:?
?樣式部分省略...
使用雙向綁定獲取 用戶輸入的昵稱和 選擇的頭像? 頭像v-for渲染數(shù)據(jù)? 點(diǎn)擊時(shí)currentIndex修改為當(dāng)前的索引 根據(jù)索引 增加高亮邊框和 選擇此數(shù)據(jù)傳給下一步
<input type="text" placeholder="請(qǐng)輸入發(fā)言昵稱" 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>驗(yàn)證輸入用戶的長(zhǎng)度要在1-9位
想服務(wù)器發(fā)起請(qǐng)求 存放昵稱(禁止其他人使用)
收集數(shù)據(jù)保存到??localStorage 中 進(jìn)行下一步
methods:{defind(){if (this.nickname.length < 1) {return alert("請(qǐng)輸入昵稱");}if (this.nickname.length > 9) {return alert("輸入昵稱過(guò)長(zhǎng)");}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}}聊天室頁(yè)面
?預(yù)留組件中需要的數(shù)據(jù)? 進(jìn)入組件和掛載元素 生命周期中進(jìn)行對(duì)應(yīng)的操作
進(jìn)入到頁(yè)面 簡(jiǎn)易判斷 前一步保存到?localStorage 的數(shù)據(jù)是否存在 如果不存在自動(dòng)返回登錄頁(yè)面重新設(shè)置? 防止用戶跳過(guò)登錄直接進(jìn)入到 聊天室 沒(méi)有昵稱導(dǎo)致的一系列錯(cuò)誤? ?這一步也可以使用 導(dǎo)航守衛(wèi)來(lái)實(shí)現(xiàn)
data(){return {nickname:'', // 用戶的昵稱message:'', // 用戶發(fā)送的消息record:[], // 消息記錄數(shù)組ws:null, // ws 實(shí)例 預(yù)留變量user_list:[] // 實(shí)時(shí)在線人數(shù)列表}},created() {this.nickname = localStorage.getItem("nickname")if (!this.nickname) {return this.$router.push('/');}},mounted?生命周期中 創(chuàng)建 WebSocket實(shí)例
添加 ws相關(guān)事件 這里只需要
open 連接上ws服務(wù)器端了 發(fā)送歡迎消息
message 接收到服務(wù)器返回來(lái)的數(shù)據(jù) 渲染到頁(yè)面 聊天信息部分 并判斷是新進(jìn)入的用戶發(fā)送的消息 還是老用戶發(fā)送的消息 如果是新用戶 就添加到左側(cè)在線列表 老用戶此步驟忽略
業(yè)務(wù)流程:連接上后端發(fā)送歡迎消息 =》后端接收消息 返回給每個(gè)客戶端? =》 客戶端接收到服務(wù)端發(fā)來(lái)的消息 渲染到 消息列表 并且根據(jù)條件 渲染左側(cè)在線列表
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 + ' 來(lái)到聊天室',}));});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);}});},點(diǎn)擊發(fā)送按鈕? 組織好數(shù)據(jù) ws.send 發(fā)送給服務(wù)端?
發(fā)送消息 返回渲染之后 滾動(dòng)跳滾到最新消息處
? ? ? ? ? this.$refs.lists.scrollTop += 100
? ? ? ? ? // window.scrollTo(0, document.body.scrollHeight);
?還有格式化時(shí)間的方法
methods:{send(){if (!this.message.trim().length) {return alert("請(qǐng)輸入內(nèi)容");}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 + "時(shí)" + min + "分" + sec + "杪";}},離開(kāi)頁(yè)面(銷毀組件)時(shí) 清楚自己的昵稱 ---左側(cè)的在線列表 和 服務(wù)器端的命名空間
deactivated(){const index = this.user_list.findIndex(x => x.user === this.nickname)this.user_list.splice(index, 1)this.$http.get(`/loginout/${this.nickname}`)},?聊天室頁(yè)面完整模板(樣式省略):
渲染消息列表時(shí) 判斷是不是自己所發(fā)的消息 返回來(lái)的user === 自己的nickname
是的話添加? meSay 樣式? 右側(cè)顯示 作為區(qū)分
<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>當(dāng)前在線人數(shù):{{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">發(fā)送</button></div></div> </template>后端完整代碼
ws部分比較簡(jiǎn)單? 監(jiān)聽(tīng)鏈接 和 接收消息的事件? 出發(fā)了就遍歷所有鏈接的客戶端 把數(shù)據(jù)原封不動(dòng)的發(fā)送出去
const ws = require('ws')const wss= new ws.Server({port:9100})wss.on('connection',(client)=>{ // clent 這個(gè)客戶端鏈接了client.on('message',(msg)=>{ // 并且發(fā)來(lái)了數(shù)據(jù)const radio = msg.toString() // 數(shù)據(jù)轉(zhuǎn)換格式防止亂碼wss.clients.forEach(e =>{ // 遍歷再原封不動(dòng)發(fā)送給每個(gè)鏈接的客戶端e.send(radio)})}) })管理昵稱命名空間的端口
引入express 快速搭建本地服務(wù)器
npm i express@4
使用中間件 解決跨域問(wèn)題
創(chuàng)建?activeUser 數(shù)組儲(chǔ)存 已在線的用戶昵稱、登錄時(shí)發(fā)送請(qǐng)求攜帶 nickname req.params 獲取路徑中的形參 判斷在?activeUser 中是否存在 如果存在添加失敗 不存在 添加進(jìn)去 這樣保證昵稱不重復(fù)、 注銷時(shí)發(fā)送請(qǐng)求 攜帶nickname 在activeUser 查找到 并且 刪除它
// 導(dǎo)入 express 模塊 const express = require('express') // 創(chuàng)建 express 的服務(wù)器實(shí)例 const app = express()// 這樣也可以解決跨域問(wèn)題 app.use(function(req,res,next){// 第二個(gè) * 代表通配符 也可以指定具體的網(wǎng)站 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')// 設(shè)置其他的請(qǐng)求頭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:'用戶昵稱已經(jīng)被占用'})}else{activeUser.push(nickname)return res.send({activeUser,status:1,msg:'成功進(jìn)入聊天室隊(duì)列'})} })// 注銷的接口 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}的命名空間`}) })// 調(diào)用 app.listen 方法,指定端口號(hào)并啟動(dòng)web服務(wù)器 app.listen(7777, function () {console.log('Express server running at http://127.0.0.1:7777') })總結(jié)
以上是生活随笔為你收集整理的Vue+WebSocket-实现多人聊天室的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux+hdmi分辨率设置,自用li
- 下一篇: 进入阿里巴巴,实现了大学时的梦想