Browse Source

feat: Rewrite part of the interface using DynamicDefinitionManager

pull/1565/head
colin 2 weeks ago
parent
commit
6580a39578
  1. 4
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN.Abp.AI.Core.csproj
  2. 9
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreModule.cs
  3. 2
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreOptions.cs
  4. 21
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/DynamicWorkspaceStrategy.cs
  5. 3
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs
  6. 252
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinitionManager.cs
  7. 197
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionManager.cs
  8. 7
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsModule.cs
  9. 2
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs
  10. 19
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/DynamicAItoolStrategy.cs
  11. 1
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN.Abp.Notifications.Core.csproj
  12. 10
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/AbpNotificationsCoreModule.cs
  13. 3
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/AbpNotificationsOptions.cs
  14. 19
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/DynamicNotificationStrategy.cs
  15. 346
      aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinitionManager.cs
  16. 4
      aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN.Abp.Webhooks.Core.csproj
  17. 16
      aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/AbpWebhooksCoreModule.cs
  18. 5
      aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs
  19. 19
      aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/DynamicWebhookStrategy.cs
  20. 360
      aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs

4
aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN.Abp.AI.Core.csproj

@ -26,4 +26,8 @@
<PackageReference Include="Volo.Abp.Timing" /> <PackageReference Include="Volo.Abp.Timing" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\dynamic-definition\LINGYUN.Abp.Dynamic.Definitions.Core\LINGYUN.Abp.Dynamic.Definitions.Core.csproj" />
</ItemGroup>
</Project> </Project>

9
aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreModule.cs

@ -1,6 +1,7 @@
using LINGYUN.Abp.AI.Internal; using LINGYUN.Abp.AI.Internal;
using LINGYUN.Abp.AI.Localization; using LINGYUN.Abp.AI.Localization;
using LINGYUN.Abp.AI.Workspaces; using LINGYUN.Abp.AI.Workspaces;
using LINGYUN.Abp.Dynamic.Definitions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -18,7 +19,8 @@ namespace LINGYUN.Abp.AI;
typeof(AbpAIModule), typeof(AbpAIModule),
typeof(AbpGuidsModule), typeof(AbpGuidsModule),
typeof(AbpTimingModule), typeof(AbpTimingModule),
typeof(AbpLocalizationModule))] typeof(AbpLocalizationModule),
typeof(AbpDynamicDefinitionsCoreModule))]
public class AbpAICoreModule : AbpModule public class AbpAICoreModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
@ -53,6 +55,11 @@ public class AbpAICoreModule : AbpModule
{ {
options.MapCodeNamespace(AbpAIErrorCodes.Namespace, typeof(AbpAIResource)); options.MapCodeNamespace(AbpAIErrorCodes.Namespace, typeof(AbpAIResource));
}); });
Configure<AbpDynamicDefinitionsOptions>(options =>
{
options.MapStrategy<WorkspaceDefinition>(DynamicDefinitionStrategy.Merge);
});
} }
private static void AutoAddDefinitionProviders(IServiceCollection services) private static void AutoAddDefinitionProviders(IServiceCollection services)

2
aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreOptions.cs

@ -9,7 +9,6 @@ using Volo.Abp.Collections;
namespace LINGYUN.Abp.AI; namespace LINGYUN.Abp.AI;
public class AbpAICoreOptions public class AbpAICoreOptions
{ {
public DynamicWorkspaceStrategy DynamicWorkspaceStrategy { get; set; }
public ITypeList<IWorkspaceDefinitionProvider> DefinitionProviders { get; } public ITypeList<IWorkspaceDefinitionProvider> DefinitionProviders { get; }
public ITypeList<IChatClientProvider> ChatClientProviders { get; } public ITypeList<IChatClientProvider> ChatClientProviders { get; }
public ITypeList<IKernelProvider> KernelProviders { get; } public ITypeList<IKernelProvider> KernelProviders { get; }
@ -21,7 +20,6 @@ public class AbpAICoreOptions
public AbpAICoreOptions() public AbpAICoreOptions()
{ {
DynamicWorkspaceStrategy = DynamicWorkspaceStrategy.Merge;
DefinitionProviders = new TypeList<IWorkspaceDefinitionProvider>(); DefinitionProviders = new TypeList<IWorkspaceDefinitionProvider>();
ChatClientProviders = new TypeList<IChatClientProvider>(); ChatClientProviders = new TypeList<IChatClientProvider>();
KernelProviders = new TypeList<IKernelProvider>(); KernelProviders = new TypeList<IKernelProvider>();

21
aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/DynamicWorkspaceStrategy.cs

@ -1,21 +0,0 @@
namespace LINGYUN.Abp.AI.Workspaces;
/// <summary>
/// 动态工作区策略
/// </summary>
public enum DynamicWorkspaceStrategy
{
/// <summary>
/// 忽略动态工作区(静态优先)
/// </summary>
Ignore,
/// <summary>
/// 动态覆盖静态
/// </summary>
Covering,
/// <summary>
/// 合并策略
/// </summary>
Merge
}

3
aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs

@ -147,8 +147,7 @@ public class WorkspaceDefinition : IHasSimpleStateCheckers<WorkspaceDefinition>
Name = name; Name = name;
Provider = provider; Provider = provider;
ModelName = modelName; ModelName = modelName;
_displayName = displayName; DisplayName = displayName;
_displayName = displayName;
Description = description; Description = description;
SystemPrompt = systemPrompt; SystemPrompt = systemPrompt;
Instructions = instructions; Instructions = instructions;

252
aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinitionManager.cs

@ -1,231 +1,117 @@
using Microsoft.Extensions.Options; using JetBrains.Annotations;
using LINGYUN.Abp.Dynamic.Definitions;
using Microsoft.Extensions.Options;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.AI.Workspaces; namespace LINGYUN.Abp.AI.Workspaces;
public class WorkspaceDefinitionManager : IWorkspaceDefinitionManager, ISingletonDependency
public class WorkspaceDefinitionManager :
DynamicDefinitionManager<WorkspaceDefinition>,
IWorkspaceDefinitionManager,
ITransientDependency
{ {
protected readonly AbpAICoreOptions Options; protected IStaticWorkspaceDefinitionStore StaticStore { get; }
protected readonly IStaticWorkspaceDefinitionStore StaticStore; protected IDynamicWorkspaceDefinitionStore DynamicStore { get; }
protected readonly IDynamicWorkspaceDefinitionStore DynamicStore; protected AbpAICoreOptions AIOptions { get; }
public WorkspaceDefinitionManager( public WorkspaceDefinitionManager(
IStaticWorkspaceDefinitionStore staticStore, IStaticWorkspaceDefinitionStore staticStore,
IDynamicWorkspaceDefinitionStore dynamicStore, IDynamicWorkspaceDefinitionStore dynamicStore,
IOptions<AbpAICoreOptions> options) IOptions<AbpDynamicDefinitionsOptions> options,
IOptions<AbpAICoreOptions> aIOptions)
: base(options)
{ {
StaticStore = staticStore; StaticStore = staticStore;
DynamicStore = dynamicStore; DynamicStore = dynamicStore;
Options = options.Value; AIOptions = aIOptions.Value;
} }
public virtual async Task<WorkspaceDefinition> GetAsync(string name) public async virtual Task<IReadOnlyList<WorkspaceDefinition>> GetAllAsync()
{ {
var workspace = await GetOrNullAsync(name); var staticDefinitions = await StaticStore.GetAllAsync();
if (workspace == null) var dynamicDefinitions = await DynamicStore.GetAllAsync();
{
throw new AbpException("Undefined Workspace: " + name);
}
return workspace; return await GetDefinitionsAsync(staticDefinitions, dynamicDefinitions);
} }
public virtual async Task<WorkspaceDefinition?> GetOrNullAsync(string name) public async virtual Task<WorkspaceDefinition> GetAsync([NotNull] string name)
{ {
Check.NotNull(name, nameof(name)); return await GetOrNullAsync(name) ?? throw new AbpException("Undefined Workspace: " + name);
var staticDefinition = await StaticStore.GetOrNullAsync(name);
var dynamicDefinition = await DynamicStore.GetOrNullAsync(name);
if (staticDefinition != null && dynamicDefinition != null)
{
return Options.DynamicWorkspaceStrategy switch
{
DynamicWorkspaceStrategy.Ignore => staticDefinition,
DynamicWorkspaceStrategy.Covering => dynamicDefinition,
DynamicWorkspaceStrategy.Merge => MergeWorkspace(staticDefinition, dynamicDefinition),
_ => MergeWorkspace(staticDefinition, dynamicDefinition)
};
}
return staticDefinition ?? dynamicDefinition;
} }
public virtual async Task<IReadOnlyList<WorkspaceDefinition>> GetAllAsync() public async virtual Task<WorkspaceDefinition?> GetOrNullAsync([NotNull] string name)
{ {
var staticWorkspaces = await StaticStore.GetAllAsync(); Check.NotNull(name, nameof(name));
var dynamicWorkspaces = await DynamicStore.GetAllAsync();
// 根据策略处理工作区定义
return Options.DynamicWorkspaceStrategy switch
{
DynamicWorkspaceStrategy.Ignore => await GetWorkspacesWithIgnoreStrategy(staticWorkspaces, dynamicWorkspaces),
DynamicWorkspaceStrategy.Covering => await GetWorkspacesWithCoveringStrategy(staticWorkspaces, dynamicWorkspaces),
DynamicWorkspaceStrategy.Merge => await GetWorkspacesWithMergeStrategy(staticWorkspaces, dynamicWorkspaces),
_ => await GetWorkspacesWithMergeStrategy(staticWorkspaces, dynamicWorkspaces) // 默认使用合并策略
};
}
#region 工作区定义策略
/// <summary> var staticDefinition = await StaticStore.GetOrNullAsync(name);
/// 忽略策略:静态优先,过滤掉同名的动态工作区 var dynamicDefinition = await DynamicStore.GetOrNullAsync(name);
/// </summary>
protected virtual Task<IReadOnlyList<WorkspaceDefinition>> GetWorkspacesWithIgnoreStrategy(
IReadOnlyList<WorkspaceDefinition> staticWorkspaces,
IReadOnlyList<WorkspaceDefinition> dynamicWorkspaces)
{
var staticWorkspaceNames = staticWorkspaces
.Select(p => p.Name)
.ToImmutableHashSet();
return Task.FromResult<IReadOnlyList<WorkspaceDefinition>>( return await GetDefinitionAsync(staticDefinition, dynamicDefinition);
staticWorkspaces
.Concat(dynamicWorkspaces.Where(d => !staticWorkspaceNames.Contains(d.Name)))
.ToImmutableList()
);
} }
/// <summary> protected override string GetDefinitionKey(WorkspaceDefinition definition)
/// 覆盖策略:动态完全覆盖静态工作区
/// </summary>
protected virtual Task<IReadOnlyList<WorkspaceDefinition>> GetWorkspacesWithCoveringStrategy(
IReadOnlyList<WorkspaceDefinition> staticWorkspaces,
IReadOnlyList<WorkspaceDefinition> dynamicWorkspaces)
{ {
var dynamicWorkspaceNames = dynamicWorkspaces return definition.Name;
.Select(p => p.Name)
.ToImmutableHashSet();
// 动态工作区完全覆盖静态工作区
var result = dynamicWorkspaces
.Concat(staticWorkspaces.Where(s => !dynamicWorkspaceNames.Contains(s.Name)))
.ToImmutableList();
return Task.FromResult<IReadOnlyList<WorkspaceDefinition>>(result);
} }
/// <summary> protected override Task AfterMergedDefinitionsAsync(Dictionary<string, WorkspaceDefinition> mergedDefinitions)
/// 合并策略:合并静态和动态工作区,创建新实例
/// </summary>
protected virtual Task<IReadOnlyList<WorkspaceDefinition>> GetWorkspacesWithMergeStrategy(
IReadOnlyList<WorkspaceDefinition> staticWorkspaces,
IReadOnlyList<WorkspaceDefinition> dynamicWorkspaces)
{ {
var mergedWorkspaces = new Dictionary<string, WorkspaceDefinition>(); foreach (var deletedWorkspaceName in AIOptions.DeletedWorkspaces)
// 先添加所有静态工作区
foreach (var staticWorkspace in staticWorkspaces)
{
mergedWorkspaces[staticWorkspace.Name] = staticWorkspace;
}
// 合并动态工作区
foreach (var dynamicWorkspace in dynamicWorkspaces)
{ {
if (mergedWorkspaces.TryGetValue(dynamicWorkspace.Name, out var existingWorkspace)) mergedDefinitions.Remove(deletedWorkspaceName);
{
// 工作区已存在,创建新的合并工作区
var mergedWorkspace = MergeWorkspace(existingWorkspace, dynamicWorkspace);
mergedWorkspaces[dynamicWorkspace.Name] = mergedWorkspace;
}
else
{
// 添加新的动态工作区
mergedWorkspaces[dynamicWorkspace.Name] = dynamicWorkspace;
}
} }
// 处理被删除的工作区 return Task.CompletedTask;
foreach (var deletedWorkspaceName in Options.DeletedWorkspaces)
{
if (mergedWorkspaces.ContainsKey(deletedWorkspaceName))
{
mergedWorkspaces.Remove(deletedWorkspaceName);
}
}
return Task.FromResult<IReadOnlyList<WorkspaceDefinition>>(mergedWorkspaces.Values.ToImmutableList());
} }
/// <summary> protected override Task<WorkspaceDefinition> MergeDefinitionAsync(WorkspaceDefinition targetDefinition, WorkspaceDefinition sourceDefinition)
/// 合并两个工作区定义,返回新的 WorkspaceDefinition 实例
/// </summary>
protected virtual WorkspaceDefinition MergeWorkspace(
WorkspaceDefinition staticWorkspace,
WorkspaceDefinition dynamicWorkspace)
{ {
// 决定使用哪个提供者(优先使用动态的) var provider = !string.IsNullOrEmpty(sourceDefinition.Provider)
var provider = !string.IsNullOrEmpty(dynamicWorkspace.Provider) ? sourceDefinition.Provider
? dynamicWorkspace.Provider : targetDefinition.Provider;
: staticWorkspace.Provider; var modelName = !string.IsNullOrEmpty(sourceDefinition.ModelName)
? sourceDefinition.ModelName
// 决定使用哪个模型名称(优先使用动态的) : targetDefinition.ModelName;
var modelName = !string.IsNullOrEmpty(dynamicWorkspace.ModelName) var displayName = sourceDefinition.DisplayName ?? targetDefinition.DisplayName;
? dynamicWorkspace.ModelName
: staticWorkspace.ModelName;
// 决定使用哪个显示名称(优先使用动态的)
var displayName = dynamicWorkspace.DisplayName ?? staticWorkspace.DisplayName;
// 创建新的工作区实例(Name是只读的)
var mergedWorkspace = new WorkspaceDefinition( var mergedWorkspace = new WorkspaceDefinition(
staticWorkspace.Name, // 保持名称不变 targetDefinition.Name,
provider, provider,
modelName, modelName,
displayName displayName
); )
{
// 设置描述(优先使用动态的) Description = sourceDefinition.Description ?? targetDefinition.Description
mergedWorkspace.Description = dynamicWorkspace.Description ?? staticWorkspace.Description; };
// 设置API密钥(优先使用动态的) if (!string.IsNullOrEmpty(sourceDefinition.ApiKey))
if (!string.IsNullOrEmpty(dynamicWorkspace.ApiKey))
{ {
mergedWorkspace.WithApiKey(dynamicWorkspace.ApiKey!); mergedWorkspace.WithApiKey(sourceDefinition.ApiKey!);
} }
else if (!string.IsNullOrEmpty(staticWorkspace.ApiKey)) else if (!string.IsNullOrEmpty(targetDefinition.ApiKey))
{ {
mergedWorkspace.WithApiKey(staticWorkspace.ApiKey!); mergedWorkspace.WithApiKey(targetDefinition.ApiKey!);
} }
// 设置API基础URL(优先使用动态的) if (!string.IsNullOrEmpty(sourceDefinition.ApiBaseUrl))
if (!string.IsNullOrEmpty(dynamicWorkspace.ApiBaseUrl))
{ {
mergedWorkspace.WithApiBaseUrl(dynamicWorkspace.ApiBaseUrl!); mergedWorkspace.WithApiBaseUrl(sourceDefinition.ApiBaseUrl!);
} }
else if (!string.IsNullOrEmpty(staticWorkspace.ApiBaseUrl)) else if (!string.IsNullOrEmpty(targetDefinition.ApiBaseUrl))
{ {
mergedWorkspace.WithApiBaseUrl(staticWorkspace.ApiBaseUrl!); mergedWorkspace.WithApiBaseUrl(targetDefinition.ApiBaseUrl!);
} }
mergedWorkspace.SystemPrompt = sourceDefinition.SystemPrompt ?? targetDefinition.SystemPrompt;
mergedWorkspace.Instructions = sourceDefinition.Instructions ?? targetDefinition.Instructions;
mergedWorkspace.Temperature = sourceDefinition.Temperature ?? targetDefinition.Temperature;
mergedWorkspace.MaxOutputTokens = sourceDefinition.MaxOutputTokens ?? targetDefinition.MaxOutputTokens;
mergedWorkspace.FrequencyPenalty = sourceDefinition.FrequencyPenalty ?? targetDefinition.FrequencyPenalty;
mergedWorkspace.PresencePenalty = sourceDefinition.PresencePenalty ?? targetDefinition.PresencePenalty;
mergedWorkspace.IsEnabled = targetDefinition.IsEnabled || sourceDefinition.IsEnabled;
// 设置系统提示词(优先使用动态的) foreach (var checker in targetDefinition.StateCheckers)
mergedWorkspace.SystemPrompt = dynamicWorkspace.SystemPrompt ?? staticWorkspace.SystemPrompt;
// 设置附加系统提示词(优先使用动态的)
mergedWorkspace.Instructions = dynamicWorkspace.Instructions ?? staticWorkspace.Instructions;
// 设置温度值(优先使用动态的)
mergedWorkspace.Temperature = dynamicWorkspace.Temperature ?? staticWorkspace.Temperature;
// 设置最大输出token数(优先使用动态的)
mergedWorkspace.MaxOutputTokens = dynamicWorkspace.MaxOutputTokens ?? staticWorkspace.MaxOutputTokens;
// 设置频率惩罚(优先使用动态的)
mergedWorkspace.FrequencyPenalty = dynamicWorkspace.FrequencyPenalty ?? staticWorkspace.FrequencyPenalty;
// 设置存在惩罚(优先使用动态的)
mergedWorkspace.PresencePenalty = dynamicWorkspace.PresencePenalty ?? staticWorkspace.PresencePenalty;
// 设置是否启用(只要有一方启用,结果就是启用)
mergedWorkspace.IsEnabled = staticWorkspace.IsEnabled || dynamicWorkspace.IsEnabled;
// 合并状态检查器
foreach (var checker in staticWorkspace.StateCheckers)
{ {
if (!mergedWorkspace.StateCheckers.Contains(checker)) if (!mergedWorkspace.StateCheckers.Contains(checker))
{ {
@ -233,7 +119,7 @@ public class WorkspaceDefinitionManager : IWorkspaceDefinitionManager, ISingleto
} }
} }
foreach (var checker in dynamicWorkspace.StateCheckers) foreach (var checker in sourceDefinition.StateCheckers)
{ {
if (!mergedWorkspace.StateCheckers.Contains(checker)) if (!mergedWorkspace.StateCheckers.Contains(checker))
{ {
@ -241,8 +127,7 @@ public class WorkspaceDefinitionManager : IWorkspaceDefinitionManager, ISingleto
} }
} }
// 合并工具列表(去重) foreach (var tool in targetDefinition.Tools)
foreach (var tool in staticWorkspace.Tools)
{ {
if (!mergedWorkspace.Tools.Contains(tool)) if (!mergedWorkspace.Tools.Contains(tool))
{ {
@ -250,7 +135,7 @@ public class WorkspaceDefinitionManager : IWorkspaceDefinitionManager, ISingleto
} }
} }
foreach (var tool in dynamicWorkspace.Tools) foreach (var tool in sourceDefinition.Tools)
{ {
if (!mergedWorkspace.Tools.Contains(tool)) if (!mergedWorkspace.Tools.Contains(tool))
{ {
@ -258,19 +143,16 @@ public class WorkspaceDefinitionManager : IWorkspaceDefinitionManager, ISingleto
} }
} }
// 合并属性(动态覆盖静态) foreach (var property in targetDefinition.Properties)
foreach (var property in staticWorkspace.Properties)
{ {
mergedWorkspace.Properties[property.Key] = property.Value; mergedWorkspace.Properties[property.Key] = property.Value;
} }
foreach (var property in dynamicWorkspace.Properties) foreach (var property in sourceDefinition.Properties)
{ {
mergedWorkspace.Properties[property.Key] = property.Value; mergedWorkspace.Properties[property.Key] = property.Value;
} }
return mergedWorkspace; return Task.FromResult(mergedWorkspace);
} }
#endregion
} }

197
aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionManager.cs

@ -1,188 +1,90 @@
using Microsoft.Extensions.Options; using JetBrains.Annotations;
using LINGYUN.Abp.Dynamic.Definitions;
using Microsoft.Extensions.Options;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.AI.Tools; namespace LINGYUN.Abp.AI.Tools;
public class AIToolDefinitionManager : IAIToolDefinitionManager, ISingletonDependency public class AIToolDefinitionManager :
DynamicDefinitionManager<AIToolDefinition>,
IAIToolDefinitionManager,
ITransientDependency
{ {
protected readonly AbpAIToolsOptions AIToolOptions; protected IStaticAIToolDefinitionStore StaticStore { get; }
protected readonly IStaticAIToolDefinitionStore StaticStore; protected IDynamicAIToolDefinitionStore DynamicStore { get; }
protected readonly IDynamicAIToolDefinitionStore DynamicStore; protected AbpAIToolsOptions AIToolsOptions { get; }
public AIToolDefinitionManager( public AIToolDefinitionManager(
IStaticAIToolDefinitionStore staticStore, IStaticAIToolDefinitionStore staticStore,
IDynamicAIToolDefinitionStore dynamicStore, IDynamicAIToolDefinitionStore dynamicStore,
IOptions<AbpAIToolsOptions> aiToolOptions) IOptions<AbpDynamicDefinitionsOptions> options,
IOptions<AbpAIToolsOptions> aIToolsOptions)
: base(options)
{ {
StaticStore = staticStore; StaticStore = staticStore;
DynamicStore = dynamicStore; DynamicStore = dynamicStore;
AIToolOptions = aiToolOptions.Value; AIToolsOptions = aIToolsOptions.Value;
} }
public virtual async Task<AIToolDefinition> GetAsync(string name) public async virtual Task<AIToolDefinition> GetAsync([NotNull] string name)
{ {
var workspace = await GetOrNullAsync(name); return await GetOrNullAsync(name) ?? throw new AbpException("Undefined AITool: " + name);
if (workspace == null)
{
throw new AbpException("Undefined AITool: " + name);
}
return workspace;
} }
public virtual async Task<AIToolDefinition?> GetOrNullAsync(string name) public async virtual Task<IReadOnlyList<AIToolDefinition>> GetAllAsync()
{ {
Check.NotNull(name, nameof(name)); var staticDefinitions = await StaticStore.GetAllAsync();
var dynamicDefinitions = await DynamicStore.GetAllAsync();
var staticDefinition = await StaticStore.GetOrNullAsync(name);
var dynamicDefinition = await DynamicStore.GetOrNullAsync(name);
if (staticDefinition != null && dynamicDefinition != null)
{
return AIToolOptions.DynamicAItoolStrategy switch
{
DynamicAItoolStrategy.Ignore => staticDefinition,
DynamicAItoolStrategy.Covering => dynamicDefinition,
DynamicAItoolStrategy.Merge => MergeAITool(staticDefinition, dynamicDefinition),
_ => MergeAITool(staticDefinition, dynamicDefinition)
};
}
return staticDefinition ?? dynamicDefinition; return await GetDefinitionsAsync(staticDefinitions, dynamicDefinitions);
} }
public virtual async Task<IReadOnlyList<AIToolDefinition>> GetAllAsync() public async virtual Task<AIToolDefinition?> GetOrNullAsync([NotNull] string name)
{ {
var staticAITools = await StaticStore.GetAllAsync(); Check.NotNull(name, nameof(name));
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工具定义策略 var staticDefinition = await StaticStore.GetOrNullAsync(name);
var dynamicDefinition = await DynamicStore.GetOrNullAsync(name);
/// <summary> return await GetDefinitionAsync(staticDefinition, dynamicDefinition);
/// 忽略策略:静态优先,过滤掉同名的动态AI工具
/// </summary>
protected virtual Task<IReadOnlyList<AIToolDefinition>> GetAIToolsWithIgnoreStrategy(
IReadOnlyList<AIToolDefinition> staticAITools,
IReadOnlyList<AIToolDefinition> dynamicAITools)
{
var staticAIToolNames = staticAITools
.Select(p => p.Name)
.ToImmutableHashSet();
return Task.FromResult<IReadOnlyList<AIToolDefinition>>(
staticAITools
.Concat(dynamicAITools.Where(d => !staticAIToolNames.Contains(d.Name)))
.ToImmutableList()
);
} }
/// <summary> protected override string GetDefinitionKey(AIToolDefinition definition)
/// 覆盖策略:动态完全覆盖静态AI工具
/// </summary>
protected virtual Task<IReadOnlyList<AIToolDefinition>> GetAIToolsWithCoveringStrategy(
IReadOnlyList<AIToolDefinition> staticAITools,
IReadOnlyList<AIToolDefinition> dynamicAITools)
{ {
var dynamicAIToolNames = dynamicAITools return definition.Name;
.Select(p => p.Name)
.ToImmutableHashSet();
// 动态AI工具完全覆盖静态AI工具
var result = dynamicAITools
.Concat(staticAITools.Where(s => !dynamicAIToolNames.Contains(s.Name)))
.ToImmutableList();
return Task.FromResult<IReadOnlyList<AIToolDefinition>>(result);
} }
/// <summary> protected override Task AfterMergedDefinitionsAsync(Dictionary<string, AIToolDefinition> mergedDefinitions)
/// 合并策略:合并静态和动态AI工具,创建新实例
/// </summary>
protected virtual Task<IReadOnlyList<AIToolDefinition>> GetAIToolsWithMergeStrategy(
IReadOnlyList<AIToolDefinition> staticAITools,
IReadOnlyList<AIToolDefinition> dynamicAITools)
{ {
var mergedAITools = new Dictionary<string, AIToolDefinition>(); foreach (var deletedToolName in AIToolsOptions.DeletedAITools)
// 先添加所有静态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)) mergedDefinitions.Remove(deletedToolName);
{
mergedAITools.Remove(deletedToolName);
}
} }
return Task.FromResult<IReadOnlyList<AIToolDefinition>>(mergedAITools.Values.ToImmutableList()); return Task.CompletedTask;
} }
/// <summary> protected override Task<AIToolDefinition> MergeDefinitionAsync(AIToolDefinition targetDefinition, AIToolDefinition sourceDefinition)
/// 合并两个AI工具定义,返回新的 AIToolDefinition 实例
/// </summary>
protected virtual AIToolDefinition MergeAITool(
AIToolDefinition staticAITool,
AIToolDefinition dynamicAITool)
{ {
// 决定使用哪个提供者(优先使用动态的) var provider = !string.IsNullOrEmpty(sourceDefinition.Provider)
var provider = !string.IsNullOrEmpty(dynamicAITool.Provider) ? sourceDefinition.Provider
? dynamicAITool.Provider : targetDefinition.Provider;
: staticAITool.Provider; var description = sourceDefinition.Description ?? targetDefinition.Description;
// 决定使用哪个描述(优先使用动态的)
var description = dynamicAITool.Description ?? staticAITool.Description;
// 创建新的AI工具实例(Name是只读的)
var mergedAITool = new AIToolDefinition( var mergedAITool = new AIToolDefinition(
staticAITool.Name, // 保持名称不变 targetDefinition.Name,
provider, provider,
description description
); )
{
// 设置是否启用(只要有一方启用,结果就是启用) IsEnabled = targetDefinition.IsEnabled || sourceDefinition.IsEnabled,
mergedAITool.IsEnabled = staticAITool.IsEnabled || dynamicAITool.IsEnabled; IsGlobal = targetDefinition.IsGlobal || sourceDefinition.IsGlobal
};
// 设置是否为全局工具(只要有一方是全局,结果就是全局)
mergedAITool.IsGlobal = staticAITool.IsGlobal || dynamicAITool.IsGlobal;
// 合并状态检查器 foreach (var checker in targetDefinition.StateCheckers)
foreach (var checker in staticAITool.StateCheckers)
{ {
if (!mergedAITool.StateCheckers.Contains(checker)) if (!mergedAITool.StateCheckers.Contains(checker))
{ {
@ -190,7 +92,7 @@ public class AIToolDefinitionManager : IAIToolDefinitionManager, ISingletonDepen
} }
} }
foreach (var checker in dynamicAITool.StateCheckers) foreach (var checker in sourceDefinition.StateCheckers)
{ {
if (!mergedAITool.StateCheckers.Contains(checker)) if (!mergedAITool.StateCheckers.Contains(checker))
{ {
@ -198,19 +100,16 @@ public class AIToolDefinitionManager : IAIToolDefinitionManager, ISingletonDepen
} }
} }
// 合并属性(动态覆盖静态) foreach (var property in targetDefinition.Properties)
foreach (var property in staticAITool.Properties)
{ {
mergedAITool.Properties[property.Key] = property.Value; mergedAITool.Properties[property.Key] = property.Value;
} }
foreach (var property in dynamicAITool.Properties) foreach (var property in sourceDefinition.Properties)
{ {
mergedAITool.Properties[property.Key] = property.Value; mergedAITool.Properties[property.Key] = property.Value;
} }
return mergedAITool; return Task.FromResult(mergedAITool);
} }
}
#endregion
}

7
aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsModule.cs

@ -1,7 +1,7 @@
using LINGYUN.Abp.AI.Localization; using LINGYUN.Abp.AI.Localization;
using LINGYUN.Abp.Dynamic.Definitions;
using Microsoft.Extensions.AI; using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Volo.Abp.Localization; using Volo.Abp.Localization;
@ -58,6 +58,11 @@ public class AbpAIToolsModule : AbpModule
.Get<AbpAIResource>() .Get<AbpAIResource>()
.AddVirtualJson("/LINGYUN/Abp/AI/Tools/Localization/Resources"); .AddVirtualJson("/LINGYUN/Abp/AI/Tools/Localization/Resources");
}); });
Configure<AbpDynamicDefinitionsOptions>(options =>
{
options.MapStrategy<AIToolDefinition>(DynamicDefinitionStrategy.Merge);
});
} }
private static void AutoAddDefinitionProviders(IServiceCollection services) private static void AutoAddDefinitionProviders(IServiceCollection services)

2
aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs

@ -5,12 +5,10 @@ namespace LINGYUN.Abp.AI.Tools;
public class AbpAIToolsOptions public class AbpAIToolsOptions
{ {
public ITypeList<IAIToolDefinitionProvider> DefinitionProviders { get; } public ITypeList<IAIToolDefinitionProvider> DefinitionProviders { get; }
public DynamicAItoolStrategy DynamicAItoolStrategy { get; set; }
public ITypeList<IAIToolProvider> AIToolProviders { get; } public ITypeList<IAIToolProvider> AIToolProviders { get; }
public HashSet<string> DeletedAITools { get; } public HashSet<string> DeletedAITools { get; }
public AbpAIToolsOptions() public AbpAIToolsOptions()
{ {
DynamicAItoolStrategy = DynamicAItoolStrategy.Merge;
DefinitionProviders = new TypeList<IAIToolDefinitionProvider>(); DefinitionProviders = new TypeList<IAIToolDefinitionProvider>();
AIToolProviders = new TypeList<IAIToolProvider>(); AIToolProviders = new TypeList<IAIToolProvider>();
DeletedAITools = new HashSet<string>(); DeletedAITools = new HashSet<string>();

19
aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/DynamicAItoolStrategy.cs

@ -1,19 +0,0 @@
namespace LINGYUN.Abp.AI.Tools;
/// <summary>
/// 动态工具策略
/// </summary>
public enum DynamicAItoolStrategy : byte
{
/// <summary>
/// 忽略
/// </summary>
Ignore = 0,
/// <summary>
/// 覆盖
/// </summary>
Covering =1,
/// <summary>
/// 合并
/// </summary>
Merge = 2
}

1
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN.Abp.Notifications.Core.csproj

@ -22,6 +22,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\framework\common\LINGYUN.Abp.RealTime\LINGYUN.Abp.RealTime.csproj" /> <ProjectReference Include="..\..\..\framework\common\LINGYUN.Abp.RealTime\LINGYUN.Abp.RealTime.csproj" />
<ProjectReference Include="..\..\..\framework\dynamic-definition\LINGYUN.Abp.Dynamic.Definitions.Core\LINGYUN.Abp.Dynamic.Definitions.Core.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

10
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/AbpNotificationsCoreModule.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.Notifications.Localization; using LINGYUN.Abp.Dynamic.Definitions;
using LINGYUN.Abp.Notifications.Localization;
using LINGYUN.Abp.RealTime; using LINGYUN.Abp.RealTime;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System; using System;
@ -12,6 +13,7 @@ using Volo.Abp.TextTemplating;
namespace LINGYUN.Abp.Notifications; namespace LINGYUN.Abp.Notifications;
[DependsOn( [DependsOn(
typeof(AbpDynamicDefinitionsCoreModule),
typeof(AbpTextTemplatingCoreModule), typeof(AbpTextTemplatingCoreModule),
typeof(AbpRealTimeModule), typeof(AbpRealTimeModule),
typeof(AbpLocalizationModule), typeof(AbpLocalizationModule),
@ -36,6 +38,12 @@ public class AbpNotificationsCoreModule : AbpModule
{ {
preActions.Configure(options); preActions.Configure(options);
}); });
Configure<AbpDynamicDefinitionsOptions>(options =>
{
options.MapStrategy<NotificationDefinition>(DynamicDefinitionStrategy.Merge);
options.MapStrategy<NotificationGroupDefinition>(DynamicDefinitionStrategy.Merge);
});
} }
private void AutoAddDefinitionProviders(IServiceCollection services) private void AutoAddDefinitionProviders(IServiceCollection services)

3
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/AbpNotificationsOptions.cs

@ -10,15 +10,12 @@ public class AbpNotificationsOptions
/// </summary> /// </summary>
public ITypeList<INotificationDefinitionProvider> DefinitionProviders { get; } public ITypeList<INotificationDefinitionProvider> DefinitionProviders { get; }
public DynamicNotificationStrategy DynamicNotificationStrategy { get; set; }
public HashSet<string> DeletedNotifications { get; } public HashSet<string> DeletedNotifications { get; }
public HashSet<string> DeletedNotificationGroups { get; } public HashSet<string> DeletedNotificationGroups { get; }
public AbpNotificationsOptions() public AbpNotificationsOptions()
{ {
DynamicNotificationStrategy = DynamicNotificationStrategy.Merge;
DefinitionProviders = new TypeList<INotificationDefinitionProvider>(); DefinitionProviders = new TypeList<INotificationDefinitionProvider>();
DeletedNotifications = new HashSet<string>(); DeletedNotifications = new HashSet<string>();

19
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/DynamicNotificationStrategy.cs

@ -1,19 +0,0 @@
namespace LINGYUN.Abp.Notifications;
/// <summary>
/// 动态通知策略
/// </summary>
public enum DynamicNotificationStrategy : byte
{
/// <summary>
/// 忽略
/// </summary>
Ignore = 0,
/// <summary>
/// 覆盖
/// </summary>
Covering =1,
/// <summary>
/// 合并
/// </summary>
Merge = 2
}

346
aspnet-core/modules/realtime-notifications/LINGYUN.Abp.Notifications.Core/LINGYUN/Abp/Notifications/NotificationDefinitionManager.cs

@ -1,224 +1,93 @@
using Microsoft.Extensions.Options; using JetBrains.Annotations;
using LINGYUN.Abp.Dynamic.Definitions;
using Microsoft.Extensions.Options;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.Notifications; namespace LINGYUN.Abp.Notifications;
public class NotificationDefinitionManager : INotificationDefinitionManager, ITransientDependency public class NotificationDefinitionManager :
DynamicDefinitionManager<NotificationGroupDefinition, NotificationDefinition>,
INotificationDefinitionManager,
ITransientDependency
{ {
private readonly AbpNotificationsOptions _notificationsOptions; protected IStaticNotificationDefinitionStore StaticStore { get; }
private readonly IStaticNotificationDefinitionStore _staticStore; protected IDynamicNotificationDefinitionStore DynamicStore { get; }
private readonly IDynamicNotificationDefinitionStore _dynamicStore;
public NotificationDefinitionManager( public NotificationDefinitionManager(
IStaticNotificationDefinitionStore staticStore, IStaticNotificationDefinitionStore staticStore,
IDynamicNotificationDefinitionStore dynamicStore, IDynamicNotificationDefinitionStore dynamicStore,
IOptions<AbpNotificationsOptions> notificationsOptions) IOptions<AbpDynamicDefinitionsOptions> options) : base(options)
{ {
_staticStore = staticStore; StaticStore = staticStore;
_dynamicStore = dynamicStore; DynamicStore = dynamicStore;
_notificationsOptions = notificationsOptions.Value;
} }
public async virtual Task<NotificationDefinition> GetAsync(string name) public async virtual Task<NotificationDefinition> GetAsync([NotNull] string name)
{ {
var notification = await GetOrNullAsync(name); return await GetOrNullAsync(name) ?? throw new AbpException("Undefined notification: " + name);
if (notification == null)
{
throw new AbpException("Undefined notification: " + name);
}
return notification;
}
public async virtual Task<NotificationDefinition?> GetOrNullAsync(string name)
{
Check.NotNull(name, nameof(name));
var staticDefinition = await _staticStore.GetOrNullAsync(name);
var dynamicDefinition = await _dynamicStore.GetOrNullAsync(name);
if (staticDefinition != null && dynamicDefinition != null)
{
return _notificationsOptions.DynamicNotificationStrategy switch
{
DynamicNotificationStrategy.Ignore => staticDefinition,
DynamicNotificationStrategy.Covering => dynamicDefinition,
DynamicNotificationStrategy.Merge => MergeNotification(staticDefinition, dynamicDefinition),
_ => staticDefinition
};
}
return staticDefinition ?? dynamicDefinition;
}
public async virtual Task<IReadOnlyList<NotificationDefinition>> GetNotificationsAsync()
{
var staticNotifications = await _staticStore.GetNotificationsAsync();
var dynamicNotifications = await _dynamicStore.GetNotificationsAsync();
// 根据策略处理通知定义
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<NotificationGroupDefinition?> GetGroupOrNullAsync(string name) public async virtual Task<NotificationGroupDefinition?> GetGroupOrNullAsync(string name)
{ {
Check.NotNull(name, nameof(name)); Check.NotNull(name, nameof(name));
var staticDefinition = await _staticStore.GetGroupOrNullAsync(name); var staticGroupDefinition = await StaticStore.GetGroupOrNullAsync(name);
var dynamicDefinition = await _dynamicStore.GetGroupOrNullAsync(name); var dynamicGroupDefinition = await DynamicStore.GetGroupOrNullAsync(name);
if (staticDefinition != null && dynamicDefinition != null)
{
switch (_notificationsOptions.DynamicNotificationStrategy)
{
case DynamicNotificationStrategy.Ignore:
return staticDefinition;
case DynamicNotificationStrategy.Covering:
return dynamicDefinition;
case DynamicNotificationStrategy.Merge:
MergeGroupNotifications(staticDefinition, dynamicDefinition);
return staticDefinition;
default:
return staticDefinition;
}
}
return staticDefinition ?? dynamicDefinition; return await GetGroupDefinitionAsync(staticGroupDefinition, dynamicGroupDefinition);
} }
public async virtual Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync() public async virtual Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsAsync()
{ {
var staticGroups = await _staticStore.GetGroupsAsync(); var staticGroupDefinitions = await StaticStore.GetGroupsAsync();
var dynamicGroups = await _dynamicStore.GetGroupsAsync(); var dynamicGroupDefinitions = await DynamicStore.GetGroupsAsync();
// 根据策略处理分组定义 return await GetGroupDefinitionsAsync(staticGroupDefinitions, dynamicGroupDefinitions);
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 通知定义策略 public async virtual Task<IReadOnlyList<NotificationDefinition>> GetNotificationsAsync()
/// <summary>
/// 忽略策略:静态优先,过滤掉同名的动态通知
/// </summary>
protected virtual Task<IReadOnlyList<NotificationDefinition>> GetNotificationsWithIgnoreStrategy(
IReadOnlyList<NotificationDefinition> staticNotifications,
IReadOnlyList<NotificationDefinition> dynamicNotifications)
{ {
var staticNotificationNames = staticNotifications var staticDefinitions = await StaticStore.GetNotificationsAsync();
.Select(p => p.Name) var dynamicDefinitions = await DynamicStore.GetNotificationsAsync();
.ToImmutableHashSet();
return await GetDefinitionsAsync(staticDefinitions, dynamicDefinitions);
return Task.FromResult<IReadOnlyList<NotificationDefinition>>(
staticNotifications
.Concat(dynamicNotifications.Where(d => !staticNotificationNames.Contains(d.Name)))
.ToImmutableList()
);
} }
/// <summary> public async virtual Task<NotificationDefinition?> GetOrNullAsync(string name)
/// 覆盖策略:动态完全覆盖静态通知
/// </summary>
protected virtual Task<IReadOnlyList<NotificationDefinition>> GetNotificationsWithCoveringStrategy(
IReadOnlyList<NotificationDefinition> staticNotifications,
IReadOnlyList<NotificationDefinition> dynamicNotifications)
{ {
var dynamicNotificationNames = dynamicNotifications Check.NotNull(name, nameof(name));
.Select(p => p.Name)
.ToImmutableHashSet();
// 动态通知完全覆盖静态通知 var staticDefinition = await StaticStore.GetOrNullAsync(name);
var result = dynamicNotifications var dynamicDefinition = await DynamicStore.GetOrNullAsync(name);
.Concat(staticNotifications.Where(s => !dynamicNotificationNames.Contains(s.Name)))
.ToImmutableList();
return Task.FromResult<IReadOnlyList<NotificationDefinition>>(result); return await GetDefinitionAsync(staticDefinition, dynamicDefinition);
} }
/// <summary> protected override string GetDefinitionKey(NotificationDefinition definition)
/// 合并策略:合并静态和动态通知,创建新实例
/// </summary>
protected virtual Task<IReadOnlyList<NotificationDefinition>> GetNotificationsWithMergeStrategy(
IReadOnlyList<NotificationDefinition> staticNotifications,
IReadOnlyList<NotificationDefinition> dynamicNotifications)
{ {
var mergedNotifications = new Dictionary<string, NotificationDefinition>(); return definition.Name;
// 先添加所有静态通知
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<IReadOnlyList<NotificationDefinition>>(mergedNotifications.Values.ToImmutableList());
} }
/// <summary> protected override Task<NotificationDefinition> MergeDefinitionAsync(NotificationDefinition targetDefinition, NotificationDefinition sourceDefinition)
/// 合并两个通知定义,返回新的 NotificationDefinition 实例
/// </summary>
protected virtual NotificationDefinition MergeNotification(
NotificationDefinition staticNotification,
NotificationDefinition dynamicNotification)
{ {
// 决定使用哪个显示名称(优先使用动态的) var displayName = sourceDefinition.DisplayName ?? targetDefinition.DisplayName;
var displayName = dynamicNotification.DisplayName ?? staticNotification.DisplayName; var description = sourceDefinition.Description ?? targetDefinition.Description;
var notificationType = sourceDefinition.NotificationType != NotificationType.Application
// 决定使用哪个描述(优先使用动态的) ? sourceDefinition.NotificationType
var description = dynamicNotification.Description ?? staticNotification.Description; : targetDefinition.NotificationType;
var lifetime = sourceDefinition.NotificationLifetime != NotificationLifetime.Persistent
// 决定通知类型(优先使用动态的) ? sourceDefinition.NotificationLifetime
var notificationType = dynamicNotification.NotificationType != NotificationType.Application : targetDefinition.NotificationLifetime;
? dynamicNotification.NotificationType var contentType = sourceDefinition.ContentType != NotificationContentType.Text
: staticNotification.NotificationType; ? sourceDefinition.ContentType
: targetDefinition.ContentType;
// 决定存活类型(优先使用动态的) var allowSubscriptionToClients = sourceDefinition.AllowSubscriptionToClients || targetDefinition.AllowSubscriptionToClients;
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( var mergedNotification = new NotificationDefinition(
staticNotification.Name, // 保持名称不变 targetDefinition.Name,
displayName, displayName,
description, description,
notificationType, notificationType,
@ -227,20 +96,17 @@ public class NotificationDefinitionManager : INotificationDefinitionManager, ITr
allowSubscriptionToClients allowSubscriptionToClients
); );
// 复制静态通知的属性 foreach (var property in targetDefinition.Properties)
foreach (var property in staticNotification.Properties)
{ {
mergedNotification.Properties[property.Key] = property.Value; mergedNotification.Properties[property.Key] = property.Value;
} }
// 复制动态通知的属性(覆盖同名的静态属性) foreach (var property in sourceDefinition.Properties)
foreach (var property in dynamicNotification.Properties)
{ {
mergedNotification.Properties[property.Key] = property.Value; mergedNotification.Properties[property.Key] = property.Value;
} }
// 合并提供者 foreach (var provider in targetDefinition.Providers)
foreach (var provider in staticNotification.Providers)
{ {
if (!mergedNotification.Providers.Contains(provider)) if (!mergedNotification.Providers.Contains(provider))
{ {
@ -248,7 +114,7 @@ public class NotificationDefinitionManager : INotificationDefinitionManager, ITr
} }
} }
foreach (var provider in dynamicNotification.Providers) foreach (var provider in sourceDefinition.Providers)
{ {
if (!mergedNotification.Providers.Contains(provider)) if (!mergedNotification.Providers.Contains(provider))
{ {
@ -256,107 +122,32 @@ public class NotificationDefinitionManager : INotificationDefinitionManager, ITr
} }
} }
// 合并模板(优先使用动态的) if (sourceDefinition.Template != null)
if (dynamicNotification.Template != null)
{ {
mergedNotification.WithTemplate(dynamicNotification.Template); mergedNotification.WithTemplate(sourceDefinition.Template);
} }
else if (staticNotification.Template != null) else if (targetDefinition.Template != null)
{ {
mergedNotification.WithTemplate(staticNotification.Template); mergedNotification.WithTemplate(targetDefinition.Template);
} }
return mergedNotification; return Task.FromResult(mergedNotification);
} }
#endregion protected override string GetGroupDefinitionKey(NotificationGroupDefinition groupDefinition)
#region 分组定义策略
/// <summary>
/// 忽略策略:静态优先,过滤掉同名的动态分组
/// </summary>
protected virtual Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsWithIgnoreStrategy(
IReadOnlyList<NotificationGroupDefinition> staticGroups,
IReadOnlyList<NotificationGroupDefinition> dynamicGroups)
{ {
var staticGroupNames = staticGroups return groupDefinition.Name;
.Select(p => p.Name)
.ToImmutableHashSet();
return Task.FromResult<IReadOnlyList<NotificationGroupDefinition>>(
staticGroups
.Concat(dynamicGroups.Where(d => !staticGroupNames.Contains(d.Name)))
.ToImmutableList()
);
}
/// <summary>
/// 覆盖策略:动态完全覆盖静态分组
/// </summary>
protected virtual Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsWithCoveringStrategy(
IReadOnlyList<NotificationGroupDefinition> staticGroups,
IReadOnlyList<NotificationGroupDefinition> dynamicGroups)
{
var dynamicGroupNames = dynamicGroups
.Select(p => p.Name)
.ToImmutableHashSet();
var result = dynamicGroups
.Concat(staticGroups.Where(s => !dynamicGroupNames.Contains(s.Name)))
.ToImmutableList();
return Task.FromResult<IReadOnlyList<NotificationGroupDefinition>>(result);
} }
/// <summary> protected override Task MergeGroupDefinitionAsync(NotificationGroupDefinition targetGroupDefinition, NotificationGroupDefinition sourceGroupDefinition)
/// 合并策略:合并静态和动态分组
/// </summary>
protected virtual Task<IReadOnlyList<NotificationGroupDefinition>> GetGroupsWithMergeStrategy(
IReadOnlyList<NotificationGroupDefinition> staticGroups,
IReadOnlyList<NotificationGroupDefinition> dynamicGroups)
{ {
var mergedGroups = new Dictionary<string, NotificationGroupDefinition>(); foreach (var sourceNotification in sourceGroupDefinition.Notifications)
// 先添加所有静态分组
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<IReadOnlyList<NotificationGroupDefinition>>(
mergedGroups.Values.ToImmutableList()
);
}
/// <summary>
/// 合并分组的通知列表
/// </summary>
private static void MergeGroupNotifications(NotificationGroupDefinition target, NotificationGroupDefinition source)
{
foreach (var sourceNotification in source.Notifications)
{ {
var existingNotification = target.GetNotificationOrNull(sourceNotification.Name); var existingNotification = targetGroupDefinition.GetNotificationOrNull(sourceNotification.Name);
if (existingNotification == null) if (existingNotification == null)
{ {
// 通知不存在,直接添加 var newNotification = targetGroupDefinition.AddNotification(
var newNotification = target.AddNotification(
sourceNotification.Name, sourceNotification.Name,
sourceNotification.DisplayName, sourceNotification.DisplayName,
sourceNotification.Description, sourceNotification.Description,
@ -366,7 +157,6 @@ public class NotificationDefinitionManager : INotificationDefinitionManager, ITr
sourceNotification.AllowSubscriptionToClients sourceNotification.AllowSubscriptionToClients
); );
// 复制提供者
foreach (var provider in sourceNotification.Providers) foreach (var provider in sourceNotification.Providers)
{ {
if (!newNotification.Providers.Contains(provider)) if (!newNotification.Providers.Contains(provider))
@ -375,13 +165,11 @@ public class NotificationDefinitionManager : INotificationDefinitionManager, ITr
} }
} }
// 复制属性
foreach (var property in sourceNotification.Properties) foreach (var property in sourceNotification.Properties)
{ {
newNotification.Properties[property.Key] = property.Value; newNotification.Properties[property.Key] = property.Value;
} }
// 复制模板
if (sourceNotification.Template != null) if (sourceNotification.Template != null)
{ {
newNotification.WithTemplate(sourceNotification.Template); newNotification.WithTemplate(sourceNotification.Template);
@ -389,13 +177,11 @@ public class NotificationDefinitionManager : INotificationDefinitionManager, ITr
} }
else else
{ {
// 通知已存在,合并属性
foreach (var property in sourceNotification.Properties) foreach (var property in sourceNotification.Properties)
{ {
existingNotification.Properties[property.Key] = property.Value; existingNotification.Properties[property.Key] = property.Value;
} }
// 合并提供者
foreach (var provider in sourceNotification.Providers) foreach (var provider in sourceNotification.Providers)
{ {
if (!existingNotification.Providers.Contains(provider)) if (!existingNotification.Providers.Contains(provider))
@ -404,30 +190,26 @@ public class NotificationDefinitionManager : INotificationDefinitionManager, ITr
} }
} }
// 更新显示名称(如果源提供了)
if (sourceNotification.DisplayName != null) if (sourceNotification.DisplayName != null)
{ {
existingNotification.DisplayName = sourceNotification.DisplayName; existingNotification.DisplayName = sourceNotification.DisplayName;
} }
// 更新描述(如果源提供了)
if (sourceNotification.Description != null) if (sourceNotification.Description != null)
{ {
existingNotification.Description = sourceNotification.Description; existingNotification.Description = sourceNotification.Description;
} }
// 更新模板(优先使用动态的)
if (sourceNotification.Template != null) if (sourceNotification.Template != null)
{ {
existingNotification.WithTemplate(sourceNotification.Template); existingNotification.WithTemplate(sourceNotification.Template);
} }
// 更新允许客户端订阅
existingNotification.AllowSubscriptionToClients = existingNotification.AllowSubscriptionToClients =
existingNotification.AllowSubscriptionToClients || sourceNotification.AllowSubscriptionToClients; existingNotification.AllowSubscriptionToClients || sourceNotification.AllowSubscriptionToClients;
} }
} }
}
#endregion return Task.CompletedTask;
} }
}

4
aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN.Abp.Webhooks.Core.csproj

@ -17,4 +17,8 @@
<PackageReference Include="Volo.Abp.Features" /> <PackageReference Include="Volo.Abp.Features" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\dynamic-definition\LINGYUN.Abp.Dynamic.Definitions.Core\LINGYUN.Abp.Dynamic.Definitions.Core.csproj" />
</ItemGroup>
</Project> </Project>

16
aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/AbpWebhooksCoreModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection; using LINGYUN.Abp.Dynamic.Definitions;
using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Volo.Abp.Features; using Volo.Abp.Features;
@ -6,7 +7,9 @@ using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Webhooks; namespace LINGYUN.Abp.Webhooks;
[DependsOn(typeof(AbpFeaturesModule))] [DependsOn(
typeof(AbpDynamicDefinitionsCoreModule),
typeof(AbpFeaturesModule))]
public class AbpWebhooksCoreModule : AbpModule public class AbpWebhooksCoreModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
@ -14,6 +17,15 @@ public class AbpWebhooksCoreModule : AbpModule
AutoAddDefinitionProviders(context.Services); AutoAddDefinitionProviders(context.Services);
} }
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpDynamicDefinitionsOptions>(options =>
{
options.MapStrategy<WebhookDefinition>(DynamicDefinitionStrategy.Merge);
options.MapStrategy<WebhookGroupDefinition>(DynamicDefinitionStrategy.Merge);
});
}
private static void AutoAddDefinitionProviders(IServiceCollection services) private static void AutoAddDefinitionProviders(IServiceCollection services)
{ {
var definitionProviders = new List<Type>(); var definitionProviders = new List<Type>();

5
aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/AbpWebhooksOptions.cs

@ -12,10 +12,6 @@ public class AbpWebhooksOptions
/// </summary> /// </summary>
public TimeSpan TimeoutDuration { get; set; } public TimeSpan TimeoutDuration { get; set; }
/// <summary> /// <summary>
/// 动态Webhook策略
/// </summary>
public DynamicWebhookStrategy DynamicWebhookStrategy { get; set; }
/// <summary>
/// 默认最大发送次数 /// 默认最大发送次数
/// </summary> /// </summary>
public int MaxSendAttemptCount { get; set; } public int MaxSendAttemptCount { get; set; }
@ -43,7 +39,6 @@ public class AbpWebhooksOptions
public string DefaultAgentIdentifier { get; set; } public string DefaultAgentIdentifier { get; set; }
public AbpWebhooksOptions() public AbpWebhooksOptions()
{ {
DynamicWebhookStrategy = DynamicWebhookStrategy.Merge;
TimeoutDuration = TimeSpan.FromSeconds(60); TimeoutDuration = TimeSpan.FromSeconds(60);
MaxSendAttemptCount = 5; MaxSendAttemptCount = 5;
MaxConsecutiveFailCountBeforeDeactivateSubscription = MaxSendAttemptCount * 3; MaxConsecutiveFailCountBeforeDeactivateSubscription = MaxSendAttemptCount * 3;

19
aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/DynamicWebhookStrategy.cs

@ -1,19 +0,0 @@
namespace LINGYUN.Abp.Webhooks;
/// <summary>
/// 动态Webhook策略
/// </summary>
public enum DynamicWebhookStrategy : byte
{
/// <summary>
/// 忽略
/// </summary>
Ignore = 0,
/// <summary>
/// 覆盖
/// </summary>
Covering =1,
/// <summary>
/// 合并
/// </summary>
Merge = 2
}

360
aspnet-core/modules/webhooks/LINGYUN.Abp.Webhooks.Core/LINGYUN/Abp/Webhooks/WebhookDefinitionManager.cs

@ -1,8 +1,8 @@
using Microsoft.Extensions.DependencyInjection; using LINGYUN.Abp.Dynamic.Definitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp; using Volo.Abp;
@ -12,127 +12,76 @@ using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.Webhooks; namespace LINGYUN.Abp.Webhooks;
internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonDependency public class WebhookDefinitionManager :
DynamicDefinitionManager<WebhookGroupDefinition, WebhookDefinition>,
IWebhookDefinitionManager,
ITransientDependency
{ {
private readonly AbpWebhooksOptions _webhooksOptions; protected IServiceProvider ServiceProvider { get; }
private readonly IServiceProvider _serviceProvider; protected IStaticWebhookDefinitionStore StaticStore { get; }
private readonly IStaticWebhookDefinitionStore _staticStore; protected IDynamicWebhookDefinitionStore DynamicStore { get; }
private readonly IDynamicWebhookDefinitionStore _dynamicStore;
public WebhookDefinitionManager( public WebhookDefinitionManager(
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
IStaticWebhookDefinitionStore staticStore, IStaticWebhookDefinitionStore staticStore,
IDynamicWebhookDefinitionStore dynamicStore, IDynamicWebhookDefinitionStore dynamicStore,
IOptions<AbpWebhooksOptions> webhooksOptions) IOptions<AbpDynamicDefinitionsOptions> options)
: base(options)
{ {
_serviceProvider = serviceProvider; ServiceProvider = serviceProvider;
_staticStore = staticStore; StaticStore = staticStore;
_dynamicStore = dynamicStore; DynamicStore = dynamicStore;
_webhooksOptions = webhooksOptions.Value;
}
public async virtual Task<WebhookDefinition?> GetOrNullAsync(string name)
{
Check.NotNull(name, nameof(name));
var staticDefinition = await _staticStore.GetOrNullAsync(name);
var dynamicDefinition = await _dynamicStore.GetOrNullAsync(name);
if (staticDefinition != null && dynamicDefinition != null)
{
return _webhooksOptions.DynamicWebhookStrategy switch
{
DynamicWebhookStrategy.Ignore => staticDefinition,
DynamicWebhookStrategy.Covering => dynamicDefinition,
DynamicWebhookStrategy.Merge => MergeWebhook(staticDefinition, dynamicDefinition),
_ => staticDefinition
};
}
return staticDefinition ?? dynamicDefinition;
} }
public async virtual Task<WebhookDefinition> GetAsync(string name) public async virtual Task<WebhookDefinition> GetAsync(string name)
{ {
var webhook = await GetOrNullAsync(name); return await GetOrNullAsync(name) ?? throw new AbpException("Undefined webhook: " + name);
if (webhook == null)
{
throw new AbpException("Undefined webhook: " + name);
}
return webhook;
} }
public async virtual Task<IReadOnlyList<WebhookDefinition>> GetWebhooksAsync() public async virtual Task<WebhookGroupDefinition> GetGroupAsync(string name)
{ {
var staticWebhooks = await _staticStore.GetWebhooksAsync(); return await GetGroupOrNullAsync(name) ?? throw new AbpException("Undefined webhook group: " + name);
var dynamicWebhooks = await _dynamicStore.GetWebhooksAsync();
// 根据策略处理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<WebhookGroupDefinition?> GetGroupOrNullAsync(string name) public async virtual Task<WebhookGroupDefinition?> GetGroupOrNullAsync(string name)
{ {
Check.NotNull(name, nameof(name)); Check.NotNull(name, nameof(name));
var staticDefinition = await _staticStore.GetGroupOrNullAsync(name); var staticGroupDefinition = await StaticStore.GetGroupOrNullAsync(name);
var dynamicDefinition = await _dynamicStore.GetGroupOrNullAsync(name); var dynamicGroupDefinition = await DynamicStore.GetGroupOrNullAsync(name);
if (staticDefinition != null && dynamicDefinition != null) return await GetGroupDefinitionAsync(staticGroupDefinition, dynamicGroupDefinition);
{ }
switch (_webhooksOptions.DynamicWebhookStrategy)
{ public async virtual Task<IReadOnlyList<WebhookGroupDefinition>> GetGroupsAsync()
case DynamicWebhookStrategy.Ignore: {
return staticDefinition; var staticGroupDefinitions = await StaticStore.GetGroupsAsync();
case DynamicWebhookStrategy.Covering: var dynamicGroupDefinitions = await DynamicStore.GetGroupsAsync();
return dynamicDefinition;
case DynamicWebhookStrategy.Merge:
MergeGroupWebhooks(staticDefinition, dynamicDefinition);
return staticDefinition;
default:
return staticDefinition;
}
}
return staticDefinition ?? dynamicDefinition; return await GetGroupDefinitionsAsync(staticGroupDefinitions, dynamicGroupDefinitions);
} }
public async virtual Task<WebhookGroupDefinition> GetGroupAsync(string name) public async virtual Task<WebhookDefinition?> GetOrNullAsync(string name)
{ {
var webhookGroup = await GetGroupOrNullAsync(name); Check.NotNull(name, nameof(name));
if (webhookGroup == null)
{
throw new AbpException("Undefined webhook group: " + name);
}
return webhookGroup; var staticDefinition = await StaticStore.GetOrNullAsync(name);
var dynamicDefinition = await DynamicStore.GetOrNullAsync(name);
return await GetDefinitionAsync(staticDefinition, dynamicDefinition);
} }
public async virtual Task<IReadOnlyList<WebhookGroupDefinition>> GetGroupsAsync() public async virtual Task<IReadOnlyList<WebhookDefinition>> GetWebhooksAsync()
{ {
var staticGroups = await _staticStore.GetGroupsAsync(); var staticDefinitions = await StaticStore.GetWebhooksAsync();
var dynamicGroups = await _dynamicStore.GetGroupsAsync(); var dynamicDefinitions = await DynamicStore.GetWebhooksAsync();
// 根据策略处理分组定义 return await GetDefinitionsAsync(staticDefinitions, dynamicDefinitions);
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<bool> IsAvailableAsync(Guid? tenantId, string name) public async virtual Task<bool> IsAvailableAsync(Guid? tenantId, string name)
{ {
if (tenantId == null) // host allowed to subscribe all webhooks if (tenantId == null)
{ {
return true; return true;
} }
@ -149,8 +98,8 @@ internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonD
return true; return true;
} }
var currentTenant = _serviceProvider.GetRequiredService<ICurrentTenant>(); var currentTenant = ServiceProvider.GetRequiredService<ICurrentTenant>();
var featureChecker = _serviceProvider.GetRequiredService<IFeatureChecker>(); var featureChecker = ServiceProvider.GetRequiredService<IFeatureChecker>();
using (currentTenant.Change(tenantId)) using (currentTenant.Change(tenantId))
{ {
if (!await featureChecker.IsEnabledAsync(true, webhookDefinition.RequiredFeatures.ToArray())) if (!await featureChecker.IsEnabledAsync(true, webhookDefinition.RequiredFeatures.ToArray()))
@ -162,111 +111,34 @@ internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonD
return true; return true;
} }
#region Webhook定义策略 protected override string GetDefinitionKey(WebhookDefinition definition)
/// <summary>
/// 忽略策略:静态优先,过滤掉同名的动态Webhook
/// </summary>
protected virtual Task<IReadOnlyList<WebhookDefinition>> GetWebhooksWithIgnoreStrategy(
IReadOnlyList<WebhookDefinition> staticWebhooks,
IReadOnlyList<WebhookDefinition> dynamicWebhooks)
{ {
var staticWebhookNames = staticWebhooks return definition.Name;
.Select(p => p.Name)
.ToImmutableHashSet();
return Task.FromResult<IReadOnlyList<WebhookDefinition>>(
staticWebhooks
.Concat(dynamicWebhooks.Where(d => !staticWebhookNames.Contains(d.Name)))
.ToImmutableList()
);
} }
/// <summary> protected override string GetGroupDefinitionKey(WebhookGroupDefinition groupDefinition)
/// 覆盖策略:动态完全覆盖静态Webhook
/// </summary>
protected virtual Task<IReadOnlyList<WebhookDefinition>> GetWebhooksWithCoveringStrategy(
IReadOnlyList<WebhookDefinition> staticWebhooks,
IReadOnlyList<WebhookDefinition> dynamicWebhooks)
{ {
var dynamicWebhookNames = dynamicWebhooks return groupDefinition.Name;
.Select(p => p.Name)
.ToImmutableHashSet();
// 动态Webhook完全覆盖静态Webhook
var result = dynamicWebhooks
.Concat(staticWebhooks.Where(s => !dynamicWebhookNames.Contains(s.Name)))
.ToImmutableList();
return Task.FromResult<IReadOnlyList<WebhookDefinition>>(result);
} }
/// <summary> protected override Task<WebhookDefinition> MergeDefinitionAsync(WebhookDefinition targetDefinition, WebhookDefinition sourceDefinition)
/// 合并策略:合并静态和动态Webhook,创建新实例
/// </summary>
protected virtual Task<IReadOnlyList<WebhookDefinition>> GetWebhooksWithMergeStrategy(
IReadOnlyList<WebhookDefinition> staticWebhooks,
IReadOnlyList<WebhookDefinition> dynamicWebhooks)
{ {
var mergedWebhooks = new Dictionary<string, WebhookDefinition>(); var displayName = sourceDefinition.DisplayName ?? targetDefinition.DisplayName;
var description = sourceDefinition.Description ?? targetDefinition.Description;
// 先添加所有静态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<IReadOnlyList<WebhookDefinition>>(mergedWebhooks.Values.ToImmutableList());
}
/// <summary>
/// 合并两个Webhook定义,返回新的 WebhookDefinition 实例
/// </summary>
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( var mergedWebhook = new WebhookDefinition(
staticWebhook.Name, // 保持名称不变 targetDefinition.Name,
displayName, displayName,
description description
); )
// 设置分组名称(优先使用动态的)
if (!string.IsNullOrEmpty(dynamicWebhook.GroupName))
{
mergedWebhook.GroupName = dynamicWebhook.GroupName;
}
else if (!string.IsNullOrEmpty(staticWebhook.GroupName))
{ {
mergedWebhook.GroupName = staticWebhook.GroupName; GroupName =
} !string.IsNullOrWhiteSpace(sourceDefinition.GroupName)
? sourceDefinition.GroupName
: targetDefinition.GroupName
};
// 合并必需的功能特性 foreach (var feature in targetDefinition.RequiredFeatures)
foreach (var feature in staticWebhook.RequiredFeatures)
{ {
if (!mergedWebhook.RequiredFeatures.Contains(feature)) if (!mergedWebhook.RequiredFeatures.Contains(feature))
{ {
@ -274,125 +146,42 @@ internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonD
} }
} }
foreach (var feature in dynamicWebhook.RequiredFeatures) foreach (var feature in sourceDefinition.RequiredFeatures)
{ {
if (!mergedWebhook.RequiredFeatures.Contains(feature)) if (!mergedWebhook.RequiredFeatures.Contains(feature))
{ {
mergedWebhook.RequiredFeatures.Add(feature); mergedWebhook.RequiredFeatures.Add(feature);
} }
} }
// 合并属性(动态覆盖静态) foreach (var property in targetDefinition.Properties)
foreach (var property in staticWebhook.Properties)
{ {
mergedWebhook.Properties[property.Key] = property.Value; mergedWebhook.Properties[property.Key] = property.Value;
} }
foreach (var property in dynamicWebhook.Properties) foreach (var property in sourceDefinition.Properties)
{ {
mergedWebhook.Properties[property.Key] = property.Value; mergedWebhook.Properties[property.Key] = property.Value;
} }
return mergedWebhook; return Task.FromResult(mergedWebhook);
}
#endregion
#region 分组定义策略
/// <summary>
/// 忽略策略:静态优先,过滤掉同名的动态分组
/// </summary>
protected virtual Task<IReadOnlyList<WebhookGroupDefinition>> GetGroupsWithIgnoreStrategy(
IReadOnlyList<WebhookGroupDefinition> staticGroups,
IReadOnlyList<WebhookGroupDefinition> dynamicGroups)
{
var staticGroupNames = staticGroups
.Select(p => p.Name)
.ToImmutableHashSet();
return Task.FromResult<IReadOnlyList<WebhookGroupDefinition>>(
staticGroups
.Concat(dynamicGroups.Where(d => !staticGroupNames.Contains(d.Name)))
.ToImmutableList()
);
}
/// <summary>
/// 覆盖策略:动态完全覆盖静态分组
/// </summary>
protected virtual Task<IReadOnlyList<WebhookGroupDefinition>> GetGroupsWithCoveringStrategy(
IReadOnlyList<WebhookGroupDefinition> staticGroups,
IReadOnlyList<WebhookGroupDefinition> dynamicGroups)
{
var dynamicGroupNames = dynamicGroups
.Select(p => p.Name)
.ToImmutableHashSet();
var result = dynamicGroups
.Concat(staticGroups.Where(s => !dynamicGroupNames.Contains(s.Name)))
.ToImmutableList();
return Task.FromResult<IReadOnlyList<WebhookGroupDefinition>>(result);
}
/// <summary>
/// 合并策略:合并静态和动态分组
/// </summary>
protected virtual Task<IReadOnlyList<WebhookGroupDefinition>> GetGroupsWithMergeStrategy(
IReadOnlyList<WebhookGroupDefinition> staticGroups,
IReadOnlyList<WebhookGroupDefinition> dynamicGroups)
{
var mergedGroups = new Dictionary<string, WebhookGroupDefinition>();
// 先添加所有静态分组
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<IReadOnlyList<WebhookGroupDefinition>>(
mergedGroups.Values.ToImmutableList()
);
} }
/// <summary> protected override Task MergeGroupDefinitionAsync(WebhookGroupDefinition targetGroupDefinition, WebhookGroupDefinition sourceGroupDefinition)
/// 合并分组的Webhook列表
/// </summary>
private void MergeGroupWebhooks(WebhookGroupDefinition target, WebhookGroupDefinition source)
{ {
foreach (var sourceWebhook in source.Webhooks) foreach (var sourceWebhook in sourceGroupDefinition.Webhooks)
{ {
var existingWebhook = target.GetWebhookOrNull(sourceWebhook.Name); var existingWebhook = targetGroupDefinition.GetWebhookOrNull(sourceWebhook.Name);
if (existingWebhook == null) if (existingWebhook == null)
{ {
// Webhook不存在,直接添加 var newWebhook = targetGroupDefinition.AddWebhook(
var newWebhook = target.AddWebhook(
sourceWebhook.Name, sourceWebhook.Name,
sourceWebhook.DisplayName, sourceWebhook.DisplayName,
sourceWebhook.Description sourceWebhook.Description
); );
newWebhook.GroupName = targetGroupDefinition.Name;
// 设置分组名称
newWebhook.GroupName = target.Name;
// 复制必需的功能特性
foreach (var feature in sourceWebhook.RequiredFeatures) foreach (var feature in sourceWebhook.RequiredFeatures)
{ {
if (!newWebhook.RequiredFeatures.Contains(feature)) if (!newWebhook.RequiredFeatures.Contains(feature))
@ -401,7 +190,6 @@ internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonD
} }
} }
// 复制属性
foreach (var property in sourceWebhook.Properties) foreach (var property in sourceWebhook.Properties)
{ {
newWebhook.Properties[property.Key] = property.Value; newWebhook.Properties[property.Key] = property.Value;
@ -409,13 +197,11 @@ internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonD
} }
else else
{ {
// Webhook已存在,合并属性
foreach (var property in sourceWebhook.Properties) foreach (var property in sourceWebhook.Properties)
{ {
existingWebhook.Properties[property.Key] = property.Value; existingWebhook.Properties[property.Key] = property.Value;
} }
// 合并必需的功能特性
foreach (var feature in sourceWebhook.RequiredFeatures) foreach (var feature in sourceWebhook.RequiredFeatures)
{ {
if (!existingWebhook.RequiredFeatures.Contains(feature)) if (!existingWebhook.RequiredFeatures.Contains(feature))
@ -424,23 +210,19 @@ internal class WebhookDefinitionManager : IWebhookDefinitionManager, ISingletonD
} }
} }
// 更新显示名称(如果源提供了)
if (sourceWebhook.DisplayName != null) if (sourceWebhook.DisplayName != null)
{ {
existingWebhook.DisplayName = sourceWebhook.DisplayName; existingWebhook.DisplayName = sourceWebhook.DisplayName;
} }
// 更新描述(如果源提供了)
if (sourceWebhook.Description != null) if (sourceWebhook.Description != null)
{ {
existingWebhook.Description = sourceWebhook.Description; existingWebhook.Description = sourceWebhook.Description;
} }
// 更新分组名称(确保保持一致) existingWebhook.GroupName = targetGroupDefinition.Name;
existingWebhook.GroupName = target.Name;
} }
} }
return Task.CompletedTask;
} }
}
#endregion
}

Loading…
Cancel
Save