Activiti——表单

Activiti的表单

1、概述

对于一些较为稳定的业务流程,全部的功能可以直接由程序员实现,这些功能包括流程的制定、具体领域业务代码的实现和表现层交互实现等,但是在实际应用中,不变的业务并不存在。为了让程序能更好地适应业务流程的变化,在工作流领域出现了动态流程和动态表单等概念。

Activiti提供了两种设置表单的方式,流程引擎的开发者可以根据不同的情况来选择合适的方式。

2、表单属性

可以在流程的开始事件或者任务中使用activiti:formProperty元素定义一个表单属性,使用
FormService的方法可以查询及设置这些属性。在流程文件中定义的这些表单属性,与具体的表现层技术无关,界面层如何将参数传递给流程引擎,由具体的表现层技术决定,Activiti的流程配置文件及FormService,只提供一个桥梁。

<process id="process1" name="process1">
	<startEvent id="startevent1" name="Start">
		<extensionElements>
			<activiti:formProperty id="userName" name="userName"
				variable="userName" type="string" />
		</extensionElements>
	</startEvent>
	<userTask id="usertask1" name="User Task"></userTask>
	<endEvent id="endevent1" name="End"></endEvent>
	<sequenceFlow id="flow1" name="" sourceRef="startevent1"
		targetRef="usertask1"></sequenceFlow>
	<sequenceFlow id="flow2" name="" sourceRef="usertask1"
		targetRef="endevent1"></sequenceFlow>
</process>

在代码中,为开始事件定义了一个“userName”表单属性,表示在该流程的开始表单中,需要用户填写“userName”字段,类型为字符串。定义了该表单属性,就可以使用FormService的submitStartFormData方法启动流程,“userName”会被设置到流程参数中,activiti:formProperty的variable属性是流程参数名称。

ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    RepositoryService repositoryService = engine.getRepositoryService();
    FormService formService = engine.getFormService();
    RuntimeService runtimeService = engine.getRuntimeService();
    //部署
    Deployment deploy = repositoryService.createDeployment().addClasspathResource("demo18/FormProperty.bpmn").deploy();
    //查找流程定义
    ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult();
    //使用表单参数启动流程
    Map<String, String> vars = new HashMap<>();
    vars.put("userName", "tom");
    ProcessInstance pi = formService.submitStartFormData(pd.getId(), vars);
    //查询参数
    System.out.println(runtimeService.getVariable(pi.getId(), "userName"));

在本例中,“userName”的值被写死为“tom’”,而在实际情况中,这些表单属性值一般来源于用户填写的表单,使用表单属性这种方式来定义流程的表单,使得流程属性(参数)与具体的表现层技术无关。用户填写的表单和流程引擎之间,通过submitStartFormData方法产生关联。

在这里插入图片描述

3、外部表单

相对于定义表单属性的方式,使用外部表单的方式使流程和表单之间的关系更加松散,只需要在开始事件或者任务中使用activiti:formKey来配置外部表单的链接,这个链接可以是个普通的HTML页面、一个XML文件或者一个URL,表单的内容和样式完全由外部决定。在部署时,需要将这个外部表单添加到部署中(DeploymentBuilder的部署方法),Activiti的部署API,会将其内容存放到资源表中,可以使用FormService提供的方法来读取这些“外部’表单的内容。采用这种方式定义表单,表面上流程与表单的具体参数解耦(流程只需要知道表单的链接),但在流程中获取参数时,流程必须很清楚外部表单的内容,因为流程中所使用的参数,在业务层面就决定了流程和表单之间不可分割的关系。

<process id="ExternalForm" name="ExternalForm" isExecutable="true">
	<startEvent id="startevent1" name="Start" activiti:formKey="form/start.jsp"></startEvent>
	<userTask id="usertask1" name="User Task" activiti:formKey="form/task.form"></userTask>
	<endEvent id="endevent1" name="End"></endEvent>
	<sequenceFlow id="flow1" sourceRef="startevent1"
		targetRef="usertask1"></sequenceFlow>
	<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
</process>

在代码中的开始事件中,添加了activiti:formKey属性,声明该流程的开始表单使用的是start.jsp。本例中的start.jsp是一个普通的JSP文件,task.form的内容与start.jsp类似,
在task.form中会将流程参数输出。

start.jsp:

<div>
	<span style="color: green;">开始表单: <input type="text" name="days" /></span>
</div>

task.form

<div>
	<span style="color: red;">任务表单: ${days}</span>
</div>
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
FormService formService = engine.getFormService();
TaskService taskService = engine.getTaskService();
// 部署全部文件
Deployment dep = repositoryService.createDeployment()
        .addClasspathResource("demo18/ExternalForm.bpmn")
        .addClasspathResource("form/start.jsp")
        .addClasspathResource("form/task.form").deploy();
// 流程定义
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();
// 启动流程并设置days参数
Map<String, String> vars = new HashMap<String, String>();
vars.put("days", "4");
ProcessInstance pi = formService.submitStartFormData(pd.getId(), vars);
// 输出开始表单内容
Object obj = formService.getRenderedStartForm(pd.getId());
System.out.println(obj);
// 输出被渲染后的任务表单内容
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
Object form = formService.getRenderedTaskForm(task.getId());
System.out.println(form);
<div>
	<span style="color: green;">开始表单: <input type="text" name="days" /></span>
</div>
<div>
	<span style="color: red;">任务表单: 4</span>
</div>

使用外部表单的方式来定制表单,完全将表单的内容交由外部决定,表单与流程之间仅仅通过formKey耦合。如果用户的表现层使用的是HTML或者JSP,那么就可以通过多种途径来获取开始表单的内容,例如调用FormService来读取,使用include等方式。用户填写的表单被提交到服务器后,就调用submitStartFormData方法来启动流程。

4、动态表单

在这里插入图片描述

<process id="leave-formkey" name="请假流程-外置表单" isExecutable="true">
  <!--
    开始节点:activiti:initiator属性的作用:可以把启动流程实例的操作人以变量名称“applyUserId”
    保存到数据库中,需要配合identityService.setAuthenticatedUserId(String userId)方法使用,
    其中userId即当前操作人,在实际的应用中应该是当前的用户ID,引擎会把setAuthenticatedUserId()
    方法的参数作为流程启动人,通过调用HistoricProcessInstance实例的getStartUserId()可以获取一
    个历史(也可能正在运行)流程实例由哪个用户启动。要获取设置的用户ID,可以通过调用Authentication.
    getAuthenticatedUserId()方法来实现。
  -->
  <startEvent id="startevent1" name="Start"
              activiti:initiator="applyUserId"
              activiti:formKey="bk/leave-start.form"></startEvent>
  <userTask id="deptLeaderVerify" name="部门经理审批"
            activiti:candidateGroups="deptLeader"
            activiti:formKey="bk/approve-deptLeader.form"></userTask>
  <exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
  <userTask id="hrVerify" name="人事经理审批" 
            activiti:candidateGroups="hr" 
            activiti:formKey="bk/approve-hr.form"></userTask>
  <exclusiveGateway id="exclusivegateway2" name="Exclusive Gateway"></exclusiveGateway>
  <userTask id="reportBack" name="销假" 
            activiti:assignee="${applyUserId}" 
            activiti:formKey="bk/report-back.form"></userTask>
  <endEvent id="endevent1" name="End"></endEvent>
  <userTask id="modifyApply" name="调整申请内容" 
            activiti:assignee="${applyUserId}" 
            activiti:formKey="bk/modify-apply.form"></userTask>
  <exclusiveGateway id="exclusivegateway3" name="Exclusive Gateway"></exclusiveGateway>
  <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="deptLeaderVerify"></sequenceFlow>
  <sequenceFlow id="flow2" sourceRef="deptLeaderVerify" targetRef="exclusivegateway1"></sequenceFlow>
  <!-- 
    当表达式 ${deptLeaderApprove == 'false'}成立时,将输出流指定到ID为modifyApply的用户任务
    当表达式 ${deptLeaderApprove == 'true'}成立时,将输出流指定到ID为hrAudit的用户任务
  -->
  <sequenceFlow id="flow3" name="同意" sourceRef="exclusivegateway1" targetRef="hrVerify">
    <conditionExpression xsi:type="tFormalExpression">
      <![CDATA[${deptLeaderApproved == 'true'}]]>
    </conditionExpression>
  </sequenceFlow>
  <sequenceFlow id="flow4" sourceRef="hrVerify" targetRef="exclusivegateway2"></sequenceFlow>
  <sequenceFlow id="flow5" name="同意" sourceRef="exclusivegateway2" targetRef="reportBack">
    <conditionExpression xsi:type="tFormalExpression">
      <![CDATA[${hrApproved == 'true'}]]>
    </conditionExpression>
  </sequenceFlow>
  <sequenceFlow id="flow6" sourceRef="reportBack" targetRef="endevent1">
    <extensionElements>
      <activiti:executionListener event="take" expression="${execution.setVariable('result', 'ok')}"></activiti:executionListener>
    </extensionElements>
  </sequenceFlow>
  <sequenceFlow id="flow7" name="不同意" sourceRef="exclusivegateway2" targetRef="modifyApply">
    <conditionExpression xsi:type="tFormalExpression">
      <![CDATA[${hrApproved == 'false'}]]>
    </conditionExpression>
  </sequenceFlow>
  <sequenceFlow id="flow8" name="不同意" sourceRef="exclusivegateway1" targetRef="modifyApply">
    <conditionExpression xsi:type="tFormalExpression">
      <![CDATA[${deptLeaderApproved == 'false'}]]>
    </conditionExpression>
  </sequenceFlow>
  <sequenceFlow id="flow9" sourceRef="modifyApply" targetRef="exclusivegateway3"></sequenceFlow>
  <sequenceFlow id="flow10" name="调整后继续申请" sourceRef="exclusivegateway3" targetRef="deptLeaderVerify">
    <conditionExpression xsi:type="tFormalExpression">
      <![CDATA[${reApply == 'true'}]]>
    </conditionExpression>
  </sequenceFlow>
  <sequenceFlow id="flow11" name="取消申请,并设置取消标志" sourceRef="exclusivegateway3" targetRef="endevent1">
    <extensionElements>
      <activiti:executionListener event="take" expression="${execution.setVariable('result', 'canceled')}"></activiti:executionListener>
    </extensionElements>
    <conditionExpression xsi:type="tFormalExpression">
      <![CDATA[${reApply == 'false'}]]>
    </conditionExpression>
  </sequenceFlow>
  <textAnnotation id="textannotation1" textFormat="text/plain">
    <text>请求被驳回后员工可以选择继续申请,或者取消本次申请</text>
  </textAnnotation>
  <association id="association1" sourceRef="modifyApply" targetRef="textannotation1"></association>
</process>

leave-start.form

<div class="control-group">
	<label class="control-label" for="startDate">开始时间:</label>
	<div class="controls">
		<input type="text" id="startDate" name="startDate" class="datepicker" data-date-format="yyyy-mm-dd" required />
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="endDate">结束时间:</label>
	<div class="controls">
		<input type="text" id="endDate" name="endDate" class="datepicker" data-date-format="yyyy-mm-dd" required />
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="reason">请假原因:</label>
	<div class="controls">
		<textarea id="reason" name="reason" required></textarea>
	</div>
</div>

approve-deptLeader.form

<div class="control-group">
	<label class="control-label" for="startDate">申请人:</label>
	<div class="controls">${applyUserId}</div>
</div>
<div class="control-group">
	<label class="control-label" for="startDate">开始时间:</label>
	<div class="controls">
		<input type="text" id="startDate" name="startDate" value="startDate" readonly />
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="endDate">结束时间:</label>
	<div class="controls">
		<input type="text" id="endDate" name="endDate" value="${endDate}" readonly />
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="reason">请假原因:</label>
	<div class="controls">
		<textarea id="reason" name="reason" readonly>${reason}</textarea>
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="deptLeaderApproved">审批意见:</label>
	<div class="controls">
		<select name="deptLeaderApproved" id="deptLeaderApproved">
			<option value="true">同意</option>
			<option value="false">拒绝</option>
		</select>
	</div>
</div>

approve-ht.form

<div class="control-group">
	<label class="control-label" for="startDate">申请人:</label>
	<div class="controls">${applyUserId}</div>
</div>
<div class="control-group">
	<label class="control-label" for="startDate">开始时间:</label>
	<div class="controls">
		<input type="text" id="startDate" name="startDate" value="startDate" readonly />
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="endDate">结束时间:</label>
	<div class="controls">
		<input type="text" id="endDate" name="endDate" value="${endDate}" readonly />
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="reason">请假原因:</label>
	<div class="controls">
		<textarea id="reason" name="reason" readonly>${reason}</textarea>
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="hrApproved">审批意见:</label>
	<div class="controls">
		<select name="hrApproved" id="hrApproved">
			<option value="true">同意</option>
			<option value="false">拒绝</option>
		</select>
	</div>
</div>

report-back.form

<div class="control-group">
	<label class="control-label" for="startDate">申请人:</label>
	<div class="controls">${applyUserId}</div>
</div>
<div class="control-group">
	<label class="control-label" for="startDate">开始时间:</label>
	<div class="controls">
		<input type="text" id="startDate" name="startDate" value="startDate" readonly />
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="endDate">结束时间:</label>
	<div class="controls">
		<input type="text" id="endDate" name="endDate" value="${endDate}" readonly />
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="reason">请假原因:</label>
	<div class="controls">
		<textarea id="reason" name="reason" readonly>${reason}</textarea>
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="reportBackDate">销假日期:</label>
	<div class="controls">
		<input type="text" id="reportBackDate" name="reportBackDate" class="datepicker" data-date-format="yyyy-mm-dd" required />
	</div>
</div>

modify-apply.form

<div class="control-group">
	<label class="control-label" for="startDate">开始时间:</label>
	<div class="controls">
		<input type="text" id="startDate" name="startDate" value="${startDate}" class="datepicker" data-date-format="yyyy-mm-dd" required />
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="endDate">结束时间:</label>
	<div class="controls">
		<input type="text" id="endDate" name="endDate" value="${endDate}" class="datepicker" data-date-format="yyyy-mm-dd" required />
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="reason">请假原因:</label>
	<div class="controls">
		<textarea id="reason" name="reason" required>${reason}</textarea>
	</div>
</div>
<div class="control-group">
	<label class="control-label" for="reason">是否继续申请:</label>
	<div class="controls">
		<select id="reApply" name="reApply">
			<option value='true'>重新申请</option>
			<option value='false'>结束流程</option>
		</select>
	</div>
</div>
public class BkTest {


    @Test
    public void test() {
        //部署文件
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        repositoryService.createDeployment()
                .addClasspathResource("bk/leave-formkey.bpmn")
                .addClasspathResource("bk/approve-deptLeader.form")
                .addClasspathResource("bk/approve-hr.form")
                .addClasspathResource("bk/leave-start.form")
                .addClasspathResource("bk/modify-apply.form")
                .addClasspathResource("bk/report-back.form")
                .deploy();
        //设置启动用户
        IdentityService identityService = processEngine.getIdentityService();
        identityService.setAuthenticatedUserId("tom");
        //启动流程
        ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().processDefinitionKey("leave-formkey").singleResult();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Map<String, String> variables = new HashMap<>();
        Calendar ca = Calendar.getInstance();
        String startDate = sdf.format(ca.getTime());
        ca.add(Calendar.DAY_OF_MONTH, 2);//请假的开始、结束日期
        String endDate = sdf.format(ca.getTime());
        variables.put("startDate", startDate);
        variables.put("endDate", endDate);
        variables.put("reason", "公休");

        FormService formService = processEngine.getFormService();
        ProcessInstance pi = formService.submitStartFormData(pd.getId(), variables);

        Assert.assertNotNull(pi);

        //部门领导审批通过
        TaskService taskService = processEngine.getTaskService();
        Task deptTask = taskService.createTaskQuery().taskCandidateGroup("deptLeader").singleResult();
        variables = new HashMap<>();
        variables.put("deptLeaderApproved", "true");
        formService.submitTaskFormData(deptTask.getId(), variables);

        //人事审批通过
        Task htTask = taskService.createTaskQuery().taskCandidateGroup("hr").singleResult();
        variables = new HashMap<>();
        variables.put("hrApproved", "true");
        formService.submitTaskFormData(htTask.getId(), variables);

        //销假(根据申请人的用户ID读取)
        Task reportBackTask = taskService.createTaskQuery().taskAssignee("tom").singleResult();
        variables = new HashMap<>();
        variables.put("reportBackDate", ca.getTime().toString());//设置销假日期
        formService.submitTaskFormData(reportBackTask.getId(), variables);

        //验证流程是否已经结束
        HistoryService historyService = processEngine.getHistoryService();
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().finished().singleResult();
        Assert.assertNotNull(historicProcessInstance);

        //读取历史变量
        Map<String, Object> map = packageVariables(pi, historyService);

        Assert.assertEquals("ok", map.get("result"));
    }

    private Map<String, Object> packageVariables(ProcessInstance processInstance, HistoryService historyService) {
        Map<String, Object> historyVariables = new HashMap<>();
        List<HistoricVariableInstance> list = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstance.getId()).list();

        for (HistoricVariableInstance instance  : list) {
            historyVariables.put(instance.getVariableName(), instance.getValue());
            System.out.println(instance.getVariableName() + "------" + instance.getValue());
        }
        return  historyVariables;
    }
}
applyUserId------tom
reason------公休
endDate------2023-09-16
startDate------2023-09-14
deptLeaderApproved------true
hrApproved------true
reportBackDate------Sat Sep 16 22:55:25 CST 2023
result------ok
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值