27 changed files with 518 additions and 10 deletions
@ -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.Ddd.Domain" Version="3.1.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,16 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Domain; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpDddDomainModule))] |
||||
|
public class AbpRulesEngineModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.OnRegistred(EntityChangedRulesInterceptorRegistrar.RegisterIfNeeded); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using Volo.Abp.DynamicProxy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
public class EntityChangedRulesInterceptor : AbpInterceptor, ITransientDependency |
||||
|
{ |
||||
|
protected IRuleFinder RuleFinder { get; } |
||||
|
protected IEntityRuleContributor EntityRuleContributor { get; } |
||||
|
|
||||
|
public EntityChangedRulesInterceptor( |
||||
|
IRuleFinder ruleFinder, |
||||
|
IEntityRuleContributor entityRuleContributor) |
||||
|
{ |
||||
|
RuleFinder = ruleFinder; |
||||
|
EntityRuleContributor = entityRuleContributor; |
||||
|
} |
||||
|
|
||||
|
public override async Task InterceptAsync(IAbpMethodInvocation invocation) |
||||
|
{ |
||||
|
var entityObj = invocation.Arguments.First(); |
||||
|
// TODO: 针对实体的变更执行一次定义的规则
|
||||
|
// IBasicRepository.InsertAsync || IBasicRepository.UpdateAsync || IBasicRepository.DeleteAsync
|
||||
|
if (entityObj != null && entityObj is IEntity entity) |
||||
|
{ |
||||
|
await ApplyEntityRuleAsync(entity); |
||||
|
} |
||||
|
|
||||
|
await invocation.ProceedAsync(); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task ApplyEntityRuleAsync(IEntity entity) |
||||
|
{ |
||||
|
Type entityType = ProxyHelper.GetUnProxiedType(entity); |
||||
|
// 加载规则列表
|
||||
|
var groups = await RuleFinder.GetRuleGroupsAsync(entityType); |
||||
|
if (groups.Any()) |
||||
|
{ |
||||
|
// 应用规则
|
||||
|
await EntityRuleContributor.ApplyAsync(new EntityRuleContext(groups, entity)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
using Volo.Abp.DynamicProxy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
public static class EntityChangedRulesInterceptorRegistrar |
||||
|
{ |
||||
|
public static void RegisterIfNeeded(IOnServiceRegistredContext context) |
||||
|
{ |
||||
|
if (ShouldIntercept(context.ImplementationType)) |
||||
|
{ |
||||
|
context.Interceptors.TryAdd<EntityChangedRulesInterceptor>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static bool ShouldIntercept(Type type) |
||||
|
{ |
||||
|
// 拦截器的要求
|
||||
|
// 1、继承自IBasicRepository的仓储
|
||||
|
// 2、继承自INeedRule接口的实体
|
||||
|
return !DynamicProxyIgnoreTypes.Contains(type) && |
||||
|
type.IsAssignableTo(typeof(IBasicRepository<>)) && |
||||
|
type.GetGenericTypeDefinition().IsAssignableTo(typeof(INeedRule)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
public class EntityRuleContext |
||||
|
{ |
||||
|
public List<RuleGroup> Groups { get; } |
||||
|
public IEntity Entity { get; } |
||||
|
public EntityRuleContext( |
||||
|
List<RuleGroup> groups, |
||||
|
IEntity entity) |
||||
|
{ |
||||
|
Groups = groups; |
||||
|
Entity = entity; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
public enum ErrorType |
||||
|
{ |
||||
|
Warning = 0, |
||||
|
Error = 1 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
public enum ExpressionType |
||||
|
{ |
||||
|
LambdaExpression = 0 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
public interface IEntityRuleContributor |
||||
|
{ |
||||
|
Task ApplyAsync(EntityRuleContext context); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
public interface INeedRule |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
public interface IRuleFinder |
||||
|
{ |
||||
|
Task<List<RuleGroup>> GetRuleGroupsAsync(Type entityType); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
public class NullEntityRuleContributor : IEntityRuleContributor, ISingletonDependency |
||||
|
{ |
||||
|
public Task ApplyAsync(EntityRuleContext context) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
public class NullRuleFinder : IRuleFinder, ISingletonDependency |
||||
|
{ |
||||
|
public Task<List<RuleGroup>> GetRuleGroupsAsync(Type entityType) |
||||
|
{ |
||||
|
return Task.FromResult(new List<RuleGroup>()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// ref: https://github.com/microsoft/RulesEngine/blob/master/src/RulesEngine/RulesEngine/Models/Rule.cs
|
||||
|
/// </summary>
|
||||
|
public class Rule |
||||
|
{ |
||||
|
[NotNull] |
||||
|
public string Name { get; } |
||||
|
public string Operator { get; } |
||||
|
public string ErrorMessage { get; } |
||||
|
public DateTime CreationTime { get; } |
||||
|
public ErrorType ErrorType { get; } |
||||
|
public ExpressionType? ExpressionType { get; } |
||||
|
public List<Rule> Rules { get; } |
||||
|
public List<string> InjectRules { get; } |
||||
|
public List<RuleParam> Params { get; } |
||||
|
public string Expression { get; } |
||||
|
public string SuccessEvent { get; } |
||||
|
|
||||
|
public Rule( |
||||
|
[NotNull] string name, |
||||
|
string @operator, |
||||
|
DateTime creationTime, |
||||
|
string expression = null, |
||||
|
string successEvent = null, |
||||
|
ErrorType errorType = ErrorType.Warning, |
||||
|
ExpressionType? expressionType = null) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(name, nameof(name)); |
||||
|
|
||||
|
Name = name; |
||||
|
Operator = @operator; |
||||
|
CreationTime = creationTime; |
||||
|
Expression = expression; |
||||
|
SuccessEvent = successEvent; |
||||
|
ErrorType = errorType; |
||||
|
ExpressionType = expressionType; |
||||
|
|
||||
|
Rules = new List<Rule>(); |
||||
|
Params = new List<RuleParam>(); |
||||
|
InjectRules = new List<string>(); |
||||
|
} |
||||
|
|
||||
|
public Rule CreateChildren(Rule rule) |
||||
|
{ |
||||
|
Rules.Add(rule); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public Rule WithParam(RuleParam param) |
||||
|
{ |
||||
|
Params.AddIfNotContains(param); |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public Rule InjectRule(string ruleName) |
||||
|
{ |
||||
|
InjectRules.AddIfNotContains(ruleName); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
return Name.GetHashCode(); |
||||
|
} |
||||
|
|
||||
|
public override bool Equals(object obj) |
||||
|
{ |
||||
|
if (obj == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (obj is Rule rule) |
||||
|
{ |
||||
|
return rule.Name.Equals(Name); |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
public class RuleGroup |
||||
|
{ |
||||
|
[NotNull] |
||||
|
public string Name { get; } |
||||
|
public List<string> InjectRules { get; } |
||||
|
public List<Rule> Rules { get; } |
||||
|
|
||||
|
public RuleGroup( |
||||
|
[NotNull] string name) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(name, nameof(name)); |
||||
|
|
||||
|
Name = name; |
||||
|
|
||||
|
Rules = new List<Rule>(); |
||||
|
InjectRules = new List<string>(); |
||||
|
} |
||||
|
|
||||
|
public RuleGroup InjectRule(string ruleName) |
||||
|
{ |
||||
|
InjectRules.AddIfNotContains(ruleName); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public RuleGroup WithRule(Rule rule) |
||||
|
{ |
||||
|
Rules.AddIfNotContains(rule); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Rules |
||||
|
{ |
||||
|
public class RuleParam |
||||
|
{ |
||||
|
[NotNull] |
||||
|
public string Name { get; } |
||||
|
|
||||
|
[NotNull] |
||||
|
public string Expression { get; } |
||||
|
public RuleParam( |
||||
|
[NotNull] string name, |
||||
|
[NotNull] string expression) |
||||
|
{ |
||||
|
Check.NotNullOrWhiteSpace(name, nameof(name)); |
||||
|
Check.NotNullOrWhiteSpace(expression, nameof(expression)); |
||||
|
|
||||
|
Name = name; |
||||
|
Expression = expression; |
||||
|
} |
||||
|
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
return Name.GetHashCode(); |
||||
|
} |
||||
|
|
||||
|
public override bool Equals(object obj) |
||||
|
{ |
||||
|
if (obj == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (obj is RuleParam param) |
||||
|
{ |
||||
|
return param.Name.Equals(Name); |
||||
|
} |
||||
|
return base.Equals(obj); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
# LINGYUN.Abp.Rules |
||||
|
|
||||
|
规则引擎定义 |
||||
|
|
||||
|
## 配置使用 |
||||
|
|
||||
|
待完善 |
||||
@ -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="2.1.2" /> |
||||
|
<PackageReference Include="Volo.Abp.AutoMapper" Version="3.1.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Rules\LINGYUN.Abp.Rules.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,22 @@ |
|||||
|
using LINGYUN.Abp.Rules; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.AutoMapper; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.RulesEngine |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpRulesEngineModule), |
||||
|
typeof(AbpAutoMapperModule))] |
||||
|
public class AbpMsRulesEngineModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddAutoMapperObjectMapper<AbpMsRulesEngineModule>(); |
||||
|
Configure<AbpAutoMapperOptions>(options => |
||||
|
{ |
||||
|
options.AddProfile<MsRulesEngineMapperProfile>(validate: true); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
using LINGYUN.Abp.Rules; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using RulesEngine.Extensions; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.ObjectMapping; |
||||
|
using MsRulesEngine = RulesEngine.RulesEngine; |
||||
|
using MsWorkflowRules = RulesEngine.Models.WorkflowRules; |
||||
|
|
||||
|
namespace LINGYUN.Abp.RulesEngine |
||||
|
{ |
||||
|
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)] |
||||
|
[ExposeServices(typeof(IEntityRuleContributor))] |
||||
|
public class MsEntityRuleContributor : IEntityRuleContributor |
||||
|
{ |
||||
|
protected ILogger Logger { get; } |
||||
|
protected IObjectMapper ObjectMapper { get; } |
||||
|
public MsEntityRuleContributor( |
||||
|
IObjectMapper objectMapper, |
||||
|
ILogger<MsEntityRuleContributor> logger) |
||||
|
{ |
||||
|
Logger = logger; |
||||
|
ObjectMapper = objectMapper; |
||||
|
} |
||||
|
|
||||
|
public Task ApplyAsync(EntityRuleContext context) |
||||
|
{ |
||||
|
var workflowRules = ObjectMapper.Map<List<RuleGroup>, List<MsWorkflowRules>>(context.Groups); |
||||
|
|
||||
|
var rulesEngine = new MsRulesEngine(workflowRules.ToArray(), Logger); |
||||
|
|
||||
|
foreach(var workflow in workflowRules) |
||||
|
{ |
||||
|
var ruleRsults = rulesEngine.ExecuteRule(workflow.WorkflowName, context.Entity); |
||||
|
ruleRsults.OnSuccess((eventName) => |
||||
|
{ |
||||
|
Logger.LogDebug($"{workflow.WorkflowName} evaluation resulted in succees - {eventName}"); |
||||
|
}); |
||||
|
ruleRsults.OnFail(() => |
||||
|
{ |
||||
|
Logger.LogWarning($"{workflow.WorkflowName} evaluation resulted in failure"); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using AutoMapper; |
||||
|
using LINGYUN.Abp.Rules; |
||||
|
using MsExpressionType = RulesEngine.Models.RuleExpressionType; |
||||
|
using MsRule = RulesEngine.Models.Rule; |
||||
|
using MsRuleErrorType = RulesEngine.Models.ErrorType; |
||||
|
using MsRuleParam = RulesEngine.Models.LocalParam; |
||||
|
using MsWorkflowRules = RulesEngine.Models.WorkflowRules; |
||||
|
|
||||
|
namespace LINGYUN.Abp.RulesEngine |
||||
|
{ |
||||
|
public class MsRulesEngineMapperProfile : Profile |
||||
|
{ |
||||
|
public MsRulesEngineMapperProfile() |
||||
|
{ |
||||
|
CreateMap<RuleParam, MsRuleParam>(); |
||||
|
CreateMap<Rule, MsRule>() |
||||
|
.ForMember(r => r.LocalParams, map => map.MapFrom(m => m.Params)) |
||||
|
.ForMember(r => r.WorkflowRulesToInject, map => map.MapFrom(m => m.InjectRules)) |
||||
|
.ForMember(r => r.ErrorType, map => map.MapFrom(m => (MsRuleErrorType)m.ErrorType.GetHashCode())) |
||||
|
.ForMember(r => r.RuleExpressionType, map => map.MapFrom(m => (MsExpressionType)m.ExpressionType.GetHashCode())); |
||||
|
CreateMap<RuleGroup, MsWorkflowRules>() |
||||
|
.ForMember(wr => wr.WorkflowName, map => map.MapFrom(m => m.Name)) |
||||
|
.ForMember(wr => wr.Rules, map => map.MapFrom(m => m.Rules)) |
||||
|
.ForMember(wr => wr.WorkflowRulesToInject, map => map.MapFrom(m => m.InjectRules)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
# LINGYUN.Abp.RulesEngine |
||||
|
|
||||
|
规则引擎 [microsoft/RulesEngine](https://github.com/microsoft/RulesEngine) (RulesEngine) 实现 |
||||
|
|
||||
|
## 配置使用 |
||||
|
|
||||
|
待完善 |
||||
Binary file not shown.
Loading…
Reference in new issue