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
}