ABP+WorkflowCore+jsplumb實現工作流

Metto發表於2020-09-28

前言

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介面

ABP+WorkflowCore+jsplumb實現工作流
  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     }
View Code
  •  服務註冊新增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,執行方法體的實現:

ABP+WorkflowCore+jsplumb實現工作流
 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如下:

ABP+WorkflowCore+jsplumb實現工作流
 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的獨立模組開源出來供大家參考!

有四年沒寫部落格了,很多東西寫著寫著覺得沒意思,就不寫了,這篇寫得不好希望各位博友口下留情!

 

相關文章