Browse Source

enhanced multi-tenant support for execution steps

pull/439/head
cKey 4 years ago
parent
commit
53cdab40f0
  1. 9
      aspnet-core/LINGYUN.MicroService.Workflow.sln
  2. 3
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore.RabbitMQ/LINGYUN/Abp/WorkflowCore/RabbitMQ/AbpRabbitMqQueueProvider.cs
  3. 1
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN.Abp.WorkflowCore.csproj
  4. 7
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/AbpWorkflowCoreModule.cs
  5. 1
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/AbpWorkflowCoreOptions.cs
  6. 33
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/ExceptionHandling/ExceptionNotifierHandler.cs
  7. 9
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/IStepMultiTenant.cs
  8. 43
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/Middleware/FeatureCheckWorkflowMiddleware.cs
  9. 65
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/Middleware/MultiTenancyStepMiddleware.cs
  10. 2
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/StepBodyAsyncBase.cs
  11. 2
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/StepBodyBase.cs
  12. 7
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/WorkflowCoreConsts.cs
  13. 29
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Application.Contracts/LINGYUN/Abp/WorkflowManagement/Workflows/Dto/WorkflowDataDto.cs
  14. 5
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Application.Contracts/LINGYUN/Abp/WorkflowManagement/Workflows/Dto/WorkflowDefinitionCreateDto.cs
  15. 5
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Application.Contracts/LINGYUN/Abp/WorkflowManagement/Workflows/IWorkflowAppService.cs
  16. 48
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Application/LINGYUN/Abp/WorkflowManagement/Workflows/WorkflowAppService.cs
  17. 11
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Application/LINGYUN/Abp/WorkflowManagement/Workflows/WorkflowDefinitionAppService.cs
  18. 12
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain.Shared/LINGYUN/Abp/WorkflowManagement/DataType.cs
  19. 3
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain.Shared/LINGYUN/Abp/WorkflowManagement/Localization/Resources/en.json
  20. 3
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain.Shared/LINGYUN/Abp/WorkflowManagement/Localization/Resources/zh-Hans.json
  21. 8
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain.Shared/LINGYUN/Abp/WorkflowManagement/WorkflowDataConsts.cs
  22. 13
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain.Shared/LINGYUN/Abp/WorkflowManagement/WorkflowManagementErrorCodes.cs
  23. 36
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain/LINGYUN/Abp/WorkflowManagement/Workflow.cs
  24. 108
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain/LINGYUN/Abp/WorkflowManagement/WorkflowData.cs
  25. 8
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.EntityFrameworkCore/LINGYUN/Abp/WorkflowManagement/EntityFrameworkCore/EfCoreWorkflowRepository.cs
  26. 14
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.EntityFrameworkCore/LINGYUN/Abp/WorkflowManagement/EntityFrameworkCore/WorkflowManagementDbContextModelCreatingExtensions.cs
  27. 3
      aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.HttpApi/LINGYUN/Abp/WorkflowManagement/Workflows/WorkflowController.cs
  28. 11
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Controllers/HomeController.cs
  29. 6
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Dockerfile
  30. 23
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/EntityFrameworkCore/WorkflowManagementMigrationsDbContext.cs
  31. 35
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/EntityFrameworkCore/WorkflowManagementMigrationsDbContextFactory.cs
  32. 85
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/EventBus/Handlers/TenantSynchronizer.cs
  33. 610
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Migrations/20211214085456_Add-Entity-Workflow-Data-With-WF-Management.Designer.cs
  34. 147
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Migrations/20211214085456_Add-Entity-Workflow-Data-With-WF-Management.cs
  35. 53
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Migrations/WorkflowManagementMigrationsDbContextModelSnapshot.cs
  36. 65
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Program.cs
  37. 30
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Startup.cs
  38. 37
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/TenantHeaderParamter.cs
  39. 315
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/WorkflowManagementHttpApiHostModule.Configure.cs
  40. 17
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/WorkflowManagementHttpApiHostModule.DataSeeder.cs
  41. 107
      aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/WorkflowManagementHttpApiHostModule.cs
  42. 25
      aspnet-core/tests/LINGYUN.Abp.WorkflowCore.Tests/LINGYUN.Abp.WorkflowCore.Tests.csproj
  43. 72
      aspnet-core/tests/LINGYUN.Abp.WorkflowCore.Tests/LINGYUN/Abp/WorkflowCore/Middleware/MultiTenancyStepMiddleware_Tests.cs

9
aspnet-core/LINGYUN.MicroService.Workflow.sln

@ -43,6 +43,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "host", "host", "{6CB521FC-A
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LY.MicroService.WorkflowManagement.HttpApi.Host", "services\LY.MicroService.WorkflowManagement.HttpApi.Host\LY.MicroService.WorkflowManagement.HttpApi.Host.csproj", "{D5ED348D-D6F0-4093-BD7D-20E05AA1EB7B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflow-tests", "workflow-tests", "{57CB3446-B825-4C55-A24A-E15EB2CAA80D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WorkflowCore.Tests", "tests\LINGYUN.Abp.WorkflowCore.Tests\LINGYUN.Abp.WorkflowCore.Tests.csproj", "{0A21C843-4175-42F2-A95D-A75ED1DC1E05}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -113,6 +117,10 @@ Global
{D5ED348D-D6F0-4093-BD7D-20E05AA1EB7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5ED348D-D6F0-4093-BD7D-20E05AA1EB7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5ED348D-D6F0-4093-BD7D-20E05AA1EB7B}.Release|Any CPU.Build.0 = Release|Any CPU
{0A21C843-4175-42F2-A95D-A75ED1DC1E05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A21C843-4175-42F2-A95D-A75ED1DC1E05}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A21C843-4175-42F2-A95D-A75ED1DC1E05}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A21C843-4175-42F2-A95D-A75ED1DC1E05}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -134,6 +142,7 @@ Global
{C4B1160A-BF25-4868-B962-342ABA4A96EF} = {C4496993-41F5-4821-829E-B80A8B3BC260}
{9F9453F3-7124-4C22-91E3-0DC41A4FD8AB} = {A2963E0D-D290-40B2-9B36-75F4A5922ABF}
{D5ED348D-D6F0-4093-BD7D-20E05AA1EB7B} = {6CB521FC-AC40-49A6-B9A5-91399CAA59AB}
{0A21C843-4175-42F2-A95D-A75ED1DC1E05} = {57CB3446-B825-4C55-A24A-E15EB2CAA80D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6BB7A5DE-DA12-44DC-BC9B-0F6CA524346F}

3
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore.RabbitMQ/LINGYUN/Abp/WorkflowCore/RabbitMQ/AbpRabbitMqQueueProvider.cs

@ -43,6 +43,7 @@ namespace LINGYUN.Abp.WorkflowCore.RabbitMQ
public async Task<string> DequeueWork(QueueType queue, CancellationToken cancellationToken)
{
// TODO: 存在已知的问题,在多租户情况下, 从队列获取的工作流标识将无法查询到工作流实例
CheckDisposed();
using (await SyncObj.LockAsync(cancellationToken))
@ -76,7 +77,7 @@ namespace LINGYUN.Abp.WorkflowCore.RabbitMQ
await EnsureInitializedAsync();
var body = Encoding.UTF8.GetBytes(id);
ChannelAccessor.Channel.BasicPublish(
exchange: "",
routingKey: QueueNameNormalizer.NormalizeKey(queue),

1
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN.Abp.WorkflowCore.csproj

@ -10,6 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Features" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="Volo.Abp.Timing" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="Volo.Abp.Threading" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="WorkflowCore.DSL" Version="3.6.1" />

7
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/AbpWorkflowCoreModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using LINGYUN.Abp.WorkflowCore.Middleware;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
@ -40,6 +41,10 @@ namespace LINGYUN.Abp.WorkflowCore
context.Services.ExecutePreConfiguredActions(options);
});
context.Services.AddWorkflowDSL();
context.Services.AddWorkflowMiddleware<FeatureCheckWorkflowMiddleware>();
context.Services.AddWorkflowStepMiddleware<MultiTenancyStepMiddleware>();
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)

1
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/AbpWorkflowCoreOptions.cs

@ -3,6 +3,7 @@
public class AbpWorkflowCoreOptions
{
public bool IsEnabled { get; set; }
public AbpWorkflowCoreOptions()
{
IsEnabled = true;

33
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/ExceptionHandling/ExceptionNotifierHandler.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using Volo.Abp.ExceptionHandling;
using WorkflowCore.Interface;
using WorkflowCore.Models;
namespace LINGYUN.Abp.WorkflowCore.ExceptionHandling
{
/// <summary>
/// 不会自动注册到容器
/// 需要时手动注册
/// </summary>
public class ExceptionNotifierHandler : IWorkflowErrorHandler
{
public WorkflowErrorHandling Type => WorkflowErrorHandling.Terminate;
private readonly IExceptionNotifier _exceptionNotifier;
public ExceptionNotifierHandler(IExceptionNotifier exceptionNotifier)
{
_exceptionNotifier = exceptionNotifier;
}
public void Handle(WorkflowInstance workflow, WorkflowDefinition def, ExecutionPointer pointer, WorkflowStep step, Exception exception, Queue<ExecutionPointer> bubbleUpQueue)
{
_ = _exceptionNotifier.NotifyAsync(
new ExceptionNotificationContext(
exception,
LogLevel.Warning));
}
}
}

9
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/IStepMultiTenant.cs

@ -0,0 +1,9 @@
namespace LINGYUN.Abp.WorkflowCore
{
/// <summary>
/// 实现接口用于启动多租户
/// </summary>
public interface IStepMultiTenant
{
}
}

43
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/Middleware/FeatureCheckWorkflowMiddleware.cs

@ -0,0 +1,43 @@
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Features;
using WorkflowCore.Interface;
using WorkflowCore.Models;
namespace LINGYUN.Abp.WorkflowCore.Middleware
{
public class FeatureCheckWorkflowMiddleware : IWorkflowMiddleware
{
private readonly IFeatureChecker _featureChecker;
private readonly ILogger<FeatureCheckWorkflowMiddleware> _logger;
public WorkflowMiddlewarePhase Phase => WorkflowMiddlewarePhase.PreWorkflow;
public FeatureCheckWorkflowMiddleware(
IFeatureChecker featureChecker,
ILogger<FeatureCheckWorkflowMiddleware> logger)
{
_featureChecker = featureChecker;
_logger = logger;
}
public async Task HandleAsync(WorkflowInstance workflow, WorkflowDelegate next)
{
if (workflow.Data is IDictionary<string, object> dictionary &&
dictionary.TryGetValue(WorkflowCoreConsts.FeatureField, out var defFeatures))
{
var requiresFeatures = defFeatures.ToString().Split(';');
var passed = await _featureChecker.IsEnabledAsync(requiresAll: true, requiresFeatures);
if (!passed)
{
_logger.LogWarning("Workflow {0} was forcibly terminated for the following reasons: These required functions must be enabled: {1}",
workflow.Id,
requiresFeatures);
workflow.Status = WorkflowStatus.Terminated;
}
}
await next();
}
}
}

65
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/Middleware/MultiTenancyStepMiddleware.cs

@ -0,0 +1,65 @@
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.MultiTenancy;
using WorkflowCore.Interface;
using WorkflowCore.Models;
namespace LINGYUN.Abp.WorkflowCore.Middleware
{
public class MultiTenancyStepMiddleware : IWorkflowStepMiddleware
{
private readonly AbpMultiTenancyOptions _multiTenancyOptions;
private readonly ICurrentTenant _currentTenant;
public MultiTenancyStepMiddleware(
ICurrentTenant currentTenant,
IOptions<AbpMultiTenancyOptions> options)
{
_currentTenant = currentTenant;
_multiTenancyOptions = options.Value;
}
public async Task<ExecutionResult> HandleAsync(IStepExecutionContext context, IStepBody body, WorkflowStepDelegate next)
{
// 触发多租户中间件条件
// 1、需要启用多租户
// 2、步骤需要实现接口 IStepMultiTenant
// 3.1、传递的工作流实现接口 IMultiTenant
// 3.2、传递的工作流数据包含 TenantId 字段
if (!_multiTenancyOptions.IsEnabled)
{
return await next();
}
if (typeof(IStepMultiTenant).IsAssignableFrom(body.GetType()))
{
if (context.Workflow.Data != null)
{
if (context.Workflow.Data is IMultiTenant tenant)
{
using (_currentTenant.Change(tenant.TenantId))
{
return await next();
}
}
var dataObj = JObject.FromObject(context.Workflow.Data);
var tenantToken = dataObj.GetOrDefault(nameof(IMultiTenant.TenantId));
if (tenantToken?.HasValues == true)
{
using (_currentTenant.Change(tenantToken.Value<Guid?>()))
{
return await next();
}
}
}
}
return await next();
}
}
}

2
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/StepBodyAsyncBase.cs

@ -2,7 +2,7 @@
namespace LINGYUN.Abp.WorkflowCore
{
public abstract class StepBodyAsyncBase : StepBodyAsync
public abstract class StepBodyAsyncBase : StepBodyAsync, IStepMultiTenant
{
}
}

2
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/StepBodyBase.cs

@ -2,7 +2,7 @@
namespace LINGYUN.Abp.WorkflowCore
{
public abstract class StepBodyBase : StepBody
public abstract class StepBodyBase : StepBody, IStepMultiTenant
{
}
}

7
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowCore/LINGYUN/Abp/WorkflowCore/WorkflowCoreConsts.cs

@ -0,0 +1,7 @@
namespace LINGYUN.Abp.WorkflowCore
{
public static class WorkflowCoreConsts
{
public const string FeatureField = "Feature";
}
}

29
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Application.Contracts/LINGYUN/Abp/WorkflowManagement/Workflows/Dto/WorkflowDataDto.cs

@ -0,0 +1,29 @@
namespace LINGYUN.Abp.WorkflowManagement.Workflows
{
public class WorkflowDataDto
{
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 显示名称
/// </summary>
public string DisplayName { get; set; }
/// <summary>
/// 数据类型
/// </summary>
public DataType DataType { get; set; }
/// <summary>
/// 是否必输
/// 默认: false
/// </summary>
public bool IsRequired { get; set; }
/// <summary>
/// 是否区分大小写
/// 默认: false
/// 暂无用
/// </summary>
public bool IsCaseSensitive { get; set; }
}
}

5
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Application.Contracts/LINGYUN/Abp/WorkflowManagement/Workflows/Dto/WorkflowDefinitionCreateDto.cs

@ -34,6 +34,11 @@ namespace LINGYUN.Abp.WorkflowManagement.Workflows
[Range(1, 100)]
public int Version { get; set; }
/// <summary>
/// 输入数据约束
/// </summary>
public List<WorkflowDataDto> Datas { get; set; } = new List<WorkflowDataDto>();
[Required]
[MinLength(1)]
public List<StepDto> Steps { get; set; } = new List<StepDto>();

5
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Application.Contracts/LINGYUN/Abp/WorkflowManagement/Workflows/IWorkflowAppService.cs

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace LINGYUN.Abp.WorkflowManagement.Workflows
@ -7,7 +8,7 @@ namespace LINGYUN.Abp.WorkflowManagement.Workflows
{
Task<WorkflowInstanceDto> GetAsync(string id);
Task<WorkflowInstanceDto> StartAsync(string id, WorkflowStartInput input);
Task<WorkflowInstanceDto> StartAsync(Guid id, WorkflowStartInput input);
Task SuspendAsync(string id);

48
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Application/LINGYUN/Abp/WorkflowManagement/Workflows/WorkflowAppService.cs

@ -1,9 +1,11 @@
using Microsoft.AspNetCore.Authorization;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.MultiTenancy;
using WorkflowCore.Interface;
using WorkflowCore.Models;
@ -14,13 +16,16 @@ namespace LINGYUN.Abp.WorkflowManagement.Workflows
{
private readonly IWorkflowController _controller;
private readonly IPersistenceProvider _persistence;
private readonly IWorkflowRepository _workflowRepository;
public WorkflowAppService(
IWorkflowController controller,
IPersistenceProvider persistence)
IPersistenceProvider persistence,
IWorkflowRepository workflowRepository)
{
_controller = controller;
_persistence = persistence;
_workflowRepository = workflowRepository;
}
public virtual async Task<WorkflowInstanceDto> GetAsync(string id)
@ -41,22 +46,53 @@ namespace LINGYUN.Abp.WorkflowManagement.Workflows
}
}
public virtual async Task<WorkflowInstanceDto> StartAsync(string id, WorkflowStartInput input)
public virtual async Task<WorkflowInstanceDto> StartAsync(Guid id, WorkflowStartInput input)
{
var workflowData = new Dictionary<string, object>();
var workflowDef = await _workflowRepository.GetAsync(id);
var workflowData = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
foreach (var data in input.Data)
{
object inputValue = data.Value;
if (data.Value is JsonElement element)
{
workflowData.TryAdd(data.Key, JsonConvert.DeserializeObject(element.ToString()));
inputValue = JsonConvert.DeserializeObject(element.ToString());
}
else
{
workflowData.TryAdd(data.Key, data.Value);
var defData = workflowDef.FindData(data.Key);
if (defData != null)
{
if (defData.TryParse(inputValue, out inputValue))
{
workflowData.TryAdd(data.Key, inputValue);
continue;
}
throw new BusinessException(WorkflowManagementErrorCodes.InvalidInputDataType)
.WithData("Property", defData.DisplayName)
.WithData("DataType", defData.DataType.ToString());
}
}
workflowData.TryAdd(data.Key, inputValue);
}
if (CurrentTenant.IsAvailable)
{
workflowData.TryAdd(nameof(IMultiTenant.TenantId), CurrentTenant.GetId());
}
foreach (var data in workflowDef.Datas)
{
if (data.IsRequired && !workflowData.ContainsKey(data.Name))
{
throw new BusinessException(WorkflowManagementErrorCodes.MissingRequiredProperty)
.WithData("Property", data.DisplayName);
}
}
var instanceId = await _controller.StartWorkflow(id, workflowData);
var instanceId = await _controller.StartWorkflow(workflowDef.Id.ToString(), workflowData);
var result = await _persistence.GetWorkflowInstance(instanceId);
return ObjectMapper.Map<WorkflowInstance, WorkflowInstanceDto>(result);

11
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Application/LINGYUN/Abp/WorkflowManagement/Workflows/WorkflowDefinitionAppService.cs

@ -47,6 +47,17 @@ namespace LINGYUN.Abp.WorkflowManagement.Workflows
IsEnabled = input.IsEnabled,
};
foreach (var data in input.Datas)
{
workflowDef.AddData(
GuidGenerator,
data.Name,
data.DisplayName,
data.DataType,
data.IsRequired,
data.IsCaseSensitive);
}
var stepDefNodes = new List<StepNode>();
var stepCompensateDefNodes = new List<CompensateNode>();

12
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain.Shared/LINGYUN/Abp/WorkflowManagement/DataType.cs

@ -0,0 +1,12 @@
namespace LINGYUN.Abp.WorkflowManagement
{
public enum DataType
{
Object,
String,
Number,
Date,
DateTime,
Booleaen
}
}

3
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain.Shared/LINGYUN/Abp/WorkflowManagement/Localization/Resources/en.json

@ -12,6 +12,9 @@
"Workflow:10100": "Unable to restart workflow temporarily, please check log.",
"Workflow:10101": "Unable to suspend workflow, please check log.",
"Workflow:10102": "Unable to terminate workflow temporarily, please check log.",
"Workflow:10110": "Missing required Property {Property}.",
"Workflow:10111": "The mandatory {Property} field cannot be null.",
"Workflow:10112": "The field {Property} is not a valid {DataType} type.",
"DisplayName:IsEnabled": "Is Enabled",
"DisplayName:Name": "Name",
"DisplayName:DisplayName": "DisplayName",

3
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain.Shared/LINGYUN/Abp/WorkflowManagement/Localization/Resources/zh-Hans.json

@ -12,6 +12,9 @@
"Workflow:10100": "暂时无法重启工作流,请检查日志.",
"Workflow:10101": "暂时无法暂停工作流,请检查日志.",
"Workflow:10102": "暂时无法终止工作流,请检查日志.",
"Workflow:10110": "缺失必要的属性 {Property}.",
"Workflow:10111": "必输字段 {Property} 不能为空.",
"Workflow:10112": "字段 {Property} 不是有效的 {DataType} 类型.",
"DisplayName:IsEnabled": "是否启用",
"DisplayName:Name": "名称",
"DisplayName:DisplayName": "显示名称",

8
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain.Shared/LINGYUN/Abp/WorkflowManagement/WorkflowDataConsts.cs

@ -0,0 +1,8 @@
namespace LINGYUN.Abp.WorkflowManagement
{
public static class WorkflowDataConsts
{
public static int MaxNameLength { get; set; } = 100;
public static int MaxDisplayNameLength { get; set; } = 100;
}
}

13
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain.Shared/LINGYUN/Abp/WorkflowManagement/WorkflowManagementErrorCodes.cs

@ -7,5 +7,18 @@
public const string ResumeError = Namespace + ":10100";
public const string SuspendError = Namespace + ":10101";
public const string TerminateError = Namespace + ":10102";
/// <summary>
/// 缺失必要的属性 {Property}
/// </summary>
public const string MissingRequiredProperty = Namespace + ":10110";
/// <summary>
/// 必输字段 {Property} 不能为空
/// </summary>
public const string InvalidInputNullable = Namespace + ":10111";
/// <summary>
/// 字段 {Property} 不是有效的 {DataType} 类型
/// </summary>
public const string InvalidInputDataType = Namespace + ":10112";
}
}

36
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain/LINGYUN/Abp/WorkflowManagement/Workflow.cs

@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using WorkflowCore.Models;
@ -39,8 +43,11 @@ namespace LINGYUN.Abp.WorkflowManagement
public virtual TimeSpan? ErrorRetryInterval { get; set; }
public virtual ICollection<WorkflowData> Datas { get; protected set; }
protected Workflow()
{
Datas = new Collection<WorkflowData>();
}
public Workflow(
@ -60,6 +67,35 @@ namespace LINGYUN.Abp.WorkflowManagement
ErrorBehavior = errorBehavior;
ErrorRetryInterval = errorRetryInterval;
TenantId = tenantId;
Datas = new Collection<WorkflowData>();
}
public void AddData(
IGuidGenerator guidGenerator,
string name,
string displayName,
DataType dataType,
bool isRequired = false,
bool isCaseSensitive = false)
{
if (FindData(name) == null)
{
Datas.Add(new WorkflowData(
guidGenerator.Create(),
Id,
name,
displayName,
dataType,
isRequired,
isCaseSensitive,
TenantId));
}
}
public WorkflowData FindData(string name)
{
return Datas.FirstOrDefault(x => x.Name.Equals(name));
}
}
}

108
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.Domain/LINGYUN/Abp/WorkflowManagement/WorkflowData.cs

@ -0,0 +1,108 @@
using System;
using Volo.Abp;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.WorkflowManagement
{
public class WorkflowData : Entity<Guid>, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
public virtual Guid WorkflowId { get; protected set; }
/// <summary>
/// 名称
/// </summary>
public virtual string Name { get; protected set; }
/// <summary>
/// 显示名称
/// </summary>
public virtual string DisplayName { get; protected set; }
/// <summary>
/// 数据类型
/// </summary>
public virtual DataType DataType { get; protected set; }
/// <summary>
/// 是否必输
/// 默认: false
/// </summary>
public virtual bool IsRequired { get; protected set; }
/// <summary>
/// 是否区分大小写
/// 默认: false
/// 暂无用
/// </summary>
public virtual bool IsCaseSensitive { get; protected set; }
protected WorkflowData() { }
public WorkflowData(
Guid id,
Guid workflowId,
string name,
string displayName,
DataType dataType = DataType.String,
bool isRequired = false,
bool isCaseSensitive = false,
Guid? tenantId = null) : base(id)
{
WorkflowId = workflowId;
Name = name;
DisplayName = displayName;
DataType = dataType;
IsRequired = isRequired;
IsCaseSensitive = isCaseSensitive;
TenantId = tenantId;
}
public bool TryParse(object input, out object value)
{
if (input == null)
{
if (IsRequired)
{
throw new BusinessException(WorkflowManagementErrorCodes.InvalidInputNullable)
.WithData("Property", DisplayName);
}
// 字典类型不能为空
value = new { };
return true;
}
switch (DataType)
{
case DataType.String:
value = input.ToString();
return true;
case DataType.Booleaen:
if (input is bool boValue)
{
value = boValue;
return true;
}
value = input.ToString().ToLower() == "true";
return true;
case DataType.Date:
case DataType.DateTime:
if (input is DateTime dateValue)
{
value = DataType == DataType.Date
? dateValue.Date
: dateValue;
return true;
}
value = DateTime.Parse(input.ToString());
return true;
case DataType.Number:
if (int.TryParse(input.ToString(), out int intValue))
{
value = intValue;
return true;
}
value = 0;
return false;
case DataType.Object:
default:
value = input;
return true;
}
}
}
}

8
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.EntityFrameworkCore/LINGYUN/Abp/WorkflowManagement/EntityFrameworkCore/EfCoreWorkflowRepository.cs

@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
@ -21,5 +22,12 @@ namespace LINGYUN.Abp.WorkflowManagement.EntityFrameworkCore
.AnyAsync(x => x.Name.Equals(name) && x.Version == version,
GetCancellationToken(cancellationToken));
}
public override async Task<IQueryable<Workflow>> WithDetailsAsync()
{
var queryable = await base.WithDetailsAsync();
return queryable.Include(x => x.Datas);
}
}
}

14
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.EntityFrameworkCore/LINGYUN/Abp/WorkflowManagement/EntityFrameworkCore/WorkflowManagementDbContextModelCreatingExtensions.cs

@ -30,6 +30,20 @@ namespace LINGYUN.Abp.WorkflowManagement.EntityFrameworkCore
b.Property(p => p.Name).HasMaxLength(WorkflowConsts.MaxNameLength).IsRequired();
b.Property(p => p.Description).HasMaxLength(WorkflowConsts.MaxDescriptionLength);
b.HasMany(u => u.Datas).WithOne().HasForeignKey(uc => uc.WorkflowId).IsRequired();
b.ConfigureByConvention();
});
builder.Entity<WorkflowData>(b =>
{
b.ToTable(options.TablePrefix + "DefinitionData", options.Schema);
b.Property(p => p.DisplayName).HasMaxLength(WorkflowDataConsts.MaxDisplayNameLength).IsRequired();
b.Property(p => p.Name).HasMaxLength(WorkflowDataConsts.MaxNameLength).IsRequired();
b.HasIndex(p => p.WorkflowId);
b.ConfigureByConvention();
});

3
aspnet-core/modules/workflow/LINGYUN.Abp.WorkflowManagement.HttpApi/LINGYUN/Abp/WorkflowManagement/Workflows/WorkflowController.cs

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
@ -33,7 +34,7 @@ namespace LINGYUN.Abp.WorkflowManagement.Workflows
[HttpPost]
[Route("{id}/start")]
public virtual async Task<WorkflowInstanceDto> StartAsync(string id, WorkflowStartInput input)
public virtual async Task<WorkflowInstanceDto> StartAsync(Guid id, WorkflowStartInput input)
{
return await _service.StartAsync(id, input);
}

11
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Controllers/HomeController.cs

@ -1,13 +1,12 @@
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
namespace LY.MicroService.WorkflowManagement.Controllers
namespace LY.MicroService.WorkflowManagement.Controllers;
public class HomeController : AbpController
{
public class HomeController : AbpController
public IActionResult Index()
{
public IActionResult Index()
{
return Redirect("/swagger/index.html");
}
return Redirect("/swagger/index.html");
}
}

6
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Dockerfile

@ -4,9 +4,9 @@ WORKDIR /app
COPY . /app
#�Ϻ�ʱ��
#ENV TZ=Asia/Shanghai
#RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone
#东8区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone
EXPOSE 80/tcp
VOLUME [ "./app/Logs" ]

23
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/EntityFrameworkCore/WorkflowManagementMigrationsDbContext.cs

@ -3,22 +3,21 @@ using LINGYUN.Abp.WorkflowManagement.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace LY.MicroService.WorkflowManagement.EntityFrameworkCore
namespace LY.MicroService.WorkflowManagement.EntityFrameworkCore;
public class WorkflowManagementMigrationsDbContext : AbpDbContext<WorkflowManagementMigrationsDbContext>
{
public class WorkflowManagementMigrationsDbContext : AbpDbContext<WorkflowManagementMigrationsDbContext>
public WorkflowManagementMigrationsDbContext(DbContextOptions<WorkflowManagementMigrationsDbContext> options)
: base(options)
{
public WorkflowManagementMigrationsDbContext(DbContextOptions<WorkflowManagementMigrationsDbContext> options)
: base(options)
{
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ConfigureWorkflow();
modelBuilder.ConfigureWorkflowManagement();
}
modelBuilder.ConfigureWorkflow();
modelBuilder.ConfigureWorkflowManagement();
}
}

35
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/EntityFrameworkCore/WorkflowManagementMigrationsDbContextFactory.cs

@ -3,29 +3,28 @@ using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using System.IO;
namespace LY.MicroService.WorkflowManagement.EntityFrameworkCore
namespace LY.MicroService.WorkflowManagement.EntityFrameworkCore;
public class WorkflowManagementMigrationsDbContextFactory : IDesignTimeDbContextFactory<WorkflowManagementMigrationsDbContext>
{
public class WorkflowManagementMigrationsDbContextFactory : IDesignTimeDbContextFactory<WorkflowManagementMigrationsDbContext>
public WorkflowManagementMigrationsDbContext CreateDbContext(string[] args)
{
public WorkflowManagementMigrationsDbContext CreateDbContext(string[] args)
{
var configuration = BuildConfiguration();
var connectionString = configuration.GetConnectionString("WorkflowManagement");
var configuration = BuildConfiguration();
var connectionString = configuration.GetConnectionString("WorkflowManagement");
var builder = new DbContextOptionsBuilder<WorkflowManagementMigrationsDbContext>()
.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
var builder = new DbContextOptionsBuilder<WorkflowManagementMigrationsDbContext>()
.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
return new WorkflowManagementMigrationsDbContext(builder.Options);
}
return new WorkflowManagementMigrationsDbContext(builder.Options);
}
private static IConfigurationRoot BuildConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile("appsettings.Development.json", optional: true);
private static IConfigurationRoot BuildConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile("appsettings.Development.json", optional: true);
return builder.Build();
}
return builder.Build();
}
}

85
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/EventBus/Handlers/TenantSynchronizer.cs

@ -10,60 +10,59 @@ using Volo.Abp.EventBus.Distributed;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace LY.MicroService.WorkflowManagement.EventBus.Handlers
{
public class TenantSynchronizer :
namespace LY.MicroService.WorkflowManagement.EventBus.Handlers;
public class TenantSynchronizer :
IDistributedEventHandler<CreateEventData>,
ITransientDependency
{
protected IDataSeeder DataSeeder { get; }
protected ICurrentTenant CurrentTenant { get; }
protected IDbSchemaMigrator DbSchemaMigrator { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
{
protected IDataSeeder DataSeeder { get; }
protected ICurrentTenant CurrentTenant { get; }
protected IDbSchemaMigrator DbSchemaMigrator { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected ILogger<TenantSynchronizer> Logger { get; }
protected ILogger<TenantSynchronizer> Logger { get; }
public TenantSynchronizer(
IDataSeeder dataSeeder,
ICurrentTenant currentTenant,
IDbSchemaMigrator dbSchemaMigrator,
IUnitOfWorkManager unitOfWorkManager,
ILogger<TenantSynchronizer> logger)
{
DataSeeder = dataSeeder;
CurrentTenant = currentTenant;
DbSchemaMigrator = dbSchemaMigrator;
UnitOfWorkManager = unitOfWorkManager;
public TenantSynchronizer(
IDataSeeder dataSeeder,
ICurrentTenant currentTenant,
IDbSchemaMigrator dbSchemaMigrator,
IUnitOfWorkManager unitOfWorkManager,
ILogger<TenantSynchronizer> logger)
{
DataSeeder = dataSeeder;
CurrentTenant = currentTenant;
DbSchemaMigrator = dbSchemaMigrator;
UnitOfWorkManager = unitOfWorkManager;
Logger = logger;
}
Logger = logger;
}
/// <summary>
/// 租户创建之后需要预置种子数据
/// </summary>
/// <param name="eventData"></param>
/// <returns></returns>
public virtual async Task HandleEventAsync(CreateEventData eventData)
/// <summary>
/// 租户创建之后需要预置种子数据
/// </summary>
/// <param name="eventData"></param>
/// <returns></returns>
public virtual async Task HandleEventAsync(CreateEventData eventData)
{
using (var unitOfWork = UnitOfWorkManager.Begin())
{
using (var unitOfWork = UnitOfWorkManager.Begin())
using (CurrentTenant.Change(eventData.Id, eventData.Name))
{
using (CurrentTenant.Change(eventData.Id, eventData.Name))
{
Logger.LogInformation("Migrating the new tenant database with WorkflowManagement...");
// 迁移租户数据
await DbSchemaMigrator.MigrateAsync<WorkflowManagementDbContext>(
(connectionString, builder) =>
{
builder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
Logger.LogInformation("Migrating the new tenant database with WorkflowManagement...");
// 迁移租户数据
await DbSchemaMigrator.MigrateAsync<WorkflowManagementDbContext>(
(connectionString, builder) =>
{
builder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
return new WorkflowManagementDbContext(builder.Options);
});
Logger.LogInformation("Migrated the new tenant database with WorkflowManagement...");
return new WorkflowManagementDbContext(builder.Options);
});
Logger.LogInformation("Migrated the new tenant database with WorkflowManagement...");
await DataSeeder.SeedAsync(new DataSeedContext(eventData.Id));
await DataSeeder.SeedAsync(new DataSeedContext(eventData.Id));
await unitOfWork.SaveChangesAsync();
}
await unitOfWork.SaveChangesAsync();
}
}
}

610
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Migrations/20211214085456_Add-Entity-Workflow-Data-With-WF-Management.Designer.cs

@ -0,0 +1,610 @@
// <auto-generated />
using System;
using LY.MicroService.WorkflowManagement.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Volo.Abp.EntityFrameworkCore;
#nullable disable
namespace LY.MicroService.WorkflowManagement.Migrations
{
[DbContext(typeof(WorkflowManagementMigrationsDbContext))]
[Migration("20211214085456_Add-Entity-Workflow-Data-With-WF-Management")]
partial class AddEntityWorkflowDataWithWFManagement
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.MySql)
.HasAnnotation("ProductVersion", "6.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedEvent", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime(6)")
.HasColumnName("CreationTime");
b.Property<string>("EventData")
.HasColumnType("longtext");
b.Property<string>("EventKey")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<string>("EventName")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<bool>("IsProcessed")
.HasColumnType("tinyint(1)");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.HasKey("Id");
b.HasIndex("CreationTime");
b.HasIndex("IsProcessed");
b.HasIndex("EventName", "EventKey");
b.ToTable("WF_Event", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedExecutionError", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("ErrorTime")
.HasColumnType("datetime(6)");
b.Property<Guid>("ExecutionPointerId")
.HasMaxLength(50)
.HasColumnType("char(50)");
b.Property<string>("Message")
.HasColumnType("longtext");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<Guid>("WorkflowId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.ToTable("WF_ExecutionError", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedExecutionPointer", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
.HasColumnType("char(50)");
b.Property<bool>("Active")
.HasColumnType("tinyint(1)");
b.Property<string>("Children")
.HasColumnType("longtext");
b.Property<string>("ContextItem")
.HasColumnType("longtext");
b.Property<DateTime?>("EndTime")
.HasColumnType("datetime(6)");
b.Property<string>("EventData")
.HasColumnType("longtext");
b.Property<string>("EventKey")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<string>("EventName")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<bool>("EventPublished")
.HasColumnType("tinyint(1)");
b.Property<string>("Outcome")
.HasColumnType("longtext");
b.Property<string>("PersistenceData")
.HasColumnType("longtext");
b.Property<string>("PredecessorId")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<int>("RetryCount")
.HasColumnType("int");
b.Property<string>("Scope")
.HasColumnType("longtext");
b.Property<DateTime?>("SleepUntil")
.HasColumnType("datetime(6)");
b.Property<DateTime?>("StartTime")
.HasColumnType("datetime(6)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<int>("StepId")
.HasColumnType("int");
b.Property<string>("StepName")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<Guid>("WorkflowId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("WorkflowId");
b.ToTable("WF_ExecutionPointer", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedExtensionAttribute", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<Guid>("ExecutionPointerId")
.HasMaxLength(50)
.HasColumnType("char(50)");
b.Property<string>("Key")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<string>("Value")
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ExecutionPointerId");
b.ToTable("WF_ExtensionAttribute", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedScheduledCommand", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<string>("CommandName")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<string>("Data")
.HasMaxLength(500)
.HasColumnType("varchar(500)");
b.Property<long>("ExecuteTime")
.HasColumnType("bigint");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.HasKey("Id");
b.HasIndex("ExecuteTime");
b.HasIndex("CommandName", "Data")
.IsUnique();
b.ToTable("WF_ScheduledCommand", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedSubscription", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("EventKey")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<string>("EventName")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<Guid>("ExecutionPointerId")
.HasMaxLength(50)
.HasColumnType("char(50)");
b.Property<string>("ExternalToken")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<DateTime?>("ExternalTokenExpiry")
.HasColumnType("datetime(6)");
b.Property<string>("ExternalWorkerId")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<int>("StepId")
.HasColumnType("int");
b.Property<DateTime>("SubscribeAsOf")
.HasColumnType("datetime(6)");
b.Property<string>("SubscriptionData")
.HasColumnType("longtext");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<Guid>("WorkflowId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("EventKey");
b.HasIndex("EventName");
b.ToTable("WF_Subscription", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedWorkflow", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<DateTime?>("CompleteTime")
.HasColumnType("datetime(6)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(40)
.HasColumnType("varchar(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime(6)")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("char(36)")
.HasColumnName("CreatorId");
b.Property<string>("Data")
.HasColumnType("longtext");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("varchar(500)");
b.Property<string>("ExtraProperties")
.HasColumnType("longtext")
.HasColumnName("ExtraProperties");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime(6)")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("char(36)")
.HasColumnName("LastModifierId");
b.Property<long?>("NextExecution")
.HasColumnType("bigint");
b.Property<string>("Reference")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<int>("Version")
.HasColumnType("int");
b.Property<string>("WorkflowDefinitionId")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.HasKey("Id");
b.HasIndex("NextExecution");
b.ToTable("WF_Workflow", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowManagement.CompensateNode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("CancelCondition")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<int?>("ErrorBehavior")
.HasColumnType("int");
b.Property<string>("Inputs")
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<string>("Outputs")
.HasColumnType("longtext");
b.Property<Guid?>("ParentId")
.HasColumnType("char(36)");
b.Property<TimeSpan?>("RetryInterval")
.HasColumnType("time(6)");
b.Property<bool>("Saga")
.HasColumnType("tinyint(1)");
b.Property<string>("SelectNextStep")
.HasColumnType("longtext");
b.Property<string>("StepType")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<Guid>("WorkflowId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.ToTable("WF_Compensate", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowManagement.StepNode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("CancelCondition")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<int?>("ErrorBehavior")
.HasColumnType("int");
b.Property<string>("Inputs")
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<string>("Outputs")
.HasColumnType("longtext");
b.Property<Guid?>("ParentId")
.HasColumnType("char(36)");
b.Property<TimeSpan?>("RetryInterval")
.HasColumnType("time(6)");
b.Property<bool>("Saga")
.HasColumnType("tinyint(1)");
b.Property<string>("SelectNextStep")
.HasColumnType("longtext");
b.Property<string>("StepType")
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<Guid>("WorkflowId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.ToTable("WF_Step", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowManagement.Workflow", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(40)
.HasColumnType("varchar(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime(6)")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("char(36)")
.HasColumnName("CreatorId");
b.Property<string>("Description")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<string>("DisplayName")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<int>("ErrorBehavior")
.HasColumnType("int");
b.Property<TimeSpan?>("ErrorRetryInterval")
.HasColumnType("time(6)");
b.Property<string>("ExtraProperties")
.HasColumnType("longtext")
.HasColumnName("ExtraProperties");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime(6)")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("char(36)")
.HasColumnName("LastModifierId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<int>("Version")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("WF_Definition", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowManagement.WorkflowData", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<int>("DataType")
.HasColumnType("int");
b.Property<string>("DisplayName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<bool>("IsCaseSensitive")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsRequired")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<Guid>("WorkflowId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("WorkflowId");
b.ToTable("WF_DefinitionData", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedExecutionPointer", b =>
{
b.HasOne("LINGYUN.Abp.WorkflowCore.Persistence.PersistedWorkflow", "Workflow")
.WithMany("ExecutionPointers")
.HasForeignKey("WorkflowId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Workflow");
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedExtensionAttribute", b =>
{
b.HasOne("LINGYUN.Abp.WorkflowCore.Persistence.PersistedExecutionPointer", "ExecutionPointer")
.WithMany("ExtensionAttributes")
.HasForeignKey("ExecutionPointerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ExecutionPointer");
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowManagement.WorkflowData", b =>
{
b.HasOne("LINGYUN.Abp.WorkflowManagement.Workflow", null)
.WithMany("Datas")
.HasForeignKey("WorkflowId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedExecutionPointer", b =>
{
b.Navigation("ExtensionAttributes");
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedWorkflow", b =>
{
b.Navigation("ExecutionPointers");
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowManagement.Workflow", b =>
{
b.Navigation("Datas");
});
#pragma warning restore 612, 618
}
}
}

147
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Migrations/20211214085456_Add-Entity-Workflow-Data-With-WF-Management.cs

@ -0,0 +1,147 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LY.MicroService.WorkflowManagement.Migrations
{
public partial class AddEntityWorkflowDataWithWFManagement : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<Guid>(
name: "ExecutionPointerId",
table: "WF_Subscription",
type: "char(50)",
maxLength: 50,
nullable: false,
collation: "ascii_general_ci",
oldClrType: typeof(string),
oldType: "char(50)",
oldMaxLength: 50)
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<Guid>(
name: "ExecutionPointerId",
table: "WF_ExtensionAttribute",
type: "char(50)",
maxLength: 50,
nullable: false,
collation: "ascii_general_ci",
oldClrType: typeof(string),
oldType: "char(50)",
oldMaxLength: 50)
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<Guid>(
name: "Id",
table: "WF_ExecutionPointer",
type: "char(50)",
maxLength: 50,
nullable: false,
collation: "ascii_general_ci",
oldClrType: typeof(string),
oldType: "char(50)",
oldMaxLength: 50)
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<Guid>(
name: "ExecutionPointerId",
table: "WF_ExecutionError",
type: "char(50)",
maxLength: 50,
nullable: false,
collation: "ascii_general_ci",
oldClrType: typeof(string),
oldType: "char(50)",
oldMaxLength: 50)
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "WF_DefinitionData",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
TenantId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
WorkflowId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Name = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
DisplayName = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
DataType = table.Column<int>(type: "int", nullable: false),
IsRequired = table.Column<bool>(type: "tinyint(1)", nullable: false),
IsCaseSensitive = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_WF_DefinitionData", x => x.Id);
table.ForeignKey(
name: "FK_WF_DefinitionData_WF_Definition_WorkflowId",
column: x => x.WorkflowId,
principalTable: "WF_Definition",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_WF_DefinitionData_WorkflowId",
table: "WF_DefinitionData",
column: "WorkflowId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "WF_DefinitionData");
migrationBuilder.AlterColumn<string>(
name: "ExecutionPointerId",
table: "WF_Subscription",
type: "char(50)",
maxLength: 50,
nullable: false,
oldClrType: typeof(Guid),
oldType: "char(50)",
oldMaxLength: 50)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("Relational:Collation", "ascii_general_ci");
migrationBuilder.AlterColumn<string>(
name: "ExecutionPointerId",
table: "WF_ExtensionAttribute",
type: "char(50)",
maxLength: 50,
nullable: false,
oldClrType: typeof(Guid),
oldType: "char(50)",
oldMaxLength: 50)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("Relational:Collation", "ascii_general_ci");
migrationBuilder.AlterColumn<string>(
name: "Id",
table: "WF_ExecutionPointer",
type: "char(50)",
maxLength: 50,
nullable: false,
oldClrType: typeof(Guid),
oldType: "char(50)",
oldMaxLength: 50)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("Relational:Collation", "ascii_general_ci");
migrationBuilder.AlterColumn<string>(
name: "ExecutionPointerId",
table: "WF_ExecutionError",
type: "char(50)",
maxLength: 50,
nullable: false,
oldClrType: typeof(Guid),
oldType: "char(50)",
oldMaxLength: 50)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("Relational:Collation", "ascii_general_ci");
}
}
}

53
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Migrations/WorkflowManagementMigrationsDbContextModelSnapshot.cs

@ -518,6 +518,45 @@ namespace LY.MicroService.WorkflowManagement.Migrations
b.ToTable("WF_Definition", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowManagement.WorkflowData", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<int>("DataType")
.HasColumnType("int");
b.Property<string>("DisplayName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<bool>("IsCaseSensitive")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsRequired")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<Guid>("WorkflowId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("WorkflowId");
b.ToTable("WF_DefinitionData", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedExecutionPointer", b =>
{
b.HasOne("LINGYUN.Abp.WorkflowCore.Persistence.PersistedWorkflow", "Workflow")
@ -540,6 +579,15 @@ namespace LY.MicroService.WorkflowManagement.Migrations
b.Navigation("ExecutionPointer");
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowManagement.WorkflowData", b =>
{
b.HasOne("LINGYUN.Abp.WorkflowManagement.Workflow", null)
.WithMany("Datas")
.HasForeignKey("WorkflowId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowCore.Persistence.PersistedExecutionPointer", b =>
{
b.Navigation("ExtensionAttributes");
@ -549,6 +597,11 @@ namespace LY.MicroService.WorkflowManagement.Migrations
{
b.Navigation("ExecutionPointers");
});
modelBuilder.Entity("LINGYUN.Abp.WorkflowManagement.Workflow", b =>
{
b.Navigation("Datas");
});
#pragma warning restore 612, 618
}
}

65
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Program.cs

@ -3,43 +3,42 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Serilog;
namespace LY.MicroService.WorkflowManagement
namespace LY.MicroService.WorkflowManagement;
public class Program
{
public class Program
public static int Main(string[] args)
{
public static int Main(string[] args)
try
{
try
{
var host = CreateHostBuilder(args).Build();
Log.Information("Starting web host.");
host.Run();
return 0;
}
finally
{
Log.CloseAndFlush();
}
var host = CreateHostBuilder(args).Build();
Log.Information("Starting web host.");
host.Run();
return 0;
}
finally
{
Log.CloseAndFlush();
}
}
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureAppConfiguration((context, config) =>
{
var configuration = config.Build();
if (configuration.GetSection("AgileConfig").Exists())
{
config.AddAgileConfig(new AgileConfig.Client.ConfigClient(configuration));
}
})
.UseSerilog((context, config) =>
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureAppConfiguration((context, config) =>
{
var configuration = config.Build();
if (configuration.GetSection("AgileConfig").Exists())
{
config.ReadFrom.Configuration(context.Configuration);
})
.UseAutofac();
}
config.AddAgileConfig(new AgileConfig.Client.ConfigClient(configuration));
}
})
.UseSerilog((context, config) =>
{
config.ReadFrom.Configuration(context.Configuration);
})
.UseAutofac();
}

30
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/Startup.cs

@ -1,18 +1,30 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System.IO;
using Volo.Abp.IO;
using Volo.Abp.Modularity.PlugIns;
namespace LY.MicroService.WorkflowManagement
namespace LY.MicroService.WorkflowManagement;
public class Startup
{
public class Startup
public void ConfigureServices(IServiceCollection services)
{
public void ConfigureServices(IServiceCollection services)
services.AddApplication<WorkflowManagementHttpApiHostModule>(options =>
{
services.AddApplication<WorkflowManagementHttpApiHostModule>();
}
// 搜索 Modules 目录下所有文件作为插件
// 取消显示引用所有其他项目的模块,改为通过插件的形式引用
var pluginFolder = Path.Combine(
Directory.GetCurrentDirectory(), "Modules");
DirectoryHelper.CreateIfNotExists(pluginFolder);
options.PlugInSources.AddFolder(
pluginFolder,
SearchOption.AllDirectories);
});
}
public void Configure(IApplicationBuilder app)
{
app.InitializeApplication();
}
public void Configure(IApplicationBuilder app)
{
app.InitializeApplication();
}
}

37
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/TenantHeaderParamter.cs

@ -4,29 +4,28 @@ using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using Volo.Abp.MultiTenancy;
namespace LY.MicroService.WorkflowManagement
namespace LY.MicroService.WorkflowManagement;
public class TenantHeaderParamter : IOperationFilter
{
public class TenantHeaderParamter : IOperationFilter
private readonly AbpMultiTenancyOptions _options;
public TenantHeaderParamter(
IOptions<AbpMultiTenancyOptions> options)
{
private readonly AbpMultiTenancyOptions _options;
public TenantHeaderParamter(
IOptions<AbpMultiTenancyOptions> options)
{
_options = options.Value;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
_options = options.Value;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (_options.IsEnabled)
{
if (_options.IsEnabled)
operation.Parameters = operation.Parameters ?? new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
operation.Parameters = operation.Parameters ?? new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = TenantResolverConsts.DefaultTenantKey,
In = ParameterLocation.Header,
Description = "Tenant Id/Name",
Required = false
});
}
Name = TenantResolverConsts.DefaultTenantKey,
In = ParameterLocation.Header,
Description = "Tenant Id/Name",
Required = false
});
}
}
}

315
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/WorkflowManagementHttpApiHostModule.Configure.cs

@ -27,168 +27,168 @@ using Volo.Abp.Uow;
using Volo.Abp.VirtualFileSystem;
using LINGYUN.Abp.WorkflowCore.Components;
namespace LY.MicroService.WorkflowManagement
namespace LY.MicroService.WorkflowManagement;
public partial class WorkflowManagementHttpApiHostModule
{
public partial class WorkflowManagementHttpApiHostModule
private void PreConfigureApp()
{
private void PreConfigureApp()
{
AbpSerilogEnrichersConsts.ApplicationName = "WorkflowManagement";
}
AbpSerilogEnrichersConsts.ApplicationName = "WorkflowManagement";
}
private void ConfigureDistributedLock(IServiceCollection services, IConfiguration configuration)
{
var redis = ConnectionMultiplexer.Connect(configuration["DistributedLock:Redis:Configuration"]);
services.AddSingleton<IDistributedLockProvider>(_ => new RedisDistributedSynchronizationProvider(redis.GetDatabase()));
}
private void ConfigureDistributedLock(IServiceCollection services, IConfiguration configuration)
{
var redis = ConnectionMultiplexer.Connect(configuration["DistributedLock:Redis:Configuration"]);
services.AddSingleton<IDistributedLockProvider>(_ => new RedisDistributedSynchronizationProvider(redis.GetDatabase()));
}
private void ConfigureBlobStoring(IServiceCollection services, IConfiguration configuration)
private void ConfigureBlobStoring(IServiceCollection services, IConfiguration configuration)
{
Configure<AbpBlobStoringOptions>(options =>
{
Configure<AbpBlobStoringOptions>(options =>
services.ExecutePreConfiguredActions(options);
options.Containers.Configure<WorkflowContainer>((containerConfiguration) =>
{
services.ExecutePreConfiguredActions(options);
options.Containers.Configure<WorkflowContainer>((containerConfiguration) =>
containerConfiguration.UseOssManagement(config =>
{
containerConfiguration.UseOssManagement(config =>
{
config.Bucket = configuration[OssManagementBlobProviderConfigurationNames.Bucket] ?? "workflow";
});
config.Bucket = configuration[OssManagementBlobProviderConfigurationNames.Bucket] ?? "workflow";
});
});
}
});
}
private void ConfigureDbContext()
private void ConfigureDbContext()
{
// 配置Ef
Configure<AbpDbContextOptions>(options =>
{
// 配置Ef
Configure<AbpDbContextOptions>(options =>
{
options.UseMySQL();
});
}
options.UseMySQL();
});
}
private void ConfigureJsonSerializer()
private void ConfigureJsonSerializer()
{
// 解决某些不支持类型的序列化
Configure<AbpJsonOptions>(options =>
{
// 解决某些不支持类型的序列化
Configure<AbpJsonOptions>(options =>
{
options.DefaultDateTimeFormat = "yyyy-MM-dd HH:mm:ss";
});
// 中文序列化的编码问题
Configure<AbpSystemTextJsonSerializerOptions>(options =>
{
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
});
}
options.DefaultDateTimeFormat = "yyyy-MM-dd HH:mm:ss";
});
// 中文序列化的编码问题
Configure<AbpSystemTextJsonSerializerOptions>(options =>
{
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
});
}
private void ConfigureExceptionHandling()
private void ConfigureExceptionHandling()
{
// 自定义需要处理的异常
Configure<AbpExceptionHandlingOptions>(options =>
{
// 自定义需要处理的异常
Configure<AbpExceptionHandlingOptions>(options =>
{
// 加入需要处理的异常类型
options.Handlers.Add<Volo.Abp.Data.AbpDbConcurrencyException>();
options.Handlers.Add<AbpInitializationException>();
options.Handlers.Add<OutOfMemoryException>();
options.Handlers.Add<System.Data.Common.DbException>();
options.Handlers.Add<Microsoft.EntityFrameworkCore.DbUpdateException>();
options.Handlers.Add<System.Data.DBConcurrencyException>();
});
// 自定义需要发送邮件通知的异常类型
Configure<AbpEmailExceptionHandlingOptions>(options =>
{
// 是否发送堆栈信息
options.SendStackTrace = true;
// 未指定异常接收者的默认接收邮件
// 指定自己的邮件地址
});
}
// 加入需要处理的异常类型
options.Handlers.Add<Volo.Abp.Data.AbpDbConcurrencyException>();
options.Handlers.Add<AbpInitializationException>();
options.Handlers.Add<OutOfMemoryException>();
options.Handlers.Add<System.Data.Common.DbException>();
options.Handlers.Add<Microsoft.EntityFrameworkCore.DbUpdateException>();
options.Handlers.Add<System.Data.DBConcurrencyException>();
});
// 自定义需要发送邮件通知的异常类型
Configure<AbpEmailExceptionHandlingOptions>(options =>
{
// 是否发送堆栈信息
options.SendStackTrace = true;
// 未指定异常接收者的默认接收邮件
// 指定自己的邮件地址
});
}
private void ConfigureAuditing(IConfiguration configuration)
private void ConfigureAuditing(IConfiguration configuration)
{
Configure<AbpAuditingOptions>(options =>
{
Configure<AbpAuditingOptions>(options =>
options.ApplicationName = "WorkflowManagement";
// 是否启用实体变更记录
var entitiesChangedConfig = configuration.GetSection("App:TrackingEntitiesChanged");
if (entitiesChangedConfig.Exists() && entitiesChangedConfig.Get<bool>())
{
options.ApplicationName = "WorkflowManagement";
// 是否启用实体变更记录
var entitiesChangedConfig = configuration.GetSection("App:TrackingEntitiesChanged");
if (entitiesChangedConfig.Exists() && entitiesChangedConfig.Get<bool>())
{
options
.EntityHistorySelectors
.AddAllEntities();
}
});
}
options
.EntityHistorySelectors
.AddAllEntities();
}
});
}
private void ConfigureCaching(IConfiguration configuration)
private void ConfigureCaching(IConfiguration configuration)
{
Configure<AbpDistributedCacheOptions>(options =>
{
Configure<AbpDistributedCacheOptions>(options =>
{
// 最好统一命名,不然某个缓存变动其他应用服务有例外发生
options.KeyPrefix = "LINGYUN.Abp.Application";
// 滑动过期30天
options.GlobalCacheEntryOptions.SlidingExpiration = TimeSpan.FromDays(30d);
// 绝对过期60天
options.GlobalCacheEntryOptions.AbsoluteExpiration = DateTimeOffset.Now.AddDays(60d);
});
// 最好统一命名,不然某个缓存变动其他应用服务有例外发生
options.KeyPrefix = "LINGYUN.Abp.Application";
// 滑动过期30天
options.GlobalCacheEntryOptions.SlidingExpiration = TimeSpan.FromDays(30d);
// 绝对过期60天
options.GlobalCacheEntryOptions.AbsoluteExpiration = DateTimeOffset.Now.AddDays(60d);
});
Configure<RedisCacheOptions>(options =>
{
var redisConfig = ConfigurationOptions.Parse(options.Configuration);
options.ConfigurationOptions = redisConfig;
options.InstanceName = configuration["Redis:InstanceName"];
});
}
Configure<RedisCacheOptions>(options =>
{
var redisConfig = ConfigurationOptions.Parse(options.Configuration);
options.ConfigurationOptions = redisConfig;
options.InstanceName = configuration["Redis:InstanceName"];
});
}
private void ConfigureVirtualFileSystem()
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<WorkflowManagementHttpApiHostModule>("LINGYUN.Abp.WorkflowManagement");
});
}
private void ConfigureVirtualFileSystem()
private void ConfigureMultiTenancy(IConfiguration configuration)
{
// 多租户
Configure<AbpMultiTenancyOptions>(options =>
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<WorkflowManagementHttpApiHostModule>("LINGYUN.Abp.WorkflowManagement");
});
}
options.IsEnabled = true;
});
private void ConfigureMultiTenancy(IConfiguration configuration)
var tenantResolveCfg = configuration.GetSection("App:Domains");
if (tenantResolveCfg.Exists())
{
// 多租户
Configure<AbpMultiTenancyOptions>(options =>
Configure<AbpTenantResolveOptions>(options =>
{
options.IsEnabled = true;
var domains = tenantResolveCfg.Get<string[]>();
foreach (var domain in domains)
{
options.AddDomainTenantResolver(domain);
}
});
}
}
var tenantResolveCfg = configuration.GetSection("App:Domains");
if (tenantResolveCfg.Exists())
private void ConfigureSwagger(IServiceCollection services)
{
// Swagger
services.AddSwaggerGen(
options =>
{
Configure<AbpTenantResolveOptions>(options =>
options.SwaggerDoc("v1", new OpenApiInfo { Title = "WorkflowManagement API", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
var domains = tenantResolveCfg.Get<string[]>();
foreach (var domain in domains)
{
options.AddDomainTenantResolver(domain);
}
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Scheme = "bearer",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT"
});
}
}
private void ConfigureSwagger(IServiceCollection services)
{
// Swagger
services.AddSwaggerGen(
options =>
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "WorkflowManagement API", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Scheme = "bearer",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
@ -196,41 +196,40 @@ namespace LY.MicroService.WorkflowManagement
},
new string[] { }
}
});
options.OperationFilter<TenantHeaderParamter>();
});
}
options.OperationFilter<TenantHeaderParamter>();
});
}
private void ConfigureLocalization()
private void ConfigureLocalization()
{
// 支持本地化语言类型
Configure<AbpLocalizationOptions>(options =>
{
// 支持本地化语言类型
Configure<AbpLocalizationOptions>(options =>
options.Languages.Add(new LanguageInfo("en", "en", "English"));
options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
// 动态语言支持
options.Resources.AddDynamic();
});
}
private void ConfigureSecurity(IServiceCollection services, IConfiguration configuration, bool isDevelopment = false)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Languages.Add(new LanguageInfo("en", "en", "English"));
options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
// 动态语言支持
options.Resources.AddDynamic();
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.Audience = configuration["AuthServer:ApiName"];
});
}
private void ConfigureSecurity(IServiceCollection services, IConfiguration configuration, bool isDevelopment = false)
if (!isDevelopment)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.Audience = configuration["AuthServer:ApiName"];
});
if (!isDevelopment)
{
var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]);
services
.AddDataProtection()
.SetApplicationName("LINGYUN.Abp.Application")
.PersistKeysToStackExchangeRedis(redis, "LINGYUN.Abp.Application:DataProtection:Protection-Keys");
}
var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]);
services
.AddDataProtection()
.SetApplicationName("LINGYUN.Abp.Application")
.PersistKeysToStackExchangeRedis(redis, "LINGYUN.Abp.Application:DataProtection:Protection-Keys");
}
}
}

17
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/WorkflowManagementHttpApiHostModule.DataSeeder.cs

@ -4,18 +4,17 @@ using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.Threading;
namespace LY.MicroService.WorkflowManagement
namespace LY.MicroService.WorkflowManagement;
public partial class WorkflowManagementHttpApiHostModule
{
public partial class WorkflowManagementHttpApiHostModule
private void SeedData(ApplicationInitializationContext context)
{
private void SeedData(ApplicationInitializationContext context)
if (context.GetEnvironment().IsDevelopment())
{
if (context.GetEnvironment().IsDevelopment())
{
AsyncHelper.RunSync(async () =>
await context.ServiceProvider.GetRequiredService<IDataSeeder>()
.SeedAsync());
}
AsyncHelper.RunSync(async () =>
await context.ServiceProvider.GetRequiredService<IDataSeeder>()
.SeedAsync());
}
}
}

107
aspnet-core/services/LY.MicroService.WorkflowManagement.HttpApi.Host/WorkflowManagementHttpApiHostModule.cs

@ -34,9 +34,9 @@ using Volo.Abp.SettingManagement.EntityFrameworkCore;
using Volo.Abp.Swashbuckle;
using Volo.Abp.TenantManagement.EntityFrameworkCore;
namespace LY.MicroService.WorkflowManagement
{
[DependsOn(
namespace LY.MicroService.WorkflowManagement;
[DependsOn(
typeof(AbpSerilogEnrichersApplicationModule),
typeof(AbpAuditLoggingElasticsearchModule),
typeof(AbpAspNetCoreSerilogModule),
@ -67,64 +67,63 @@ namespace LY.MicroService.WorkflowManagement
typeof(AbpSwashbuckleModule),
typeof(AbpAutofacModule)
)]
public partial class WorkflowManagementHttpApiHostModule : AbpModule
public partial class WorkflowManagementHttpApiHostModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigureApp();
}
PreConfigureApp();
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
ConfigureDbContext();
ConfigureLocalization();
ConfigureJsonSerializer();
ConfigureExceptionHandling();
ConfigureVirtualFileSystem();
ConfigureCaching(configuration);
ConfigureAuditing(configuration);
ConfigureMultiTenancy(configuration);
ConfigureSwagger(context.Services);
ConfigureBlobStoring(context.Services, configuration);
ConfigureDistributedLock(context.Services, configuration);
ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment());
ConfigureDbContext();
ConfigureLocalization();
ConfigureJsonSerializer();
ConfigureExceptionHandling();
ConfigureVirtualFileSystem();
ConfigureCaching(configuration);
ConfigureAuditing(configuration);
ConfigureMultiTenancy(configuration);
ConfigureSwagger(context.Services);
ConfigureBlobStoring(context.Services, configuration);
ConfigureDistributedLock(context.Services, configuration);
ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment());
// 开发取消权限检查
// context.Services.AddAlwaysAllowAuthorization();
}
// 开发取消权限检查
// context.Services.AddAlwaysAllowAuthorization();
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
app.UseStaticFiles();
app.UseCorrelationId();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseJwtTokenMiddleware();
app.UseMultiTenancy();
app.UseAbpRequestLocalization(options => options.SetDefaultCulture(CultureInfo.CurrentCulture.Name));
app.UseAuthorization();
app.UseSwagger();
app.UseAbpSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Support APP API");
app.UseStaticFiles();
app.UseCorrelationId();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseJwtTokenMiddleware();
app.UseMultiTenancy();
app.UseAbpRequestLocalization(options => options.SetDefaultCulture(CultureInfo.CurrentCulture.Name));
app.UseAuthorization();
app.UseSwagger();
app.UseAbpSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Support APP API");
var configuration = context.GetConfiguration();
options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);
options.OAuthScopes("WorkflowManagement");
});
app.UseAuditing();
app.UseAbpSerilogEnrichers();
app.UseConfiguredEndpoints();
var configuration = context.GetConfiguration();
options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);
options.OAuthScopes("WorkflowManagement");
});
app.UseAuditing();
app.UseAbpSerilogEnrichers();
app.UseConfiguredEndpoints();
SeedData(context);
}
SeedData(context);
}
}

25
aspnet-core/tests/LINGYUN.Abp.WorkflowCore.Tests/LINGYUN.Abp.WorkflowCore.Tests.csproj

@ -1,18 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace />
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace />
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<!--<PackageReference Include="WorkflowCore.Testing" Version="3.5.2" />-->
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\modules\workflow\LINGYUN.Abp.WorkflowCore\LINGYUN.Abp.WorkflowCore.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\modules\workflow\LINGYUN.Abp.WorkflowCore\LINGYUN.Abp.WorkflowCore.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" />
</ItemGroup>
</Project>

72
aspnet-core/tests/LINGYUN.Abp.WorkflowCore.Tests/LINGYUN/Abp/WorkflowCore/Middleware/MultiTenancyStepMiddleware_Tests.cs

@ -0,0 +1,72 @@
using Shouldly;
using System;
using System.Threading.Tasks;
using Volo.Abp.MultiTenancy;
using WorkflowCore.Interface;
using WorkflowCore.Models;
using Xunit;
namespace LINGYUN.Abp.WorkflowCore.Middleware
{
public class MultiTenancyStepMiddleware_Tests : AbpWorkflowCoreTestBase
{
public static bool IsCompleted = false;
public static readonly Guid TenantId = Guid.NewGuid();
private readonly IWorkflowController _controller;
public MultiTenancyStepMiddleware_Tests()
{
_controller = GetRequiredService<IWorkflowController>();
}
[Fact]
public async Task Should_Be_Resolved_Multi_Tenant_Id_On_Step()
{
await _controller.StartWorkflow(
"MiddlewareWorkflow",
new MiddlewareWorkflowData
{
TenantId = TenantId
});
}
}
public class MiddlewareWorkflowData : IMultiTenant
{
public Guid? TenantId { get; set; }
}
public class MiddlewareWorkflow : IWorkflow<MiddlewareWorkflowData>
{
public string Id => "MiddlewareWorkflow";
public int Version => 1;
public void Build(IWorkflowBuilder<MiddlewareWorkflowData> builder)
{
builder.StartWith((_) => Console.WriteLine("Start Workflow"))
.Then<MessageStep>()
.Then((_) => MultiTenancyStepMiddleware_Tests.IsCompleted = true)
.EndWorkflow();
}
}
public class MessageStep : StepBodyBase
{
private readonly ICurrentTenant _currentTenant;
public MessageStep(ICurrentTenant currentTenant)
{
_currentTenant = currentTenant;
}
public override ExecutionResult Run(IStepExecutionContext context)
{
MultiTenancyStepMiddleware_Tests.TenantId.ShouldBe(_currentTenant.Id.Value);
Console.WriteLine("Current Tenant Id: {0}", _currentTenant.Id?.ToString() ?? "Null");
return ExecutionResult.Next();
}
}
}
Loading…
Cancel
Save