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