From 4db79235129c672db43f00a1957cc02c67b9782d Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:44:44 +0800 Subject: [PATCH 1/7] =?UTF-8?q?docs(core):=20=E6=B7=BB=E5=8A=A0=20CQRS=20?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E6=A8=A1=E5=BC=8F=E8=AF=A6=E7=BB=86=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 完整介绍 CQRS 核心概念包括命令、查询、处理器和分发器 - 提供命令和查询的定义与实现示例代码 - 详细介绍处理器编写方法和注册流程 - 说明管道行为(Behaviors)的使用方式 - 展示通知(Notification)和流式处理功能 - 提供最佳实践和常见问题解决方案 - 包含完整的 API 参考和用法示例 --- CLAUDE.md | 3 +- .../Architectures/IArchitecture.cs | 16 +- GFramework.Core.Abstractions/GlobalUsings.cs | 3 +- .../Ioc/IIocContainer.cs | 17 +- ...ArchitectureAdditionalCqrsHandlersTests.cs | 165 ++++++++++++++++++ .../RegistryInitializationHookBaseTests.cs | 21 +++ GFramework.Core/Architectures/Architecture.cs | 21 +++ .../Architectures/ArchitectureBootstrapper.cs | 10 +- .../Architectures/ArchitectureModules.cs | 23 +++ GFramework.Core/Ioc/MicrosoftDiContainer.cs | 69 ++++++++ docs/zh-CN/core/cqrs.md | 22 ++- 11 files changed, 360 insertions(+), 10 deletions(-) create mode 100644 GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs diff --git a/CLAUDE.md b/CLAUDE.md index 4ea95597..da7e13c6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -105,7 +105,8 @@ major 版本中移除。 - `PriorityGenerator` (`[Priority]`): 生成优先级比较相关实现。 - `EnumExtensionsGenerator` (`[GenerateEnumExtensions]`): 生成枚举扩展能力。 - `ContextAwareGenerator` (`[ContextAware]`): 自动实现 `IContextAware` 相关样板逻辑。 -- `CqrsHandlerRegistryGenerator`: 为消费端程序集生成 CQRS handler 注册器,运行时优先使用生成产物,无法覆盖时回退到反射扫描。 +- `CqrsHandlerRegistryGenerator`: 为消费端程序集生成 CQRS handler 注册器,运行时优先使用生成产物,无法覆盖时回退到反射扫描;非默认程序集可通过 + `RegisterCqrsHandlersFromAssembly(...)` / `RegisterCqrsHandlersFromAssemblies(...)` 显式接入同一路径。 这些生成器的目标是减少重复代码,同时保持框架层 API 的一致性与可维护性。 diff --git a/GFramework.Core.Abstractions/Architectures/IArchitecture.cs b/GFramework.Core.Abstractions/Architectures/IArchitecture.cs index 16386d5a..ab54bd54 100644 --- a/GFramework.Core.Abstractions/Architectures/IArchitecture.cs +++ b/GFramework.Core.Abstractions/Architectures/IArchitecture.cs @@ -1,9 +1,9 @@ using System.ComponentModel; +using System.Reflection; using GFramework.Core.Abstractions.Lifecycle; using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Utility; -using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Abstractions.Architectures; @@ -96,6 +96,20 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia void RegisterMediatorBehavior() where TBehavior : class; + /// + /// 从指定程序集显式注册 CQRS 处理器。 + /// 当处理器位于默认架构程序集之外的模块或扩展程序集中时,可在初始化阶段调用该入口接入对应程序集。 + /// + /// 包含 CQRS 处理器或生成注册器的程序集。 + void RegisterCqrsHandlersFromAssembly(Assembly assembly); + + /// + /// 从多个程序集显式注册 CQRS 处理器。 + /// 该入口会对程序集集合去重,适用于统一接入多个扩展包或模块程序集。 + /// + /// 要接入的程序集集合。 + void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies); + /// /// 安装架构模块 /// diff --git a/GFramework.Core.Abstractions/GlobalUsings.cs b/GFramework.Core.Abstractions/GlobalUsings.cs index 7c0f226a..204433de 100644 --- a/GFramework.Core.Abstractions/GlobalUsings.cs +++ b/GFramework.Core.Abstractions/GlobalUsings.cs @@ -16,4 +16,5 @@ global using System.Collections.Generic; global using System.Runtime; global using System.Linq; global using System.Threading; -global using System.Threading.Tasks; \ No newline at end of file +global using System.Threading.Tasks; +global using Microsoft.Extensions.DependencyInjection; diff --git a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs index aead5a9d..b1908020 100644 --- a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs +++ b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs @@ -1,7 +1,7 @@ using System.ComponentModel; +using System.Reflection; using GFramework.Core.Abstractions.Rule; using GFramework.Core.Abstractions.Systems; -using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Abstractions.Ioc; @@ -109,6 +109,21 @@ public interface IIocContainer : IContextAware void RegisterMediatorBehavior() where TBehavior : class; + /// + /// 从指定程序集显式注册 CQRS 处理器。 + /// 该入口适用于处理器不位于默认架构程序集中的场景,例如扩展包、模块程序集或拆分后的业务程序集。 + /// 运行时会优先使用程序集级源码生成注册器;若不存在可用注册器,则自动回退到反射扫描。 + /// + /// 包含 CQRS 处理器或生成注册器的程序集。 + void RegisterCqrsHandlersFromAssembly(Assembly assembly); + + /// + /// 从多个程序集显式注册 CQRS 处理器。 + /// 容器会按稳定程序集键去重,避免默认启动路径与扩展模块重复接入同一程序集时产生重复 handler 映射。 + /// + /// 要接入的程序集集合。 + void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies); + /// /// 配置服务 diff --git a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs new file mode 100644 index 00000000..ae0bbad0 --- /dev/null +++ b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs @@ -0,0 +1,165 @@ +using System.Reflection; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Logging; +using GFramework.Core.Architectures; +using GFramework.Core.Logging; + +namespace GFramework.Core.Tests.Architectures; + +/// +/// 验证架构初始化阶段可以显式接入默认程序集之外的 CQRS handlers。 +/// +[TestFixture] +public sealed class ArchitectureAdditionalCqrsHandlersTests +{ + /// + /// 初始化日志工厂和共享测试状态。 + /// + [SetUp] + public void SetUp() + { + LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); + GameContext.Clear(); + AdditionalAssemblyNotificationHandler.Reset(); + } + + /// + /// 清理测试过程中写入的共享状态。 + /// + [TearDown] + public void TearDown() + { + AdditionalAssemblyNotificationHandler.Reset(); + GameContext.Clear(); + } + + /// + /// 验证显式声明的额外程序集会在初始化阶段接入当前架构容器。 + /// + [Test] + public async Task RegisterCqrsHandlersFromAssembly_Should_Register_Handlers_From_Explicit_Assembly() + { + var generatedAssembly = CreateGeneratedHandlerAssembly(); + var architecture = new AdditionalHandlersTestArchitecture(target => + target.RegisterCqrsHandlersFromAssembly(generatedAssembly.Object)); + + await architecture.InitializeAsync(); + await architecture.Context.PublishAsync(new AdditionalAssemblyNotification()); + + Assert.That(AdditionalAssemblyNotificationHandler.InvocationCount, Is.EqualTo(1)); + + await architecture.DestroyAsync(); + } + + /// + /// 验证同一额外程序集被重复声明时,不会向容器重复写入相同 handler 映射。 + /// + [Test] + public async Task RegisterCqrsHandlersFromAssembly_Should_Deduplicate_Repeated_Assembly_Registration() + { + var generatedAssembly = CreateGeneratedHandlerAssembly(); + var architecture = new AdditionalHandlersTestArchitecture(target => + { + target.RegisterCqrsHandlersFromAssembly(generatedAssembly.Object); + target.RegisterCqrsHandlersFromAssemblies([generatedAssembly.Object]); + }); + + await architecture.InitializeAsync(); + await architecture.Context.PublishAsync(new AdditionalAssemblyNotification()); + + Assert.That(AdditionalAssemblyNotificationHandler.InvocationCount, Is.EqualTo(1)); + + await architecture.DestroyAsync(); + } + + /// + /// 创建一个仅暴露程序集级 CQRS registry 元数据的 mocked Assembly。 + /// 该测试替身模拟“扩展程序集已经挂接 source-generator,运行时只需显式接入该程序集”的真实路径。 + /// + /// 包含程序集级 handler registry 元数据的 mocked Assembly。 + private static Mock CreateGeneratedHandlerAssembly() + { + var generatedAssembly = new Mock(); + generatedAssembly + .SetupGet(static assembly => assembly.FullName) + .Returns("GFramework.Core.Tests.Architectures.ExplicitAdditionalHandlers, Version=1.0.0.0"); + generatedAssembly + .Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false)) + .Returns([new CqrsHandlerRegistryAttribute(typeof(AdditionalAssemblyNotificationHandlerRegistry))]); + return generatedAssembly; + } + + /// + /// 用于测试额外程序集注册入口的最小架构实现。 + /// + private sealed class AdditionalHandlersTestArchitecture(Action configure) : + Architecture + { + /// + /// 在初始化阶段执行测试注入的额外 CQRS 程序集接入逻辑。 + /// + protected override void OnInitialize() + { + configure(this); + } + } +} + +/// +/// 用于验证额外程序集接入是否成功的测试通知。 +/// +public sealed record AdditionalAssemblyNotification : INotification; + +/// +/// 由模拟扩展程序集的生成注册器挂入当前容器的通知处理器。 +/// +public sealed class AdditionalAssemblyNotificationHandler : INotificationHandler +{ + /// + /// 获取当前测试进程中该处理器的执行次数。 + /// + public static int InvocationCount { get; private set; } + + /// + /// 记录一次通知处理,供测试断言显式程序集接入后的运行时行为。 + /// + /// 通知实例。 + /// 取消令牌。 + /// 已完成任务。 + public ValueTask Handle(AdditionalAssemblyNotification notification, CancellationToken cancellationToken) + { + InvocationCount++; + return ValueTask.CompletedTask; + } + + /// + /// 清理共享计数器,避免测试间相互污染。 + /// + public static void Reset() + { + InvocationCount = 0; + } +} + +/// +/// 模拟由 source-generator 为扩展程序集生成的 CQRS handler registry。 +/// +internal sealed class AdditionalAssemblyNotificationHandlerRegistry : ICqrsHandlerRegistry +{ + /// + /// 将扩展程序集中的通知处理器映射写入服务集合。 + /// + /// 目标服务集合。 + /// 日志记录器。 + public void Register(IServiceCollection services, ILogger logger) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(logger); + + services + .AddTransient, + AdditionalAssemblyNotificationHandler>(); + logger.Debug( + $"Registered CQRS handler {typeof(AdditionalAssemblyNotificationHandler).FullName} as {typeof(INotificationHandler).FullName}."); + } +} diff --git a/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs b/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs index 38e06805..66f7869c 100644 --- a/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs +++ b/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs @@ -1,3 +1,4 @@ +using System.Reflection; using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Enums; using GFramework.Core.Abstractions.Lifecycle; @@ -185,6 +186,16 @@ public class TestArchitectureWithRegistry : IArchitecture throw new NotImplementedException(); } + public void RegisterCqrsHandlersFromAssembly(Assembly assembly) + { + throw new NotImplementedException(); + } + + public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies) + { + throw new NotImplementedException(); + } + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] public void RegisterMediatorBehavior() where TBehavior : class { @@ -316,6 +327,16 @@ public class TestArchitectureWithoutRegistry : IArchitecture throw new NotImplementedException(); } + public void RegisterCqrsHandlersFromAssembly(Assembly assembly) + { + throw new NotImplementedException(); + } + + public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies) + { + throw new NotImplementedException(); + } + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] public void RegisterMediatorBehavior() where TBehavior : class { diff --git a/GFramework.Core/Architectures/Architecture.cs b/GFramework.Core/Architectures/Architecture.cs index 0395347d..0fb361a4 100644 --- a/GFramework.Core/Architectures/Architecture.cs +++ b/GFramework.Core/Architectures/Architecture.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Reflection; using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Enums; using GFramework.Core.Abstractions.Environment; @@ -169,6 +170,26 @@ public abstract class Architecture : IArchitecture RegisterCqrsPipelineBehavior(); } + /// + /// 从指定程序集显式注册 CQRS 处理器。 + /// 该入口适用于把拆分到其他模块或扩展包程序集中的 handlers 接入当前架构。 + /// + /// 包含 CQRS 处理器或生成注册器的程序集。 + public void RegisterCqrsHandlersFromAssembly(Assembly assembly) + { + _modules.RegisterCqrsHandlersFromAssembly(assembly); + } + + /// + /// 从多个程序集显式注册 CQRS 处理器。 + /// 适用于在初始化阶段批量接入多个扩展程序集,并沿用容器的去重策略避免重复注册。 + /// + /// 要接入的程序集集合。 + public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies) + { + _modules.RegisterCqrsHandlersFromAssemblies(assemblies); + } + /// /// 安装架构模块 /// diff --git a/GFramework.Core/Architectures/ArchitectureBootstrapper.cs b/GFramework.Core/Architectures/ArchitectureBootstrapper.cs index d022c806..343bcdd6 100644 --- a/GFramework.Core/Architectures/ArchitectureBootstrapper.cs +++ b/GFramework.Core/Architectures/ArchitectureBootstrapper.cs @@ -1,7 +1,6 @@ using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Environment; using GFramework.Core.Abstractions.Logging; -using GFramework.Core.Cqrs.Internal; namespace GFramework.Core.Architectures; @@ -99,10 +98,11 @@ internal sealed class ArchitectureBootstrapper( private void ConfigureServices(IArchitectureContext context, Action? configurator) { services.SetContext(context); - CqrsHandlerRegistrar.RegisterHandlers( - services.Container, - [architectureType.Assembly, typeof(ArchitectureContext).Assembly], - logger); + services.Container.RegisterCqrsHandlersFromAssemblies( + [ + architectureType.Assembly, + typeof(ArchitectureContext).Assembly + ]); if (configurator is null) logger.Debug("No external service configurator provided. Using built-in CQRS runtime registration only."); diff --git a/GFramework.Core/Architectures/ArchitectureModules.cs b/GFramework.Core/Architectures/ArchitectureModules.cs index a0c1b149..8e659fa4 100644 --- a/GFramework.Core/Architectures/ArchitectureModules.cs +++ b/GFramework.Core/Architectures/ArchitectureModules.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Reflection; using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Logging; @@ -38,6 +39,28 @@ internal sealed class ArchitectureModules( RegisterCqrsPipelineBehavior(); } + /// + /// 从指定程序集显式注册 CQRS 处理器。 + /// 该入口用于把默认架构程序集之外的扩展处理器接入当前架构容器。 + /// + /// 包含 CQRS 处理器或生成注册器的程序集。 + public void RegisterCqrsHandlersFromAssembly(Assembly assembly) + { + logger.Debug($"Registering CQRS handlers from assembly: {assembly.FullName ?? assembly.GetName().Name}"); + services.Container.RegisterCqrsHandlersFromAssembly(assembly); + } + + /// + /// 从多个程序集显式注册 CQRS 处理器。 + /// 它会复用容器级去重逻辑,避免模块重复接入相同程序集时重复注册 handler。 + /// + /// 要接入的程序集集合。 + public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies) + { + logger.Debug("Registering CQRS handlers from additional assemblies."); + services.Container.RegisterCqrsHandlersFromAssemblies(assemblies); + } + /// /// 安装架构模块 /// diff --git a/GFramework.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs index 3b0396c1..b3dfcb03 100644 --- a/GFramework.Core/Ioc/MicrosoftDiContainer.cs +++ b/GFramework.Core/Ioc/MicrosoftDiContainer.cs @@ -1,9 +1,11 @@ using System.ComponentModel; +using System.Reflection; using GFramework.Core.Abstractions.Bases; using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Systems; +using GFramework.Core.Cqrs.Internal; using GFramework.Core.Logging; using GFramework.Core.Rule; @@ -56,6 +58,12 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) /// private readonly HashSet _registeredInstances = []; + /// + /// 已接入 CQRS handler 注册流程的程序集键集合。 + /// 使用稳定字符串键而不是 Assembly 引用本身,以避免默认路径和显式扩展路径使用不同 Assembly 对象时重复注册。 + /// + private readonly HashSet _registeredCqrsHandlerAssemblyKeys = new(StringComparer.Ordinal); + /// /// 日志记录器,用于记录容器操作日志 /// @@ -372,6 +380,56 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) RegisterCqrsPipelineBehavior(); } + /// + /// 从指定程序集显式注册 CQRS 处理器。 + /// + /// 包含 CQRS 处理器或生成注册器的程序集。 + public void RegisterCqrsHandlersFromAssembly(Assembly assembly) + { + ArgumentNullException.ThrowIfNull(assembly); + RegisterCqrsHandlersFromAssemblies([assembly]); + } + + /// + /// 从多个程序集显式注册 CQRS 处理器。 + /// 同一程序集只会被接入一次,避免默认启动路径与扩展模块重复注册相同 handlers。 + /// + /// 要接入的程序集集合。 + public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies) + { + ArgumentNullException.ThrowIfNull(assemblies); + + _lock.EnterWriteLock(); + try + { + ThrowIfFrozen(); + + var processedAssemblyKeys = new HashSet(StringComparer.Ordinal); + foreach (var assembly in assemblies + .Where(static assembly => assembly is not null) + .OrderBy(GetCqrsAssemblyRegistrationKey, StringComparer.Ordinal)) + { + var assemblyKey = GetCqrsAssemblyRegistrationKey(assembly); + if (!processedAssemblyKeys.Add(assemblyKey)) + continue; + + if (_registeredCqrsHandlerAssemblyKeys.Contains(assemblyKey)) + { + _logger.Debug( + $"Skipping CQRS handler registration for assembly {assemblyKey} because it was already registered."); + continue; + } + + CqrsHandlerRegistrar.RegisterHandlers(this, [assembly], _logger); + _registeredCqrsHandlerAssemblyKeys.Add(assemblyKey); + } + } + finally + { + _lock.ExitWriteLock(); + } + } + /// /// 配置服务 /// @@ -816,5 +874,16 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) } } + /// + /// 生成 CQRS handler 注册用的稳定程序集键。 + /// 该键需要同时兼顾真实程序集与测试中使用的 mocked Assembly,避免仅靠引用比较导致重复接入。 + /// + /// 目标程序集。 + /// 稳定的程序集标识字符串。 + private static string GetCqrsAssemblyRegistrationKey(Assembly assembly) + { + return assembly.FullName ?? assembly.GetName().Name ?? assembly.ToString(); + } + #endregion } diff --git a/docs/zh-CN/core/cqrs.md b/docs/zh-CN/core/cqrs.md index 5c0a2203..9d2410fe 100644 --- a/docs/zh-CN/core/cqrs.md +++ b/docs/zh-CN/core/cqrs.md @@ -224,7 +224,27 @@ public class GameArchitecture : Architecture 如果该程序集没有生成注册器,或者包含生成代码无法合法引用的处理器类型,则会自动回退到运行时反射扫描。 `GFramework.Core` 等未挂接该生成器的程序集仍会继续走反射扫描。 -如果处理器位于其他模块或扩展程序集中,需要额外接入对应程序集的处理器注册,而不是只依赖默认接入范围。 +如果处理器位于其他模块或扩展程序集中,需要额外接入对应程序集的处理器注册,而不是只依赖默认接入范围: + +```csharp +public class GameArchitecture : Architecture +{ + protected override void OnInitialize() + { + RegisterCqrsPipelineBehavior>(); + + RegisterCqrsHandlersFromAssemblies( + [ + typeof(InventoryCqrsMarker).Assembly, + typeof(BattleCqrsMarker).Assembly + ]); + } +} +``` + +`RegisterCqrsHandlersFromAssembly(...)` / `RegisterCqrsHandlersFromAssemblies(...)` 会复用与默认启动路径相同的注册逻辑: +优先使用程序集级生成注册器,失败时自动回退到反射扫描;如果同一程序集已经由默认路径或其他模块接入,框架会自动去重,避免重复注册 +handler。 `RegisterCqrsPipelineBehavior()` 是推荐入口;旧的 `RegisterMediatorBehavior()` 仅作为兼容名称保留,当前已标记为 `Obsolete` 并从 IntelliSense 主路径隐藏,计划在未来 major 版本中移除。 From 27266d037d7052afe5cdfdb29d13b2f9ccc249a4 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:38:45 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat(arch):=20=E6=B7=BB=E5=8A=A0=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E5=9F=BA=E7=A1=80=E7=B1=BB=E5=92=8C=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E6=B3=A8=E5=85=A5=E5=AE=B9=E5=99=A8=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 Architecture 基类提供系统、模型、工具等组件的注册与管理功能 - 实现架构生命周期管理、初始化流程控制和阶段转换功能 - 添加 ArchitectureModules 模块管理器负责 CQRS 行为注册和模块安装 - 实现 MicrosoftDiContainer 依赖注入容器适配器 - 支持单例、瞬态、作用域服务注册和工厂方法注册 - 添加 CQRS 请求管道行为和处理器注册功能 - 实现线程安全的读写锁保护容器操作 - 提供服务获取、排序和优先级管理功能 --- .../Architectures/IArchitecture.cs | 4 + .../Ioc/IIocContainer.cs | 4 + ...ArchitectureAdditionalCqrsHandlersTests.cs | 80 +++++++++++++------ .../RegistryInitializationHookBaseTests.cs | 20 +++++ .../Ioc/MicrosoftDiContainerTests.cs | 32 +++++++- GFramework.Core/Architectures/Architecture.cs | 4 + .../Architectures/ArchitectureModules.cs | 6 ++ GFramework.Core/Ioc/MicrosoftDiContainer.cs | 5 ++ 8 files changed, 130 insertions(+), 25 deletions(-) diff --git a/GFramework.Core.Abstractions/Architectures/IArchitecture.cs b/GFramework.Core.Abstractions/Architectures/IArchitecture.cs index ab54bd54..e1687ff9 100644 --- a/GFramework.Core.Abstractions/Architectures/IArchitecture.cs +++ b/GFramework.Core.Abstractions/Architectures/IArchitecture.cs @@ -101,6 +101,8 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia /// 当处理器位于默认架构程序集之外的模块或扩展程序集中时,可在初始化阶段调用该入口接入对应程序集。 /// /// 包含 CQRS 处理器或生成注册器的程序集。 + /// + /// 当前架构的底层容器已冻结,无法继续注册处理器。 void RegisterCqrsHandlersFromAssembly(Assembly assembly); /// @@ -108,6 +110,8 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia /// 该入口会对程序集集合去重,适用于统一接入多个扩展包或模块程序集。 /// /// 要接入的程序集集合。 + /// + /// 当前架构的底层容器已冻结,无法继续注册处理器。 void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies); /// diff --git a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs index b1908020..3149b3c4 100644 --- a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs +++ b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs @@ -115,6 +115,8 @@ public interface IIocContainer : IContextAware /// 运行时会优先使用程序集级源码生成注册器;若不存在可用注册器,则自动回退到反射扫描。 /// /// 包含 CQRS 处理器或生成注册器的程序集。 + /// + /// 容器已冻结,无法继续注册 CQRS 处理器。 void RegisterCqrsHandlersFromAssembly(Assembly assembly); /// @@ -122,6 +124,8 @@ public interface IIocContainer : IContextAware /// 容器会按稳定程序集键去重,避免默认启动路径与扩展模块重复接入同一程序集时产生重复 handler 映射。 /// /// 要接入的程序集集合。 + /// + /// 容器已冻结,无法继续注册 CQRS 处理器。 void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies); diff --git a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs index ae0bbad0..23725e7c 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs @@ -18,9 +18,10 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests [SetUp] public void SetUp() { + _previousLoggerFactoryProvider = LoggerFactoryResolver.Provider; LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); GameContext.Clear(); - AdditionalAssemblyNotificationHandler.Reset(); + AdditionalAssemblyNotificationHandlerState.Reset(); } /// @@ -29,10 +30,15 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests [TearDown] public void TearDown() { - AdditionalAssemblyNotificationHandler.Reset(); + AdditionalAssemblyNotificationHandlerState.Reset(); GameContext.Clear(); + LoggerFactoryResolver.Provider = _previousLoggerFactoryProvider + ?? throw new InvalidOperationException( + "LoggerFactoryResolver.Provider should be captured during setup."); } + private ILoggerFactoryProvider? _previousLoggerFactoryProvider; + /// /// 验证显式声明的额外程序集会在初始化阶段接入当前架构容器。 /// @@ -44,11 +50,16 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests target.RegisterCqrsHandlersFromAssembly(generatedAssembly.Object)); await architecture.InitializeAsync(); - await architecture.Context.PublishAsync(new AdditionalAssemblyNotification()); + try + { + await architecture.Context.PublishAsync(new AdditionalAssemblyNotification()); - Assert.That(AdditionalAssemblyNotificationHandler.InvocationCount, Is.EqualTo(1)); - - await architecture.DestroyAsync(); + Assert.That(AdditionalAssemblyNotificationHandlerState.InvocationCount, Is.EqualTo(1)); + } + finally + { + await architecture.DestroyAsync(); + } } /// @@ -65,11 +76,16 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests }); await architecture.InitializeAsync(); - await architecture.Context.PublishAsync(new AdditionalAssemblyNotification()); + try + { + await architecture.Context.PublishAsync(new AdditionalAssemblyNotification()); - Assert.That(AdditionalAssemblyNotificationHandler.InvocationCount, Is.EqualTo(1)); - - await architecture.DestroyAsync(); + Assert.That(AdditionalAssemblyNotificationHandlerState.InvocationCount, Is.EqualTo(1)); + } + finally + { + await architecture.DestroyAsync(); + } } /// @@ -111,25 +127,26 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests public sealed record AdditionalAssemblyNotification : INotification; /// -/// 由模拟扩展程序集的生成注册器挂入当前容器的通知处理器。 +/// 记录模拟扩展程序集通知处理器的执行次数。 /// -public sealed class AdditionalAssemblyNotificationHandler : INotificationHandler +public static class AdditionalAssemblyNotificationHandlerState { + private static int _invocationCount; + /// /// 获取当前测试进程中该处理器的执行次数。 /// - public static int InvocationCount { get; private set; } + /// + /// 该计数器通过原子读写维护,以支持 NUnit 并行执行环境中的并发访问。 + /// + public static int InvocationCount => Volatile.Read(ref _invocationCount); /// /// 记录一次通知处理,供测试断言显式程序集接入后的运行时行为。 /// - /// 通知实例。 - /// 取消令牌。 - /// 已完成任务。 - public ValueTask Handle(AdditionalAssemblyNotification notification, CancellationToken cancellationToken) + public static void RecordInvocation() { - InvocationCount++; - return ValueTask.CompletedTask; + Interlocked.Increment(ref _invocationCount); } /// @@ -137,7 +154,7 @@ public sealed class AdditionalAssemblyNotificationHandler : INotificationHandler /// public static void Reset() { - InvocationCount = 0; + Interlocked.Exchange(ref _invocationCount, 0); } } @@ -156,10 +173,25 @@ internal sealed class AdditionalAssemblyNotificationHandlerRegistry : ICqrsHandl ArgumentNullException.ThrowIfNull(services); ArgumentNullException.ThrowIfNull(logger); - services - .AddTransient, - AdditionalAssemblyNotificationHandler>(); + services.AddTransient>(_ => CreateHandler()); logger.Debug( - $"Registered CQRS handler {typeof(AdditionalAssemblyNotificationHandler).FullName} as {typeof(INotificationHandler).FullName}."); + $"Registered CQRS handler proxy for {typeof(INotificationHandler).FullName}."); + } + + /// + /// 创建一个仅供显式程序集注册路径使用的动态通知处理器。 + /// + /// 用于记录通知触发次数的测试替身处理器。 + private static INotificationHandler CreateHandler() + { + var handler = new Mock>(); + handler + .Setup(target => target.Handle(It.IsAny(), It.IsAny())) + .Returns(() => + { + AdditionalAssemblyNotificationHandlerState.RecordInvocation(); + return ValueTask.CompletedTask; + }); + return handler.Object; } } diff --git a/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs b/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs index 66f7869c..b224804d 100644 --- a/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs +++ b/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs @@ -186,11 +186,21 @@ public class TestArchitectureWithRegistry : IArchitecture throw new NotImplementedException(); } + /// + /// 测试替身未实现显式程序集 CQRS 处理器接入入口。 + /// + /// 包含 CQRS 处理器或生成注册器的程序集。 + /// 该测试替身不参与 CQRS 程序集接入路径验证。 public void RegisterCqrsHandlersFromAssembly(Assembly assembly) { throw new NotImplementedException(); } + /// + /// 测试替身未实现显式程序集 CQRS 处理器接入入口。 + /// + /// 要接入的程序集集合。 + /// 该测试替身不参与 CQRS 程序集接入路径验证。 public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies) { throw new NotImplementedException(); @@ -327,11 +337,21 @@ public class TestArchitectureWithoutRegistry : IArchitecture throw new NotImplementedException(); } + /// + /// 测试替身未实现显式程序集 CQRS 处理器接入入口。 + /// + /// 包含 CQRS 处理器或生成注册器的程序集。 + /// 该测试替身不参与 CQRS 程序集接入路径验证。 public void RegisterCqrsHandlersFromAssembly(Assembly assembly) { throw new NotImplementedException(); } + /// + /// 测试替身未实现显式程序集 CQRS 处理器接入入口。 + /// + /// 要接入的程序集集合。 + /// 该测试替身不参与 CQRS 程序集接入路径验证。 public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies) { throw new NotImplementedException(); diff --git a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs index 0621bcfc..b4e5af67 100644 --- a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs +++ b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs @@ -1,7 +1,9 @@ using System.Reflection; using GFramework.Core.Abstractions.Bases; +using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Ioc; using GFramework.Core.Logging; +using GFramework.Core.Tests.Cqrs; using GFramework.Core.Tests.Systems; namespace GFramework.Core.Tests.Ioc; @@ -306,6 +308,34 @@ public class MicrosoftDiContainerTests Assert.That(_container.Contains(), Is.False); } + /// + /// 测试清空容器后可以重新接入同一程序集中的 CQRS 处理器。 + /// + [Test] + public void Clear_Should_Reset_Cqrs_Assembly_Deduplication_State() + { + var assembly = typeof(CqrsHandlerRegistrarTests).Assembly; + + _container.RegisterCqrsHandlersFromAssembly(assembly); + Assert.That( + _container.GetServicesUnsafe.Any(static descriptor => + descriptor.ServiceType == typeof(INotificationHandler)), + Is.True); + + _container.Clear(); + Assert.That( + _container.GetServicesUnsafe.Any(static descriptor => + descriptor.ServiceType == typeof(INotificationHandler)), + Is.False); + + _container.RegisterCqrsHandlersFromAssembly(assembly); + + Assert.That( + _container.GetServicesUnsafe.Any(static descriptor => + descriptor.ServiceType == typeof(INotificationHandler)), + Is.True); + } + /// /// 测试冻结容器以防止进一步注册的功能 /// @@ -676,4 +706,4 @@ public sealed class PrioritizedService : IPrioritizedService, IMixedService public sealed class NonPrioritizedService : IMixedService { public string? Name { get; set; } -} \ No newline at end of file +} diff --git a/GFramework.Core/Architectures/Architecture.cs b/GFramework.Core/Architectures/Architecture.cs index 0fb361a4..8d2582aa 100644 --- a/GFramework.Core/Architectures/Architecture.cs +++ b/GFramework.Core/Architectures/Architecture.cs @@ -175,6 +175,8 @@ public abstract class Architecture : IArchitecture /// 该入口适用于把拆分到其他模块或扩展包程序集中的 handlers 接入当前架构。 /// /// 包含 CQRS 处理器或生成注册器的程序集。 + /// + /// 当前架构的底层容器已冻结,无法继续注册处理器。 public void RegisterCqrsHandlersFromAssembly(Assembly assembly) { _modules.RegisterCqrsHandlersFromAssembly(assembly); @@ -185,6 +187,8 @@ public abstract class Architecture : IArchitecture /// 适用于在初始化阶段批量接入多个扩展程序集,并沿用容器的去重策略避免重复注册。 /// /// 要接入的程序集集合。 + /// + /// 当前架构的底层容器已冻结,无法继续注册处理器。 public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies) { _modules.RegisterCqrsHandlersFromAssemblies(assemblies); diff --git a/GFramework.Core/Architectures/ArchitectureModules.cs b/GFramework.Core/Architectures/ArchitectureModules.cs index 8e659fa4..f5d2a55d 100644 --- a/GFramework.Core/Architectures/ArchitectureModules.cs +++ b/GFramework.Core/Architectures/ArchitectureModules.cs @@ -44,8 +44,11 @@ internal sealed class ArchitectureModules( /// 该入口用于把默认架构程序集之外的扩展处理器接入当前架构容器。 /// /// 包含 CQRS 处理器或生成注册器的程序集。 + /// + /// 底层容器已冻结,无法继续注册处理器。 public void RegisterCqrsHandlersFromAssembly(Assembly assembly) { + ArgumentNullException.ThrowIfNull(assembly); logger.Debug($"Registering CQRS handlers from assembly: {assembly.FullName ?? assembly.GetName().Name}"); services.Container.RegisterCqrsHandlersFromAssembly(assembly); } @@ -55,8 +58,11 @@ internal sealed class ArchitectureModules( /// 它会复用容器级去重逻辑,避免模块重复接入相同程序集时重复注册 handler。 /// /// 要接入的程序集集合。 + /// + /// 底层容器已冻结,无法继续注册处理器。 public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies) { + ArgumentNullException.ThrowIfNull(assemblies); logger.Debug("Registering CQRS handlers from additional assemblies."); services.Container.RegisterCqrsHandlersFromAssemblies(assemblies); } diff --git a/GFramework.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs index b3dfcb03..712a41c1 100644 --- a/GFramework.Core/Ioc/MicrosoftDiContainer.cs +++ b/GFramework.Core/Ioc/MicrosoftDiContainer.cs @@ -384,6 +384,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) /// 从指定程序集显式注册 CQRS 处理器。 /// /// 包含 CQRS 处理器或生成注册器的程序集。 + /// + /// 容器已冻结,无法继续注册 CQRS 处理器。 public void RegisterCqrsHandlersFromAssembly(Assembly assembly) { ArgumentNullException.ThrowIfNull(assembly); @@ -395,6 +397,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) /// 同一程序集只会被接入一次,避免默认启动路径与扩展模块重复注册相同 handlers。 /// /// 要接入的程序集集合。 + /// + /// 容器已冻结,无法继续注册 CQRS 处理器。 public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies) { ArgumentNullException.ThrowIfNull(assemblies); @@ -803,6 +807,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) GetServicesUnsafe.Clear(); _registeredInstances.Clear(); + _registeredCqrsHandlerAssemblyKeys.Clear(); _provider = null; _logger.Info("Container cleared"); } From 0cd1e9e83a9c444027bd56196680694a02649d3b Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:47:22 +0800 Subject: [PATCH 3/7] =?UTF-8?q?feat(ci):=20=E6=B7=BB=E5=8A=A0CI/CD?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=B5=81=E5=92=8CCQRS=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 配置CI构建和测试工作流,支持多.NET版本和并发测试 - 添加CodeQL静态代码分析工作流 - 实现自动版本递增和标签创建工作流 - 定义CQRS命令接口规范,包括响应式和流式命令 - 为架构测试添加空值参数异常文档注释 --- .github/workflows/auto-tag.yml | 21 +++++++++++-------- .github/workflows/ci.yml | 6 +++--- .github/workflows/codeql.yml | 8 +++---- ...ArchitectureAdditionalCqrsHandlersTests.cs | 3 +++ 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 45b9b112..a8e761bd 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -5,6 +5,8 @@ on: workflows: ["CI - Build & Test"] types: - completed + branches: + - main workflow_dispatch: concurrency: group: auto-tag-main @@ -13,15 +15,16 @@ concurrency: jobs: auto-tag: if: > - github.ref == 'refs/heads/main' && ( - ( - github.event_name == 'workflow_run' && - github.event.workflow_run.conclusion == 'success' && - contains(github.event.workflow_run.head_commit.message, '[release ci]') - ) - || - github.event_name == 'workflow_dispatch' + github.event_name == 'workflow_run' && + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_branch == 'main' && + contains(github.event.workflow_run.head_commit.message, '[release ci]') + ) + || + ( + github.event_name == 'workflow_dispatch' && + github.ref == 'refs/heads/main' ) runs-on: ubuntu-latest @@ -61,4 +64,4 @@ jobs: fi git tag -a "$TAG" -m "Auto tag $TAG" - git push "https://x-access-token:${PAT}@github.com/${{ github.repository }}.git" "$TAG" \ No newline at end of file + git push "https://x-access-token:${PAT}@github.com/${{ github.repository }}.git" "$TAG" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2b53c25..14606c84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,12 @@ # CI/CD工作流配置:构建和测试.NET项目 -# 该工作流在push到main/master分支或创建pull request时触发 +# 该工作流在推送到任意分支或创建面向任意分支的 pull request 时触发 name: CI - Build & Test on: push: - branches: [ main, master ] + branches: [ '**' ] pull_request: - branches: [ main, master ] + branches: [ '**' ] permissions: contents: read diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1802de8f..1bbc0abd 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -4,14 +4,14 @@ name: "CodeQL" # 触发事件配置 # 在以下情况下触发工作流: -# 1. 推送到main分支时 -# 2. 针对main分支的拉取请求时 +# 1. 推送到任意分支时 +# 2. 针对任意分支的拉取请求时 # 3. 每天凌晨2点执行一次 on: push: - branches: [ "main" ] + branches: [ '**' ] pull_request: - branches: [ "main" ] + branches: [ '**' ] schedule: - cron: '0 2 * * *' diff --git a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs index 23725e7c..d757beaa 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs @@ -168,6 +168,9 @@ internal sealed class AdditionalAssemblyNotificationHandlerRegistry : ICqrsHandl /// /// 目标服务集合。 /// 日志记录器。 + /// + /// 当 时抛出。 + /// public void Register(IServiceCollection services, ILogger logger) { ArgumentNullException.ThrowIfNull(services); From 340b6cae90eb06cdc17233d56ef67fc2f321c915 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:51:59 +0800 Subject: [PATCH 4/7] =?UTF-8?q?chore(ci):=20=E6=9B=B4=E6=96=B0GitHub=20Act?= =?UTF-8?q?ions=E5=B7=A5=E4=BD=9C=E6=B5=81=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除push触发器,仅保留pull request触发CI构建测试 - 添加CodeQL静态代码分析工作流,支持安全漏洞检测 - 配置每日凌晨2点定时执行CodeQL分析 - 设置.NET 8.0.x运行时环境支持 - 启用C#语言自动构建模式进行代码扫描 --- .github/workflows/ci.yml | 4 +--- .github/workflows/codeql.yml | 7 ++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14606c84..1bb32d49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,8 @@ # CI/CD工作流配置:构建和测试.NET项目 -# 该工作流在推送到任意分支或创建面向任意分支的 pull request 时触发 +# 该工作流仅在创建或更新面向任意分支的 pull request 时触发 name: CI - Build & Test on: - push: - branches: [ '**' ] pull_request: branches: [ '**' ] diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1bbc0abd..0a068113 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -4,12 +4,9 @@ name: "CodeQL" # 触发事件配置 # 在以下情况下触发工作流: -# 1. 推送到任意分支时 -# 2. 针对任意分支的拉取请求时 -# 3. 每天凌晨2点执行一次 +# 1. 针对任意分支的拉取请求时 +# 2. 每天凌晨2点执行一次 on: - push: - branches: [ '**' ] pull_request: branches: [ '**' ] schedule: From 49df81e46f83d86dd7f3f9d68fbe7c4d440f99b2 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:59:12 +0800 Subject: [PATCH 5/7] =?UTF-8?q?refactor(tests):=20=E9=87=8D=E6=9E=84=20CQR?= =?UTF-8?q?S=20=E5=A4=84=E7=90=86=E7=A8=8B=E5=BA=8F=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除自定义测试架构类,改用现有的 SyncTestArchitecture - 将 RegisterCqrsHandlersFromAssembly 测试方法中的架构创建逻辑提取为统一方法 - 更新重复程序集注册去重测试,验证不同 Assembly 实例但相同程序集键的情况 - 简化测试架构初始化逻辑,使用 AddPostRegistrationHook 替代自定义配置 - 调整注释文档以反映新的测试架构创建方式 - 移除 GitHub 工作流中对 main 分支的限制条件 --- .github/workflows/auto-tag.yml | 1 - ...ArchitectureAdditionalCqrsHandlersTests.cs | 34 +++++++++---------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index a8e761bd..9588c304 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -18,7 +18,6 @@ jobs: ( github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.head_branch == 'main' && contains(github.event.workflow_run.head_commit.message, '[release ci]') ) || diff --git a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs index d757beaa..5cb6206c 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs @@ -12,6 +12,8 @@ namespace GFramework.Core.Tests.Architectures; [TestFixture] public sealed class ArchitectureAdditionalCqrsHandlersTests { + private ILoggerFactoryProvider? _previousLoggerFactoryProvider; + /// /// 初始化日志工厂和共享测试状态。 /// @@ -37,8 +39,6 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests "LoggerFactoryResolver.Provider should be captured during setup."); } - private ILoggerFactoryProvider? _previousLoggerFactoryProvider; - /// /// 验证显式声明的额外程序集会在初始化阶段接入当前架构容器。 /// @@ -46,7 +46,7 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests public async Task RegisterCqrsHandlersFromAssembly_Should_Register_Handlers_From_Explicit_Assembly() { var generatedAssembly = CreateGeneratedHandlerAssembly(); - var architecture = new AdditionalHandlersTestArchitecture(target => + var architecture = CreateArchitecture(target => target.RegisterCqrsHandlersFromAssembly(generatedAssembly.Object)); await architecture.InitializeAsync(); @@ -63,16 +63,17 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests } /// - /// 验证同一额外程序集被重复声明时,不会向容器重复写入相同 handler 映射。 + /// 验证不同 实例只要解析到相同程序集键,就不会向容器重复写入相同 handler 映射。 /// [Test] public async Task RegisterCqrsHandlersFromAssembly_Should_Deduplicate_Repeated_Assembly_Registration() { - var generatedAssembly = CreateGeneratedHandlerAssembly(); - var architecture = new AdditionalHandlersTestArchitecture(target => + var generatedAssemblyA = CreateGeneratedHandlerAssembly(); + var generatedAssemblyB = CreateGeneratedHandlerAssembly(); + var architecture = CreateArchitecture(target => { - target.RegisterCqrsHandlersFromAssembly(generatedAssembly.Object); - target.RegisterCqrsHandlersFromAssemblies([generatedAssembly.Object]); + target.RegisterCqrsHandlersFromAssembly(generatedAssemblyA.Object); + target.RegisterCqrsHandlersFromAssemblies([generatedAssemblyB.Object]); }); await architecture.InitializeAsync(); @@ -106,18 +107,15 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests } /// - /// 用于测试额外程序集注册入口的最小架构实现。 + /// 创建复用现有测试架构基建的测试架构,并在注册阶段后执行额外程序集接入逻辑。 /// - private sealed class AdditionalHandlersTestArchitecture(Action configure) : - Architecture + /// 初始化阶段执行的额外 CQRS 程序集接入逻辑。 + /// 带有注册后钩子的测试架构实例。 + private static SyncTestArchitecture CreateArchitecture(Action configure) { - /// - /// 在初始化阶段执行测试注入的额外 CQRS 程序集接入逻辑。 - /// - protected override void OnInitialize() - { - configure(this); - } + var architecture = new SyncTestArchitecture(); + architecture.AddPostRegistrationHook(configure); + return architecture; } } From 2329cba3a604f2d3340b3b53d6dc570359b2f972 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 05:07:59 +0000 Subject: [PATCH 6/7] fix: apply CodeRabbit auto-fixes Fixed 1 file(s) based on 1 unresolved review comment. Co-authored-by: CodeRabbit --- .../Architectures/ArchitectureAdditionalCqrsHandlersTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs index 5cb6206c..04ee0571 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs @@ -42,6 +42,7 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests /// /// 验证显式声明的额外程序集会在初始化阶段接入当前架构容器。 /// + /// The asynchronous test task. [Test] public async Task RegisterCqrsHandlersFromAssembly_Should_Register_Handlers_From_Explicit_Assembly() { @@ -65,6 +66,7 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests /// /// 验证不同 实例只要解析到相同程序集键,就不会向容器重复写入相同 handler 映射。 /// + /// The asynchronous test task. [Test] public async Task RegisterCqrsHandlersFromAssembly_Should_Deduplicate_Repeated_Assembly_Registration() { @@ -195,4 +197,4 @@ internal sealed class AdditionalAssemblyNotificationHandlerRegistry : ICqrsHandl }); return handler.Object; } -} +} \ No newline at end of file From a01ec8d29cfbfad2ecfe8ea73788abf410e29e34 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:17:08 +0800 Subject: [PATCH 7/7] =?UTF-8?q?fix(ci):=20=E4=BF=AE=E5=A4=8DPR=E6=89=AB?= =?UTF-8?q?=E6=8F=8F=E7=9A=84=E5=9F=BA=E7=BA=BF=E5=92=8C=E5=A4=B4=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将基础提交哈希从 github.event.before 更新为 github.event.pull_request.base.sha - 将当前提交哈希从 github.sha 更新为 github.event.pull_request.head.sha - 确保PR工作流正确比较基线和目标分支的差异 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bb32d49..c9bfcf89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,9 +67,9 @@ jobs: # 扫描路径,. 表示扫描整个仓库 path: . # 基础提交哈希,用于与当前提交进行比较 - base: ${{ github.event.before }} + base: ${{ github.event.pull_request.base.sha }} # 当前提交哈希,作为扫描的目标版本 - head: ${{ github.sha }} + head: ${{ github.event.pull_request.head.sha }} # 构建和测试 job(并行执行) build-and-test: