10 changed files with 477 additions and 466 deletions
@ -1,16 +1,24 @@ |
|||
using Volo.Abp.Collections; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
public class AbpRulesEngineOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 是否忽略租户
|
|||
/// </summary>
|
|||
public bool IgnoreMultiTenancy { get; set; } |
|||
|
|||
public AbpRulesEngineOptions() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
using RulesEngine.Models; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
public class AbpRulesEngineOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 是否忽略租户
|
|||
/// </summary>
|
|||
public bool IgnoreMultiTenancy { get; set; } |
|||
/// <summary>
|
|||
/// 规则引擎可配置
|
|||
/// </summary>
|
|||
public ReSettings Settings { get; set; } |
|||
|
|||
public AbpRulesEngineOptions() |
|||
{ |
|||
Settings = new ReSettings |
|||
{ |
|||
NestedRuleExecutionMode = NestedRuleExecutionMode.Performance |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,105 +1,104 @@ |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
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 FileProviderWorkflowRulesResolveContributor : WorkflowRulesResolveContributorBase |
|||
{ |
|||
protected IMemoryCache RulesCache { get; private set; } |
|||
protected IJsonSerializer JsonSerializer { get; private set; } |
|||
|
|||
protected IFileProvider FileProvider { get; private set; } |
|||
|
|||
protected FileProviderWorkflowRulesResolveContributor() |
|||
{ |
|||
} |
|||
|
|||
public override void Initialize(RulesInitializationContext context) |
|||
{ |
|||
Initialize(context.ServiceProvider); |
|||
|
|||
RulesCache = context.ServiceProvider.GetRequiredService<IMemoryCache>(); |
|||
JsonSerializer = context.ServiceProvider.GetRequiredService<IJsonSerializer>(); |
|||
|
|||
FileProvider = BuildFileProvider(context); |
|||
} |
|||
|
|||
protected virtual void Initialize(IServiceProvider serviceProvider) |
|||
{ |
|||
} |
|||
|
|||
protected abstract IFileProvider BuildFileProvider(RulesInitializationContext context); |
|||
|
|||
public override async Task ResolveAsync(IWorkflowRulesResolveContext context) |
|||
{ |
|||
if (FileProvider != null) |
|||
{ |
|||
context.WorkflowRules = await GetCachedRulesAsync(context.Type); |
|||
} |
|||
context.Handled = true; |
|||
} |
|||
|
|||
public override void Shutdown() |
|||
{ |
|||
if (FileProvider != null && FileProvider is IDisposable resource) |
|||
{ |
|||
resource.Dispose(); |
|||
} |
|||
} |
|||
|
|||
private async Task<WorkflowRules[]> GetCachedRulesAsync(Type type, CancellationToken cancellationToken = default) |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
|
|||
var ruleId = GetRuleId(type); |
|||
|
|||
return await RulesCache.GetOrCreateAsync(ruleId, |
|||
async (entry) => |
|||
{ |
|||
entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(30)); |
|||
|
|||
return await GetFileSystemRulesAsync(type, cancellationToken); |
|||
}); |
|||
} |
|||
protected abstract int GetRuleId(Type type); |
|||
|
|||
protected abstract string GetRuleName(Type type); |
|||
|
|||
protected virtual async Task<WorkflowRules[]> GetFileSystemRulesAsync(Type type, CancellationToken cancellationToken = default) |
|||
{ |
|||
var ruleId = GetRuleId(type); |
|||
var ruleFile = GetRuleName(type); |
|||
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]; |
|||
} |
|||
} |
|||
} |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
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 FileProviderWorkflowRulesResolveContributor : WorkflowRulesResolveContributorBase |
|||
{ |
|||
protected IMemoryCache RulesCache { get; private set; } |
|||
protected IJsonSerializer JsonSerializer { get; private set; } |
|||
|
|||
protected IFileProvider FileProvider { get; private set; } |
|||
protected FileProviderWorkflowRulesResolveContributor() |
|||
{ |
|||
} |
|||
|
|||
public override void Initialize(RulesInitializationContext context) |
|||
{ |
|||
Initialize(context.ServiceProvider); |
|||
|
|||
RulesCache = context.GetRequiredService<IMemoryCache>(); |
|||
JsonSerializer = context.GetRequiredService<IJsonSerializer>(); |
|||
|
|||
FileProvider = BuildFileProvider(context); |
|||
} |
|||
|
|||
protected virtual void Initialize(IServiceProvider serviceProvider) |
|||
{ |
|||
} |
|||
|
|||
protected abstract IFileProvider BuildFileProvider(RulesInitializationContext context); |
|||
|
|||
public override async Task ResolveAsync(IWorkflowRulesResolveContext context) |
|||
{ |
|||
if (FileProvider != null) |
|||
{ |
|||
context.WorkflowRules = await GetCachedRulesAsync(context.Type); |
|||
} |
|||
context.Handled = true; |
|||
} |
|||
|
|||
public override void Shutdown() |
|||
{ |
|||
if (FileProvider != null && FileProvider is IDisposable resource) |
|||
{ |
|||
resource.Dispose(); |
|||
} |
|||
} |
|||
|
|||
private async Task<WorkflowRules[]> GetCachedRulesAsync(Type type, CancellationToken cancellationToken = default) |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
|
|||
var ruleId = GetRuleId(type); |
|||
|
|||
return await RulesCache.GetOrCreateAsync(ruleId, |
|||
async (entry) => |
|||
{ |
|||
entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(30)); |
|||
|
|||
return await GetFileSystemRulesAsync(type, cancellationToken); |
|||
}); |
|||
} |
|||
protected abstract int GetRuleId(Type type); |
|||
|
|||
protected abstract string GetRuleName(Type type); |
|||
|
|||
protected virtual async Task<WorkflowRules[]> GetFileSystemRulesAsync(Type type, CancellationToken cancellationToken = default) |
|||
{ |
|||
var ruleId = GetRuleId(type); |
|||
var ruleFile = GetRuleName(type); |
|||
var fileInfo = FileProvider.GetFileInfo(ruleFile); |
|||
if (fileInfo != null && fileInfo.Exists) |
|||
{ |
|||
// 规则文件监控
|
|||
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]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
20
aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/FileProviders/Physical/AbpRulesEnginePthsicalFileResolveOptions.cs → aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/FileProviders/Physical/AbpRulesEnginePhysicalFileResolveOptions.cs
20
aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/FileProviders/Physical/AbpRulesEnginePthsicalFileResolveOptions.cs → aspnet-core/modules/rules/LINGYUN.Abp.Rules.RulesEngine/LINGYUN/Abp/Rules/RulesEngine/FileProviders/Physical/AbpRulesEnginePhysicalFileResolveOptions.cs
@ -1,10 +1,10 @@ |
|||
namespace LINGYUN.Abp.Rules.RulesEngine.FileProviders.Physical |
|||
{ |
|||
public class AbpRulesEnginePthsicalFileResolveOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 本地文件路径
|
|||
/// </summary>
|
|||
public string PhysicalPath { get; set; } |
|||
} |
|||
} |
|||
namespace LINGYUN.Abp.Rules.RulesEngine.FileProviders.Physical |
|||
{ |
|||
public class AbpRulesEnginePhysicalFileResolveOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 本地文件路径
|
|||
/// </summary>
|
|||
public string PhysicalPath { get; set; } |
|||
} |
|||
} |
|||
@ -1,44 +1,44 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.IO; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine.FileProviders.Physical |
|||
{ |
|||
public class PhysicalFileWorkflowRulesResolveContributor : FileProviderWorkflowRulesResolveContributor, ISingletonDependency |
|||
{ |
|||
public override string Name => "PhysicalFile"; |
|||
|
|||
private RuleIdGenerator _ruleIdGenerator; |
|||
private AbpRulesEngineOptions _rulesEngineOptions; |
|||
private AbpRulesEnginePthsicalFileResolveOptions _fileResolveOptions; |
|||
|
|||
public PhysicalFileWorkflowRulesResolveContributor() |
|||
{ |
|||
} |
|||
|
|||
protected override void Initialize(IServiceProvider serviceProvider) |
|||
{ |
|||
_ruleIdGenerator = serviceProvider.GetRequiredService<RuleIdGenerator>(); |
|||
_rulesEngineOptions = serviceProvider.GetRequiredService<IOptions<AbpRulesEngineOptions>>().Value; |
|||
_fileResolveOptions = serviceProvider.GetRequiredService<IOptions<AbpRulesEnginePthsicalFileResolveOptions>>().Value; |
|||
} |
|||
|
|||
protected override IFileProvider BuildFileProvider(RulesInitializationContext context) |
|||
{ |
|||
// 未指定路径不启用
|
|||
if (!_fileResolveOptions.PhysicalPath.IsNullOrWhiteSpace() && |
|||
Directory.Exists(_fileResolveOptions.PhysicalPath)) |
|||
{ |
|||
return new PhysicalFileProvider(_fileResolveOptions.PhysicalPath); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
protected override int GetRuleId(Type type) => _ruleIdGenerator.CreateRuleId(type, _rulesEngineOptions.IgnoreMultiTenancy); |
|||
|
|||
protected override string GetRuleName(Type type) => $"{_ruleIdGenerator.CreateRuleName(type, _rulesEngineOptions.IgnoreMultiTenancy)}.json"; |
|||
} |
|||
} |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.IO; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine.FileProviders.Physical |
|||
{ |
|||
public class PhysicalFileWorkflowRulesResolveContributor : FileProviderWorkflowRulesResolveContributor, ISingletonDependency |
|||
{ |
|||
public override string Name => "PhysicalFile"; |
|||
|
|||
private RuleIdGenerator _ruleIdGenerator; |
|||
private AbpRulesEngineOptions _rulesEngineOptions; |
|||
private AbpRulesEnginePhysicalFileResolveOptions _fileResolveOptions; |
|||
|
|||
public PhysicalFileWorkflowRulesResolveContributor() |
|||
{ |
|||
} |
|||
|
|||
protected override void Initialize(IServiceProvider serviceProvider) |
|||
{ |
|||
_ruleIdGenerator = serviceProvider.GetRequiredService<RuleIdGenerator>(); |
|||
_rulesEngineOptions = serviceProvider.GetRequiredService<IOptions<AbpRulesEngineOptions>>().Value; |
|||
_fileResolveOptions = serviceProvider.GetRequiredService<IOptions<AbpRulesEnginePhysicalFileResolveOptions>>().Value; |
|||
} |
|||
|
|||
protected override IFileProvider BuildFileProvider(RulesInitializationContext context) |
|||
{ |
|||
// 未指定路径不启用
|
|||
if (!_fileResolveOptions.PhysicalPath.IsNullOrWhiteSpace() && |
|||
Directory.Exists(_fileResolveOptions.PhysicalPath)) |
|||
{ |
|||
return new PhysicalFileProvider(_fileResolveOptions.PhysicalPath); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
protected override int GetRuleId(Type type) => _ruleIdGenerator.CreateRuleId(type, _rulesEngineOptions.IgnoreMultiTenancy); |
|||
|
|||
protected override string GetRuleName(Type type) => $"{_ruleIdGenerator.CreateRuleName(type, _rulesEngineOptions.IgnoreMultiTenancy)}.json"; |
|||
} |
|||
} |
|||
|
|||
@ -1,82 +1,81 @@ |
|||
using RulesEngine; |
|||
using RulesEngine.Interfaces; |
|||
using RulesEngine.Models; |
|||
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 IWorkflowRulesResolver _workflowRulesResolver; |
|||
|
|||
public RulesEngineContributor( |
|||
IWorkflowRulesResolver workflowRulesResolver) |
|||
{ |
|||
_workflowRulesResolver = workflowRulesResolver; |
|||
} |
|||
|
|||
public override void Initialize(RulesInitializationContext context) |
|||
{ |
|||
_ruleEngine = CreateRulesEngine(); |
|||
|
|||
_workflowRulesResolver.Initialize(context); |
|||
} |
|||
|
|||
public override async Task ExecuteAsync<T>(T input, object[] @params = null, CancellationToken cancellationToken = default) |
|||
{ |
|||
var result = await _workflowRulesResolver.ResolveWorkflowRulesAsync(typeof(T)); |
|||
|
|||
if (result.WorkflowRules.Any()) |
|||
{ |
|||
await ExecuteRulesAsync(input, result.WorkflowRules.ToArray(), @params); |
|||
} |
|||
} |
|||
|
|||
public override void Shutdown() |
|||
{ |
|||
} |
|||
/// <summary>
|
|||
/// 重写自行构建规则引擎
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
protected virtual Engine CreateRulesEngine() |
|||
{ |
|||
var reSetting = new ReSettings |
|||
{ |
|||
NestedRuleExecutionMode = NestedRuleExecutionMode.Performance |
|||
}; |
|||
|
|||
return new Engine(Logger, reSetting); |
|||
} |
|||
|
|||
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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
using Microsoft.Extensions.Options; |
|||
using RulesEngine; |
|||
using RulesEngine.Interfaces; |
|||
using RulesEngine.Models; |
|||
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 AbpRulesEngineOptions _options; |
|||
private readonly IWorkflowRulesResolver _workflowRulesResolver; |
|||
|
|||
public RulesEngineContributor( |
|||
IWorkflowRulesResolver workflowRulesResolver, |
|||
IOptions<AbpRulesEngineOptions> options) |
|||
{ |
|||
_options = options.Value; |
|||
_workflowRulesResolver = workflowRulesResolver; |
|||
} |
|||
|
|||
public override void Initialize(RulesInitializationContext context) |
|||
{ |
|||
_ruleEngine = CreateRulesEngine(); |
|||
_workflowRulesResolver.Initialize(context); |
|||
} |
|||
|
|||
public override async Task ExecuteAsync<T>(T input, object[] @params = null, CancellationToken cancellationToken = default) |
|||
{ |
|||
var result = await _workflowRulesResolver.ResolveWorkflowRulesAsync(typeof(T)); |
|||
|
|||
if (result.WorkflowRules.Any()) |
|||
{ |
|||
await ExecuteRulesAsync(input, result.WorkflowRules.ToArray(), @params); |
|||
} |
|||
} |
|||
|
|||
public override void Shutdown() |
|||
{ |
|||
} |
|||
/// <summary>
|
|||
/// 重写自行构建规则引擎
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
protected virtual Engine CreateRulesEngine() |
|||
{ |
|||
return new Engine(Logger, _options.Settings); |
|||
} |
|||
|
|||
protected virtual async Task ExecuteRulesAsync<T>(T input, WorkflowRules[] workflowRules, object[] @params = null) |
|||
{ |
|||
// TODO: 性能缺陷 规则文件每一次调用都会重复编译
|
|||
_ruleEngine.AddOrUpdateWorkflow(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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,54 +1,54 @@ |
|||
# LINGYUN.Abp.Rules.RulesEngine |
|||
|
|||
## 模块说明 |
|||
|
|||
集成微软规则引擎的实现 |
|||
|
|||
默认实现一个本地文件系统规则提供者,根据用户配置的 **AbpRulesEnginePthsicalFileResolveOptions.PhysicalPath** 路径检索规则文件 |
|||
|
|||
文件名如下: |
|||
|
|||
PhysicalPath/CurrentTenant.Id[如果存在]/验证规则实体类型名称[typeof(Input).Name].json |
|||
|
|||
自定义的规则提供者需要实现 **IWorkflowRulesResolveContributor** 接口,可能不需要实现初始化与释放资源,因此提供了一个抽象的 **WorkflowRulesResolveContributorBase** |
|||
|
|||
并添加到 **AbpRulesEngineResolveOptions.WorkflowRulesResolvers** 中 |
|||
|
|||
### 基础模块 |
|||
|
|||
### 高阶模块 |
|||
|
|||
### 权限定义 |
|||
|
|||
### 功能定义 |
|||
|
|||
### 配置定义 |
|||
|
|||
### 如何使用 |
|||
|
|||
|
|||
```csharp |
|||
|
|||
[DependsOn( |
|||
typeof(AbpRulesEngineModule))] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpRulesEngineResolveOptions>(options => |
|||
{ |
|||
// 添加自行实现的规则解析提供者 |
|||
options.WorkflowRulesResolvers.Add(new FakeWorkflowRulesResolveContributor()); |
|||
}); |
|||
|
|||
Configure<AbpRulesEnginePthsicalFileResolveOptions>(options => |
|||
{ |
|||
// 指定真实存在的本地文件路径, 否则将不会检索本地规则文件 |
|||
options.PhysicalPath = Path.Combine(Directory.GetCurrentDirectory(), "Rules"); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
``` |
|||
|
|||
### 更新日志 |
|||
# LINGYUN.Abp.Rules.RulesEngine |
|||
|
|||
## 模块说明 |
|||
|
|||
集成微软规则引擎的实现 |
|||
|
|||
默认实现一个本地文件系统规则提供者,根据用户配置的 **AbpRulesEnginePthsicalFileResolveOptions.PhysicalPath** 路径检索规则文件 |
|||
|
|||
文件名如下: |
|||
|
|||
PhysicalPath/CurrentTenant.Id[如果存在]/验证规则实体类型名称[typeof(Input).Name].json |
|||
|
|||
自定义的规则提供者需要实现 **IWorkflowRulesResolveContributor** 接口,可能不需要实现初始化与释放资源,因此提供了一个抽象的 **WorkflowRulesResolveContributorBase** |
|||
|
|||
并添加到 **AbpRulesEngineResolveOptions.WorkflowRulesResolvers** 中 |
|||
|
|||
### 基础模块 |
|||
|
|||
### 高阶模块 |
|||
|
|||
### 权限定义 |
|||
|
|||
### 功能定义 |
|||
|
|||
### 配置定义 |
|||
|
|||
### 如何使用 |
|||
|
|||
|
|||
```csharp |
|||
|
|||
[DependsOn( |
|||
typeof(AbpRulesEngineModule))] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpRulesEngineResolveOptions>(options => |
|||
{ |
|||
// 添加自行实现的规则解析提供者 |
|||
options.WorkflowRulesResolvers.Add(new FakeWorkflowRulesResolveContributor()); |
|||
}); |
|||
|
|||
Configure<AbpRulesEnginePhysicalFileResolveOptions>(options => |
|||
{ |
|||
// 指定真实存在的本地文件路径, 否则将不会检索本地规则文件 |
|||
options.PhysicalPath = Path.Combine(Directory.GetCurrentDirectory(), "Rules"); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
``` |
|||
|
|||
### 更新日志 |
|||
|
|||
@ -1,16 +1,20 @@ |
|||
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); |
|||
} |
|||
} |
|||
using System; |
|||
using Volo.Abp.Data; |
|||
|
|||
namespace LINGYUN.Abp.Rules |
|||
{ |
|||
public class RulesInitializationContext : IServiceProvider, IHasExtraProperties |
|||
{ |
|||
public IServiceProvider ServiceProvider { get; } |
|||
|
|||
public ExtraPropertyDictionary ExtraProperties { get; } |
|||
|
|||
internal RulesInitializationContext(IServiceProvider serviceProvider) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
ExtraProperties = new ExtraPropertyDictionary(); |
|||
} |
|||
|
|||
public object GetService(Type serviceType) => ServiceProvider.GetService(serviceType); |
|||
} |
|||
} |
|||
|
|||
@ -1,21 +1,21 @@ |
|||
using LINGYUN.Abp.Rules.RulesEngine.FileProviders.Physical; |
|||
using LINGYUN.Abp.Tests; |
|||
using System.IO; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpRulesEngineModule), |
|||
typeof(AbpTestsBaseModule))] |
|||
public class AbpRulesEngineTestModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpRulesEnginePthsicalFileResolveOptions>(options => |
|||
{ |
|||
options.PhysicalPath = Path.Combine(Directory.GetCurrentDirectory(), "Rules"); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
using LINGYUN.Abp.Rules.RulesEngine.FileProviders.Physical; |
|||
using LINGYUN.Abp.Tests; |
|||
using System.IO; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Rules.RulesEngine |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpRulesEngineModule), |
|||
typeof(AbpTestsBaseModule))] |
|||
public class AbpRulesEngineTestModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpRulesEnginePhysicalFileResolveOptions>(options => |
|||
{ |
|||
options.PhysicalPath = Path.Combine(Directory.GetCurrentDirectory(), "Rules"); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,117 +1,118 @@ |
|||
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!"); |
|||
} |
|||
} |
|||
} |
|||
using Shouldly; |
|||
using System; |
|||
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!"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue