diff --git a/src/views/chat/store/websocketStore.ts b/src/views/chat/store/websocketStore.ts new file mode 100644 index 00000000..bca2695d --- /dev/null +++ b/src/views/chat/store/websocketStore.ts @@ -0,0 +1,199 @@ +import { defineStore } from 'pinia' +import { ref, onUnmounted } from 'vue' +import { getRefreshToken } from '@/utils/auth' +import { useChatStore } from './chatstore' +import { + ContentType, + ImMessageContent, + ImMessageReceiveResponse, + WEBSOCKET_MESSAGE_TYPE_ENUM +} from '../types/types' +import TextMessage from '../model/TextMessage' +import { debug } from 'console' +import { useUserStore } from '@/store/modules/user' + +interface Message { + type: string + data: any +} + +enum ImConversationTypeEnum { + SINGLE = 1, + GROUP = 2 + // Add other conversation types if needed +} + +export function generateConversationNo( + fromUserId: number, + receiverId: number, + conversationType: number +): string | null { + if (conversationType === ImConversationTypeEnum.SINGLE) { + return `s_${fromUserId}_${receiverId}` + } else if (conversationType === ImConversationTypeEnum.GROUP) { + return `g_${receiverId}` + } + return null +} + +export const useWebSocketStore = defineStore('webSocket', () => { + const socket = ref(null) + const messages = ref([]) + const isConnected = ref(false) + let reconnectTimer: ReturnType | null = null + let heartbeatTimer: ReturnType | null = null + + // 初始化 WebSocket 连接 + function connect() { + const refreshToken = getRefreshToken() + + // 设置 WebSocket URL + if (refreshToken) { + console.log('refreshToken null') + } + + const url = `ws://localhost:48080/infra/ws?token=${refreshToken}` + socket.value = new WebSocket(url) + + socket.value.onopen = () => { + isConnected.value = true + console.log('WebSocket connected') + startHeartbeat() + } + + // {"type":"im-message-receive", + // "content": + // "{\ + // "id\":239,\ + // "conversationType\":1,\ + // "senderId\":144,\ + // "senderNickname\":\"dylan\",\ + // "senderAvatar\":\"http://192.168.0.208:48083/admin-api/infra/file/4/get/c34f9521ce6a2e21148f16b73ab652e578b5bb572dbc259a5043f754c19c8a3f.png\",\ + // "receiverId\":1,\ + // "contentType\":101,\ + // "content\":\"111\",\ + // "sendTime\":1731139168438,\ + // "sequence\":2}" + // } + socket.value.onmessage = (event) => { + + if (event.data === 'pong') { + return + } + + try { + const websoketMessage = JSON.parse(event.data) as ImMessageReceiveResponse + if (websoketMessage.type === WEBSOCKET_MESSAGE_TYPE_ENUM.IM_MESSAGE_RECEIVE.toString()) { + const socketChatMessage = JSON.parse(websoketMessage.content) as ImMessageContent + + // 暂不处理自己发送的消息 + if (socketChatMessage.senderId === useUserStore().user.id) { + return + } + + if (socketChatMessage.contentType === ContentType.TEXT) { + const chatStore = useChatStore() + const localTextMessage = TextMessage.fromWebsocket(socketChatMessage) + chatStore.addMessageToConversation(localTextMessage) + } else if (socketChatMessage.contentType === ContentType.IMAGE) { + + } else if (socketChatMessage.contentType === ContentType.AUDIO) { + + } + + } else { + // TODO:[dylan] + } + + console.log('Received message:', websoketMessage) + + } catch (error) { + console.info(error) + } + + // messages.value.push(message) + + } + + socket.value.onclose = () => { + isConnected.value = false + console.log('WebSocket disconnected') + reconnect() + } + + socket.value.onerror = (error) => { + console.error('WebSocket error:', error) + isConnected.value = false + reconnect() + } + } + + // 发送消息 + function sendMessage(message: Message) { + if (socket.value && isConnected.value) { + socket.value.send(JSON.stringify(message)) + } + } + + /** + * 发送心跳消息 + */ + function sendHeartBeat() { + if (socket.value && isConnected.value) { + socket.value.send('ping') + } + } + + // 断开 WebSocket 连接 + function disconnect() { + if (socket.value) { + socket.value.close() + socket.value = null + } + stopHeartbeat() + clearTimeout(reconnectTimer!) + } + + // 自动重连逻辑 + function reconnect() { + stopHeartbeat() + if (reconnectTimer) clearTimeout(reconnectTimer) + reconnectTimer = setTimeout(() => { + console.log('Reconnecting WebSocket...') + connect() + }, 3000) // 3秒后重连 + } + + // 启动心跳 + function startHeartbeat() { + if (heartbeatTimer) clearInterval(heartbeatTimer) + heartbeatTimer = setInterval(() => { + if (socket.value && isConnected.value) { + sendHeartBeat() + console.log('Heartbeat sent') + } + }, 5000) // 每5秒发送一次心跳 + } + + // 停止心跳 + function stopHeartbeat() { + if (heartbeatTimer) { + clearInterval(heartbeatTimer) + heartbeatTimer = null + } + } + + // 自动断开连接,清理资源 + onUnmounted(() => { + disconnect() + }) + + return { + connect, + disconnect, + sendMessage, + messages, + isConnected, + generateConversationNo + } +}) diff --git a/src/views/chat/types/index.d.ts b/src/views/chat/types/types.ts similarity index 62% rename from src/views/chat/types/index.d.ts rename to src/views/chat/types/types.ts index 7f44286f..69cd9e44 100644 --- a/src/views/chat/types/index.d.ts +++ b/src/views/chat/types/types.ts @@ -34,6 +34,29 @@ export const enum CONVERSATION_TYPE { NOTIFICATION = 4 } +export enum WEBSOCKET_MESSAGE_TYPE_ENUM { + IM_MESSAGE_RECEIVE = 'im-message-receive' +} + + export type MessageModelType = BaseMessage | TextMessage | ImageMessage export type ConversationModelType = BaseConversation | ChatConversation export type ConversationType = CONVERSATION_TYPE + +export type ImMessageReceiveResponse = { + type: WEBSOCKET_MESSAGE_TYPE_ENUM + content: string +}; + +export type ImMessageContent = { + id: number; + conversationType: number; + senderId: number; + senderNickname: string; + senderAvatar: string; + receiverId: number; + contentType: number; + content: string; + sendTime: number; // Use `Date` if you'd prefer the time to be a `Date` object + sequence: number; +}