Vue+WebSocket-实现多人聊天室

14 篇文章 0 订阅

在前端中 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修改为当前的索引 根据索引 增加高亮边框和 选择此数据传给下一步

  <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.com
    const  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.nickname
  const 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.nickname
    const 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')
})

  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的使用Spring Boot、Vue.js和WebSocket实现聊天室的代码示例: Spring Boot后端代码: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new WebSocketHandler(), "/chat").setAllowedOrigins("*"); } @Bean public ObjectMapper objectMapper() { return new ObjectMapper(); } } class WebSocketHandler extends TextWebSocketHandler { private static final Map<WebSocketSession, String> users = new ConcurrentHashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) { users.put(session, "Anonymous"); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { ChatMessage chatMessage = new ObjectMapper().readValue(message.getPayload(), ChatMessage.class); if (chatMessage.getType() == ChatMessage.MessageType.JOIN) { users.put(session, chatMessage.getSender()); } for (WebSocketSession user : users.keySet()) { user.sendMessage(new TextMessage(new ObjectMapper().writeValueAsString(chatMessage))); } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { users.remove(session); } } @Data @AllArgsConstructor @NoArgsConstructor class ChatMessage { public enum MessageType { CHAT, JOIN, LEAVE } private String sender; private String content; private MessageType type; public static ChatMessage joinMessage(String sender) { return new ChatMessage(sender, "", MessageType.JOIN); } public static ChatMessage leaveMessage(String sender) { return new ChatMessage(sender, "", MessageType.LEAVE); } } @RestController public class ChatController { @GetMapping("/users") public List<String> users() { return new ArrayList<>(WebSocketHandler.users.values()); } } ``` Vue.js前端代码: ```html <template> <div> <h2>Chat Room</h2> <div> <label>Your name:</label> <input v-model="name" @keyup.enter="join" /> <button @click="join">Join</button> </div> <div v-if="joined"> <div> <label>Message:</label> <input v-model="message" @keyup.enter="send" /> <button @click="send">Send</button> </div> <div> <h3>Users:</h3> <ul> <li v-for="user in users" :key="user">{{ user }}</li> </ul> </div> <div> <h3>Chat:</h3> <ul> <li v-for="chat in chats" :key="chat.id"> <strong>{{ chat.sender }}:</strong> {{ chat.content }} </li> </ul> </div> </div> </div> </template> <script> import SockJS from "sockjs-client"; import Stomp from "stompjs"; export default { data() { return { name: "", message: "", joined: false, chats: [], users: [], stompClient: null, }; }, methods: { join() { const socket = new SockJS("/chat"); this.stompClient = Stomp.over(socket); this.stompClient.connect({}, () => { this.stompClient.subscribe("/topic/chat", (message) => { const chat = JSON.parse(message.body); if (chat.type === "JOIN") { this.users.push(chat.sender); } else if (chat.type === "LEAVE") { this.users.splice(this.users.indexOf(chat.sender), 1); } this.chats.push(chat); }); this.stompClient.send( "/app/chat", JSON.stringify(ChatMessage.joinMessage(this.name)) ); this.joined = true; }); }, send() { this.stompClient.send( "/app/chat", JSON.stringify( new ChatMessage(this.name, this.message, ChatMessage.MessageType.CHAT) ) ); this.message = ""; }, }, }; class ChatMessage { static MessageType = { CHAT: "CHAT", JOIN: "JOIN", LEAVE: "LEAVE", }; constructor(sender, content, type) { this.sender = sender; this.content = content; this.type = type; } static joinMessage(sender) { return new ChatMessage(sender, "", ChatMessage.MessageType.JOIN); } static leaveMessage(sender) { return new ChatMessage(sender, "", ChatMessage.MessageType.LEAVE); } } </script> ``` 在这个示例中,我们使用了Spring Boot的WebSocket支持来处理来自客户端的事件。我们创建了一个WebSocket处理程序,它维护了一个用户会话列表,并在用户加入、离开或发送聊天消息时广播消息到所有连接的客户端。我们还为WebSocket处理程序创建了一个控制器,以便在客户端请求所有当前连接的用户列表时返回它们。 在Vue.js应用程序中,我们使用SockJS和Stomp.js来建立与服务器的WebSocket连接,并处理来自服务器的事件。我们使用Vue.js的数据绑定来更新聊天消息、用户列表和用户输入框中的数据,并在加入聊天室、发送聊天消息或断开连接时发送相关的WebSocket事件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值