<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ABP+WorkflowCore+jsplumb實(shí)現(xiàn)工作流

          共 24226字,需瀏覽 49分鐘

           ·

          2020-09-30 18:16

          前言

          ABP目前已經(jīng)是很成熟的開(kāi)發(fā)框架了,它提供了很多我們?nèi)粘i_(kāi)發(fā)所必須的功能,并且很方便擴(kuò)展,讓我們能更專注于業(yè)務(wù)的開(kāi)發(fā)。但是ABP官方并沒(méi)有給我們實(shí)現(xiàn)工作流。

          在.net core環(huán)境下的開(kāi)源工作流引擎很少,其中WorkflowCore是一款輕量級(jí)工作流引擎,對(duì)于小型工作流和責(zé)任鏈類型的需求開(kāi)發(fā)很適合,但只能通過(guò)后臺(tái)編碼或者json的方式定義工作流程,看了源碼后覺(jué)得擴(kuò)展性還是挺好的,至少能滿足我的需求,于是選擇對(duì)它下手。

          jsPlumb是一個(gè)開(kāi)源的比較強(qiáng)大的繪圖組件,這里不多介紹,我就是用它實(shí)現(xiàn)一個(gè)簡(jiǎn)單的流程設(shè)計(jì)器。

          花了差不多一個(gè)月的時(shí)間,把這三者結(jié)合到一起實(shí)現(xiàn)一個(gè)簡(jiǎn)單而強(qiáng)大的工作流模塊。

          目錄

          1. ?ABP模塊實(shí)現(xiàn)WorkflowCore持久化存儲(chǔ)接口(IPersistenceProvider)

          2. ABP中AbpWorkflow和AbpStepBody的自定義注冊(cè)

          3. 設(shè)計(jì)器實(shí)現(xiàn)

          4. 設(shè)計(jì)器提交的流程數(shù)據(jù)轉(zhuǎn)換成WorkflowCore支持的Json數(shù)據(jù)結(jié)構(gòu)

          5. 總結(jié)

          ?注:公眾號(hào)閱讀效果不佳,可以點(diǎn)擊閱讀全文,網(wǎng)頁(yè)瀏覽

          ?1.ABP模塊實(shí)現(xiàn)WorkflowCore持久化存儲(chǔ)接口(IPersistenceProvider)

          這里我參考了WorkflowCore.Persistence.EntityFramework 持久化項(xiàng)目的實(shí)現(xiàn)方式 用ABP的方式實(shí)現(xiàn)了WorkflowCore的持久化。這樣做有兩個(gè)好處:

          1.讓工作流能支持ABP的多租戶和全局?jǐn)?shù)據(jù)過(guò)濾功能

          2.數(shù)據(jù)庫(kù)操作能使用統(tǒng)一的數(shù)據(jù)上下文,方便事務(wù)提交和回滾。

          • ABP實(shí)現(xiàn)的流程Workflow持久化存儲(chǔ)所必須的實(shí)體類,其中PersistedWorkflowDefinition是用來(lái)持久化存儲(chǔ)流程定義(在Workflow中流程定義在內(nèi)存中)如下圖:

          • 實(shí)現(xiàn)IPersistenceProvider接口

            1 public interface IAbpPersistenceProvider : IPersistenceProvider
          2 {
          3 Task GetPersistedWorkflow(Guid id);
          4
          5 Task GetPersistedExecutionPointer(string id);
          6 Task GetPersistedWorkflowDefinition(string id, int version);
          7 }
          8
          9
          10 public class AbpPersistenceProvider : DomainService, IAbpPersistenceProvider
          11 {
          12 protected readonly IRepository _eventRepository;
          13 protected readonly IRepositorystring> _executionPointerRepository;
          14 protected readonly IRepository _workflowRepository;
          15 protected readonly IRepositorystring > _workflowDefinitionRepository;
          16 protected readonly IRepository _eventSubscriptionRepository;
          17 protected readonly IRepository _executionErrorRepository;
          18 protected readonly IGuidGenerator _guidGenerator;
          19 protected readonly IAsyncQueryableExecuter _asyncQueryableExecuter;
          20 public IAbpSession AbpSession { get; set; }
          21
          22
          23 public AbpPersistenceProvider(IRepository eventRepository, IRepositorystring> executionPointerRepository, IRepository workflowRepository, IRepository eventSubscriptionRepository, IGuidGenerator guidGenerator, IAsyncQueryableExecuter asyncQueryableExecuter, IRepository executionErrorRepository, IRepositorystring > 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 Taskstring>> 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> GetWorkflowInstances(WorkflowStatus? status, string type, DateTime? createdFrom, DateTime? createdTo, int skip, int take)
          72 {
          73
          74 IQueryable 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 result = new List();
          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 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> GetWorkflowInstances(IEnumerable<string> ids)
          120 {
          121 if (ids == null)
          122 {
          123 return new List();
          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> 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 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 Taskstring>> 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 Taskstring>> 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 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 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 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 GetPersistedWorkflow(Guid id)
          338 {
          339 return _workflowRepository.GetAsync(id);
          340 }
          341
          342 public Task GetPersistedWorkflowDefinition(string id, int version)
          343 {
          344 return _workflowDefinitionRepository.GetAll().AsNoTracking().FirstOrDefaultAsync(u => u.Id == id && u.Version == version);
          345 }
          346
          347 public Task GetPersistedExecutionPointer(string id)
          348 {
          349 return _executionPointerRepository.GetAsync(id);
          350 }
          351 }

          • ?服務(wù)注冊(cè)添加AddWorkflow時(shí)把IPersistenceProvider提供的默認(rèn)實(shí)現(xiàn)換成AbpPersistenceProvider

          1

          2

          3

          4

          5

          6

          7

          8

          9

          10

          11

          12

          13

          14

          15

          public?static?class?ServiceCollectionExtensions

          ???{

          ???????public?static?IServiceCollection AddAbpWorkflow(this?IServiceCollection services, Action setupAction =?null)

          ???????{

          ???????????services.AddSingleton();

          ???????????services.AddWorkflow(options =>

          ???????????{

          ?

          ???????????????options.UsePersistence(sp => sp.GetService());

          ???????????????setupAction?.Invoke(options);

          ???????????});

          ???????????services.AddWorkflowDSL();

          ???????????return?services;

          ???????}

          ???}

            到此為止,ABP已經(jīng)實(shí)現(xiàn)了WorkflowCore的默認(rèn)的持久化存儲(chǔ)。

          2.ABP中AbpWorkflow和AbpStepBody的自定義注冊(cè)

          為了滿足開(kāi)發(fā)人員和用戶的需求,我提供了兩種流程注冊(cè)方式,一種是開(kāi)發(fā)人員后臺(tái)編碼定義固定流程另一種是用戶通過(guò)流程設(shè)計(jì)器實(shí)現(xiàn)自定義業(yè)務(wù)流程。

          • 開(kāi)發(fā)人員后臺(tái)編碼定義固定流程

          這里參考ABP的EventBus注冊(cè)方式,實(shí)現(xiàn)IWindsorInstaller ,在組件注冊(cè)時(shí)攔截并注冊(cè):

          1

          2

          3

          4

          5

          6

          7

          8

          9

          10

          11

          12

          13

          14

          15

          16

          17

          18

          19

          20

          21

          22

          23

          24

          25

          26

          27

          28

          29

          30

          31

          32

          33

          34

          35

          36

          37

          38

          39

          40

          41

          42

          43

          44

          45

          46

          47

          48

          49

          50

          51

          52

          53

          54

          55

          56

          57

          58

          59

          60

          61

          62

          63

          64

          65

          66

          67

          68

          69

          70

          71

          72

          73

          74

          75

          76

          77

          //ABP工作流接口

          public?interface?IAbpWorkflow : IWorkflow

          ????{

          ????}

          ?

          ?

          //工作流注冊(cè)接口

          ?public?interface?IAbpWorkflowRegisty

          ????{

          ????????void?RegisterWorkflow(Type type);

          ????}

          ?

          ?

          //Abp工作流注冊(cè)實(shí)現(xiàn)

          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);

          ????????}

          ?

          ?

          ????}

          ?

          ?

          ?

          //攔截器實(shí)現(xiàn)

          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();

          ????????????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);

          ????????????}

          ????????}

          ????}

            到這里,把攔截器注冊(cè)到模塊類的Initialize中,開(kāi)發(fā)人員定義流程只需要實(shí)現(xiàn)IAbpWorkflow接口,系統(tǒng)啟動(dòng)時(shí)會(huì)自動(dòng)注冊(cè)。如圖:

          ?

          ?

          • ?自定義注冊(cè)StepBody

          ?這里參考ABP中標(biāo)準(zhǔn)的配置模式(不清楚的可以去看下ABP的源碼,ABP的配置系統(tǒng)和權(quán)限系統(tǒng)都是這樣配置的),將注冊(cè)的StepBody存儲(chǔ)在內(nèi)存中提供給用戶自定義組合流程節(jié)點(diǎn)使用,下列代碼展示了注冊(cè)指定用戶審核的StepBody,執(zhí)行方法體的實(shí)現(xiàn):

           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 ///
          23 /// 指定用戶審批StepBody
          24 ///

          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 _auditorRepository;
          33
          34 public GeneralAuditingStepBody(INotificationPublisher notificationPublisher, UserManager userManager, IAbpPersistenceProvider abpPersistenceProvider,
          35 IRepository auditorRepository)
          36 {
          37 _notificationPublisher = notificationPublisher;
          38 _abpPersistenceProvider = abpPersistenceProvider;
          39 _userManager = userManager;
          40 _auditorRepository = auditorRepository;
          41 }
          42
          43 ///
          44 /// 審核人
          45 ///

          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.設(shè)計(jì)器實(shí)現(xiàn)

          流程設(shè)計(jì)器我用的是Abp提供的Vue項(xiàng)目模板+jsplumb來(lái)實(shí)現(xiàn)的,話不多說(shuō)直接上圖把:


          上圖所示,每個(gè)節(jié)點(diǎn)執(zhí)行操作選擇的是我們后臺(tái)注冊(cè)的AbpStepBody。

          注:開(kāi)發(fā)人員可根據(jù)業(yè)務(wù)需求盡可能的給用戶提供所需的StepBody。這樣一來(lái),整個(gè)流程的靈活性是非常好的。

          ?4.設(shè)計(jì)器提交的流程數(shù)據(jù)轉(zhuǎn)換成WorkflowCore支持的Json數(shù)據(jù)結(jié)構(gòu)

          前端傳給后臺(tái)的數(shù)據(jù)結(jié)構(gòu)如下:

          ?

          ?后臺(tái)接收數(shù)據(jù)后轉(zhuǎn)換成Workflow 支持的Josn字符串,再使用WorkflowCore.DSL提供的幫助類注冊(cè)流程即可,轉(zhuǎn)換后的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": "請(qǐng)假"
          78 }

          ?

          ?總結(jié)

          一句話,上面所寫(xiě)的一切都是為了將流程注冊(cè)到WorkflowCore中而做的鋪墊。

          后面我會(huì)把代碼整理一份作為一個(gè)ABP的獨(dú)立模塊開(kāi)源出來(lái)供大家參考!

          有四年沒(méi)寫(xiě)博客了,很多東西寫(xiě)著寫(xiě)著覺(jué)得沒(méi)意思,就不寫(xiě)了,這篇寫(xiě)得不好希望各位博友口下留情!



          往期精彩回顧




          【推薦】.NET Core開(kāi)發(fā)實(shí)戰(zhàn)視頻課程?★★★

          .NET Core實(shí)戰(zhàn)項(xiàng)目之CMS 第一章 入門(mén)篇-開(kāi)篇及總體規(guī)劃

          【.NET Core微服務(wù)實(shí)戰(zhàn)-統(tǒng)一身份認(rèn)證】開(kāi)篇及目錄索引

          Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)

          .NET Core中的一個(gè)接口多種實(shí)現(xiàn)的依賴注入與動(dòng)態(tài)選擇看這篇就夠了

          10個(gè)小技巧助您寫(xiě)出高性能的ASP.NET Core代碼

          用abp vNext快速開(kāi)發(fā)Quartz.NET定時(shí)任務(wù)管理界面

          在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務(wù)輕松實(shí)現(xiàn)作業(yè)調(diào)度

          現(xiàn)身說(shuō)法:實(shí)際業(yè)務(wù)出發(fā)分析百億數(shù)據(jù)量下的多表查詢優(yōu)化

          關(guān)于C#異步編程你應(yīng)該了解的幾點(diǎn)建議

          C#異步編程看這篇就夠了


          瀏覽 78
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲九九九九 | 无码无遮挡 | 婷婷六月综合 | 91黑人大屌啪啪 | 国产天天综合 |