设计目的:

每个流程表单涉及不同的表单变量。比如请假流程有3个任务节点,分别是

  • Task1:开始流程,填写请假人、请假原因、请假开始时间、请假结束时间;
  • Task2:上级审批,填写是否同意,审批意见;
  • Task3:HR审批,填写是否同意,审批意见;

这里不处理消假;

那么各任务周期的变量分别是:

  • Task1:initiator、reason、startDatetime、endDatetime;
  • Task2:  isApprove、remark;
  • Task3:isApprove、remark;

在开发中,针对请假流程表单对应的流程变量数据,我们可以定一个独立的VO实体类、独立的DAO数据操作、定义独立的表,比如叫Leave Table,然后JPA持久化流程表单实例数据,通过这样的方式定义一个portlet,实现Liferay 和Activiti的业务集成。

持久化的过程类似这样:

TaskService taskService = ……
Leave leave = new Leave ();
leave.setId();
leave.setInitiator ("张三");
leave.setReason ("想请假休息2天");
……
taskService.setVariable(taskId, "Leave1.1", leave);

那么又有一个新流程,叫报销流程,有三个任务:

  • Task1:开始流程,填写请款人、请款原因、请款金额;
  • Task2:上级审批,填写是否同意,审批意见;
  • Task3:HR审批,填写是否同意,审批意见;

那么各任务周期的变量分别是:

  • Task1:initiator、reason、amount;
  • Task2:  isApprove、remark;
  • Task3:isApprove、remark;

  在传统开发中,又要针对请款流程表单对应的变量数据,又要定一个独立的VO实体类、独立的DAO数据操作、定义独立的表,比如叫BorrowMoneyTable,然后JPA持久化流程表单实例数据。通过这样的方式定义又一个portlet,实现Liferay 和Activiti的业务集成。

…… 循环往复,生生不息。

这种做法比较传统,也没有风险,但存在效率问题,需要不断地新建portlet,以及VO、DAO…;

实际上,Activiti有完整的变量记录,提供了4种历史级别:

  • none: 不保存任何历史记录,可以提高系统性能;
  • activity:保存所有的流程实例、任务、活动信息;
  • audit:也是Activiti的默认级别,保存所有的流程实例、任务、活动、表单属性;
  • full: 最完整的历史记录,除了包含audit级别的信息之外还能保存详细,例如:流程变量。

如果要得到完整历史记录,只需要修改配置:

<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">

    <property name="history" value="full">

</property></bean>

数据存在两个表中:

  • act_ru_variable (运行时暂存)
  • act_hi_varinst (历史数据)

查询:

List<historicvariableinstance> list = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).list();

for (HistoricVariableInstance variableInstance : list) {
System.out.println("variable: " + variable.getVariableName() + " = " + variable.getValue());
}

表单字段存在另一个表:ACT_HI_DETAIL

读取表单字段

List<historicdetail> formProperties = historyService.createHistoricDetailQuery().processInstanceId(processInstance.getId()).formProperties().list();

for (HistoricDetail historicDetail : formProperties) {
HistoricFormProperty field = (HistoricFormProperty) historicDetail;
System.out.println("field id: " + field.getPropertyId() + ", value: " + field.getPropertyValue());
}

一个改良的设计方法(表单数据映射体系):

需要最简便的使用activiti:formProperty属性定义(即内置动态表单),这样可以在开始事件(Start Event)和Task上设置变量,而且支持变量自动替换,即利用表达式,语法就是UEL。

简化和强大完美的结合!

第一步:先建立一个中间表ACT_LIFE_ BRIDGE,只有3个字段:

  • ProcessInstID
  • ExecutionID
  • TaskID

通过这三个字段可以获取到BPM的变量数据映射。

第二步:依然要新建portlet,但只需要新建jsp,不再需要vo、dao…

表单类似:

...

<form:form action="<portlet:actionURL/>" modelAttribute="DynamicBean" method="post">
<div id = "task1">
<form:input path="initiator"/>
<form:input path="reason"/>
<form:input path="startDatetime"/>
<form:input path="endDatetime"/>
</div>
<div id = "task2">
<form:select path="task2_isApprove">
<option value="1">同意</option>
<option value="0">不同意</option>
</form:select>
<form:input path="task2_remark"/>
</div>
<div id = "task3">
<form:select path="task3_isApprove">
<option value="1">同意</option>
<option value="0">不同意</option>
</form:select>
<form:input path="task3_remark"/>
</div>
<div id = "submit">
<input type="submit"/>
</div>
</form>
...

第三步,开发动态模型类

其中ProcessInstID在流程启动的时候建立,TaskID和ExecutionID在流程运行时建立,流程表单对应的变量在提交时注入。

最重点的实现是通过运行时创建动态实体类,模型是DynamicBean,实现的思路是:

import java.util.Iterator;
import java.util.Map;
import java.util.Set; import net.sf.cglib.beans.BeanGenerator;
import net.sf.cglib.beans.BeanMap; public class DynamicBean { private Object object = null;//动态生成的类
private BeanMap beanMap = null;//存放属性名称以及属性的类型 public DynamicBean() {
super();
} @SuppressWarnings("rawtypes")
public DynamicBean(Map propertyMap) {
this.object = generateBean(propertyMap);
this.beanMap = BeanMap.create(this.object);
} /**
* 给bean属性赋值
* @param property 属性名
* @param value 值
*/
public void setValue(Object property, Object value) {
beanMap.put(property, value);
} /**
* 通过属性名得到属性值
* @param property 属性名
* @return 值
*/
public Object getValue(String property) {
return beanMap.get(property);
} /**
* 得到该实体bean对象
* @return
*/
public Object getObject() {
return this.object;
} /**
* @param propertyMap
* @return
*/
@SuppressWarnings("rawtypes")
private Object generateBean(Map propertyMap) {
BeanGenerator generator = new BeanGenerator();
Set keySet = propertyMap.keySet();
for (Iterator i = keySet.iterator(); i.hasNext();) {
String key = (String) i.next();
generator.addProperty(key, (Class) propertyMap.get(key));
}
return generator.create();
} }

测试类

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map; public class DyBean { public static void main(String[] args) throws ClassNotFoundException { System.out.println("Generate JavaBean ...");
// 设置类成员属性
Map properties = new HashMap();
properties.put("id", Class.forName("java.lang.Integer"));
properties.put("name", Class.forName("java.lang.String"));
properties.put("address", Class.forName("java.lang.String"));
// 生成动态 Bean
DynamicBean bean = new DynamicBean(properties);
System.out.println(" OK!"); System.out.println("Set values ...");
// 给 Bean 设置值
bean.setValue("id", new Integer(123));
bean.setValue("name", "454");
bean.setValue("address", "789");
System.out.println(" OK!"); System.out.println("Get values");
// 从 Bean 中获取值,当然了获得值的类型是 Object
System.out.println(" >> id = " + bean.getValue("id"));
System.out.println(" >> name = " + bean.getValue("name"));
System.out.println(" >> address = " + bean.getValue("address")); System.out.println("Class name");
// 查看动态 Bean 的类名
Class clazz = bean.getObject().getClass();
System.out.println(" >> " + clazz.getName()); System.out.println("Show all methods");
// 查看动态 Bean 中声明的方法
Method[] methods = clazz.getDeclaredMethods();
for(int i = 0; i < methods.length; i++) {
System.out.println(" >> " + methods[i].getName());
} System.out.println("Show all properties");
// 查看动态 Bean 中声明的字段
Field[] fields = clazz.getDeclaredFields();
for(int i = 0; i < fields.length; i++) {
System.out.println(" >> " + fields[i].getName());
}
}
}

或者

import java.util.HashMap;
import java.util.Map; import org.apache.commons.beanutils.BasicDynaClass;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.beanutils.PropertyUtils; public class ddBean {
public static void main(String[] args) throws Exception {
//定义动态属性
DynaProperty[] props = new DynaProperty[]{
new DynaProperty("username", String.class),
new DynaProperty("address", java.util.Map.class)
};
//动态类
BasicDynaClass dynaClass = new BasicDynaClass("person", null, props);
//动态bean
DynaBean person = dynaClass.newInstance();
person.set("username", "jhlishero");//设置值
Map<String, String> maps = new HashMap<String, String>();
maps.put("key1", "value1");
maps.put("key2", "value2");
person.set("address",maps);//设置值
person.set("address", "key3", "value3");//第二种方法设置map中的值 System.err.println(person.get("username"));//获取字符串值
System.err.println(person.get("address", "key1"));//获取map中值
System.err.println(person.get("address", "key2"));
System.err.println(person.get("address", "key3"));
//使用PropertyUtils工具获取属性值
System.out.println(PropertyUtils.getSimpleProperty(person, "username"));
}
}

第四步,开发属性关联 (通过XML解析器)

属性表不需要再新建XML,直接就用Activiti流程定义文件XML来获取变量名称,

类似于:

<startEvent id="request" activiti:initiator="initiator">
<extensionElements>
<activiti:formProperty id="startDatetime" name="请假开始时间" datePattern="yyyy-MM-dd hh:mm" type="date" required="true" />
<activiti:formProperty id="endDatetime" name="请假结束时间" datePattern="yyyy-MM-dd hh:mm" type="date" required="true" />
<activiti:formProperty id="reason" name="请假事由" type="string" />
</extensionElements>
</startEvent>
<userTask id="task2" name="上级审批" >
<documentation>
${initiator} 申请休假
</documentation>
<extensionElements>
<activiti:formProperty id="task2_isApprove" name="是否同意" type="enum" required="true">
<activiti:value id="true" name="Approve" />
<activiti:value id="false" name="Reject" />
</activiti:formProperty>
<activiti:formProperty id="task2_remark" name="审批意见" type="string" />
</extensionElements>
</userTask>
<sequenceFlow id="flow1" sourceRef="request" targetRef="task2" />
<sequenceFlow ...

还需要再开发一个XML解析器,用于属性注入。

实现代码略。

Liferay7 BPM门户开发之9: 流程表单数据动态映射体系的更多相关文章

  1. Liferay7 BPM门户开发之40: Form表单的Action到Render的数据传递

    在Form提交后的变量,很多情况是要展现在jsp页面中,这时Action到Render的变量传递就非常有用. 例如,您在数据库中添加了学生的详细信息. 为了实现这一需求,先创建Form表单(学生的细节 ...

  2. Liferay7 BPM门户开发之39: Form表单提交的ProcessAction处理

    在v6.2开始后,需要设置<requires-namespaced-parameters>false</requires-namespaced-parameters>  来避免 ...

  3. Liferay7 BPM门户开发之10: 通用流程实现从Servlet到Portlet(Part1)

    开发目的: 实现通用流程自动化处理(即实现不需要hardcode代码的bpm统一处理后台,仅需要写少量前端html form代码和拖拽设计BPM定义) 既可独立运行或可依托于Liferay或依托其它门 ...

  4. Liferay7 BPM门户开发之37: Liferay7下的OSGi Hook集成开发

    hook开发是Liferay客制扩展的一种方式,比插件灵活,即可以扩展liferay门户,也能对原有特性进行更改,Liferay有许多内置的服务,比如用hook甚至可以覆盖Liferay服务. 可作为 ...

  5. Liferay7 BPM门户开发之17: Portlet 生命周期

    Portlet 生命周期 init() =〉 render() =〉 processAction() =〉 processEvent() =〉 serveResource() =〉destroy() ...

  6. Liferay7 BPM门户开发之8: Activiti实用问题集合

    1.如何实现审核的上级获取(任务逐级审批) 这个是必备功能,通过Spring的注入+Activiti表达式可以很容易解决. 可参考: http://blog.csdn.net/sunxing007/a ...

  7. Liferay7 BPM门户开发之12:acitiviti和liferay用户权限体系集成

    写到第12章才出现Liferay的内容,希望可以厚积薄发. 我们的目标是不使用不维护Activiti的用户组织架构,只维护Liferay的体系,这样的好处是非常明显的,即不用做组织架构的同步工作. 原 ...

  8. Liferay7 BPM门户开发之44: 集成Activiti展示流程列表

    处理依赖关系 集成Activiti之前,必须搞清楚其中的依赖关系,才能在Gradle里进行配置. 依赖关系: 例如,其中activiti-engine依赖于activiti-bpmn-converte ...

  9. Liferay7 BPM门户开发之11: Activiti工作流程开发的一些统一规则和实现原理(完整版)

    注意:以下规则是我为了规范流程的处理过程,不是Activiti公司的官方规定. 1.流程启动需要设置启动者,在Demo程序中,“启动者变量”名统一设置为initUserId 启动时要做的: ident ...

随机推荐

  1. python入门(二):isinstance、内置函数、常用运算等

    1.    isinstance(变量名,类型)                           #判断什么类型 ps: 只支持输入两个参数,输入3个参数会报错 >>> isin ...

  2. 大数据spark学习第一周Scala语言基础

    Scala简单介绍 Scala(Scala Language的简称)语言是一种能够执行于JVM和.Net平台之上的通用编程语言.既可用于大规模应用程序开发,也可用于脚本编程,它由由Martin Ode ...

  3. Windows驱动开发调试工具

    [开发工具] VS2012 [调试工具] Windbg:和VM配合实现双机联合调试,完成双机调试功能,可以结合<软件调试>这本书对Windbg有较为深入的认识. DebugView: 可以 ...

  4. 深入JVM之类的加载器

    类加载器有两种: —java虚拟机的自带加载器 根类加载器(Bootstrap) 扩展类加载器(Extension) 系统类加载器(AppClassLoder) —自定义的类加载器 java.lang ...

  5. 随笔 | 分布式版本控制系统Git的安装与使用

    作业要求来自https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/2097 GitHub远程仓库的地址https://github.com/W ...

  6. 每月最后一周的周六晚上21:00执行任务-crontab

    0 21 * * 6 /bin/sh /root/time.sh #“6”代表周六 时间判断脚本如下: #!/bin/bash if [ "$(date -d "+7 days&q ...

  7. goaccess

    找了各种工具,最终还是觉得goaccess不仅图文并茂,而且速度快,每秒8W 的日志记录解析速度,websocket10秒刷新统计数据,站在巨人肩膀上你也会看得更远…先上图:  具体方案如下步骤: 一 ...

  8. Python Day 5

    阅读目录: 数字类型: 字符串类型: 列表类型: 可变与不可变类型: ##数字类型: # 了了解:py2中小整数用int存放,大整数用long # 1.整型 num = -10000000000000 ...

  9. shell脚本学习-执行

    跟着RUNOOB网站的教程学习的笔记 Shell与Shell脚本 Shell是用户与Linux系统的桥梁.它既是一种命令语言,也是一种程序设计语言. Shell脚本是一种Shell编写的脚本程序,其实 ...

  10. 51nod1683

    代码参考:http://blog.csdn.net/sdfzyhx/article/details/74359927 //dp[i][j][k]表示到了第i列,最下边的点最短路为j,剩下的点之间的关系 ...