1. 概述
流程任务是流程平台让用户参与审批的环节,基于bpmn的配置中,其节点的类型为人工任务,该任务会产生待办事项推送到用户的审批事项中心。平台通过关联任务的单据,实现流程与业务的关联处理。
平台通过任务解决如下内容:
- 任务关联单据与字段及数据权限
- 任务审批人
- 任务办理,包括同意,反对,加签,抄送,追回,转办,沟通,任务回退(上一步,驳回发起人)
- 任务事件,包括(创建事件、结束事件)
- 任务办理的外围触发动作切入口
2. 表设计
平台基于activiti中增加一些表设计,包括:
2.1. bpm_task 任务表
| 是否主键 | 字段名 | 字段描述 | 数据类型 | 长度 | 可空 | 备注 |
|---|---|---|---|---|---|---|
| 是 | TASK_ID_ | 任务ID | VARCHAR(64) | 64 | 任务ID | |
| ACT_TASK_ID_ | 流程任务ID | VARCHAR(64) | 64 | 是 | 流程任务ID | |
| TREE_ID_ | 分类ID | VARCHAR(64) | 64 | 是 | 分类ID | |
| NAME_ | 任务名称 | VARCHAR(100) | 100 | 是 | 任务名称 | |
| KEY_ | 任务Key | VARCHAR(64) | 64 | 是 | 任务Key | |
| BILL_TYPE_ | 流程类型 | VARCHAR(64) | 64 | 是 | 流程类型 | |
| BILL_NO_ | 流程单号 | VARCHAR(64) | 64 | 是 | 流程单号 | |
| BUS_KEY_ | 业务主键 | VARCHAR(64) | 64 | 是 | 业务主键 | |
| DESCP_ | 任务描述 | VARCHAR(255) | 255 | 是 | 任务描述 | |
| SUBJECT_ | 事项标题 | VARCHAR(512) | 512 | 是 | 事项标题 | |
| OWNER_ | 任务所属人 | VARCHAR(64) | 64 | 是 | 任务所属人 | |
| ASSIGNEE_ | 任务执行人 | VARCHAR(64) | 64 | 是 | 任务执行人 | |
| ACT_INST_ID_ | 流程实例ID | VARCHAR(64) | 64 | 是 | 流程实例ID | |
| ACT_DEF_ID_ | ACT流程定义ID | VARCHAR(64) | 64 | 是 | ACT流程定义ID | |
| DEF_ID_ | 流程定义ID | VARCHAR(64) | 64 | 是 | 流程定义ID | |
| INST_ID_ | 流程扩展实例ID | VARCHAR(64) | 64 | 是 | 流程扩展实例ID | |
| STATUS_ | 任务状态 | VARCHAR(64) | 64 | 是 | 任务状态 | |
| PRIORITY_ | 任务优先级 | VARCHAR(64) | 64 | 是 | 任务优先级 | |
| EXPIRED_TIME_ | 任务过期时间 | DATETIME | 是 | 任务过期时间 | ||
| FORWARD_TIMES_ | 转发次数 | INT | 是 | 转发次数 | ||
| TASK_TYPE_ | 任务类型 | VARCHAR(64) | 64 | 是 | 任务类型,FLOW_TASK=流程任务,MAN_TASK=人工任务(创建) | |
| PARENT_ID_ | 父任务ID | VARCHAR(64) | 64 | 是 | 父任务ID | |
| FROM_TASK_KEY_ | 上一任务KEY | VARCHAR(255) | 255 | 是 | 上一任务KEY | |
| FROM_TASK_NAME_ | 上一任务名称 | VARCHAR(255) | 255 | 是 | 上一任务名称 | |
| TENANT_ID_ | 租用ID | VARCHAR(64) | 64 | 是 | 租用用户Id | |
| CREATE_DEP_ID_ | 创建部门ID | VARCHAR(64) | 64 | 是 | 创建部门ID | |
| CREATE_BY_ | 创建人ID | VARCHAR(64) | 64 | 是 | 创建人ID | |
| CREATE_TIME_ | 创建时间 | DATETIME | 是 | 创建时间 | ||
| UPDATE_BY_ | 更新人ID | VARCHAR(64) | 64 | 是 | 更新人ID | |
| UPDATE_TIME_ | 更新时间 | DATETIME | 是 | 更新时间 |
【说明】
activiti原生有一actru_task表,但实际流程业务中存在一些非activiti的流程任务,因此平台扩展一表 bpm_task表,其中映射到act_ru_task表中,通过act_task_id关联。
非Activiti的任务,其不存在acttask_id,同时任务类型为MAN_TASK.
2.2. bpm_task_user 任务用户表
bpm_task_user表为流程任务的用户关系表,对于任务的候选用户或组则存于此表
| 是否主键 | 字段名 | 字段描述 | 数据类型 | 长度 | 可空 | 备注 |
|---|---|---|---|---|---|---|
| 是 | ID_ | 主键 | VARCHAR(64) | 64 | 主键 | |
| TASK_ID_ | 任务ID | VARCHAR(64) | 64 | 任务ID | ||
| USER_ID_ | 用户ID | VARCHAR(64) | 64 | 是 | 用户ID | |
| GROUP_ID_ | 用户组ID | VARCHAR(64) | 64 | 是 | 用户组ID | |
| USER_TYPE_ | 用户类型 | VARCHAR(64) | 64 | 是 | 用户类型,USER=用户,GROUP=用户组 | |
| PART_TYPE_ | 参与类型 | VARCHAR(64) | 64 | 是 | 参与类型,执行人=ASSIGNEE,抄送人=COPY,候选人=CANDIDATE | |
| IS_READ_ | 是否已读 | VARCHAR(20) | 20 | 是 | 是否已读, YES,NO | |
| TENANT_ID_ | 租用ID | VARCHAR(64) | 64 | 是 | 租用用户Id | |
| CREATE_DEP_ID_ | 创建部门ID | VARCHAR(64) | 64 | 是 | 创建部门ID | |
| CREATE_BY_ | 创建人ID | VARCHAR(64) | 64 | 是 | 创建人ID | |
| CREATE_TIME_ | 创建时间 | DATETIME | 是 | 创建时间 | ||
| UPDATE_BY_ | 更新人ID | VARCHAR(64) | 64 | 是 | 更新人ID | |
| UPDATE_TIME_ | 更新时间 | DATETIME | 是 | 更新时间 |
2.3. bpm_task_transfer 流程任务转移记录
| 是否主键 | 字段名 | 字段描述 | 数据类型 | 长度 | 可空 | 备注 |
|---|---|---|---|---|---|---|
| 是 | ID_ | 主键 | VARCHAR(64) | 64 | 主键 | |
| TREE_ID_ | 分类ID | VARCHAR(64) | 64 | 是 | 分类ID | |
| INST_ID_ | 流程实例ID | VARCHAR(64) | 64 | 是 | 流程实例ID | |
| OWNER_ID_ | 所有人ID | VARCHAR(64) | 64 | 是 | 所有人ID | |
| SUBJECT_ | 任务标题 | VARCHAR(128) | 128 | 是 | 任务标题 | |
| TASK_ID_ | 任务ID | VARCHAR(64) | 64 | 是 | 任务ID | |
| TO_USER_ID_ | 转办人ID | VARCHAR(64) | 64 | 是 | 转办人ID | |
| TYPE_ | 类型 | VARCHAR(20) | 20 | 是 | 类型(trans,agent) | |
| TENANT_ID_ | 租用ID | VARCHAR(64) | 64 | 是 | 租用用户Id | |
| CREATE_DEP_ID_ | 创建部门ID | VARCHAR(64) | 64 | 是 | 创建部门ID | |
| CREATE_BY_ | 创建人ID | VARCHAR(64) | 64 | 是 | 创建人ID | |
| CREATE_TIME_ | 创建时间 | DATETIME | 是 | 创建时间 | ||
| UPDATE_BY_ | 更新人ID | VARCHAR(64) | 64 | 是 | 更新人ID | |
| UPDATE_TIME_ | 更新时间 | DATETIME | 是 | 更新时间 |
2.4. bpm_sign_data 流程任务会签数据
| 是否主键 | 字段名 | 字段描述 | 数据类型 | 长度 | 可空 | 备注 |
|---|---|---|---|---|---|---|
| 是 | DATA_ID_ | 主键 | VARCHAR(64) | 64 | 主键 | |
| ACT_DEF_ID_ | 流程定义ID | VARCHAR(64) | 64 | 是 | 流程定义ID | |
| ACT_INST_ID_ | 流程实例ID | VARCHAR(64) | 64 | 是 | 流程实例ID | |
| NODE_ID_ | 节点ID | VARCHAR(64) | 64 | 是 | 节点ID | |
| USER_ID_ | 用户ID | VARCHAR(64) | 64 | 是 | 用户ID | |
| VOTE_STATUS_ | 投票意见 | VARCHAR(40) | 40 | 是 | 投票意见 | |
| TENANT_ID_ | 租用ID | VARCHAR(64) | 64 | 是 | 租用用户Id | |
| CREATE_DEP_ID_ | 创建部门ID | VARCHAR(64) | 64 | 是 | 创建部门ID | |
| CREATE_BY_ | 创建人ID | VARCHAR(64) | 64 | 是 | 创建人ID | |
| CREATE_TIME_ | 创建时间 | DATETIME | 是 | 创建时间 | ||
| UPDATE_BY_ | 更新人ID | VARCHAR(64) | 64 | 是 | 更新人ID | |
| UPDATE_TIME_ | 更新时间 | DATETIME | 是 | 更新时间 |
2.5. bpm_ru_path 流程节点运行路径
bpm_ru_path记录了流程实例在运行的过程中的每个节点执行过程,通过该记录可以获取任务的回退路径等信息。
| 是否主键 | 字段名 | 字段描述 | 数据类型 | 长度 | 可空 | 备注 |
|---|---|---|---|---|---|---|
| 是 | PATHID | 主键 | VARCHAR(64) | 64 | 主键 | |
| INST_ID_ | 流程实例ID | VARCHAR(64) | 64 | 流程实例ID | ||
| DEF_ID_ | 流程定义Id | VARCHAR(64) | 64 | 是 | ||
| ACT_DEF_ID_ | Act定义ID | VARCHAR(64) | 64 | Act定义ID | ||
| ACT_INST_ID_ | Act实例ID | VARCHAR(64) | 64 | Act实例ID | ||
| NODE_ID_ | 节点ID | VARCHAR(255) | 255 | 节点ID | ||
| NODE_NAME_ | 节点名称 | VARCHAR(255) | 255 | 是 | 节点名称 | |
| NODE_TYPE_ | 节点类型 | VARCHAR(50) | 50 | 是 | 节点类型 | |
| START_TIME_ | 开始时间 | DATETIME | 开始时间 | |||
| END_TIME_ | 结束时间 | DATETIME | 是 | 结束时间 | ||
| ASSIGNEE_ | 处理人ID | VARCHAR(64) | 64 | 是 | 处理人ID | |
| TO_USER_ID_ | 代理人ID | VARCHAR(64) | 64 | 是 | 代理人ID | |
| USER_IDS_ | 原执行人IDS | VARCHAR(300) | 300 | 是 | 原执行人IDS | |
| MULTIPLE_TYPE_ | 是否为多实例 | VARCHAR(20) | 20 | 是 | 是否为多实例 | |
| EXECUTION_ID_ | 活动执行ID | VARCHAR(64) | 64 | 是 | 活动执行ID | |
| PARENT_ID_ | 父ID | VARCHAR(64) | 64 | 是 | 父ID | |
| LEVEL_ | 层次 | INT | 是 | 层次 | ||
| OUT_TRAN_ID_ | 跳出路线ID | VARCHAR(255) | 255 | 是 | 跳出路线ID | |
| TOKEN_ | 路线令牌 | VARCHAR(255) | 255 | 是 | 路线令牌 | |
| JUMP_TYPE_ | 跳转方式 | VARCHAR(50) | 50 | 是 | 跳到该节点的方式,正常跳转,自由跳转,回退跳转 | |
| NEXT_JUMP_TYPE_ | 下一步跳转方式 | VARCHAR(50) | 50 | 是 | 下一步跳转方式 | |
| REF_PATH_ID_ | 引用路径ID | VARCHAR(64) | 64 | 是 | 引用路径ID,当回退时,重新生成的结点,需要记录引用的回退节点,方便新生成的路径再次回退。 | |
| TENANT_ID_ | 租用ID | VARCHAR(64) | 64 | 是 | 租用用户Id | |
| CREATE_DEP_ID_ | 创建部门ID | VARCHAR(64) | 64 | 是 | 创建部门ID | |
| CREATE_BY_ | 创建人ID | VARCHAR(64) | 64 | 是 | 创建人ID | |
| CREATE_TIME_ | 创建时间 | DATETIME | 是 | 创建时间 | ||
| UPDATE_BY_ | 更新人ID | VARCHAR(64) | 64 | 是 | 更新人ID | |
| UPDATE_TIME_ | 更新时间 | DATETIME | 是 | 更新时间 |
3. 类设计

类说明:
- BpmTaskController 流程任务对外请求服务控制类
- BpmTask 流程任务扩展业务实体
- BpmTaskService 流程任务逻辑服务类
- BpmTaskMapper 流程任务数据库映射类
- BpmInstServiceImpl 流程实例业务逻辑服务类
- BpmTaskUserServiceImpl 流程任务人员业务逻辑服务类
- TaskService Activiti原生的任务服务类
- BpmDefService 流程定义业务逻辑服务类
- BpmCheckHistoryServiceImpl 审批历史业务逻辑服务类
- BpmRuPathServiceImpl 流程执行路径业务逻辑服务类
- ActRepService Activiti库操作服务类
- BpmInstLogServiceImpl 流程实例操作日志服务类
- BpmSignDataServiceImpl 流程会签数据业务逻辑服务类
- FormDataService 单据数据服务类
- BpmTaskSkipService 流程任务跳过服务类
- GroovyEngine 脚本引擎类
- ProcessHandlerExecutor 流程前置与后置处理器执行类
- BpmTaskTransferServiceImpl 流程任务转移业务逻辑处理类
- BpmMultiTaskService 流程多实体任务业务逻辑服务类
- IOrgService 组织架构服务类
- BpmTaskUserMapper 流程任务映射服务类
- RuntimeService Activiti运行服务类
- TaskExecutorService 流程任务执行人的业务逻辑服务类
4. 任务关联单据
任务关联的单据的数据来源来自流程定义中的单据配置,可参考7.3.1.的流程定义扩展一节点的单据扩展部分,当用户进入任务审批窗口时,只需要查询到当前节点的配置,可通过以下接口获取:
UserTaskConfig config = (UserTaskConfig) bpmDefService.getNodeConfig(bpmTask.getActDefId(), bpmTask.getKey());
UserTaskConfig中可获取FormConfig,以支持获取不同的流程任务节点参数。
5.前端单据加载
<rx-forms ref="rxForms" />
BpmtaskApi.getAllDetail(this.taskId).then(res=>{this.bpmTaskDetail=res;Object.assign(self,res);var contextData={type:"usetask",curUserId:res.curUser.userId,curUserName:res.curUser.fullName,nodeId:self.bpmTask.key,nodeName:self.bpmTask.name,instId:self.instId,defId:self.bpmInst.defId,instNo:self.bpmInst.billNo,taskId:self.taskId,opinionHistorys:res.bpmCheckHistories};self.$refs.rxForms.setData(res.formData.data,false,contextData);});
6. 任务用例处理
6.1. 办理人、所属人、候选人
任务审批人是指任务办理人,流程任务在创建过程中,会根据以下几种情况找到对应的任务审批人,并且把任务分配给任务人进行办理:
- 流程节点配置的人员
- 上一步指定下一步的执行人员
- 回退时找回之前审批人
流程所属人任务在创建过程中,默认只分给一个人时,这个用户即为任务的所属人,同时也是这个任务的执行人,若一个任务同时分配给多个人时,这些用户都能看到该任务,并且可以抢占这些任务进行办理,我们称这批用户为候选用户。
若一个任务A 所属人是张三、执行人也是张三,但通过转办,可把任务转给李四,但任务所属人还是张三。李四在待办事项中看到该任务。张三可查看到转出去的任务的办理情况。
6.2. 我的待办事项查询
查询我的待办事项时,需要查询关联bpm_task与bpm_task_user表,为了查询的效率,可以先把用户所属的用户组查询出来,然后传入至以下关联查询SQL中即可。
/*** 按条件查询所有的个人待办* @return* @throws Exception*/@ApiOperation(value="按条件查询所有的个人待办", notes="按条件查询所有的个人待办")@PostMapping(value="/myTasks")public JsonPageResult myTasks(@RequestBody QueryData queryData) throws Exception{JsonPageResult result=JsonPageResult.getSuccess("");QueryFilter filter= QueryFilterBuilder.createQueryFilter(queryData);handleFilter(filter);IUser curUser=ContextUtil.getCurrentUser();String userId=curUser.getUserId();List<String> groupIds=curUser.getRoles();IPage<BpmTask> page= bpmTaskService.getByUserId(userId,groupIds,filter);result.setPageData(page);List<BpmTask> list=page.getRecords();//在任务中显示任务执行人与候选用户for(BpmTask task:list){Set<TaskExecutor> executors=bpmTaskUserService.getTaskExecutors(task);task.setTaskExecutors(executors);}return result;}
<select id="getByUserId" resultType="com.redxun.bpm.core.entity.BpmTask" parameterType="java.util.Map">select v.* from (select t.* from BPM_TASK t left join bpm_def d on t.def_id_= d.def_id_ where ASSIGNEE_=#{userId} and t.STATUS_!='COMPLETED' and t.STATUS_!='LOCKED'UNIONselect t.* from bpm_task t left join bpm_def d on t.def_id_= d.def_id_ left join bpm_task_user u ont.TASK_ID_=u.TASK_ID_where t.ASSIGNEE_ is null AND t.STATUS_!='COMPLETED' and t.STATUS_!='LOCKED' and (u.USER_ID_=#{userId}<if test="@rx.Ognl@isNotEmpty(groupIds)">or u.GROUP_ID_ in<foreach collection="groupIds" item="id" separator="," open="(" close=")">#{id}</foreach></if>)) v where 1=1<if test="@rx.Ognl@isNotEmpty(w.whereSql)">and ${w.whereSql}</if><if test="@rx.Ognl@isNotEmpty(w.orderBySql)">ORDER BY ${w.orderBySql}</if></select>
6.3. 任务审批人配置与计算
不同流程不同节点的任务审批其审批节点不一样,平台提供不同的人员查找策略,用来支撑不同的人员查找,并且分配给任务,生成任务所属人、执行人与候选人。
6.3.1. 流程任务级的节点配置
"userConfigs": [{"uid": "QXXDHCL4IQC1R1WKY6ZZ39WHIP6UIQ8B","condition": "","name": "人员配置","configList": [{"display": "发起人","calcType": "yes","logic": "or","type": "starter","config": "发起人","currentComponet": "starterEdit"},{"display": "郭靖,黄蓉","calcType": "yes","logic": "or","type": "user","config": "1273526648723415041,1273526443793915905","currentComponet": "userEdit"},{"display": "财务部,研发部","calcType": "yes","logic": "or","type": "group","config": "1273526190164353025,1273526098128740353","currentComponet": "groupEdit"}]}],
6.3.2. 流程任务人员计算实现接口

平台只需要实现接口ITaskExecutorCalc,该接口只有两方法,如下:
import com.redxun.bpm.activiti.config.UserConfig;import com.redxun.dto.bpm.TaskExecutor;import java.util.Collection;import java.util.Map;/*** 流程任务节点计算实现类* @author csx*/public interface ITaskExecutorCalc {ExecutorType getType();/*** 计算用的执行人接口* @param userConfig 当前节点的人员配置* @param vars 流程变量* @return*/Collection<TaskExecutor> getExecutors(UserConfig userConfig, Map<String,Object> vars);}
其中TaskExecutor为任务执行者,代表用户或组,一般可通过OsUserDto或OsGroupDto实现转化处理。
说明:
- FormJsonExecutorCalc 执行人来自来自表单数据
- GroupByUserGroupRelExexecutorCalc 用户组来自用户与组关系运算
- GroupExecutorCalc 执行人来自用户组
- GroupPropertiesExecutorCalc 用户组来自扩展属性计算
- GroupRankTypeRelEexecutorCalc 用户来自发起人所在部门往上查找符合等级的部门的关系用户
- GroupScriptExecutorCalc 用户组来自人员脚本运算
- PreNodeUserExecutorCalc 用户来自其他节点的审批人
- StartUserExecutorCalc 执行人来自发起人
- UserByUserGroupRelExecutorCalc 用户来自用户与组关系运算
- UserExecutorCalc 执行人来自用户
- UserPropertiesExecutorCalc 执行人来自
- UserRelExecutorCalc 执行人来自用户关系运行
- VarExecutorCalc 执行人来自流程变量
其他人员的查找策略,可具体实现可参考以上一扩展实现,如:
package com.redxun.bpm.activiti.user.impl;import com.redxun.bpm.activiti.config.UserConfig;import com.redxun.bpm.activiti.user.ExecutorType;import com.redxun.bpm.activiti.user.ITaskExecutorCalc;import com.redxun.dto.bpm.TaskExecutor;import com.redxun.dto.user.OsGroupDto;import com.redxun.dto.user.OsRelInstDto;import com.redxun.dto.user.OsUserDto;import com.redxun.feign.org.OrgClient;import net.sf.json.JSONObject;import org.apache.commons.lang.StringUtils;import org.springframework.stereotype.Component;import javax.annotation.Resource;import java.util.*;@Componentpublic class GroupByUserGroupRelExecutorCalc implements ITaskExecutorCalc {@ResourceOrgClient orgClient;@Overridepublic ExecutorType getType() {return new ExecutorType("groupByUserGroupRel","用户组来自用户与组关系运算",11);}@Overridepublic Collection<TaskExecutor> getExecutors(UserConfig userConfig, Map<String, Object> vars) {Set<TaskExecutor> idInfos=new LinkedHashSet<>();if(StringUtils.isEmpty(userConfig.getConfig())) {return idInfos;}JSONObject jsonObj=JSONObject.fromObject(userConfig.getConfig());String varType=jsonObj.getString("varType");String userIdVar=jsonObj.getString("userId");//从变量中取得该用户的实际值,其他来源有:上一任务审批人、发起人、变量。String userId=(String)vars.get(userIdVar);//获得关系Key及需要查找的关系方String relTypeKey=jsonObj.getString("relTypeKey");String orgDimId = jsonObj.getString("orgDimId");//查找一方List<OsRelInstDto> osRelInsts = new ArrayList<>();if("user".equals(varType)) { //基于用户关系来查找if(StringUtils.isNotEmpty(orgDimId)){osRelInsts=orgClient.getByRelTypeKeyParty2AndDim1(relTypeKey, userId,orgDimId);}else {osRelInsts=orgClient.getByRelTypeKeyParty2(relTypeKey, userId);}for(OsRelInstDto inst:osRelInsts){OsGroupDto osGroup=orgClient.getGroupById(inst.getParty1());if(osGroup!=null) {idInfos.add(TaskExecutor.getGroup(osGroup.getGroupId(),osGroup.getName()) );}}}else if("org".equals(varType)) { // 基于组关系来查找if(StringUtils.isNotEmpty(orgDimId)){osRelInsts=orgClient.getByRelTypeKeyParty1AndDim1(relTypeKey, userId,orgDimId);}else {osRelInsts=orgClient.getByRelTypeKeyParty1(relTypeKey, userId);}for(OsRelInstDto inst:osRelInsts){OsUserDto user = orgClient.getUserById(inst.getParty2());if(user!=null) {idInfos.add(TaskExecutor.getUser(user.getUserId(), user.getFullName()));}}}return idInfos;}}
6.4. 任务办理
任务办理是一个实现流程任务往下流转的一个过程,其需要完成的工作内容包括:
- 单据数据的保存与更新
- 流程往下流转
- 审批历史保存
- 流转记录保存
- 相关人员的消息通知
其中调用的时序图如下所示:

主要类的实现,请参考BpmTaskService的completeTask方法的实现
/** 完成任务处理、撤回,回退任务处理入口* @param cmd* @return*/@GlobalTransactionalpublic JsonResult completeTask(ProcessNextCmd cmd) {JsonResult result=new JsonResult();BpmTask bpmTask=get(cmd.getTaskId());BpmInst bpmInst=bpmInstService.getById(bpmTask.getInstId());result=checkTask(bpmTask,bpmInst);if(!result.isSuccess()){return result;}//传入,为后续的任务执行直接获取该定义,而不用进行转换获取。cmd.setDefId(bpmTask.getDefId());//放置线程变量,在后续可获取到cmd.setInstId(bpmTask.getInstId());//设置上一节点IDcmd.setPreNodeId(bpmTask.getKey());//加上审批的流程变更ProcessConfig processConfig=bpmDefService.getProcessConfig(bpmTask.getActDefId());UserTaskConfig userTaskConfig=(UserTaskConfig) bpmDefService.getNodeConfig(bpmTask.getActDefId(),bpmTask.getKey());cmd.addTransientVar(BpmConst.PROCESS_CONFIG,processConfig);cmd.addTransientVar(BpmConst.USERTASK_CONFIG,userTaskConfig);cmd.addTransientVar(BpmConst.BPM_INST,bpmInst);cmd.addTransientVar(BpmConst.BPM_APPROVE_TASK,bpmTask);//是否允许审批JsonResult jsonResult = bpmInstService.getAllowApprove(userTaskConfig,cmd.getFormData(),cmd.getVars());if(!jsonResult.isSuccess()){return jsonResult;}if(!TaskOptionType.SKIP.equals(cmd.getCheckType())){//处理表单数据formDataService.handFormData(cmd,userTaskConfig.getDataSetting(),"approve");}//处理任务跳转规则。String targetNodeId= handJumpRules(bpmTask, userTaskConfig);if(StringUtils.isNotEmpty(targetNodeId) && StringUtils.isEmpty(cmd.getDestNodeId())){cmd.setDestNodeId(targetNodeId);}//处理任务前置数据processHandlerExecutor.handTaskBeforeHandler(userTaskConfig,bpmTask,bpmInst.getBusKey());//创建审批历史。bpmCheckHistoryService.createHistory(bpmTask, cmd.getCheckType(),cmd.getOpinionName(), cmd.getOpinion(),cmd.getOpFiles());//若为回退,包括 上一步、撤回、驳回发起人等ITaskHandler taskHandler= TaskHandlerContext.getJumpType(cmd.getCheckType());taskHandler.handTask(bpmTask,cmd,userTaskConfig);//任务完成处理器。processHandlerExecutor.handTaskAfterHandler(userTaskConfig,bpmTask.getKey(),bpmInst.getBusKey());//处理任务跳过。bpmTaskSkipService.handSkipTask(cmd);//添加审批任务日志bpmInstLogService.addTaskLog(bpmTask.getInstId(),bpmTask.getTaskId(),bpmTask.getName(),bpmTask.getKey(),"审批任务");result.setMessage("成功完成处理任务!");return result;}
6.4.1. 任务前置、后置处理器
流程任务在执行以上completeTask方法时,平台提供两个处理器接口允许开发人员在外进行围的数据扩展调用处理。

用户只需要实现该接口,并只需要实现以下两个方法:
/*** 当前任务处理前置处理器** @param cmd 当前任务执行的上下文命令参数* @param nodeId 当前节点Id* @param busKey 业务主键*/default void taskAfterHandle(IExecutionCmd cmd, String nodeId, String busKey) {}/*** 当前任务处理前置后置处理器** @param cmd 当前任务执行的上下文命令参数* @param task 当前节点* @param busKey 业务主键*/default void taskPreHandle(IExecutionCmd cmd, BpmTask task, String busKey) {}
在流程设计器中,选择中流程节点,即可在节点的编程扩展中可以选择对应的处理器:

6.5. 任务加签
任务加签是指任务执行人可对多实例的任务进行动态增加新的节点任务,并且基于此节点任务进行审批处理,不受主任务的影响。
//TODO
6.6. 任务追回
6.7. 任务追回处理
任务追回是指任务发出去后,还可以通过追回把新的任务追回至本节点,希望再重新往下执行。
因BPMN的流程是按顺序往下执行的,一旦产生了新的任务后,我们只能变更流程执行顺序,比较理想的办法是重新产生新的回退到当前的任务节点,原执行的节点锁定,流程的执行顺序还是按流程节点的原顺序往下执行。
如:
若当前任务处理于C节点,用户在他的已办事项A中可以进行追回,这时会产生待办A,同时C待办被锁住不允许进行审批,直至待办A任务处理完成后,C待办才恢复正常处理。
6.7.1. 追回时序图

6.8. 任务转办
6.8.1. 业务描述
任务转办是指把本属于自己的任务转交给他人办理的过程,它需要完成以下步骤:
- 只需要修改任务的执行人,一般来说是修改任务表bpmtask表中的assignee_字段即可
- 产生转办历史
- 产生任务转移信息
- 发送相关消息通知转办人
6.8.2. 处理界面

6.8.3. 任务转办的时序图

6.9. 任务沟通
6.9.1. 业务描述
业务的沟通指的是任务办理方发起沟通,产生了新的沟通任务给沟通人,沟通人员可以进行意见的填写与单据的数据处理,沟通任务的处理与不处理均不影响原有的流程任务的执行,被沟通的人还允许进行下一步的沟通。
6.9.2. 处理界面
发起沟通

沟通办理

6.9.3. 发起沟通时序图

6.10. 任务回退
TODO
7. 任务事件
Activiti的流程引擎提供了任务级的事件监听处理,通过平台配置的GlobalEventListener触发调用不同的事件监听处理器调用,最终实现流程任务的事件触发处理。

7.1. 任务创建
Activiti的任务是基于流程的任务办理,它不能脱离流程定义来创建,即任务的产生一定要通过流程引擎解析流程定义的XML的节点定义来产生,但BPM为了适应中国式的流程实施,需要扩展外围一些非流程的审批任务,因此需要在该事件统一创建扩展的任务实例,后续查询待办任务只需要查询该扩展的任务表BPM_TASK表即可。
因此我们在该事件中,实现了以下事项的工作,即创建待办事项,进行待办事项的人员分配,消息通知,生成流程的执行路径等。
/*** 任务创建事件监听处理器* @author csx* @CreateTime 2019-03-03*/@Slf4jpublic class TaskCreatedEventHandler extends BaseTaskHandler<ActivitiEntityEvent> {@Overridepublic EventType getEventType() {return EventType.TASK_CREATED;}@Overridepublic void handle(ActivitiEntityEvent eventEntity) {BpmDefService bpmDefService=SpringUtil.getBean(BpmDefService.class);IExecutionCmd cmd=ProcessHandleUtil.getProcessCmd();TaskEntity taskEntity=(TaskEntityImpl) eventEntity.getEntity();//获取流程的全局配置ProcessConfig processConfig= (ProcessConfig) cmd.getTransientVar(BpmConst.PROCESS_CONFIG);if(BeanUtil.isEmpty(processConfig)){processConfig = bpmDefService.getProcessConfig(eventEntity.getProcessDefinitionId());}ExecutionEntity execution = taskEntity.getExecution();//获取当前任务的配置UserTaskConfig userTaskConfig= (UserTaskConfig) bpmDefService.getNodeConfig(execution.getProcessDefinitionId(),execution.getCurrentActivityId());//1. 设置任务执行人。setAsignee(taskEntity,userTaskConfig);//2. 执行事件handEvent(taskEntity);//3. 发布事件供外部应用监听使用TaskCreateApplicationEvent ev = new TaskCreateApplicationEvent(taskEntity, userTaskConfig,processConfig);SpringUtil.publishEvent(ev);}//...}
7.2. 任务完成
同样,我们监听Activiti的完成事件,并在该事件中通过增加触发完成后一些公共事件,并且清空当前完成后的流程任务实例数据。
/*** 流程任务完成事件* @author csx* @CreateTime 2019-03-03*/public class TaskCompletedEventHandler extends BaseTaskHandler<ActivitiEntityEvent> {@Overridepublic EventType getEventType() {return EventType.TASK_COMPLETED;}@Overridepublic void handle(ActivitiEntityEvent eventEntity) {BpmDefService bpmDefService=SpringUtil.getBean(BpmDefService.class);IExecutionCmd cmd= ProcessHandleUtil.getProcessCmd();//TODO 删除任务,任务处理人,catch 并抛出处理的异常,如调用外部接口返回的错误TaskEntityImpl taskEntity = (TaskEntityImpl) eventEntity.getEntity();UserTaskConfig userTaskConfig= (UserTaskConfig) cmd.getTransientVar(BpmConst.USERTASK_CONFIG);ProcessConfig processConfig= (ProcessConfig) cmd.getTransientVar(BpmConst.PROCESS_CONFIG);if(BeanUtil.isEmpty(processConfig)){processConfig = bpmDefService.getProcessConfig(eventEntity.getProcessDefinitionId());}if(BeanUtil.isEmpty(userTaskConfig)){ExecutionEntity execution = taskEntity.getExecution();userTaskConfig= (UserTaskConfig) bpmDefService.getNodeConfig(execution.getProcessDefinitionId(),execution.getCurrentActivityId());}BpmTask bpmTask= (BpmTask) cmd.getTransientVar(BpmConst.BPM_APPROVE_TASK);//1. 处理任务完成相关的数据handTaskComplete(bpmTask,cmd);//2. 处理事件一些公共数据处理,如前置与后置的公共事件调用handEvent(taskEntity);//3. 往外抛出处理事件TaskCompleteApplicationEvent ev = new TaskCompleteApplicationEvent(taskEntity, userTaskConfig,processConfig);SpringUtil.publishEvent(ev);}/*** 删除人员,记录审批意见。* @param bpmTask* @param cmd*/private void handTaskComplete(BpmTask bpmTask,IExecutionCmd cmd){BpmTaskService bpmTaskService = SpringUtil.getBean(BpmTaskService.class);//删除树形任务(一般沟通任务都会删除)bpmTaskService.deleteBpmTaskCascade(bpmTask.getTaskId());//删除根据ACT_TASK_派生的任务,这种情况会删除会签任务。bpmTaskService.delByActTaskId(bpmTask.getActTaskId());}}