Spring Boot 整合 Activiti6.0.0

工作流之前一直有在了解,也做過預(yù)研,算是參加工作后第一個(gè)預(yù)研的項(xiàng)目吧,以前的工作流對(duì)數(shù)據(jù)和流程控制的耦合太嚴(yán)重蚀之,對(duì)于系統(tǒng)之間的集成較困難,所以當(dāng)時(shí)預(yù)研的一個(gè)結(jié)論就是要把數(shù)據(jù)和流程控制分開捷泞,也做了些DEMO足删,最后進(jìn)去另外項(xiàng)目組了,此事就沒有進(jìn)行下去了锁右。

現(xiàn)在工作中需要使用工作流失受,選型用Activiti讶泰,可能是Actitivi比較簡單,在國內(nèi)受歡迎程度較高吧拂到,用的人比較多痪署。但是文檔相對(duì)較少,而且貌似大家都在使用較老的版本兄旬,目前Activiti 7.0.0是已經(jīng)有的狼犯,Activiti 6.0.0在2017年發(fā)布,應(yīng)該出來時(shí)間也算比較久了领铐,但是在一些技術(shù)交流群里面大家還是在用5.22.0版本悯森。 目前網(wǎng)上對(duì)于Activiti 5.x 的DEMO 還是比較多的,如使用自定義數(shù)據(jù)源绪撵,整合自有業(yè)務(wù)用戶角色關(guān)系呐馆,整合建模工具等等。Activiti 6.0.0 相對(duì)于 5.x包結(jié)構(gòu)和類都做了一些優(yōu)化莲兢,整合一起遇到了一些困難,記錄一下分享到給各位伙伴续膳。

此處 有Activiti 5.x 遷移到 6.0.0的主要變化改艇,遇到問題可以參考。

目錄 (貌似導(dǎo)航無用哦 應(yīng)該是簡書的問題)

Spring boot 1.5.6 + Activiti 6.0.0

先給出項(xiàng)目中的最后的POM文件:

<?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>

    <artifactId>workflow</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <properties>
        <activiti.version>6.0.0</activiti.version>
        <activiti-exp.version>5.22.0</activiti-exp.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter-basic</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.197</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.driver.version}</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter-rest-api</artifactId>
            <version>6.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter-actuator</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter-jpa</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-explorer</artifactId>
            <version>${activiti-exp.version}</version>
        </dependency>
        <!--<dependency> 這個(gè)包看起來沒什么用坟岔,一旦加入的話由于他依賴于5.22的activiti-engine包谒兄,所以不能兼容6.0.0
            <groupId>org.activiti</groupId>
            <artifactId>activiti-diagram-rest</artifactId>
            <version>${activiti-exp.version}</version>
        </dependency>-->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-simple-workflow</artifactId>
            <version>${activiti-exp.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-codec</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-css</artifactId>
            <version>1.7</version>
        </dependency>

        <!-- Testing -->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.18.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.subethamail</groupId>
            <artifactId>subethasmtp-wiser</artifactId>
            <version>1.2</version>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>
</project>

配置Activiti數(shù)據(jù)源

一般會(huì)有兩個(gè)數(shù)據(jù)源,業(yè)務(wù)數(shù)據(jù)源+Activiti(自帶)數(shù)據(jù)庫, 業(yè)務(wù)數(shù)據(jù)源的配置這里不提社付,大家應(yīng)該個(gè)有自己的配置方式承疲,簡單的直接使用spring boot默認(rèn)的屬性配置方式即可,這里重點(diǎn)列出配置Activiti自帶數(shù)據(jù)源(自定義節(jié)點(diǎn)spring.activiti.datasource來存儲(chǔ)activiti數(shù)據(jù)源配置):

spring:
  activiti:
    async-executor-activate: true
    mail-server-use-ssl: true
    #自動(dòng)更新數(shù)據(jù)庫
    database-schema-update: true
    #校驗(yàn)流程文件鸥咖,默認(rèn)校驗(yàn)resources下的processes文件夾里的流程文件
    #check-process-definitions: false
    #restApiEnabled: false
    datasource:
      driverClassName: com.mysql.jdbc.Driver
      type: com.zaxxer.hikari.HikariDataSource
      url: jdbc:mysql://127.0.0.1:3306/activiti6ui?useUnicode=true&characterEncoding=UTF-8
      username: root
      password: 123456
      hikari:
        maximum-pool-size: 30
        idle-timeout: 30000
        connection-test-query: select 1 from DUAL
        auto-commit: true
        minimum-idle: 5
        connection-timeout: 30000
        pool-name: activiti-datasource-pool

這里使用的屬性是spring.activiti.datasource 屬于自定義屬性燕鸽,所以需要如下配置:

@Configuration
@EnableTransactionManagement//開啟事物管理
@EnableJpaRepositories(//自定義數(shù)據(jù)管理的配置
        entityManagerFactoryRef = "activitiEntityManagerFactory", //指定EntityManager的創(chuàng)建工廠Bean
        transactionManagerRef = "activitiTransactionManager",//指定事物管理的Bean
        basePackages = {"com.fintech.insurance.micro.workflow.persist.entity"})//指定管理的實(shí)體位置
public class ActivitiConfiguration extends AbstractProcessEngineAutoConfiguration {

    @Bean("activitiDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.activiti.datasource")
    public DataSourceProperties activitiDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean("activitiDataSource")
    public HikariDataSource activitiDataSource() {
        DataSourceProperties activitiDataSourceProperties = activitiDataSourceProperties();
        return (HikariDataSource)activitiDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }

    @Primary
    @Bean("insuranceActivitiConfig")
    public SpringProcessEngineConfiguration insuranceActivitiConfig(PlatformTransactionManager transactionManager,
            SpringAsyncExecutor springAsyncExecutor) throws IOException {
        SpringProcessEngineConfiguration springProcessEngineConfiguration = baseSpringProcessEngineConfiguration(
                activitiDataSource(),
                transactionManager,
                springAsyncExecutor);

        return springProcessEngineConfiguration;
    }

}

這里使用spring boot的配置重載了SpringProcessEngineConfiguration, 使得springProcessEngineConfiguration能讀取activiti自有數(shù)據(jù)源。

配置自定義用戶-組/角色管理

Activiti有自己的用戶啼辣、角色管理功能以及數(shù)據(jù)庫表啊研,用于給工作流中的工作項(xiàng)分配、指派任務(wù)鸥拧,Activiti中與此相關(guān)的表主要有:
身份相關(guān)的表:
act_id_user:用戶表
act_id_info:用戶信息表
act_id_group:組(或角色)
act_id_memership:用戶與組的關(guān)系 党远,查詢?nèi)蝿?wù)獲選人需關(guān)聯(lián)的表
自己的業(yè)務(wù)系統(tǒng)一般也有自己的用戶-角色(組)管理模塊,可參考鏈接 富弦,上面簡要說了幾種解決方案沟娱,可以根據(jù)自己的現(xiàn)實(shí)情況進(jìn)行決策。我這邊是重新自己實(shí)現(xiàn)了UserEntityManager腕柜, GroupEntityManager接口济似,把自己業(yè)務(wù)中的用戶-角色(組管理)注入到Activiti引擎之中矫废,這里只重寫一兩個(gè)比較重要的方法,大部分方法不需要實(shí)現(xiàn)碱屁,基本能滿足需求磷脯。這部分跟Activiti 5.x的整合不一樣,網(wǎng)上至今未看到例子娩脾。

  • 需要自定義ActivitiUser類赵誓,實(shí)現(xiàn)org.activiti.engine.impl.persistence.entity.UserEntity接口
  • 需要自定義ActivitiRole類,實(shí)現(xiàn)org.activiti.engine.impl.persistence.entity.GroupEntity接口
  • 下面代碼中ActivitiUserGroupFeign是自有業(yè)務(wù)系統(tǒng)的用戶角色(組)服務(wù)
import org.activiti.engine.identity.Group;
import org.activiti.engine.identity.Picture;
import org.activiti.engine.identity.User;
import org.activiti.engine.identity.UserQuery;
import org.activiti.engine.impl.Page;
import org.activiti.engine.impl.UserQueryImpl;
import org.activiti.engine.impl.interceptor.Session;
import org.activiti.engine.impl.persistence.entity.UserEntity;
import org.activiti.engine.impl.persistence.entity.UserEntityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

/**
 * @Description: (some words)
 * @Author: Yong Li
 * @Date: 2018/8/8 16:52
 */
@Component
public class CustomUserEntityManager implements UserEntityManager, Session {

    @Autowired
    private ActivitiUserGroupFeign activitiUserGroupFeign;

    // session interface
    @Override
    public void flush() {
        // do nothing
    }

    // session interface
    @Override
    public void close() {
        // do nothing
    }

    @Override
    public User createNewUser(String userId) {
        return new ActivitiUser();
    }

    @Override
    public void updateUser(User updatedUser) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public List<User> findUserByQueryCriteria(UserQueryImpl query, Page page) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public long findUserCountByQueryCriteria(UserQueryImpl query) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public List<Group> findGroupsByUser(String userId) {
        List<ActivitiRole> roles = activitiUserGroupFeign.listRolesByUserMobile(userId).getDataNoPatience();
        List<Group> result = new ArrayList<>();
        if (null != roles) {
            for (ActivitiRole r : roles) {
                result.add(r);
            }
        }

        return result;
    }

    @Override
    public UserQuery createNewUserQuery() {
        return new UserQueryImpl();
    }

    @Override
    public Boolean checkPassword(String userId, String password) {
        ActivitiUser user = activitiUserGroupFeign.findUserByMobile(userId).getDataNoPatience();
        return null != user && user.getPassword().equals(password);
    }

    @Override
    public List<User> findUsersByNativeQuery(Map<String, Object> parameterMap, int firstResult, int maxResults) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public long findUserCountByNativeQuery(Map<String, Object> parameterMap) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public boolean isNewUser(User user) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public Picture getUserPicture(String userId) {
        ActivitiUser user = activitiUserGroupFeign.findUserByMobile(userId).getDataNoPatience();
        return user.getPicture();
    }

    @Override
    public void setUserPicture(String userId, Picture picture) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public void deletePicture(User user) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public UserEntity create() {
        return new ActivitiUser();
    }

    @Override
    public UserEntity findById(String entityId) {
        ActivitiUser user = activitiUserGroupFeign.findUserByMobile(entityId).getDataNoPatience();
        return user;
    }

    @Override
    public void insert(UserEntity entity) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public void insert(UserEntity entity, boolean fireCreateEvent) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public UserEntity update(UserEntity entity) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public UserEntity update(UserEntity entity, boolean fireUpdateEvent) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public void delete(String id) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public void delete(UserEntity entity) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public void delete(UserEntity entity, boolean fireDeleteEvent) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }
}
import org.activiti.engine.identity.*;
import org.activiti.engine.impl.GroupQueryImpl;
import org.activiti.engine.impl.Page;
import org.activiti.engine.impl.interceptor.Session;
import org.activiti.engine.impl.persistence.entity.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

/**
 * @Description: (some words)
 * @Author: Yong Li
 * @Date: 2018/8/8 17:53
 */
@Component
public class CustomGroupEntityManager implements GroupEntityManager, Session {

    @Autowired
    private ActivitiUserGroupFeign activitiUserGroupFeign;

    // session interface
    @Override
    public void flush() {
        // do nothing
    }

    // session interface
    @Override
    public void close() {
        // do nothing
    }

    @Override
    public Group createNewGroup(String groupId) {
        return new ActivitiRole();
    }

    @Override
    public GroupQuery createNewGroupQuery() {
        return new GroupQueryImpl();
    }

    @Override
    public List<Group> findGroupByQueryCriteria(GroupQueryImpl query, Page page) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public long findGroupCountByQueryCriteria(GroupQueryImpl query) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public List<Group> findGroupsByUser(String userId) {
        List<ActivitiRole> roles = activitiUserGroupFeign.listRolesByUserMobile(userId).getDataNoPatience();
        List<Group> result = new ArrayList<>();
        if (null != roles) {
            for (ActivitiRole r : roles) {
                result.add(r);
            }
        }

        return result;
    }

    @Override
    public List<Group> findGroupsByNativeQuery(Map<String, Object> parameterMap, int firstResult, int maxResults) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public long findGroupCountByNativeQuery(Map<String, Object> parameterMap) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public boolean isNewGroup(Group group) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public GroupEntity create() {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public GroupEntity findById(String entityId) {
        return activitiUserGroupFeign.findRoleByCode(entityId).getDataNoPatience();
    }

    @Override
    public void insert(GroupEntity entity) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public void insert(GroupEntity entity, boolean fireCreateEvent) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public GroupEntity update(GroupEntity entity) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public GroupEntity update(GroupEntity entity, boolean fireUpdateEvent) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public void delete(String id) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public void delete(GroupEntity entity) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }

    @Override
    public void delete(GroupEntity entity, boolean fireDeleteEvent) {
        throw BaseExceptionFactory.buildBaseException(201001);
    }
}

再分別實(shí)現(xiàn)CustomUserEntityManagerFactory, CustomGroupEntityManagerFactory

@Service
public class CustomUserEntityManagerFactory implements SessionFactory {

    // 使用自定義的User管理類
    @Autowired
    private CustomUserEntityManager customUserEntityManager;

    @Autowired
    public Class<?> getSessionType() {
        //注意此處也必須為Activiti原生類
        return UserEntityManager.class;
    }

    @Override
    public Session openSession(CommandContext commandContext) {
        return customUserEntityManager;
    }
}
@Service
public class CustomGroupEntityManagerFactory implements SessionFactory {

    @Autowired
    private CustomGroupEntityManager customGroupEntityManager;

    @Override
    public Class<?> getSessionType() {
        //注意此處必須為Activiti原生的類柿赊,否則自定義類不會(huì)生效
        return GroupEntityManager.class;
    }

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

最后把自定義的用戶-組管理注入到Activiti配置中:

    @Primary
    @Bean("insuranceActivitiConfig")
    public SpringProcessEngineConfiguration insuranceActivitiConfig(PlatformTransactionManager transactionManager,
            SpringAsyncExecutor springAsyncExecutor) throws IOException {
        SpringProcessEngineConfiguration springProcessEngineConfiguration = baseSpringProcessEngineConfiguration(
                activitiDataSource(),
                transactionManager,
                springAsyncExecutor);

        // 配置自定義的用戶和組管理
        springProcessEngineConfiguration.setUserEntityManager(customUserEntityManager);
        springProcessEngineConfiguration.setGroupEntityManager(customGroupEntityManager);

        List<SessionFactory> customSessionFactories = new ArrayList<>();
        customSessionFactories.add(customUserEntityManagerFactory);
        customSessionFactories.add(customGroupEntityManagerFactory);
        springProcessEngineConfiguration.setCustomSessionFactories(customSessionFactories);

        return springProcessEngineConfiguration;
    }

自定義用戶-角色(組)配置完成俩功。

流程測試用例編寫

流程設(shè)計(jì)完之后,需要對(duì)流程的過程進(jìn)行測試碰声,特別對(duì)于剛?cè)腴T的新人诡蜓,不熟悉BPMN每個(gè)元素的作用,需要通過實(shí)驗(yàn)或測試觀察結(jié)果是否與期望的結(jié)果一致胰挑。如果直接使用spring boot環(huán)境蔓罚,或spring cloud,在多個(gè)微服務(wù)集成的環(huán)境中進(jìn)行測試瞻颂,前期特別耗費(fèi)時(shí)間豺谈,所以建議可以用如下的測試方法: 數(shù)據(jù)源使用h2內(nèi)存數(shù)據(jù)源, 在test/resources下面準(zhǔn)備文件activiti.cfg.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">
        <property name="jdbcUrl" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
        <property name="jdbcDriver" value="org.h2.Driver" />
        <property name="jdbcUsername" value="sa" />
        <property name="jdbcPassword" value="" />

        <!-- Database configurations -->
        <property name="databaseSchemaUpdate" value="drop-create" />

        <!-- job executor configurations  如果有定時(shí)任務(wù)贡这,比如 Time Boundoury Activiti茬末, 需要開啟下面配置-->
        <property name="asyncExecutorActivate" value="true" />

        <!-- mail server configurations -->
        <property name="mailServerPort" value="5025" />
    </bean>
</beans>

測試用例例子

public class RequisitionFlowTest extends PluggableActivitiTestCase {

    protected static final long REQUISITION_CANCEL_TIMEOUT_SECONDES = 4;

    protected static final long REQUISITION_PAYMENT_TIMEOUT_SECONDES = 4;

    protected Map<String, Object> processVars;

    @Override
    protected void setUp() throws Exception {
        super.setUp();

        processVars = new HashMap<>();
        processVars.put("requisitionFlowService", new RequisitionFlowService());
        processVars.put("requisitionNumber", "test_requisition");

        String cancelTime = DateCommonUtils.formatDateISO8601(DateCommonUtils.getAfterSeconds(new Date(), (int)REQUISITION_CANCEL_TIMEOUT_SECONDES));
        System.out.println("=====cancel time expression:" + cancelTime);
        processVars.put("confirmTimeoutTime", cancelTime);
    }

    @Override
    public void tearDown() throws Exception {
        super.tearDown();
    }

    @Test
    @Deployment(resources = {"processes/Insurance_Requisition.bpmn20.xml"})
    public void testCustomerTimeoutForConfirm() throws InterruptedException {
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("requisitionV1", processVars);
        // 檢查流程是否未結(jié)束
        ProcessInstance qpi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstance.getId()).singleResult();
        Assert.assertNotNull(qpi);

        Task task1 = taskService.createTaskQuery().processDefinitionKey(processInstance.getProcessDefinitionKey()).taskDefinitionKey("customerConfirmTask").singleResult();
        Assert.assertNotNull(task1);

        // 等待用戶任務(wù)完成
        Thread.sleep((REQUISITION_CANCEL_TIMEOUT_SECONDES + 12) * 1000); //定時(shí)任務(wù)貌似每隔10秒重新掃一次
        // 檢查歷史流程實(shí)例是否已經(jīng)結(jié)束
        Assert.assertNull(runtimeService.createProcessInstanceQuery().processInstanceId(processInstance.getId()).singleResult());
    }

    protected String generatePayDueDateVariable() {
        return DateCommonUtils.formatDateISO8601(DateCommonUtils.getAfterSeconds(new Date(), (int)REQUISITION_PAYMENT_TIMEOUT_SECONDES));
    }
}
使用這種測試用例進(jìn)行測試時(shí),基本可以完成所有與用戶無關(guān)的活動(dòng)連接測試盖矫,比如從節(jié)點(diǎn)A到節(jié)點(diǎn)F的邏輯丽惭,對(duì)于初步的練習(xí)和測試來說是非常高效的。

整合Activiti-Explorer

Activiti 建模工具提供有兩種: 一種是eclipse plugins, 一種的自帶的web 版的辈双,可以從官方網(wǎng)站上下載责掏,在activiti-6.0.0 下面wars/activiti-app.war。我比較喜歡后者湃望,如果能在自己的業(yè)務(wù)系統(tǒng)中提供一個(gè)如下的工作流流程設(shè)計(jì)器給運(yùn)營或者產(chǎn)品經(jīng)理拷橘,是一個(gè)不錯(cuò)的選擇。
遺憾的是activiti 6.0.0并未提供流程設(shè)計(jì)器依賴的jar包(也許是我沒有找到)喜爷,所以我這還是Activiti engine 6.0.0 + Activiti Explorer 5.22.0冗疮。
我這里參考github 項(xiàng)目 , 只需要把pom.xml文件依賴文件換成最開始提到的pom就好了。
最終檩帐,就能在自己的項(xiàng)目里面嵌入了流程設(shè)計(jì)器术幔,并且能在上面編輯/創(chuàng)建流程, 并對(duì)流程進(jìn)行在線發(fā)布。

由于版本的不匹配湃密,是否會(huì)對(duì)流程設(shè)計(jì)器最終設(shè)計(jì)的圖在engine6.0上有什么樣的問題诅挑,目前還未知四敞。

最后,歡迎大家提出問題或有意思的優(yōu)化拔妥。聯(lián)系郵箱: 13902443572@163.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末忿危,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子没龙,更是在濱河造成了極大的恐慌铺厨,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件硬纤,死亡現(xiàn)場離奇詭異解滓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)筝家,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門洼裤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人溪王,你說我怎么就攤上這事腮鞍。” “怎么了莹菱?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵缕减,是天一觀的道長。 經(jīng)常有香客問我芒珠,道長,這世上最難降的妖魔是什么搅裙? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任皱卓,我火速辦了婚禮,結(jié)果婚禮上部逮,老公的妹妹穿的比我還像新娘娜汁。我一直安慰自己,他們只是感情好兄朋,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布掐禁。 她就那樣靜靜地躺著,像睡著了一般颅和。 火紅的嫁衣襯著肌膚如雪傅事。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天峡扩,我揣著相機(jī)與錄音蹭越,去河邊找鬼。 笑死教届,一個(gè)胖子當(dāng)著我的面吹牛响鹃,可吹牛的內(nèi)容都是我干的驾霜。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼买置,長吁一口氣:“原來是場噩夢啊……” “哼粪糙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起忿项,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤蓉冈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后倦卖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洒擦,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年怕膛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了熟嫩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡褐捻,死狀恐怖掸茅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柠逞,我是刑警寧澤昧狮,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站板壮,受9級(jí)特大地震影響逗鸣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绰精,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一撒璧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧笨使,春花似錦卿樱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至靶草,卻和暖如春蹄胰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奕翔。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工烤送, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人糠悯。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓帮坚,卻偏偏與公主長得像妻往,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子试和,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348