十分鐘認識Activiti6.0工作流引擎

Totoro-ing發表於2019-03-17

十分鐘認識Activiti6.0工作流引擎

概述
瞭解Activiti
假如你還不知道Activiti是什麼,那麼工作流引擎對你來說一定也是新的,解決Activiti和工作流知識盲點,所以看到到這裡就對了,還有Android同學會誤以為是“Activity”,工作流引擎Activiti和Android開發的Activity這是兩碼事兒,Activiti是一個開源的自動化業務工作流引擎,具體概念就不碎碎念介紹了,

使用工作流引擎
工作流引擎最常見用於審批流程中,現在一線網際網路公司也開始使用,並有快速推廣的趨勢,複雜繁多的業務流程如果採用if else實現那將是崩潰的,程式碼不可維護,業務流程在程式碼中可讀性很差,所以高人設計了業務流程模型圖示BPMN2.0,我們要做到就是把業務場景抽象為標準流程圖,把流程圖丟到流程引擎中按流程定義約定逐步流轉,很顯然擴充套件性和業務可描述性會好很多,所以工作流引擎主要用於解決複雜的業務,目前經常被提起的中臺系統抽象業務為服務,也涉及大量智慧的業務流程引擎做支撐。

讓工作流引擎執行
我們快速體驗一個流程的執行過程

搭建流程引擎

我們在IDEA環境中使用 Spring Initializr引導我們建立一個標準工程,採用最新穩定版spring boot 2.0.2,Activiti 6.0,通過引入H2記憶體資料庫,方便我們快速啟動程式演示,而不用費心在建立資料庫相關工作,當程式啟動Spring boot會基於自動配置原理給我們隱式的建立一個工作流引擎物件ProcessEngine,並把RuntimeService(控制流程執行時資料流轉)等核心服務註冊到 Spring 容器中,我們只需要 依賴注入使用即可。

繪製流程圖

繪製流程圖建議大家通過Eclipse + Activiti外掛來繪製,如果大家對流程圖元素還不瞭解,可以直接拷貝流程定義檔案執行,這樣方便快速的執行我們的快速程式

部署流程圖

我們程式碼裡面並沒有體現流程部署的過程,也得益於Spring Boot提供的自動部署功能,也是在Spring 容器啟動的過程通過 activiti-spring元件,把Class path對應的/processes目錄的流程檔案自動載入部署,約定大於配置,快速演示程式碼推薦大家把流程圖放到/processes 資源包下面,線上系統則不建議這樣做

啟動流程

啟動流程是通過流程定義檔案Key建立一個流程例項的過程,類似於我們根據類名new出一個物件的原理,一個流程定義檔案可以生成許許多多的流程例項

多使用者協助審批流程

在命令列下體驗流程審批的效果,對Java後端開發來說,更多的傾向於瞭解底層的原理,而對UI效果興趣不高,其實Activiti也跟我們提供了還不錯的activiti-app來更好的體驗流程的流轉。我們這裡在命令列下執行工作流程,可以滿足一下極客青年小小的虛榮心。

開發
建立工程
基於spring Initializr建立一個標準的maven工程,並新增相關依賴 spring boot 2.0.2最新穩定版,Activiti 6最新穩定版

// pom.xml
<?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>com.imooc.activiti</groupId>
    <artifactId>activiti-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
 
    <name>activiti-demo</name>
    <description>Demo project for Spring Boot</description>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>24.0-jre</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter-basic</artifactId>
            <version>6.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</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>
Spring Initializr會自動建立程式啟動入口類ActivitiDemoApplication

/src/main/java/com.imooc.activiti.demo.ActivitiDemoApplicationpackage 

com.imooc.activiti.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplicationpublic class ActivitiDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ActivitiDemoApplication.class, args);
    }
}

設計流程定義檔案
https://img4.mukewang.com/5b1fde170001823818100620.jpg

剛開始設計流程定義檔案推薦大家使用 Eclipse+ Activiti外掛的方式, Activiti也提供了基於瀏覽器的web 流程設計器,都可以分別體驗一下,這個流程定義檔案就是普通的xml,大家應該能很容易看明白這個xml檔案

  // src/main/resources/processes/second_approve.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" xmlns:tns="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test" id="m1528728906382" name="">
      <process id="second_approve" name="二級審批流程" isExecutable="true" isClosed="false" processType="None">
        <startEvent id="startEvent" name="開始"></startEvent>
        <userTask id="submitForm" name="填寫審批資訊">
          <extensionElements>
            <activiti:formProperty id="message" name="申請資訊" type="string" required="true"></activiti:formProperty>
            <activiti:formProperty id="name" name="申請人姓名" type="string" required="true"></activiti:formProperty>
            <activiti:formProperty id="submitTime" name="提交時間" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
            <activiti:formProperty id="submitType" name="確認申請" type="string" required="true"></activiti:formProperty>
          </extensionElements>
        </userTask>
        <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="submitForm"></sequenceFlow>
        <exclusiveGateway id="decideSubmit" name="提交OR取消"></exclusiveGateway>
        <sequenceFlow id="flow2" sourceRef="submitForm" targetRef="decideSubmit"></sequenceFlow>
        <userTask id="tl_approve" name="主管審批">
          <extensionElements>
            <activiti:formProperty id="tlApprove" name="主管審批結果" type="string"></activiti:formProperty>
            <activiti:formProperty id="tlMessage" name="主管備註" type="string" required="true"></activiti:formProperty>
          </extensionElements>
        </userTask>
        <sequenceFlow id="flow3" sourceRef="decideSubmit" targetRef="tl_approve">
          <conditionExpression xsi:type="tFormalExpression"><![CDATA[${submitType == "y" || submitType == "Y"}]]></conditionExpression>
        </sequenceFlow>
        <exclusiveGateway id="decideTLApprove" name="主管審批校驗"></exclusiveGateway>
        <sequenceFlow id="flow4" sourceRef="tl_approve" targetRef="decideTLApprove"></sequenceFlow>
        <endEvent id="endEvent" name="結束"></endEvent>
        <endEvent id="endEventCancel" name="取消"></endEvent>
        <sequenceFlow id="flow8" sourceRef="decideSubmit" targetRef="endEventCancel">
          <conditionExpression xsi:type="tFormalExpression"><![CDATA[${submitType == "n" || submitType == "N"}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="flow9" sourceRef="decideTLApprove" targetRef="submitForm">
          <conditionExpression xsi:type="tFormalExpression"><![CDATA[${tlApprove == "n" || tlApprove == "N"}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="_2" sourceRef="decideTLApprove" targetRef="endEvent">
          <conditionExpression xsi:type="tFormalExpression"><![CDATA[${tlApprove == "y" || tlApprove == "Y"}]]></conditionExpression>
        </sequenceFlow>
      </process>
     </definitions>
    開發可以根據spring boot自動裝配的流程樣例
    流程執行我們使用了CommandLineRunner,這個類的事例會在Spring boot容器啟動載入完成後直接執行
    
    //src/main/java/com.imooc.activiti.demo.AutoConfigurationProcessDemo
    
    @Configuration
    @Slf4jpublic class AutoConfigurationProcessDemo {
        @Autowired    private RuntimeService runtimeService;
        @Autowired    private TaskService taskService;
        @Autowired    private FormService formService;   
         /**
         * 命令列執行
         *
         * @return
         */
        @Bean  
        public CommandLineRunner commandLineRunner() {        
            return new CommandLineRunner() {
                @Override            
                public void run(String... args) throws Exception {                
                    log.info("啟動我們的程式");                
                    //啟動執行流程
                    ProcessInstance processInstance = startProcessInstance("second_approve");                
                    //處理流程任務
                    processTask(processInstance);                
                    log.info("結束我們的程式");
                }
            };
        }    /**
         * 處理流程任務
         *
         * @param processInstance
         * @throws ParseException
         */
        private void processTask(ProcessInstance processInstance) throws ParseException {
            Scanner scanner = new Scanner(System.in);        
            while (processInstance != null && !processInstance.isEnded()) {
                List<Task> list = taskService.createTaskQuery().list();            
                log.info("待處理任務數量 [{}]", list.size());            
                for (Task task : list) {                
                log.info("待處理任務 [{}]", task.getName());
                    Map<String, Object> variables = buildVariablesByScanner(scanner, task);
                    taskService.complete(task.getId(), variables);
                    processInstance = runtimeService
                            .createProcessInstanceQuery()
                            .processInstanceId(processInstance.getId())
                            .singleResult();
                }
            }
            scanner.close();
        }    /**
         * 從控制檯獲取變數
         *
         * @param scanner
         * @param task
         * @return
         * @throws ParseException
         */
        private Map<String, Object> buildVariablesByScanner(Scanner scanner, Task task) throws ParseException {
            TaskFormData taskFormData = formService.getTaskFormData(task.getId());
            List<FormProperty> formProperties = taskFormData.getFormProperties();        
            return buildVariablesByScanner(scanner, formProperties);
        }    
        
        public static Map<String, Object> buildVariablesByScanner(Scanner scanner, List<FormProperty> formProperties) throws ParseException {
            Map<String, Object> variables = Maps.newHashMap();        
            for (FormProperty property : formProperties) {            
            String line = null;            
            if (StringFormType.class.isInstance(property.getType())) {                
            log.info("請輸入 {} ?", property.getName());                
            line = scanner.nextLine();
                    variables.put(property.getId(), line);
                } else if (DateFormType.class.isInstance(property.getType())) {                
                log.info("請輸入 {} ? 格式 (yyyy-MM-dd)", property.getName());               
                line = scanner.nextLine();
                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                    Date date = dateFormat.parse(line);
                    variables.put(property.getId(), date);
                } else {                
                    log.info("型別暫不支援 {}", property.getType());
                }            
                log.info("您輸入的內容是 [{}]", line);
    
            }        
            return variables;
        }    /**
         * 獲取流程例項
         *
         * @param processDefinitionId
         * @return
         */
        private ProcessInstance startProcessInstance(String processDefinitionId) {
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionId);        log.info("啟動流程 [{}]", processInstance.getProcessDefinitionKey());        return processInstance;
        }
    
    }

命令列執行效果
可以看到我簡單3個檔案就可以讓工作流程式執行起來,為了讓控制檯輸出更清晰直觀,讓控制檯日誌僅僅輸出message日誌資訊,忽略預設格式中日誌級別時間資訊等等

//src/main/resources/application.yml
logging:
pattern:
console: “%m%n”
我們可以直接在控制檯執行 mvn spring-boot:run,當然也可以mvn package打出一個Jar包後,執行獨立jar包

mvn spring-boot:run
// 輸入正常流程審批通過
啟動我們的程式
啟動流程 [second_approve]
待處理任務數量 [1]
待處理任務 [填寫審批資訊]
請輸入 申請資訊 ?
$辭職,世界那麼大,我想去看看
您輸入的內容是 [辭職,世界那麼大,我想去看看]
請輸入 申請人姓名 ?
$Jimmy
您輸入的內容是 [Jimmy]
請輸入 提交時間 ? 格式 (yyyy-MM-dd)
$2018-05-21
您輸入的內容是 [2018-05-21]
請輸入 確認申請 ?
$Y
您輸入的內容是 [Y]
待處理任務數量 [1]
待處理任務 [主管審批]
請輸入 主管審批結果 ?
$Y
您輸入的內容是 [Y]
請輸入 主管備註 ?
$錢包那麼小,勸你別作妖
您輸入的內容是 [錢包那麼小,勸你別作妖]
結束我們的程式

轉載的是下面這位大牛的, 怕這篇文章刪掉,就轉載過來了,如果侵權馬上刪!

作者:JimmyNo1
連結:https://www.imooc.com/article/details/id/35590

相關文章