前端:
1、优化消息排序逻辑 2、新增加载历史消息 3、修复已知问题 后端: 1、优化消息排序逻辑 2、增加用户信息缓存机制 3、修改日期类型为DateTimeOffset改善时区信息丢失问题 3、修复了已知问题 数据库: 1、新增SequenceId字段用于消息排序 2、新增ClientMsgId字段用于客户端消息回执
This commit is contained in:
@@ -1,30 +1,41 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { messagesDb } from "@/utils/db/messageDB";
|
||||
import { messageService } from "@/services/message";
|
||||
import { useConversationStore } from "./conversation";
|
||||
|
||||
export const useChatStore = defineStore('chat', {
|
||||
state: () => ({
|
||||
activeSessionId: null,
|
||||
activeConversationId: null,
|
||||
activeSessionId: null,
|
||||
maxSequenceId: null,
|
||||
isEnded: false,
|
||||
messages: [],
|
||||
pageSize: 20
|
||||
}),
|
||||
actions: {
|
||||
// 抽取统一的排序去重方法
|
||||
pushAndSortMessages(newMsgs) {
|
||||
const combined = [...this.messages, ...newMsgs];
|
||||
// 1. 根据 msgId 或唯一 key 去重
|
||||
const uniqueMap = new Map();
|
||||
combined.forEach(m => uniqueMap.set(m.msgId || m.id, m));
|
||||
async pushAndSortMessagesAsync(newMsgs, sessionId, shouldSaveToDb = true) {
|
||||
if (shouldSaveToDb) {
|
||||
for (const m of newMsgs) {
|
||||
await messagesDb.save({ ...m, sessionId });
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 转换为数组并按时间戳升序排序 (旧的在前,新的在后)
|
||||
this.messages = Array.from(uniqueMap.values()).sort((a, b) => {
|
||||
return new Date(a.timeStamp).getTime() - new Date(b.timeStamp).getTime();
|
||||
});
|
||||
},
|
||||
async addMessage(msg, sessionId) {
|
||||
await messagesDb.save({ ...msg, sessionId, isLoading: false });
|
||||
this.messages.push({ ...msg, sessionId })
|
||||
if (sessionId == this.activeSessionId) {
|
||||
const combined = [...this.messages, ...newMsgs];
|
||||
// 1. 根据 msgId 或唯一 key 去重
|
||||
const uniqueMap = new Map();
|
||||
combined.forEach(m => uniqueMap.set(m.msgId || m.sequenceId, m));
|
||||
|
||||
// 2. 转换为数组并按sequenceId升序排序 (旧的在前,新的在后)
|
||||
this.messages = Array.from(uniqueMap.values()).sort((a, b) => {
|
||||
return a.sequenceId - b.sequenceId;
|
||||
});
|
||||
this.maxSequenceId = this.messages.reduce((max, m) =>
|
||||
m.sequenceId > max ? m.sequenceId : max,
|
||||
null // 初始值
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 切换会话加载当前会话消息列表
|
||||
@@ -34,38 +45,26 @@ export const useChatStore = defineStore('chat', {
|
||||
this.activeSessionId = sessionId;
|
||||
this.activeConversationId = conversationId;
|
||||
this.messages = [];
|
||||
this.isEnded = false;
|
||||
//先从浏览器缓存加载一部分消息列表
|
||||
const localHistory = await messagesDb.getPageMessages(sessionId, new Date().toISOString(), this.pageSize);
|
||||
const localHistory = await messagesDb.getLatestMessages(sessionId, this.pageSize);
|
||||
console.log(localHistory)
|
||||
if (localHistory.length > 0) {
|
||||
this.messages = localHistory;
|
||||
} else {
|
||||
//如果本地没有消息数据则从后端拉取数据
|
||||
const conversation = (await messageService.getConversationById(this.activeConversationId)).data;
|
||||
const serverHistoryMsg = await this.fetchHistoryFromServer(this.activeConversationId, conversation.lastReadMessageId);
|
||||
//对消息进行过滤,防止重复消息
|
||||
const filterMsg = serverHistoryMsg.filter(m => !this.messages.find(exist => exist.msgId === m.msgId));
|
||||
this.pushAndSortMessages([...filterMsg, ...this.messages]);
|
||||
this.maxSequenceId = this.messages.reduce((max, m) =>
|
||||
m.sequenceId > max ? m.sequenceId : max,
|
||||
null // 初始值
|
||||
);
|
||||
}
|
||||
//拉取新消息
|
||||
this.fetchNewMsgFromServier(this.activeConversationId).then((newMsg) => {
|
||||
//去重
|
||||
const filterNewMsg = newMsg.filter(m => !this.messages.find(exist => exist.msgId === m.msgId));
|
||||
this.pushAndSortMessages([...filterNewMsg, ...this.messages])
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 从服务器加载新消息
|
||||
* @param {*} sessionId
|
||||
* @returns
|
||||
*/
|
||||
async fetchNewMsgFromServier(conversationId) {
|
||||
const newMsg = (await messageService.getMessages(conversationId)).data;
|
||||
async fetchNewMsgFromServier(conversationId, sequenceId) {
|
||||
const newMsg = (await messageService.getMessages(conversationId, sequenceId, sequenceId ? 1 : 0, this.pageSize)).data;
|
||||
if (newMsg.length > 0) {
|
||||
const sessionId = this.activeSessionId;
|
||||
await Promise.all(newMsg.map(msg =>
|
||||
messagesDb.save({ ...msg, sessionId })
|
||||
));
|
||||
return newMsg;
|
||||
} else {
|
||||
return [];
|
||||
@@ -77,14 +76,11 @@ export const useChatStore = defineStore('chat', {
|
||||
* @param {*} msgId
|
||||
* @returns
|
||||
*/
|
||||
async fetchHistoryFromServer(conversationId, msgId) {
|
||||
const res = (await messageService.getHistoryMessages(conversationId, msgId, this.pageSize)).data;
|
||||
async fetchHistoryFromServer(conversationId, sequenceId) {
|
||||
const res = (await messageService.getMessages(conversationId, sequenceId, 0, this.pageSize)).data;
|
||||
|
||||
if (res.length > 0) {
|
||||
const sessionId = this.activeSessionId;
|
||||
await Promise.all(res.map(msg =>
|
||||
messagesDb.save({ ...msg, sessionId })
|
||||
));
|
||||
return res;
|
||||
} else {
|
||||
return [];
|
||||
@@ -94,14 +90,28 @@ export const useChatStore = defineStore('chat', {
|
||||
* 加载更多历史消息
|
||||
*/
|
||||
async loadMoreMessages() {
|
||||
const lastTimeStamp = this.messages.length > 0 ? this.messages[0].timeStamp : new Date().toISOString();
|
||||
const history = await messagesDb.getPageMessages(this.activeSessionId, lastTimeStamp, this.pageSize);
|
||||
if (history.length > 0) {
|
||||
this.messages = [...history, ...this.messages]
|
||||
} else {
|
||||
const fetchMsg = await this.fetchHistoryFromServer(this.conversationId, this.messages[0].msgId);
|
||||
const newMsgs = fetchMsg.filter(m => !this.messages.find(exist => exist.msgId === m.msgId));
|
||||
this.pushAndSortMessages([...newMsgs, ...this.messages])
|
||||
let minSequenceId = 0;
|
||||
if (!this.messages || this.messages.length === 0) return;
|
||||
minSequenceId = this.messages.reduce((min, m) =>
|
||||
(m.sequenceId < min ? m.sequenceId : min),
|
||||
this.messages[0].sequenceId // 使用第一项作为初始参考值
|
||||
);
|
||||
const dbCacheList = await messagesDb.getPageMessages(this.activeSessionId, minSequenceId, this.pageSize)
|
||||
const dbMaxSequenceId = dbCacheList.reduce((max, m) =>
|
||||
m.sequenceId > max ? m.sequenceId : max,
|
||||
null // 初始值
|
||||
);
|
||||
if (dbCacheList.length < this.pageSize) {
|
||||
const newList = await this.fetchHistoryFromServer(this.activeConversationId, minSequenceId)
|
||||
if (newList.length === 0) this.isEnded = true;
|
||||
await this.pushAndSortMessagesAsync(newList, this.activeSessionId, true);
|
||||
} else if (dbMaxSequenceId < minSequenceId - 1) {
|
||||
const newList = await this.fetchHistoryFromServer(this.activeConversationId, minSequenceId)
|
||||
if (newList.length === 0) this.isEnded = true;
|
||||
await this.pushAndSortMessagesAsync(newList, this.activeSessionId, true);
|
||||
}
|
||||
else {
|
||||
await this.pushAndSortMessagesAsync(dbCacheList, this.activeSessionId, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const useConversationStore = defineStore('conversation', {
|
||||
try {
|
||||
const covnersationsCache = await conversationDb.getAll();
|
||||
if (covnersationsCache && covnersationsCache.length > 0) {
|
||||
covnersationsCache.sort((a, b) => {
|
||||
this.conversations = covnersationsCache.sort((a, b) => {
|
||||
return new Date(a.dateTime) - new Date(b.dateTime);
|
||||
})
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export const useConversationStore = defineStore('conversation', {
|
||||
console.log('读取本地会话缓存失败:', e);
|
||||
}
|
||||
}
|
||||
await this.fetchConversationsFromServier()
|
||||
//await this.fetchConversationsFromServier()
|
||||
},
|
||||
/**
|
||||
* 从服务器加载新消息
|
||||
|
||||
@@ -8,6 +8,8 @@ import { generateSessionId } from "@/utils/sessionIdTools";
|
||||
import { messageHandler } from "@/handler/messageHandler";
|
||||
import { useBrowserNotification } from "@/services/useBrowserNotification";
|
||||
import { useConversationStore } from "./conversation";
|
||||
import { SignalRMessageHandler } from "@/utils/signalr/SignalMessageHandler";
|
||||
import { signalRConnectionEventHandler } from "@/utils/signalr/signalRConnectionEventHandler";
|
||||
|
||||
export const useSignalRStore = defineStore('signalr', {
|
||||
state: () => ({
|
||||
@@ -21,44 +23,43 @@ export const useSignalRStore = defineStore('signalr', {
|
||||
const url = import.meta.env.VITE_SIGNALR_BASE_URL || 'http://localhost:5202/chat/';
|
||||
this.connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl(url,
|
||||
{
|
||||
{
|
||||
|
||||
accessTokenFactory: async () => {
|
||||
if (authStore.isTokenExpired) {
|
||||
const res = await authService.refresh(authStore.refreshToken)
|
||||
authStore.setLoginInfo(res.data.token, res.data.refreshToken, res.data.userInfo)
|
||||
accessTokenFactory: async () => {
|
||||
if (authStore.isTokenExpired) {
|
||||
const res = await authService.refresh(authStore.refreshToken)
|
||||
authStore.setLoginInfo(res.data.token, res.data.refreshToken, res.data.userInfo)
|
||||
}
|
||||
return authStore.token;
|
||||
}
|
||||
return authStore.token;
|
||||
}
|
||||
})
|
||||
})
|
||||
.withAutomaticReconnect()
|
||||
.build();
|
||||
this.registerHandlers();
|
||||
try {
|
||||
await this.connection.start();
|
||||
this.isConnected = true;
|
||||
signalRConnectionEventHandler();
|
||||
console.log('SignalR建立通信成功!')
|
||||
} catch (e) {
|
||||
message.error('与服务器建立通信失败,请检查网络连接...');
|
||||
}
|
||||
},
|
||||
registerHandlers() {
|
||||
const chatStore = useChatStore()
|
||||
const browserNotification = useBrowserNotification();
|
||||
|
||||
|
||||
this.connection.on('ReceiveMessage', (msg) => {
|
||||
const sessionId = generateSessionId(msg.senderId, msg.receiverId);
|
||||
messageHandler(msg);
|
||||
chatStore.addMessage(msg, sessionId);
|
||||
const conversation = useConversationStore().conversations.find(x => x.targetId == msg.senderId);
|
||||
browserNotification.send(`${conversation.targetName}发来一条消息`, {
|
||||
body: msg.content,
|
||||
icon: conversation.targetAvatar
|
||||
});
|
||||
console.log(msg)
|
||||
SignalRMessageHandler(msg)
|
||||
});
|
||||
|
||||
this.connection.onclose(() => { this.isConnected = false });
|
||||
this.connection.onreconnected(() => { this.isConnected = true });
|
||||
this.connection.onclose(() => {
|
||||
this.isConnected = false;
|
||||
});
|
||||
this.connection.onreconnected(() => {
|
||||
this.isConnected = true;
|
||||
signalRConnectionEventHandler();
|
||||
});
|
||||
|
||||
},
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user