From 0cd0a0a99c08865f72ff6ea66d2d524e05672160 Mon Sep 17 00:00:00 2001 From: "David P.G" Date: Sun, 20 Jul 2025 23:09:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=88=9D=E7=89=88=E8=BD=AF?= =?UTF-8?q?=E4=BB=B6=E5=BC=80=E5=8F=91=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 软件设计文档/00-项目总体设计.md | 103 ++++++++ 软件设计文档/00-项目总体设计与依赖.md | 74 ++++++ 软件设计文档/01-DMS.Core-详细设计.md | 232 +++++++++++++++++ 软件设计文档/01-核心领域模型与接口.md | 190 ++++++++++++++ 软件设计文档/02-DMS.Application-详细设计.md | 175 +++++++++++++ 软件设计文档/02-数据库设计.md | 215 ++++++++++++++++ .../03-DMS.Infrastructure-详细设计.md | 164 ++++++++++++ 软件设计文档/03-应用层与基础设施层设计.md | 182 ++++++++++++++ 软件设计文档/04-DMS.WPF-详细设计.md | 235 ++++++++++++++++++ 软件设计文档/04-数据处理链设计.md | 223 +++++++++++++++++ 软件设计文档/05-WPF表现层设计.md | 146 +++++++++++ 软件设计文档/05-事务与工作单元设计.md | 201 +++++++++++++++ 软件设计文档/06-动态菜单与导航设计.md | 234 +++++++++++++++++ 软件设计文档/06-实现步骤与示例.md | 216 ++++++++++++++++ 软件设计文档/07-S7后台服务设计.md | 173 +++++++++++++ .../08-中央通道总线(ChannelBus)设计.md | 154 ++++++++++++ 软件设计文档/09-日志记录与聚合过滤设计.md | 184 ++++++++++++++ 软件设计文档/10-变量与MQTT别名设计.md | 179 +++++++++++++ 18 files changed, 3280 insertions(+) create mode 100644 软件设计文档/00-项目总体设计.md create mode 100644 软件设计文档/00-项目总体设计与依赖.md create mode 100644 软件设计文档/01-DMS.Core-详细设计.md create mode 100644 软件设计文档/01-核心领域模型与接口.md create mode 100644 软件设计文档/02-DMS.Application-详细设计.md create mode 100644 软件设计文档/02-数据库设计.md create mode 100644 软件设计文档/03-DMS.Infrastructure-详细设计.md create mode 100644 软件设计文档/03-应用层与基础设施层设计.md create mode 100644 软件设计文档/04-DMS.WPF-详细设计.md create mode 100644 软件设计文档/04-数据处理链设计.md create mode 100644 软件设计文档/05-WPF表现层设计.md create mode 100644 软件设计文档/05-事务与工作单元设计.md create mode 100644 软件设计文档/06-动态菜单与导航设计.md create mode 100644 软件设计文档/06-实现步骤与示例.md create mode 100644 软件设计文档/07-S7后台服务设计.md create mode 100644 软件设计文档/08-中央通道总线(ChannelBus)设计.md create mode 100644 软件设计文档/09-日志记录与聚合过滤设计.md create mode 100644 软件设计文档/10-变量与MQTT别名设计.md diff --git a/软件设计文档/00-项目总体设计.md b/软件设计文档/00-项目总体设计.md new file mode 100644 index 0000000..07dddc7 --- /dev/null +++ b/软件设计文档/00-项目总体设计.md @@ -0,0 +1,103 @@ +# 软件开发文档 - 设备管理系统 (DMS) + +## 1. 引言 + +### 1.1. 项目概述 + +本文档旨在详细阐述设备管理系统(DMS)的设计与实现方案。该系统是一个用于管理、监控和数据采集的综合平台,核心功能包括: + +* **多协议设备支持**:能够管理和监控多种通信协议(如 Siemens S7、OPC UA、Modbus)的设备。 +* **层级化设备模型**:支持“设备 -> 变量表 -> 变量”的层级结构化管理。 +* **灵活的数据转发**:变量数据在采集后,可配置转发至一个或多个MQTT服务器。 +* **实时数据处理**:通过链式处理器对采集到的原始数据进行清洗、计算和转换。 +* **历史数据存储**:将处理后的数据存入数据库,用于历史追溯和分析。 +* **现代化的用户界面**:基于WPF框架,提供直观、响应迅速的操作界面。 + +### 1.2. 设计原则 + +* **分层架构 (Layered Architecture)**:采用经典的洋葱架构思想,将项目分为`核心(Core)`、`应用(Application)`、`基础设施(Infrastructure)`和`表现(Presentation)`四层,实现高内聚、低耦合。 +* **依赖倒置原则 (DIP)**:高层模块不依赖于低层模块的具体实现,而是依赖于抽象(接口)。接口定义在核心层,具体实现在基础设施层。 +* **单一职责原则 (SRP)**:每个类和模块都只负责一项功能。 +* **可扩展性**:系统设计应易于扩展,例如,未来可以方便地增加新的通信协议或数据处理器。 + +## 2. 项目结构设计 + +系统将由以下四个主要项目组成,以实现关注点分离。 + +### 2.1. `DMS.Core` - 核心层 + +这是整个系统的核心,不依赖于任何外部框架或技术。 + +* **职责**: + * 定义核心的业务领域模型(Domain Models),如 `Device`, `VariableTable`, `Variable`, `MqttServer`。 + * 定义业务规则和逻辑。 + * 定义仓储库(Repositories)和领域服务的接口,如 `IDeviceRepository`, `IVariableRepository`。 +* **包含内容**: + * `Models/`: 存放领域模型类。 + * `Enums/`: 存放核心业务相关的枚举。 + * `Interfaces/`: 存放仓储库和领域服务的接口定义。 + * `Exceptions/`: 存放自定义的业务异常。 + +### 2.2. `DMS.Application` - 应用层 + +负责编排和协调领域对象以完成具体的业务用例。 + +* **职责**: + * 实现具体的应用服务(Use Cases),如“添加新设备”、“更新变量值”。 + * 定义数据传输对象(DTOs),用于在表现层和应用层之间传递数据。 + * 处理事务和授权等应用级关注点。 +* **包含内容**: + * `Services/`: 存放应用服务的实现。 + * `DTOs/`: 存放数据传输对象。 + * `Interfaces/`: 存放应用服务的接口。 + +### 2.3. `DMS.Infrastructure` - 基础设施层 + +提供所有与外部世界交互的技术实现。 + +* **职责**: + * 实现 `DMS.Core` 中定义的仓储接口,负责与数据库进行交互(使用 `SqlSugar` ORM)。 + * 实现与其他外部系统的集成,如S7协议通信、MQTT客户端、日志记录(NLog)等。 + * 管理数据库连接、迁移和配置。 +* **包含内容**: + * `Data/`: 包含 `DbContext` 和数据库初始化逻辑。 + * `Repositories/`: 仓储接口的具体实现。 + * `Services/`: 存放与基础设施相关的服务实现(如S7通信服务、MQTT发布服务)。 + * `Entities/`: 数据库表对应的实体类。 + +### 2.4. `DMS.WPF` - 表现层 + +用户界面(UI),负责与用户交互和数据显示。 + +* **职责**: + * 提供用户操作界面。 + * 显示来自应用层的数据。 + * 将用户的操作请求发送给应用层。 +* **包含内容**: + * `Views/`: XAML窗口和用户控件。 + * `ViewModels/`: 视图模型(MVVM模式)。 + * `Services/`: UI相关的服务,如导航、对话框和通知服务。 + * `App.xaml.cs`: 程序启动和依赖注入(DI)容器的配置。 + +### 2.5. 项目依赖关系图 + +```mermaid +graph TD + subgraph 表现层 + DMS.WPF + end + subgraph 应用层 + DMS.Application + end + subgraph 核心层 + DMS.Core + end + subgraph 基础设施层 + DMS.Infrastructure + end + + DMS.WPF --> DMS.Application + DMS.Application --> DMS.Core + DMS.Infrastructure --> DMS.Application + DMS.Infrastructure --> DMS.Core +``` diff --git a/软件设计文档/00-项目总体设计与依赖.md b/软件设计文档/00-项目总体设计与依赖.md new file mode 100644 index 0000000..2e1d435 --- /dev/null +++ b/软件设计文档/00-项目总体设计与依赖.md @@ -0,0 +1,74 @@ +# 软件开发文档 - 00. 项目总体设计与依赖 + +本文档是整个项目的顶层设计,定义了项目结构、职责划分以及每个项目所需的NuGet包依赖。 + +## 1. 项目结构与职责 (修订版) + +| 项目名 | 核心职责 | 关键内容 | +| ---------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| `DMS.Core` | **领域核心**:定义业务规则和数据结构,不依赖任何具体技术实现。是整个系统的稳定内核。 | 领域模型 (`Device`, `Variable`)、业务枚举、仓储接口 (`IDeviceRepository`)、自定义业务异常。 | +| `DMS.Application` | **应用服务**:编排领域模型完成业务用例(Use Case),处理事务、DTO转换。是UI层与核心层的桥梁。 | 应用服务 (`DeviceAppService`)、数据传输对象 (DTOs)、应用层接口、AutoMapper配置。 | +| `DMS.Infrastructure` | **基础设施**:提供所有外部技术的具体实现,如数据库访问、协议通信、日志记录等。负责实现核心层的接口。 | 数据库实体、SqlSugar仓储实现、S7/MQTT通信服务、NLog日志服务、数据处理链的具体处理器。 | +| `DMS.WPF` | **表现层**:提供用户界面(UI),采用MVVM模式。负责用户交互、数据显示和请求派发。 | 视图 (Views)、视图模型 (ViewModels)、UI服务(导航、对话框)、依赖注入容器配置、程序入口。 | + +## 2. NuGet包依赖清单 + +以下是每个项目必须安装的NuGet包,请严格按照此清单进行安装。 + +### 2.1. `DMS.Core` + +此项目应保持纯净,**不安装任何第三方NuGet包**,以确保其独立性。 + +### 2.2. `DMS.Application` + +| 包名 | 用途 | +| ----------------------------------- | -------------------------------------------------------------------- | +| `AutoMapper` | 用于在领域模型和DTO之间进行自动对象映射。 | +| `AutoMapper.Extensions.Microsoft.DependencyInjection` | 将AutoMapper与依赖注入容器集成。 | + +### 2.3. `DMS.Infrastructure` + +| 包名 | 用途 | +| ----------------------------------- | -------------------------------------------------------------------- | +| `SqlSugarCore` | 强大的ORM框架,用于数据库操作。 | +| `S7netplus` | 用于与Siemens S7系列PLC进行通信。 | +| `MQTTnet` | 高性能的.NET MQTT客户端库,用于数据发布。 | +| `MQTTnet.Extensions.ManagedClient` | 提供了更稳定的托管MQTT客户端,支持自动重连。 | +| `NLog` | 灵活的日志记录框架。 | +| `NLog.Extensions.Logging` | 将NLog与Microsoft的通用日志抽象集成。 | +| `Microsoft.Extensions.Caching.Memory` | 提供内存缓存功能,用于实现变量值变化检测。 | + +### 2.4. `DMS.WPF` + +| 包名 | 用途 | +| ----------------------------------------- | -------------------------------------------------------------------- | +| `Microsoft.Extensions.DependencyInjection` | 官方依赖注入框架,用于管理整个应用的生命周期和依赖关系。 | +| `CommunityToolkit.Mvvm` | 官方MVVM工具包,提供`ObservableObject`, `RelayCommand`等,极大简化MVVM开发。 | +| `HandyControls` | 一套美观且功能强大的WPF UI控件库,用于构建现代化界面(如对话框、通知)。 | +| `NLog.Extensions.Logging` | 用于在WPF项目中配置和使用NLog。 | +| `Microsoft.Xaml.Behaviors.Wpf` | 提供事件到命令的绑定等行为支持,是MVVM的有力补充。 | + +## 3. 开发环境初始化命令示例 + +您可以使用 `dotnet` CLI 快速创建项目并添加包: + +```bash +# 创建解决方案和项目 +dotnet new sln -n DMS +dotnet new classlib -n DMS.Core +dotnet new classlib -n DMS.Application +dotnet new classlib -n DMS.Infrastructure +dotnet new wpf -n DMS.WPF + +# 将项目添加到解决方案 +dotnet sln add ./**/*.csproj + +# 为DMS.Application添加包 +dotnet add DMS.Application package AutoMapper +# ...其他包 + +# 添加项目引用 +dotnet add DMS.Application reference DMS.Core +dotnet add DMS.Infrastructure reference DMS.Application +dotnet add DMS.WPF reference DMS.Infrastructure +``` diff --git a/软件设计文档/01-DMS.Core-详细设计.md b/软件设计文档/01-DMS.Core-详细设计.md new file mode 100644 index 0000000..b718538 --- /dev/null +++ b/软件设计文档/01-DMS.Core-详细设计.md @@ -0,0 +1,232 @@ +# 软件开发文档 - 01. DMS.Core 详细设计 + +`DMS.Core` 是系统的核心,定义了所有业务逻辑的蓝图。它不包含任何具体实现,确保了业务规则的独立性和可移植性。 + +## 1. 目录结构 + +``` +DMS.Core/ +├── Enums/ +│ ├── ProtocolType.cs +│ └── SignalType.cs +├── Models/ +│ ├── Device.cs +│ ├── MqttServer.cs +│ ├── Variable.cs +│ ├── VariableHistory.cs +│ └── VariableTable.cs +├── Interfaces/ +│ ├── IBaseRepository.cs +│ ├── IDeviceRepository.cs +│ ├── IMqttServerRepository.cs +│ ├── IVariableHistoryRepository.cs +│ ├── IVariableRepository.cs +│ └── IVariableTableRepository.cs +└── DMS.Core.csproj +``` + +## 2. 文件详解 + +### 2.1. 枚举 (`Enums/`) + +#### `ProtocolType.cs` + +```csharp +// 文件: DMS.Core/Enums/ProtocolType.cs +namespace DMS.Core.Enums; + +/// +/// 定义了设备支持的通信协议类型。 +/// +public enum ProtocolType +{ + /// + /// Siemens S7 通信协议。 + /// + S7, + + /// + /// OPC UA (Unified Architecture) 协议。 + /// + OpcUa, + + /// + /// Modbus TCP 协议。 + /// + ModbusTcp +} +``` + +#### `SignalType.cs` + +```csharp +// 文件: DMS.Core/Enums/SignalType.cs +namespace DMS.Core.Enums; + +/// +/// 定义了变量支持的数据类型。 +/// +public enum SignalType +{ + /// + /// 布尔值 (true/false)。 + /// + Boolean, + + /// + /// 字节 (8-bit 无符号整数)。 + /// + Byte, + + /// + /// 16位有符号整数。 + /// + Int16, + + /// + /// 32位有符号整数。 + /// + Int32, + + /// + /// 单精度浮点数。 + /// + Float, + + /// + /// 字符串。 + /// + String +} +``` + +### 2.2. 领域模型 (`Models/`) + +#### `Device.cs` + +```csharp +// 文件: DMS.Core/Models/Device.cs +namespace DMS.Core.Models; + +/// +/// 代表一个可管理的物理或逻辑设备。 +/// +public class Device +{ + /// + /// 唯一标识符。 + /// + public int Id { get; set; } + + /// + /// 设备名称,用于UI显示和识别。 + /// + public string Name { get; set; } + + /// + /// 设备使用的通信协议。 + /// + public ProtocolType Protocol { get; set; } + + /// + /// 设备的IP地址。 + /// + public string IpAddress { get; set; } + + /// + /// 设备的通信端口号。 + /// + public int Port { get; set; } + + /// + /// 指示此设备是否处于激活状态。只有激活的设备才会被轮询。 + /// + public bool IsActive { get; set; } + + /// + /// 此设备包含的变量表集合。 + /// + public List VariableTables { get; set; } = new(); +} +``` + +... (其他模型 `MqttServer.cs`, `Variable.cs`, `VariableHistory.cs`, `VariableTable.cs` 的代码结构与此类似,包含详细的属性和注释) ... + +### 2.3. 仓储接口 (`Interfaces/`) + +#### `IBaseRepository.cs` + +```csharp +// 文件: DMS.Core/Interfaces/IBaseRepository.cs +namespace DMS.Core.Interfaces; + +/// +/// 提供泛型数据访问操作的基础仓储接口。 +/// +/// 领域模型的类型。 +public interface IBaseRepository where T : class +{ + /// + /// 异步根据ID获取单个实体。 + /// + /// 实体的主键ID。 + /// 找到的实体,如果不存在则返回null。 + Task GetByIdAsync(int id); + + /// + /// 异步获取所有实体。 + /// + /// 所有实体的列表。 + Task> GetAllAsync(); + + /// + /// 异步添加一个新实体。 + /// + /// 要添加的实体。 + Task AddAsync(T entity); + + /// + /// 异步更新一个已存在的实体。 + /// + /// 要更新的实体。 + Task UpdateAsync(T entity); + + /// + /// 异步根据ID删除一个实体。 + /// + /// 要删除的实体的主键ID。 + Task DeleteAsync(int id); +} +``` + +#### `IDeviceRepository.cs` + +```csharp +// 文件: DMS.Core/Interfaces/IDeviceRepository.cs +using DMS.Core.Models; + +namespace DMS.Core.Interfaces; + +/// +/// 继承自IBaseRepository,提供设备相关的特定数据查询功能。 +/// +public interface IDeviceRepository : IBaseRepository +{ + /// + /// 异步获取所有激活的设备,并级联加载其下的变量表和变量。 + /// 这是后台轮询服务需要的主要数据。 + /// + /// 包含完整层级结构的激活设备列表。 + Task> GetActiveDevicesWithDetailsAsync(); + + /// + /// 异步根据协议类型获取设备列表。 + /// + /// 协议类型。 + /// 符合条件的设备列表。 + Task> GetDevicesByProtocolAsync(ProtocolType protocol); +} +``` + +... (其他仓储接口 `IMqttServerRepository.cs`, `IVariableRepository.cs` 等的代码结构与此类似) ... + diff --git a/软件设计文档/01-核心领域模型与接口.md b/软件设计文档/01-核心领域模型与接口.md new file mode 100644 index 0000000..5d73888 --- /dev/null +++ b/软件设计文档/01-核心领域模型与接口.md @@ -0,0 +1,190 @@ +# 软件开发文档 - 01. 核心领域模型与接口 + +本文档详细定义 `DMS.Core` 项目中的领域模型、仓储接口和核心枚举。 + +## 1. 核心枚举 (`Enums/`) + +```csharp +// 文件: DMS.Core/Enums/ProtocolType.cs +namespace DMS.Core.Enums; + +public enum ProtocolType +{ + S7, + OpcUa, + ModbusTcp +} + +// 文件: DMS.Core/Enums/SignalType.cs +namespace DMS.Core.Enums; + +public enum SignalType +{ + Boolean, + Byte, + Int16, + Int32, + Float, + String +} +``` + +## 2. 领域模型 (`Models/`) + +领域模型是业务核心的C#类表示。 + +### 2.1. `Device` - 设备 + +代表一个物理或逻辑设备。 + +```csharp +// 文件: DMS.Core/Models/Device.cs +namespace DMS.Core.Models; + +public class Device +{ + public int Id { get; set; } + public string Name { get; set; } + public ProtocolType Protocol { get; set; } + public string IpAddress { get; set; } // 设备IP地址 + public int Port { get; set; } // 端口号 + public bool IsActive { get; set; } // 是否启用 + public List VariableTables { get; set; } = new(); +} +``` + +### 2.2. `VariableTable` - 变量表 + +组织和管理一组相关的变量。 + +```csharp +// 文件: DMS.Core/Models/VariableTable.cs +namespace DMS.Core.Models; + +public class VariableTable +{ + public int Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public bool IsActive { get; set; } // 是否启用 + public int DeviceId { get; set; } + public Device Device { get; set; } + public List Variables { get; set; } = new(); +} +``` + +### 2.3. `Variable` - 变量 + +核心数据点,代表从设备读取的单个值。 + +```csharp +// 文件: DMS.Core/Models/Variable.cs +namespace DMS.Core.Models; + +public class Variable +{ + public int Id { get; set; } + public string Name { get; set; } // 变量名 + public string Address { get; set; } // 在设备中的地址 (例如: DB1.DBD0, M100.0) + public SignalType DataType { get; set; } // 数据类型 + public bool IsActive { get; set; } // 是否启用 + public int VariableTableId { get; set; } + public VariableTable VariableTable { get; set; } + public List MqttServers { get; set; } = new(); // 关联的MQTT服务器 +} +``` + +### 2.4. `MqttServer` - MQTT服务器 + +代表一个MQTT Broker的配置。 + +```csharp +// 文件: DMS.Core/Models/MqttServer.cs +namespace DMS.Core.Models; + +public class MqttServer +{ + public int Id { get; set; } + public string ServerName { get; set; } + public string BrokerAddress { get; set; } // Broker地址 + public int Port { get; set; } // 端口 + public string Username { get; set; } + public string Password { get; set; } + public bool IsActive { get; set; } // 是否启用 + public List Variables { get; set; } = new(); // 关联的变量 +} +``` + +### 2.5. `VariableHistory` - 变量历史记录 + +用于存储变量值的变化记录。 + +```csharp +// 文件: DMS.Core/Models/VariableHistory.cs +namespace DMS.Core.Models; + +public class VariableHistory +{ + public long Id { get; set; } + public int VariableId { get; set; } + public string Value { get; set; } // 以字符串形式存储,便于通用性 + public DateTime Timestamp { get; set; } +} +``` + +## 3. 仓储接口 (`Interfaces/`) + +仓储接口定义了数据持久化的契约,由基础设施层实现。 + +### 3.1. `IBaseRepository` - 基础仓储接口 + +```csharp +// 文件: DMS.Core/Interfaces/IBaseRepository.cs +namespace DMS.Core.Interfaces; + +public interface IBaseRepository where T : class +{ + Task GetByIdAsync(int id); + Task> GetAllAsync(); + Task AddAsync(T entity); + Task UpdateAsync(T entity); + Task DeleteAsync(int id); +} +``` + +### 3.2. 专用仓储接口 + +这些接口继承自 `IBaseRepository` 并可以添加特定于该模型的查询方法。 + +```csharp +// 文件: DMS.Core/Interfaces/IDeviceRepository.cs +namespace DMS.Core.Interfaces; + +public interface IDeviceRepository : IBaseRepository +{ + // 获取所有激活的设备,并包含其变量表和变量 + Task> GetActiveDevicesWithDetailsAsync(); + // 根据协议类型获取设备 + Task> GetDevicesByProtocolAsync(ProtocolType protocol); +} + +// 文件: DMS.Core/Interfaces/IVariableRepository.cs +namespace DMS.Core.Interfaces; + +public interface IVariableRepository : IBaseRepository +{ + // 获取一个变量及其关联的MQTT服务器 + Task GetVariableWithMqttServersAsync(int variableId); +} + +// 文件: DMS.Core/Interfaces/IMqttServerRepository.cs +namespace DMS.Core.Interfaces; + +public interface IMqttServerRepository : IBaseRepository +{ + // 获取一个MQTT服务器及其关联的所有变量 + Task GetMqttServerWithVariablesAsync(int serverId); +} + +// 其他仓储接口,如 IVariableTableRepository, IVariableHistoryRepository 等,结构类似。 +``` diff --git a/软件设计文档/02-DMS.Application-详细设计.md b/软件设计文档/02-DMS.Application-详细设计.md new file mode 100644 index 0000000..0d80589 --- /dev/null +++ b/软件设计文档/02-DMS.Application-详细设计.md @@ -0,0 +1,175 @@ +# 软件开发文档 - 02. DMS.Application 详细设计 + +`DMS.Application` 层是业务逻辑的执行者和协调者。它接收来自表现层的请求,使用 `DMS.Core` 的领域模型和接口来完成任务,并向上层返回DTOs。 + +## 1. 目录结构 + +``` +DMS.Application/ +├── DTOs/ +│ ├── DeviceDto.cs +│ ├── MqttServerDto.cs +│ ├── VariableDto.cs +│ └── ... (其他增、删、改、查相关的DTO) +├── Interfaces/ +│ ├── IDeviceAppService.cs +│ ├── IMqttAppService.cs +│ └── ... (其他应用服务接口) +├── Services/ +│ ├── DeviceAppService.cs +│ ├── MqttAppService.cs +│ └── ... (其他应用服务实现) +├── Profiles/ +│ └── MappingProfile.cs +└── DMS.Application.csproj +``` + +## 2. 文件详解 + +### 2.1. 数据传输对象 (`DTOs/`) + +DTOs 是为了隔离领域模型和UI而设计的简单数据容器。 + +#### `DeviceDto.cs` + +```csharp +// 文件: DMS.Application/DTOs/DeviceDto.cs +namespace DMS.Application.DTOs; + +/// +/// 用于在UI上显示设备基本信息的DTO。 +/// +public class DeviceDto +{ + public int Id { get; set; } + public string Name { get; set; } + public string Protocol { get; set; } + public string IpAddress { get; set; } + public int Port { get; set; } + public bool IsActive { get; set; } + public string Status { get; set; } // "在线", "离线", "连接中..." +} +``` + +#### `CreateDeviceDto.cs` + +```csharp +// 文件: DMS.Application/DTOs/CreateDeviceDto.cs +using DMS.Core.Enums; + +namespace DMS.Application.DTOs; + +/// +/// 用于创建新设备时传输数据的DTO。 +/// +public class CreateDeviceDto +{ + public string Name { get; set; } + public ProtocolType Protocol { get; set; } + public string IpAddress { get; set; } + public int Port { get; set; } +} +``` + +... (其他DTOs如 `UpdateDeviceDto`, `VariableTableDto`, `MqttServerDto` 等结构类似) ... + +### 2.2. 应用服务接口 (`Interfaces/`) + +#### `IDeviceAppService.cs` + +```csharp +// 文件: DMS.Application/Interfaces/IDeviceAppService.cs +using DMS.Application.DTOs; + +namespace DMS.Application.Interfaces; + +/// +/// 定义设备管理相关的应用服务操作。 +/// +public interface IDeviceAppService +{ + Task GetDeviceByIdAsync(int id); + Task> GetAllDevicesAsync(); + Task CreateDeviceAsync(CreateDeviceDto deviceDto); + Task UpdateDeviceAsync(UpdateDeviceDto deviceDto); + Task DeleteDeviceAsync(int id); + Task ToggleDeviceActiveStateAsync(int id); +} +``` + +### 2.3. 应用服务实现 (`Services/`) + +#### `DeviceAppService.cs` + +```csharp +// 文件: DMS.Application/Services/DeviceAppService.cs +using AutoMapper; +using DMS.Core.Interfaces; +using DMS.Core.Models; + +namespace DMS.Application.Services; + +/// +/// 实现设备管理的应用服务。 +/// +public class DeviceAppService : IDeviceAppService +{ + private readonly IDeviceRepository _deviceRepository; + private readonly IMapper _mapper; + + public DeviceAppService(IDeviceRepository deviceRepository, IMapper mapper) + { + _deviceRepository = deviceRepository; + _mapper = mapper; + } + + public async Task CreateDeviceAsync(CreateDeviceDto deviceDto) + { + var device = _mapper.Map(deviceDto); + device.IsActive = true; // 默认激活 + await _deviceRepository.AddAsync(device); + return device.Id; // 返回新创建的ID + } + + // ... 其他方法的完整实现 +} +``` + +### 2.4. AutoMapper配置 (`Profiles/`) + +#### `MappingProfile.cs` + +```csharp +// 文件: DMS.Application/Profiles/MappingProfile.cs +using AutoMapper; +using DMS.Core.Models; +using DMS.Application.DTOs; +using DMS.Core.Enums; + +namespace DMS.Application.Profiles; + +/// +/// 配置AutoMapper的映射规则。 +/// +public class MappingProfile : Profile +{ + public MappingProfile() + { + // Device 映射 + CreateMap(); + CreateMap(); + CreateMap() + .ForMember(dest => dest.Protocol, opt => opt.MapFrom(src => src.Protocol.ToString())); + + // VariableTable 映射 + CreateMap(); + + // Variable 映射 + CreateMap() + .ForMember(dest => dest.DataType, opt => opt.MapFrom(src => src.DataType.ToString())); + + // MqttServer 映射 + CreateMap(); + } +} +``` diff --git a/软件设计文档/02-数据库设计.md b/软件设计文档/02-数据库设计.md new file mode 100644 index 0000000..c0168c8 --- /dev/null +++ b/软件设计文档/02-数据库设计.md @@ -0,0 +1,215 @@ +# 软件开发文档 - 02. 数据库设计 + +本文档详细描述了DMS系统的数据库结构,包括表、字段和关系。数据库实体类将放在 `DMS.Infrastructure/Entities/` 目录下。 + +## 1. 数据库关系图 (ERD) + +```mermaid +erDiagram + DEVICE { + int Id PK + varchar Name + int Protocol + varchar IpAddress + int Port + bool IsActive + } + + VARIABLE_TABLE { + int Id PK + varchar Name + varchar Description + bool IsActive + int DeviceId FK + } + + VARIABLE { + int Id PK + varchar Name + varchar Address + int DataType + bool IsActive + int VariableTableId FK + } + + MQTT_SERVER { + int Id PK + varchar ServerName + varchar BrokerAddress + int Port + varchar Username + varchar Password + bool IsActive + } + + VARIABLE_HISTORY { + bigint Id PK + int VariableId FK + varchar Value + datetime Timestamp + } + + VARIABLE_MQTT_SERVER_MAP { + int VariableId PK, FK + int MqttServerId PK, FK + } + + DEVICE ||--o{ VARIABLE_TABLE : "包含" + VARIABLE_TABLE ||--o{ VARIABLE : "包含" + VARIABLE ||--o{ VARIABLE_HISTORY : "记录" + VARIABLE }o--o{ MQTT_SERVER : "关联 (多对多)" + VARIABLE_MQTT_SERVER_MAP }|..|{ VARIABLE : "" + VARIABLE_MQTT_SERVER_MAP }|..|{ MQTT_SERVER : "" + +``` + +## 2. 数据库实体类 (`Entities/`) + +这些是与数据库表一一对应的C#类,使用了 `SqlSugar` 的特性(Attribute)来定义主键、外键等。 + +### 2.1. `DbDevice` + +```csharp +// 文件: DMS.Infrastructure/Entities/DbDevice.cs +using SqlSugar; + +namespace DMS.Infrastructure.Entities; + +[SugarTable("Devices")] +public class DbDevice +{ + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + public string Name { get; set; } + public int Protocol { get; set; } // 对应 ProtocolType 枚举 + public string IpAddress { get; set; } + public int Port { get; set; } + public bool IsActive { get; set; } +} +``` + +### 2.2. `DbVariableTable` + +```csharp +// 文件: DMS.Infrastructure/Entities/DbVariableTable.cs +using SqlSugar; + +namespace DMS.Infrastructure.Entities; + +[SugarTable("VariableTables")] +public class DbVariableTable +{ + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public bool IsActive { get; set; } + public int DeviceId { get; set; } +} +``` + +### 2.3. `DbVariable` + +```csharp +// 文件: DMS.Infrastructure/Entities/DbVariable.cs +using SqlSugar; + +namespace DMS.Infrastructure.Entities; + +[SugarTable("Variables")] +public class DbVariable +{ + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + public string Name { get; set; } + public string Address { get; set; } + public int DataType { get; set; } // 对应 SignalType 枚举 + public bool IsActive { get; set; } + public int VariableTableId { get; set; } +} +``` + +### 2.4. `DbMqttServer` + +```csharp +// 文件: DMS.Infrastructure/Entities/DbMqttServer.cs +using SqlSugar; + +namespace DMS.Infrastructure.Entities; + +[SugarTable("MqttServers")] +public class DbMqttServer +{ + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + public string ServerName { get; set; } + public string BrokerAddress { get; set; } + public int Port { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public bool IsActive { get; set; } +} +``` + +### 2.5. `DbVariableHistory` + +```csharp +// 文件: DMS.Infrastructure/Entities/DbVariableHistory.cs +using SqlSugar; + +namespace DMS.Infrastructure.Entities; + +[SugarTable("VariableHistories")] +public class DbVariableHistory +{ + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public long Id { get; set; } + public int VariableId { get; set; } + public string Value { get; set; } + public DateTime Timestamp { get; set; } +} +``` + +### 2.6. `DbVariableMqttMap` - 多对多关系映射表 + +用于连接 `Variable` 和 `MqttServer`。 + +```csharp +// 文件: DMS.Infrastructure/Entities/DbVariableMqttMap.cs +using SqlSugar; + +namespace DMS.Infrastructure.Entities; + +[SugarTable("VariableMqttMap")] +public class DbVariableMqttMap +{ + [SugarColumn(IsPrimaryKey = true)] + public int VariableId { get; set; } + + [SugarColumn(IsPrimaryKey = true)] + public int MqttServerId { get; set; } +} +``` + +## 3. 数据映射 (Mapping) + +为了在领域模型 (`DMS.Core.Models`) 和数据库实体 (`DMS.Infrastructure.Entities`) 之间进行转换,我们将使用 `AutoMapper`。映射配置将放在 `DMS.Infrastructure/Profiles/MappingProfile.cs` 中。 + +```csharp +// 文件: DMS.Infrastructure/Profiles/MappingProfile.cs +using AutoMapper; +using DMS.Core.Models; +using DMS.Infrastructure.Entities; + +public class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + // 注意:对于复杂的嵌套映射,可能需要更详细的配置。 + } +} +``` diff --git a/软件设计文档/03-DMS.Infrastructure-详细设计.md b/软件设计文档/03-DMS.Infrastructure-详细设计.md new file mode 100644 index 0000000..6efb1e4 --- /dev/null +++ b/软件设计文档/03-DMS.Infrastructure-详细设计.md @@ -0,0 +1,164 @@ +# 软件开发文档 - 03. DMS.Infrastructure 详细设计 + +`DMS.Infrastructure` 层是所有外部技术和服务的具体实现地。它实现了 `DMS.Core` 定义的接口,为 `DMS.Application` 层提供数据和功能支持。 + +## 1. 目录结构 + +``` +DMS.Infrastructure/ +├── Data/ +│ └── SqlSugarDbContext.cs +├── Entities/ +│ ├── DbDevice.cs +│ ├── DbMqttServer.cs +│ ├── DbVariable.cs +│ └── ... (所有数据库表对应的实体) +├── Repositories/ +│ ├── BaseRepository.cs +│ ├── DeviceRepository.cs +│ └── ... (所有仓储接口的实现) +├── Services/ +│ ├── Communication/ +│ │ ├── S7CommunicationService.cs +│ │ └── MqttPublishService.cs +│ ├── Processing/ +│ │ ├── ChangeDetectionProcessor.cs +│ │ ├── HistoryStorageProcessor.cs +│ │ └── MqttPublishProcessor.cs +│ └── DatabaseInitializerService.cs +└── DMS.Infrastructure.csproj +``` + +## 2. 文件详解 + +### 2.1. 数据库实体 (`Entities/`) + +#### `DbDevice.cs` + +```csharp +// 文件: DMS.Infrastructure/Entities/DbDevice.cs +using SqlSugar; + +namespace DMS.Infrastructure.Entities; + +[SugarTable("Devices")] +public class DbDevice +{ + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + public string Name { get; set; } + public int Protocol { get; set; } + public string IpAddress { get; set; } + public int Port { get; set; } + public bool IsActive { get; set; } +} +``` +... (其他实体 `DbVariableTable`, `DbVariable`, `DbMqttServer`, `DbVariableHistory`, `DbVariableMqttMap` 结构类似) ... + +### 2.2. 仓储实现 (`Repositories/`) + +#### `BaseRepository.cs` + +```csharp +// 文件: DMS.Infrastructure/Repositories/BaseRepository.cs +// 注意:这里需要一个从领域模型到数据库实体的映射,实际项目中通常使用AutoMapper的ProjectTo或手动映射。 +// 为简化示例,此处仅做基础实现。 +``` + +#### `DeviceRepository.cs` + +```csharp +// 文件: DMS.Infrastructure/Repositories/DeviceRepository.cs +using AutoMapper; +using DMS.Core.Interfaces; +using DMS.Core.Models; +using DMS.Infrastructure.Entities; +using SqlSugar; + +public class DeviceRepository : IDeviceRepository +{ + private readonly ISqlSugarClient _db; + private readonly IMapper _mapper; + + public DeviceRepository(ISqlSugarClient db, IMapper mapper) { /* ... */ } + + public async Task> GetActiveDevicesWithDetailsAsync() + { + var dbResult = await _db.Queryable() + .Where(d => d.IsActive) + .ToListAsync(); + // 此处需要手动或通过AutoMapper加载关联的变量表和变量 + return _mapper.Map>(dbResult); + } + // ... 其他方法的实现 +} +``` + +### 2.3. 外部服务 (`Services/`) + +#### `S7CommunicationService.cs` + +```csharp +// 文件: DMS.Infrastructure/Services/Communication/S7CommunicationService.cs +using S7.Net; +using System.Collections.Concurrent; + +public class S7CommunicationService : IS7CommunicationService +{ + private readonly ConcurrentDictionary _clients = new(); + + public async Task ReadVariableAsync(Device device, Variable variable) + { + if (!_clients.TryGetValue(device.Id, out var plc)) + { + plc = new Plc(CpuType.S71500, device.IpAddress, 0, 1); + await plc.OpenAsync(); + _clients[device.Id] = plc; + } + return await plc.ReadAsync(variable.Address); + } + // ... 其他连接、断开等管理方法 +} +``` + +#### `MqttPublishService.cs` + +```csharp +// 文件: DMS.Infrastructure/Services/Communication/MqttPublishService.cs +using MQTTnet; +using MQTTnet.Client; + +public class MqttPublishService : IMqttPublishService +{ + private readonly IMqttClient _mqttClient; + + public MqttPublishService() { + var factory = new MqttFactory(); + _mqttClient = factory.CreateMqttClient(); + } + + public async Task PublishAsync(MqttServer server, string topic, string payload) + { + if (!_mqttClient.IsConnected) + { + var options = new MqttClientOptionsBuilder() + .WithTcpServer(server.BrokerAddress, server.Port) + .WithCredentials(server.Username, server.Password) + .Build(); + await _mqttClient.ConnectAsync(options, CancellationToken.None); + } + + var message = new MqttApplicationMessageBuilder() + .WithTopic(topic) + .WithPayload(payload) + .WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce) + .Build(); + + await _mqttClient.PublishAsync(message, CancellationToken.None); + } +} +``` + +### 2.4. 数据处理器 (`Services/Processing/`) + +(此处包含 `ChangeDetectionProcessor`, `HistoryStorageProcessor`, `MqttPublishProcessor` 的完整实现,如之前文档所示,但会更加细化,并注入 `ILogger`) diff --git a/软件设计文档/03-应用层与基础设施层设计.md b/软件设计文档/03-应用层与基础设施层设计.md new file mode 100644 index 0000000..43575c8 --- /dev/null +++ b/软件设计文档/03-应用层与基础设施层设计.md @@ -0,0 +1,182 @@ +# 软件开发文档 - 03. 应用层与基础设施层设计 + +本文档定义了 `DMS.Application` 和 `DMS.Infrastructure` 两个项目的详细设计。 + +## 1. `DMS.Application` - 应用层设计 + +应用层是业务流程的协调者。 + +### 1.1. 应用服务接口 (`Interfaces/`) + +```csharp +// 文件: DMS.Application/Interfaces/IDeviceAppService.cs +namespace DMS.Application.Interfaces; + +public interface IDeviceAppService +{ + Task GetDeviceByIdAsync(int id); + Task> GetAllDevicesAsync(); + Task CreateDeviceAsync(CreateDeviceDto deviceDto); + Task UpdateDeviceAsync(UpdateDeviceDto deviceDto); + Task DeleteDeviceAsync(int id); +} + +// 其他应用服务接口,如 IMqttAppService, IVariableAppService 等,结构类似。 +``` + +### 1.2. 数据传输对象 (`DTOs/`) + +DTOs 用于封装数据,在各层之间传递,特别是用于UI。 + +```csharp +// 文件: DMS.Application/DTOs/DeviceDto.cs +namespace DMS.Application.DTOs; + +public class DeviceDto +{ + public int Id { get; set; } + public string Name { get; set; } + public string Protocol { get; set; } + public bool IsActive { get; set; } + public int OnlineStatus { get; set; } // 0: Offline, 1: Online +} + +// 文件: DMS.Application/DTOs/CreateDeviceDto.cs +namespace DMS.Application.DTOs; + +public class CreateDeviceDto +{ + public string Name { get; set; } + public ProtocolType Protocol { get; set; } + public string IpAddress { get; set; } + public int Port { get; set; } +} + +// 其他DTOs,如 UpdateDeviceDto, VariableDto, MqttServerDto 等。 +``` + +### 1.3. 应用服务实现 (`Services/`) + +应用服务实现了接口,并使用仓储库来执行操作。 + +```csharp +// 文件: DMS.Application/Services/DeviceAppService.cs +namespace DMS.Application.Services; + +public class DeviceAppService : IDeviceAppService +{ + private readonly IDeviceRepository _deviceRepository; + private readonly IMapper _mapper; + + public DeviceAppService(IDeviceRepository deviceRepository, IMapper mapper) + { + _deviceRepository = deviceRepository; + _mapper = mapper; + } + + public async Task CreateDeviceAsync(CreateDeviceDto deviceDto) + { + var device = _mapper.Map(deviceDto); + device.IsActive = true; // 默认激活 + await _deviceRepository.AddAsync(device); + } + + // ... 其他方法的实现 +} +``` + +## 2. `DMS.Infrastructure` - 基础设施层设计 + +基础设施层提供具体的技术实现。 + +### 2.1. 仓储库实现 (`Repositories/`) + +这里是 `DMS.Core` 中定义的仓储接口的具体实现。 + +```csharp +// 文件: DMS.Infrastructure/Repositories/DeviceRepository.cs +namespace DMS.Infrastructure.Repositories; + +public class DeviceRepository : BaseRepository, IDeviceRepository +{ + public DeviceRepository(ISqlSugarClient db, IMapper mapper) : base(db, mapper) { } + + public async Task> GetActiveDevicesWithDetailsAsync() + { + // 使用 SqlSugar 的 Include/Navigate 功能来实现嵌套查询 + var dbDevices = await _db.Queryable() + .Where(d => d.IsActive) + .ToListAsync(); + // ... 此处需要手动或通过AutoMapper的ProjectTo来加载关联数据 + return _mapper.Map>(dbDevices); + } + + public async Task> GetDevicesByProtocolAsync(ProtocolType protocol) + { + var dbDevices = await _db.Queryable().Where(d => d.Protocol == (int)protocol).ToListAsync(); + return _mapper.Map>(dbDevices); + } +} +``` + +### 2.2. 外部服务 (`Services/`) + +#### 2.2.1. S7通信服务 + +负责与Siemens S7 PLC进行通信。 + +```csharp +// 文件: DMS.Infrastructure/Services/S7CommunicationService.cs +namespace DMS.Infrastructure.Services; + +public interface IS7CommunicationService +{ + Task ReadVariableAsync(Device device, Variable variable); + Task ConnectAsync(Device device); + Task DisconnectAsync(Device device); +} + +public class S7CommunicationService : IS7CommunicationService +{ + // 使用第三方库,如 S7.Net Plus + private readonly ConcurrentDictionary _clients = new(); + + public async Task ConnectAsync(Device device) + { + // ... 实现连接逻辑 + } + + public async Task ReadVariableAsync(Device device, Variable variable) + { + // ... 实现读变量逻辑 + // 1. 检查连接是否存在 + // 2. 解析 variable.Address + // 3. 调用 S7.Net 的 Read 方法 + // 4. 返回读取到的值 + } + // ... 其他方法 +} +``` + +#### 2.2.2. MQTT发布服务 + +负责将数据发布到MQTT Broker。 + +```csharp +// 文件: DMS.Infrastructure/Services/MqttPublishService.cs +namespace DMS.Infrastructure.Services; + +public interface IMqttPublishService +{ + Task PublishAsync(MqttServer server, string topic, string payload); +} + +public class MqttPublishService : IMqttPublishService +{ + // 使用第三方库,如 MQTTnet + public async Task PublishAsync(MqttServer server, string topic, string payload) + { + // ... 实现MQTT发布逻辑 + } +} +``` diff --git a/软件设计文档/04-DMS.WPF-详细设计.md b/软件设计文档/04-DMS.WPF-详细设计.md new file mode 100644 index 0000000..fd640cf --- /dev/null +++ b/软件设计文档/04-DMS.WPF-详细设计.md @@ -0,0 +1,235 @@ +# 软件开发文档 - 04. DMS.WPF 详细设计 (修订版) + +`DMS.WPF` 是系统的用户界面层。本文档详细描述其设计,此修订版特别强调了构建**响应式UI**的核心模式。 + +## 1. 核心设计模式 + +### 1.1. MVVM (Model-View-ViewModel) +严格遵循MVVM模式,实现UI与逻辑的彻底分离。 + +### 1.2. 依赖注入 (Dependency Injection) +使用 `Microsoft.Extensions.DependencyInjection` 统一管理所有服务的生命周期,实现松耦合。 + +### 1.3. ItemViewModel 模式 +**问题**:从应用层获取的DTO(如`DeviceDto`)是简单数据对象,不具备变更通知能力。当后台数据变化时,直接绑定DTO的UI无法自动更新。 + +**解决方案**:为集合中的每一项数据(如每个设备)创建一个专属的、能发出变更通知的ViewModel。这个`ItemViewModel`会“包裹”原始的DTO,并暴露绑定到UI的属性。当`ItemViewModel`的属性更新时,UI会通过数据绑定自动刷新。 + +### 1.4. 消息总线 (Messenger) +**问题**:后台服务(如S7通信服务)与UI线程中的ViewModel需要通信(例如,通知设备状态已更新),但它们之间不应有直接引用,否则会造成紧耦合。 + +**解决方案**:使用消息总线(或事件聚合器)作为解耦的通信中介。`CommunityToolkit.Mvvm` 提供了轻量级的 `IMessenger` 实现。后台服务向总线“广播”消息,所有“订阅”了该消息的ViewModel都会收到通知并做出响应。 + +## 2. 目录结构 (修订版) + +``` +DMS.WPF/ +├── App.xaml.cs +├── ... +├── Messages/ <-- (新增) 存放消息总线的消息类 +│ └── DeviceStatusChangedMessage.cs +├── Services/ +│ ├── ... (导航、对话框服务) +├── ViewModels/ +│ ├── Base/ <-- 存放ViewModel基类和命令 +│ │ ├── BaseViewModel.cs +│ │ └── RelayCommand.cs +│ ├── Items/ <-- (新增) 存放所有ItemViewModel +│ │ └── DeviceItemViewModel.cs +│ ├── DashboardViewModel.cs +│ ├── DeviceListViewModel.cs <-- (已修改) +│ └── MainViewModel.cs +├── Views/ +│ └── ... +└── DMS.WPF.csproj +``` + +## 3. 文件详解 + +### 3.1. 消息 (`Messages/`) + +#### `DeviceStatusChangedMessage.cs` + +```csharp +// 文件: DMS.WPF/Messages/DeviceStatusChangedMessage.cs +namespace DMS.WPF.Messages; + +/// +/// 当设备状态在后台发生变化时,通过IMessenger广播此消息。 +/// +public class DeviceStatusChangedMessage +{ + /// + /// 状态发生变化的设备ID。 + /// + public int DeviceId { get; } + + /// + /// 设备的新状态文本 (例如: "在线", "离线", "错误")。 + /// + public string NewStatus { get; } + + public DeviceStatusChangedMessage(int deviceId, string newStatus) + { + DeviceId = deviceId; + NewStatus = newStatus; + } +} +``` + +### 3.2. ItemViewModel (`ViewModels/Items/`) + +#### `DeviceItemViewModel.cs` + +```csharp +// 文件: DMS.WPF/ViewModels/Items/DeviceItemViewModel.cs +using CommunityToolkit.Mvvm.ComponentModel; +using DMS.Application.DTOs; + +namespace DMS.WPF.ViewModels.Items; + +/// +/// 代表设备列表中的单个设备项的ViewModel。 +/// 实现了INotifyPropertyChanged,其任何属性变化都会自动通知UI。 +/// +public partial class DeviceItemViewModel : ObservableObject +{ + public int Id { get; } + + [ObservableProperty] + private string _name; + + [ObservableProperty] + private string _protocol; + + [ObservableProperty] + private string _ipAddress; + + [ObservableProperty] + private bool _isActive; + + [ObservableProperty] + private string _status; // 这个属性的改变会立刻反映在UI上 + + public DeviceItemViewModel(DeviceDto dto) + { + Id = dto.Id; + _name = dto.Name; + _protocol = dto.Protocol; + _ipAddress = dto.IpAddress; + _isActive = dto.IsActive; + _status = dto.Status; // 初始状态 + } +} +``` + +### 3.3. 核心视图模型 (`ViewModels/`) + +#### `DeviceListViewModel.cs` (修订版) + +```csharp +// 文件: DMS.WPF/ViewModels/DeviceListViewModel.cs +using CommunityToolkit.Mvvm.Messaging; +using DMS.Application.Interfaces; +using DMS.WPF.Messages; +using DMS.WPF.ViewModels.Items; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; + +namespace DMS.WPF.ViewModels; + +/// +/// 设备列表视图的ViewModel,现在接收消息以更新设备状态。 +/// +public class DeviceListViewModel : BaseViewModel, IRecipient +{ + private readonly IDeviceAppService _deviceAppService; + + /// + /// 绑定到UI的设备集合,类型已变为DeviceItemViewModel。 + /// + public ObservableCollection Devices { get; } = new(); + + public DeviceListViewModel(IDeviceAppService deviceAppService, IMessenger messenger) + { + _deviceAppService = deviceAppService; + // 注册消息,以便可以接收到它 + messenger.Register(this); + } + + public override async Task LoadAsync() + { + IsBusy = true; + Devices.Clear(); + var deviceDtos = await _deviceAppService.GetAllDevicesAsync(); + foreach (var dto in deviceDtos) + { + Devices.Add(new DeviceItemViewModel(dto)); + } + IsBusy = false; + } + + /// + /// 实现IRecipient接口,当接收到DeviceStatusChangedMessage消息时此方法被调用。 + /// + public void Receive(DeviceStatusChangedMessage message) + { + var deviceToUpdate = Devices.FirstOrDefault(d => d.Id == message.DeviceId); + if (deviceToUpdate != null) + { + // 直接更新ItemViewModel的属性,UI会自动响应! + deviceToUpdate.Status = message.NewStatus; + } + } +} +``` + +### 3.4. 依赖注入 (`App.xaml.cs`) (修订版) + +```csharp +// 文件: DMS.WPF/App.xaml.cs +using CommunityToolkit.Mvvm.Messaging; +// ...其他using + +public partial class App : System.Windows.Application +{ + // ... + private void ConfigureServices(IServiceCollection services) + { + // 消息总线 (关键新增) + // 使用弱引用信使,避免内存泄漏 + services.AddSingleton(WeakReferenceMessenger.Default); + + // 应用层 & 基础设施层 + // ... + + // WPF UI 服务 + // ... + + // ViewModels + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); // 每次导航都创建新的实例 + + // Views + services.AddSingleton(); + } +} +``` + +### 3.5. 视图 (`Views/`) + +`DeviceListView.xaml` 的绑定目标现在是 `DeviceItemViewModel` 的属性,无需更改绑定路径,但现在它具备了实时更新的能力。 + +```xml + + + + + + + + + +``` \ No newline at end of file diff --git a/软件设计文档/04-数据处理链设计.md b/软件设计文档/04-数据处理链设计.md new file mode 100644 index 0000000..9fc23d5 --- /dev/null +++ b/软件设计文档/04-数据处理链设计.md @@ -0,0 +1,223 @@ +# 软件开发文档 - 04. 数据处理链设计 + +本文档专门阐述当系统从S7 PLC等设备读取到变量值后,如何通过一个可扩展的、链式的处理流程,对数据进行加工、存储和转发。 + +## 1. 设计目标 + +* **解耦**:每个处理步骤应是独立的、可复用的。 +* **可扩展**:可以轻松地添加新的处理环节(例如:数据校验、报警判断、线性转换等)。 +* **高性能**:处理流程应高效,避免阻塞数据采集线程。 +* **可配置**:能够根据不同变量的需求,动态构建处理链。 + +## 2. 核心概念:`VariableContext` + +为了在处理链中传递数据,我们定义一个上下文对象 `VariableContext`。它将携带变量的原始值、当前值以及其他相关信息,在整个处理链中流动。 + +```csharp +// 文件: DMS.Core/Models/VariableContext.cs +namespace DMS.Core.Models; + +public class VariableContext +{ + public Variable Variable { get; init; } + public object RawValue { get; init; } // 从设备读取的原始值 + public object CurrentValue { get; set; } // 当前处理后的值 + public DateTime Timestamp { get; init; } + public bool IsValueChanged { get; set; } // 标记值是否发生变化 + public bool IsProcessingTerminated { get; set; } // 是否终止后续处理 + + public VariableContext(Variable variable, object rawValue) + { + Variable = variable; + RawValue = rawValue; + CurrentValue = rawValue; + Timestamp = DateTime.UtcNow; + } +} +``` + +## 3. 处理器接口与抽象基类 + +我们定义一个统一的处理器接口 `IVariableProcessor` 和一个实现了链式调用的抽象基类 `VariableProcessorBase`。 + +### 3.1. `IVariableProcessor` 接口 + +```csharp +// 文件: DMS.Application/Interfaces/IVariableProcessor.cs +namespace DMS.Application.Interfaces; + +public interface IVariableProcessor +{ + IVariableProcessor SetNext(IVariableProcessor next); + Task ProcessAsync(VariableContext context); +} +``` + +### 3.2. `VariableProcessorBase` 抽象基类 + +```csharp +// 文件: DMS.Application/Services/Processors/VariableProcessorBase.cs +namespace DMS.Application.Services.Processors; + +public abstract class VariableProcessorBase : IVariableProcessor +{ + private IVariableProcessor _next; + + public IVariableProcessor SetNext(IVariableProcessor next) + { + _next = next; + return next; + } + + public virtual async Task ProcessAsync(VariableContext context) + { + if (context.IsProcessingTerminated) return; + + await HandleAsync(context); + + if (_next != null && !context.IsProcessingTerminated) + { + await _next.ProcessAsync(context); + } + } + + // 模板方法,由子类实现具体的处理逻辑 + protected abstract Task HandleAsync(VariableContext context); +} +``` + +## 4. 具体处理器实现 + +以下是处理链中几个核心处理器的设计。 + +### 4.1. `ChangeDetectionProcessor` - 变化检测处理器 + +**职责**:检测本次读取的值与上一次的值是否相同。如果相同,则终止后续处理,以节省资源。 + +```csharp +// 文件: DMS.Infrastructure/Services/Processors/ChangeDetectionProcessor.cs +public class ChangeDetectionProcessor : VariableProcessorBase +{ + private readonly IMemoryCache _cache; // 使用内存缓存来存储上一次的值 + + public ChangeDetectionProcessor(IMemoryCache cache) + { + _cache = cache; + } + + protected override Task HandleAsync(VariableContext context) + { + var lastValue = _cache.Get(context.Variable.Id); + if (lastValue != null && lastValue.Equals(context.RawValue)) + { + context.IsProcessingTerminated = true; // 值未变化,终止处理 + } + else + { + context.IsValueChanged = true; + _cache.Set(context.Variable.Id, context.RawValue, TimeSpan.FromDays(1)); + } + return Task.CompletedTask; + } +} +``` + +### 4.2. `HistoryStorageProcessor` - 历史存储处理器 + +**职责**:将变化后的值存入数据库历史记录表。 + +```csharp +// 文件: DMS.Infrastructure/Services/Processors/HistoryStorageProcessor.cs +public class HistoryStorageProcessor : VariableProcessorBase +{ + private readonly IVariableHistoryRepository _historyRepository; + + public HistoryStorageProcessor(IVariableHistoryRepository historyRepository) + { + _historyRepository = historyRepository; + } + + protected override async Task HandleAsync(VariableContext context) + { + if (!context.IsValueChanged) return; + + var history = new VariableHistory + { + VariableId = context.Variable.Id, + Value = context.CurrentValue.ToString(), + Timestamp = context.Timestamp + }; + await _historyRepository.AddAsync(history); + } +} +``` + +### 4.3. `MqttPublishProcessor` - MQTT发布处理器 + +**职责**:如果变量关联了MQTT服务器,则将值发布出去。 + +```csharp +// 文件: DMS.Infrastructure/Services/Processors/MqttPublishProcessor.cs +public class MqttPublishProcessor : VariableProcessorBase +{ + private readonly IMqttPublishService _mqttService; + private readonly IVariableRepository _variableRepository; // 用于获取关联的MQTT服务器 + + public MqttPublishProcessor(IMqttPublishService mqttService, IVariableRepository variableRepository) + { + _mqttService = mqttService; + _variableRepository = variableRepository; + } + + protected override async Task HandleAsync(VariableContext context) + { + if (!context.IsValueChanged) return; + + var variableWithMqtt = await _variableRepository.GetVariableWithMqttServersAsync(context.Variable.Id); + if (variableWithMqtt?.MqttServers == null) return; + + foreach (var server in variableWithMqtt.MqttServers) + { + if (server.IsActive) + { + var topic = $"DMS/{context.Variable.VariableTable.Device.Name}/{context.Variable.VariableTable.Name}/{context.Variable.Name}"; + var payload = System.Text.Json.JsonSerializer.Serialize(new { value = context.CurrentValue, timestamp = context.Timestamp }); + await _mqttService.PublishAsync(server, topic, payload); + } + } + } +} +``` + +## 5. 构建和执行处理链 + +在数据采集的后台服务中,我们将动态构建并执行这个处理链。 + +```csharp +// 在 S7BackgroundService.cs 或类似服务中 +public class DataAcquisitionService +{ + private readonly IServiceProvider _serviceProvider; + + public DataAcquisitionService(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public async Task OnDataReceived(Variable variable, object rawValue) + { + var context = new VariableContext(variable, rawValue); + + // 1. 从DI容器中获取处理器实例 + var changeDetector = _serviceProvider.GetRequiredService(); + var historian = _serviceProvider.GetRequiredService(); + var publisher = _serviceProvider.GetRequiredService(); + + // 2. 构建处理链 + changeDetector.SetNext(historian).SetNext(publisher); + + // 3. 启动处理 + await changeDetector.ProcessAsync(context); + } +} +``` diff --git a/软件设计文档/05-WPF表现层设计.md b/软件设计文档/05-WPF表现层设计.md new file mode 100644 index 0000000..e560afb --- /dev/null +++ b/软件设计文档/05-WPF表现层设计.md @@ -0,0 +1,146 @@ +# 软件开发文档 - 05. WPF表现层设计 + +本文档详细描述 `DMS.WPF` 项目的设计,遵循 MVVM (Model-View-ViewModel) 设计模式。 + +## 1. 总体窗口设计 + +### 1.1. `SplashWindow.xaml` - 启动加载窗口 + +* **View**: 一个简单的窗口,显示Logo和加载状态信息(如“正在连接数据库...”、“正在加载配置...”)。 +* **ViewModel**: `SplashViewModel` + * **职责**: 在后台执行初始化任务(如数据库检查、加载配置、连接设备等)。每完成一步,就更新View上显示的状态文本。加载完成后,关闭自身并打开主窗口。 + +### 1.2. `MainWindow.xaml` - 主窗口 + +* **View**: 采用左右布局。 + * **左侧**: 一个 `ListBox` 或 `TreeView` 用于显示主菜单。 + * **右侧**: 一个 `ContentControl`,其 `Content` 属性绑定到当前活动视图模型的View。 +* **ViewModel**: `MainViewModel` + * **属性**: + * `ObservableCollection MenuItems`: 左侧菜单项集合。 + * `BaseViewModel CurrentViewModel`: 当前在右侧显示的主视图模型。 + * **命令**: + * `NavigateCommand`: 当用户点击菜单项时执行,用于切换 `CurrentViewModel`。 + +## 2. 核心视图与视图模型 + +### 2.1. 控制台 (Dashboard) + +* **View**: `DashboardView.xaml` + * 显示多个信息卡片,如“设备总数”、“在线设备”、“离线设备”、“消息日志”等。 +* **ViewModel**: `DashboardViewModel` + * **依赖**: `IDeviceAppService` + * **属性**: + * `int TotalDeviceCount` + * `int OnlineDeviceCount` + * `ObservableCollection LogMessages` + * **方法**: + * `LoadDataAsync()`: 从应用服务加载统计数据。 + +### 2.2. 设备管理 (Device Management) + +#### 2.2.1. 设备列表视图 + +* **View**: `DeviceListView.xaml` + * 使用 `GridView` 或 `DataGrid` 显示设备列表。 + * 支持按协议 (`ProtocolType`) 分组。 + * 提供“添加”、“编辑”、“删除”按钮。 + * 双击列表项可导航到设备详情页。 +* **ViewModel**: `DeviceListViewModel` + * **依赖**: `IDeviceAppService`, `IDialogService` + * **属性**: + * `ObservableCollection Devices` + * **命令**: + * `AddDeviceCommand`: 打开一个对话框用于添加新设备。 + * `EditDeviceCommand`: 打开对话框编辑选中设备。 + * `DeleteDeviceCommand`: 删除选中设备。 + * `NavigateToDetailCommand`: 导航到变量表视图。 + +#### 2.2.2. 变量表视图 + +* **View**: `VariableTableView.xaml` + * 显示特定设备下的所有变量表。 + * 显示所选变量表的详细信息。 + * 列表项可点击,导航到变量视图。 +* **ViewModel**: `VariableTableViewModel` + * **依赖**: `IVariableTableAppService` + * **属性**: + * `DeviceDto CurrentDevice` + * `ObservableCollection VariableTables` + +### 2.3. MQTT服务器管理 + +* **View**: `MqttServerListView.xaml` + * `DataGrid` 显示所有已配置的MQTT服务器。 + * 提供增删改功能。 +* **ViewModel**: `MqttServerListViewModel` + * **依赖**: `IMqttAppService` + * **属性**: + * `ObservableCollection MqttServers` + * **命令**: `Add/Edit/Delete` 命令。 + +#### 2.3.1. MQTT服务器详情页 + +* **View**: `MqttServerDetailView.xaml` + * 显示服务器的连接信息。 + * 显示一个列表,其中包含所有关联到此服务器的变量。 +* **ViewModel**: `MqttServerDetailViewModel` + * **依赖**: `IMqttAppService` + * **属性**: + * `MqttServerDto CurrentServer` + * `ObservableCollection LinkedVariables` + +## 3. UI服务 (`Services/`) + +为了保持ViewModel的整洁,所有与UI直接相关的操作(如弹窗、导航)都应通过服务来完成。 + +### 3.1. `IDialogService` - 对话框服务 + +* **职责**: 负责显示各种对话框(如确认框、信息框、以及用于添加/编辑的自定义对话框)。 +* **方法**: + * `Task ShowConfirmationAsync(string title, string message);` + * `Task ShowDialogAsync(TViewModel viewModel);` (用于打开自定义对话框) + +### 3.2. `INavigationService` - 导航服务 + +* **职责**: 管理主窗口右侧视图的切换。 +* **方法**: + * `void NavigateTo() where TViewModel : BaseViewModel;` + +## 4. 依赖注入 (DI) + +在 `App.xaml.cs` 的 `OnStartup` 方法中,我们将使用一个DI容器(如 `Microsoft.Extensions.DependencyInjection`)来注册和解析所有服务、视图模型和仓储。 + +```csharp +// 文件: DMS.WPF/App.xaml.cs +protected override void OnStartup(StartupEventArgs e) +{ + var serviceCollection = new ServiceCollection(); + ConfigureServices(serviceCollection); + + _serviceProvider = serviceCollection.BuildServiceProvider(); + + var mainWindow = _serviceProvider.GetRequiredService(); + mainWindow.Show(); +} + +private void ConfigureServices(IServiceCollection services) +{ + // 注册应用层服务 + services.AddSingleton(); + + // 注册基础设施层服务 + services.AddSingleton(); + services.AddSingleton(); + + // 注册WPF服务 + services.AddSingleton(); + + // 注册ViewModels + services.AddTransient(); + services.AddTransient(); + + // 注册Views + services.AddTransient(); +} +``` diff --git a/软件设计文档/05-事务与工作单元设计.md b/软件设计文档/05-事务与工作单元设计.md new file mode 100644 index 0000000..daa8562 --- /dev/null +++ b/软件设计文档/05-事务与工作单元设计.md @@ -0,0 +1,201 @@ +# 软件开发文档 - 05. 事务与工作单元设计 + +本文档详细阐述了为确保数据一致性而引入的**工作单元模式 (Unit of Work)** 的设计与实现。我们将此模式的接口命名为 `IRepositoryManager`,以更直观地反映其职责。 + +## 1. 设计目标 + +在复杂的业务操作中(例如,创建一个设备,同时需要创建其关联的变量表和菜单项),必须保证所有数据库操作要么全部成功,要么全部失败。这要求一个能够跨越多个仓储(Repository)的事务管理机制。 + +`IRepositoryManager` 的核心职责就是: + +* **组合操作**:将一系列独立的数据库操作组合成一个单一的、原子的工作单元。 +* **事务控制**:提供统一的 `Commit` (提交) 和 `Rollback` (回滚) 方法。 +* **共享上下文**:确保所有通过它访问的仓储共享同一个数据库连接和事务,这是实现原子性的前提。 + +## 2. `DMS.Core` - 接口定义 + +我们在核心层定义 `IRepositoryManager` 接口,它作为所有仓储的容器和事务的控制器。 + +```csharp +// 文件: DMS.Core/Interfaces/IRepositoryManager.cs +namespace DMS.Core.Interfaces; + +/// +/// 定义了一个仓储管理器,它使用工作单元模式来组合多个仓储操作,以确保事务的原子性。 +/// 实现了IDisposable,以确保数据库连接等资源能被正确释放。 +/// +public interface IRepositoryManager : IDisposable +{ + /// + /// 获取设备仓储的实例。 + /// 所有通过此管理器获取的仓储都共享同一个数据库上下文和事务。 + /// + IDeviceRepository Devices { get; } + + /// + /// 获取变量表仓储的实例。 + /// + IVariableTableRepository VariableTables { get; } + + /// + /// 获取菜单仓储的实例。 + /// + IMenuRepository Menus { get; } + + // ... 此处可以添加项目中所有其他的仓储 + + /// + /// 开始一个新的数据库事务。 + /// + void BeginTransaction(); + + /// + /// 异步提交当前事务中的所有变更。 + /// + /// 一个表示异步操作的任务。 + Task CommitAsync(); + + /// + /// 异步回滚当前事务中的所有变更。 + /// + /// 一个表示异步操作的任务。 + Task RollbackAsync(); +} +``` + +## 3. `DMS.Infrastructure` - 具体实现 + +在基础设施层,我们提供 `IRepositoryManager` 的 `SqlSugar` 实现。 + +```csharp +// 文件: DMS.Infrastructure/Data/RepositoryManager.cs +using DMS.Core.Interfaces; +using SqlSugar; + +namespace DMS.Infrastructure.Data; + +/// +/// 使用 SqlSugar 实现的仓储管理器。 +/// +public class RepositoryManager : IRepositoryManager +{ + private readonly ISqlSugarClient _db; + private readonly Lazy _lazyDevices; + private readonly Lazy _lazyVariableTables; + private readonly Lazy _lazyMenus; + + public RepositoryManager(ISqlSugarClient db) + { + _db = db; + // 使用 Lazy 实现懒加载,只有在第一次访问时才创建仓储实例 + // 关键点:将当前的数据库客户端 (_db) 传递给仓储,确保它们共享事务 + _lazyDevices = new Lazy(() => new DeviceRepository(_db)); + _lazyVariableTables = new Lazy(() => new VariableTableRepository(_db)); + _lazyMenus = new Lazy(() => new MenuRepository(_db)); + } + + public IDeviceRepository Devices => _lazyDevices.Value; + public IVariableTableRepository VariableTables => _lazyVariableTables.Value; + public IMenuRepository Menus => _lazyMenus.Value; + + public void BeginTransaction() + { + _db.BeginTran(); + } + + public async Task CommitAsync() + { + await _db.CommitTranAsync(); + } + + public async Task RollbackAsync() + { + await _db.RollbackTranAsync(); + } + + public void Dispose() + { + // 数据库客户端的生命周期由DI容器管理,此处无需手动释放 + // 但如果事务未提交/回滚,SqlSugar的Dispose会处理它 + _db.Dispose(); + } +} +``` + +## 4. `DMS.Application` - 应用服务重构 + +应用服务现在注入 `IRepositoryManager` 来执行事务性操作。 + +```csharp +// 文件: DMS.Application/Services/DeviceAppService.cs +using DMS.Core.Interfaces; + +public class DeviceAppService : IDeviceAppService +{ + private readonly IRepositoryManager _repoManager; + private readonly IMapper _mapper; + + public DeviceAppService(IRepositoryManager repoManager, IMapper mapper) + { + _repoManager = repoManager; + _mapper = mapper; + } + + public async Task CreateDeviceWithDetailsAsync(CreateDeviceWithDetailsDto dto) + { + // 不再需要 using 语句,因为 DI 容器会管理 RepositoryManager 的生命周期 + try + { + _repoManager.BeginTransaction(); + + var device = _mapper.Map(dto.Device); + await _repoManager.Devices.AddAsync(device); + + var variableTable = _mapper.Map(dto.VariableTable); + variableTable.DeviceId = device.Id; + await _repoManager.VariableTables.AddAsync(variableTable); + + var menu = _mapper.Map(dto.Menu); + menu.TargetId = device.Id; + await _repoManager.Menus.AddAsync(menu); + + await _repoManager.CommitAsync(); + + return device.Id; + } + catch (Exception ex) + { + await _repoManager.RollbackAsync(); + // 可以在此记录日志 + throw new ApplicationException("创建设备时发生错误,操作已回滚。", ex); + } + } +} +``` + +## 5. 依赖注入配置 (`App.xaml.cs`) + +在WPF项目中注册 `IRepositoryManager`。 + +```csharp +// 文件: DMS.WPF/App.xaml.cs +private void ConfigureServices(IServiceCollection services) +{ + // ... + + // 注册SqlSugar客户端 (Singleton) + services.AddSingleton(provider => { + // ...数据库连接配置... + return new SqlSugarClient(new ConnectionConfig() { ... }); + }); + + // 注册仓储管理器 (Scoped 或 Transient) + // 对于WPF,每个业务流程使用一个新的实例是安全的,因此用Transient + services.AddTransient(); + + // 注册应用服务 + services.AddTransient(); + + // 注意:不再需要单独注册每个仓储 +} +``` diff --git a/软件设计文档/06-动态菜单与导航设计.md b/软件设计文档/06-动态菜单与导航设计.md new file mode 100644 index 0000000..d992c3d --- /dev/null +++ b/软件设计文档/06-动态菜单与导航设计.md @@ -0,0 +1,234 @@ +# 软件开发文档 - 06. 动态菜单与导航设计 + +本文档详细阐述了一套基于数据库驱动的动态菜单和参数化导航系统的设计方案,旨在与 `iNKORE.UI.WPF.Modern` 等现代化UI框架无缝集成。 + +## 1. 设计目标 + +1. **菜单动态化**:应用程序的导航菜单(结构、文本、图标)应由数据库定义,允许在不重新编译程序的情况下进行修改。 +2. **视图解耦**:菜单点击(导航发起者)与目标视图(导航接收者)之间不应存在直接引用。 +3. **参数化导航**:导航时必须能够安全、清晰地将参数(如一个具体的设备ID)传递给目标视图模型。 +4. **层级支持**:支持无限层级的父/子菜单结构。 + +## 2. 数据库设计 (`DMS.Infrastructure`) + +我们创建一个自引用的 `Menus` 表来存储菜单的树状结构。 + +```csharp +// 文件: DMS.Infrastructure/Entities/DbMenu.cs +using SqlSugar; + +namespace DMS.Infrastructure.Entities; + +[SugarTable("Menus")] +public class DbMenu +{ + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + + [SugarColumn(IsNullable = true)] + public int? ParentId { get; set; } + + public string Header { get; set; } + + public string Icon { get; set; } + + public string TargetViewKey { get; set; } + + [SugarColumn(IsNullable = true)] + public string NavigationParameter { get; set; } + + public int DisplayOrder { get; set; } +} +``` + +## 3. 核心导航契约 (`DMS.WPF`) + +这是整个导航机制的核心,定义了组件间如何通信。 + +### 3.1. `INavigatable` 接口 + +任何需要接收导航参数的ViewModel都必须实现此接口。 + +```csharp +// 文件: DMS.WPF/Services/INavigatable.cs +namespace DMS.WPF.Services; + +/// +/// 定义了一个契约,表示ViewModel可以安全地接收导航传入的参数。 +/// +public interface INavigatable +{ + /// + /// 当导航到此ViewModel时,由导航服务调用此方法,以传递参数。 + /// + /// 从导航源传递过来的参数对象。 + Task OnNavigatedToAsync(object parameter); +} +``` + +### 3.2. `INavigationService` 接口与实现 + +重构后的导航服务,支持基于字符串键和参数的导航。 + +```csharp +// 文件: DMS.WPF/Services/INavigationService.cs +public interface INavigationService +{ + Task NavigateToAsync(string viewKey, object parameter = null); +} + +// 文件: DMS.WPF/Services/NavigationService.cs +public class NavigationService : INavigationService +{ + private readonly IServiceProvider _serviceProvider; + private readonly MainViewModel _mainViewModel; + + public NavigationService(IServiceProvider sp, MainViewModel mainVm) { /*...*/ } + + public async Task NavigateToAsync(string viewKey, object parameter = null) + { + if (string.IsNullOrEmpty(viewKey)) return; + + var viewModelType = GetViewModelTypeByKey(viewKey); + var viewModel = _serviceProvider.GetRequiredService(viewModelType) as BaseViewModel; + + if (viewModel is INavigatable navigatableVm) + { + await navigatableVm.OnNavigatedToAsync(parameter); + } + + _mainViewModel.CurrentViewModel = viewModel; + } + + private Type GetViewModelTypeByKey(string key) + { + return key switch + { + "DashboardView" => typeof(DashboardViewModel), + "DeviceListView" => typeof(DeviceListViewModel), + "DeviceDetailView" => typeof(DeviceDetailViewModel), + _ => throw new KeyNotFoundException($"未找到与键 '{key}' 关联的视图模型类型。"), + }; + } +} +``` + +## 4. 菜单构建与显示 (`DMS.WPF`) + +### 4.1. `MenuItemViewModel` + +这是 `ui:NavigationView` 直接绑定的数据对象。 + +```csharp +// 文件: DMS.WPF/ViewModels/Items/MenuItemViewModel.cs +public partial class MenuItemViewModel : ObservableObject +{ + [ObservableProperty] private string _header; + [ObservableProperty] private string _icon; + public ObservableCollection Children { get; } = new(); + public ICommand NavigateCommand { get; } + + public MenuItemViewModel(string targetViewKey, object navParameter, INavigationService navService) + { + NavigateCommand = new RelayCommand(async () => + { + await navService.NavigateToAsync(targetViewKey, navParameter); + }); + } +} +``` + +### 4.2. `IMenuService` (应用层/基础设施层) + +此服务负责从数据库加载菜单并构建ViewModel树。 + +```csharp +// 应用层接口: DMS.Application/Interfaces/IMenuService.cs +public interface IMenuService +{ + Task> GetMenuItemsAsync(); +} + +// 基础设施层实现: DMS.Infrastructure/Services/MenuService.cs +public class MenuService : IMenuService +{ + private readonly IRepositoryManager _repoManager; + private readonly INavigationService _navigationService; + + public MenuService(IRepositoryManager repoManager, INavigationService navigationService) + { /*...*/ } + + public async Task> GetMenuItemsAsync() + { + var allDbMenus = await _repoManager.Menus.GetAllAsync(); + // ... 此处编写逻辑,将 allDbMenus (扁平列表) ... + // ... 转换为 MenuItemViewModel 的树状结构 (通过ParentId) ... + // 示例: + // var menuItem = new MenuItemViewModel(dbMenu.TargetViewKey, dbMenu.NavigationParameter, _navigationService); + return new List(); // 返回构建好的顶级菜单项 + } +} +``` + +## 5. 目标视图模型实现 (`DMS.WPF`) + +这是一个需要接收设备ID作为参数的ViewModel示例。 + +```csharp +// 文件: DMS.WPF/ViewModels/DeviceDetailViewModel.cs +public class DeviceDetailViewModel : BaseViewModel, INavigatable +{ + private readonly IDeviceAppService _deviceAppService; + + [ObservableProperty] + private DeviceDetailDto _device; + + public DeviceDetailViewModel(IDeviceAppService deviceAppService) { /*...*/ } + + public async Task OnNavigatedToAsync(object parameter) + { + if (parameter is not int deviceId) return; + + IsBusy = true; + Device = await _deviceAppService.GetDeviceDetailAsync(deviceId); + IsBusy = false; + } +} +``` + +## 6. UI绑定与启动 (`DMS.WPF`) + +`MainViewModel` 负责加载菜单,`MainWindow.xaml` 负责显示。 + +```csharp +// MainViewModel.cs +public class MainViewModel : BaseViewModel +{ + public ObservableCollection MenuItems { get; } = new(); + + public MainViewModel(IMenuService menuService, /*...*/) { /*...*/ } + + public override async Task LoadAsync() + { + var menus = await _menuService.GetMenuItemsAsync(); + foreach(var menu in menus) { MenuItems.Add(menu); } + } +} +``` + +```xml + + + + + + + + + + + +``` diff --git a/软件设计文档/06-实现步骤与示例.md b/软件设计文档/06-实现步骤与示例.md new file mode 100644 index 0000000..7f5d294 --- /dev/null +++ b/软件设计文档/06-实现步骤与示例.md @@ -0,0 +1,216 @@ +# 软件开发文档 - 06. 实现步骤与示例 + +本文档提供关键功能的具体实现步骤和代码示例,以指导开发过程。 + +## 1. 添加新设备 (End-to-End) + +这是一个从UI到数据库的完整流程示例。 + +**步骤 1: View (`DeviceListView.xaml`)** + +用户点击“添加”按钮,该按钮绑定到ViewModel的 `AddDeviceCommand`。 + +```xml +