# 工作流引擎对接 **Repository Path**: mengtree/workflow-engine-docking ## Basic Information - **Project Name**: 工作流引擎对接 - **Description**: 工作流引擎对接demo项目 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 1 - **Created**: 2023-07-30 - **Last Updated**: 2025-05-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 简介 这是一个与[工作流引擎](https://gitee.com/mengtree/workflow-engine)进行对接的demo项目。 项目基于 [abp boilerplate](https://aspnetboilerplate.com/Templates) 生成,包含基本的系统用户,角色等信息。通过在现有系统上新增一个业务表单,同时对接工作流引擎的几个插件功能(工作流引擎几乎无需编码通过插件配置即可对接),实现与工作流引擎的交互。 # 业务表单 为了展示业务系统上的表单如何接入工作流引擎,在该demo实现了一个简单的“请假”表单。表单的增删改查均在业务系统上实现,其结构如下: ``` public class LeaveForm : Entity, IWorkflowStatus { /// /// 天数 /// public int Days { get; set; } /// /// 原因 /// public string Reason { get; set; } /// /// 备注 /// public string Memo { get; set; } public DateTime UpdateTime { get; set; } public WorkTaskStatus WorkflowStatus { get; set; } public DateTime? WorkflowStatusUpdateTime { get; set; } } ``` 其中 IWorkflowStatus 用于统一给表单管理流程审批状态。 ``` public interface IWorkflowStatus { public WorkTaskStatus WorkflowStatus { get; set; } public DateTime? WorkflowStatusUpdateTime { get; set; } } ``` 对应表单在前端的展示如下: ![Alt text](image.png) ## 发起审批 在业务平台创建和编辑业务表单后,通过“发起审批” 调用流程引擎的创建流程和发起流程接口进行表单的审批。 ### 先决条件 - 流程引擎需先进行流程设计,确定相应的流程。在这里,对应的流程为 ![Alt text](image-1.png) 之后进行创建审批和发起审批接口的调用: - 创建审批。调用 [新建审批 /api/WorkFlow/CreateWorkTask](https://gitee.com/mengtree/workflow-engine#%E6%96%B0%E5%BB%BA%E5%AE%A1%E6%89%B9--apiworkflowcreateworktask) 接口创建审批。 - 发起审批。创建好审批实例后,再调用[发起接口](https://gitee.com/mengtree/workflow-engine#%E5%8F%91%E8%B5%B7%E5%AE%A1%E6%89%B9--apiworkflowstartworktask)发起审批。 对应demo这里的发起代码为: ``` click: async () => { this.$Modal.confirm({ title: this.L("Tips"), content: this.L("发起审批"), okText: this.L("Yes"), cancelText: this.L("No"), onOk: async () => { // 构造参数 let workTaskForm = { WorkflowId: { versionId: 1, id: "f55bf8c2-d800-4424-a795-1cc674de31b8", }, name: "对接系统请假流程" + new Date().getTime(), formData: JSON.stringify(params.row), entityFullName: "WorkflowCoreDockingDemo.CustomForm.LeaveForm", entityKeyValue: params.row.id + "", }; // 创建审批 let dispatchPath = "leaveForm/createWorkTask"; this.$store .dispatch(dispatchPath, { data: workTaskForm }) .then((res) => { return res.data.data; }) .then((task) => { const data = { workTaskId: task.id, }; //发起审批 this.$store .dispatch("leaveForm/startWorkTask", { data: data, }) .then((res) => { this.getpage(); }) .catch((e) => {}); }) .catch((e) => {}); await this.getpage(); }, }); } ``` 两个接口都是直接调用工作流引擎接口: ``` const workflowHost = process.env.NODE_ENV === 'production' ? 'http://39.101.74.14:8083/' : 'http://localhost:5000/'; async startWorkTask(context:ActionContextpayload:any){ return await thirdpartajax.post(workflowHost+'api/WorkFlow/StartWorkTask',payload.data); }, async createWorkTask(context:ActionContextpayload:any){ return await thirdpartajax.post(workflowHost+'api/WorkFlow/CreateWorkTask',payload.data); }, ``` ### 关于身份认证 在上面调用发起审批的接口上,有一个特殊的处理,就是将当前系统的访问token 值作为头部 **ThirdpartAuthorization** 一起请求: ``` ajax.interceptors.request.use(function (config) { if(!!window.abp.auth.getToken()){ config.headers.common["ThirdpartAuthorization"]="Bearer "+window.abp.auth.getToken(); } config.headers.common[".AspNetCore.Culture"]=window.abp.utils.getCookieValue("Abp.Localization.CultureName"); config.headers.common["Abp.TenantId"]=window.abp.multiTenancy.getTenantIdCookie(); return config; }, function (error) { return Promise.reject(error); }); ``` 这是为了配合工作流引擎那边做身份认证。工作流引擎平台实现了一个身份认证的插件,该插件会解析请求信息获取业务平台的token,然后调用业务平台的接口获取当前发起请求的用户信息。 - 工作流平台身份认证插件 ![Alt text](image-2.png) - 业务平台验证返回当前用户信息。Account/WorkflowLogin 接口本身要求登录才能访问,所以这里能进入到接口逻辑就说明token是认证通过了的。 ![Alt text](image-3.png) 相关逻辑示意图如下: ![Alt text](image-4.png) ***在业务平台这里,直接在前端发起调用,如果需要在后端发起也是一样的。*** ## 同步审批状态 业务表单发起审批后,我们还需要表单的流程状态与工作流引擎的流程状态保持一致。为了能够同步状态,有两种方案:1 在流程状态发生变化时,接受状态通知;2 定时查询审批状态。 在这里,我们两种方案都会用上,因为状态通知也有可能因为异常而通知失败,定时查询可以针对没有即时更新到状态的数据进行补差,多以原则上定时的频率可以相对低一些。 ### 订阅状态通知 订阅状态通知,可以通过对接工作流引擎的状态同步插件即可。 - 工作流状态同步插件 ![Alt text](image-5.png) 该插件 post 的 事件类型基本信息如下: ``` public class TaskStateChangeEventData : BaseEventData { public WorkTasks.WorkTask WorkTask { get; set; } //流程实例 public WorkTaskStatus OldWorkTaskStatus { get; set; } //之前的状态 public WorkTaskStatus NewWorkTaskStatus { get; set; } //更新后的状态 public List WorkSteps { get; set; } //当前的审批人 员和状态 } ``` - 业务平台接收状态同步接口 ![Alt text](image-6.png) 内部的做法和简单:通过 entityfullname 和 entitykeyvalue 确认表单,然后更新状态。 ``` public async Task HandleStatusChange(TaskStateChangeData taskStateChangeData) { foreach (var _WorkflowStatusUpdatable in _WorkflowStatusUpdatables) { await _WorkflowStatusUpdatable.UpdateWorkflowStatus(taskStateChangeData.WorkTask.EntityFullName, taskStateChangeData.WorkTask.EntityKeyValue, taskStateChangeData.NewWorkTaskStatus); } } ``` ``` public async Task UpdateWorkflowStatus(string entityFullName, string entityKeyValue, WorkTaskStatus workTaskStatus) { if (!typeof(LeaveForm).FullName.Equals(entityFullName)) return; var entity = leaveFormRepository.Get(long.Parse(entityKeyValue)); if (entity == null) return; await entity.UpdateWorkflowStatus(workTaskStatus); await leaveFormRepository.UpdateAsync(entity); } ``` ### 定时同步状态 在这个demo平台的框架里,有一个 “后台作业”的工作者,直接通过 httpclient 来同步流程引擎的状态。 - 业务平台的定时任务 ``` /// /// 根据实体类型获取所有的处理中的工作流 /// /// /// public async Task> GetAllProcessingWorkTasksByEntityType(string entityFullName, params string[] entityKeyValues) { try { var workflowHost = _configuration.GetValueFromManyChanels("WorkFlowHost"); //workflowHost = "http://localhost:5000/"; var url = $"{workflowHost.TrimEnd('/')}/api/workflow/GetAllProcessingWorkTasksByEntityType?EntityFullName={entityFullName}&{string.Join("&", entityKeyValues.Select(v => $"EntityKeyValues={v}"))}"; var httpClient = httpClientFactory.CreateClient(); var result = await httpClient.GetStringAsync(url); var jObj = JObject.Parse(result); return JsonConvert.DeserializeObject>(jObj["data"].ToString()); } catch (Exception ex) { logger.LogError(ex.ToString()); // return new List(); } } ``` - 工作流引擎对应的接口 ``` /// /// 根据实体类型获取所有的处理中的工作流 /// /// /// [HttpGet("GetAllProcessingWorkTasksByEntityType"),AllowAnonymous] public async Task> GetAllProcessingWorkTasksByEntityType([FromQuery] GetAllProcessingWorkTasksByEntityTypeInput input) { var worktaskInfos = await worktaskRepository.GetListAsync(wt => input.EntityKeyValues.Contains(wt.EntityKeyValue) && !wt.Deleted && wt.EntityFullName == input.EntityFullName); return worktaskInfos.Select(wt => wt.ToWorkTask()).ToList(); } ``` **关于这个流程状态同步的扩展,在这里,我们只处理了状态同步,但是基于这个同步插件,我们还可以做其它扩展,比如实现通知接口,则可以实现每次审批时,通知到办理人。如果这个插件不够用,还可以在工作流引擎实现[自定义插件](https://gitee.com/mengtree/workflow-engine#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B5%81%E7%A8%8B%E5%AE%A1%E6%89%B9%E7%8A%B6%E6%80%81%E5%90%8C%E6%AD%A5)来实现想要的效果** # 关于审批用户的获取 在上面简单说明了流程审批的对接,但是在审批过程中,还涉及到了用户信息的获取,对应的就是工作流引擎的 ***用户选择器*** 。在上面的流程设计中,有一个节点的用户选择如下: ![Alt text](image-7.png) ![Alt text](image-10.png) 通过该用户选择器,我们获取到了业务平台的“用户3”用户。这部分是如何对接呢? - 通过 ***用户选择器插件*** 配置用户选择器 ![Alt text](image-8.png) - 业务平台提供返回用户列表和根据用户id查询单个用户信息的接口 ![Alt text](image-9.png) 这样,在审批过程中,节点根据用户选择器解析获取到对接的用户信息,即可派发审批任务。 类似的,除了这个获取用户列表的插件适配,还有按角色获取用户的插件适配: ![Alt text](image-11.png) ![Alt text](image-12.png) 对应的插件配置和业务接口如下: ![Alt text](image-13.png) ![Alt text](image-14.png) # 关于自动登录 在业务系统demo上,我们看到两个特殊的跳转连接: ![Alt text](image-15.png) 其作用的通过连接实现自动登录工作流引擎平台,其实现也是基于前面提到的 ***身份认证插件*** 实现。通过带上业务平台的token打开工作流引擎,工作流引擎会自动将token添加到请求头部,在身份认证插件认证成功后,会自动生成工作流引擎平台的token。 - 业务平台调用 ``` gotoRemoteWorkflow() { var url = "http://39.101.74.14:8083/index.html#/login?access_token=" + window.abp.auth.getToken(); window.open(url, "_bank"); } ``` - 工作流引擎平台处理 ``` autoLogin() { this.loading = true; this.$store .dispatch("user/autoLogin", {access_token:this.$route.query.access_token}) .then(() => { // }) .catch(() => { this.loading = false; }); }, ``` 后端接口如果通过身份认证,workflowSession.User 才会有有效数据。 ``` [HttpPost("AutoLogin")] public LoginOutput AutoLogin() { if (workflowSession?.User == null|| string.IsNullOrWhiteSpace(workflowSession?.User.Id)) { throw new Exception("验证失败"); } var user = new Common.Authorization.JWT.AuthorizationUser { Id = workflowSession?.User?.Id, Name = workflowSession?.User?.Name, IsManager = workflowSession?.User?.IsManager ?? false }; return new LoginOutput { token = authenticationManager.Login(user, null), userInfo = user, }; } ``` 这样就跟手动登录一样返回用户登录信息。