Activiti 工作流引擎的初步使用

最近領導讓我研究下工作流,于是查啊查就查到了Activiti,特么剛開始一直查的是Activity,查出來一堆Android的東西,我也是醉了。話不多說,下面就記錄下這2天的研究成果吧。

所用環境

Maven工程
 JDK:jdk1.8.0_73
 IDE:eclipse Mars.2 Release (4.5.2)
 數據庫:mysql 5.1.39-ndb-7.0.9-cluster-gpl
 SSM框架:(spring + spring-mvc)4.3.2.RELEASE + mybatis3.4.1
 Activiti:5.21.0

spring+mvc+mybatis整合就不貼了,網上一大堆了

eclipse安裝流程設計插件

eclipse依次點擊 Help -> Install New Software -> Add:
 Name:Activiti Designer
 Location:http://activiti.org/designer/update/
 點擊OK選中插件安裝即可

添加 Activiti 到項目中

  1. 在 pom.xml 中添加 Activiti 依賴
    <activiti.version>5.21.0</activiti.version>

     <dependency>
         <groupId>org.activiti</groupId>
         <artifactId>activiti-engine</artifactId>
         <version>${activiti.version}</version>
     </dependency>
     
     <dependency>
         <groupId>org.activiti</groupId>
         <artifactId>activiti-spring</artifactId>
         <version>${activiti.version}</version>
     </dependency>
     
     <dependency>
         <groupId>org.activiti</groupId>
         <artifactId>activiti-rest</artifactId>
         <version>${activiti.version}</version>
     </dependency>
    

2.新建 applicationContext-activiti.xml,別忘了在主配置文件中將其import

<?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:aop="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">

        <!-- 流程配置 -->
        <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
            <property name="dataSource" ref="dataSource" />
            <property name="transactionManager" ref="transactionManager" />
            <property name="databaseSchemaUpdate" value="true" />
            <property name="jobExecutorActivate" value="true" />
            <!-- 以下2個是為了防止生成流程圖片時出現亂碼 -->
            <property name="activityFontName" value="宋體"/>  
            <property name="labelFontName" value="宋體"/>  
        </bean>

        <!-- 流程引擎 -->
        <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
            <property name="processEngineConfiguration" ref="processEngineConfiguration" />
        </bean>

        <!-- 流程服務 -->
        <bean id="repositoryService" factory-bean="processEngine"
            factory-method="getRepositoryService" />
        <bean id="runtimeService" factory-bean="processEngine"
            factory-method="getRuntimeService" />
        <bean id="taskService" factory-bean="processEngine"
            factory-method="getTaskService" />
        <bean id="historyService" factory-bean="processEngine"
            factory-method="getHistoryService" />
        <bean id="managementService" factory-bean="processEngine"
            factory-method="getManagementService" />
        <bean id="IdentityService" factory-bean="processEngine"
            factory-method="getIdentityService" />
    
    </beans>

3.啟動項目,如果未出現錯誤,Activiti會在連接的數據庫中自動新建25張表,如下:

25張表.png

至此Activiti流程引擎添加完畢,下面開始使用Activiti,實現一次請假流程

設計流程

流程設計插件安裝成功后會在eclipse新建向導中出現Activiti向導,如圖

Paste_Image.png

1.我們新建一個 Activiti Diagram 命名為 leave.bpmn,完成時如下圖:

Paste_Image.png

主要就是拖拖拉拉,從左至右控件分別為
StartEvent,UserTask,ExlusiveGateway,UserTask,EndEvent;連線都是SequenceFlow

屬性可在Properties視圖中設置,如果沒有這個視圖,可在 eclipse 中依次點擊
Window->Show View->Other 搜索Properties點擊OK即可

  • 流程屬性:一般設置一個Id,Name,NameSpace就可以了,此處為分別為leaveProcess、Leave Process、http://www.mario.com; 這個Id在之后啟動流程時會用到,不同流程Id不可相同
  • 開始事件(StartEvent):流程開始
  • 結束事件(EndEvent):流程結束
  • 用戶任務(UserTask):主要用到Id,Name,Assignee
    Assignee為此任務的辦理人,在查找任務時需要用到,三個任務分別指派給 apply,pm,boss
Paste_Image.png
Paste_Image.png
  • 排他網關(ExlusiveGateway):只會尋找唯一一條能走完的順序流
  • 順序流(SequenceFlow):主要用到Id,Name,Condition
    Condition用于指定該順序流表達式 ,day的值會在調用執行任務方法時傳入
Paste_Image.png

除了使用可視化組件,我們也可以通過xml來設計流程,以上流程的xml定義如下:

<?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.mario.com">
  <process id="leaveProcess" name="Leave Process" isExecutable="true">
    <startEvent id="startevent1" name="開始"></startEvent>
    <userTask id="usertask1" name="請假申請" activiti:assignee="apply"></userTask>
    <exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
    <sequenceFlow id="flow2" name="天數判斷" sourceRef="usertask1" targetRef="exclusivegateway1"></sequenceFlow>
    <userTask id="usertask2" name="審批(項目經理)" activiti:assignee="pm"></userTask>
    <sequenceFlow id="flow3" name="小于等于三天" sourceRef="exclusivegateway1" targetRef="usertask2">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${day<=3}]]></conditionExpression>
    </sequenceFlow>
    <userTask id="usertask3" name="審批(老板)" activiti:assignee="boss"></userTask>
    <sequenceFlow id="flow4" name="大于三天" sourceRef="exclusivegateway1" targetRef="usertask3">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${day>3}]]></conditionExpression>
    </sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow5" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
    <sequenceFlow id="flow6" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
    <sequenceFlow id="flow7" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_leaveProcess">
    <bpmndi:BPMNPlane bpmnElement="leaveProcess" id="BPMNPlane_leaveProcess">
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="30.0" y="211.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
        <omgdc:Bounds height="55.0" width="105.0" x="110.0" y="201.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1">
        <omgdc:Bounds height="40.0" width="40.0" x="285.0" y="208.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
        <omgdc:Bounds height="55.0" width="105.0" x="400.0" y="120.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
        <omgdc:Bounds height="55.0" width="105.0" x="400.0" y="290.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="560.0" y="211.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="215.0" y="228.0"></omgdi:waypoint>
        <omgdi:waypoint x="285.0" y="228.0"></omgdi:waypoint>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="14.0" width="48.0" x="230.0" y="228.0"></omgdc:Bounds>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="305.0" y="208.0"></omgdi:waypoint>
        <omgdi:waypoint x="452.0" y="175.0"></omgdi:waypoint>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="14.0" width="100.0" x="295.0" y="180.0"></omgdc:Bounds>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="305.0" y="248.0"></omgdi:waypoint>
        <omgdi:waypoint x="452.0" y="290.0"></omgdi:waypoint>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="14.0" width="48.0" x="285.0" y="257.0"></omgdc:Bounds>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
        <omgdi:waypoint x="452.0" y="175.0"></omgdi:waypoint>
        <omgdi:waypoint x="577.0" y="211.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
        <omgdi:waypoint x="452.0" y="290.0"></omgdi:waypoint>
        <omgdi:waypoint x="577.0" y="246.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
        <omgdi:waypoint x="65.0" y="228.0"></omgdi:waypoint>
        <omgdi:waypoint x="110.0" y="228.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

流程部署

有了流程圖,我們就可以部署該流程了,Activiti提供多種部署方法(有自動部署,手動部署等),這里演示兩種手動部署方法

Workflow.java代碼片段

private static ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
/**
 * 通過定義好的流程圖文件部署,一次只能部署一個流程
 */
public static void deploy() {
    RepositoryService repositoryService = processEngine.getRepositoryService();
    Deployment deployment = repositoryService.createDeployment()
            .addClasspathResource("death/note/lawliet/web/workflow/leave.bpmn").deploy();
}
/**
 * 將多個流程文件打包部署,一次可以部署多個流程
 */
public void deployByZip() {
    InputStream is = this.getClass().getClassLoader().getResourceAsStream("diagrams/bpm.zip");
    ZipInputStream zip = new ZipInputStream(is);
    Deployment deployment = processEngine
            .getRepositoryService()
            .createDeployment()
            .addZipInputStream(zip)
            .deploy();
}

方便起見,通過一個Deploy按鈕來部署流程

Paste_Image.png

部署成功后,會分別在 act_ge_bytearray,act_re_deployment,act_re_procdef三張表插入相應數據,多次部署同一流程的話會增加版本號,以此獲取最新的流程

啟動流程

我們通過一個表單來用于請假申請

Paste_Image.png

我們定義一個 Leave 對象用于保存請假信息,相應的數據表為 leave 。Result 對象用于封裝一些返回信息。這里的 "leaveProcess" 就是在流程屬性中定義的Id。啟動成功后會返回一個 ProcessInstance 對象,這個對象主要是一些流程的基本信息,具體可以查看文檔或源碼。這里將返回的流程實例 id 存入到我們的 leave 表,以便以后可以通過這個 id 查詢相關的流程。

@ResponseBody
@RequestMapping(value = "/save", method = RequestMethod.POST)
public Result save(@RequestBody Leave user) {
    Result result = new Result();
    ProcessInstance pi = Workflow.startInstanceByKey("leaveProcess");
    user.setInstaceId(pi.getId());
    leaveService.insert(user);
    result.info(true, 0, "保存成功");
    return result;
}

Workflow.java代碼片段

public static ProcessInstance startInstanceByKey(String instanceByKey) {
    RuntimeService runtimeService = processEngine.getRuntimeService();
    ProcessInstance instance = runtimeService.startProcessInstanceByKey(instanceByKey);
    return instance;
}

流程啟動成功后會在 act_hi_actinst,act_hi_identitylink,act_hi_procinst,act_hi_taskinst,act_ru_execution,act_ru_identitylink,act_ru_task 表中插入相應數據。我們比較關心的就是 act_ru_task 表了,它存儲了任務的相關信息。

查看任務

流程啟動完畢后,應該就是進入了請假申請任務,我們可以通過請假申請的辦理人 apply 來查詢該任務(當然還有其他方法),這里可以指定不同查詢條件和過濾條件。返回 taskList 就是當前的任務列表了,Task是Activiti為我們定義好的接口對象,主要封裝了任務的信息。
 這里由于 Activiti 的Task是接口對象無法轉換為json,所以自定義了一個Task對象,來轉換成json

@ResponseBody
@RequestMapping(value = "/data/{assignee}")
public List<death.note.lawliet.web.model.Task> data(@PathVariable String assignee){
    List<Task> tasks = Workflow.findTaskByAssignee(assignee);
    
    List<death.note.lawliet.web.model.Task> list = new ArrayList<>();
    for(Task task : tasks){
        death.note.lawliet.web.model.Task t = new death.note.lawliet.web.model.Task();
        t.setTaskId(task.getId());
        t.setName(task.getName());
        t.setAssignee(task.getAssignee());
        t.setExecutionId(task.getExecutionId());
        t.setProcessInstanceId(task.getProcessInstanceId());
        t.setProcessDefinitionId(task.getProcessDefinitionId());
        list.add(t);
    }
    return list;
}

Workflow.java代碼片段

 public static List<Task> findTaskByAssignee(String assignee) {
    TaskService taskService = processEngine.getTaskService();
    List<Task> taskList = taskService.createTaskQuery().taskAssignee(assignee).list();
    return taskList;
}
Paste_Image.png

查看流程圖

我們可以通過流程定義ID(processDefinitionId)來獲取流程圖

@RequestMapping(value = "/shwoImg/{procDefId}")  
public void shwoImg(@PathVariable String procDefId,HttpServletResponse response){  
    try {  
        InputStream pic = Workflow.findProcessPic(procDefId);  
          
        byte[] b = new byte[1024];  
        int len = -1;  
        while((len = pic.read(b, 0, 1024)) != -1) {  
            response.getOutputStream().write(b, 0, len);  
        }  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
}  

Workflow.java代碼片段

public static InputStream findProcessPic(String procDefId) throws Exception {
    RepositoryService repositoryService = processEngine.getRepositoryService();
    ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId)
            .singleResult();
    String diagramResourceName = procDef.getDiagramResourceName();
    InputStream imageStream = repositoryService.getResourceAsStream(procDef.getDeploymentId(), diagramResourceName);
    return imageStream;
}

然后通過processDefinitionId,executionId 來獲取當前正在執行任務的位置坐標,以便用于標識流程圖上的位置。ActivityImpl 對象封裝了任務的位置信息,包括xy坐標,長和寬。自定義 Rect 對象用于轉換json

@ResponseBody
@RequestMapping(value = "/showImg/{procDefId}/{executionId}")
public Rect showImg(@PathVariable String procDefId,@PathVariable String executionId ) {
    Rect rect = new Rect();
    try {
        ActivityImpl img = Workflow.getProcessMap(procDefId,executionId );
        rect.setX(img.getX());
        rect.setY(img.getY());
        rect.setWidth(img.getWidth());
        rect.setHeight(img.getHeight());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return rect;
}

Workflow.java代碼片段

public static ActivityImpl getProcessMap(String procDefId, String executionId) throws Exception {
    ActivityImpl actImpl = null;
    RepositoryService repositoryService = processEngine.getRepositoryService();
    //獲取流程定義實體
    ProcessDefinitionEntity def = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
            .getDeployedProcessDefinition(procDefId);
    RuntimeService runtimeService = processEngine.getRuntimeService();
    //獲取執行實體
    ExecutionEntity execution = (ExecutionEntity) runtimeService.createExecutionQuery().executionId(executionId)
            .singleResult();
    // 獲取當前任務執行到哪個節點
    String activitiId = execution.getActivityId();
    // 獲得當前任務的所有節點
    List<ActivityImpl> activitiList = def.getActivities();
    for (ActivityImpl activityImpl : activitiList) {
        String id = activityImpl.getId();
        if (id.equals(activitiId)) {
            actImpl = activityImpl;
            break;
        }
    }
    return actImpl;
}

最終生成圖片時分別調用2個 showImg 方法即可,紅框可以根據返回的 Rect 來繪制,起始坐標根據自己的布局自行調整

LeavePicController.js片段

var pic.rect = {};
var pic.procDefId = $stateParams.procDefId;
$http.get('workflow/showImg/'+$stateParams.procDefId +'/'+$stateParams.executionId) 
          .success(function(data) {
              pic.rect.x = data.x;
              pic.rect.y = data.y;
              pic.rect.width = data.width;
              pic.rect.height = data.height;
          });

picture.html

<div class="container-fluid" ng-controller="LeavePicController as pic">
  <img src="workflow/showImg/{{pic.procDefId}}">
  <div style="position:absolute; border:2px solid red;
      left:{{pic.rect.x + 20 }}px;
      top:{{pic.rect.y + 88 }}px;
      width:{{pic.rect.width }}px;
      height:{{pic.rect.height }}px;">
  </div>  
</div>
Paste_Image.png

流程審批

通過 taskId 就可以對當前執行的任務進行審批,這里的 day 應該從 leave 表中查詢出來,方便起見就寫死了,這個day也就是在順序流的Condition中指定的變量,小于等于3就會流向項目經理(pm)審批任務了

@ResponseBody
@RequestMapping(value = "/check/{taskId}")
public Result check(@PathVariable String taskId) {
    Result result = new Result();
    Map<String, Object> map = new HashMap<>();
    map.put("day", 3);
    Workflow.completeTask(taskId,map);
    result.info(true, 0, "審批成功");
    return result;
}

Workflow.java代碼片段

public static void completeTask(String taskid,Map<String, Object> map map) {
    TaskService taskService = processEngine.getTaskService();
    taskService.complete(taskid, map);
}

審批成功后,就需要通過辦理人(pm)來查詢任務了,并且請假辦理(apply)中的任務被移除了

Paste_Image.png
Paste_Image.png

同時流程圖也流向了下一節點

Paste_Image.png

項目經理再執行一次審批方法,這個流程就算走完了

最后

以上就是一次相對簡單的流程:部署,啟動,查詢任務,顯示流程圖,審批。Activiti流程引擎還有很多核心操作,包括駁回、會簽、轉辦、中止、掛起等,等有空的時候再深入研究吧
 最后的最后,初來乍到,不喜勿噴或輕噴 - -!Thanks...

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容