现代桌面应用型IT自动化运维系统的设计与实现

随着企业IT基础设施规模不断扩大和复杂度不断提高,传统的手动运维方式已无法满足现代企业的需求。构建一个高效、稳定、易用的自动化运维平台成为企业IT部门的迫切需求。本章将详细介绍如何设计和实现一个基于C/S架构的桌面版自动化运维平台,该平台能够提供丰富的功能,帮助IT管理员高效管理和维护企业IT基础设施。

16.1 平台功能介绍

桌面版C/S自动化运维平台是一款面向中小型企业IT部门的运维管理工具,通过客户端和服务器的架构,为IT管理员提供全面的基础设施管理和自动化运维能力。本节将详细介绍平台的核心功能模块和主要特点。

核心功能模块

服务器资产管理

服务器资产管理模块是运维平台的基础功能,负责管理企业内所有服务器的基本信息:

  • 基本信息管理:记录服务器的品牌、型号、序列号、购买日期、保修期等基本信息
  • 配置信息管理:记录服务器的CPU、内存、硬盘、网卡等硬件配置信息
  • 系统信息管理:记录服务器的操作系统类型、版本、安装日期等信息
  • 网络信息管理:记录服务器的IP地址、MAC地址、网络区域等信息
  • 责任人管理:记录服务器的管理员、使用部门等信息
  • 分类管理:支持按照应用、环境、部门等多种维度对服务器进行分类
监控告警系统

监控告警系统负责实时监控服务器的运行状态,并在发现异常时及时通知管理员:

  • 性能监控:监控CPU使用率、内存使用率、磁盘使用率、网络流量等性能指标
  • 服务监控:监控关键服务的运行状态,如数据库服务、Web服务、应用服务等
  • 日志监控:收集和分析系统日志、应用日志,检测异常情况
  • 阈值设置:支持为各类监控项设置告警阈值
  • 告警通知:支持邮件、短信、企业微信等多种告警通知方式
  • 告警级别:支持设置不同的告警级别,如信息、警告、错误、严重等
  • 告警处理:支持记录告警处理过程和结果
远程控制管理

远程控制管理模块提供对服务器的远程访问和控制能力:

  • 远程终端:提供基于SSH、Telnet的命令行终端访问
  • 远程桌面:提供Windows远程桌面和VNC远程访问
  • 文件传输:支持SFTP、FTP文件上传下载
  • 批量操作:支持对多台服务器同时执行命令或脚本
  • 会话管理:记录远程操作会话,支持会话回放
  • 权限控制:基于角色的远程访问权限控制
配置管理

配置管理模块负责管理服务器的配置信息,确保配置的一致性和合规性:

  • 配置采集:自动采集服务器的配置信息
  • 配置比对:比较不同服务器之间的配置差异
  • 配置模板:创建标准配置模板
  • 配置部署:快速部署标准配置
  • 配置备份:定期备份重要配置文件
  • 配置审计:检查配置是否符合安全标准和最佳实践
自动化任务

自动化任务模块提供强大的任务编排和执行能力:

  • 任务编排:通过可视化界面创建复杂的自动化任务流程
  • 计划任务:设置定时执行的任务
  • 触发任务:基于特定事件触发执行的任务
  • 任务模板:预定义常用任务模板
  • 执行记录:详细记录任务执行过程和结果
  • 任务通知:任务执行完成后发送通知
软件管理

软件管理模块负责管理服务器上的软件:

  • 软件清单:自动采集服务器上安装的软件清单
  • 软件分发:集中分发软件包到多台服务器
  • 软件更新:管理软件更新和升级
  • 补丁管理:管理系统补丁的分发和安装
  • 许可证管理:管理软件许可证信息
报表分析

报表分析模块提供丰富的数据分析和可视化功能:

  • 资产报表:服务器资产统计报表
  • 性能报表:服务器性能趋势分析
  • 告警报表:告警统计和分析
  • 任务报表:自动化任务执行统计
  • 自定义报表:支持用户自定义报表
  • 导出功能:支持将报表导出为PDF、Excel等格式

平台特点

桌面版C/S自动化运维平台具有以下特点:

  1. 易用性:通过图形化界面,降低使用门槛,使不具备编程能力的IT管理员也能快速上手
  2. 轻量级:客户端资源占用小,安装简便,适合中小型企业使用
  3. 安全性:支持多种身份认证方式,严格的权限控制,加密数据传输
  4. 扩展性:模块化设计,支持通过插件扩展功能
  5. 离线能力:支持离线工作,网络恢复后自动同步数据
  6. 跨平台:客户端支持Windows、macOS和Linux操作系统
  7. 定制化:支持按照企业需求进行界面和功能定制

16.2 系统构架设计

为了实现前述功能,我们需要设计一个灵活、可靠、安全的系统架构。本节将详细介绍桌面版C/S自动化运维平台的系统架构设计。

总体架构

系统采用典型的C/S(客户端/服务器)架构,主要包括以下部分:

  1. 客户端(Client)

    • 表现层:用户界面,负责与用户交互
    • 业务逻辑层:实现客户端的业务逻辑
    • 数据访问层:处理与服务器的数据交换
    • 本地存储:支持离线工作的本地数据库
    • 插件系统:支持功能扩展的插件框架
  2. 服务器(Server)

    • API网关:统一的API访问入口,处理认证授权
    • 业务逻辑层:实现核心业务功能
    • 数据访问层:处理数据库操作
    • 任务调度系统:管理后台任务的执行
    • 通知服务:处理各类通知和告警
    • 文件存储服务:管理系统文件存储
  3. 代理(Agent)

    • 部署在被管理服务器上的轻量级代理程序
    • 负责采集数据、执行命令、监控状态
    • 支持与服务器的加密通信
  4. 数据库

    • 存储系统配置数据
    • 存储监控数据
    • 存储资产信息
    • 存储用户和权限数据
    • 存储操作日志
  5. 外部集成

    • 邮件服务器:发送邮件通知
    • 短信网关:发送短信通知
    • 企业微信/钉钉:发送即时消息
    • LDAP/AD:集成企业目录服务
    • 其他第三方系统

技术架构

客户端技术栈
  • 开发语言:C#/.NET
  • UI框架:WPF (Windows Presentation Foundation)
  • 本地数据库:SQLite
  • 网络通信:gRPC, WebSocket, REST API
  • 数据序列化:JSON, Protocol Buffers
  • 安全组件:TLS/SSL, AES加密
  • 日志框架:NLog
  • 依赖注入:Microsoft.Extensions.DependencyInjection
  • 插件框架:MEF (Managed Extensibility Framework)
服务器技术栈
  • 开发语言:C#/.NET Core
  • Web框架:ASP.NET Core
  • 数据库访问:Entity Framework Core
  • API文档:Swagger/OpenAPI
  • 认证授权:JWT (JSON Web Token), OAuth 2.0
  • 消息队列:RabbitMQ
  • 缓存:Redis
  • 监控:Prometheus + Grafana
  • 容器化:Docker
代理技术栈
  • 开发语言:Go (适用于各种操作系统)
  • 配置管理:YAML
  • 数据收集:自定义插件 + 开源工具集成
  • 安全通信:HTTPS, TLS/SSL
  • 本地缓存:LevelDB

数据流设计

系统的主要数据流如下:

  1. 资产数据流

    • Agent采集服务器信息 → 上报给Server → 存储到数据库 → Client查询显示
    • Client手动录入服务器信息 → 提交给Server → 存储到数据库
  2. 监控数据流

    • Agent采集监控指标 → 上报给Server → 存储到数据库 → 分析处理 → 触发告警 → 通知服务 → 发送告警
    • Client查询监控数据 → Server处理请求 → 返回数据 → Client展示
  3. 远程控制流

    • Client发起远程连接请求 → Server处理认证授权 → 建立与目标服务器的连接 → 双向数据传输
    • Server记录操作日志 → 存储到数据库
  4. 自动化任务流

    • Client创建任务 → 提交给Server → 存储到数据库
    • Server调度任务 → 分配给Agent执行 → Agent执行任务 → 返回结果 → Server处理结果 → 存储到数据库
    • Client查询任务结果 → Server返回数据 → Client展示

系统安全设计

考虑到运维平台的敏感性,系统安全设计至关重要:

  1. 认证与授权

    • 支持多种认证方式:用户名密码、证书、LDAP/AD、双因素认证
    • 基于角色的访问控制(RBAC)
    • 最小权限原则
    • 会话管理和超时控制
  2. 数据安全

    • 传输数据加密:TLS/SSL
    • 敏感数据存储加密:密码、密钥等
    • 数据完整性校验
    • 定期数据备份
  3. 通信安全

    • 客户端与服务器间的通信加密
    • 服务器与代理间的通信加密
    • 证书验证和信任机制
    • 防重放攻击
  4. 操作审计

    • 详细记录所有关键操作
    • 不可篡改的审计日志
    • 支持审计日志查询和分析
    • 异常操作检测和告警
  5. 漏洞防护

    • 定期安全更新
    • 代码安全审计
    • 第三方组件漏洞扫描
    • 安全开发生命周期

高可用设计

为保证系统的稳定运行,采用以下高可用设计:

  1. 服务器高可用

    • 主备架构或集群部署
    • 负载均衡
    • 故障自动转移
  2. 数据库高可用

    • 主从复制
    • 自动备份
    • 故障恢复机制
  3. 客户端容错

    • 本地缓存
    • 离线工作模式
    • 自动重连机制
  4. 代理高可用

    • 健康检查
    • 自动恢复
    • 数据缓冲

16.3 数据库结构设计

数据库是系统的核心组成部分,良好的数据库设计对系统的性能和可扩展性至关重要。本节将详细介绍系统的数据库结构设计。

16.3.1 数据库分析

在设计数据库之前,我们需要分析系统的数据需求和访问模式。

数据类型分析

系统涉及的主要数据类型如下:

  1. 配置数据

    • 系统配置参数
    • 用户偏好设置
    • 功能模块配置
    • 数据量小,变化频率低
  2. 用户和权限数据

    • 用户信息
    • 角色定义
    • 权限分配
    • 数据量小,变化频率低
  3. 资产数据

    • 服务器基本信息
    • 硬件配置信息
    • 系统信息
    • 网络配置
    • 数据量中等,变化频率低
  4. 监控数据

    • 性能监控指标
    • 服务状态数据
    • 告警记录
    • 数据量大,变化频率高
  5. 操作日志

    • 用户操作记录
    • 系统事件日志
    • 审计日志
    • 数据量大,只增不改
  6. 任务数据

    • 任务定义
    • 任务执行记录
    • 任务结果
    • 数据量中等,变化频率中等
数据访问模式分析

不同类型的数据有不同的访问模式:

  1. 配置数据

    • 读多写少
    • 全表查询为主
    • 高一致性要求
  2. 用户和权限数据

    • 读多写少
    • 单条查询为主
    • 高一致性要求
  3. 资产数据

    • 读多写少
    • 条件查询为主
    • 中等一致性要求
  4. 监控数据

    • 写入频繁
    • 时序查询为主
    • 低一致性要求
  5. 操作日志

    • 只写入,几乎不修改
    • 条件查询为主
    • 低一致性要求
  6. 任务数据

    • 读写均衡
    • 状态更新频繁
    • 中等一致性要求
数据量估算

假设一个中型企业环境:

  • 服务器数量:500台
  • 用户数量:50人
  • 每台服务器监控指标:50个
  • 监控采集频率:每分钟一次
  • 每天执行任务数:100个

数据量估算:

  • 资产数据:约500KB(不含历史记录)
  • 每日监控数据:500台 × 50指标 × 60分钟 × 24小时 × 100字节 ≈ 3.6GB
  • 每日操作日志:约50MB
  • 每日任务数据:约10MB

根据上述分析,我们需要采用合适的数据库来存储不同类型的数据:

  • 关系型数据库(如SQL Server, MySQL):存储配置数据、用户权限数据、资产数据和任务数据
  • 时序数据库(如InfluxDB, TimescaleDB):存储监控数据
  • 文档数据库(可选,如MongoDB):存储非结构化或半结构化数据

16.3.2 数据字典

基于上述分析,我们定义系统的主要数据实体和属性。

用户和权限管理
  1. Users(用户表)

    • UserID (PK): int, 用户ID
    • Username: varchar(50), 用户名
    • PasswordHash: varchar(256), 密码哈希
    • Salt: varchar(50), 密码盐值
    • Email: varchar(100), 电子邮箱
    • FullName: varchar(100), 用户全名
    • PhoneNumber: varchar(20), 电话号码
    • IsActive: bit, 是否激活
    • LastLoginTime: datetime, 最后登录时间
    • CreatedTime: datetime, 创建时间
    • UpdatedTime: datetime, 更新时间
    • IsAdmin: bit, 是否是管理员
    • AvatarPath: varchar(256), 头像路径
  2. Roles(角色表)

    • RoleID (PK): int, 角色ID
    • RoleName: varchar(50), 角色名称
    • Description: varchar(200), 角色描述
    • CreatedTime: datetime, 创建时间
    • UpdatedTime: datetime, 更新时间
  3. Permissions(权限表)

    • PermissionID (PK): int, 权限ID
    • PermissionName: varchar(50), 权限名称
    • Description: varchar(200), 权限描述
    • PermissionCode: varchar(50), 权限代码
    • ModuleCode: varchar(50), 模块代码
    • CreatedTime: datetime, 创建时间
  4. UserRoles(用户角色关联表)

    • UserRoleID (PK): int, 用户角色ID
    • UserID (FK): int, 用户ID
    • RoleID (FK): int, 角色ID
    • AssignedTime: datetime, 分配时间
  5. RolePermissions(角色权限关联表)

    • RolePermissionID (PK): int, 角色权限ID
    • RoleID (FK): int, 角色ID
    • PermissionID (FK): int, 权限ID
    • AssignedTime: datetime, 分配时间
资产管理
  1. Servers(服务器表)

    • ServerID (PK): int, 服务器ID
    • Hostname: varchar(100), 主机名
    • IPAddress: varchar(39), IP地址
    • MACAddress: varchar(17), MAC地址
    • ServerType: varchar(50), 服务器类型
    • OSType: varchar(50), 操作系统类型
    • OSVersion: varchar(50), 操作系统版本
    • CPUModel: varchar(100), CPU型号
    • CPUCores: int, CPU核心数
    • MemorySize: int, 内存大小(MB)
    • DiskSize: int, 磁盘大小(GB)
    • Department: varchar(100), 所属部门
    • Location: varchar(100), 位置
    • ResponsiblePerson: varchar(100), 负责人
    • PurchaseDate: date, 购买日期
    • WarrantyEndDate: date, 保修结束日期
    • Status: varchar(20), 状态
    • Description: text, 描述
    • LastCheckTime: datetime, 最后检查时间
    • CreatedTime: datetime, 创建时间
    • UpdatedTime: datetime, 更新时间
    • AgentStatus: varchar(20), 代理状态
    • AgentVersion: varchar(20), 代理版本
  2. ServerGroups(服务器组表)

    • GroupID (PK): int, 组ID
    • GroupName: varchar(100), 组名称
    • Description: text, 描述
    • ParentGroupID (FK): int, 父组ID
    • CreatedTime: datetime, 创建时间
    • UpdatedTime: datetime, 更新时间
  3. ServerGroupMemberships(服务器组成员表)

    • MembershipID (PK): int, 成员ID
    • ServerID (FK): int, 服务器ID
    • GroupID (FK): int, 组ID
    • AddedTime: datetime, 添加时间
  4. ServerNetworkInterfaces(服务器网络接口表)

    • InterfaceID (PK): int, 接口ID
    • ServerID (FK): int, 服务器ID
    • InterfaceName: varchar(50), 接口名称
    • IPAddress: varchar(39), IP地址
    • MACAddress: varchar(17), MAC地址
    • SubnetMask: varchar(39), 子网掩码
    • Gateway: varchar(39), 网关
    • DNSServers: varchar(200), DNS服务器
    • IsActive: bit, 是否激活
    • UpdatedTime: datetime, 更新时间
  5. ServerDisks(服务器磁盘表)

    • DiskID (PK): int, 磁盘ID
    • ServerID (FK): int, 服务器ID
    • DiskName: varchar(50), 磁盘名称
    • DiskType: varchar(20), 磁盘类型
    • TotalSize: int, 总大小(GB)
    • FreeSize: int, 可用大小(GB)
    • FileSystem: varchar(20), 文件系统
    • MountPoint: varchar(200), 挂载点
    • UpdatedTime: datetime, 更新时间
监控和告警
  1. MonitorItems(监控项目表)

    • ItemID (PK): int, 项目ID
    • ItemName: varchar(100), 项目名称
    • ItemType: varchar(50), 项目类型
    • MetricName: varchar(50), 指标名称
    • Description: text, 描述
    • CollectionInterval: int, 采集间隔(秒)
    • IsActive: bit, 是否激活
    • CreatedTime: datetime, 创建时间
    • UpdatedTime: datetime, 更新时间
  2. MonitorThresholds(监控阈值表)

    • ThresholdID (PK): int, 阈值ID
    • ItemID (FK): int, 监控项目ID
    • ServerID (FK): int, 服务器ID (可为NULL表示全局阈值)
    • GroupID (FK): int, 组ID (可为NULL)
    • WarningThreshold: varchar(50), 警告阈值
    • CriticalThreshold: varchar(50), 严重阈值
    • ComparisonOperator: varchar(10), 比较操作符
    • CreatedBy (FK): int, 创建人ID
    • CreatedTime: datetime, 创建时间
    • UpdatedTime: datetime, 更新时间
  3. Alerts(告警表)

    • AlertID (PK): int, 告警ID
    • ServerID (FK): int, 服务器ID
    • ItemID (FK): int, 监控项目ID
    • AlertLevel: varchar(20), 告警级别
    • AlertMessage: text, 告警消息
    • CurrentValue: varchar(50), 当前值
    • ThresholdValue: varchar(50), 阈值
    • FirstOccurTime: datetime, 首次发生时间
    • LastOccurTime: datetime, 最后发生时间
    • OccurrenceCount: int, 发生次数
    • Status: varchar(20), 状态
    • ProcessedBy (FK): int, 处理人ID
    • ProcessedTime: datetime, 处理时间
    • ProcessingComments: text, 处理说明
  4. NotificationRules(通知规则表)

    • RuleID (PK): int, 规则ID
    • RuleName: varchar(100), 规则名称
    • AlertLevel: varchar(20), 告警级别
    • ItemID (FK): int, 监控项目ID (可为NULL)
    • ServerID (FK): int, 服务器ID (可为NULL)
    • GroupID (FK): int, 组ID (可为NULL)
    • NotificationType: varchar(20), 通知类型
    • NotificationTarget: varchar(200), 通知目标
    • IsActive: bit, 是否激活
    • CreatedBy (FK): int, 创建人ID
    • CreatedTime: datetime, 创建时间
    • UpdatedTime: datetime, 更新时间
任务管理
  1. Tasks(任务表)

    • TaskID (PK): int, 任务ID
    • TaskName: varchar(100), 任务名称
    • TaskType: varchar(50), 任务类型
    • ScriptContent: text, 脚本内容
    • Parameters: text, 参数
    • Description: text, 描述
    • Timeout: int, 超时时间(秒)
    • CreatedBy (FK): int, 创建人ID
    • CreatedTime: datetime, 创建时间
    • UpdatedTime: datetime, 更新时间
  2. ScheduledTasks(计划任务表)

    • ScheduleID (PK): int, 计划ID
    • TaskID (FK): int, 任务ID
    • ScheduleName: varchar(100), 计划名称
    • ScheduleType: varchar(20), 计划类型
    • CronExpression: varchar(100), Cron表达式
    • StartTime: datetime, 开始时间
    • EndTime: datetime, 结束时间
    • IsActive: bit, 是否激活
    • LastRunTime: datetime, 最后运行时间
    • NextRunTime: datetime, 下次运行时间
    • CreatedBy (FK): int, 创建人ID
    • CreatedTime: datetime, 创建时间
    • UpdatedTime: datetime, 更新时间
  3. TaskTargets(任务目标表)

    • TargetID (PK): int, 目标ID
    • TaskID (FK): int, 任务ID
    • ServerID (FK): int, 服务器ID (与GroupID二选一)
    • GroupID (FK): int, 组ID (与ServerID二选一)
    • AddedTime: datetime, 添加时间
  4. TaskExecutions(任务执行表)

    • ExecutionID (PK): int, 执行ID
    • TaskID (FK): int, 任务ID
    • ScheduleID (FK): int, 计划ID (可为NULL表示手动执行)
    • ExecutionTime: datetime, 执行时间
    • CompletionTime: datetime, 完成时间
    • Status: varchar(20), 状态
    • TriggerType: varchar(20), 触发类型
    • TriggeredBy (FK): int, 触发人ID
    • ParentExecutionID (FK): int, 父执行ID (可为NULL)
  5. TaskExecutionDetails(任务执行详情表)

    • DetailID (PK): int, 详情ID
    • ExecutionID (FK): int, 执行ID
    • ServerID (FK): int, 服务器ID
    • StartTime: datetime, 开始时间
    • EndTime: datetime, 结束时间
    • Status: varchar(20), 状态
    • Output: text, 输出内容
    • ErrorMessage: text, 错误消息
    • ReturnCode: int, 返回代码
系统配置和日志
  1. SystemSettings(系统设置表)

    • SettingID (PK): int, 设置ID
    • SettingKey: varchar(100), 设置键
    • SettingValue: text, 设置值
    • SettingGroup: varchar(50), 设置组
    • Description: varchar(200), 描述
    • UpdatedBy (FK): int, 更新人ID
    • UpdatedTime: datetime, 更新时间
  2. AuditLogs(审计日志表)

    • LogID (PK): int, 日志ID
    • UserID (FK): int, 用户ID
    • IPAddress: varchar(39), IP地址
    • ModuleCode: varchar(50), 模块代码
    • ActionType: varchar(50), 操作类型
    • ActionTarget: varchar(200), 操作目标
    • ActionContent: text, 操作内容
    • ActionTime: datetime, 操作时间
    • Status: varchar(20), 状态
    • ErrorMessage: text, 错误消息
  3. SystemLogs(系统日志表)

    • LogID (PK): int, 日志ID
    • LogLevel: varchar(20), 日志级别
    • Logger: varchar(100), 日志记录器
    • Message: text, 消息
    • Exception: text, 异常信息
    • ServerName: varchar(100), 服务器名称
    • LogTime: datetime, 日志时间

16.3.3 数据库模型

基于上述数据字典,我们可以构建Entity Framework Core的数据模型。以下是主要实体类的定义:

csharp

// 用户和权限模型
public class User
{
    public int UserID { get; set; }
    public string Username { get; set; }
    public string PasswordHash { get; set; }
    public string Salt { get; set; }
    public string Email { get; set; }
    public string FullName { get; set; }
    public string PhoneNumber { get; set; }
    public bool IsActive { get; set; }
    public DateTime? LastLoginTime { get; set; }
    public DateTime CreatedTime { get; set; }
    public DateTime UpdatedTime { get; set; }
    public bool IsAdmin { get; set; }
    public string AvatarPath { get; set; }
    
    public ICollection<UserRole> UserRoles { get; set; }
}

public class Role
{
    public int RoleID { get; set; }
    public string RoleName { get; set; }
    public string Description { get; set; }
    public DateTime CreatedTime { get; set; }
    public DateTime UpdatedTime { get; set; }
    
    public ICollection<UserRole> UserRoles { get; set; }
    public ICollection<RolePermission> RolePermissions { get; set; }
}

public class Permission
{
    public int PermissionID { get; set; }
    public string PermissionName { get; set; }
    public string Description { get; set; }
    public string PermissionCode { get; set; }
    public string ModuleCode { get; set; }
    public DateTime CreatedTime { get; set; }
    
    public ICollection<RolePermission> RolePermissions { get; set; }
}

public class UserRole
{
    public int UserRoleID { get; set; }
    public int UserID { get; set; }
    public int RoleID { get; set; }
    public DateTime AssignedTime { get; set; }
    
    public User User { get; set; }
    public Role Role { get; set; }
}

public class RolePermission
{
    public int RolePermissionID { get; set; }
    public int RoleID { get; set; }
    public int PermissionID { get; set; }
    public DateTime AssignedTime { get; set; }
    
    public Role Role { get; set; }
    public Permission Permission { get; set; }
}

// 服务器资产模型
public class Server
{
    public int ServerID { get; set; }
    public string Hostname { get; set; }
    public string IPAddress { get; set; }
    public string MACAddress { get; set; }
    public string ServerType { get; set; }
    public string OSType { get; set; }
    public string OSVersion { get; set; }
    public string CPUModel { get; set; }
    public int CPUCores { get; set; }
    public int MemorySize { get; set; }
    public int DiskSize { get; set; }
    public string Department { get; set; }
    public string Location { get; set; }
    public string ResponsiblePerson { get; set; }
    public DateTime? PurchaseDate { get; set; }
    public DateTime? WarrantyEndDate { get; set; }
    public string Status { get; set; }
    public string Description { get; set; }
    public DateTime? LastCheckTime { get; set; }
    public DateTime CreatedTime { get; set; }
    public DateTime UpdatedTime { get; set; }
    public string AgentStatus { get; set; }
    public string AgentVersion { get; set; }
    
    public ICollection<ServerGroupMembership> GroupMemberships { get; set; }
    public ICollection<ServerNetworkInterface> NetworkInterfaces { get; set; }
    public ICollection<ServerDisk> Disks { get; set; }
    public ICollection<Alert> Alerts { get; set; }
    public ICollection<MonitorThreshold> Thresholds { get; set; }
    public ICollection<TaskTarget> TaskTargets { get; set; }
    public ICollection<TaskExecutionDetail> ExecutionDetails { get; set; }
}

// 监控和告警模型
public class MonitorItem
{
    public int ItemID { get; set; }
    public string ItemName { get; set; }
    public string ItemType { get; set; }
    public string MetricName { get; set; }
    public string Description { get; set; }
    public int CollectionInterval { get; set; }
    public bool IsActive { get; set; }
    public DateTime CreatedTime { get; set; }
    public DateTime UpdatedTime { get; set; }
    
    public ICollection<MonitorThreshold> Thresholds { get; set; }
    public ICollection<Alert> Alerts { get; set; }
}

public class Alert
{
    public int AlertID { get; set; }
    public int ServerID { get; set; }
    public int ItemID { get; set; }
    public string AlertLevel { get; set; }
    public string AlertMessage { get; set; }
    public string CurrentValue { get; set; }
    public string ThresholdValue { get; set; }
    public DateTime FirstOccurTime { get; set; }
    public DateTime LastOccurTime { get; set; }
    public int OccurrenceCount { get; set; }
    public string Status { get; set; }
    public int? ProcessedBy { get; set; }
    public DateTime? ProcessedTime { get; set; }
    public string ProcessingComments { get; set; }
    
    public Server Server { get; set; }
    public MonitorItem Item { get; set; }
    public User Processor { get; set; }
}

// 任务管理模型
public class Task
{
    public int TaskID { get; set; }
    public string TaskName { get; set; }
    public string TaskType { get; set; }
    public string ScriptContent { get; set; }
    public string Parameters { get; set; }
    public string Description { get; set; }
    public int Timeout { get; set; }
    public int CreatedBy { get; set; }
    public DateTime CreatedTime { get; set; }
    public DateTime UpdatedTime { get; set; }
    
    public User Creator { get; set; }
    public ICollection<ScheduledTask> Schedules { get; set; }
    public ICollection<TaskTarget> Targets { get; set; }
    public ICollection<TaskExecution> Executions { get; set; }
}

public class TaskExecution
{
    public int ExecutionID { get; set; }
    public int TaskID { get; set; }
    public int? ScheduleID { get; set; }
    public DateTime ExecutionTime { get; set; }
    public DateTime? CompletionTime { get; set; }
    public string Status { get; set; }
    public string TriggerType { get; set; }
    public int? TriggeredBy { get; set; }
    public int? ParentExecutionID { get; set; }
    
    public Task Task { get; set; }
    public ScheduledTask Schedule { get; set; }
    public User Trigger { get; set; }
    public TaskExecution ParentExecution { get; set; }
    public ICollection<TaskExecution> ChildExecutions { get; set; }
    public ICollection<TaskExecutionDetail> Details { get; set; }
}

16.4 系统环境部署

系统环境部署是C/S自动化运维平台实施的关键步骤,本节将详细介绍系统的部署环境和方案。

16.4.1 系统环境说明

服务器端环境要求
  1. 硬件要求

    • CPU:至少4核,推荐8核或更高
    • 内存:至少8GB,推荐16GB或更高
    • 磁盘:至少200GB,推荐SSD
    • 网络:千兆网卡,固定IP地址
  2. 软件要求

    • 操作系统:Windows Server 2019/2022 或 Linux (如Ubuntu 20.04 LTS)
    • .NET:.NET Core 6.0 或更高版本
    • 数据库:SQL Server 2019 或 MySQL 8.0
    • 时序数据库:InfluxDB 2.0(可选)
    • Web服务器:IIS 10 或 Nginx
    • 消息队列:RabbitMQ 3.9 或更高版本
    • 容器支持:Docker 20.10 或更高版本(可选)
客户端环境要求
  1. 硬件要求

    • CPU:双核或更高
    • 内存:至少4GB
    • 磁盘:至少10GB可用空间
    • 显示:至少1366x768分辨率
  2. 软件要求

    • 操作系统:Windows 10/11
    • .NET Framework:4.8或更高版本
    • 数据库:SQLite(内置)
代理环境要求
  1. 硬件要求

    • CPU:无特殊要求
    • 内存:至少256MB可用内存
    • 磁盘:至少200MB可用空间
  2. 软件要求

    • 操作系统:
      • Windows Server 2012 R2/2016/2019/2022
      • CentOS/RHEL 7.x/8.x
      • Ubuntu 18.04/20.04
      • Debian 10/11
      • SUSE Linux Enterprise 12/15

16.4.2 系统环境搭建

服务器端部署
Windows环境下的部署
  1. 安装必要组件

    powershell

    # 安装IIS
    Install-WindowsFeature -Name Web-Server -IncludeManagementTools
    
    # 安装.NET Hosting Bundle
    Invoke-WebRequest -Uri https://download.visualstudio.microsoft.com/download/pr/4b8ad533-4464-4b8a-8b11-7f378fa80726/8ce95970db51aae093e3d9233214e2cc/dotnet-hosting-6.0.10-win.exe -OutFile dotnet-hosting.exe
    Start-Process -FilePath .\dotnet-hosting.exe -ArgumentList '/quiet' -Wait
    
    # 安装URL Rewrite模块
    Invoke-WebRequest -Uri https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi -OutFile rewrite_amd64.msi
    Start-Process -FilePath .\rewrite_amd64.msi -ArgumentList '/quiet' -Wait
    
    # 重启IIS
    Restart-Service W3SVC
    
  2. 安装SQL Server

    • 下载并安装SQL Server 2019 Express或标准版
    • 配置SQL Server认证,创建应用程序所需的数据库和用户
  3. 安装RabbitMQ

    powershell

    # 安装Erlang
    Invoke-WebRequest -Uri https://github.com/erlang/otp/releases/download/OTP-24.1.7/otp_win64_24.1.7.exe -OutFile erlang.exe
    Start-Process -FilePath .\erlang.exe -ArgumentList '/S' -Wait
    
    # 安装RabbitMQ
    Invoke-WebRequest -Uri https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.9.13/rabbitmq-server-3.9.13.exe -OutFile rabbitmq.exe
    Start-Process -FilePath .\rabbitmq.exe -ArgumentList '/S' -Wait
    
    # 启用管理插件
    & 'C:\Program Files\RabbitMQ Server\rabbitmq_server-3.9.13\sbin\rabbitmq-plugins.bat' enable rabbitmq_management
    
  4. 部署应用

    powershell

    # 创建应用目录
    New-Item -ItemType Directory -Path C:\OpsManager
    
    # 解压应用文件
    Expand-Archive -Path OpsManager-Server.zip -DestinationPath C:\OpsManager
    
    # 创建IIS应用程序池
    Import-Module WebAdministration
    New-WebAppPool -Name "OpsManagerAppPool" -Force
    Set-ItemProperty -Path IIS:\AppPools\OpsManagerAppPool -Name managedRuntimeVersion -Value ""
    Set-ItemProperty -Path IIS:\AppPools\OpsManagerAppPool -Name processModel.identityType -Value ApplicationPoolIdentity
    
    # 创建IIS网站
    New-Website -Name "OpsManager" -PhysicalPath C:\OpsManager -ApplicationPool "OpsManagerAppPool" -Port 8080 -Force
    
    # 设置应用程序配置
    $configPath = "C:\OpsManager\appsettings.json"
    $config = Get-Content -Path $configPath | ConvertFrom-Json
    $config.ConnectionStrings.DefaultConnection = "Server=localhost;Database=OpsManager;User Id=opsuser;Password=StrongPassword123!;"
    $config.ConnectionStrings.InfluxDB = "http://localhost:8086?token=mytoken&org=myorg&bucket=monitoring"
    $config.RabbitMQ.Host = "localhost"
    $config.RabbitMQ.Username = "guest"
    $config.RabbitMQ.Password = "guest"
    $config | ConvertTo-Json -Depth 10 | Set-Content -Path $configPath
    
Linux环境下的部署
  1. 安装.NET运行时

    bash

    # Ubuntu 20.04
    wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
    sudo dpkg -i packages-microsoft-prod.deb
    rm packages-microsoft-prod.deb
    
    sudo apt-get update
    sudo apt-get install -y apt-transport-https
    sudo apt-get update
    sudo apt-get install -y dotnet-sdk-6.0
    
  2. 安装MySQL

    bash

    sudo apt update
    sudo apt install -y mysql-server
    sudo mysql_secure_installation
    
    # 创建数据库和用户
    sudo mysql -e "CREATE DATABASE OpsManager CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
    sudo mysql -e "CREATE USER 'opsuser'@'localhost' IDENTIFIED BY 'StrongPassword123!';"
    sudo mysql -e "GRANT ALL PRIVILEGES ON OpsManager.* TO 'opsuser'@'localhost';"
    sudo mysql -e "FLUSH PRIVILEGES;"
    
  3. 安装RabbitMQ

    bash

    sudo apt update
    sudo apt install -y rabbitmq-server
    sudo systemctl enable rabbitmq-server
    sudo systemctl start rabbitmq-server
    sudo rabbitmq-plugins enable rabbitmq_management
    
  4. 安装Nginx

    bash

    sudo apt update
    sudo apt install -y nginx
    sudo systemctl enable nginx
    sudo systemctl start nginx
    
  5. 部署应用

    bash

    # 创建应用目录
    sudo mkdir -p /opt/OpsManager
    sudo chown -R $USER:$USER /opt/OpsManager
    
    # 解压应用文件
    unzip OpsManager-Server.zip -d /opt/OpsManager
    
    # 配置应用
    cd /opt/OpsManager
    cat > appsettings.json << EOF
    {
      "ConnectionStrings": {
        "DefaultConnection": "Server=localhost;Database=OpsManager;User=opsuser;Password=StrongPassword123!;",
        "InfluxDB": "http://localhost:8086?token=mytoken&org=myorg&bucket=monitoring"
      },
      "RabbitMQ": {
        "Host": "localhost",
        "Username": "guest",
        "Password": "guest",
        "VirtualHost": "/"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft": "Warning",
          "Microsoft.Hosting.Lifetime": "Information"
        }
      },
      "AllowedHosts": "*"
    }
    EOF
    
    # 创建服务
    sudo cat > /etc/systemd/system/opsmanager.service << EOF
    [Unit]
    Description=OpsManager Service
    After=network.target
    
    [Service]
    WorkingDirectory=/opt/OpsManager
    ExecStart=/usr/bin/dotnet /opt/OpsManager/OpsManager.Server.dll
    Restart=always
    RestartSec=10
    SyslogIdentifier=opsmanager
    User=www-data
    Environment=ASPNETCORE_ENVIRONMENT=Production
    
    [Install]
    WantedBy=multi-user.target
    EOF
    
    sudo systemctl enable opsmanager
    sudo systemctl start opsmanager
    
  6. 配置Nginx反向代理

    bash

    sudo cat > /etc/nginx/sites-available/opsmanager << EOF
    server {
        listen 80;
        server_name opsmanager.example.com;
    
        location / {
            proxy_pass http://localhost:5000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade \$http_upgrade;
            proxy_set_header Connection keep-alive;
            proxy_set_header Host \$host;
            proxy_cache_bypass \$http_upgrade;
            proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto \$scheme;
        }
    }
    EOF
    
    sudo ln -s /etc/nginx/sites-available/opsmanager /etc/nginx/sites-enabled/
    sudo nginx -t
    sudo systemctl reload nginx
    
客户端部署

客户端的部署相对简单:

  1. 创建安装包

    • 使用Visual Studio或其他工具创建Windows安装程序(MSI)
    • 包含所有必要的依赖项和运行时
    • 配置自动更新功能
  2. 安装步骤

    powershell

    # 通过命令行静默安装
    msiexec /i OpsManager-Client.msi /quiet SERVER_URL=https://opsmanager.example.com INSTALL_DIR="C:\Program Files\OpsManager"
    
  3. 配置文件

    xml

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <appSettings>
        <add key="ServerUrl" value="https://opsmanager.example.com" />
        <add key="AutoUpdateEnabled" value="true" />
        <add key="UpdateCheckInterval" value="240" />
        <add key="LogLevel" value="Info" />
      </appSettings>
    </configuration>
    
代理部署

代理程序需要部署到每台被管理的服务器上:

Windows服务器
  1. 创建安装脚本

    powershell

    # agent_install.ps1
    param (
        [string]$ServerUrl = "https://opsmanager.example.com",
        [string]$AgentKey = "",
        [string]$InstallDir = "C:\Program Files\OpsManagerAgent"
    )
    
    # 创建安装目录
    if (-not (Test-Path $InstallDir)) {
        New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
    }
    
    # 下载代理程序
    $agentUrl = "$ServerUrl/download/agent/windows"
    $agentZip = "$env:TEMP\opsmanager-agent.zip"
    Invoke-WebRequest -Uri $agentUrl -OutFile $agentZip
    
    # 解压文件
    Expand-Archive -Path $agentZip -DestinationPath $InstallDir -Force
    Remove-Item -Path $agentZip -Force
    
    # 创建配置文件
    $configPath = "$InstallDir\config.json"
    $config = @{
        ServerUrl = $ServerUrl
        AgentKey = $AgentKey
        Hostname = [System.Net.Dns]::GetHostName()
        LogLevel = "Info"
        CollectionInterval = 60
        HeartbeatInterval = 30
    }
    $config | ConvertTo-Json | Set-Content -Path $configPath
    
    # 安装为Windows服务
    & "$InstallDir\install-service.ps1"
    
    Write-Host "OpsManager Agent installed successfully."
    
  2. 运行安装脚本

    powershell

    # 手动安装
    .\agent_install.ps1 -ServerUrl "https://opsmanager.example.com" -AgentKey "your-agent-key"
    
    # 或通过组策略批量部署
    
Linux服务器
  1. 创建安装脚本

    bash

    #!/bin/bash
    # agent_install.sh
    
    # 默认参数
    SERVER_URL="https://opsmanager.example.com"
    AGENT_KEY=""
    INSTALL_DIR="/opt/opsmanager-agent"
    
    # 解析命令行参数
    while [[ $# -gt 0 ]]; do
        key="$1"
        case $key in
            --server-url)
                SERVER_URL="$2"
                shift
                shift
                ;;
            --agent-key)
                AGENT_KEY="$2"
                shift
                shift
                ;;
            --install-dir)
                INSTALL_DIR="$2"
                shift
                shift
                ;;
            *)
                shift
                ;;
        esac
    done
    
    # 创建安装目录
    mkdir -p $INSTALL_DIR
    
    # 下载代理程序
    echo "Downloading agent..."
    if command -v curl > /dev/null; then
        curl -sSL "$SERVER_URL/download/agent/linux" -o /tmp/opsmanager-agent.tar.gz
    else
        wget -q -O /tmp/opsmanager-agent.tar.gz "$SERVER_URL/download/agent/linux"
    fi
    
    # 解压文件
    echo "Extracting files..."
    tar -xzf /tmp/opsmanager-agent.tar.gz -C $INSTALL_DIR
    rm -f /tmp/opsmanager-agent.tar.gz
    
    # 创建配置文件
    echo "Creating configuration..."
    cat > $INSTALL_DIR/config.json << EOF
    {
        "ServerUrl": "$SERVER_URL",
        "AgentKey": "$AGENT_KEY",
        "Hostname": "$(hostname)",
        "LogLevel": "Info",
        "CollectionInterval": 60,
        "HeartbeatInterval": 30
    }
    EOF
    
    # 设置权限
    chmod +x $INSTALL_DIR/opsmanager-agent
    chmod +x $INSTALL_DIR/install-service.sh
    
    # 安装服务
    echo "Installing service..."
    $INSTALL_DIR/install-service.sh
    
    echo "OpsManager Agent installed successfully."
    
  2. 运行安装脚本

    bash

    # 下载安装脚本
    curl -sSL https://opsmanager.example.com/download/install.sh -o install.sh
    chmod +x install.sh
    
    # 运行安装脚本
    ./install.sh --server-url https://opsmanager.example.com --agent-key your-agent-key
    

16.5 系统功能模块设计

系统功能模块是C/S自动化运维平台的核心组成部分,本节将详细介绍各个功能模块的设计和实现。

16.5.1 用户登录模块

用户登录模块是用户访问系统的入口,负责身份验证和会话管理。

登录界面设计

登录界面采用简洁明了的设计风格,包括以下元素:

  1. 系统标志和名称
  2. 用户名输入框
  3. 密码输入框
  4. 记住登录状态选项
  5. 登录按钮
  6. 版本信息
登录功能实现

以下是登录模块的核心代码实现:

csharp

// LoginViewModel.cs
using System;
using System.Security;
using System.Threading.Tasks;
using System.Windows.Input;
using OpsManager.Client.Core;
using OpsManager.Client.Services;

namespace OpsManager.Client.ViewModels
{
    public class LoginViewModel : ViewModelBase
    {
        private readonly IAuthService _authService;
        private readonly INavigationService _navigationService;
        private readonly ISettingsService _settingsService;
        
        private string _username;
        private bool _isLoading;
        private string _errorMessage;
        private bool _rememberMe;
        
        public string Username
        {
            get => _username;
            set
            {
                _username = value;
                OnPropertyChanged();
            }
        }
        
        public bool IsLoading
        {
            get => _isLoading;
            set
            {
                _isLoading = value;
                OnPropertyChanged();
                // 登录状态改变时,刷新命令可执行状态
                LoginCommand.RaiseCanExecuteChanged();
            }
        }
        
        public string ErrorMessage
        {
            get => _errorMessage;
            set
            {
                _errorMessage = value;
                OnPropertyChanged();
            }
        }
        
        public bool RememberMe
        {
            get => _rememberMe;
            set
            {
                _rememberMe = value;
                OnPropertyChanged();
            }
        }
        
        public RelayCommand<SecureString> LoginCommand { get; }
        
        public LoginViewModel(
            IAuthService authService,
            INavigationService navigationService,
            ISettingsService settingsService)
        {
            _authService = authService ?? throw new ArgumentNullException(nameof(authService));
            _navigationService = navigationService ?? throw new ArgumentNullException(nameof(navigationService));
            _settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
            
            LoginCommand = new RelayCommand<SecureString>(ExecuteLoginAsync, CanLogin);
            
            // 加载保存的用户名
            LoadSavedCredentials();
        }
        
        private bool CanLogin(SecureString password)
        {
            return !IsLoading && !string.IsNullOrWhiteSpace(Username) && password != null && password.Length > 0;
        }
        
        private async void ExecuteLoginAsync(SecureString password)
        {
            try
            {
                IsLoading = true;
                ErrorMessage = string.Empty;
                
                // 调用认证服务进行登录
                var result = await _authService.LoginAsync(Username, password);
                
                if (result.Success)
                {
                    // 保存用户名(如果选择了记住我)
                    if (RememberMe)
                    {
                        _settingsService.SaveSetting("RememberUsername", true);
                        _settingsService.SaveSetting("Username", Username);
                    }
                    else
                    {
                        _settingsService.SaveSetting("RememberUsername", false);
                        _settingsService.SaveSetting("Username", string.Empty);
                    }
                    
                    // 导航到主页
                    _navigationService.NavigateTo("MainView");
                }
                else
                {
                    ErrorMessage = result.Message;
                }
            }
            catch (Exception ex)
            {
                ErrorMessage = $"登录时发生错误: {ex.Message}";
                Logger.Error(ex, "登录失败");
            }
            finally
            {
                IsLoading = false;
            }
        }
        
        private void LoadSavedCredentials()
        {
            var rememberUsername = _settingsService.GetSetting<bool>("RememberUsername");
            if (rememberUsername)
            {
                Username = _settingsService.GetSetting<string>("Username");
                RememberMe = true;
            }
        }
    }
}

csharp

// AuthService.cs
using System;
using System.Net.Http;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using OpsManager.Client.Models;
using OpsManager.Client.Utils;

namespace OpsManager.Client.Services
{
    public class AuthService : IAuthService
    {
        private readonly HttpClient _httpClient;
        private readonly ISettingsService _settingsService;
        private readonly ITokenService _tokenService;
        
        public AuthService(
            HttpClient httpClient,
            ISettingsService settingsService,
            ITokenService tokenService)
        {
            _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
            _settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
            _tokenService = tokenService ?? throw new ArgumentNullException(nameof(tokenService));
        }
        
        public async Task<LoginResult> LoginAsync(string username, SecureString password)
        {
            try
            {
                var loginData = new
                {
                    Username = username,
                    Password = SecureStringHelper.ConvertToUnsecureString(password)
                };
                
                var content = new StringContent(
                    JsonConvert.SerializeObject(loginData),
                    Encoding.UTF8,
                    "application/json");
                
                var response = await _httpClient.PostAsync("api/auth/login", content);
                var responseContent = await response.Content.ReadAsStringAsync();
                
                if (response.IsSuccessStatusCode)
                {
                    var loginResponse = JsonConvert.DeserializeObject<LoginResponse>(responseContent);
                    
                    // 保存令牌
                    _tokenService.SetToken(loginResponse.Token);
                    
                    // 保存用户信息
                    CurrentUser.SetUser(loginResponse.User);
                    
                    return new LoginResult { Success = true };
                }
                else
                {
                    var errorResponse = JsonConvert.DeserializeObject<ErrorResponse>(responseContent);
                    return new LoginResult
                    {
                        Success = false,
                        Message = errorResponse?.Message ?? "登录失败,请检查用户名和密码。"
                    };
                }
            }
            catch (Exception ex)
            {
                return new LoginResult
                {
                    Success = false,
                    Message = $"登录过程中发生错误: {ex.Message}"
                };
            }
        }
        
        public void Logout()
        {
            // 清除令牌
            _tokenService.ClearToken();
            
            // 清除用户信息
            CurrentUser.Clear();
        }
        
        public bool IsAuthenticated()
        {
            return _tokenService.HasValidToken();
        }
    }
    
    public class LoginResponse
    {
        public string Token { get; set; }
        public UserInfo User { get; set; }
    }
    
    public class ErrorResponse
    {
        public string Message { get; set; }
    }
}
安全性考虑

登录模块采取以下安全措施:

  1. 密码处理

    • 使用SecureString类型保存内存中的密码
    • 密码在传输前才转换为普通字符串
    • 登录完成后立即清除内存中的密码
  2. 传输安全

    • 使用HTTPS加密传输
    • 使用TLS 1.2或更高版本
  3. 认证令牌

    • 使用JWT作为认证令牌
    • 令牌包含过期时间
    • 令牌安全存储在本地
  4. 防暴力破解

    • 实现登录失败次数限制
    • 多次失败后增加延迟
    • 支持账户锁定机制

16.5.2 系统配置功能

系统配置功能允许管理员配置系统的各项参数,包括服务器连接、告警通知、监控参数等。

配置界面设计

配置界面采用分类标签页的设计,包括以下主要分类:

  1. 基本设置

    • 服务器连接配置
    • 客户端显示设置
    • 语言和时区设置
  2. 监控设置

    • 默认监控间隔
    • 默认告警阈值
    • 数据保留策略
  3. 通知设置

    • 邮件通知配置
    • 短信通知配置
    • 企业微信/钉钉配置
  4. 代理设置

    • 代理部署配置
    • 代理升级设置
    • 代理安全设置
  5. 备份设置

    • 备份策略配置
    • 备份存储位置
    • 自动备份计划
设置存储模型

csharp

// 系统设置模型
public class SystemSettings
{
    // 基本设置
    public ServerConnectionSettings ServerConnection { get; set; }
    public ClientDisplaySettings DisplaySettings { get; set; }
    public LocalizationSettings Localization { get; set; }

    // 监控设置
    public MonitoringSettings Monitoring { get; set; }

    // 通知设置
    public NotificationSettings Notification { get; set; }

    // 代理设置
    public AgentSettings Agent { get; set; }

    // 备份设置
    public BackupSettings Backup { get; set; }
}

// 服务器连接设置
public class ServerConnectionSettings
{
    public string ServerUrl { get; set; }
    public bool UseHttps { get; set; } = true;
    public int ConnectionTimeout { get; set; } = 30;
    public int RetryCount { get; set; } = 3;
    public int RetryInterval { get; set; } = 5;
}

// 客户端显示设置
public class ClientDisplaySettings
{
    public string Theme { get; set; } = "Light";
    public int RefreshInterval { get; set; } = 60;
    public bool ShowStatusBar { get; set; } = true;
    public bool EnableAnimations { get; set; } = true;
    public int DefaultPageSize { get; set; } = 20;
}

// 本地化设置
public class LocalizationSettings
{
    public string Language { get; set; } = "en-US";
    public string TimeZone { get; set; } = "UTC";
    public string DateFormat { get; set; } = "yyyy-MM-dd";
    public string TimeFormat { get; set; } = "HH:mm:ss";
}

// 监控设置
public class MonitoringSettings
{
    public int DefaultCollectionInterval { get; set; } = 60;
    public int DataRetentionDays { get; set; } = 90;
    public Dictionary<string, ThresholdSetting> DefaultThresholds { get; set; }
}

// 阈值设置
public class ThresholdSetting
{
    public double WarningValue { get; set; }
    public double CriticalValue { get; set; }
    public string ComparisonOperator { get; set; } = ">";
}

// 通知设置
public class NotificationSettings
{
    public EmailSettings Email { get; set; }
    public SmsSettings Sms { get; set; }
    public WebhookSettings Webhook { get; set; }
}

// 邮件设置
public class EmailSettings
{
    public bool Enabled { get; set; } = false;
    public string SmtpServer { get; set; }
    public int SmtpPort { get; set; } = 25;
    public bool EnableSsl { get; set; } = true;
    public string Username { get; set; }
    public string Password { get; set; }
    public string FromAddress { get; set; }
    public string FromName { get; set; }
}

// 短信设置
public class SmsSettings
{
    public bool Enabled { get; set; } = false;
    public string Provider { get; set; }
    public string ApiKey { get; set; }
    public string ApiSecret { get; set; }
    public Dictionary<string, string> AdditionalSettings { get; set; }
}

// Webhook设置
public class WebhookSettings
{
    public bool Enabled { get; set; } = false;
    public List<WebhookEndpoint> Endpoints { get; set; }
}

// Webhook端点
public class WebhookEndpoint
{
    public string Name { get; set; }
    public string Url { get; set; }
    public string Method { get; set; } = "POST";
    public Dictionary<string, string> Headers { get; set; }
    public string PayloadTemplate { get; set; }
}

// 代理设置
public class AgentSettings
{
    public string DefaultInstallPath { get; set; }
    public bool AutoUpgrade { get; set; } = true;
    public int HeartbeatInterval { get; set; } = 30;
    public int CommandTimeout { get; set; } = 60;
    public bool EnableCompression { get; set; } = true;
    public bool EnableEncryption { get; set; } = true;
}

// 备份设置
public class BackupSettings
{
    public bool AutoBackup { get; set; } = true;
    public string BackupPath { get; set; }
    public int BackupRetentionCount { get; set; } = 10;
    public List<BackupSchedule> Schedules { get; set; }
}

// 备份计划
public class BackupSchedule
{
    public string Name { get; set; }
    public string CronExpression { get; set; }
    public bool Enabled { get; set; } = true;
    public BackupType Type { get; set; } = BackupType.Full;
}

// 备份类型
public enum BackupType
{
    Full,
    Incremental,
    Differential
}
配置界面实现

csharp

// SettingsViewModel.cs
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using OpsManager.Client.Core;
using OpsManager.Client.Models;
using OpsManager.Client.Services;

namespace OpsManager.Client.ViewModels
{
    public class SettingsViewModel : ViewModelBase
    {
        private readonly ISettingsService _settingsService;
        private readonly IDialogService _dialogService;
        
        private SystemSettings _settings;
        private bool _isLoading;
        private bool _isSaving;
        private bool _hasChanges;
        
        public SystemSettings Settings
        {
            get => _settings;
            set
            {
                _settings = value;
                OnPropertyChanged();
            }
        }
        
        public bool IsLoading
        {
            get => _isLoading;
            set
            {
                _isLoading = value;
                OnPropertyChanged();
            }
        }
        
        public bool IsSaving
        {
            get => _isSaving;
            set
            {
                _isSaving = value;
                OnPropertyChanged();
                SaveCommand.RaiseCanExecuteChanged();
            }
        }
        
        public bool HasChanges
        {
            get => _hasChanges;
            set
            {
                _hasChanges = value;
                OnPropertyChanged();
                SaveCommand.RaiseCanExecuteChanged();
            }
        }
        
        public RelayCommand LoadCommand { get; }
        public RelayCommand SaveCommand { get; }
        public RelayCommand ResetCommand { get; }
        public RelayCommand TestEmailCommand { get; }
        public RelayCommand TestSmsCommand { get; }
        
        public SettingsViewModel(
            ISettingsService settingsService,
            IDialogService dialogService)
        {
            _settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
            _dialogService = dialogService ?? throw new ArgumentNullException(nameof(dialogService));
            
            LoadCommand = new RelayCommand(ExecuteLoadAsync);
            SaveCommand = new RelayCommand(ExecuteSaveAsync, CanSave);
            ResetCommand = new RelayCommand(ExecuteReset);
            TestEmailCommand = new RelayCommand(ExecuteTestEmailAsync);
            TestSmsCommand = new RelayCommand(ExecuteTestSmsAsync);
            
            // 初始化默认设置
            Settings = new SystemSettings
            {
                ServerConnection = new ServerConnectionSettings(),
                DisplaySettings = new ClientDisplaySettings(),
                Localization = new LocalizationSettings(),
                Monitoring = new MonitoringSettings(),
                Notification = new NotificationSettings(),
                Agent = new AgentSettings(),
                Backup = new BackupSettings()
            };
            
            // 加载设置
            ExecuteLoadAsync();
        }
        
        private async void ExecuteLoadAsync()
        {
            try
            {
                IsLoading = true;
                
                // 从服务加载设置
                var settings = await _settingsService.LoadSystemSettingsAsync();
                Settings = settings;
                
                HasChanges = false;
            }
            catch (Exception ex)
            {
                await _dialogService.ShowErrorAsync("加载设置失败", ex.Message);
                Logger.Error(ex, "加载系统设置失败");
            }
            finally
            {
                IsLoading = false;
            }
        }
        
        private bool CanSave()
        {
            return HasChanges && !IsSaving;
        }
        
        private async void ExecuteSaveAsync()
        {
            try
            {
                IsSaving = true;
                
                // 保存设置到服务
                await _settingsService.SaveSystemSettingsAsync(Settings);
                
                HasChanges = false;
                await _dialogService.ShowInfoAsync("保存成功", "系统设置已成功保存。");
            }
            catch (Exception ex)
            {
                await _dialogService.ShowErrorAsync("保存设置失败", ex.Message);
                Logger.Error(ex, "保存系统设置失败");
            }
            finally
            {
                IsSaving = false;
            }
        }
        
        private void ExecuteReset()
        {
            // 询问用户确认
            _dialogService.ShowConfirmationAsync(
                "重置设置",
                "确定要重置所有设置到默认值吗?此操作不可撤销。",
                confirmed =>
                {
                    if (confirmed)
                    {
                        // 重置设置到默认值
                        Settings = new SystemSettings
                        {
                            ServerConnection = new ServerConnectionSettings(),
                            DisplaySettings = new ClientDisplaySettings(),
                            Localization = new LocalizationSettings(),
                            Monitoring = new MonitoringSettings(),
                            Notification = new NotificationSettings(),
                            Agent = new AgentSettings(),
                            Backup = new BackupSettings()
                        };
                        
                        HasChanges = true;
                    }
                });
        }
        
        private async void ExecuteTestEmailAsync()
        {
            try
            {
                await _dialogService.ShowProgressAsync("测试邮件", "正在发送测试邮件...");
                
                // 发送测试邮件
                var result = await _settingsService.TestEmailSettingsAsync(Settings.Notification.Email);
                
                await _dialogService.HideProgressAsync();
                
                if (result.Success)
                {
                    await _dialogService.ShowInfoAsync("测试成功", "测试邮件发送成功。");
                }
                else
                {
                    await _dialogService.ShowErrorAsync("测试失败", result.Message);
                }
            }
            catch (Exception ex)
            {
                await _dialogService.HideProgressAsync();
                await _dialogService.ShowErrorAsync("测试失败", ex.Message);
                Logger.Error(ex, "测试邮件设置失败");
            }
        }
        
        private async void ExecuteTestSmsAsync()
        {
            try
            {
                await _dialogService.ShowProgressAsync("测试短信", "正在发送测试短信...");
                
                // 发送测试短信
                var result = await _settingsService.TestSmsSettingsAsync(Settings.Notification.Sms);
                
                await _dialogService.HideProgressAsync();
                
                if (result.Success)
                {
                    await _dialogService.ShowInfoAsync("测试成功", "测试短信发送成功。");
                }
                else
                {
                    await _dialogService.ShowErrorAsync("测试失败", result.Message);
                }
            }
            catch (Exception ex)
            {
                await _dialogService.HideProgressAsync();
                await _dialogService.ShowErrorAsync("测试失败", ex.Message);
                Logger.Error(ex, "测试短信设置失败");
            }
        }
    }
}
设置服务实现

csharp

// SettingsService.cs
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using OpsManager.Client.Models;

namespace OpsManager.Client.Services
{
    public class SettingsService : ISettingsService
    {
        private readonly HttpClient _httpClient;
        private readonly ILocalStorageService _localStorageService;
        
        public SettingsService(
            HttpClient httpClient,
            ILocalStorageService localStorageService)
        {
            _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
            _localStorageService = localStorageService ?? throw new ArgumentNullException(nameof(localStorageService));
        }
        
        public async Task<SystemSettings> LoadSystemSettingsAsync()
        {
            try
            {
                // 先尝试从服务器加载设置
                var response = await _httpClient.GetAsync("api/settings/system");
                
                if (response.IsSuccessStatusCode)
                {
                    var content = await response.Content.ReadAsStringAsync();
                    var settings = JsonConvert.DeserializeObject<SystemSettings>(content);
                    
                    // 保存到本地缓存
                    await _localStorageService.SetItemAsync("SystemSettings", settings);
                    
                    return settings;
                }
                
                // 如果服务器请求失败,尝试从本地缓存加载
                var cachedSettings = await _localStorageService.GetItemAsync<SystemSettings>("SystemSettings");
                if (cachedSettings != null)
                {
                    return cachedSettings;
                }
                
                // 如果本地缓存也没有,返回默认设置
                return new SystemSettings
                {
                    ServerConnection = new ServerConnectionSettings(),
                    DisplaySettings = new ClientDisplaySettings(),
                    Localization = new LocalizationSettings(),
                    Monitoring = new MonitoringSettings(),
                    Notification = new NotificationSettings(),
                    Agent = new AgentSettings(),
                    Backup = new BackupSettings()
                };
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "加载系统设置失败");
                throw;
            }
        }
        
        public async Task SaveSystemSettingsAsync(SystemSettings settings)
        {
            try
            {
                var content = new StringContent(
                    JsonConvert.SerializeObject(settings),
                    Encoding.UTF8,
                    "application/json");
                
                var response = await _httpClient.PostAsync("api/settings/system", content);
                
                if (!response.IsSuccessStatusCode)
                {
                    var errorContent = await response.Content.ReadAsStringAsync();
                    throw new Exception($"保存设置失败: {errorContent}");
                }
                
                // 更新本地缓存
                await _localStorageService.SetItemAsync("SystemSettings", settings);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "保存系统设置失败");
                throw;
            }
        }
        
        public void SaveSetting<T>(string key, T value)
        {
            _localStorageService.SetItem(key, value);
        }
        
        public T GetSetting<T>(string key, T defaultValue = default)
        {
            return _localStorageService.GetItem<T>(key, defaultValue);
        }
        
        public async Task<TestResult> TestEmailSettingsAsync(EmailSettings settings)
        {
            try
            {
                var content = new StringContent(
                    JsonConvert.SerializeObject(settings),
                    Encoding.UTF8,
                    "application/json");
                
                var response = await _httpClient.PostAsync("api/settings/test-email", content);
                var responseContent = await response.Content.ReadAsStringAsync();
                
                if (response.IsSuccessStatusCode)
                {
                    return new TestResult { Success = true };
                }
                else
                {
                    var error = JsonConvert.DeserializeObject<ErrorResponse>(responseContent);
                    return new TestResult
                    {
                        Success = false,
                        Message = error?.Message ?? "测试邮件发送失败"
                    };
                }
            }
            catch (Exception ex)
            {
                return new TestResult
                {
                    Success = false,
                    Message = $"测试过程中发生错误: {ex.Message}"
                };
            }
        }
        
        public async Task<TestResult> TestSmsSettingsAsync(SmsSettings settings)
        {
            try
            {
                var content = new StringContent(
                    JsonConvert.SerializeObject(settings),
                    Encoding.UTF8,
                    "application/json");
                
                var response = await _httpClient.PostAsync("api/settings/test-sms", content);
                var responseContent = await response.Content.ReadAsStringAsync();
                
                if (response.IsSuccessStatusCode)
                {
                    return new TestResult { Success = true };
                }
                else
                {
                    var error = JsonConvert.DeserializeObject<ErrorResponse>(responseContent);
                    return new TestResult
                    {
                        Success = false,
                        Message = error?.Message ?? "测试短信发送失败"
                    };
                }
            }
            catch (Exception ex)
            {
                return new TestResult
                {
                    Success = false,
                    Message = $"测试过程中发生错误: {ex.Message}"
                };
            }
        }
    }
    
    public class TestResult
    {
        public bool Success { get; set; }
        public string Message { get; set; }
    }
}

16.5.3 服务器分类模块

服务器分类模块用于对服务器进行分类管理,便于批量操作和权限控制。

分类管理界面

服务器分类管理界面包括以下主要功能:

  1. 分类树形结构显示
  2. 分类的添加、编辑、删除功能
  3. 服务器的分配和移除功能
  4. 拖放操作支持
  5. 搜索和过滤功能
数据模型设计

csharp

// 服务器分类模型
public class ServerGroup
{
    public int GroupID { get; set; }
    public string GroupName { get; set; }
    public string Description { get; set; }
    public int? ParentGroupID { get; set; }
    public DateTime CreatedTime { get; set; }
    public DateTime UpdatedTime { get; set; }
    
    // 导航属性
    public ServerGroup ParentGroup { get; set; }
    public ICollection<ServerGroup> ChildGroups { get; set; }
    public ICollection<Server> Servers { get; set; }
}

// 树形节点模型(用于UI显示)
public class TreeNodeModel : ViewModelBase
{
    private bool _isExpanded;
    private bool _isSelected;
    
    public int ID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public NodeType Type { get; set; }
    public int? ParentID { get; set; }
    public int Count { get; set; }
    
    public bool IsExpanded
    {
        get => _isExpanded;
        set
        {
            _isExpanded = value;
            OnPropertyChanged();
        }
    }
    
    public bool IsSelected
    {
        get => _isSelected;
        set
        {
            _isSelected = value;
            OnPropertyChanged();
        }
    }
    
    public ObservableCollection<TreeNodeModel> Children { get; } = new ObservableCollection<TreeNodeModel>();
}

public enum NodeType
{
    Root,
    Group,
    Server
}
分类管理实现

csharp

// ServerGroupViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using OpsManager.Client.Core;
using OpsManager.Client.Models;
using OpsManager.Client.Services;

namespace OpsManager.Client.ViewModels
{
    public class ServerGroupViewModel : ViewModelBase
    {
        private readonly IServerGroupService _serverGroupService;
        private readonly IServerService _serverService;
        private readonly IDialogService _dialogService;
        
        private bool _isLoading;
        private string _searchText;
        private TreeNodeModel _selectedNode;
        private ObservableCollection<TreeNodeModel> _rootNodes;
        
        public bool IsLoading
        {
            get => _isLoading;
            set
            {
                _isLoading = value;
                OnPropertyChanged();
            }
        }
        
        public string SearchText
        {
            get => _searchText;
            set
            {
                _searchText = value;
                OnPropertyChanged();
                ApplySearch();
            }
        }
        
        public TreeNodeModel SelectedNode
        {
            get => _selectedNode;
            set
            {
                _selectedNode = value;
                OnPropertyChanged();
                
                // 更新命令可执行状态
                AddGroupCommand.RaiseCanExecuteChanged();
                EditGroupCommand.RaiseCanExecuteChanged();
                DeleteGroupCommand.RaiseCanExecuteChanged();
                AssignServersCommand.RaiseCanExecuteChanged();
            }
        }
        
        public ObservableCollection<TreeNodeModel> RootNodes
        {
            get => _rootNodes;
            set
            {
                _rootNodes = value;
                OnPropertyChanged();
            }
        }
        
        public RelayCommand LoadCommand { get; }
        public RelayCommand<TreeNodeModel> SelectNodeCommand { get; }
        public RelayCommand AddGroupCommand { get; }
        public RelayCommand EditGroupCommand { get; }
        public RelayCommand DeleteGroupCommand { get; }
        public RelayCommand AssignServersCommand { get; }
        
        public ServerGroupViewModel(
            IServerGroupService serverGroupService,
            IServerService serverService,
            IDialogService dialogService)
        {
            _serverGroupService = serverGroupService ?? throw new ArgumentNullException(nameof(serverGroupService));
            _serverService = serverService ?? throw new ArgumentNullException(nameof(serverService));
            _dialogService = dialogService ?? throw new ArgumentNullException(nameof(dialogService));
            
            RootNodes = new ObservableCollection<TreeNodeModel>();
            
            LoadCommand = new RelayCommand(ExecuteLoadAsync);
            SelectNodeCommand = new RelayCommand<TreeNodeModel>(ExecuteSelectNode);
            AddGroupCommand = new RelayCommand(ExecuteAddGroupAsync, CanAddGroup);
            EditGroupCommand = new RelayCommand(ExecuteEditGroupAsync, CanEditGroup);
            DeleteGroupCommand = new RelayCommand(ExecuteDeleteGroupAsync, CanDeleteGroup);
            AssignServersCommand = new RelayCommand(ExecuteAssignServersAsync, CanAssignServers);
            
            // 加载分类树
            ExecuteLoadAsync();
        }
        
        private async void ExecuteLoadAsync()
        {
            try
            {
                IsLoading = true;
                
                // 清除现有数据
                RootNodes.Clear();
                
                // 加载所有分类
                var groups = await _serverGroupService.GetAllGroupsAsync();
                
                // 构建树形结构
                var rootNode = new TreeNodeModel
                {
                    ID = 0,
                    Name = "所有服务器",
                    Type = NodeType.Root,
                    IsExpanded = true
                };
                
                RootNodes.Add(rootNode);
                
                // 添加顶级分类
                var topGroups = groups.Where(g => g.ParentGroupID == null).ToList();
                foreach (var group in topGroups)
                {
                    var node = CreateGroupNode(group);
                    rootNode.Children.Add(node);
                    
                    // 递归添加子分类
                    AddChildGroups(node, groups);
                }
                
                // 统计每个分类的服务器数量
                await UpdateGroupCounts();
            }
            catch (Exception ex)
            {
                await _dialogService.ShowErrorAsync("加载失败", $"加载服务器分类失败: {ex.Message}");
                Logger.Error(ex, "加载服务器分类失败");
            }
            finally
            {
                IsLoading = false;
            }
        }
        
        private TreeNodeModel CreateGroupNode(ServerGroup group)
        {
            return new TreeNodeModel
            {
                ID = group.GroupID,
                Name = group.GroupName,
                Description = group.Description,
                Type = NodeType.Group,
                ParentID = group.ParentGroupID
            };
        }
        
        private void AddChildGroups(TreeNodeModel parentNode, IEnumerable<ServerGroup> allGroups)
        {
            var childGroups = allGroups.Where(g => g.ParentGroupID == parentNode.ID).ToList();
            
            foreach (var group in childGroups)
            {
                var node = CreateGroupNode(group);
                parentNode.Children.Add(node);
                
                // 递归处理子分类
                AddChildGroups(node, allGroups);
            }
        }
        
        private async Task UpdateGroupCounts()
        {
            try
            {
                // 获取分类的服务器计数
                var counts = await _serverGroupService.GetGroupServerCountsAsync();
                
                // 更新分类节点的计数
                UpdateNodeCounts(RootNodes, counts);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "更新分类服务器计数失败");
            }
        }
        
        private void UpdateNodeCounts(IEnumerable<TreeNodeModel> nodes, Dictionary<int, int> counts)
        {
            foreach (var node in nodes)
            {
                if (node.Type == NodeType.Group && counts.ContainsKey(node.ID))
                {
                    node.Count = counts[node.ID];
                }
                
                // 递归处理子节点
                UpdateNodeCounts(node.Children, counts);
            }
        }
        
        private void ExecuteSelectNode(TreeNodeModel node)
        {
            if (node != null)
            {
                // 取消之前选中的节点
                if (SelectedNode != null)
                {
                    SelectedNode.IsSelected = false;
                }
                
                // 选中当前节点
                node.IsSelected = true;
                SelectedNode = node;
            }
        }
        
        private bool CanAddGroup()
        {
            // 可以添加根级分类,或者在任意分类下添加子分类
            return true;
        }
        
        private async void ExecuteAddGroupAsync()
        {
            try
            {
                // 准备父分类ID
                int? parentId = null;
                string parentName = "根级";
                
                if (SelectedNode != null && SelectedNode.Type == NodeType.Group)
                {
                    parentId = SelectedNode.ID;
                    parentName = SelectedNode.Name;
                }
                
                // 显示添加分类对话框
                var model = new GroupEditModel
                {
                    ParentID = parentId,
                    ParentName = parentName
                };
                
                var result = await _dialogService.ShowGroupEditDialogAsync("添加分类", model);
                
                if (result.Confirmed)
                {
                    // 创建新分类
                    var newGroup = new ServerGroup
                    {
                        GroupName = result.Model.Name,
                        Description = result.Model.Description,
                        ParentGroupID = result.Model.ParentID
                    };
                    
                    var createdGroup = await _serverGroupService.CreateGroupAsync(newGroup);
                    
                    // 重新加载分类树
                    ExecuteLoadAsync();
                }
            }
            catch (Exception ex)
            {
                await _dialogService.ShowErrorAsync("添加失败", $"添加服务器分类失败: {ex.Message}");
                Logger.Error(ex, "添加服务器分类失败");
            }
        }
        
        private bool CanEditGroup()
        {
            // 只能编辑分类节点
            return SelectedNode != null && SelectedNode.Type == NodeType.Group;
        }
        
        private async void ExecuteEditGroupAsync()
        {
            try
            {
                if (SelectedNode == null || SelectedNode.Type != NodeType.Group)
                {
                    return;
                }
                
                // 获取分类详情
                var group = await _serverGroupService.GetGroupByIdAsync(SelectedNode.ID);
                
                // 显示编辑分类对话框
                var model = new GroupEditModel
                {
                    ID = group.GroupID,
                    Name = group.GroupName,
                    Description = group.Description,
                    ParentID = group.ParentGroupID
                };
                
                // 如果有父分类,获取父分类名称
                if (group.ParentGroupID.HasValue)
                {
                    var parentGroup = await _serverGroupService.GetGroupByIdAsync(group.ParentGroupID.Value);
                    model.ParentName = parentGroup?.GroupName ?? "未知";
                }
                else
                {
                    model.ParentName = "根级";
                }
                
                var result = await _dialogService.ShowGroupEditDialogAsync("编辑分类", model);
                
                if (result.Confirmed)
                {
                    // 更新分类
                    group.GroupName = result.Model.Name;
                    group.Description = result.Model.Description;
                    
                    await _serverGroupService.UpdateGroupAsync(group);
                    
                    // 重新加载分类树
                    ExecuteLoadAsync();
                }
            }
            catch (Exception ex)
            {
                await _dialogService.ShowErrorAsync("编辑失败", $"编辑服务器分类失败: {ex.Message}");
                Logger.Error(ex, "编辑服务器分类失败");
            }
        }
        
        private bool CanDeleteGroup()
        {
            // 只能删除分类节点
            return SelectedNode != null && SelectedNode.Type == NodeType.Group;
        }
        
        private async void ExecuteDeleteGroupAsync()
        {
            try
            {
                if (SelectedNode == null || SelectedNode.Type != NodeType.Group)
                {
                    return;
                }
                
                // 确认删除
                var confirmed = await _dialogService.ShowConfirmationAsync(
                    "删除分类",
                    $"确定要删除分类 \"{SelectedNode.Name}\" 吗?其中的服务器将不会被删除,但会从该分类中移除。");
                
                if (confirmed)
                {
                    await _serverGroupService.DeleteGroupAsync(SelectedNode.ID);
                    
                    // 重新加载分类树
                    ExecuteLoadAsync();
                }
            }
            catch (Exception ex)
            {
                await _dialogService.ShowErrorAsync("删除失败", $"删除服务器分类失败: {ex.Message}");
                Logger.Error(ex, "删除服务器分类失败");
            }
        }
        
        private bool CanAssignServers()
        {
            // 只能为分类节点分配服务器
            return SelectedNode != null && SelectedNode.Type == NodeType.Group;
        }
        
        private async void ExecuteAssignServersAsync()
        {
            try
            {
                if (SelectedNode == null || SelectedNode.Type != NodeType.Group)
                {
                    return;
                }
                
                // 获取分类中的服务器
                var groupServers = await _serverGroupService.GetServersInGroupAsync(SelectedNode.ID);
                
                // 获取所有服务器
                var allServers = await _serverService.GetAllServersAsync();
                
                // 准备服务器选择模型
                var model = new ServerSelectionModel
                {
                    GroupID = SelectedNode.ID,
                    GroupName = SelectedNode.Name,
                    AllServers = allServers.Select(s => new ServerViewModel
                    {
                        ID = s.ServerID,
                        Hostname = s.Hostname,
                        IPAddress = s.IPAddress,
                        OSType = s.OSType,
                        Status = s.Status,
                        IsSelected = groupServers.Any(gs => gs.ServerID == s.ServerID)
                    }).ToList()
                };
                
                // 显示服务器选择对话框
                var result = await _dialogService.ShowServerSelectionDialogAsync("分配服务器", model);
                
                if (result.Confirmed)
                {
                    // 获取选中的服务器ID
                    var selectedServerIds = result.Model.AllServers
                        .Where(s => s.IsSelected)
                        .Select(s => s.ID)
                        .ToList();
                    
                    // 更新分类的服务器
                    await _serverGroupService.UpdateGroupServersAsync(SelectedNode.ID, selectedServerIds);
                    
                    // 更新分类计数
                    await UpdateGroupCounts();
                }
            }
            catch (Exception ex)
            {
                await _dialogService.ShowErrorAsync("分配失败", $"分配服务器失败: {ex.Message}");
                Logger.Error(ex, "分配服务器到分类失败");
            }
        }
        
        private void ApplySearch()
        {
            // 如果搜索文本为空,显示所有节点
            if (string.IsNullOrWhiteSpace(SearchText))
            {
                ShowAllNodes(RootNodes);
                return;
            }
            
            // 否则根据搜索文本过滤节点
            FilterNodes(RootNodes, SearchText.ToLower());
        }
        
        private void ShowAllNodes(IEnumerable<TreeNodeModel> nodes)
        {
            foreach (var node in nodes)
            {
                node.IsExpanded = false;
                
                if (node.Children.Any())
                {
                    ShowAllNodes(node.Children);
                }
            }
        }
        
        private bool FilterNodes(IEnumerable<TreeNodeModel> nodes, string searchText)
        {
            bool anyVisible = false;
            
            foreach (var node in nodes)
            {
                // 检查当前节点是否匹配
                bool isMatch = node.Name.ToLower().Contains(searchText) ||
                               (node.Description?.ToLower().Contains(searchText) ?? false);
                
                // 递归检查子节点
                bool hasVisibleChildren = node.Children.Any() && FilterNodes(node.Children, searchText);
                
                // 如果当前节点或其子节点有匹配项,显示节点
                if (isMatch || hasVisibleChildren)
                {
                    anyVisible = true;
                    
                    // 展开包含匹配项的父节点
                    if (hasVisibleChildren)
                    {
                        node.IsExpanded = true;
                    }
                }
            }
            
            return anyVisible;
        }
    }
    
    public class GroupEditModel
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public int? ParentID { get; set; }
        public string ParentName { get; set; }
    }
    
    public class ServerSelectionModel
    {
        public int GroupID { get; set; }
        public string GroupName { get; set; }
        public List<ServerViewModel> AllServers { get; set; }
    }
    
    public class ServerViewModel
    {
        public int ID { get; set; }
        public string Hostname { get; set; }
        public string IPAddress { get; set; }
        public string OSType { get; set; }
        public string Status { get; set; }
        public bool IsSelected { get; set; }
    }
}

16.5.4 系统升级功能

系统升级功能负责客户端和代理程序的版本管理和升级。

升级流程设计

系统升级流程包括以下步骤:

  1. 版本检测

    • 客户端定期检查服务器是否有新版本
    • 代理程序向服务器报告自身版本
  2. 版本比较

    • 比较本地版本和服务器版本
    • 判断是否需要升级
  3. 下载升级包

    • 下载适用于当前系统的升级包
    • 验证下载包的完整性和签名
  4. 安装升级

    • 备份当前版本
    • 安装新版本
    • 迁移配置和数据
  5. 升级验证

    • 验证升级是否成功
    • 报告升级结果
客户端升级实现

csharp

// UpdateService.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Newtonsoft.Json;
using OpsManager.Client.Models;
using OpsManager.Client.Utils;

namespace OpsManager.Client.Services
{
    public class UpdateService : IUpdateService
    {
        private readonly HttpClient _httpClient;
        private readonly ISettingsService _settingsService;
        private readonly IDialogService _dialogService;
        
        public UpdateService(
            HttpClient httpClient,
            ISettingsService settingsService,
            IDialogService dialogService)
        {
            _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
            _settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
            _dialogService = dialogService ?? throw new ArgumentNullException(nameof(dialogService));
        }
        
        public async Task<UpdateCheckResult> CheckForUpdatesAsync()
        {
            try
            {
                // 获取当前版本
                var currentVersion = AppInfo.Version;
                
                // 请求服务器版本信息
                var response = await _httpClient.GetAsync("api/updates/check?version=" + currentVersion);
                
                if (!response.IsSuccessStatusCode)
                {
                    return new UpdateCheckResult
                    {
                        HasUpdate = false,
                        Message = "检查更新失败,服务器返回错误。"
                    };
                }
                
                var content = await response.Content.ReadAsStringAsync();
                var updateInfo = JsonConvert.DeserializeObject<UpdateInfo>(content);
                
                // 比较版本
                if (updateInfo.HasUpdate)
                {
                    return new UpdateCheckResult
                    {
                        HasUpdate = true,
                        LatestVersion = updateInfo.Version,
                        ReleaseNotes = updateInfo.ReleaseNotes,
                        DownloadUrl = updateInfo.DownloadUrl,
                        IsRequired = updateInfo.IsRequired,
                        PublishDate = updateInfo.PublishDate,
                        FileSize = updateInfo.FileSize,
                        Checksum = updateInfo.Checksum
                    };
                }
                
                return new UpdateCheckResult
                {
                    HasUpdate = false,
                    Message = "当前版本已是最新。"
                };
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "检查更新失败");
                
                return new UpdateCheckResult
                {
                    HasUpdate = false,
                    Message = $"检查更新失败: {ex.Message}"
                };
            }
        }
        
        public async Task<UpdateResult> DownloadAndInstallUpdateAsync(UpdateCheckResult updateInfo, IProgress<ProgressInfo> progress)
        {
            try
            {
                // 报告进度:开始下载
                progress?.Report(new ProgressInfo { Status = "正在下载更新...", Percentage = 0 });
                
                // 创建临时目录
                var tempDir = Path.Combine(Path.GetTempPath(), "OpsManagerUpdate");
                Directory.CreateDirectory(tempDir);
                
                // 下载文件路径
                var downloadPath = Path.Combine(tempDir, "update.zip");
                
                // 下载更新文件
                using (var response = await _httpClient.GetAsync(updateInfo.DownloadUrl, HttpCompletionOption.ResponseHeadersRead))
                {
                    response.EnsureSuccessStatusCode();
                    
                    var totalBytes = response.Content.Headers.ContentLength ?? 0;
                    var buffer = new byte[8192];
                    var bytesRead = 0L;
                    
                    using (var fileStream = new FileStream(downloadPath, FileMode.Create, FileAccess.Write, FileShare.None))
                    using (var stream = await response.Content.ReadAsStreamAsync())
                    {
                        int read;
                        while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                        {
                            await fileStream.WriteAsync(buffer, 0, read);
                            
                            bytesRead += read;
                            var percentage = totalBytes > 0 ? (int)(bytesRead * 100 / totalBytes) : 0;
                            
                            // 报告下载进度
                            progress?.Report(new ProgressInfo
                            {
                                Status = $"正在下载更新... {BytesFormatter.Format(bytesRead)} / {BytesFormatter.Format(totalBytes)}",
                                Percentage = percentage
                            });
                        }
                    }
                }
                
                // 报告进度:验证文件
                progress?.Report(new ProgressInfo { Status = "正在验证文件...", Percentage = 50 });
                
                // 验证文件完整性
                if (!await VerifyFileChecksumAsync(downloadPath, updateInfo.Checksum))
                {
                    return new UpdateResult
                    {
                        Success = false,
                        Message = "文件验证失败,可能已损坏。"
                    };
                }
                
                // 报告进度:准备安装
                progress?.Report(new ProgressInfo { Status = "正在准备安装...", Percentage = 60 });
                
                // 解压更新文件
                var extractPath = Path.Combine(tempDir, "extracted");
                Directory.CreateDirectory(extractPath);
                
                System.IO.Compression.ZipFile.ExtractToDirectory(downloadPath, extractPath);
                
                // 报告进度:安装更新
                progress?.Report(new ProgressInfo { Status = "正在安装更新...", Percentage = 70 });
                
                // 准备更新器程序路径
                var updaterPath = Path.Combine(extractPath, "Updater.exe");
                
                if (!File.Exists(updaterPath))
                {
                    return new UpdateResult
                    {
                        Success = false,
                        Message = "更新包不完整,缺少更新器程序。"
                    };
                }
                
                // 创建参数
                var arguments = $"--app-path \"{AppInfo.ExecutablePath}\" --update-path \"{extractPath}\" --version \"{updateInfo.LatestVersion}\"";
                
                // 启动更新器
                var processStartInfo = new ProcessStartInfo
                {
                    FileName = updaterPath,
                    Arguments = arguments,
                    UseShellExecute = true
                };
                
                Process.Start(processStartInfo);
                
                // 关闭当前应用程序
                progress?.Report(new ProgressInfo { Status = "更新器已启动,应用程序将重启...", Percentage = 100 });
                
                // 返回成功结果
                return new UpdateResult
                {
                    Success = true,
                    Message = "更新器已启动,应用程序将重启。"
                };
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "下载或安装更新失败");
                
                return new UpdateResult
                {
                    Success = false,
                    Message = $"更新失败: {ex.Message}"
                };
            }
        }
        
        private async Task<bool> VerifyFileChecksumAsync(string filePath, string expectedChecksum)
        {
            try
            {
                using (var stream = File.OpenRead(filePath))
                using (var sha256 = SHA256.Create())
                {
                    var hashBytes = await sha256.ComputeHashAsync(stream);
                    var hash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
                    
                    return string.Equals(hash, expectedChecksum, StringComparison.OrdinalIgnoreCase);
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "验证文件校验和失败");
                return false;
            }
        }
    }
    
    public class UpdateCheckResult
    {
        public bool HasUpdate { get; set; }
        public string LatestVersion { get; set; }
        public string ReleaseNotes { get; set; }
        public string DownloadUrl { get; set; }
        public bool IsRequired { get; set; }
        public DateTime PublishDate { get; set; }
        public long FileSize { get; set; }
        public string Checksum { get; set; }
        public string Message { get; set; }
    }
    
    public class UpdateResult
    {
        public bool Success { get; set; }
        public string Message { get; set; }
    }
    
    public class UpdateInfo
    {
        public bool HasUpdate { get; set; }
        public string Version { get; set; }
        public string ReleaseNotes { get; set; }
        public string DownloadUrl { get; set; }
        public bool IsRequired { get; set; }
        public DateTime PublishDate { get; set; }
        public long FileSize { get; set; }
        public string Checksum { get; set; }
    }
    
    public class ProgressInfo
    {
        public string Status { get; set; }
        public int Percentage { get; set; }
    }
}
代理升级实现

csharp

// AgentUpgradeService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using OpsManager.Client.Models;
using OpsManager.Client.Utils;

namespace OpsManager.Client.Services
{
    public class AgentUpgradeService : IAgentUpgradeService
    {
        private readonly HttpClient _httpClient;
        private readonly IDialogService _dialogService;
        
        public AgentUpgradeService(
            HttpClient httpClient,
            IDialogService dialogService)
        {
            _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
            _dialogService = dialogService ?? throw new ArgumentNullException(nameof(dialogService));
        }
        
        public async Task<IEnumerable<AgentUpgradeInfo>> GetAgentsNeedingUpgradeAsync()
        {
            try
            {
                var response = await _httpClient.GetAsync("api/agents/needing-upgrade");
                
                if (!response.IsSuccessStatusCode)
                {
                    return Enumerable.Empty<AgentUpgradeInfo>();
                }
                
                var content = await response.Content.ReadAsStringAsync();
                var agents = JsonConvert.DeserializeObject<List<AgentUpgradeInfo>>(content);
                
                return agents;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "获取需要升级的代理失败");
                return Enumerable.Empty<AgentUpgradeInfo>();
            }
        }
        
        public async Task<UpgradeResult> UpgradeAgentsAsync(IEnumerable<int> serverIds, IProgress<ProgressInfo> progress)
        {
            try
            {
                // 报告进度:开始升级
                progress?.Report(new ProgressInfo { Status = "正在准备升级代理...", Percentage = 0 });
                
                var serverIdList = serverIds.ToList();
                
                // 获取服务器详情
                var serversResponse = await _httpClient.PostAsync(
                    "api/servers/batch",
                    new StringContent(JsonConvert.SerializeObject(serverIdList), Encoding.UTF8, "application/json"));
                
                if (!serversResponse.IsSuccessStatusCode)
                {
                    return new UpgradeResult
                    {
                        Success = false,
                        Message = "获取服务器信息失败"
                    };
                }
                
                var serversContent = await serversResponse.Content.ReadAsStringAsync();
                var servers = JsonConvert.DeserializeObject<List<Server>>(serversContent);
                
                // 报告进度:开始升级
                progress?.Report(new ProgressInfo
                {
                    Status = $"正在升级 {servers.Count} 台服务器的代理...",
                    Percentage = 10
                });
                
                // 发送升级请求
                var upgradeResponse = await _httpClient.PostAsync(
                    "api/agents/upgrade",
                    new StringContent(JsonConvert.SerializeObject(serverIdList), Encoding.UTF8, "application/json"));
                
                if (!upgradeResponse.IsSuccessStatusCode)
                {
                    var errorContent = await upgradeResponse.Content.ReadAsStringAsync();
                    return new UpgradeResult
                    {
                        Success = false,
                        Message = $"升级请求失败: {errorContent}"
                    };
                }
                
                var upgradeContent = await upgradeResponse.Content.ReadAsStringAsync();
                var upgradeResult = JsonConvert.DeserializeObject<UpgradeTaskResult>(upgradeContent);
                
                // 报告进度:等待升级结果
                progress?.Report(new ProgressInfo
                {
                    Status = "升级任务已提交,正在等待结果...",
                    Percentage = 30
                });
                
                // 轮询升级任务状态
                var taskId = upgradeResult.TaskId;
                var isCompleted = false;
                var totalSteps = 10;
                var currentStep = 0;
                
                while (!isCompleted && currentStep < totalSteps)
                {
                    // 等待一段时间
                    await Task.Delay(3000);
                    currentStep++;
                    
                    // 查询任务状态
                    var statusResponse = await _httpClient.GetAsync($"api/tasks/{taskId}/status");
                    
                    if (statusResponse.IsSuccessStatusCode)
                    {
                        var statusContent = await statusResponse.Content.ReadAsStringAsync();
                        var taskStatus = JsonConvert.DeserializeObject<TaskStatus>(statusContent);
                        
                        if (taskStatus.IsCompleted)
                        {
                            isCompleted = true;
                            
                            // 报告进度:完成
                            progress?.Report(new ProgressInfo
                            {
                                Status = "代理升级已完成",
                                Percentage = 100
                            });
                            
                            return new UpgradeResult
                            {
                                Success = true,
                                Message = $"已成功升级 {taskStatus.SuccessCount} 台服务器的代理,失败 {taskStatus.FailureCount} 台",
                                SuccessCount = taskStatus.SuccessCount,
                                FailureCount = taskStatus.FailureCount,
                                Details = taskStatus.Details
                            };
                        }
                        else
                        {
                            // 报告进度:进行中
                            var percentage = 30 + (currentStep * 70 / totalSteps);
                            progress?.Report(new ProgressInfo
                            {
                                Status = $"正在升级代理 ({taskStatus.SuccessCount}/{serverIdList.Count} 完成)...",
                                Percentage = percentage
                            });
                        }
                    }
                }
                
                // 如果超时,返回未完成状态
                return new UpgradeResult
                {
                    Success = true,
                    Message = "代理升级任务已提交,但尚未完成。请稍后检查结果。",
                    TaskId = taskId
                };
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "升级代理失败");
                
                return new UpgradeResult
                {
                    Success = false,
                    Message = $"升级代理时发生错误: {ex.Message}"
                };
            }
        }
    }
    
    public class AgentUpgradeInfo
    {
        public int ServerID { get; set; }
        public string Hostname { get; set; }
        public string IPAddress { get; set; }
        public string CurrentVersion { get; set; }
        public string LatestVersion { get; set; }
        public string OSType { get; set; }
        public string Status { get; set; }
    }
    
    public class UpgradeResult
    {
        public bool Success { get; set; }
        public string Message { get; set; }
        public int SuccessCount { get; set; }
        public int FailureCount { get; set; }
        public string TaskId { get; set; }
        public List<string> Details { get; set; }
    }
    
    public class UpgradeTaskResult
    {
        public string TaskId { get; set; }
        public string Message { get; set; }
    }
    
    public class TaskStatus
    {
        public bool IsCompleted { get; set; }
        public int SuccessCount { get; set; }
        public int FailureCount { get; set; }
        public List<string> Details { get; set; }
    }
}

16.5.5 客户端模块编写

客户端模块是用户与系统交互的界面,采用WPF技术实现,支持丰富的交互体验。

主窗口设计

主窗口包含以下主要元素:

  1. 顶部菜单栏和工具栏
  2. 左侧导航菜单
  3. 主内容区域
  4. 底部状态栏

xaml

<!-- MainWindow.xaml -->
<Window x:Class="OpsManager.Client.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OpsManager.Client.Views"
        xmlns:vm="clr-namespace:OpsManager.Client.ViewModels"
        xmlns:controls="clr-namespace:OpsManager.Client.Controls"
        mc:Ignorable="d"
        Title="{Binding WindowTitle}" 
        Height="768" Width="1024" 
        MinHeight="600" MinWidth="800"
        Icon="/Resources/Images/app_icon.ico"
        WindowStartupLocation="CenterScreen">
    
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Resources/Styles/MainStyles.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        
        <!-- 顶部菜单和工具栏 -->
        <Grid Grid.Row="0">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            
            <!-- 主菜单 -->
            <Menu Grid.Row="0" Style="{StaticResource MainMenu}">
                <MenuItem Header="文件">
                    <MenuItem Header="导出数据" Command="{Binding ExportDataCommand}"/>
                    <MenuItem Header="导入数据" Command="{Binding ImportDataCommand}"/>
                    <Separator/>
                    <MenuItem Header="打印" Command="{Binding PrintCommand}"/>
                    <Separator/>
                    <MenuItem Header="退出" Command="{Binding ExitCommand}"/>
                </MenuItem>
                <MenuItem Header="视图">
                    <MenuItem Header="刷新" Command="{Binding RefreshCommand}" InputGestureText="F5"/>
                    <MenuItem Header="全屏" Command="{Binding ToggleFullScreenCommand}" InputGestureText="F11"/>
                    <Separator/>
                    <MenuItem Header="状态栏" IsCheckable="True" IsChecked="{Binding ShowStatusBar}"/>
                </MenuItem>
                <MenuItem Header="工具">
                    <MenuItem Header="系统设置" Command="{Binding OpenSettingsCommand}"/>
                    <MenuItem Header="检查更新" Command="{Binding CheckUpdateCommand}"/>
                    <Separator/>
                    <MenuItem Header="日志查看器" Command="{Binding OpenLogViewerCommand}"/>
                </MenuItem>
                <MenuItem Header="帮助">
                    <MenuItem Header="帮助文档" Command="{Binding OpenHelpCommand}"/>
                    <MenuItem Header="在线支持" Command="{Binding OpenSupportCommand}"/>
                    <Separator/>
                    <MenuItem Header="关于" Command="{Binding OpenAboutCommand}"/>
                </MenuItem>
            </Menu>
            
            <!-- 工具栏 -->
            <ToolBar Grid.Row="1" Style="{StaticResource MainToolBar}">
                <Button Command="{Binding RefreshCommand}" ToolTip="刷新">
                    <Image Source="/Resources/Images/refresh.png" Width="16" Height="16"/>

                </Button>
                <Separator/>
                <Button Command="{Binding AddServerCommand}" ToolTip="添加服务器">
                    <Image Source="/Resources/Images/add_server.png" Width="16" Height="16"/>
                </Button>
                <Button Command="{Binding ManageGroupsCommand}" ToolTip="管理分组">
                    <Image Source="/Resources/Images/groups.png" Width="16" Height="16"/>
                </Button>
                <Separator/>
                <Button Command="{Binding StartTaskCommand}" ToolTip="执行任务">
                    <Image Source="/Resources/Images/task_run.png" Width="16" Height="16"/>
                </Button>
                <Button Command="{Binding CreateTaskCommand}" ToolTip="创建任务">
                    <Image Source="/Resources/Images/task_new.png" Width="16" Height="16"/>
                </Button>
                <Separator/>
                <Button Command="{Binding OpenSettingsCommand}" ToolTip="系统设置">
                    <Image Source="/Resources/Images/settings.png" Width="16" Height="16"/>
                </Button>
                <Button Command="{Binding OpenHelpCommand}" ToolTip="帮助">
                    <Image Source="/Resources/Images/help.png" Width="16" Height="16"/>
                </Button>
            </ToolBar>
        </Grid>
        
        <!-- 主内容区 -->
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="250" MinWidth="200"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            
            <!-- 左侧导航菜单 -->
            <Grid Grid.Column="0" Background="{StaticResource NavigationBackground}">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>
                
                <!-- 用户信息 -->
                <Border Grid.Row="0" Padding="10" BorderThickness="0 0 0 1" BorderBrush="{StaticResource BorderBrush}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        
                        <Ellipse Grid.Column="0" Width="40" Height="40" Margin="0 0 10 0">
                            <Ellipse.Fill>
                                <ImageBrush ImageSource="{Binding UserAvatar}"/>
                            </Ellipse.Fill>
                        </Ellipse>
                        
                        <StackPanel Grid.Column="1" VerticalAlignment="Center">
                            <TextBlock Text="{Binding UserName}" FontWeight="Bold"/>
                            <TextBlock Text="{Binding UserRole}" Foreground="{StaticResource SubtleBrush}"/>
                        </StackPanel>
                    </Grid>
                </Border>
                
                <!-- 导航菜单 -->
                <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
                    <ItemsControl ItemsSource="{Binding NavigationItems}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <controls:NavigationItem 
                                    Icon="{Binding Icon}"
                                    Title="{Binding Title}" 
                                    IsSelected="{Binding IsSelected}"
                                    Command="{Binding Command}"
                                    Badge="{Binding Badge}"/>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </ScrollViewer>
            </Grid>
            
            <!-- 分隔线 -->
            <GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" Background="Transparent"/>
            
            <!-- 内容区域 -->
            <Border Grid.Column="2" Background="{StaticResource ContentBackground}" Padding="10">
                <ContentControl Content="{Binding CurrentView}"/>
            </Border>
        </Grid>
        
        <!-- 底部状态栏 -->
        <StatusBar Grid.Row="2" Visibility="{Binding ShowStatusBar, Converter={StaticResource BooleanToVisibilityConverter}}">
            <StatusBar.ItemsPanel>
                <ItemsPanelTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                    </Grid>
                </ItemsPanelTemplate>
            </StatusBar.ItemsPanel>
            
            <!-- 连接状态 -->
            <StatusBarItem Grid.Column="0">
                <StackPanel Orientation="Horizontal">
                    <Ellipse Width="10" Height="10" Margin="0 0 5 0" Fill="{Binding ConnectionStatus, Converter={StaticResource ConnectionStatusToColorConverter}}"/>
                    <TextBlock Text="{Binding ConnectionStatusText}"/>
                </StackPanel>
            </StatusBarItem>
            
            <!-- 消息 -->
            <StatusBarItem Grid.Column="1">
                <TextBlock Text="{Binding StatusMessage}"/>
            </StatusBarItem>
            
            <!-- 服务器计数 -->
            <StatusBarItem Grid.Column="2">
                <TextBlock Text="{Binding ServerStatusSummary}"/>
            </StatusBarItem>
            
            <!-- 版本信息 -->
            <StatusBarItem Grid.Column="3">
                <TextBlock Text="{Binding VersionInfo}"/>
            </StatusBarItem>
        </StatusBar>
    </Grid>
</Window>
主窗口视图模型

csharp

// MainWindowViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input;
using OpsManager.Client.Core;
using OpsManager.Client.Models;
using OpsManager.Client.Services;
using OpsManager.Client.Utils;
using OpsManager.Client.Views;

namespace OpsManager.Client.ViewModels
{
    public class MainWindowViewModel : ViewModelBase
    {
        private readonly INavigationService _navigationService;
        private readonly IAuthService _authService;
        private readonly IDialogService _dialogService;
        private readonly ISettingsService _settingsService;
        private readonly IUpdateService _updateService;
        private readonly IServerService _serverService;
        
        private string _windowTitle;
        private string _userName;
        private string _userRole;
        private string _userAvatar;
        private object _currentView;
        private bool _showStatusBar;
        private ConnectionStatus _connectionStatus;
        private string _statusMessage;
        private string _serverStatusSummary;
        private string _versionInfo;
        
        public string WindowTitle
        {
            get => _windowTitle;
            set
            {
                _windowTitle = value;
                OnPropertyChanged();
            }
        }
        
        public string UserName
        {
            get => _userName;
            set
            {
                _userName = value;
                OnPropertyChanged();
            }
        }
        
        public string UserRole
        {
            get => _userRole;
            set
            {
                _userRole = value;
                OnPropertyChanged();
            }
        }
        
        public string UserAvatar
        {
            get => _userAvatar;
            set
            {
                _userAvatar = value;
                OnPropertyChanged();
            }
        }
        
        public object CurrentView
        {
            get => _currentView;
            set
            {
                _currentView = value;
                OnPropertyChanged();
            }
        }
        
        public bool ShowStatusBar
        {
            get => _showStatusBar;
            set
            {
                _showStatusBar = value;
                OnPropertyChanged();
                _settingsService.SaveSetting("ShowStatusBar", value);
            }
        }
        
        public ConnectionStatus ConnectionStatus
        {
            get => _connectionStatus;
            set
            {
                _connectionStatus = value;
                OnPropertyChanged();
                OnPropertyChanged(nameof(ConnectionStatusText));
            }
        }
        
        public string ConnectionStatusText
        {
            get
            {
                return ConnectionStatus switch
                {
                    ConnectionStatus.Connected => "已连接",
                    ConnectionStatus.Connecting => "正在连接...",
                    ConnectionStatus.Disconnected => "未连接",
                    _ => "未知状态"
                };
            }
        }
        
        public string StatusMessage
        {
            get => _statusMessage;
            set
            {
                _statusMessage = value;
                OnPropertyChanged();
            }
        }
        
        public string ServerStatusSummary
        {
            get => _serverStatusSummary;
            set
            {
                _serverStatusSummary = value;
                OnPropertyChanged();
            }
        }
        
        public string VersionInfo
        {
            get => _versionInfo;
            set
            {
                _versionInfo = value;
                OnPropertyChanged();
            }
        }
        
        public ObservableCollection<NavigationItemViewModel> NavigationItems { get; } = new ObservableCollection<NavigationItemViewModel>();
        
        public ICommand RefreshCommand { get; }
        public ICommand ExportDataCommand { get; }
        public ICommand ImportDataCommand { get; }
        public ICommand PrintCommand { get; }
        public ICommand ExitCommand { get; }
        public ICommand ToggleFullScreenCommand { get; }
        public ICommand OpenSettingsCommand { get; }
        public ICommand CheckUpdateCommand { get; }
        public ICommand OpenLogViewerCommand { get; }
        public ICommand OpenHelpCommand { get; }
        public ICommand OpenSupportCommand { get; }
        public ICommand OpenAboutCommand { get; }
        public ICommand AddServerCommand { get; }
        public ICommand ManageGroupsCommand { get; }
        public ICommand StartTaskCommand { get; }
        public ICommand CreateTaskCommand { get; }
        
        public MainWindowViewModel(
            INavigationService navigationService,
            IAuthService authService,
            IDialogService dialogService,
            ISettingsService settingsService,
            IUpdateService updateService,
            IServerService serverService)
        {
            _navigationService = navigationService ?? throw new ArgumentNullException(nameof(navigationService));
            _authService = authService ?? throw new ArgumentNullException(nameof(authService));
            _dialogService = dialogService ?? throw new ArgumentNullException(nameof(dialogService));
            _settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
            _updateService = updateService ?? throw new ArgumentNullException(nameof(updateService));
            _serverService = serverService ?? throw new ArgumentNullException(nameof(serverService));
            
            // 初始化命令
            RefreshCommand = new RelayCommand(ExecuteRefresh);
            ExportDataCommand = new RelayCommand(ExecuteExportData);
            ImportDataCommand = new RelayCommand(ExecuteImportData);
            PrintCommand = new RelayCommand(ExecutePrint);
            ExitCommand = new RelayCommand(ExecuteExit);
            ToggleFullScreenCommand = new RelayCommand(ExecuteToggleFullScreen);
            OpenSettingsCommand = new RelayCommand(ExecuteOpenSettings);
            CheckUpdateCommand = new RelayCommand(ExecuteCheckUpdateAsync);
            OpenLogViewerCommand = new RelayCommand(ExecuteOpenLogViewer);
            OpenHelpCommand = new RelayCommand(ExecuteOpenHelp);
            OpenSupportCommand = new RelayCommand(ExecuteOpenSupport);
            OpenAboutCommand = new RelayCommand(ExecuteOpenAbout);
            AddServerCommand = new RelayCommand(ExecuteAddServer);
            ManageGroupsCommand = new RelayCommand(ExecuteManageGroups);
            StartTaskCommand = new RelayCommand(ExecuteStartTask);
            CreateTaskCommand = new RelayCommand(ExecuteCreateTask);
            
            // 初始化导航菜单
            InitializeNavigationItems();
            
            // 设置初始视图
            NavigateTo("Dashboard");
            
            // 初始化窗口标题
            WindowTitle = $"运维管理平台 - {AppInfo.Version}";
            
            // 加载用户信息
            LoadUserInfo();
            
            // 加载设置
            LoadSettings();
            
            // 设置版本信息
            VersionInfo = $"版本 {AppInfo.Version}";
            
            // 检查服务器状态
            UpdateServerStatus();
            
            // 检查连接状态
            CheckConnectionStatus();
            
            // 自动检查更新
            CheckForUpdatesAsync();
        }
        
        private void InitializeNavigationItems()
        {
            NavigationItems.Add(new NavigationItemViewModel
            {
                Icon = "Dashboard",
                Title = "仪表板",
                ViewName = "Dashboard",
                Command = new RelayCommand(() => NavigateTo("Dashboard"))
            });
            
            NavigationItems.Add(new NavigationItemViewModel
            {
                Icon = "Servers",
                Title = "服务器管理",
                ViewName = "Servers",
                Command = new RelayCommand(() => NavigateTo("Servers"))
            });
            
            NavigationItems.Add(new NavigationItemViewModel
            {
                Icon = "Monitoring",
                Title = "监控管理",
                ViewName = "Monitoring",
                Command = new RelayCommand(() => NavigateTo("Monitoring"))
            });
            
            NavigationItems.Add(new NavigationItemViewModel
            {
                Icon = "RemoteControl",
                Title = "远程控制",
                ViewName = "RemoteControl",
                Command = new RelayCommand(() => NavigateTo("RemoteControl"))
            });
            
            NavigationItems.Add(new NavigationItemViewModel
            {
                Icon = "Task",
                Title = "任务管理",
                ViewName = "Tasks",
                Command = new RelayCommand(() => NavigateTo("Tasks"))
            });
            
            NavigationItems.Add(new NavigationItemViewModel
            {
                Icon = "Software",
                Title = "软件管理",
                ViewName = "Software",
                Command = new RelayCommand(() => NavigateTo("Software"))
            });
            
            NavigationItems.Add(new NavigationItemViewModel
            {
                Icon = "Reports",
                Title = "报表中心",
                ViewName = "Reports",
                Command = new RelayCommand(() => NavigateTo("Reports"))
            });
            
            NavigationItems.Add(new NavigationItemViewModel
            {
                Icon = "Settings",
                Title = "系统设置",
                ViewName = "Settings",
                Command = new RelayCommand(() => NavigateTo("Settings"))
            });
        }
        
        private void NavigateTo(string viewName)
        {
            // 更新导航项选中状态
            foreach (var item in NavigationItems)
            {
                item.IsSelected = item.ViewName == viewName;
            }
            
            // 使用导航服务切换视图
            CurrentView = _navigationService.GetView(viewName);
            
            // 更新状态消息
            StatusMessage = $"已切换到{GetViewTitle(viewName)}";
        }
        
        private string GetViewTitle(string viewName)
        {
            return viewName switch
            {
                "Dashboard" => "仪表板",
                "Servers" => "服务器管理",
                "Monitoring" => "监控管理",
                "RemoteControl" => "远程控制",
                "Tasks" => "任务管理",
                "Software" => "软件管理",
                "Reports" => "报表中心",
                "Settings" => "系统设置",
                _ => viewName
            };
        }
        
        private void LoadUserInfo()
        {
            var currentUser = CurrentUser.Instance;
            UserName = currentUser.FullName;
            UserRole = currentUser.RoleName;
            UserAvatar = currentUser.AvatarUrl ?? "/Resources/Images/default_avatar.png";
        }
        
        private void LoadSettings()
        {
            ShowStatusBar = _settingsService.GetSetting("ShowStatusBar", true);
        }
        
        private async void UpdateServerStatus()
        {
            try
            {
                var summary = await _serverService.GetServerStatusSummaryAsync();
                ServerStatusSummary = $"服务器: {summary.TotalCount} 总数, {summary.OnlineCount} 在线, {summary.OfflineCount} 离线, {summary.WarningCount} 警告";
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "获取服务器状态摘要失败");
                ServerStatusSummary = "服务器状态: 未知";
            }
        }
        
        private async void CheckConnectionStatus()
        {
            try
            {
                ConnectionStatus = ConnectionStatus.Connecting;
                
                var isConnected = await _authService.CheckConnectionAsync();
                
                ConnectionStatus = isConnected ? ConnectionStatus.Connected : ConnectionStatus.Disconnected;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "检查连接状态失败");
                ConnectionStatus = ConnectionStatus.Disconnected;
            }
        }
        
        private async void CheckForUpdatesAsync()
        {
            try
            {
                var autoCheck = _settingsService.GetSetting("AutoCheckUpdates", true);
                
                if (!autoCheck)
                {
                    return;
                }
                
                var result = await _updateService.CheckForUpdatesAsync();
                
                if (result.HasUpdate)
                {
                    // 如果发现更新,显示通知
                    var message = $"发现新版本 {result.LatestVersion}。是否立即更新?";
                    
                    var confirmed = await _dialogService.ShowConfirmationAsync("更新可用", message);
                    
                    if (confirmed)
                    {
                        await DownloadAndInstallUpdateAsync(result);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "自动检查更新失败");
            }
        }
        
        private async Task DownloadAndInstallUpdateAsync(UpdateCheckResult updateInfo)
        {
            try
            {
                var progress = new Progress<ProgressInfo>(info =>
                {
                    StatusMessage = info.Status;
                });
                
                var result = await _updateService.DownloadAndInstallUpdateAsync(updateInfo, progress);
                
                if (!result.Success)
                {
                    await _dialogService.ShowErrorAsync("更新失败", result.Message);
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "下载和安装更新失败");
                await _dialogService.ShowErrorAsync("更新失败", $"无法完成更新: {ex.Message}");
            }
        }
        
        private void ExecuteRefresh()
        {
            // 刷新当前视图
            var refreshable = CurrentView as IRefreshable;
            refreshable?.Refresh();
            
            // 更新服务器状态
            UpdateServerStatus();
            
            // 检查连接状态
            CheckConnectionStatus();
            
            StatusMessage = "已刷新";
        }
        
        private void ExecuteExportData()
        {
            // 实现导出数据功能
        }
        
        private void ExecuteImportData()
        {
            // 实现导入数据功能
        }
        
        private void ExecutePrint()
        {
            // 实现打印功能
        }
        
        private void ExecuteExit()
        {
            // 退出应用程序
            App.Current.Shutdown();
        }
        
        private void ExecuteToggleFullScreen()
        {
            // 实现全屏切换功能
        }
        
        private void ExecuteOpenSettings()
        {
            NavigateTo("Settings");
        }
        
        private async void ExecuteCheckUpdateAsync()
        {
            try
            {
                StatusMessage = "正在检查更新...";
                
                var result = await _updateService.CheckForUpdatesAsync();
                
                if (result.HasUpdate)
                {
                    var message = $"发现新版本 {result.LatestVersion}。是否立即更新?\n\n发布日期: {result.PublishDate:yyyy-MM-dd}\n大小: {BytesFormatter.Format(result.FileSize)}\n\n更新内容:\n{result.ReleaseNotes}";
                    
                    var confirmed = await _dialogService.ShowConfirmationAsync("更新可用", message);
                    
                    if (confirmed)
                    {
                        await DownloadAndInstallUpdateAsync(result);
                    }
                }
                else
                {
                    await _dialogService.ShowInfoAsync("检查更新", "当前已是最新版本。");
                    StatusMessage = "检查更新完成";
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "检查更新失败");
                await _dialogService.ShowErrorAsync("检查更新失败", ex.Message);
                StatusMessage = "检查更新失败";
            }
        }
        
        private void ExecuteOpenLogViewer()
        {
            // 打开日志查看器
        }
        
        private void ExecuteOpenHelp()
        {
            // 打开帮助文档
            ProcessHelper.OpenUrl("https://example.com/help");
        }
        
        private void ExecuteOpenSupport()
        {
            // 打开在线支持
            ProcessHelper.OpenUrl("https://example.com/support");
        }
        
        private void ExecuteOpenAbout()
        {
            // 显示关于对话框
            _dialogService.ShowDialog(new AboutView());
        }
        
        private void ExecuteAddServer()
        {
            // 打开添加服务器对话框
        }
        
        private void ExecuteManageGroups()
        {
            // 打开管理分组对话框
        }
        
        private void ExecuteStartTask()
        {
            // 打开执行任务对话框
        }
        
        private void ExecuteCreateTask()
        {
            // 打开创建任务对话框
        }
    }
    
    public class NavigationItemViewModel : ViewModelBase
    {
        private bool _isSelected;
        private int _badge;
        
        public string Icon { get; set; }
        public string Title { get; set; }
        public string ViewName { get; set; }
        public ICommand Command { get; set; }
        
        public bool IsSelected
        {
            get => _isSelected;
            set
            {
                _isSelected = value;
                OnPropertyChanged();
            }
        }
        
        public int Badge
        {
            get => _badge;
            set
            {
                _badge = value;
                OnPropertyChanged();
                OnPropertyChanged(nameof(ShowBadge));
            }
        }
        
        public bool ShowBadge => Badge > 0;
    }
    
    public enum ConnectionStatus
    {
        Connected,
        Connecting,
        Disconnected
    }
}

16.5.6 执行功能模块

执行功能模块是系统的核心功能之一,负责在远程服务器上执行命令和脚本。

远程命令执行

csharp

// RemoteExecutionService.cs
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using OpsManager.Client.Models;

namespace OpsManager.Client.Services
{
    public class RemoteExecutionService : IRemoteExecutionService
    {
        private readonly HttpClient _httpClient;
        
        public RemoteExecutionService(HttpClient httpClient)
        {
            _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
        }
        
        public async Task<CommandResult> ExecuteCommandAsync(
            int serverId, 
            string command, 
            int timeoutSeconds = 60,
            CancellationToken cancellationToken = default)
        {
            try
            {
                var requestData = new
                {
                    Command = command,
                    Timeout = timeoutSeconds
                };
                
                var content = new StringContent(
                    JsonConvert.SerializeObject(requestData),
                    Encoding.UTF8,
                    "application/json");
                
                var response = await _httpClient.PostAsync(
                    $"api/servers/{serverId}/execute", 
                    content,
                    cancellationToken);
                
                var responseContent = await response.Content.ReadAsStringAsync();
                
                if (response.IsSuccessStatusCode)
                {
                    return JsonConvert.DeserializeObject<CommandResult>(responseContent);
                }
                else
                {
                    var error = JsonConvert.DeserializeObject<ErrorResponse>(responseContent);
                    return new CommandResult
                    {
                        Success = false,
                        ExitCode = -1,
                        Output = string.Empty,
                        Error = error?.Message ?? "执行命令失败"
                    };
                }
            }
            catch (TaskCanceledException)
            {
                return new CommandResult
                {
                    Success = false,
                    ExitCode = -1,
                    Output = string.Empty,
                    Error = "命令执行已取消"
                };
            }
            catch (Exception ex)
            {
                return new CommandResult
                {
                    Success = false,
                    ExitCode = -1,
                    Output = string.Empty,
                    Error = $"执行命令时发生错误: {ex.Message}"
                };
            }
        }
        
        public async Task<ScriptResult> ExecuteScriptAsync(
            int serverId, 
            string scriptContent, 
            string scriptType = "bash", 
            Dictionary<string, string> parameters = null,
            int timeoutSeconds = 300,
            CancellationToken cancellationToken = default)
        {
            try
            {
                var requestData = new
                {
                    ScriptContent = scriptContent,
                    ScriptType = scriptType,
                    Parameters = parameters ?? new Dictionary<string, string>(),
                    Timeout = timeoutSeconds
                };
                
                var content = new StringContent(
                    JsonConvert.SerializeObject(requestData),
                    Encoding.UTF8,
                    "application/json");
                
                var response = await _httpClient.PostAsync(
                    $"api/servers/{serverId}/execute-script", 
                    content,
                    cancellationToken);
                
                var responseContent = await response.Content.ReadAsStringAsync();
                
                if (response.IsSuccessStatusCode)
                {
                    return JsonConvert.DeserializeObject<ScriptResult>(responseContent);
                }
                else
                {
                    var error = JsonConvert.DeserializeObject<ErrorResponse>(responseContent);
                    return new ScriptResult
                    {
                        Success = false,
                        ExitCode = -1,
                        Output = string.Empty,
                        Error = error?.Message ?? "执行脚本失败",
                        ExecutionTime = 0
                    };
                }
            }
            catch (TaskCanceledException)
            {
                return new ScriptResult
                {
                    Success = false,
                    ExitCode = -1,
                    Output = string.Empty,
                    Error = "脚本执行已取消",
                    ExecutionTime = 0
                };
            }
            catch (Exception ex)
            {
                return new ScriptResult
                {
                    Success = false,
                    ExitCode = -1,
                    Output = string.Empty,
                    Error = $"执行脚本时发生错误: {ex.Message}",
                    ExecutionTime = 0
                };
            }
        }
        
        public async Task<BatchCommandResult> ExecuteBatchCommandAsync(
            IEnumerable<int> serverIds, 
            string command, 
            int timeoutSeconds = 60,
            bool parallel = true,
            CancellationToken cancellationToken = default)
        {
            try
            {
                var requestData = new
                {
                    ServerIds = serverIds,
                    Command = command,
                    Timeout = timeoutSeconds,
                    Parallel = parallel
                };
                
                var content = new StringContent(
                    JsonConvert.SerializeObject(requestData),
                    Encoding.UTF8,
                    "application/json");
                
                var response = await _httpClient.PostAsync(
                    "api/servers/batch-execute", 
                    content,
                    cancellationToken);
                
                var responseContent = await response.Content.ReadAsStringAsync();
                
                if (response.IsSuccessStatusCode)
                {
                    return JsonConvert.DeserializeObject<BatchCommandResult>(responseContent);
                }
                else
                {
                    var error = JsonConvert.DeserializeObject<ErrorResponse>(responseContent);
                    return new BatchCommandResult
                    {
                        Success = false,
                        Message = error?.Message ?? "批量执行命令失败",
                        Results = new Dictionary<int, CommandResult>()
                    };
                }
            }
            catch (TaskCanceledException)
            {
                return new BatchCommandResult
                {
                    Success = false,
                    Message = "批量命令执行已取消",
                    Results = new Dictionary<int, CommandResult>()
                };
            }
            catch (Exception ex)
            {
                return new BatchCommandResult
                {
                    Success = false,
                    Message = $"批量执行命令时发生错误: {ex.Message}",
                    Results = new Dictionary<int, CommandResult>()
                };
            }
        }
        
        public async Task<BatchScriptResult> ExecuteBatchScriptAsync(
            IEnumerable<int> serverIds, 
            string scriptContent, 
            string scriptType = "bash", 
            Dictionary<string, string> parameters = null,
            int timeoutSeconds = 300,
            bool parallel = true,
            CancellationToken cancellationToken = default)
        {
            try
            {
                var requestData = new
                {
                    ServerIds = serverIds,
                    ScriptContent = scriptContent,
                    ScriptType = scriptType,
                    Parameters = parameters ?? new Dictionary<string, string>(),
                    Timeout = timeoutSeconds,
                    Parallel = parallel
                };
                
                var content = new StringContent(
                    JsonConvert.SerializeObject(requestData),
                    Encoding.UTF8,
                    "application/json");
                
                var response = await _httpClient.PostAsync(
                    "api/servers/batch-execute-script", 
                    content,
                    cancellationToken);
                
                var responseContent = await response.Content.ReadAsStringAsync();
                
                if (response.IsSuccessStatusCode)
                {
                    return JsonConvert.DeserializeObject<BatchScriptResult>(responseContent);
                }
                else
                {
                    var error = JsonConvert.DeserializeObject<ErrorResponse>(responseContent);
                    return new BatchScriptResult
                    {
                        Success = false,
                        Message = error?.Message ?? "批量执行脚本失败",
                        Results = new Dictionary<int, ScriptResult>()
                    };
                }
            }
            catch (TaskCanceledException)
            {
                return new BatchScriptResult
                {
                    Success = false,
                    Message = "批量脚本执行已取消",
                    Results = new Dictionary<int, ScriptResult>()
                };
            }
            catch (Exception ex)
            {
                return new BatchScriptResult
                {
                    Success = false,
                    Message = $"批量执行脚本时发生错误: {ex.Message}",
                    Results = new Dictionary<int, ScriptResult>()
                };
            }
        }
    }
    
    public class CommandResult
    {
        public bool Success { get; set; }
        public int ExitCode { get; set; }
        public string Output { get; set; }
        public string Error { get; set; }
    }
    
    public class ScriptResult
    {
        public bool Success { get; set; }
        public int ExitCode { get; set; }
        public string Output { get; set; }
        public string Error { get; set; }
        public double ExecutionTime { get; set; }
    }
    
    public class BatchCommandResult
    {
        public bool Success { get; set; }
        public string Message { get; set; }
        public Dictionary<int, CommandResult> Results { get; set; }
    }
    
    public class BatchScriptResult
    {
        public bool Success { get; set; }
        public string Message { get; set; }
        public Dictionary<int, ScriptResult> Results { get; set; }
    }
}
远程执行界面

csharp

// RemoteExecutionViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using OpsManager.Client.Core;
using OpsManager.Client.Models;
using OpsManager.Client.Services;

namespace OpsManager.Client.ViewModels
{
    public class RemoteExecutionViewModel : ViewModelBase, IRefreshable
    {
        private readonly IRemoteExecutionService _remoteExecutionService;
        private readonly IServerService _serverService;
        private readonly IDialogService _dialogService;
        
        private bool _isLoading;
        private string _command;
        private string _scriptContent;
        private string _scriptType;
        private int _timeout;
        private bool _isExecuting;
        private CancellationTokenSource _cancellationTokenSource;
        private ObservableCollection<ServerViewModel> _servers;
        private ObservableCollection<ExecutionResultViewModel> _results;
        
        public bool IsLoading
        {
            get => _isLoading;
            set
            {
                _isLoading = value;
                OnPropertyChanged();
            }
        }
        
        public string Command
        {
            get => _command;
            set
            {
                _command = value;
                OnPropertyChanged();
                ExecuteCommandCommand.RaiseCanExecuteChanged();
            }
        }
        
        public string ScriptContent
        {
            get => _scriptContent;
            set
            {
                _scriptContent = value;
                OnPropertyChanged();
                ExecuteScriptCommand.RaiseCanExecuteChanged();
            }
        }
        
        public string ScriptType
        {
            get => _scriptType;
            set
            {
                _scriptType = value;
                OnPropertyChanged();
            }
        }
        
        public int Timeout
        {
            get => _timeout;
            set
            {
                _timeout = value;
                OnPropertyChanged();
            }
        }
        
        public bool IsExecuting
        {
            get => _isExecuting;
            set
            {
                _isExecuting = value;
                OnPropertyChanged();
                ExecuteCommandCommand.RaiseCanExecuteChanged();
                ExecuteScriptCommand.RaiseCanExecuteChanged();
                CancelExecutionCommand.RaiseCanExecuteChanged();
            }
        }
        
        public ObservableCollection<ServerViewModel> Servers
        {
            get => _servers;
            set
            {
                _servers = value;
                OnPropertyChanged();
            }
        }
        
        public ObservableCollection<ExecutionResultViewModel> Results
        {
            get => _results;
            set
            {
                _results = value;
                OnPropertyChanged();
            }
        }
        
        public RelayCommand LoadServersCommand { get; }
        public RelayCommand<ServerViewModel> SelectAllCommand { get; }
        public RelayCommand<ServerViewModel> UnselectAllCommand { get; }
        public RelayCommand ExecuteCommandCommand { get; }
        public RelayCommand ExecuteScriptCommand { get; }
        public RelayCommand CancelExecutionCommand { get; }
        public RelayCommand ClearResultsCommand { get; }
        
        public RemoteExecutionViewModel(
            IRemoteExecutionService remoteExecutionService,
            IServerService serverService,
            IDialogService dialogService)
        {
            _remoteExecutionService = remoteExecutionService ?? throw new ArgumentNullException(nameof(remoteExecutionService));
            _serverService = serverService ?? throw new ArgumentNullException(nameof(serverService));
            _dialogService = dialogService ?? throw new ArgumentNullException(nameof(dialogService));
            
            Servers = new ObservableCollection<ServerViewModel>();
            Results = new ObservableCollection<ExecutionResultViewModel>();
            
            LoadServersCommand = new RelayCommand(ExecuteLoadServersAsync);
            SelectAllCommand = new RelayCommand<ServerViewModel>(_ => ExecuteSelectAll());
            UnselectAllCommand = new RelayCommand<ServerViewModel>(_ => ExecuteUnselectAll());
            ExecuteCommandCommand = new RelayCommand(ExecuteCommandAsync, CanExecuteCommand);
            ExecuteScriptCommand = new RelayCommand(ExecuteScriptAsync, CanExecuteScript);
            CancelExecutionCommand = new RelayCommand(ExecuteCancelExecution, CanCancelExecution);
            ClearResultsCommand = new RelayCommand(ExecuteClearResults);
            
            // 初始化默认值
            ScriptType = "bash";
            Timeout = 60;
            
            // 加载服务器列表
            ExecuteLoadServersAsync();
        }
        
        public void Refresh()
        {
            ExecuteLoadServersAsync();
        }
        
        private async void ExecuteLoadServersAsync()
        {
            try
            {
                IsLoading = true;
                
                // 获取服务器列表
                var servers = await _serverService.GetAllServersAsync();
                
                // 转换为视图模型
                var serverViewModels = servers.Select(s => new ServerViewModel
                {
                    ID = s.ServerID,
                    Hostname = s.Hostname,
                    IPAddress = s.IPAddress,
                    OSType = s.OSType,
                    Status = s.Status,
                    IsSelected = false
                }).ToList();
                
                // 更新服务器列表
                Servers.Clear();
                foreach (var server in serverViewModels)
                {
                    Servers.Add(server);
                }
            }
            catch (Exception ex)
            {
                await _dialogService.ShowErrorAsync("加载失败", $"加载服务器列表失败: {ex.Message}");
                Logger.Error(ex, "加载服务器列表失败");
            }
            finally
            {
                IsLoading = false;
            }
        }
        
        private void ExecuteSelectAll()
        {
            foreach (var server in Servers)
            {
                server.IsSelected = true;
            }
        }
        
        private void ExecuteUnselectAll()
        {
            foreach (var server in Servers)
            {
                server.IsSelected = false;
            }
        }
        
        private bool CanExecuteCommand()
        {
            return !IsExecuting && !string.IsNullOrWhiteSpace(Command) && Servers.Any(s => s.IsSelected);
        }
        
        private async void ExecuteCommandAsync()
        {
            try
            {
                // 检查是否有选中的服务器
                var selectedServers = Servers.Where(s => s.IsSelected).ToList();
                
                if (!selectedServers.Any())
                {
                    await _dialogService.ShowWarningAsync("警告", "请至少选择一台服务器");
                    return;
                }
                
                // 开始执行
                IsExecuting = true;
                
                // 创建取消令牌
                _cancellationTokenSource = new CancellationTokenSource();
                
                // 清空之前的结果
                Results.Clear();
                
                // 为每个选中的服务器创建结果项
                foreach (var server in selectedServers)
                {
                    Results.Add(new ExecutionResultViewModel
                    {
                        ServerID = server.ID,
                        ServerName = server.Hostname,
                        Status = "等待中...",
                        Output = "",
                        Error = ""
                    });
                }
                
                // 获取选中服务器的ID列表
                var serverIds = selectedServers.Select(s => s.ID).ToList();
                
                // 批量执行命令
                var result = await _remoteExecutionService.ExecuteBatchCommandAsync(
                    serverIds,
                    Command,
                    Timeout,
                    true,
                    _cancellationTokenSource.Token);
                
                // 处理结果
                if (result.Success)
                {
                    // 更新每个服务器的执行结果
                    foreach (var serverResult in result.Results)
                    {
                        var serverId = serverResult.Key;
                        var commandResult = serverResult.Value;
                        
                        var resultViewModel = Results.FirstOrDefault(r => r.ServerID == serverId);
                        if (resultViewModel != null)
                        {
                            resultViewModel.Status = commandResult.Success ? "成功" : "失败";
                            resultViewModel.ExitCode = commandResult.ExitCode;
                            resultViewModel.Output = commandResult.Output;
                            resultViewModel.Error = commandResult.Error;
                        }
                    }
                }
                else
                {
                    await _dialogService.ShowErrorAsync("执行失败", result.Message);
                }
            }
            catch (Exception ex)
            {
                await _dialogService.ShowErrorAsync("执行失败", $"执行命令时发生错误: {ex.Message}");
                Logger.Error(ex, "执行命令失败");
            }
            finally
            {
                IsExecuting = false;
                _cancellationTokenSource?.Dispose();
                _cancellationTokenSource = null;
            }
        }
        
        private bool CanExecuteScript()
        {
            return !IsExecuting && !string.IsNullOrWhiteSpace(ScriptContent) && Servers.Any(s => s.IsSelected);
        }
        
        private async void ExecuteScriptAsync()
        {
            try
            {
                // 检查是否有选中的服务器
                var selectedServers = Servers.Where(s => s.IsSelected).ToList();
                
                if (!selectedServers.Any())
                {
                    await _dialogService.ShowWarningAsync("警告", "请至少选择一台服务器");
                    return;
                }
                
                // 开始执行
                IsExecuting = true;
                
                // 创建取消令牌
                _cancellationTokenSource = new CancellationTokenSource();
                
                // 清空之前的结果
                Results.Clear();
                
                // 为每个选中的服务器创建结果项
                foreach (var server in selectedServers)
                {
                    Results.Add(new ExecutionResultViewModel
                    {
                        ServerID = server.ID,
                        ServerName = server.Hostname,
                        Status = "等待中...",
                        Output = "",
                        Error = ""
                    });
                }
                
                // 获取选中服务器的ID列表
                var serverIds = selectedServers.Select(s => s.ID).ToList();
                
                // 批量执行脚本
                var result = await _remoteExecutionService.ExecuteBatchScriptAsync(
                    serverIds,
                    ScriptContent,
                    ScriptType,
                    null,
                    Timeout,
                    true,
                    _cancellationTokenSource.Token);
                
                // 处理结果
                if (result.Success)
                {
                    // 更新每个服务器的执行结果
                    foreach (var serverResult in result.Results)
                    {
                        var serverId = serverResult.Key;
                        var scriptResult = serverResult.Value;
                        
                        var resultViewModel = Results.FirstOrDefault(r => r.ServerID == serverId);
                        if (resultViewModel != null)
                        {
                            resultViewModel.Status = scriptResult.Success ? "成功" : "失败";
                            resultViewModel.ExitCode = scriptResult.ExitCode;
                            resultViewModel.Output = scriptResult.Output;
                            resultViewModel.Error = scriptResult.Error;
                            resultViewModel.ExecutionTime = scriptResult.ExecutionTime;
                        }
                    }
                }
                else
                {
                    await _dialogService.ShowErrorAsync("执行失败", result.Message);
                }
            }
            catch (Exception ex)
            {
                await _dialogService.ShowErrorAsync("执行失败", $"执行脚本时发生错误: {ex.Message}");
                Logger.Error(ex, "执行脚本失败");
            }
            finally
            {
                IsExecuting = false;
                _cancellationTokenSource?.Dispose();
                _cancellationTokenSource = null;
            }
        }
        
        private bool CanCancelExecution()
        {
            return IsExecuting && _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested;
        }
        
        private void ExecuteCancelExecution()
        {
            try
            {
                // 取消执行
                _cancellationTokenSource?.Cancel();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "取消执行失败");
            }
        }
        
        private void ExecuteClearResults()
        {
            Results.Clear();
        }
    }
    
    public class ServerViewModel : ViewModelBase
    {
        private bool _isSelected;
        
        public int ID { get; set; }
        public string Hostname { get; set; }
        public string IPAddress { get; set; }
        public string OSType { get; set; }
        public string Status { get; set; }
        
        public bool IsSelected
        {
            get => _isSelected;
            set
            {
                _isSelected = value;
                OnPropertyChanged();
            }
        }
    }
    
    public class ExecutionResultViewModel : ViewModelBase
    {
        public int ServerID { get; set; }
        public string ServerName { get; set; }
        public string Status { get; set; }
        public int ExitCode { get; set; }
        public string Output { get; set; }
        public string Error { get; set; }
        public double ExecutionTime { get; set; }
    }
}

16.5.7 平台程序发布

平台程序的发布是将开发完成的软件打包并准备部署的过程。下面介绍发布桌面版C/S自动化运维平台的关键步骤。

版本管理

在发布前,需要进行适当的版本管理:

csharp

// 版本信息类
public static class VersionInfo
{
    // 应用程序的版本号
    public const string Version = "1.0.0";
    
    // 内部版本号,用于区分不同的构建
    public const string BuildNumber = "10001";
    
    // 发布日期
    public static readonly DateTime ReleaseDate = new DateTime(2023, 5, 28);
    
    // 版本类型
    public const string VersionType = "Release"; // "Alpha", "Beta", "RC", "Release"
    
    // 完整版本字符串
    public static string FullVersionString => $"{Version} ({VersionType}) Build {BuildNumber}";
    
    // 版权信息
    public const string Copyright = "Copyright © 2023 Your Company. All rights reserved.";
    
    // 是否是调试版本
    #if DEBUG
    public const bool IsDebugBuild = true;
    #else
    public const bool IsDebugBuild = false;
    #endif
}
打包脚本

使用PowerShell脚本自动化打包过程:

powershell

# build_and_package.ps1

param (
    [string]$Configuration = "Release",
    [string]$Version = "1.0.0",
    [string]$BuildNumber = (Get-Date -Format "yyyyMMddHHmm")
)

# 项目路径
$solutionPath = ".\OpsManager.sln"
$clientProjectPath = ".\OpsManager.Client\OpsManager.Client.csproj"
$serverProjectPath = ".\OpsManager.Server\OpsManager.Server.csproj"
$outputPath = ".\Release"
$packagePath = "$outputPath\Packages"

# 确保输出目录存在
if (!(Test-Path $outputPath)) {
    New-Item -ItemType Directory -Path $outputPath | Out-Null
}

if (!(Test-Path $packagePath)) {
    New-Item -ItemType Directory -Path $packagePath | Out-Null
}

# 更新版本信息
Write-Host "Updating version information to $Version (Build $BuildNumber)..." -ForegroundColor Cyan

# 更新AssemblyInfo.cs文件
$assemblyInfoPath = ".\OpsManager.Client\Properties\AssemblyInfo.cs"
$assemblyInfo = Get-Content $assemblyInfoPath
$assemblyInfo = $assemblyInfo -replace 'AssemblyVersion\("[0-9]+(\.([0-9]+|\*)){1,3}"\)', "AssemblyVersion(`"$Version`")"
$assemblyInfo = $assemblyInfo -replace 'AssemblyFileVersion\("[0-9]+(\.([0-9]+|\*)){1,3}"\)', "AssemblyFileVersion(`"$Version`")"
$assemblyInfo | Set-Content $assemblyInfoPath

# 更新版本信息类
$versionInfoPath = ".\OpsManager.Client\Utils\VersionInfo.cs"
$versionInfo = Get-Content $versionInfoPath
$versionInfo = $versionInfo -replace 'public const string Version = "[0-9]+(\.([0-9]+|\*)){1,3}";', "public const string Version = `"$Version`";"
$versionInfo = $versionInfo -replace 'public const string BuildNumber = "[0-9]+";', "public const string BuildNumber = `"$BuildNumber`";"
$versionInfo = $versionInfo -replace 'public static readonly DateTime ReleaseDate = new DateTime\([0-9]+, [0-9]+, [0-9]+\);', "public static readonly DateTime ReleaseDate = new DateTime($((Get-Date).Year), $((Get-Date).Month), $((Get-Date).Day));"
$versionInfo | Set-Content $versionInfoPath

# 编译解决方案
Write-Host "Building solution..." -ForegroundColor Cyan
dotnet restore $solutionPath
dotnet build $solutionPath -c $Configuration

# 发布客户端
Write-Host "Publishing client application..." -ForegroundColor Cyan
dotnet publish $clientProjectPath -c $Configuration -o "$outputPath\Client" --self-contained true -r win-x64 /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true

# 发布服务器
Write-Host "Publishing server application..." -ForegroundColor Cyan
dotnet publish $serverProjectPath -c $Configuration -o "$outputPath\Server"

# 创建客户端安装包
Write-Host "Creating client installer package..." -ForegroundColor Cyan
$clientPackageName = "OpsManager-Client-$Version.zip"
Compress-Archive -Path "$outputPath\Client\*" -DestinationPath "$packagePath\$clientPackageName" -Force

# 创建服务器安装包
Write-Host "Creating server installer package..." -ForegroundColor Cyan
$serverPackageName = "OpsManager-Server-$Version.zip"
Compress-Archive -Path "$outputPath\Server\*" -DestinationPath "$packagePath\$serverPackageName" -Force

# 创建代理安装包
Write-Host "Creating agent installer packages..." -ForegroundColor Cyan
$winAgentPackageName = "OpsManager-Agent-Windows-$Version.zip"
$linuxAgentPackageName = "OpsManager-Agent-Linux-$Version.tar.gz"

Compress-Archive -Path ".\OpsManager.Agent\bin\$Configuration\net6.0\win-x64\publish\*" -DestinationPath "$packagePath\$winAgentPackageName" -Force

# 使用tar命令创建Linux代理包
Set-Location ".\OpsManager.Agent\bin\$Configuration\net6.0\linux-x64\publish"
tar -czf "$packagePath\$linuxAgentPackageName" *
Set-Location -Path (Get-Location).Path.Substring(0, (Get-Location).Path.IndexOf("\OpsManager.Agent"))

# 创建发布说明
Write-Host "Creating release notes..." -ForegroundColor Cyan
$releaseNotesPath = "$packagePath\ReleaseNotes-$Version.txt"
$releaseDate = Get-Date -Format "yyyy-MM-dd"

@"
# OpsManager $Version Release Notes
Release Date: $releaseDate
Build Number: $BuildNumber

## New Features
- Initial release of OpsManager platform
- Server asset management
- Monitoring and alerting
- Remote control
- Task automation
- Software management
- Reporting

## Improvements
- N/A

## Bug Fixes
- N/A

## Known Issues
- N/A

## Installation Instructions
1. For client: Extract OpsManager-Client-$Version.zip and run setup.exe
2. For server: See installation guide in documentation

## System Requirements
- Client: Windows 10/11 with .NET Framework 4.8+
- Server: Windows Server 2019/2022 or Linux with .NET Core 6.0+
"@ | Set-Content $releaseNotesPath

Write-Host "Build and packaging completed successfully!" -ForegroundColor Green
Write-Host "Client package: $packagePath\$clientPackageName" -ForegroundColor Yellow
Write-Host "Server package: $packagePath\$serverPackageName" -ForegroundColor Yellow
Write-Host "Windows Agent package: $packagePath\$winAgentPackageName" -ForegroundColor Yellow
Write-Host "Linux Agent package: $packagePath\$linuxAgentPackageName" -ForegroundColor Yellow
Write-Host "Release Notes: $releaseNotesPath" -ForegroundColor Yellow
安装程序制作

使用WiX Toolset创建Windows安装程序:

xml

<!-- Product.wxs -->
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <Product Id="*" 
             Name="OpsManager Client" 
             Language="1033" 
             Version="1.0.0.0" 
             Manufacturer="Your Company" 
             UpgradeCode="PUT-GUID-HERE">
        
        <Package InstallerVersion="200" 
                 Compressed="yes" 
                 InstallScope="perMachine" 
                 Description="OpsManager Client Installer"
                 Comments="Installs the OpsManager client application" />

        <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
        <MediaTemplate EmbedCab="yes" />

        <Feature Id="ProductFeature" Title="OpsManager Client" Level="1">
            <ComponentGroupRef Id="ProductComponents" />
            <ComponentRef Id="ApplicationShortcut" />
        </Feature>
        
        <Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
        <UIRef Id="WixUI_InstallDir" />
        
        <WixVariable Id="WixUILicenseRtf" Value="License.rtf" />
        <WixVariable Id="WixUIBannerBmp" Value="banner.bmp" />
        <WixVariable Id="WixUIDialogBmp" Value="dialog.bmp" />
        
        <Property Id="SERVER_URL" Value="https://localhost:5001" />
        <Property Id="INSTALL_DIR" Value="[ProgramFilesFolder]OpsManager" />
    </Product>

    <Fragment>
        <Directory Id="TARGETDIR" Name="SourceDir">
            <Directory Id="ProgramFilesFolder">
                <Directory Id="INSTALLFOLDER" Name="OpsManager">
                    <!-- Application files will go here -->
                </Directory>
            </Directory>
            <Directory Id="ProgramMenuFolder">
                <Directory Id="ApplicationProgramsFolder" Name="OpsManager"/>
            </Directory>
            <Directory Id="DesktopFolder" Name="Desktop" />
        </Directory>
    </Fragment>

    <Fragment>
        <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
            <!-- Add application files here -->
            <Component Id="MainExecutable" Guid="*">
                <File Id="OpsManagerEXE" Source="$(var.BinDir)\OpsManager.Client.exe" KeyPath="yes" />
            </Component>
            <!-- Add other application files here -->
        </ComponentGroup>
        
        <DirectoryRef Id="ApplicationProgramsFolder">
            <Component Id="ApplicationShortcut" Guid="*">
                <Shortcut Id="ApplicationStartMenuShortcut" 
                          Name="OpsManager" 
                          Description="OpsManager Client"
                          Target="[INSTALLFOLDER]OpsManager.Client.exe"
                          WorkingDirectory="INSTALLFOLDER"/>
                <Shortcut Id="DesktopShortcut"
                          Directory="DesktopFolder"
                          Name="OpsManager"
                          Description="OpsManager Client"
                          Target="[INSTALLFOLDER]OpsManager.Client.exe"
                          WorkingDirectory="INSTALLFOLDER"/>
                <RemoveFolder Id="CleanUpShortCut" Directory="ApplicationProgramsFolder" On="uninstall"/>
                <RegistryValue Root="HKCU" Key="Software\OpsManager" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
            </Component>
        </DirectoryRef>
    </Fragment>
</Wix>
自动更新配置

配置自动更新服务:

xml

<!-- update.config -->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="UpdateCheckUrl" value="https://example.com/updates/check" />
    <add key="UpdateDownloadUrl" value="https://example.com/updates/download" />
    <add key="UpdateCheckInterval" value="240" /> <!-- In minutes -->
    <add key="AutomaticUpdates" value="true" />
    <add key="UpdateLogPath" value="%APPDATA%\OpsManager\Logs\Updates" />
  </appSettings>
</configuration>

通过以上步骤,我们完成了桌面版C/S自动化运维平台的设计和实现。该平台提供了丰富的功能,包括服务器资产管理、监控告警、远程控制、自动化任务、软件管理等,能够满足中小型企业IT部门的自动化运维需求。采用C/S架构,该平台具有响应速度快、用户体验好、功能丰富等特点,是企业IT基础设施管理的有力工具。

Logo

一站式 AI 云服务平台

更多推荐