diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/AbpSettingManagementDbProperties.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/AbpSettingManagementDbProperties.cs
new file mode 100644
index 0000000..82c3574
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/AbpSettingManagementDbProperties.cs
@@ -0,0 +1,13 @@
+using Volo.Abp.Data;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+///
+/// Abp设置管理器数据库参数
+///
+public class AbpSettingManagementDbProperties
+{
+ public static string DbTablePrefix { get; set; } = AbpCommonDbProperties.DbTablePrefix;
+ public static string? DbSchema { get; set; } = AbpCommonDbProperties.DbSchema;
+ public const string ConnectionStringName = "AbpSettingManagement";
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Caching/SettingCacheItem.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Caching/SettingCacheItem.cs
new file mode 100644
index 0000000..76320b0
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Caching/SettingCacheItem.cs
@@ -0,0 +1,47 @@
+using JetBrains.Annotations;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.Text.Formatting;
+
+namespace NPin.Framework.SettingManagement.Domain.Caching;
+
+///
+/// 设置缓存项
+/// 忽略多租户
+///
+[Serializable]
+[IgnoreMultiTenancy]
+public class SettingCacheItem
+{
+ ///
+ /// 缓存Key格式化参数
+ /// pn: providerName
+ /// pk: providerKey
+ /// n: name
+ ///
+ private const string CacheKeyFormat = "pn:{0},pk:{1},n:{2}";
+
+ ///
+ /// 值
+ ///
+ public string? Value { get; set; }
+
+ public SettingCacheItem()
+ {
+ }
+
+ public SettingCacheItem(string? value)
+ {
+ Value = value;
+ }
+
+ public static string CalculateCacheKey(string name, string providerName, string providerKey)
+ {
+ return string.Format(CacheKeyFormat, providerName, providerKey, name);
+ }
+
+ public static string? GetSettingNameFormCacheKeyOrNull(string cacheKey)
+ {
+ var result = FormattedStringValueExtracter.Extract(cacheKey, CacheKeyFormat, true);
+ return result.IsMatch ? result.Matches.Last().Value : null;
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Caching/SettingCacheItemInvalidator.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Caching/SettingCacheItemInvalidator.cs
new file mode 100644
index 0000000..71c030b
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Caching/SettingCacheItemInvalidator.cs
@@ -0,0 +1,38 @@
+using NPin.Framework.SettingManagement.Domain.Entities;
+using Volo.Abp.Caching;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Domain.Entities.Events;
+using Volo.Abp.EventBus;
+
+namespace NPin.Framework.SettingManagement.Domain.Caching;
+
+///
+/// 设置缓存项 过期
+/// 变动时 过期
+///
+public class SettingCacheItemInvalidator : ILocalEventHandler>,
+ ITransientDependency
+{
+ protected IDistributedCache Cache { get; }
+
+ public SettingCacheItemInvalidator(IDistributedCache cache)
+ {
+ Cache = cache;
+ }
+
+ public virtual async Task HandleEventAsync(EntityChangedEventData eventData)
+ {
+ var entity = eventData.Entity;
+ var cacheKey = CalculateCacheKey(
+ entity.Name,
+ entity.ProviderName,
+ entity.ProviderKey);
+
+ await Cache.RemoveAsync(cacheKey, considerUow: true);
+ }
+
+ protected virtual string CalculateCacheKey(string name, string providerName, string providerKey)
+ {
+ return SettingCacheItem.CalculateCacheKey(name, providerName, providerKey);
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Entities/SettingEntity.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Entities/SettingEntity.cs
new file mode 100644
index 0000000..4fffffb
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Entities/SettingEntity.cs
@@ -0,0 +1,46 @@
+using JetBrains.Annotations;
+using Volo.Abp.Domain.Entities;
+
+namespace NPin.Framework.SettingManagement.Domain.Entities;
+
+public class SettingEntity: Entity, IAggregateRoot
+{
+ [NotNull]
+ public virtual string Name { get; protected set; }
+
+ [NotNull]
+ public virtual string Value { get; internal set; }
+
+ [CanBeNull]
+ public virtual string ProviderName { get; protected set; }
+
+ [CanBeNull]
+ public virtual string ProviderKey { get; protected set; }
+
+ public SettingEntity()
+ {
+
+ }
+
+ public SettingEntity(
+ Guid id,
+ [NotNull] string name,
+ [NotNull] string value,
+ [CanBeNull] string providerName = null,
+ [CanBeNull] string providerKey = null)
+ {
+ Check.NotNull(name, nameof(name));
+ Check.NotNull(value, nameof(value));
+
+ Id = id;
+ Name = name;
+ Value = value;
+ ProviderName = providerName;
+ ProviderKey = providerKey;
+ }
+
+ public override string ToString()
+ {
+ return $"{base.ToString()}, Name = {Name}, Value = {Value}, ProviderName = {ProviderName}, ProviderKey = {ProviderKey}";
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/ISettingManager.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/ISettingManager.cs
new file mode 100644
index 0000000..d527b3d
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/ISettingManager.cs
@@ -0,0 +1,16 @@
+using JetBrains.Annotations;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+///
+/// 设置管理器
+///
+public interface ISettingManager
+{
+ Task GetOrNullAsync([NotNull] string name, [NotNull] string providerName, string? providerKey, bool fallback = true);
+
+ Task> GetAllAsync([NotNull] string providerName, string? providerKey, bool fallback = true);
+
+ Task SetAsync([NotNull] string name, string? value, [NotNull] string providerName, string? providerKey, bool forceToSet = false);
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/ISettingRepository.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/ISettingRepository.cs
new file mode 100644
index 0000000..768c36a
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/ISettingRepository.cs
@@ -0,0 +1,24 @@
+using NPin.Framework.SettingManagement.Domain.Entities;
+using Volo.Abp.Domain.Repositories;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+public interface ISettingRepository: IBasicRepository
+{
+ Task FindAsync(
+ string name,
+ string providerName,
+ string providerKey,
+ CancellationToken cancellationToken = default);
+
+ Task> GetListAsync(
+ string providerName,
+ string providerKey,
+ CancellationToken cancellationToken = default);
+
+ Task> GetListAsync(
+ string[] names,
+ string providerName,
+ string providerKey,
+ CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/NPin.Framework.SettingManagement.Domain.csproj b/module/setting-management/NPin.Framework.SettingManagement.Domain/NPin.Framework.SettingManagement.Domain.csproj
new file mode 100644
index 0000000..1aaa0fc
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/NPin.Framework.SettingManagement.Domain.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/NPinFrameworkSettingManagementDomainModule.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/NPinFrameworkSettingManagementDomainModule.cs
new file mode 100644
index 0000000..967f544
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/NPinFrameworkSettingManagementDomainModule.cs
@@ -0,0 +1,28 @@
+using NPin.Framework.SettingManagement.Domain.Options;
+using Volo.Abp.Caching;
+using Volo.Abp.Domain;
+using Volo.Abp.SettingManagement;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+[DependsOn(
+ typeof(AbpSettingsModule),
+ typeof(AbpDddDomainModule),
+ typeof(AbpSettingManagementDomainSharedModule),
+ typeof(AbpCachingModule)
+)]
+public class NPinFrameworkSettingManagementDomainModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.Providers.Add();
+ options.Providers.Add();
+ options.Providers.Add();
+ options.Providers.Add();
+ options.Providers.Add();
+ });
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Options/SettingManagementOptions.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Options/SettingManagementOptions.cs
new file mode 100644
index 0000000..5ce2180
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Options/SettingManagementOptions.cs
@@ -0,0 +1,13 @@
+using Volo.Abp.Collections;
+
+namespace NPin.Framework.SettingManagement.Domain.Options;
+
+public class SettingManagementOptions
+{
+ public ITypeList Providers { get; }
+
+ public SettingManagementOptions()
+ {
+ Providers = new TypeList();
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/ConfigurationSettingManagementProvider.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/ConfigurationSettingManagementProvider.cs
new file mode 100644
index 0000000..dcade41
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/ConfigurationSettingManagementProvider.cs
@@ -0,0 +1,32 @@
+using Microsoft.Extensions.Configuration;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+public class ConfigurationSettingManagementProvider: ISettingManagementProvider, ITransientDependency
+{
+ public string Name => ConfigurationSettingValueProvider.ProviderName;
+
+ protected IConfiguration Configuration { get; }
+
+ public ConfigurationSettingManagementProvider(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public virtual Task GetOrNullAsync(SettingDefinition setting, string providerKey)
+ {
+ return Task.FromResult(Configuration[ConfigurationSettingValueProvider.ConfigurationNamePrefix + setting.Name]);
+ }
+
+ public Task SetAsync(SettingDefinition setting, string value, string providerKey)
+ {
+ throw new AbpException($"Can not set a setting value to the application configuration.");
+ }
+
+ public Task ClearAsync(SettingDefinition setting, string providerKey)
+ {
+ throw new AbpException($"Can not clear provider to the application configuration.");
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/DefaultValueSettingManagementProvider.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/DefaultValueSettingManagementProvider.cs
new file mode 100644
index 0000000..c857036
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/DefaultValueSettingManagementProvider.cs
@@ -0,0 +1,26 @@
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+public class DefaultValueSettingManagementProvider : ISettingManagementProvider, ISingletonDependency
+{
+ public string Name => DefaultValueSettingValueProvider.ProviderName;
+
+ public virtual Task GetOrNullAsync(SettingDefinition setting, string providerKey)
+ {
+ return Task.FromResult(setting.DefaultValue);
+ }
+
+ public Task SetAsync(SettingDefinition setting, string value, string providerKey)
+ {
+ throw new AbpException(
+ $"Can not set default value of a setting. It is only possible while defining the setting in a {typeof(ISettingDefinitionProvider)} implementation.");
+ }
+
+ public Task ClearAsync(SettingDefinition setting, string providerKey)
+ {
+ throw new AbpException(
+ $"Can not clear default value of a setting. It is only possible while defining the setting in a {typeof(ISettingDefinitionProvider)} implementation.");
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/ConfigurationValueSettingManagerExtensions.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/ConfigurationValueSettingManagerExtensions.cs
new file mode 100644
index 0000000..3c5563d
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/ConfigurationValueSettingManagerExtensions.cs
@@ -0,0 +1,19 @@
+using JetBrains.Annotations;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain.Provider.Extensions;
+
+public static class ConfigurationValueSettingManagerExtensions
+{
+ public static Task GetOrNullConfigurationAsync(this ISettingManager settingManager, [NotNull] string name,
+ bool fallback = true)
+ {
+ return settingManager.GetOrNullAsync(name, ConfigurationSettingValueProvider.ProviderName, null, fallback);
+ }
+
+ public static Task> GetAllConfigurationAsync(this ISettingManager settingManager,
+ bool fallback = true)
+ {
+ return settingManager.GetAllAsync(ConfigurationSettingValueProvider.ProviderName, null, fallback);
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/DefaultValueSettingManagerExtensions.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/DefaultValueSettingManagerExtensions.cs
new file mode 100644
index 0000000..6c1e352
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/DefaultValueSettingManagerExtensions.cs
@@ -0,0 +1,18 @@
+using JetBrains.Annotations;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain.Provider.Extensions;
+
+public static class DefaultValueSettingManagerExtensions
+{
+ public static Task GetOrNullDefaultAsync(this ISettingManager settingManager, [NotNull] string name,
+ bool fallback = true)
+ {
+ return settingManager.GetOrNullAsync(name, DefaultValueSettingValueProvider.ProviderName, null, fallback);
+ }
+
+ public static Task> GetAllDefaultAsync(this ISettingManager settingManager, bool fallback = true)
+ {
+ return settingManager.GetAllAsync(DefaultValueSettingValueProvider.ProviderName, null, fallback);
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/GlobalSettingManagerExtensions.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/GlobalSettingManagerExtensions.cs
new file mode 100644
index 0000000..2c27d46
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/GlobalSettingManagerExtensions.cs
@@ -0,0 +1,24 @@
+using JetBrains.Annotations;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain.Provider.Extensions;
+
+public static class GlobalSettingManagerExtensions
+{
+ public static Task GetOrNullGlobalAsync(this ISettingManager settingManager, [NotNull] string name,
+ bool fallback = true)
+ {
+ return settingManager.GetOrNullAsync(name, GlobalSettingValueProvider.ProviderName, null, fallback);
+ }
+
+ public static Task> GetAllGlobalAsync(this ISettingManager settingManager, bool fallback = true)
+ {
+ return settingManager.GetAllAsync(GlobalSettingValueProvider.ProviderName, null, fallback);
+ }
+
+ public static Task SetGlobalAsync(this ISettingManager settingManager, [NotNull] string name,
+ string? value)
+ {
+ return settingManager.SetAsync(name, value, GlobalSettingValueProvider.ProviderName, null);
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/TenantSettingManagerExtensions.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/TenantSettingManagerExtensions.cs
new file mode 100644
index 0000000..3ac43e3
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/TenantSettingManagerExtensions.cs
@@ -0,0 +1,53 @@
+using JetBrains.Annotations;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain.Provider.Extensions;
+
+public static class TenantSettingManagerExtensions
+{
+ public static Task GetOrNullForTenantAsync(this ISettingManager settingManager, [NotNull] string name,
+ Guid tenantId, bool fallback = true)
+ {
+ return settingManager.GetOrNullAsync(name, TenantSettingValueProvider.ProviderName, tenantId.ToString(),
+ fallback);
+ }
+
+ public static Task GetOrNullForCurrentTenantAsync(this ISettingManager settingManager,
+ [NotNull] string name, bool fallback = true)
+ {
+ return settingManager.GetOrNullAsync(name, TenantSettingValueProvider.ProviderName, null, fallback);
+ }
+
+ public static Task> GetAllForTenantAsync(this ISettingManager settingManager, Guid tenantId,
+ bool fallback = true)
+ {
+ return settingManager.GetAllAsync(TenantSettingValueProvider.ProviderName, tenantId.ToString(), fallback);
+ }
+
+ public static Task> GetAllForCurrentTenantAsync(this ISettingManager settingManager,
+ bool fallback = true)
+ {
+ return settingManager.GetAllAsync(TenantSettingValueProvider.ProviderName, null, fallback);
+ }
+
+ public static Task SetForTenantAsync(this ISettingManager settingManager, Guid tenantId, [NotNull] string name,
+ string? value, bool forceToSet = false)
+ {
+ return settingManager.SetAsync(name, value, TenantSettingValueProvider.ProviderName, tenantId.ToString(),
+ forceToSet);
+ }
+
+ public static Task SetForCurrentTenantAsync(this ISettingManager settingManager, [NotNull] string name,
+ string? value, bool forceToSet = false)
+ {
+ return settingManager.SetAsync(name, value, TenantSettingValueProvider.ProviderName, null, forceToSet);
+ }
+
+ public static Task SetForTenantOrGlobalAsync(this ISettingManager settingManager, Guid? tenantId,
+ [NotNull] string name, string? value, bool forceToSet = false)
+ {
+ return tenantId.HasValue
+ ? settingManager.SetForTenantAsync(tenantId.Value, name, value, forceToSet)
+ : settingManager.SetGlobalAsync(name, value);
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/UserSettingManagerExtensions.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/UserSettingManagerExtensions.cs
new file mode 100644
index 0000000..2052c97
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/Extensions/UserSettingManagerExtensions.cs
@@ -0,0 +1,44 @@
+using JetBrains.Annotations;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain.Provider.Extensions;
+
+public static class UserSettingManagerExtensions
+{
+ public static Task GetOrNullForUserAsync(this ISettingManager settingManager, [NotNull] string name,
+ Guid userId, bool fallback = true)
+ {
+ return settingManager.GetOrNullAsync(name, UserSettingValueProvider.ProviderName, userId.ToString(), fallback);
+ }
+
+ public static Task GetOrNullForCurrentUserAsync(this ISettingManager settingManager, [NotNull] string name,
+ bool fallback = true)
+ {
+ return settingManager.GetOrNullAsync(name, UserSettingValueProvider.ProviderName, null, fallback);
+ }
+
+ public static Task> GetAllForUserAsync(this ISettingManager settingManager, Guid userId,
+ bool fallback = true)
+ {
+ return settingManager.GetAllAsync(UserSettingValueProvider.ProviderName, userId.ToString(), fallback);
+ }
+
+ public static Task> GetAllForCurrentUserAsync(this ISettingManager settingManager,
+ bool fallback = true)
+ {
+ return settingManager.GetAllAsync(UserSettingValueProvider.ProviderName, null, fallback);
+ }
+
+ public static Task SetForUserAsync(this ISettingManager settingManager, Guid userId, [NotNull] string name,
+ string? value, bool forceToSet = false)
+ {
+ return settingManager.SetAsync(name, value, UserSettingValueProvider.ProviderName, userId.ToString(),
+ forceToSet);
+ }
+
+ public static Task SetForCurrentUserAsync(this ISettingManager settingManager, [NotNull] string name, string? value,
+ bool forceToSet = false)
+ {
+ return settingManager.SetAsync(name, value, UserSettingValueProvider.ProviderName, null, forceToSet);
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/GlobalSettingManagementProvider.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/GlobalSettingManagementProvider.cs
new file mode 100644
index 0000000..87567bf
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/GlobalSettingManagementProvider.cs
@@ -0,0 +1,18 @@
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+public class GlobalSettingManagementProvider: SettingManagementProvider, ITransientDependency
+{
+ public override string Name => GlobalSettingValueProvider.ProviderName;
+
+ public GlobalSettingManagementProvider(ISettingManagementStore settingManagementStore) : base(settingManagementStore)
+ {
+ }
+
+ protected override string? NormalizeProviderKey(string providerKey)
+ {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/ISettingManagementProvider.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/ISettingManagementProvider.cs
new file mode 100644
index 0000000..414c916
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/ISettingManagementProvider.cs
@@ -0,0 +1,34 @@
+using JetBrains.Annotations;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+public interface ISettingManagementProvider
+{
+ string Name { get; }
+
+ ///
+ /// 获取配置项
+ ///
+ ///
+ ///
+ ///
+ Task GetOrNullAsync([NotNull] SettingDefinition setting, [CanBeNull] string providerKey);
+
+ ///
+ /// 设置配置数据
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task SetAsync([NotNull] SettingDefinition setting, [NotNull] string value, [CanBeNull] string providerKey);
+
+ ///
+ /// 清除配置项
+ ///
+ ///
+ ///
+ ///
+ Task ClearAsync([NotNull] SettingDefinition setting, [CanBeNull] string providerKey);
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/TenantSettingManagementProvider.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/TenantSettingManagementProvider.cs
new file mode 100644
index 0000000..2f17537
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/TenantSettingManagementProvider.cs
@@ -0,0 +1,28 @@
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+public class TenantSettingManagementProvider : SettingManagementProvider, ITransientDependency
+{
+ public override string Name => TenantSettingValueProvider.ProviderName;
+
+ protected ICurrentTenant CurrentTenant { get; }
+
+ public TenantSettingManagementProvider(ISettingManagementStore settingManagementStore, ICurrentTenant currentTenant)
+ : base(settingManagementStore)
+ {
+ CurrentTenant = currentTenant;
+ }
+
+ protected override string? NormalizeProviderKey(string? providerKey)
+ {
+ if (providerKey != null)
+ {
+ return providerKey;
+ }
+
+ return CurrentTenant.Id?.ToString();
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/UserSettingManagementProvider.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/UserSettingManagementProvider.cs
new file mode 100644
index 0000000..46509e4
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Provider/UserSettingManagementProvider.cs
@@ -0,0 +1,28 @@
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Settings;
+using Volo.Abp.Users;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+public class UserSettingManagementProvider : SettingManagementProvider, ITransientDependency
+{
+ public override string Name => UserSettingValueProvider.ProviderName;
+
+ protected ICurrentUser CurrentUser { get; }
+
+ public UserSettingManagementProvider(ISettingManagementStore settingManagementStore, ICurrentUser currentUser) :
+ base(settingManagementStore)
+ {
+ CurrentUser = currentUser;
+ }
+
+ protected override string? NormalizeProviderKey(string? providerKey)
+ {
+ if (providerKey != null)
+ {
+ return providerKey;
+ }
+
+ return CurrentUser?.Id.ToString();
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/SettingManagementProvider.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/SettingManagementProvider.cs
new file mode 100644
index 0000000..3118321
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/SettingManagementProvider.cs
@@ -0,0 +1,38 @@
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+///
+/// 设置管理器提供者
+///
+public abstract class SettingManagementProvider : ISettingManagementProvider
+{
+ public abstract string Name { get; }
+
+ protected ISettingManagementStore SettingManagementStore { get; }
+
+ public SettingManagementProvider(ISettingManagementStore settingManagementStore)
+ {
+ SettingManagementStore = settingManagementStore;
+ }
+
+ public virtual async Task GetOrNullAsync(SettingDefinition setting, string providerKey)
+ {
+ return await SettingManagementStore.GetOrNullAsync(setting.Name, Name, NormalizeProviderKey(providerKey));
+ }
+
+ public virtual async Task SetAsync(SettingDefinition setting, string value, string providerKey)
+ {
+ await SettingManagementStore.SetAsync(setting.Name, value, Name, NormalizeProviderKey(providerKey));
+ }
+
+ public virtual async Task ClearAsync(SettingDefinition setting, string providerKey)
+ {
+ await SettingManagementStore.DeleteAsync(setting.Name, Name, NormalizeProviderKey(providerKey));
+ }
+
+ protected virtual string? NormalizeProviderKey(string? providerKey)
+ {
+ return providerKey;
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/SettingManager.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/SettingManager.cs
new file mode 100644
index 0000000..262f981
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/SettingManager.cs
@@ -0,0 +1,200 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using NPin.Framework.SettingManagement.Domain.Options;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+public class SettingManager : ISettingManager, ISingletonDependency
+{
+ private readonly Lazy> _lazyProviders;
+ protected ISettingDefinitionManager SettingDefinitionManager { get; }
+ protected ISettingEncryptionService SettingEncryptionService { get; }
+ protected List Providers => _lazyProviders.Value;
+ protected SettingManagementOptions Options { get; }
+
+ public SettingManager(
+ IServiceProvider serviceProvider,
+ ISettingDefinitionManager settingDefinitionManager,
+ ISettingEncryptionService settingEncryptionService,
+ IOptions options)
+ {
+ SettingDefinitionManager = settingDefinitionManager;
+ SettingEncryptionService = settingEncryptionService;
+ Options = options.Value;
+
+ // TODO use IServiceScopeFactory and create a scope ?
+ _lazyProviders = new Lazy>(
+ () => Options
+ .Providers
+ .Select(c => serviceProvider.GetRequiredService(c) as ISettingManagementProvider)
+ .ToList(),
+ true
+ );
+ }
+
+ public virtual Task GetOrNullAsync(string name, string providerName, string? providerKey,
+ bool fallback = true)
+ {
+ Check.NotNull(name, nameof(name));
+ Check.NotNull(providerName, nameof(providerName));
+
+ return GetOrNullInternalAsync(name, providerName, providerKey, fallback);
+ }
+
+ public virtual async Task> GetAllAsync(string providerName, string? providerKey,
+ bool fallback = true)
+ {
+ Check.NotNull(providerName, nameof(providerName));
+
+ var settingDefinitions = await SettingDefinitionManager.GetAllAsync();
+ var providers = Enumerable.Reverse(Providers)
+ .SkipWhile(c => c.Name != providerName);
+
+ if (!fallback)
+ {
+ providers = providers.TakeWhile(c => c.Name == providerName);
+ }
+
+ var providerList = providers.Reverse().ToList();
+
+ if (!providerList.Any())
+ {
+ return new List();
+ }
+
+ var settingValues = new Dictionary();
+
+ foreach (var setting in settingDefinitions)
+ {
+ string value = null;
+
+ if (setting.IsInherited)
+ {
+ foreach (var provider in providerList)
+ {
+ var providerValue = await provider.GetOrNullAsync(
+ setting,
+ provider.Name == providerName ? providerKey : null
+ );
+ if (providerValue != null)
+ {
+ value = providerValue;
+ }
+ }
+ }
+ else
+ {
+ value = await providerList[0].GetOrNullAsync(
+ setting,
+ providerKey
+ );
+ }
+
+ if (setting.IsEncrypted)
+ {
+ value = SettingEncryptionService.Decrypt(setting, value);
+ }
+
+ if (value != null)
+ {
+ settingValues[setting.Name] = new SettingValue(setting.Name, value);
+ }
+ }
+
+ return settingValues.Values.ToList();
+ }
+
+ public virtual async Task SetAsync(string name, string? value, string providerName, string? providerKey,
+ bool forceToSet = false)
+ {
+ Check.NotNull(name, nameof(name));
+ Check.NotNull(providerName, nameof(providerName));
+
+ var setting = await SettingDefinitionManager.GetAsync(name);
+
+ var providers = Enumerable
+ .Reverse(Providers)
+ .SkipWhile(p => p.Name != providerName)
+ .ToList();
+
+ if (!providers.Any())
+ {
+ return;
+ }
+
+ if (setting.IsEncrypted)
+ {
+ value = SettingEncryptionService.Encrypt(setting, value);
+ }
+
+ if (providers.Count > 1 && !forceToSet && setting.IsInherited && value != null)
+ {
+ var fallbackValue = await GetOrNullInternalAsync(name, providers[1].Name, null);
+ if (fallbackValue == value)
+ {
+ //Clear the value if it's same as it's fallback value
+ value = null;
+ }
+ }
+
+ providers = providers
+ .TakeWhile(p => p.Name == providerName)
+ .ToList(); //Getting list for case of there are more than one provider with same providerName
+
+ if (value == null)
+ {
+ foreach (var provider in providers)
+ {
+ await provider.ClearAsync(setting, providerKey);
+ }
+ }
+ else
+ {
+ foreach (var provider in providers)
+ {
+ await provider.SetAsync(setting, value, providerKey);
+ }
+ }
+ }
+
+ protected virtual async Task GetOrNullInternalAsync(string name, string? providerName, string? providerKey,
+ bool fallback = true)
+ {
+ var setting = await SettingDefinitionManager.GetAsync(name);
+ var providers = Enumerable
+ .Reverse(Providers);
+
+ if (providerName != null)
+ {
+ providers = providers.SkipWhile(c => c.Name != providerName);
+ }
+
+ if (!fallback || !setting.IsInherited)
+ {
+ providers = providers.TakeWhile(c => c.Name == providerName);
+ }
+
+ string value = null;
+ foreach (var provider in providers)
+ {
+ value = await provider.GetOrNullAsync(
+ setting,
+ provider.Name == providerName ? providerKey : null
+ );
+
+ if (value != null)
+ {
+ break;
+ }
+ }
+
+ if (setting.IsEncrypted)
+ {
+ value = SettingEncryptionService.Decrypt(setting, value);
+ }
+
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Store/ISettingManagementStore.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Store/ISettingManagementStore.cs
new file mode 100644
index 0000000..83c1c46
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Store/ISettingManagementStore.cs
@@ -0,0 +1,19 @@
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+///
+/// 设置存储器
+///
+public interface ISettingManagementStore
+{
+ Task GetOrNullAsync(string name, string providerName, string providerKey);
+
+ Task> GetListAsync(string providerName, string providerKey);
+
+ Task> GetListAsync(string[] names, string providerName, string providerKey);
+
+ Task SetAsync(string name, string value, string providerName, string providerKey);
+
+ Task DeleteAsync(string name, string providerName, string providerKey);
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Store/SettingManagementStore.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Store/SettingManagementStore.cs
new file mode 100644
index 0000000..2028c07
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Store/SettingManagementStore.cs
@@ -0,0 +1,215 @@
+using NPin.Framework.SettingManagement.Domain.Caching;
+using NPin.Framework.SettingManagement.Domain.Entities;
+using Volo.Abp.Caching;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Guids;
+using Volo.Abp.Settings;
+using Volo.Abp.Uow;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+public class SettingManagementStore : ISettingManagementStore, ITransientDependency
+{
+ protected IDistributedCache Cache { get; }
+ protected ISettingDefinitionManager SettingDefinitionManager { get; }
+ protected ISettingRepository SettingRepository { get; }
+ protected IGuidGenerator GuidGenerator { get; }
+
+ public SettingManagementStore(IDistributedCache cache,
+ ISettingDefinitionManager settingDefinitionManager, ISettingRepository settingRepository,
+ IGuidGenerator guidGenerator)
+ {
+ Cache = cache;
+ SettingDefinitionManager = settingDefinitionManager;
+ SettingRepository = settingRepository;
+ GuidGenerator = guidGenerator;
+ }
+
+ [UnitOfWork]
+ public virtual async Task GetOrNullAsync(string name, string providerName, string providerKey)
+ {
+ return (await GetCacheItemAsync(name, providerName, providerKey)).Value;
+ }
+
+ [UnitOfWork]
+ public virtual async Task> GetListAsync(string providerName, string providerKey)
+ {
+ var settings = await SettingRepository.GetListAsync(providerName, providerKey);
+ return settings.Select(s => new SettingValue(s.Name, s.Value)).ToList();
+ }
+
+ [UnitOfWork]
+ public virtual async Task> GetListAsync(string[] names, string providerName, string providerKey)
+ {
+ Check.NotNullOrEmpty(names, nameof(names));
+
+ var result = new List();
+
+ if (names.Length == 1)
+ {
+ var name = names.First();
+ result.Add(new SettingValue(name, (await GetCacheItemAsync(name, providerName, providerKey)).Value));
+ return result;
+ }
+
+ var cacheItems = await GetCacheItemsAsync(names, providerName, providerKey);
+ result.AddRange(cacheItems.Select(item =>
+ new SettingValue(GetSettingNameFormCacheKeyOrNull(item.Key), item.Value?.Value)));
+
+ return result;
+ }
+
+ [UnitOfWork]
+ public virtual async Task SetAsync(string name, string value, string providerName, string providerKey)
+ {
+ var setting = await SettingRepository.FindAsync(name, providerName, providerKey);
+ if (setting == null)
+ {
+ setting = new SettingEntity(GuidGenerator.Create(), name, value, providerName, providerKey);
+ await SettingRepository.InsertAsync(setting);
+ }
+ else
+ {
+ setting.Value = value;
+ await SettingRepository.UpdateAsync(setting);
+ }
+
+ await Cache.SetAsync(CalculateCacheKey(name, providerName, providerKey), new SettingCacheItem(setting.Value),
+ considerUow: true);
+ }
+
+ [UnitOfWork]
+ public virtual async Task DeleteAsync(string name, string providerName, string providerKey)
+ {
+ var setting = await SettingRepository.FindAsync(name, providerName, providerKey);
+ if (setting != null)
+ {
+ await SettingRepository.DeleteAsync(setting);
+ await Cache.RemoveAsync(CalculateCacheKey(name, providerName, providerKey), considerUow: true);
+ }
+ }
+
+ protected virtual async Task GetCacheItemAsync(string name, string providerName,
+ string providerKey)
+ {
+ var cacheKey = CalculateCacheKey(name, providerName, providerKey);
+ var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true);
+
+ if (cacheItem != null)
+ {
+ return cacheItem;
+ }
+
+ cacheItem = new SettingCacheItem(null);
+
+ await SetCacheItemsAsync(providerName, providerKey, name, cacheItem);
+
+ return cacheItem;
+ }
+
+ protected virtual async Task>> GetCacheItemsAsync(string[] names,
+ string providerName, string providerKey)
+ {
+ var cacheKeys = names.Select(x => CalculateCacheKey(x, providerName, providerKey)).ToList();
+
+ var cacheItems = (await Cache.GetManyAsync(cacheKeys, considerUow: true)).ToList();
+
+ if (cacheItems.All(x => x.Value != null))
+ {
+ return cacheItems;
+ }
+
+ var notCacheKeys = cacheItems.Where(x => x.Value == null).Select(x => x.Key).ToList();
+
+ var newCacheItems = await SetCacheItemsAsync(providerName, providerKey, notCacheKeys);
+
+ var result = new List>();
+ foreach (var key in cacheKeys)
+ {
+ var item = newCacheItems.FirstOrDefault(x => x.Key == key);
+ if (item.Value == null)
+ {
+ item = cacheItems.FirstOrDefault(x => x.Key == key);
+ }
+
+ result.Add(new KeyValuePair(key, item.Value));
+ }
+
+ return result;
+ }
+
+ private async Task SetCacheItemsAsync(
+ string providerName,
+ string providerKey,
+ string currentName,
+ SettingCacheItem currentCacheItem)
+ {
+ var settingDefinitions = await SettingDefinitionManager.GetAllAsync();
+ var settingsDictionary = (await SettingRepository.GetListAsync(providerName, providerKey))
+ .ToDictionary(s => s.Name, s => s.Value);
+
+ var cacheItems = new List>();
+
+ foreach (var settingDefinition in settingDefinitions)
+ {
+ var settingValue = settingsDictionary.GetOrDefault(settingDefinition.Name);
+
+ cacheItems.Add(
+ new KeyValuePair(
+ CalculateCacheKey(settingDefinition.Name, providerName, providerKey),
+ new SettingCacheItem(settingValue)
+ )
+ );
+
+ if (settingDefinition.Name == currentName)
+ {
+ currentCacheItem.Value = settingValue;
+ }
+ }
+
+ await Cache.SetManyAsync(cacheItems, considerUow: true);
+ }
+
+ private async Task>> SetCacheItemsAsync(
+ string providerName,
+ string providerKey,
+ List notCacheKeys)
+ {
+ var settingDefinitions = (await SettingDefinitionManager.GetAllAsync()).Where(x =>
+ notCacheKeys.Any(k => GetSettingNameFormCacheKeyOrNull(k) == x.Name));
+
+ var settingsDictionary =
+ (await SettingRepository.GetListAsync(notCacheKeys.Select(GetSettingNameFormCacheKeyOrNull).ToArray(),
+ providerName, providerKey))
+ .ToDictionary(s => s.Name, s => s.Value);
+
+ var cacheItems = new List>();
+
+ foreach (var settingDefinition in settingDefinitions)
+ {
+ var settingValue = settingsDictionary.GetOrDefault(settingDefinition.Name);
+ cacheItems.Add(
+ new KeyValuePair(
+ CalculateCacheKey(settingDefinition.Name, providerName, providerKey),
+ new SettingCacheItem(settingValue)
+ )
+ );
+ }
+
+ await Cache.SetManyAsync(cacheItems, considerUow: true);
+
+ return cacheItems;
+ }
+
+
+ protected virtual string CalculateCacheKey(string name, string providerName, string providerKey)
+ {
+ return SettingCacheItem.CalculateCacheKey(name, providerName, providerKey);
+ }
+
+ protected virtual string? GetSettingNameFormCacheKeyOrNull(string key)
+ {
+ //TODO: throw ex when name is null?
+ return SettingCacheItem.GetSettingNameFormCacheKeyOrNull(key);
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.Domain/Store/SettingStore.cs b/module/setting-management/NPin.Framework.SettingManagement.Domain/Store/SettingStore.cs
new file mode 100644
index 0000000..1dfd97f
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.Domain/Store/SettingStore.cs
@@ -0,0 +1,27 @@
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Settings;
+
+namespace NPin.Framework.SettingManagement.Domain;
+
+///
+/// 设置存储
+///
+public class SettingStore : ISettingStore, ITransientDependency
+{
+ protected ISettingManagementStore ManagementStore { get; }
+
+ public SettingStore(ISettingManagementStore managementStore)
+ {
+ ManagementStore = managementStore;
+ }
+
+ public virtual Task GetOrNullAsync(string name, string? providerName, string? providerKey)
+ {
+ return ManagementStore.GetOrNullAsync(name, providerName, providerKey);
+ }
+
+ public virtual Task> GetAllAsync(string[] names, string? providerName, string? providerKey)
+ {
+ return ManagementStore.GetListAsync(names, providerName, providerKey);
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.SqlSugarCore/NPin.Framework.SettingManagement.SqlSugarCore.csproj b/module/setting-management/NPin.Framework.SettingManagement.SqlSugarCore/NPin.Framework.SettingManagement.SqlSugarCore.csproj
new file mode 100644
index 0000000..8806d33
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.SqlSugarCore/NPin.Framework.SettingManagement.SqlSugarCore.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/module/setting-management/NPin.Framework.SettingManagement.SqlSugarCore/NPinFrameworkSettingManagementSqlSugarCoreModule.cs b/module/setting-management/NPin.Framework.SettingManagement.SqlSugarCore/NPinFrameworkSettingManagementSqlSugarCoreModule.cs
new file mode 100644
index 0000000..8c7b693
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.SqlSugarCore/NPinFrameworkSettingManagementSqlSugarCoreModule.cs
@@ -0,0 +1,18 @@
+using Microsoft.Extensions.DependencyInjection;
+using NPin.Framework.SettingManagement.Domain;
+using NPin.Framework.SqlSugarCore;
+
+namespace NPin.Framework.SettingManagement.SqlSugarCore;
+
+[DependsOn(
+ typeof(NPinFrameworkSettingManagementDomainModule),
+ typeof(NPinFrameworkSqlSugarCoreModule)
+)]
+public class NPinFrameworkSettingManagementSqlSugarCoreModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ var services = context.Services;
+ services.AddTransient();
+ }
+}
\ No newline at end of file
diff --git a/module/setting-management/NPin.Framework.SettingManagement.SqlSugarCore/SqlSugarCoreSettingRepository.cs b/module/setting-management/NPin.Framework.SettingManagement.SqlSugarCore/SqlSugarCoreSettingRepository.cs
new file mode 100644
index 0000000..0542ab8
--- /dev/null
+++ b/module/setting-management/NPin.Framework.SettingManagement.SqlSugarCore/SqlSugarCoreSettingRepository.cs
@@ -0,0 +1,39 @@
+using NPin.Framework.SettingManagement.Domain;
+using NPin.Framework.SettingManagement.Domain.Entities;
+using NPin.Framework.SqlSugarCore.Abstractions;
+using NPin.Framework.SqlSugarCore.Repositories;
+
+namespace NPin.Framework.SettingManagement.SqlSugarCore;
+
+public class SqlSugarCoreSettingRepository : SqlSugarRepository, ISettingRepository
+{
+ public SqlSugarCoreSettingRepository(ISugarDbContextProvider sugarDbContextProvider) : base(
+ sugarDbContextProvider)
+ {
+ }
+
+ public virtual async Task FindAsync(string name, string providerName, string providerKey,
+ CancellationToken cancellationToken = default)
+ {
+ return await DbQueryable
+ .Where(s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey)
+ .OrderBy(x => x.Id)
+ .FirstAsync(cancellationToken);
+ }
+
+ public virtual async Task> GetListAsync(string providerName, string providerKey,
+ CancellationToken cancellationToken = default)
+ {
+ return await DbQueryable
+ .Where(s => s.ProviderName == providerName && s.ProviderKey == providerKey)
+ .ToListAsync(cancellationToken);
+ }
+
+ public virtual async Task> GetListAsync(string[] names, string providerName, string providerKey,
+ CancellationToken cancellationToken = default)
+ {
+ return await DbQueryable
+ .Where(s => names.Contains(s.Name) && s.ProviderName == providerName && s.ProviderKey == providerKey)
+ .ToListAsync(cancellationToken);
+ }
+}
\ No newline at end of file