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

现代桌面应用型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自动化运维平台具有以下特点:

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

16.2 系统构架设计

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

总体架构

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

客户端(Client)

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

服务器(Server)

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

代理(Agent)

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

数据库

存储系统配置数据
存储监控数据
存储资产信息
存储用户和权限数据
存储操作日志

外部集成

邮件服务器:发送邮件通知
短信网关:发送短信通知
企业微信/钉钉:发送即时消息
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

数据流设计

系统的主要数据流如下:

资产数据流

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

监控数据流

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

远程控制流

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

自动化任务流

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

系统安全设计

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

认证与授权

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

数据安全

传输数据加密:TLS/SSL
敏感数据存储加密:密码、密钥等
数据完整性校验
定期数据备份

通信安全

客户端与服务器间的通信加密
服务器与代理间的通信加密
证书验证和信任机制
防重放攻击

操作审计

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

漏洞防护

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

高可用设计

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

服务器高可用

主备架构或集群部署
负载均衡
故障自动转移

数据库高可用

主从复制
自动备份
故障恢复机制

客户端容错

本地缓存
离线工作模式
自动重连机制

代理高可用

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

16.3 数据库结构设计

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

16.3.1 数据库分析

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

数据类型分析

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

配置数据

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

用户和权限数据

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

资产数据

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

监控数据

性能监控指标
服务状态数据
告警记录
数据量大,变化频率高

操作日志

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

任务数据

任务定义
任务执行记录
任务结果
数据量中等,变化频率中等

数据访问模式分析

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

配置数据

读多写少
全表查询为主
高一致性要求

用户和权限数据

读多写少
单条查询为主
高一致性要求

资产数据

读多写少
条件查询为主
中等一致性要求

监控数据

写入频繁
时序查询为主
低一致性要求

操作日志

只写入,几乎不修改
条件查询为主
低一致性要求

任务数据

读写均衡
状态更新频繁
中等一致性要求

数据量估算

假设一个中型企业环境:

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

数据量估算:

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

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

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

16.3.2 数据字典

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

用户和权限管理

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), 头像路径

Roles(角色表)

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

Permissions(权限表)

PermissionID (PK): int, 权限ID
PermissionName: varchar(50), 权限名称
Description: varchar(200), 权限描述
PermissionCode: varchar(50), 权限代码
ModuleCode: varchar(50), 模块代码
CreatedTime: datetime, 创建时间

UserRoles(用户角色关联表)

UserRoleID (PK): int, 用户角色ID
UserID (FK): int, 用户ID
RoleID (FK): int, 角色ID
AssignedTime: datetime, 分配时间

RolePermissions(角色权限关联表)

RolePermissionID (PK): int, 角色权限ID
RoleID (FK): int, 角色ID
PermissionID (FK): int, 权限ID
AssignedTime: datetime, 分配时间

资产管理

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), 代理版本

ServerGroups(服务器组表)

GroupID (PK): int, 组ID
GroupName: varchar(100), 组名称
Description: text, 描述
ParentGroupID (FK): int, 父组ID
CreatedTime: datetime, 创建时间
UpdatedTime: datetime, 更新时间

ServerGroupMemberships(服务器组成员表)

MembershipID (PK): int, 成员ID
ServerID (FK): int, 服务器ID
GroupID (FK): int, 组ID
AddedTime: datetime, 添加时间

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, 更新时间

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, 更新时间

监控和告警

MonitorItems(监控项目表)

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

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, 更新时间

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, 处理说明

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, 更新时间

任务管理

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, 更新时间

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, 更新时间

TaskTargets(任务目标表)

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

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)

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, 返回代码

系统配置和日志

SystemSettings(系统设置表)

SettingID (PK): int, 设置ID
SettingKey: varchar(100), 设置键
SettingValue: text, 设置值
SettingGroup: varchar(50), 设置组
Description: varchar(200), 描述
UpdatedBy (FK): int, 更新人ID
UpdatedTime: datetime, 更新时间

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, 错误消息

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 系统环境说明

服务器端环境要求

硬件要求

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

软件要求

操作系统: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 或更高版本(可选)

客户端环境要求

硬件要求

CPU:双核或更高
内存:至少4GB
磁盘:至少10GB可用空间
显示:至少1366×768分辨率

软件要求

操作系统:Windows 10/11
.NET Framework:4.8或更高版本
数据库:SQLite(内置)

代理环境要求

硬件要求

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

软件要求

操作系统:

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环境下的部署

安装必要组件

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 .
ewrite_amd64.msi -ArgumentList '/quiet' -Wait

# 重启IIS
Restart-Service W3SVC

安装SQL Server

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

安装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 .
abbitmq.exe -ArgumentList '/S' -Wait

# 启用管理插件
& 'C:Program FilesRabbitMQ Server
abbitmq_server-3.9.13sbin
abbitmq-plugins.bat' enable rabbitmq_management

部署应用

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:AppPoolsOpsManagerAppPool -Name managedRuntimeVersion -Value ""
Set-ItemProperty -Path IIS:AppPoolsOpsManagerAppPool -Name processModel.identityType -Value ApplicationPoolIdentity

# 创建IIS网站
New-Website -Name "OpsManager" -PhysicalPath C:OpsManager -ApplicationPool "OpsManagerAppPool" -Port 8080 -Force

# 设置应用程序配置
$configPath = "C:OpsManagerappsettings.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环境下的部署

安装.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

安装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;"

安装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

安装Nginx

bash

sudo apt update
sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx

部署应用

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

配置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
客户端部署

客户端的部署相对简单:

创建安装包

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

安装步骤

powershell

# 通过命令行静默安装
msiexec /i OpsManager-Client.msi /quiet SERVER_URL=https://opsmanager.example.com INSTALL_DIR="C:Program FilesOpsManager"

配置文件

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服务器

创建安装脚本

powershell

# agent_install.ps1
param (
    [string]$ServerUrl = "https://opsmanager.example.com",
    [string]$AgentKey = "",
    [string]$InstallDir = "C:Program FilesOpsManagerAgent"
)

# 创建安装目录
if (-not (Test-Path $InstallDir)) {
    New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
}

# 下载代理程序
$agentUrl = "$ServerUrl/download/agent/windows"
$agentZip = "$env:TEMPopsmanager-agent.zip"
Invoke-WebRequest -Uri $agentUrl -OutFile $agentZip

# 解压文件
Expand-Archive -Path $agentZip -DestinationPath $InstallDir -Force
Remove-Item -Path $agentZip -Force

# 创建配置文件
$configPath = "$InstallDirconfig.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服务
& "$InstallDirinstall-service.ps1"

Write-Host "OpsManager Agent installed successfully."

运行安装脚本

powershell

# 手动安装
.agent_install.ps1 -ServerUrl "https://opsmanager.example.com" -AgentKey "your-agent-key"

# 或通过组策略批量部署
Linux服务器

创建安装脚本

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."

运行安装脚本

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 用户登录模块

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

登录界面设计

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

系统标志和名称
用户名输入框
密码输入框
记住登录状态选项
登录按钮
版本信息

登录功能实现

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

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; }
    }
}
安全性考虑

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

密码处理

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

传输安全

使用HTTPS加密传输
使用TLS 1.2或更高版本

认证令牌

使用JWT作为认证令牌
令牌包含过期时间
令牌安全存储在本地

防暴力破解

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

16.5.2 系统配置功能

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

配置界面设计

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

基本设置

服务器连接配置
客户端显示设置
语言和时区设置

监控设置

默认监控间隔
默认告警阈值
数据保留策略

通知设置

邮件通知配置
短信通知配置
企业微信/钉钉配置

代理设置

代理部署配置
代理升级设置
代理安全设置

备份设置

备份策略配置
备份存储位置
自动备份计划

设置存储模型

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 服务器分类模块

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

分类管理界面

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

分类树形结构显示
分类的添加、编辑、删除功能
服务器的分配和移除功能
拖放操作支持
搜索和过滤功能

数据模型设计

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 系统升级功能

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

升级流程设计

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

版本检测

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

版本比较

比较本地版本和服务器版本
判断是否需要升级

下载升级包

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

安装升级

备份当前版本
安装新版本
迁移配置和数据

升级验证

验证升级是否成功
报告升级结果

客户端升级实现

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技术实现,支持丰富的交互体验。

主窗口设计

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

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

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}。是否立即更新?

发布日期: {result.PublishDate:yyyy-MM-dd}
大小: {BytesFormatter.Format(result.FileSize)}

更新内容:
{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.ClientOpsManager.Client.csproj"
$serverProjectPath = ".OpsManager.ServerOpsManager.Server.csproj"
$outputPath = ".Release"
$packagePath = "$outputPathPackages"

# 确保输出目录存在
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.ClientPropertiesAssemblyInfo.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.ClientUtilsVersionInfo.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 "$outputPathClient" --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 "$outputPathServer"

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

# 创建服务器安装包
Write-Host "Creating server installer package..." -ForegroundColor Cyan
$serverPackageName = "OpsManager-Server-$Version.zip"
Compress-Archive -Path "$outputPathServer*" -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.Agentin$Configuration
et6.0win-x64publish*" -DestinationPath "$packagePath$winAgentPackageName" -Force

# 使用tar命令创建Linux代理包
Set-Location ".OpsManager.Agentin$Configuration
et6.0linux-x64publish"
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 = "$packagePathReleaseNotes-$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="SoftwareOpsManager" 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%OpsManagerLogsUpdates" />
  </appSettings>
</configuration>

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

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
爱喝柠檬水的追星狗的头像 - 宋马
评论 抢沙发

请登录后发表评论

    暂无评论内容