Spring学习之整合Activiti(一)

上一篇:Spring学习之整合MyBatis
下一篇:Spring学习之整合Activiti(二)

1. 背景

Activiti是现在应用很广的一个流程框架,自己在学习过程中看到官网有Activiti Modeler可以使用页面管理Activiti流程,所以试着自己整合SpringMVC+Activiti Modeler。

1.1. 工作流与工作流引擎

工作流(workflow)就是工作流程的计算模型,即将工作流程中的工作如何前后组织在一起的逻辑和规则在计算机中以恰当的模型进行表示并对其实施计算。它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。(我的理解就是:将部分或者全部的工作流程、逻辑让计算机帮你来处理,实现自动化)

所谓工作流引擎是指workflow作为应用系统的一部分,并为之提供对各应用系统有决定作用的根据角色、分工和条件的不同决定信息传递路由、内容等级等核心解决方案。

1.2. BPMN2.0规范

BPMN(Business Process Model and Notation)--业务流程模型与符号。

BPMN是一套流程建模的标准,主要目标是被所有业务用户容易理解的符号,支持从创建流程轮廓的业务分析到这些流程的最终实现,知道最终用户的管理监控。

通俗一点其实就是一套规范,画流程模型的规范。流程模型包括:流程图、协作图、编排图、会话图。详细信息请google。

1.3. Activiti概述

1.3.1. Activiti由来

学习过Activiti的朋友都知道,Activiti的创始人也就是JBPM(也是一个优秀的BPM引擎)的创始人,从Jboss离职后开发了一个新的BPM引擎:Activiti。所以,Activiti有很多地方都有JBPM的影子。所以,据说学习过JBPM的朋友学起Activiti来非常顺手。

由于本人之前没有工作流及JBPM的相关基础,刚开始学习Activiti的时候可以说是无比痛苦的,根本不知道从何下手,这里也建议大家先进行工作流及BPMN2.0规范的学习,有了一定的基础后,再着手学习Activiti。

1.3.2. Activiti简介

Activiti是一个开源的工作流引擎,它实现了BPMN 2.0规范,可以发布设计好的流程定义,并通过api进行流程调度。

Activiti 作为一个遵从 Apache 许可的工作流和业务流程管理开源平台,其核心是基于 Java 的超快速、超稳定的 BPMN2.0 流程引擎,强调流程服务的可嵌入性和可扩展性,同时更加强调面向业务人员。

Activiti 流程引擎重点关注在系统开发的易用性和轻量性上。每一项 BPM 业务功能 Activiti 流程引擎都以服务的形式提供给开发人员。通过使用这些服务,开发人员能够构建出功能丰富、轻便且高效的 BPM 应用程序。

2. 前期准备

本文是在Spring学习之整合MyBatis的基础上完成的,所以不清楚的可以点击查看

2.1. Activiti所需环境

使用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" />
        <!-- 自动创建表 -->
        <property name="databaseSchemaUpdate" value="false" />
        <!-- 是否激活Activiti的任务调度 -->
        <property name="jobExecutorActivate" value="false" />
        <!-- 是否开启工作的数据日志 -->
        <!-- <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> -->
        <!--不创建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,项目启动是否自动创建数据表(activiti的23张表),这里我设置成false,因为我没有用activiti的用户管理表,而是采用自定义的用户管理表,所以在项目启动之前需要将所有需要的表创建完成。
2、dbIdentityUsed:是否创建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、注册自动义用户管理类。

由于我们这个项目中设置任务执行人时没有涉及到候选组,都是指定具体的执行人或获取某个角色组中所有的人员,然后调用setCandidateUsers方法将某个候选组所有的成员加进去,如果项目需要设置候选组,请扩展这两个类:CustomUserEntityManagerCustomGroupEntityManager,重写其中的
hasUser()(必须实现)、
getUserName()(可选)、
getUserPassword()(可选)、
getUserEmail()(可选)、
getRoleList()(必须实现)。
然后将实现类注册到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使用到的数据库信息的sql文件,它支持的数据库类型如下图,使用时只需执行你自己的数据库类型的文件即可。如:你的数据库是mysql,那么就执行activiti.mysql.create.*.sql即可。( 注意: 这里由于采用的是自定义用户管理,则去掉activiti其中创建用户相关表的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 '节点id',
  `TASK_NAME` varchar(255) DEFAULT NULL COMMENT '节点名',
  `ACTIVITY_TYPE` varchar(255) DEFAULT '' COMMENT '当前Activiti节点类型:N-普通用户任务;M-多实例节点',
  `TASK_TYPE` varchar(255) DEFAULT NULL COMMENT '节点的处理人员类型:assignee(人员)、candidateUser(候选人)、candidateGroup(候选组)',
  `CANDIDATE_NAME` varchar(255) DEFAULT NULL COMMENT '执行人名',
  `CANDIDATE_IDS` varchar(255) DEFAULT NULL COMMENT '执行人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:业务用户信息关联至工作流用户任务表

  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类是根据模型名称读取以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:流程部署对象,部署一个流程时创建。

  2. ProcessDefinitions:流程定义,部署成功后自动创建。

  3. ProcessInstances:流程实例,启动流程时创建。

  4. Task:任务,在Activiti中的Task仅指有角色参与的任务,即定义中的UserTask。

  5. Execution:执行计划,流程实例和流程执行中的所有节点都是Execution,如UserTask、ServiceTask等。

3.2.2. 服务接口
  1. ProcessEngine:流程引擎的抽象,通过它我们可以获得我们需要的一切服务。

  2. RepositoryService:Activiti中每一个不同版本的业务流程的定义都需要使用一些定义文件,部署文件和支持数据(例如BPMN2.0 XML文件,表单定义文件,流程定义图像文件等),这些文件都存储在Activiti内建的Repository中。RepositoryService提供了对 repository的存取服务。

  3. RuntimeService:在Activiti中,每当一个流程定义被启动一次之后,都会生成一个相应的流程对象实例。RuntimeService提供了启动流程、查询流程实例、设置获取流程实例变量等功能。此外它还提供了对流程部署,流程定义和流程实例的存取服务。

  4. TaskService: 在Activiti中业务流程定义中的每一个执行节点被称为一个Task,对流程中的数据存取,状态变更等操作均需要在Task中完成。TaskService提供了对用户Task 和Form相关的操作。它提供了运行时任务查询、领取、完成、删除以及变量设置等功能。

  5. IdentityService: Activiti中内置了用户以及组管理的功能,必须使用这些用户和组的信息才能获取到相应的Task。IdentityService提供了对Activiti 系统中的用户和组的管理功能。

  6. ManagementService: ManagementService提供了对Activiti流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于Activiti系统的日常维护。

  7. HistoryService: HistoryService用于获取正在运行或已经完成的流程实例的信息,与RuntimeService中获取的流程信息不同,历史信息包含已经持久化存储的永久信息,并已经被针对查询优化。

现在至少要知道有这些对象和接口。并结合Activiti Api这一章节来看,你就会对部署流程、启动流程、执行任务等操作有一个基本的概念。

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个像数的东西显示不出来。:

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 请求创建模型接口
获取到modelId后;
再输入http://localhost:8080/ActivitiWorkFlowDemo/modeler.html?modelId=获取到的modelId 即可进入modeler.html创建模型页面。
结果如下:

image.png

如果报:
Failed to load resource: the server responded with a status of 404 (Not Found)
首先确认路径没有错。

在请求分发时,没有找到"/ActivitiWorkFlowDemo/modeler.html"的映射(No mapping found for ...)。原来是spring mvc拦截了页面对静态资源的请求,但你的controller中又没有这个路径的映射,所以页面对静态资源文件的请求并没有正确下发,那么该怎么解决这个问题呢?下面我给出参考的方法:

解决方案:

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


    <!-- 静态资源文件,不会被Spring MVC拦截 -->
    <mvc:default-servlet-handler />
    <mvc:resources location="/resources/" mapping="/resources/**" />

加入之后,spring mvc就会对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。这个方法是最快捷的。

2.采用<mvc:resources />。可以使用<mvc:resources />,并将静态资源放在WEB-INF目录下(或者其他你喜欢的地方),然后在springMVC-servlet中添加如下配置:

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

根据实际情况填写路径。

3.在web.xml文件中将spring mvc的拦截路径改为/springmvc/*("springmvc"可以替换成你喜欢的路径)

附加知识点:Activiti工作流的自带数据表的含义

  1. 资源库流程规则表
    1)act_re_deployment 部署信息表
    2)act_re_model 流程设计模型部署表
    3)act_re_procdef 流程定义数据表

  2. 运行时数据库表
    1)act_ru_execution 运行时流程执行实例表
    2)act_ru_identitylink 运行时流程人员表,主要存储任务节点与参与者的相关信息
    3)act_ru_task 运行时任务节点表
    4)act_ru_variable 运行时流程变量数据表

  3. 历史数据库表
    1)act_hi_actinst 历史节点表
    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 用户信息表
    这四张表很常见,基本的组织机构管理,关于用户认证方面建议还是自己开发一套,组件自带的功能太简单,使用中有很多需求难以满足

  5. 通用数据表
    1)act_ge_bytearray 二进制数据表
    2)act_ge_property 属性数据表存储整个流程引擎级别的数据,初始化表结构时,会默认插入三条记录

  6. 自定义数据表
    act_cus_user_task:流程各节点执行人信息表,启动流程之前初始化流程节点信息时插入节点key、节点名称、业务key和已知的各节点执行人信息。

:代码已上传至GitHub:
https://github.com/ruoki/ActivitiWorkFlowDemo
欢迎star

上一篇:Spring学习之整合MyBatis
下一篇:Spring学习之整合Activiti(二)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269