前端:

1、优化消息排序逻辑
2、新增加载历史消息
3、修复已知问题
后端:
1、优化消息排序逻辑
2、增加用户信息缓存机制
3、修改日期类型为DateTimeOffset改善时区信息丢失问题
3、修复了已知问题
数据库:
1、新增SequenceId字段用于消息排序
2、新增ClientMsgId字段用于客户端消息回执
This commit is contained in:
2026-02-07 22:37:56 +08:00
118 changed files with 10691 additions and 452 deletions
+57 -47
View File
@@ -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);
}
}
}
+2 -2
View File
@@ -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()
},
/**
* 从服务器加载新消息
+21 -20
View File
@@ -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();
});
},
/**