Просмотр исходного кода

优化: 提升拉取离线消息效率

xsx 7 месяцев назад
Родитель
Сommit
4f205e1cbe

+ 8 - 1
im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java

@@ -35,12 +35,19 @@ public class GroupMessageController {
     }
 
     @GetMapping("/pullOfflineMessage")
-    @Operation(summary = "拉取离线消息", description = "拉取离线消息,消息将通过webscoket异步推送")
+    @Operation(summary = "拉取离线消息(已废弃)", description = "拉取离线消息,消息将通过webscoket异步推送")
     public Result pullOfflineMessage(@RequestParam Long minId) {
         groupMessageService.pullOfflineMessage(minId);
         return ResultUtils.success();
     }
 
+    @GetMapping(value = "/loadOfflineMessage")
+    @Operation(summary = "拉取离线消息", description = "拉取离线消息")
+    public Result<List<GroupMessageVO>> loadOfflineMessage(@RequestParam Long minId) {
+        return ResultUtils.success(groupMessageService.loadOffineMessage(minId));
+    }
+
+
     @PutMapping("/readed")
     @Operation(summary = "消息已读", description = "将群聊中的消息状态置为已读")
     public Result readedMessage(@RequestParam Long groupId) {

+ 8 - 1
im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java

@@ -35,12 +35,19 @@ public class PrivateMessageController {
     }
 
     @GetMapping("/pullOfflineMessage")
-    @Operation(summary = "拉取离线消息", description = "拉取离线消息,消息将通过webscoket异步推送")
+    @Operation(summary = "拉取离线消息(已废弃)", description = "拉取离线消息,消息将通过webscoket异步推送")
     public Result pullOfflineMessage(@RequestParam Long minId) {
         privateMessageService.pullOfflineMessage(minId);
         return ResultUtils.success();
     }
 
+    @GetMapping(value = "/loadOfflineMessage")
+    @Operation(summary = "拉取离线消息", description = "拉取离线消息")
+    public Result<List<PrivateMessageVO>> loadOfflineMessage(@RequestParam Long minId) {
+        return ResultUtils.success(privateMessageService.loadOfflineMessage(minId));
+    }
+
+
     @PutMapping("/readed")
     @Operation(summary = "消息已读", description = "将会话中接收的消息状态置为已读")
     public Result readedMessage(@RequestParam Long friendId) {

+ 8 - 0
im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java

@@ -31,6 +31,14 @@ public interface GroupMessageService extends IService<GroupMessage> {
      */
     void  pullOfflineMessage(Long minId);
 
+    /**
+     * 拉取离线消息,只能拉取最近1个月的消息
+     *
+     * @param minId 消息起始id
+     */
+    List<GroupMessageVO> loadOffineMessage(Long minId);
+
+
     /**
      * 消息已读,同步其他终端,清空未读数量
      *

+ 8 - 0
im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java

@@ -43,6 +43,14 @@ public interface PrivateMessageService extends IService<PrivateMessage> {
      */
     void pullOfflineMessage(Long minId);
 
+    /**
+     * 拉取离线消息,只能拉取最近1个月的消息
+     *
+     * @param minId 消息起始id
+     */
+    List<PrivateMessageVO> loadOfflineMessage(Long minId);
+
+
     /**
      * 消息已读,将整个会话的消息都置为已读状态
      *

+ 79 - 4
im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java

@@ -80,6 +80,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
         msg.setSendTime(new Date());
         msg.setSendNickName(member.getShowNickName());
         msg.setAtUserIds(CommaTextUtils.asText(dto.getAtUserIds()));
+        msg.setStatus(MessageStatus.PENDING.code());
         // 过滤内容中的敏感词
         if (MessageType.TEXT.code().equals(dto.getType())) {
             msg.setContent(sensitiveFilterUtil.filter(dto.getContent()));
@@ -164,10 +165,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
         wrapper.gt(GroupMessage::getSendTime, minDate);
         wrapper.in(GroupMessage::getGroupId, groupIds);
         wrapper.orderByDesc(GroupMessage::getId);
-        if (minId <= 0) {
-            // 首次拉取限制消息数量大小,防止内存溢出
-            wrapper.last("limit 100000");
-        }
+        wrapper.last("limit 50000");
         List<GroupMessage> messages = this.list(wrapper);
         // 通过群聊对消息进行分组
         Map<Long, List<GroupMessage>> messageGroupMap =
@@ -254,6 +252,83 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
         });
     }
 
+    @Override
+    public List<GroupMessageVO> loadOffineMessage(Long minId) {
+        UserSession session = SessionContext.getSession();
+        // 查询用户加入的群组
+        List<GroupMember> members = groupMemberService.findByUserId(session.getUserId());
+        Map<Long, GroupMember> groupMemberMap = CollStreamUtil.toIdentityMap(members, GroupMember::getGroupId);
+        Set<Long> groupIds = groupMemberMap.keySet();
+        // 只能拉取最近1个月的消息
+        Date minDate = DateUtils.addMonths(new Date(), -1);
+        LambdaQueryWrapper<GroupMessage> wrapper = Wrappers.lambdaQuery();
+        wrapper.gt(GroupMessage::getId, minId);
+        wrapper.gt(GroupMessage::getSendTime, minDate);
+        wrapper.in(GroupMessage::getGroupId, groupIds);
+        wrapper.orderByDesc(GroupMessage::getId);
+        wrapper.last("limit 50000");
+        List<GroupMessage> messages = this.list(wrapper);
+        // 退群前的消息
+        List<GroupMember> quitMembers = groupMemberService.findQuitInMonth(session.getUserId());
+        for (GroupMember quitMember : quitMembers) {
+            wrapper = Wrappers.lambdaQuery();
+            wrapper.gt(GroupMessage::getId, minId);
+            wrapper.between(GroupMessage::getSendTime, minDate, quitMember.getQuitTime());
+            wrapper.eq(GroupMessage::getGroupId, quitMember.getGroupId());
+            wrapper.orderByDesc(GroupMessage::getId);
+            wrapper.last("limit 1000");
+            List<GroupMessage> groupMessages = this.list(wrapper);
+            if (!groupMessages.isEmpty()) {
+                messages.addAll(groupMessages);
+                groupMemberMap.put(quitMember.getGroupId(), quitMember);
+            }
+        }
+        // 通过群聊对消息进行分组
+        Map<Long, List<GroupMessage>> messageGroupMap =
+            messages.stream().collect(Collectors.groupingBy(GroupMessage::getGroupId));
+        List<GroupMessageVO> vos = new LinkedList<>();
+        for (Map.Entry<Long, List<GroupMessage>> entry : messageGroupMap.entrySet()) {
+            Long groupId = entry.getKey();
+            List<GroupMessage> groupMessages = entry.getValue();
+            // 填充消息状态
+            String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
+            Object o = redisTemplate.opsForHash().get(key, session.getUserId().toString());
+            long readedMaxId = Objects.isNull(o) ? -1 : Long.parseLong(o.toString());
+            Map<Object, Object> maxIdMap = null;
+            for (GroupMessage m : groupMessages) {
+                // 排除加群之前的消息
+                GroupMember member = groupMemberMap.get(m.getGroupId());
+                if (DateUtil.compare(member.getCreatedTime(), m.getSendTime()) > 0) {
+                    continue;
+                }
+                // 排除不需要接收的消息
+                List<String> recvIds = CommaTextUtils.asList(m.getRecvIds());
+                if (!recvIds.isEmpty() && !recvIds.contains(session.getUserId().toString())) {
+                    continue;
+                }
+                // 组装vo
+                GroupMessageVO vo = BeanUtils.copyProperties(m, GroupMessageVO.class);
+                // 被@用户列表
+                List<String> atIds = CommaTextUtils.asList(m.getAtUserIds());
+                vo.setAtUserIds(atIds.stream().map(Long::parseLong).collect(Collectors.toList()));
+                // 填充状态
+                vo.setStatus(readedMaxId >= m.getId() ? MessageStatus.READED.code() : MessageStatus.PENDING.code());
+                // 针对回执消息填充已读人数
+                if (m.getReceipt()) {
+                    if (Objects.isNull(maxIdMap)) {
+                        maxIdMap = redisTemplate.opsForHash().entries(key);
+                    }
+                    int count = getReadedUserIds(maxIdMap, m.getId(), m.getSendId()).size();
+                    vo.setReadedCount(count);
+                }
+                vos.add(vo);
+            }
+        }
+        // 排序
+        return vos.stream().sorted(Comparator.comparing(GroupMessageVO::getId)).collect(Collectors.toList());
+    }
+
+
     @Override
     public void readedMessage(Long groupId) {
         UserSession session = SessionContext.getSession();

+ 33 - 3
im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java

@@ -32,6 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.stream.Collectors;
@@ -140,9 +141,8 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
         UserSession session = SessionContext.getSession();
         // 获取当前用户的消息
         LambdaQueryWrapper<PrivateMessage> wrapper = Wrappers.lambdaQuery();
-        // 只能拉取最近3个月的消息,移动端只拉取一个月消息
-        int months = session.getTerminal().equals(IMTerminalType.APP.code()) ? 1 : 3;
-        Date minDate = DateUtils.addMonths(new Date(), -months);
+        // 只能拉取最近1个月的消息
+        Date minDate = DateUtils.addMonths(new Date(), -1);
         wrapper.gt(PrivateMessage::getId, minId);
         wrapper.ge(PrivateMessage::getSendTime, minDate);
         wrapper.and(wp -> wp.eq(PrivateMessage::getSendId, session.getUserId()).or()
@@ -175,6 +175,36 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
         });
     }
 
+    @Override
+    public List<PrivateMessageVO> loadOfflineMessage(Long minId) {
+        UserSession session = SessionContext.getSession();
+        // 获取当前用户的消息
+        LambdaQueryWrapper<PrivateMessage> wrapper = Wrappers.lambdaQuery();
+        // 只能拉取最近1个月的消息
+        Date minDate = DateUtils.addMonths(new Date(), -1);
+        wrapper.gt(PrivateMessage::getId, minId);
+        wrapper.ge(PrivateMessage::getSendTime, minDate);
+        wrapper.and(wp -> wp.eq(PrivateMessage::getSendId, session.getUserId()).or()
+            .eq(PrivateMessage::getRecvId, session.getUserId()));
+        wrapper.orderByAsc(PrivateMessage::getId);
+        List<PrivateMessage> messages = this.list(wrapper);
+        // 更新消息为送达状态
+        List<Long> messageIds =
+            messages.stream().filter(m -> m.getStatus().equals(MessageStatus.PENDING.code())).map(PrivateMessage::getId)
+                .collect(Collectors.toList());
+        if (!messageIds.isEmpty()) {
+            LambdaUpdateWrapper<PrivateMessage> updateWrapper = Wrappers.lambdaUpdate();
+            updateWrapper.in(PrivateMessage::getId, messageIds);
+            updateWrapper.set(PrivateMessage::getStatus, MessageStatus.DELIVERED.code());
+            update(updateWrapper);
+        }
+        // 转换vo
+        List<PrivateMessageVO> vos = messages.stream().map(m -> BeanUtils.copyProperties(m, PrivateMessageVO.class))
+            .collect(Collectors.toList());
+        log.info("拉取私聊消息,用户id:{},数量:{},minId:{}", session.getUserId(), messages.size(), minId);
+        return vos;
+    }
+
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void readedMessage(Long friendId) {

+ 53 - 39
im-uniapp/App.vue

@@ -11,7 +11,9 @@ export default {
 		return {
 			isExit: false, // 是否已退出
 			audioTip: null,
-			reconnecting: false // 正在重连标志
+			reconnecting: false, // 正在重连标志
+			privateMessagesBuffer: [],
+			groupMessagesBuffer: [],
 		}
 	},
 	methods: {
@@ -36,8 +38,7 @@ export default {
 					this.onReconnectWs();
 				} else {
 					// 加载离线消息
-					this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId);
-					this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId);
+					this.pullOfflineMessage();
 					this.configStore.setAppInit(true);
 				}
 			});
@@ -50,11 +51,21 @@ export default {
 					})
 					this.exit();
 				} else if (cmd == 3) {
-					// 私聊消息
-					this.handlePrivateMessage(msgInfo);
+					if (!this.configStore.appInit || this.chatStore.loading) {
+						// 如果正在拉取离线消息,先存入缓存区,等待消息拉取完成再处理,防止消息乱序
+						this.privateMessagesBuffer.push(msgInfo);
+					} else {
+						// 插入私聊消息
+						this.handlePrivateMessage(msgInfo);
+					}
 				} else if (cmd == 4) {
-					// 群聊消息
-					this.handleGroupMessage(msgInfo);
+					if (!this.configStore.appInit || this.chatStore.loading) {
+						// 如果正在拉取离线消息,先存入缓存区,等待消息拉取完成再处理,防止消息乱序
+						this.privateMessagesBuffer.push(msgInfo);
+					} else {
+						// 插入私聊消息
+						this.handlePrivateMessage(msgInfo);
+					}
 				} else if (cmd == 5) {
 					// 系统消息
 					this.handleSystemMessage(msgInfo);
@@ -84,30 +95,43 @@ export default {
 			this.configStore.clear();
 			this.userStore.clear();
 		},
+		pullOfflineMessage() {
+			this.chatStore.setLoading(true);
+			const promises = [];
+			promises.push(this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId));
+			promises.push(this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId));
+			Promise.all(promises).then(messages => {
+				// 处理离线消息
+				messages[0].forEach(m => this.handlePrivateMessage(m));
+				messages[1].forEach(m => this.handleGroupMessage(m));
+				// 处理缓冲区收到的实时消息
+				this.privateMessagesBuffer.forEach(m => this.handlePrivateMessage(m));
+				this.groupMessagesBuffer.forEach(m => this.handleGroupMessage(m));
+				// 清空缓冲区
+				this.privateMessagesBuffer = [];
+				this.groupMessagesBuffer = [];
+				// 关闭加载离线标记
+				this.chatStore.setLoading(false);
+				// 刷新会话
+				this.chatStore.refreshChats();
+			}).catch((e) => {
+				console.log(e)
+				this.$message.error("拉取离线消息失败");
+				this.onExit();
+			})
+		},
 		pullPrivateOfflineMessage(minId) {
-			this.chatStore.setLoadingPrivateMsg(true)
-			http({
-				url: "/message/private/pullOfflineMessage?minId=" + minId,
-				method: 'GET'
-			}).catch(() => {
-				uni.showToast({
-					title: "消息拉取失败,请重新登陆",
-					icon: 'none'
-				})
-				this.exit()
+			return this.$http({
+				url: "/message/private/loadOfflineMessage?minId=" + minId,
+				method: 'GET',
+				timeout: 300000
 			})
 		},
 		pullGroupOfflineMessage(minId) {
-			this.chatStore.setLoadingGroupMsg(true)
-			http({
-				url: "/message/group/pullOfflineMessage?minId=" + minId,
-				method: 'GET'
-			}).catch(() => {
-				uni.showToast({
-					title: "消息拉取失败,请重新登陆",
-					icon: 'none'
-				})
-				this.exit()
+			return this.$http({
+				url: "/message/group/loadOfflineMessage?minId=" + minId,
+				method: 'GET',
+				timeout: 300000
 			})
 		},
 		handlePrivateMessage(msg) {
@@ -120,11 +144,6 @@ export default {
 				type: 'PRIVATE',
 				targetId: friendId
 			}
-			// 消息加载标志
-			if (msg.type == enums.MESSAGE_TYPE.LOADING) {
-				this.chatStore.setLoadingPrivateMsg(JSON.parse(msg.content))
-				return;
-			}
 			// 消息已读处理,清空已读数量
 			if (msg.type == enums.MESSAGE_TYPE.READED) {
 				this.chatStore.resetUnreadCount(chatInfo);
@@ -204,7 +223,7 @@ export default {
 				this.chatStore.insertMessage(msg, chatInfo);
 				// 播放提示音
 				this.chatStore.insertMessage(msg, chatInfo);
-				if (!friend.isDnd && !this.chatStore.isLoading() &&
+				if (!friend.isDnd && !this.chatStore.loading &&
 					!msg.selfSend && msgType.isNormal(msg.type) &&
 					msg.status != enums.MESSAGE_STATUS.READED) {
 					this.playAudioTip();
@@ -220,11 +239,6 @@ export default {
 				type: 'GROUP',
 				targetId: msg.groupId
 			}
-			// 消息加载标志
-			if (msg.type == enums.MESSAGE_TYPE.LOADING) {
-				this.chatStore.setLoadingGroupMsg(JSON.parse(msg.content))
-				return;
-			}
 			// 消息已读处理
 			if (msg.type == enums.MESSAGE_TYPE.READED) {
 				// 我已读对方的消息,清空已读数量
@@ -323,7 +337,7 @@ export default {
 				// 插入消息
 				this.chatStore.insertMessage(msg, chatInfo);
 				// 播放提示音
-				if (!group.isDnd && !this.chatStore.isLoading() &&
+				if (!group.isDnd && !this.chatStore.loading &&
 					!msg.selfSend && msgType.isNormal(msg.type) &&
 					msg.status != enums.MESSAGE_STATUS.READED) {
 					this.playAudioTip();

+ 1 - 0
im-uniapp/common/request.js

@@ -17,6 +17,7 @@ const request = (options) => {
 			method: options.method || 'GET',
 			header: header,
 			data: options.data || {},
+			timeout: options.timeout || 3000,
 			async success(res) {
 				if (res.data.code == 200) {
 					return resolve(res.data.data)

+ 1 - 1
im-uniapp/components/chat-item/chat-item.vue

@@ -45,7 +45,7 @@ export default {
 	methods: {
 		showChatBox() {
 			// 初始化期间进入会话会导致消息不刷新
-			if (!this.configStore.appInit || this.chatStore.isLoading()) {
+			if (!this.configStore.appInit || this.chatStore.loading) {
 				uni.showToast({
 					title: "正在初始化页面,请稍后...",
 					icon: 'none'

+ 1 - 2
im-uniapp/pages/chat/chat-box.vue

@@ -325,7 +325,6 @@ export default {
 					let tmpMessage = this.buildTmpMessage(msgInfo);
 					this.chatStore.insertMessage(tmpMessage, chat);
 					this.moveChatToTop();
-
 					this.sendMessageRequest(msgInfo).then((m) => {
 						// 更新消息
 						tmpMessage = JSON.parse(JSON.stringify(tmpMessage));
@@ -542,7 +541,7 @@ export default {
 			// 删除旧消息
 			this.chatStore.deleteMessage(msgInfo, chat);
 			// 重新发送
-			msgInfo.temId = this.generateId();
+			msgInfo.tmpId = this.generateId();
 			let tmpMessage = this.buildTmpMessage(msgInfo);
 			this.chatStore.insertMessage(tmpMessage, chat);
 			this.moveChatToTop();

+ 1 - 1
im-uniapp/pages/chat/chat.vue

@@ -111,7 +111,7 @@ export default {
 			return count;
 		},
 		loading() {
-			return this.chatStore.isLoading();
+			return this.chatStore.loading;
 		},
 		initializing() {
 			return !this.configStore.appInit;

+ 27 - 48
im-uniapp/store/chatStore.js

@@ -3,7 +3,6 @@ import { MESSAGE_TYPE, MESSAGE_STATUS } from '@/common/enums.js';
 import useFriendStore from './friendStore.js';
 import useGroupStore from './groupStore.js';
 import useUserStore from './userStore';
-import useConfigStore from './configStore.js';
 
 let cacheChats = [];
 export default defineStore('chatStore', {
@@ -12,8 +11,7 @@ export default defineStore('chatStore', {
 			chats: [],
 			privateMsgMaxId: 0,
 			groupMsgMaxId: 0,
-			loadingPrivateMsg: false,
-			loadingGroupMsg: false
+			loading: false
 		}
 	},
 	actions: {
@@ -140,7 +138,7 @@ export default defineStore('chatStore', {
 			}
 		},
 		moveTop(idx) {
-			if (this.isLoading()) {
+			if (this.loading) {
 				return;
 			}
 			let chats = this.curChats;
@@ -156,20 +154,18 @@ export default defineStore('chatStore', {
 		insertMessage(msgInfo, chatInfo) {
 			// 获取对方id或群id
 			let type = chatInfo.type;
-			// 完成初始化之前不能修改消息最大id,否则可能导致拉不到离线消息
-			if (useConfigStore().appInit) {
-				// 记录消息的最大id
-				if (msgInfo.id && type == "PRIVATE" && msgInfo.id > this.privateMsgMaxId) {
-					this.privateMsgMaxId = msgInfo.id;
-				}
-				if (msgInfo.id && type == "GROUP" && msgInfo.id > this.groupMsgMaxId) {
-					this.groupMsgMaxId = msgInfo.id;
-				}
+			// 记录消息的最大id
+			if (msgInfo.id && type == "PRIVATE" && msgInfo.id > this.privateMsgMaxId) {
+				this.privateMsgMaxId = msgInfo.id;
+			}
+			if (msgInfo.id && type == "GROUP" && msgInfo.id > this.groupMsgMaxId) {
+				this.groupMsgMaxId = msgInfo.id;
 			}
 			// 如果是已存在消息,则覆盖旧的消息数据
 			let chat = this.findChat(chatInfo);
 			let message = this.findMessage(chat, msgInfo);
 			if (message) {
+				console.log("message:",message)
 				Object.assign(message, msgInfo);
 				chat.stored = false;
 				this.saveToStorage();
@@ -218,24 +214,8 @@ export default defineStore('chatStore', {
 				});
 				chat.lastTimeTip = msgInfo.sendTime;
 			}
-			// 根据id顺序插入,防止消息乱序
-			let insertPos = chat.messages.length;
-			// 防止 图片、文件 在发送方 显示 在顶端  因为还没存库,id=0
-			if (msgInfo.id && msgInfo.id > 0) {
-				for (let idx in chat.messages) {
-					if (chat.messages[idx].id && msgInfo.id < chat.messages[idx].id) {
-						insertPos = idx;
-						console.log(`消息出现乱序,位置:${chat.messages.length},修正至:${insertPos}`);
-						break;
-					}
-				}
-			}
-			if (insertPos == chat.messages.length) {
-				// 这种赋值效率最高
-				chat.messages[insertPos] = msgInfo;
-			} else {
-				chat.messages.splice(insertPos, 0, msgInfo);
-			}
+			// 插入消息
+			chat.messages.push(msgInfo);
 			chat.stored = false;
 			this.saveToStorage();
 		},
@@ -345,17 +325,8 @@ export default defineStore('chatStore', {
 				this.saveToStorage();
 			}
 		},
-		setLoadingPrivateMsg(loading) {
-			this.loadingPrivateMsg = loading;
-			if (!this.isLoading()) {
-				this.refreshChats()
-			}
-		},
-		setLoadingGroupMsg(loading) {
-			this.loadingGroupMsg = loading;
-			if (!this.isLoading()) {
-				this.refreshChats()
-			}
+		setLoading(loading) {
+			this.loading = loading;
 		},
 		setDnd(chatInfo, isDnd) {
 			let chat = this.findChat(chatInfo);
@@ -406,7 +377,7 @@ export default defineStore('chatStore', {
 		},
 		saveToStorage(withColdMessage) {
 			// 加载中不保存,防止卡顿
-			if (this.isLoading()) {
+			if (this.loading) {
 				return;
 			}
 			const userStore = useUserStore();
@@ -493,11 +464,8 @@ export default defineStore('chatStore', {
 		}
 	},
 	getters: {
-		isLoading: (state) => () => {
-			return state.loadingPrivateMsg || state.loadingGroupMsg
-		},
 		curChats: (state) => {
-			if (cacheChats && state.isLoading()) {
+			if (cacheChats && state.loading) {
 				return cacheChats;
 			}
 			return state.chats;
@@ -529,7 +497,10 @@ export default defineStore('chatStore', {
 			if (!chat) {
 				return null;
 			}
-			for (let idx in chat.messages) {
+			for (let idx = chat.messages.length - 1; idx >= 0; idx--) {
+				if (!chat.messages[idx].id && !chat.messages[idx].tmpId) {
+					continue;
+				}
 				// 通过id判断
 				if (msgInfo.id && chat.messages[idx].id == msgInfo.id) {
 					return chat.messages[idx];
@@ -539,7 +510,15 @@ export default defineStore('chatStore', {
 					chat.messages[idx].tmpId == msgInfo.tmpId) {
 					return chat.messages[idx];
 				}
+				// 如果id比要查询的消息小,说明没有这条消息
+				if (msgInfo.id && msgInfo.id > chat.messages[idx].id) {
+					break;
+				}
+				if (msgInfo.tmpId && msgInfo.tmpId > chat.messages[idx].tmpId) {
+					break;
+				}
 			}
+			return null;
 		}
 	}
 });

+ 28 - 41
im-web/src/store/chatStore.js

@@ -3,7 +3,6 @@ import { MESSAGE_TYPE, MESSAGE_STATUS } from "../api/enums.js"
 import useFriendStore from './friendStore.js';
 import useGroupStore from './groupStore.js';
 import useUserStore from './userStore.js';
-import useConfigStore from './configStore.js';
 import localForage from 'localforage';
 
 /**
@@ -28,11 +27,10 @@ export default defineStore('chatStore', {
 	state: () => {
 		return {
 			activeChat: null,
+			chats: [],
 			privateMsgMaxId: 0,
 			groupMsgMaxId: 0,
-			loadingPrivateMsg: false,
-			loadingGroupMsg: false,
-			chats: []
+			loading: false
 		}
 	},
 	actions: {
@@ -141,7 +139,7 @@ export default defineStore('chatStore', {
 		},
 		moveTop(idx) {
 			// 加载中不移动,很耗性能
-			if (this.isLoading()) {
+			if (this.loading) {
 				return;
 			}
 			if (idx > 0) {
@@ -156,15 +154,12 @@ export default defineStore('chatStore', {
 		},
 		insertMessage(msgInfo, chatInfo) {
 			let type = chatInfo.type;
-			// 完成初始化之前不能修改消息最大id,否则可能导致拉不到离线消息
-			if (useConfigStore().appInit) {
-				// 记录消息的最大id
-				if (msgInfo.id && type == "PRIVATE" && msgInfo.id > this.privateMsgMaxId) {
-					this.privateMsgMaxId = msgInfo.id;
-				}
-				if (msgInfo.id && type == "GROUP" && msgInfo.id > this.groupMsgMaxId) {
-					this.groupMsgMaxId = msgInfo.id;
-				}
+			// 记录消息的最大id
+			if (msgInfo.id && type == "PRIVATE" && msgInfo.id > this.privateMsgMaxId) {
+				this.privateMsgMaxId = msgInfo.id;
+			}
+			if (msgInfo.id && type == "GROUP" && msgInfo.id > this.groupMsgMaxId) {
+				this.groupMsgMaxId = msgInfo.id;
 			}
 			// 如果是已存在消息,则覆盖旧的消息数据
 			let chat = this.findChat(chatInfo);
@@ -218,19 +213,7 @@ export default defineStore('chatStore', {
 				});
 				chat.lastTimeTip = msgInfo.sendTime;
 			}
-			// 根据id顺序插入,防止消息乱序
-			let insertPos = chat.messages.length;
-			// 防止 图片、文件 在发送方 显示 在顶端  因为还没存库,id=0
-			if (msgInfo.id && msgInfo.id > 0) {
-				for (let idx in chat.messages) {
-					if (chat.messages[idx].id && msgInfo.id < chat.messages[idx].id) {
-						insertPos = idx;
-						console.log(`消息出现乱序,位置:${chat.messages.length},修正至:${insertPos}`);
-						break;
-					}
-				}
-			}
-			chat.messages.splice(insertPos, 0, msgInfo);
+			chat.messages.push(msgInfo);
 			chat.stored = false;
 			this.saveToStorage();
 		},
@@ -340,17 +323,8 @@ export default defineStore('chatStore', {
 				this.saveToStorage()
 			}
 		},
-		setLoadingPrivateMsg(loading) {
-			this.loadingPrivateMsg = loading;
-			if (!this.isLoading()) {
-				this.refreshChats();
-			}
-		},
-		setLoadingGroupMsg(loading) {
-			this.loadingGroupMsg = loading;
-			if (!this.isLoading()) {
-				this.refreshChats();
-			}
+		setLoading(loading) {
+			this.loading = loading;
 		},
 		setDnd(chatInfo, isDnd) {
 			let chat = this.findChat(chatInfo);
@@ -399,7 +373,7 @@ export default defineStore('chatStore', {
 		},
 		saveToStorage(withColdMessage) {
 			// 加载中不保存,防止卡顿
-			if (this.isLoading()) {
+			if (this.loading) {
 				return;
 			}
 			const userStore = useUserStore();
@@ -456,6 +430,9 @@ export default defineStore('chatStore', {
 			cacheChats = []
 			this.chats = [];
 			this.activeChat = null;
+			this.privateMsgMaxId = 0;
+			this.groupMsgMaxId = 0;
+			this.loading = false;
 		},
 		loadChat() {
 			return new Promise((resolve, reject) => {
@@ -511,7 +488,7 @@ export default defineStore('chatStore', {
 			return state.loadingPrivateMsg || state.loadingGroupMsg
 		},
 		findChats: (state) => () => {
-			if (cacheChats && state.isLoading()) {
+			if (cacheChats && state.loading) {
 				return cacheChats;
 			}
 			return state.chats;
@@ -545,7 +522,10 @@ export default defineStore('chatStore', {
 			if (!chat) {
 				return null;
 			}
-			for (let idx in chat.messages) {
+			for (let idx = chat.messages.length - 1; idx >= 0; idx--) {
+				if (!chat.messages[idx].id && !chat.messages[idx].tmpId) {
+					continue;
+				}
 				// 通过id判断
 				if (msgInfo.id && chat.messages[idx].id == msgInfo.id) {
 					return chat.messages[idx];
@@ -555,6 +535,13 @@ export default defineStore('chatStore', {
 					chat.messages[idx].tmpId == msgInfo.tmpId) {
 					return chat.messages[idx];
 				}
+				// 如果id比要查询的消息小,说明没有这条消息
+				if (msgInfo.id && msgInfo.id > chat.messages[idx].id) {
+					break;
+				}
+				if (msgInfo.tmpId && msgInfo.tmpId > chat.messages[idx].tmpId) {
+					break;
+				}
 			}
 		}
 	}

+ 1 - 1
im-web/src/view/Chat.vue

@@ -89,7 +89,7 @@ export default {
   },
   computed: {
     loading() {
-      return this.chatStore.loadingGroupMsg || this.chatStore.loadingPrivateMsg
+      return this.chatStore.loading;
     }
   }
 }

+ 50 - 31
im-web/src/view/Home.vue

@@ -78,7 +78,9 @@ export default {
 			showSettingDialog: false,
 			lastPlayAudioTime: new Date().getTime() - 1000,
 			isFullscreen: true,
-			reconnecting: false
+			reconnecting: false,
+			privateMessagesBuffer: [],
+			groupMessagesBuffer: []
 		}
 	},
 	methods: {
@@ -108,8 +110,7 @@ export default {
 						this.onReconnectWs();
 					} else {
 						// 加载离线消息
-						this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId);
-						this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId);
+						this.pullOfflineMessage();
 						this.configStore.setAppInit(true);
 					}
 				});
@@ -126,11 +127,21 @@ export default {
 						});
 
 					} else if (cmd == 3) {
-						// 插入私聊消息
-						this.handlePrivateMessage(msgInfo);
+						if (!this.configStore.appInit || this.chatStore.loading) {
+							// 如果正在拉取离线消息,先放进缓存区,等待消息拉取完成再处理,防止消息乱序
+							this.privateMessagesBuffer.push(msgInfo);
+						} else {
+							// 插入私聊消息
+							this.handlePrivateMessage(msgInfo);
+						}
 					} else if (cmd == 4) {
-						// 插入群聊消息
-						this.handleGroupMessage(msgInfo);
+						if (!this.configStore.appInit || this.chatStore.loading) {
+							// 如果正在拉取离线消息,先放进缓存区,等待消息拉取完成再处理,防止消息乱序
+							this.groupMessagesBuffer.push(msgInfo);
+						} else {
+							// 插入群聊消息
+							this.handleGroupMessage(msgInfo);
+						}
 					} else if (cmd == 5) {
 						// 处理系统消息
 						this.handleSystemMessage(msgInfo);
@@ -170,8 +181,7 @@ export default {
 			promises.push(this.groupStore.loadGroup());
 			Promise.all(promises).then(() => {
 				// 加载离线消息
-				this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId);
-				this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId);
+				this.pullOfflineMessage();
 				this.configStore.setAppInit(true)
 				this.$message.success("重新连接成功");
 			}).catch(() => {
@@ -194,23 +204,42 @@ export default {
 			this.groupStore.clear();
 			this.chatStore.clear();
 			this.userStore.clear();
+		},
+				pullOfflineMessage() {
+			this.chatStore.setLoading(true);
+			const promises = [];
+			promises.push(this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId));
+			promises.push(this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId));
+			Promise.all(promises).then(messages => {
+				// 处理离线消息
+				messages[0].forEach(m => this.handlePrivateMessage(m));
+				messages[1].forEach(m => this.handleGroupMessage(m));
+				// 处理缓冲区收到的实时消息
+				this.privateMessagesBuffer.forEach(m => this.handlePrivateMessage(m));
+				this.groupMessagesBuffer.forEach(m => this.handleGroupMessage(m));
+				// 清空缓冲区
+				this.privateMessagesBuffer = [];
+				this.groupMessagesBuffer = [];
+				// 关闭加载离线标记
+				this.chatStore.setLoading(false);
+				// 刷新会话
+				this.chatStore.refreshChats();
+			}).catch((e) => {
+				console.log(e)
+				this.$message.error("拉取离线消息失败");
+				this.onExit();
+			})
 		},
 		pullPrivateOfflineMessage(minId) {
-			this.chatStore.setLoadingPrivateMsg(true)
-			this.$http({
-				url: "/message/private/pullOfflineMessage?minId=" + minId,
+			return this.$http({
+				url: "/message/private/loadOfflineMessage?minId=" + minId,
 				method: 'GET'
-			}).catch(() => {
-				this.chatStore.setLoadingPrivateMsg(false)
 			})
 		},
 		pullGroupOfflineMessage(minId) {
-			this.chatStore.setLoadingGroupMsg(true)
-			this.$http({
-				url: "/message/group/pullOfflineMessage?minId=" + minId,
+			return this.$http({
+				url: "/message/group/loadOfflineMessage?minId=" + minId,
 				method: 'GET'
-			}).catch(() => {
-				this.chatStore.setLoadingGroupMsg(false)
 			})
 		},
 		handlePrivateMessage(msg) {
@@ -223,11 +252,6 @@ export default {
 				type: 'PRIVATE',
 				targetId: friendId
 			}
-			// 消息加载标志
-			if (msg.type == this.$enums.MESSAGE_TYPE.LOADING) {
-				this.chatStore.setLoadingPrivateMsg(JSON.parse(msg.content))
-				return;
-			}
 			// 消息已读处理,清空已读数量
 			if (msg.type == this.$enums.MESSAGE_TYPE.READED) {
 				this.chatStore.resetUnreadCount(chatInfo)
@@ -285,7 +309,7 @@ export default {
 			// 插入消息
 			this.chatStore.insertMessage(msg, chatInfo);
 			// 播放提示音
-			if (!friend.isDnd && !this.chatStore.isLoading() && !msg.selfSend && this.$msgType.isNormal(msg.type) &&
+			if (!friend.isDnd && !this.chatStore.loading && !msg.selfSend && this.$msgType.isNormal(msg.type) &&
 				msg.status != this.$enums.MESSAGE_STATUS.READED) {
 				this.playAudioTip();
 			}
@@ -297,11 +321,6 @@ export default {
 				type: 'GROUP',
 				targetId: msg.groupId
 			}
-			// 消息加载标志
-			if (msg.type == this.$enums.MESSAGE_TYPE.LOADING) {
-				this.chatStore.setLoadingGroupMsg(JSON.parse(msg.content))
-				return;
-			}
 			// 消息已读处理
 			if (msg.type == this.$enums.MESSAGE_TYPE.READED) {
 				// 我已读对方的消息,清空已读数量
@@ -367,7 +386,7 @@ export default {
 			// 插入消息
 			this.chatStore.insertMessage(msg, chatInfo);
 			// 播放提示音
-			if (!group.isDnd && !this.chatStore.isLoading() &&
+			if (!group.isDnd && !this.chatStore.loading &&
 				!msg.selfSend && this.$msgType.isNormal(msg.type)
 				&& msg.status != this.$enums.MESSAGE_STATUS.READED) {
 				this.playAudioTip();