概述
流程定义配置完成后,需要进行流程的启动,启动完成后,平台会生产流程实例的数据。平台支持:
- 流程启动
- 流程作废
- 流程恢复
- 流程明细
- 流程干预
流程实例关联查询,如:
- 我的流程草稿
- 我的流程
- 我的已办
表设计
为了更好基于流程实例与业务作关联,我们增加了bpm_inst表,并且进行了以下扩展的表设计:
是否主键 | 字段名 | 字段描述 | 数据类型 | 长度 | 可空 | 备注 |
---|---|---|---|---|---|---|
是 | INST_ID_ | 实例ID | VARCHAR(64) | 64 | ||
DEF_ID_ | 流程定义ID | VARCHAR(64) | 64 | |||
ACT_INST_ID_ | Activiti实例ID | VARCHAR(64) | 64 | 是 | Activiti实例ID | |
ACT_DEF_ID_ | Activiti定义ID | VARCHAR(64) | 64 | Activiti定义ID | ||
SOL_ID_ | 解决方案ID_ | VARCHAR(64) | 64 | 是 | 解决方案ID_ | |
INST_NO_ | 流程实例单号 | VARCHAR(50) | 50 | 是 | 流程实例工单号 | |
IS_USE_BMODEL_ | 单独使用业务模型 | VARCHAR(20) | 20 | 是 | 单独使用业务模型,YES=表示不带任何表单视图 | |
SUBJECT_ | 标题 | VARCHAR(255) | 255 | 是 | 标题 | |
STATUS_ | 运行状态 | VARCHAR(20) | 20 | 是 | 运行状态 | |
VERSION_ | 版本 | INT | 是 | 版本 | ||
SOL_KEY_ | 业务解决方案KEY | VARCHAR(64) | 64 | 是 | 业务解决方案KEY | |
BUS_KEY_ | 业务键ID | VARCHAR(64) | 64 | 是 | 业务键ID | |
CHECK_FILE_ID_ | 审批正文依据ID | VARCHAR(64) | 64 | 是 | 审批正文依据ID | |
FORM_INST_ID_ | 业务表单数据 | VARCHAR(64) | 64 | 是 | 业务表单数据ID | |
IS_TEST_ | 是否为测试 | VARCHAR(20) | 20 | 是 | 是否为测试 | |
ERRORS_ | 出错 | TEXT | 是 | |||
END_TIME_ | 结束时间 | DATETIME | 是 | 结束时间 | ||
TENANT_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 | 是 | 更新时间 | ||
DATA_SAVE_MODE_ | 数据保存模式 | VARCHAR(10) | 10 | 是 | 数据保存模式(all,json,db) | |
SUPPORT_MOBILE_ | 支持手机端 | INT | 是 | 支持手机端 | ||
BO_DEF_ID_ | BO定义ID | VARCHAR(20) | 20 | 是 | BO定义ID | |
BILL_NO_ | 单号 | VARCHAR(255) | 255 | 是 | 单号 | |
START_DEP_ID_ | 发起部门ID | VARCHAR(64) | 64 | 是 | 发起部门ID | |
START_DEP_FULL_ | 发起部门全名 | VARCHAR(300) | 300 | 是 | 发起部门全名 | |
IS_LIVE_ | 是否复活 | VARCHAR(64) | 64 | 是 | 是否复活 | |
LIVE_INST_ID_ | 复活的流程实例 | VARCHAR(64) | 64 | 是 | 复活的流程实例 |
说明:
- Activiti的流程定义Id(actdef_id)
- Activiti的流程实例Id(actinst_id)
- 与业务的关联主键 (buskey)
- 单号 用来记录不同的审批事项的唯一标识
- 状态 记录流程是在运行还是结束
类设计
说明
- BpmInstController 流程实例的对外数据交互控制类
- BpmInstServiceImpl 流程实例的业务逻辑处理类
- SystemClient 系统业务管理模块的客户端,基于feign的调用封装,用来获取系统的分类、数据字典数据等。
- UserClient 用户模块的客户端,基于feign的调用封装,用来获取组织架构的数据,如通过用户Id获取用户数据等
- TaskExecutorService 用来获取任务节点的执行人员
- BpmInstLogServiceImpl 流程实例日志服务类
- RuntimeService Activiti的实例运行服务类
- BpmDefService 扩展的流程定义的服务类
- BpmTaskMapper 扩展任务数据映射操作类
- FormDataService 单据数据服务类,基于feign的调用处理
- GroovyEngine Groove脚本引擎
- BpmCheckHistoryServiceImpl 流程审批历史服务类
- BpmTaskSkipService 流程任务跳过服务类
- ProcessHandlerExecutor 流程处理器执行类
- ProcessScriptEngine 带有流程实例上下文数据的脚本操作引擎
- BpmRuPathServiceImpl 流程实例的执行路径的服务类
- BpmInstDataServiceImpl 流程实例数据服务类
- BpmTaskUserMapper 流程任务用户数据映射类
- BpmSignDataServiceImpl 会签数据服务类
- BpmTaskService 扩展流程数据业务逻辑处理类
- ActRepService Activiti库的操作服务类
流程启动
流程启动是流程业务与业务单据数据关联的一个过程,通过流程的任务审批,驱动不同的流程业务往下流转。
流程启动的代码实现请参考BpmInstServiceImpl以下方法代码:
@GlobalTransactional
public BpmInst doStartProcess(ProcessStartCmd cmd, BpmDef bpmDef) throws Exception {
//初始流程实例数据。
BpmInst bpmInst = initBpmInst(cmd,bpmDef);
//获取流程组配置
ProcessConfig processConfig = bpmDefService.getProcessConfig(bpmDef.getActDefId());
//处理表单数据
handBusinessData(bpmInst,cmd,processConfig);
//处理流程实例数据保存。
handBpmInst( bpmInst, cmd, bpmDef, processConfig);
//处理流程变量。
handVars(cmd, bpmDef, bpmInst,processConfig);
//在启动时执行前置处理器
processHandlerExecutor.handStartBeforeHandler(processConfig);
//调用Activiti引擎启动流程
ProcessInstance processInstance = runtimeService.startProcessInstanceById(bpmDef.getActDefId(),bpmInst.getBusKey(), cmd.getVars());
bpmInst.setActInstId(processInstance.getId());
//更新Act流程实例至流程变量中
runtimeService.setVariable(processInstance.getId(),BpmInstVars.ACT_INST_ID.getKey(),processInstance.getId());
//在流程结束时处理后置处理器。
processHandlerExecutor.handStartAfterHandler(processConfig,bpmInst);
//保存流程实例
saveBpmInst(cmd,bpmInst,true);
//跳过第一个节点。
handFirstJump(cmd,processConfig,bpmInst);
//任务跳过
IExecutionCmd nextCmd=ProcessHandleUtil.getProcessCmd();
taskSkipService.handSkipTask(nextCmd);
return bpmInst;
}
说明
- 该方法依赖于Seata的全局事务,以保证流程启时与外部表单的数据保存时的一致性。
- 在页面提交的单据数据需要遵行单据的数据结构格式要求
- 调用Activiti的流程启动接口,传入流程定义Id,业务键,流程变量,并且返回activiti的流程引擎Id
- 根据流程级的配置,是否跳过第一个任务节点等
- 根据流程配置的前置与后置处理器,相应触发处理器的业务逻辑调用。
流程启动Cmd的参数
ProcessStartCmd 流程的启动参数说明
属性 | 描述 | 必须 |
---|---|---|
defId | 流程定义ID | defId与defKey必需传入一个 |
defKey | 流程定义Key | defId与defKey必需传入一个 |
actDefId | Activiti的流程Id | |
busKey | 业务键 | 是 |
billType | 单据类型 | |
instId | 流程实例Id,当从草稿中启动流程时才需要传入 | |
checkType | 审批类型,默认为同意,值为AGREE | 是 |
opinionName | 审批名称,同意(AGREE),不同意(REFUSE) | |
opinion | 审批意见内容 | |
opFiles | 提交意见可带附件 | |
destNodeId | 目标节点Id,即提交完成后可跳到某一节点上 | |
vars | 变量,提交至流程实例中的变量 | |
formData | 表单的数据,格式如:{ form1:{ field1:’a’, field2:’b’ },form2:{ field3:’c’, field4:’d’ } } | |
nodeUserIds | 下一步节点及执行人员,{userTask1:’1,2,3’,userTask2:’2,3,4’} |
流程的前置与后置处理器
流程在执行启动时,若在流程定义中配置了前置与后置处理器,即可在activiti的流程启动时,执行对应的流程触发器。
前置处理器:
//在启动时执行前置处理器
processHandlerExecutor.handStartBeforeHandler(processConfig);
用户的自定义前置处理器,即可类似如下定义:
package com.redxun.bpm.activiti.processhandler;
import com.redxun.bpm.activiti.config.ProcessConfig;
import com.redxun.bpm.core.entity.BpmInst;
import com.redxun.bpm.core.entity.BpmTask;
import com.redxun.bpm.core.entity.IExecutionCmd;
import com.redxun.bpm.core.entity.ProcessStartCmd;
public class SalesOrderHandler implements ProcessHandler{
@Override
public String getName() {
return null;
}
@Override
public void endHandle(BpmInst bpmInst) {
}
/**
* 任务前置处理器
* @param processConfig
* @param cmd
* @param bpmInst
*/
@Override
public void processStartAfterHandle(ProcessConfig processConfig, ProcessStartCmd cmd, BpmInst bpmInst) {
//在此编写前置的业务处理,如获取单据的数据处理业务
}
@Override
public void processStartPreHandle(ProcessStartCmd cmd) {
//在此编写前置的业务处理,如获取单据的数据处理业务
}
@Override
public void taskAfterHandle(IExecutionCmd cmd, String nodeId, String busKey) {
}
@Override
public void taskPreHandle(IExecutionCmd cmd, BpmTask task, String busKey) {
}
}
编写完成后,在流程定义的流程级的前置处理器选择该配置
后置处理器:
//在流程结束时处理后置处理器。
processHandlerExecutor.handStartAfterHandler(processConfig,bpmInst);
流程挂起与恢复
流程实例在执行过程中,可以对它进行挂起,挂起的流程实例的任务不允许往下执行。实现方式只是需要对其状态进行修改即可,如下所示:
参考BpmInstController:
/**
* 更新流程实例状态
* @param instId
* @param status
* @return
*/
@ApiOperation(value = "更新流程实例状态")
@PostMapping("updateProcessStatus")
public JsonResult updateProcessStatus(@ApiParam @RequestParam(value="instId") String instId,@ApiParam @RequestParam(value="status") String status){
bpmInstService.updateStatusByInstId(instId,status);
return new JsonResult(true,"成功更新流程状态!");
}
传入的状态值包括:
RUNNING:运行中
SUPSPEND: 挂起
流程实例作废
平台允许对运行中的流程实例进行作废,参考BpmInstController的以下方法:
/**
* 作废流程实例
* @param instId
* @param reason
* @return
*/
@ApiOperation(value = "作废流程实例")
@PostMapping("cancelProcess")
public JsonResult cancelProcess(@ApiParam @RequestParam(value="instId") String instId,@ApiParam @RequestParam(value="reason") String reason){
bpmInstService.cancelProcess(instId,reason);
return new JsonResult(true,"成功更新流程状态!");
}
/**
* 作废流程实例
* @param instId
* @param reason
*/
@Transactional
public void cancelProcess(String instId,String reason){
BpmInst bpmInst=get(instId);
if(bpmInst==null){
return;
}
ProcessConfig processConfig = bpmDefService.getProcessConfig(bpmInst.getActDefId());
String script=processConfig.getEndProcessScript();
//执行流程结束时配置的脚本
if(StringUtils.isNotEmpty(script)){
JsonResult formData = getFormData(instId,processConfig,"NO");
JSONObject formDataJson=new JSONObject();
for(BpmView bpmView:(List<BpmView>)formData.getData()){
formDataJson.put(bpmView.getBoAlias(),bpmView.getData());
}
IExecutionCmd cmd = ProcessHandleUtil.getProcessCmd();
Map<String, Object> contextData = new HashMap<>(formDataJson.size() + 2);
if (formDataJson != null) {
Set<Map.Entry<String, Object>> ents = formDataJson.entrySet();
for (Map.Entry<String, Object> ent : ents) {
contextData.put(ent.getKey(), ent.getValue());
}
}
contextData.put("cmd", cmd);
contextData.put("vars", getVariables(bpmInst.getActInstId()));
processScriptEngine.exeScript(script, contextData);
}
//更新流程实例作废状态
bpmInstMapper.updateStatusByInstId(instId,BpmInstStatus.CANCEL.name());
//加上实例的日志
bpmInstLogService.addInstLog(instId,"进行流程实例的作废,作废原因为:" + reason );
//插入作废的流程审批历史
List<BpmTask> bpmTasks = bpmTaskService.getByInstId(instId);
if(bpmTasks.size()>0){
String curUserId = ContextUtil.getCurrentUserId();
insertCheckHistory(bpmTasks.get(0),curUserId,curUserId,"CANCEL","CANCEL",reason);
}
//删除流程实例
runtimeService.deleteProcessInstance(bpmInst.getActInstId(),reason);
//删除任务
bpmTaskMapper.deleteByInstId(instId);
}
说明:
流程实例作废时,需要完成以下事项:
- 流程实例结束的脚本调用
- 更新实例状态为作废状态
- 添加实例作废的日志
- 添加审批历史
- 删除Activiti的流程实例
- 删除对应的流程任务
流程明细
流程明细需要展示流程的明细信息,包括实例的明细信息,单据的信息,流程图的信息,审批日志。
后台准备的单据数据,参考BpmInstService
/**
* 获取流程实例的相关数据
* @param instId
* @param isMobile
* @param defaultWrite
* @return
*/
public BpmInstDetail getInstDetail( String instId, String isMobile,Boolean defaultWrite){
BpmInst bpmInst=get(instId);
BpmDef bpmDef=bpmDefService.get(bpmInst.getDefId());
ProcessConfig processConfig=bpmDefService.getProcessConfig(bpmDef.getActDefId());
//获取单据的数据
JsonResult dataResult=getFormData( instId, processConfig,isMobile,defaultWrite);
BpmInstDetail detail=new BpmInstDetail();
//设置实例主数据
detail.setBpmInst(bpmInst);
//设置单据的数据
detail.setFormData(dataResult);
//设置流程级的配置
detail.setProcessConfig(processConfig);
//审批意见记录
List<BpmCheckHistory> opinionHistoryList= bpmCheckHistoryService.getOpinionNameNotEmpty(instId);
detail.setBpmCheckHistories(opinionHistoryList);
return detail;
}
单据明细
在单据通过FormClient可加载单据的数据与表单页面,通过表单页面可加载对应的业务数据,如在前端,通过以下Vue组件进行加载展示:
<rx-forms ref="rxForms"> </rx-forms>
在页面动态加载单据并设置单据的数据,如下所示:
BpmInstApi.getInstDetail(this.instId).then(res => {
var contextData={
type:"detail",
instNo:res.bpmInst.instNo,
instId:self.instId,
opinionHistorys:res.bpmCheckHistories
};
this.status=res.bpmInst.status;
self.$refs.rxForms.setData(res.formData.data,true,contextData);
})
后台准备单据的数据:
/**
* 获取单据的数据
* @param instId
* @param processConfig
* @param isMobile
* @param defaultWrite
* @return
*/
private JsonResult getFormData(String instId,ProcessConfig processConfig,String isMobile,Boolean defaultWrite){
FormConfig formConfig= getForms(processConfig);
//检查单据的配置是否为空,若不空,设置PC单据与移动端单据
if(BeanUtil.isNotEmpty(formConfig)){
formConfig.setFormpc(setReadOnly(formConfig.getFormpc()));
formConfig.setMobile(setReadOnly(formConfig.getMobile()));
}
/**
* 通过Feign的服务获取单据的基本信息结果
*/
JsonResult result= formDataService.getByInstId(processConfig.getDataSetting(),
processConfig.getBoDefs().getValue(),
formConfig,
instId,
isMobile,
defaultWrite);
return result;
}
调用时序图:
说明:
最终是通过FormClient来进行实现,FormClient是访问单据模块的基础接口的封装,主要是调用单据模块的Restful API
审批历史
实例审批历史数据展示
实现效果:
参考BpmCheckHistoryController的按实例获取审批历史
@ApiOperation("查看流程的审批历史")
@GetMapping("getCheckHistorys")
public List<BpmCheckHistory> getCheckHistorys(@ApiParam @RequestParam String instId){
return bpmCheckHistoryService.getByInstId(instId);
}
审批历史表设计
BPM_CHECK_HISTORY 表
是否主键 | 字段名 | 字段描述 | 数据类型 | 长度 | 可空 | 备注 |
---|---|---|---|---|---|---|
是 | HIS_ID_ | HIS_ID_ | VARCHAR(64) | 64 | ||
ACT_DEF_ID_ | ACT流程定义ID | VARCHAR(64) | 64 | 是 | ACT流程定义ID | |
INST_ID_ | 流程实例ID | VARCHAR(64) | 64 | 是 | 流程实例ID | |
TREE_ID_ | 分类ID | VARCHAR(64) | 64 | 是 | 分类ID | |
SUBJECT_ | 主题 | VARCHAR(64) | 64 | 是 | 主题 | |
NODE_NAME_ | 节点名称 | VARCHAR(255) | 255 | 是 | 节点名称 | |
NODE_ID_ | 节点Key | VARCHAR(255) | 255 | 节点Key | ||
TASK_ID_ | 任务ID | VARCHAR(64) | 64 | 是 | 任务ID | |
CM_ST_TASK_ID_ | 回复的沟通任务ID | VARCHAR(64) | 64 | 是 | 回复的沟通任务ID | |
COMPLETE_TIME_ | 完成时间 | DATETIME | 是 | 完成时间 | ||
DURATION_ | 持续时长 | BIGINT | 是 | 持续时长 | ||
DURATION_VAL_ | 有效审批时长 | BIGINT | 是 | 有效审批时长 | ||
OWNER_ID_ | 任务所属人ID | VARCHAR(64) | 64 | 是 | 任务所属人ID | |
HANDLER_ID_ | 处理人ID | VARCHAR(64) | 64 | 是 | 处理人ID | |
AGENT_USER_ID_ | 被代理人 | VARCHAR(64) | 64 | 是 | 被代理人 | |
CHECK_STATUS_ | 审批状态 | VARCHAR(50) | 50 | 是 | 审批状态 | |
JUMP_TYPE_ | 跳转类型 | VARCHAR(50) | 50 | 是 | 跳转类型 | |
REMARK_ | 意见备注 | VARCHAR(512) | 512 | 是 | 意见备注 | |
OPINION_NAME_ | 字段意见名称 | VARCHAR(50) | 50 | 是 | 字段意见名称 | |
HANDLE_DEP_ID_ | 处理部门ID | VARCHAR(64) | 64 | 是 | 处理部门ID | |
HANDLE_DEP_FULL_ | 处理部门全名 | VARCHAR(300) | 300 | 是 | 处理部门全名 | |
ENABLE_MOBILE_ | 是否支持手机 | SMALLINT | 是 | 是否支持手机 | ||
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 | 是 | 更新时间 |
说明:
- 流程审批历史通过流程实例Id与ActInstId关联,通过该字段可以获取流程实例对应的审批历史记录
- 流程审批历史记录用户的每次审批记录
- 记录每次审批的审批意见与审批状态
流程干预
流程的干预指是管理员在流程实例的运行过程中,可以以下的流程相关属性进行干预变更:
- 单据的数据
- 节点人员
- 流程变量
- 流程跳转路径
- 流程配置属性更改
效果图:
单据的数据
单据的展现与流程明细中的单据明细获取的接口一样,差别是这边允许对单据的所有权限均是开放出来,以允许进行编辑与修改,以达到支持基于上面进行单据数据变更处理。
<rx-forms ref="rxForms"></rx-forms>
参考BpmInstOperator.vue其加载的方法如下:
loadDetail() {
let self = this;
if(!this.instId){
this.instId=this.$route.params.instId;
}
BpmInstApi.getInstDetailForInterpose(this.instId).then(res => {
this.bpmInstDetail=res;
this.actInstId=this.bpmInstDetail.bpmInst.actInstId;
this.defId=this.bpmInstDetail.bpmInst.defId;
let contextData={
instId:self.instId,
opinionHistorys:res.bpmCheckHistories
};
self.$refs.rxForms.setData(res.formData.data,false,contextData);
})
},
后端代码BpmInstController参考:
/**
* 获取实例明细
* @param instId
* @return
*/
@GetMapping("getInstDetailForInterpose")
public BpmInstDetail getInstDetailForInterpose(@RequestParam String instId,@RequestParam(required = false,defaultValue = "NO") String isMobile) {
BpmInstDetail detail = bpmInstService.getInstDetail(instId, isMobile, true);
return detail;
}
说明:
bpmInstService.getInstDetail最后的参数为true则代表可写,设置为false则表示单据是只读状态。
节点人员
节点的人员分为四种级别:
- 任务实例已分配的人员,即正在审批的人员。
- 已执行的历史审批节点人员
- 前置已经设置好的节点人员,但任务节点实例尚未创建,我们称之为可干预的节点人员配置
- 流程定义配置级的人员
如下图所示:
在界面中呈现节点的人员时,我们通过在后台扩展类按以上优先级分别展示不同的节点人员,
分别展示为节点人员配置,干预人员配置,审批人与历史审批节点人员。
具体实现参考BpmInstController以下实现方法:
/**
* 获取所有的流程节点及节点对应的人员
* @param actInstId
* @return
*/
@ApiOperation("通过实例Id获取所有的任务节点执行人员列表")
@GetMapping("getTaskNodeUsers")
public List getTaskNodeUsers(@ApiParam("actInstId") @RequestParam("actInstId") String actInstId){
List<TaskNodeUser> taskNodeUserList=new ArrayList<>();
BpmInst bpmInst=bpmInstService.getByActInstId(actInstId);
// 获得当前的待办任务
List<BpmTask> curTasks = bpmTaskService.getByActInstId(actInstId);
//取得流程的所有人工节点
Collection<FlowNode> userNodes=actRepService.getUserNodes(bpmInst.getActDefId());
//取得到之前在流程任务节点中增加的所有的节点人员Id映射
String nodeUserIds=(String)runtimeService.getVariable(actInstId,BpmInstVars.NODE_USER_IDS.getKey());
Map<String,Object> nodeUserIdMap=null;
if(StringUtils.isNotEmpty(nodeUserIds)){
nodeUserIdMap = JSON.parseObject(nodeUserIds).getInnerMap();
}
//查找流程所有的节点,并且把审批中的、未审批的、已审批的节点的人员计算全部计算出来
for (FlowNode flowNode : userNodes) {
TaskNodeUser taskNodeUser = new TaskNodeUser(flowNode.getId(),flowNode.getName());
// 1 查找已经审批的
List<BpmCheckHistory> BpmCheckHistories = bpmCheckHistoryService.getByInstIdNodeId(bpmInst.getInstId(), flowNode.getId());
List<TaskExecutor> checkExecutors = new ArrayList<TaskExecutor>();
Set<String> userIdSet = new HashSet<String>();
for (BpmCheckHistory history : BpmCheckHistories) {
if (StringUtils.isEmpty(history.getHandlerId())
|| userIdSet.contains(history.getHandlerId())) {
continue;
}
userIdSet.add(history.getHandlerId());
IUser osUser = userClient.findByUserId(history.getHandlerId());
if (osUser != null) {
checkExecutors.add(TaskExecutor.getUser(osUser.getUserId(), osUser.getFullName()));
}
}
taskNodeUser.getCheckExecutors().addAll(checkExecutors);
// 2 从正在运行中的流程任务实例中获得执行人
for (BpmTask task : curTasks) {
if (task.getKey().equals(flowNode.getId())) {
Collection<TaskExecutor> taskExecutors = bpmTaskService.getTaskExecutors(task.getTaskId());
taskNodeUser.getRunExecutors().addAll(taskExecutors);
taskNodeUser.setRunning(true);
//if(BpmTask.TYPE_FLOW_TASK.equals(task.getTaskType())) {
//设置为当前运行的任务Id,只需要获取一个当前其中一个任务Id即可
taskNodeUser.setTaskId(task.getTaskId());
// }
}
}
// 3 从流程变量中获得其人员列表
if (nodeUserIdMap!=null && nodeUserIdMap.containsKey(flowNode.getId())) {
Set<TaskExecutor> operatorExecutors = new HashSet<>();
String userIds = (String) nodeUserIdMap.get(flowNode.getId());
if (StringUtils.isNotEmpty(userIds)) {
String[] uIds = userIds.split("[,]");
for (String uId : uIds) {
IUser osUser = userClient.findByUserId(uId);
operatorExecutors.add(TaskExecutor.getUser(osUser.getUserId(),osUser.getFullName()));
}
taskNodeUser.getOperateExecutors().addAll(operatorExecutors);
}
}
//4 从流程配置文件中获取人员信息
Map<String, Object> variables = runtimeService.getVariables(actInstId);
UserTaskConfig userTaskConfig= (UserTaskConfig) bpmDefService.getNodeConfig(bpmInst.getActDefId(),flowNode.getId());
Set<TaskExecutor> taskExecutors = taskExecutorService.getTaskExecutors(userTaskConfig.getUserConfigs(), variables);
taskNodeUser.getConfigExecutors().addAll(taskExecutors);
taskNodeUserList.add(taskNodeUser);
}
return taskNodeUserList;
}
流程变量
平台对Activiti的流程变量部分不进行扩展,因此保留了Acitiviti的原生API操作。
获取流程实例所有的变量
Map<String, Object> varMaps = runtimeService.getVariables(actInstId);
添加及更新流程变量
runtimeService.setVariable(actInstId, var.getKey(), val);
删除流程变量
runtimeService.removeVariable(actInstId,varKey);
干预流程跳转
标准的BPMN流程定义中,节点间的跳转需要严格遵循流程定义的跳转规则,若需要实现不同的节点跳转,需要进行扩展实现,平台提供了一个标准的节点可实现节点间的跳转:
/**
* 流程干预跳转
* @param taskNodeJump
* @return
* @throws Exception
*/
@ApiParam("任务干预跳转")
@PostMapping("jumpToNode")
public JsonResult jumpToNode(@RequestBody TaskNodeJump taskNodeJump) throws Exception {
try{
BpmTask bpmTask=bpmTaskService.get(taskNodeJump.getTaskId());
if(bpmTask==null){
List<BpmTask> bpmTasks = bpmTaskService.getByActInstId(taskNodeJump.getActInstId());
if(bpmTasks.size()>0){
bpmTask=bpmTasks.get(0);
}
}
if(bpmTask==null){
return new JsonResult(false,"当前任务已经完成,不允许干预跳转");
}
ProcessNextCmd cmd=new ProcessNextCmd();
cmd.setTaskId(bpmTask.getTaskId());
cmd.setCheckType(TaskOptionType.INTERPOSE.name());
cmd.setDestNodeId(taskNodeJump.getDestNodeId());
//设置目标节点的人员
cmd.setNodeUserIds(bpmTask.getKey(),taskNodeJump.getToUserIds());
cmd.setOpinion(taskNodeJump.getOpinion());
return completeTask(cmd);
} catch (Exception ex){
ex.printStackTrace();
String message=ExceptionUtil.getExceptionMessage(ex);
JsonResult jsonResult=new JsonResult(false,"任务干预跳转失败!");
jsonResult.setMessage(message);
return jsonResult;
}
}
流程属性变更
流程属性的变更可通过流程节点的人员通过流程设计器那边进行节点与流程级的属性变更,然后通过流程保存、变更、节点属性的变更处理。
流程抄送
流程抄送是指在任务办理过程中,把流程实例的相关信息抄送给一个或多个人进行查看,抄送的时机为包括:
- 任务办理创建或完成时
- 流程开始时
- 流程结束中
存储设计
BPM_INST_CC 流程实例抄送主表
流程实例的抄送主表,记录某一次实例的抄送主表信息。
是否主键 | 字段名 | 字段描述 | 数据类型 | 长度 | 可空 | 备注 |
---|---|---|---|---|---|---|
是 | ID_ | 主键 | VARCHAR(64) | 64 | 主键 | |
SUBJECT_ | 主题 | VARCHAR(64) | 64 | 是 | 主题 | |
NODE_ID_ | 节点ID | VARCHAR(64) | 64 | 是 | 节点ID | |
NODE_NAME_ | NODE_NAME_ | VARCHAR(64) | 64 | 是 | ||
FROM_USER_ | 发送人 | VARCHAR(64) | 64 | 是 | 发送人 | |
FROM_USER_ID_ | 发送人ID | VARCHAR(64) | 64 | 是 | 发送人ID | |
INST_ID_ | 流程实例ID | VARCHAR(64) | 64 | 是 | 流程实例ID | |
DEF_ID_ | 流程定义ID | VARCHAR(64) | 64 | 是 | 流程定义ID | |
CC_TYPE_ | 类型 | VARCHAR(20) | 20 | 是 | 类型 | |
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 | 是 | 更新时间 |
BPM_INST_CP 流程实例抄送人员表
BPM流程实例抄送人,记录流程实例抄送给哪些人查看,是否已读。
是否主键 | 字段名 | 字段描述 | 数据类型 | 长度 | 可空 | 备注 |
---|---|---|---|---|---|---|
是 | ID_ | 主键 | VARCHAR(64) | 64 | 主键 | |
CC_ID_ | 抄送ID | VARCHAR(64) | 64 | 是 | 抄送ID | |
INST_ID_ | 实例ID | VARCHAR(64) | 64 | 是 | 实例ID | |
USER_ID_ | 接收用户ID | VARCHAR(64) | 64 | 是 | 接收用户ID | |
IS_READ_ | 是否阅读 | VARCHAR(64) | 64 | 是 | 是否阅读 | |
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 | 是 | 更新时间 |
类设计
关键类说明:
CopyListener 是抄送的监听类,主要负责抄送监听事件的发送后的抄送的数据插入处理与抄送人员的消息通知。