前言

ABP目前已经是很成熟的开发框架了,它提供了很多我们日常开发所必须的功能,并且很方便扩展,让我们能更专注于业务的开发。但是ABP官方并没有给我们实现工作流。

在.net core环境下的开源工作流引擎很少,其中WorkflowCore是一款轻量级工作流引擎,对于小型工作流和责任链类型的需求开发很适合,但只能通过后台编码或者json的方式定义工作流程,看了源码后觉得扩展性还是挺好的,至少能满足我的需求,于是选择对它下手。

jsPlumb是一个开源的比较强大的绘图组件,这里不多介绍,我就是用它实现一个简单的流程设计器。

花了差不多一个月的时间,把这三者结合到一起实现一个简单而强大的工作流模块。

目录

  1.  ABP模块实现WorkflowCore持久化存储接口(IPersistenceProvider)
  2. ABP中AbpWorkflow和AbpStepBody的自定义注册
  3. 设计器实现
  4. 设计器提交的流程数据转换成WorkflowCore支持的Json数据结构
  5. 总结

1.ABP模块实现WorkflowCore持久化存储接口(IPersistenceProvider)

这里我参考了WorkflowCore.Persistence.EntityFramework 持久化项目的实现方式 用ABP的方式实现了WorkflowCore的持久化。这样做有两个好处:

1.让工作流能支持ABP的多租户和全局数据过滤功能

2.数据库操作能使用统一的数据上下文,方便事务提交和回滚。

  • ABP实现的流程Workflow持久化存储所必须的实体类,其中PersistedWorkflowDefinition是用来持久化存储流程定义(在Workflow中流程定义在内存中)如下图:

  • 实现IPersistenceProvider接口

  1 public interface IAbpPersistenceProvider : IPersistenceProvider
2 {
3 Task<PersistedWorkflow> GetPersistedWorkflow(Guid id);
4
5 Task<PersistedExecutionPointer> GetPersistedExecutionPointer(string id);
6 Task<PersistedWorkflowDefinition> GetPersistedWorkflowDefinition(string id, int version);
7 }
8
9
10 public class AbpPersistenceProvider : DomainService, IAbpPersistenceProvider
11 {
12 protected readonly IRepository<PersistedEvent, Guid> _eventRepository;
13 protected readonly IRepository<PersistedExecutionPointer, string> _executionPointerRepository;
14 protected readonly IRepository<PersistedWorkflow, Guid> _workflowRepository;
15 protected readonly IRepository<PersistedWorkflowDefinition, string > _workflowDefinitionRepository;
16 protected readonly IRepository<PersistedSubscription, Guid> _eventSubscriptionRepository;
17 protected readonly IRepository<PersistedExecutionError, Guid> _executionErrorRepository;
18 protected readonly IGuidGenerator _guidGenerator;
19 protected readonly IAsyncQueryableExecuter _asyncQueryableExecuter;
20 public IAbpSession AbpSession { get; set; }
21
22
23 public AbpPersistenceProvider(IRepository<PersistedEvent, Guid> eventRepository, IRepository<PersistedExecutionPointer, string> executionPointerRepository, IRepository<PersistedWorkflow, Guid> workflowRepository, IRepository<PersistedSubscription, Guid> eventSubscriptionRepository, IGuidGenerator guidGenerator, IAsyncQueryableExecuter asyncQueryableExecuter, IRepository<PersistedExecutionError, Guid> executionErrorRepository, IRepository<PersistedWorkflowDefinition, string > workflowDefinitionRepository)
24 {
25
26 _eventRepository = eventRepository;
27 _executionPointerRepository = executionPointerRepository;
28 _workflowRepository = workflowRepository;
29 _eventSubscriptionRepository = eventSubscriptionRepository;
30 _guidGenerator = guidGenerator;
31 _asyncQueryableExecuter = asyncQueryableExecuter;
32 _executionErrorRepository = executionErrorRepository;
33 _workflowDefinitionRepository = workflowDefinitionRepository;
34
35
36 }
37 [UnitOfWork]
38 public virtual async Task<string> CreateEventSubscription(EventSubscription subscription)
39 {
40
41 subscription.Id = _guidGenerator.Create().ToString();
42 var persistable = subscription.ToPersistable();
43 await _eventSubscriptionRepository.InsertAsync(persistable);
44 return subscription.Id;
45 }
46 [UnitOfWork]
47 public virtual async Task<string> CreateNewWorkflow(WorkflowInstance workflow)
48 {
49 workflow.Id = _guidGenerator.Create().ToString();
50 var persistable = workflow.ToPersistable();
51 if (AbpSession.UserId.HasValue)
52 {
53 var userCache = AbpSession.GetCurrentUser();
54 persistable.CreateUserIdentityName = userCache.FullName;
55 }
56 await _workflowRepository.InsertAsync(persistable);
57 return workflow.Id;
58 }
59 [UnitOfWork]
60 public virtual async Task<IEnumerable<string>> GetRunnableInstances(DateTime asAt)
61 {
62 var now = asAt.ToUniversalTime().Ticks;
63
64 var query = _workflowRepository.GetAll().Where(x => x.NextExecution.HasValue && (x.NextExecution <= now) && (x.Status == WorkflowStatus.Runnable))
65 .Select(x => x.Id);
66 var raw = await _asyncQueryableExecuter.ToListAsync(query);
67
68 return raw.Select(s => s.ToString()).ToList();
69 }
70 [UnitOfWork]
71 public virtual async Task<IEnumerable<WorkflowInstance>> GetWorkflowInstances(WorkflowStatus? status, string type, DateTime? createdFrom, DateTime? createdTo, int skip, int take)
72 {
73
74 IQueryable<PersistedWorkflow> query = _workflowRepository.GetAll()
75 .Include(wf => wf.ExecutionPointers)
76 .ThenInclude(ep => ep.ExtensionAttributes)
77 .Include(wf => wf.ExecutionPointers)
78 .AsQueryable();
79
80 if (status.HasValue)
81 query = query.Where(x => x.Status == status.Value);
82
83 if (!String.IsNullOrEmpty(type))
84 query = query.Where(x => x.WorkflowDefinitionId == type);
85
86 if (createdFrom.HasValue)
87 query = query.Where(x => x.CreateTime >= createdFrom.Value);
88
89 if (createdTo.HasValue)
90 query = query.Where(x => x.CreateTime <= createdTo.Value);
91
92 var rawResult = await query.Skip(skip).Take(take).ToListAsync();
93 List<WorkflowInstance> result = new List<WorkflowInstance>();
94
95 foreach (var item in rawResult)
96 result.Add(item.ToWorkflowInstance());
97
98 return result;
99
100 }
101 [UnitOfWork]
102 public virtual async Task<WorkflowInstance> GetWorkflowInstance(string Id)
103 {
104
105 var uid = new Guid(Id);
106 var raw = await _workflowRepository.GetAll()
107 .Include(wf => wf.ExecutionPointers)
108 .ThenInclude(ep => ep.ExtensionAttributes)
109 .Include(wf => wf.ExecutionPointers)
110 .FirstAsync(x => x.Id == uid);
111
112 if (raw == null)
113 return null;
114
115 return raw.ToWorkflowInstance();
116
117 }
118 [UnitOfWork]
119 public virtual async Task<IEnumerable<WorkflowInstance>> GetWorkflowInstances(IEnumerable<string> ids)
120 {
121 if (ids == null)
122 {
123 return new List<WorkflowInstance>();
124 }
125
126
127 var uids = ids.Select(i => new Guid(i));
128 var raw = _workflowRepository.GetAll()
129 .Include(wf => wf.ExecutionPointers)
130 .ThenInclude(ep => ep.ExtensionAttributes)
131 .Include(wf => wf.ExecutionPointers)
132 .Where(x => uids.Contains(x.Id));
133
134 return (await raw.ToListAsync()).Select(i => i.ToWorkflowInstance());
135
136 }
137 [UnitOfWork]
138 public virtual async Task PersistWorkflow(WorkflowInstance workflow)
139 {
140
141 var uid = new Guid(workflow.Id);
142 var existingEntity = await _workflowRepository.GetAll()
143 .Where(x => x.Id == uid)
144 .Include(wf => wf.ExecutionPointers)
145 .ThenInclude(ep => ep.ExtensionAttributes)
146 .Include(wf => wf.ExecutionPointers)
147 .AsTracking()
148 .FirstAsync();
149 var persistable = workflow.ToPersistable(existingEntity);
150 await CurrentUnitOfWork.SaveChangesAsync();
151 }
152 [UnitOfWork]
153 public virtual async Task TerminateSubscription(string eventSubscriptionId)
154 {
155
156 var uid = new Guid(eventSubscriptionId);
157 var existing = await _eventSubscriptionRepository.FirstOrDefaultAsync(x => x.Id == uid);
158 _eventSubscriptionRepository.Delete(existing);
159 await CurrentUnitOfWork.SaveChangesAsync();
160
161 }
162 [UnitOfWork]
163 public virtual void EnsureStoreExists()
164 {
165
166
167 }
168 [UnitOfWork]
169 public virtual async Task<IEnumerable<EventSubscription>> GetSubscriptions(string eventName, string eventKey, DateTime asOf)
170 {
171
172 asOf = asOf.ToUniversalTime();
173 var raw = await _eventSubscriptionRepository.GetAll()
174 .Where(x => x.EventName == eventName && x.EventKey == eventKey && x.SubscribeAsOf <= asOf)
175 .ToListAsync();
176
177 return raw.Select(item => item.ToEventSubscription()).ToList();
178
179 }
180 [UnitOfWork]
181 public virtual async Task<string> CreateEvent(Event newEvent)
182 {
183
184 newEvent.Id = _guidGenerator.Create().ToString();
185 var persistable = newEvent.ToPersistable();
186 var result = _eventRepository.InsertAsync(persistable);
187 await CurrentUnitOfWork.SaveChangesAsync();
188 return newEvent.Id;
189 }
190 [UnitOfWork]
191 public virtual async Task<Event> GetEvent(string id)
192 {
193
194 Guid uid = new Guid(id);
195 var raw = await _eventRepository
196 .FirstOrDefaultAsync(x => x.Id == uid);
197
198 if (raw == null)
199 return null;
200
201 return raw.ToEvent();
202
203 }
204 [UnitOfWork]
205 public virtual async Task<IEnumerable<string>> GetRunnableEvents(DateTime asAt)
206 {
207 var now = asAt.ToUniversalTime();
208
209 asAt = asAt.ToUniversalTime();
210 var raw = await _eventRepository.GetAll()
211 .Where(x => !x.IsProcessed)
212 .Where(x => x.EventTime <= now)
213 .Select(x => x.Id)
214 .ToListAsync();
215
216 return raw.Select(s => s.ToString()).ToList();
217
218 }
219 [UnitOfWork]
220 public virtual async Task MarkEventProcessed(string id)
221 {
222
223 var uid = new Guid(id);
224 var existingEntity = await _eventRepository.GetAll()
225 .Where(x => x.Id == uid)
226 .AsTracking()
227 .FirstAsync();
228
229 existingEntity.IsProcessed = true;
230 await CurrentUnitOfWork.SaveChangesAsync();
231 }
232 [UnitOfWork]
233 public virtual async Task<IEnumerable<string>> GetEvents(string eventName, string eventKey, DateTime asOf)
234 {
235
236 var raw = await _eventRepository.GetAll()
237 .Where(x => x.EventName == eventName && x.EventKey == eventKey)
238 .Where(x => x.EventTime >= asOf)
239 .Select(x => x.Id)
240 .ToListAsync();
241
242 var result = new List<string>();
243
244 foreach (var s in raw)
245 result.Add(s.ToString());
246
247 return result;
248
249 }
250 [UnitOfWork]
251 public virtual async Task MarkEventUnprocessed(string id)
252 {
253
254 var uid = new Guid(id);
255 var existingEntity = await _eventRepository.GetAll()
256 .Where(x => x.Id == uid)
257 .AsTracking()
258 .FirstAsync();
259
260 existingEntity.IsProcessed = false;
261 await CurrentUnitOfWork.SaveChangesAsync();
262
263 }
264 [UnitOfWork]
265 public virtual async Task PersistErrors(IEnumerable<ExecutionError> errors)
266 {
267
268 var executionErrors = errors as ExecutionError[] ?? errors.ToArray();
269 if (executionErrors.Any())
270 {
271 foreach (var error in executionErrors)
272 {
273 await _executionErrorRepository.InsertAsync(error.ToPersistable());
274 }
275 await CurrentUnitOfWork.SaveChangesAsync();
276
277 }
278
279 }
280 [UnitOfWork]
281 public virtual async Task<EventSubscription> GetSubscription(string eventSubscriptionId)
282 {
283
284 var uid = new Guid(eventSubscriptionId);
285 var raw = await _eventSubscriptionRepository.FirstOrDefaultAsync(x => x.Id == uid);
286
287 return raw?.ToEventSubscription();
288
289 }
290 [UnitOfWork]
291 public virtual async Task<EventSubscription> GetFirstOpenSubscription(string eventName, string eventKey, DateTime asOf)
292 {
293
294 var raw = await _eventSubscriptionRepository.FirstOrDefaultAsync(x => x.EventName == eventName && x.EventKey == eventKey && x.SubscribeAsOf <= asOf && x.ExternalToken == null);
295
296 return raw?.ToEventSubscription();
297
298 }
299 [UnitOfWork]
300 public virtual async Task<bool> SetSubscriptionToken(string eventSubscriptionId, string token, string workerId, DateTime expiry)
301 {
302
303 var uid = new Guid(eventSubscriptionId);
304 var existingEntity = await _eventSubscriptionRepository.GetAll()
305 .Where(x => x.Id == uid)
306 .AsTracking()
307 .FirstAsync();
308
309 existingEntity.ExternalToken = token;
310 existingEntity.ExternalWorkerId = workerId;
311 existingEntity.ExternalTokenExpiry = expiry;
312 await CurrentUnitOfWork.SaveChangesAsync();
313
314 return true;
315
316 }
317 [UnitOfWork]
318 public virtual async Task ClearSubscriptionToken(string eventSubscriptionId, string token)
319 {
320
321 var uid = new Guid(eventSubscriptionId);
322 var existingEntity = await _eventSubscriptionRepository.GetAll()
323 .Where(x => x.Id == uid)
324 .AsTracking()
325 .FirstAsync();
326
327 if (existingEntity.ExternalToken != token)
328 throw new InvalidOperationException();
329
330 existingEntity.ExternalToken = null;
331 existingEntity.ExternalWorkerId = null;
332 existingEntity.ExternalTokenExpiry = null;
333 await CurrentUnitOfWork.SaveChangesAsync();
334
335 }
336
337 public Task<PersistedWorkflow> GetPersistedWorkflow(Guid id)
338 {
339 return _workflowRepository.GetAsync(id);
340 }
341
342 public Task<PersistedWorkflowDefinition> GetPersistedWorkflowDefinition(string id, int version)
343 {
344 return _workflowDefinitionRepository.GetAll().AsNoTracking().FirstOrDefaultAsync(u => u.Id == id && u.Version == version);
345 }
346
347 public Task<PersistedExecutionPointer> GetPersistedExecutionPointer(string id)
348 {
349 return _executionPointerRepository.GetAsync(id);
350 }
351 }
  • 服务注册添加AddWorkflow时把IPersistenceProvider提供的默认实现换成AbpPersistenceProvider

 public static class ServiceCollectionExtensions
{
public static IServiceCollection AddAbpWorkflow(this IServiceCollection services, Action<WorkflowOptions> setupAction = null)
{
services.AddSingleton<IPersistenceProvider, AbpPersistenceProvider>();
services.AddWorkflow(options =>
{ options.UsePersistence(sp => sp.GetService<AbpPersistenceProvider>());
setupAction?.Invoke(options);
});
services.AddWorkflowDSL();
return services;
}
}

  到此为止,ABP已经实现了WorkflowCore的默认的持久化存储。

2.ABP中AbpWorkflow和AbpStepBody的自定义注册

为了满足开发人员和用户的需求,我提供了两种流程注册方式,一种是开发人员后台编码定义固定流程另一种是用户通过流程设计器实现自定义业务流程。

  • 开发人员后台编码定义固定流程

这里参考ABP的EventBus注册方式,实现IWindsorInstaller ,在组件注册时拦截并注册:

//ABP工作流接口
public interface IAbpWorkflow : IWorkflow<WorkflowParamDictionary>
{
} //工作流注册接口
public interface IAbpWorkflowRegisty
{
void RegisterWorkflow(Type type);
} //Abp工作流注册实现
public class AbpWorkflowRegisty : IAbpWorkflowRegisty, ISingletonDependency
{
private IWorkflowRegistry _workflowRegistry;
private readonly IIocManager _iocManager; public AbpWorkflowRegisty(IWorkflowRegistry workflowRegistry, IIocManager iocManager)
{
this._workflowRegistry = workflowRegistry;
this._iocManager = iocManager;
} public void RegisterWorkflow(Type type)
{
var workflow = _iocManager.Resolve(type);
if (!(workflow is IAbpWorkflow))
{
throw new AbpException("RegistType must implement from AbpWorkflow!");
}
_workflowRegistry.RegisterWorkflow(workflow as IWorkflow<WorkflowParamDictionary>);
} } //拦截器实现
internal class WorkflowInstaller : IWindsorInstaller
{
private readonly IIocResolver _iocResolver; private IAbpWorkflowRegisty serviceSelector; public WorkflowInstaller(IIocResolver iocResolver)
{
_iocResolver = iocResolver;
} public void Install(IWindsorContainer container, IConfigurationStore store)
{
serviceSelector = container.Resolve<IAbpWorkflowRegisty>();
container.Kernel.ComponentRegistered += Kernel_ComponentRegistered;
} private void Kernel_ComponentRegistered(string key, IHandler handler)
{
if (!typeof(IAbpWorkflow).GetTypeInfo().IsAssignableFrom(handler.ComponentModel.Implementation))
{
return;
} var interfaces = handler.ComponentModel.Implementation.GetTypeInfo().GetInterfaces();
foreach (var @interface in interfaces)
{
if (!typeof(IAbpWorkflow).GetTypeInfo().IsAssignableFrom(@interface))
{
continue;
}
serviceSelector.RegisterWorkflow( handler.ComponentModel.Implementation);
}
}
}

  到这里,把拦截器注册到模块类的Initialize中,开发人员定义流程只需要实现IAbpWorkflow接口,系统启动时会自动注册。如图:

  • 自定义注册StepBody

这里参考ABP中标准的配置模式(不清楚的可以去看下ABP的源码,ABP的配置系统和权限系统都是这样配置的),将注册的StepBody存储在内存中提供给用户自定义组合流程节点使用,下列代码展示了注册指定用户审核的StepBody,执行方法体的实现:

 1  public class DefaultStepBodyProvider : AbpStepBodyProvider
2 {
3 public override void Build(IAbpStepBodyDefinitionContext context)
4 {
5 var step1 = new AbpWorkflowStepBody();
6 step1.Name = "FixedUserAudit";
7 step1.DisplayName = "指定用户审核";
8 step1.StepBodyType = typeof(GeneralAuditingStepBody);
9 step1.Inputs.Add(new WorkflowParam()
10 {
11 InputType = new SelectUserInputType(),//定义前端输入类型,继承Abp.UI.Inputs.InputTypeBase
12 Name = "UserId",
13 DisplayName = "审核人"
14 });
15 context.Create(step1);
16
17 }
18 }
19
20
21
22 /// <summary>
23 /// 指定用户审批StepBody
24 /// </summary>
25 public class GeneralAuditingStepBody : StepBody, ITransientDependency
26 {
27 private const string ActionName = "AuditEvent";
28 protected readonly INotificationPublisher _notificationPublisher;
29 protected readonly IAbpPersistenceProvider _abpPersistenceProvider;
30 protected readonly UserManager _userManager;
31
32 public readonly IRepository<PersistedWorkflowAuditor, Guid> _auditorRepository;
33
34 public GeneralAuditingStepBody(INotificationPublisher notificationPublisher, UserManager userManager, IAbpPersistenceProvider abpPersistenceProvider,
35 IRepository<PersistedWorkflowAuditor, Guid> auditorRepository)
36 {
37 _notificationPublisher = notificationPublisher;
38 _abpPersistenceProvider = abpPersistenceProvider;
39 _userManager = userManager;
40 _auditorRepository = auditorRepository;
41 }
42
43 /// <summary>
44 /// 审核人
45 /// </summary>
46 public long UserId { get; set; }
47
48 [UnitOfWork]
49 public override ExecutionResult Run(IStepExecutionContext context)
50 {
51 if (!context.ExecutionPointer.EventPublished)
52 {
53 var workflow = _abpPersistenceProvider.GetPersistedWorkflow(context.Workflow.Id.ToGuid()).Result;
54 var workflowDefinition = _abpPersistenceProvider.GetPersistedWorkflowDefinition(context.Workflow.WorkflowDefinitionId, context.Workflow.Version).Result;
55
56 var userIdentityName = _userManager.Users.Where(u => u.Id == workflow.CreatorUserId).Select(u => u.FullName).FirstOrDefault();
57
58 //通知审批人
59 _notificationPublisher.PublishTaskAsync(new Abp.Notifications.TaskNotificationData($"【{userIdentityName}】提交的{workflowDefinition.Title}需要您审批!"),
60 userIds: new UserIdentifier[] { new UserIdentifier(workflow.TenantId, UserId) },
61 entityIdentifier: new EntityIdentifier(workflow.GetType(), workflow.Id)
62 ).Wait();
63 //添加审核人记录
64 var auditUserInfo = _userManager.GetUserById(UserId);
65 _auditorRepository.Insert(new PersistedWorkflowAuditor() { WorkflowId = workflow.Id, ExecutionPointerId = context.ExecutionPointer.Id, Status = Abp.Entitys.CommEnum.EnumAuditStatus.UnAudited, UserId = UserId, TenantId = workflow.TenantId, UserHeadPhoto = auditUserInfo.HeadImage, UserIdentityName = auditUserInfo.FullName });
66 DateTime effectiveDate = DateTime.MinValue;
67 return ExecutionResult.WaitForEvent(ActionName, Guid.NewGuid().ToString(), effectiveDate);
68 }
69 var pass = _auditorRepository.GetAll().Any(u => u.ExecutionPointerId == context.ExecutionPointer.Id && u.UserId == UserId && u.Status == Abp.Entitys.CommEnum.EnumAuditStatus.Pass);
70
71 if (!pass)
72 {
73 context.Workflow.Status = WorkflowStatus.Complete;
74 return ExecutionResult.Next();
75 }
76 return ExecutionResult.Next();
77 }
78 }

查看代码

3.设计器实现

流程设计器我用的是Abp提供的Vue项目模板+jsplumb来实现的,话不多说直接上图把:

上图所示,每个节点执行操作选择的是我们后台注册的AbpStepBody。

注:开发人员可根据业务需求尽可能的给用户提供所需的StepBody。这样一来,整个流程的灵活性是非常好的。

4.设计器提交的流程数据转换成WorkflowCore支持的Json数据结构

前端传给后台的数据结构如下:

后台接收数据后转换成Workflow 支持的Josn字符串,再使用WorkflowCore.DSL提供的帮助类注册流程即可,转换后的Json如下:

 1 {
2 "DataType": "System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e",
3 "DefaultErrorBehavior": 0,
4 "DefaultErrorRetryInterval": null,
5 "Steps": [{
6 "StepType": "Abp.Workflows.DefaultSteps.NullStepBody, Abp.Workflows, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
7 "Id": "start_1600248885360yurl0hgrvpd",
8 "Name": "start_1600248885360yurl0hgrvpd",
9 "CancelCondition": null,
10 "ErrorBehavior": null,
11 "RetryInterval": null,
12 "Do": [],
13 "CompensateWith": [],
14 "Saga": false,
15 "NextStepId": null,
16 "Inputs": {},
17 "Outputs": {},
18 "SelectNextStep": {
19 "step_1600248890720r3o927aajy8": "1==1"
20 }
21 }, {
22 "StepType": "Abp.Workflows.StepBodys.GeneralAuditingStepBody, Abp.Workflows, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
23 "Id": "step_1600248890720r3o927aajy8",
24 "Name": "step_1600248890720r3o927aajy8",
25 "CancelCondition": null,
26 "ErrorBehavior": null,
27 "RetryInterval": null,
28 "Do": [],
29 "CompensateWith": [],
30 "Saga": false,
31 "NextStepId": null,
32 "Inputs": {
33 "UserId": "\"4\""
34 },
35 "Outputs": {},
36 "SelectNextStep": {
37 "end_16002488928403hmjauowus7": "decimal.Parse(data[\"Days\"].ToString()) <= 1",
38 "step_160032897781681o9ko9j3nr": "decimal.Parse(data[\"Days\"].ToString()) > 1"
39 }
40 }, {
41 "StepType": "Abp.Workflows.DefaultSteps.SendNotificationToInitiatorStepBody, Abp.Workflows, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
42 "Id": "end_16002488928403hmjauowus7",
43 "Name": "end_16002488928403hmjauowus7",
44 "CancelCondition": null,
45 "ErrorBehavior": null,
46 "RetryInterval": null,
47 "Do": [],
48 "CompensateWith": [],
49 "Saga": false,
50 "NextStepId": null,
51 "Inputs": {
52 "Message": "\"您的流程已完成\""
53 },
54 "Outputs": {},
55 "SelectNextStep": {}
56 }, {
57 "StepType": "Abp.Workflows.StepBodys.GeneralAuditingStepBody, Abp.Workflows, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
58 "Id": "step_160032897781681o9ko9j3nr",
59 "Name": "step_160032897781681o9ko9j3nr",
60 "CancelCondition": null,
61 "ErrorBehavior": null,
62 "RetryInterval": null,
63 "Do": [],
64 "CompensateWith": [],
65 "Saga": false,
66 "NextStepId": null,
67 "Inputs": {
68 "UserId": "\"5\""
69 },
70 "Outputs": {},
71 "SelectNextStep": {
72 "end_16002488928403hmjauowus7": "1==1"
73 }
74 }],
75 "Id": "c51e908f-60e3-4a01-ab63-3bce0eaedc48",
76 "Version": 1,
77 "Description": "请假"
78 }

查看Json

总结

一句话,上面所写的一切都是为了将流程注册到WorkflowCore中而做的铺垫。

后面我会把代码整理一份作为一个ABP的独立模块开源出来供大家参考!

有四年没写博客了,很多东西写着写着觉得没意思,就不写了,这篇写得不好希望各位博友口下留情!

ABP+WorkflowCore+jsplumb实现工作流的更多相关文章

  1. 工作流引擎之Elsa入门系列教程之一 初始化项目并创建第一个工作流

    引子 工作流(Workflow)是对工作流程及其各操作步骤之间业务规则的抽象.概括描述. 为了实现某个业务目标,需要多方参与.按预定规则提交数据时,就可以用到工作流. 通过流程引擎,我们按照流程图,编 ...

  2. ABP Framework V4.4 RC 新增功能介绍

    目录 新增功能概述 启动模板删除 EntityFrameworkCore.DbMigrations 项目 CMS-Kit 动态菜单管理 Razor引擎对文本模板的支持 DbContext/Entiti ...

  3. 一个适合于.NET Core的超轻量级工作流引擎:Workflow-Core

    一.关于Workflow-Core 近期工作上有一个工作流的开发需求,自己基于面向对象和职责链模式捣鼓了一套小框架,后来在github上发现一个轻量级的工作流引擎轮子:Workflow-Core,看完 ...

  4. 企业级工作流解决方案(十五)--集成Abp和ng-alain--Abp其他改造

    配置功能增强 Abp定义了各种配置接口,但是没有定义这些配置数据从哪里来,但是管理配置数据对于一个应用程序来说,是必不可少的一件事情. .net的配置数据管理,一般放在Web.config文件或者Ap ...

  5. 企业级工作流解决方案(十)--集成Abp和ng-alain--权限系统

    权限系统 应用系统离不开权限控制,权限中心不一定能抽象出所有的业务场景,这里定义的权限系统不一定能够满足所有的场景,但应该可以满足多数的业务需求. Abp的zero项目也定义了权限相关的表,但里面很多 ...

  6. 开源轻量级工作流WorkflowCore介绍

    在.Net Framework环境下,我们使用Windows Workflow Foundation(WF)作为项目的工作流引擎,可是.Net Core已经不支持WF了,需要为基于.Net Core的 ...

  7. 企业级工作流解决方案(十三)--集成Abp和ng-alain--数据库读写分离

    说到程序里面数据库管理,无非就是两件事情,一是数据库操作,对于数据库的操作,各种程序语言都有封装,也就是所谓的ORM框架,.net 方向一般用得比较多和就是.net framework和dapper, ...

  8. 企业级工作流解决方案(十四)--集成Abp和ng-alain--自动化脚本

    对于.net方向,做过自动化的,应该没有人不熟悉msbuild吧,非常强大的代码编译工具,.net平台的编译工作都是交给他来完成的,包括.net core的命令,本质上都是调用msbuild来执行的 ...

  9. 企业级工作流解决方案(十二)--集成Abp和ng-alain--用户身份认证与权限验证

    多租户 如果系统需要支持多租户,那么最好事先定义好多租户的存储部署方式,Abp提供了几种方式,根据需要选择,每一个用户身份认证与权限验证都需要完全的隔离 这里设计的权限数据全部存储在缓存中,每个租户单 ...

随机推荐

  1. MySQL进阶之MySQL索引以及索引优化

    本文配合B站学习视频BV1es411u7we使用效果更佳. 1. MySQL版本 主流版本:5.x版 5.0 - 5.1:早期产品的延续,升级维护 5.4 - 5.x:MySQL整合了三方公司的新存储 ...

  2. Tensorflow2(一)深度学习基础和tf.keras

    代码和其他资料在 github 一.tf.keras概述 首先利用tf.keras实现一个简单的线性回归,如 \(f(x) = ax + b\),其中 \(x\) 代表学历,\(f(x)\) 代表收入 ...

  3. Python趣味入门5:循环语句while

    跟着小牛叔,找准正确编程入门姿势,每天只要阅读10分钟. 任何语言都有循环语句,在Python里循环更是变化无穷,有基本的循环,有循环else语句,引伸出来的还有迭代器.推导式,咱们先学习最简单的一种 ...

  4. IDEA下Maven项目搭建踩坑记----2.项目编译之后 在service层运行时找不到 com.dao.CarDao

    项目写的差不多 想运行一下,然后发现运行到Service层的时候报错说找不到Dao层文件 ,纠结半天之后看了下编译好的项目文件,发现mapper文件下边是空的, 于是就百度找一下原因,结果说是IDEA ...

  5. el-table表头v-for循环遇到的问题

    这两天在项目中遇到了el-table表头需要动态变化,也就是点击不同的标签显示对应的表格,主要表头都不一样,那么表格也就是动态的,表头也需要循环 一开始以为很简单 <el-table       ...

  6. 服务发现Eureka、zookeeper、consul

    Spring Cloud为开发人员提供了工具,以快速构建分布式系统中的某些常见模式(例如,配置管理,服务发现,断路器,智能路由,微代理,控制总线,一次性令牌,全局锁,领导选举,分布式会话,群集状态). ...

  7. Centos7安装Oracle12c教程

    12c数据库 创建oracle的系统用户和用户组 [root@localhost /]# groupadd oinstall [root@localhost /]# groupadd dba [roo ...

  8. Android Studio出现:Your project path contains non-ASCII 错误代码解决方法

    导入Project的出现: Error:(1, 0) Your project path contains non-ASCII characters. This will most likely ca ...

  9. Fitness - 05.04

    倒计时241天 运动38分钟,共计9组.拉伸10分钟. 每组跑步2分钟(6.3KM/h),走路2分钟(6KM/h). 上午下了课,直奔健身房. 手机坏了,没有听音乐. 没有吃午饭,但是上午喝的咖啡还是 ...

  10. 【HttpRunner v3.x】笔记—8.运行testcase的几种方式

    在之前的demo过程中,已经运行过testcase了,那这篇就也来汇总一下,运行case相关的知识点. 一.运行testcase的几种场景 1. 运行单个case 通常单个case的话我会在编辑器里用 ...