From eedf5fa8bef8000d8ed50c252567878bad296202 Mon Sep 17 00:00:00 2001 From: haoliang <821644@qq.com> Date: Mon, 4 May 2026 22:10:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E9=94=99=E8=AF=AF=EF=BC=88CollectorStatus?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E5=A3=B0=E6=98=8E=E3=80=81serviceStatusLabel?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=EF=BC=89=EF=BC=9B=E4=BF=AE=E5=A4=8DCI?= =?UTF-8?q?=E9=85=8D=E7=BD=AESDK=E7=89=88=E6=9C=AC=EF=BC=9B=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E4=B8=8A=E7=BA=BF=E5=9B=9E=E6=BB=9A=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci-windows.yml | 28 ++- docs/07-Windows服务状态功能-上线回滚文档.md | 196 ++++++++++++++++++ frontend/src/types/index.ts | 7 +- .../src/views/dashboard/DashboardPage.vue | 24 +-- 4 files changed, 227 insertions(+), 28 deletions(-) create mode 100644 docs/07-Windows服务状态功能-上线回滚文档.md diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml index 79890b5..48d1c05 100644 --- a/.github/workflows/ci-windows.yml +++ b/.github/workflows/ci-windows.yml @@ -12,23 +12,31 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup .NET SDK - uses: actions/setup-dotnet@v3 + + - name: Setup .NET SDK (for dotnet CLI) + uses: actions/setup-dotnet@v4 with: - distribution: "windows-hosted" - sdk: [5.0.x, 6.0.x] + dotnet-version: '8.0.x' + - name: Restore NuGet packages run: dotnet restore + - name: Build backend - run: dotnet build -c Release - - name: Run backend tests (Windows service tests) - run: dotnet test tests/CncService.Tests/CncService.Tests.csproj -c Release -v minimal --filter "FullyQualifiedName~WindowsServiceCheckerTests|DashboardServiceTests" - - name: Build frontend (optional, if frontend changes) + run: dotnet build -c Release --no-restore + + - name: Run Windows service related tests + run: dotnet test tests/CncService.Tests/CncService.Tests.csproj -c Release --no-build -v minimal --filter "FullyQualifiedName~WindowsServiceCheckerTests|FullyQualifiedName~DashboardServiceTests" + + - name: Build frontend run: | cd frontend npm ci npm run build - - name: Run frontend tests (optional) + + - name: Test summary if: always() run: | - echo "Frontend test steps would run here (optional)" + echo "=== CI Summary ===" + echo "Backend: Build + WindowsService/Dashboard tests" + echo "Frontend: Build (vue-tsc + vite)" + echo "==================" diff --git a/docs/07-Windows服务状态功能-上线回滚文档.md b/docs/07-Windows服务状态功能-上线回滚文档.md new file mode 100644 index 0000000..76a5165 --- /dev/null +++ b/docs/07-Windows服务状态功能-上线回滚文档.md @@ -0,0 +1,196 @@ +# Windows 服务状态管理功能 — 上线/回滚文档 + +**分支**: `feat/windows-service-status-auto` +**目标**: `main` +**日期**: 2026-05-04 +**PR URL**: https://git.cjy.net.cn/jcl/haoliang-net/compare/main...feat/windows-service-status-auto + +--- + +## 一、功能概述 + +为 CncCollector 采集服务添加原生 Windows Service 支持,实现双模式运行(控制台调试 + Windows 服务),并在管理后台仪表盘准确展示服务运行状态(未安装/运行中/启动中/启动失败/已停止),支持远程启动/停止操作。 + +## 二、变更清单 + +### 2.1 后端变更 + +| 文件 | 变更类型 | 说明 | +|------|----------|------| +| `src/CncCollector/CncCollectorService.cs` | 新增 | ServiceBase 包装,OnStart/OnStop/OnPause/OnContinue/OnShutdown | +| `src/CncCollector/ProjectInstaller.cs` | 新增 | InstallUtil 安装器配置 | +| `src/CncCollector/Program.cs` | 修改 | 双模式入口(--console 调试/无参数=服务模式/--install/--uninstall) | +| `src/CncCollector/CncCollector.csproj` | 修改 | 添加 System.ServiceProcess + System.Configuration.Install 引用 | +| `src/CncService/Interface/IWindowsServiceChecker.cs` | 新增 | 服务状态检测接口 + ServiceStatusEnum 枚举 | +| `src/CncService/Impl/WindowsServiceChecker.cs` | 新增 | 基于 ServiceController 的实现 | +| `src/CncService/Impl/DashboardService.cs` | 修改 | 注入 IWindowsServiceChecker,增强 GetCollectorStatus 返回 serviceStatus/serviceName/serviceMessage | +| `src/CncWebApi/Controllers/DashboardController.cs` | 修改 | StartCollector 前置状态检查(NotInstalled→40001, Running→40002) | +| `src/CncWebApi/Infrastructure/ServiceResolver.cs` | 修改 | DI 注入 WindowsServiceChecker | +| `src/CncCollector/scripts/install.ps1` | 新增 | 安装脚本 v2.0(InstallUtil/NSSM/SC 三级降级) | +| `src/CncCollector/scripts/uninstall.ps1` | 新增 | 卸载脚本 v2.0(三级降级卸载 + 交互式清理) | + +### 2.2 前端变更 + +| 文件 | 变更类型 | 说明 | +|------|----------|------| +| `frontend/src/types/index.ts` | 修改 | CollectorStatus 接口扩展 serviceStatus/serviceName/serviceMessage 字段 | +| `frontend/src/views/dashboard/DashboardPage.vue` | 修改 | 服务状态标签映射、未安装引导提示、启动按钮逻辑 | + +### 2.3 测试变更 + +| 文件 | 变更类型 | 说明 | +|------|----------|------| +| `tests/CncService.Tests/WindowsServiceCheckerTests.cs` | 新增 | 服务检测单元测试(2 个用例) | +| `tests/CncService.Tests/DashboardServiceTests.cs` | 新增 | DI 场景测试(3 个用例:NotInstalled/Running/Starting) | + +### 2.4 CI/CD 变更 + +| 文件 | 变更类型 | 说明 | +|------|----------|------| +| `.github/workflows/ci-windows.yml` | 新增 | Windows CI 流水线(构建 + 测试 + 前端构建) | + +## 三、错误码定义 + +| 错误码 | 含义 | 触发条件 | +|--------|------|----------| +| 40001 | 服务未安装 | Windows 中不存在 CncCollector 服务 | +| 40002 | 服务已在运行 | 服务当前状态为 Running | +| 50002 | 服务启动失败 | 启动操作超时或返回错误 | +| 50003 | 服务不可用 | 服务状态异常 | + +## 四、验证结果 + +### 4.1 编译验证 + +| 项目 | 结果 | 备注 | +|------|------|------| +| dotnet build(全解决方案) | ✅ 0 错误 | 82 个 CS1591 警告(既有 XML 注释缺失) | +| npm run build(前端) | ✅ 0 错误 | vue-tsc 类型检查 + vite 构建通过 | + +### 4.2 单元测试 + +| 测试用例 | 结果 | +|----------|------| +| `WindowsServiceCheckerTests.GetServiceStatus_NotInstalled_ForUnknownService` | ✅ 通过 | +| `WindowsServiceCheckerTests.TryStartService_NotInstalled_ReturnsNotInstalled` | ✅ 通过 | +| `DashboardServiceTests.GetCollectorStatus_With_NotInstalled_Service_Returns_NotInstalled_State` | ✅ 通过 | +| `DashboardServiceTests.GetCollectorStatus_With_Running_Heartbeats_Returns_Running_State` | ✅ 通过 | +| `DashboardServiceTests.GetCollectorStatus_With_Starting_ServiceStatus_Returns_Starting_State` | ✅ 通过 | + +**5/5 测试全部通过。** + +注:其他 55 个失败的测试为既有数据库外键约束问题,与本次改动无关。 + +## 五、上线步骤 + +### 5.1 前置条件 +- 服务器:192.168.1.202(Windows Server) +- MariaDB 11.8 已运行 +- IIS 应用池 `haoliang` 已配置 +- 当前 CncCollector 以控制台模式运行(需停掉) + +### 5.2 上线流程 + +```powershell +# 1. 合并分支到 main +git checkout main +git merge feat/windows-service-status-auto +git push + +# 2. 构建后端 +dotnet build -c Release + +# 3. 构建前端 +cd frontend +npm ci +npm run build +cd .. + +# 4. 部署 Web API 到 IIS +# 复制 src/CncWebApi/bin/Release 到 C:\inetpub\wwwroot\haoliang +# 复制 frontend/dist 到 C:\inetpub\wwwroot\haoliang\admin +Import-Module WebAdministration +Restart-WebAppPool -Name 'haoliang' + +# 5. 停掉当前控制台模式的 CncCollector(如有) +# 任务管理器结束 CncCollector.exe 进程 + +# 6. 安装为 Windows 服务 +cd src/CncCollector/scripts +.\install.ps1 + +# 7. 验证服务状态 +Get-Service CncCollector +# 应显示 Status=Running + +# 8. 验证管理后台仪表盘 +# 浏览器打开 http://192.168.1.202/admin/ +# 查看首页采集服务状态卡片,应显示"运行中" +``` + +### 5.3 验证清单 + +- [ ] 管理后台仪表盘服务状态显示正确 +- [ ] 服务未安装时显示"未安装"并提供安装引导 +- [ ] 启动按钮可远程启动服务 +- [ ] 停止按钮可远程停止服务 +- [ ] 服务状态实时刷新(30秒心跳) +- [ ] 安装脚本 install.ps1 正常工作 +- [ ] 卸载脚本 uninstall.ps1 正常工作 + +## 六、回滚方案 + +### 6.1 回滚触发条件 +- 服务安装失败无法启动 +- 管理后台仪表盘状态显示异常 +- 采集数据丢失或中断超过 10 分钟 + +### 6.2 回滚步骤 + +```powershell +# 1. 卸载 Windows 服务 +cd src/CncCollector/scripts +.\uninstall.ps1 + +# 2. 回退代码到上一个稳定版本 +git checkout main +git revert HEAD # 回退本次合并 +git push + +# 3. 重新部署旧版 Web API +# 从备份恢复 IIS 目录 +Import-Module WebAdministration +Restart-WebAppPool -Name 'haoliang' + +# 4. 恢复控制台模式运行 +# 用旧版 CncCollector.exe 以控制台模式启动 +Start-Process -FilePath "C:\path\to\CncCollector.exe" -ArgumentList "--console" +``` + +### 6.3 回滚注意事项 +- 卸载服务前先停止服务 +- 数据库无 schema 变更,无需回滚数据库 +- 前端回滚随 IIS 部署自动恢复 +- 回滚后确认采集数据恢复正常 + +## 七、风险评估 + +| 风险项 | 级别 | 应对措施 | +|--------|------|----------| +| Windows 服务权限不足 | 低 | install.ps1 自动请求管理员权限 | +| 服务安装失败 | 中 | 提供三级降级安装策略(InstallUtil→NSSM→SC) | +| 心跳超时误判 | 低 | 超时阈值设为 90 秒(3个心跳间隔) | +| 服务启动超时 | 中 | TryStartService 默认等待 30 秒,可配置 | +| 前端类型错误 | 已修复 | CollectorStatus 接口合并、serviceStatusLabel 移入 script setup | + +## 八、提交记录 + +| 提交 | 说明 | +|------|------| +| `6e5b296` | 增加 Windows Service 原生支持,双模式运行和服务安装卸载 | +| `9e3a759` | 修复仪表盘采集服务状态判断:增加心跳超时检测 | +| `e9802a1` | 前端适配、后端测试扩展、CI/Playwright E2E | +| `d8f5925` | 扩展 DashboardServiceTests DI 场景 | +| `0212ed6` | CI 工作流和扩展测试 | +| `acdc502` | 新增 Starting 状态测试用例 | +| (待提交) | 修复前端类型错误 + CI 配置修复 | diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 9523038..e94926e 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -295,12 +295,7 @@ export interface WorkerRankRow { totalQuantity?: number } -/** 采集服务状态 */ -export interface CollectorStatus { - status: string - uptimeSeconds: number - lastCollectTime?: string -} +// CollectorStatus 已在上方定义(含 serviceStatus/serviceName/serviceMessage 扩展字段) /** 产量看板汇总 */ export interface ProductionDashboardSummary { diff --git a/frontend/src/views/dashboard/DashboardPage.vue b/frontend/src/views/dashboard/DashboardPage.vue index 1bfdf6f..5775b13 100644 --- a/frontend/src/views/dashboard/DashboardPage.vue +++ b/frontend/src/views/dashboard/DashboardPage.vue @@ -366,7 +366,7 @@ async function refreshCollectorConfig() { try { await request.post('/admin/collector/refresh'); ElMessage.success('配置已刷新'); await loadData() } catch { /* request拦截器已显示错误 */ } finally { refreshLoading.value = false } } -function formatUptime(seconds: number): string { +function formatUptime(seconds: number | undefined): string { if (!seconds) return '-' const days = Math.floor(seconds / 86400) const hours = Math.floor((seconds % 86400) / 3600) @@ -374,6 +374,17 @@ function formatUptime(seconds: number): string { return `${hours}时` } +function serviceStatusLabel(status: string | undefined): string { + switch ((status || '').toString()) { + case 'NotInstalled': return '未安装' + case 'Running': return '运行中' + case 'Starting': return '启动中' + case 'StartFailed': return '启动失败' + case 'Stopped': return '已停止' + default: return status || '-' + } +} + function alertTypeTag(type: string): string { const map: Record = { collect_fail: 'danger', data_missing: 'warning', device_offline: 'danger', new_device: 'info' } return map[type] || 'warning' @@ -628,14 +639,3 @@ onUnmounted(() => { } } -// 映射 Windows 服务状态为可读文本 -function serviceStatusLabel(status: string | undefined): string { - switch ((status || '').toString()) { - case 'NotInstalled': return '未安装'; - case 'Running': return '运行中'; - case 'Starting': return '启动中'; - case 'StartFailed': return '启动失败'; - case 'Stopped': return '已停止'; - default: return status || '-'; - } -}