扩展activiti 支持任意属性扩展

        本文主要讲解如何扩展activiti,使其支持任意属性配置。

        先来看下效果

这个流程的分支二这个人员设置,实际存放xml格式如下: 

<userTask id="Activity_03o1wx2" name="分支二" workflow:assignee="">
      <extensionElements>
        <workflow:rules>
          <workflow:field name="assignmentrule">
            <workflow:string>{"combo":{"type":"sdar"},"advanced":{"dataType":"user"},"handleType":"candidate","handler":"02020072317480678600000101001100","handlerName":"流程发起人","candidate":{"users":[{"value":"25711a8bbcc542f985877c2d09a5d744","name":"王五","lname":"wangwu"}]}}</workflow:string>
          </workflow:field>
          <workflow:field name="transferrule">
            <workflow:string>{"type":"manual","nodeSelectType":"system","autoCondition":[]}</workflow:string>
          </workflow:field>
          <workflow:field name="handlerule">
            <workflow:string>{"handleType":"grab","counterSign":{"handleScope":"user"}}</workflow:string>
          </workflow:field>
          <workflow:field name="formrule">
            <workflow:string>{}</workflow:string>
          </workflow:field>
        </workflow:rules>
      </extensionElements>
      <incoming>Flow_1wjzuu6</incoming>
      <outgoing>Flow_1dv7b8l</outgoing>
    </userTask>

assignmentrule就是我自己定义的办理人员规则,实际使用中我们可以根据我们的需求任务扩展属性,只要设计好我们属性对象的json格式就行,大概看一下我们定义的一些属性规则 

"stencil": {
	"id": "UserTask"
},
"properties": {
			"overrideid": "applyStep", //自定义ID
			"name": "申请登记",//节点名称
			"documentation": "这是申请登记",//节点描述
			"formrule" :{//表单设置
				"formType":"dynamic",//表单类型 dynamic:动态 url:在线
				"urlForm":"/apply/form",//formType为url 时有值
				"dynamicForm":{//formType为dynamic 时有值
					"formId":"applyForm",//表单ID
					"formName":"申请登记单",//表单名称
					"fields":[{//表单字段设置
						"fieldId":"applyId",//字段 ID
						"fieldName":"申请人",//字段 名称
						"readOnly":true,//是否只读
						"hidden":false//是否显示
					}]
				},
				"mobileForm":""//手机表单
			},
			"assignmentrule": { //分配设置
			    "handler":"zsld", //选择常用处理人是有值
				"candidate": {//人员选择
					"users": [{ //候选人
						"value": "11", //用户ID
						"name":"张三"//用户名称
					}, {
						"value": "22",
						"name":"李四"
					}],
					"depts": [{//候选组
						"value": "33",//机构ID
						"name":"武汉研发中心"//机构名称
					}],
					"roles": [{//修行角色
						"value": "44",//角色ID
						"name":"项目经理"//角色名称
					}]
				},
				"advanced":{//高级设置
					"type":"class", //class:条件类 sql:sql查询 var:变量 rest:restfull接口
					"classSetting":{//条件类设置
						"className":"com.cestc.test.Handler",//类
						"expression": "",//监听表达式
					    "delegateExpression": "",//监听代理表达式
						"inputParams":[{//输入参数
							"paramName":"age", //参数名称
							"paramType":"int", //参数类型
							"paramValue":"18" //参数值
						}],
						"outputResult":{//输出结果变量
							"value":"conditionResult", //变量值
							"name":"条件结果" //变量显示
						}
					},
					"restSetting":{
						"url":"http://test/getValue"
					},
					"sqlSetting":{//sql设置
						"dataSource":"",//数据源
						"sql":"select count(1) from user" //具体SQL
					},
					"varSetting":{//变量条件
						"varId":"11",//比较变量ID
						"varName":"年龄",//比较变量名称
					}
				}
			},
			"handlerule":{//办理设置
				"handleType":"grab", //grab:抢占 counterSign:会签
				"counterSign":{//会签设置
					"type": "Parallel", //Parallel:并行 Sequence:串行
					"checkAll":true, //是否参与人都要会签完成
					"passRate": 0.5, //同意比例
					"rejectBack":true, //结论不同意是否退回
					"backNode":{//结论不同意退回节点
						"nodeId":"applyId",//退回节点ID
						"nodeName":"申请节点"//退回节点名称
					},
					"resultVar":{//会签结论变量绑定
						"varId":"applyId",//绑定变量ID
						"varName":"申请人"//绑定变量名
					},
					"decisionMaker":{//决策人
						"passDM":[{//同意决策人
							"value":"11",//决策人ID
							"name":"张三"
						}],
						"rejectDM":[{//拒绝决策人
							"value":"2",
							"name":"李四"
						}]
					}
				}
				
			},
			"transferrule":{//流转设置
				"type":"manual",//manual:手动 auto:自动批转 conditionAuto:条件自动批转
				"autoCondition":"HANDLER_EQ_STARTER,HANDLER_APPEARED"//选择自动审批条件 HANDLER_EQ_STARTER:处理人是发起人 HANDLER_APPEARED:处理人已经出现过 HANDLER_EQ_PRE:处理人与上一步相同HANDLER_NONE:当前节点找不到处理人
			},
			"backrule":{ //驳回设置
				"nodes":[{ //可驳回节点
					"nodeId":"apply", //节点ID
					"nodeName":"申请登记"//节点名称
				}],
				"backWay":"order"//驳回返回方式 order :按流程图运行 direct:直流回该节点
			},
			"recallrule":{//撤回设置
				"recallType":"none", //none:不能撤回 all://所有节点可撤回  pre_node://前一节点可以撤回
				"backWay":"order"//撤回返回方式 order :按流程图运行 direct:直流回该节点
			},
			"timelimitrule":{//时效规则
				"timeType":"workday",//workday:工作日 calday:自然日
				"timeLimit":{ //审批时间限制
					"day":0, //天
					"hour":1,//小时
					"minute":0//分
				},
				"timeoutNotify":"imsg,wmsg",//imsg:内部消息 emsg:邮件消息 smsg:短信 wmsg:微信消息
				"urgeTime":{//催办提前时间
					"day":0, //天
					"hour":1,//小时
					"minute":0//分
				},
				"circularUrge":false,
				"circularUrgeTime":{
					"day":0, //天
					"hour":1,//小时
					"minute":0//分
				},
				"urgeNotify":"imsg"//imsg:内部消息 emsg:邮件消息 smsg:短信 wmsg:微信消息
			},
			"executionlisteners": {//执行监听
				"executionListeners": [{
					"event": "start",//监控事件
					"implementation": "com.test.userListener",//监听实现
					"api":"//data/api/getUsers",//网关API
					"className": "com.test.userListener",//监听实现类
					"expression": "",//监听表达式
					"delegateExpression": "",//监听代理表达式
					"fields": [{//输入参数
						"name": "fieldName2",
						"implementation": "21233",
						"stringValue": "21233",
						"expression": "",
						"string": ""
					}, {
						"name": "fieldName111",
						"implementation": "${dfsd}",
						"stringValue": "",
						"expression": "${dfsd}",
						"string": ""
					}]
				}, {
					"event": "end",
					"implementation": "${testMethod}",
					"className": "",
					"expression": "${testMethod}",
					"delegateExpression": ""
				}]
			},
			
			"tasklisteners": {//任务监听
				"taskListeners": [
				  {
					"event": "create",
					"implementation": "${test}",
					"className": "",
					"expression": "${test}",
					"delegateExpression": "",
					"fields": [
					  {
						"name": "age",
						"implementation": "18",
						"stringValue": "18",
						"expression": "",
						"string": ""
					  }
					]
				  }
				]
			  },

			"operaterule": {//按钮设置
				"opbtns": [{//按钮集合
					"value": "02020061213421486600000101001101", //按钮ID
					"btnName": "同意"//按钮名称
				}, {
					"value": "02020061213421486600000101001102",
					"btnName": "拒绝"
				}]
			}
		}

那是如何实现的呢,主要的原理就是扩展  extensionElements 元素,让各个属性规则以json的格式存放到下面。

参考以前的 

writeListeners处理新增 
writeRules

package com.cestc.workflow.core.definition.xmlmodelconverter.converter.export;

import com.cestc.workflow.utils.SpringUtil;
import com.cestc.workflow.core.definition.xmlmodelconverter.constants.BpmnXMLConstants;
import com.cestc.workflow.core.engine.util.BpmnXMLUtil;
import com.cestc.workflow.core.model.*;
import com.cestc.workflow.core.engine.rulemanager.converter.RuleConverter;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.xml.stream.XMLStreamWriter;
import java.util.Map;

/**
 * 规则解析,对应extensionElements下的workflow:rules标签
 */
public class WorkflowRuleExport implements BpmnXMLConstants {

    public static boolean writeRules(BaseElement element, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception {


        didWriteExtensionStartElement = writeRules(ELEMENT_RULES, element, didWriteExtensionStartElement, xtw);


        return didWriteExtensionStartElement;
    }

    private static boolean writeRules(String xmlElementName, BaseElement element, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception {

        if (!didWriteExtensionStartElement) {
            xtw.writeStartElement(ELEMENT_EXTENSIONS);
            didWriteExtensionStartElement = true;
        }
        xtw.writeStartElement(WF_EXTENSIONS_PREFIX, xmlElementName, WF_EXTENSIONS_NAMESPACE);

        for (Map.Entry<String, RuleConverter> entry : RuleConverter.ruleConverterMap.entrySet()) {

            if (entry.getValue().getRule(element) != null) {

                xtw.writeStartElement(WF_EXTENSIONS_PREFIX, ELEMENT_FIELD, WF_EXTENSIONS_NAMESPACE);
                BpmnXMLUtil.writeDefaultAttribute(ATTRIBUTE_FIELD_NAME, entry.getKey(), xtw);

                xtw.writeStartElement(WF_EXTENSIONS_PREFIX, ELEMENT_FIELD_STRING, WF_EXTENSIONS_NAMESPACE);
                xtw.writeCData(SpringUtil.getBean(ObjectMapper.class).writeValueAsString(entry.getValue().getRule(element)));

                xtw.writeEndElement();
                xtw.writeEndElement();
            }
        }

        xtw.writeEndElement();


        return didWriteExtensionStartElement;
    }

}

参考listenerParse 新增worflowRuleParase


package com.cestc.workflow.core.definition.xmlmodelconverter.converter.parser.child;

import com.cestc.workflow.core.model.*;
import com.cestc.workflow.core.model.Process;
import com.cestc.workflow.core.engine.rulemanager.converter.RuleConverter;
import org.springframework.util.CollectionUtils;

import javax.xml.stream.XMLStreamReader;

/**
 * 元素规则解析
 *
 * @author liubo
 */
public class WorkflowRuleParser extends BaseChildElementParser {

    @Override
    public String getElementName() {
        return ELEMENT_RULES;
    }

    @Override
    public void parseChildElement(XMLStreamReader xtr, BaseElement parentElement, BpmnModel model) throws Exception {
        boolean readyWithChildElements = false;
        BaseChildElementParser parser = new FieldExtensionParser();
        WorkflowRule workflowRule = new WorkflowRule();
        while (readyWithChildElements == false && xtr.hasNext()) {
            xtr.next();
            if (xtr.isStartElement()) {
                if (parser.getElementName().equals(xtr.getLocalName())) {
                    parser.parseChildElement(xtr, workflowRule, model);
                }

            } else if (xtr.isEndElement() && getElementName().equalsIgnoreCase(xtr.getLocalName())) {
                readyWithChildElements = true;
            }
        }

        if (parentElement instanceof UserTask || parentElement instanceof SequenceFlow || parentElement instanceof Process || parentElement instanceof TimerEventDefinition || parentElement instanceof ServiceTask) {
            if (!CollectionUtils.isEmpty(workflowRule.getFieldExtensions())) {
                workflowRule.getFieldExtensions().stream().forEach(fieldExtension -> {
                    RuleConverter converter = RuleConverter.ruleConverterMap.get(fieldExtension.getFieldName());
                    if (converter != null) {
                        converter.convertJsonToRule(parentElement, fieldExtension.getStringValue());
                    }
                });
            }
        }

    }

}

定义规则转换处理类负责各个属性规则的处理就行了

package com.cestc.workflow.core.engine.rulemanager.converter;

import com.cestc.workflow.core.model.BaseElement;
import com.cestc.workflow.core.model.UserTask;
import com.cestc.workflow.core.definition.TaskDefinition;
import com.fasterxml.jackson.databind.JsonNode;

import java.util.HashMap;
import java.util.Map;

/**
 * 规则转换
 * @author breeze
 * @since 2020/7/16 19:33
 */
public abstract class RuleConverter {
    public static Map<String, RuleConverter> ruleConverterMap = new HashMap<String, RuleConverter>();

    public static final String NODE_FORM_RULE = "formrule";
    public static final String NODE_ASSIGNMENT_RULE = "assignmentrule";
    public static final String NODE_HANDLE_RULE = "handlerule";
    public static final String NODE_BACK_RULE = "backrule";
    public static final String NODE_RECALL_RULE = "recallrule";
    public static final String NODE_TRANSFER_RULE = "transferrule";
    public static final String NODE_OPERATE_RULE = "operaterule";
    public static final String NODE_TIMELIMIT_RULE = "timelimitrule";
    public static final String NODE_VAR_RULE = "varrule";
    public static final String NODE_CONDITION_RULE = "conditionrule";
    public static final String NODE_APP_RULE="apprule";
    public static final String NODE_TIMER_RULE="timerrule";
    public static final String NODE_SAVE_RULE="saverule";
    public static final String NODE_APIPARAM_RULE="apiparamrule";
    public static final String NODE_CUSTOMBTN_RULE="custombtnrule";
    public static final String NODE_FLOWDURATION_RULE="flowdurationrule";
    public static final String NODE_APPROVE_RULE="approverule";
    public static final String NODE_SELECT_RULE="nodeselectrule";

    static{
        ruleConverterMap.put(NODE_FORM_RULE,new FormRuleConverter());//表单规则
        ruleConverterMap.put(NODE_ASSIGNMENT_RULE,new AssignmentRuleConverter());//分配规则
        ruleConverterMap.put(NODE_HANDLE_RULE,new HandleRuleConverter());//办理规则
        ruleConverterMap.put(NODE_BACK_RULE,new BackRuleConverter());//回退规则
        ruleConverterMap.put(NODE_RECALL_RULE,new RecallRuleConverter());//撤回规则
        ruleConverterMap.put(NODE_TRANSFER_RULE,new TransferRuleConverter());//流转规则
        ruleConverterMap.put(NODE_OPERATE_RULE,new OperateRuleConverter());//操作规则
        ruleConverterMap.put(NODE_TIMELIMIT_RULE,new TimeLimitRuleConverter());//时效规则
        ruleConverterMap.put(NODE_VAR_RULE,new VarRuleConverter());//流程变量规则
        ruleConverterMap.put(NODE_CONDITION_RULE,new ConditionRuleConverter());//连线条件规则
        ruleConverterMap.put(NODE_SAVE_RULE,new SaveRuleConverter());//保存分类
        ruleConverterMap.put(NODE_TIMER_RULE,new TimerRuleConverter());//定时设置分类
        ruleConverterMap.put(NODE_APP_RULE,new AppRuleConverter());//保存分类
        ruleConverterMap.put(NODE_APIPARAM_RULE,new ApiParamRuleConverter());//api参数规则
        ruleConverterMap.put(NODE_CUSTOMBTN_RULE,new CustomBtnRuleConverter());//自定义按钮参数规则
        ruleConverterMap.put(NODE_FLOWDURATION_RULE,new FlowDurationRuleConverter());//流程期限参数规则
        ruleConverterMap.put(NODE_APPROVE_RULE,new ApproveRuleConverter());//审批规则
        // 节点选择模式
        ruleConverterMap.put(NODE_SELECT_RULE, new NodeSelectRuleConverter());
    }
    public abstract void convertJsonToElement(JsonNode objectNode, BaseElement element);

    public abstract String getNodeFlag();
    public abstract Object getRule(BaseElement element);

    public abstract void convertJsonToRule(BaseElement element, String ruleJsonStr);

    public abstract void copyTaskToDefinition(TaskDefinition taskDefinition, UserTask userTask);
}

 

 

更多源码见 https://gitee.com/wtr/workflow

<think>我们正在使用Activiti6.0,并且希望为FormProperty添加自定义属性(如readable)来控制前端页面的显示行为。根据之前的讨论,我们可以通过以下步骤实现:1.**在BPMNXML中定义自定义属性**:在`<activiti:formProperty>`元素中直接添加自定义属性,例如`readable`。2.**扩展FormProperty处理逻辑**:通过自定义表单类型(FormType)来解析这些自定义属性。3.**前端渲染控制**:根据自定义属性的值来控制表单项的显示。由于Activiti6.0的FormProperty支持在XML中添加任意属性,我们可以利用这一特性。###步骤详解####1.在BPMNXML中定义自定义属性在流程定义的BPMN文件中,为表单属性添加`readable`属性,可以使用固定值或表达式。```xml<userTaskid="userTask"name="审批"><extensionElements><activiti:formPropertyid="name"name="姓名"type="string"readable="true"/><activiti:formPropertyid="salary"name="薪资"type="long"readable="${showSalary}"/></extensionElements></userTask>```这里:-`name`字段的`readable`属性设置为固定值`true`,表示始终可读。-`salary`字段的`readable`属性使用表达式`${showSalary}`,表示其可读性由流程变量`showSalary`控制。####2.扩展表单属性处理(后端Activiti6.0中,我们可以通过`FormService`获取表单数据,其中包含表单属性的定义。每个`FormProperty`对象都有一个`getAttributes()`方法,可以获取在XML中定义的所有自定义属性。我们不需要创建新的表单类型,因为Activiti已经将自定义属性存储在`FormProperty`的`attributes`中。但是,如果我们需要在表单类型中封装特定的行为,可以自定义表单类型。**自定义表单类型示例(可选)**:如果我们希望创建一个支持`readable`属性的自定义表单类型,可以继承`AbstractFormType`:```javapublicclassReadableStringFormTypeextendsStringFormType{publicstaticfinalStringTYPE_NAME="readableString";@OverridepublicStringgetName(){returnTYPE_NAME;}//从FormProperty中获取readable属性publicbooleanisReadable(FormPropertyformProperty){Stringreadable=formProperty.getAttributes().get("readable");//这里可以解析表达式,但通常在前端或提交时解析表达式更合适returnBoolean.parseBoolean(readable);}}```然后,我们需要在流程引擎配置中注册这个表单类型:```xml<beanid="processEngineConfiguration"class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"><!--...其他配置...--><propertyname="customFormTypes"><list><beanclass="com.example.ReadableStringFormType"/></list></property></bean>```但是,请注意,在BPMNXML中,我们需要使用`type="readableString"`来使用这个自定义类型。####3.前端渲染控制当我们通过`FormService`获取表单数据(`StartFormData`或`TaskFormData`)时,每个`FormProperty`对象都包含一个属性映射(`attributes`),我们可以从中获取`readable`属性。前端在渲染表单时,根据`readable`属性的值(注意,表达式需要解析)来决定是否显示该字段。**后端获取表单数据示例**:```java//获取任务表单数据TaskFormDatataskFormData=formService.getTaskFormData(taskId);for(FormPropertyformProperty:taskFormData.getFormProperties()){Map<String,String>attributes=formProperty.getAttributes();Stringreadable=attributes.get("readable");//如果readable是表达式,则需要解析if(readable!=null&&readable.startsWith("${")){//解析表达式,需要流程实例ID或执行IDStringexpression=readable.substring(2,readable.length()-1);BooleanisReadable=(Boolean)runtimeService.getVariable(executionId,expression);//将解析后的值放入属性中,以便前端使用attributes.put("readable",isReadable.toString());}//其他处理...}```**注意**:在实际应用中,我们可能不希望在后端修改原始的表单属性,而是将解析后的属性值传递给前端。或者,我们可以将表达式保留,由前端解析(但这样会暴露表达式逻辑,不推荐)。另一种做法是,前端在接收到表单数据后,对于每个字段的`readable`属性,如果是表达式,则再次向服务器请求解析(通过单独的API)。####4.安全考虑-**后端验证**:在提交表单时,即使前端隐藏了某些字段(`readable=false`),恶意用户也可能修改请求数据。因此,后端需要验证用户提交的数据是否符合`readable`属性的要求。-**表达式解析安全**:确保解析表达式时不会执行危险操作。###完整示例代码####后端Java代码(获取任务表单数据并解析表达式)```javapublicMap<String,Object>getTaskFormDataWithAttributes(StringtaskId){TaskFormDatataskFormData=formService.getTaskFormData(taskId);Map<String,Object>result=newHashMap<>();List<Map<String,Object>>properties=newArrayList<>();for(FormPropertyformProperty:taskFormData.getFormProperties()){Map<String,Object>propInfo=newHashMap<>();propInfo.put("id",formProperty.getId());propInfo.put("name",formProperty.getName());propInfo.put("type",formProperty.getType().getName());propInfo.put("value",formProperty.getValue());propInfo.put("attributes",formProperty.getAttributes());//处理readable属性(如果是表达式则解析)Stringreadable=formProperty.getAttributes().get("readable");if(readable!=null&&readable.startsWith("${")){Stringexpression=readable.substring(2,readable.length()-1);//获取任务对应的执行IDTasktask=taskService.createTaskQuery().taskId(taskId).singleResult();BooleanisReadable=(Boolean)runtimeService.getVariable(task.getExecutionId(),expression);propInfo.put("readable",isReadable);}elseif(readable!=null){propInfo.put("readable",Boolean.parseBoolean(readable));}else{propInfo.put("readable",true);//默认true}properties.add(propInfo);}result.put("properties",properties);returnresult;}```####前端渲染(示例JavaScript)```javascript//假设从API获取到表单数据fetch(`/api/task-form-data/${taskId}`).then(response=>response.json()).then(data=>{constform=document.getElementById('form');data.properties.forEach(prop=>{if(prop.readable){//创建表单元素并显示constinput=document.createElement('input');input.type='text';input.id=prop.id;input.name=prop.id;input.value=prop.value||'';form.appendChild(input);}//否则,不显示该字段(或者显示为只读、隐藏等)});});```###注意事项-**表达式解析的时机**:在任务表单中,表达式解析需要依赖流程变量。因此,在获取表单数据时,必须确保流程变量已经存在。-**性能**:如果表单属性很多,且每个都需要解析表达式,可能会影响性能。可以考虑批量获取变量或缓存。-**默认值**:如果没有指定`readable`属性,我们默认为`true`(即可读)。###相关问题1.如何为Activiti表单属性添加动态的`required`(必填)属性?2.如何在Activiti中实现表单字段之间的联动(例如:选择某个选项后显示/隐藏其他字段)?3.Activiti6.0中如何自定义表单渲染引擎?4.如何确保表单提交时自定义属性(如`readable`)的安全性?5.在Activiti中,如何将表单属性与流程变量进行绑定?>参考Activiti6.0官方文档:表单属性(FormProperty)可以包含任意自定义属性[^3],这些属性可以通过`FormProperty.getAttributes()`方法获取[^5]。同时,表单类型可以通过扩展`AbstractFormType`来实现自定义行为[^1]。</think>在 Activiti 6.0 中为 `FormProperty` 添加自定义属性(如 `readable`)来控制页面行为,可通过以下步骤实现: ### 1. **在 BPMN 文件中定义自定义属性** 在流程定义的 XML 文件中,直接在 `<activiti:formProperty>` 中添加自定义属性: ```xml <userTask id="approvalTask" name="审批"> <extensionElements> <!-- 添加 readable 自定义属性 --> <activiti:formProperty id="salary" name="薪资信息" type="long" readable="${hasSalaryViewPermission}"/> </extensionElements> </userTask> ``` 关键点: - `readable` 是自定义属性名 - 支持固定值 (`readable="false"`) 或表达式 (`readable="${expression}"`) - 表达式可引用流程变量(如 `${hasSalaryViewPermission}`) ### 2. **后端获取自定义属性** 通过 `FormService` 获取表单属性时,自定义属性会存储在 `attributes` 中: ```java TaskFormData formData = formService.getTaskFormData(taskId); for (FormProperty prop : formData.getFormProperties()) { // 获取自定义属性 String readable = prop.getAttributes().get("readable"); // 解析表达式(如需要) if (readable != null && readable.startsWith("${")) { String expr = readable.substring(2, readable.length()-1); Boolean isReadable = (Boolean) runtimeService.getVariable(executionId, expr); // 使用解析后的值... } } ``` ### 3. **前端渲染控制** 根据 `readable` 属性值控制页面元素: ```javascript // 获取表单数据 const formData = await fetch(`/form-api/task/${taskId}`); // 渲染字段 formData.properties.forEach(prop => { const isReadable = prop.attributes.readable === "true"; if (prop.id === "salary") { const field = document.createElement('div'); field.innerHTML = ` <label>${prop.name}</label> <input type="number" ${isReadable ? '' : 'readonly style="opacity:0.5"'} value="${prop.value}"> `; formContainer.appendChild(field); } }); ``` ### 4. **自定义表单类型(高级用法)** 如需更复杂的控制,可创建自定义表单类型: ```java public class ReadableFormType extends AbstractFormType { @Override public String getName() { return "readableField"; } @Override public Object convertFormValueToModelValue(String value) { return value; } @Override public String convertModelValueToFormValue(Object value) { return (String) value; } // 获取自定义属性 public boolean isReadable(FormProperty property) { return Boolean.parseBoolean( property.getAttributes().getOrDefault("readable", "true") ); } } ``` 在 `activiti.cfg.xml` 中注册: ```xml <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"> <property name="customFormTypes"> <list> <bean class="com.example.ReadableFormType"/> </list> </property> </bean> ``` ### 5. **安全验证(必做)** 在表单提交时进行后端验证: ```java formService.submitTaskFormData(taskId, formValues); // 自定义拦截器验证 public class ReadableFieldInterceptor implements CommandInterceptor { @Override public <T> T execute(CommandConfig config, Command<T> command) { if (command instanceof SubmitTaskFormCmd) { String taskId = ((SubmitTaskFormCmd) command).getTaskId(); Map<String, String> values = ((SubmitTaskFormCmd) command).getProperties(); TaskFormData formData = formService.getTaskFormData(taskId); formData.getFormProperties().forEach(prop -> { if ("false".equals(prop.getAttributes().get("readable"))) { if (values.containsKey(prop.getId())) { throw new ActivitiException("无权限修改只读字段: " + prop.getId()); } } }); } return next.execute(config, command); } } ``` ### 6. **动态控制属性值** 在流程中动态设置控制变量: ```java // 设置权限变量 runtimeService.setVariable(processInstanceId, "hasSalaryViewPermission", false); // 在服务任务中更新权限 <serviceTask id="updatePermission" activiti:class="com.example.UpdatePermissionDelegate"/> ``` ### 注意事项 1. **属性访问方式**: - 所有自定义属性都存储在 `FormProperty.getAttributes()` 返回的 Map 中 - 属性名大小写敏感(建议全小写) 2. **表达式最佳实践**: ```xml <!-- 使用安全控制表达式 --> <activiti:formProperty id="confidentialInfo" readable="${securityService.hasPermission('VIEW_CONFIDENTIAL')}"/> ``` 3. **与内置属性协作**: ```xml <activiti:formProperty id="report" name="报告" type="string" readable="${isAuditor}" writable="false" <!-- 双重保护 --> required="true"/> ``` 4. **前端安全**: ```javascript // 防止隐藏字段被篡改 document.querySelectorAll('input[type=hidden]').forEach(input => { input.setAttribute('data-original-value', input.value); input.addEventListener('change', e => { if (e.target.value !== e.target.dataset.originalValue) { alert("禁止修改只读字段"); e.target.value = e.target.dataset.originalValue; } }); }); ``` > **重要提示**:Activiti 6.0 中表单属性的自定义属性直接存储在 `FormProperty` 的 attributes Map 中[^2][^5],无需特殊配置即可通过 `getAttributes()` 方法访问[^3]。但需注意表达式解析需自行实现。 ### 相关问题 1. 如何在 Activiti 6.0 中实现表单字段的动态显示/隐藏逻辑? 2. 如何为自定义表单属性添加前端验证规则? 3. Activiti 6.0 表单属性与流程变量如何双向绑定? 4. 如何在不同任务节点间传递表单字段的显示状态? 5. 自定义表单属性Activiti 内置属性(如 `writable`)的优先级如何确定?
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

eclipsewangwfUN

棋盘内,车无轮马无缰,叫声将军

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值