# 软件开发文档 - DMS.Application - 应用服务与DTOs

本文档详细阐述了 `DMS.Application` 层的设计。该层作为业务逻辑的协调者，负责处理用例、数据转换，并作为表现层与核心层的桥梁。

## 1. 目录结构

```
DMS.Application/
├── DTOs/
│   ├── DeviceDto.cs
│   ├── CreateDeviceDto.cs
│   ├── UpdateDeviceDto.cs
│   ├── VariableDto.cs
│   ├── VariableTableDto.cs
│   ├── MqttServerDto.cs
│   ├── VariableMqttAliasDto.cs
│   └── CreateDeviceWithDetailsDto.cs
├── Interfaces/
│   ├── IDeviceAppService.cs
│   ├── IMqttAppService.cs
│   ├── IVariableAppService.cs
│   ├── IMqttAliasAppService.cs
│   └── IMenuService.cs
├── Services/
│   ├── DeviceAppService.cs
│   ├── MqttAppService.cs
│   ├── VariableAppService.cs
│   ├── MqttAliasAppService.cs
│   └── MenuService.cs
├── Profiles/
│   └── MappingProfile.cs
└── DMS.Application.csproj
```

## 2. 应用服务设计

### 2.1. 设计思路与考量

*   **职责**：应用服务（Application Services）是应用程序的用例（Use Case）实现者。它们负责编排领域模型和仓储来完成特定的业务操作，处理事务、授权、日志等应用级关注点。
*   **依赖**：应用服务依赖于 `DMS.Core` 中定义的接口（如 `IRepositoryManager`），而不直接依赖 `DMS.Infrastructure` 的具体实现，遵循依赖倒置原则。
*   **事务管理**：通过注入 `IRepositoryManager` 来管理事务，确保业务操作的原子性。

### 2.2. 设计优势

*   **业务流程清晰**：每个应用服务方法对应一个具体的业务用例，代码结构清晰，易于理解业务流程。
*   **解耦**：将业务逻辑与UI层和数据访问层解耦，提高了代码的可维护性和可测试性。
*   **事务边界明确**：应用服务是定义事务边界的理想位置，确保业务操作的完整性。
*   **可重用性**：应用服务可以被不同的客户端（如WPF UI、Web API等）复用。

### 2.3. 设计劣势/权衡

*   **代码量增加**：相比于直接在UI层或仓储层编写业务逻辑，应用服务模式会增加一些代码量和抽象层级。
*   **贫血应用服务**：如果应用服务仅仅是简单地调用仓储方法，而没有包含任何业务逻辑，则可能退化为“贫血应用服务”，失去了其应有的价值。

### 2.4. 示例：`IDeviceAppService.cs`

```csharp
// 文件: DMS.Application/Interfaces/IDeviceAppService.cs
using DMS.Application.DTOs;
using DMS.Core.Enums;

namespace DMS.Application.Interfaces;

/// <summary>
/// 定义设备管理相关的应用服务操作。
/// </summary>
public interface IDeviceAppService
{
    /// <summary>
    /// 异步根据ID获取设备DTO。
    /// </summary>
    Task<DeviceDto> GetDeviceByIdAsync(int id);

    /// <summary>
    /// 异步获取所有设备DTO列表。
    /// </summary>
    Task<List<DeviceDto>> GetAllDevicesAsync();

    /// <summary>
    /// 异步创建一个新设备及其关联的变量表和菜单（事务性操作）。
    /// </summary>
    /// <param name="dto">包含设备、变量表和菜单信息的DTO。</param>
    /// <returns>新创建设备的ID。</returns>
    Task<int> CreateDeviceWithDetailsAsync(CreateDeviceWithDetailsDto dto);

    /// <summary>
    /// 异步更新一个已存在的设备。
    /// </summary>
    Task UpdateDeviceAsync(UpdateDeviceDto deviceDto);

    /// <summary>
    /// 异步删除一个设备。
    /// </summary>
    Task DeleteDeviceAsync(int id);

    /// <summary>
    /// 异步切换设备的激活状态。
    /// </summary>
    Task ToggleDeviceActiveStateAsync(int id);

    /// <summary>
    /// 异步获取指定协议类型的设备列表。
    /// </summary>
    Task<List<DeviceDto>> GetDevicesByProtocolAsync(ProtocolType protocol);
}
```

### 2.5. 示例：`DeviceAppService.cs`

```csharp
// 文件: DMS.Application/Services/DeviceAppService.cs
using AutoMapper;
using DMS.Core.Interfaces;
using DMS.Core.Models;

namespace DMS.Application.Services;

/// <summary>
/// 实现设备管理的应用服务。
/// </summary>
public class DeviceAppService : IDeviceAppService
{
    private readonly IRepositoryManager _repoManager;
    private readonly IMapper _mapper;

    /// <summary>
    /// 构造函数，通过依赖注入获取仓储管理器和AutoMapper实例。
    /// </summary>
    public DeviceAppService(IRepositoryManager repoManager, IMapper mapper)
    {
        _repoManager = repoManager;
        _mapper = mapper;
    }

    /// <summary>
    /// 异步创建一个新设备及其关联的变量表和菜单（事务性操作）。
    /// </summary>
    public async Task<int> CreateDeviceWithDetailsAsync(CreateDeviceWithDetailsDto dto)
    {
        try
        {
            _repoManager.BeginTransaction();

            var device = _mapper.Map<Device>(dto.Device);
            device.IsActive = true; // 默认激活
            await _repoManager.Devices.AddAsync(device);

            // 假设 CreateDeviceWithDetailsDto 包含了变量表和菜单信息
            if (dto.VariableTable != null)
            {
                var variableTable = _mapper.Map<VariableTable>(dto.VariableTable);
                variableTable.DeviceId = device.Id; // 关联新设备ID
                await _repoManager.VariableTables.AddAsync(variableTable);
            }

            // 假设有菜单服务或仓储
            // if (dto.Menu != null)
            // {
            //     var menu = _mapper.Map<Menu>(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);
        }
    }

    /// <summary>
    /// 异步获取所有设备DTO列表。
    /// </summary>
    public async Task<List<DeviceDto>> GetAllDevicesAsync()
    {
        var devices = await _repoManager.Devices.GetAllAsync();
        return _mapper.Map<List<DeviceDto>>(devices);
    }

    // ... 其他方法的实现
}
```

## 3. 数据传输对象 (DTOs)

### 3.1. 设计思路与考量

*   **职责**：DTOs (Data Transfer Objects) 是用于在不同层之间（特别是应用层和表现层之间）传输数据的简单对象。它们通常是扁平的，只包含数据属性，不包含行为。
*   **隔离**：DTOs 隔离了领域模型和UI层，避免了领域模型直接暴露给UI，从而保护了领域模型的完整性和不变性。
*   **定制化**：DTOs 可以根据UI的需求进行定制，例如，只包含UI需要显示的字段，或者将多个领域模型的字段组合成一个DTO。

### 3.2. 设计优势

*   **解耦**：UI层不直接依赖领域模型，领域模型的修改不会直接影响UI层。
*   **安全性**：避免了敏感数据或不必要的复杂领域逻辑暴露给UI层。
*   **性能优化**：可以只传输UI所需的数据，减少网络传输量（对于分布式系统）。
*   **简化UI绑定**：DTOs 通常是扁平的，更适合UI的数据绑定。

### 3.3. 设计劣势/权衡

*   **映射开销**：需要在领域模型和DTO之间进行映射，增加了代码量和运行时开销（尽管AutoMapper可以简化）。
*   **DTOs 膨胀**：如果每个UI视图都需要一个独立的DTO，可能导致DTO类的数量膨胀。

### 3.4. 示例：`DeviceDto.cs`

```csharp
// 文件: DMS.Application/DTOs/DeviceDto.cs
namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示设备基本信息的DTO。
/// </summary>
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 int Rack { get; set; }
    public int Slot { get; set; }
    public string OpcUaServerUrl { get; set; }
    public bool IsActive { get; set; }
    public string Status { get; set; } // "在线", "离线", "连接中..."
}
```

### 3.5. 示例：`CreateDeviceDto.cs`

```csharp
// 文件: DMS.Application/DTOs/CreateDeviceDto.cs
using DMS.Core.Enums;

namespace DMS.Application.DTOs;

/// <summary>
/// 用于创建新设备时传输数据的DTO。
/// </summary>
public class CreateDeviceDto
{
    public string Name { get; set; }
    public ProtocolType Protocol { get; set; }
    public string IpAddress { get; set; }
    public int Port { get; set; }
}
```

### 3.6. 示例：`VariableMqttAliasDto.cs`

```csharp
// 文件: DMS.Application/DTOs/VariableMqttAliasDto.cs
namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示和管理变量与MQTT服务器关联别名的DTO。
/// </summary>
public class VariableMqttAliasDto
{
    public int Id { get; set; }
    public int VariableId { get; set; }
    public int MqttServerId { get; set; }
    public string MqttServerName { get; set; } // 用于UI显示关联的服务器名称
    public string Alias { get; set; }
}
```

### 3.7. 示例：`VariableDto.cs`

```csharp
// 文件: DMS.Application/DTOs/VariableDto.cs
using DMS.Core.Enums;
using System;

namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示变量基本信息的DTO。
/// </summary>
public class VariableDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public SignalType DataType { get; set; }
    public PollLevelType PollLevel { get; set; }
    public bool IsActive { get; set; }
    public int VariableTableId { get; set; }
    public string OpcUaNodeId { get; set; }
    public bool IsHistoryEnabled { get; set; }
    public double HistoryDeadband { get; set; }
    public bool IsAlarmEnabled { get; set; }
    public double AlarmMinValue { get; set; }
    public double AlarmMaxValue { get; set; }
    public double AlarmDeadband { get; set; }
    public ProtocolType Protocol { get; set; }
    public CSharpDataType CSharpDataType { get; set; }
    public string ConversionFormula { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime UpdatedAt { get; set; }
    public string UpdatedBy { get; set; }
    public bool IsModified { get; set; }
}
```

### 3.8. 示例：`MqttServerDto.cs`

```csharp
// 文件: DMS.Application/DTOs/MqttServerDto.cs
using System;

namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示MQTT服务器配置信息的DTO。
/// </summary>
public class MqttServerDto
{
    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; }
    public string SubscribeTopic { get; set; }
    public string PublishTopic { get; set; }
    public string ClientId { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? ConnectedAt { get; set; }
    public long ConnectionDuration { get; set; }
    public string MessageFormat { get; set; }
}
```

### 3.9. 示例：`VariableTableDto.cs`

```csharp
// 文件: DMS.Application/DTOs/VariableTableDto.cs
using DMS.Core.Enums;

namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示变量表基本信息的DTO。
/// </summary>
public class VariableTableDto
{
    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 ProtocolType Protocol { get; set; }
}
```

### 3.10. 示例：`VariableHistoryDto.cs`

```csharp
// 文件: DMS.Application/DTOs/VariableHistoryDto.cs
using System;

namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示变量历史记录的DTO。
/// </summary>
public class VariableHistoryDto
{
    public long Id { get; set; }
    public int VariableId { get; set; }
    public string Value { get; set; }
    public DateTime Timestamp { get; set; }
}
```

### 3.11. 示例：`MenuBeanDto.cs`

```csharp
// 文件: DMS.Application/DTOs/MenuBeanDto.cs
namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示菜单项的DTO。
/// </summary>
public class MenuBeanDto
{
    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; }
}
```

### 3.12. 示例：`UserDto.cs`

```csharp
// 文件: DMS.Application/DTOs/UserDto.cs
namespace DMS.Application.DTOs;

/// <summary>
/// 用于在UI上显示用户信息的DTO。
/// </summary>
public class UserDto
{
    public int Id { get; set; }
    public string Username { get; set; }
    public string Role { get; set; }
    public bool IsActive { get; set; }
}
```

## 4. AutoMapper 配置

### 4.1. 设计思路与考量

*   **自动化映射**：使用 `AutoMapper` 库来自动化领域模型和DTO之间的对象映射，减少手动映射的重复代码。
*   **集中配置**：所有映射规则集中在一个 `MappingProfile` 类中进行配置。

### 4.2. 设计优势

*   **减少样板代码**：显著减少了手动进行属性赋值的重复代码。
*   **提高开发效率**：开发者可以专注于业务逻辑，而不是数据转换。
*   **可维护性**：映射规则集中管理，修改和审查方便。

### 4.3. 设计劣势/权衡

*   **隐式性**：映射过程是隐式的，对于不熟悉AutoMapper的开发者可能难以理解数据流向。
*   **调试难度**：当映射出现问题时，调试可能比手动映射更复杂。
*   **性能开销**：虽然AutoMapper经过优化，但在极端性能敏感的场景下，仍然可能比手动映射有轻微的性能开销。

### 4.4. 示例：`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;

/// <summary>
/// 配置AutoMapper的映射规则。
/// </summary>
public class MappingProfile : Profile
{
    public MappingProfile()
    {
        // Device 映射
        CreateMap<CreateDeviceDto, Device>();
        CreateMap<UpdateDeviceDto, Device>();
        CreateMap<Device, DeviceDto>()
            .ForMember(dest => dest.Protocol, opt => opt.MapFrom(src => src.Protocol.ToString()));

        // VariableTable 映射
        CreateMap<VariableTable, VariableTableDto>().ReverseMap();

        // Variable 映射
        CreateMap<Variable, VariableDto>()
            .ForMember(dest => dest.DataType, opt => opt.MapFrom(src => src.DataType.ToString()))
            .ForMember(dest => dest.CSharpDataType, opt => opt.MapFrom(src => src.CSharpDataType));

        // MqttServer 映射
        CreateMap<MqttServer, MqttServerDto>().ReverseMap();

        // VariableMqttAlias 映射
        CreateMap<VariableMqttAlias, VariableMqttAliasDto>().ReverseMap();

        // VariableTable 映射
        CreateMap<VariableTable, VariableTableDto>().ReverseMap();

        // VariableHistory 映射
        CreateMap<VariableHistory, VariableHistoryDto>().ReverseMap();

        // MenuBean 映射
        CreateMap<MenuBean, MenuBeanDto>().ReverseMap();

        // User 映射
        CreateMap<User, UserDto>().ReverseMap();
    }
}
```