Activiti7工做流+SpringBoot

一. Activiti相關概念

1. Activiti介紹

    Activiti是基於Apache許可的開源BPM平臺,創始人Tom Baeyens原是JBPM架構師,能夠理解爲與JBPM出自同一祖師爺。它提供了Eclipse插件,開發能夠經過插件直接繪製業務流程圖。基於Spring,ibatis等框架,並在此之上構建了很是清晰的開發框架。是由Alfresco軟件發佈的業務流程管理(BPM)框架,它是覆蓋了業務流程管理、工做流、服務協做等領域的一個開源的、靈活的、易擴展的可執行流程語言框架。 本文基於Activiti7的Activiti Core,基於Spring Boot作簡單學習總結。(Activiti最新版本向微服務這邊靠齊了,並分Activiti Core與Activiti Cloud兩塊,Activiti Cloud還沒研究)javascript

2. 核心類

2.1 ProcessEngine

    流程引擎的抽象,能夠經過此類獲取須要的全部服務。html

2.2 服務(Service)類

    經過ProcessEngine獲取,Activiti將不一樣生命週期的服務封裝在不一樣Service中,包括定義、部署、運行。經過服務類可獲取相關生命週期中的服務信息。前端

2.2.1 TaskService

    流程運行過程當中,每一個任務節點的相關操做接口,如complete,delete,delegate等。java

2.2.2 RepositoryService

    流程定義和部署相關的存儲服務。mysql

2.2.3 RuntimeService

    流程運行時相關的服務,如根據流程好啓動流程實例startProcessInstanceByKey。jquery

2.2.3 HistoryService

    歷史記錄相關服務接口。web

2.3 CommandContextIntercepter或CommandExecutor

    Activiti使用命令模式做爲基礎開發模式,如Service中調用的各個方法都對應相應的命令對象。Service將請求委託給命令對象,命令對象來命令接受者,接受者接收後執行並返回結果。而CommandContextIntercepter的做用是攔截全部命令,並在命令先後執行一些公共方法。ajax

2.4 核心業務對象

    org.activiti.engine.impl.persistence.entity包下的類,包括Task,ProcessInstance,Execution等。會根據不一樣職責實現相應接口的方法(如須要持久化則繼承PersistentObject接口),與傳統的實體類不一樣。spring

3. 上下文組件(Context)

    用來保存生命週期比較長,全局性的信息,相似Application,主要包括以下三類。sql

3.1 CommandContext

    命令上下文,保存每一個命令必要的資源,如持久化須要的session。

3.2 ProcessEngineConfigurationImpl

    流程引擎相關配置信息,整個引擎全局的配置信息,如數據源DataSource等。該對象爲單例,在流程引擎建立的時候初始化。

3.3 ExecutionContext

    持有ExecutionEntity對象。

4. 持久化組件

    Activiti使用ibatis做OR映射,並在此基礎上增長設計了本身的持久化框架。在流程引擎建立時初始化。頂層接口Session、SessionFactory。Session有兩個實現類:DbSqlSession,負責sql表達式的執行。AbstractManager負責對象的持久化操做。SessionFactory有兩個實現類:DbSqlSessionFactory負責DbSqlSession相關操做,GenericManagerFactory負責AbstractManager相關操做。

5. Event-Listener組件

    Activiti容許客戶代碼介入流程執行,提供了事件監聽組件。監聽的事件類型能夠分爲TaskListener、JavaDelegate、Expression、ExecutionListener。ProcessEngineConfigurationImpl持有DelegateInterceptor的某個實例,方便調用handleInvocation。

6. Cache組件

    DbSqlSession中有cache的實現,Activiti基於List和Map來作緩存。如查詢時先查緩存,沒有則直接查詢並放入緩存。

7. 異步執行組件

    Activiti能夠執行任務,JobExecutor爲啓核心類,JobExecutor包含三個主要屬性:JobAcquisitionThread,BlockingQueue,ThreadPoolExecutor。方法ProcessEngines在引擎啓動時調用JobExecutor.start,JobAcquisitionThread 線程即開始工做,其run方法不斷循環執行AcquiredJobs中的job,執行一次後線程等待必定時間直到超時或者JobExecutor.jobWasAdded方法,由於有新任務而被調用。

8. PVM:Process Virtal Machine

    流程虛擬機API暴露了流程虛擬機的POJO核心,流程虛擬機API描述了一個工做流流程必備的組件,這些組件包括:
    PvmProcessDefinition:流程的定義,形象點說就是用戶畫的那個圖。靜態含義。
    PvmProcessInstance:流程實例,用戶發起的某個PvmProcessDefinition的一個實例,動態含義。
    PvmActivity:流程中的一個節點
    PvmTransition:銜接各個節點之間的路徑,形象點說就是圖中各個節點之間的鏈接線。
    PvmEvent:流程執行過程當中觸發的事件

二. Eclipse插件安裝:

    個人Eclipse版本以下:
在這裏插入圖片描述
    下載離線安裝包(在線安裝始終失敗,應該出於網絡限制),地址:http://www.activiti.org/designer/archived/activiti-designer-5.18.0.zip
    離線安裝包安裝安裝依然提示相關包找不到,因而下載另外三個依賴包(org.eclipse.emf.transaction_1.4.0.v20100331-1738.jar、org.eclipse.emf.validation_1.7.0.201306111341.jar、org.eclipse.emf.workspace_1.5.1.v20120328-0001.jar,在Maven倉庫能夠找到),放到Eclipse的plugin目錄下,繼續安裝,以下:
在這裏插入圖片描述
    肯定後勾選相關選項,完成安裝,重啓Eclipse,New->Other
在這裏插入圖片描述
    出現以上標誌,則安裝完成。

三. 項目搭建

1. 新建Spring Boot工程

    (個人Eclipse已經安裝Spring Boot插件)
個人Eclipse已經安裝Spring Boot插件
    而後Next->Next…->Finish便可,而後改application.properties爲application.yml(我的習慣)
在這裏插入圖片描述

2. 引入Activiti相關依賴

    在pom屬性中定義版本號,並添加Activiti相關依賴:

   
   
   
   
    <activiti-dependencies.version>7.0.56</activiti-dependencies.version>
    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>org.activiti.dependencies</groupId>
          <artifactId>activiti-dependencies</artifactId>
          <version>${activiti-dependencies.version}</version>
          <scope>import</scope>
          <type>pom</type>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
        
        
        
    
       
    <dependency>
          <groupId>org.activiti</groupId>
          <artifactId>activiti-spring-boot-starter</artifactId>
    </dependency>
    
        
        
        
    
       

        因爲Activiti默認使用H2數據庫,因此需添加H2數據庫支持(這裏使用此SpringBoot版本默認1.4.197):

    <dependency>
          <groupId>com.h2database</groupId>
          <artifactId>h2</artifactId>
    </dependency>
    
        
        
        
    
       

        出現錯誤:Missing artifact org.activiti:activiti-spring-boot-starter:jar:7.0.56

    在這裏插入圖片描述
        添加私服倉庫地址:

    <repositories>
            <repository>
              <id>alfresco</id>
              <name>Activiti Releases</name>
              <url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases/</url>
              <releases>
                <enabled>true</enabled>
              </releases>
            </repository>
    </repositories>
    
        
        
        
    
       

    在這裏插入圖片描述
        錯誤消失。

    3. 建立流程圖

        在此版本Activiti+SpringBoot,默認加載/processes/目錄下流程,因而在resources下新建processes目錄,並在目錄下new->Other,以下:
    在這裏插入圖片描述
        使用Activiti圖編輯工具打開,建立以下流程(建立過程在此不介紹,就是右側工具欄的運用):
    在這裏插入圖片描述

    4. 啓動工程

        看日誌:
    在這裏插入圖片描述
        從日誌中能夠看出,流程引擎已經默認建立,並能夠看到使用的默認數據源是H2的數據源,咱們建立的流程也已經部署。

    5. 修改配置

        在正常使用中,通常系統會有本身的數據庫,而不會採用默認內存的H2數據庫,這裏以MySQL爲例。修改application.yml。

    5.1 添加MySQL依賴

    <!--使用mysql數據庫,導入mysql驅動-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
        
        
        
    
       

    5.2 修改數據庫

    spring:
      ##數據庫鏈接信息
     datasource:
        # 數據源配置
     url: jdbc:mysql://127.0.0.1:3306/activity?useUnicode=true&characterEncoding=utf-8&useSSL=false
     username: root
     password: root
     driver-class-name: com.mysql.jdbc.Driver
        # SQLException: XAER_INVAL: Invalid arguments (or unsupported command)問題
     xa:
     properties:
     pinGlobalTxToPhysicalConnection: true
     useServerPrepStmts: true
    
        
        
        
    
       

    5.3 Activiti相關配置

    # 參考配置https://www.cnblogs.com/liaojie970/p/8857710.html
     activiti:
        # 自動建表
     database-schema: ACTIVITI
     database-schema-update: true
     history-level: full
     db-history-used: true
    
        
        
        
    
       

        注意:
        database-schema-update表示啓動時檢查數據庫表,不存在則建立
        history-level表示哪一種狀況下使用歷史表,這裏配置爲full表示所有記錄歷史,方便繪製流程圖
        db-history-used爲true表示使用歷史表,若是不配置,則工程啓動後能夠檢查數據庫,只創建了17張表,歷史表沒有創建,則流程圖及運行節點沒法展現(暫未找到可行方式)

    5.4 附上application.yml完整配置:

    # 服務配置
    server:
     display-name: actdemo
     port: 8085
      
    # Spring相關配置
    spring:
      ##數據庫鏈接信息
     datasource:
        # 數據源配置
     url: jdbc:mysql://127.0.0.1:3306/activity?useUnicode=true&characterEncoding=utf-8&useSSL=false
     username: root
     password: 888
     driver-class-name: com.mysql.jdbc.Driver
        
        # SQLException: XAER_INVAL: Invalid arguments (or unsupported command)問題
     xa:
     properties:
     pinGlobalTxToPhysicalConnection: true
     useServerPrepStmts: true
    
     thymeleaf:
     mode: HTML
     encoding: utf-8
        # 禁用緩存
     cache: false
     application:
        # 註冊應用名
     name: actdemo
     mvc:
        # 靜態資源路徑
     static-path-pattern: /static/**
      # 參考配置https://www.cnblogs.com/liaojie970/p/8857710.html
     activiti:
        # 自動建表
     database-schema: ACTIVITI
     database-schema-update: true
     history-level: full
     db-history-used: true
    
        
        
        
    
       

    5.5 啓動工程

        觀察日誌,流程引擎已成功加載,並已使用MySQL數據庫,以下:
    在這裏插入圖片描述
        再看數據庫,已經建立25張表(老版本的Activiti建立表有手動執行SQL和經過調用流程引擎建立兩種方式,該版本與SpringBoot整合後,啓動默認建立須要的表):
    在這裏插入圖片描述
        注意:
        原SpringBoot工程使用版本2.1.1.RELEASE,啓動始終失敗,各類錯誤,後改成2.0.4.RELEASE版本,則啓動正常。

    6. 編寫實例

        本例子使用Thymeleaf作前端頁面展現(SpringBoot推薦使用),並建立控制器Controller調用工做流接口與前端交互。

    6.1 引入Thymeleaf依賴,(前端使用Thymeleaf的配置已經在application.yml中)

    <!-- Thymeleaf依賴 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
        
        
        
    
       

    6.2 建立Controller控制器

        建立啓動流程方法,主要代碼以下

    /**  * <p>啓動請假流程</p>  * @return String 流程實例ID  * @author FRH  * @time 2018年12月10日上午11:03:36  * @version 1.0  */
        @RequestMapping(value="/start")
        @ResponseBody
        public String start() {
            // xml中定義的ID
            String instanceKey = "leaveProcess";
            logger.info("開啓請假流程...");
            
            // 設置流程參數,開啓流程
            Map<String,Object> map = new HashMap<String,Object>();
            map.put("jobNumber","A1001");
            map.put("busData","bus data");
            ProcessInstance instance = runtimeService.startProcessInstanceByKey(instanceKey, map);//使用流程定義的key啓動流程實例,key對應helloworld.bpmn文件中id的屬性值,使用key值啓動,默認是按照最新版本的流程定義啓動
            
            logger.info("啓動流程實例成功:{}", instance);
            logger.info("流程實例ID:{}", instance.getId());
            logger.info("流程定義ID:{}", instance.getProcessDefinitionId());
            
            
            //驗證是否啓動成功
            //經過查詢正在運行的流程實例來判斷
            ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
            //根據流程實例ID來查詢
            List<ProcessInstance> runningList = processInstanceQuery.processInstanceId(instance.getProcessInstanceId()).list();
            logger.info("根據流程ID查詢條數:{}", runningList.size());
            
            // 返回流程ID
            return instance.getId();
        }
    
        
        
        
    
       

    6.3 流程跟蹤與流程圖展現

        Activiti流程圖展現,使用流程圖生成器,本例生成的流程圖在頁面使用以下方式展現便可:

    <embed src="/demo/showImg?instanceId=5070fd58-f859-11e8-a359-484d7ec5762d" style="display:block;width:1000px;height:450px" />
    
        
        
        
    
       

        引入相關工具包,版本使用默認版本7.0.65

    <!-- Activiti生成流程圖 -->
    <dependency>
      <groupId>org.activiti</groupId>
      <artifactId>activiti-image-generator</artifactId>
    </dependency>
    
        
        
        
    
       

        調用輸出流程圖

    /** * <p>查看當前流程圖</p> * @param instanceId 流程實例 * @param response void 響應 * @author FRH * @time 2018年12月10日上午11:14:12 * @version 1.0 */
        @ResponseBody
        @RequestMapping(value="/showImg")
        public void showImg(String instanceId, HttpServletResponse response) {
            /* * 參數校驗 */
            logger.info("查看完整流程圖!流程實例ID:{}", instanceId);
            if(StringUtils.isBlank(instanceId)) return;
            
            
            /* * 獲取流程實例 */
            HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).singleResult();
            if(processInstance == null) {
                logger.error("流程實例ID:{}沒查詢到流程實例!", instanceId);
                return;
            }
            
            // 根據流程對象獲取流程對象模型
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
            
            
            /* * 查看已執行的節點集合 * 獲取流程歷史中已執行節點,並按照節點在流程中執行前後順序排序 */
            // 構造歷史流程查詢
            HistoricActivityInstanceQuery historyInstanceQuery = historyService.createHistoricActivityInstanceQuery().processInstanceId(instanceId);
            // 查詢歷史節點
            List<HistoricActivityInstance> historicActivityInstanceList = historyInstanceQuery.orderByHistoricActivityInstanceStartTime().asc().list();
            if(historicActivityInstanceList == null || historicActivityInstanceList.size() == 0) {
                logger.info("流程實例ID:{}沒有歷史節點信息!", instanceId);
                outputImg(response, bpmnModel, null, null);
                return;
            }
            // 已執行的節點ID集合(將historicActivityInstanceList中元素的activityId字段取出封裝到executedActivityIdList)
            List<String> executedActivityIdList = historicActivityInstanceList.stream().map(item -> item.getActivityId()).collect(Collectors.toList());
            
            /* * 獲取流程走過的線 */
            // 獲取流程定義
            ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition(processInstance.getProcessDefinitionId());
            List<String> flowIds = ActivitiUtils.getHighLightedFlows(bpmnModel, processDefinition, historicActivityInstanceList);
            
            
            /* * 輸出圖像,並設置高亮 */
            outputImg(response, bpmnModel, flowIds, executedActivityIdList);
        }
    
        /** * <p>輸出圖像</p> * @param response 響應實體 * @param bpmnModel 圖像對象 * @param flowIds 已執行的線集合 * @param executedActivityIdList void 已執行的節點ID集合 * @author FRH * @time 2018年12月10日上午11:23:01 * @version 1.0 */
        private void outputImg(HttpServletResponse response, BpmnModel bpmnModel, List<String> flowIds, List<String> executedActivityIdList) {
            InputStream imageStream = null;
            try {
                imageStream = processDiagramGenerator.generateDiagram(bpmnModel, executedActivityIdList, flowIds, "宋體", "微軟雅黑", "黑體", true, "png");
                // 輸出資源內容到相應對象
                byte[] b = new byte[1024];
                int len;
                while ((len = imageStream.read(b, 0, 1024)) != -1) {
                    response.getOutputStream().write(b, 0, len);
                }
                response.getOutputStream().flush();
            }catch(Exception e) {
                logger.error("流程圖輸出異常!", e);
            } finally { // 流關閉
                StreamUtils.closeInputStream(imageStream);
            }
        }
    
        
        
        
    
       

        流程圖工具類ActivitiUtils

    package com.mypro.activiti.utils;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.activiti.bpmn.model.BpmnModel;
    import org.activiti.bpmn.model.FlowNode;
    import org.activiti.bpmn.model.SequenceFlow;
    import org.activiti.engine.history.HistoricActivityInstance;
    import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
    
    /** * <p>Activiti工做流工具類</p> * @author FRH * @time 2018年12月10日上午11:26:02 * @version 1.0 */
    public class ActivitiUtils {
        
    
        /** * <p>獲取流程走過的線</p> * @param bpmnModel 流程對象模型 * @param processDefinitionEntity 流程定義對象 * @param historicActivityInstances 歷史流程已經執行的節點,並已經按執行的前後順序排序 * @return List<String> 流程走過的線 * @author FRH * @time 2018年12月10日上午11:26:19 * @version 1.0 */
        public static List<String> getHighLightedFlows(BpmnModel bpmnModel, ProcessDefinitionEntity processDefinitionEntity, List<HistoricActivityInstance> historicActivityInstances) {
            // 用以保存高亮的線flowId
            List<String> highFlows = new ArrayList<String>();
            if(historicActivityInstances == null || historicActivityInstances.size() == 0) return highFlows;
    
            // 遍歷歷史節點
            for (int i = 0; i < historicActivityInstances.size() - 1; i++) {
                // 取出已執行的節點
                HistoricActivityInstance activityImpl_ = historicActivityInstances.get(i);
    
                // 用以保存後續開始時間相同的節點
                List<FlowNode> sameStartTimeNodes = new ArrayList<FlowNode>();
    
                // 獲取下一個節點(用於連線)
                FlowNode sameActivityImpl = getNextFlowNode(bpmnModel, historicActivityInstances, i, activityImpl_);
    // FlowNode sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i + 1).getActivityId());
                
                // 將後面第一個節點放在時間相同節點的集合裏
                if(sameActivityImpl != null) sameStartTimeNodes.add(sameActivityImpl);
                
                // 循環後面節點,看是否有與此後繼節點開始時間相同的節點,有則添加到後繼節點集合
                for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) {
                    HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 後續第一個節點
                    HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 後續第二個節點
                    if (activityImpl1.getStartTime().getTime() != activityImpl2.getStartTime().getTime()) break;
                    
                    // 若是第一個節點和第二個節點開始時間相同保存
                    FlowNode sameActivityImpl2 = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityImpl2.getActivityId());
                    sameStartTimeNodes.add(sameActivityImpl2);
                }
                
                // 獲得節點定義的詳細信息
                FlowNode activityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i).getActivityId());
                // 取出節點的全部出去的線,對全部的線進行遍歷
                List<SequenceFlow> pvmTransitions = activityImpl.getOutgoingFlows();
                for (SequenceFlow pvmTransition : pvmTransitions) {
                    // 獲取節點
                    FlowNode pvmActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(pvmTransition.getTargetRef());
                    
                    // 不是後繼節點
                    if(!sameStartTimeNodes.contains(pvmActivityImpl)) continue;
                    
                    // 若是取出的線的目標節點存在時間相同的節點裏,保存該線的id,進行高亮顯示
                    highFlows.add(pvmTransition.getId());
                }
            }
            
            //返回高亮的線
            return highFlows;
        }
    
    
    
        /** * <p>獲取下一個節點信息</p> * @param bpmnModel 流程模型 * @param historicActivityInstances 歷史節點 * @param i 當前已經遍歷到的歷史節點索引(找下一個節點今後節點後) * @param activityImpl_ 當前遍歷到的歷史節點實例 * @return FlowNode 下一個節點信息 * @author FRH * @time 2018年12月10日上午11:26:55 * @version 1.0 */
        private static FlowNode getNextFlowNode(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances, int i, HistoricActivityInstance activityImpl_) {
            // 保存後一個節點
            FlowNode sameActivityImpl = null;
            
            // 若是當前節點不是用戶任務節點,則取排序的下一個節點爲後續節點
            if(!"userTask".equals(activityImpl_.getActivityType())) {
                // 是最後一個節點,沒有下一個節點
                if(i == historicActivityInstances.size()) return sameActivityImpl;
                // 不是最後一個節點,取下一個節點爲後繼節點
                sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i + 1).getActivityId());// 找到緊跟在後面的一個節點
                // 返回
                return sameActivityImpl;
            }
            
            // 遍歷後續節點,獲取當前節點後續節點
            for (int k = i + 1; k <= historicActivityInstances.size() - 1; k++) {
                // 後續節點
                HistoricActivityInstance activityImp2_ = historicActivityInstances.get(k);
                // 都是userTask,且主節點與後續節點的開始時間相同,說明不是真實的後繼節點
                if("userTask".equals(activityImp2_.getActivityType()) && activityImpl_.getStartTime().getTime() == activityImp2_.getStartTime().getTime()) continue;
                // 找到緊跟在後面的一個節點
                sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(k).getActivityId());
                break;
            }
            return sameActivityImpl;
        }
    }
    
    
        
        
        
    
       

    6.4 附上DemoController完整代碼

    package com.mypro.activiti.controller;
    
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.activiti.bpmn.model.BpmnModel;
    import org.activiti.engine.HistoryService;
    import org.activiti.engine.RepositoryService;
    import org.activiti.engine.RuntimeService;
    import org.activiti.engine.TaskService;
    import org.activiti.engine.history.HistoricActivityInstance;
    import org.activiti.engine.history.HistoricActivityInstanceQuery;
    import org.activiti.engine.history.HistoricProcessInstance;
    import org.activiti.engine.impl.RepositoryServiceImpl;
    import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
    import org.activiti.engine.runtime.ProcessInstance;
    import org.activiti.engine.runtime.ProcessInstanceQuery;
    import org.activiti.engine.task.Task;
    import org.activiti.image.ProcessDiagramGenerator;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import com.mypro.activiti.utils.ActivitiUtils;
    import com.mypro.activiti.utils.StreamUtils;
    
    /** * <p>Activiti控制器</p> * @author FRH * @time 2018年12月10日上午9:30:18 * @version 1.0 */
    @Controller
    @RequestMapping("/demo")
    public class DemoController {
    
    private static final Logger logger = LoggerFactory.getLogger(DemoController.class);
        
        /** 流程定義和部署相關的存儲服務 */
        @Autowired
        private RepositoryService repositoryService;
        
        /** 流程運行時相關的服務 */
        @Autowired
        private RuntimeService runtimeService;
        
        /** 節點任務相關操做接口 */
        @Autowired
        private TaskService taskService;
        
        /** 流程圖生成器 */
        @Autowired
        private ProcessDiagramGenerator processDiagramGenerator;
    
        /** 歷史記錄相關服務接口 */
        @Autowired
        private HistoryService historyService;
    
        
        
        /**  * <p>跳轉到測試主頁面</p>  * @return String 測試主頁面  * @author FRH  * @time 2018年12月10日上午11:12:28  * @version 1.0  */
        @RequestMapping(value="/toIndex.html")
        public String toTestPage() {
            return "/index";
        }
        
        
        
        /**  * <p>跳轉到上級審覈頁面</p>  * @return String 上級審覈頁面  * @author FRH  * @time 2018年12月5日下午2:31:42  * @version 1.0  */
        @RequestMapping(value="/toLeave")
        public String employeeLeave() {
            return "/employeeLeave";
        }
        
        
        
        /**  * <p>啓動請假流程(流程key即xml中定義的ID爲leaveProcess)</p>  * @return String 啓動的流程ID  * @author FRH  * @time 2018年12月10日上午11:12:50  * @version 1.0  */
        @RequestMapping(value="/start")
        @ResponseBody
        public String start() {
            /* * xml中定義的ID */
            String instanceKey = "leaveProcess";
            logger.info("開啓請假流程...");
            
            
            /* * 設置流程參數,開啓流程 */
            Map<String,Object> map = new HashMap<String,Object>();
            map.put("jobNumber","A1001");
            map.put("busData","bus data");
            ProcessInstance instance = runtimeService.startProcessInstanceByKey(instanceKey, map);//使用流程定義的key啓動流程實例,key對應helloworld.bpmn文件中id的屬性值,使用key值啓動,默認是按照最新版本的流程定義啓動
            
            logger.info("啓動流程實例成功:{}", instance);
            logger.info("流程實例ID:{}", instance.getId());
            logger.info("流程定義ID:{}", instance.getProcessDefinitionId());
            
            
            /* * 驗證是否啓動成功 */
            //經過查詢正在運行的流程實例來判斷
            ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
            //根據流程實例ID來查詢
            List<ProcessInstance> runningList = processInstanceQuery.processInstanceId(instance.getProcessInstanceId()).list();
            logger.info("根據流程ID查詢條數:{}", runningList.size());
            
            
            /* * 返回流程ID */
            return instance.getId();
        }
        
        
        
        /**  * <p>查看當前流程圖</p>  * @param instanceId 流程實例  * @param response void 響應  * @author FRH  * @time 2018年12月10日上午11:14:12  * @version 1.0  */
        @ResponseBody
        @RequestMapping(value="/showImg")
        public void showImg(String instanceId, HttpServletResponse response) {
            /* * 參數校驗 */
            logger.info("查看完整流程圖!流程實例ID:{}", instanceId);
            if(StringUtils.isBlank(instanceId)) return;
            
            
            /* * 獲取流程實例 */
            HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).singleResult();
            if(processInstance == null) {
                logger.error("流程實例ID:{}沒查詢到流程實例!", instanceId);
                return;
            }
            
            // 根據流程對象獲取流程對象模型
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
            
            
            /* * 查看已執行的節點集合 * 獲取流程歷史中已執行節點,並按照節點在流程中執行前後順序排序 */
            // 構造歷史流程查詢
            HistoricActivityInstanceQuery historyInstanceQuery = historyService.createHistoricActivityInstanceQuery().processInstanceId(instanceId);
            // 查詢歷史節點
            List<HistoricActivityInstance> historicActivityInstanceList = historyInstanceQuery.orderByHistoricActivityInstanceStartTime().asc().list();
            if(historicActivityInstanceList == null || historicActivityInstanceList.size() == 0) {
                logger.info("流程實例ID:{}沒有歷史節點信息!", instanceId);
                outputImg(response, bpmnModel, null, null);
                return;
            }
            // 已執行的節點ID集合(將historicActivityInstanceList中元素的activityId字段取出封裝到executedActivityIdList)
            List<String> executedActivityIdList = historicActivityInstanceList.stream().map(item -> item.getActivityId()).collect(Collectors.toList());
            
            /* * 獲取流程走過的線 */
            // 獲取流程定義
            ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition(processInstance.getProcessDefinitionId());
            List<String> flowIds = ActivitiUtils.getHighLightedFlows(bpmnModel, processDefinition, historicActivityInstanceList);
            
            
            /* * 輸出圖像,並設置高亮 */
            outputImg(response, bpmnModel, flowIds, executedActivityIdList);
        }
        
    
    
        /**  * <p>員工提交申請</p>  * @param request 請求  * @return String 申請受理結果  * @author FRH  * @time 2018年12月10日上午11:15:09  * @version 1.0  */
        @RequestMapping(value="/employeeApply")
        @ResponseBody
        public String employeeApply(HttpServletRequest request){
            /* * 獲取請求參數 */
            String taskId = request.getParameter("taskId"); // 任務ID
            String jobNumber = request.getParameter("jobNumber"); // 工號
            String leaveDays = request.getParameter("leaveDays"); // 請假天數
            String leaveReason = request.getParameter("leaveReason"); // 請假緣由
            
            
            /* * 查詢任務 */
            Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
            if(task == null) {
                logger.info("任務ID:{}查詢到任務爲空!", taskId);
                return "fail";
            }
    
            
            /* * 參數傳遞並提交申請 */
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("days", leaveDays);
            map.put("date", new Date());
            map.put("reason", leaveReason);
            map.put("jobNumber", jobNumber);
            taskService.complete(task.getId(), map);
            logger.info("執行【員工申請】環節,流程推進到【上級審覈】環節");
            
            /* * 返回成功 */
            return "success";
        }
        
        
        /**  * <p>跳轉到上級審覈頁面</p>  * @return String 頁面  * @author FRH  * @time 2018年12月5日下午2:31:42  * @version 1.0  */
        @RequestMapping(value="/viewTask")
        public String toHigherAudit(String taskId, HttpServletRequest request) {
            /* * 獲取參數 */
            logger.info("跳轉到任務詳情頁面,任務ID:{}", taskId);
            if(StringUtils.isBlank(taskId)) return "/higherAudit";
            
            
            /* * 查看任務詳細信息 */
            Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
            if(task == null) {
                logger.info("任務ID:{}不存在!", taskId);
                return "/higherAudit";
            }
            
            
            /* * 完成任務 */
            Map<String, Object> paramMap = taskService.getVariables(taskId);
            request.setAttribute("task", task);
            request.setAttribute("paramMap", paramMap);
            return "higherAudit";
        }
        
        
        
        /**  * <p>跳轉到部門經理審覈頁面</p>  * @param taskId 任務ID  * @param request 請求  * @return String 響應頁面  * @author FRH  * @time 2018年12月6日上午9:54:34  * @version 1.0  */
        @RequestMapping(value="/viewTaskManager")
        public String viewTaskManager(String taskId, HttpServletRequest request) {
            /* * 獲取參數 */
            logger.info("跳轉到任務詳情頁面,任務ID:{}", taskId);
            if(StringUtils.isBlank(taskId)) return "/manageAudit";
            
            
            /* * 查看任務詳細信息 */
            Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
            if(task == null) {
                logger.info("任務ID:{}不存在!", taskId);
                return "/manageAudit";
            }
            
            
            /* * 完成任務 */
            Map<String, Object> paramMap = taskService.getVariables(taskId);
            request.setAttribute("task", task);
            request.setAttribute("paramMap", paramMap);
            return "manageAudit";
        }
        
        
    
        /**  * <p>上級審覈</p>  * @param request 請求  * @return String 受理結果  * @author FRH  * @time 2018年12月10日上午11:19:44  * @version 1.0  */
        @ResponseBody
        @RequestMapping(value="/higherLevelAudit")
        public String higherLevelAudit(HttpServletRequest request) {
            /* * 獲取請求參數 */
            String taskId = request.getParameter("taskId");
            String higherLevelOpinion = request.getParameter("sug");
            String auditStr = request.getParameter("audit");
            logger.info("上級審覈任務ID:{}", taskId);
            if(StringUtils.isBlank(taskId)) return "fail";
    
            
            /* * 查找任務 */
            Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
            if(task == null) {
                logger.info("審覈任務ID:{}查詢到任務爲空!", taskId);
                return "fail";
            }
            
            
            /* * 設置局部變量參數,完成任務 */
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("audit", "1".equals(auditStr) ? false : true);
            map.put("higherLevelOpinion", higherLevelOpinion);
            taskService.complete(taskId, map);
            return "success";
        }
        
        
        
        /**  * <p>部門經理審覈</p>  * @param request 請求  * @return String 受理結果  * @author FRH  * @time 2018年12月10日上午11:20:44  * @version 1.0  */
        @ResponseBody
        @RequestMapping(value="/divisionManagerAudit")
        public String divisionManagerAudit(HttpServletRequest request) {
            /* * 獲取請求參數 */
            String taskId = request.getParameter("taskId");
            String opinion = request.getParameter("sug");
            String auditStr = request.getParameter("audit");
            logger.info("上級審覈任務ID:{}", taskId);
            if(StringUtils.isBlank(taskId)) return "fail";
    
            
            /* * 查找任務 */
            Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
            if(task == null) {
                logger.info("審覈任務ID:{}查詢到任務爲空!", taskId);
                return "fail";
            }
            
            
            /* * 設置局部變量參數,完成任務 */
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("audit", "1".equals(auditStr) ? false : true);
            map.put("managerOpinion", opinion);
            taskService.complete(taskId, map);
            return "success";
        }
        
        
        /**  * <p>查看任務</p>  * @param request 請求  * @return String 任務展現頁面  * @author FRH  * @time 2018年12月10日上午11:21:33  * @version 1.0  */
        @RequestMapping(value="/toShowTask")
        public String toShowTask(HttpServletRequest request) {
            /* * 獲取請求參數 */
            List<Task> taskList = taskService.createTaskQuery().list();
            if(taskList == null || taskList.size() == 0) {
                logger.info("查詢任務列表爲空!");
                return "/task";
            }
            
            
            /* * 查詢全部任務,並封裝 */
            List<Map<String, String>> resultList = new ArrayList<Map<String, String>>();
            for(Task task : taskList) {
                Map<String, String> map = new HashMap<String, String>();
                map.put("taskId", task.getId());
                map.put("name", task.getName());
                map.put("createTime", task.getCreateTime().toString());
                map.put("assignee", task.getAssignee());
                map.put("instanceId", task.getProcessInstanceId());
                map.put("executionId", task.getExecutionId());
                map.put("definitionId", task.getProcessDefinitionId());
                resultList.add(map);
            }
            
            
            /* * 返回結果 */
            logger.info("返回集合:{}", resultList.toString());
            request.setAttribute("resultList", resultList);
            return "/task";
        }
        
    
    
        /**  * <p>輸出圖像</p>  * @param response 響應實體  * @param bpmnModel 圖像對象  * @param flowIds 已執行的線集合  * @param executedActivityIdList void 已執行的節點ID集合  * @author FRH  * @time 2018年12月10日上午11:23:01  * @version 1.0  */
        private void outputImg(HttpServletResponse response, BpmnModel bpmnModel, List<String> flowIds, List<String> executedActivityIdList) {
            InputStream imageStream = null;
            try {
                imageStream = processDiagramGenerator.generateDiagram(bpmnModel, executedActivityIdList, flowIds, "宋體", "微軟雅黑", "黑體", true, "png");
                // 輸出資源內容到相應對象
                byte[] b = new byte[1024];
                int len;
                while ((len = imageStream.read(b, 0, 1024)) != -1) {
                    response.getOutputStream().write(b, 0, len);
                }
                response.getOutputStream().flush();
            }catch(Exception e) {
                logger.error("流程圖輸出異常!", e);
            } finally { // 流關閉
                StreamUtils.closeInputStream(imageStream);
            }
        }
        
    
    
        /**  * <p>判斷流程是否完成</p>  * @param processInstanceId 流程實例ID  * @return boolean 已完成-true,未完成-false  * @author FRH  * @time 2018年12月10日上午11:23:26  * @version 1.0  */
        public boolean isFinished(String processInstanceId) {
            return historyService.createHistoricProcessInstanceQuery().finished().processInstanceId(processInstanceId).count() > 0;
        }
        
    }
    
    
        
        
        
    
       

    7. 效果

    在這裏插入圖片描述
        看出Activiti默認使用Spring的security,添加配置,關閉安全認證,以下:

    # 關閉activiti登陸驗證
    security:
     basic:
     enabled: false
    
        
        
        
    
       

        重啓後繼續訪問,可正常進入首頁(如仍是沒法進入首頁,請添加@EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class}),或者從pom.xml中移除Security包):
    在這裏插入圖片描述
        點擊我要請假後,獲得流程實例ID,再查看流程圖,以下:

    在這裏插入圖片描述

        附上首頁/index.html代碼,其它頁面相比比較簡單:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>首頁</title>
        <script th:src="@{/static/jquery.min.js}"></script>
        <script type="text/javascript"> $(function(){ // 點擊菜單 $(".show-page").bind("click", function(){ $(".main-body").html(""); $(".result-div").html(""); var url = $(this).attr("url"); $.ajax({ async : false, cache : false, type : 'POST', url : url, dataType : "html", error : function() { alert('請求失敗'); }, success : function(data) { $(".result-div").html(data); } }); }); // 點擊我要請假,開啓流程 $(".show-instance").bind("click", function(){ $(".main-body").html(""); $(".result-div").html(""); var url = $(this).attr("url"); $.ajax({ async : false, cache : false, type : 'POST', url : url, dataType : "html", error : function() { alert('請求失敗'); }, success : function(data) { $("input[name='instanceId']").val(data); } }); }); // 綁定查看流程圖 $(".show-img").bind("click", function(){ var instanceId = $("input[name='instanceId']").val(); if(instanceId == "") { alert("暫無流程!"); return; } var imgHtml = '<embed src="/demo/showImg?instanceId=' + instanceId + '" style="display:block;width:1000px;height:450px" />'; $(".result-div").html(imgHtml); }); // 查看任務 $(".show-task").bind("click", function(){ $.ajax({ async : false, cache : false, type : 'POST', url : "/demo/toShowTask", data : {"aaabbbccc":"aa"}, dataType : "html", error : function() { alert('請求失敗'); }, success : function(data) { $(".result-div").html(data); } }); }); }); /** * 員工提交申請 */ function toLeave() { $.ajax({ async : false, cache : false, type : 'POST', url : "/demo/employeeApply", dataType: "text", data: $(".employee-leave").serialize(), error : function() { alert('請求失敗'); }, success : function(data) { alert(data); } }); } /** * 上級審覈 */ function higherAudit() { $.ajax({ async : false, cache : false, type : 'POST', url : "/demo/higherLevelAudit", dataType: "text", data: $(".higher-audit").serialize(), error : function() { alert('請求失敗'); }, success : function(data) { alert(data); } }); } /** * 部門經理審覈 */ function managerAudit() { $.ajax({ async : false, cache : false, type : 'POST', url : "/demo/divisionManagerAudit", dataType: "text", data: $(".manager-audit").serialize(), error : function() { alert('請求失敗'); }, success : function(data) { alert(data); } }); } /** * 上級審覈 */ function viewTask(taskId, name) { var url = "/demo/viewTask"; if(name != "上級審覈") { url = "/demo/viewTaskManager"; } $.ajax({ async : false, cache : false, type : 'POST', url : url, data : {"taskId" : taskId}, dataType : "html", error : function() { alert('請求失敗'); }, success : function(data) { $(".result-div").html(data); } }); } </script>
    </head>
    <body>
        <!-- 菜單欄 -->
        <div class="main-menu">
            <button class="show-instance" url="/demo/start">我要請假</button>
            <button class="show-page" url="/demo/toLeave">開始填單</button>
            <button class="show-img">查看流程圖</button>
            <button class="show-task">查看任務</button>
        </div>
        <br/>
            流程實例ID:<input type="text" name="instanceId"/>
        <br/>
        <!-- 操做欄 -->
        <div class="main-body">
            
        </div>    
        <br/>
        <!-- 結果欄 -->
        <div class="result-div">
            <embed src="/demo/showImg?instanceId=5070fd58-f859-11e8-a359-484d7ec5762d" style="display:block;width:1000px;height:450px" />
            <br>
            <!-- <img src="/static/leave-process.png"/> -->
        </div>
    </body>
    </html>
    原文地址:https://blog.csdn.net/qq_40451631/article/details/84937251
    相關文章
    相關標籤/搜索