zoukankan      html  css  js  c++  java
  • Liferay7 BPM门户开发之9: 流程表单数据动态映射体系

    设计目的:

    每个流程表单涉及不同的表单变量。比如请假流程有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(20);
    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解析器,用于属性注入。

    实现代码略。 

  • 相关阅读:
    SSH 远程执行任务
    C# 创建压缩文件
    迁移 SQL Server 到 Azure SQL 实战
    在 Azure 上部署 Asp.NET Core Web App
    linux kill 命令
    VS 远程调试 Azure Web App
    Azure 基础:自定义 Table storage 查询条件
    NSOperation的使用细节 [2]
    NSOperation的使用细节 [1]
    [翻译] SSKeychain
  • 原文地址:https://www.cnblogs.com/starcrm/p/5970431.html
Copyright © 2011-2022 走看看