⚡ Cancel use HTTP services and WebSockets
This commit is contained in:
@@ -50,13 +50,5 @@ export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* SetProgressBroadcaster 设置进度广播函数
|
||||
*/
|
||||
export function SetProgressBroadcaster(broadcaster: any): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3244071921, broadcaster) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = $models.MigrationProgress.createFrom;
|
||||
|
@@ -1,423 +0,0 @@
|
||||
import { ref, onUnmounted, reactive, computed, watch, nextTick } from 'vue';
|
||||
|
||||
// 基础WebSocket消息接口
|
||||
interface WebSocketMessage<T = any> {
|
||||
type: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
// 迁移进度接口(与后端保持一致)
|
||||
interface MigrationProgress {
|
||||
status: 'migrating' | 'completed' | 'failed';
|
||||
progress: number; // 0-100
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// 连接状态枚举
|
||||
enum ConnectionState {
|
||||
DISCONNECTED = 'disconnected',
|
||||
CONNECTING = 'connecting',
|
||||
CONNECTED = 'connected',
|
||||
RECONNECTING = 'reconnecting',
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
// WebSocket配置选项
|
||||
interface WebSocketOptions {
|
||||
url?: string;
|
||||
reconnectInterval?: number;
|
||||
maxReconnectAttempts?: number;
|
||||
debug?: boolean;
|
||||
autoConnect?: boolean;
|
||||
protocols?: string | string[];
|
||||
heartbeat?: {
|
||||
enabled: boolean;
|
||||
interval: number;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 消息处理器类型
|
||||
type MessageHandler<T = any> = (data: T) => void;
|
||||
|
||||
// 事件处理器映射
|
||||
interface EventHandlers {
|
||||
[messageType: string]: MessageHandler[];
|
||||
}
|
||||
|
||||
// 连接事件类型
|
||||
type ConnectionEventType = 'connect' | 'disconnect' | 'error' | 'reconnect';
|
||||
type ConnectionEventHandler = (event?: Event | CloseEvent | ErrorEvent) => void;
|
||||
|
||||
export function useWebSocket(options: WebSocketOptions = {}) {
|
||||
const {
|
||||
url = 'ws://localhost:8899/ws/migration',
|
||||
reconnectInterval = 3000,
|
||||
maxReconnectAttempts = 10,
|
||||
debug = false,
|
||||
autoConnect = true,
|
||||
protocols,
|
||||
heartbeat = { enabled: false, interval: 30000, message: 'ping' }
|
||||
} = options;
|
||||
|
||||
// === 状态管理 ===
|
||||
const connectionState = ref<ConnectionState>(ConnectionState.DISCONNECTED);
|
||||
const connectionError = ref<string | null>(null);
|
||||
const reconnectAttempts = ref(0);
|
||||
const lastMessage = ref<WebSocketMessage | null>(null);
|
||||
const messageHistory = ref<WebSocketMessage[]>([]);
|
||||
|
||||
// 迁移进度状态(保持向后兼容)
|
||||
const migrationProgress = reactive<MigrationProgress>({
|
||||
status: 'completed',
|
||||
progress: 0
|
||||
});
|
||||
|
||||
// === 计算属性 ===
|
||||
const isConnected = computed(() => connectionState.value === ConnectionState.CONNECTED);
|
||||
const isConnecting = computed(() =>
|
||||
connectionState.value === ConnectionState.CONNECTING ||
|
||||
connectionState.value === ConnectionState.RECONNECTING
|
||||
);
|
||||
const canReconnect = computed(() => reconnectAttempts.value < maxReconnectAttempts);
|
||||
|
||||
// === 内部状态 ===
|
||||
let ws: WebSocket | null = null;
|
||||
let reconnectTimer: number | null = null;
|
||||
let heartbeatTimer: number | null = null;
|
||||
let isManualDisconnect = false;
|
||||
|
||||
// 事件处理器
|
||||
const eventHandlers: EventHandlers = {};
|
||||
const connectionEventHandlers: Map<ConnectionEventType, ConnectionEventHandler[]> = new Map();
|
||||
|
||||
// === 工具函数 ===
|
||||
const log = (level: 'info' | 'warn' | 'error', message: string, ...args: any[]) => {
|
||||
if (debug) {
|
||||
console[level](`[WebSocket] ${message}`, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
const clearTimers = () => {
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer);
|
||||
reconnectTimer = null;
|
||||
}
|
||||
if (heartbeatTimer) {
|
||||
clearInterval(heartbeatTimer);
|
||||
heartbeatTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
const updateConnectionState = (newState: ConnectionState, error?: string) => {
|
||||
connectionState.value = newState;
|
||||
connectionError.value = error || null;
|
||||
log('info', `Connection state changed to: ${newState}`, error);
|
||||
};
|
||||
|
||||
// === 事件系统 ===
|
||||
const on = <T = any>(messageType: string, handler: MessageHandler<T>) => {
|
||||
if (!eventHandlers[messageType]) {
|
||||
eventHandlers[messageType] = [];
|
||||
}
|
||||
eventHandlers[messageType].push(handler as MessageHandler);
|
||||
|
||||
// 返回取消订阅函数
|
||||
return () => off(messageType, handler);
|
||||
};
|
||||
|
||||
const off = <T = any>(messageType: string, handler: MessageHandler<T>) => {
|
||||
if (eventHandlers[messageType]) {
|
||||
const index = eventHandlers[messageType].indexOf(handler as MessageHandler);
|
||||
if (index > -1) {
|
||||
eventHandlers[messageType].splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onConnection = (eventType: ConnectionEventType, handler: ConnectionEventHandler) => {
|
||||
if (!connectionEventHandlers.has(eventType)) {
|
||||
connectionEventHandlers.set(eventType, []);
|
||||
}
|
||||
connectionEventHandlers.get(eventType)!.push(handler);
|
||||
|
||||
return () => offConnection(eventType, handler);
|
||||
};
|
||||
|
||||
const offConnection = (eventType: ConnectionEventType, handler: ConnectionEventHandler) => {
|
||||
const handlers = connectionEventHandlers.get(eventType);
|
||||
if (handlers) {
|
||||
const index = handlers.indexOf(handler);
|
||||
if (index > -1) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const emit = (eventType: ConnectionEventType, event?: Event | CloseEvent | ErrorEvent) => {
|
||||
const handlers = connectionEventHandlers.get(eventType);
|
||||
if (handlers) {
|
||||
handlers.forEach(handler => {
|
||||
try {
|
||||
handler(event);
|
||||
} catch (error) {
|
||||
log('error', `Error in ${eventType} event handler:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// === 消息处理 ===
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
try {
|
||||
const message: WebSocketMessage = JSON.parse(event.data);
|
||||
log('info', 'Received message:', message);
|
||||
|
||||
// 更新消息历史
|
||||
lastMessage.value = message;
|
||||
messageHistory.value.push(message);
|
||||
|
||||
// 限制历史记录长度
|
||||
if (messageHistory.value.length > 100) {
|
||||
messageHistory.value.shift();
|
||||
}
|
||||
|
||||
// 特殊处理迁移进度消息(保持向后兼容)
|
||||
if (message.type === 'migration_progress') {
|
||||
Object.assign(migrationProgress, message.data);
|
||||
}
|
||||
|
||||
// 触发注册的处理器
|
||||
const handlers = eventHandlers[message.type];
|
||||
if (handlers) {
|
||||
handlers.forEach(handler => {
|
||||
try {
|
||||
handler(message.data);
|
||||
} catch (error) {
|
||||
log('error', `Error in message handler for ${message.type}:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
log('error', 'Failed to parse message:', error, event.data);
|
||||
}
|
||||
};
|
||||
|
||||
// === 心跳机制 ===
|
||||
const startHeartbeat = () => {
|
||||
if (!heartbeat.enabled || heartbeatTimer) return;
|
||||
|
||||
heartbeatTimer = window.setInterval(() => {
|
||||
if (isConnected.value) {
|
||||
send(heartbeat.message);
|
||||
}
|
||||
}, heartbeat.interval);
|
||||
};
|
||||
|
||||
const stopHeartbeat = () => {
|
||||
if (heartbeatTimer) {
|
||||
clearInterval(heartbeatTimer);
|
||||
heartbeatTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
// === 连接管理 ===
|
||||
const connect = async (): Promise<void> => {
|
||||
if (isConnecting.value || isConnected.value) {
|
||||
log('warn', 'Already connecting or connected');
|
||||
return;
|
||||
}
|
||||
|
||||
updateConnectionState(ConnectionState.CONNECTING);
|
||||
isManualDisconnect = false;
|
||||
|
||||
try {
|
||||
log('info', 'Connecting to:', url);
|
||||
|
||||
ws = new WebSocket(url, protocols);
|
||||
|
||||
// 连接超时处理
|
||||
const connectTimeout = setTimeout(() => {
|
||||
if (ws && ws.readyState === WebSocket.CONNECTING) {
|
||||
ws.close();
|
||||
updateConnectionState(ConnectionState.ERROR, 'Connection timeout');
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
ws.onopen = () => {
|
||||
clearTimeout(connectTimeout);
|
||||
log('info', 'Connected successfully');
|
||||
updateConnectionState(ConnectionState.CONNECTED);
|
||||
reconnectAttempts.value = 0;
|
||||
clearTimers();
|
||||
startHeartbeat();
|
||||
emit('connect');
|
||||
};
|
||||
|
||||
ws.onmessage = handleMessage;
|
||||
|
||||
ws.onclose = (event) => {
|
||||
clearTimeout(connectTimeout);
|
||||
stopHeartbeat();
|
||||
log('info', 'Connection closed:', event.code, event.reason);
|
||||
|
||||
const wasConnected = connectionState.value === ConnectionState.CONNECTED;
|
||||
updateConnectionState(ConnectionState.DISCONNECTED);
|
||||
ws = null;
|
||||
|
||||
emit('disconnect', event);
|
||||
|
||||
// 自动重连逻辑
|
||||
if (!isManualDisconnect && event.code !== 1000 && canReconnect.value) {
|
||||
scheduleReconnect();
|
||||
} else if (reconnectAttempts.value >= maxReconnectAttempts) {
|
||||
updateConnectionState(ConnectionState.ERROR, 'Max reconnection attempts reached');
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (event) => {
|
||||
clearTimeout(connectTimeout);
|
||||
log('error', 'Connection error:', event);
|
||||
updateConnectionState(ConnectionState.ERROR, 'WebSocket connection error');
|
||||
emit('error', event);
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
log('error', 'Failed to create WebSocket:', error);
|
||||
updateConnectionState(ConnectionState.ERROR, 'Failed to create WebSocket connection');
|
||||
}
|
||||
};
|
||||
|
||||
const disconnect = (code: number = 1000, reason: string = 'Manual disconnect') => {
|
||||
isManualDisconnect = true;
|
||||
clearTimers();
|
||||
stopHeartbeat();
|
||||
|
||||
if (ws) {
|
||||
log('info', 'Disconnecting manually');
|
||||
ws.close(code, reason);
|
||||
}
|
||||
|
||||
updateConnectionState(ConnectionState.DISCONNECTED);
|
||||
};
|
||||
|
||||
const scheduleReconnect = () => {
|
||||
if (!canReconnect.value || isManualDisconnect) return;
|
||||
|
||||
clearTimers();
|
||||
reconnectAttempts.value++;
|
||||
updateConnectionState(ConnectionState.RECONNECTING,
|
||||
`Reconnecting... (${reconnectAttempts.value}/${maxReconnectAttempts})`);
|
||||
|
||||
log('info', `Scheduling reconnect attempt ${reconnectAttempts.value}/${maxReconnectAttempts} in ${reconnectInterval}ms`);
|
||||
|
||||
reconnectTimer = window.setTimeout(() => {
|
||||
connect();
|
||||
}, reconnectInterval);
|
||||
|
||||
emit('reconnect');
|
||||
};
|
||||
|
||||
const reconnect = () => {
|
||||
disconnect();
|
||||
reconnectAttempts.value = 0;
|
||||
nextTick(() => {
|
||||
connect();
|
||||
});
|
||||
};
|
||||
|
||||
// === 消息发送 ===
|
||||
const send = (message: any): boolean => {
|
||||
if (!isConnected.value || !ws) {
|
||||
log('warn', 'Cannot send message: not connected');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = typeof message === 'string' ? message : JSON.stringify(message);
|
||||
ws.send(data);
|
||||
log('info', 'Sent message:', data);
|
||||
return true;
|
||||
} catch (error) {
|
||||
log('error', 'Failed to send message:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const sendMessage = <T = any>(type: string, data?: T): boolean => {
|
||||
return send({ type, data });
|
||||
};
|
||||
|
||||
// === 状态查询 ===
|
||||
const getConnectionInfo = () => ({
|
||||
state: connectionState.value,
|
||||
error: connectionError.value,
|
||||
reconnectAttempts: reconnectAttempts.value,
|
||||
maxReconnectAttempts,
|
||||
canReconnect: canReconnect.value,
|
||||
url,
|
||||
readyState: ws?.readyState,
|
||||
protocol: ws?.protocol,
|
||||
extensions: ws?.extensions
|
||||
});
|
||||
|
||||
// === 初始化 ===
|
||||
if (autoConnect) {
|
||||
nextTick(() => {
|
||||
connect();
|
||||
});
|
||||
}
|
||||
|
||||
// === 清理 ===
|
||||
onUnmounted(() => {
|
||||
disconnect();
|
||||
});
|
||||
|
||||
// 监听连接状态变化,用于调试
|
||||
if (debug) {
|
||||
watch(connectionState, (newState, oldState) => {
|
||||
log('info', `State transition: ${oldState} -> ${newState}`);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// === 状态(只读) ===
|
||||
connectionState: computed(() => connectionState.value),
|
||||
isConnected,
|
||||
isConnecting,
|
||||
connectionError: computed(() => connectionError.value),
|
||||
reconnectAttempts: computed(() => reconnectAttempts.value),
|
||||
canReconnect,
|
||||
lastMessage: computed(() => lastMessage.value),
|
||||
messageHistory: computed(() => messageHistory.value),
|
||||
|
||||
// === 向后兼容的状态 ===
|
||||
migrationProgress,
|
||||
|
||||
// === 连接控制 ===
|
||||
connect,
|
||||
disconnect,
|
||||
reconnect,
|
||||
|
||||
// === 消息发送 ===
|
||||
send,
|
||||
sendMessage,
|
||||
|
||||
// === 事件系统 ===
|
||||
on,
|
||||
off,
|
||||
onConnection,
|
||||
offConnection,
|
||||
|
||||
// === 工具方法 ===
|
||||
getConnectionInfo,
|
||||
clearHistory: () => {
|
||||
messageHistory.value = [];
|
||||
lastMessage.value = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 导出类型
|
||||
export type { WebSocketMessage, MigrationProgress, WebSocketOptions, MessageHandler, ConnectionEventType };
|
||||
export { ConnectionState };
|
@@ -6,19 +6,51 @@ import SettingSection from '../components/SettingSection.vue';
|
||||
import SettingItem from '../components/SettingItem.vue';
|
||||
import ToggleSwitch from '../components/ToggleSwitch.vue';
|
||||
import {useErrorHandler} from '@/utils/errorHandler';
|
||||
import {DialogService, MigrationService} from '@/../bindings/voidraft/internal/services';
|
||||
import {useWebSocket} from '@/composables/useWebSocket';
|
||||
import {DialogService, MigrationService, MigrationProgress, MigrationStatus} from '@/../bindings/voidraft/internal/services';
|
||||
import * as runtime from '@wailsio/runtime';
|
||||
|
||||
const {t} = useI18n();
|
||||
const configStore = useConfigStore();
|
||||
const {safeCall} = useErrorHandler();
|
||||
|
||||
// WebSocket连接
|
||||
const {migrationProgress, isConnected, connectionState, connect, disconnect, on} = useWebSocket({
|
||||
debug: true,
|
||||
autoConnect: false // 手动控制连接
|
||||
});
|
||||
// 迁移进度状态
|
||||
const migrationProgress = ref<MigrationProgress>(new MigrationProgress({
|
||||
status: MigrationStatus.MigrationStatusCompleted,
|
||||
progress: 0
|
||||
}));
|
||||
|
||||
// 轮询相关
|
||||
let pollingTimer: number | null = null;
|
||||
const isPolling = ref(false);
|
||||
|
||||
// 开始轮询迁移进度
|
||||
const startPolling = () => {
|
||||
if (isPolling.value) return;
|
||||
|
||||
isPolling.value = true;
|
||||
pollingTimer = window.setInterval(async () => {
|
||||
try {
|
||||
const progress = await MigrationService.GetProgress();
|
||||
migrationProgress.value = progress;
|
||||
|
||||
// 如果迁移完成或失败,停止轮询
|
||||
if (progress.status === MigrationStatus.MigrationStatusCompleted || progress.status === MigrationStatus.MigrationStatusFailed) {
|
||||
stopPolling();
|
||||
}
|
||||
} catch (error) {
|
||||
stopPolling();
|
||||
}
|
||||
}, 500); // 每500ms轮询一次
|
||||
};
|
||||
|
||||
// 停止轮询
|
||||
const stopPolling = () => {
|
||||
if (pollingTimer) {
|
||||
clearInterval(pollingTimer);
|
||||
pollingTimer = null;
|
||||
}
|
||||
isPolling.value = false;
|
||||
};
|
||||
|
||||
// 迁移消息链
|
||||
interface MigrationMessage {
|
||||
@@ -52,26 +84,15 @@ const clearMigrationMessages = () => {
|
||||
showMessages.value = false;
|
||||
};
|
||||
|
||||
// 监听连接状态变化
|
||||
const connectionWatcher = computed(() => isConnected.value);
|
||||
watch(connectionWatcher, (connected) => {
|
||||
if (connected && isMigrating.value) {
|
||||
// 如果连接成功且正在迁移,添加连接消息
|
||||
if (!migrationMessages.value.some(msg => msg.content === '实时连接中')) {
|
||||
addMigrationMessage('实时连接中', 'progress');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 监听迁移进度变化
|
||||
on('migration_progress', (data) => {
|
||||
watch(() => migrationProgress.value, (progress, oldProgress) => {
|
||||
// 清除之前的隐藏定时器
|
||||
if (hideMessagesTimer) {
|
||||
clearTimeout(hideMessagesTimer);
|
||||
hideMessagesTimer = null;
|
||||
}
|
||||
|
||||
if (data.status === 'migrating') {
|
||||
if (progress.status === MigrationStatus.MigrationStatusMigrating) {
|
||||
// 如果是第一次收到迁移状态,添加开始消息
|
||||
if (migrationMessages.value.length === 0) {
|
||||
addMigrationMessage(t('migration.started'), 'start');
|
||||
@@ -80,34 +101,24 @@ on('migration_progress', (data) => {
|
||||
if (!migrationMessages.value.some(msg => msg.type === 'progress' && msg.content === t('migration.migrating'))) {
|
||||
addMigrationMessage(t('migration.migrating'), 'progress');
|
||||
}
|
||||
} else if (data.status === 'completed') {
|
||||
} else if (progress.status === MigrationStatus.MigrationStatusCompleted && oldProgress?.status === MigrationStatus.MigrationStatusMigrating) {
|
||||
addMigrationMessage(t('migration.completed'), 'success');
|
||||
|
||||
// 3秒后断开连接
|
||||
setTimeout(() => {
|
||||
disconnect();
|
||||
}, 3000);
|
||||
|
||||
// 5秒后开始逐个隐藏消息
|
||||
hideMessagesTimer = setTimeout(() => {
|
||||
hideMessagesSequentially();
|
||||
}, 5000);
|
||||
|
||||
} else if (data.status === 'failed') {
|
||||
const errorMsg = data.error || t('migration.failed');
|
||||
} else if (progress.status === MigrationStatus.MigrationStatusFailed && oldProgress?.status === MigrationStatus.MigrationStatusMigrating) {
|
||||
const errorMsg = progress.error || t('migration.failed');
|
||||
addMigrationMessage(errorMsg, 'error');
|
||||
|
||||
// 3秒后断开连接
|
||||
setTimeout(() => {
|
||||
disconnect();
|
||||
}, 3000);
|
||||
|
||||
// 8秒后开始逐个隐藏消息
|
||||
hideMessagesTimer = setTimeout(() => {
|
||||
hideMessagesSequentially();
|
||||
}, 8000);
|
||||
}
|
||||
});
|
||||
}, { deep: true });
|
||||
|
||||
// 逐个隐藏消息
|
||||
const hideMessagesSequentially = () => {
|
||||
@@ -129,18 +140,18 @@ const hideMessagesSequentially = () => {
|
||||
};
|
||||
|
||||
// 迁移状态
|
||||
const isMigrating = computed(() => migrationProgress.status === 'migrating');
|
||||
const migrationComplete = computed(() => migrationProgress.status === 'completed');
|
||||
const migrationFailed = computed(() => migrationProgress.status === 'failed');
|
||||
const isMigrating = computed(() => migrationProgress.value.status === MigrationStatus.MigrationStatusMigrating);
|
||||
const migrationComplete = computed(() => migrationProgress.value.status === MigrationStatus.MigrationStatusCompleted);
|
||||
const migrationFailed = computed(() => migrationProgress.value.status === MigrationStatus.MigrationStatusFailed);
|
||||
|
||||
// 进度条样式和宽度
|
||||
const progressBarClass = computed(() => {
|
||||
switch (migrationProgress.status) {
|
||||
case 'migrating':
|
||||
switch (migrationProgress.value.status) {
|
||||
case MigrationStatus.MigrationStatusMigrating:
|
||||
return 'migrating';
|
||||
case 'completed':
|
||||
case MigrationStatus.MigrationStatusCompleted:
|
||||
return 'success';
|
||||
case 'failed':
|
||||
case MigrationStatus.MigrationStatusFailed:
|
||||
return 'error';
|
||||
default:
|
||||
return '';
|
||||
@@ -150,7 +161,7 @@ const progressBarClass = computed(() => {
|
||||
const progressBarWidth = computed(() => {
|
||||
// 只有在显示消息且正在迁移时才显示进度条
|
||||
if (showMessages.value && isMigrating.value) {
|
||||
return migrationProgress.progress + '%';
|
||||
return migrationProgress.value.progress + '%';
|
||||
} else if (showMessages.value && (migrationComplete.value || migrationFailed.value)) {
|
||||
// 迁移完成或失败时,短暂显示100%,然后随着消息隐藏而隐藏
|
||||
return '100%';
|
||||
@@ -264,17 +275,17 @@ const currentDataPath = computed(() => configStore.config.general.dataPath);
|
||||
const selectDataDirectory = async () => {
|
||||
if (isMigrating.value) return;
|
||||
|
||||
const selectedPath = await DialogService.SelectDirectory();
|
||||
const selectedPath = await DialogService.SelectDirectory();
|
||||
|
||||
if (selectedPath && selectedPath.trim() && selectedPath !== currentDataPath.value) {
|
||||
// 清除之前的消息
|
||||
clearMigrationMessages();
|
||||
|
||||
if (selectedPath && selectedPath.trim() && selectedPath !== currentDataPath.value) {
|
||||
// 清除之前的消息
|
||||
clearMigrationMessages();
|
||||
|
||||
// 连接WebSocket以接收迁移进度
|
||||
await connect();
|
||||
|
||||
// 开始迁移
|
||||
try {
|
||||
// 开始轮询迁移进度
|
||||
startPolling();
|
||||
|
||||
// 开始迁移
|
||||
try {
|
||||
await safeCall(async () => {
|
||||
const oldPath = currentDataPath.value;
|
||||
const newPath = selectedPath.trim();
|
||||
@@ -282,16 +293,17 @@ const selectDataDirectory = async () => {
|
||||
await MigrationService.MigrateDirectory(oldPath, newPath);
|
||||
await configStore.setDataPath(newPath);
|
||||
}, '');
|
||||
} catch (error) {
|
||||
// 发生错误时清除消息
|
||||
clearMigrationMessages();
|
||||
} catch (error) {
|
||||
// 发生错误时清除消息并停止轮询
|
||||
clearMigrationMessages();
|
||||
stopPolling();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 清理定时器和WebSocket连接
|
||||
// 清理定时器
|
||||
onUnmounted(() => {
|
||||
disconnect();
|
||||
stopPolling();
|
||||
if (resetConfirmTimer) {
|
||||
clearTimeout(resetConfirmTimer);
|
||||
}
|
||||
|
23
go.mod
23
go.mod
@@ -6,8 +6,6 @@ toolchain go1.24.2
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/lxzan/gws v1.8.9
|
||||
github.com/spf13/viper v1.20.1
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.9
|
||||
)
|
||||
@@ -18,44 +16,27 @@ require (
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/adrg/xdg v0.5.3 // indirect
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-git/go-git/v5 v5.16.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||
github.com/leaanthony/u v1.1.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lmittmann/tint v1.1.1 // indirect
|
||||
github.com/matryer/is v1.4.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
@@ -69,19 +50,15 @@ require (
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.21 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
64
go.sum
64
go.sum
@@ -13,23 +13,13 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
@@ -40,12 +30,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
@@ -58,41 +42,22 @@ github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9
|
||||
github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
|
||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -104,12 +69,8 @@ github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lmittmann/tint v1.1.1 h1:xmmGuinUsCSxWdwH1OqMUQ4tzQsq3BdjJLAAmVKJ9Dw=
|
||||
github.com/lmittmann/tint v1.1.1/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/lxzan/gws v1.8.9 h1:VU3SGUeWlQrEwfUSfokcZep8mdg/BrUF+y73YYshdBM=
|
||||
github.com/lxzan/gws v1.8.9/go.mod h1:d9yHaR1eDTBHagQC6KY7ycUOaz5KWeqQtP3xu7aMK8Y=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
@@ -117,11 +78,6 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
@@ -159,23 +115,12 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA=
|
||||
github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
@@ -186,9 +131,6 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
@@ -206,7 +148,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
@@ -217,8 +158,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@@ -229,8 +168,5 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
@@ -1,156 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
)
|
||||
|
||||
// HTTPService HTTP 服务
|
||||
type HTTPService struct {
|
||||
logger *log.LoggerService
|
||||
server *http.Server
|
||||
wsService *WebSocketService
|
||||
}
|
||||
|
||||
// NewHTTPService 创建 HTTP 服务
|
||||
func NewHTTPService(logger *log.LoggerService, wsService *WebSocketService) *HTTPService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
|
||||
return &HTTPService{
|
||||
logger: logger,
|
||||
wsService: wsService,
|
||||
}
|
||||
}
|
||||
|
||||
// StartServer 启动 HTTP 服务器
|
||||
func (hs *HTTPService) StartServer(port string) error {
|
||||
// 设置 Gin 为发布模式
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
// 创建 Gin 路由器
|
||||
router := gin.New()
|
||||
|
||||
// 添加中间件
|
||||
router.Use(gin.Recovery())
|
||||
router.Use(hs.corsMiddleware())
|
||||
|
||||
// 设置路由
|
||||
hs.setupRoutes(router)
|
||||
|
||||
// 创建 HTTP 服务器
|
||||
hs.server = &http.Server{
|
||||
Addr: ":" + port,
|
||||
Handler: router,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20, // 1MB
|
||||
}
|
||||
|
||||
hs.logger.Info("HTTP: Starting server", "port", port)
|
||||
|
||||
// 启动服务器
|
||||
go func() {
|
||||
if err := hs.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
hs.logger.Error("HTTP: Server failed to start", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupRoutes 设置路由
|
||||
func (hs *HTTPService) setupRoutes(router *gin.Engine) {
|
||||
// WebSocket 端点
|
||||
router.GET("/ws/migration", hs.handleWebSocket)
|
||||
|
||||
// API 端点组
|
||||
api := router.Group("/api")
|
||||
{
|
||||
api.GET("/health", hs.handleHealth)
|
||||
api.GET("/ws/clients", hs.handleWSClients)
|
||||
}
|
||||
}
|
||||
|
||||
// handleWebSocket 处理 WebSocket 连接
|
||||
func (hs *HTTPService) handleWebSocket(c *gin.Context) {
|
||||
if hs.wsService == nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "WebSocket service not available"})
|
||||
return
|
||||
}
|
||||
|
||||
hs.wsService.HandleUpgrade(c.Writer, c.Request)
|
||||
}
|
||||
|
||||
// handleHealth 健康检查端点
|
||||
func (hs *HTTPService) handleHealth(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "ok",
|
||||
"timestamp": time.Now().Unix(),
|
||||
"service": "voidraft-http",
|
||||
})
|
||||
}
|
||||
|
||||
// handleWSClients 获取 WebSocket 客户端数量
|
||||
func (hs *HTTPService) handleWSClients(c *gin.Context) {
|
||||
if hs.wsService == nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "WebSocket service not available"})
|
||||
return
|
||||
}
|
||||
|
||||
count := hs.wsService.GetConnectedClientsCount()
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"connected_clients": count,
|
||||
})
|
||||
}
|
||||
|
||||
// corsMiddleware CORS 中间件
|
||||
func (hs *HTTPService) corsMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization")
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// StopServer 停止 HTTP 服务器
|
||||
func (hs *HTTPService) StopServer() error {
|
||||
if hs.server == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
hs.logger.Info("HTTP: Stopping server...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
return hs.server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// ServiceShutdown 服务关闭
|
||||
func (hs *HTTPService) ServiceShutdown() error {
|
||||
hs.logger.Info("HTTP: Service is shutting down...")
|
||||
|
||||
if err := hs.StopServer(); err != nil {
|
||||
hs.logger.Error("HTTP: Failed to stop server", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
hs.logger.Info("HTTP: Service shutdown completed")
|
||||
return nil
|
||||
}
|
@@ -32,12 +32,11 @@ type MigrationProgress struct {
|
||||
|
||||
// MigrationService 迁移服务
|
||||
type MigrationService struct {
|
||||
logger *log.LoggerService
|
||||
mu sync.RWMutex
|
||||
currentProgress MigrationProgress
|
||||
cancelFunc context.CancelFunc
|
||||
ctx context.Context
|
||||
progressBroadcaster func(MigrationProgress) // WebSocket广播函数
|
||||
logger *log.LoggerService
|
||||
mu sync.RWMutex
|
||||
currentProgress MigrationProgress
|
||||
cancelFunc context.CancelFunc
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewMigrationService 创建迁移服务
|
||||
@@ -67,11 +66,6 @@ func (ms *MigrationService) updateProgress(progress MigrationProgress) {
|
||||
ms.mu.Lock()
|
||||
ms.currentProgress = progress
|
||||
ms.mu.Unlock()
|
||||
|
||||
// 通过WebSocket广播进度
|
||||
if ms.progressBroadcaster != nil {
|
||||
ms.progressBroadcaster(progress)
|
||||
}
|
||||
}
|
||||
|
||||
// MigrateDirectory 迁移目录
|
||||
@@ -407,13 +401,6 @@ func (ms *MigrationService) CancelMigration() error {
|
||||
return fmt.Errorf("No active migration to cancel")
|
||||
}
|
||||
|
||||
// SetProgressBroadcaster 设置进度广播函数
|
||||
func (ms *MigrationService) SetProgressBroadcaster(broadcaster func(MigrationProgress)) {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
ms.progressBroadcaster = broadcaster
|
||||
}
|
||||
|
||||
// ServiceShutdown 服务关闭
|
||||
func (ms *MigrationService) ServiceShutdown() error {
|
||||
ms.logger.Info("Migration: Service is shutting down...")
|
||||
|
@@ -15,8 +15,6 @@ type ServiceManager struct {
|
||||
systemService *SystemService
|
||||
hotkeyService *HotkeyService
|
||||
dialogService *DialogService
|
||||
websocketService *WebSocketService
|
||||
httpService *HTTPService
|
||||
trayService *TrayService
|
||||
logger *log.LoggerService
|
||||
}
|
||||
@@ -44,29 +42,11 @@ func NewServiceManager() *ServiceManager {
|
||||
// 初始化对话服务
|
||||
dialogService := NewDialogService(logger)
|
||||
|
||||
// 初始化 WebSocket 服务
|
||||
websocketService := NewWebSocketService(logger)
|
||||
|
||||
// 初始化 HTTP 服务
|
||||
httpService := NewHTTPService(logger, websocketService)
|
||||
|
||||
// 初始化托盘服务
|
||||
trayService := NewTrayService(logger, configService)
|
||||
|
||||
// 设置迁移服务的WebSocket广播
|
||||
migrationService.SetProgressBroadcaster(func(progress MigrationProgress) {
|
||||
websocketService.BroadcastMigrationProgress(progress)
|
||||
})
|
||||
|
||||
// 启动 HTTP 服务器
|
||||
err := httpService.StartServer("8899")
|
||||
if err != nil {
|
||||
logger.Error("Failed to start HTTP server", "error", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 使用新的配置通知系统设置热键配置变更监听
|
||||
err = configService.SetHotkeyChangeCallback(func(enable bool, hotkey *models.HotkeyCombo) error {
|
||||
err := configService.SetHotkeyChangeCallback(func(enable bool, hotkey *models.HotkeyCombo) error {
|
||||
return hotkeyService.UpdateHotkey(enable, hotkey)
|
||||
})
|
||||
if err != nil {
|
||||
@@ -97,8 +77,6 @@ func NewServiceManager() *ServiceManager {
|
||||
systemService: systemService,
|
||||
hotkeyService: hotkeyService,
|
||||
dialogService: dialogService,
|
||||
websocketService: websocketService,
|
||||
httpService: httpService,
|
||||
trayService: trayService,
|
||||
logger: logger,
|
||||
}
|
||||
|
@@ -1,159 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/lxzan/gws"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
)
|
||||
|
||||
// WebSocketService WebSocket 服务
|
||||
type WebSocketService struct {
|
||||
logger *log.LoggerService
|
||||
upgrader *gws.Upgrader
|
||||
clients map[*gws.Conn]bool
|
||||
clientsMu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewWebSocketService 创建 WebSocket 服务
|
||||
func NewWebSocketService(logger *log.LoggerService) *WebSocketService {
|
||||
if logger == nil {
|
||||
logger = log.New()
|
||||
}
|
||||
|
||||
ws := &WebSocketService{
|
||||
logger: logger,
|
||||
clients: make(map[*gws.Conn]bool),
|
||||
}
|
||||
|
||||
// 创建 WebSocket 升级器
|
||||
ws.upgrader = gws.NewUpgrader(&WebSocketHandler{service: ws}, &gws.ServerOption{
|
||||
ParallelEnabled: true,
|
||||
Recovery: gws.Recovery,
|
||||
PermessageDeflate: gws.PermessageDeflate{Enabled: true},
|
||||
})
|
||||
|
||||
return ws
|
||||
}
|
||||
|
||||
// WebSocketHandler WebSocket 事件处理器
|
||||
type WebSocketHandler struct {
|
||||
service *WebSocketService
|
||||
}
|
||||
|
||||
// OnOpen 连接建立时调用
|
||||
func (h *WebSocketHandler) OnOpen(socket *gws.Conn) {
|
||||
h.service.logger.Info("WebSocket: Client connected")
|
||||
|
||||
h.service.clientsMu.Lock()
|
||||
h.service.clients[socket] = true
|
||||
h.service.clientsMu.Unlock()
|
||||
}
|
||||
|
||||
// OnClose 连接关闭时调用
|
||||
func (h *WebSocketHandler) OnClose(socket *gws.Conn, err error) {
|
||||
h.service.logger.Info("WebSocket: Client disconnected", "error", err)
|
||||
|
||||
h.service.clientsMu.Lock()
|
||||
delete(h.service.clients, socket)
|
||||
h.service.clientsMu.Unlock()
|
||||
}
|
||||
|
||||
// OnPing 收到 Ping 时调用
|
||||
func (h *WebSocketHandler) OnPing(socket *gws.Conn, payload []byte) {
|
||||
_ = socket.WritePong(nil)
|
||||
}
|
||||
|
||||
// OnPong 收到 Pong 时调用
|
||||
func (h *WebSocketHandler) OnPong(socket *gws.Conn, payload []byte) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
// OnMessage 收到消息时调用
|
||||
func (h *WebSocketHandler) OnMessage(socket *gws.Conn, message *gws.Message) {
|
||||
defer message.Close()
|
||||
|
||||
h.service.logger.Debug("WebSocket: Received message", "message", string(message.Bytes()))
|
||||
|
||||
}
|
||||
|
||||
// HandleUpgrade 处理 WebSocket 升级请求
|
||||
func (ws *WebSocketService) HandleUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
socket, err := ws.upgrader.Upgrade(w, r)
|
||||
if err != nil {
|
||||
ws.logger.Error("WebSocket: Failed to upgrade connection", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 启动读取循环(必须在 goroutine 中运行以防止阻塞)
|
||||
go func() {
|
||||
socket.ReadLoop()
|
||||
}()
|
||||
}
|
||||
|
||||
// BroadcastMessage 广播消息给所有连接的客户端
|
||||
func (ws *WebSocketService) BroadcastMessage(messageType string, data interface{}) {
|
||||
message := map[string]interface{}{
|
||||
"type": messageType,
|
||||
"data": data,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
ws.logger.Error("WebSocket: Failed to marshal message", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
ws.clientsMu.RLock()
|
||||
clients := make([]*gws.Conn, 0, len(ws.clients))
|
||||
for client := range ws.clients {
|
||||
clients = append(clients, client)
|
||||
}
|
||||
ws.clientsMu.RUnlock()
|
||||
|
||||
// 使用广播器进行高效广播
|
||||
broadcaster := gws.NewBroadcaster(gws.OpcodeText, jsonData)
|
||||
defer broadcaster.Close()
|
||||
|
||||
for _, client := range clients {
|
||||
if err := broadcaster.Broadcast(client); err != nil {
|
||||
ws.logger.Error("WebSocket: Failed to broadcast to client", "error", err)
|
||||
// 清理失效的连接
|
||||
ws.clientsMu.Lock()
|
||||
delete(ws.clients, client)
|
||||
ws.clientsMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
ws.logger.Debug("WebSocket: Broadcasted message", "type", messageType, "clients", len(clients))
|
||||
}
|
||||
|
||||
// BroadcastMigrationProgress 广播迁移进度
|
||||
func (ws *WebSocketService) BroadcastMigrationProgress(progress MigrationProgress) {
|
||||
ws.BroadcastMessage("migration_progress", progress)
|
||||
}
|
||||
|
||||
// GetConnectedClientsCount 获取连接的客户端数量
|
||||
func (ws *WebSocketService) GetConnectedClientsCount() int {
|
||||
ws.clientsMu.RLock()
|
||||
defer ws.clientsMu.RUnlock()
|
||||
return len(ws.clients)
|
||||
}
|
||||
|
||||
// ServiceShutdown 服务关闭
|
||||
func (ws *WebSocketService) ServiceShutdown() error {
|
||||
ws.logger.Info("WebSocket: Service is shutting down...")
|
||||
|
||||
// 关闭所有客户端连接
|
||||
ws.clientsMu.Lock()
|
||||
for client := range ws.clients {
|
||||
_ = client.WriteClose(1000, []byte("Server shutting down"))
|
||||
}
|
||||
ws.clients = make(map[*gws.Conn]bool)
|
||||
ws.clientsMu.Unlock()
|
||||
|
||||
ws.logger.Info("WebSocket: Service shutdown completed")
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user