在基于 **领域驱动设计(DDD)** 的 **WpfPowerTester** 项目中,领域事件(Domain Events)是实现松耦合、提高模块化设计和系统可扩展性的重要机制
结合前述项目的热瞬态测试系统需求,我将深入探讨领域事件的定义、设计、实现、发布与订阅机制,并给出详细的代码示例和中文详解,涵盖测试开始、测试结束、用户登录、器件更新、权限变化等场景,集成 Prism 的。项目的完整领域事件实现,包含事件定义、发布、订阅和使用场景,覆盖所有功能模块(测试、器件维护、日志、系统设置、用户管理)。领域事件是 DDD 中的核心概念,表示领域中发生的、需要被其他模块或服务感
在基于 领域驱动设计(DDD) 的 WpfPowerTester 项目中,领域事件(Domain Events)是实现松耦合、提高模块化设计和系统可扩展性的重要机制。通过领域事件,系统可以在不同模块、聚合根或服务之间传递状态变化或重要业务事件,而无需直接调用对方逻辑。结合前述项目的热瞬态测试系统需求,我将深入探讨领域事件的定义、设计、实现、发布与订阅机制,并给出详细的代码示例和中文详解,涵盖测试开始、测试结束、用户登录、器件更新、权限变化等场景,集成 Prism 的 IEventAggregator
实现事件通信。
一、领域事件概述
1. 什么是领域事件?
领域事件是 DDD 中的核心概念,表示领域中发生的、需要被其他模块或服务感知的重要事件。它通常描述的是已经发生的事情(过去时),如“测试已开始”或“用户已登录”。领域事件具有以下特点:
- 单一职责:每个事件聚焦于一个具体的业务变化。
- 不可变:事件是不可变的数据结构,记录发生时的状态。
- 松耦合:事件发布者与订阅者解耦,订阅者无需知道发布者的存在。
- 异步处理:支持异步订阅处理,适配 UI 响应需求。
2. 领域事件在 WpfPowerTester 中的作用
在 WpfPowerTester 项目中,领域事件用于:
- 跨模块通信:如测试模块(ModuleA)通知数据分析模块(ModuleB)测试结果。
- UI 更新:如测试状态变化通知
MainWindowViewModel
更新状态栏。 - 日志记录:事件触发日志写入
Logs
表。 - 权限控制:用户登录或角色变更通知系统更新权限。
- 异步操作:支持触摸屏应用的非阻塞 UI 更新。
3. 事件清单
根据项目需求,定义以下领域事件:
TestStartedEvent
:测试开始,携带测试结果。TestStoppedEvent
:测试停止。UserLoggedInEvent
:用户登录成功,携带用户信息。DeviceUpdatedEvent
:器件信息更新,携带器件数据。PermissionChangedEvent
:用户权限变更,携带用户角色。
二、领域事件设计
1. 设计原则
- 事件命名:使用过去时,如
TestStartedEvent
,表示已发生。 - 事件数据:携带足够的信息(如
TestResult
、UserModel
),但避免冗余。 - 线程安全:结合 Prism 的
IEventAggregator
,支持 UI 线程和后台线程订阅。 - 可扩展性:事件定义在独立项目(
WpfPowerTester.Events
),便于模块引用。 - 日志记录:事件发布和订阅时记录日志,存储到
Logs
表。
2. 事件发布与订阅流程
- 发布:
- 领域服务(如
TestService
)或应用服务(如TestApplicationService
)在业务逻辑完成后发布事件。 - 使用
IEventAggregator.GetEvent<T>().Publish(payload)
。
- 领域服务(如
- 订阅:
- ViewModel(如
MainWindowViewModel
)或模块(如ModuleB
)订阅事件。 - 使用
IEventAggregator.GetEvent<T>().Subscribe(action, threadOption)
。
- ViewModel(如
- 处理:
- 订阅者处理事件,如更新 UI、记录日志或触发其他操作。
- 支持
ThreadOption.UIThread
(UI 线程)或ThreadOption.BackgroundThread
(后台线程)。
3. 事件与 Prism 集成
Prism 的 IEventAggregator
提供事件发布与订阅的轻量级实现:
- 优点:内置线程切换,易于与 WPF 的 MVVM 模式集成。
- 扩展:通过自定义事件类(如
TestStartedEvent
)携带复杂数据。 - 触摸屏适配:异步订阅(
async/await
)确保 UI 不阻塞。
三、领域事件实现
以下是基于 WpfPowerTester 项目的完整领域事件实现,包含事件定义、发布、订阅和使用场景,覆盖所有功能模块(测试、器件维护、日志、系统设置、用户管理)。
1. 事件定义(WpfPowerTester.Events)
Events/TestStartedEvent.cs:
using Prism.Events;
using WpfPowerTester.Core.Models;
namespace WpfPowerTester.Events
{
/// <summary>
/// 测试开始事件,携带测试结果
/// </summary>
public class TestStartedEvent : PubSubEvent<TestResult>
{
}
}
Events/TestStoppedEvent.cs:
using Prism.Events;
namespace WpfPowerTester.Events
{
/// <summary>
/// 测试停止事件,无需携带数据
/// </summary>
public class TestStoppedEvent : PubSubEvent
{
}
}
Events/UserLoggedInEvent.cs:
using Prism.Events;
using WpfPowerTester.Core.Models;
namespace WpfPowerTester.Events
{
/// <summary>
/// 用户登录事件,携带用户信息
/// </summary>
public class UserLoggedInEvent : PubSubEvent<UserModel>
{
}
}
Events/DeviceUpdatedEvent.cs:
using Prism.Events;
using WpfPowerTester.Core.Models;
namespace WpfPowerTester.Events
{
/// <summary>
/// 器件更新事件,携带器件信息
/// </summary>
public class DeviceUpdatedEvent : PubSubEvent<TestDeviceModel>
{
}
}
Events/PermissionChangedEvent.cs:
using Prism.Events;
namespace WpfPowerTester.Events
{
/// <summary>
/// 权限变更事件,携带用户角色
/// </summary>
public class PermissionChangedEvent : PubSubEvent<string>
{
}
}
说明:
- 事件类继承
PubSubEvent<T>
,T
是事件数据类型。 - 无需数据的空事件(如
TestStoppedEvent
)直接继承PubSubEvent
。 - 定义在
WpfPowerTester.Events
项目,供所有模块引用。
2. 事件发布
以下是事件在领域服务、应用服务或 ViewModel 中的发布代码。
Core/Services/TestApplicationService.cs(发布 TestStartedEvent
和 TestStoppedEvent
):
using System.Threading.Tasks;
using WpfPowerTester.Core.Models;
using WpfPowerTester.Core.Repositories;
using WpfPowerTester.Domain.Entities;
using WpfPowerTester.Domain.Services;
using WpfPowerTester.Domain.ValueObjects;
using WpfPowerTester.Events;
namespace WpfPowerTester.Core.Services
{
public class TestApplicationService
{
private readonly IRepository<TestDevice> _deviceRepository;
private readonly TestService _testService;
private readonly IDatabaseService _databaseService;
private readonly IEventAggregator _eventAggregator;
private readonly ILoggingService _loggingService;
public TestApplicationService(IRepository<TestDevice> deviceRepository, TestService testService,
IDatabaseService databaseService, IEventAggregator eventAggregator, ILoggingService loggingService)
{
_deviceRepository = deviceRepository;
_testService = testService;
_databaseService = databaseService;
_eventAggregator = eventAggregator;
_loggingService = loggingService;
}
public async Task StartTestAsync(int deviceId, TestCondition condition)
{
var device = await _deviceRepository.GetByIdAsync(deviceId);
if (device == null)
throw new InvalidOperationException("器件不存在");
var result = await _testService.StartTestAsync(device, condition);
await _databaseService.SaveTestResultAsync(result);
_eventAggregator.GetEvent<TestStartedEvent>().Publish(result); // 发布测试开始事件
_loggingService.LogInfo($"Test started for Device ID {deviceId}, TjMax = {result.TjMax:F2}°C.", "TestApplicationService");
}
public void StopTest()
{
_testService.StopTest();
_eventAggregator.GetEvent<TestStoppedEvent>().Publish(); // 发布测试停止事件
_loggingService.LogInfo("Test stopped.", "TestApplicationService");
}
}
}
ViewModels/LoginViewModel.cs(发布 UserLoggedInEvent
):
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using WpfPowerTester.Core.Models;
using WpfPowerTester.Core.Services;
using WpfPowerTester.Events;
namespace WpfPowerTester.ViewModels
{
public class LoginViewModel : BindableBase
{
private readonly IDatabaseService _databaseService;
private readonly IEventAggregator _eventAggregator;
private readonly ILoggingService _loggingService;
private readonly IMessageTipService _messageTipService;
private string _username;
public string Username { get => _username; set => SetProperty(ref _username, value); }
public DelegateCommand<object> LoginCommand { get; }
public LoginViewModel(IDatabaseService databaseService, IEventAggregator eventAggregator,
ILoggingService loggingService, IMessageTipService messageTipService)
{
_databaseService = databaseService;
_eventAggregator = eventAggregator;
_loggingService = loggingService;
_messageTipService = messageTipService;
LoginCommand = new DelegateCommand<object>(LoginAsync);
}
private async void LoginAsync(object passwordBox)
{
var password = (passwordBox as System.Windows.Controls.PasswordBox)?.Password;
if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(password))
{
_messageTipService.ShowMessage(MessageTip.MessageType.Error, "错误", "用户名或密码不能为空");
return;
}
var users = await _databaseService.GetUsersAsync();
var user = users.FirstOrDefault(u => u.Username == Username && u.PasswordHash == ComputeHash(password));
if (user == null)
{
_messageTipService.ShowMessage(MessageTip.MessageType.Error, "错误", "用户名或密码错误");
return;
}
_eventAggregator.GetEvent<UserLoggedInEvent>().Publish(user); // 发布用户登录事件
_loggingService.LogInfo($"User {Username} logged in.", "LoginView");
}
private string ComputeHash(string input)
{
using var sha256 = SHA256.Create();
var bytes = Encoding.UTF8.GetBytes(input);
var hash = sha256.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
}
}
ViewModels/DeviceManagementViewModel.cs(发布 DeviceUpdatedEvent
):
using Prism.Commands;
using Prism.Mvvm;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using WpfPowerTester.Core.Models;
using WpfPowerTester.Core.Repositories;
using WpfPowerTester.Core.Services;
using WpfPowerTester.Events;
namespace WpfPowerTester.ViewModels
{
public class DeviceManagementViewModel : BindableBase
{
private readonly IMessageBoxService _messageBoxService;
private readonly IMessageTipService _messageTipService;
private readonly IEventAggregator _eventAggregator;
private readonly IRepository<TestDevice> _deviceRepository;
private readonly ILoggingService _loggingService;
private ObservableCollection<TestDeviceModel> _devices = new ObservableCollection<TestDeviceModel>();
private TestDeviceModel _selectedDevice = new TestDeviceModel();
public ObservableCollection<TestDeviceModel> Devices { get => _devices; set => SetProperty(ref _devices, value); }
public TestDeviceModel SelectedDevice { get => _selectedDevice; set => SetProperty(ref _selectedDevice, value); }
public DelegateCommand AddDeviceCommand { get; }
public DelegateCommand<int?> DeleteDeviceCommand { get; }
public DeviceManagementViewModel(IMessageBoxService messageBoxService, IMessageTipService messageTipService,
IEventAggregator eventAggregator, IRepository<TestDevice> deviceRepository, ILoggingService loggingService)
{
_messageBoxService = messageBoxService;
_messageTipService = messageTipService;
_eventAggregator = eventAggregator;
_deviceRepository = deviceRepository;
_loggingService = loggingService;
AddDeviceCommand = new DelegateCommand(AddDeviceAsync);
DeleteDeviceCommand = new DelegateCommand<int?>(DeleteDeviceAsync);
LoadDevicesAsync();
}
private async void LoadDevicesAsync()
{
Devices.Clear();
var devices = await _deviceRepository.GetAllAsync();
foreach (var device in devices)
Devices.Add(new TestDeviceModel
{
DeviceId = device.DeviceId,
DeviceType = device.DeviceType,
KFactor = device.KFactor,
CreatedAt = device.CreatedAt,
UpdatedAt = device.UpdatedAt
});
_loggingService.LogInfo($"Loaded {devices.Count} devices.", "DeviceManagement");
}
private async void AddDeviceAsync()
{
if (string.IsNullOrEmpty(SelectedDevice.DeviceType))
{
await _messageBoxService.ShowMessageAsync("器件类型不能为空", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
var existingDevice = Devices.FirstOrDefault(d => d.DeviceId == SelectedDevice.DeviceId);
if (existingDevice != null)
{
bool? result = await _messageBoxService.ShowConfirmationAsync($"器件 ID {SelectedDevice.DeviceId} 已存在,是否覆盖?", "确认覆盖");
if (result != true) return;
Devices.Remove(existingDevice);
}
SelectedDevice.CreatedAt = DateTime.Now;
Devices.Add(SelectedDevice);
await _deviceRepository.AddAsync(new Domain.Entities.TestDevice(SelectedDevice.DeviceId, SelectedDevice.DeviceType, SelectedDevice.KFactor));
_eventAggregator.GetEvent<DeviceUpdatedEvent>().Publish(SelectedDevice); // 发布器件更新事件
_messageTipService.ShowMessage(MessageTip.MessageType.Success, "器件添加", $"器件 ID {SelectedDevice.DeviceId} 已添加");
_loggingService.LogInfo($"Device added: ID {SelectedDevice.DeviceId}", "DeviceManagement");
SelectedDevice = new TestDeviceModel();
}
private async void DeleteDeviceAsync(int? deviceId)
{
if (!deviceId.HasValue) return;
bool? result = await _messageBoxService.ShowConfirmationAsync($"确认删除器件 ID {deviceId}?", "确认删除");
if (result != true) return;
var device = Devices.FirstOrDefault(d => d.DeviceId == deviceId.Value);
if (device != null)
{
Devices.Remove(device);
await _deviceRepository.DeleteAsync(deviceId.Value);
_eventAggregator.GetEvent<DeviceUpdatedEvent>().Publish(device); // 发布器件更新事件
_messageTipService.ShowMessage(MessageTip.MessageType.Success, "器件删除", $"器件 ID {deviceId} 已删除");
_loggingService.LogInfo($"Device deleted: ID {deviceId}", "DeviceManagement");
}
}
}
}
ViewModels/UserManagementViewModel.cs(发布 PermissionChangedEvent
):
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using WpfPowerTester.Core.Models;
using WpfPowerTester.Core.Services;
using WpfPowerTester.Events;
namespace WpfPowerTester.ViewModels
{
public class UserManagementViewModel : BindableBase
{
private readonly IMessageBoxService _messageBoxService;
private readonly IMessageTipService _messageTipService;
private readonly IEventAggregator _eventAggregator;
private readonly IDatabaseService _databaseService;
private readonly ILoggingService _loggingService;
private ObservableCollection<UserModel> _users = new ObservableCollection<UserModel>();
private UserModel _selectedUser = new UserModel();
private string _selectedRole;
public ObservableCollection<UserModel> Users { get => _users; set => SetProperty(ref _users, value); }
public UserModel SelectedUser { get => _selectedUser; set => SetProperty(ref _selectedUser, value); }
public string SelectedRole { get => _selectedRole; set => SetProperty(ref _selectedRole, value); }
public List<string> UserRoles => new List<string> { "Admin", "Operator", "Guest" };
public DelegateCommand AddUserCommand { get; }
public DelegateCommand<int?> DeleteUserCommand { get; }
public DelegateCommand ChangeRoleCommand { get; }
public UserManagementViewModel(IMessageBoxService messageBoxService, IMessageTipService messageTipService,
IEventAggregator eventAggregator, IDatabaseService databaseService, ILoggingService loggingService)
{
_messageBoxService = messageBoxService;
_messageTipService = messageTipService;
_eventAggregator = eventAggregator;
_databaseService = databaseService;
_loggingService = loggingService;
AddUserCommand = new DelegateCommand(AddUserAsync);
DeleteUserCommand = new DelegateCommand<int?>(DeleteUserAsync);
ChangeRoleCommand = new DelegateCommand(ChangeRoleAsync);
LoadUsersAsync();
}
private async void LoadUsersAsync()
{
Users.Clear();
var users = await _databaseService.GetUsersAsync();
foreach (var user in users)
Users.Add(user);
_loggingService.LogInfo($"Loaded {users.Count} users.", "UserManagement");
}
private async void AddUserAsync()
{
if (string.IsNullOrEmpty(SelectedUser.Username) || string.IsNullOrEmpty(SelectedUser.Role))
{
await _messageBoxService.ShowMessageAsync("用户名或角色不能为空", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
if (Users.Any(u => u.Username == SelectedUser.Username))
{
bool? result = await _messageBoxService.ShowConfirmationAsync($"用户 {SelectedUser.Username} 已存在,是否覆盖?", "确认覆盖");
if (result != true) return;
Users.Remove(Users.First(u => u.Username == SelectedUser.Username));
}
SelectedUser.CreatedAt = DateTime.Now;
SelectedUser.PasswordHash = ComputeHash("default"); // 默认密码
Users.Add(SelectedUser);
await _databaseService.SaveUserAsync(SelectedUser);
_eventAggregator.GetEvent<PermissionChangedEvent>().Publish(SelectedUser.Role); // 发布权限变更事件
_messageTipService.ShowMessage(MessageTip.MessageType.Success, "用户添加", $"用户 {SelectedUser.Username} 已添加");
_loggingService.LogInfo($"User added: {SelectedUser.Username}", "UserManagement");
SelectedUser = new UserModel();
}
private async void DeleteUserAsync(int? userId)
{
if (!userId.HasValue) return;
bool? result = await _messageBoxService.ShowConfirmationAsync($"确认删除用户 ID {userId}?", "确认删除");
if (result != true) return;
var user = Users.FirstOrDefault(u => u.UserId == userId.Value);
if (user != null)
{
Users.Remove(user);
await _databaseService.DeleteUserAsync(userId.Value);
_eventAggregator.GetEvent<PermissionChangedEvent>().Publish(user.Role); // 发布权限变更事件
_messageTipService.ShowMessage(MessageTip.MessageType.Success, "用户删除", $"用户 ID {userId} 已删除");
_loggingService.LogInfo($"User deleted: ID {userId}", "UserManagement");
}
}
private async void ChangeRoleAsync()
{
if (SelectedUser == null || string.IsNullOrEmpty(SelectedRole)) return;
bool? result = await _messageBoxService.ShowConfirmationAsync($"确认将用户 {SelectedUser.Username} 的角色更改为 {SelectedRole}?", "确认更改角色");
if (result != true) return;
SelectedUser.Role = SelectedRole;
await _databaseService.SaveUserAsync(SelectedUser);
_eventAggregator.GetEvent<PermissionChangedEvent>().Publish(SelectedRole); // 发布权限变更事件
_messageTipService.ShowMessage(MessageTip.MessageType.Success, "角色更改", $"用户 {SelectedUser.Username} 的角色已更改为 {SelectedRole}");
_loggingService.LogInfo($"User role changed: {SelectedUser.Username} to {SelectedRole}", "UserManagement");
}
private string ComputeHash(string input)
{
using var sha256 = SHA256.Create();
var bytes = Encoding.UTF8.GetBytes(input);
var hash = sha256.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
}
}
说明:
- TestApplicationService:在测试开始和停止时发布
TestStartedEvent
和TestStoppedEvent
。 - LoginViewModel:登录成功后发布
UserLoggedInEvent
。 - DeviceManagementViewModel:器件添加或删除时发布
DeviceUpdatedEvent
。 - UserManagementViewModel:用户添加、删除或角色变更时发布
PermissionChangedEvent
。 - 日志:每次发布事件时记录日志,存储到
Logs
表。
3. 事件订阅
以下是事件在 ViewModel 和模块中的订阅代码。
ViewModels/MainWindowViewModel.cs(订阅 UserLoggedInEvent
和 PermissionChangedEvent
):
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using Prism.Regions;
using System;
using System.Threading.Tasks;
using WpfPowerTester.Core.Models;
using WpfPowerTester.Core.Services;
using WpfPowerTester.Domain.Services;
using WpfPowerTester.Events;
namespace WpfPowerTester.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
private readonly IEventAggregator _eventAggregator;
private readonly ILoggingService _loggingService;
private readonly IMessageTipService _messageTipService;
private readonly PermissionService _permissionService;
private readonly IMessageBoxService _messageBoxService;
private string _currentTime;
private DateTime _appStartTime;
private bool _hasPermission;
private UserModel _currentUser;
public string CurrentTime { get => _currentTime; set => SetProperty(ref _currentTime, value); }
public DateTime AppStartTime { get => _appStartTime; set => SetProperty(ref _appStartTime, value); }
public bool HasPermission { get => _hasPermission; set => SetProperty(ref _hasPermission, value); }
public DelegateCommand NavigateToTestCommand { get; }
public DelegateCommand NavigateToDeviceManagementCommand { get; }
public DelegateCommand NavigateToLogCommand { get; }
public DelegateCommand NavigateToSettingsCommand { get; }
public DelegateCommand NavigateToUserManagementCommand { get; }
public DelegateCommand LockScreenCommand { get; }
public DelegateCommand SwitchUserCommand { get; }
public DelegateCommand RestartAppCommand { get; }
public MainWindowViewModel(IRegionManager regionManager, IEventAggregator eventAggregator, ILoggingService loggingService,
IMessageTipService messageTipService, PermissionService permissionService, IMessageBoxService messageBoxService)
{
_regionManager = regionManager;
_eventAggregator = eventAggregator;
_loggingService = loggingService;
_messageTipService = messageTipService;
_permissionService = permissionService;
_messageBoxService = messageBoxService;
AppStartTime = DateTime.Now;
NavigateToTestCommand = new DelegateCommand(() => NavigateTo("TestView"));
NavigateToDeviceManagementCommand = new DelegateCommand(() => NavigateTo("DeviceManagementView"));
NavigateToLogCommand = new DelegateCommand(() => NavigateTo("LogView"));
NavigateToSettingsCommand = new DelegateCommand(() => NavigateTo("SettingsView"), () => HasPermission);
NavigateToUserManagementCommand = new DelegateCommand(() => NavigateTo("UserManagementView"), () => HasPermission);
LockScreenCommand = new DelegateCommand(LockScreenAsync);
SwitchUserCommand = new DelegateCommand(SwitchUserAsync);
RestartAppCommand = new DelegateCommand(RestartAppAsync);
// 订阅用户登录事件
_eventAggregator.GetEvent<UserLoggedInEvent>().Subscribe(OnUserLoggedIn, ThreadOption.UIThread);
// 订阅权限变更事件
_eventAggregator.GetEvent<PermissionChangedEvent>().Subscribe(OnPermissionChanged, ThreadOption.UIThread);
UpdateCurrentTime();
NavigateTo("LoginView"); // 默认进入登录页面
_loggingService.LogInfo("MainWindowViewModel initialized.", "MainWindow");
}
private void NavigateTo(string viewName)
{
_regionManager.RequestNavigate("MainRegion", viewName);
_loggingService.LogInfo($"Navigated to {viewName}.", "MainWindow");
}
private async void LockScreenAsync()
{
await _messageBoxService.ShowMessageAsync("已锁屏,返回登录页面", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
NavigateTo("LoginView");
}
private async void SwitchUserAsync()
{
bool? result = await _messageBoxService.ShowConfirmationAsync("确认切换用户?", "切换用户");
if (result == true)
NavigateTo("LoginView");
}
private async void RestartAppAsync()
{
bool? result = await _messageBoxService.ShowConfirmationAsync("确认重启应用?", "重启应用");
if (result == true)
{
System.Windows.Application.Current.Shutdown();
System.Diagnostics.Process.Start(System.Windows.Application.ResourceAssembly.Location);
}
}
private void OnUserLoggedIn(UserModel user)
{
_currentUser = user;
HasPermission = _permissionService.HasPermission(new Domain.Entities.User(user.UserId, user.Username, user.PasswordHash, user.Role), "SystemSettings");
NavigateTo("TestView"); // 登录后默认进入测试页面
_messageTipService.ShowMessage(MessageTip.MessageType.Success, "登录成功", $"欢迎,{user.Username}");
_loggingService.LogInfo($"User logged in: {user.Username}.", "MainWindow");
}
private void OnPermissionChanged(string role)
{
if (_currentUser != null && _currentUser.Role == role)
{
HasPermission = _permissionService.HasPermission(new Domain.Entities.User(_currentUser.UserId, _currentUser.Username, _currentUser.PasswordHash, role), "SystemSettings");
_messageTipService.ShowMessage(MessageTip.MessageType.Info, "权限更新", $"用户 {_currentUser.Username} 的权限已更新");
_loggingService.LogInfo($"Permission updated for user {_currentUser.Username}: {role}.", "MainWindow");
}
}
private async void UpdateCurrentTime()
{
while (true)
{
CurrentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
await Task.Delay(1000);
}
}
}
}
ViewModels/TestViewModel.cs(订阅 TestStartedEvent
和 TestStoppedEvent
):
using OxyPlot;
using OxyPlot.Series;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Media;
using WpfPowerTester.Core.Models;
using WpfPowerTester.Core.Services;
using WpfPowerTester.Core.Repositories;
using WpfPowerTester.Domain.ValueObjects;
using WpfPowerTester.Events;
namespace WpfPowerTester.ViewModels
{
public class TestViewModel : BindableBase
{
private readonly TestApplicationService _testService;
private readonly IHardwareService _hardwareService;
private readonly IRepository<TestDevice> _deviceRepository;
private readonly IEventAggregator _eventAggregator;
private readonly ILoggingService _loggingService;
private readonly IMessageTipService _messageTipService;
private string _selectedTestArea = "Area1";
private Temperature _temperature;
private string _selectedTestProject = "Project1";
private double _testProgress;
private ObservableCollection<TestResult> _testResults = new ObservableCollection<TestResult>();
private PlotModel _plotModel = new PlotModel { Title = "热瞬态曲线" };
private bool _isTesting;
public string SelectedTestArea { get => _selectedTestArea; set => SetProperty(ref _selectedTestArea, value); }
public Temperature Temperature { get => _temperature; set => SetProperty(ref _temperature, value); }
public string SelectedTestProject { get => _selectedTestProject; set => SetProperty(ref _selectedTestProject, value); }
public double TestProgress { get => _testProgress; set => SetProperty(ref _testProgress, value); }
public ObservableCollection<TestResult> TestResults { get => _testResults; set => SetProperty(ref _testResults, value); }
public PlotModel PlotModel { get => _plotModel; set => SetProperty(ref _plotModel, value); }
public Brush TestStatusColor => _isTesting ? Brushes.Green : Brushes.Red;
public List<string> TestAreas => new List<string> { "Area1", "Area2" };
public List<string> TestProjects => new List<string> { "Project1", "Project2" };
public DelegateCommand StartTestCommand { get; }
public DelegateCommand StopTestCommand { get; }
public DelegateCommand ShowAllProjectsCommand { get; }
public TestViewModel(TestApplicationService testService, IHardwareService hardwareService,
IRepository<TestDevice> deviceRepository, IEventAggregator eventAggregator,
ILoggingService loggingService, IMessageTipService messageTipService)
{
_testService = testService;
_hardwareService = hardwareService;
_deviceRepository = deviceRepository;
_eventAggregator = eventAggregator;
_loggingService = loggingService;
_messageTipService = messageTipService;
StartTestCommand = new DelegateCommand(StartTestAsync, () => !_isTesting).ObservesProperty(() => _isTesting);
StopTestCommand = new DelegateCommand(StopTest, () => _isTesting).ObservesProperty(() => _isTesting);
ShowAllProjectsCommand = new DelegateCommand(ShowAllProjectsAsync);
// 订阅测试开始事件
_eventAggregator.GetEvent<TestStartedEvent>().Subscribe(OnTestStarted, ThreadOption.UIThread);
// 订阅测试停止事件
_eventAggregator.GetEvent<TestStoppedEvent>().Subscribe(OnTestStopped, ThreadOption.UIThread);
// 订阅器件更新事件
_eventAggregator.GetEvent<DeviceUpdatedEvent>().Subscribe(OnDeviceUpdated, ThreadOption.UIThread);
InitializePlot();
UpdateTemperatureAsync();
_loggingService.LogInfo("TestViewModel initialized.", "TestView");
}
private async void StartTestAsync()
{
_isTesting = true;
RaisePropertyChanged(nameof(TestStatusColor));
TestProgress = 0;
var device = (await _deviceRepository.GetAllAsync()).FirstOrDefault();
if (device == null)
{
_messageTipService.ShowMessage(MessageTip.MessageType.Error, "错误", "无可用器件");
_isTesting = false;
RaisePropertyChanged(nameof(TestStatusColor));
return;
}
var condition = new TestCondition(Current: 10.0, Voltage: 100.0, GateVoltage: 5.0);
await _testService.StartTestAsync(device.DeviceId, condition);
for (int i = 0; i <= 100; i += 10)
{
TestProgress = i;
await Task.Delay(500);
}
}
private void StopTest()
{
_testService.StopTest();
}
private async void ShowAllProjectsAsync()
{
_messageTipService.ShowMessage(MessageTip.MessageType.Info, "工程列表", string.Join(", ", TestProjects));
// 实际应显示筛选对话框
}
private async void UpdateTemperatureAsync()
{
while (true)
{
Temperature = await _hardwareService.CollectTemperatureDataAsync();
await Task.Delay(2000);
}
}
private void InitializePlot()
{
_plotModel.Series.Add(new LineSeries { Title = "结温" });
_plotModel.Series.Add(new LineSeries { Title = "热阻" });
}
private void OnTestStarted(TestResult result)
{
TestResults.Add(result);
var tjSeries = _plotModel.Series[0] as LineSeries;
var rthSeries = _plotModel.Series[1] as LineSeries;
tjSeries.Points.Add(new DataPoint(TestResults.Count, result.TjMax));
rthSeries.Points.Add(new DataPoint(TestResults.Count, result.RthJa));
_plotModel.InvalidatePlot(true);
_messageTipService.ShowMessage(MessageTip.MessageType.Success, "测试完成", $"器件 ID {result.DeviceId} 测试完成");
_loggingService.LogInfo($"Test started event received: Device ID {result.DeviceId}.", "TestView");
}
private void OnTestStopped()
{
_isTesting = false;
TestProgress = 0;
RaisePropertyChanged(nameof(TestStatusColor));
_messageTipService.ShowMessage(MessageTip.MessageType.Info, "测试停止", "测试已停止");
_loggingService.LogInfo("Test stopped event received.", "TestView");
}
private void OnDeviceUpdated(TestDeviceModel device)
{
_messageTipService.ShowMessage(MessageTip.MessageType.Info, "器件更新", $"器件 ID {device.DeviceId} 已更新");
_loggingService.LogInfo($"Device updated event received: ID {device.DeviceId}.", "TestView");
}
}
}
ModuleB/ModuleBModule.cs(订阅 TestStartedEvent
和 DeviceUpdatedEvent
):
using Prism.Ioc;
using Prism.Modularity;
using Prism.Events;
using WpfPowerTester.Core.Models;
using WpfPowerTester.Core.Services;
using WpfPowerTester.Events;
namespace WpfPowerTester.ModuleB
{
public class ModuleBModule : IModule
{
private readonly IEventAggregator _eventAggregator;
private readonly ILoggingService _loggingService;
public ModuleBModule(IEventAggregator eventAggregator, ILoggingService loggingService)
{
_eventAggregator = eventAggregator;
_loggingService = loggingService;
}
public void OnInitialized(IContainerProvider containerProvider)
{
// 订阅测试开始事件(后台线程处理)
_eventAggregator.GetEvent<TestStartedEvent>().Subscribe(OnTestStarted, ThreadOption.BackgroundThread);
// 订阅器件更新事件
_eventAggregator.GetEvent<DeviceUpdatedEvent>().Subscribe(OnDeviceUpdated, ThreadOption.BackgroundThread);
var messageTipService = containerProvider.Resolve<IMessageTipService>();
messageTipService.ShowMessage(MessageTip.MessageType.Info, "模块初始化", "ModuleB(数据分析模块)已加载");
_loggingService.LogInfo("ModuleB initialized.", "ModuleB");
}
private void OnTestStarted(TestResult result)
{
var messageTipService = ContainerLocator.Container.Resolve<IMessageTipService>();
messageTipService.ShowMessage(MessageTip.MessageType.Info, "ModuleB", $"检测到测试结果:器件 ID {result.DeviceId}, TjMax = {result.TjMax:F2}°C");
_loggingService.LogInfo($"ModuleB received test result: Device ID {result.DeviceId}.", "ModuleB");
// 实际应执行数据分析
}
private void OnDeviceUpdated(TestDeviceModel device)
{
var messageTipService = ContainerLocator.Container.Resolve<IMessageTipService>();
messageTipService.ShowMessage(MessageTip.MessageType.Info, "ModuleB", $"检测到器件更新:ID {device.DeviceId}");
_loggingService.LogInfo($"ModuleB received device update: ID {device.DeviceId}.", "ModuleB");
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
// 注册 ModuleB 特定服务
_loggingService.LogInfo("ModuleB registered services.", "ModuleB");
}
}
}
说明:
- MainWindowViewModel:
- 订阅
UserLoggedInEvent
,更新权限和导航到测试页面。 - 订阅
PermissionChangedEvent
,动态更新HasPermission
。
- 订阅
- TestViewModel:
- 订阅
TestStartedEvent
,更新测试结果和曲线。 - 订阅
TestStoppedEvent
,重置测试状态。 - 订阅
DeviceUpdatedEvent
,显示器件更新提示。
- 订阅
- ModuleBModule:
- 订阅
TestStartedEvent
和DeviceUpdatedEvent
,执行数据分析(模拟)。 - 使用
ThreadOption.BackgroundThread
避免阻塞 UI。
- 订阅
4. 事件使用场景
-
测试开始(TestStartedEvent):
- 触发:
TestApplicationService.StartTestAsync
完成测试后发布。 - 订阅:
TestViewModel
:更新TestResults
和曲线。ModuleBModule
:记录测试结果,准备分析。
- 效果:UI 显示新测试数据,ModuleB 记录日志。
- 触发:
-
测试停止(TestStoppedEvent):
- 触发:
TestApplicationService.StopTest
调用后发布。 - 订阅:
TestViewModel
重置状态和进度。 - 效果:UI 更新状态为红色,显示停止提示。
- 触发:
-
用户登录(UserLoggedInEvent):
- 触发:
LoginViewModel.LoginAsync
验证成功后发布。 - 订阅:
MainWindowViewModel
更新权限并导航。 - 效果:导航到
TestView
,显示欢迎消息。
- 触发:
-
器件更新(DeviceUpdatedEvent):
- 触发:
DeviceManagementViewModel
添加或删除器件后发布。 - 订阅:
TestViewModel
:显示更新提示。ModuleBModule
:记录器件变化。
- 效果:UI 显示提示,ModuleB 更新分析数据。
- 触发:
-
权限变更(PermissionChangedEvent):
- 触发:
UserManagementViewModel
添加、删除或更改用户角色后发布。 - 订阅:
MainWindowViewModel
更新HasPermission
。 - 效果:动态显示/隐藏设置和用户管理按钮。
- 触发:
四、领域事件扩展
1. 新增事件
- TestFailedEvent:测试失败时发布,携带错误信息:
public class TestFailedEvent : PubSubEvent<(int DeviceId, string ErrorMessage)> { }
- DataSavedEvent:测试数据保存到数据库后发布:
public class DataSavedEvent : PubSubEvent<TestResult> { }
发布示例(在 TestApplicationService
中):
public async Task StartTestAsync(int deviceId, TestCondition condition)
{
try
{
var device = await _deviceRepository.GetByIdAsync(deviceId);
if (device == null)
throw new InvalidOperationException("器件不存在");
var result = await _testService.StartTestAsync(device, condition);
await _databaseService.SaveTestResultAsync(result);
_eventAggregator.GetEvent<TestStartedEvent>().Publish(result);
_eventAggregator.GetEvent<DataSavedEvent>().Publish(result); // 发布数据保存事件
_loggingService.LogInfo($"Test started for Device ID {deviceId}.", "TestApplicationService");
}
catch (Exception ex)
{
_eventAggregator.GetEvent<TestFailedEvent>().Publish((deviceId, ex.Message)); // 发布测试失败事件
_loggingService.LogError($"Test failed for Device ID {deviceId}: {ex.Message}", "TestApplicationService");
throw;
}
}
2. 事件持久化
- 事件存储:将事件记录到数据库
Events
表,用于审计或重放:CREATE TABLE Events ( EventId INTEGER PRIMARY KEY AUTOINCREMENT, EventType TEXT NOT NULL, Payload TEXT NOT NULL, Timestamp TEXT NOT NULL );
- 实现(在
DatabaseService
中):public async Task SaveEventAsync(string eventType, string payload) { using var connection = new SQLiteConnection(_connectionString); await connection.OpenAsync(); var command = connection.CreateCommand(); command.CommandText = "INSERT INTO Events (EventType, Payload, Timestamp) VALUES (@EventType, @Payload, @Timestamp)"; command.Parameters.AddWithValue("@EventType", eventType); command.Parameters.AddWithValue("@Payload", payload); command.Parameters.AddWithValue("@Timestamp", DateTime.Now.ToString("o")); await command.ExecuteNonQueryAsync(); _loggingService.LogInfo($"Event saved: {eventType}.", "DatabaseService"); }
3. 事件重放
- 场景:系统恢复时重放事件,重建状态(如测试历史)。
- 实现:
public async Task ReplayEventsAsync() { using var connection = new SQLiteConnection(_connectionString); await connection.OpenAsync(); var command = connection.CreateCommand(); command.CommandText = "SELECT EventType, Payload FROM Events ORDER BY Timestamp"; using var reader = await command.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var eventType = reader.GetString(0); var payload = reader.GetString(1); switch (eventType) { case nameof(TestStartedEvent): var result = JsonSerializer.Deserialize<TestResult>(payload); _eventAggregator.GetEvent<TestStartedEvent>().Publish(result); break; // 其他事件处理 } } _loggingService.LogInfo("Events replayed.", "DatabaseService"); }
4. 异步订阅
- 异步处理:为耗时操作(如数据分析)使用异步订阅:
_eventAggregator.GetEvent<TestStartedEvent>().Subscribe(async result => { await Task.Delay(1000); // 模拟耗时分析 _loggingService.LogInfo($"Async analysis for Device ID {result.DeviceId}.", "ModuleB"); }, ThreadOption.BackgroundThread);
五、运行与验证
- 初始化:
- 启动时注册
IEventAggregator
,加载模块(ModuleA
、ModuleB
、ModuleC
)。 MainWindowViewModel
订阅UserLoggedInEvent
和PermissionChangedEvent
。TestViewModel
订阅TestStartedEvent
、TestStoppedEvent
和DeviceUpdatedEvent
。
- 启动时注册
- 验证:
- 测试开始:触发
StartTestCommand
,验证TestStartedEvent
更新 UI 和 ModuleB 日志。 - 测试停止:触发
StopTestCommand
,验证TestStoppedEvent
重置状态。 - 用户登录:登录后验证
UserLoggedInEvent
导航到TestView
。 - 器件更新:添加/删除器件,验证
DeviceUpdatedEvent
通知TestViewModel
和ModuleB
。 - 权限变更:更改用户角色,验证
PermissionChangedEvent
更新HasPermission
。
- 测试开始:触发
- 日志检查:
- 查看
Logs
表,确认事件发布和订阅记录。 - 示例日志:
Timestamp: 2025-09-06T11:30:00Z, Type: Info, Message: Test started event received: Device ID 1., Module: TestView
- 查看
六、领域事件优势与注意事项
优势
- 松耦合:模块和 ViewModel 通过事件通信,无需直接依赖。
- 可扩展性:新增事件(如
TestFailedEvent
)无需修改现有代码。 - 异步支持:
ThreadOption.BackgroundThread
适合耗时操作,ThreadOption.UIThread
适合 UI 更新。 - 审计能力:事件存储到数据库,支持追踪和重放。
注意事项
- 事件一致性:确保事件发布后数据库操作成功,使用事务:
using var transaction = await connection.BeginTransactionAsync(); await _databaseService.SaveTestResultAsync(result); _eventAggregator.GetEvent<TestStartedEvent>().Publish(result); await transaction.CommitAsync();
- 事件爆炸:避免过多事件导致复杂性,聚焦核心业务事件。
- 性能:异步订阅可能导致延迟,需优化订阅处理逻辑。
- 调试:日志记录事件发布和订阅,便于追踪问题。
更多推荐
所有评论(0)