vue2实现websocket的聊天应用

前端刚入职小白,记录websocket在vue前端的实际应用。

1:websocket简单介绍

        首先,我们需要简单了解一下websocket:

HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理,HTTP 协议无法实现服务器主动向客户端发起消息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 JavaScript 和 XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源。

Websocket应运而生,WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。

2:websocket的实现

其实说到底就是将服务器发送过来的数据在通过websocket接收函数时,通过字段判断和操作

1:是正在和我聊天的人发的还是别人?

2:收到的未读信息数量?

3:我发送的人的标识ID。

4:我要通过一个方法来让后端知道我已经读取了信息。

5:也要注意发送和接收数据的时间。

话不多说,上代码:

<script>
  export default {
    data() {
      return {
        selfId: 0,
        // 在线状态
        state: 1,
        goodsID:0,
        recenttext:'',
        selfListData: {},
        //搜索用户
        search: "",
        //用户列表渲染数据
        userListData: [],
        //用户点击选中变色
        act: 0,
        // 加号弹框
        dialogVisible: false,
        //模拟花间一壶酒用户的历史信息
        userInfoList2: [],
        //历史信息
        userInfoList: [],
        //输入框
        textarea: "",
        //滚动条距离顶部距离
        scrollTop: 0,
        //发送和输入显隐
        isshow: 0,
        websock: null,
        page: 1,
        page1: 1,
        urlhh: '',
        abcd: 0,
        index: -1,
        dialogInfo:{},
        firstRunFlag: true,
        other_id:0,
        category:0,
        isUpdate:true,
      };
    },
    created() {
      this.initWebSocket()
    },
    destroyed() {
      this.websock.close() //离开路由之后断开websocket连接
    },
  
    methods: {
      initWebSocket() {
        // token为访问令牌,一般登录成功,就会返回token
        const token = window.sessionStorage.getItem('token')
        // 其中线上的链接为wss://,在本地上的链接为ws://
        const wsuri = "wss://www.xxxxxxxx.com:8002/chat_ws/"
        // const wsuri = `ws://192.168.0.0:9000/chat_ws/`
        // 建立websocket连接
        this.websock = new WebSocket(wsuri, [token])
        // websocket接收信息
        this.websock.onmessage = this.websocketonmessage
        // 打开websocket
        this.websock.onopen = this.websocketonopen
        // websocket连接错误时重连
        this.websock.onerror = this.websocketonerror
      },
      websocketonopen() {
        window.console.log('连接成功')
      },
      websocketonerror() {
        //连接建立失败重连
        this.initWebSocket()
      },
      websocketonmessage(e) {
        //数据接收,首先要解析服务器传过来的数据
        const redata = JSON.parse(e.data)
        if (redata.msg_type==3) {
          // msg_type字段为3时,代表服务器给我发了文本信息了,注意是明确文本信息了
          // 例如对方发送 “你好” 给我时,msg_type就为3,text:'你好'
          // 下面这个if判断条件
          // this.dialogInfo.other_userid的意思是我跟这个人的对话框的ID
          // 比如我跟小明正在聊天,那我跟小明的对话框ID就假设为1
          // 此时我收到websocket服务器发来的文本信息text:在吗?,sender:1
          // 此时就意味着是小明给我发的信息,如果sender不是1,就代表着不是小明回我的
          if(this.dialogInfo.other_userid==redata.sender){
            // 如果跟我正在聊天的人给我回消息,下面这个代码,在此页面不影响
            // 这个传值的意思就是,将跟我聊天的对话框ID传递给其他组件或页面,可以用来判断未读信息条数,如果收到的信息的sender和我传递过去的ID相等,意味着未读信息就不需要加一
            this.$bus.$emit("hi",redata.sender);
          }
          // 当接收到数据时,就会更新消息列表,为防止没有对话框的用户不在信息列表,
          // 同时将该用户的ID记录下来,并记录已经更新过消息列表
          if(this.firstRunFlag==true){
            // 如果不是正在聊天的人给我回消息,就更新消息列表,为防止没有对话框的用户不在信息列表,
            if(this.dialogInfo.other_userid!=redata.sender){
              // getChatmessage()该函数为获取用户的列表,比如小明,小红等
              this.getChatmessage()
            }
            this.other_id=redata.sender
            this.firstRunFlag=false
          }
          // 当继续接受到数据时,由于已经记录过更新消息列表了,所以上一步不会执行
          // 当接收的数据如果和上次接收的数据发送ID相同时,也不会更新消息列表,因为该用户一定会有对话框在消息列表
          // 当接收的数据如果和上次接收的数据发送ID不相同时,即另外一个用户给我发消息时,
          // 又要更新消息列表,同样是防止没有对话框的用户不在信息列表
          if(this.other_id!=redata.sender){
            if(this.dialogInfo.other_userid!=redata.sender){
              this.getChatmessage()
            }
            this.firstRunFlag=true
          }
          //聊天框的人给你发消息时
          if(this.dialogInfo.other_userid==redata.sender){
            // userInfoList为信息列表
            this.userInfoList.push({
              url: this.urlhh, // url为用户的头像,
              info: redata.text, // 返回的文本信息
              timer: this.dealDate(), // 收到信息的时间
              position: "left", // 是对方给我发送信息,所以该信息应该是展示在左边
            });
          }
          // 这个for循环的意思是,遍历整个用户列表,找到发送的人的对话列表,然后将最近的信息和时间进行更新
          for(let i=0;i<this.userListData.length;i++){
            if(this.userListData[i].other_userid==redata.sender){
              this.userListData[i].last_message.text=redata.text,
              this.userListData[i].last_message.created=this.dealDate(new Date())
              break
            } 
          }
          // 收到信息就把信息列表的滚动条置底
          this.$nextTick(() => { // 一定要用nextTick
            this.setPageScrollTo();
            //页面滚动条距离顶部高度等于这个盒子的高度
            this.$refs.scrollBox.scrollTop = this.$refs.scrollBox.scrollHeight;
          })
        } else if (redata.msg_type==2) {
            window.console.log('下线')
        } else if (redata.msg_type==1) {
            window.console.log('上线')
        }else if (redata.msg_type==9) {
            // unread_count为未读消息个数,msg_type为9,返回未读信息个数
          if(this.dialogInfo.other_userid==redata.sender&&redata.unread_count!=0){
                // 如果我正在跟小明聊天,他又给我发个信息,我这个方法不能自动已读,只能手动发送信息,来告诉服务器我已读了
						let actions = {msg_type:6,user_pk:this.dialogInfo.other_userid,random_id:-5,dialog_id:this.dialogInfo.id}
						this.websocketsend(JSON.stringify(actions))
						// 上述就是手动发送已读,msg_type为6向服务器发送已读信息
						// for循环遍历用户列表,如果发送者的ID是和正在跟我聊天人的ID相等,就强制将用户列表上的跟我聊天的未读信息为0
						for(let i=0;i<this.userListData.length;i++){
							if(this.userListData[i].other_userid==redata.sender){
								// this.userListData[i].unread_count=0
								let user = this.userListData[i]
								user.unread_count=0
								this.$set(this.userListData, i, user);
								break
							} 
						}
					}
          for(let i=0;i<this.userListData.length;i++){
            if(this.userListData[i].other_userid==redata.sender){
              if(this.userListData[i].other_userid==this.dialogInfo.other_userid){
								// for循环遍历用户列表,如果发送者的ID是和正在跟我聊天人的ID相等,就强制将用户列表上的跟我聊天的未读信息为0
                // this.userListData[i].unread_count=0
                let user = this.userListData[i]
                user.unread_count=0
                this.$set(this.userListData, i, user);
              }else{
								// 如果发送者的ID和正在跟我聊天人的ID不相等,将用户列表上的那个人的未读信息重新渲染成最新的
                this.userListData[i].unread_count=redata.unread_count
                break
              }
            } 
          }
        }
      },
      websocketsend(Data) {
        //数据发送
        this.websock.send(Data)
      },
      websocketclose() {
        //关闭
      },
      //获取用户自己的信息
      getSelf() {
				//getUserDetail接口为自己写的获取自己的用户信息的接口,如头像,昵称,ID等
        getUserDetail().then((response) => {
          this.selfListData = response.data
        }).catch(function () {
        });
      },
      load(){
        this.page1++
				//获取下一页聊天列表
        this.getChatnextmessage()
      },
			// 滚动条置底时触发
      scrollEvent (e) {
        if (e.srcElement.scrollTop + e.srcElement.clientHeight + 1 > e.srcElement.scrollHeight) {
					//当isUpdate为true时,表示用户列表信息还存在,就要继续获取下一页,当isUpdate为false时,用户信息列表没有更多了
          if(this.isUpdate) {
            this.load();
          } else {
              this.$message("消息列表已加载完毕");
          }
        }
      },
      //获取下一页列表聊天数据
      getChatnextmessage() {
        return new Promise(resolve=>{
					// chatexport为自己写的获取用户信息列表的接口,我这里是一次传20条数据
        chatexport({
          page:this.page1
        }).then((response)=> {
          if(response.data.results.length<20){
						// 如果返回的信息列表条数小于20,就代表着不会有更多的信息了
            this.isUpdate=false
          }
          let userListDatanext = response.data.results
          userListDatanext.forEach(item=>{
            if(item.other_userimage==null){
              item.other_userimage='这里放上匿名用户的头像'
            }
						// 处理时间的格式
            item.last_message.created = this.dealDate(item.last_message.created)
          })
					// 将获取的下一页数据合并到用户列表数组中
          this.userListData = this.userListData.concat(userListDatanext);
          resolve()
        }).catch(function (error) {
            window.console.log(error);
            resolve()
        });
      })
      },
      //获取全部渲染到列表聊天数据
      getChatmessage() {
        return new Promise(resolve=>{
					// 滚动条置顶
          this.$nextTick(() => {
            let scrollEl = this.$refs.mianscroll;
            scrollEl.scrollTo({ top: 0, behavior: 'smooth' });
          });
          this.isUpdate=true
          this.page1=1
					//原理和上个函数类似
          chatexport({
            page:this.page1
          }).then((response)=> {
            if(response.data.results.length<20){
              this.isUpdate=false
            }
            this.userListData = response.data.results
            this.userListData.forEach(item=>{
                if(item.other_userimage==null){
                  item.other_userimage='这里放上匿名用户的头像'
                }
                item.last_message.created = this.dealDate(item.last_message.created)
              })
              resolve()
          }).catch(function (error) {
              window.console.log(error);
              resolve()
          });
        })
      },
      //获取个人聊天数据
      getPriChatmessage() {
        return new Promise((resolve)=>{
					//chatPriMessage为自己写的获取自己和别人的聊天记录,一次传20条数据
        chatPriMessage({
          dialog_with_id:this.act,//传值dialog_with_id为聊天对象的ID,我这里就是传对话框的ID,其实就是唯一识别就行
          page:this.page
        }).then((response)=> {
          if(response.data.results.length==0){
						//比如有40条聊天数据,获取第三页就为0了,就不能在继续获取了
            this.abcd=1
						//abcd为标识,为1时,不能再继续获取聊天数据了
            resolve()
						// 长度为0时,没数据了就return
            return
          }
          response.data.results.forEach(item=>{
						//is_out为true时为我发的信息,放右边
            if(item.is_out){
							//username字段可有可无(全文都是)
              this.userInfoList.unshift({
                url: this.selfListData.wechat_headimgurl,
                username: this.selfListData.name,
                info: item.text,
                timer: this.dealDate(item.created),//created字段为时间
                position: "right",
              });
              resolve()
            }else{
							//is_out为false时为聊天对象发的信息,放左边
              if(item.recipient_url){
                this.urlhh=item.recipient_url
              }else{
                this.urlhh='这里放匿名图片链接'
              }
							this.userInfoList.unshift({
							  url: this.urlhh,
							  username: item.recipient_username,
							  info: item.text,
							  timer: this.dealDate(item.created),
							  position: "left",
							});
              resolve()
            }
          })  
        }).catch(function (error) {
            window.console.log(error);
        });
      })
      },
      //获取个人最近感兴趣聊天数据(可有可无)
      getRecentmessage() {
        return new Promise((resolve)=>{
					// 这个接口可有可无
          getRecentgoods({
          dialog_with_id:this.act
        }).then((response)=> { 
          if(response.data.results.length){
            this.recenttext=response.data.results[0].text
            this.recenttext=this.recenttext.substr(0,50)
            this.goodsID=response.data.results[0].object_id
            this.category=response.data.results[0].category
          }else{
            this.recenttext=''
          }
          resolve()  
        }).catch(function (error) {
            window.console.log(error);
        });
      })
      },
			//此功能可有可无
      toDetail(){
        if(this.category==1){
          window.open('要去的链接', "_blank");
        }else if(this.category==2){
          window.open('要去的链接', "_blank");
        }else if(this.category==3){
          window.open('要去的链接', "_blank");
        }
      },
      //点击用户
      async getAct(val, index) {
        this.dialogInfo = val //点击用户列表上的详细信息
        this.page=1  
        this.isshow = 1   //更改样式
        this.index=index
        this.act=val.other_userid
        this.getRecentmessage() //可有可无
        this.$bus.$emit("hello", this.userListData[index].unread_count);//传值给其他组件
        this.userListData[index].unread_count=0		//未读改为0
        let actions = {msg_type:6,user_pk:val.other_userid,random_id:-7,dialog_id:val.id}
        this.websocketsend(JSON.stringify(actions))//发送已读信息
        // 点击用户切换数据时先清除监听滚动事件,防止出现没有历史数据的用户,滚动条为0,会触发滚动事件
        this.$refs.scrollBox.removeEventListener("scroll", this.srTop);
        //点击变色
        this.userInfoList = [];
        await this.getPriChatmessage()
				//滚动条置底
        this.$nextTick(() => { 
          this.setPageScrollTo();
          this.$refs.scrollBox.scrollTop = this.$refs.scrollBox.scrollHeight;
        })
      },
      //发送
      setUp() {
        if(this.textarea == "") {
          alert("发送信息不能为空!");
          return;
        }
        let actions = {text:this.textarea,msg_type:3,user_pk:this.act,random_id:-7}
        this.websocketsend(JSON.stringify(actions)) //发送至服务器
        this.userInfoList.push({
          url: this.selfListData.wechat_headimgurl,
          username: "超人",
          info: this.textarea,
          timer: this.dealDate(),
          position: "right",
        });//渲染到信息列表
        this.dialogInfo.last_message.text=this.textarea,//更新用户列表最近信息
        this.dialogInfo.last_message.created=this.dealDate(new Date()), //更新用户列表最近时间
        this.textarea = "";
        // 页面滚动到底部
        this.$nextTick(() => { // 一定要用nextTick
          this.setPageScrollTo();
          //页面滚动条距离顶部高度等于这个盒子的高度
          this.$refs.scrollBox.scrollTop = this.$refs.scrollBox.scrollHeight;
        })
      },
      // 监听键盘回车阻止换行并发送
      handlePushKeyword(event) {
        if (event.keyCode === 13) {
          event.preventDefault(); // 阻止浏览器默认换行操作
          this.setUp(); //发送文本
          return false;
        }
      },
      // 监听按的是ctrl + 回车,就换行
      lineFeed() {
        this.textarea = this.textarea + "\n";
      },
      //点击icon
      extend(val) {
        alert("你点击了:" + val);
      },
      //滚动条默认滚动到最底部
      setPageScrollTo() {
        //获取中间内容盒子的可见区域高度
        this.scrollTop = document.querySelector("#box").offsetHeight;
        setTimeout(() => {
          //加个定时器,防止上面高度没获取到,再获取一遍。
          if (this.scrollTop != this.$refs.scrollBox.offsetHeight) {
            this.scrollTop = document.querySelector("#box").offsetHeight;
          }
        }, 100);
        //scrollTop:滚动条距离顶部的距离。
        //把上面获取到的高度座位距离,把滚动条顶到最底部
        this.$refs.scrollBox.scrollTop = this.scrollTop;
        //判断是否有滚动条,有滚动条就创建一个监听滚动事件,滚动到顶部触发srTop方法
        this.$refs.scrollBox.addEventListener("scroll", this.srTop);
      },
      //滚动条到达顶部
      srTop() {
        //判断:当滚动条距离顶部为0时代表滚动到顶部了
        if (this.$refs.scrollBox.scrollTop == 0 && this.abcd == 0) {
          this.page = this.page + 1
					//获取下一页聊天数据
          this.getPriChatmessage()
        }
      },
			//处理时间函数
      dealDate(time) {
        let d = time ? new Date(time) : new Date();
        let year = d.getFullYear();
        let month = d.getMonth() + 1;
        let day = d.getDate();
        let hours = d.getHours();
        let min = d.getMinutes();
        let seconds = d.getSeconds();
        if (month < 10) month = '0' + month;
        if (day < 10) day = '0' + day;
        if (hours < 0) hours = '0' + hours;
        if (min < 10) min = '0' + min;
        if (seconds < 10) seconds = '0' + seconds;
  
        return (year + '/' + month + '/' + day + ' ' + hours + ':' + min);
      }
    },
  };
  </script>

3:总结

上述代码中注释部分已经将前端的代码逻辑讲的大概了,其中后端接口和后端字段数据,我这是从后端同事取得。应当选择适合自己的方法和逻辑。

2023/10/23,记录

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现在Spring Boot和Vue中使用WebSocket实现实时聊天的过程如下: 1. 后端使用Spring Boot,首先需要在pom.xml文件中添加依赖项以支持WebSocket和Spring Boot的WebSocket集成。例如: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2. 创建一个WebSocket配置类,用于配置WebSocket处理程序和端点。例如: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new ChatHandler(), "/chat").setAllowedOrigins("*"); } } ``` 3. 创建WebSocket处理程序,用于处理WebSocket连接、消息发送和接收。例如: ```java @Component public class ChatHandler extends TextWebSocketHandler { private static final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.add(session); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String payload = message.getPayload(); for (WebSocketSession currentSession : sessions) { currentSession.sendMessage(new TextMessage(payload)); } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { sessions.remove(session); } } ``` 4. 在Vue中使用`vue-native-websocket`或`vue-socket.io`等库来创建WebSocket连接并处理消息。例如: ```javascript import VueNativeSock from 'vue-native-websocket' Vue.use(VueNativeSock, 'ws://localhost:8080/chat', { format: 'json', reconnection: true, store: VuexStore // 如果需要将消息存储到Vuex中,可以提供一个Vuex store }) ``` 5. 在Vue组件中使用WebSocket连接,发送和接收消息。例如: ```javascript this.$socket.send('Hello') // 发送消息 this.$socket.onMessage((message) => { console.log(message) // 收到消息 }) ``` 通过上述步骤,就可以在Spring Boot和Vue中使用WebSocket实现实时聊天功能。当用户在Vue组件中发送消息时,消息将通过WebSocket连接发送到后端的Spring Boot应用程序,然后由WebSocket处理程序将消息广播给所有连接的客户端。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值