SpringBoot + Activiti6

  • 一、Activiti6简述
  • 1、简述
  • 2、接口
  • 3、库表
  • 二、创建BPMN业务流程模型
  • 二、整合
  • 1.POM依赖
  • 2. bpmn20.xml部署
  • 3.application.properties配置
  • 4.关闭SpringSecurity权限配置。
  • 5.调用服务
  • 5.1 接口描述
  • 5.2 接口实例
  • 6、节点跳转
  • 6.1、回退
  • 6.2、任意跳转
  • 三、问题
  • 1、Could not erite JSON:lazy loading outside command context;


一、Activiti6简述

1、简述

activiti介绍 Activiti是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理,工作流,服务协作等领域的一个开源,灵活的,易扩展的可执行流程语言框架。

2、接口

  • RepositoryService:提供一系列管理流程部署和流程定义的API。
  • RuntimeService:在流程运行时对流程实例进行管理与控制。
  • TaskService:对流程任务进行管理,例如任务提醒、任务完成和创建任务等。
  • IdentityService:提供对流程角色数据进行管理的API,这些角色数据包括用户组、用户及它们之间的关系。
  • ManagementService:提供对流程引擎进行管理和维护的服务。
  • HistoryService:对流程的历史数据进行操作,包括查询、删除这些历史数据。
  • FormService:表单服务。

3、库表

  • act_ge_ 通用数据表,ge是general的缩写
  • act_hi_ 历史数据表,hi是history的缩写,对应HistoryService接口
  • act_id_ 身份数据表,id是identity的缩写,对应IdentityService接口
  • act_re_ 流程存储表,re是repository的缩写,对应RepositoryService接口,存储流程部署和流程定义等静态数据
  • act_ru_ 运行时数据表,ru是runtime的缩写,对应RuntimeService接口和TaskService接口,存储流程实例和用户任务等动态数据

二、创建BPMN业务流程模型

bpmn编辑方式很多,最后生成的文件也是有一定差异,比如idea的actiBPM、eclipse的Designer等。
尝试几个之后,创建bpmn文件后内容标签很乱,因此采用官方Activiti提供的流程设计器应用,最终生成xml文件。提供了三个war包,分别是activiti-admin.war、activiti-app.war、activiti-rest.war。如果只是画图的话,将activiti-app.war部署到Tomcat的webapps目录,启动Tomcat即可。

springboot集成工作流activiti没有自动生成表 springboot activiti6_activiti

二、整合

SpringBoot部分就不在描述了,不论使用idea还是eclipse都一样,先直接创建一个SpringBoot项目,配置好数据库、连接池、持久层等即可。以下支展示流程引擎相关部分。

1.POM依赖

<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-spring-boot-starter-basic</artifactId>
    <version>6.0.0</version>
</dependency>

2. bpmn20.xml部署

将创建出的.bpmn20.xml文件拷贝到项目文件夹/resources/processes下,可进行自动部署。若业务要求手动,也可以不添加此步骤,开发接口直接上传文件部署也可以。

3.application.properties配置

#是否每次都更新数据库
spring.activiti.database-schema-update=true
# 自动部署验证设置:true-开启(默认)、false-关闭
spring.activiti.check-process-definitions=true
spring.activiti.process-definition-location-prefix=classpath:/processes/ *.bpmn
#保存历史数据级别设置为full最高级别,便于历史数据的追溯
spring.activiti.history-level=full
spring.activiti.db-history-used=true

4.关闭SpringSecurity权限配置。

如未引入SpringSecurity权限,则需要关闭权限校验的自动配置加载:exclude = SecurityAutoConfiguration.class。如果就是使用的这个则无需操作此步骤。

@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@MapperScan("net.cnki.mapper")
public class ActiviotiBoot6Application {
	public static void main(String[] args) {
		SpringApplication.run(ActiviotiBoot6Application.class, args);
	}
}

5.调用服务

一下服用调用过程中,尽量保持与业务脱离,进行解耦。

5.1 接口描述

/**
     * 引擎管理服务,和具体业务无关,主要是可以查询引擎配置,数据库,作业等
     */
	@Autowired
	private ManagementService managementService;

    /**
     * 仓储服务,用于管理流程仓库,例如:部署,删除,读取流程资源
     * 可以用来部署我们的流程图,还可以创建我们的流程部署查询对象,用于查询刚刚部署的流程列表,便于我们管理流程
     */
    @Autowired
    private RepositoryService repositoryService;
    /**
     * 运行时服务,可以处理所有正在运行状态的流程实例,任务等
     * 主要用来开启流程实例,一个流程实例对应多个任务,也就是多个流程节点
     */
    @Autowired
    private RuntimeService runtimeService;
    /**
     * 任务服务,用于管理,查询任务,例如:签收,办理,指派等
     * 是用来可以用来领取,完成,查询任务列表功能的
     */
    @Autowired
    private TaskService taskService;
    /**
     * 唯一服务,可以管理,查询用户,组之间的关系
     * 操作用户信息,用户分组信息等,组信息包括如部门表和职位表,可以自己建表来存储用户信息和组信息
     */
    @Autowired
    private IdentityService identityService;
    /**
     * 历史服务,可以查询所有历史数据,例如:流程实例,任务,活动,变量,附件等
     * 可以查看审批人曾经审批完成了哪些项目,审批项目总共花了多少时间,以及在哪个环节比较耗费时间等等
     */
    @Autowired
    private HistoryService historyService;

5.2 接口实例

1、流程部署,在3张表中产生数据

  • act_ge_bytearray 流程资源文件(2条数据)
  • act_re_deployment 流程部署(1条数据)
  • act_re_procdef 流程实例(1条数据)
方式一:
Deployment deployment = repositoryService.createDeployment()
                    .name(processName)//为本次部署命名
                    .addClasspathResource("processes/"+ bpmnName +".bpmn")//添加流程规则文件
                    .addClasspathResource("processes/"+ bpmnName +".png")//添加流程规则图片
                    .deploy();//部署
方式二:
Deployment deployment = repositoryService.createDeployment()
                    .name(deployName)
                    .addInputStream(fileName,ipt)
                    .deploy();//部署

2、启动流程实例
关注2张表:

  • act_ru_execution : 所有流程按照规则指定到活动节点时,都会产生一个对应的Execution
  • act_ru_task : 只针对人工任务,针对Execution做的扩展信息描述
//一般使用时不会直接记录流程过程中的各种id,而是通过绑定的业务id(businessKey)处理
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey,businessKey,variables);

3、执行流程节点
有两种情况,一是指定执行人,此时当前用户直接处理即可。二是指定角色,需要用户认领后再处理。此过程根据需要添加批注,便于记录过程中操作

//认领节点
taskService.claim(taskId,userId);
//完成节点
taskService.complete(taskId, variables);

4、添加批注
此方法有两步,先设置认证用户、再添加批注。

//设置认证用户
identityService.setAuthenticatedUserId(userId);
//添加批注
taskService.addComment(taskId,processInstanceId,message);

5、查询待处理节点
为了方便,此处使用了流程引擎的角色用户关联模块,同时包含当前流程参数的过滤条件、当前用户所属角色组以及自己所属的任务查询。
variables:启动流程实例时,对应的实例自定义参数,一般为业务相关参数。
assignee:当前用户信息,用户id或用户名均可。只要都统一就行

TaskQuery query = taskService.createTaskQuery();
variables.forEach((k,v) -> {if(!StringUtils.isEmpty(v)) query.processVariableValueEquals(k, v);});
List<Task> list = query.taskCandidateOrAssigned(assignee).includeProcessVariables().orderByTaskCreateTime().asc().list();

6、节点跳转

总有一些特别需求,不按套路出牌的。更加灵活的处理节点,还别说,这样情况不少。这是实现所需要的接口服务层。

/**
     * 仓储服务,用于管理流程仓库,例如:部署,删除,读取流程资源
     * 可以用来部署我们的流程图,还可以创建我们的流程部署查询对象,用于查询刚刚部署的流程列表,便于我们管理流程
     */
    @Autowired
    private RepositoryService repositoryService;

    /**
     * 运行时服务,可以处理所有正在运行状态的流程实例,任务等
     * 主要用来开启流程实例,一个流程实例对应多个任务,也就是多个流程节点
     */
    @Autowired
    private RuntimeService runtimeService;

    /**
     * 任务服务,用于管理,查询任务,例如:签收,办理,指派等
     * 是用来可以用来领取,完成,查询任务列表功能的
     */
    @Autowired
    private TaskService taskService;

    /**
     * 历史服务,可以查询所有历史数据,例如:流程实例,任务,活动,变量,附件等
     * 可以查看审批人曾经审批完成了哪些项目,审批项目总共花了多少时间,以及在哪个环节比较耗费时间等等
     */
    @Autowired
    private HistoryService historyService;

    /**
     * 唯一服务,可以管理,查询用户,组之间的关系
     * 操作用户信息,用户分组信息等,组信息包括如部门表和职位表,可以自己建表来存储用户信息和组信息
     */
    @Autowired
    private IdentityService identityService;

6.1、回退

回退到指定历史节点

/**
     * 回退到指定历史节点
     * @param processInstanceId 流程实例ID
     * @param targetTaskId 回跳任务ID,为null是默认选择最近的上一任务
     * @param variables 撤回者处理当前任务参数
     * @return
     */
    Boolean rollBackTargetHisNode(String processInstanceId, String targetTaskId, Map<String, Object> variables);
@Override
    public Boolean rollBackTargetHisNode(String processInstanceId, String targetTaskId, Map<String, Object> variables) {
        //获取流程实例
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (pi == null){//流程已结束
            return false;
        }
        //对应实例的任务历史节点
        List<HistoricTaskInstance> htiList = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).orderByTaskCreateTime().desc().list();
        // 跳转的之前某一节点
        HistoricTaskInstance targetTask = getTargetTask(htiList,targetTaskId);
        // list里第一条代表当前任务
        HistoricTaskInstance curTask = htiList.get(0);

        if (targetTask == null){
            return false;
        }
        targetTaskId = targetTask.getId();

        BpmnModel bpmnModel = repositoryService.getBpmnModel(targetTask.getProcessDefinitionId());

        // 得到ActivityId,只有HistoricActivityInstance对象里才有此方法
        String lastActivityId = getLastActivityId(targetTaskId,targetTask.getExecutionId());

        // 得到上个节点的信息
        FlowNode lastFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(lastActivityId);

        // 取得当前节点的信息
        Execution execution = runtimeService.createExecutionQuery().executionId(curTask.getExecutionId()).singleResult();
        FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(execution.getActivityId());

        // 记录当前节点的原活动方向
        List<SequenceFlow> oriSequenceFlows = new ArrayList<>();
        oriSequenceFlows.addAll(curFlowNode.getOutgoingFlows());

        // 清理活动方向
        curFlowNode.getOutgoingFlows().clear();

        // 建立新方向
        List<SequenceFlow> newSequenceFlow = newSequenceFlowList(curFlowNode,lastFlowNode);
        curFlowNode.setOutgoingFlows(newSequenceFlow);

        // 添加批注并完成任务
        Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        commentAndComplete(processInstanceId,task.getId(),task.getAssignee(),variables,"撤回");

        // 恢复原方向
        curFlowNode.setOutgoingFlows(oriSequenceFlows);

        Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        // 设置执行人
        if (nextTask != null) {
            taskService.setAssignee(nextTask.getId(), targetTask.getAssignee());
        }
        return true;
    }

6.2、任意跳转

可以跳转到本实例的任意节点-向前向后

/**
     * 跳转到指定节点<br>
     * 可以跳转到本实例的任意节点-向前向后
     * @param processInstanceId 实例ID
     * @param targetTaskId 任务ID,与运行时任务ID不同,为xml文件上的ID
     * @param variables 本节点处理参数
     * @return
     */
    Boolean jumpSpecifiedNode(String processInstanceId, String targetTaskId, Map<String, Object> variables);
@Override
    public Boolean jumpSpecifiedNode(String processInstanceId, String targetTaskId, Map<String, Object> variables) {
        //获取流程实例
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (pi == null){//流程已结束
            return false;
        }
        //当前任务
        Task curTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();

        BpmnModel bpmnModel = repositoryService.getBpmnModel(curTask.getProcessDefinitionId());

        //目标任务
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
        Optional<FlowElement> optional = flowElements.stream().filter(target -> targetTaskId.equals(target.getId())).findFirst();
        if (!optional.isPresent()){//无指定任务
            return false;
        }
        UserTask targetUserTask = (UserTask) optional.get();

        // 获取目标节点的信息
        FlowNode targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(targetUserTask.getId());
        // 取得当前节点的信息
        Execution execution = runtimeService.createExecutionQuery().executionId(curTask.getExecutionId()).singleResult();
        FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(execution.getActivityId());

        // 记录当前节点的原活动方向
        List<SequenceFlow> oriSequenceFlows = new ArrayList<>();
        oriSequenceFlows.addAll(curFlowNode.getOutgoingFlows());

        // 清理活动方向
        curFlowNode.getOutgoingFlows().clear();

        // 建立新方向
        List<SequenceFlow> newSequenceFlow = newSequenceFlowList(curFlowNode,targetFlowNode);
        curFlowNode.setOutgoingFlows(newSequenceFlow);

        // 添加批注并完成任务
        commentAndComplete(processInstanceId,curTask.getId(),curTask.getAssignee(),variables,"跳转");

        // 恢复原方向
        curFlowNode.setOutgoingFlows(oriSequenceFlows);

        // 设置执行人
        Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        if (nextTask != null) {
            taskService.setAssignee(nextTask.getId(), targetUserTask.getAssignee());
        }
        return true;
    }

针对上边的两个跳转,提出一些不影响查看思路的公共部分。大体思路相同。

/**
     * 添加批注并完成任务
     * @param processInstanceId
     * @param curTaskId 当前任务ID
     * @param targetTaskAssignee
     * @param variables
     * @param message
     */
    private void commentAndComplete(String processInstanceId,String curTaskId,String targetTaskAssignee,Map<String, Object> variables,String message){
        identityService.setAuthenticatedUserId(targetTaskAssignee);
        taskService.addComment(curTaskId,processInstanceId,message);
        taskService.complete(curTaskId,variables);
    }

    /**
     * 获取指定的目标历史任务
     * @param htiList 历史任务列表
     * @param targetTaskId 目标任务ID,为null是默认选择最近的上一任务
     * @return
     */
    private HistoricTaskInstance getTargetTask(List<HistoricTaskInstance> htiList,String targetTaskId){
        HistoricTaskInstance targetTask = null;
        if (targetTaskId == null){
            targetTask = htiList.get(1);
        } else {
            Optional<HistoricTaskInstance> optional = htiList.stream().filter(hisTask -> targetTaskId.equals(hisTask.getId())).findFirst();
            targetTask = optional.isPresent() ? optional.get() : null;
        }
        return targetTask;
    }

    /**
     * 新建连线
     * @param curFlowNode 当前节点
     * @param targetFlowNode 目标节点
     * @return
     */
    private List<SequenceFlow> newSequenceFlowList(FlowNode curFlowNode,FlowNode targetFlowNode) {
        List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
        SequenceFlow newSequenceFlow = new SequenceFlow();
        newSequenceFlow.setId("newJumpFlow");
        newSequenceFlow.setSourceFlowElement(curFlowNode);
        newSequenceFlow.setTargetFlowElement(targetFlowNode);
        newSequenceFlowList.add(newSequenceFlow);
        return newSequenceFlowList;
    }

    /**
     * 获取目标历史节点的活动ID
     * @param targetTaskId 目标历史节点
     * @param targetExecutionId 目标历史执行流ID
     * @return
     */
    private String getLastActivityId(String targetTaskId,String targetExecutionId){
        List<HistoricActivityInstance> haiFinishedList = historyService.createHistoricActivityInstanceQuery().executionId(targetExecutionId).finished().list();
        Optional<HistoricActivityInstance> optional = haiFinishedList.stream().filter(his -> targetTaskId.equals(his.getTaskId())).findFirst();
        String lastActivityId = optional.isPresent() ? optional.get().getActivityId() : null;
        return lastActivityId;
    }

三、问题

1、Could not erite JSON:lazy loading outside command context;

返回数据json转化异常
自己测试的时候,如果直接流程引擎相关返回的对象数据直接返回,就会有json问题,因此一般都需要自己转化一下,不要直接使用task等。