Springboot整合activiti(最详细版)

写在最前:flowable和activiti本是一家,所以有很多api和设计是一样的,流程设计器war包也可以共用,建议搜不到activiti某些资料的搜flowable的试试。

1.导包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>activiti_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.5.RELEASE</version>
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <mysql.version>5.1.29</mysql.version>
    </properties>
    <dependencies>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>7.1.0.M3.1</version>
        </dependency>
        <!--解决activiti坐标导致的security注入问题-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.3.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.新建启动类,配置如下:
注:启动类上的exclude = {}一定要有,不然会报错

package com.wanglj;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Author 955
 * @Date 2022-06-21 13:47
 * @Description
 */
@SpringBootApplication(exclude = {
        org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class,
        org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration.class
})
public class ActivitiApplication {
    public static void main(String[] args) {
        SpringApplication.run(ActivitiApplication.class, args);
    }
}

3.idea下载bpmn画图插件Activiti BPMN visualizer,安装重启idea(actiBPM插件早就不适配2021的idea了,想用可以降级idea版本或者用eclipse画,我相信没人想用这两个方法,推荐使用下文官方画图工具,Activiti BPMN visualizer这个插件只能有一些简单的功能)
Springboot整合activiti(最详细版)_第1张图片
4.在resources目录下新建processes文件夹,并点击右键选择bpmn文件新建(注意:流程名字随便取,但文件必须是xx.bpmn20.xml格式)
Springboot整合activiti(最详细版)_第2张图片
5.填写相应参数
Springboot整合activiti(最详细版)_第3张图片
6.打开新建的xml文件,点击右键打开流程图工具
Springboot整合activiti(最详细版)_第4张图片
7.画流程图(以user task为例,其他功能自行参考官网),点击右键新建开始–>Activities–>User task,注意每个步骤写好流程名称
Springboot整合activiti(最详细版)_第5张图片

Springboot整合activiti(最详细版)_第6张图片

注:流转下一步人名称得写上(这里提交请假申请-->worker,部门经理审批-->leader,财务审批-->finance)

Springboot整合activiti(最详细版)_第7张图片

拖动箭头使流程连接

Springboot整合activiti(最详细版)_第8张图片

最终效果

Springboot整合activiti(最详细版)_第9张图片

8.保存流程图到项目,右键Save to PNG,选择路径为xx.bpmn20.xml同路径
Springboot整合activiti(最详细版)_第10张图片

完整结构如图

Springboot整合activiti(最详细版)_第11张图片
9.新建activiti配置,也可使用yml方式springbean注入,这里使用xml方式配置(注:必须叫activiti.cfg.xml且必须放resources根目录下,否则需要在代码配置放置路径,内容如下)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">

        <property name="jdbcUrl" value="jdbc:mysql://xxxxxxx:3306/activiti_demo" />
        <property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver" />
        <property name="jdbcUsername" value="root" />
        <property name="jdbcPassword" value="root" />

        <property name="databaseSchemaUpdate" value="true" />

    </bean>

</beans>

10.编写测试代码

注:其中开始流程的key值为新建流程文件时的名称,如果不清楚可打开流程图面板,左键点击空白处,可显示id,对应key值

Springboot整合activiti(最详细版)_第12张图片

import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricActivityInstanceQuery;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.apache.commons.io.IOUtils;
import org.junit.Test;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.List;

/**
 * @Author 955
 * @Date 2022-06-21 15:01
 * @Description
 */
@Slf4j
public class test {
    /**
     * 初始化流程部署
     */
    @Test
    public void testDeployment() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        Deployment deployment = repositoryService.createDeployment().addClasspathResource("processes/process_955.bpmn20.xml").addClasspathResource("processes/process_955.png").name("请假申请流程").deploy();
        System.out.println("流程部署id:" + deployment.getId());
        System.out.println("流程部署名称:" + deployment.getName());
    }

    /**
     * 开始流程
     */
    @Test
    public void testStartProcess() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RuntimeService runtimeService = processEngine.getRuntimeService();
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("process_955");

        System.out.println("流程部署id:" + processInstance.getId());
        System.out.println("流程部署名称:" + processInstance.getName());
        System.out.println("processInstanceId:" + processInstance.getProcessInstanceId());
    }

    /**
     * 查询流转到该所属角色的任务
     */
    @Test
    public void testFindPersonalTaskList() {
    	//对应各流程节点流转下一步人名称,这里第一步从worker开始
    	//调用下方completTask方法可通过审批,再查询下一个名称leader,以此类推直到结束,因为流程图没有不通过的情况所以暂不考虑
        String assignee = "worker";
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        List<Task> list = taskService.createTaskQuery().processDefinitionKey("process_955").taskAssignee(assignee).list();
        for (Task task : list) {
            System.out.println("流程实例id:" + task.getProcessInstanceId());
            System.out.println("任务id:" + task.getId());
            System.out.println("任务负责人:" + task.getAssignee());
            System.out.println("任务名称:" + task.getName());
        }
    }

    /**
     * 完成任务
     */
    @Test
    public void completTask() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        //根据流程key和任务的负责人 查询任务
        //返回一个任务对象
        //对应各流程节点流转下一步人名称,这里第一步从worker开始,分别为worker-->leader-->finance
        Task task = taskService.createTaskQuery().processDefinitionKey("process_955").taskAssignee("worker").singleResult();
        //完成任务,参数:任务id
        taskService.complete(task.getId());
    }

    /**
     * 查询出当前所有的流程定义
     */
    @Test
    public void queryProcessDefinition() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        //查询出当前所有的流程定义
        List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKey("process_955").orderByProcessDefinitionVersion().desc().list();
        //输出流程定义信息
        for (ProcessDefinition processDefinition : list) {
            System.out.println("流程定义 id=" + processDefinition.getId());
            System.out.println("流程定义 name=" + processDefinition.getName());
            System.out.println("流程定义 key=" + processDefinition.getKey());
            System.out.println("流程部署id =" + processDefinition.getDeploymentId());
            System.out.println("<=========================================>");
        }
    }

    /**
     * 删除流程
     */
    @Test
    public void deleteDeployment(){
        String deploymentId = "1";
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //通过流程引擎获取repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //删除流程定义,如果该流程定义已有流程实例启动则删除报错
        repositoryService.deleteDeployment(deploymentId);
        //设置为true,则有流程实例在启动也可以强制删除
//        repositoryService.deleteDeployment(deploymentId,true);
    }

    /**
     * 输出流程文件和流程图到文件夹
     * @throws Exception
     */
    @Test
    public void queryBpmnFile()throws Exception{
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey("process_955").singleResult();
        //通过流程定义信息,得到部署id
        String deploymentId = processDefinition.getDeploymentId();
        //得到png图片流
        InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
        //得到bpmn文件流
        InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
        File file_png = new File("C:\\Users\\HYGK\\Desktop\\bpmn\\process_955.png");
        File file_bpmn = new File("C:\\Users\\HYGK\\Desktop\\bpmn\\process_955.bpmn");
        FileOutputStream pngOut = new FileOutputStream(file_png);
        FileOutputStream bpmnOut = new FileOutputStream(file_bpmn);
        IOUtils.copy(pngInput,pngOut);
        IOUtils.copy(bpmnInput,bpmnOut);
        pngOut.close();
        bpmnOut.close();
    }

    /**
     * 根据instanceId查询整个流程
     */
    @Test
    public void findHistoryInfo(){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        HistoryService historyService = processEngine.getHistoryService();
        //获取actinst表的查询对象
        HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
        //根据instanceId查询整个流程
        instanceQuery.processInstanceId("5001").orderByHistoricActivityInstanceStartTime().asc();
        List<HistoricActivityInstance> list = instanceQuery.list();
        for (HistoricActivityInstance historicActivityInstance : list) {
            System.out.println(historicActivityInstance.getActivityId());
            System.out.println(historicActivityInstance.getActivityName());
            System.out.println(historicActivityInstance.getProcessDefinitionId());
            System.out.println(historicActivityInstance.getCalledProcessInstanceId());
            System.out.println("<=========================================>");
        }
    }

}

==============================================================================
进阶使用:activiti-adit使用
要使用监听器最好使用其他的画图工具,idea的画图工具支持不是很好,这里推荐官方的工具
https://download.csdn.net/download/m0_49605579/87255395
(0积分下载即可,资源内含3个war包,需要放到tomcat的webapps目录下,运行tomcat服务即可)

Springboot整合activiti(最详细版)_第13张图片
<1>成功运行后访问http://localhost:8080/activiti-app(默认账号密码为:admin/test)Springboot整合activiti(最详细版)_第14张图片
<2>登录成功后点击Kickstart App,并新建Process
Springboot整合activiti(最详细版)_第15张图片
Springboot整合activiti(最详细版)_第16张图片
<3>填写模块名
Springboot整合activiti(最详细版)_第17张图片
<4>画流程图
Springboot整合activiti(最详细版)_第18张图片
Springboot整合activiti(最详细版)_第19张图片

注:
Execution listeners为执行监听器,可以查看执行中的一些流程信息等。
Task listeners为任务监听器,可以当任务流转到某一步骤时,如动态设置受理人等。
Assignments为指定用户/用户组,可以指定当前步骤由谁执行。

流程指向箭头:
在流程指向箭头中,可以设置el表达式来设定满足某些条件时指定流程走向,具体用法为${逻辑},此外流程箭头可以点击工具类上方拆分按钮使连线拆分,满足强迫症患者。
Springboot整合activiti(最详细版)_第20张图片
Springboot整合activiti(最详细版)_第21张图片
执行/任务监听器设定:
点击:Execution listeners/Task listeners,添加监听器,监听器配置为项目相对路径


保存,下载流程图文件:
画好流程图后,点击左上角保存按钮保存退出编辑页面,在点击此流程图,进入预览页面,点击下载按钮,保存bpmn文件到本地。

Springboot整合activiti(最详细版)_第22张图片
Springboot整合activiti(最详细版)_第23张图片

流程文件放入项目中:
将生成好的流程文件放到项目src/main/resources/processes目录下,此通过idea进入xx.bpmn20.xml文件中,点击右键选择View BPMN (Activiti) Diagram,可在idea中查看/编辑流程图,并保存png格式的流程图图片(这里想在idea中打开流程图需要下载上方的Activiti BPMN visualizer插件方可使用)。
Springboot整合activiti(最详细版)_第24张图片

<5>编写监听类(以经理审批任务监听类为例,执行监听器和任务监听器的代码区别就是分别实现ExecutionListener 和TaskListener,重写notify方法的参数DelegateTaskDelegateExecution不同):
注:想了解执行/任务监听器的设计区别,请参考这一文章:https://blog.csdn.net/yabushandaxue/article/details/119297080

package com.wanglj.listener;

import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;

/**
 * @Author 955
 * @Date 2022-10-14 14:34
 * @Description 经理审批任务监听器
 */
public class ManagerTaskListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
    //动态配置用户组
        delegateTask.addCandidateGroup("jl1,jl2,jl3");
    动态配置执行人
        delegateTask.setAssignee("s");
    }
}


/**
 * @Author 955
 * @Date 2022-10-14 10:10
 * @Description 经理审批执行监听器
 */
public class ManagerExecutionListener implements ExecutionListener {
    @Override
    public void notify(DelegateExecution delegateExecution) {
        System.out.println("经理审批监听");
    }
}


<6>提供一些service方法:
注:如果是新建项目未建表的,在springboot-test类执行此方法,配置好流程文件路径即可

    @Test
    public void testDeployment1() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        Deployment deployment = repositoryService.createDeployment().addClasspathResource("processes/leave.bpmn20.xml").addClasspathResource("processes/leave.png").name("借款申请流程").deploy();
        System.out.println("流程部署id:" + deployment.getId());
        System.out.println("流程部署名称:" + deployment.getName());
    }

(1)act业务父类

FinshBpVo
package com.wanglj.vo;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.Map;

/**
 * @Author 955
 * @Date 2022-10-14 11:15
 * @Description
 */
@Data
public class FinshBpVo implements Serializable {
    @ApiModelProperty(value = "任务id")
    private String taskId;

    @ApiModelProperty(value = "下一步需要的参数,可为空")
    private Map<String, Object> variables;

    @ApiModelProperty(value = "用户")
    private String userId;

    @ApiModelProperty(value = "完成时的批注内容")
    private String comment;
}

package com.wanglj.service.impl;

import com.wanglj.util.R;
import com.wanglj.vo.EndProcessVo;
import com.wanglj.vo.StartProcessInstanceVo;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.engine.*;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * act业务父类
 * @Author 955
 * @Date 2022-07-04 10:21
 * @Description
 */
@Service
public class BaseActService {
    @Autowired
    protected ManagementService managementService;
    @Autowired
    protected TaskService taskService;
    @Autowired
    protected RuntimeService runtimeService;
    @Autowired
    protected RepositoryService repositoryService;
    @Autowired
    protected HistoryService historyService;

    /**
     * 初始化流程部署
     */
    public void testDeployment() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        Deployment deployment = repositoryService.createDeployment().addClasspathResource("processes/leave.bpmn20.xml").addClasspathResource("processes/leave.png").name("借款申请流程").deploy();
        System.out.println("流程部署id:" + deployment.getId());
        System.out.println("流程部署名称:" + deployment.getName());
    }


    public R<String> startProcessInstance(StartProcessInstanceVo vo) {
        testDeployment();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(vo.getProcessDefinitionKey())
                .latestVersion().singleResult();
        if (processDefinition != null && processDefinition.isSuspended()) {
            return R.fail("该流程已存在");
        }
        ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder()
                .processDefinitionKey(vo.getProcessDefinitionKey().trim())
                .businessKey(vo.getBusinessKey().trim())
                .variables(vo.getVariables())
                .start();

        String processInstanceId = processInstance.getProcessInstanceId();
        return R.ok(processInstanceId);
    }


    public R<String> stopProcessInstanceById(EndProcessVo endVo) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(endVo.getProcessInstanceId()).singleResult();
        if (processInstance != null) {
            //1、添加审批记录
//            flowableCommentService.addComment(null,endVo.getUserId(),endVo.getUserName(),endVo.getDeptId(), endVo.getProcessInstanceId(), TaskStatusEnum.QZOVER.getStatus(),
//                    endVo.getMessage());
//            List endNodes = flowableBpmnModelService.findEndFlowElement(processInstance.getProcessDefinitionId());
//            String endId = endNodes.get(0).getId();
            String processInstanceId = endVo.getProcessInstanceId();
            //2、执行终止
            List<Execution> executions = runtimeService.createExecutionQuery().parentId(processInstanceId).list();
            List<String> executionIds = new ArrayList<>();
            executions.forEach(execution -> executionIds.add(execution.getId()));
//            this.moveExecutionsToSingleActivityId(executionIds, endId);
            return R.ok("终止成功!");
        } else {
            return R.fail("不存在运行的流程实例,请确认!");
        }

    }


    public List<FlowNode> findFlowNodes(String processDefId) {
        List<FlowNode> flowNodes = new ArrayList<>();
        BpmnModel bpmnModel = this.getBpmnModelByProcessDefId(processDefId);
        org.activiti.bpmn.model.Process process = bpmnModel.getMainProcess();
        Collection<FlowElement> list = process.getFlowElements();
        list.forEach(flowElement -> {
            if (flowElement instanceof FlowNode) {
                flowNodes.add((FlowNode) flowElement);
            }
        });
        return flowNodes;
    }

    public BpmnModel getBpmnModelByProcessDefId(String processDefId) {
        return repositoryService.getBpmnModel(processDefId);
    }


    public void testFindPersonalTaskList() {
        String assignee = "xmfzr1";
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
//        List list = taskService.createTaskQuery().processDefinitionKey("order_ex_flow").taskAssignee(assignee).list();
        List<Task> list = taskService.createTaskQuery().processDefinitionKey("order_ex_flow").taskCandidateUser(assignee).list();
        for (Task task : list) {
            System.out.println("流程实例id:" + task.getProcessInstanceId());
            System.out.println("ProcessDefinitionId:" + task.getProcessDefinitionId());
            System.out.println("任务id:" + task.getId());
            System.out.println("任务负责人:" + task.getAssignee());
            System.out.println("任务名称:" + task.getName());
            System.out.println("任务名称:" + task.getCreateTime());
        }
    }

}

(2)具体业务实现类:

package com.wanglj.service.impl;

import com.wanglj.service.ActivitiService;
import com.wanglj.util.Kit;
import com.wanglj.util.R;
import com.wanglj.vo.FinshBpVo;
import com.wanglj.vo.StartProcessInstanceVo;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.activiti.engine.ActivitiTaskAlreadyClaimedException;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricActivityInstanceQuery;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.task.Comment;
import org.activiti.engine.task.Task;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestParam;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @Author 955
 * @Date 2022-10-09 15:38
 * @Description
 */
@Service
@Slf4j
public class ActivitiServiceImpl extends BaseActService implements ActivitiService {

    @Override
    public R<String> startApplyProcess(StartProcessInstanceVo vo) {
        return startProcessInstance(vo);
    }

    /**
     * 获取最新版本的所有的流程定义列表
     *
     * @return
     */
    @Override
    public List<ProcessDefinition> getLastVersionProcessDefinitions() {
        //我们需要的是最新的版本
        return repositoryService.createProcessDefinitionQuery().latestVersion().list();
    }

    /**
     * 根据流程定义id 获取到对应的模板节点信息 节点名称,节点id,监听事件也可以获取
     *
     * @param processDefinitionId
     * @return
     */
    @Override
    public List<FlowElement> getFlowElementByProcessDefId(String processDefinitionId) {

        log.info("根据流程定义获取节点信息入参:{}", processDefinitionId);
        //流程定义中的模板信息
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);

        //模板中的所有流程
        List<Process> processes = bpmnModel.getProcesses();
        log.info("流程定义id:{}的流程条数大小:{}", processDefinitionId, processes.size());

        //取得流程的节点包括 连线实体
        Process process = processes.get(0);

        //把所有节点 用map装好。
        Map<String, FlowElement> flowElements = process.getFlowElementMap();
        List<FlowElement> flowElementList = new ArrayList<>();
        if (CollectionUtils.isEmpty(flowElements)) {
            return flowElementList;
        }
        //转换一下 成为list
        for (Map.Entry<String, FlowElement> flowElement : flowElements.entrySet()) {
            flowElementList.add(flowElement.getValue());
        }

        return flowElementList;
    }


    /**
     * 根据用户id 或者该用户的所有 有权限审核的流程task
     *
     * @param userId
     * @return
     */
    @Override
    public List<Task> getAuthorityTaskByUserId(String userId) {
        return taskService
                .createTaskQuery()
                .taskCandidateUser(userId)
                .taskUnassigned()
                .list();
    }

    /**
     * 用户认领task,如果该用户有权限并且该task未被认领
     *
     * @param taskId
     * @param userId
     */
    @Override
    public R<Boolean> claimTaskByUserId(String taskId, String userId) {
        try {
            taskService.claim(taskId, userId);
        } catch (ActivitiTaskAlreadyClaimedException e) {
            log.info("该任务已经被认领taskId:{},userId:{}", taskId, userId);
            R.fail("该任务已经被其他的人认领");
        } catch (Exception e) {
            log.info("无权限认领该任务:{},userId:{}", taskId, userId);
        }
        return R.ok(true);
    }

    /**
     * 根据用户id 获取需要立即审核的流程task
     *
     * @param userId
     * @return
     */
    @Override
    public List<Task> getAssignedTaskByUserId(String userId) {
        return taskService.createTaskQuery().taskAssignee(userId).list();
    }

    /**
     * 完成当前节点 进入到下一步。
     *
     * @param taskId finshBpVo
     * @throws Exception
     */
    @Override
    public R<Boolean> completeTask(FinshBpVo finshBpVo) {
        if (Kit.isNotEmpty(finshBpVo)) {
            String taskId = finshBpVo.getTaskId();
            String comment = finshBpVo.getComment();
            String userId = finshBpVo.getUserId();
            Map<String, Object> variables = finshBpVo.getVariables();
            Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
            if (Objects.isNull(task)) {
                log.info("该任务taskId:{},已被完成或者不存在", taskId);
                return R.fail("任务已被完成或者不存在");
            }
            if (Objects.nonNull(task) && !userId.equals(task.getAssignee())) {
                log.info("该用户没有权限完成处理该任务taskId:{},userId:{},yUserId:{}", taskId, userId, task.getAssignee());
                return R.fail("该用户没有权限完成处理该任务");
            }
            //如果批注信息不为空,新增批注信息内容
            if (StringUtils.isNotBlank(comment)) {
                taskService.addComment(taskId, task.getProcessInstanceId(), comment);
            }
            taskService.complete(taskId, variables);
            return R.ok(true);
        }
        return R.ok(false);
    }

    /**
     * 根据实例id 获取已经完成的节点
     *
     * @param processInstanceId
     * @return
     */
    @Override
    public List<HistoricTaskInstance> getHistoryTaskByProcessInstanceId(String processInstanceId) {
        return historyService
                .createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId)
                .finished().orderByTaskCreateTime().desc().list();
    }

    /**
     * 根据用户id获取到 用户处理过的所有任务信息
     *
     * @param userId
     * @return
     */
    @Override
    public List<HistoricTaskInstance> getHistoryTaskByUserId(String userId) {
        return historyService
                .createHistoricTaskInstanceQuery()
                .taskAssignee(userId).finished()
                .orderByTaskCreateTime().desc().list();
    }

    /**
     * 根据流程实例id 结束任务 任何时候都可以结束
     *
     * @param processId
     * @return
     */
    @Override
    public boolean finishProcess(String processId) {
        runtimeService.deleteProcessInstance(processId, "结束");
        Task task = taskService.createTaskQuery().processInstanceId(processId).singleResult();
        if (null == task) {
            return true;
        }
        return false;
    }

    /**
     * 根据taskId 获取批注消息
     *
     * @param taskId 任务id
     * @return
     */
    @Override
    public Comment getComment(@RequestParam("taskId") String taskId) {
        return taskService.getComment(taskId);
    }

    /**
     * 根据instanceId查询整个流程
     */
    @Override
    public List<HistoricActivityInstance> findHistoryInfo(String processInstanceId) {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        HistoryService historyService = processEngine.getHistoryService();
        //获取actinst表的查询对象
        HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
        //根据instanceId查询整个流程
        instanceQuery.processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc();
        List<HistoricActivityInstance> list = instanceQuery.list();
        return list;
    }

    /**
     * 撤回或者驳回任务, 撤回是当前处理人 处理任务后后悔了,在下一个处理人还未处理的情况可以撤回
     * 驳回:当前处理人 查看任务后发现上一步处理人未处理到位,可以驳回到上一个人。
     *
     * @param processInstanceId 流程实例id
     * @param userId            当前用户id
     * @param setUserId         撤回或者驳回后的 设置用户处理人id
     * @param recallOrRebutFlag 是撤回还是驳回标识  1代表撤回 2代表驳回
     * @param comment           撤回或者驳回时的批注内容
     * @throws Exception
     */
    @Override
    public R<Boolean> cancel(String processInstanceId, String userId, String setUserId, Integer recallOrRebutFlag, String comment) {
        log.info("撤回或者驳回的任务的参数processInstanceId:{}, userId:{},serUserId:{}, recallOrRebutFlag:{}",
                processInstanceId, userId, setUserId, recallOrRebutFlag);


        //参数验证
        if (StringUtils.isBlank(processInstanceId)) {
            return R.fail("流程实例id不能为空");
        }
        if (StringUtils.isBlank(userId)) {
            return R.fail("当前用户id不能为空");
        }
        if (StringUtils.isBlank(setUserId)) {
            return R.fail("撤回或者驳回后的处理人id不能为空");
        }
        if (Objects.isNull(recallOrRebutFlag)) {
            return R.fail("撤回或这驳回类型不能为空");
        }

        //获取当前的task
        Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();

        if (task == null) {
            log.info("任务节点已完成或者未启动,无法撤回processInstanceId:{}", processInstanceId);
            return R.fail("任务节点已完成或者未启动");
        }


        //根据时间的正序 输出,最后的一个就是正在进行中的节点任务。
        List<HistoricTaskInstance> historicTaskInstances = historyService
                .createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByTaskCreateTime().asc().list();
        //撤回的应该是 正在进行中的上一个节点
        HistoricTaskInstance myTask = null;
        for (int i = 0; i < historicTaskInstances.size(); i++) {
            if (historicTaskInstances.get(i).getId().equals(task.getId()) && i > 0) {
                myTask = historicTaskInstances.get(i - 1);
                break;
            }
        }
        if (myTask == null) {
            log.info("流程实例id,{},上一步任务节点为空");
            return R.fail("上一步任务节点为空,无法撤回或驳回");
        }
        log.info("流程实例id,{}的上一个节点的id:{},节点处理人:{}", myTask.getId(), myTask.getAssignee());
        //权限校验
        if (recallOrRebutFlag.equals(1)) {

            if (!userId.equals(myTask.getAssignee())) {
                log.info("流程实例id:{},用户id:{}无权限,需要用户id为:{}处理撤回", processInstanceId, userId, myTask.getAssignee());
                return R.fail("无权限撤回");
            }
        } else if (recallOrRebutFlag.equals(2)) {
            if (!userId.equals(task.getAssignee())) {
                log.info("流程实例id:{},用户id:{}无权限,需要用户id为:{}处理驳回", processInstanceId, userId, myTask.getAssignee());
                return R.fail("无权限驳回");
            }
        } else {
            log.info("流程实例id:{},类型标识:{},不是撤回也不是驳回类型", processInstanceId, recallOrRebutFlag);
            return R.fail("类型标识无法识别");
        }

        //获取到当前节点的上一个节点的id
        String myTaskId = myTask.getId();

        String processDefinitionId = myTask.getProcessDefinitionId();

        //获取上一个节点流程定义
        ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity) repositoryService
                .createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);

        //获取到 历史的activity 节点
        List<HistoricActivityInstance> haiList = historyService
                .createHistoricActivityInstanceQuery()
                .executionId(myTask.getExecutionId())
                .finished().list();

        String myActivityId = null;
        for (HistoricActivityInstance hai : haiList) {
            if (myTaskId.equals(hai.getTaskId())) {
                myActivityId = hai.getActivityId();
                break;
            }
        }

        FlowNode myFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(myActivityId);


        Execution execution = runtimeService.createExecutionQuery().executionId(task.getExecutionId()).singleResult();
        //获取当前节点的 活动节点
        String activityId = execution.getActivityId();
        log.info("流程实例id:{},需要撤回的活动id:{},当前活动id:{}", processInstanceId, myActivityId, activityId);
        //获取当前的 node
        FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityId);

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

        //清理当前的活动方向
        flowNode.getOutgoingFlows().clear();
        //建立新的方向
        List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
        SequenceFlow newSequenceFlow = new SequenceFlow();
        newSequenceFlow.setId("newSequenceFlowId");
        newSequenceFlow.setSourceFlowElement(flowNode);
        newSequenceFlow.setTargetFlowElement(myFlowNode);
        newSequenceFlowList.add(newSequenceFlow);
        flowNode.setOutgoingFlows(newSequenceFlowList);

        taskService.addComment(task.getId(), task.getProcessInstanceId(), comment);
        taskService.complete(task.getId());

        //设置回原来的 方向
        flowNode.setOutgoingFlows(oriSequenceFlows);
        //再次获取到当前的 活动节点就已经是 撤销后的节点了
        Task cuTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        //设置受理人
        taskService.setAssignee(cuTask.getId(), setUserId);
        return R.ok(true);
    }

}

<7>swagger测试:
1.启动流程:
注:其他几个参数都不重要,processDefinitionKey参数一定得填对,值为流程图创建时输入的model_key,执行成功会返回processInstanceId

Springboot整合activiti(最详细版)_第25张图片
2.根据processInstanceId查询任务进度
Springboot整合activiti(最详细版)_第26张图片
3.完成当前节点,进入下一步
注:comment参数为批注,可不填,taskId为第2步任务进度最后一步的taskid,userId为流程图设置的userid,variables参数为流程指向线的条件等

Springboot整合activiti(最详细版)_第27张图片
4.其他业务方法自行研究即可

项目整体结构
Springboot整合activiti(最详细版)_第28张图片
一些工具类和返回实体:

返回实体:


import java.io.Serializable;
import java.util.List;

import com.xxxx.common.core.constant.CommonConstants;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * 响应信息体
* @author:955 
* @date:2022年5月9日 下午9:53:56    
*/
@Builder
@ToString
@Accessors(chain = true)
@ApiModel(description = "响应信息主体")
@AllArgsConstructor
public class R<T> implements Serializable
{
	private static final long serialVersionUID = 1L;

	//@Builder.Default
	@Getter
	@Setter
	@ApiModelProperty(value = "返回标记:成功标记=0,失败标记=1")
	private int code = CommonConstants.SUCCESS;

	//@Builder.Default
	@Getter
	@Setter
	@ApiModelProperty(value = "返回信息")
	private String msg = "success";
	
	
	
	@Getter
	@Setter
	@ApiModelProperty(value = "返回错误集合信息")
	private List<ErrorMessage> errorMessage;

	@Getter
	@Setter
	@ApiModelProperty(value = "数据")
	private T data;
	
	@Getter
	@Setter
	@ApiModelProperty(value = "是否成功")
	private boolean success;

	public R()
	{
		super();
	}

	public R(T data)
	{
		super();
		this.data = data;
	}

	public R(T data, String msg)
	{
		super();
		this.data = data;
		this.msg = msg;
	}

	public R(Throwable e)
	{
		super();
		this.msg = e.getMessage();
		this.code = CommonConstants.FAIL;
	}

	public static <T> R<T> ok()
	{
		return R.restResult(null, CommonConstants.SUCCESS, null);
	}

	public static <T> R<T> ok(T data)
	{
		return R.restResult(data, CommonConstants.SUCCESS, null);
	}

	public static <T> R<T> ok(T data, String msg)
	{
		return R.restResult(data, CommonConstants.SUCCESS, msg);
	}

	public static <T> R<T> fail()
	{
		return R.restResult(null, CommonConstants.FAIL, null);
	}

	public static <T> R<T> fail(int code)
	{
		return R.restResult(null, code, null);
	}

	public static <T> R<T> fail(String msg)
	{
		return R.restResult(null, CommonConstants.FAIL, msg);
	}

	public static <T> R<T> fail(int code, String msg)
	{
		return R.restResult(null, code, msg);
	}
	
	public static <T> R<T> fail(List<ErrorMessage> errorMessages)
	{
		return R.restErrorMessageResult(null, CommonConstants.FAIL, errorMessages);
	}

	public static <T> R<T> fail(T data)
	{
		return R.restResult(data, CommonConstants.FAIL, null);
	}

	public static <T> R<T> fail(T data, String msg)
	{
		return R.restResult(data, CommonConstants.FAIL, msg);
	}

	private static <T> R<T> restResult(T data, int code, String msg)
	{
		R<T> apiResult = new R<>();
		apiResult.setCode(code);
		if(code==CommonConstants.SUCCESS) {
			apiResult.setSuccess(true);
		}else if(code==CommonConstants.FAIL) {
			apiResult.setSuccess(false);
		}
		apiResult.setData(data);
		apiResult.setMsg(msg);
		return apiResult;
	}
	
	
	private static <T> R<T> restErrorMessageResult(T data, int code, List<ErrorMessage> errorMessages)
	{
		R<T> apiResult = new R<>();
		apiResult.setCode(code);
		if(code==CommonConstants.SUCCESS) {
			apiResult.setSuccess(true);
		}else if(code==CommonConstants.FAIL) {
			apiResult.setSuccess(false);
		}
		apiResult.setData(data);
		apiResult.setErrorMessage(errorMessages);
		return apiResult;
	}

	/**
	 * @param localizedMessage
	 * @return
	 */
	public static R<?> failed(String localizedMessage) {
		return R.restResult(null, CommonConstants.FAIL, localizedMessage);
	}
}


错误实体:

@Builder
@Data
@ApiModel(description = "响应错误列表")
@AllArgsConstructor
public class ErrorMessage implements Serializable {


    /**
     *
     */
    private static final long serialVersionUID = 1L;

    private String field;

    private String message;

}

公共属性抽象类:

public interface CommonConstants
{
	/**
	 * 编码
	 */
	String UTF8 = "UTF-8";


	/**
	 * 成功标记
	 */
	Integer SUCCESS = 0;

	/**
	 * 失败标记
	 */
	Integer FAIL = 1;
	
	String AC_TASK_MSG ="msg";

	
	String AC_TASK_USER= "taskUser";
	
	
	String AC_TASK_USERNAME= "taskUserName";
	

	String AC_TASK_STATUS= "taskStatus";

	String ACT_TASK_FLAG = "flag";

	String AC_TASK_DEPT = "taskDept";
	
	
	String AC_AGENT = "agent";
	
	
	String AC_TASK_ID = "taskUnId";
	
	
	

	
	
}

工具类:

package com.txqc.mlk.util;

import org.apache.commons.lang3.time.DateUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DateUtil;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 工具方法类
 */
public class Kit {
    /**
     * 15位身份证号
     */
    private static final Integer FIFTEEN_ID_CARD = 15;
    /**
     * 18位身份证号
     */
    private static final Integer EIGHTEEN_ID_CARD = 18;
    private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");


    public static boolean isValidCard18(String idcard) {
        if (EIGHTEEN_ID_CARD != idcard.length()) {
            return false;
        }
        return true;
    }

    /**
     * 验证15位身份编码是否合法
     *
     * @param idcard 身份编码
     * @return 是否合法
     */
    public static boolean isValidCard15(String idcard) {
        if (FIFTEEN_ID_CARD != idcard.length()) {
            return false;
        }
        return true;
    }

    private static <T> void setFieldValue(T rowData, Field field, Object value) throws IllegalAccessException {

        if (field.getType() == int.class || field.getType() == Integer.class) {
            field.set(rowData, value);
        } else if (field.getType() == long.class || field.getType() == Long.class) {
            field.set(rowData, value);
        } else if (field.getType() == double.class || field.getType() == Double.class) {
            field.set(rowData, value);
        } else if (field.getType() == String.class) {
            field.set(rowData, String.valueOf(value));
        } else if (field.getType() == LocalDateTime.class) {
            field.set(rowData, LocalDateTime.parse(String.valueOf(value), dateTimeFormatter));
        }
    }

    private static Object getCellValue(Cell cell) {
        CellType cellType = cell.getCellType();
        Object cellValue = null;

        if (cellType == CellType._NONE) {

        } else if (cellType == CellType.NUMERIC) {
            // 数值型
            if (DateUtil.isCellDateFormatted(cell)) {
                // 日期类型
                Date d = cell.getDateCellValue();
                cellValue = dateTimeFormatter.format(LocalDateTime.ofInstant(d.toInstant(), ZoneId.systemDefault()));
            } else {
                double numericCellValue = cell.getNumericCellValue();
                BigDecimal bdVal = new BigDecimal(numericCellValue);
                if ((bdVal + ".0").equals(Double.toString(numericCellValue))) {
                    // 整型
                    cellValue = bdVal;
                } else if (String.valueOf(numericCellValue).contains("E10")) {
                    // 科学记数法
                    cellValue = new BigDecimal(numericCellValue).toPlainString();
                } else {
                    // 浮点型
                    cellValue = numericCellValue;
                }
            }
        } else if (cellType == CellType.STRING) {
            // 字符串型
            cellValue = cell.getStringCellValue();
        } else if (cellType == CellType.FORMULA) {
            // 公式型
        } else if (cellType == CellType.BLANK) {
            // 空值
        } else if (cellType == CellType.BOOLEAN) {
            // 布尔型
            cellValue = cell.getBooleanCellValue();
        } else if (cellType == CellType.ERROR) {
            // 错误
            cellValue = cell.getErrorCellValue();
        }
        return cellValue;
    }


    /**
     * 根据身份编号获取年龄
     *
     * @param idCard 身份编号
     * @return 年龄
     */
    public static int getAgeByIdCard(String idCard) {
        int iAge = 0;
        Calendar cal = Calendar.getInstance();
        String year = idCard.substring(6, 10);
        int iCurrYear = cal.get(Calendar.YEAR);
        iAge = iCurrYear - Integer.valueOf(year);
        return iAge;
    }

    /**
     * 获取出生日期  yyyy年MM月dd日
     *
     * @param IDCard
     * @return
     */
    public static String getBirthByIdCard(String IDCard) {
        String birthday = "";
        String year = "";
        String month = "";
        String day = "";
        if (isNotEmpty(IDCard)) {
            //15位身份证号
            if (IDCard.length() == FIFTEEN_ID_CARD) {
                // 身份证上的年份(15位身份证为1980年前的)
                year = "19" + IDCard.substring(6, 8);
                //身份证上的月份
                month = IDCard.substring(8, 10);
                //身份证上的日期
                day = IDCard.substring(10, 12);
                //18位身份证号
            } else if (IDCard.length() == EIGHTEEN_ID_CARD) {
                // 身份证上的年份
                year = IDCard.substring(6).substring(0, 4);
                // 身份证上的月份
                month = IDCard.substring(10).substring(0, 2);
                //身份证上的日期
                day = IDCard.substring(12).substring(0, 2);
            }
            birthday = year + "-" + month + "-" + day;
        }
        return birthday;
    }

    /**
     * 根据身份编号获取生日年
     *
     * @param idCard 身份编号
     * @return 生日(yyyy)
     */
    public static Short getYearByIdCard(String idCard) {
        return Short.valueOf(idCard.substring(6, 10));
    }

    /**
     * 根据身份编号获取生日月
     *
     * @param idCard 身份编号
     * @return 生日(MM)
     */
    public static Short getMonthByIdCard(String idCard) {
        return Short.valueOf(idCard.substring(10, 12));
    }

    /**
     * 根据身份编号获取生日天
     *
     * @param idCard 身份编号
     * @return 生日(dd)
     */
    public static Short getDateByIdCard(String idCard) {
        return Short.valueOf(idCard.substring(12, 14));
    }

    /**
     * 根据身份编号获取性别
     *
     * @param idCard 身份编号
     * @return 性别(M - 男 , F - 女 , N - 未知)
     */
    public static String getGenderByIdCard(String idCard) {
        String sGender = "未知";

        String sCardNum = idCard.substring(16, 17);
        if (Integer.parseInt(sCardNum) % 2 != 0) {
            sGender = "1";//男
        } else {
            sGender = "2";//女
        }
        return sGender;
    }

    /**
     * 去掉null
     *
     * @param source
     * @return
     */
    public static String[] getNullPropertyNames(Object source) {
        final BeanWrapper src = new BeanWrapperImpl(source);
        java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();
        Set<String> emptyNames = new HashSet<String>();
        for (java.beans.PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue != null) {
                emptyNames.add(pd.getName());
            }
        }
        String[] result = new String[emptyNames.size()];
        return emptyNames.toArray(result);
    }

    /**
     * 判空方法
     *
     * @param obj
     * @return
     */
    public static boolean isEmpty(Object obj) {
        if (obj != null && !"".equals(obj.toString())) {
            if (obj instanceof List) {
                if (((List<?>) obj).size() == 0) {
                    return true;
                }
            }
            return false;
        }
        return true;
    }

    /**
     * 判不为空方法
     *
     * @param obj
     * @return
     */
    public static boolean isNotEmpty(Object obj) {
        if (obj != null && !"".equals(obj.toString())) {
            if (obj instanceof List) {
                if (((List<?>) obj).size() == 0) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    /**
     * 验证文件后缀是否符合要求
     *
     * @param fileType 后缀类型数组(如:xlsx/pdf/docx)
     * @param file
     * @return
     */
    public static R verifyFileLegal(String[] fileType, MultipartFile file) {
        if (isEmpty(file)) {
            return R.fail("未获取到文件");
        } else if (isNotEmpty(file)) {
            String fileName = file.getResource().getFilename();
            if (isNotEmpty(fileName) && isNotEmpty(fileType)) {
                String[] fileNames = fileName.split("\\.");
                if (isNotEmpty(fileNames) && fileNames.length > 0) {
                    if (!Arrays.asList(fileType).contains(fileNames[1])) {
                        return R.fail("文件类型不匹配");
                    }
                }
            }
        }
        return R.ok();
    }

    /**
     * String转换为LocalDateTime
     *
     * @param timeString
     * @return
     */

    public static LocalDateTime getStringToLocalDateTime(String timeString) {
        if (isNotEmpty(timeString)) {
            DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            return LocalDateTime.parse(timeString, df);
        }
        return null;
    }

    /**
     * String转换为LocalDate
     *
     * @param timeString
     * @return
     */
    public static LocalDate getStringToLocalDate(String timeString) {
        if (isNotEmpty(timeString)) {
            DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            return LocalDate.parse(timeString, df);
        }
        return null;
    }

    public static Map<String, Object> objectToMap(Object obj) throws IllegalAccessException {
        Map<String, Object> map = new HashMap<String, Object>();
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object value = field.get(obj);
            if (value == null) {
                value = "";
            }
            map.put(fieldName, value);
        }
        return map;
    }


    private static String[] parsePatterns = {"yyyy-MM-dd", "yyyy年MM月dd日",
            "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy/MM/dd",
            "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyyMMdd",
            "yyyy.MM.dd", "yyyy.MM.dd HH:mm", "yyyy.MM.dd HH:mm:ss"};

    /**
     * 校验输入的字符串是否为日期格式
     *
     * @param str
     * @return
     */
    public static boolean verifyDate(String str) {
        boolean convertSuccess = true;
        try {
            // 设置lenient为false. 否则SimpleDateFormat会比较宽松地验证日期,比如2007/02/29会被接受,并转换成2007/03/01
            DateUtils.parseDate(str, parsePatterns);
        } catch (ParseException e) {
//            e.printStackTrace();
            // 如果throw java.text.ParseException或者NullPointerException,就说明格式不对
            convertSuccess = false;
        }
        return convertSuccess;
    }

    /**
     * 求差集
     * 求List1中有的但是List2中没有的元素
     */
    public static List<String> subList2(List<String> list1, List<String> list2) {
        Map<String, String> tempMap = list2.parallelStream().collect(Collectors.toMap(Function.identity(), Function.identity(), (oldData, newData) -> newData));
        return list1.parallelStream().filter(str -> {
            return !tempMap.containsKey(str);
        }).collect(Collectors.toList());
    }

    public static boolean isBlank(String temp) {
        boolean returnValue = true;
        if (temp != null) {
            returnValue = temp.matches("\\s*");
        }

        return returnValue;
    }

    public static String nullToStr(String temp) {
        if (temp == null) {
            temp = "";
        }

        return temp;
    }
    /**
     * sql注入
     *
     * @param str
     * @return
     */
    public static boolean sqlValidate(String str) {

        str = str.toLowerCase();//统一转为小写

        String badStr = "'|and|exec|execute|insert|drop|" +

                "char|declare|sitename|net user|xp_cmdshell|or|like'|create|" +

                "table|from|grant|group_concat|column_name|" +

                "information_schema.columns|table_schema|union|where|select|delete|update|order|by|count|" +

                "chr|mid|master|truncate|";//过滤掉的sql关键字,可以手动添加

        String[] badStrs = badStr.split("\\|");

        for (int i = 0; i < badStrs.length; i++) {

            if (str.indexOf(badStrs[i]) >= 0) {

                return false;

            }

        }

        return true;

    }

    /**
     * Date转LocalDate
     *
     * @param date
     * @return
     */
    public static LocalDate date2LocalDate(Date date) {
        if (null == date) {
            return null;
        }
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    }

    /**
     * LocalDate转Date
     *
     * @param localDate
     * @return
     */
    public static Date localDate2Date(LocalDate localDate) {
        if (null == localDate) {
            return null;
        }
        ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
        return Date.from(zonedDateTime.toInstant());
    }

}

你可能感兴趣的:(spring,boot,流程图)