上一篇: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配置文件:
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
方法將某個候選組所有的成員加進去,如果項目需要設置候選組,請擴展這兩個類:CustomUserEntityManager
和CustomGroupEntityManager
,重寫其中的
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目錄下:
解壓出activiti-5.22.0.rar,看到如下目錄:
- 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:):
注意:
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)至工作流用戶任務表
docs:毫無疑問,api文檔。
libs:使用Activiti所需要的所有的jar包和源文件。
wars:官方給我們提供的示例Demo,通過使用Demo可以更加快速的了解Activiti。
找到wars目錄下的 activiti-explorer.war, 將其拷貝到Tomcat 的 webapps目錄下,然后運行tomcat: /bin/statup.bat(如果啟動時控制臺一閃而過,請看Tomcat7中雙擊bin文件的startup.bat一閃而過解決辦法),啟動tomcat 之后,會自動將activiti-explorer.war 解壓。
進入到下圖目錄中,將diagram-viewer,editor-app和modeler.html拷貝到自己工程的webapp目錄下。
將下圖路徑中的StencilsetRestResource.class。和下圖路徑中的ModelEditorJsonRestResource.class,ModelSaveRestResource.class。反編譯.
在自己的項目中新建對應的class,將反編譯內容復制進去,如圖:
其中,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. 關鍵對象
Deployment:流程部署對象,部署一個流程時創(chuàng)建。
ProcessDefinitions:流程定義,部署成功后自動創(chuàng)建。
ProcessInstances:流程實例,啟動流程時創(chuàng)建。
Task:任務,在Activiti中的Task僅指有角色參與的任務,即定義中的UserTask。
Execution:執(zhí)行計劃,流程實例和流程執(zhí)行中的所有節(jié)點都是Execution,如UserTask、ServiceTask等。
3.2.2. 服務接口
ProcessEngine:流程引擎的抽象,通過它我們可以獲得我們需要的一切服務。
RepositoryService:Activiti中每一個不同版本的業(yè)務流程的定義都需要使用一些定義文件,部署文件和支持數(shù)據(jù)(例如BPMN2.0 XML文件,表單定義文件,流程定義圖像文件等),這些文件都存儲在Activiti內建的Repository中。RepositoryService提供了對 repository的存取服務。
RuntimeService:在Activiti中,每當一個流程定義被啟動一次之后,都會生成一個相應的流程對象實例。RuntimeService提供了啟動流程、查詢流程實例、設置獲取流程實例變量等功能。此外它還提供了對流程部署,流程定義和流程實例的存取服務。
TaskService: 在Activiti中業(yè)務流程定義中的每一個執(zhí)行節(jié)點被稱為一個Task,對流程中的數(shù)據(jù)存取,狀態(tài)變更等操作均需要在Task中完成。TaskService提供了對用戶Task 和Form相關的操作。它提供了運行時任務查詢、領取、完成、刪除以及變量設置等功能。
IdentityService: Activiti中內置了用戶以及組管理的功能,必須使用這些用戶和組的信息才能獲取到相應的Task。IdentityService提供了對Activiti 系統(tǒng)中的用戶和組的管理功能。
ManagementService: ManagementService提供了對Activiti流程引擎的管理和維護功能,這些功能不在工作流驅動的應用程序中使用,主要用于Activiti系統(tǒng)的日常維護。
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ù)的東西顯示不出來。:
在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>
如圖所示:
3.4. 測試
在瀏覽器中輸入以下地址:
http://localhost:8080/ActivitiWorkFlowDemo(自己項目名)/model/create 請求創(chuàng)建模型接口
獲取到modelId后;
再輸入http://localhost:8080/ActivitiWorkFlowDemo/modeler.html?modelId=獲取到的modelId 即可進入modeler.html創(chuàng)建模型頁面。
結果如下:
如果報:
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ù)表的含義
資源庫流程規(guī)則表
1)act_re_deployment 部署信息表
2)act_re_model 流程設計模型部署表
3)act_re_procdef 流程定義數(shù)據(jù)表運行時數(shù)據(jù)庫表
1)act_ru_execution 運行時流程執(zhí)行實例表
2)act_ru_identitylink 運行時流程人員表,主要存儲任務節(jié)點與參與者的相關信息
3)act_ru_task 運行時任務節(jié)點表
4)act_ru_variable 運行時流程變量數(shù)據(jù)表歷史數(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 歷史變量表組織機構表
1)act_id_group 用戶組信息表
2)act_id_info 用戶擴展信息表
3)act_id_membership 用戶與用戶組對應信息表
4)act_id_user 用戶信息表
這四張表很常見,基本的組織機構管理,關于用戶認證方面建議還是自己開發(fā)一套,組件自帶的功能太簡單,使用中有很多需求難以滿足通用數(shù)據(jù)表
1)act_ge_bytearray 二進制數(shù)據(jù)表
2)act_ge_property 屬性數(shù)據(jù)表存儲整個流程引擎級別的數(shù)據(jù),初始化表結構時,會默認插入三條記錄自定義數(shù)據(jù)表
act_cus_user_task:流程各節(jié)點執(zhí)行人信息表,啟動流程之前初始化流程節(jié)點信息時插入節(jié)點key、節(jié)點名稱、業(yè)務key和已知的各節(jié)點執(zhí)行人信息。
注:代碼已上傳至GitHub:
https://github.com/ruoki/ActivitiWorkFlowDemo
歡迎star