committed by
GitHub
48 changed files with 1621 additions and 13 deletions
@ -0,0 +1,27 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:25348/", |
|||
"sslPort": 44377 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"LINGYUN.Abp.Dapr.Actors.IdentityModel.Web": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "https://localhost:5001;http://localhost:5000" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Http.Client" Version="4.3.0" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.OssManagement.Application.Contracts\LINGYUN.Abp.OssManagement.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,20 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.OssManagement |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpOssManagementApplicationContractsModule), |
|||
typeof(AbpHttpClientModule))] |
|||
public class AbpOssManagementHttpApiClientModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(AbpOssManagementApplicationContractsModule).Assembly, |
|||
OssManagementRemoteServiceConsts.RemoteServiceName |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Net.Http; |
|||
using System.Net.Http.Headers; |
|||
using System.Reflection; |
|||
using System.Text; |
|||
using System.Text.Json; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http.Client.DynamicProxying; |
|||
using Volo.Abp.Http.Modeling; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Threading; |
|||
using Volo.Abp.Tracing; |
|||
|
|||
namespace LINGYUN.Abp.OssManagement |
|||
{ |
|||
public class LINGYUNApiDescriptionFinder : IApiDescriptionFinder, ITransientDependency |
|||
{ |
|||
public ICancellationTokenProvider CancellationTokenProvider { get; set; } |
|||
protected IApiDescriptionCache Cache { get; } |
|||
protected AbpCorrelationIdOptions AbpCorrelationIdOptions { get; } |
|||
protected ICorrelationIdProvider CorrelationIdProvider { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
|
|||
public LINGYUNApiDescriptionFinder( |
|||
IApiDescriptionCache cache, |
|||
IOptions<AbpCorrelationIdOptions> abpCorrelationIdOptions, |
|||
ICorrelationIdProvider correlationIdProvider, |
|||
ICurrentTenant currentTenant) |
|||
{ |
|||
Cache = cache; |
|||
AbpCorrelationIdOptions = abpCorrelationIdOptions.Value; |
|||
CorrelationIdProvider = correlationIdProvider; |
|||
CurrentTenant = currentTenant; |
|||
CancellationTokenProvider = NullCancellationTokenProvider.Instance; |
|||
} |
|||
|
|||
public async Task<ActionApiDescriptionModel> FindActionAsync(HttpClient client, string baseUrl, Type serviceType, MethodInfo method) |
|||
{ |
|||
var apiDescription = await GetApiDescriptionAsync(client, baseUrl); |
|||
|
|||
//TODO: Cache finding?
|
|||
|
|||
var methodParameters = method.GetParameters().ToArray(); |
|||
|
|||
foreach (var module in apiDescription.Modules.Values) |
|||
{ |
|||
foreach (var controller in module.Controllers.Values) |
|||
{ |
|||
if (!controller.Implements(serviceType)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
foreach (var action in controller.Actions.Values) |
|||
{ |
|||
if (action.Name == method.Name && action.ParametersOnMethod.Count == methodParameters.Length) |
|||
{ |
|||
var found = true; |
|||
|
|||
for (int i = 0; i < methodParameters.Length; i++) |
|||
{ |
|||
if (!TypeMatches(action.ParametersOnMethod[i], methodParameters[i])) |
|||
{ |
|||
found = false; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (found) |
|||
{ |
|||
return action; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
throw new AbpException($"Could not found remote action for method: {method} on the URL: {baseUrl}"); |
|||
} |
|||
|
|||
public virtual async Task<ApplicationApiDescriptionModel> GetApiDescriptionAsync(HttpClient client, string baseUrl) |
|||
{ |
|||
return await Cache.GetAsync(baseUrl, () => GetApiDescriptionFromServerAsync(client, baseUrl)); |
|||
} |
|||
|
|||
protected virtual async Task<ApplicationApiDescriptionModel> GetApiDescriptionFromServerAsync( |
|||
HttpClient client, |
|||
string baseUrl) |
|||
{ |
|||
var requestMessage = new HttpRequestMessage( |
|||
HttpMethod.Get, |
|||
baseUrl.EnsureEndsWith('/') + "api/abp/api-definition" |
|||
); |
|||
|
|||
AddHeaders(requestMessage); |
|||
|
|||
var response = await client.SendAsync( |
|||
requestMessage, |
|||
CancellationTokenProvider.Token |
|||
); |
|||
|
|||
if (!response.IsSuccessStatusCode) |
|||
{ |
|||
throw new AbpException("Remote service returns error! StatusCode = " + response.StatusCode); |
|||
} |
|||
|
|||
var content = await response.Content.ReadAsStringAsync(); |
|||
|
|||
var result = JsonSerializer.Deserialize<ApplicationApiDescriptionModel>(content, new JsonSerializerOptions |
|||
{ |
|||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase |
|||
}); |
|||
|
|||
return (ApplicationApiDescriptionModel)result; |
|||
} |
|||
|
|||
protected virtual void AddHeaders(HttpRequestMessage requestMessage) |
|||
{ |
|||
//CorrelationId
|
|||
requestMessage.Headers.Add(AbpCorrelationIdOptions.HttpHeaderName, CorrelationIdProvider.Get()); |
|||
|
|||
//TenantId
|
|||
if (CurrentTenant.Id.HasValue) |
|||
{ |
|||
//TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key
|
|||
requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString()); |
|||
} |
|||
|
|||
//Culture
|
|||
//TODO: Is that the way we want? Couldn't send the culture (not ui culture)
|
|||
var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name; |
|||
if (!currentCulture.IsNullOrEmpty()) |
|||
{ |
|||
requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture)); |
|||
} |
|||
|
|||
//X-Requested-With
|
|||
requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest"); |
|||
} |
|||
|
|||
protected virtual bool TypeMatches(MethodParameterApiDescriptionModel actionParameter, ParameterInfo methodParameter) |
|||
{ |
|||
return NormalizeTypeName(actionParameter.TypeAsString) == |
|||
NormalizeTypeName(methodParameter.ParameterType.GetFullNameWithAssemblyName()); |
|||
} |
|||
|
|||
protected virtual string NormalizeTypeName(string typeName) |
|||
{ |
|||
const string placeholder = "%COREFX%"; |
|||
const string netCoreLib = "System.Private.CoreLib"; |
|||
const string netFxLib = "mscorlib"; |
|||
|
|||
return typeName.Replace(netCoreLib, placeholder).Replace(netFxLib, placeholder); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="NRules" Version="0.9.2" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Rules\LINGYUN.Abp.Rules.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,22 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using NRules.Fluent; |
|||
using NRules.RuleModel; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Rules.NRules |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpRulesModule))] |
|||
public class AbpNRulesModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddSingleton<IRuleRepository, RuleRepository>(); |
|||
|
|||
Configure<AbpRulesOptions>(options => |
|||
{ |
|||
options.Contributors.Add<NRulesContributor>(); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using NRules.Extensibility; |
|||
using NRules.RuleModel; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.Rules.NRules |
|||
{ |
|||
public class DefaultActionInterceptor : IActionInterceptor |
|||
{ |
|||
public void Intercept(IContext context, IEnumerable<IActionInvocation> actions) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using NRules.Extensibility; |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Rules.NRules |
|||
{ |
|||
public class DefaultDependencyResolver : IDependencyResolver |
|||
{ |
|||
private readonly IServiceProvider _serviceProvider; |
|||
|
|||
public DefaultDependencyResolver( |
|||
IServiceProvider serviceProvider) |
|||
{ |
|||
_serviceProvider = serviceProvider; |
|||
} |
|||
|
|||
public object Resolve(IResolutionContext context, Type serviceType) |
|||
{ |
|||
return _serviceProvider.GetService(serviceType); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using NRules; |
|||
using NRules.RuleModel; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Rules.NRules |
|||
{ |
|||
public class NRulesContributor : RuleContributorBase, ISingletonDependency |
|||
{ |
|||
private ISessionFactory _sessionFactory; |
|||
|
|||
public override void Initialize(RulesInitializationContext context) |
|||
{ |
|||
var repository = context.GetRequiredService<IRuleRepository>(); |
|||
_sessionFactory = repository.Compile(); |
|||
_sessionFactory.DependencyResolver = new DefaultDependencyResolver(context.ServiceProvider); |
|||
_sessionFactory.ActionInterceptor = new DefaultActionInterceptor(); |
|||
} |
|||
|
|||
public override Task ExecuteAsync<T>(T input, object[] @params = null, CancellationToken cancellationToken = default) |
|||
{ |
|||
var session = _sessionFactory.CreateSession(); |
|||
|
|||
session.Insert(input); |
|||
if (@params != null && @params.Any()) |
|||
{ |
|||
session.InsertAll(@params); |
|||
} |
|||
|
|||
// TODO: 需要研究源码
|
|||
session.Fire(cancellationToken); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="RulesEngine" Version="3.1.0" /> |
|||
<PackageReference Include="Volo.Abp.Json" Version="4.3.0" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Rules\LINGYUN.Abp.Rules.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,30 @@ |
|||
using LINGYUN.Abp.Rules.RulesEngine.FileProviders.Physical; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpRulesModule), |
|||
typeof(AbpJsonModule))] |
|||
public class AbpRulesEngineModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddMemoryCache(); |
|||
|
|||
Configure<AbpRulesOptions>(options => |
|||
{ |
|||
options.Contributors.Add<RulesEngineContributor>(); |
|||
}); |
|||
|
|||
Configure<AbpRulesEngineOptions>(options => |
|||
{ |
|||
// 加入防止空引用
|
|||
options.Contributors.Add<NullWorkflowRulesContributor>(); |
|||
options.Contributors.Add<PhysicalFileWorkflowRulesContributor>(); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using Volo.Abp.Collections; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
public class AbpRulesEngineOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 本地文件路径
|
|||
/// </summary>
|
|||
public string PhysicalPath { get; set; } |
|||
/// <summary>
|
|||
/// 是否忽略租户
|
|||
/// </summary>
|
|||
public bool IgnoreMultiTenancy { get; set; } |
|||
/// <summary>
|
|||
/// 规则提供者类型
|
|||
/// </summary>
|
|||
public ITypeList<IWorkflowRulesContributor> Contributors { get; } |
|||
|
|||
public AbpRulesEngineOptions() |
|||
{ |
|||
Contributors = new TypeList<IWorkflowRulesContributor>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Microsoft.Extensions.Primitives; |
|||
using RulesEngine.Models; |
|||
using System; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine.FileProviders |
|||
{ |
|||
public abstract class FileProviderWorkflowRulesContributor : IWorkflowRulesContributor |
|||
{ |
|||
protected IMemoryCache RulesCache { get; } |
|||
protected IJsonSerializer JsonSerializer { get; } |
|||
|
|||
protected IFileProvider FileProvider { get; private set; } |
|||
|
|||
protected FileProviderWorkflowRulesContributor( |
|||
IMemoryCache ruleCache, |
|||
IJsonSerializer jsonSerializer) |
|||
{ |
|||
RulesCache = ruleCache; |
|||
JsonSerializer = jsonSerializer; |
|||
} |
|||
|
|||
public void Initialize() |
|||
{ |
|||
FileProvider = BuildFileProvider(); |
|||
} |
|||
|
|||
protected abstract IFileProvider BuildFileProvider(); |
|||
|
|||
public async Task<WorkflowRules[]> LoadAsync<T>(CancellationToken cancellationToken = default) |
|||
{ |
|||
if (FileProvider != null) |
|||
{ |
|||
return await GetCachedRulesAsync<T>(cancellationToken); |
|||
} |
|||
return new WorkflowRules[0]; |
|||
} |
|||
|
|||
public void Shutdown() |
|||
{ |
|||
if (FileProvider != null && FileProvider is IDisposable resource) |
|||
{ |
|||
resource.Dispose(); |
|||
} |
|||
} |
|||
|
|||
private async Task<WorkflowRules[]> GetCachedRulesAsync<T>(CancellationToken cancellationToken = default) |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
|
|||
var ruleId = GetRuleId<T>(); |
|||
|
|||
return await RulesCache.GetOrCreateAsync(ruleId, |
|||
async (entry) => |
|||
{ |
|||
entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(30)); |
|||
|
|||
return await GetFileSystemRulesAsync<T>(cancellationToken); |
|||
}); |
|||
} |
|||
protected abstract int GetRuleId<T>(); |
|||
|
|||
protected abstract string GetRuleName<T>(); |
|||
|
|||
protected virtual async Task<WorkflowRules[]> GetFileSystemRulesAsync<T>(CancellationToken cancellationToken = default) |
|||
{ |
|||
var ruleId = GetRuleId<T>(); |
|||
var ruleFile = GetRuleName<T>(); |
|||
var fileInfo = FileProvider.GetFileInfo(ruleFile); |
|||
if (fileInfo != null && fileInfo.Exists) |
|||
{ |
|||
// 规则文件监控
|
|||
// TODO: 删除模块的规则缓存还需要删除RulesEngine中rulesCache已编译的规则缓存
|
|||
ChangeToken.OnChange( |
|||
() => FileProvider.Watch(ruleFile), |
|||
(int ruleId) => |
|||
{ |
|||
RulesCache.Remove(ruleId); |
|||
}, ruleId); |
|||
|
|||
// 打开文本流
|
|||
using (var stream = fileInfo.CreateReadStream()) |
|||
{ |
|||
var result = new byte[stream.Length]; |
|||
await stream.ReadAsync(result, 0, (int)stream.Length); |
|||
var ruleDsl = Encoding.UTF8.GetString(result); |
|||
// 解析
|
|||
return JsonSerializer.Deserialize<WorkflowRules[]>(ruleDsl); |
|||
} |
|||
} |
|||
return new WorkflowRules[0]; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.IO; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine.FileProviders.Physical |
|||
{ |
|||
public class PhysicalFileWorkflowRulesContributor : FileProviderWorkflowRulesContributor, ISingletonDependency |
|||
{ |
|||
private readonly RuleIdGenerator _ruleIdGenerator; |
|||
private readonly AbpRulesEngineOptions _options; |
|||
public PhysicalFileWorkflowRulesContributor( |
|||
IMemoryCache ruleCache, |
|||
RuleIdGenerator ruleIdGenerator, |
|||
IJsonSerializer jsonSerializer, |
|||
IOptions<AbpRulesEngineOptions> options) |
|||
: base(ruleCache, jsonSerializer) |
|||
{ |
|||
_ruleIdGenerator = ruleIdGenerator; |
|||
|
|||
_options = options.Value; |
|||
} |
|||
|
|||
protected override IFileProvider BuildFileProvider() |
|||
{ |
|||
// 未指定路径不启用
|
|||
if (!_options.PhysicalPath.IsNullOrWhiteSpace() && |
|||
Directory.Exists(_options.PhysicalPath)) |
|||
{ |
|||
return new PhysicalFileProvider(_options.PhysicalPath); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
protected override int GetRuleId<T>() => _ruleIdGenerator.CreateRuleId(typeof(T), _options.IgnoreMultiTenancy); |
|||
|
|||
protected override string GetRuleName<T>() => $"{_ruleIdGenerator.CreateRuleName(typeof(T), _options.IgnoreMultiTenancy)}.json"; |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using RulesEngine.Models; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
public interface IWorkflowRulesContributor |
|||
{ |
|||
void Initialize(); |
|||
|
|||
Task<WorkflowRules[]> LoadAsync<T>(CancellationToken cancellationToken = default); |
|||
|
|||
void Shutdown(); |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using RulesEngine.Models; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
public class NullWorkflowRulesContributor : IWorkflowRulesContributor, ISingletonDependency |
|||
{ |
|||
public void Initialize() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public Task<WorkflowRules[]> LoadAsync<T>(CancellationToken cancellationToken = default) |
|||
{ |
|||
return Task.FromResult(new WorkflowRules[0]); |
|||
} |
|||
|
|||
public void Shutdown() |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using RulesEngine; |
|||
using RulesEngine.Interfaces; |
|||
using RulesEngine.Models; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Engine = RulesEngine.RulesEngine; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
public class RulesEngineContributor : RuleContributorBase, ISingletonDependency |
|||
{ |
|||
private IRulesEngine _ruleEngine; |
|||
private readonly IEnumerable<IWorkflowRulesContributor> _workflowRulesContributors; |
|||
|
|||
public RulesEngineContributor( |
|||
IServiceProvider serviceProvider, |
|||
IOptions<AbpRulesEngineOptions> options) |
|||
{ |
|||
_workflowRulesContributors = options.Value |
|||
.Contributors |
|||
.Select(serviceProvider.GetRequiredService) |
|||
.Cast<IWorkflowRulesContributor>() |
|||
.ToArray(); |
|||
} |
|||
|
|||
public override void Initialize(RulesInitializationContext context) |
|||
{ |
|||
var reSetting = new ReSettings |
|||
{ |
|||
NestedRuleExecutionMode = NestedRuleExecutionMode.Performance |
|||
}; |
|||
|
|||
_ruleEngine = new Engine(Logger, reSetting); |
|||
|
|||
foreach (var contributor in _workflowRulesContributors) |
|||
{ |
|||
contributor.Initialize(); |
|||
} |
|||
} |
|||
|
|||
public override async Task ExecuteAsync<T>(T input, object[] @params = null, CancellationToken cancellationToken = default) |
|||
{ |
|||
List<WorkflowRules> workflowRules = new(); |
|||
|
|||
foreach (var contributor in _workflowRulesContributors) |
|||
{ |
|||
workflowRules.AddRange(await contributor.LoadAsync<T>(cancellationToken)); |
|||
} |
|||
|
|||
if (workflowRules.Any()) |
|||
{ |
|||
await ExecuteRulesAsync(input, workflowRules.ToArray(), @params); |
|||
} |
|||
} |
|||
|
|||
public override void Shutdown() |
|||
{ |
|||
foreach (var contributor in _workflowRulesContributors) |
|||
{ |
|||
contributor.Shutdown(); |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task ExecuteRulesAsync<T>(T input, WorkflowRules[] workflowRules, object[] @params = null) |
|||
{ |
|||
_ruleEngine.AddWorkflow(workflowRules); |
|||
|
|||
// 传入参与验证的实体参数
|
|||
var inputs = new List<object>() |
|||
{ |
|||
input |
|||
}; |
|||
if (@params != null && @params.Any()) |
|||
{ |
|||
inputs.AddRange(@params); |
|||
} |
|||
// 其他参数以此类推
|
|||
|
|||
foreach (var workflowRule in workflowRules) |
|||
{ |
|||
// 执行当前的规则
|
|||
var ruleResult = await _ruleEngine.ExecuteAllRulesAsync(workflowRule.WorkflowName, inputs.ToArray()); |
|||
// 用户自定义扩展方法,规则校验错误抛出异常
|
|||
ruleResult.ThrowOfFaildExecute(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
# LINGYUN.Abp.Rules.RulesEngine |
|||
|
|||
## 模块说明 |
|||
|
|||
集成微软规则引擎的实现 |
|||
|
|||
默认实现一个本地文件系统规则提供者,根据用户配置的 **PhysicalPath** 路径检索规则文件 |
|||
|
|||
文件名如下: |
|||
|
|||
PhysicalPath/CurrentTenant.Id[如果存在]/验证规则实体类型名称[typeof(Input).Name].json |
|||
|
|||
### 基础模块 |
|||
|
|||
### 高阶模块 |
|||
|
|||
### 权限定义 |
|||
|
|||
### 功能定义 |
|||
|
|||
### 配置定义 |
|||
|
|||
### 如何使用 |
|||
|
|||
|
|||
```csharp |
|||
|
|||
[DependsOn( |
|||
typeof(AbpRulesEngineModule))] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpRulesEngineOptions>(options => |
|||
{ |
|||
// 指定真实存在的本地文件路径, 否则将不会检索本地规则文件 |
|||
options.PhysicalPath = Path.Combine(Directory.GetCurrentDirectory(), "Rules"); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
``` |
|||
|
|||
### 更新日志 |
|||
@ -0,0 +1,76 @@ |
|||
using RulesEngine.Models; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace RulesEngine |
|||
{ |
|||
public static class ListofRuleResultTreeExtension |
|||
{ |
|||
public static void ThrowOfFaildExecute(this IEnumerable<RuleResultTree> ruleResultTrees) |
|||
{ |
|||
List<ValidationResult> validationResults = new List<ValidationResult>(); |
|||
|
|||
ruleResultTrees.WrapValidationResult(validationResults); |
|||
|
|||
if (validationResults.Any()) |
|||
{ |
|||
throw new AbpValidationException("一个或多个规则未通过", validationResults); |
|||
} |
|||
} |
|||
|
|||
private static void WrapValidationResult(this IEnumerable<RuleResultTree> ruleResultTrees, List<ValidationResult> validationResults) |
|||
{ |
|||
var failedResults = ruleResultTrees.Where(rule => !rule.IsSuccess).ToArray(); |
|||
|
|||
foreach (var failedResult in failedResults) |
|||
{ |
|||
string member = null; |
|||
var errorBuilder = new StringBuilder(36); |
|||
if (!failedResult.ExceptionMessage.IsNullOrWhiteSpace()) |
|||
{ |
|||
errorBuilder.AppendLine(failedResult.ExceptionMessage); |
|||
} |
|||
|
|||
// TODO: 需要修改源代码, ChildResults的验证错误个人认为有必要展示出来
|
|||
if (failedResult.ChildResults != null && failedResult.ChildResults.Any()) |
|||
{ |
|||
errorBuilder.Append(failedResult.ChildResults.GetErrorMessage(out member)); |
|||
} |
|||
|
|||
validationResults.Add(new ValidationResult( |
|||
errorBuilder.ToString().TrimEnd(), |
|||
new string[] { member ?? failedResult.Rule?.Properties?.GetOrDefault("Property")?.ToString() ?? "input" })); |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
private static string GetErrorMessage(this IEnumerable<RuleResultTree> ruleResultTrees, out string member) |
|||
{ |
|||
member = null; |
|||
var errorBuilder = new StringBuilder(36); |
|||
var failedResults = ruleResultTrees.Where(rule => !rule.IsSuccess).ToArray(); |
|||
|
|||
for (int index = 0; index < failedResults.Length; index++) |
|||
{ |
|||
member = failedResults[index].Rule?.Properties?.GetOrDefault("Property")?.ToString(); |
|||
|
|||
if (!failedResults[index].ExceptionMessage.IsNullOrWhiteSpace()) |
|||
{ |
|||
errorBuilder.AppendLine(failedResults[index].ExceptionMessage); |
|||
} |
|||
|
|||
if (failedResults[index].ChildResults != null && failedResults[index].ChildResults.Any()) |
|||
{ |
|||
errorBuilder.Append(failedResults[index].ChildResults.GetErrorMessage(out member)); |
|||
} |
|||
} |
|||
|
|||
return errorBuilder.ToString(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.MultiTenancy" Version="4.3.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,26 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.Rules |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpMultiTenancyModule))] |
|||
public class AbpRulesModule : AbpModule |
|||
{ |
|||
public override void OnPostApplicationInitialization(ApplicationInitializationContext context) |
|||
{ |
|||
context.ServiceProvider |
|||
.GetRequiredService<RuleProvider>() |
|||
.Initialize(); |
|||
} |
|||
|
|||
public override void OnApplicationShutdown(ApplicationShutdownContext context) |
|||
{ |
|||
context.ServiceProvider |
|||
.GetRequiredService<RuleProvider>() |
|||
.Shutdown(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using Volo.Abp.Collections; |
|||
|
|||
namespace LINGYUN.Abp.Rules |
|||
{ |
|||
public class AbpRulesOptions |
|||
{ |
|||
public ITypeList<IRuleContributor> Contributors { get; } |
|||
|
|||
public AbpRulesOptions() |
|||
{ |
|||
Contributors = new TypeList<IRuleContributor>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Rules |
|||
{ |
|||
public interface IRuleContributor |
|||
{ |
|||
void Initialize(RulesInitializationContext context); |
|||
|
|||
Task ExecuteAsync<T>(T input, object[] @params = null, CancellationToken cancellationToken = default); |
|||
|
|||
void Shutdown(); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Rules |
|||
{ |
|||
public interface IRuleProvider |
|||
{ |
|||
Task ExecuteAsync<T>(T input, object[] @params = null, CancellationToken cancellationToken = default); |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Rules |
|||
{ |
|||
public abstract class RuleContributorBase : IRuleContributor |
|||
{ |
|||
public ILogger Logger { get; protected set; } |
|||
|
|||
protected RuleContributorBase() |
|||
{ |
|||
Logger = NullLogger<RuleContributorBase>.Instance; |
|||
} |
|||
|
|||
public virtual void Initialize(RulesInitializationContext context) |
|||
{ |
|||
} |
|||
|
|||
public virtual Task ExecuteAsync<T>(T input, object[] @params = null, CancellationToken cancellationToken = default) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public virtual void Shutdown() |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
using System; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.Rules |
|||
{ |
|||
public class RuleIdGenerator : ISingletonDependency |
|||
{ |
|||
private readonly ICurrentTenant _currentTenant; |
|||
|
|||
public RuleIdGenerator( |
|||
ICurrentTenant currentTenant) |
|||
{ |
|||
_currentTenant = currentTenant; |
|||
} |
|||
|
|||
public virtual int CreateRuleId(Type type, bool ignoreMultiTenancy = false) |
|||
{ |
|||
var typeId = type.GetHashCode(); |
|||
|
|||
if (!ignoreMultiTenancy) |
|||
{ |
|||
if (_currentTenant.Id.HasValue) |
|||
{ |
|||
return _currentTenant.Id.GetHashCode() & typeId; |
|||
} |
|||
} |
|||
|
|||
return typeId; |
|||
} |
|||
|
|||
public virtual string CreateRuleName(Type type, bool ignoreMultiTenancy = false) |
|||
{ |
|||
var ruleName = type.Name; |
|||
if (!ignoreMultiTenancy) |
|||
{ |
|||
if (_currentTenant.Id.HasValue) |
|||
{ |
|||
return $"{_currentTenant.Id.Value:D}/{ruleName}"; |
|||
} |
|||
} |
|||
return ruleName; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Rules |
|||
{ |
|||
public class RuleProvider : IRuleProvider, ISingletonDependency |
|||
{ |
|||
private readonly IServiceProvider _serviceProvider; |
|||
private readonly ILogger<RuleProvider> _logger; |
|||
|
|||
private readonly IEnumerable<IRuleContributor> _rulesEngineContributors; |
|||
|
|||
public RuleProvider( |
|||
IServiceProvider serviceProvider, |
|||
IOptions<AbpRulesOptions> options, |
|||
ILogger<RuleProvider> logger) |
|||
{ |
|||
_rulesEngineContributors = options.Value |
|||
.Contributors |
|||
.Select(serviceProvider.GetRequiredService) |
|||
.Cast<IRuleContributor>() |
|||
.ToArray(); |
|||
|
|||
_logger = logger; |
|||
_serviceProvider = serviceProvider; |
|||
} |
|||
|
|||
public virtual async Task ExecuteAsync<T>(T input, object[] @params = null, CancellationToken cancellationToken = default) |
|||
{ |
|||
_logger.LogDebug("Starting all typed rules engine."); |
|||
|
|||
foreach (var contributor in _rulesEngineContributors) |
|||
{ |
|||
await contributor.ExecuteAsync(input, @params, cancellationToken); |
|||
} |
|||
|
|||
_logger.LogDebug("Executed all typed rules engine."); |
|||
} |
|||
|
|||
internal void Initialize() |
|||
{ |
|||
var context = new RulesInitializationContext(_serviceProvider); |
|||
|
|||
foreach (var contributor in _rulesEngineContributors) |
|||
{ |
|||
contributor.Initialize(context); |
|||
} |
|||
} |
|||
|
|||
internal void Shutdown() |
|||
{ |
|||
foreach (var contributor in _rulesEngineContributors) |
|||
{ |
|||
contributor.Shutdown(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Rules |
|||
{ |
|||
public class RulesInitializationContext : IServiceProvider |
|||
{ |
|||
public IServiceProvider ServiceProvider { get; } |
|||
|
|||
internal RulesInitializationContext(IServiceProvider serviceProvider) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
} |
|||
|
|||
public object GetService(Type serviceType) => ServiceProvider.GetService(serviceType); |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
# LINGYUN.Abp.Rules |
|||
|
|||
## 模块说明 |
|||
|
|||
规则引擎基础模块 |
|||
|
|||
### 基础模块 |
|||
|
|||
### 高阶模块 |
|||
|
|||
### 权限定义 |
|||
|
|||
### 功能定义 |
|||
|
|||
### 配置定义 |
|||
|
|||
### 如何使用 |
|||
|
|||
|
|||
```csharp |
|||
|
|||
[DependsOn( |
|||
typeof(AbpRulesModule))] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
} |
|||
|
|||
public class YouService |
|||
{ |
|||
private readonly IRuleProvider _ruleProvider; |
|||
|
|||
public YouService(IRuleProvider ruleProvider) |
|||
{ |
|||
_ruleProvider = ruleProvider; |
|||
} |
|||
|
|||
public async Task DoAsync() |
|||
{ |
|||
var input = new YouInput(); |
|||
// 规则校验 |
|||
await _ruleProvider.ExecuteAsync(input); |
|||
|
|||
var inputParams = new object[1] |
|||
{ |
|||
new InputParam() |
|||
}; |
|||
// 带参数规则校验 |
|||
await _ruleProvider.ExecuteAsync(input, inputParams); |
|||
} |
|||
} |
|||
|
|||
``` |
|||
|
|||
### 更新日志 |
|||
@ -0,0 +1,27 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net5.0</TargetFramework> |
|||
<RootNamespace /> |
|||
<IsPackable>false</IsPackable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" /> |
|||
<PackageReference Include="xunit" Version="2.4.1" /> |
|||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> |
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
|||
<PrivateAssets>all</PrivateAssets> |
|||
</PackageReference> |
|||
<PackageReference Include="coverlet.collector" Version="1.3.0"> |
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
|||
<PrivateAssets>all</PrivateAssets> |
|||
</PackageReference> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\modules\oss-management\LINGYUN.Abp.OssManagement.HttpApi.Client\LINGYUN.Abp.OssManagement.HttpApi.Client.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,13 @@ |
|||
using LINGYUN.Abp.Tests; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.OssManagement |
|||
{ |
|||
public class AbpOssManagementHttpApiClientTestBase : AbpTestsBase<AbpOssManagementHttpApiClientTestModule> |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using LINGYUN.Abp.Tests; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.OssManagement |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpTestsBaseModule), |
|||
typeof(AbpOssManagementHttpApiClientModule))] |
|||
public class AbpOssManagementHttpApiClientTestModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.ReplaceConfiguration( |
|||
ConfigurationHelper.BuildConfiguration( |
|||
new AbpConfigurationBuilderOptions |
|||
{ |
|||
BasePath = @"D:\Projects\Development\Abp\OssManagement", |
|||
EnvironmentName = "Development" |
|||
})); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Http.Client.DynamicProxying; |
|||
using Xunit; |
|||
|
|||
namespace LINGYUN.Abp.OssManagement |
|||
{ |
|||
public class OssObjectAppServiceTests : AbpOssManagementHttpApiClientTestBase |
|||
{ |
|||
private readonly IOssObjectAppService _service; |
|||
|
|||
public OssObjectAppServiceTests() |
|||
{ |
|||
_service = GetRequiredService<IOssObjectAppService>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Get_By_Bucket_And_Object_Shouldly_Not_Null() |
|||
{ |
|||
var ossObject = await _service |
|||
.GetAsync( |
|||
new GetOssObjectInput |
|||
{ |
|||
Bucket = "abp-file-management", |
|||
Object = "123.png" |
|||
}); |
|||
|
|||
ossObject.ShouldNotBeNull(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Http.Client.DynamicProxying; |
|||
using Xunit; |
|||
|
|||
namespace LINGYUN.Abp.OssManagement |
|||
{ |
|||
public class OssObjectAppServiceTests : AbpOssManagementHttpApiClientTestBase |
|||
{ |
|||
private readonly IOssObjectAppService _service; |
|||
|
|||
public OssObjectAppServiceTests() |
|||
{ |
|||
_service = GetRequiredService<IOssObjectAppService>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Get_By_Bucket_And_Object_Shouldly_Not_Null() |
|||
{ |
|||
var ossObject = await _service |
|||
.GetAsync( |
|||
new GetOssObjectInput |
|||
{ |
|||
Bucket = "abp-file-management", |
|||
Object = "123.png" |
|||
}); |
|||
|
|||
ossObject.ShouldNotBeNull(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net5.0</TargetFramework> |
|||
<RootNamespace /> |
|||
<IsPackable>false</IsPackable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" /> |
|||
<PackageReference Include="xunit" Version="2.4.1" /> |
|||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> |
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
|||
<PrivateAssets>all</PrivateAssets> |
|||
</PackageReference> |
|||
<PackageReference Include="coverlet.collector" Version="1.3.0"> |
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
|||
<PrivateAssets>all</PrivateAssets> |
|||
</PackageReference> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\modules\rules\LINGYUN.Abp.Rules.RulesEngine\LINGYUN.Abp.Rules.RulesEngine.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Update="Rules\TestMultipleRuleInput.json"> |
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> |
|||
</None> |
|||
<None Update="Rules\TestInput.json"> |
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> |
|||
</None> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,8 @@ |
|||
using LINGYUN.Abp.Tests; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
public class AbpRulesEngineTestBase : AbpTestsBase<AbpRulesEngineTestModule> |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using LINGYUN.Abp.Tests; |
|||
using System.IO; |
|||
using Volo.Abp.Modularity; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpRulesEngineModule), |
|||
typeof(AbpTestsBaseModule))] |
|||
public class AbpRulesEngineTestModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddMemoryCache(); |
|||
|
|||
Configure<AbpRulesEngineOptions>(options => |
|||
{ |
|||
options.PhysicalPath = Path.Combine(Directory.GetCurrentDirectory(), "Rules"); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
public class TestInput |
|||
{ |
|||
public string Required { get; set; } |
|||
|
|||
public int Integer1 { get; set; } |
|||
|
|||
public int Integer2 { get; set; } |
|||
|
|||
public string Length { get; set; } |
|||
} |
|||
|
|||
public class TestMultipleRuleInput |
|||
{ |
|||
public string Required { get; set; } |
|||
|
|||
public int Integer1 { get; set; } |
|||
|
|||
public int Integer2 { get; set; } |
|||
|
|||
public string Length { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
using Shouldly; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Validation; |
|||
using Xunit; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
public class TestInputRuleTests : AbpRulesEngineTestBase |
|||
{ |
|||
private readonly IRuleProvider _ruleProvider; |
|||
|
|||
public TestInputRuleTests() |
|||
{ |
|||
_ruleProvider = GetRequiredService<IRuleProvider>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Input_Required_Should_Required() |
|||
{ |
|||
var input = new TestInput |
|||
{ |
|||
Integer1 = 101, |
|||
Integer2 = 99, |
|||
Length = "123456" |
|||
} |
|||
; |
|||
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () => |
|||
{ |
|||
await _ruleProvider.ExecuteAsync(input); |
|||
}); |
|||
|
|||
exception.Message.ShouldBe("一个或多个规则未通过"); |
|||
exception.ValidationErrors.Count.ShouldBe(1); |
|||
exception.ValidationErrors[0].ErrorMessage.ShouldBe("字段 Required 必须输入!"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Input_Integer1_Should_MustBeGreaterThan100() |
|||
{ |
|||
var input = new TestInput |
|||
{ |
|||
Required = "123456", |
|||
Integer1 = 99, |
|||
Integer2 = 99, |
|||
Length = "123456" |
|||
}; |
|||
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () => |
|||
{ |
|||
await _ruleProvider.ExecuteAsync(input); |
|||
}); |
|||
|
|||
exception.Message.ShouldBe("一个或多个规则未通过"); |
|||
exception.ValidationErrors.Count.ShouldBe(1); |
|||
exception.ValidationErrors[0].ErrorMessage.ShouldBe("字段 Integer1 必须大于100!"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Input_Integer2_Should_MustBeLessThan100() |
|||
{ |
|||
var input = new TestInput |
|||
{ |
|||
Required = "123456", |
|||
Integer1 = 101, |
|||
Integer2 = 100, |
|||
Length = "123456" |
|||
}; |
|||
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () => |
|||
{ |
|||
await _ruleProvider.ExecuteAsync(input); |
|||
}); |
|||
|
|||
exception.Message.ShouldBe("一个或多个规则未通过"); |
|||
exception.ValidationErrors.Count.ShouldBe(1); |
|||
exception.ValidationErrors[0].ErrorMessage.ShouldBe("字段 Integer2 必须小于100!"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Input_Sum_Integer1_And_Integer2_Should_MustBeGreaterThan150() |
|||
{ |
|||
var input = new TestInput |
|||
{ |
|||
Required = "1", |
|||
Integer1 = 101, |
|||
Integer2 = 48, |
|||
Length = "123456" |
|||
}; |
|||
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () => |
|||
{ |
|||
await _ruleProvider.ExecuteAsync(input); |
|||
}); |
|||
|
|||
exception.Message.ShouldBe("一个或多个规则未通过"); |
|||
exception.ValidationErrors.Count.ShouldBe(1); |
|||
exception.ValidationErrors[0].ErrorMessage.ShouldBe("字段 Integer1 与 Integer2 之和 必须大于150!"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Input_Required_Length_Should_MustBeGreaterThan5() |
|||
{ |
|||
var input = new TestInput |
|||
{ |
|||
Required = "1", |
|||
Integer1 = 101, |
|||
Integer2 = 50, |
|||
Length = "12345" |
|||
}; |
|||
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () => |
|||
{ |
|||
await _ruleProvider.ExecuteAsync(input); |
|||
}); |
|||
|
|||
exception.Message.ShouldBe("一个或多个规则未通过"); |
|||
exception.ValidationErrors.Count.ShouldBe(1); |
|||
exception.ValidationErrors[0].ErrorMessage.ShouldBe("字段 Length 长度必须大于5!"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
using Shouldly; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Validation; |
|||
using Xunit; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
public class TestMultipleRuleInputTests : AbpRulesEngineTestBase |
|||
{ |
|||
private readonly IRuleProvider _ruleProvider; |
|||
|
|||
public TestMultipleRuleInputTests() |
|||
{ |
|||
_ruleProvider = GetRequiredService<IRuleProvider>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Multiple_Rule_Input_Should_Failed() |
|||
{ |
|||
var input = new TestMultipleRuleInput |
|||
{ |
|||
Length = "12345", |
|||
Integer1 = 100, |
|||
Integer2 = 100 |
|||
}; |
|||
|
|||
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () => |
|||
{ |
|||
await _ruleProvider.ExecuteAsync(input); |
|||
}); |
|||
|
|||
exception.Message.ShouldBe("一个或多个规则未通过"); |
|||
exception.ValidationErrors.Count.ShouldBe(2); |
|||
exception.ValidationErrors[0].ErrorMessage.ShouldBe("输入字段验证无效!"); |
|||
exception.ValidationErrors[1].ErrorMessage.ShouldBe("长度与求和验证无效!"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
[ |
|||
{ |
|||
"WorkflowName": "TestInputValidation", |
|||
"Rules": [ |
|||
{ |
|||
"RuleName": "InputRequired", |
|||
"ErrorMessage": "字段 Required 必须输入!", |
|||
"ErrorType": "Error", |
|||
"RuleExpressionType": "LambdaExpression", |
|||
"Expression": "!string.IsNullOrWhiteSpace(input1.Required)" |
|||
}, |
|||
{ |
|||
"RuleName": "MustBeGreaterThan100", |
|||
"ErrorMessage": "字段 Integer1 必须大于100!", |
|||
"ErrorType": "Error", |
|||
"RuleExpressionType": "LambdaExpression", |
|||
"Expression": "input1.Integer1 > 100" |
|||
}, |
|||
{ |
|||
"RuleName": "MustBeLessThan100", |
|||
"ErrorMessage": "字段 Integer2 必须小于100!", |
|||
"ErrorType": "Error", |
|||
"RuleExpressionType": "LambdaExpression", |
|||
"Expression": "input1.Integer2 < 100" |
|||
}, |
|||
{ |
|||
"RuleName": "SumMustBeGreaterThan150", |
|||
"ErrorMessage": "字段 Integer1 与 Integer2 之和 必须大于150!", |
|||
"ErrorType": "Error", |
|||
"RuleExpressionType": "LambdaExpression", |
|||
"Expression": "input1.Integer1 + input1.Integer2 > 150" |
|||
}, |
|||
{ |
|||
"RuleName": "RequiredLengthMustBeGreaterThan5", |
|||
"ErrorMessage": "字段 Length 长度必须大于5!", |
|||
"ErrorType": "Error", |
|||
"RuleExpressionType": "LambdaExpression", |
|||
"Expression": "input1.Length.Length > 5" |
|||
} |
|||
] |
|||
} |
|||
] |
|||
@ -0,0 +1,58 @@ |
|||
[ |
|||
{ |
|||
"WorkflowName": "TestMultipleRuleInputValidation", |
|||
"Rules": [ |
|||
{ |
|||
"RuleName": "ValidationInputField", |
|||
"ErrorMessage": "输入字段验证无效!", |
|||
"ErrorType": "Error", |
|||
"Operator": "AndAlso", |
|||
"Rules": [ |
|||
{ |
|||
"RuleName": "InputRequired", |
|||
"ErrorMessage": "字段 Required 必须输入!", |
|||
"ErrorType": "Error", |
|||
"RuleExpressionType": "LambdaExpression", |
|||
"Expression": "!string.IsNullOrWhiteSpace(input1.Required)" |
|||
}, |
|||
{ |
|||
"RuleName": "MustBeGreaterThan100", |
|||
"ErrorMessage": "字段 Integer1 必须大于100!", |
|||
"ErrorType": "Error", |
|||
"RuleExpressionType": "LambdaExpression", |
|||
"Expression": "input1.Integer1 > 100" |
|||
}, |
|||
{ |
|||
"RuleName": "MustBeLessThan100", |
|||
"ErrorMessage": "字段 Integer2 必须小于100!", |
|||
"ErrorType": "Error", |
|||
"RuleExpressionType": "LambdaExpression", |
|||
"Expression": "input1.Integer2 < 100" |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
"RuleName": "ValidationLengthAndSum", |
|||
"ErrorMessage": "长度与求和验证无效!", |
|||
"ErrorType": "Error", |
|||
"Operator": "AndAlso", |
|||
"Rules": [ |
|||
{ |
|||
"RuleName": "SumMustBeGreaterThan150", |
|||
"ErrorMessage": "字段 Integer1 与 Integer2 之和 必须大于200!", |
|||
"ErrorType": "Error", |
|||
"RuleExpressionType": "LambdaExpression", |
|||
"Expression": "input1.Integer1 + input1.Integer2 > 200" |
|||
}, |
|||
{ |
|||
"RuleName": "RequiredLengthMustBeGreaterThan5", |
|||
"ErrorMessage": "字段 Length 长度必须大于5!", |
|||
"ErrorType": "Error", |
|||
"RuleExpressionType": "LambdaExpression", |
|||
"Expression": "input1.Length.Length > 5" |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
] |
|||
Loading…
Reference in new issue