Files
cunkebao_v3/Cunkebao/src/store/module/websocket.ts

377 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createPersistStore } from "@/store/createPersistStore";
import { Toast } from "antd-mobile";
import { useUserStore } from "./user";
// WebSocket消息类型
export interface WebSocketMessage {
id: string;
type: string;
content: any;
timestamp: number;
sender?: string;
receiver?: string;
}
// WebSocket连接状态
export enum WebSocketStatus {
DISCONNECTED = "disconnected",
CONNECTING = "connecting",
CONNECTED = "connected",
RECONNECTING = "reconnecting",
ERROR = "error",
}
// WebSocket配置
interface WebSocketConfig {
url: string;
client: string;
accountId: string;
accessToken: string;
autoReconnect: boolean;
reconnectInterval: number;
maxReconnectAttempts: number;
}
interface WebSocketState {
// 连接状态
status: WebSocketStatus;
ws: WebSocket | null;
// 配置信息
config: WebSocketConfig | null;
// 消息相关
messages: WebSocketMessage[];
unreadCount: number;
// 重连相关
reconnectAttempts: number;
reconnectTimer: NodeJS.Timeout | null;
// 方法
connect: (config: Partial<WebSocketConfig>) => void;
disconnect: () => void;
sendMessage: (message: Omit<WebSocketMessage, "id" | "timestamp">) => void;
sendCommand: (cmdType: string, data?: any) => void;
clearMessages: () => void;
markAsRead: () => void;
reconnect: () => void;
// 内部方法
_handleOpen: () => void;
_handleMessage: (event: MessageEvent) => void;
_handleClose: (event: CloseEvent) => void;
_handleError: (event: Event) => void;
_startReconnectTimer: () => void;
_stopReconnectTimer: () => void;
}
// 默认配置
const DEFAULT_CONFIG: WebSocketConfig = {
url: (import.meta as any).env?.VITE_API_WS_URL || "ws://localhost:8080",
client: "kefu-client",
accountId: "",
accessToken: "",
autoReconnect: true,
reconnectInterval: 3000,
maxReconnectAttempts: 5,
};
export const useWebSocketStore = createPersistStore<WebSocketState>(
(set, get) => ({
status: WebSocketStatus.DISCONNECTED,
ws: null,
config: null,
messages: [],
unreadCount: 0,
reconnectAttempts: 0,
reconnectTimer: null,
// 连接WebSocket
connect: (config: Partial<WebSocketConfig>) => {
const currentState = get();
// 如果已经连接,先断开
if (currentState.ws) {
currentState.disconnect();
}
// 合并配置
const fullConfig: WebSocketConfig = {
...DEFAULT_CONFIG,
...config,
};
// 获取用户信息
const { token, token2, user } = useUserStore.getState();
const accessToken = fullConfig.accessToken || token2 || token;
if (!accessToken) {
Toast.show({ content: "未找到有效的访问令牌", position: "top" });
return;
}
// 构建WebSocket URL
const params = {
client: fullConfig.client,
accountId: fullConfig.accountId || user?.s2_accountId || "",
accessToken: accessToken,
t: Date.now().toString(),
};
const wsUrl =
fullConfig.url + "?" + new URLSearchParams(params).toString();
set({
status: WebSocketStatus.CONNECTING,
config: fullConfig,
});
try {
const ws = new WebSocket(wsUrl);
// 绑定事件处理器
ws.onopen = () => get()._handleOpen();
ws.onmessage = event => get()._handleMessage(event);
ws.onclose = event => get()._handleClose(event);
ws.onerror = event => get()._handleError(event);
set({ ws });
console.log("WebSocket连接创建成功", wsUrl);
} catch (error) {
console.error("WebSocket连接失败:", error);
set({ status: WebSocketStatus.ERROR });
Toast.show({ content: "WebSocket连接失败", position: "top" });
}
},
// 断开连接
disconnect: () => {
const currentState = get();
if (currentState.ws) {
currentState.ws.close();
}
currentState._stopReconnectTimer();
set({
status: WebSocketStatus.DISCONNECTED,
ws: null,
reconnectAttempts: 0,
});
console.log("WebSocket连接已断开");
},
// 发送消息
sendMessage: (message: Omit<WebSocketMessage, "id" | "timestamp">) => {
const currentState = get();
if (
currentState.status !== WebSocketStatus.CONNECTED ||
!currentState.ws
) {
Toast.show({ content: "WebSocket未连接", position: "top" });
return;
}
const fullMessage: WebSocketMessage = {
...message,
id: Date.now().toString(),
timestamp: Date.now(),
};
try {
currentState.ws.send(JSON.stringify(fullMessage));
console.log("消息发送成功:", fullMessage);
} catch (error) {
console.error("消息发送失败:", error);
Toast.show({ content: "消息发送失败", position: "top" });
}
},
// 发送命令
sendCommand: (cmdType: string, data?: any) => {
const currentState = get();
if (
currentState.status !== WebSocketStatus.CONNECTED ||
!currentState.ws
) {
Toast.show({ content: "WebSocket未连接", position: "top" });
return;
}
const { user } = useUserStore.getState();
const { token, token2 } = useUserStore.getState();
const accessToken = token2 || token;
const command = {
accessToken: accessToken,
accountId: user?.s2_accountId,
client: currentState.config?.client || "kefu-client",
cmdType: cmdType,
seq: Date.now(),
...data,
};
try {
currentState.ws.send(JSON.stringify(command));
console.log("命令发送成功:", command);
} catch (error) {
console.error("命令发送失败:", error);
Toast.show({ content: "命令发送失败", position: "top" });
}
},
// 清除消息
clearMessages: () => {
set({ messages: [], unreadCount: 0 });
},
// 标记为已读
markAsRead: () => {
set({ unreadCount: 0 });
},
// 重连
reconnect: () => {
const currentState = get();
if (currentState.config) {
currentState.connect(currentState.config);
}
},
// 内部方法:处理连接打开
_handleOpen: () => {
const currentState = get();
set({
status: WebSocketStatus.CONNECTED,
reconnectAttempts: 0,
});
console.log("WebSocket连接成功");
// 发送登录命令
if (currentState.config) {
currentState.sendCommand("CmdSignIn");
}
Toast.show({ content: "WebSocket连接成功", position: "top" });
},
// 内部方法:处理消息接收
_handleMessage: (event: MessageEvent) => {
try {
const data = JSON.parse(event.data);
console.log("收到WebSocket消息:", data);
const currentState = get();
const newMessage: WebSocketMessage = {
id: Date.now().toString(),
type: data.type || "message",
content: data,
timestamp: Date.now(),
sender: data.sender,
receiver: data.receiver,
};
set({
messages: [...currentState.messages, newMessage],
unreadCount: currentState.unreadCount + 1,
});
// 可以在这里添加消息处理逻辑
// 比如播放提示音、显示通知等
} catch (error) {
console.error("解析WebSocket消息失败:", error);
}
},
// 内部方法:处理连接关闭
_handleClose: (event: CloseEvent) => {
const currentState = get();
console.log("WebSocket连接关闭:", event.code, event.reason);
set({
status: WebSocketStatus.DISCONNECTED,
ws: null,
});
// 自动重连逻辑
if (
currentState.config?.autoReconnect &&
currentState.reconnectAttempts <
(currentState.config?.maxReconnectAttempts || 5)
) {
currentState._startReconnectTimer();
}
},
// 内部方法:处理连接错误
_handleError: (event: Event) => {
console.error("WebSocket连接错误:", event);
set({ status: WebSocketStatus.ERROR });
Toast.show({ content: "WebSocket连接错误", position: "top" });
},
// 内部方法:启动重连定时器
_startReconnectTimer: () => {
const currentState = get();
currentState._stopReconnectTimer();
set({
status: WebSocketStatus.RECONNECTING,
reconnectAttempts: currentState.reconnectAttempts + 1,
});
const timer = setTimeout(() => {
console.log(
`尝试重连 (${currentState.reconnectAttempts + 1}/${currentState.config?.maxReconnectAttempts})`,
);
currentState.reconnect();
}, currentState.config?.reconnectInterval || 3000);
set({ reconnectTimer: timer });
},
// 内部方法:停止重连定时器
_stopReconnectTimer: () => {
const currentState = get();
if (currentState.reconnectTimer) {
clearTimeout(currentState.reconnectTimer);
set({ reconnectTimer: null });
}
},
}),
{
name: "websocket-store",
partialize: state => ({
// 只持久化必要的状态不持久化WebSocket实例
status: state.status,
config: state.config,
messages: state.messages.slice(-100), // 只保留最近100条消息
unreadCount: state.unreadCount,
reconnectAttempts: state.reconnectAttempts,
}),
onRehydrateStorage: () => state => {
// 页面刷新后,如果之前是连接状态,尝试重新连接
if (state && state.status === WebSocketStatus.CONNECTED && state.config) {
// 延迟一下再重连,确保页面完全加载
setTimeout(() => {
state.connect(state.config);
}, 1000);
}
},
},
);