feat: 添加远程VHD选择功能和USB设备支持
实现远程VHD选择服务集成,支持从配置文件中读取远程服务URL 添加NX_INS USB设备检测功能,支持从USB设备扫描和替换VHD文件 新增配置文件解析和HTTP客户端请求功能 优化VHD文件管理逻辑,支持关键词匹配和文件替换
This commit is contained in:
+54
-13
@@ -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<string> usbVhdFiles = new List<string>();
|
||||
|
||||
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)
|
||||
|
||||
+234
@@ -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<string> StatusChanged;
|
||||
public event Action<List<string>> 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<string> ScanUSBForVHDFiles(DriveInfo usbDrive)
|
||||
{
|
||||
StatusChanged?.Invoke($"正在扫描USB设备 {usbDrive.Name} 中的VHD文件...");
|
||||
var vhdFiles = new List<string>();
|
||||
|
||||
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<bool> ReplaceLocalVHDFiles(List<string> usbVhdFiles, List<string> 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<string, string> ReadConfig()
|
||||
{
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
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<string> 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<string> 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<List<string>> ScanForVHDFiles()
|
||||
{
|
||||
StatusChanged?.Invoke("正在扫描VHD文件...");
|
||||
|
||||
@@ -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
|
||||
@@ -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/
|
||||
@@ -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 <PID> /F # Windows
|
||||
kill -9 <PID> # 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
|
||||
```
|
||||
@@ -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"]
|
||||
@@ -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选择服务器!**
|
||||
@@ -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
|
||||
@@ -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 "🎉 部署完成!"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
[Settings]
|
||||
; 是否启用远程VHD选择功能 (true/false)
|
||||
EnableRemoteSelection=false
|
||||
|
||||
; 远程VHD选择服务的URL
|
||||
BootImageSelectUrl=http://localhost:8080/api/boot-image-select
|
||||
Reference in New Issue
Block a user