码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 【分布式websocket 】前端vuex管理客户端消息crud!使用localStorage来存储【第19期】


    前言

    聊天系统客户端是要存储消息的,因为所有所有的历史消息都从服务器拉的话一方面服务器压力大,另一方面也耗费用户流量。所以客户端存储消息是势在必行的。如何存储呢上一篇文章也写了,大概就是浏览器的话是localStorage或者IndexedDB。然后手机端和桌面端就是sqllite了。这样子消息的存储结构以及消息的增删改查也是需要一套的了。本篇文章将着重从自己的开源项目技术选型来进行分享。vuex进行增删改查。B站会录制视频同步分享。

    目前已经写的文章有。并且有对应视频版本。
    git项目地址 【IM即时通信系统(企聊聊)】点击可跳转
    sprinboot单体项目升级成springcloud项目 【第一期】
    前端项目技术选型以及页面展示【第二期】
    分布式权限 shiro + jwt + redis【第三期】
    给为服务添加运维模块 统一管理【第四期】
    微服务数据库模块【第五期】
    netty与mq在项目中的使用(第六期(废弃))】
    分布式websocket即时通信(IM)系统构建指南【第七期】
    分布式websocket即时通信(IM)系统保证消息可靠性【第八期】
    分布式websocket IM聊天系统相关问题问答【第九期】
    什么?websocket也有权限!这个应该怎么做?【第十期】
    分布式ID是什么,以美团Leaf为例改造融入自己项目【第十一期】
    IM聊天系统为什么需要做消息幂等?如何使用Redis以及Lua脚本做消息幂等【第12期】
    微信发送一条消息经历哪些过程。企业微信以及钉钉的IM架构对比【第13期】
    微信群为什么上限是500人,IM设计系统中的群聊的设计难点【第14期】
    【分布式websocket】RocketMQ发送消息保证消息最终一致性需要做哪些处理?【第15期】

    【分布式websocket】群聊中的各种难点以及解决推拉结合【第16期】

    【分布式webscoket】未读消息如何设计?解决缓存与数据库数据一致性!推送未读消息流程【第17期】

    IM系统客户端消息存储在手机电脑浏览器分别存储在什么地方?对消息加密策略?如何保证服务端消息和客户端消息一致性【第18期】
    【分布式websocket】聊天系统消息加密如何做【第20期】
    【分布式webscoket】IM聊天系统消息如何存储 如何分库分表以及Seata解决事务以及ShardingSphere-Scaling解决数据迁移【第21期】

    客户端消息结构:

    技术选型在浏览器端的localStorage,当然是有缺陷的。后续根据情况再进行优化。页面如下。
    在这里插入图片描述

    在这里插入图片描述

    chats: []            数组 存放每一个聊天用户
    	lastContent:	 存放最后一条消息用于显示
    	tagrgetId: 		 标识唯一会话,可以考虑改成会话id,目前使用的是单聊是对方的id,群聊是群聊id
    	type :			 标记是私聊还是群聊
    	unreadCount:	 未读消息数量 (TODO)
    	messgaes :[]    存放每一个用户下面具体的聊天消息,数组
    		type:		 用于标记消息是自己发的还是别人的,用于前端显示样式
    		group:        用于区分消息是群聊还是单聊 ,创建消息的时候会使用到
    		msgid:        消息唯一id
    		avatarUrl:    用于页面上面显示聊天头像框
    		content:	     消息内容
    		tagrgetId:     标识唯一会话id
    privateMsgMaxId: 拉取消息id
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    如下
    客户端消息操作主要是 .

    • 添加 1.上线后拉取未读消息要存储未读消息 2.发送消息需要添加消息3.收到消息需要添加
    • 查询 进入聊天页面需要可以查到消息
    • 修改 消息发送失败需要修改状态发送失败
    • 删除 前端存储有限制只能维护一定时间的历史消息。更多的历史消息查询客户端

    vuex基本概念概述

    export default createStore({
      state,
      mutations,
      actions,
      getters,
      modules: {}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    大概就是这么几个部分。
    总结来说,Getter 用于获取由 state 计算得出的数据;
    Mutation 用于同步地改变 state,
    Action 则封装了异步操作,并最终通过 commit 来间接触发 mutations 更新状态。
    state 就是封装变量的地方。

    Vuex 允许将应用程序的状态集中存储在一个共享的 store 中,避免了组件之间通过 props 和 events 进行状态传递的复杂性和繁琐性。这使得状态管理更加清晰和易于维护。通过 Vuex 管理状态,整个应用程序共享同一个状态树,确保了状态的一致性和同步性。

    场景介绍

    消息查询
    
        // 创建一个计算属性,该属性基于其他响应式状态计算值
        const computedChats = computed(() => {
          let chat = null;
          console.log("computedChats route.query.groupId", route.query.groupId);
          if (state.current == 1) {
            chat = {
              targetId: state.toUser.openid,
            };
          } else {
            chat = {
              // targetId: state.toUser.openid,
              targetId: state.groupId,
            };
          }
          const idx = store.getters.findChatIdx(chat);
          if (idx == null || idx == undefined) {
            return [];
          }
          if (
            store.state.chats[idx] == null ||
            store.state.chats[idx] == undefined
          ) {
            return [];
          }
          console.log("computedChats idx", idx);
          console.log("computedChats 寻找成功啦", store.state.chats[idx]);
          return store.state.chats[idx];
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    封装了一个计算属性。用于监听state里面的消息变化。逻辑大概是拿到当前会话的id,单聊的话就是对话的openid。然后去store里面去找一下。找不到的返回空数组。找到的话返回当前聊天下的所有信息.

    对应前台页面

    渲染一下这个compute属性

       <list-scroll :scroll-data="computedChats.messages">
                <div class="swiper-container">
                  <div
                    class="content"
                    v-for="(item, index) in computedChats.messages"
                    :key="index"
                  >
                    <div class="d-felx justify-start " v-if="item.type === 'self'">
                      <div style="display: flex;">
                        <van-image
                          width="35px"
                          height="35px"
                          fit="cover"
                          :src="userInfo.avatarUrl"
                        />
                        <div class="font-18 content1">
                          <text>{{ item.content }}</text>
                        </div>
                      </div>
                    </div>
                    <div
                      style="display: flex; justify-content: flex-end;"
                      v-if="item.type === 'receive'"
                    >
                      <div class="font-18 content2">
                        <text>{{ item.content }}</text>
                      </div>
                      <div class="">
                        <van-image
                          width="35px"
                          height="35px"
                          fit="cover"
                          :src="toUser.avatarUrl"
                        />
                      </div>
                    </div>
                  </div>
                </div>
              </list-scroll>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    消息添加

    分为离线数据添加 和在线数据添加。
    离线数据添加需要使用到action,异步的去后台拉取然后插入。
    在线数据直接调用mutation数据插入。

     /**
       * 插入消息.
       * @param {*} state
       * @param {*} msgInfo 当前消息
       */
      insertMessage(state, msgInfo) {
        console.log("insertMessage",msgInfo)
          state.privateMsgMaxId = msgInfo.msgId;
          state.groupMsgMaxId = msgInfo.msgId;
        // 如果是已存在消息,则覆盖旧的消息数据
        let chat = this.getters.findChat(msgInfo);
        if (chat == null) {
          this.commit("createChat", msgInfo);
          chat = this.getters.findChat(msgInfo);
        }
        if(chat == null){
          console.log("没有找到chat",chat);
          return;
        }
        chat.messages.push(msgInfo);
        this.commit("saveToStorage");
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    第一步,先更新一下这个最大的消息id。然后去store里面去找当前消息。找到之后给当前chat里面推送消息。并且同步的保存到Storgae里面.

    
     /**
       *  state.chats 将更新后的存储.
       * @param {*} state
       */
      saveToStorage(state) {
        let userId = state.userInfo.openid;
        let key = "chats-" + userId;
        let chatsData = {
          privateMsgMaxId: state.privateMsgMaxId,
          groupMsgMaxId: state.groupMsgMaxId,
          chats: state.chats,
        };
        localStorage.setItem(key, JSON.stringify(chatsData));
      },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这个存储的逻辑就是简单的将消息序列化后放到localStorage里面。

    强调一下这个离线消息拉取的步骤;需要后台sql的配合。
     async pullOffline(ctx) {
        // 获取当前store中的privateMsgMaxId
        const privateMsgMaxId = ctx.state.privateMsgMaxId+"";
        console.log("privateMsgMaxId",ctx.state.privateMsgMaxId)
        const res = await getChatContentAll(privateMsgMaxId);
        const contentAll = res.content
        for (var i = 0; i < contentAll.length; i++) { 
          ctx.commit("initInsertMessage", contentAll[i]); 
        }
       
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    调用后台接口getChatContentAll 然后获取未拉取的离线消息然后进行存储。

  • 相关阅读:
    LAXCUS分布式操作系统是怎么实现的?
    中创技术|2022全球最常用密码名单,快看有没有你的
    DDR3-基于正点原子达芬奇100Tpro1.0版本
    如何克服发票自动化的 4 个最常见障碍
    48 基于 jdk9 编译的 jdk8 的字节码报错
    C++多线程编程(第四章 promise和future)
    亚马逊卖家售后管理做得好,差评投诉不见了!一文分享售后管理技巧
    PyQt5报错Process finished with exit code -1073740791 (0xC0000409)
    【《高性能 MySQL》摘录】第 8 章 优化服务器设置
    SIM8100、SIM8800CE、SIM8800E 车规级模组 特性
  • 原文地址:https://blog.csdn.net/qq_21561833/article/details/136709894
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号