feat: FunConnect v1.0.0 - Minecraft联机平台完整版

- server: Node.js TCP中继服务器,支持多节点集群
- web: React管理面板(仪表盘、房间管理、节点管理)
- client: Electron桌面客户端(连接、创建/加入房间、本地代理)
- deploy: Ubuntu一键部署脚本
This commit is contained in:
FunMC
2026-02-22 23:33:00 +08:00
commit b17679cec6
44 changed files with 13783 additions and 0 deletions

173
client/src/main/index.ts Normal file
View File

@@ -0,0 +1,173 @@
import { app, BrowserWindow, ipcMain, dialog } from 'electron';
import * as path from 'path';
import { RelayClient } from './relay-client';
import { LocalProxy } from './local-proxy';
import { ApiClient } from './api-client';
let mainWindow: BrowserWindow | null = null;
let relayClient: RelayClient | null = null;
let localProxy: LocalProxy | null = null;
let apiClient: ApiClient | null = null;
function createWindow() {
mainWindow = new BrowserWindow({
width: 900,
height: 650,
minWidth: 800,
minHeight: 600,
title: 'FunConnect - Minecraft 联机客户端',
frame: false,
titleBarStyle: 'hidden',
backgroundColor: '#1a1a2e',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
mainWindow.loadFile(path.join(__dirname, '../../renderer/index.html'));
mainWindow.on('closed', () => {
mainWindow = null;
cleanup();
});
}
function cleanup() {
if (localProxy) {
localProxy.stop();
localProxy = null;
}
if (relayClient) {
relayClient.disconnect();
relayClient = null;
}
}
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', () => {
cleanup();
if (process.platform !== 'darwin') app.quit();
});
// ===== IPC Handlers =====
// Window controls
ipcMain.handle('window:minimize', () => mainWindow?.minimize());
ipcMain.handle('window:maximize', () => {
if (mainWindow?.isMaximized()) mainWindow.unmaximize();
else mainWindow?.maximize();
});
ipcMain.handle('window:close', () => mainWindow?.close());
// Server connection
ipcMain.handle('server:connect', async (_event, serverUrl: string) => {
try {
apiClient = new ApiClient(serverUrl);
const health = await apiClient.getHealth();
return { success: true, data: health };
} catch (err: any) {
return { success: false, error: err.message };
}
});
// Room operations
ipcMain.handle('rooms:list', async () => {
if (!apiClient) return { success: false, error: '未连接服务器' };
try {
const data = await apiClient.getRooms();
return { success: true, data };
} catch (err: any) {
return { success: false, error: err.message };
}
});
ipcMain.handle('rooms:create', async (_event, roomData: any) => {
if (!apiClient) return { success: false, error: '未连接服务器' };
try {
const data = await apiClient.createRoom(roomData);
return { success: true, data };
} catch (err: any) {
return { success: false, error: err.response?.data?.error || err.message };
}
});
ipcMain.handle('rooms:delete', async (_event, roomId: string) => {
if (!apiClient) return { success: false, error: '未连接服务器' };
try {
await apiClient.deleteRoom(roomId);
return { success: true };
} catch (err: any) {
return { success: false, error: err.message };
}
});
// Join room - start local proxy and relay connection
ipcMain.handle('rooms:join', async (_event, opts: { serverHost: string; serverPort: number; roomId: string; localPort: number }) => {
try {
cleanup();
relayClient = new RelayClient(opts.serverHost, opts.serverPort, opts.roomId);
localProxy = new LocalProxy(opts.localPort, relayClient);
await localProxy.start();
relayClient.on('connected', () => {
mainWindow?.webContents.send('relay:status', { status: 'connected' });
});
relayClient.on('disconnected', () => {
mainWindow?.webContents.send('relay:status', { status: 'disconnected' });
});
relayClient.on('error', (err: string) => {
mainWindow?.webContents.send('relay:status', { status: 'error', error: err });
});
return { success: true, localPort: opts.localPort };
} catch (err: any) {
return { success: false, error: err.message };
}
});
// Host room - start relay and local proxy from local MC server
ipcMain.handle('rooms:host', async (_event, opts: { serverUrl: string; roomName: string; localMcPort: number; gameVersion: string; gameEdition: string; maxPlayers: number; password?: string }) => {
if (!apiClient) return { success: false, error: '未连接服务器' };
try {
const result = await apiClient.createRoom({
name: opts.roomName,
hostName: 'FunConnect',
hostPort: opts.localMcPort,
gameVersion: opts.gameVersion,
gameEdition: opts.gameEdition,
maxPlayers: opts.maxPlayers,
password: opts.password,
});
return { success: true, data: result };
} catch (err: any) {
return { success: false, error: err.response?.data?.error || err.message };
}
});
// Disconnect
ipcMain.handle('relay:disconnect', () => {
cleanup();
return { success: true };
});
// Stats
ipcMain.handle('server:stats', async () => {
if (!apiClient) return { success: false, error: '未连接服务器' };
try {
const data = await apiClient.getStats();
return { success: true, data };
} catch (err: any) {
return { success: false, error: err.message };
}
});