在基于 领域驱动设计(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,表示已发生。
  • 事件数据:携带足够的信息(如 TestResultUserModel),但避免冗余。
  • 线程安全:结合 Prism 的 IEventAggregator,支持 UI 线程和后台线程订阅。
  • 可扩展性:事件定义在独立项目(WpfPowerTester.Events),便于模块引用。
  • 日志记录:事件发布和订阅时记录日志,存储到 Logs 表。
2. 事件发布与订阅流程
  1. 发布
    • 领域服务(如 TestService)或应用服务(如 TestApplicationService)在业务逻辑完成后发布事件。
    • 使用 IEventAggregator.GetEvent<T>().Publish(payload)
  2. 订阅
    • ViewModel(如 MainWindowViewModel)或模块(如 ModuleB)订阅事件。
    • 使用 IEventAggregator.GetEvent<T>().Subscribe(action, threadOption)
  3. 处理
    • 订阅者处理事件,如更新 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(发布 TestStartedEventTestStoppedEvent):

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:在测试开始和停止时发布 TestStartedEventTestStoppedEvent
  • LoginViewModel:登录成功后发布 UserLoggedInEvent
  • DeviceManagementViewModel:器件添加或删除时发布 DeviceUpdatedEvent
  • UserManagementViewModel:用户添加、删除或角色变更时发布 PermissionChangedEvent
  • 日志:每次发布事件时记录日志,存储到 Logs 表。
3. 事件订阅

以下是事件在 ViewModel 和模块中的订阅代码。

ViewModels/MainWindowViewModel.cs(订阅 UserLoggedInEventPermissionChangedEvent):

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(订阅 TestStartedEventTestStoppedEvent):

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(订阅 TestStartedEventDeviceUpdatedEvent):

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
    • 订阅 TestStartedEventDeviceUpdatedEvent,执行数据分析(模拟)。
    • 使用 ThreadOption.BackgroundThread 避免阻塞 UI。
4. 事件使用场景
  1. 测试开始(TestStartedEvent)

    • 触发TestApplicationService.StartTestAsync 完成测试后发布。
    • 订阅
      • TestViewModel:更新 TestResults 和曲线。
      • ModuleBModule:记录测试结果,准备分析。
    • 效果:UI 显示新测试数据,ModuleB 记录日志。
  2. 测试停止(TestStoppedEvent)

    • 触发TestApplicationService.StopTest 调用后发布。
    • 订阅TestViewModel 重置状态和进度。
    • 效果:UI 更新状态为红色,显示停止提示。
  3. 用户登录(UserLoggedInEvent)

    • 触发LoginViewModel.LoginAsync 验证成功后发布。
    • 订阅MainWindowViewModel 更新权限并导航。
    • 效果:导航到 TestView,显示欢迎消息。
  4. 器件更新(DeviceUpdatedEvent)

    • 触发DeviceManagementViewModel 添加或删除器件后发布。
    • 订阅
      • TestViewModel:显示更新提示。
      • ModuleBModule:记录器件变化。
    • 效果:UI 显示提示,ModuleB 更新分析数据。
  5. 权限变更(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);
    

五、运行与验证

  1. 初始化
    • 启动时注册 IEventAggregator,加载模块(ModuleAModuleBModuleC)。
    • MainWindowViewModel 订阅 UserLoggedInEventPermissionChangedEvent
    • TestViewModel 订阅 TestStartedEventTestStoppedEventDeviceUpdatedEvent
  2. 验证
    • 测试开始:触发 StartTestCommand,验证 TestStartedEvent 更新 UI 和 ModuleB 日志。
    • 测试停止:触发 StopTestCommand,验证 TestStoppedEvent 重置状态。
    • 用户登录:登录后验证 UserLoggedInEvent 导航到 TestView
    • 器件更新:添加/删除器件,验证 DeviceUpdatedEvent 通知 TestViewModelModuleB
    • 权限变更:更改用户角色,验证 PermissionChangedEvent 更新 HasPermission
  3. 日志检查
    • 查看 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();
    
  • 事件爆炸:避免过多事件导致复杂性,聚焦核心业务事件。
  • 性能:异步订阅可能导致延迟,需优化订阅处理逻辑。
  • 调试:日志记录事件发布和订阅,便于追踪问题。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐