Activiti7工作流引擎

什么是Activiti7

Activiti 下载地址: http://activiti.org/download.html
       Activiti 是一个工作流引擎, activiti 可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言(BPMN2.0)进行定义,业务系统按照预先定义的流程进行执行,实现了业务系统的业务流程由 activiti 进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
官方网站:https://www.activiti.org/ 

BPMN

 业务流程模型符号, 是由 BPMI(BusinessProcess Management Initiative)开发的一套标准的业务流程建模符号,使用 BPMN 提供的符号可以创建业务流程

例如:通过bpmn提供的符号进行规划流程图。

并且会生成对应的bpmn流程文件。上面创建的流程如下

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef">
  <process id="myLeave" name="员工请假审批流程" isExecutable="true">
    <startEvent id="sid-e0e56545-3bd7-4bdc-8f99-97532acabde7"/>
    <userTask id="sid-b2fde8af-9440-45ee-b9ce-c95f23f2f22d" name="提交请假申请" activiti:assignee="worker"/>
    <sequenceFlow id="sid-e74d066d-791f-49aa-8364-d7b2162fd5e7" sourceRef="sid-e0e56545-3bd7-4bdc-8f99-97532acabde7" targetRef="sid-b2fde8af-9440-45ee-b9ce-c95f23f2f22d"/>
    <userTask id="sid-1c34d32c-8762-4c1f-9e7d-63a996d7d13c" name="部门经理审批" activiti:assignee="manager"/>
    <sequenceFlow id="sid-d6a5a4d8-b12d-4608-8e6b-9509d9425a9a" sourceRef="sid-b2fde8af-9440-45ee-b9ce-c95f23f2f22d" targetRef="sid-1c34d32c-8762-4c1f-9e7d-63a996d7d13c"/>
    <userTask id="sid-5498d56f-7950-4a51-b073-92b86c164485" name="财务审批" activiti:assignee="finan"/>
    <sequenceFlow id="sid-becc3864-eeb3-4d95-95c6-84d476419b9b" sourceRef="sid-1c34d32c-8762-4c1f-9e7d-63a996d7d13c" targetRef="sid-5498d56f-7950-4a51-b073-92b86c164485"/>
    <endEvent id="sid-81f835a6-32a9-4711-84de-4f928701d12e"/>
    <sequenceFlow id="sid-2f0297a8-9a97-4a07-a737-645c76baed84" sourceRef="sid-5498d56f-7950-4a51-b073-92b86c164485" targetRef="sid-81f835a6-32a9-4711-84de-4f928701d12e"/>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_APAP">
    <bpmndi:BPMNPlane bpmnElement="myLeave" id="BPMNPlane_APAP">
      <bpmndi:BPMNShape id="shape-e393af7b-9214-4c5b-b930-ec29178c773b" bpmnElement="sid-e0e56545-3bd7-4bdc-8f99-97532acabde7">
        <omgdc:Bounds x="-75.74249" y="-314.97385" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-c2294974-d2fb-4ade-b837-23e88b8df48e" bpmnElement="sid-b2fde8af-9440-45ee-b9ce-c95f23f2f22d">
        <omgdc:Bounds x="-110.74249" y="-239.59384" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-b09f2f24-c3d6-47d1-8b8a-b62c6d8c7fa2" bpmnElement="sid-e74d066d-791f-49aa-8364-d7b2162fd5e7">
        <omgdi:waypoint x="-60.742493" y="-284.97385"/>
        <omgdi:waypoint x="-60.742493" y="-239.59384"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-e8cefd3c-408e-42ff-ab1c-66ee470ac548" bpmnElement="sid-1c34d32c-8762-4c1f-9e7d-63a996d7d13c">
        <omgdc:Bounds x="-110.74248" y="-122.658646" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-65146d2f-8272-4ad8-a3a1-33f2ac574d7b" bpmnElement="sid-d6a5a4d8-b12d-4608-8e6b-9509d9425a9a">
        <omgdi:waypoint x="-60.742493" y="-159.59384"/>
        <omgdi:waypoint x="-60.742477" y="-122.658646"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-5c690fa7-b8fc-46bb-8022-6b94f6206c34" bpmnElement="sid-5498d56f-7950-4a51-b073-92b86c164485">
        <omgdc:Bounds x="-110.74248" y="8.427467" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-6494fef5-307c-4cf3-91e9-a5d38f79754c" bpmnElement="sid-becc3864-eeb3-4d95-95c6-84d476419b9b">
        <omgdi:waypoint x="-60.742477" y="-42.658646"/>
        <omgdi:waypoint x="-60.742477" y="8.427467"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-127055c0-7cf5-46e5-9820-2273b4625485" bpmnElement="sid-81f835a6-32a9-4711-84de-4f928701d12e">
        <omgdc:Bounds x="89.08009" y="33.427475" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-c028109a-30d5-42f6-b1c6-a31b2c5eea49" bpmnElement="sid-2f0297a8-9a97-4a07-a737-645c76baed84">
        <omgdi:waypoint x="-10.742477" y="48.427467"/>
        <omgdi:waypoint x="89.08009" y="48.427475"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

 Activiti使用

 配置activiti依赖

      <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-engine</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-model</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-converter</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-json-converter</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-layout</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>
        <dependency>
            <groupId>org.activiti.cloud</groupId>
            <artifactId>activiti-cloud-services-api</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.28</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.14</version>
        </dependency>
        

        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>

配置log4j.properties

# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=D:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n

配置activiti.cfg.xml 

activiti需要生成数据库中的表格,所以需要配置数据源

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

<!--    使用jdbc数据源 链接数据库-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/activiti?characterEncoding=utf8" />
        <property name="username" value="root" />
        <property name="password" value="root" />
        <property name="maxActive" value="3" />
        <property name="maxIdle" value="1" />
    </bean>

<!--    processEngineConfiguration 用来创建 ProcessEngine,
在创建 ProcessEngine 时会执行数据库的操作。-->
    <bean id="processEngineConfiguration"
          class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource" />
        <!-- activiti数据库表处理策略 -->
        <property name="databaseSchemaUpdate" value="true"/>
    </bean>
</beans>

创建activiti数据库

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.junit.Test;


public class TestCreateTable {
    /**
     * 生成 activiti 的数据库表
     */
    @Test
    public void testCreateTable(){
//        //创建ProcessEngineConfiguration
//        ProcessEngineConfiguration configuration =ProcessEngineConfiguration
//                .createProcessEngineConfigurationFromResource("activiti.cfg.xml");
//        //通过ProcessEngineConfiguration创建ProcessEngine,此时会创建数据库
//        ProcessEngine processEngine =configuration.buildProcessEngine();
//        System.out.println(processEngine);

        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        System.out.println("processEngine = " + processEngine);
    }
}

指定的数据库生成25张表格 。

数据库表的命名规则
Activiti 的表都以 ACT_开头。 第二部分是表示表的用途的两个字母标识。 用途也和服务的 API 对应。

ACT_RE_*: RE表示 repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。
ACT_RU_*: RU表示 runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti 只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
ACT_HI_*: HI表示 history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。
ACT_GE_*: GE 表示 general。 通用数据, 用于不同场景下。

​​​​​​​

7大核心服务(重要)

所谓核心服务,就是通过调用服务中开放的接口,去获取自己所需要的数据,甚至改变流程

 配置插件

网上很多人都是 使用的 actiBPM 插件。但是他有很大的弊端,因为我们idea的版本的原因,很多都是不兼容的。所以博主这里使用的是其他插件,同样的非常好用。

博主这里使用 的是Activiti BPMN visualizer 插件。可以直接搜到

 插件的使用

创建bpmn文件

 生成如下文件

 右键此文件点击view

 右键出现的界面

 依次建立想要的流程,会自动再bpmn文件中生成代码 

 添加流程

流程建立成功后,将xml文件名字改成  xxx.bpmn文件,方便使用

Service 总览 

注: 红色标注为常用 service。

RepositoryService activiti 的资源管理类
RuntimeService activiti 的流程运行管理类
TaskService activiti 的任务管理类
HistoryService activiti 的历史管理类
ManagerService activiti 的引擎管理类

部署定义的流程

第一种方式:通过指定的文件名字进行上传

   /**
     * 第一种方式:根据指定的bpmn文件和png文件
     * 部署定义的流程  文件上传方式
     */
    @Test
    public void leaveDemo(){
        //启动一个流程实例
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //1.得到RepositoryService实例
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //2.使用RepositoryService进行部署
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("bpmn/Leave.bpmn") //添加bpmn资源,需要将文件名字改成当前样式。带xml是不行的
                .addClasspathResource("bpmn/Leave.myLeave.png")   //添加png资源
                .name("请假申请流程")
                .deploy();
        //获得部署信息
        System.out.println("流程部署id:"+deployment.getId());
        System.out.println("流程部署名字:"+deployment.getName());

    }

第二种方式:通过zip文件的格式上传


    /**
     * 第二种方式:通过zip压缩文件方式上传
     *
     * 将bpmn文件和对应的png打成压缩包放入
     */
    @Test
    public void deployProcessByZip(){
        //定义zip输入流
        InputStream inputStream = this
                .getClass()
                .getClassLoader()
                .getResourceAsStream("bpmn/look.zip");

        ZipInputStream zipInputStream = new ZipInputStream(inputStream);
        //获取repositoryService
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //流程部署
        Deployment deployment = repositoryService.createDeployment()
                .addZipInputStream(zipInputStream)
                .deploy();
        System.out.println("流程部署id:"+deployment.getId());
        System.out.println("流程部署名字:"+deployment.getName());
    }

执行上面的代码后,数据库中就会出现定义的流程内容

 其他表中也会对应添加数据

启动当前实例流程

    /**
     * 启动流程实例
     */
    // 启动一个流程实例
    @Test
    public void startProcessInstance() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取RunTimeService
        RuntimeService runtimeService =processEngine.getRuntimeService();
        // 根据流程定义key启动流程
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey("demo");
        System.out.println("流程定义id : " +processInstance.getProcessDefinitionId());
        System.out.println("流程实例id: " + processInstance.getId());
        System.out.println("当前活动Id : " +processInstance.getActivityId());
    }

查询当前个人待执行的任务

当启动一个实例时,就会按照流程进行下去,我们这里设置的第一个负责人就是张三。

    /**
     * 查询当前个人待执行的任务
     */
    @Test
    public void findPersonalTaskList() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 当前任务负责人
//        String assignee = "worker";
        String assignee = "张三";
        // 创建TaskService 管理任务的
        TaskService taskService = processEngine.getTaskService();

        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("demo")
                .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());
        }
    }

 完成当前流程任务

输入张三的任务id ,完成此任务。再查张三的任务时就已经不存在。而任务已经根据流程到李四的下面。

   /**
     * 完成当前任务
     */
    @Test
    public void completTask() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 创建TaskService
        TaskService taskService = processEngine.getTaskService();
        //根据流程key 和 任务负责人 查询任务

//        //返回一个任务对象
//        Task task = taskService.createTaskQuery()
//                .processDefinitionKey("myLeave")
//                .taskAssignee("manager")
//                .singleResult();
//        taskService.complete(task.getId());
//        System.out.println("完成任务id="+task.getId());
        //上面是根据负责人去处理任务,是不合适的
        // 因为有多个任务就会报错,所以直接使用当前任务的id
        String taskId = "57505";

        //完成任务
        taskService.complete(taskId);
        System.out.println("完成任务id="+taskId);
    }

查询定义的流程

 /**
     * 查询当前bpmn中有多少个流程
     * :一个bpmn可以部署多个流程,建议一次部署一个
     */
    @Test
    public void queryProcessDefinition() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 流程定义key
        String processDefinitionKey = "demo";
        // 获取repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 查询流程定义
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        //遍历查询结果
        List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKey(processDefinitionKey)
                .orderByProcessDefinitionVersion()
                .desc()
                .list();
        for (ProcessDefinition processDefinition : list) {
            System.out.println("------------------------");
            System.out.println(" 流 程 部 署 id : " +processDefinition.getDeploymentId());
            System.out.println("流程定义id: " + processDefinition.getId());
            System.out.println("流程定义名称: " + processDefinition.getName());
            System.out.println("流程定义key: " + processDefinition.getKey());
            System.out.println("流程定义版本: " + processDefinition.getVersion());//同一个名字多次部署,就会变更版本
        }
    }

查询当前流程下正在进行的内容


    /**
     * 查询当前流程下正在进行的内容
     */
    @Test
    public void queryProcessInstance() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 流程定义key
        String processDefinitionKey = "demo";
        // 获取RunTimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
        List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().processDefinitionKey(processDefinitionKey).list();
        for (ProcessInstance processInstance : list) {
            System.out.println("----------------------------");
            System.out.println("流程实例id: "+ processInstance.getProcessInstanceId());
            System.out.println("所属流程定义id: "+ processInstance.getProcessDefinitionId());
            System.out.println("是否执行完成: " + processInstance.isEnded());
            System.out.println("是否暂停: " + processInstance.isSuspended());
            System.out.println(" 当 前 活 动 标 识 : " + processInstance.getActivityId());
        }
    }

撤销流程 

    /**
     * 撤销当前流程
     */
    @Test
    public void cancel(){
        String procInstId = "103f6b25-29be-11ed-8271-48f31701aada"; //流程实例id
        String reason = "canceled-"+"我想撤销就撤销"; //撤销理由
        runtimeService.deleteProcessInstance(procInstId,reason);
        System.out.println("撤销" );
    }

删除

    /**
     * 删除定义流程,且删除时必须流程中内容必须全部审批完成
     */
    @Test
    public void deleteDeployment() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 流程部署id
        String deploymentId = "37501";
        // 通过流程引擎获取repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //删除流程定义, 如果该流程定义已有流程实例启动则删除时出错
        repositoryService.deleteDeployment(deploymentId);
        //设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除:强制删除
        //repositoryService.deleteDeployment(deploymentId, true);
    }

获取定义的流程资源

/**
     * 获取流程定义的        bpmn 和 png。
     * @throws IOException
     */
    @Test
    public void getProcessResources() throws IOException {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        // 流程定义id
        String processDefinitionId = "";
        // 获取repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 流程定义对象
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
//                .processDefinitionId(processDefinitionId)   //通过定义的id 二选一即可
                .processDefinitionKey("myLeave")     //通过定义的名字
                .singleResult();
        //获取bpmn
        String resource_bpmn = processDefinition.getResourceName();
        //获取png
        String resource_png =processDefinition.getDiagramResourceName();
        // 资源信息
        System.out.println("bpmn: " + resource_bpmn);
        System.out.println("png: " + resource_png);
        File file_png = new File("d:/myLeave.png");
        File file_bpmn = new File("d:/myLeave.bpmn");
        // 输出bpmn
        InputStream resourceAsStream = null;
        resourceAsStream = repositoryService.getResourceAsStream(
                processDefinition.getDeploymentId(), resource_bpmn);
        FileOutputStream fileOutputStream = new FileOutputStream(file_bpmn);
        byte[] b = new byte[1024];
        int len = -1;
        while ((len = resourceAsStream.read(b, 0, 1024)) != -1) {
            fileOutputStream.write(b, 0, len);
        }
        // 输出图片
        resourceAsStream = repositoryService.getResourceAsStream(
                processDefinition.getDeploymentId(), resource_png);
        fileOutputStream = new FileOutputStream(file_png);
        // byte[] b = new byte[1024];
        // int len = -1;
        while ((len = resourceAsStream.read(b, 0, 1024)) != -1) {
            fileOutputStream.write(b, 0, len);
        }
    }

查询流程历史信息


    /**
     * 查询历史信息
     * 即使流程定义已经删除了,流程执行的历史信息通过前面的分析
     * 依然保存在 activiti 的 act_hi_*相关的表中。
     * 所以我们还是可以查询流程执行的历史信息
     * 可以通过 HistoryService 来查看相关的历史记录。
     */
    @Test
    public void testHistoric01(){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        HistoryService historyService = processEngine.getHistoryService();
        HistoricActivityInstanceQuery query =
                historyService.createHistoricActivityInstanceQuery();
        query.processInstanceId("45001"); //查询某一个流程实例  可以注掉这一行就是查所有
        List<HistoricActivityInstance> list = query.list();
        for(HistoricActivityInstance ai :list){
            System.out.println(ai.getActivityId());
            System.out.println(ai.getActivityName());
            System.out.println(ai.getProcessDefinitionId());
            System.out.println(ai.getProcessInstanceId());
            System.out.println("==============================");
        }
    }

流程实例挂起与激活

    /**
     * 流程实例挂起
     * 某些情况可能由于流程变更需要将当前运行的流程暂停而不是直接删除
     * 流程暂停后将不会继续执行,也不会创建新的流程实例
     * :挂起当前流程的全部
     */
    // 挂起激活流程定义
    @Test
    public void suspendOrActivateProcessDefinition() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 流程定义id
        String processDefinitionId = "";
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 获得流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
//                .processDefinitionId(processDefinitionId)
                .processDefinitionKey("myLeave")
                .singleResult();
        //是否暂停
        boolean suspend = processDefinition.isSuspended();
        String id = processDefinition.getId();//流程定义id
        if(suspend){
            //如果暂停则激活,这里将流程定义下的所有流程实例全部激活
            repositoryService.activateProcessDefinitionById(id,true, null);
            System.out.println("流程定义id: "+id+"已激活");
        }else{
            //如果激活则挂起,这里将流程定义下的所有流程实例全部挂起
            repositoryService.suspendProcessDefinitionById(id,true, null);
            System.out.println("流程定义id: "+id+"已挂起");
        }
    }

单个流程实例的挂起与激活

   /**
     * :单个流程实例挂机
     * 操作流程实例对象,针对单个流程执行挂起操作
     * 某个流程实例挂起则此流程不再继续执行
     * 完成该流程实例的当前任务将报异常。
     */
    @Test
    public void suspendOrActiveProcessInstance() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 流程某个实例id
        String processInstanceId = "40001";
        // 获取RunTimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
        //根据流程实例id查询流程实例
        ProcessInstance processInstance =runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        boolean suspend = processInstance.isSuspended();
        if(suspend){
            //如果暂停则激活
            runtimeService.activateProcessInstanceById(processInstanceId);
            System.out.println("流程实例: "+processInstanceId+"激活");
        }else{
            //如果激活则挂起
            runtimeService.suspendProcessInstanceById(processInstanceId);
            System.out.println("流程实例: "+processInstanceId+"挂起");
        }
    }

设定变量 

流程变量在 activiti 中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和 activiti结合时少不了流程变量,流程变量就是 activiti 在管理工作流时根据管理需要而设置的变量。比如在请假流程流转时如果请假天数大于 3 天则由总经理审核否则由人事直接审核, 请假天数就可以设置为流程变量,在流程流转时使用。

注意:虽然流程变量中可以存储业务数据可以通过 activiti 的 api 查询流程变量从而实现查询业务数据,但是不建议这样使用,因为业务数据查询由业务系统负责, activiti 设置流程变量是为了流程执行需要而创建。

添加对应变量信息:大于3 老板审批

 小于3财务审批

 设定任务变量:其他相同

 其中的Holiday 只是自己随意设定的一个对象,只封装了一个参数 num。

 通过variables进行绑定,流程实例进行时,就会自动寻找设定的变量,根据变量内容往下进行

   // 启动流程时设置流程变量
    @Test
    public void startProcessInstance() {

        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 流程定义key
        String processDefinitionKey = "qqqq";
        Holiday holiday = new Holiday();
        holiday.setNum(1);//设置变量 日期
        // 定义流程变量
        Map<String, Object> variables = new HashMap<String, Object>();
        //变量名是num,变量值是holiday.getNum(),变量名也可以是一个对象
        variables.put("num", holiday.getNum());
        //设定任务的负责人
        variables.put("assignee0", "张三");
        variables.put("assignee1", "张四");
        variables.put("assignee3", "张五");
        variables.put("assignee4", "张六");
//        activiti:assignee="赵六"
        RuntimeService runtimeService = processEngine.getRuntimeService();
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey(processDefinitionKey, variables);
        System.out.println(" 流 程 实 例 id:" + processInstance.getProcessInstanceId());
    }

执行过程中更改变量内容:

map.put("assignee1",assignee);

taskService.complete(taskId,map); //放入map 会覆盖之前设定的assignee1

   /**
     * 完成当前任务
     */
    @Test
    public void completTask() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 创建TaskService
        TaskService taskService = processEngine.getTaskService();

        String taskId = "197503"; //任务id
        String assignee = "张三";  //当我完成自己的任务时,可以通过下面的方法指定下一个任务的完成人
        HashMap<String, Object> map = new HashMap<>();
//        map.put("assignee1",assignee);

        //完成任务
        taskService.complete(taskId,map); //放入map 会覆盖之前设定的assignee1
        System.out.println("完成任务id="+taskId);
    }

关联businessKey

需求:
在 activiti 实际应用时,查询流程实例列表时可能要显示出业务系统的一些相关信息,比如:查询当
前运行的请假流程列表需要将请假单名称、 请假天数等信息显示出来, 请假天数等信息在业务系统
中存在,而并没有在 activiti 数据库中存在,所以是无法通过 activiti 的 api 查询到请假天数等信息。

实现:
在查询流程实例时,通过 businessKey(业务标识 )关联查询业务系统的请假单表,查询出请假天
数等信息。
通过下面的代码就可以获取 activiti 中所对应实例保存的业务 Key。而这个业务 Key 一般都会保存相
关联的业务操作表的主键,再通过主键 ID 去查询业务信息,比如通过请假单的 ID,去查询更多的
请假信息(请假人,请假时间,请假天数,请假事由等)

启动流程的过程中,添加businessKey
      第一个参数:流程定义的key
      第二个参数:businessKey ,请假单的 id 作为业务标识存储到 activiti 中                第三个参数:变量内向信息

添加:直接在启动流程实例时进行添加,放在变量对象的前面

   // 启动流程时设置流程变量
    @Test
    public void startProcessInstance() {

        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 流程定义key
        String processDefinitionKey = "qqqq";
        String s1 = "123456789"; //请假单id
        Holiday holiday = new Holiday();
        holiday.setNum(1);//设置变量 日期
        // 定义流程变量
        Map<String, Object> variables = new HashMap<String, Object>();
        //变量名是num,变量值是holiday.getNum(),变量名也可以是一个对象
        variables.put("num", holiday.getNum());
        //设定任务的负责人
        variables.put("assignee0", "张三");
        variables.put("assignee1", "张四");
        variables.put("assignee2", "张五");
        variables.put("assignee3", "张六");
        variables.put("assignee4", "张七");
        variables.put("assignee5", "张八");
//        activiti:assignee="赵六"
        RuntimeService runtimeService = processEngine.getRuntimeService();
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey(processDefinitionKey,s1, variables);
        System.out.println(" 流 程 实 例 id:" + processInstance.getProcessInstanceId());
    }

获取:String businessKey = processInstance.getBusinessKey();  得到key以后就可以通过key查询到对应的请假单信息

  /**
     * 查询当前个人待执行的任务
     */
    @Test
    public void findPersonalTaskList() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 当前任务负责人
//        String assignee = "worker";
        String assignee = "张三";
        // 创建TaskService 管理任务的
        TaskService taskService = processEngine.getTaskService();

        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("qqqq")
                .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());
            String processInstanceId = task.getProcessInstanceId();//流程实例id
            RuntimeService runtimeService = processEngine.getRuntimeService();
            //通过流程实例id,得到流程实例对象
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(processInstanceId)
                    .singleResult();
            //根据businessKey就可以得到具体请假单的信息
            String businessKey = processInstance.getBusinessKey();
            System.out.println("businessKey = " + businessKey);
        }
    }

 Candidate-users 候选人

添加候选人,使用逗号隔开 

查看候选人可接任务:

    /**
     * 查询当前个人待执行的任务
     */
    @Test
    public void findPersonalTaskList() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 当前任务负责人
//        String assignee = "worker";
        String assignee = "张三";
        // 创建TaskService 管理任务的
        TaskService taskService = processEngine.getTaskService();

        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("myLeave")
//                .taskAssignee(assignee)//只查询该任务负责人的任务
                .taskCandidateUser(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 claimTask(){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        //要拾取的任务id
        String taskId = "220002";
        //任务候选人id
        String userId = "张三";
        //拾取任务
        //即使该用户不是候选人也能拾取(建议拾取时校验是否有资格)
        //校验该用户有没有拾取任务的资格
        Task task = taskService.createTaskQuery()//
                .taskId(taskId)
                .taskCandidateUser(userId)//根据候选人查询
                .singleResult();
        if(task!=null){
            taskService.claim(taskId, userId);
            System.out.println("任务拾取成功");
        }
    }

 退回任务

    /**
     * 归还组任务,由个人任务变为组任务,还可以进行任务交接
     */
    @Test
    public void setAssigneeToGroupTask() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 查询任务使用TaskService
        TaskService taskService = processEngine.getTaskService();
        // 当前待办任务
        String taskId = "220002";
        // 任务负责人
        String userId = "张三";
        // 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
        Task task = taskService.createTaskQuery().taskId(taskId)
                .taskAssignee(userId).singleResult();
        if (task != null) {
            // 如果设置为null,归还组任务,该 任务没有负责人
            taskService.setAssignee(taskId, null);
            System.out.println("退回任务成功");
        }
    }

 任务交接

    /**
     * 任务交接,任务负责人将任务交给其它候选人办理该任务
     */
    @Test
    public void setAssigneeToCandidateUser() {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 查询任务使用TaskService
        TaskService taskService = processEngine.getTaskService();
        // 当前待办任务
        String taskId = "220002";
        // 任务负责人
        String userId = "张三";
        // 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
        Task task = taskService.createTaskQuery().taskId(taskId)
                .taskAssignee(userId).singleResult();
        if (task != null) {
            // 将此任务交给其它候选人办理该 任务
            String candidateuser = "王五";
            
            // 才可以交接
            taskService.setAssignee(taskId, candidateuser);
            System.out.println("交接成功");
            
        }
    }

驳回审批 

驳回到上一级

  /**
     * 驳回
     * 驳回的任务id
     */
    @Test
    public void getPreOneIncomeNode() {
        String taskId = "6ef7bd04-29cc-11ed-aab9-48f31701aada";
        List<Map<String, String>> incomeNodes = new ArrayList<>();

        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        String currActivityId = task.getTaskDefinitionKey();

        // 获取当前用户任务节点
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        Process process = bpmnModel.getProcesses().get(0);

        getIncomeNodesRecur(currActivityId, incomeNodes, process, false);

        FlowNode currFlow = (FlowNode)bpmnModel.getMainProcess().getFlowElement(currActivityId);
        if (currFlow == null) {
            List<SubProcess> subProcessList = bpmnModel.getMainProcess().findFlowElementsOfType(SubProcess.class, true);
            for (SubProcess subProcess : subProcessList) {
                FlowElement flowElement = subProcess.getFlowElement(currActivityId);

                if (flowElement != null) {
                    currFlow = (FlowNode) flowElement;
                    break;
                }
            }
        }

        // 记录原活动方向
        List<SequenceFlow> oriSequenceFlows = Lists.newArrayList();
        oriSequenceFlows.addAll(currFlow.getOutgoingFlows());

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

        List<SequenceFlow> newSequenceFlows = Lists.newArrayList();

        for (int i = 0; i< incomeNodes.size(); i++) {
            Map<String, String> item = incomeNodes.get(i);
            String nodeId = item.get("id");

            // 获取目标节点
            FlowNode target = (FlowNode)bpmnModel.getFlowElement(nodeId);

            //如果不是同一个流程(子流程)不能驳回
            if (!(currFlow.getParentContainer().equals(target.getParentContainer()))) {
                continue;
            }

            // 建立新方向
            SequenceFlow newSequenceFlow = new SequenceFlow();
            String uuid = UUID.randomUUID().toString().replace("-","");
            newSequenceFlow.setId(uuid);
            newSequenceFlow.setSourceFlowElement(currFlow);// 原节点
            newSequenceFlow.setTargetFlowElement(target);// 目标节点
            newSequenceFlows.add(newSequenceFlow);
        };

        currFlow.setOutgoingFlows(newSequenceFlows);

        // 拒接、通过、驳回指定节点
        taskService.complete(taskId);

        //恢复原方向
        currFlow.setOutgoingFlows(oriSequenceFlows);
    }
    /**
     * 递归遍历获取上个任务节点
     **/
    public void getIncomeNodesRecur(String currentNodeId, List<Map<String, String>> incomeNodes, Process process, boolean isAll) {
        FlowElement currentFlowElement = process.getFlowElement(currentNodeId);
        List<SequenceFlow> incomingFlows = null;
        if (currentFlowElement instanceof UserTask) {
            incomingFlows = ((UserTask) currentFlowElement).getIncomingFlows();
        } else if (currentFlowElement instanceof Gateway) {
            incomingFlows = ((Gateway) currentFlowElement).getIncomingFlows();
        } else if (currentFlowElement instanceof StartEvent) {
            incomingFlows = ((StartEvent) currentFlowElement).getIncomingFlows();
        }
        if (incomingFlows != null && incomingFlows.size() > 0) {
            incomingFlows.forEach(incomingFlow -> {
                String expression = incomingFlow.getConditionExpression();
                // 出线的上一节点
                String sourceFlowElementID = incomingFlow.getSourceRef();
                // 查询上一节点的信息
                FlowElement preFlowElement = process.getFlowElement(sourceFlowElementID);

                //用户任务
                if (preFlowElement instanceof UserTask) {
                    Map<String,String> tempMap = new HashMap<>();
                    tempMap.put("id", preFlowElement.getId());
                    tempMap.put("name", preFlowElement.getName());
                    incomeNodes.add(tempMap);
                    if (isAll) {
                        getIncomeNodesRecur(preFlowElement.getId(), incomeNodes, process, true);
                    }
                }
                //排他网关
                else if (preFlowElement instanceof ExclusiveGateway) {
                    getIncomeNodesRecur(preFlowElement.getId(), incomeNodes, process, isAll);
                }
                //并行网关
                else if (preFlowElement instanceof ParallelGateway) {
                    getIncomeNodesRecur(preFlowElement.getId(), incomeNodes, process, isAll);
                }
            });
        }
    }

 网关

 排他网关与并行网关

排他网关(也叫异或(XOR)网关,或叫基于数据的排他网关),用来在流程中实现决策。 当流程
执行到这个网关,所有分支都会判断条件是否为 true,如果为 true 则执行该分支,注意,排他网关只会选择一个为 true 的分支执行。 (即使有两个分支条件都为 true, 排他网关也会只选择一条分支去执行)

缺点:
如果条件都不满足,不使用排他网关,流程就结束了(是异常结束)。

并行网关并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:

fork 分支:
并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
join 汇聚:
所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通
过汇聚网关。
注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。
与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。


包含网关

上下俩个执行排他网关操作(有条件的),中间的同时进行并行网关操作(无条件的)。

包含网关可以看做是排他网关和并行网关结合体。 和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。
包含网关的功能是基于进入和外出顺序流的:

分支:
所有外出顺序流的条件都会被解析,结果为 true 的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。
汇聚:
所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程 token 的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。
 

Spring整合Activiti7

spring整合了activiti,非常方便,不需要多余的依赖和其他配置。一个依赖即可。

直接整合了所用的所有service,直接调用即可

引入依赖

        <!--引入activiti的springboot启动器 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>7.1.0.M6</version>
            <!--            <exclusions>-->
            <!--                <exclusion>-->
            <!--                    <artifactId>mybatis</artifactId>-->
            <!--                    <groupId>org.mybatis</groupId>-->
            <!--                </exclusion>-->
            <!--            </exclusions>-->
        </dependency>
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.jt</groupId>
    <artifactId>testActiviti</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>
        <!--   springSecurity 依赖   -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- mysql  -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--引入activiti的springboot启动器 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>7.1.0.M6</version>
            <!--            <exclusions>-->
            <!--                <exclusion>-->
            <!--                    <artifactId>mybatis</artifactId>-->
            <!--                    <groupId>org.mybatis</groupId>-->
            <!--                </exclusion>-->
            <!--            </exclusions>-->
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


server:
  port: 8090

# Spring配置
spring:
  # activiti配置
  activiti:
    #自动更新数据库结构
    #1.flase:默认值。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
    #2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
    #3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
    #4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
    database-schema-update: true
    #activiti7默认不生成历史信息表,开启历史表
    db-history-used: true
    #记录历史等级 可配置的历史级别有none, activity, audit, full
    #none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。
    #activity:级别高于none,保存流程实例与流程行为,其他数据不保存。
    #audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。audit为history的默认值。
    #full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。
    history-level: full
    # =============================
    #自动检查、部署流程定义文件
    check-process-definitions: false
    # asyncExecutorActivate是指activiti在流程引擎启动就激活AsyncExecutor,异步:true-开启(默认)、false-关闭
    async-executor-activate: true
    #流程定义文件存放目录,要具体到某个目录
    #      process-definition-location-prefix: classpath:/processes/holliday/
    #process-definition-location-suffixes: #流程文件格式
    #  - **.bpmn20.xml
    #  - **.bpmn


  application:
    name: weixinZF
  datasource:
    #高版本驱动使用
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/activiti1?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&autoReconnect=true&allowMultiQueries=true
    #设定用户名和密码
    username: root
    password: root

  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

#SpringBoot整合Mybatis
mybatis-plus:
  #指定别名包
  type-aliases-package: com.jt.pojo
  #扫描指定路径下的映射文件
  mapper-locations: classpath:/mapper/*.xml
  #开启驼峰映射
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #sql日志
    map-underscore-to-camel-case: true
  # 一二级缓存默认开始 所以可以简化
#打印mysql日志
logging:
  level:
    com.jt.mapper: debug

 Service

spring直接封装了所有的service,直接注入使用即可

 其他使用方法依旧如故

猜你喜欢

转载自blog.csdn.net/Java_Mr_Jin/article/details/126605752