Browse Source

Merge pull request #88 from colinin/3.1

Add feature: Function check for call limit
pull/115/head
cKey 5 years ago
committed by GitHub
parent
commit
72b53bb38e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 32
      aspnet-core/LINGYUN.MicroService.sln
  2. 25
      aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/LINGYUN.Abp.Features.Validation.Redis.csproj
  3. 25
      aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/LINGYUN/Abp/Features/Validation/Redis/AbpFeaturesValidationRedisModule.cs
  4. 26
      aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/LINGYUN/Abp/Features/Validation/Redis/AbpRedisRequiresLimitFeatureOptions.cs
  5. 4
      aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/LINGYUN/Abp/Features/Validation/Redis/Lua/check.lua
  6. 6
      aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/LINGYUN/Abp/Features/Validation/Redis/Lua/process.lua
  7. 198
      aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/LINGYUN/Abp/Features/Validation/Redis/RedisRequiresLimitFeatureChecker.cs
  8. 16
      aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/System/BytesExtensions.cs
  9. 17
      aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/System/StringExtensions.cs
  10. 12
      aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN.Abp.Features.Validation.csproj
  11. 15
      aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/AbpFeaturesValidationModule.cs
  12. 82
      aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/FeaturesValidationInterceptor.cs
  13. 38
      aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/FeaturesValidationInterceptorRegistrar.cs
  14. 12
      aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/IRequiresLimitFeatureChecker.cs
  15. 26
      aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/LimitPolicy.cs
  16. 19
      aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/NullRequiresLimitFeatureChecker.cs
  17. 34
      aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/RequiresLimitFeatureAttribute.cs
  18. 56
      aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/RequiresLimitFeatureContext.cs
  19. 10
      aspnet-core/services/account/AuthServer.Host/AuthIdentityServerModule.cs
  20. 23
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Redis.Tests/LINGYUN.Abp.Features.Validation.Redis.Tests.csproj
  21. 9
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Redis.Tests/LINGYUN/Abp/Features/Validation/Redis/AbpFeaturesValidationRedisTestBase.cs
  22. 25
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Redis.Tests/LINGYUN/Abp/Features/Validation/Redis/AbpFeaturesValidationRedisTestModule.cs
  23. 53
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Redis.Tests/LINGYUN/Abp/Features/Validation/Redis/RedisRequiresLimitFeatureCheckerTests.cs
  24. 21
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN.Abp.Features.Validation.Tests.csproj
  25. 8
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/AbpFeaturesValidationTestBase.cs
  26. 23
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/AbpFeaturesValidationTestModule.cs
  27. 44
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/FakeRequiresFeatureLimitChecker.cs
  28. 42
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/FeaturesValidationTests.cs
  29. 17
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/TestFeatureDefinitionProvider.cs
  30. 9
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/TestFeatureNames.cs
  31. 7
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/TestFeatureTenant.cs
  32. 50
      aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/TestValidationFeatureClass.cs
  33. 1
      aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN.Abp.TestsBase.csproj
  34. 10
      aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/AbpTestsBaseModule.cs
  35. 20
      aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/Features/FakeFeatureOptions.cs
  36. 29
      aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/Features/FakeFeatureStore.cs

32
aspnet-core/LINGYUN.MicroService.sln

@ -213,9 +213,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Identity.Entity
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "account", "account", "{685188AC-A145-4A27-BF5F-9C970A59AA9C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.IdentityServer4.HttpApi.Host", "services\identity-server\LINGYUN.Abp.IdentityServer4.HttpApi.Host\LINGYUN.Abp.IdentityServer4.HttpApi.Host.csproj", "{F85552D4-D22E-483A-B1F8-3DFB840F6F7C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.IdentityServer4.HttpApi.Host", "services\identity-server\LINGYUN.Abp.IdentityServer4.HttpApi.Host\LINGYUN.Abp.IdentityServer4.HttpApi.Host.csproj", "{F85552D4-D22E-483A-B1F8-3DFB840F6F7C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Settings", "modules\common\LINGYUN.Abp.Settings\LINGYUN.Abp.Settings.csproj", "{6AA0785D-9B6C-4EAE-AB83-0C4CF2B6B473}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Settings", "modules\common\LINGYUN.Abp.Settings\LINGYUN.Abp.Settings.csproj", "{6AA0785D-9B6C-4EAE-AB83-0C4CF2B6B473}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Features.Validation", "modules\common\LINGYUN.Abp.Features\LINGYUN.Abp.Features.Validation.csproj", "{65DE28D5-DFEA-43E5-B820-BAF09A1FC4ED}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Features.Validation.Redis", "modules\common\LINGYUN.Abp.Features.Validation.Redis\LINGYUN.Abp.Features.Validation.Redis.csproj", "{D3E65610-4167-4235-9C9D-1E1FAD4C0CE6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Features.Validation.Redis.Tests", "tests\LINGYUN.Abp.Features.Validation.Redis.Tests\LINGYUN.Abp.Features.Validation.Redis.Tests.csproj", "{F12F4645-C0FE-4129-8C71-65B4039DC445}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Features.Validation.Tests", "tests\LINGYUN.Abp.Features.Validation.Tests\LINGYUN.Abp.Features.Validation.Tests.csproj", "{C457FA70-8732-44B8-A018-C96D14025D4B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -575,6 +583,22 @@ Global
{6AA0785D-9B6C-4EAE-AB83-0C4CF2B6B473}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6AA0785D-9B6C-4EAE-AB83-0C4CF2B6B473}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AA0785D-9B6C-4EAE-AB83-0C4CF2B6B473}.Release|Any CPU.Build.0 = Release|Any CPU
{65DE28D5-DFEA-43E5-B820-BAF09A1FC4ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65DE28D5-DFEA-43E5-B820-BAF09A1FC4ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65DE28D5-DFEA-43E5-B820-BAF09A1FC4ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65DE28D5-DFEA-43E5-B820-BAF09A1FC4ED}.Release|Any CPU.Build.0 = Release|Any CPU
{D3E65610-4167-4235-9C9D-1E1FAD4C0CE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3E65610-4167-4235-9C9D-1E1FAD4C0CE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3E65610-4167-4235-9C9D-1E1FAD4C0CE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3E65610-4167-4235-9C9D-1E1FAD4C0CE6}.Release|Any CPU.Build.0 = Release|Any CPU
{F12F4645-C0FE-4129-8C71-65B4039DC445}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F12F4645-C0FE-4129-8C71-65B4039DC445}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F12F4645-C0FE-4129-8C71-65B4039DC445}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F12F4645-C0FE-4129-8C71-65B4039DC445}.Release|Any CPU.Build.0 = Release|Any CPU
{C457FA70-8732-44B8-A018-C96D14025D4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C457FA70-8732-44B8-A018-C96D14025D4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C457FA70-8732-44B8-A018-C96D14025D4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C457FA70-8732-44B8-A018-C96D14025D4B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -684,6 +708,10 @@ Global
{685188AC-A145-4A27-BF5F-9C970A59AA9C} = {672E1170-7B18-474B-85C7-1961BF2A48AE}
{F85552D4-D22E-483A-B1F8-3DFB840F6F7C} = {E2408063-FB1F-4513-B4A7-1FE50667C6E8}
{6AA0785D-9B6C-4EAE-AB83-0C4CF2B6B473} = {8AC72641-30D3-4ACF-89FA-808FADC55C2E}
{65DE28D5-DFEA-43E5-B820-BAF09A1FC4ED} = {8AC72641-30D3-4ACF-89FA-808FADC55C2E}
{D3E65610-4167-4235-9C9D-1E1FAD4C0CE6} = {8AC72641-30D3-4ACF-89FA-808FADC55C2E}
{F12F4645-C0FE-4129-8C71-65B4039DC445} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F}
{C457FA70-8732-44B8-A018-C96D14025D4B} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718}

25
aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/LINGYUN.Abp.Features.Validation.Redis.csproj

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.7" />
<PackageReference Include="Polly" Version="7.2.1" />
<PackageReference Include="StackExchange.Redis" Version="2.0.593" />
<PackageReference Include="Volo.Abp.Core" Version="3.1.0" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="LINGYUN\Abp\Features\Validation\Redis\Lua\*.lua" />
<Content Remove="LINGYUN\Abp\Features\Validation\Redis\Lua\*.lua" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.Features\LINGYUN.Abp.Features.Validation.csproj" />
</ItemGroup>
</Project>

25
aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/LINGYUN/Abp/Features/Validation/Redis/AbpFeaturesValidationRedisModule.cs

@ -0,0 +1,25 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.Features.Validation.Redis
{
[DependsOn(
typeof(AbpFeaturesValidationModule))]
public class AbpFeaturesValidationRedisModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<AbpRedisRequiresLimitFeatureOptions>(configuration.GetSection("Features:Validation:Redis"));
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpFeaturesValidationRedisModule>();
});
context.Services.Replace(ServiceDescriptor.Singleton<IRequiresLimitFeatureChecker, RedisRequiresLimitFeatureChecker>());
}
}
}

26
aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/LINGYUN/Abp/Features/Validation/Redis/AbpRedisRequiresLimitFeatureOptions.cs

@ -0,0 +1,26 @@
using Microsoft.Extensions.Options;
using StackExchange.Redis;
namespace LINGYUN.Abp.Features.Validation.Redis
{
public class AbpRedisRequiresLimitFeatureOptions : IOptions<AbpRedisRequiresLimitFeatureOptions>
{
public string Configuration { get; set; }
public string InstanceName { get; set; }
public ConfigurationOptions ConfigurationOptions { get; set; }
/// <summary>
/// 失败重试次数
/// default: 3
/// </summary>
public int FailedRetryCount { get; set; } = 3;
/// <summary>
/// 失败重试间隔 ms
/// default: 1000
/// </summary>
public int FailedRetryInterval { get; set; } = 1000;
AbpRedisRequiresLimitFeatureOptions IOptions<AbpRedisRequiresLimitFeatureOptions>.Value
{
get { return this; }
}
}
}

4
aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/LINGYUN/Abp/Features/Validation/Redis/Lua/check.lua

@ -0,0 +1,4 @@
if (redis.call('EXISTS', KEYS[1]) == 0) then
redis.call('SETEX',KEYS[1],ARGV[1], 0)
end
return tonumber(redis.call('GET', KEYS[1]))

6
aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/LINGYUN/Abp/Features/Validation/Redis/Lua/process.lua

@ -0,0 +1,6 @@
if (redis.call('EXISTS',KEYS[1]) ~= 0) then
redis.call('INCRBY',KEYS[1], 1)
else
redis.call('SETEX',KEYS[1],ARGV[1],0)
end
return tonumber(redis.call('GET',KEYS[1]))

198
aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/LINGYUN/Abp/Features/Validation/Redis/RedisRequiresLimitFeatureChecker.cs

@ -0,0 +1,198 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StackExchange.Redis;
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Authorization;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.Features.Validation.Redis
{
[DisableConventionalRegistration]
public class RedisRequiresLimitFeatureChecker : IRequiresLimitFeatureChecker
{
private const string CHECK_LUA_SCRIPT = "/LINGYUN/Abp/Features/Validation/Redis/Lua/check.lua";
private const string PROCESS_LUA_SCRIPT = "/LINGYUN/Abp/Features/Validation/Redis/Lua/process.lua";
public ILogger<RedisRequiresLimitFeatureChecker> Logger { protected get; set; }
private volatile ConnectionMultiplexer _connection;
private volatile ConfigurationOptions _redisConfig;
private IDatabaseAsync _redis;
private IServer _server;
private IVirtualFileProvider _virtualFileProvider;
private ICurrentTenant _currentTenant;
private AbpRedisRequiresLimitFeatureOptions _options;
private readonly string _instance;
private readonly SemaphoreSlim _connectionLock = new SemaphoreSlim(initialCount: 1, maxCount: 1);
public RedisRequiresLimitFeatureChecker(
ICurrentTenant currentTenant,
IVirtualFileProvider virtualFileProvider,
IOptions<AbpRedisRequiresLimitFeatureOptions> optionsAccessor)
{
if (optionsAccessor == null)
{
throw new ArgumentNullException(nameof(optionsAccessor));
}
_options = optionsAccessor.Value;
_currentTenant = currentTenant;
_virtualFileProvider = virtualFileProvider;
_instance = _options.InstanceName ?? string.Empty;
Logger = NullLogger<RedisRequiresLimitFeatureChecker>.Instance;
}
public virtual async Task CheckAsync(RequiresLimitFeatureContext context, CancellationToken cancellation = default)
{
await ConnectAsync(cancellation);
var result = await EvaluateAsync(CHECK_LUA_SCRIPT, context, cancellation);
if (result + 1 > context.Limit)
{
throw new AbpAuthorizationException("已经超出功能次数限制,请联系管理员");
}
}
public virtual async Task ProcessAsync(RequiresLimitFeatureContext context, CancellationToken cancellation = default)
{
await ConnectAsync(cancellation);
await EvaluateAsync(PROCESS_LUA_SCRIPT, context, cancellation);
}
private async Task<int> EvaluateAsync(string luaScriptFilePath, RequiresLimitFeatureContext context, CancellationToken cancellation = default)
{
var luaScriptFile = _virtualFileProvider.GetFileInfo(luaScriptFilePath);
using var luaScriptFileStream = luaScriptFile.CreateReadStream();
var fileBytes = await luaScriptFileStream.GetAllBytesAsync(cancellation);
var luaSha1 = fileBytes.Sha1();
if (!await _server.ScriptExistsAsync(luaSha1))
{
var luaScript = Encoding.UTF8.GetString(fileBytes);
luaSha1 = await _server.ScriptLoadAsync(luaScript);
}
var keys = new RedisKey[1] { NormalizeKey(context) };
var values = new RedisValue[] { context.GetEffectTicks() };
var result = await _redis.ScriptEvaluateAsync(luaSha1, keys, values);
if (result.Type == ResultType.Error)
{
throw new AbpException($"脚本执行错误:{result}");
}
return (int)result;
}
private string NormalizeKey(RequiresLimitFeatureContext context)
{
if (_currentTenant.IsAvailable)
{
return $"{_instance}t:RequiresLimitFeature;t:{_currentTenant.Id};f:{context.Feature}";
}
return $"{_instance}c:RequiresLimitFeature;f:{context.Feature}";
}
private void RegistenConnectionEvent(ConnectionMultiplexer connection)
{
if (connection != null)
{
connection.ConnectionFailed += OnConnectionFailed;
connection.ConnectionRestored += OnConnectionRestored;
connection.ErrorMessage += OnErrorMessage;
connection.ConfigurationChanged += OnConfigurationChanged;
connection.HashSlotMoved += OnHashSlotMoved;
connection.InternalError += OnInternalError;
connection.ConfigurationChangedBroadcast += OnConfigurationChangedBroadcast;
}
}
private async Task ConnectAsync(CancellationToken token = default(CancellationToken))
{
token.ThrowIfCancellationRequested();
if (_redis != null)
{
return;
}
await _connectionLock.WaitAsync(token);
try
{
if (_redis == null)
{
if (_options.ConfigurationOptions != null)
{
_redisConfig = _options.ConfigurationOptions;
}
else
{
_redisConfig = ConfigurationOptions.Parse(_options.Configuration);
}
_redisConfig.AllowAdmin = true;
_redisConfig.SetDefaultPorts();
_connection = await ConnectionMultiplexer.ConnectAsync(_redisConfig);
RegistenConnectionEvent(_connection);
_redis = _connection.GetDatabase();
_server = _connection.GetServer(_redisConfig.EndPoints[0]);
}
}
finally
{
_connectionLock.Release();
}
}
private void OnConfigurationChangedBroadcast(object sender, EndPointEventArgs e)
{
Logger.LogInformation("Redis server master/slave changes");
}
private void OnInternalError(object sender, InternalErrorEventArgs e)
{
Logger.LogError("Redis internal error, origin:{0}, connectionType:{1}",
e.Origin, e.ConnectionType);
Logger.LogError(e.Exception, "Redis internal error");
}
private void OnHashSlotMoved(object sender, HashSlotMovedEventArgs e)
{
Logger.LogInformation("Redis configuration changed");
}
private void OnConfigurationChanged(object sender, EndPointEventArgs e)
{
Logger.LogInformation("Redis configuration changed");
}
private void OnErrorMessage(object sender, RedisErrorEventArgs e)
{
Logger.LogWarning("Redis error, message:{0}", e.Message);
}
private void OnConnectionRestored(object sender, ConnectionFailedEventArgs e)
{
Logger.LogWarning("Redis connection restored, failureType:{0}, connectionType:{1}",
e.FailureType, e.ConnectionType);
}
private void OnConnectionFailed(object sender, ConnectionFailedEventArgs e)
{
Logger.LogError("Redis connection failed, failureType:{0}, connectionType:{1}",
e.FailureType, e.ConnectionType);
Logger.LogError(e.Exception, "Redis lock connection failed");
}
}
}

16
aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/System/BytesExtensions.cs

@ -0,0 +1,16 @@
using System.Security.Cryptography;
namespace System
{
internal static class BytesExtensions
{
public static byte[] Sha1(this byte[] data)
{
using (var sha = SHA1.Create())
{
var hashBytes = sha.ComputeHash(data);
return hashBytes;
}
}
}
}

17
aspnet-core/modules/common/LINGYUN.Abp.Features.Validation.Redis/System/StringExtensions.cs

@ -0,0 +1,17 @@
using System.Security.Cryptography;
using System.Text;
namespace System
{
internal static class StringExtensions
{
public static byte[] Sha1(this string str)
{
using (var sha = SHA1.Create())
{
var hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(str));
return hashBytes;
}
}
}
}

12
aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN.Abp.Features.Validation.csproj

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Features" Version="3.1.0" />
</ItemGroup>
</Project>

15
aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/AbpFeaturesValidationModule.cs

@ -0,0 +1,15 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Features.Validation
{
[DependsOn(typeof(AbpFeaturesModule))]
public class AbpFeaturesValidationModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.OnRegistred(FeaturesValidationInterceptorRegistrar.RegisterIfNeeded);
}
}
}

82
aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/FeaturesValidationInterceptor.cs

@ -0,0 +1,82 @@
using System.Reflection;
using System.Threading.Tasks;
using Volo.Abp.Aspects;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Features;
using Volo.Abp.Validation.StringValues;
namespace LINGYUN.Abp.Features.Validation
{
public class FeaturesValidationInterceptor : AbpInterceptor, ITransientDependency
{
private readonly IFeatureChecker _featureChecker;
private readonly IRequiresLimitFeatureChecker _limitFeatureChecker;
private readonly IFeatureDefinitionManager _featureDefinitionManager;
public FeaturesValidationInterceptor(
IFeatureChecker featureChecker,
IRequiresLimitFeatureChecker limitFeatureChecker,
IFeatureDefinitionManager featureDefinitionManager)
{
_featureChecker = featureChecker;
_limitFeatureChecker = limitFeatureChecker;
_featureDefinitionManager = featureDefinitionManager;
}
public override async Task InterceptAsync(IAbpMethodInvocation invocation)
{
if (AbpCrossCuttingConcerns.IsApplied(invocation.TargetObject, AbpCrossCuttingConcerns.FeatureChecking))
{
await invocation.ProceedAsync();
return;
}
var limitFeature = GetRequiresLimitFeature(invocation.Method);
if (limitFeature == null)
{
await invocation.ProceedAsync();
return;
}
// 获取功能限制时长
var limit = await _featureChecker.GetAsync(limitFeature.Feature, limitFeature.DefaultLimit);
var limitFeatureContext = new RequiresLimitFeatureContext(limitFeature.Feature, limitFeature.Policy, limit);
// 检查次数限制
await PreCheckFeatureAsync(limitFeatureContext);
// 执行代理方法
await invocation.ProceedAsync();
// 调用次数递增
// TODO: 使用Redis结合Lua脚本?
await PostCheckFeatureAsync(limitFeatureContext);
}
protected virtual async Task PreCheckFeatureAsync(RequiresLimitFeatureContext context)
{
await _limitFeatureChecker.CheckAsync(context);
}
protected virtual async Task PostCheckFeatureAsync(RequiresLimitFeatureContext context)
{
await _limitFeatureChecker.ProcessAsync(context);
}
protected virtual RequiresLimitFeatureAttribute GetRequiresLimitFeature(MethodInfo methodInfo)
{
var limitFeature = methodInfo.GetCustomAttribute<RequiresLimitFeatureAttribute>(false);
if (limitFeature != null)
{
var featureDefinition = _featureDefinitionManager.GetOrNull(limitFeature.Feature);
if (featureDefinition != null &&
typeof(NumericValueValidator).IsAssignableFrom(featureDefinition.ValueType.Validator.GetType()))
{
return limitFeature;
}
}
return null;
}
}
}

38
aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/FeaturesValidationInterceptorRegistrar.cs

@ -0,0 +1,38 @@
using System;
using System.Linq;
using System.Reflection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace LINGYUN.Abp.Features.Validation
{
public static class FeaturesValidationInterceptorRegistrar
{
public static void RegisterIfNeeded(IOnServiceRegistredContext context)
{
if (ShouldIntercept(context.ImplementationType))
{
context.Interceptors.TryAdd<FeaturesValidationInterceptor>();
}
}
private static bool ShouldIntercept(Type type)
{
return !DynamicProxyIgnoreTypes.Contains(type) &&
(type.IsDefined(typeof(RequiresLimitFeatureAttribute), true) ||
AnyMethodHasRequiresLimitFeatureAttribute(type));
}
private static bool AnyMethodHasRequiresLimitFeatureAttribute(Type implementationType)
{
return implementationType
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Any(HasRequiresLimitFeatureAttribute);
}
private static bool HasRequiresLimitFeatureAttribute(MemberInfo methodInfo)
{
return methodInfo.IsDefined(typeof(RequiresLimitFeatureAttribute), true);
}
}
}

12
aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/IRequiresLimitFeatureChecker.cs

@ -0,0 +1,12 @@
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Features.Validation
{
public interface IRequiresLimitFeatureChecker
{
Task CheckAsync(RequiresLimitFeatureContext context, CancellationToken cancellation = default);
Task ProcessAsync(RequiresLimitFeatureContext context, CancellationToken cancellation = default);
}
}

26
aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/LimitPolicy.cs

@ -0,0 +1,26 @@
namespace LINGYUN.Abp.Features.Validation
{
public enum LimitPolicy
{
/// <summary>
/// 按小时限制
/// </summary>
Hours = 0,
/// <summary>
/// 按天限制
/// </summary>
Days = 1,
/// <summary>
/// 按周限制
/// </summary>
Weeks = 2,
/// <summary>
/// 按月限制
/// </summary>
Month = 3,
/// <summary>
/// 按年限制
/// </summary>
Years = 4
}
}

19
aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/NullRequiresLimitFeatureChecker.cs

@ -0,0 +1,19 @@
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.Features.Validation
{
public class NullRequiresLimitFeatureChecker : IRequiresLimitFeatureChecker, ISingletonDependency
{
public Task CheckAsync(RequiresLimitFeatureContext context, CancellationToken cancellation = default)
{
return Task.CompletedTask;
}
public Task ProcessAsync(RequiresLimitFeatureContext context, CancellationToken cancellation = default)
{
return Task.CompletedTask;
}
}
}

34
aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/RequiresLimitFeatureAttribute.cs

@ -0,0 +1,34 @@
using System;
namespace LINGYUN.Abp.Features.Validation
{
/// <summary>
/// 单个功能的调用量限制
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class RequiresLimitFeatureAttribute : Attribute
{
/// <summary>
/// 功能限制策略
/// </summary>
public LimitPolicy Policy { get; }
/// <summary>
/// 默认限制时长
/// </summary>
public int DefaultLimit { get; }
/// <summary>
/// 功能名称
/// </summary>
public string Feature { get; }
public RequiresLimitFeatureAttribute(
string feature,
LimitPolicy policy = LimitPolicy.Month,
int defaultLimit = 1)
{
DefaultLimit = defaultLimit;
Policy = policy;
Feature = feature;
}
}
}

56
aspnet-core/modules/common/LINGYUN.Abp.Features/LINGYUN/Abp/Features/Validation/RequiresLimitFeatureContext.cs

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
namespace LINGYUN.Abp.Features.Validation
{
public class RequiresLimitFeatureContext
{
/// <summary>
/// 功能限制策略
/// </summary>
public LimitPolicy Policy { get; }
/// <summary>
/// 功能限制时长
/// </summary>
public int Limit { get; }
/// <summary>
/// 功能名称
/// </summary>
public string Feature { get; }
private Lazy<IDictionary<LimitPolicy, Func<int, long>>> effectPolicysLazy;
private IDictionary<LimitPolicy, Func<int, long>> effectPolicys => effectPolicysLazy.Value;
public RequiresLimitFeatureContext(
string feature,
LimitPolicy policy = LimitPolicy.Month,
int limit = 1)
{
Limit = limit;
Policy = policy;
Feature = feature;
effectPolicysLazy = new Lazy<IDictionary<LimitPolicy, Func<int, long>>>(() => CreateFeatureLimitPolicy());
}
/// <summary>
/// 获取生效时间戳
/// </summary>
/// <returns></returns>
public long GetEffectTicks()
{
return effectPolicys[Policy](Limit);
}
protected IDictionary<LimitPolicy, Func<int, long>> CreateFeatureLimitPolicy()
{
return new Dictionary<LimitPolicy, Func<int, long>>()
{
{ LimitPolicy.Days, (time) => { return (long)(DateTimeOffset.UtcNow.AddDays(time) - DateTimeOffset.UtcNow).TotalSeconds; } },
{ LimitPolicy.Hours, (time) => { return (long)(DateTimeOffset.UtcNow.AddHours(time) - DateTimeOffset.UtcNow).TotalSeconds; } },
{ LimitPolicy.Month, (time) => { return (long)(DateTimeOffset.UtcNow.AddMonths(time) - DateTimeOffset.UtcNow).TotalSeconds; } },
{ LimitPolicy.Weeks, (time) => { return (long)(DateTimeOffset.UtcNow.AddDays(time * 7) - DateTimeOffset.UtcNow).TotalSeconds; } },
{ LimitPolicy.Years, (time) => { return (long)(DateTimeOffset.UtcNow.AddYears(time) - DateTimeOffset.UtcNow).TotalSeconds; } }
};
}
}
}

10
aspnet-core/services/account/AuthServer.Host/AuthIdentityServerModule.cs

@ -1,4 +1,5 @@
using DotNetCore.CAP;
using IdentityServer4;
using LINGYUN.Abp.EventBus.CAP;
using LINGYUN.Abp.IdentityServer;
using LINGYUN.Abp.MultiTenancy.DbFinder;
@ -10,6 +11,7 @@ using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using StackExchange.Redis;
using System;
using System.Linq;
@ -147,13 +149,7 @@ namespace AuthServer.Host
});
// context.Services.AddAuthentication();
//context.Services.AddAuthentication()
// .AddIdentityServerAuthentication(options =>
// {
// options.Authority = configuration["AuthServer:Authority"];
// options.RequireHttpsMetadata = false;
// options.ApiName = configuration["AuthServer:ApiName"];
// });
context.Services.AddAuthentication().AddCookie("Cookie");
Configure<AbpMultiTenancyOptions>(options =>
{

23
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Redis.Tests/LINGYUN.Abp.Features.Validation.Redis.Tests.csproj

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace />
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\modules\common\LINGYUN.Abp.Features.Validation.Redis\LINGYUN.Abp.Features.Validation.Redis.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.Features.Validation.Tests\LINGYUN.Abp.Features.Validation.Tests.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" />
</ItemGroup>
</Project>

9
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Redis.Tests/LINGYUN/Abp/Features/Validation/Redis/AbpFeaturesValidationRedisTestBase.cs

@ -0,0 +1,9 @@
using LINGYUN.Abp.Tests;
namespace LINGYUN.Abp.Features.Validation.Redis
{
public class AbpFeaturesValidationRedisTestBase : AbpTestsBase<AbpFeaturesValidationRedisTestModule>
{
}
}

25
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Redis.Tests/LINGYUN/Abp/Features/Validation/Redis/AbpFeaturesValidationRedisTestModule.cs

@ -0,0 +1,25 @@
using LINGYUN.Abp.Tests;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Features.Validation.Redis
{
[DependsOn(
typeof(AbpFeaturesValidationTestModule),
typeof(AbpFeaturesValidationRedisModule),
typeof(AbpTestsBaseModule))]
public class AbpFeaturesValidationRedisTestModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configurationOptions = new AbpConfigurationBuilderOptions
{
BasePath = @"D:\Projects\Development\Abp\FeaturesValidation\Redis",
EnvironmentName = "Development"
};
context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(configurationOptions));
}
}
}

53
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Redis.Tests/LINGYUN/Abp/Features/Validation/Redis/RedisRequiresLimitFeatureCheckerTests.cs

@ -0,0 +1,53 @@
using Shouldly;
using System;
using System.Threading.Tasks;
using Volo.Abp.Authorization;
using Xunit;
namespace LINGYUN.Abp.Features.Validation.Redis
{
public class RedisRequiresLimitFeatureCheckerTests : AbpFeaturesValidationRedisTestBase
{
protected IRequiresLimitFeatureChecker RequiresLimitFeatureChecker { get; }
protected TestValidationFeatureClass TestValidationFeatureClass { get; }
public RedisRequiresLimitFeatureCheckerTests()
{
RequiresLimitFeatureChecker = GetRequiredService<IRequiresLimitFeatureChecker>();
TestValidationFeatureClass = GetRequiredService<TestValidationFeatureClass>();
}
[Fact]
public virtual async Task Check_Test_Async()
{
var context = new RequiresLimitFeatureContext(TestFeatureNames.TestFeature1, LimitPolicy.Days, 10);
await RequiresLimitFeatureChecker.CheckAsync(context);
}
[Fact]
public virtual async Task Process_Test_Async()
{
var context = new RequiresLimitFeatureContext(TestFeatureNames.TestFeature1, LimitPolicy.Days, 10);
await RequiresLimitFeatureChecker.ProcessAsync(context);
}
[Fact]
public virtual async Task Check_Limit_Test_Async()
{
var context = new RequiresLimitFeatureContext(TestFeatureNames.TestFeature1, LimitPolicy.Days, 5);
await RequiresLimitFeatureChecker.ProcessAsync(context);
await RequiresLimitFeatureChecker.ProcessAsync(context);
await RequiresLimitFeatureChecker.ProcessAsync(context);
await RequiresLimitFeatureChecker.ProcessAsync(context);
await RequiresLimitFeatureChecker.ProcessAsync(context);
await RequiresLimitFeatureChecker.ProcessAsync(context);
try
{
await RequiresLimitFeatureChecker.CheckAsync(context);
}
catch(Exception ex)
{
ex.ShouldBeOfType<AbpAuthorizationException>();
}
}
}
}

21
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN.Abp.Features.Validation.Tests.csproj

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace />
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\modules\common\LINGYUN.Abp.Features\LINGYUN.Abp.Features.Validation.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" />
</ItemGroup>
</Project>

8
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/AbpFeaturesValidationTestBase.cs

@ -0,0 +1,8 @@
using LINGYUN.Abp.Tests;
namespace LINGYUN.Abp.Features.Validation
{
public class AbpFeaturesValidationTestBase : AbpTestsBase<AbpFeaturesValidationTestModule>
{
}
}

23
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/AbpFeaturesValidationTestModule.cs

@ -0,0 +1,23 @@
using LINGYUN.Abp.Tests;
using LINGYUN.Abp.Tests.Features;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Features.Validation
{
[DependsOn(
typeof(AbpTestsBaseModule),
typeof(AbpFeaturesValidationModule))]
public class AbpFeaturesValidationTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<FakeFeatureOptions>(options =>
{
options.Map(TestFeatureNames.TestFeature1, (feature) =>
{
return 2.ToString();
});
});
}
}
}

44
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/FakeRequiresFeatureLimitChecker.cs

@ -0,0 +1,44 @@
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Authorization;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.Features.Validation
{
[Dependency(ServiceLifetime.Singleton, ReplaceServices = true)]
[ExposeServices(typeof(IRequiresLimitFeatureChecker))]
public class FakeRequiresFeatureLimitChecker : IRequiresLimitFeatureChecker
{
private readonly IDictionary<string, int> limitFeatures;
public FakeRequiresFeatureLimitChecker()
{
limitFeatures = new Dictionary<string, int>();
}
public virtual Task CheckAsync(RequiresLimitFeatureContext context, CancellationToken cancellation = default)
{
if (!limitFeatures.ContainsKey(context.Feature))
{
limitFeatures.Add(context.Feature, 0);
}
if (limitFeatures[context.Feature] > context.Limit)
{
throw new AbpAuthorizationException("已经超出功能次数限制,请联系管理员");
}
return Task.CompletedTask;
}
public Task ProcessAsync(RequiresLimitFeatureContext context, CancellationToken cancellation = default)
{
if (!limitFeatures.ContainsKey(context.Feature))
{
limitFeatures.Add(context.Feature, 1);
}
limitFeatures[context.Feature] += 1;
return Task.CompletedTask;
}
}
}

42
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/FeaturesValidationTests.cs

@ -0,0 +1,42 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.Authorization;
using Volo.Abp.MultiTenancy;
using Xunit;
namespace LINGYUN.Abp.Features.Validation
{
public class FeaturesValidationTests : AbpFeaturesValidationTestBase
{
protected ICurrentTenant CurrentTenant { get; }
protected TestValidationFeatureClass TestValidationFeatureClass { get; }
public FeaturesValidationTests()
{
CurrentTenant = GetRequiredService<ICurrentTenant>();
TestValidationFeatureClass = GetRequiredService<TestValidationFeatureClass>();
}
[Theory]
[InlineData(TestFeatureTenant.TenantId)] //Features were not enabled for Tenant 2
public async Task Should_Not_Allow_To_Call_Method_If_Has_Limit_Feature_Async(string tenantId)
{
using (CurrentTenant.Change(ParseNullableGuid(tenantId)))
{
// it's ok
await TestValidationFeatureClass.Test1HoursAsync();
await TestValidationFeatureClass.Test1HoursAsync();
await TestValidationFeatureClass.Test1HoursAsync();
await Assert.ThrowsAsync<AbpAuthorizationException>(async () =>
{
await TestValidationFeatureClass.Test1HoursAsync();
});
}
}
private static Guid? ParseNullableGuid(string tenantIdValue)
{
return tenantIdValue == null ? (Guid?)null : new Guid(tenantIdValue);
}
}
}

17
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/TestFeatureDefinitionProvider.cs

@ -0,0 +1,17 @@
using Volo.Abp.Features;
using Volo.Abp.Validation.StringValues;
namespace LINGYUN.Abp.Features.Validation
{
public class TestFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var featureGroup = context.AddGroup(TestFeatureNames.GroupName);
featureGroup.AddFeature(
name: TestFeatureNames.TestFeature1,
defaultValue: 100.ToString(),
valueType: new ToggleStringValueType(new NumericValueValidator(1, 1000)));
}
}
}

9
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/TestFeatureNames.cs

@ -0,0 +1,9 @@
namespace LINGYUN.Abp.Features.Validation
{
public class TestFeatureNames
{
public const string GroupName = "Abp.Features.Validation.Tests";
public const string TestFeature1 = GroupName + ".TestFeature1";
}
}

7
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/TestFeatureTenant.cs

@ -0,0 +1,7 @@
namespace LINGYUN.Abp.Features.Validation
{
public class TestFeatureTenant
{
public const string TenantId = "6355C04C-59D1-4133-944E-157179346BDC";
}
}

50
aspnet-core/tests/LINGYUN.Abp.Features.Validation.Tests/LINGYUN/Abp/Features/Validation/TestValidationFeatureClass.cs

@ -0,0 +1,50 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
namespace LINGYUN.Abp.Features.Validation
{
public class TestValidationFeatureClass : ITransientDependency
{
[RequiresLimitFeature(TestFeatureNames.TestFeature1, LimitPolicy.Days, 1)]
public virtual Task Test1DaysAsync()
{
Console.WriteLine("this limit 1 days feature");
return Task.CompletedTask;
}
[RequiresLimitFeature(TestFeatureNames.TestFeature1, LimitPolicy.Month, 1)]
public virtual Task Test1MonthsAsync()
{
Console.WriteLine("this limit 1 month feature");
return Task.CompletedTask;
}
[RequiresLimitFeature(TestFeatureNames.TestFeature1, LimitPolicy.Weeks, 1)]
public virtual Task Test1WeeksAsync()
{
Console.WriteLine("this limit 1 weeks feature");
return Task.CompletedTask;
}
[RequiresLimitFeature(TestFeatureNames.TestFeature1, LimitPolicy.Hours, 1)]
public virtual Task Test1HoursAsync()
{
Console.WriteLine("this limit 1 hours feature");
return Task.CompletedTask;
}
[RequiresLimitFeature(TestFeatureNames.TestFeature1, LimitPolicy.Years, 1)]
public virtual Task Test1YearsAsync()
{
Console.WriteLine("this limit 1 years feature");
return Task.CompletedTask;
}
}
}

1
aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN.Abp.TestsBase.csproj

@ -15,6 +15,7 @@
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="Volo.Abp.Autofac" Version="3.1.0" />
<PackageReference Include="Volo.Abp.Authorization" Version="3.1.0" />
<PackageReference Include="Volo.Abp.Features" Version="3.1.0" />
<PackageReference Include="Volo.Abp.TestBase" Version="3.1.0" />
</ItemGroup>

10
aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/AbpTestsBaseModule.cs

@ -1,7 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
using LINGYUN.Abp.Tests.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp;
using Volo.Abp.Authorization;
using Volo.Abp.Autofac;
using Volo.Abp.Features;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Tests
@ -9,13 +12,16 @@ namespace LINGYUN.Abp.Tests
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpTestBaseModule),
typeof(AbpAuthorizationModule)
typeof(AbpAuthorizationModule),
typeof(AbpFeaturesModule)
)]
public class AbpTestsBaseModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAlwaysAllowAuthorization();
context.Services.Replace(ServiceDescriptor.Singleton<IFeatureStore, FakeFeatureStore>());
}
}
}

20
aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/Features/FakeFeatureOptions.cs

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Features;
namespace LINGYUN.Abp.Tests.Features
{
public class FakeFeatureOptions
{
public IDictionary<string, Func<FeatureDefinition, string>> FeatureMaps { get; }
public FakeFeatureOptions()
{
FeatureMaps = new Dictionary<string, Func<FeatureDefinition, string>>();
}
public void Map(string featureName, Func<FeatureDefinition, string> func)
{
FeatureMaps.AddIfNotContains(new KeyValuePair<string, Func<FeatureDefinition, string>>(featureName, func));
}
}
}

29
aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/Features/FakeFeatureStore.cs

@ -0,0 +1,29 @@
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Volo.Abp.Features;
namespace LINGYUN.Abp.Tests.Features
{
public class FakeFeatureStore : IFeatureStore
{
protected FakeFeatureOptions FakeFeatureOptions { get; }
protected IFeatureDefinitionManager FeatureDefinitionManager { get; }
public FakeFeatureStore(
IOptions<FakeFeatureOptions> options,
IFeatureDefinitionManager featureDefinitionManager)
{
FakeFeatureOptions = options.Value;
FeatureDefinitionManager = featureDefinitionManager;
}
public Task<string> GetOrNullAsync(string name, string providerName, string providerKey)
{
var feature = FeatureDefinitionManager.Get(name);
var featureFunc = FakeFeatureOptions.FeatureMaps[name];
return Task.FromResult(featureFunc(feature));
}
}
}
Loading…
Cancel
Save