diff --git a/client/src/pages/GameDeploymentPage.tsx b/client/src/pages/GameDeploymentPage.tsx index fec5b35..bbc99c5 100644 --- a/client/src/pages/GameDeploymentPage.tsx +++ b/client/src/pages/GameDeploymentPage.tsx @@ -319,6 +319,44 @@ const GameDeploymentPage: React.FC = () => { } } + // 取消Minecraft下载 + const cancelMinecraftDownload = async () => { + if (!currentDownloadId.current) { + addNotification({ + type: 'error', + title: '取消失败', + message: '没有正在进行的下载任务' + }) + return + } + + try { + const response = await apiClient.cancelMinecraftDownload(currentDownloadId.current) + + if (response.success) { + // 重置下载状态 + setMinecraftDownloading(false) + setDownloadProgress(null) + currentDownloadId.current = null + + addNotification({ + type: 'info', + title: '下载已取消', + message: 'Minecraft服务端下载已取消' + }) + } else { + throw new Error(response.message || '取消下载失败') + } + } catch (error: any) { + console.error('取消下载失败:', error) + addNotification({ + type: 'error', + title: '取消失败', + message: error.message || '无法取消下载' + }) + } + } + // 创建Minecraft实例 const createMinecraftInstance = async () => { if (!instanceName.trim() || !downloadResult) { @@ -782,28 +820,41 @@ const GameDeploymentPage: React.FC = () => { {/* 下载按钮 */} - + + {/* 取消下载按钮 */} + {minecraftDownloading && ( + )} - + {/* 下载进度 */} {(downloadProgress || minecraftDownloading) && ( diff --git a/client/src/utils/api.ts b/client/src/utils/api.ts index 6226568..f80fbe6 100644 --- a/client/src/utils/api.ts +++ b/client/src/utils/api.ts @@ -413,6 +413,10 @@ class ApiClient { return this.post('/minecraft/download', data) } + async cancelMinecraftDownload(downloadId: string) { + return this.post('/minecraft/cancel-download', { downloadId }) + } + async createMinecraftInstance(data: { name: string description?: string diff --git a/server/src/modules/game/minecraft-server-api.ts b/server/src/modules/game/minecraft-server-api.ts index 410d406..4a6851e 100644 --- a/server/src/modules/game/minecraft-server-api.ts +++ b/server/src/modules/game/minecraft-server-api.ts @@ -406,10 +406,37 @@ export class FileManager { export class MinecraftServerDownloader { private onProgress?: ProgressCallback; private onLog?: LogCallback; + private cancelled: boolean = false; + private downloadId?: string; - constructor(onProgress?: ProgressCallback, onLog?: LogCallback) { + constructor(onProgress?: ProgressCallback, onLog?: LogCallback, downloadId?: string) { this.onProgress = onProgress; this.onLog = onLog; + this.downloadId = downloadId; + } + + /** + * 取消下载 + */ + cancel(): void { + this.cancelled = true; + if (this.onLog) { + this.onLog('下载已被用户取消', 'warn'); + } + } + + /** + * 检查是否已取消 + */ + isCancelled(): boolean { + return this.cancelled; + } + + /** + * 获取下载ID + */ + getDownloadId(): string | undefined { + return this.downloadId; } /** @@ -429,6 +456,11 @@ export class MinecraftServerDownloader { const log = silent ? undefined : this.onLog; try { + // 检查是否已取消 + if (this.cancelled) { + throw new Error('下载已被取消'); + } + // 验证Java环境(除非跳过) if (!skipJavaCheck) { if (log) log('检查Java环境...', 'info'); @@ -443,23 +475,43 @@ export class MinecraftServerDownloader { throw new Error('缺少必要参数:server 和 version'); } + // 检查是否已取消 + if (this.cancelled) { + throw new Error('下载已被取消'); + } + // 创建临时目录 if (log) log('创建临时工作目录...', 'info'); const tempDir = await FileManager.createTempDirectory(); if (log) log(`临时目录创建成功: ${tempDir}`, 'success'); try { + // 检查是否已取消 + if (this.cancelled) { + throw new Error('下载已被取消'); + } + // 获取下载地址 if (log) log('正在获取下载地址...', 'info'); const downloadData = await ApiService.getDownloadUrl(server, version); if (log) log('下载地址获取成功。', 'success'); + // 检查是否已取消 + if (this.cancelled) { + throw new Error('下载已被取消'); + } + // 下载服务端核心 const jarPath = FileManager.getServerJarPath(server, version); if (log) log(`正在下载服务端核心到: ${jarPath}`, 'info'); await ApiService.downloadFile(downloadData.url, jarPath, this.onProgress, log); if (log) log('服务端核心下载完成。', 'success'); + // 检查是否已取消 + if (this.cancelled) { + throw new Error('下载已被取消'); + } + // 运行服务端直到EULA协议(除非跳过) if (!skipServerRun) { if (log) log('正在运行服务端核心...', 'info'); @@ -468,6 +520,11 @@ export class MinecraftServerDownloader { if (log) log('服务端运行完成。', 'success'); } + // 检查是否已取消 + if (this.cancelled) { + throw new Error('下载已被取消'); + } + // 移动文件到目标目录 if (log) log(`正在移动文件到目标目录: ${targetDirectory}`, 'info'); await FileManager.moveFilesToTarget(targetDirectory, log); diff --git a/server/src/routes/minecraft.ts b/server/src/routes/minecraft.ts index 0e6e0d6..2df0cfd 100644 --- a/server/src/routes/minecraft.ts +++ b/server/src/routes/minecraft.ts @@ -10,6 +10,9 @@ const router = Router() let io: SocketIOServer let instanceManager: InstanceManager +// 全局下载器管理器 +const activeDownloads = new Map() + // 设置Socket.IO和InstanceManager export function setMinecraftDependencies(socketIO: SocketIOServer, instManager: InstanceManager) { io = socketIO @@ -39,6 +42,50 @@ router.get('/server-categories', async (req: Request, res: Response) => { } }) +// 取消Minecraft服务端下载 +router.post('/cancel-download', async (req: Request, res: Response) => { + try { + const { downloadId } = req.body + + if (!downloadId) { + return res.status(400).json({ + success: false, + message: '缺少下载ID参数' + }) + } + + // 查找活跃的下载任务 + const downloader = activeDownloads.get(downloadId) + + if (!downloader) { + return res.status(404).json({ + success: false, + message: '未找到指定的下载任务' + }) + } + + // 取消下载 + downloader.cancel() + + // 从活跃下载列表中移除 + activeDownloads.delete(downloadId) + + logger.info(`下载任务已取消: ${downloadId}`) + + res.json({ + success: true, + message: '下载已取消' + }) + + } catch (error: any) { + logger.error('取消下载失败:', error) + res.status(500).json({ + success: false, + message: error.message || '取消下载失败' + }) + } +}) + // 获取指定服务端的可用版本 router.get('/versions/:server', async (req: Request, res: Response) => { try { @@ -172,9 +219,14 @@ router.post('/download', async (req: Request, res: Response) => { }) } logger.info(`[${type.toUpperCase()}] ${message}`) - } + }, + // 下载ID + downloadId ) + // 将下载器添加到活跃下载列表 + activeDownloads.set(downloadId, downloader) + // 执行下载 await downloader.downloadServer({ server, @@ -186,6 +238,9 @@ router.post('/download', async (req: Request, res: Response) => { logger.info(`Minecraft服务端下载完成: ${server} ${version}`) + // 从活跃下载列表中移除 + activeDownloads.delete(downloadId) + // 下载完成,发送完成事件 if (io && socketId) { io.to(socketId).emit('minecraft-download-complete', { @@ -203,6 +258,9 @@ router.post('/download', async (req: Request, res: Response) => { } catch (error: any) { logger.error('Minecraft服务端下载失败:', error) + // 从活跃下载列表中移除 + activeDownloads.delete(downloadId) + // 发送错误事件 if (io && socketId) { io.to(socketId).emit('minecraft-download-error', { diff --git a/server/temp-minecraft-server/vanilla-snapshot-1.21.7-rc2.jar b/server/temp-minecraft-server/vanilla-snapshot-1.21.7-rc2.jar deleted file mode 100644 index a11d6eb..0000000 Binary files a/server/temp-minecraft-server/vanilla-snapshot-1.21.7-rc2.jar and /dev/null differ