diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionManager.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionManager.cs index 2c33bc160..fceacf1cd 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionManager.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionManager.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using Microsoft.Extensions.Options; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; @@ -6,17 +7,21 @@ using Volo.Abp; using Volo.Abp.DependencyInjection; namespace LINGYUN.Abp.AI.Tools; + public class AIToolDefinitionManager : IAIToolDefinitionManager, ISingletonDependency { + protected readonly AbpAIToolsOptions AIToolOptions; protected readonly IStaticAIToolDefinitionStore StaticStore; protected readonly IDynamicAIToolDefinitionStore DynamicStore; public AIToolDefinitionManager( IStaticAIToolDefinitionStore staticStore, - IDynamicAIToolDefinitionStore dynamicStore) + IDynamicAIToolDefinitionStore dynamicStore, + IOptions aiToolOptions) { StaticStore = staticStore; DynamicStore = dynamicStore; + AIToolOptions = aiToolOptions.Value; } public virtual async Task GetAsync(string name) @@ -34,19 +39,165 @@ public class AIToolDefinitionManager : IAIToolDefinitionManager, ISingletonDepen { Check.NotNull(name, nameof(name)); - return await StaticStore.GetOrNullAsync(name) ?? await DynamicStore.GetOrNullAsync(name); + return await StaticStore.GetOrNullAsync(name) ?? + await DynamicStore.GetOrNullAsync(name); } public virtual async Task> GetAllAsync() { var staticAITools = await StaticStore.GetAllAsync(); + var dynamicAITools = await DynamicStore.GetAllAsync(); + + // 根据策略处理AI工具定义 + return AIToolOptions.DynamicAItoolStrategy switch + { + DynamicAItoolStrategy.Ignore => await GetAIToolsWithIgnoreStrategy(staticAITools, dynamicAITools), + DynamicAItoolStrategy.Covering => await GetAIToolsWithCoveringStrategy(staticAITools, dynamicAITools), + DynamicAItoolStrategy.Merge => await GetAIToolsWithMergeStrategy(staticAITools, dynamicAITools), + _ => await GetAIToolsWithMergeStrategy(staticAITools, dynamicAITools) // 默认使用合并策略 + }; + } + + #region AI工具定义策略 + + /// + /// 忽略策略:静态优先,过滤掉同名的动态AI工具 + /// + protected virtual Task> GetAIToolsWithIgnoreStrategy( + IReadOnlyList staticAITools, + IReadOnlyList dynamicAITools) + { var staticAIToolNames = staticAITools .Select(p => p.Name) .ToImmutableHashSet(); - var dynamicAITools = await DynamicStore.GetAllAsync(); + return Task.FromResult>( + staticAITools + .Concat(dynamicAITools.Where(d => !staticAIToolNames.Contains(d.Name))) + .ToImmutableList() + ); + } - return staticAITools.Concat(dynamicAITools.Where(d => !staticAIToolNames.Contains(d.Name))) + /// + /// 覆盖策略:动态完全覆盖静态AI工具 + /// + protected virtual Task> GetAIToolsWithCoveringStrategy( + IReadOnlyList staticAITools, + IReadOnlyList dynamicAITools) + { + var dynamicAIToolNames = dynamicAITools + .Select(p => p.Name) + .ToImmutableHashSet(); + + // 动态AI工具完全覆盖静态AI工具 + var result = dynamicAITools + .Concat(staticAITools.Where(s => !dynamicAIToolNames.Contains(s.Name))) .ToImmutableList(); + + return Task.FromResult>(result); } -} + + /// + /// 合并策略:合并静态和动态AI工具,创建新实例 + /// + protected virtual Task> GetAIToolsWithMergeStrategy( + IReadOnlyList staticAITools, + IReadOnlyList dynamicAITools) + { + var mergedAITools = new Dictionary(); + + // 先添加所有静态AI工具 + foreach (var staticAITool in staticAITools) + { + mergedAITools[staticAITool.Name] = staticAITool; + } + + // 合并动态AI工具 + foreach (var dynamicAITool in dynamicAITools) + { + if (mergedAITools.TryGetValue(dynamicAITool.Name, out var existingAITool)) + { + // AI工具已存在,创建新的合并AI工具 + var mergedAITool = MergeAITool(existingAITool, dynamicAITool); + mergedAITools[dynamicAITool.Name] = mergedAITool; + } + else + { + // 添加新的动态AI工具 + mergedAITools[dynamicAITool.Name] = dynamicAITool; + } + } + + // 处理被删除的AI工具 + foreach (var deletedToolName in AIToolOptions.DeletedAITools) + { + if (mergedAITools.ContainsKey(deletedToolName)) + { + mergedAITools.Remove(deletedToolName); + } + } + + return Task.FromResult>(mergedAITools.Values.ToImmutableList()); + } + + /// + /// 合并两个AI工具定义,返回新的 AIToolDefinition 实例 + /// + protected virtual AIToolDefinition MergeAITool( + AIToolDefinition staticAITool, + AIToolDefinition dynamicAITool) + { + // 决定使用哪个提供者(优先使用动态的) + var provider = !string.IsNullOrEmpty(dynamicAITool.Provider) + ? dynamicAITool.Provider + : staticAITool.Provider; + + // 决定使用哪个描述(优先使用动态的) + var description = dynamicAITool.Description ?? staticAITool.Description; + + // 创建新的AI工具实例(Name是只读的) + var mergedAITool = new AIToolDefinition( + staticAITool.Name, // 保持名称不变 + provider, + description + ); + + // 设置是否启用(只要有一方启用,结果就是启用) + mergedAITool.IsEnabled = staticAITool.IsEnabled || dynamicAITool.IsEnabled; + + // 设置是否为全局工具(只要有一方是全局,结果就是全局) + mergedAITool.IsGlobal = staticAITool.IsGlobal || dynamicAITool.IsGlobal; + + // 合并状态检查器 + foreach (var checker in staticAITool.StateCheckers) + { + if (!mergedAITool.StateCheckers.Contains(checker)) + { + mergedAITool.StateCheckers.Add(checker); + } + } + + foreach (var checker in dynamicAITool.StateCheckers) + { + if (!mergedAITool.StateCheckers.Contains(checker)) + { + mergedAITool.StateCheckers.Add(checker); + } + } + + // 合并属性(动态覆盖静态) + foreach (var property in staticAITool.Properties) + { + mergedAITool.Properties[property.Key] = property.Value; + } + + foreach (var property in dynamicAITool.Properties) + { + mergedAITool.Properties[property.Key] = property.Value; + } + + return mergedAITool; + } + + #endregion +} \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs index fa552fa29..e1bd082c1 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs @@ -5,10 +5,12 @@ namespace LINGYUN.Abp.AI.Tools; public class AbpAIToolsOptions { public ITypeList DefinitionProviders { get; } + public DynamicAItoolStrategy DynamicAItoolStrategy { get; set; } public ITypeList AIToolProviders { get; } public HashSet DeletedAITools { get; } public AbpAIToolsOptions() { + DynamicAItoolStrategy = DynamicAItoolStrategy.Merge; DefinitionProviders = new TypeList(); AIToolProviders = new TypeList(); DeletedAITools = new HashSet(); diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/DynamicAItoolStrategy.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/DynamicAItoolStrategy.cs new file mode 100644 index 000000000..3435eb580 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/DynamicAItoolStrategy.cs @@ -0,0 +1,19 @@ +namespace LINGYUN.Abp.AI.Tools; +/// +/// 动态工具策略 +/// +public enum DynamicAItoolStrategy : byte +{ + /// + /// 忽略 + /// + Ignore = 0, + /// + /// 覆盖 + /// + Covering =1, + /// + /// 合并 + /// + Merge = 2 +} diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/AbpNotificationsOptions.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/AbpNotificationsOptions.cs index a88701015..d158f25c5 100644 --- a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/AbpNotificationsOptions.cs +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/AbpNotificationsOptions.cs @@ -10,12 +10,15 @@ public class AbpNotificationsOptions /// public ITypeList DefinitionProviders { get; } + public DynamicNotificationStrategy DynamicNotificationStrategy { get; set; } + public HashSet DeletedNotifications { get; } public HashSet DeletedNotificationGroups { get; } public AbpNotificationsOptions() { + DynamicNotificationStrategy = DynamicNotificationStrategy.Merge; DefinitionProviders = new TypeList(); DeletedNotifications = new HashSet(); diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/DynamicNotificationStrategy.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/DynamicNotificationStrategy.cs new file mode 100644 index 000000000..f473fed87 --- /dev/null +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/DynamicNotificationStrategy.cs @@ -0,0 +1,19 @@ +namespace LINGYUN.Abp.Notifications; +/// +/// 动态通知策略 +/// +public enum DynamicNotificationStrategy : byte +{ + /// + /// 忽略 + /// + Ignore = 0, + /// + /// 覆盖 + /// + Covering =1, + /// + /// 合并 + /// + Merge = 2 +} diff --git a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinitionManager.cs b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinitionManager.cs index 2eb85fbd6..25b7f91c0 100644 --- a/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinitionManager.cs +++ b/aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinitionManager.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using Microsoft.Extensions.Options; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; @@ -9,15 +10,18 @@ namespace LINGYUN.Abp.Notifications; public class NotificationDefinitionManager : INotificationDefinitionManager, ITransientDependency { + private readonly AbpNotificationsOptions _notificationsOptions; private readonly IStaticNotificationDefinitionStore _staticStore; private readonly IDynamicNotificationDefinitionStore _dynamicStore; public NotificationDefinitionManager( IStaticNotificationDefinitionStore staticStore, - IDynamicNotificationDefinitionStore dynamicStore) + IDynamicNotificationDefinitionStore dynamicStore, + IOptions notificationsOptions) { _staticStore = staticStore; _dynamicStore = dynamicStore; + _notificationsOptions = notificationsOptions.Value; } public async virtual Task GetAsync(string name) @@ -42,15 +46,16 @@ public class NotificationDefinitionManager : INotificationDefinitionManager, ITr public async virtual Task> GetNotificationsAsync() { var staticNotifications = await _staticStore.GetNotificationsAsync(); - var staticNotificationNames = staticNotifications - .Select(p => p.Name) - .ToImmutableHashSet(); - var dynamicNotifications = await _dynamicStore.GetNotificationsAsync(); - return staticNotifications - .Concat(dynamicNotifications.Where(d => !staticNotificationNames.Contains(d.Name))) - .ToImmutableList(); + // 根据策略处理通知定义 + return _notificationsOptions.DynamicNotificationStrategy switch + { + DynamicNotificationStrategy.Ignore => await GetNotificationsWithIgnoreStrategy(staticNotifications, dynamicNotifications), + DynamicNotificationStrategy.Covering => await GetNotificationsWithCoveringStrategy(staticNotifications, dynamicNotifications), + DynamicNotificationStrategy.Merge => await GetNotificationsWithMergeStrategy(staticNotifications, dynamicNotifications), + _ => await GetNotificationsWithIgnoreStrategy(staticNotifications, dynamicNotifications) + }; } public async virtual Task GetGroupOrNullAsync(string name) @@ -64,14 +69,334 @@ public class NotificationDefinitionManager : INotificationDefinitionManager, ITr public async virtual Task> GetGroupsAsync() { var staticGroups = await _staticStore.GetGroupsAsync(); + var dynamicGroups = await _dynamicStore.GetGroupsAsync(); + + // 根据策略处理分组定义 + return _notificationsOptions.DynamicNotificationStrategy switch + { + DynamicNotificationStrategy.Ignore => await GetGroupsWithIgnoreStrategy(staticGroups, dynamicGroups), + DynamicNotificationStrategy.Covering => await GetGroupsWithCoveringStrategy(staticGroups, dynamicGroups), + DynamicNotificationStrategy.Merge => await GetGroupsWithMergeStrategy(staticGroups, dynamicGroups), + _ => await GetGroupsWithIgnoreStrategy(staticGroups, dynamicGroups) + }; + } + + #region 通知定义策略 + + /// + /// 忽略策略:静态优先,过滤掉同名的动态通知 + /// + protected virtual Task> GetNotificationsWithIgnoreStrategy( + IReadOnlyList staticNotifications, + IReadOnlyList dynamicNotifications) + { + var staticNotificationNames = staticNotifications + .Select(p => p.Name) + .ToImmutableHashSet(); + + return Task.FromResult>( + staticNotifications + .Concat(dynamicNotifications.Where(d => !staticNotificationNames.Contains(d.Name))) + .ToImmutableList() + ); + } + + /// + /// 覆盖策略:动态完全覆盖静态通知 + /// + protected virtual Task> GetNotificationsWithCoveringStrategy( + IReadOnlyList staticNotifications, + IReadOnlyList dynamicNotifications) + { + var dynamicNotificationNames = dynamicNotifications + .Select(p => p.Name) + .ToImmutableHashSet(); + + // 动态通知完全覆盖静态通知 + var result = dynamicNotifications + .Concat(staticNotifications.Where(s => !dynamicNotificationNames.Contains(s.Name))) + .ToImmutableList(); + + return Task.FromResult>(result); + } + + /// + /// 合并策略:合并静态和动态通知,创建新实例 + /// + protected virtual Task> GetNotificationsWithMergeStrategy( + IReadOnlyList staticNotifications, + IReadOnlyList dynamicNotifications) + { + var mergedNotifications = new Dictionary(); + + // 先添加所有静态通知 + foreach (var staticNotification in staticNotifications) + { + mergedNotifications[staticNotification.Name] = staticNotification; + } + + // 合并动态通知 + foreach (var dynamicNotification in dynamicNotifications) + { + if (mergedNotifications.TryGetValue(dynamicNotification.Name, out var existingNotification)) + { + // 通知已存在,创建新的合并通知 + var mergedNotification = MergeNotification(existingNotification, dynamicNotification); + mergedNotifications[dynamicNotification.Name] = mergedNotification; + } + else + { + // 添加新的动态通知 + mergedNotifications[dynamicNotification.Name] = dynamicNotification; + } + } + + return Task.FromResult>(mergedNotifications.Values.ToImmutableList()); + } + + /// + /// 合并两个通知定义,返回新的 NotificationDefinition 实例 + /// + protected virtual NotificationDefinition MergeNotification( + NotificationDefinition staticNotification, + NotificationDefinition dynamicNotification) + { + // 决定使用哪个显示名称(优先使用动态的) + var displayName = dynamicNotification.DisplayName ?? staticNotification.DisplayName; + + // 决定使用哪个描述(优先使用动态的) + var description = dynamicNotification.Description ?? staticNotification.Description; + + // 决定通知类型(优先使用动态的) + var notificationType = dynamicNotification.NotificationType != NotificationType.Application + ? dynamicNotification.NotificationType + : staticNotification.NotificationType; + + // 决定存活类型(优先使用动态的) + var lifetime = dynamicNotification.NotificationLifetime != NotificationLifetime.Persistent + ? dynamicNotification.NotificationLifetime + : staticNotification.NotificationLifetime; + + // 决定内容类型(优先使用动态的) + var contentType = dynamicNotification.ContentType != NotificationContentType.Text + ? dynamicNotification.ContentType + : staticNotification.ContentType; + + // 决定是否允许客户端订阅(优先使用动态的) + var allowSubscriptionToClients = dynamicNotification.AllowSubscriptionToClients || staticNotification.AllowSubscriptionToClients; + + // 创建新的通知实例 + var mergedNotification = new NotificationDefinition( + staticNotification.Name, // 保持名称不变 + displayName, + description, + notificationType, + lifetime, + contentType, + allowSubscriptionToClients + ); + + // 复制静态通知的属性 + foreach (var property in staticNotification.Properties) + { + mergedNotification.Properties[property.Key] = property.Value; + } + + // 复制动态通知的属性(覆盖同名的静态属性) + foreach (var property in dynamicNotification.Properties) + { + mergedNotification.Properties[property.Key] = property.Value; + } + + // 合并提供者 + foreach (var provider in staticNotification.Providers) + { + if (!mergedNotification.Providers.Contains(provider)) + { + mergedNotification.Providers.Add(provider); + } + } + + foreach (var provider in dynamicNotification.Providers) + { + if (!mergedNotification.Providers.Contains(provider)) + { + mergedNotification.Providers.Add(provider); + } + } + + // 合并模板(优先使用动态的) + if (dynamicNotification.Template != null) + { + mergedNotification.WithTemplate(dynamicNotification.Template); + } + else if (staticNotification.Template != null) + { + mergedNotification.WithTemplate(staticNotification.Template); + } + + return mergedNotification; + } + + #endregion + + #region 分组定义策略 + + /// + /// 忽略策略:静态优先,过滤掉同名的动态分组 + /// + protected virtual Task> GetGroupsWithIgnoreStrategy( + IReadOnlyList staticGroups, + IReadOnlyList dynamicGroups) + { var staticGroupNames = staticGroups .Select(p => p.Name) .ToImmutableHashSet(); - var dynamicGroups = await _dynamicStore.GetGroupsAsync(); + return Task.FromResult>( + staticGroups + .Concat(dynamicGroups.Where(d => !staticGroupNames.Contains(d.Name))) + .ToImmutableList() + ); + } - return staticGroups - .Concat(dynamicGroups.Where(d => !staticGroupNames.Contains(d.Name))) + /// + /// 覆盖策略:动态完全覆盖静态分组 + /// + protected virtual Task> GetGroupsWithCoveringStrategy( + IReadOnlyList staticGroups, + IReadOnlyList dynamicGroups) + { + var dynamicGroupNames = dynamicGroups + .Select(p => p.Name) + .ToImmutableHashSet(); + + var result = dynamicGroups + .Concat(staticGroups.Where(s => !dynamicGroupNames.Contains(s.Name))) .ToImmutableList(); + + return Task.FromResult>(result); } -} + + /// + /// 合并策略:合并静态和动态分组 + /// + protected virtual Task> GetGroupsWithMergeStrategy( + IReadOnlyList staticGroups, + IReadOnlyList dynamicGroups) + { + var mergedGroups = new Dictionary(); + + // 先添加所有静态分组 + foreach (var staticGroup in staticGroups) + { + mergedGroups[staticGroup.Name] = staticGroup; + } + + // 合并动态分组 + foreach (var dynamicGroup in dynamicGroups) + { + if (mergedGroups.TryGetValue(dynamicGroup.Name, out var existingGroup)) + { + // 分组已存在,合并通知 + MergeGroupNotifications(existingGroup, dynamicGroup); + } + else + { + // 添加新的动态分组 + mergedGroups[dynamicGroup.Name] = dynamicGroup; + } + } + + return Task.FromResult>( + mergedGroups.Values.ToImmutableList() + ); + } + + /// + /// 合并分组的通知列表 + /// + private static void MergeGroupNotifications(NotificationGroupDefinition target, NotificationGroupDefinition source) + { + foreach (var sourceNotification in source.Notifications) + { + var existingNotification = target.GetNotificationOrNull(sourceNotification.Name); + + if (existingNotification == null) + { + // 通知不存在,直接添加 + var newNotification = target.AddNotification( + sourceNotification.Name, + sourceNotification.DisplayName, + sourceNotification.Description, + sourceNotification.NotificationType, + sourceNotification.NotificationLifetime, + sourceNotification.ContentType, + sourceNotification.AllowSubscriptionToClients + ); + + // 复制提供者 + foreach (var provider in sourceNotification.Providers) + { + if (!newNotification.Providers.Contains(provider)) + { + newNotification.Providers.Add(provider); + } + } + + // 复制属性 + foreach (var property in sourceNotification.Properties) + { + newNotification.Properties[property.Key] = property.Value; + } + + // 复制模板 + if (sourceNotification.Template != null) + { + newNotification.WithTemplate(sourceNotification.Template); + } + } + else + { + // 通知已存在,合并属性 + foreach (var property in sourceNotification.Properties) + { + existingNotification.Properties[property.Key] = property.Value; + } + + // 合并提供者 + foreach (var provider in sourceNotification.Providers) + { + if (!existingNotification.Providers.Contains(provider)) + { + existingNotification.Providers.Add(provider); + } + } + + // 更新显示名称(如果源提供了) + if (sourceNotification.DisplayName != null) + { + existingNotification.DisplayName = sourceNotification.DisplayName; + } + + // 更新描述(如果源提供了) + if (sourceNotification.Description != null) + { + existingNotification.Description = sourceNotification.Description; + } + + // 更新模板(优先使用动态的) + if (sourceNotification.Template != null) + { + existingNotification.WithTemplate(sourceNotification.Template); + } + + // 更新允许客户端订阅 + existingNotification.AllowSubscriptionToClients = + existingNotification.AllowSubscriptionToClients || sourceNotification.AllowSubscriptionToClients; + } + } + } + + #endregion +} \ No newline at end of file diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs index 421e24331..15435e52c 100644 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs @@ -12,6 +12,10 @@ public class AbpWebhooksOptions /// public TimeSpan TimeoutDuration { get; set; } /// + /// 动态Webhook策略 + /// + public DynamicWebhookStrategy DynamicWebhookStrategy { get; set; } + /// /// 默认最大发送次数 /// public int MaxSendAttemptCount { get; set; } @@ -39,6 +43,7 @@ public class AbpWebhooksOptions public string DefaultAgentIdentifier { get; set; } public AbpWebhooksOptions() { + DynamicWebhookStrategy = DynamicWebhookStrategy.Merge; TimeoutDuration = TimeSpan.FromSeconds(60); MaxSendAttemptCount = 5; MaxConsecutiveFailCountBeforeDeactivateSubscription = MaxSendAttemptCount * 3; diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/DynamicWebhookStrategy.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/DynamicWebhookStrategy.cs new file mode 100644 index 000000000..4e505be32 --- /dev/null +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/DynamicWebhookStrategy.cs @@ -0,0 +1,19 @@ +namespace LINGYUN.Abp.Webhooks; +/// +/// 动态Webhook策略 +/// +public enum DynamicWebhookStrategy : byte +{ + /// + /// 忽略 + /// + Ignore = 0, + /// + /// 覆盖 + /// + Covering =1, + /// + /// 合并 + /// + Merge = 2 +} diff --git a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs index 36b5593e6..79468bef8 100644 --- a/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs +++ b/aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -13,6 +14,7 @@ namespace LINGYUN.Abp.Webhooks; internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonDependency { + private readonly AbpWebhooksOptions _webhooksOptions; private readonly IServiceProvider _serviceProvider; private readonly IStaticWebhookDefinitionStore _staticStore; private readonly IDynamicWebhookDefinitionStore _dynamicStore; @@ -20,11 +22,13 @@ internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonD public WebhookDefinitionManager( IServiceProvider serviceProvider, IStaticWebhookDefinitionStore staticStore, - IDynamicWebhookDefinitionStore dynamicStore) + IDynamicWebhookDefinitionStore dynamicStore, + IOptions webhooksOptions) { _serviceProvider = serviceProvider; _staticStore = staticStore; _dynamicStore = dynamicStore; + _webhooksOptions = webhooksOptions.Value; } public async virtual Task GetOrNullAsync(string name) @@ -49,15 +53,16 @@ internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonD public async virtual Task> GetWebhooksAsync() { var staticWebhooks = await _staticStore.GetWebhooksAsync(); - var staticWebhookNames = staticWebhooks - .Select(p => p.Name) - .ToImmutableHashSet(); - var dynamicWebhooks = await _dynamicStore.GetWebhooksAsync(); - return staticWebhooks - .Concat(dynamicWebhooks.Where(d => !staticWebhookNames.Contains(d.Name))) - .ToImmutableList(); + // 根据策略处理Webhook定义 + return _webhooksOptions.DynamicWebhookStrategy switch + { + DynamicWebhookStrategy.Ignore => await GetWebhooksWithIgnoreStrategy(staticWebhooks, dynamicWebhooks), + DynamicWebhookStrategy.Covering => await GetWebhooksWithCoveringStrategy(staticWebhooks, dynamicWebhooks), + DynamicWebhookStrategy.Merge => await GetWebhooksWithMergeStrategy(staticWebhooks, dynamicWebhooks), + _ => await GetWebhooksWithIgnoreStrategy(staticWebhooks, dynamicWebhooks) + }; } public async virtual Task GetGroupOrNullAsync(string name) @@ -82,15 +87,16 @@ internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonD public async virtual Task> GetGroupsAsync() { var staticGroups = await _staticStore.GetGroupsAsync(); - var staticGroupNames = staticGroups - .Select(p => p.Name) - .ToImmutableHashSet(); - var dynamicGroups = await _dynamicStore.GetGroupsAsync(); - return staticGroups - .Concat(dynamicGroups.Where(d => !staticGroupNames.Contains(d.Name))) - .ToImmutableList(); + // 根据策略处理分组定义 + return _webhooksOptions.DynamicWebhookStrategy switch + { + DynamicWebhookStrategy.Ignore => await GetGroupsWithIgnoreStrategy(staticGroups, dynamicGroups), + DynamicWebhookStrategy.Covering => await GetGroupsWithCoveringStrategy(staticGroups, dynamicGroups), + DynamicWebhookStrategy.Merge => await GetGroupsWithMergeStrategy(staticGroups, dynamicGroups), + _ => await GetGroupsWithIgnoreStrategy(staticGroups, dynamicGroups) + }; } public async Task IsAvailableAsync(Guid? tenantId, string name) @@ -124,4 +130,286 @@ internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonD return true; } -} + + #region Webhook定义策略 + + /// + /// 忽略策略:静态优先,过滤掉同名的动态Webhook + /// + protected virtual Task> GetWebhooksWithIgnoreStrategy( + IReadOnlyList staticWebhooks, + IReadOnlyList dynamicWebhooks) + { + var staticWebhookNames = staticWebhooks + .Select(p => p.Name) + .ToImmutableHashSet(); + + return Task.FromResult>( + staticWebhooks + .Concat(dynamicWebhooks.Where(d => !staticWebhookNames.Contains(d.Name))) + .ToImmutableList() + ); + } + + /// + /// 覆盖策略:动态完全覆盖静态Webhook + /// + protected virtual Task> GetWebhooksWithCoveringStrategy( + IReadOnlyList staticWebhooks, + IReadOnlyList dynamicWebhooks) + { + var dynamicWebhookNames = dynamicWebhooks + .Select(p => p.Name) + .ToImmutableHashSet(); + + // 动态Webhook完全覆盖静态Webhook + var result = dynamicWebhooks + .Concat(staticWebhooks.Where(s => !dynamicWebhookNames.Contains(s.Name))) + .ToImmutableList(); + + return Task.FromResult>(result); + } + + /// + /// 合并策略:合并静态和动态Webhook,创建新实例 + /// + protected virtual Task> GetWebhooksWithMergeStrategy( + IReadOnlyList staticWebhooks, + IReadOnlyList dynamicWebhooks) + { + var mergedWebhooks = new Dictionary(); + + // 先添加所有静态Webhook + foreach (var staticWebhook in staticWebhooks) + { + mergedWebhooks[staticWebhook.Name] = staticWebhook; + } + + // 合并动态Webhook + foreach (var dynamicWebhook in dynamicWebhooks) + { + if (mergedWebhooks.TryGetValue(dynamicWebhook.Name, out var existingWebhook)) + { + // Webhook已存在,创建新的合并Webhook + var mergedWebhook = MergeWebhook(existingWebhook, dynamicWebhook); + mergedWebhooks[dynamicWebhook.Name] = mergedWebhook; + } + else + { + // 添加新的动态Webhook + mergedWebhooks[dynamicWebhook.Name] = dynamicWebhook; + } + } + + return Task.FromResult>(mergedWebhooks.Values.ToImmutableList()); + } + + /// + /// 合并两个Webhook定义,返回新的 WebhookDefinition 实例 + /// + protected virtual WebhookDefinition MergeWebhook( + WebhookDefinition staticWebhook, + WebhookDefinition dynamicWebhook) + { + // 决定使用哪个显示名称(优先使用动态的) + var displayName = dynamicWebhook.DisplayName ?? staticWebhook.DisplayName; + + // 决定使用哪个描述(优先使用动态的) + var description = dynamicWebhook.Description ?? staticWebhook.Description; + + // 创建新的Webhook实例(WebhookDefinition的Name是只读的) + var mergedWebhook = new WebhookDefinition( + staticWebhook.Name, // 保持名称不变 + displayName, + description + ); + + // 设置分组名称(优先使用动态的) + if (!string.IsNullOrEmpty(dynamicWebhook.GroupName)) + { + mergedWebhook.GroupName = dynamicWebhook.GroupName; + } + else if (!string.IsNullOrEmpty(staticWebhook.GroupName)) + { + mergedWebhook.GroupName = staticWebhook.GroupName; + } + + // 合并必需的功能特性 + foreach (var feature in staticWebhook.RequiredFeatures) + { + if (!mergedWebhook.RequiredFeatures.Contains(feature)) + { + mergedWebhook.RequiredFeatures.Add(feature); + } + } + + foreach (var feature in dynamicWebhook.RequiredFeatures) + { + if (!mergedWebhook.RequiredFeatures.Contains(feature)) + { + mergedWebhook.RequiredFeatures.Add(feature); + } + } + + // 合并属性(动态覆盖静态) + foreach (var property in staticWebhook.Properties) + { + mergedWebhook.Properties[property.Key] = property.Value; + } + + foreach (var property in dynamicWebhook.Properties) + { + mergedWebhook.Properties[property.Key] = property.Value; + } + + return mergedWebhook; + } + + #endregion + + #region 分组定义策略 + + /// + /// 忽略策略:静态优先,过滤掉同名的动态分组 + /// + protected virtual Task> GetGroupsWithIgnoreStrategy( + IReadOnlyList staticGroups, + IReadOnlyList dynamicGroups) + { + var staticGroupNames = staticGroups + .Select(p => p.Name) + .ToImmutableHashSet(); + + return Task.FromResult>( + staticGroups + .Concat(dynamicGroups.Where(d => !staticGroupNames.Contains(d.Name))) + .ToImmutableList() + ); + } + + /// + /// 覆盖策略:动态完全覆盖静态分组 + /// + protected virtual Task> GetGroupsWithCoveringStrategy( + IReadOnlyList staticGroups, + IReadOnlyList dynamicGroups) + { + var dynamicGroupNames = dynamicGroups + .Select(p => p.Name) + .ToImmutableHashSet(); + + var result = dynamicGroups + .Concat(staticGroups.Where(s => !dynamicGroupNames.Contains(s.Name))) + .ToImmutableList(); + + return Task.FromResult>(result); + } + + /// + /// 合并策略:合并静态和动态分组 + /// + protected virtual Task> GetGroupsWithMergeStrategy( + IReadOnlyList staticGroups, + IReadOnlyList dynamicGroups) + { + var mergedGroups = new Dictionary(); + + // 先添加所有静态分组 + foreach (var staticGroup in staticGroups) + { + mergedGroups[staticGroup.Name] = staticGroup; + } + + // 合并动态分组 + foreach (var dynamicGroup in dynamicGroups) + { + if (mergedGroups.TryGetValue(dynamicGroup.Name, out var existingGroup)) + { + // 分组已存在,合并Webhook + MergeGroupWebhooks(existingGroup, dynamicGroup); + } + else + { + // 添加新的动态分组 + mergedGroups[dynamicGroup.Name] = dynamicGroup; + } + } + + return Task.FromResult>( + mergedGroups.Values.ToImmutableList() + ); + } + + /// + /// 合并分组的Webhook列表 + /// + private void MergeGroupWebhooks(WebhookGroupDefinition target, WebhookGroupDefinition source) + { + foreach (var sourceWebhook in source.Webhooks) + { + var existingWebhook = target.GetWebhookOrNull(sourceWebhook.Name); + + if (existingWebhook == null) + { + // Webhook不存在,直接添加 + var newWebhook = target.AddWebhook( + sourceWebhook.Name, + sourceWebhook.DisplayName, + sourceWebhook.Description + ); + + // 设置分组名称 + newWebhook.GroupName = target.Name; + + // 复制必需的功能特性 + foreach (var feature in sourceWebhook.RequiredFeatures) + { + if (!newWebhook.RequiredFeatures.Contains(feature)) + { + newWebhook.RequiredFeatures.Add(feature); + } + } + + // 复制属性 + foreach (var property in sourceWebhook.Properties) + { + newWebhook.Properties[property.Key] = property.Value; + } + } + else + { + // Webhook已存在,合并属性 + foreach (var property in sourceWebhook.Properties) + { + existingWebhook.Properties[property.Key] = property.Value; + } + + // 合并必需的功能特性 + foreach (var feature in sourceWebhook.RequiredFeatures) + { + if (!existingWebhook.RequiredFeatures.Contains(feature)) + { + existingWebhook.RequiredFeatures.Add(feature); + } + } + + // 更新显示名称(如果源提供了) + if (sourceWebhook.DisplayName != null) + { + existingWebhook.DisplayName = sourceWebhook.DisplayName; + } + + // 更新描述(如果源提供了) + if (sourceWebhook.Description != null) + { + existingWebhook.Description = sourceWebhook.Description; + } + + // 更新分组名称(确保保持一致) + existingWebhook.GroupName = target.Name; + } + } + } + + #endregion +} \ No newline at end of file