前端&后端:
互发消息流程打通
This commit is contained in:
@@ -8,6 +8,31 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
|
||||
//判断是否已登录
|
||||
const isLoggedIn = computed(() => !!refreshToken.value);
|
||||
/**
|
||||
* 安全解析 JWT
|
||||
*/
|
||||
const getPayload = (t) => {
|
||||
try {
|
||||
const base64Url = t.split('.')[1];
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
// 处理 Unicode 字符解码
|
||||
return JSON.parse(decodeURIComponent(atob(base64).split('').map(c =>
|
||||
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
||||
).join('')));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 检查 Token 是否过期
|
||||
const isTokenExpired = computed(() => {
|
||||
if (!token.value) return true;
|
||||
const payload = getPayload(token.value);
|
||||
if (!payload || !payload.exp) return true;
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
return (payload.exp - now) < 30; // 预留 30 秒缓冲
|
||||
});
|
||||
|
||||
/**
|
||||
* 登录成功保存状态
|
||||
@@ -36,5 +61,5 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
localStorage.removeItem('user_info')
|
||||
}
|
||||
|
||||
return { token, refreshToken, userInfo, isLoggedIn, setLoginInfo, logout };
|
||||
return { token, refreshToken, userInfo, isLoggedIn, isTokenExpired, setLoginInfo, logout };
|
||||
})
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { messagesDb } from "@/utils/db/messageDB";
|
||||
import { messageService } from "@/services/message";
|
||||
|
||||
export const useChatStore = defineStore('chat', {
|
||||
state: () => ({
|
||||
activeSessionId: null,
|
||||
activeConversationId: null,
|
||||
messages: [],
|
||||
pageSize: 20
|
||||
}),
|
||||
actions: {
|
||||
async addMessage(msg, sessionId) {
|
||||
await messagesDb.save({ ...msg, sessionId });
|
||||
this.messages.push({ ...msg, sessionId })
|
||||
},
|
||||
/**
|
||||
* 切换会话加载当前会话消息列表
|
||||
* @param {*} sessionId
|
||||
*/
|
||||
async swtichSession(sessionId, conversationId) {
|
||||
this.activeSessionId = sessionId;
|
||||
this.activeConversationId = conversationId;
|
||||
this.messages = [];
|
||||
//先从浏览器缓存加载一部分消息列表
|
||||
const localHistory = await messagesDb.getPageMessages(sessionId, new Date().toISOString(), 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.messages = [...filterMsg, ...this.messages]
|
||||
}
|
||||
//拉取新消息
|
||||
this.fetchNewMsgFromServier(this.activeConversationId).then((newMsg) => {
|
||||
//去重
|
||||
const filterNewMsg = newMsg.filter(m => !this.messages.find(exist => exist.msgId === m.msgId));
|
||||
this.messages = [...filterNewMsg, ...this.messages]
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 从服务器加载新消息
|
||||
* @param {*} sessionId
|
||||
* @returns
|
||||
*/
|
||||
async fetchNewMsgFromServier(conversationId) {
|
||||
const newMsg = (await messageService.getMessages(conversationId)).data;
|
||||
if (newMsg.length > 0) {
|
||||
const sessionId = this.activeSessionId;
|
||||
await Promise.all(newMsg.map(msg =>
|
||||
messagesDb.save({ ...msg, sessionId })
|
||||
));
|
||||
return newMsg;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 从服务器加载历史消息
|
||||
* @param {*} sessionId
|
||||
* @param {*} msgId
|
||||
* @returns
|
||||
*/
|
||||
async fetchHistoryFromServer(conversationId, msgId) {
|
||||
const res = (await messageService.getHistoryMessages(conversationId, msgId, 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 [];
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 加载更多历史消息
|
||||
*/
|
||||
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.messages = [...newMsgs, ...this.messages]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -2,9 +2,8 @@ import { defineStore } from "pinia";
|
||||
import * as signalR from '@microsoft/signalr';
|
||||
import { useMessage } from "@/components/messages/useAlert";
|
||||
import { useAuthStore } from "./auth";
|
||||
|
||||
const message = useMessage()
|
||||
const authStore = useAuthStore()
|
||||
import { useChatStore } from "./chat";
|
||||
import { authService } from "@/services/auth";
|
||||
|
||||
export const useSignalRStore = defineStore('signalr', {
|
||||
state: () => ({
|
||||
@@ -13,10 +12,16 @@ export const useSignalRStore = defineStore('signalr', {
|
||||
}),
|
||||
actions: {
|
||||
async initSignalR() {
|
||||
const message = useMessage()
|
||||
const authStore = useAuthStore()
|
||||
const url = import.meta.env.VITE_SIGNALR_BASE_URL || 'http://localhost:5202/chat';
|
||||
this.connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl(url, {
|
||||
accessTokenFactory: () => {
|
||||
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;
|
||||
}
|
||||
})
|
||||
@@ -32,13 +37,35 @@ export const useSignalRStore = defineStore('signalr', {
|
||||
}
|
||||
},
|
||||
registerHandlers() {
|
||||
const chatStore = useChatStore()
|
||||
this.connection.on('ReceiveMessage', (msg) => {
|
||||
console.log(msg)
|
||||
chatStore.addMessage(msg);
|
||||
});
|
||||
|
||||
this.connection.onclose(() => { this.isConnected = false });
|
||||
this.connection.onreconnected(() => { this.isConnected = true });
|
||||
|
||||
},
|
||||
async sendMsg(msg) {
|
||||
const message = useMessage()
|
||||
const chatStore = useChatStore()
|
||||
if (!this.isConnected) {
|
||||
message.error('与服务器连接中断,请重连后尝试...');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 后端 Hub 定义的方法名通常为 SendMessage
|
||||
// 参数顺序需要与后端 ChatHub 中的方法签名一致
|
||||
if (msg.msgId == null) {
|
||||
msg.msgId = self.crypto.randomUUID();
|
||||
}
|
||||
await this.connection.invoke("SendMessage", msg);
|
||||
chatStore.addMessage(msg);
|
||||
console.log("消息发送成功!");
|
||||
} catch (err) {
|
||||
console.error("消息发送失败:", err);
|
||||
message.error("消息发送失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user