From a5386e69e58ed5a3faa820d3c23ff8c689cbaa80 Mon Sep 17 00:00:00 2001 From: Lannamokia Date: Sun, 19 Oct 2025 04:06:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=BF=9C=E7=A8=8BVHD?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E5=8A=9F=E8=83=BD=E5=92=8CUSB=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现远程VHD选择服务集成,支持从配置文件中读取远程服务URL 添加NX_INS USB设备检测功能,支持从USB设备扫描和替换VHD文件 新增配置文件解析和HTTP客户端请求功能 优化VHD文件管理逻辑,支持关键词匹配和文件替换 --- MainWindow.xaml.cs | 67 ++++-- VHDManager.cs | 234 +++++++++++++++++++ VHDSelectServer/.dockerignore | 44 ++++ VHDSelectServer/.gitignore | 116 ++++++++++ VHDSelectServer/DOCKER_TROUBLESHOOTING.md | 182 +++++++++++++++ VHDSelectServer/Dockerfile | 36 +++ VHDSelectServer/README.md | 266 ++++++++++++++++++++++ VHDSelectServer/deploy.bat | 49 ++++ VHDSelectServer/deploy.sh | 59 +++++ VHDSelectServer/docker-compose.yml | 28 +++ VHDSelectServer/package.json | 27 +++ VHDSelectServer/server.js | 152 +++++++++++++ VHDSelectServer/server.py | 245 ++++++++++++++++++++ VHDSelectServer/start.bat | 47 ++++ vhdmonter_config.ini | 6 + 15 files changed, 1545 insertions(+), 13 deletions(-) create mode 100644 VHDSelectServer/.dockerignore create mode 100644 VHDSelectServer/.gitignore create mode 100644 VHDSelectServer/DOCKER_TROUBLESHOOTING.md create mode 100644 VHDSelectServer/Dockerfile create mode 100644 VHDSelectServer/README.md create mode 100644 VHDSelectServer/deploy.bat create mode 100644 VHDSelectServer/deploy.sh create mode 100644 VHDSelectServer/docker-compose.yml create mode 100644 VHDSelectServer/package.json create mode 100644 VHDSelectServer/server.js create mode 100644 VHDSelectServer/server.py create mode 100644 VHDSelectServer/start.bat create mode 100644 vhdmonter_config.ini diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index cbd856a..7d25a17 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -74,19 +74,44 @@ namespace VHDMounter { isProcessing = true; - // 调试:测试特定文件 - var testFile = @"C:\SDEZ_1.56.00_20250317134137.vhd"; - if (System.IO.File.Exists(testFile)) + // 尝试远程获取VHD选择 + OnStatusChanged("正在检查远程VHD选择配置..."); + string remoteSelectedKeyword = await vhdManager.GetRemoteVHDSelection(); + + // 检查是否存在NX_INS USB设备 + OnStatusChanged("正在检查NX_INS USB设备..."); + var nxInsUSB = vhdManager.FindNXInsUSBDrive(); + List usbVhdFiles = new List(); + + if (nxInsUSB != null) { - bool isValid = vhdManager.IsVHDFileValid(testFile); - OnStatusChanged($"测试文件 {testFile}: {(isValid ? "符合条件" : "不符合条件")}"); - await Task.Delay(2000); + OnStatusChanged($"找到NX_INS USB设备: {nxInsUSB.Name}"); + // 扫描USB设备中的VHD文件 + usbVhdFiles = vhdManager.ScanUSBForVHDFiles(nxInsUSB); + OnStatusChanged($"在USB设备中找到 {usbVhdFiles.Count} 个VHD文件"); + } + else + { + OnStatusChanged("未找到NX_INS USB设备,将使用本地VHD文件"); } - // 扫描VHD文件 - var vhdFiles = await vhdManager.ScanForVHDFiles(); + // 扫描本地VHD文件 + var localVhdFiles = await vhdManager.ScanForVHDFiles(); - if (vhdFiles.Count == 0) + // 如果找到USB设备和VHD文件,替换本地文件 + if (nxInsUSB != null && usbVhdFiles.Count > 0 && localVhdFiles.Count > 0) + { + OnStatusChanged("开始替换本地VHD文件..."); + bool replaced = await vhdManager.ReplaceLocalVHDFiles(usbVhdFiles, localVhdFiles); + if (replaced) + { + OnStatusChanged("本地VHD文件已替换,重新扫描本地文件..."); + // 重新扫描本地文件 + localVhdFiles = await vhdManager.ScanForVHDFiles(); + } + } + + if (localVhdFiles.Count == 0) { OnStatusChanged("未找到符合条件的VHD文件"); OnStatusChanged("请检查文件名是否包含SDEZ、SDHD或SDDT关键词"); @@ -97,16 +122,32 @@ namespace VHDMounter string selectedVHD; - if (vhdFiles.Count == 1) + // 如果有远程选择的关键词,优先使用 + if (!string.IsNullOrWhiteSpace(remoteSelectedKeyword)) { - selectedVHD = vhdFiles[0]; + selectedVHD = vhdManager.FindVHDByKeyword(localVhdFiles, remoteSelectedKeyword); + if (selectedVHD != null) + { + OnStatusChanged($"根据远程选择启动VHD: {System.IO.Path.GetFileName(selectedVHD)}"); + await ProcessSelectedVHD(selectedVHD); + return; + } + else + { + OnStatusChanged($"远程选择的关键词 '{remoteSelectedKeyword}' 未找到对应VHD文件,将显示选择器"); + } + } + + if (localVhdFiles.Count == 1) + { + selectedVHD = localVhdFiles[0]; await ProcessSelectedVHD(selectedVHD); } else { // 显示选择器 - availableVHDs = vhdFiles; - ShowVHDSelector(vhdFiles); + availableVHDs = localVhdFiles; + ShowVHDSelector(localVhdFiles); } } catch (Exception ex) diff --git a/VHDManager.cs b/VHDManager.cs index 3f7dda2..5752f17 100644 --- a/VHDManager.cs +++ b/VHDManager.cs @@ -5,6 +5,10 @@ using System.IO; using System.Linq; using System.Management; using System.Threading.Tasks; +using System.Text.RegularExpressions; +using System.Net.Http; +using System.Text.Json; +using System.Text; namespace VHDMounter { @@ -17,6 +21,9 @@ namespace VHDMounter public event Action StatusChanged; public event Action> VHDFilesFound; + private static readonly HttpClient httpClient = new HttpClient(); + private string configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "vhdmonter_config.ini"); + // 调试方法:检查特定文件是否符合条件 public bool IsVHDFileValid(string filePath) { @@ -45,6 +52,233 @@ namespace VHDMounter } } + // 检查是否存在卷标为NX_INS的USB设备 + public DriveInfo FindNXInsUSBDrive() + { + try + { + var usbDrives = DriveInfo.GetDrives().Where(d => d.IsReady && d.DriveType == DriveType.Removable); + foreach (var drive in usbDrives) + { + try + { + if (drive.VolumeLabel == "NX_INS") + { + StatusChanged?.Invoke($"找到NX_INS USB设备: {drive.Name}"); + return drive; + } + } + catch (Exception ex) + { + Debug.WriteLine($"读取驱动器 {drive.Name} 卷标时出错: {ex.Message}"); + } + } + return null; + } + catch (Exception ex) + { + Debug.WriteLine($"查找NX_INS USB设备时出错: {ex.Message}"); + return null; + } + } + + // 从USB设备中扫描VHD文件 + public List ScanUSBForVHDFiles(DriveInfo usbDrive) + { + StatusChanged?.Invoke($"正在扫描USB设备 {usbDrive.Name} 中的VHD文件..."); + var vhdFiles = new List(); + + try + { + var rootFiles = Directory.GetFiles(usbDrive.RootDirectory.FullName, "*.vhd", SearchOption.TopDirectoryOnly) + .Where(f => TARGET_KEYWORDS.Any(keyword => Path.GetFileName(f).ToUpper().Contains(keyword))) + .ToList(); + + vhdFiles.AddRange(rootFiles); + StatusChanged?.Invoke($"在USB设备中找到 {vhdFiles.Count} 个VHD文件"); + + foreach (var file in vhdFiles) + { + Debug.WriteLine($"USB中找到VHD文件: {file}"); + } + } + catch (Exception ex) + { + Debug.WriteLine($"扫描USB设备 {usbDrive.Name} 时出错: {ex.Message}"); + StatusChanged?.Invoke($"扫描USB设备时出错: {ex.Message}"); + } + + return vhdFiles; + } + + // 替换本地VHD文件 + public async Task ReplaceLocalVHDFiles(List usbVhdFiles, List localVhdFiles) + { + if (usbVhdFiles.Count == 0 || localVhdFiles.Count == 0) + return false; + + bool anyReplaced = false; + StatusChanged?.Invoke("正在替换本地VHD文件..."); + + foreach (var usbFile in usbVhdFiles) + { + string usbFileName = Path.GetFileName(usbFile); + string usbKeyword = ExtractKeyword(usbFileName); + + if (string.IsNullOrEmpty(usbKeyword)) + continue; + + // 查找对应关键词的本地文件 + var matchingLocalFiles = localVhdFiles.Where(f => + ExtractKeyword(Path.GetFileName(f)) == usbKeyword).ToList(); + + if (matchingLocalFiles.Count > 0) + { + foreach (var localFile in matchingLocalFiles) + { + try + { + // 直接删除本地文件 + StatusChanged?.Invoke($"正在删除本地文件: {Path.GetFileName(localFile)}"); + if (File.Exists(localFile)) + File.Delete(localFile); + + // 复制USB文件到本地 + StatusChanged?.Invoke($"正在用 {Path.GetFileName(usbFile)} 替换 {Path.GetFileName(localFile)}"); + File.Copy(usbFile, localFile); + + anyReplaced = true; + StatusChanged?.Invoke($"替换完成: {Path.GetFileName(localFile)}"); + } + catch (Exception ex) + { + StatusChanged?.Invoke($"替换文件时出错: {ex.Message}"); + Debug.WriteLine($"替换文件时出错: {ex.Message}"); + } + } + } + } + + return anyReplaced; + } + + // 提取VHD文件名中的关键词部分 + private string ExtractKeyword(string fileName) + { + foreach (var keyword in TARGET_KEYWORDS) + { + if (fileName.ToUpper().Contains(keyword)) + return keyword; + } + return string.Empty; + } + + // 读取配置文件 + private Dictionary ReadConfig() + { + var config = new Dictionary(); + + try + { + if (!File.Exists(configFilePath)) + { + StatusChanged?.Invoke("配置文件不存在,使用默认设置"); + return config; + } + + var lines = File.ReadAllLines(configFilePath); + foreach (var line in lines) + { + if (string.IsNullOrWhiteSpace(line) || line.StartsWith(";") || line.StartsWith("[")) + continue; + + var parts = line.Split('=', 2); + if (parts.Length == 2) + { + config[parts[0].Trim()] = parts[1].Trim(); + } + } + } + catch (Exception ex) + { + StatusChanged?.Invoke($"读取配置文件失败: {ex.Message}"); + } + + return config; + } + + // 远程获取VHD选择 + public async Task GetRemoteVHDSelection() + { + try + { + var config = ReadConfig(); + + if (!config.TryGetValue("EnableRemoteSelection", out var enableRemote) || + !bool.TryParse(enableRemote, out var isEnabled) || !isEnabled) + { + StatusChanged?.Invoke("远程VHD选择功能已禁用"); + return null; + } + + if (!config.TryGetValue("BootImageSelectUrl", out var url) || string.IsNullOrWhiteSpace(url)) + { + StatusChanged?.Invoke("未配置BootImageSelectUrl"); + return null; + } + + StatusChanged?.Invoke($"正在从远程获取VHD选择: {url}"); + + var response = await httpClient.GetAsync(url); + if (!response.IsSuccessStatusCode) + { + StatusChanged?.Invoke($"远程请求失败: {response.StatusCode}"); + return null; + } + + var jsonContent = await response.Content.ReadAsStringAsync(); + var jsonDoc = JsonDocument.Parse(jsonContent); + + if (jsonDoc.RootElement.TryGetProperty("BootImageSelected", out var bootImageElement)) + { + var selectedKeyword = bootImageElement.GetString(); + StatusChanged?.Invoke($"远程选择的VHD关键词: {selectedKeyword}"); + return selectedKeyword; + } + else + { + StatusChanged?.Invoke("远程响应中未找到BootImageSelected字段"); + return null; + } + } + catch (Exception ex) + { + StatusChanged?.Invoke($"远程获取VHD选择失败: {ex.Message}"); + return null; + } + } + + // 根据关键词查找对应的VHD文件 + public string FindVHDByKeyword(List vhdFiles, string keyword) + { + if (string.IsNullOrWhiteSpace(keyword) || vhdFiles == null || vhdFiles.Count == 0) + return null; + + var matchingFile = vhdFiles.FirstOrDefault(f => + ExtractKeyword(Path.GetFileName(f)).Equals(keyword, StringComparison.OrdinalIgnoreCase)); + + if (matchingFile != null) + { + StatusChanged?.Invoke($"找到匹配关键词 '{keyword}' 的VHD文件: {Path.GetFileName(matchingFile)}"); + } + else + { + StatusChanged?.Invoke($"未找到匹配关键词 '{keyword}' 的VHD文件"); + } + + return matchingFile; + } + public async Task> ScanForVHDFiles() { StatusChanged?.Invoke("正在扫描VHD文件..."); diff --git a/VHDSelectServer/.dockerignore b/VHDSelectServer/.dockerignore new file mode 100644 index 0000000..feaa7cf --- /dev/null +++ b/VHDSelectServer/.dockerignore @@ -0,0 +1,44 @@ +# Node.js +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# 运行时生成的文件 +vhd-config.json +*.log + +# 开发工具 +.git +.gitignore +README.md + +# Docker相关 +Dockerfile +docker-compose.yml +.dockerignore + +# IDE和编辑器 +.vscode +.idea +*.swp +*.swo +*~ + +# 操作系统 +.DS_Store +Thumbs.db + +# 临时文件 +*.tmp +*.temp + +# 测试覆盖率 +coverage + +# 环境变量文件 +.env +.env.local +.env.development.local +.env.test.local +.env.production.local \ No newline at end of file diff --git a/VHDSelectServer/.gitignore b/VHDSelectServer/.gitignore new file mode 100644 index 0000000..ac9e5e9 --- /dev/null +++ b/VHDSelectServer/.gitignore @@ -0,0 +1,116 @@ +# Node.js 依赖 +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json + +# 运行时生成的配置文件 +vhd-config.json + +# 日志文件 +*.log +logs/ + +# 环境变量文件 +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE 和编辑器 +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# 操作系统生成的文件 +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# 临时文件 +*.tmp +*.temp +*.bak +*.backup + +# 测试覆盖率报告 +coverage/ +.nyc_output/ + +# Docker 相关(可选,根据需要调整) +# Dockerfile +# docker-compose.yml +# .dockerignore + +# 构建输出 +dist/ +build/ + +# 缓存目录 +.cache/ +.parcel-cache/ + +# 运行时数据 +data/ +*.pid +*.seed +*.pid.lock + +# 依赖目录 +jspm_packages/ + +# TypeScript 缓存 +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt + +# Gatsby files +.cache/ +public + +# Storybook build outputs +.out +.storybook-out + +# Temporary folders +tmp/ +temp/ \ No newline at end of file diff --git a/VHDSelectServer/DOCKER_TROUBLESHOOTING.md b/VHDSelectServer/DOCKER_TROUBLESHOOTING.md new file mode 100644 index 0000000..b7f9168 --- /dev/null +++ b/VHDSelectServer/DOCKER_TROUBLESHOOTING.md @@ -0,0 +1,182 @@ +# Docker 部署故障排除指南 + +## 常见问题及解决方案 + +### 1. Docker镜像拉取失败 + +**问题**: `failed to resolve source metadata for docker.io/library/node:18-alpine` + +**原因**: 网络连接问题或Docker Hub访问受限 + +**解决方案**: + +#### 方案A: 配置Docker镜像源(推荐) +```bash +# 创建或编辑Docker配置文件 +# Windows: %USERPROFILE%\.docker\daemon.json +# Linux/macOS: /etc/docker/daemon.json + +{ + "registry-mirrors": [ + "https://docker.mirrors.ustc.edu.cn", + "https://hub-mirror.c.163.com", + "https://mirror.baidubce.com" + ] +} + +# 重启Docker服务 +# Windows: 重启Docker Desktop +# Linux: sudo systemctl restart docker +``` + +#### 方案B: 使用国内镜像 +修改 `Dockerfile` 第一行: +```dockerfile +# 原始 +FROM node:18-alpine + +# 替换为 +FROM registry.cn-hangzhou.aliyuncs.com/library/node:18-alpine +``` + +#### 方案C: 使用本地Node.js部署 +```bash +npm install +npm start +``` + +### 2. 端口占用问题 + +**问题**: `port is already allocated` + +**解决方案**: +```bash +# 查看端口占用 +netstat -ano | findstr :8080 # Windows +lsof -i :8080 # Linux/macOS + +# 停止占用端口的进程 +taskkill /PID /F # Windows +kill -9 # Linux/macOS + +# 或修改端口 +docker-compose down +# 编辑 docker-compose.yml 中的端口映射 +docker-compose up -d +``` + +### 3. 权限问题 + +**问题**: `permission denied` + +**解决方案**: +```bash +# Linux/macOS +sudo chown -R $USER:$USER ./config +chmod 755 ./config + +# Windows +# 右键config文件夹 -> 属性 -> 安全 -> 编辑权限 +``` + +### 4. 配置文件不持久化 + +**问题**: 容器重启后配置丢失 + +**检查**: +```bash +# 确认卷映射 +docker-compose ps +docker inspect vhd-select-server + +# 检查配置目录 +ls -la ./config/ +``` + +**解决方案**: +```bash +# 确保config目录存在 +mkdir -p config + +# 检查docker-compose.yml中的volumes配置 +# 应该包含: - ./config:/app/config +``` + +### 5. 容器无法启动 + +**诊断步骤**: +```bash +# 查看容器状态 +docker-compose ps + +# 查看详细日志 +docker-compose logs vhd-select-server + +# 进入容器调试 +docker-compose exec vhd-select-server sh + +# 检查配置 +docker-compose config +``` + +### 6. 网络连接问题 + +**问题**: 无法访问 http://localhost:8080 + +**检查**: +```bash +# 确认容器运行状态 +docker-compose ps + +# 确认端口映射 +docker port vhd-select-server + +# 测试网络连接 +curl http://localhost:8080/api/status +``` + +## 完全重置 + +如果遇到无法解决的问题,可以完全重置: + +```bash +# 停止并删除所有容器 +docker-compose down -v + +# 删除本地镜像(如果有) +docker rmi lty271104/vhd-select-server:latest + +# 清理Docker缓存 +docker system prune -a + +# 重新部署 +docker-compose up -d +``` + +## 获取帮助 + +1. 查看详细日志: `docker-compose logs -f` +2. 检查Docker版本: `docker --version` +3. 检查系统资源: `docker system df` +4. 如果问题持续,请使用本地Node.js部署方式 + +## 性能优化 + +### 减少镜像大小 +```dockerfile +# 使用多阶段构建 +FROM node:18-alpine AS builder +# ... 构建步骤 + +FROM node:18-alpine AS runtime +# ... 运行时步骤 +``` + +### 加速构建 +```bash +# 使用.dockerignore排除不必要文件 +# 已包含在项目中 + +# 利用构建缓存 +docker-compose build --parallel +``` \ No newline at end of file diff --git a/VHDSelectServer/Dockerfile b/VHDSelectServer/Dockerfile new file mode 100644 index 0000000..7725c64 --- /dev/null +++ b/VHDSelectServer/Dockerfile @@ -0,0 +1,36 @@ +# 使用官方Node.js运行时作为基础镜像 +FROM node:18-alpine + +# 设置工作目录 +WORKDIR /app + +# 复制package.json和package-lock.json(如果存在) +COPY package*.json ./ + +# 安装依赖 +RUN npm ci --only=production + +# 复制应用源代码 +COPY . . + +# 创建配置文件目录 +RUN mkdir -p /app/config + +# 暴露端口 +EXPOSE 8080 + +# 设置环境变量 +ENV NODE_ENV=production +ENV PORT=8080 +ENV CONFIG_PATH=/app/config + +# 创建非root用户 +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nodejs -u 1001 + +# 更改文件所有权 +RUN chown -R nodejs:nodejs /app +USER nodejs + +# 启动应用 +CMD ["npm", "start"] \ No newline at end of file diff --git a/VHDSelectServer/README.md b/VHDSelectServer/README.md new file mode 100644 index 0000000..477f1a2 --- /dev/null +++ b/VHDSelectServer/README.md @@ -0,0 +1,266 @@ +# VHD选择服务器 - 跨平台版本 + +一个现代化的跨平台VHD关键词管理服务器,提供Web GUI界面和HTTP API接口。 + +## ✨ 特性 + +- 🌐 **跨平台支持**: 支持Windows、Linux、macOS +- 🎨 **现代化Web界面**: 响应式设计,支持移动设备 +- 🔧 **RESTful API**: 标准HTTP API接口 +- 💾 **持久化存储**: 自动保存配置到JSON文件 +- 🚀 **零配置启动**: 开箱即用 +- 📱 **实时更新**: 界面自动刷新状态 +- 🔒 **输入验证**: 安全的用户输入处理 + +## 🛠️ 系统要求 + +### Node.js版本(推荐) +- Node.js 14.0+ +- npm(随Node.js安装) + +### Python版本(备选) +- Python 3.6+ +- 无需额外依赖 + +## 🚀 快速开始 + +### 方法1: Docker部署(推荐) + +1. **一键部署脚本(最简单)** + ```bash + # Windows + deploy.bat + + # Linux/macOS + chmod +x deploy.sh + ./deploy.sh + ``` + +2. **使用Docker Compose** + ```bash + # 构建并启动服务 + docker-compose up -d + + # 查看日志 + docker-compose logs -f + + # 停止服务 + docker-compose down + ``` + +3. **使用Docker命令** + ```bash + # 运行容器(配置持久化) + docker run -d \ + --name vhd-select-server \ + -p 8080:8080 \ + -v $(pwd)/config:/app/config \ + lty271104/vhd-select-server:latest + ``` + +### 方法2: Node.js版本 + +1. **安装Node.js** + - 访问 [https://nodejs.org/](https://nodejs.org/) + - 下载并安装LTS版本 + +2. **启动服务器** + ```bash + # 双击运行 + start.bat + + # 或者命令行运行 + npm install + npm start + ``` + +### 方法3: Python版本 + +```bash +# 直接运行Python服务器 +python server.py + +# 或指定端口 +python server.py 8080 +``` + +## 🌐 访问界面 + +服务器启动后,访问以下地址: + +- **Web界面**: http://localhost:8080 +- **API文档**: http://localhost:8080/api/status + +## 📡 API接口 + +### 获取当前VHD关键词 +```http +GET /api/boot-image-select +``` + +**响应示例**: +```json +{ + "success": true, + "BootImageSelected": "SDEZ", + "timestamp": "2025-10-19T03:05:33.486615" +} +``` + +### 设置VHD关键词 +```http +POST /api/set-vhd +Content-Type: application/json + +{ + "BootImageSelected": "NEW_KEYWORD" +} +``` + +**响应示例**: +```json +{ + "success": true, + "BootImageSelected": "NEW_KEYWORD", + "message": "VHD关键词更新成功" +} +``` + +### 服务器状态 +```http +GET /api/status +``` + +**响应示例**: +```json +{ + "success": true, + "status": "running", + "BootImageSelected": "SDEZ", + "timestamp": "2025-10-19T03:05:33.486615", + "version": "1.0.0" +} +``` + +## 🎯 使用说明 + +### Web界面操作 + +1. **查看当前状态**: 页面会显示服务器运行状态和当前VHD关键词 +2. **设置新关键词**: 在输入框中输入新的VHD关键词,点击"更新"按钮 +3. **自动刷新**: 页面每30秒自动刷新状态 +4. **实时反馈**: 操作结果会立即显示在界面上 + +### 配置持久化 + +- **Docker部署**: 配置保存在 `/app/config/vhd-config.json`,通过卷映射到主机的 `./config` 目录 +- **本地部署**: 配置保存在项目根目录的 `vhd-config.json` 文件中 +- **环境变量**: 可通过 `CONFIG_PATH` 环境变量自定义配置文件路径 + +### Docker相关 + +#### 环境变量 +- `PORT`: 服务端口(默认: 8080) +- `CONFIG_PATH`: 配置文件目录路径(默认: /app/config) +- `NODE_ENV`: 运行环境(默认: production) + +#### 数据卷 +- `/app/config`: 配置文件持久化目录 +- `/app/data`: 数据文件持久化目录(可选) + +#### 健康检查 +- 端点: `http://localhost:8080/api/status` +- 间隔: 30秒 +- 超时: 10秒 +- 重试: 3次 + +#### 故障排除 +如果Docker部署遇到问题,请参考 [Docker故障排除指南](DOCKER_TROUBLESHOOTING.md) + +### 命令行测试 + +```bash +# 获取当前VHD关键词 +curl http://localhost:8080/api/boot-image-select + +# 设置新的VHD关键词 +curl -X POST http://localhost:8080/api/set-vhd \ + -H "Content-Type: application/json" \ + -d '{"BootImageSelected":"CUSTOM_VHD"}' +``` + +## 📁 文件结构 + +``` +VHDSelectServer/ +├── server.js # Node.js服务器 +├── server.py # Python服务器 +├── package.json # Node.js依赖配置 +├── start.bat # Windows启动脚本 +├── public/ +│ └── index.html # Web界面 +├── vhd-config.json # 配置文件(自动生成) +└── README.md # 说明文档 +``` + +## 🔧 配置说明 + +- **端口**: 默认8080,可通过环境变量`PORT`修改 +- **配置文件**: `vhd-config.json`自动生成,存储当前VHD关键词 +- **日志**: 服务器会在控制台输出访问日志 + +## 🔗 与VHD挂载程序集成 + +此服务器与原有的VHD挂载程序完全兼容: + +1. **API兼容**: 保持原有的`/api/boot-image-select`接口 +2. **数据格式**: 返回相同的JSON格式 +3. **配置持久化**: VHD关键词会自动保存和恢复 + +## 🛡️ 安全特性 + +- **输入验证**: 自动验证和清理用户输入 +- **CORS支持**: 支持跨域请求 +- **错误处理**: 完善的错误处理和用户反馈 +- **格式标准化**: 自动转换为大写格式 + +## 🐛 故障排除 + +### 端口被占用 +```bash +# 检查端口占用 +netstat -ano | findstr :8080 + +# 使用其他端口 +python server.py 8081 +``` + +### Node.js未安装 +- 下载安装: https://nodejs.org/ +- 使用Python版本作为备选方案 + +### 权限问题 +- 确保有写入配置文件的权限 +- 在管理员模式下运行(如需要) + +## 📝 更新日志 + +### v1.0.0 +- ✅ 跨平台支持(Node.js + Python) +- ✅ 现代化Web界面 +- ✅ RESTful API接口 +- ✅ 用户自定义VHD关键词 +- ✅ 配置持久化存储 +- ✅ 实时状态更新 + +## 🤝 技术支持 + +如有问题,请检查: +1. 服务器是否正常启动 +2. 端口是否被占用 +3. 防火墙设置 +4. 浏览器控制台错误信息 + +--- + +**🎉 享受使用全新的跨平台VHD选择服务器!** \ No newline at end of file diff --git a/VHDSelectServer/deploy.bat b/VHDSelectServer/deploy.bat new file mode 100644 index 0000000..3019020 --- /dev/null +++ b/VHDSelectServer/deploy.bat @@ -0,0 +1,49 @@ +@echo off +echo VHD Select Server Docker 部署脚本 +echo ================================ + +echo 检查Docker是否运行... +docker --version >nul 2>&1 +if %errorlevel% neq 0 ( + echo 错误: Docker未安装或未运行 + echo 请先安装并启动Docker Desktop + pause + exit /b 1 +) + +echo 检查Docker Compose... +docker-compose --version >nul 2>&1 +if %errorlevel% neq 0 ( + echo 错误: Docker Compose未安装 + pause + exit /b 1 +) + +echo 创建配置目录... +if not exist "config" mkdir config + +echo 拉取并启动服务... +docker-compose up -d + +if %errorlevel% equ 0 ( + echo. + echo ✅ 部署成功! + echo 🌐 Web界面: http://localhost:8080 + echo 📁 配置文件: ./config/vhd-config.json + echo. + echo 常用命令: + echo 查看日志: docker-compose logs -f + echo 停止服务: docker-compose down + echo 重启服务: docker-compose restart +) else ( + echo. + echo ❌ 部署失败,请检查错误信息 + echo 如果是网络问题,请尝试: + echo 1. 检查网络连接 + echo 2. 配置Docker镜像源 + echo 3. 使用本地Node.js部署: npm start +) + +echo. +echo 按任意键退出... +pause >nul \ No newline at end of file diff --git a/VHDSelectServer/deploy.sh b/VHDSelectServer/deploy.sh new file mode 100644 index 0000000..56e6852 --- /dev/null +++ b/VHDSelectServer/deploy.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +echo "VHD Select Server Docker 部署脚本" +echo "================================" + +# 检查Docker是否安装 +if ! command -v docker &> /dev/null; then + echo "❌ 错误: Docker未安装" + echo "请先安装Docker: https://docs.docker.com/get-docker/" + exit 1 +fi + +# 检查Docker是否运行 +if ! docker info &> /dev/null; then + echo "❌ 错误: Docker未运行" + echo "请启动Docker服务" + exit 1 +fi + +echo "✅ Docker检查通过" + +# 检查Docker Compose +if ! command -v docker-compose &> /dev/null; then + echo "❌ 错误: Docker Compose未安装" + echo "请安装Docker Compose" + exit 1 +fi + +echo "✅ Docker Compose检查通过" + +# 创建配置目录 +echo "📁 创建配置目录..." +mkdir -p config + +# 拉取并启动服务 +echo "🚀 拉取并启动服务..." +if docker-compose up -d; then + echo "" + echo "✅ 部署成功!" + echo "🌐 Web界面: http://localhost:8080" + echo "📁 配置文件: ./config/vhd-config.json" + echo "" + echo "常用命令:" + echo " 查看日志: docker-compose logs -f" + echo " 停止服务: docker-compose down" + echo " 重启服务: docker-compose restart" + echo " 查看状态: docker-compose ps" +else + echo "" + echo "❌ 部署失败,请检查错误信息" + echo "如果是网络问题,请尝试:" + echo " 1. 检查网络连接" + echo " 2. 配置Docker镜像源" + echo " 3. 使用本地Node.js部署: npm start" + exit 1 +fi + +echo "" +echo "🎉 部署完成!" \ No newline at end of file diff --git a/VHDSelectServer/docker-compose.yml b/VHDSelectServer/docker-compose.yml new file mode 100644 index 0000000..a8521c8 --- /dev/null +++ b/VHDSelectServer/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3.8' + +services: + vhd-select-server: + image: lty271104/vhd-select-server:latest + container_name: vhd-select-server + ports: + - "8080:8080" + volumes: + # 配置文件持久化映射 + - ./config:/app/config + # 可选:映射整个数据目录 + - vhd-data:/app/data + environment: + - NODE_ENV=production + - PORT=8080 + - CONFIG_PATH=/app/config + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/status"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +volumes: + vhd-data: + driver: local \ No newline at end of file diff --git a/VHDSelectServer/package.json b/VHDSelectServer/package.json new file mode 100644 index 0000000..bece156 --- /dev/null +++ b/VHDSelectServer/package.json @@ -0,0 +1,27 @@ +{ + "name": "vhd-select-server", + "version": "1.0.0", + "description": "Cross-platform VHD selection server with web GUI", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "node server.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "vhd", + "server", + "cross-platform", + "web-gui" + ], + "author": "VHD Select Server", + "license": "MIT", + "dependencies": { + "express": "^4.18.2", + "cors": "^2.8.5", + "body-parser": "^1.20.2" + }, + "engines": { + "node": ">=14.0.0" + } +} \ No newline at end of file diff --git a/VHDSelectServer/server.js b/VHDSelectServer/server.js new file mode 100644 index 0000000..5228f02 --- /dev/null +++ b/VHDSelectServer/server.js @@ -0,0 +1,152 @@ +const express = require('express'); +const cors = require('cors'); +const bodyParser = require('body-parser'); +const path = require('path'); +const fs = require('fs'); + +const app = express(); +const PORT = process.env.PORT || 8080; + +// 中间件 +app.use(cors()); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); +app.use(express.static(path.join(__dirname, 'public'))); + +// VHD关键词存储 +let currentVhdKeyword = 'SDEZ'; // 默认值 + +// 配置文件路径 - 支持Docker环境 +const configDir = process.env.CONFIG_PATH || __dirname; +const configFile = path.join(configDir, 'vhd-config.json'); + +// 确保配置目录存在 +if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true }); +} + +// 加载配置 +function loadConfig() { + try { + if (fs.existsSync(configFile)) { + const config = JSON.parse(fs.readFileSync(configFile, 'utf8')); + currentVhdKeyword = config.vhdKeyword || 'SDEZ'; + } + } catch (error) { + console.log('配置文件加载失败,使用默认值:', error.message); + } +} + +// 保存配置 +function saveConfig() { + try { + const config = { vhdKeyword: currentVhdKeyword }; + fs.writeFileSync(configFile, JSON.stringify(config, null, 2)); + } catch (error) { + console.error('配置文件保存失败:', error.message); + } +} + +// API路由 + +// 获取当前VHD关键词 +app.get('/api/boot-image-select', (req, res) => { + console.log(`[${new Date().toISOString()}] API请求: GET /api/boot-image-select`); + res.json({ + success: true, + BootImageSelected: currentVhdKeyword, + timestamp: new Date().toISOString() + }); +}); + +// 设置VHD关键词 +app.post('/api/set-vhd', (req, res) => { + const { BootImageSelected } = req.body; + + if (!BootImageSelected || typeof BootImageSelected !== 'string') { + return res.status(400).json({ + success: false, + error: 'VHD关键词不能为空且必须是字符串' + }); + } + + // 验证关键词格式(可选:添加更严格的验证) + const trimmedKeyword = BootImageSelected.trim().toUpperCase(); + if (trimmedKeyword.length === 0) { + return res.status(400).json({ + success: false, + error: 'VHD关键词不能为空' + }); + } + + currentVhdKeyword = trimmedKeyword; + saveConfig(); + + console.log(`[${new Date().toISOString()}] VHD关键词已更新为: ${currentVhdKeyword}`); + + res.json({ + success: true, + BootImageSelected: currentVhdKeyword, + message: 'VHD关键词更新成功' + }); +}); + +// 获取服务器状态 +app.get('/api/status', (req, res) => { + res.json({ + success: true, + status: 'running', + BootImageSelected: currentVhdKeyword, + uptime: process.uptime(), + timestamp: new Date().toISOString(), + version: '1.0.0' + }); +}); + +// 主页路由 +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, 'public', 'index.html')); +}); + +// 404处理 +app.use((req, res) => { + res.status(404).json({ + success: false, + error: '页面未找到' + }); +}); + +// 错误处理 +app.use((err, req, res, next) => { + console.error('服务器错误:', err); + res.status(500).json({ + success: false, + error: '服务器内部错误' + }); +}); + +// 启动服务器 +loadConfig(); + +app.listen(PORT, () => { + console.log('='.repeat(50)); + console.log('🚀 VHD选择服务器已启动'); + console.log(`📡 服务器地址: http://localhost:${PORT}`); + console.log(`🔧 API地址: http://localhost:${PORT}/api/boot-image-select`); + console.log(`📊 状态页面: http://localhost:${PORT}/api/status`); + console.log(`🎯 当前VHD关键词: ${currentVhdKeyword}`); + console.log('='.repeat(50)); +}); + +// 优雅关闭 +process.on('SIGINT', () => { + console.log('\n正在关闭服务器...'); + saveConfig(); + process.exit(0); +}); + +process.on('SIGTERM', () => { + console.log('\n正在关闭服务器...'); + saveConfig(); + process.exit(0); +}); \ No newline at end of file diff --git a/VHDSelectServer/server.py b/VHDSelectServer/server.py new file mode 100644 index 0000000..9d33ff0 --- /dev/null +++ b/VHDSelectServer/server.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +VHD选择服务器 - Python版本 +跨平台的VHD关键词管理服务器,带有Web GUI界面 +""" + +import json +import os +import sys +from datetime import datetime +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.parse import urlparse, parse_qs +import threading +import webbrowser +from pathlib import Path + +class VHDSelectHandler(BaseHTTPRequestHandler): + def __init__(self, *args, **kwargs): + self.config_file = Path(__file__).parent / 'vhd-config.json' + super().__init__(*args, **kwargs) + + def load_config(self): + """加载VHD配置""" + try: + if self.config_file.exists(): + with open(self.config_file, 'r', encoding='utf-8') as f: + config = json.load(f) + return config.get('vhdKeyword', 'SDEZ') + except Exception as e: + print(f"配置加载失败: {e}") + return 'SDEZ' + + def save_config(self, vhd_keyword): + """保存VHD配置""" + try: + config = {'vhdKeyword': vhd_keyword} + with open(self.config_file, 'w', encoding='utf-8') as f: + json.dump(config, f, ensure_ascii=False, indent=2) + return True + except Exception as e: + print(f"配置保存失败: {e}") + return False + + def send_json_response(self, data, status_code=200): + """发送JSON响应""" + self.send_response(status_code) + self.send_header('Content-Type', 'application/json; charset=utf-8') + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type') + self.end_headers() + + json_str = json.dumps(data, ensure_ascii=False, indent=2) + self.wfile.write(json_str.encode('utf-8')) + + def send_html_file(self, file_path): + """发送HTML文件""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + + self.send_response(200) + self.send_header('Content-Type', 'text/html; charset=utf-8') + self.end_headers() + self.wfile.write(content.encode('utf-8')) + except FileNotFoundError: + self.send_error(404, '文件未找到') + except Exception as e: + self.send_error(500, f'服务器错误: {e}') + + def do_OPTIONS(self): + """处理CORS预检请求""" + self.send_response(200) + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type') + self.end_headers() + + def do_GET(self): + """处理GET请求""" + parsed_path = urlparse(self.path) + path = parsed_path.path + + print(f"[{datetime.now().isoformat()}] GET {path}") + + if path == '/': + # 主页 + html_file = Path(__file__).parent / 'public' / 'index.html' + self.send_html_file(html_file) + + elif path == '/api/boot-image-select': + # 获取当前VHD关键词 + current_keyword = self.load_config() + response = { + 'success': True, + 'BootImageSelected': current_keyword, + 'timestamp': datetime.now().isoformat() + } + self.send_json_response(response) + + elif path == '/api/status': + # 服务器状态 + current_keyword = self.load_config() + response = { + 'success': True, + 'status': 'running', + 'BootImageSelected': current_keyword, + 'timestamp': datetime.now().isoformat(), + 'version': '1.0.0 (Python)' + } + self.send_json_response(response) + + else: + # 404错误 + response = { + 'success': False, + 'error': '页面未找到' + } + self.send_json_response(response, 404) + + def do_POST(self): + """处理POST请求""" + parsed_path = urlparse(self.path) + path = parsed_path.path + + print(f"[{datetime.now().isoformat()}] POST {path}") + + if path == '/api/set-vhd': + try: + # 读取请求体 + content_length = int(self.headers.get('Content-Length', 0)) + post_data = self.rfile.read(content_length) + + # 解析JSON数据 + data = json.loads(post_data.decode('utf-8')) + vhd_keyword = data.get('BootImageSelected', '').strip().upper() + + if not vhd_keyword: + response = { + 'success': False, + 'error': 'VHD关键词不能为空' + } + self.send_json_response(response, 400) + return + + # 保存配置 + if self.save_config(vhd_keyword): + response = { + 'success': True, + 'BootImageSelected': vhd_keyword, + 'message': 'VHD关键词更新成功' + } + print(f"VHD关键词已更新为: {vhd_keyword}") + self.send_json_response(response) + else: + response = { + 'success': False, + 'error': '配置保存失败' + } + self.send_json_response(response, 500) + + except json.JSONDecodeError: + response = { + 'success': False, + 'error': '无效的JSON数据' + } + self.send_json_response(response, 400) + except Exception as e: + response = { + 'success': False, + 'error': f'服务器错误: {str(e)}' + } + self.send_json_response(response, 500) + else: + # 404错误 + response = { + 'success': False, + 'error': '页面未找到' + } + self.send_json_response(response, 404) + + def log_message(self, format, *args): + """自定义日志格式""" + pass # 禁用默认日志,使用自定义日志 + +def start_server(port=8080): + """启动服务器""" + server_address = ('', port) + httpd = HTTPServer(server_address, VHDSelectHandler) + + print('=' * 50) + print('🚀 VHD选择服务器已启动 (Python版本)') + print(f'📡 服务器地址: http://localhost:{port}') + print(f'🔧 API地址: http://localhost:{port}/api/boot-image-select') + print(f'📊 状态页面: http://localhost:{port}/api/status') + + # 加载当前配置 + config_file = Path(__file__).parent / 'vhd-config.json' + try: + if config_file.exists(): + with open(config_file, 'r', encoding='utf-8') as f: + config = json.load(f) + current_keyword = config.get('vhdKeyword', 'SDEZ') + else: + current_keyword = 'SDEZ' + except: + current_keyword = 'SDEZ' + + print(f'🎯 当前VHD关键词: {current_keyword}') + print('=' * 50) + print('💡 提示: 按 Ctrl+C 停止服务器') + print() + + # 自动打开浏览器 + def open_browser(): + import time + time.sleep(1) # 等待服务器启动 + try: + webbrowser.open(f'http://localhost:{port}') + except: + pass + + browser_thread = threading.Thread(target=open_browser) + browser_thread.daemon = True + browser_thread.start() + + try: + httpd.serve_forever() + except KeyboardInterrupt: + print('\n正在关闭服务器...') + httpd.shutdown() + print('服务器已关闭') + +if __name__ == '__main__': + # 检查命令行参数 + port = 8080 + if len(sys.argv) > 1: + try: + port = int(sys.argv[1]) + except ValueError: + print('错误: 端口号必须是数字') + sys.exit(1) + + start_server(port) \ No newline at end of file diff --git a/VHDSelectServer/start.bat b/VHDSelectServer/start.bat new file mode 100644 index 0000000..3690e70 --- /dev/null +++ b/VHDSelectServer/start.bat @@ -0,0 +1,47 @@ +@echo off +chcp 65001 >nul +echo ================================================ +echo VHD选择服务器启动脚本 +echo ================================================ +echo. + +:: 检查Node.js是否已安装 +node --version >nul 2>&1 +if %errorlevel% neq 0 ( + echo ❌ 未检测到Node.js,请先安装Node.js + echo. + echo 📥 请访问以下网址下载并安装Node.js: + echo https://nodejs.org/ + echo. + echo 💡 建议下载LTS版本(长期支持版本) + echo. + pause + exit /b 1 +) + +echo ✅ Node.js已安装 +node --version +echo. + +:: 检查依赖是否已安装 +if not exist "node_modules" ( + echo 📦 正在安装依赖包... + npm install + if %errorlevel% neq 0 ( + echo ❌ 依赖安装失败 + pause + exit /b 1 + ) + echo ✅ 依赖安装完成 + echo. +) + +echo 🚀 正在启动VHD选择服务器... +echo. +node server.js + +if %errorlevel% neq 0 ( + echo. + echo ❌ 服务器启动失败 + pause +) \ No newline at end of file diff --git a/vhdmonter_config.ini b/vhdmonter_config.ini new file mode 100644 index 0000000..a77e929 --- /dev/null +++ b/vhdmonter_config.ini @@ -0,0 +1,6 @@ +[Settings] +; 是否启用远程VHD选择功能 (true/false) +EnableRemoteSelection=false + +; 远程VHD选择服务的URL +BootImageSelectUrl=http://localhost:8080/api/boot-image-select \ No newline at end of file