当前位置:   article > 正文

Vue+WebSocket-实现多人聊天室_局域网聊天室vue

局域网聊天室vue

在前端中 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里面

案例流程梳理

  1. 首先登录 选择 头像 起昵称(不能和聊天室内已经有的的重复)
  2. 进入聊天室 发送欢迎信息  显示在线人数+1
  3. 发消息时 后端会自动转发给其他连接上的客户端
  4. 退出的时候 释放 昵称的命名空间 

登录页面展示: 

 样式部分省略...

使用双向绑定获取 用户输入的昵称和 选择的头像  头像v-for渲染数据  点击时currentIndex修改为当前的索引 根据索引 增加高亮边框和 选择此数据传给下一步

  1. <input type="text" placeholder="请输入发言昵称" v-model="nickname" id="input" />
  2. <ul class="avatar">
  3. <li v-for="(aa,index) in avatar_list"
  4. :key="index"
  5. :class="{curr : currentIndex===index}"
  6. @click="bianse(index)">
  7. <img :src="aa" alt="">
  8. </li>

验证输入用户的长度要在1-9位

想服务器发起请求 存放昵称(禁止其他人使用)

收集数据保存到  localStorage 中 进行下一步

  1. methods:{
  2. defind(){
  3. if (this.nickname.length < 1) {
  4. return alert("请输入昵称");
  5. }
  6. if (this.nickname.length > 9) {
  7. return alert("输入昵称过长");
  8. }
  9. this.$http.get(`/login/${this.nickname}`).then(res => {
  10. if(res.data.status === 1){
  11. localStorage.setItem("nickname", this.nickname);
  12. localStorage.setItem("avatar", this.avatar_list[this.currentIndex]);
  13. this.$router.push('/about')
  14. }else{
  15. return alert("昵称已被占用");
  16. }
  17. })
  18. },
  19. bianse(index){
  20. this.currentIndex = index
  21. }
  22. }

聊天室页面

 预留组件中需要的数据  进入组件和挂载元素 生命周期中进行对应的操作

进入到页面 简易判断 前一步保存到 localStorage 的数据是否存在 如果不存在自动返回登录页面重新设置  防止用户跳过登录直接进入到 聊天室 没有昵称导致的一系列错误   这一步也可以使用 导航守卫来实现

  1. data(){
  2. return {
  3. nickname:'', // 用户的昵称
  4. message:'', // 用户发送的消息
  5. record:[], // 消息记录数组
  6. ws:null, // ws 实例 预留变量
  7. user_list:[] // 实时在线人数列表
  8. }
  9. },
  10. created() {
  11. this.nickname = localStorage.getItem("nickname")
  12. if (!this.nickname) {
  13. return this.$router.push('/');
  14. }
  15. },

mounted 生命周期中 创建 WebSocket实例

添加 ws相关事件 这里只需要

open 连接上ws服务器端了 发送欢迎消息

message 接收到服务器返回来的数据 渲染到页面 聊天信息部分 并判断是新进入的用户发送的消息 还是老用户发送的消息 如果是新用户 就添加到左侧在线列表 老用户此步骤忽略

业务流程:连接上后端发送欢迎消息 =》后端接收消息 返回给每个客户端  =》 客户端接收到服务端发来的消息 渲染到 消息列表 并且根据条件 渲染左侧在线列表

  1. mounted() {
  2. this.ws = new WebSocket("ws://localhost:9100");
  3. this.ws.addEventListener("open", () => {
  4. this.ws.send(
  5. JSON.stringify({
  6. user: this.nickname,
  7. avatar:localStorage.getItem('avatar'),
  8. dateTime: this.nowTimeFormatChinese(new Date()),
  9. message:'欢迎 ' +this.nickname + ' 来到聊天室',
  10. })
  11. );
  12. });
  13. this.ws.addEventListener("message", (e) => {
  14. const data = JSON.parse(e.data)
  15. this.record.push(data);
  16. const flag = this.user_list.filter(x => x.user === data.user)
  17. if(flag.length === 0){
  18. this.user_list.push(data);
  19. }
  20. });
  21. },

点击发送按钮  组织好数据 ws.send 发送给服务端 

发送消息 返回渲染之后 滚动跳滚到最新消息处

          this.$refs.lists.scrollTop += 100

          // window.scrollTo(0, document.body.scrollHeight);

 还有格式化时间的方法

  1. methods:{
  2. send(){
  3. if (!this.message.trim().length) {
  4. return alert("请输入内容");
  5. }
  6. this.ws.send(
  7. JSON.stringify({
  8. user: this.nickname,
  9. avatar:localStorage.getItem('avatar'),
  10. dateTime: this.nowTimeFormatChinese(new Date()),
  11. message: this.message,
  12. })
  13. );
  14. this.message = "";
  15. setTimeout(()=>{
  16. this.$refs.lists.scrollTop += 100
  17. // window.scrollTo(0, document.body.scrollHeight);
  18. },100)
  19. },
  20. padZero(n){
  21. return n > 9 ? n : "0" + n;
  22. },
  23. nowTimeFormatChinese(riqi){
  24. let hour = this.padZero(riqi.getHours()),
  25. min = this.padZero(riqi.getMinutes()),
  26. sec = this.padZero(riqi.getSeconds())
  27. return hour + "时" + min + "分" + sec + "杪";
  28. }
  29. },

离开页面(销毁组件)时 清楚自己的昵称 ---左侧的在线列表 和 服务器端的命名空间

  1. deactivated(){
  2. const index = this.user_list.findIndex(x => x.user === this.nickname)
  3. this.user_list.splice(index, 1)
  4. this.$http.get(`/loginout/${this.nickname}`)
  5. },

 聊天室页面完整模板(样式省略):

渲染消息列表时 判断是不是自己所发的消息 返回来的user === 自己的nickname

是的话添加  meSay 样式  右侧显示 作为区分

  1. <template>
  2. <div class="about">
  3. <ul id="list" ref="lists">
  4. <li v-for="(n,index) in record" :key="index" :class="{meSay : n.user === nickname}">
  5. <div>
  6. <div class="cow">
  7. <img :src="n.avatar" alt="" class="avatar">
  8. <p class="ppp">
  9. <span>{{n.user}}</span>
  10. <br>
  11. <span>{{n.dateTime}}</span>
  12. </p>
  13. </div>
  14. <div class="nei">
  15. {{n.message}}
  16. </div>
  17. </div>
  18. </li>
  19. </ul>
  20. <div class="bottom">
  21. <div class="people">
  22. <h3>当前在线人数:{{this.user_list.length}}</h3>
  23. <div class="user_list" v-for="n in user_list" :key="n.user">
  24. <img :src="n.avatar" alt="">
  25. {{n.user}}
  26. </div>
  27. </div>
  28. <textarea id="message" v-model="message" @keyup.enter="send"></textarea>
  29. <button id="send" @click="send">发送</button>
  30. </div>
  31. </div>
  32. </template>

后端完整代码

ws部分比较简单  监听链接 和 接收消息的事件  出发了就遍历所有链接的客户端 把数据原封不动的发送出去

  1. const ws = require('ws')
  2. const wss= new ws.Server({port:9100})
  3. wss.on('connection',(client)=>{ // clent 这个客户端链接了
  4. client.on('message',(msg)=>{ // 并且发来了数据
  5. const radio = msg.toString() // 数据转换格式防止乱码
  6. wss.clients.forEach(e =>{ // 遍历再原封不动发送给每个链接的客户端
  7. e.send(radio)
  8. })
  9. })
  10. })

管理昵称命名空间的端口

引入express 快速搭建本地服务器

npm i express@4

使用中间件 解决跨域问题

创建 activeUser 数组储存 已在线的用户昵称、登录时发送请求携带 nickname req.params 获取路径中的形参 判断在 activeUser 中是否存在 如果存在添加失败 不存在 添加进去 这样保证昵称不重复、 注销时发送请求 携带nickname 在activeUser 查找到 并且 删除它

  1. // 导入 express 模块
  2. const express = require('express')
  3. // 创建 express 的服务器实例
  4. const app = express()
  5. // 这样也可以解决跨域问题
  6. app.use(function(req,res,next){
  7. // 第二个 * 代表通配符 也可以指定具体的网站 http://www.wsg3096.com
  8. const contentType = 'application/json; charset=utf-8'
  9. res.setHeader('Content-Type',contentType)
  10. res.setHeader('Access-Control-Allow-Origin','*')
  11. // 后面的也可以用通配符
  12. res.setHeader('Access-Control-Allow-Methods','OPTIONS,GET,PUT,POST,DELETE')
  13. // 设置其他的请求头
  14. res.setHeader('Access-Control-Allow-Headers','Content-Type','X-Custom-Header')
  15. next()
  16. })
  17. const activeUser = []
  18. // 登录的 API 接口
  19. app.get('/api/login/:nickname', (req, res) => {
  20. const nickname = req.params.nickname
  21. const find = activeUser.find(x => x=== nickname)
  22. if(find){
  23. return res.send({
  24. status:0,
  25. msg:'用户昵称已经被占用'
  26. })
  27. }else{
  28. activeUser.push(nickname)
  29. return res.send({
  30. activeUser,
  31. status:1,
  32. msg:'成功进入聊天室队列'
  33. })
  34. }
  35. })
  36. // 注销的接口
  37. app.get('/api/loginout/:nickname', (req, res) => {
  38. const nickname = req.params.nickname
  39. const index = activeUser.findIndex(x => x=== nickname)
  40. activeUser.splice(index,1)
  41. return res.send({
  42. activeUser,
  43. status:200,
  44. msg: `成功释放${nickname}的命名空间`
  45. })
  46. })
  47. // 调用 app.listen 方法,指定端口号并启动web服务器
  48. app.listen(7777, function () {
  49. console.log('Express server running at http://127.0.0.1:7777')
  50. })

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/576603
推荐阅读
  

闽ICP备14008679号