做倫理電影網(wǎng)站百度推廣關(guān)鍵詞質(zhì)量度
前言
聊天系統(tǒng)客戶端是要存儲消息的,因為所有所有的歷史消息都從服務(wù)器拉的話一方面服務(wù)器壓力大,另一方面也耗費用戶流量。所以客戶端存儲消息是勢在必行的。如何存儲呢上一篇文章也寫了,大概就是瀏覽器的話是localStorage或者IndexedDB。然后手機端和桌面端就是sqllite了。這樣子消息的存儲結(jié)構(gòu)以及消息的增刪改查也是需要一套的了。本篇文章將著重從自己的開源項目技術(shù)選型來進行分享。vuex進行增刪改查。B站會錄制視頻同步分享。
目前已經(jīng)寫的文章有。并且有對應(yīng)視頻版本。
git項目地址 【IM即時通信系統(tǒng)(企聊聊)】點擊可跳轉(zhuǎn)
sprinboot單體項目升級成springcloud項目 【第一期】
前端項目技術(shù)選型以及頁面展示【第二期】
分布式權(quán)限 shiro + jwt + redis【第三期】
給為服務(wù)添加運維模塊 統(tǒng)一管理【第四期】
微服務(wù)數(shù)據(jù)庫模塊【第五期】
netty與mq在項目中的使用(第六期(廢棄))】
分布式websocket即時通信(IM)系統(tǒng)構(gòu)建指南【第七期】
分布式websocket即時通信(IM)系統(tǒng)保證消息可靠性【第八期】
分布式websocket IM聊天系統(tǒng)相關(guān)問題問答【第九期】
什么?websocket也有權(quán)限!這個應(yīng)該怎么做?【第十期】
分布式ID是什么,以美團Leaf為例改造融入自己項目【第十一期】
IM聊天系統(tǒng)為什么需要做消息冪等?如何使用Redis以及Lua腳本做消息冪等【第12期】
微信發(fā)送一條消息經(jīng)歷哪些過程。企業(yè)微信以及釘釘?shù)腎M架構(gòu)對比【第13期】
微信群為什么上限是500人,IM設(shè)計系統(tǒng)中的群聊的設(shè)計難點【第14期】
【分布式websocket】RocketMQ發(fā)送消息保證消息最終一致性需要做哪些處理?【第15期】
【分布式websocket】群聊中的各種難點以及解決推拉結(jié)合【第16期】
【分布式webscoket】未讀消息如何設(shè)計?解決緩存與數(shù)據(jù)庫數(shù)據(jù)一致性!推送未讀消息流程【第17期】
IM系統(tǒng)客戶端消息存儲在手機電腦瀏覽器分別存儲在什么地方?對消息加密策略?如何保證服務(wù)端消息和客戶端消息一致性【第18期】
【分布式websocket】聊天系統(tǒng)消息加密如何做【第20期】
【分布式webscoket】IM聊天系統(tǒng)消息如何存儲 如何分庫分表以及Seata解決事務(wù)以及ShardingSphere-Scaling解決數(shù)據(jù)遷移【第21期】
客戶端消息結(jié)構(gòu):
技術(shù)選型在瀏覽器端的localStorage,當然是有缺陷的。后續(xù)根據(jù)情況再進行優(yōu)化。頁面如下。
chats: [] 數(shù)組 存放每一個聊天用戶lastContent: 存放最后一條消息用于顯示tagrgetId: 標識唯一會話,可以考慮改成會話id,目前使用的是單聊是對方的id,群聊是群聊idtype : 標記是私聊還是群聊unreadCount: 未讀消息數(shù)量 (TODO)messgaes :[] 存放每一個用戶下面具體的聊天消息,數(shù)組type: 用于標記消息是自己發(fā)的還是別人的,用于前端顯示樣式group: 用于區(qū)分消息是群聊還是單聊 ,創(chuàng)建消息的時候會使用到msgid: 消息唯一idavatarUrl: 用于頁面上面顯示聊天頭像框content: 消息內(nèi)容tagrgetId: 標識唯一會話id
privateMsgMaxId: 拉取消息id
如下
客戶端消息操作主要是 .
- 添加 1.上線后拉取未讀消息要存儲未讀消息 2.發(fā)送消息需要添加消息3.收到消息需要添加
- 查詢 進入聊天頁面需要可以查到消息
- 修改 消息發(fā)送失敗需要修改狀態(tài)發(fā)送失敗
- 刪除 前端存儲有限制只能維護一定時間的歷史消息。更多的歷史消息查詢客戶端
vuex基本概念概述
export default createStore({state,mutations,actions,getters,modules: {}
})
大概就是這么幾個部分。
總結(jié)來說,Getter 用于獲取由 state 計算得出的數(shù)據(jù);
Mutation 用于同步地改變 state,
Action 則封裝了異步操作,并最終通過 commit 來間接觸發(fā) mutations 更新狀態(tài)。
state 就是封裝變量的地方。
Vuex 允許將應(yīng)用程序的狀態(tài)集中存儲在一個共享的 store 中,避免了組件之間通過 props 和 events 進行狀態(tài)傳遞的復(fù)雜性和繁瑣性。這使得狀態(tài)管理更加清晰和易于維護。通過 Vuex 管理狀態(tài),整個應(yīng)用程序共享同一個狀態(tài)樹,確保了狀態(tài)的一致性和同步性。
場景介紹
消息查詢
// 創(chuàng)建一個計算屬性,該屬性基于其他響應(yīng)式狀態(tài)計算值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];});
封裝了一個計算屬性。用于監(jiān)聽state里面的消息變化。邏輯大概是拿到當前會話的id,單聊的話就是對話的openid。然后去store里面去找一下。找不到的返回空數(shù)組。找到的話返回當前聊天下的所有信息.
對應(yīng)前臺頁面
渲染一下這個compute屬性
<list-scroll :scroll-data="computedChats.messages"><div class="swiper-container"><divclass="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-imagewidth="35px"height="35px"fit="cover":src="userInfo.avatarUrl"/><div class="font-18 content1"><text>{{ item.content }}</text></div></div></div><divstyle="display: flex; justify-content: flex-end;"v-if="item.type === 'receive'"><div class="font-18 content2"><text>{{ item.content }}</text></div><div class=""><van-imagewidth="35px"height="35px"fit="cover":src="toUser.avatarUrl"/></div></div></div></div></list-scroll>
消息添加
分為離線數(shù)據(jù)添加 和在線數(shù)據(jù)添加。
離線數(shù)據(jù)添加需要使用到action,異步的去后臺拉取然后插入。
在線數(shù)據(jù)直接調(diào)用mutation數(shù)據(jù)插入。
/*** 插入消息.* @param {*} state* @param {*} msgInfo 當前消息*/insertMessage(state, msgInfo) {console.log("insertMessage",msgInfo)state.privateMsgMaxId = msgInfo.msgId;state.groupMsgMaxId = msgInfo.msgId;// 如果是已存在消息,則覆蓋舊的消息數(shù)據(jù)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里面.
/*** 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里面。
強調(diào)一下這個離線消息拉取的步驟;需要后臺sql的配合。
async pullOffline(ctx) {// 獲取當前store中的privateMsgMaxIdconst privateMsgMaxId = ctx.state.privateMsgMaxId+"";console.log("privateMsgMaxId",ctx.state.privateMsgMaxId)const res = await getChatContentAll(privateMsgMaxId);const contentAll = res.contentfor (var i = 0; i < contentAll.length; i++) { ctx.commit("initInsertMessage", contentAll[i]); }}
調(diào)用后臺接口getChatContentAll 然后獲取未拉取的離線消息然后進行存儲。