按照软件设计文档开始重构代码01

This commit is contained in:
2025-07-21 14:35:17 +08:00
parent a7b0a5d108
commit 29a2d44319
127 changed files with 12265 additions and 1586 deletions

View File

@@ -0,0 +1,216 @@
# 软件开发文档 - 06. 实现步骤与示例
本文档提供关键功能的具体实现步骤和代码示例,以指导开发过程。
## 1. 添加新设备 (End-to-End)
这是一个从UI到数据库的完整流程示例。
**步骤 1: View (`DeviceListView.xaml`)**
用户点击“添加”按钮该按钮绑定到ViewModel的 `AddDeviceCommand`
```xml
<Button Content="添加设备" Command="{Binding AddDeviceCommand}" />
```
**步骤 2: ViewModel (`DeviceListViewModel.cs`)**
`AddDeviceCommand` 执行,调用对话框服务来显示一个用于输入新设备信息的窗口。
```csharp
public ICommand AddDeviceCommand { get; }
public DeviceListViewModel(IDeviceAppService deviceAppService, IDialogService dialogService)
{
_deviceAppService = deviceAppService;
_dialogService = dialogService;
AddDeviceCommand = new RelayCommand(async () => await ExecuteAddDevice());
}
private async Task ExecuteAddDevice()
{
var vm = new AddDeviceDialogViewModel(); // 一个专门用于对话框的ViewModel
var result = await _dialogService.ShowDialogAsync(vm);
if (result != null) // 假设对话框返回创建的DTO
{
await _deviceAppService.CreateDeviceAsync(result);
await LoadDevicesAsync(); // 刷新列表
}
}
```
**步骤 3: Application Service (`DeviceAppService.cs`)**
应用服务接收DTO将其映射为领域模型并调用仓储来保存。
```csharp
public async Task CreateDeviceAsync(CreateDeviceDto deviceDto)
{
// 参数校验
if (string.IsNullOrWhiteSpace(deviceDto.Name) || string.IsNullOrWhiteSpace(deviceDto.IpAddress))
{
throw new ArgumentException("设备名称和IP地址不能为空");
}
var device = _mapper.Map<Device>(deviceDto);
device.IsActive = true; // 设置默认值
await _deviceRepository.AddAsync(device);
}
```
**步骤 4: Repository (`DeviceRepository.cs`)**
仓储将领域模型映射为数据库实体,并使用 `SqlSugar` 将其插入数据库。
```csharp
public async Task AddAsync(Device entity)
{
var dbEntity = _mapper.Map<DbDevice>(entity);
await _db.Insertable(dbEntity).ExecuteCommandAsync();
}
```
## 2. S7数据采集后台服务
后台服务将轮询所有激活的S7设备读取变量值并启动数据处理链。
**步骤 1: 创建后台服务 (`S7BackgroundService.cs`)**
这个服务应该是一个长时间运行的服务,可以使用 `IHostedService` (如果项目是.NET Core) 或一个简单的 `Task.Run` 循环。
```csharp
// 文件: DMS.WPF/Services/S7BackgroundService.cs
public class S7BackgroundService : IDisposable
{
private readonly IDeviceRepository _deviceRepo;
private readonly IS7CommunicationService _s7Service;
private readonly DataAcquisitionService _acquisitionService; // 包含处理链逻辑的服务
private CancellationTokenSource _cts;
public S7BackgroundService(IDeviceRepository deviceRepo, IS7CommunicationService s7Service, DataAcquisitionService acquisitionService)
{
// ... 注入依赖
}
public void Start()
{
_cts = new CancellationTokenSource();
Task.Run(async () => await ExecuteAsync(_cts.Token));
}
protected async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var activeDevices = await _deviceRepo.GetActiveDevicesWithDetailsAsync();
foreach (var device in activeDevices.Where(d => d.Protocol == ProtocolType.S7))
{
await _s7Service.ConnectAsync(device); // 确保连接
foreach (var table in device.VariableTables.Where(t => t.IsActive))
{
foreach (var variable in table.Variables.Where(v => v.IsActive))
{
try
{
var value = await _s7Service.ReadVariableAsync(device, variable);
// 触发数据处理链
await _acquisitionService.OnDataReceived(variable, value);
}
catch (Exception ex)
{
// Log error
}
}
}
}
await Task.Delay(1000, stoppingToken); // 轮询间隔
}
}
public void Stop()
{
_cts?.Cancel();
}
}
```
**步骤 2: 在 `App.xaml.cs` 中启动服务**
在应用程序启动时从DI容器获取服务实例并启动它。
```csharp
// 在 App.xaml.cs OnStartup
var s7Service = _serviceProvider.GetRequiredService<S7BackgroundService>();
s7Service.Start();
```
## 3. 菜单导航实现
**步骤 1: `MainViewModel`**
```csharp
public class MainViewModel : BaseViewModel
{
private readonly INavigationService _navigationService;
public ICommand NavigateCommand { get; }
public MainViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
NavigateCommand = new RelayCommand<Type>(OnNavigate);
}
private void OnNavigate(Type viewModelType)
{
_navigationService.NavigateTo(viewModelType);
}
}
```
**步骤 2: `NavigationService`**
这个服务负责实际的ViewModel切换它会更新 `MainViewModel``CurrentViewModel` 属性。
```csharp
public class NavigationService : INavigationService
{
private readonly IServiceProvider _serviceProvider;
private readonly MainViewModel _mainViewModel; // 持有MainViewModel的引用
public NavigationService(IServiceProvider serviceProvider, MainViewModel mainViewModel)
{
_serviceProvider = serviceProvider;
_mainViewModel = mainViewModel;
}
public void NavigateTo(Type viewModelType)
{
var viewModel = (BaseViewModel)_serviceProvider.GetRequiredService(viewModelType);
_mainViewModel.CurrentViewModel = viewModel;
}
}
```
**步骤 3: View (`MainWindow.xaml`)**
`ContentControl` 绑定到 `CurrentViewModel`,并使用 `DataTemplate` 来根据ViewModel的类型选择对应的View。
```xml
<ContentControl Content="{Binding CurrentViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:DashboardViewModel}">
<views:DashboardView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:DeviceListViewModel}">
<views:DeviceListView />
</DataTemplate>
<!-- 其他模板 -->
</ContentControl.Resources>
</ContentControl>
```