Files
DMS/软件设计文档/01-DMS.Core-领域模型与接口.md

824 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 软件开发文档 - DMS.Core 领域模型与接口
本文档详细阐述了 `DMS.Core` 项目的设计,它是整个系统的基石,包含所有业务实体和核心接口的定义。`DMS.Core` 不包含任何具体实现,确保了业务规则的独立性和可移植性。
## 1. 目录结构
```
DMS.Core/
├── Enums/
│ ├── PollLevelType.cs
│ ├── ProtocolType.cs
│ └── SignalType.cs
├── Models/
│ ├── Device.cs
│ ├── MqttServer.cs
│ ├── Variable.cs
│ ├── VariableHistory.cs
│ ├── VariableMqttAlias.cs
│ ├── VariableTable.cs
│ ├── MenuBean.cs
│ └── User.cs
├── Interfaces/
│ ├── IRepositoryManager.cs
│ ├── IBaseRepository.cs
│ ├── IDeviceRepository.cs
│ ├── IMqttServerRepository.cs
│ ├── IVariableHistoryRepository.cs
│ ├── IVariableMqttAliasRepository.cs
│ ├── IVariableRepository.cs
│ ├── IVariableTableRepository.cs
│ ├── IMenuRepository.cs
│ └── IUserRepository.cs
└── DMS.Core.csproj
```
## 2. 核心枚举 (`Enums/`)
使用C#枚举来表示业务中固定的、有限的分类,如协议类型、信号类型、轮询级别。这提供了类型安全和代码可读性。
### `PollLevelType.cs`
```csharp
// 文件: DMS.Core/Enums/PollLevelType.cs
using System.ComponentModel;
namespace DMS.Core.Enums;
public enum PollLevelType
{
[Description("10毫秒")]
TenMilliseconds = 10,
[Description("100毫秒")]
HundredMilliseconds = 100,
[Description("500毫秒")]
FiveHundredMilliseconds = 500,
[Description("1秒钟")]
OneSecond = 1000,
[Description("5秒钟")]
FiveSeconds = 5000,
[Description("10秒钟")]
TenSeconds = 10000,
[Description("20秒钟")]
TwentySeconds = 20000,
[Description("30秒钟")]
ThirtySeconds = 30000,
[Description("1分钟")]
OneMinute = 60000,
[Description("3分钟")]
ThreeMinutes = 180000,
[Description("5分钟")]
FiveMinutes = 300000,
[Description("10分钟")]
TenMinutes = 600000,
[Description("30分钟")]
ThirtyMinutes = 1800000,
[Description("1小时")]
OneHour = 3600000
}
```
### `ProtocolType.cs`
```csharp
// 文件: DMS.Core/Enums/ProtocolType.cs
/// <summary>
/// 定义了设备支持的通信协议类型。
/// </summary>
public enum ProtocolType
{
/// <summary>
/// Siemens S7 通信协议。
/// </summary>
S7,
/// <summary>
/// OPC UA (Unified Architecture) 协议。
/// </summary>
OpcUa,
/// <summary>
/// Modbus TCP 协议。
/// </summary>
ModbusTcp
}
```
### `SignalType.cs`
```csharp
// 文件: DMS.Core/Enums/SignalType.cs
using System.ComponentModel;
namespace DMS.Core.Enums;
public enum SignalType
{
[Description("启动信号")] StartSignal,
[Description("停止信号")] StopSignal,
[Description("报警信号")] AlarmSignal,
[Description("准备信号")] ReadySignal,
[Description("复位信号")] ResetSignal,
[Description("运行信号")] RunSignal,
[Description("设定频率")] SetHZSignal,
[Description("当前频率")] GetHZSignal,
[Description("当前电流")] CurrentASignal,
[Description("其他信号")] OtherASignal
}
/// <summary>
/// 定义了C#中常用的数据类型。
/// </summary>
public enum CSharpDataType
{
[Description("布尔型")] Bool,
[Description("字节型")] Byte,
[Description("短整型")] Short,
[Description("整型")] Int,
[Description("长整型")] Long,
[Description("浮点型")] Float,
[Description("双精度浮点型")] Double,
[Description("字符串型")] String,
[Description("日期时间型")] DateTime,
[Description("时间跨度型")] TimeSpan,
[Description("对象型")] Object,
[Description("未知类型")] Unknown
}
```
## 3. 领域模型 (`Models/`)
领域模型是业务核心的C#类表示。它们是贫血模型,主要包含数据属性,行为逻辑则由应用服务和领域服务处理。模型之间通过导航属性建立关系,反映业务实体间的关联。
### `Device.cs`
```csharp
// 文件: DMS.Core/Models/Device.cs
/// <summary>
/// 代表一个可管理的物理或逻辑设备。
/// </summary>
public class Device
{
/// <summary>
/// 唯一标识符。
/// </summary>
public int Id { get; set; }
/// <summary>
/// 设备名称用于UI显示和识别。
/// </summary>
public string Name { get; set; }
/// <summary>
/// 设备的描述信息。
/// </summary>
public string Description { get; set; }
/// <summary>
/// 设备使用的通信协议。
/// </summary>
public ProtocolType Protocol { get; set; }
/// <summary>
/// 设备的IP地址。
/// </summary>
public string IpAddress { get; set; }
/// <summary>
/// 设备的通信端口号。
/// </summary>
public int Port { get; set; }
/// <summary>
/// S7 PLC的机架号。
/// </summary>
public int Rack { get; set; }
/// <summary>
/// S7 PLC的槽号。
/// </summary>
public int Slot { get; set; }
/// <summary>
/// OPC UA 服务器地址 (仅当 Protocol 为 OpcUa 时有效)。
/// </summary>
public string OpcUaServerUrl { get; set; }
/// <summary>
/// 指示此设备是否处于激活状态。只有激活的设备才会被轮询。
/// </summary>
public bool IsActive { get; set; }
/// <summary>
/// 此设备包含的变量表集合。
/// </summary>
public List<VariableTable> VariableTables { get; set; } = new();
}
```
### `VariableTable.cs`
```csharp
// 文件: DMS.Core/Models/VariableTable.cs
namespace DMS.Core.Models;
/// <summary>
/// 组织和管理一组相关的变量。
/// </summary>
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 ProtocolType Protocol { get; set; } // 通讯协议
public List<Variable> Variables { get; set; } = new();
}
```
### `Variable.cs`
```csharp
// 文件: DMS.Core/Models/Variable.cs
/// <summary>
/// 核心数据点,代表从设备读取的单个值。
/// </summary>
public class Variable
{
/// <summary>
/// 唯一标识符。
/// </summary>
public int Id { get; set; }
/// <summary>
/// 变量名。
/// </summary>
public string Name { get; set; }
/// <summary>
/// 变量的描述信息。
/// </summary>
public string Description { get; set; }
/// <summary>
/// 在设备中的地址 (例如: DB1.DBD0, M100.0)。
/// </summary>
public string Address { get; set; }
/// <summary>
/// 变量的信号类型,例如启动信号、停止信号。
/// </summary>
public SignalType DataType { get; set; }
/// <summary>
/// 变量的轮询级别,决定了其读取频率。
/// </summary>
public PollLevelType PollLevel { get; set; }
/// <summary>
/// 指示此变量是否处于激活状态。
/// </summary>
public bool IsActive { get; set; }
/// <summary>
/// 所属变量表的ID。
/// </summary>
public int VariableTableId { get; set; }
/// <summary>
/// 所属变量表的导航属性。
/// </summary>
public VariableTable VariableTable { get; set; }
/// <summary>
/// 此变量的所有MQTT发布别名关联。一个变量可以关联多个MQTT服务器每个关联可以有独立的别名。
/// </summary>
public List<VariableMqttAlias> MqttAliases { get; set; } = new();
/// <summary>
/// OPC UA NodeId (仅当 Protocol 为 OpcUa 时有效)。
/// </summary>
public string OpcUaNodeId { get; set; }
/// <summary>
/// 是否启用历史数据保存。
/// </summary>
public bool IsHistoryEnabled { get; set; }
/// <summary>
/// 历史数据保存的死区值。当变量值变化超过此死区时才保存。
/// </summary>
public double HistoryDeadband { get; set; }
/// <summary>
/// 是否启用报警。
/// </summary>
public bool IsAlarmEnabled { get; set; }
/// <summary>
/// 报警最小值。
/// </summary>
public double AlarmMinValue { get; set; }
/// <summary>
/// 报警最大值。
/// </summary>
public double AlarmMaxValue { get; set; }
/// <summary>
/// 报警死区。当变量值变化超过此死区时才触发报警。
/// </summary>
public double AlarmDeadband { get; set; }
/// <summary>
/// 存储从设备读取到的最新值。此属性不应持久化到数据库,仅用于运行时。
/// </summary>
[System.ComponentModel.DataAnnotations.Schema.NotMapped] // 标记此属性不映射到数据库
public object DataValue { get; set; }
/// <summary>
/// 变量的通讯协议。
/// </summary>
public ProtocolType Protocol { get; set; }
/// <summary>
/// 变量的数据类型。
/// </summary>
public CSharpDataType CSharpDataType { get; set; }
/// <summary>
/// 数值转换公式,例如 "+3*5"。
/// </summary>
public string ConversionFormula { get; set; }
/// <summary>
/// 经过转换公式计算后的显示值。此属性不应持久化到数据库,仅用于运行时。
/// </summary>
[System.ComponentModel.DataAnnotations.Schema.NotMapped]
public object DisplayValue { get; set; }
/// <summary>
/// 变量的创建时间。
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 变量的最后更新时间。
/// </summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// 最后更新变量的用户。
/// </summary>
public string UpdatedBy { get; set; }
/// <summary>
/// 指示变量是否被修改。
/// </summary>
public bool IsModified { get; set; }
}
```
### `MqttServer.cs`
```csharp
// 文件: DMS.Core/Models/MqttServer.cs
namespace DMS.Core.Models;
/// <summary>
/// 代表一个MQTT Broker的配置。
/// </summary>
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; } // 是否启用
/// <summary>
/// MQTT订阅主题。
/// </summary>
public string SubscribeTopic { get; set; }
/// <summary>
/// MQTT发布主题。
/// </summary>
public string PublishTopic { get; set; }
/// <summary>
/// MQTT客户端ID。
/// </summary>
public string ClientId { get; set; }
/// <summary>
/// MQTT服务器配置的创建时间。
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// MQTT客户端连接到Broker的时间。
/// </summary>
public DateTime? ConnectedAt { get; set; }
/// <summary>
/// MQTT客户端连接时长
/// </summary>
public long ConnectionDuration { get; set; }
/// <summary>
/// 报文格式例如JSON, PlainText等。
/// </summary>
public string MessageFormat { get; set; }
/// <summary>
/// 与此服务器关联的所有变量别名。通过此集合可以反向查找关联的变量。
/// </summary>
public List<VariableMqttAlias> VariableAliases { get; set; } = new();
}
```
### `VariableHistory.cs`
```csharp
// 文件: DMS.Core/Models/VariableHistory.cs
namespace DMS.Core.Models;
/// <summary>
/// 用于存储变量值的变化记录。
/// </summary>
public class VariableHistory
{
public long Id { get; set; }
public int VariableId { get; set; }
public string Value { get; set; } // 以字符串形式存储,便于通用性
public DateTime Timestamp { get; set; }
}
```
### `VariableMqttAlias.cs`
```csharp
// 文件: DMS.Core/Models/VariableMqttAlias.cs
/// <summary>
/// 领域模型代表一个变量到一个MQTT服务器的特定关联包含专属别名。
/// 这是一个关联实体,用于解决多对多关系中需要额外属性(别名)的问题。
/// </summary>
public class VariableMqttAlias
{
/// <summary>
/// 唯一标识符。
/// </summary>
public int Id { get; set; }
/// <summary>
/// 关联的变量ID。
/// </summary>
public int VariableId { get; set; }
/// <summary>
/// 关联的MQTT服务器ID。
/// </summary>
public int MqttServerId { get; set; }
/// <summary>
/// 针对此特定[变量-服务器]连接的发布别名。此别名将用于构建MQTT Topic。
/// </summary>
public string Alias { get; set; }
/// <summary>
/// 关联的变量导航属性。
/// </summary>
public Variable Variable { get; set; }
/// <summary>
/// 关联的MQTT服务器导航属性。
/// </summary>
public MqttServer MqttServer { get; set; }
}
```
### `MenuBean.cs`
```csharp
// 文件: DMS.Core/Models/MenuBean.cs
namespace DMS.Core.Models;
/// <summary>
/// 领域模型:代表一个菜单项。
/// </summary>
public class MenuBean
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Header { get; set; }
public string Icon { get; set; }
public string TargetViewKey { get; set; }
public string NavigationParameter { get; set; }
public int DisplayOrder { get; set; }
}
```
### `User.cs`
```csharp
// 文件: DMS.Core/Models/User.cs
namespace DMS.Core.Models;
/// <summary>
/// 领域模型:代表一个用户。
/// </summary>
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string PasswordHash { get; set; } // 存储密码哈希值
public string Role { get; set; } // 用户角色,例如 "Admin", "Operator"
public bool IsActive { get; set; }
}
```
## 4. 核心接口 (`Interfaces/`)
### 4.1. `IRepositoryManager.cs` (工作单元模式)
定义了一个仓储管理器,它使用工作单元模式来组合多个仓储操作,以确保事务的原子性。它作为所有仓储的统一入口,并管理数据库事务。
```csharp
// 文件: DMS.Core/Interfaces/IRepositoryManager.cs
namespace DMS.Core.Interfaces;
/// <summary>
/// 定义了一个仓储管理器,它使用工作单元模式来组合多个仓储操作,以确保事务的原子性。
/// 实现了IDisposable以确保数据库连接等资源能被正确释放。
/// </summary>
public interface IRepositoryManager : IDisposable
{
/// <summary>
/// 获取设备仓储的实例。
/// 所有通过此管理器获取的仓储都共享同一个数据库上下文和事务。
/// </summary>
IDeviceRepository Devices { get; }
/// <summary>
/// 获取变量表仓储的实例。
/// </summary>
IVariableTableRepository VariableTables { get; }
/// <summary>
/// 获取变量仓储的实例。
/// </summary>
IVariableRepository Variables { get; }
/// <summary>
/// 获取MQTT服务器仓储的实例。
/// </summary>
IMqttServerRepository MqttServers { get; }
/// <summary>
/// 获取变量MQTT别名仓储的实例。
/// </summary>
IVariableMqttAliasRepository VariableMqttAliases { get; }
/// <summary>
/// 获取菜单仓储的实例。
/// </summary>
IMenuRepository Menus { get; }
/// <summary>
/// 获取变量历史仓储的实例。
/// </summary>
IVariableHistoryRepository VariableHistories { get; }
/// <summary>
/// 获取用户仓储的实例。
/// </summary>
IUserRepository Users { get; }
/// <summary>
/// 开始一个新的数据库事务。
/// </summary>
void BeginTransaction();
/// <summary>
/// 异步提交当前事务中的所有变更。
/// </summary>
/// <returns>一个表示异步操作的任务。</returns>
Task CommitAsync();
/// <summary>
/// 异步回滚当前事务中的所有变更。
/// </summary>
/// <returns>一个表示异步操作的任务。</returns>
Task RollbackAsync();
}
```
### 4.2. 仓储接口 (`IBaseRepository.cs`, `IDeviceRepository.cs` 等)
采用仓储Repository模式为每个聚合根或主要实体定义一个数据访问接口。这些接口定义了对领域对象集合的操作隐藏了底层数据存储的细节。
#### `IBaseRepository.cs`
```csharp
// 文件: DMS.Core/Interfaces/IBaseRepository.cs
namespace DMS.Core.Interfaces;
/// <summary>
/// 提供泛型数据访问操作的基础仓储接口。
/// </summary>
/// <typeparam name="T">领域模型的类型。</typeparam>
public interface IBaseRepository<T> where T : class
{
/// <summary>
/// 异步根据ID获取单个实体。
/// </summary>
/// <param name="id">实体的主键ID。</param>
/// <returns>找到的实体如果不存在则返回null。</returns>
Task<T> GetByIdAsync(int id);
/// <summary>
/// 异步获取所有实体。
/// </summary>
/// <returns>所有实体的列表。</returns>
Task<List<T>> GetAllAsync();
/// <summary>
/// 异步添加一个新实体。
/// </summary>
/// <param name="entity">要添加的实体。</param>
Task AddAsync(T entity);
/// <summary>
/// 异步更新一个已存在的实体。
/// </summary>
/// <param name="entity">要更新的实体。</param>
Task UpdateAsync(T entity);
/// <summary>
/// 异步根据ID删除一个实体。
/// </summary>
/// <param name="id">要删除的实体的主键ID。</param>
Task DeleteAsync(int id);
}
```
#### `IDeviceRepository.cs`
```csharp
// 文件: DMS.Core/Interfaces/IDeviceRepository.cs
using DMS.Core.Models;
namespace DMS.Core.Interfaces;
/// <summary>
/// 继承自IBaseRepository提供设备相关的特定数据查询功能。
/// </summary>
public interface IDeviceRepository : IBaseRepository<Device>
{
/// <summary>
/// 异步获取所有激活的设备,并级联加载其下的变量表和变量。
/// 这是后台轮询服务需要的主要数据。
/// </summary>
/// <returns>包含完整层级结构的激活设备列表。</returns>
Task<List<Device>> GetActiveDevicesWithDetailsAsync(ProtocolType protocol);
/// <summary>
/// 异步根据设备ID获取设备及其所有详细信息变量表、变量、MQTT别名等
/// </summary>
/// <param name="deviceId">设备ID。</param>
/// <returns>包含详细信息的设备对象。</returns>
Task<Device> GetDeviceWithDetailsAsync(int deviceId);
}
```
#### `IVariableRepository.cs`
```csharp
// 文件: DMS.Core/Interfaces/IVariableRepository.cs
using DMS.Core.Models;
namespace DMS.Core.Interfaces;
public interface IVariableRepository : IBaseRepository<Variable>
{
/// <summary>
/// 异步获取一个变量及其关联的所有MQTT别名和对应的MQTT服务器信息。
/// </summary>
/// <param name="variableId">变量ID。</param>
/// <returns>包含别名和服务器信息的变量对象。</returns>
Task<Variable> GetVariableWithMqttAliasesAsync(int variableId);
}
```
#### `IMqttServerRepository.cs`
```csharp
// 文件: DMS.Core/Interfaces/IMqttServerRepository.cs
using DMS.Core.Models;
namespace DMS.Core.Interfaces;
public interface IMqttServerRepository : IBaseRepository<MqttServer>
{
/// <summary>
/// 异步获取一个MQTT服务器及其关联的所有变量别名。
/// </summary>
/// <param name="serverId">MQTT服务器ID。</param>
/// <returns>包含变量别名信息的MQTT服务器对象。</returns>
Task<MqttServer> GetMqttServerWithVariableAliasesAsync(int serverId);
}
```
#### `IVariableTableRepository.cs`
```csharp
// 文件: DMS.Core/Interfaces/IVariableTableRepository.cs
using DMS.Core.Models;
namespace DMS.Core.Interfaces;
public interface IVariableTableRepository : IBaseRepository<VariableTable>
{
// 可以添加特定于VariableTable的查询方法
}
```
#### `IVariableHistoryRepository.cs`
```csharp
// 文件: DMS.Core/Interfaces/IVariableHistoryRepository.cs
using DMS.Core.Models;
namespace DMS.Core.Interfaces;
public interface IVariableHistoryRepository : IBaseRepository<VariableHistory>
{
// 可以添加特定于VariableHistory的查询方法
}
```
#### `IVariableMqttAliasRepository.cs`
```csharp
// 文件: DMS.Core/Interfaces/IVariableMqttAliasRepository.cs
using DMS.Core.Models;
namespace DMS.Core.Interfaces;
public interface IVariableMqttAliasRepository : IBaseRepository<VariableMqttAlias>
{
/// <summary>
/// 异步获取指定变量的所有MQTT别名关联并加载关联的MQTT服务器信息。
/// </summary>
/// <param name="variableId">变量ID。</param>
/// <returns>指定变量的所有MQTT别名关联列表。</returns>
Task<List<VariableMqttAlias>> GetAliasesForVariableAsync(int variableId);
/// <summary>
/// 异步根据变量ID和MQTT服务器ID获取特定的MQTT别名关联。
/// </summary>
/// <param name="variableId">变量ID。</param>
/// <param name="mqttServerId">MQTT服务器ID。</param>
/// <returns>匹配的VariableMqttAlias对象如果不存在则为null。</returns>
Task<VariableMqttAlias> GetByVariableAndServerAsync(int variableId, int mqttServerId);
}
```
#### `IMenuRepository.cs`
```csharp
// 文件: DMS.Core/Interfaces/IMenuRepository.cs
using DMS.Core.Models;
namespace DMS.Core.Interfaces;
public interface IMenuRepository : IBaseRepository<MenuBean>
{
// 可以添加特定于菜单的查询方法,例如获取所有菜单项
}
```
#### `IUserRepository.cs`
```csharp
// 文件: DMS.Core/Interfaces/IUserRepository.cs
using DMS.Core.Models;
namespace DMS.Core.Interfaces;
public interface IUserRepository : IBaseRepository<User>
{
/// <summary>
/// 异步根据用户名获取用户。
/// </summary>
/// <param name="username">用户名。</param>
/// <returns>用户对象如果不存在则为null。</returns>
Task<User> GetByUsernameAsync(string username);
}
```