committed by
GitHub
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