Spring學習之整合Activiti(一)

上一篇:Spring學習之整合MyBatis
下一篇:Spring學習之整合Activiti(二)

1. 背景

Activiti是現(xiàn)在應用很廣的一個流程框架,自己在學習過程中看到官網有Activiti Modeler可以使用頁面管理Activiti流程,所以試著自己整合SpringMVC+Activiti Modeler。

1.1. 工作流與工作流引擎

工作流(workflow)就是工作流程的計算模型,即將工作流程中的工作如何前后組織在一起的邏輯和規(guī)則在計算機中以恰當?shù)哪P瓦M行表示并對其實施計算。它主要解決的是“使在多個參與者之間按照某種預定義的規(guī)則傳遞文檔、信息或任務的過程自動進行,從而實現(xiàn)某個預期的業(yè)務目標,或者促使此目標的實現(xiàn)”。(我的理解就是:將部分或者全部的工作流程、邏輯讓計算機幫你來處理,實現(xiàn)自動化)

所謂工作流引擎是指workflow作為應用系統(tǒng)的一部分,并為之提供對各應用系統(tǒng)有決定作用的根據(jù)角色、分工和條件的不同決定信息傳遞路由、內容等級等核心解決方案。

1.2. BPMN2.0規(guī)范

BPMN(Business Process Model and Notation)--業(yè)務流程模型與符號。

BPMN是一套流程建模的標準,主要目標是被所有業(yè)務用戶容易理解的符號,支持從創(chuàng)建流程輪廓的業(yè)務分析到這些流程的最終實現(xiàn),知道最終用戶的管理監(jiān)控。

通俗一點其實就是一套規(guī)范,畫流程模型的規(guī)范。流程模型包括:流程圖、協(xié)作圖、編排圖、會話圖。詳細信息請google。

1.3. Activiti概述

1.3.1. Activiti由來

學習過Activiti的朋友都知道,Activiti的創(chuàng)始人也就是JBPM(也是一個優(yōu)秀的BPM引擎)的創(chuàng)始人,從Jboss離職后開發(fā)了一個新的BPM引擎:Activiti。所以,Activiti有很多地方都有JBPM的影子。所以,據(jù)說學習過JBPM的朋友學起Activiti來非常順手。

由于本人之前沒有工作流及JBPM的相關基礎,剛開始學習Activiti的時候可以說是無比痛苦的,根本不知道從何下手,這里也建議大家先進行工作流及BPMN2.0規(guī)范的學習,有了一定的基礎后,再著手學習Activiti。

1.3.2. Activiti簡介

Activiti是一個開源的工作流引擎,它實現(xiàn)了BPMN 2.0規(guī)范,可以發(fā)布設計好的流程定義,并通過api進行流程調度。

Activiti 作為一個遵從 Apache 許可的工作流和業(yè)務流程管理開源平臺,其核心是基于 Java 的超快速、超穩(wěn)定的 BPMN2.0 流程引擎,強調流程服務的可嵌入性和可擴展性,同時更加強調面向業(yè)務人員。

Activiti 流程引擎重點關注在系統(tǒng)開發(fā)的易用性和輕量性上。每一項 BPM 業(yè)務功能 Activiti 流程引擎都以服務的形式提供給開發(fā)人員。通過使用這些服務,開發(fā)人員能夠構建出功能豐富、輕便且高效的 BPM 應用程序。

2. 前期準備

本文是在Spring學習之整合MyBatis的基礎上完成的,所以不清楚的可以點擊查看

2.1. Activiti所需環(huán)境

使用Activiti,首先當然要有jdk了!6+版本就可以了。其次,要有一款IDE,我們當然會使用Eclipse。然后,web容器當然也要有,這里使用Tomcat7.0版本。然后就是Activiti的Eclipse插件了,這個后面再介紹。

2.2. 下載Activiti Demo包

下載activiti-5.22.0.rar,官網地址大家可以自行百度,但是下載會被墻,網盤地址:https://pan.baidu.com/s/1XVTammPbIrbzU1MK7TBFOA

2.3. 配置pom.xml文件

新增activiti依賴:

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <junit.version>4.10</junit.version>
    <spring.version>4.1.3.RELEASE</spring.version>
    <jackson.version>2.7.4</jackson.version>
    <activiti.version>5.22.0</activiti.version>
  </properties>

<!-- 其他依賴省略... -->
<!-- activiti start -->
    <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-bpmn-model</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-layout</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-common-rest</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-crystalball</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-diagram-rest</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-explorer</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-json-converter</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-modeler</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-simple-workflow</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.xmlgraphics</groupId>
        <artifactId>xmlgraphics-commons</artifactId>
        <version>1.2</version>
    </dependency>
    <!-- activiti end -->

Batik包 在添加activiti-modeler依賴后自動加載,不用顯式添加依賴

2.4. 配置spring-activiti.xml文件

在resources/spring/ 新建spring-activiti.xml配置文件:

image.png

spring-activiti.xml文件內容如下:

<?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:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">
        

    <!-- ==================== Activiti配置 start =================== -->
    <!-- 單例json對象 -->
    <bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper"/>

    <!-- activiti的processEngine配置 -->
    <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
        <property name="dataSource" ref="jdbcDataSource" />
        <property name="transactionManager" ref="transactionManager" />
        <!-- 自動創(chuàng)建表 -->
        <property name="databaseSchemaUpdate" value="false" />
        <!-- 是否激活Activiti的任務調度 -->
        <property name="jobExecutorActivate" value="false" />
        <!-- 是否開啟工作的數(shù)據(jù)日志 -->
        <!-- <property name="enableDatabaseEventLogging" value="true" /> -->
        <!--<property name="history" value="full"/>-->
        <property name="processDefinitionCacheLimit" value="10"/>
        <!-- mail -->
        <!-- <property name="mailServerHost" value="localhost"/>
        <property name="mailServerUsername" value="kafeitu"/>
        <property name="mailServerPassword" value="000000"/>
        <property name="mailServerPort" value="2025"/> -->
        <!-- UUID作為主鍵生成策略  -->
        <!-- <property name="idGenerator" ref="uuidGenerator" /> -->
        
        <!-- 生成流程圖的字體 -->
        <property name="activityFontName" value="宋體"/>
        <property name="labelFontName" value="宋體"/>
        
        <!-- 緩存支持
        <property name="processDefinitionCache">
            <bean class="me.kafeitu.demo.activiti.util.cache.DistributedCache" />
        </property>-->

        <!-- 自動部署 -->
        <!-- <property name="deploymentResources">
            <list>
                <value>classpath*:/deployments/*</value>
            </list>
        </property> -->

        <!-- 自定義表單字段類型 -->
        <!-- <property name="customFormTypes">
            <list>
                <bean class="me.kafeitu.demo.activiti.activiti.form.UsersFormType"/>
            </list>
        </property> -->
        <!--不創(chuàng)建identity表 -->
        <property name="dbIdentityUsed" value="false"/>
        <!-- 自定義用戶管理 -->
        <property name="customSessionFactories">
            <list>
                <bean class="com.zr.workflow.activiti.utils.CustomUserEntityManagerFactory">
                    <property name="customUserEntityManager" ref="customUserEntityManager"></property>
                </bean>
                <bean class="com.zr.workflow.activiti.utils.CustomGroupEntityManagerFactory">
                    <property name="customGroupEntityManager" ref="customGroupEntityManager"></property>
                </bean>
            </list>
        </property>
    </bean>
    <!-- 注冊自定義用戶管理類 -->
    <bean id="customUserEntityManager" class="com.zr.workflow.activiti.utils.CustomUserEntityManager"></bean>
    <bean id="customGroupEntityManager" class="com.zr.workflow.activiti.utils.CustomGroupEntityManager"></bean>
    
    
    <!-- 加載activiti引擎processEngine --> 
    <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean" destroy-method="destroy">
        <property name="processEngineConfiguration" ref="processEngineConfiguration" />
    </bean>
    
    <!-- activiti的7大服務接口 -->
    <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="formService" factory-bean="processEngine" factory-method="getFormService" />
    <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" /> -->
    <!-- ==================== Activiti配置 end =================== -->
</beans>

其中,需要注意的是:
1、databaseSchemaUpdate,項目啟動是否自動創(chuàng)建數(shù)據(jù)表(activiti的23張表),這里我設置成false,因為我沒有用activiti的用戶管理表,而是采用自定義的用戶管理表,所以在項目啟動之前需要將所有需要的表創(chuàng)建完成。
2、dbIdentityUsed:是否創(chuàng)建identity用戶相關表;由于項目已經有一套用戶管理表,所以這里設置成false
3、新增property customSessionFactories 指定自定義用戶管理工廠,包括:用戶管理和組管理。
其中,用戶管理工廠CustomUserEntityManagerFactory.java如下:

package com.zr.workflow.activiti.util;

import javax.annotation.Resource;

import org.activiti.engine.impl.interceptor.Session;
import org.activiti.engine.impl.interceptor.SessionFactory;
import org.activiti.engine.impl.persistence.entity.UserIdentityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 自定義用戶管理,用戶session
 * 這里的屬性需要與`spring-activiti.xml`中的該類的property一致
 * 不能使用identityService
 * @author Administrator
 *
 */
@Service
public class CustomUserEntityManagerFactory implements SessionFactory {
    
    @Resource
    private CustomUserEntityManager customUserEntityManager;
    

    @Override
    public Class<?> getSessionType() {
        return UserIdentityManager.class;
    }

    @Override
    public Session openSession() {
        return customUserEntityManager;
    }

    
    @Autowired
    public void setCustomUserEntityManager(CustomUserEntityManager customUserEntityManager) {
        this.customUserEntityManager = customUserEntityManager;
    }

}


組管理工廠 CustomGroupEntityManagerFactory.java:

package com.zr.workflow.activiti.util;

import javax.annotation.Resource;

import org.activiti.engine.impl.interceptor.Session;
import org.activiti.engine.impl.interceptor.SessionFactory;
import org.activiti.engine.impl.persistence.entity.GroupIdentityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 自定義用戶管理,用戶組session
 * 這里的屬性需要與spring-activiti.xml中的該類的property一致
 * 不能使用identityService
 * @author Administrator
 *
 */
@Service
public class CustomGroupEntityManagerFactory implements SessionFactory {
    
    @Resource
    private CustomGroupEntityManager customGroupEntityManager;
    
    
    @Override
    public Class<?> getSessionType() {
        return GroupIdentityManager.class;
    }

    @Override
    public Session openSession() {
        return customGroupEntityManager;
    }

    @Autowired  
    public void setCustomGroupEntityManager(CustomGroupEntityManager customGroupEntityManager) {
        this.customGroupEntityManager = customGroupEntityManager;
    }
}


4、注冊自動義用戶管理類。

由于我們這個項目中設置任務執(zhí)行人時沒有涉及到候選組,都是指定具體的執(zhí)行人或獲取某個角色組中所有的人員,然后調用setCandidateUsers方法將某個候選組所有的成員加進去,如果項目需要設置候選組,請擴展這兩個類:CustomUserEntityManagerCustomGroupEntityManager,重寫其中的
hasUser()(必須實現(xiàn))、
getUserName()(可選)、
getUserPassword()(可選)、
getUserEmail()(可選)、
getRoleList()(必須實現(xiàn))。
然后將實現(xiàn)類注冊到spring_activiti.xml中,替換以下兩行:

    <!-- 注冊自定義用戶管理類 -->
    <bean id="customUserEntityManager" class="com.zr.workflow.activiti.utils.CustomUserEntityManager"></bean>
    <bean id="customGroupEntityManager" class="com.zr.workflow.activiti.utils.CustomGroupEntityManager"></bean>
    

CustomUserEntityManager.java代碼如下:

package com.zr.workflow.activiti.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.activiti.engine.identity.Group;
import org.activiti.engine.identity.User;
import org.activiti.engine.impl.persistence.entity.GroupEntity;
import org.activiti.engine.impl.persistence.entity.UserEntity;
import org.activiti.engine.impl.persistence.entity.UserEntityManager;
import org.springframework.dao.EmptyResultDataAccessException;


import org.springframework.stereotype.Component;

@Component
public class CustomUserEntityManager extends UserEntityManager {


    public User findUserById(String userId) {
        System.out.println("CustomUserEntityManager  findUserById userId:" + userId);
        if (userId == null)
            return null;
        try {
            UserEntity userEntity = new UserEntity();
            boolean hasUser = hasUser(userId);
            if (!hasUser)return null;

            userEntity.setId(userId);
            userEntity.setFirstName(getUserName(userId));
            userEntity.setPassword(getUserPassword(userId));
            userEntity.setEmail(getUserEmail(userId));
            userEntity.setRevision(1);
            return userEntity;
        } catch (EmptyResultDataAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    public boolean hasUser(String userId) {
        return true;
    }
    
    public List<Group> findGroupsByUser(String userId) {
        System.out.println("CustomUserEntityManager  findGroupsByUser userId:" + userId);
        if (userId == null)
            return null;
        boolean hasUser = hasUser(userId);
        if (!hasUser)return null;
        List<Map<String,Object>> roleList = getRoleList(userId);
        List<Group> groupEntitys = new ArrayList<Group>();
        if(null != roleList) {
            for (Map<String, Object> role : roleList) {
                String roleCode = null == role.get("roleCode")?"":role.get("roleCode").toString();
                String roleName = null == role.get("roleName")?"":role.get("roleName").toString();
                GroupEntity groupEntity = toActivitiGroup(roleCode,roleName);
                groupEntitys.add(groupEntity);
            }
        }
        return groupEntitys;
    }

    public static GroupEntity toActivitiGroup(String roleCode,String roleName) {
        GroupEntity groupEntity = new GroupEntity();
        groupEntity.setRevision(1);
        groupEntity.setType("assignment");
        groupEntity.setId(roleCode);
        groupEntity.setName(roleName);
        return groupEntity;
    }
    
    public String getUserName(String userId) {
        return "";
    }
    
    public String getUserPassword(String userId) {
        return "";
    }
    
    public String getUserEmail(String userId) {
        return "";
    }
    
    public List<Map<String, Object>> getRoleList(String userId) {
        return null;
//      List<Map<String,Object>> roleList = new ArrayList<>();
//      List<MisUserRole> misUserRoleList = misUserRoleDao.findByUserId(userId);
//      for (MisUserRole misUserRole : misUserRoleList) {
//          final String roleId = misUserRole.getRoleId();
//          boolean isExitRole = misRoleDao.findRoleById(roleId) != null && misRoleDao.findRoleById(roleId).size()>0;
//          MisRole role = isExitRole ? misRoleDao.findRoleById(roleId).get(0) : null;
//          Map<String, Object> roleMap = new HashMap<>();
//          if(role != null){
//              roleMap.put("roleCode", role.getCode());
//              roleMap.put("roleName", role.getName());
//              
//          }
//          roleList.add(roleMap);
//      }
//      return roleList;
    }

}

CustomGroupEntityManager.java代碼如下:

package com.zr.workflow.activiti.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.activiti.engine.identity.Group;
import org.activiti.engine.impl.persistence.entity.GroupEntity;
import org.activiti.engine.impl.persistence.entity.GroupEntityManager;
import org.springframework.stereotype.Component;


@Component
public class CustomGroupEntityManager extends GroupEntityManager {

    public boolean hasUser(String userId) {
        return true;
    }
    
    public List<Group> findGroupsByUser(String userId) {
        System.out.println("CustomUserEntityManager  findGroupsByUser userId:" + userId);
        if (userId == null)
            return null;
        boolean hasUser = hasUser(userId);
        if (!hasUser)return null;
        List<Map<String,Object>> roleList = getRoleList(userId);
        List<Group> groupEntitys = new ArrayList<Group>();
        if(null != roleList) {
            for (Map<String, Object> role : roleList) {
                String roleCode = null == role.get("roleCode")?"":role.get("roleCode").toString();
                String roleName = null == role.get("roleName")?"":role.get("roleName").toString();
                GroupEntity groupEntity = toActivitiGroup(roleCode,roleName);
                groupEntitys.add(groupEntity);
            }
        }
        return groupEntitys;
    }

    public static GroupEntity toActivitiGroup(String roleCode,String roleName) {
        GroupEntity groupEntity = new GroupEntity();
        groupEntity.setRevision(1);
        groupEntity.setType("assignment");
        groupEntity.setId(roleCode);
        groupEntity.setName(roleName);
        return groupEntity;
    }
    
    public List<Map<String, Object>> getRoleList(String userId) {
        return null;
//      List<Map<String,Object>> roleList = new ArrayList<>();
//      List<MisUserRole> misUserRoleList = misUserRoleDao.findByUserId(userId);
//      for (MisUserRole misUserRole : misUserRoleList) {
//          final String roleId = misUserRole.getRoleId();
//          boolean isExitRole = misRoleDao.findRoleById(roleId) != null && misRoleDao.findRoleById(roleId).size()>0;
//          MisRole role = isExitRole ? misRoleDao.findRoleById(roleId).get(0) : null;
//          Map<String, Object> roleMap = new HashMap<>();
//          if(role != null){
//              roleMap.put("roleCode", role.getCode());
//              roleMap.put("roleName", role.getName());
//              
//          }
//          roleList.add(roleMap);
//      }
//      return roleList;
    }
}

3. 開始整合

3.1. 代碼拷貝

把Activiti-webapp-explorer2項目的resources下的stencilset.json文件拷至我的項目中的resources目錄下:

image.png

解壓出activiti-5.22.0.rar,看到如下目錄:

  1. database:里面存放的是Activiti使用到的數(shù)據(jù)庫信息的sql文件,它支持的數(shù)據(jù)庫類型如下圖,使用時只需執(zhí)行你自己的數(shù)據(jù)庫類型的文件即可。如:你的數(shù)據(jù)庫是mysql,那么就執(zhí)行activiti.mysql.create.*.sql即可。( 注意: 這里由于采用的是自定義用戶管理,則去掉activiti其中創(chuàng)建用戶相關表的sql語句,最終的sql文件請見項目中的init_activities_empty.sql:):
image.png

注意:
sql中新增表act_cus_user_task:


-- ----------------------------
-- Table structure for act_cus_user_task
-- ----------------------------
DROP TABLE IF EXISTS `act_cus_user_task`;
CREATE TABLE `act_cus_user_task` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `PROC_DEF_KEY` varchar(255) DEFAULT NULL COMMENT '流程id',
  `PROC_DEF_NAME` varchar(255) DEFAULT NULL COMMENT '流程名',
  `TASK_DEF_KEY` varchar(255) DEFAULT NULL COMMENT '節(jié)點id',
  `TASK_NAME` varchar(255) DEFAULT NULL COMMENT '節(jié)點名',
  `ACTIVITY_TYPE` varchar(255) DEFAULT '' COMMENT '當前Activiti節(jié)點類型:N-普通用戶任務;M-多實例節(jié)點',
  `TASK_TYPE` varchar(255) DEFAULT NULL COMMENT '節(jié)點的處理人員類型:assignee(人員)、candidateUser(候選人)、candidateGroup(候選組)',
  `CANDIDATE_NAME` varchar(255) DEFAULT NULL COMMENT '執(zhí)行人名',
  `CANDIDATE_IDS` varchar(255) DEFAULT NULL COMMENT '執(zhí)行人id',
  `GROUP_ID` varchar(255) DEFAULT NULL COMMENT '組id',
  `GROUP_NAME` varchar(255) DEFAULT NULL COMMENT '組名稱',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=465 DEFAULT CHARSET=utf8;

act_cus_user_task:業(yè)務用戶信息關聯(lián)至工作流用戶任務表

  1. docs:毫無疑問,api文檔。

  2. libs:使用Activiti所需要的所有的jar包和源文件。

  3. wars:官方給我們提供的示例Demo,通過使用Demo可以更加快速的了解Activiti。

找到wars目錄下的 activiti-explorer.war, 將其拷貝到Tomcat 的 webapps目錄下,然后運行tomcat: /bin/statup.bat(如果啟動時控制臺一閃而過,請看Tomcat7中雙擊bin文件的startup.bat一閃而過解決辦法),啟動tomcat 之后,會自動將activiti-explorer.war 解壓。

image.png

進入到下圖目錄中,將diagram-viewer,editor-app和modeler.html拷貝到自己工程的webapp目錄下。

image.png

將下圖路徑中的StencilsetRestResource.class。和下圖路徑中的ModelEditorJsonRestResource.class,ModelSaveRestResource.class。反編譯.

image.png
image.png

在自己的項目中新建對應的class,將反編譯內容復制進去,如圖:

image.png

其中,StencilsetRestResource類,是加載模板設置,加載resources下的stencilset.json,其代碼如下:

package com.zr.activiti.controller.design.model;

import java.io.InputStream;
import org.activiti.engine.ActivitiException;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StencilsetRestResource
{
  @RequestMapping(value={"/editor/stencilset"}, method={org.springframework.web.bind.annotation.RequestMethod.GET}, produces={"application/json;charset=utf-8"})
  public String getStencilset()
  {
      System.out.println("StencilsetRestResource.getStencilset-----------");
    InputStream stencilsetStream = getClass().getClassLoader().getResourceAsStream("stencilset.json");
    try {
      return IOUtils.toString(stencilsetStream, "utf-8");
    } catch (Exception e) {
      throw new ActivitiException("Error while loading stencil set", e);
    }
  }
}

ModelEditorJsonRestResource類是根據(jù)模型名稱讀取以Json存儲在act_ge_bytearray表中的source,編輯器接收到之后解析展示為圖片,其核心代碼如下:

 package com.zr.activiti.controller.design.model;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Model;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ModelEditorJsonRestResource
  implements ModelDataJsonConstants
{
  protected static final Logger LOGGER = LoggerFactory.getLogger(ModelEditorJsonRestResource.class);

  @Autowired
  private RepositoryService repositoryService;

  @Autowired
  private ObjectMapper objectMapper;

  @RequestMapping(value={"/model/{modelId}/json"}, method={org.springframework.web.bind.annotation.RequestMethod.GET}, produces={"application/json"})
  public ObjectNode getEditorJson(@PathVariable String modelId) { ObjectNode modelNode = null;

    System.out.println("ModelEditorJsonRestResource.getEditorJson---------");
    Model model = this.repositoryService.getModel(modelId);

    if (model != null) {
      try {
        if (StringUtils.isNotEmpty(model.getMetaInfo())) {
          modelNode = (ObjectNode)this.objectMapper.readTree(model.getMetaInfo());
        } else {
          modelNode = this.objectMapper.createObjectNode();
          modelNode.put("name", model.getName());
        }
        modelNode.put("modelId", model.getId());
        ObjectNode editorJsonNode = (ObjectNode)this.objectMapper.readTree(new String(this.repositoryService
          .getModelEditorSource(model
          .getId()), "utf-8"));
        modelNode.put("model", editorJsonNode);
      }
      catch (Exception e) {
        LOGGER.error("Error creating model JSON", e);
        throw new ActivitiException("Error creating model JSON", e);
      }
    }
    return modelNode;
  }
}

ModelSaveRestResource就是將經過編輯器編輯過的模型保存起來,核心代碼如下:

 package com.zr.activiti.controller.design.model;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;


import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Model;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ModelSaveRestResource
  implements ModelDataJsonConstants
{
  protected static final Logger LOGGER = LoggerFactory.getLogger(ModelSaveRestResource.class);

  @Autowired
  private RepositoryService repositoryService;

  @Autowired
  private ObjectMapper objectMapper;
  
  @RequestMapping(value={"/model/{modelId}/save"}, method={org.springframework.web.bind.annotation.RequestMethod.PUT})
  @ResponseStatus(HttpStatus.OK)
  public void saveModel(@PathVariable String modelId, @RequestBody MultiValueMap<String, String> values) { 
      try { 
          Model model = this.repositoryService.getModel(modelId);
          System.out.println("ModelSaveRestResource.saveModel----------");
          ObjectNode modelJson = (ObjectNode)this.objectMapper.readTree(model.getMetaInfo());
    
          modelJson.put("name", (String)values.getFirst("name"));
          modelJson.put("description", (String)values.getFirst("description"));
          model.setMetaInfo(modelJson.toString());
          model.setName((String)values.getFirst("name"));
          model.setKey(modelId);
    
          this.repositoryService.saveModel(model);
    
          this.repositoryService.addModelEditorSource(model.getId(), ((String)values.getFirst("json_xml")).getBytes("utf-8"));
    
          InputStream svgStream = new ByteArrayInputStream(((String)values.getFirst("svg_xml")).getBytes("utf-8"));
          TranscoderInput input = new TranscoderInput(svgStream);
    
          PNGTranscoder transcoder = new PNGTranscoder();
    
          ByteArrayOutputStream outStream = new ByteArrayOutputStream();
          TranscoderOutput output = new TranscoderOutput(outStream);
    
          transcoder.transcode(input, output);
          byte[] result = outStream.toByteArray();
          this.repositoryService.addModelEditorSourceExtra(model.getId(), result);
          outStream.close();
        } catch (Exception e){
          LOGGER.error("Error saving model", e);
          throw new ActivitiException("Error saving model", e);
        }
  } 
}

3.2. 核心組件介紹

3.2.1. 關鍵對象
  1. Deployment:流程部署對象,部署一個流程時創(chuàng)建。

  2. ProcessDefinitions:流程定義,部署成功后自動創(chuàng)建。

  3. ProcessInstances:流程實例,啟動流程時創(chuàng)建。

  4. Task:任務,在Activiti中的Task僅指有角色參與的任務,即定義中的UserTask。

  5. Execution:執(zhí)行計劃,流程實例和流程執(zhí)行中的所有節(jié)點都是Execution,如UserTask、ServiceTask等。

3.2.2. 服務接口
  1. ProcessEngine:流程引擎的抽象,通過它我們可以獲得我們需要的一切服務。

  2. RepositoryService:Activiti中每一個不同版本的業(yè)務流程的定義都需要使用一些定義文件,部署文件和支持數(shù)據(jù)(例如BPMN2.0 XML文件,表單定義文件,流程定義圖像文件等),這些文件都存儲在Activiti內建的Repository中。RepositoryService提供了對 repository的存取服務。

  3. RuntimeService:在Activiti中,每當一個流程定義被啟動一次之后,都會生成一個相應的流程對象實例。RuntimeService提供了啟動流程、查詢流程實例、設置獲取流程實例變量等功能。此外它還提供了對流程部署,流程定義和流程實例的存取服務。

  4. TaskService: 在Activiti中業(yè)務流程定義中的每一個執(zhí)行節(jié)點被稱為一個Task,對流程中的數(shù)據(jù)存取,狀態(tài)變更等操作均需要在Task中完成。TaskService提供了對用戶Task 和Form相關的操作。它提供了運行時任務查詢、領取、完成、刪除以及變量設置等功能。

  5. IdentityService: Activiti中內置了用戶以及組管理的功能,必須使用這些用戶和組的信息才能獲取到相應的Task。IdentityService提供了對Activiti 系統(tǒng)中的用戶和組的管理功能。

  6. ManagementService: ManagementService提供了對Activiti流程引擎的管理和維護功能,這些功能不在工作流驅動的應用程序中使用,主要用于Activiti系統(tǒng)的日常維護。

  7. HistoryService: HistoryService用于獲取正在運行或已經完成的流程實例的信息,與RuntimeService中獲取的流程信息不同,歷史信息包含已經持久化存儲的永久信息,并已經被針對查詢優(yōu)化。

現(xiàn)在至少要知道有這些對象和接口。并結合Activiti Api這一章節(jié)來看,你就會對部署流程、啟動流程、執(zhí)行任務等操作有一個基本的概念。

3.3. 代碼修改

1、在editor-app目錄下找到app-cfg.js文件,將'contextRoot' : '/activiti-explorer/service',修改為本項目的路徑。如下所示:

/*
 * Activiti Modeler component part of the Activiti project
 * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
'use strict';

var ACTIVITI = ACTIVITI || {};
ACTIVITI.CONFIG = {
        'contextRoot' : appContextRoot+'/service',
    };
//ACTIVITI.CONFIG = {
//  'contextRoot' : '/activiti-explorer/service',
//};

2、修改modeler.html
去掉Activiti Afresco的logo標題欄,并且把樣式上的空白欄去掉:
注意不要把該文本刪除,建議加style=”display:none”,刪除后其會造成底層下的一些內容有40個像數(shù)的東西顯示不出來。:

image.png

在load app-cfg.js之前加上以下腳本:

    <script type="text/javascript">
        var pathName = window.document.location.pathname;
        var appContextRoot=pathName.substring(0,pathName.substr(1).indexOf('/')+1);;
    </script>

如圖所示:

image.png

3.4. 測試

在瀏覽器中輸入以下地址:
http://localhost:8080/ActivitiWorkFlowDemo(自己項目名)/model/create 請求創(chuàng)建模型接口
獲取到modelId后;
再輸入http://localhost:8080/ActivitiWorkFlowDemo/modeler.html?modelId=獲取到的modelId 即可進入modeler.html創(chuàng)建模型頁面。
結果如下:

image.png

如果報:
Failed to load resource: the server responded with a status of 404 (Not Found)
首先確認路徑沒有錯。

在請求分發(fā)時,沒有找到"/ActivitiWorkFlowDemo/modeler.html"的映射(No mapping found for ...)。原來是spring mvc攔截了頁面對靜態(tài)資源的請求,但你的controller中又沒有這個路徑的映射,所以頁面對靜態(tài)資源文件的請求并沒有正確下發(fā),那么該怎么解決這個問題呢?下面我給出參考的方法:

解決方案:

1.采用<mvc:default-servlet-handler />。 在spring mvc的xml配置文件上加上一句:<mvc:default-servlet-handler />。如下圖所示:


    <!-- 靜態(tài)資源文件,不會被Spring MVC攔截 -->
    <mvc:default-servlet-handler />
    <mvc:resources location="/resources/" mapping="/resources/**" />

加入之后,spring mvc就會對進入DispatcherServlet的URL進行篩查,如果發(fā)現(xiàn)是靜態(tài)資源的請求,就將該請求轉由Web應用服務器默認的Servlet處理,如果不是靜態(tài)資源的請求,才由DispatcherServlet繼續(xù)處理。這個方法是最快捷的。

2.采用<mvc:resources />。可以使用<mvc:resources />,并將靜態(tài)資源放在WEB-INF目錄下(或者其他你喜歡的地方),然后在springMVC-servlet中添加如下配置:

<mvc:resources location="/文件路徑" mapping="/映射路徑"/>

根據(jù)實際情況填寫路徑。

3.在web.xml文件中將spring mvc的攔截路徑改為/springmvc/*("springmvc"可以替換成你喜歡的路徑)

附加知識點:Activiti工作流的自帶數(shù)據(jù)表的含義

  1. 資源庫流程規(guī)則表
    1)act_re_deployment 部署信息表
    2)act_re_model 流程設計模型部署表
    3)act_re_procdef 流程定義數(shù)據(jù)表

  2. 運行時數(shù)據(jù)庫表
    1)act_ru_execution 運行時流程執(zhí)行實例表
    2)act_ru_identitylink 運行時流程人員表,主要存儲任務節(jié)點與參與者的相關信息
    3)act_ru_task 運行時任務節(jié)點表
    4)act_ru_variable 運行時流程變量數(shù)據(jù)表

  3. 歷史數(shù)據(jù)庫表
    1)act_hi_actinst 歷史節(jié)點表
    2)act_hi_attachment 歷史附件表
    3)act_hi_comment 歷史意見表
    4)act_hi_identitylink 歷史流程人員表
    5)act_hi_detail 歷史詳情表,提供歷史變量的查詢
    6)act_hi_procinst 歷史流程實例表
    7)act_hi_taskinst 歷史任務實例表
    8)act_hi_varinst 歷史變量表

  4. 組織機構表
    1)act_id_group 用戶組信息表
    2)act_id_info 用戶擴展信息表
    3)act_id_membership 用戶與用戶組對應信息表
    4)act_id_user 用戶信息表
    這四張表很常見,基本的組織機構管理,關于用戶認證方面建議還是自己開發(fā)一套,組件自帶的功能太簡單,使用中有很多需求難以滿足

  5. 通用數(shù)據(jù)表
    1)act_ge_bytearray 二進制數(shù)據(jù)表
    2)act_ge_property 屬性數(shù)據(jù)表存儲整個流程引擎級別的數(shù)據(jù),初始化表結構時,會默認插入三條記錄

  6. 自定義數(shù)據(jù)表
    act_cus_user_task:流程各節(jié)點執(zhí)行人信息表,啟動流程之前初始化流程節(jié)點信息時插入節(jié)點key、節(jié)點名稱、業(yè)務key和已知的各節(jié)點執(zhí)行人信息。

:代碼已上傳至GitHub:
https://github.com/ruoki/ActivitiWorkFlowDemo
歡迎star

上一篇:Spring學習之整合MyBatis
下一篇:Spring學習之整合Activiti(二)

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