【分布式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,当然是有缺陷的。后续根据情况再进行优化。页面如下。

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

如下

客户端消息操作主要是 .

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

vuex基本概念概述

js 复制代码
export default createStore({
  state,
  mutations,
  actions,
  getters,
  modules: {}
})

大概就是这么几个部分。

总结来说,Getter 用于获取由 state 计算得出的数据;

Mutation 用于同步地改变 state,

Action 则封装了异步操作,并最终通过 commit 来间接触发 mutations 更新状态。

state 就是封装变量的地方。

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

场景介绍

消息查询
js 复制代码
    // 创建一个计算属性,该属性基于其他响应式状态计算值
    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];
    });

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

对应前台页面

渲染一下这个compute属性

js 复制代码
   <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>

消息添加

分为离线数据添加 和在线数据添加。

离线数据添加需要使用到action,异步的去后台拉取然后插入。

在线数据直接调用mutation数据插入。

js 复制代码
 /**
   * 插入消息.
   * @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");
  },

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

js 复制代码
 /**
   *  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));
  },

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

强调一下这个离线消息拉取的步骤;需要后台sql的配合。
js 复制代码
 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]); 
    }
   
  }

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

相关推荐
崔庆才丨静觅16 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606117 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了17 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅17 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅18 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅18 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment18 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅18 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊18 小时前
jwt介绍
前端
爱敲代码的小鱼19 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax