在之前的章节中,我们聊到了activity可以解决动态的任意加签节点的问题,但那时候我们的加签节点是临时存储的,而在现实的业务中,往往需要通过创建自身的业务表与activity的表建立一定的业务关系,从而实现数据持久化,即我们加签的节点不能因为加签完毕后就找不到历史数据了
activity和mybatis实现了较好了的整合,如果采用spring的xml方式进行配置的话,我们只需要导入mybatis的依赖并进行简单的配置即可
下面我们先通过一个简单的例子,体验一下如何将自建的业务表数据存储进去
1、建表语句
CREATE TABLE `act_creation` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`PROCESS_DEFINITION_ID` varchar(255) DEFAULT NULL COMMENT '流程定义id',
`DOUSERID` varchar(20) DEFAULT NULL COMMENT '操作人id',
`ACT_ID` varchar(64) DEFAULT NULL,
`PROCESS_INSTANCE_ID` varchar(255) NOT NULL DEFAULT '0' COMMENT '流程实例id',
`PROPERTIES_TEXT` varchar(2000) DEFAULT NULL COMMENT '参数',
`STATE_` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0代表有效 1代表无效',
`create_time` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT='临时创建活动节点';
我们将该表创建在和activity本身的表在一个库中,该表的作用表示用户自定义加签的节点属性信息,
对应的实体类:
public class ActCreation {
private String id;
/**
* 流程定义ID
*/
private String processDefinitionId;
/**
* 对比assignee字段
*/
private String doUserId;
/**
* 流程节点ID
*/
private String actId;
/**
* 流程实例ID
*/
private String processInstanceId;
private String processText;
/**
* 状态
*/
private Integer state;
public String getProcessText() {
return processText;
}
public void setProcessText(String processText) {
this.processText = processText;
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getProcessDefinitionId() {
return processDefinitionId;
}
public void setProcessDefinitionId(String processDefinitionId) {
this.processDefinitionId = processDefinitionId;
}
public String getDoUserId() {
return doUserId;
}
public void setDoUserId(String doUserId) {
this.doUserId = doUserId;
}
public String getActId() {
return actId;
}
public void setActId(String actId) {
this.actId = actId;
}
public String getProcessInstanceId() {
return processInstanceId;
}
public void setProcessInstanceId(String processInstanceId) {
this.processInstanceId = processInstanceId;
}
@Override
public String toString() {
return "ActCreation{" +
"id='" + id + '\'' +
", processDefinitionId='" + processDefinitionId + '\'' +
", doUserId='" + doUserId + '\'' +
", actId='" + actId + '\'' +
", processInstanceId='" + processInstanceId + '\'' +
", processText='" + processText + '\'' +
", state=" + state +
'}';
}
}
2、创建接口
该接口有3个方法,和数据库映射与交互的原理就不再过多讲述了
public interface CreationMapper {
@Select("select * from act_creation where STATE_=0 ")
@Results({
@Result(property="id",column="ID"),
@Result(property="processDefinitionId",column="PROCESS_DEFINITION_ID"),
@Result(property="doUserId",column="DOUSERID"),
@Result(property="actId",column="ACT_ID"),
@Result(property="processInstanceId",column="PROCESS_INSTANCE_ID"),
@Result(property="processText",column="PROPERTIES_TEXT"),
@Result(property="state",column="STATE_"),
})
public List find(String processInatanceId);
@Insert("insert into act_creation(PROCESS_DEFINITION_ID, PROCESS_INSTANCE_ID,PROPERTIES_TEXT,create_time) values(#{processDefinitionId}, #{processInstanceId}, #{processText},now())")
public void insert(ActCreation actCreation);
@Update("update act_creation set STATE_ = 1 where STATE_=0 and PROCESS_INSTANCE_ID=#{processInstanceId}")
void updateState(String processInstanceId);
}
3、将该接口配置到activiti.cfg.xml中
事实上,在真正的项目整合中,我们没有必要这样做的,那只需要全局配置即可
com.congge.addpersistant.CreationMapper
4、测试
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) processEngine
.getProcessEngineConfiguration();
SqlSessionFactory sqlSessionFactory = processEngineConfiguration.getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
List selectList = sqlSession.selectList("com.congge.addpersistant.CreationMapper.find",
null);
System.out.println(selectList);
}
假如在表中我们初始化了一条数据,运行上面的代码,可以看到获取到了数据
知道了如何通过整合mybatis完成自定义加签节点的数据持久化,下面我们开始思考如何开始编写代码呢?
考虑如下的流程图:
假如我们需要在auth1和auth2之间增加一个审批节点,其添加的时机如何呢?按照流程实例的实际运转情况,有这么几点值得思考,第一点,加签的节点是在auth1审批完成了以后添加还是未审批之前添加呢?第二点,加签成功后,下一步的开始审批的地方从哪里开始呢?第三点,是不是可以增加很多节点呢?
假如说可以无无限增加加签节点的话,那么可能还会出现下面的情形
在之前的那篇任意加签节点中,其实我们说了加签节点的实现思路,这里再简单总结下,
1、构建加签节点的实例任务
最终加签的节点要运转,必须以任务的方式存在
2、构建任务节点的基本信息
节点的存在,和其他节点通过连线的方式连接在一起的,因此需要构建加签节点的出线、入线信息
3、将新增的加签节点信息保存到process
process中存储了所有节点的信息,任务信息邓,因此需要把构建好的完整的加签节点添加process
4、进行加签后任务的审批
加签成功后,就可以按照新的流程实例进行节点任务的审批了
按照这个思路,我们开始实现动态加签的代码实现过程
1、构建新增任务节点的userTask
/**
* 加签节点操作相关
*/
public class AddNode {
/**
* @param processDefinitionId 流程定义ID
* @param processInatanceId 流程实例ID
* @param processEngine
* @param taskModelList
* @param firstNodeId 加签的源节点
* @param lastNodeId 加签的目标节点
* @param persistenceDataToDataBase 是否持久化到数据库
* @param isFire
* @param taskId 当前的任务ID
* @param targetNodeId 加签的节点ID
*/
public void addUserTask(String processDefinitionId, String processInatanceId, ProcessEngine processEngine,
List taskModelList, String firstNodeId, String lastNodeId, boolean persistenceDataToDataBase,
boolean isFire, String taskId, String targetNodeId) {
ManagementService managementService = processEngine.getManagementService();
managementService.executeCommand(new GetProcessCmd(processDefinitionId));
//确保这里一定可以从缓存中拿到流程定义相关信息
ProcessDefinitionCacheEntry processDefinitionCacheEntry = managementService.executeCommand(new GetProcessDefinitionCacheEntryCmd(processDefinitionId));
Process process = processDefinitionCacheEntry.getProcess();
List userTasks = new ArrayList<>(taskModelList.size());
// 批量生成任务,将用户自定义的相关流程信息转为activity的userTask
for (TaskModel taskModel : taskModelList) {
UserTask userTask = GenerateActivityUtils.transformation(taskModel, processEngine);
userTasks.add(userTask);
process.addFlowElement(userTask);
}
for (int i = 0; i < userTasks.size(); i++) {
UserTask userTask = userTasks.get(i);
SequenceFlow sequenceFlow = null;
if (i == userTasks.size() - 1) {
//是最后一个任务节点,即你要加签的节点数量,可以加多个
sequenceFlow = GenerateActivityUtils.genarateSequenceFlow(userTask.getId() + "---->>>>" + lastNodeId, userTask.getId() + "---->>>>" + lastNodeId,
userTask.getId(), lastNodeId);
sequenceFlow.setTargetFlowElement(process.getFlowElement(lastNodeId));
userTask.setOutgoingFlows(Arrays.asList(sequenceFlow));
} else {
//不是最后一个节点
sequenceFlow = GenerateActivityUtils.genarateSequenceFlow(userTask.getId() + "--->>>" + userTasks.get(i + 1).getId(), userTask.getId() + "--->>>" + userTasks.get(i + 1).getId(), userTask.getId(),
userTasks.get(i + 1).getId());
sequenceFlow.setTargetFlowElement(userTasks.get(i + 1));
userTask.setOutgoingFlows(Arrays.asList(sequenceFlow));
}
//将连线信息添加到process
process.addFlowElement(sequenceFlow);
}
//更新缓存,确保缓存中存储的是最新的数据
processDefinitionCacheEntry.setProcess(process);
if (isFire) {
managementService.executeCommand(new JumpNodeCmd(taskId, targetNodeId));
}
if (persistenceDataToDataBase) {
persistenceDataToDataBase(processDefinitionId, processInatanceId, firstNodeId, lastNodeId, taskModelList, processEngine);
}
}
/**
* 将加签的任务节存储到数据库中
*
* @param processDefinitionId
* @param processInatanceId
* @param firstNodeId
* @param lastNodeId
* @param taskModelList
* @param processEngine
*/
private void persistenceDataToDataBase(String processDefinitionId, String processInatanceId, String firstNodeId,
String lastNodeId, List taskModelList, ProcessEngine processEngine) {
ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) processEngine.getProcessEngineConfiguration();
SqlSessionFactory sqlSessionFactory = processEngineConfiguration.getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
TempActivityModel tempActivityModel = new TempActivityModel();
tempActivityModel.setFirst(firstNodeId);
tempActivityModel.setLast(lastNodeId);
tempActivityModel.setActivity(taskModelList);
StringBuffer sb = new StringBuffer();
for (TaskModel taskModel : taskModelList) {
sb.append(taskModel.getId() + ",");
}
tempActivityModel.setActivityIds(sb.toString());
CreationMapper cm = sqlSession.getMapper(CreationMapper.class);
ActCreation actCreation = new ActCreation();
actCreation.setProcessInstanceId(processInatanceId);
actCreation.setProcessDefinitionId(processDefinitionId);
actCreation.setProcessText(JSON.toJSONString(tempActivityModel));
cm.insert(actCreation);
sqlSession.commit();
sqlSession.close();
}
}
上述代码用到的GetProcessDefinitionCacheEntryCmd
public class GetProcessDefinitionCacheEntryCmd implements Command{
String processDefinitionId;
public GetProcessDefinitionCacheEntryCmd(String processDefinitionId) {
this.processDefinitionId = processDefinitionId;
}
@Override
public ProcessDefinitionCacheEntry execute(CommandContext commandContext) {
DeploymentManager deploymentManager = commandContext.getProcessEngineConfiguration().getDeploymentManager();
ProcessDefinitionCacheEntry processDefinitionCacheEntry = deploymentManager.getProcessDefinitionCache().get(processDefinitionId);
return processDefinitionCacheEntry;
}
}
用到的节点转换工具类:
public class GenerateActivityUtils {
/**
* 生成连线
*
* @param id
* @param name
* @param source
* @param target
* @return
*/
public static SequenceFlow genarateSequenceFlow(String id, String name, String source, String target) {
SequenceFlow sequenceFlow = new SequenceFlow();
sequenceFlow.setId(id);
sequenceFlow.setName(name);
sequenceFlow.setSourceRef(source);
sequenceFlow.setTargetRef(target);
return sequenceFlow;
}
/**
* 生成任务节点
*
* @param id
* @param name
* @param assignee
* @param processEngine
* @return
*/
public static UserTask generateUserTask(String id, String name, String assignee, ProcessEngine processEngine) {
UserTask userTask = new UserTask();
userTask.setId(id);
userTask.setName(name);
userTask.setAssignee(assignee);
userTask.setBehavior(createUserTaskBehvior(userTask, processEngine));
return userTask;
}
/**
* 生成自定义的任务模型
* @param id
* @param name
* @param assignee
* @return
*/
public static TaskModel generateTaskModel(String id, String name, String assignee) {
TaskModel taskModel=new TaskModel();
taskModel.setId(id);
taskModel.setName(name);
taskModel.setDoUserId(assignee);
return taskModel;
}
/**
* 将自定义的TaskModel转换为UserTask;
* @param taskModel
* @param processEngine
* @return
*/
public static UserTask transformation(TaskModel taskModel,ProcessEngine processEngine) {
UserTask generateUserTask = generateUserTask(taskModel.getId(), taskModel.getName(), taskModel.getDoUserId(), processEngine);
return generateUserTask;
}
/**
* 生成任务行为类
*
* @param userTask
* @param processEngine
* @return
*/
private static UserTaskActivityBehavior createUserTaskBehvior(UserTask userTask, ProcessEngine processEngine) {
ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) processEngine
.getProcessEngineConfiguration();
ActivityBehaviorFactory activityBehaviorFactory = processEngineConfiguration.getActivityBehaviorFactory();
UserTaskActivityBehavior userTaskActivityBehavior = activityBehaviorFactory
.createUserTaskActivityBehavior(userTask);
return userTaskActivityBehavior;
}
}
JumpNodeCmd
public class JumpNodeCmd implements Command {
private String taskId;
private String targetNodeId;
public JumpNodeCmd(String taskId, String targetNodeId) {
this.taskId = taskId;
this.targetNodeId = targetNodeId;
}
public Void execute(CommandContext commandContext) {
ActivitiEngineAgenda agenda = commandContext.getAgenda();
TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
TaskEntity te = taskEntityManager.findById(taskId);
//执行实例ID
String executionId = te.getExecutionId();
String processDefinitionId = te.getProcessDefinitionId();
ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
HistoryManager historyManager = commandContext.getHistoryManager();
//执行实例实例对象
ExecutionEntity ee = executionEntityManager.findById(executionId);
Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);
FlowElement flowElement = process.getFlowElement(targetNodeId);
if (flowElement == null) {
throw new RuntimeException("目标节点不存在");
}
historyManager.recordActivityEnd(ee, "jump");
ee.setCurrentFlowElement(flowElement);
agenda.planContinueProcessInCompensation(ee);
//删除当前的任务
taskEntityManager.delete(taskId);
//更新的是历史活动表
historyManager.recordTaskEnd(taskId, "jump");
return null;
}
}
2、加签代码测试
其实最重要的就是上面的这个类,下面就通过几个测试的方法来模拟一下
3、部署流程文件和启动流程
//部署
public static void deploy(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment =
repositoryService.createDeployment()
.addClasspathResource("process/demo/dynamicAddNode.bpmn").name("dynamicAddNode").deploy();
System.out.println("流程部署的id:" + deployment.getId());
System.out.println("流程部署的name:" +deployment.getName());
}
//启动流程实例
public static void runProcess(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
runtimeService.startProcessInstanceByKey("dynamicAdd");
System.out.println("流程实例启动成功");
}
public static void main(String[] args) throws Exception{
deploy();
runProcess();
}
我们在main方法中调用一下,可以看到当前任务表中,任务来到auth1节点
4、加签节点测试
按照前文所说,我们在auth1和auth2之间进行节点加签,最终代码如下
/**
* 测试新增一个任务节点
* @throws Exception
*/
public static void addUserTasks() throws Exception {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
String taskId = "2505";
TaskEntity taskEntity = (TaskEntity) taskService.createTaskQuery().taskId(taskId).singleResult();
String firstNodeId = "auth1";
String lastNodeId = "auth2";
List taskModelList = new ArrayList<>();
TaskModel generateTaskModel1 = GenerateActivityUtils.generateTaskModel("authMiddle1", "authMiddle1","authMiddle1");
TaskModel generateTaskModel2 = GenerateActivityUtils.generateTaskModel("authMiddle2", "authMiddle2","authMiddle2");
taskModelList.add(generateTaskModel1);
taskModelList.add(generateTaskModel2);
AddNode shareniuAddNode = new AddNode();
String processDefinitionId = taskEntity.getProcessDefinitionId();
String processInatanceId = taskEntity.getProcessInstanceId();
shareniuAddNode.addUserTask(processDefinitionId, processInatanceId, processEngine, taskModelList, firstNodeId,
lastNodeId, true, true, taskId, taskModelList.get(0).getId());
}
public static void main(String[] args) throws Exception{
addUserTasks();
}
在测试的过程中,为了避免出现空指针异常,我们在这里配置了一个监听器
public class MyProcessEngineLifecycleListener implements ProcessEngineLifecycleListener {
@Override
public void onProcessEngineBuilt(ProcessEngine processEngine) {
ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) processEngine .getProcessEngineConfiguration();
if(processEngineConfiguration==null){
System.out.println("null");
}
SqlSessionFactory sqlSessionFactory = processEngineConfiguration.getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
if(sqlSession ==null){
System.out.println("null");
}
List selectList = sqlSession.selectList("com.congge.addpersistant.CreationMapper.find");
for (ActCreation actCreation : selectList) {
String processDefinitionId = actCreation.getProcessDefinitionId();
String processInatanceId = actCreation.getProcessInstanceId();
String processText = actCreation.getProcessText();
TempActivityModel parseObject = JSON.parseObject(processText, TempActivityModel.class);
AddNode addNode = new AddNode();
addNode.addUserTask(processDefinitionId, processInatanceId, processEngine, parseObject.getActivity(), parseObject.getFirst(), parseObject.getLast(), false, false, null, null);
}
}
@Override
public void onProcessEngineClosed(ProcessEngine processEngine) {
}
}
然后再在activiti.cfg.xml配置一下即可
运行上述代码,测试一下是否能够成功进行加签,观察数据库的任务表,发现已经成功加签,任务节点来到authMiddle1
5、完成加签节点的任务审批
public static void completeTask() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
taskService.complete("5002");
}
public static void main(String[] args) throws Exception{
completeTask();
}
继续运行上面的代码,发现任务来到我们的第二个加签节点authMiddle2
通过上面的代码测试,可以看到不仅成功完成了加签,同时加签的节点仍然可以按照我们预期的效果进行流转
本篇到此结束,最后感谢观看!