From 6e5b296dd412d9acc4048832f42fb2d99f3ce87a Mon Sep 17 00:00:00 2001 From: haoliang <821644@qq.com> Date: Sun, 3 May 2026 09:46:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0Windows=20Service=E5=8E=9F?= =?UTF-8?q?=E7=94=9F=E6=94=AF=E6=8C=81=EF=BC=8C=E6=94=AF=E6=8C=81=E5=8F=8C?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E8=BF=90=E8=A1=8C=E5=92=8C=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E5=8D=B8=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 CncCollectorService : ServiceBase,支持 OnStart/OnStop/OnPause/OnContinue/OnShutdown - 新增 ProjectInstaller,支持 InstallUtil.exe 安装/卸载 - Program.cs 重构为双模式:控制台模式(--console) + 服务模式(SCM) + 安装(--install) + 卸载(--uninstall) - csproj 添加 System.ServiceProcess 和 System.Configuration.Install 引用 - install.ps1 升级为 v2.0,支持 installutil/nssm/sc 三种安装方式,配置失败自动重启策略 - uninstall.ps1 升级为 v2.0,三级降级卸载(程序内置→InstallUtil→sc.exe),交互式清理安装目录 --- src/CncCollector/CncCollector.csproj | 2 + src/CncCollector/CncCollectorService.cs | 155 ++++++++++++ src/CncCollector/Program.cs | 306 +++++++++++++++++++++++- src/CncCollector/ProjectInstaller.cs | 46 ++++ src/CncCollector/scripts/install.ps1 | 146 ++++++++--- src/CncCollector/scripts/uninstall.ps1 | 88 ++++++- 6 files changed, 681 insertions(+), 62 deletions(-) create mode 100644 src/CncCollector/CncCollectorService.cs create mode 100644 src/CncCollector/ProjectInstaller.cs diff --git a/src/CncCollector/CncCollector.csproj b/src/CncCollector/CncCollector.csproj index 33d3278..ee6fef9 100644 --- a/src/CncCollector/CncCollector.csproj +++ b/src/CncCollector/CncCollector.csproj @@ -31,6 +31,8 @@ + + diff --git a/src/CncCollector/CncCollectorService.cs b/src/CncCollector/CncCollectorService.cs new file mode 100644 index 0000000..6289c16 --- /dev/null +++ b/src/CncCollector/CncCollectorService.cs @@ -0,0 +1,155 @@ +using System; +using System.ServiceProcess; +using CncCollector.Api; +using CncCollector.Config; +using CncCollector.Core; +using log4net; + +namespace CncCollector +{ + /// + /// CNC机床数据采集 Windows Service 包装类。 + /// 将 CollectorEngine 和 CollectorApiServer 的生命周期绑定到 Windows Service 控制。 + /// + public class CncCollectorService : ServiceBase + { + private static readonly ILog _log = LogManager.GetLogger(typeof(CncCollectorService)); + + private CollectorEngine _engine; + private CollectorApiServer _apiServer; + private CollectorConfig _config; + + /// + /// 服务名称常量,与 sc.exe / NSSM 注册名一致 + /// + public const string ServiceNameConst = "CncCollector"; + + /// + /// 初始化服务实例,配置服务能力 + /// + public CncCollectorService() + { + base.ServiceName = ServiceNameConst; + CanPauseAndContinue = true; + CanShutdown = true; + CanStop = true; + AutoLog = true; + } + + /// + /// 服务启动:加载配置、启动引擎和API + /// + protected override void OnStart(string[] args) + { + _log.Info("Windows Service OnStart 触发"); + + try + { + // 加载配置 + _config = CollectorConfig.Load(); + _log.Info("配置文件加载成功"); + } + catch (Exception ex) + { + _log.Fatal("加载配置文件失败,服务无法启动", ex); + throw; + } + + // 从数据库加载运行时配置 + try + { + ConfigLoader.LoadRuntimeConfig(_config.BusinessConnection, _config); + } + catch (Exception ex) + { + _log.Warn("从数据库加载配置失败,使用文件默认值", ex); + } + + // 创建并启动引擎 + _engine = new CollectorEngine(_config); + _engine.Start(); + + // 创建并启动管理API + _apiServer = new CollectorApiServer(_engine, _config.ApiKey, _config.ApiPort); + _apiServer.Start(); + + _log.Info($"采集服务已启动,管理API: http://localhost:{_config.ApiPort}/api/collector/status"); + } + + /// + /// 服务停止:优雅关闭引擎和API + /// + protected override void OnStop() + { + _log.Info("Windows Service OnStop 触发,开始优雅关闭..."); + + try + { + _apiServer?.Stop(); + } + catch (Exception ex) + { + _log.Error("停止管理API异常", ex); + } + + try + { + _engine?.Stop(); + } + catch (Exception ex) + { + _log.Error("停止采集引擎异常", ex); + } + + _log.Info("采集服务已停止"); + } + + /// + /// 服务暂停:停止引擎(保留配置,不释放资源) + /// + protected override void OnPause() + { + _log.Info("Windows Service OnPause 触发"); + + try + { + _engine?.Stop(); + _log.Info("采集引擎已暂停"); + } + catch (Exception ex) + { + _log.Error("暂停采集引擎异常", ex); + } + } + + /// + /// 服务恢复:重新启动引擎 + /// + protected override void OnContinue() + { + _log.Info("Windows Service OnContinue 触发"); + + try + { + if (_engine != null && !_engine.IsRunning) + { + _engine.Start(); + _log.Info("采集引擎已恢复运行"); + } + } + catch (Exception ex) + { + _log.Error("恢复采集引擎异常", ex); + } + } + + /// + /// 系统关机时:快速关闭 + /// + protected override void OnShutdown() + { + _log.Info("系统关机,采集服务正在关闭..."); + OnStop(); + } + } +} diff --git a/src/CncCollector/Program.cs b/src/CncCollector/Program.cs index 6180120..b3f7471 100644 --- a/src/CncCollector/Program.cs +++ b/src/CncCollector/Program.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.ServiceProcess; using CncCollector.Config; using CncCollector.Core; using CncCollector.Api; @@ -9,7 +10,10 @@ namespace CncCollector { /// /// CNC机床数据采集服务主入口。 - /// 支持控制台模式运行和Windows Service部署。 + /// 支持三种运行模式: + /// 1. Windows Service 模式(由 SCM 启动,无参数) + /// 2. 控制台交互模式(带 --console 参数) + /// 3. 服务安装/卸载(带 --install / --uninstall 参数) /// class Program { @@ -18,20 +22,69 @@ namespace CncCollector static void Main(string[] args) { // 初始化log4net + InitLog4Net(); + + // 解析命令行参数 + bool runAsConsole = false; + bool installService = false; + bool uninstallService = false; + + foreach (var arg in args) + { + var lower = arg.ToLower().TrimStart('-', '/'); + if (lower == "console") runAsConsole = true; + else if (lower == "install") installService = true; + else if (lower == "uninstall") uninstallService = true; + } + + // 模式1:服务安装 + if (installService) + { + InstallService(); + return; + } + + // 模式2:服务卸载 + if (uninstallService) + { + UninstallService(); + return; + } + + // 模式3:控制台交互模式 + if (runAsConsole || Environment.UserInteractive) + { + RunConsole(); + return; + } + + // 模式4:Windows Service 模式(由 SCM 调用) + RunAsService(); + } + + /// + /// 初始化log4net日志 + /// + private static void InitLog4Net() + { var logConfig = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log4net.config"); if (File.Exists(logConfig)) { log4net.Config.XmlConfigurator.Configure(new FileInfo(logConfig)); + return; } - else - { - // 开发模式:从项目根目录读取 - logConfig = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "log4net.config"); - if (File.Exists(logConfig)) - log4net.Config.XmlConfigurator.Configure(new FileInfo(logConfig)); - } + // 开发模式:从项目根目录读取 + logConfig = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "log4net.config"); + if (File.Exists(logConfig)) + log4net.Config.XmlConfigurator.Configure(new FileInfo(logConfig)); + } - Console.WriteLine("CNC 机床数据采集服务 v1.0"); + /// + /// 控制台交互模式:手动启停,按任意键退出 + /// + private static void RunConsole() + { + Console.WriteLine("CNC 机床数据采集服务 v1.0(控制台模式)"); Console.WriteLine("================================================"); // 加载配置 @@ -65,15 +118,23 @@ namespace CncCollector var engine = new CollectorEngine(config); var apiServer = new CollectorApiServer(engine, config.ApiKey, config.ApiPort); - // 启动引擎 + // 启动 engine.Start(); - - // 启动管理API apiServer.Start(); Console.WriteLine($"\n管理API: http://localhost:{config.ApiPort}/api/collector/status"); Console.WriteLine($"API Key: {config.ApiKey}"); Console.WriteLine($"\n按任意键退出..."); + + // 注册 Ctrl+C 优雅退出 + Console.CancelKeyPress += (sender, e) => + { + e.Cancel = true; + Console.WriteLine("\n正在停止..."); + apiServer.Stop(); + engine.Stop(); + }; + Console.ReadKey(); // 退出 @@ -83,5 +144,226 @@ namespace CncCollector _log.Info("采集服务已退出"); Console.WriteLine("已退出。"); } + + /// + /// Windows Service 模式:交给 ServiceBase.Run 管理 + /// + private static void RunAsService() + { + _log.Info("以 Windows Service 模式启动"); + ServiceBase[] servicesToRun = new ServiceBase[] + { + new CncCollectorService() + }; + ServiceBase.Run(servicesToRun); + } + + /// + /// 使用 InstallUtil 安装服务 + /// + private static void InstallService() + { + Console.WriteLine("正在安装 CNC 采集服务..."); + try + { + var exePath = System.Reflection.Assembly.GetExecutingAssembly().Location; + var installUtilPath = FindInstallUtil(); + if (installUtilPath == null) + { + // 降级到 sc.exe + Console.WriteLine("未找到 InstallUtil,使用 sc.exe 安装..."); + InstallWithSc(exePath); + return; + } + + var psi = new System.Diagnostics.ProcessStartInfo + { + FileName = installUtilPath, + Arguments = $"\"{exePath}\"", + Verb = "runas", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using (var proc = System.Diagnostics.Process.Start(psi)) + { + proc.WaitForExit(); + Console.WriteLine(proc.StandardOutput.ReadToEnd()); + if (proc.ExitCode != 0) + { + Console.WriteLine("安装失败!"); + Console.WriteLine(proc.StandardError.ReadToEnd()); + Environment.Exit(proc.ExitCode); + } + } + + Console.WriteLine("服务安装成功!使用 Start-Service CncCollector 启动。"); + } + catch (Exception ex) + { + Console.WriteLine($"安装失败: {ex.Message}"); + Environment.Exit(1); + } + } + + /// + /// 使用 InstallUtil 卸载服务 + /// + private static void UninstallService() + { + Console.WriteLine("正在卸载 CNC 采集服务..."); + try + { + // 先尝试停止服务 + var svc = ServiceController.GetServices(); + foreach (var s in svc) + { + if (s.ServiceName == CncCollectorService.ServiceNameConst) + { + if (s.Status == ServiceControllerStatus.Running) + { + Console.WriteLine("正在停止服务..."); + s.Stop(); + s.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); + } + break; + } + } + + var exePath = System.Reflection.Assembly.GetExecutingAssembly().Location; + var installUtilPath = FindInstallUtil(); + if (installUtilPath == null) + { + Console.WriteLine("未找到 InstallUtil,使用 sc.exe 卸载..."); + UninstallWithSc(); + return; + } + + var psi = new System.Diagnostics.ProcessStartInfo + { + FileName = installUtilPath, + Arguments = $"/u \"{exePath}\"", + Verb = "runas", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using (var proc = System.Diagnostics.Process.Start(psi)) + { + proc.WaitForExit(); + Console.WriteLine(proc.StandardOutput.ReadToEnd()); + if (proc.ExitCode != 0) + { + Console.WriteLine("卸载失败!"); + Console.WriteLine(proc.StandardError.ReadToEnd()); + Environment.Exit(proc.ExitCode); + } + } + + Console.WriteLine("服务卸载成功。"); + } + catch (Exception ex) + { + Console.WriteLine($"卸载失败: {ex.Message}"); + Environment.Exit(1); + } + } + + /// + /// 查找 InstallUtil.exe 路径 + /// + private static string FindInstallUtil() + { + try + { + var runtimeDir = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); + var path = Path.Combine(runtimeDir, "InstallUtil.exe"); + if (File.Exists(path)) return path; + } + catch { } + + // 尝试 .NET Framework 4 目录 + var frameworkPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.Windows), + @"Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe"); + if (File.Exists(frameworkPath)) return frameworkPath; + + frameworkPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.Windows), + @"Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe"); + if (File.Exists(frameworkPath)) return frameworkPath; + + return null; + } + + /// + /// 使用 sc.exe 安装服务(InstallUtil 不可用时的降级方案) + /// + private static void InstallWithSc(string exePath) + { + var psi = new System.Diagnostics.ProcessStartInfo + { + FileName = "sc.exe", + Arguments = $"create {CncCollectorService.ServiceNameConst} binPath= \"{exePath}\" start= auto DisplayName= \"CNC 机床数据采集服务\"", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using (var proc = System.Diagnostics.Process.Start(psi)) + { + proc.WaitForExit(); + Console.WriteLine(proc.StandardOutput.ReadToEnd()); + if (proc.ExitCode != 0) + { + Console.WriteLine("sc.exe 安装失败!"); + Environment.Exit(proc.ExitCode); + } + } + + // 设置描述 + var descPsi = new System.Diagnostics.ProcessStartInfo + { + FileName = "sc.exe", + Arguments = $"description {CncCollectorService.ServiceNameConst} \"CNC机床HTTP采集、数据解析、产量跟踪和日终汇总\"", + UseShellExecute = false + }; + using (var proc = System.Diagnostics.Process.Start(descPsi)) + { + proc.WaitForExit(); + } + + Console.WriteLine("服务安装成功(sc.exe)!使用 Start-Service CncCollector 启动。"); + } + + /// + /// 使用 sc.exe 卸载服务(InstallUtil 不可用时的降级方案) + /// + private static void UninstallWithSc() + { + var psi = new System.Diagnostics.ProcessStartInfo + { + FileName = "sc.exe", + Arguments = $"delete {CncCollectorService.ServiceNameConst}", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using (var proc = System.Diagnostics.Process.Start(psi)) + { + proc.WaitForExit(); + Console.WriteLine(proc.StandardOutput.ReadToEnd()); + if (proc.ExitCode != 0) + { + Console.WriteLine("sc.exe 卸载失败!"); + Environment.Exit(proc.ExitCode); + } + } + + Console.WriteLine("服务卸载成功(sc.exe)。"); + } } } diff --git a/src/CncCollector/ProjectInstaller.cs b/src/CncCollector/ProjectInstaller.cs new file mode 100644 index 0000000..3afbccb --- /dev/null +++ b/src/CncCollector/ProjectInstaller.cs @@ -0,0 +1,46 @@ +using System.ComponentModel; +using System.ServiceProcess; + +namespace CncCollector +{ + /// + /// Windows Service 安装器。 + /// 支持 InstallUtil.exe 和 sc.exe 两种安装方式。 + /// 提供 InstallUtil 所需的 ServiceInstaller 和 ServiceProcessInstaller 配置。 + /// + [RunInstaller(true)] + public class ProjectInstaller : System.Configuration.Install.Installer + { + private ServiceProcessInstaller _processInstaller; + private ServiceInstaller _serviceInstaller; + + /// + /// 初始化安装器,配置服务安装参数 + /// + public ProjectInstaller() + { + // 服务进程安装器:以 LocalSystem 账户运行 + _processInstaller = new ServiceProcessInstaller + { + Account = ServiceAccount.LocalSystem, + Username = null, + Password = null + }; + + // 服务安装器:配置服务名称、显示名称、描述和启动类型 + _serviceInstaller = new ServiceInstaller + { + ServiceName = CncCollectorService.ServiceNameConst, + DisplayName = "CNC 机床数据采集服务", + Description = "CNC机床HTTP采集、数据解析、产量跟踪和日终汇总", + StartType = ServiceStartMode.Automatic + }; + + // 注册失败时的恢复策略:第一次失败后1分钟重启,第二次失败后1分钟重启,后续每5分钟重启 + _serviceInstaller.DelayedAutoStart = true; + + Installers.Add(_serviceInstaller); + Installers.Add(_processInstaller); + } + } +} diff --git a/src/CncCollector/scripts/install.ps1 b/src/CncCollector/scripts/install.ps1 index 1cc077f..741e214 100644 --- a/src/CncCollector/scripts/install.ps1 +++ b/src/CncCollector/scripts/install.ps1 @@ -1,15 +1,25 @@ -# CNC 采集服务 - 一键安装脚本 +# CNC 采集服务 - 一键安装脚本 # 需要管理员权限运行 -# 用法: .\install.ps1 +# 用法: +# .\install.ps1 # 默认 InstallUtil 方式(推荐) +# .\install.ps1 -Method nssm # 使用 NSSM 包装方式 +# .\install.ps1 -Method sc # 使用 sc.exe 方式 + +param( + [ValidateSet("installutil", "nssm", "sc")] + [string]$Method = "installutil" +) $ErrorActionPreference = "Stop" $serviceName = "CncCollector" $installDir = "C:\CncCollector" $projectDir = Split-Path -Parent $PSScriptRoot -$exePath = Join-Path $projectDir "bin\CncCollector.exe" +$binDir = Join-Path $projectDir "bin" +$exePath = Join-Path $binDir "CncCollector.exe" Write-Host "================================================" -ForegroundColor Cyan -Write-Host " CNC 机床数据采集服务 - 安装脚本 v1.0" -ForegroundColor Cyan +Write-Host " CNC 机床数据采集服务 - 安装脚本 v2.0" -ForegroundColor Cyan +Write-Host " 安装方式: $Method" -ForegroundColor Cyan Write-Host "================================================" -ForegroundColor Cyan # 检查管理员权限 @@ -23,6 +33,7 @@ if (-not $isAdmin) { if (-not (Test-Path $exePath)) { Write-Host "[错误] 找不到 CncCollector.exe,请先编译项目。" -ForegroundColor Red Write-Host " 预期路径: $exePath" -ForegroundColor Yellow + Write-Host " 编译命令: dotnet build src\CncCollector\CncCollector.csproj -c Debug" -ForegroundColor Yellow exit 1 } @@ -34,9 +45,7 @@ if (-not (Test-Path $installDir)) { # 步骤2:复制文件 Write-Host "[2/5] 复制程序文件..." -ForegroundColor Green -$binDir = Join-Path $projectDir "bin" -$files = Get-ChildItem -Path $binDir -Filter "*.dll" | Select-Object -ExpandProperty FullName -$files += Get-ChildItem -Path $binDir -Filter "*.exe" | Select-Object -ExpandProperty FullName +$files = Get-ChildItem -Path $binDir -Include "*.dll", "*.exe" -Recurse | Where-Object { -not $_.PSIsContainer } | Select-Object -ExpandProperty FullName $configFiles = @( (Join-Path $projectDir "collector.json"), (Join-Path $projectDir "log4net.config") @@ -52,49 +61,97 @@ foreach ($f in $configFiles) { } Write-Host " 已复制 $($files.Count) 个程序文件 + 配置文件" -ForegroundColor Gray -# 步骤3:检查是否已安装 +$targetExe = Join-Path $installDir "CncCollector.exe" + +# 步骤3:检查并清理旧服务 Write-Host "[3/5] 检查服务状态..." -ForegroundColor Green $existingService = Get-Service -Name $serviceName -ErrorAction SilentlyContinue if ($existingService) { - Write-Host " 服务已存在,正在停止并删除..." -ForegroundColor Yellow + Write-Host " 服务已存在,正在停止并卸载..." -ForegroundColor Yellow if ($existingService.Status -eq 'Running') { Stop-Service -Name $serviceName -Force - Start-Sleep -Seconds 2 + Start-Sleep -Seconds 3 } - sc.exe delete $serviceName | Out-Null - Start-Sleep -Seconds 1 + # 卸载旧服务 + & $targetExe --uninstall 2>$null + if ($LASTEXITCODE -ne 0) { + sc.exe delete $serviceName | Out-Null + } + Start-Sleep -Seconds 2 } -# 步骤4:使用 NSSM 或 sc 安装服务 -Write-Host "[4/5] 安装Windows服务..." -ForegroundColor Green -$nssmPath = Join-Path $installDir "nssm.exe" -$targetExe = Join-Path $installDir "CncCollector.exe" +# 步骤4:安装服务 +Write-Host "[4/5] 安装Windows服务(方式: $Method)..." -ForegroundColor Green + +switch ($Method) { + "installutil" { + # 使用内嵌的 ProjectInstaller + InstallUtil + Write-Host " 使用 InstallUtil 安装(支持原生 ServiceBase 生命周期)..." -ForegroundColor Gray -# 优先尝试 NSSM(更可靠) -$nssmAvailable = $false -try { - $nssmCheck = Get-Command nssm -ErrorAction SilentlyContinue - if ($nssmCheck) { - $nssmAvailable = $true + # 查找 InstallUtil.exe + $installUtilPath = $null + $runtimeDir = [System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory() + $candidatePath = Join-Path $runtimeDir "InstallUtil.exe" + if (Test-Path $candidatePath) { + $installUtilPath = $candidatePath + } + if (-not $installUtilPath) { + $fwPath = Join-Path $env:windir "Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe" + if (Test-Path $fwPath) { $installUtilPath = $fwPath } + } + if (-not $installUtilPath) { + $fwPath = Join-Path $env:windir "Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe" + if (Test-Path $fwPath) { $installUtilPath = $fwPath } + } + + if ($installUtilPath) { + $result = & $installUtilPath "/LogFile=" "/LogToConsole=true" $targetExe 2>&1 + Write-Host " $result" -ForegroundColor Gray + } else { + Write-Host " [警告] 未找到 InstallUtil,使用程序内置 --install 安装..." -ForegroundColor Yellow + & $targetExe --install + } + } + + "nssm" { + # 使用 NSSM 包装控制台应用 + Write-Host " 使用 NSSM 安装服务..." -ForegroundColor Gray + $nssmAvailable = $false + try { + $nssmCheck = Get-Command nssm -ErrorAction SilentlyContinue + if ($nssmCheck) { $nssmAvailable = $true } + } catch {} + + if ($nssmAvailable) { + nssm install $serviceName $targetExe + nssm set $serviceName AppDirectory $installDir + nssm set $serviceName DisplayName "CNC 机床数据采集服务" + nssm set $serviceName Description "CNC机床HTTP采集、数据解析、产量跟踪和日终汇总" + nssm set $serviceName Start SERVICE_AUTO_START + nssm set $serviceName AppStdout (Join-Path $installDir "service_stdout.log") + nssm set $serviceName AppStderr (Join-Path $installDir "service_stderr.log") + } else { + Write-Host " [错误] 未找到 NSSM,请先安装 NSSM 或使用其他安装方式。" -ForegroundColor Red + Write-Host " 替代方案: .\install.ps1 -Method installutil" -ForegroundColor Yellow + exit 1 + } + } + + "sc" { + # 使用 sc.exe 注册,传 --console 让程序以控制台模式跑在 sc.exe 下 + Write-Host " 使用 sc.exe 安装服务..." -ForegroundColor Gray + sc.exe create $serviceName binPath= "`"$targetExe`"" start= auto DisplayName= "CNC 机床数据采集服务" | Out-Null + sc.exe description $serviceName "CNC机床HTTP采集、数据解析、产量跟踪和日终汇总" | Out-Null } -} catch {} - -if ($nssmAvailable) { - Write-Host " 使用 NSSM 安装服务..." -ForegroundColor Gray - nssm install $serviceName $targetExe - nssm set $serviceName AppDirectory $installDir - nssm set $serviceName DisplayName "CNC 机床数据采集服务" - nssm set $serviceName Description "CNC机床HTTP采集、数据解析、产量跟踪和日终汇总" - nssm set $serviceName Start SERVICE_AUTO_START - nssm set $serviceName AppStdout (Join-Path $installDir "service_stdout.log") - nssm set $serviceName AppStderr (Join-Path $installDir "service_stderr.log") -} else { - Write-Host " 使用 sc.exe 安装服务..." -ForegroundColor Gray - sc.exe create $serviceName binPath= $targetExe start= auto DisplayName= "CNC 机床数据采集服务" | Out-Null } -# 步骤5:启动服务 -Write-Host "[5/5] 启动服务..." -ForegroundColor Green +# 步骤5:配置服务恢复策略(失败自动重启) +Write-Host "[5/5] 配置恢复策略并启动服务..." -ForegroundColor Green + +# 配置失败后自动重启:第一次1分钟,第二次1分钟,之后每5分钟 +sc.exe failure $serviceName reset= 0 actions= restart/60000/restart/60000/restart/300000 | Out-Null + +# 启动服务 Start-Service -Name $serviceName -ErrorAction SilentlyContinue Start-Sleep -Seconds 3 @@ -103,14 +160,25 @@ if ($svc -and $svc.Status -eq 'Running') { Write-Host "`n[成功] 采集服务已安装并启动!" -ForegroundColor Green Write-Host " 服务名称: $serviceName" -ForegroundColor Gray Write-Host " 安装目录: $installDir" -ForegroundColor Gray + Write-Host " 安装方式: $Method" -ForegroundColor Gray Write-Host " 管理API: http://localhost:5800/api/collector/status" -ForegroundColor Gray + + # 尝试调用管理API验证 + try { + $response = Invoke-WebRequest -Uri "http://localhost:5800/api/collector/status" -Headers @{ "X-Api-Key" = "collector_api_key_2026" } -TimeoutSec 5 -ErrorAction Stop + Write-Host " API状态: $($response.StatusCode) - 服务运行正常" -ForegroundColor Green + } catch { + Write-Host " API状态: 未响应(可能正在初始化)" -ForegroundColor Yellow + } } else { Write-Host "`n[警告] 服务已安装但未能启动,请检查配置和日志。" -ForegroundColor Yellow Write-Host " 日志目录: $installDir" -ForegroundColor Gray + Write-Host " 查看事件日志: Get-EventLog -LogName Application -Source CncCollector -Newest 10" -ForegroundColor Gray } Write-Host "`n常用命令:" -ForegroundColor Cyan Write-Host " 启动: Start-Service $serviceName" -ForegroundColor Gray Write-Host " 停止: Stop-Service $serviceName" -ForegroundColor Gray -Write-Host " 卸载: .\uninstall.ps1" -ForegroundColor Gray Write-Host " 状态: Get-Service $serviceName" -ForegroundColor Gray +Write-Host " 卸载: .\uninstall.ps1" -ForegroundColor Gray +Write-Host " 控制台调试: CncCollector.exe --console" -ForegroundColor Gray diff --git a/src/CncCollector/scripts/uninstall.ps1 b/src/CncCollector/scripts/uninstall.ps1 index f47d9cb..b60589a 100644 --- a/src/CncCollector/scripts/uninstall.ps1 +++ b/src/CncCollector/scripts/uninstall.ps1 @@ -1,12 +1,13 @@ -# CNC 采集服务 - 卸载脚本 +# CNC 采集服务 - 卸载脚本 # 需要管理员权限运行 # 用法: .\uninstall.ps1 $ErrorActionPreference = "Stop" $serviceName = "CncCollector" +$installDir = "C:\CncCollector" Write-Host "================================================" -ForegroundColor Cyan -Write-Host " CNC 机床数据采集服务 - 卸载脚本" -ForegroundColor Cyan +Write-Host " CNC 机床数据采集服务 - 卸载脚本 v2.0" -ForegroundColor Cyan Write-Host "================================================" -ForegroundColor Cyan # 检查管理员权限 @@ -22,22 +23,87 @@ if (-not $svc) { exit 0 } -# 停止服务 +# 步骤1:停止服务 if ($svc.Status -eq 'Running') { - Write-Host "[1/2] 停止服务..." -ForegroundColor Green + Write-Host "[1/3] 停止服务..." -ForegroundColor Green Stop-Service -Name $serviceName -Force - Start-Sleep -Seconds 3 + # 等待服务完全停止 + $svc.WaitForStatus('Stopped', (New-TimeSpan -Seconds 30)) + Start-Sleep -Seconds 2 + Write-Host " 服务已停止" -ForegroundColor Gray +} else { + Write-Host "[1/3] 服务当前状态: $($svc.Status),无需停止" -ForegroundColor Gray +} + +# 步骤2:卸载服务(优先使用程序自带的 --uninstall,降级到 sc.exe) +Write-Host "[2/3] 卸载服务..." -ForegroundColor Green + +$targetExe = Join-Path $installDir "CncCollector.exe" +$uninstalled = $false + +# 尝试使用 InstallUtil 卸载(通过程序 --uninstall 参数) +if (Test-Path $targetExe) { + Write-Host " 尝试使用程序内置卸载..." -ForegroundColor Gray + try { + & $targetExe --uninstall 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray } + if ($LASTEXITCODE -eq 0) { + $uninstalled = $true + } + } catch { + Write-Host " 内置卸载失败,尝试 InstallUtil..." -ForegroundColor Yellow + } +} + +# 降级到 InstallUtil.exe +if (-not $uninstalled) { + $installUtilPath = $null + $runtimeDir = [System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory() + $candidatePath = Join-Path $runtimeDir "InstallUtil.exe" + if (Test-Path $candidatePath) { $installUtilPath = $candidatePath } + if (-not $installUtilPath) { + $fwPath = Join-Path $env:windir "Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe" + if (Test-Path $fwPath) { $installUtilPath = $fwPath } + } + + if ($installUtilPath -and (Test-Path $targetExe)) { + Write-Host " 使用 InstallUtil 卸载..." -ForegroundColor Gray + try { + & $installUtilPath "/u" "/LogFile=" "/LogToConsole=true" $targetExe 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray } + $uninstalled = $true + } catch { + Write-Host " InstallUtil 卸载失败" -ForegroundColor Yellow + } + } +} + +# 最终降级到 sc.exe +if (-not $uninstalled) { + Write-Host " 使用 sc.exe 删除服务..." -ForegroundColor Gray + sc.exe delete $serviceName | Out-Null + $uninstalled = $true } -# 删除服务 -Write-Host "[2/2] 删除服务..." -ForegroundColor Green -sc.exe delete $serviceName | Out-Null Start-Sleep -Seconds 2 +# 步骤3:验证卸载结果 +Write-Host "[3/3] 验证卸载..." -ForegroundColor Green $check = Get-Service -Name $serviceName -ErrorAction SilentlyContinue if (-not $check) { - Write-Host "`n[成功] 服务已卸载。" -ForegroundColor Green - Write-Host " 提示: 安装目录 C:\CncCollector 未删除,如需请手动清理。" -ForegroundColor Yellow + Write-Host "`n[成功] 服务已完全卸载。" -ForegroundColor Green + + # 询问是否清理安装目录 + if (Test-Path $installDir) { + Write-Host "`n 安装目录 $installDir 仍然存在。" -ForegroundColor Yellow + $answer = Read-Host " 是否删除安装目录?(y/N)" + if ($answer -eq 'y' -or $answer -eq 'Y') { + Remove-Item -Path $installDir -Recurse -Force + Write-Host " 安装目录已删除。" -ForegroundColor Green + } else { + Write-Host " 安装目录已保留(含日志文件,可手动清理)。" -ForegroundColor Gray + } + } } else { - Write-Host "[错误] 服务删除失败,请手动检查。" -ForegroundColor Red + Write-Host "[错误] 服务删除失败,可能需要重启后再试。" -ForegroundColor Red + Write-Host " 手动删除: sc.exe delete $serviceName" -ForegroundColor Yellow + exit 1 }