文章目录
一、动态表单
表单分类
Flowable提供了一种简单灵活的方式,用来为业务流程中的人工步骤添加表单。由俩种方式使用表单的方法:
- 使用(由表单设计器创建的)表单定义的内置表单渲染(弊端是每一步都要设计表单)。
- 外部表单渲染。使用外部表单渲染时,可以使用表单参数;也可以使用表单key定义,引用外部的、使用自定义的代码解析表单。
动态表单相较于流程变量的优点在于变量是零散的,而表单是完整的,整存整取。
案例
- 部署流程定义,启动流程实例
/**
* 部署流程
*/
@Test
void test_deploy() {
Deployment deploy = repositoryService.createDeployment()
.name("ManualDeployment").deploy();
System.out.println(deploy.getId());
}
/**
* 启动流程
*/
@Test
void startFormFlow() throws Exception{
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(PROC_KEY)
.latestVersion()
.singleResult();
Map<String,String> map = new HashMap<>();
map.put("day","1");
map.put("startTime","2023-01-29 21:03");
map.put("reason","去看电影《深海》");
ProcessInstance processInstance = formService.submitStartFormData(processDefinition.getId(), map);
System.out.println("processInstance.getProcessInstanceId() = " + processInstance.getProcessInstanceId());
}
- 获取表单字段
/**
* 获取表单字段
*/
@Test
public void getStartFromData() {
ProcessDefinition processDefinition = repositoryService
.createProcessDefinitionQuery()
.processDefinitionKey(PROC_KEY)
.latestVersion()
.singleResult();
StartFormData startFormData = formService.getStartFormData(processDefinition.getId());
List<FormProperty> formProperties = startFormData.getFormProperties();
for (FormProperty formProperty : formProperties) {
String id = formProperty.getId();
String name = formProperty.getName();
FormType type = formProperty.getType();
System.out.println("id = " + id);
System.out.println("name = " + name);
System.out.println("type.getClass() = " + type.getClass());
}
}
- 表单信息修改保存与查询
/**
* 保存表单,并查询表单
*/
@Test
void saveAndQueryFormData() {
Task task = taskService.createTaskQuery()
.processDefinitionKey(PROC_KEY)
.singleResult();
Map<String, String> map = new HashMap<>();
map.put("day", "3");
map.put("startTime", "2023-01-27 22:42");
map.put("reason", "测试以下提交流程");
formService.saveFormData(task.getId(), map);
TaskFormData taskFormData = formService.getTaskFormData(task.getId());
List<FormProperty> formProperties = taskFormData.getFormProperties();
for (FormProperty formProperty : formProperties) {
System.out.println("formProperty.getId() = " + formProperty.getId());
System.out.println("formProperty.getName() = " + formProperty.getName());
System.out.println("formProperty.getValue() = " + formProperty.getValue());
}
}
- 完成任务
@Test
void completeTask() {
List<Task> tasks = taskService.createTaskQuery()
.processDefinitionKey(PROC_KEY)
.orderByTaskCreateTime().desc().list();
Task task = tasks.get(0);
System.out.println(task.getAssignee() + "-完成任务-" + task.getId());
Map<String, String> map = new HashMap<>();
map.put("day", "4");
map.put("startTime", "2023-01-30 22:42");
map.put("reason", "一起去看《深海》吧");
formService.submitTaskFormData(task.getId(), map);
}
二、 外置表单
1. json表单
集成SpringBoot的Flowable提供了.form
的自动部署机制。会对保存在forms
文件夹下的表单自动部署。
1.1 配置表单位置和表单后缀名
flowable:
form:
resource-suffixes: "**.form" # 默认的表单⽂件后缀
resource-location: "classpath*:/forms" # 默认的表单⽂件位置
1.2 创建form表单
{
"key": "qjlc_form.form",
"name": "经理审批表单",
"fields": [
{
"id": "days",
"name": "请假天数",
"type": "string",
"required": true,
"placeholder": "empty"
},
{
"id": "reason",
"name": "请假原因",
"type": "string",
"required": true,
"placeholder": "empty"
},
{
"id": "startTime",
"name": "开始时间",
"type": "date",
"required": true,
"placeholder": "empty"
}
]
}
1.3 绘制流程
1.4 测试案例
- 部署流程与表单信息
@Test
void test_deploy() {
Deployment deploy = repositoryService.createDeployment()
.category("qjcl-table类型")
.key("qjlc-json")
.addClasspathResource("process/请假流程-json表单.bpmn20.xml")
.name("ManualDeploy-" + PROC_KEY + new Date()).deploy();
FormDeployment formDeployment = formRepositoryService.createDeployment()
.addClasspathResource("forms/application_form.form")
.name("外置表单-" + PROC_KEY)
.parentDeploymentId(deploy.getId())
.category("qjcl-table类型")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("formDeployment.getId() = " + formDeployment.getId());
}
部署表单影响表结构:
2. 启动流程
@Test
void startFlow() {
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(PROC_KEY)
.latestVersion().singleResult();
Map<String, Object> params = this.buildParams(3, "出去玩");
ProcessInstance processInstance = runtimeService.startProcessInstanceWithForm(processDefinition.getId(), "请假开始",
params
, processDefinition.getName());
System.out.println("processInstance.getId() = " + processInstance.getId());
}
- 获取表单信息
@Test
void getFormFields() {
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(PROC_KEY)
.latestVersion().singleResult();
Task task = taskService.createTaskQuery()
.processDefinitionId(processDefinition.getId())
.singleResult();
FormInfo formInfo = taskService.getTaskFormModel(task.getId());
System.out.println("formInfo.getId() = " + formInfo.getId());
SimpleFormModel formModel = (SimpleFormModel) formInfo.getFormModel();
List<FormField> fields = formModel.getFields();
fields.forEach(e -> {
System.out.println("===================================");
System.out.println("e.getId() = " + e.getId());
System.out.println("e.getName() = " + e.getName());
System.out.println("e.getType() = " + e.getType());
System.out.println("e.getValue() = " + e.getValue());
});
}
- 完成任务
@Test
void complete() {
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(PROC_KEY)
.latestVersion().singleResult();
Task task = taskService.createTaskQuery()
.processDefinitionId(processDefinition.getId())
.singleResult();
FormInfo formInfo = taskService.getTaskFormModel(task.getId());
Map<String, Object> params = this.buildParams(6, "go home");
taskService.completeTaskWithForm(task.getId(),
formInfo.getId()
, "请假处理中",
params);
System.out.println(task.getAssignee() + " 处理完成 task.getId() = " + task.getId());
}
/**
* 保存表单与完成任务
*/
@Test
void complete2() {
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(PROC_KEY)
.latestVersion().singleResult();
Task task = taskService.createTaskQuery()
.processDefinitionId(processDefinition.getId())
.singleResult();
FormInfo formInfo = taskService.getTaskFormModel(task.getId());
Map<String, String> params = new HashMap<>();
params.put("days", "20");
params.put("reason", "happy year");
Map<String, Object> vars = new HashMap<>();
vars.put("op", true);
formService.saveFormData(task.getId(), params);
taskService.complete(task.getId(), vars);
System.out.println(task.getAssignee() + " 处理完成 task.getId() = " + task.getId());
}
- 查询历史表单信息
@Test
void getHiFormFields() {
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(PROC_KEY)
.latestVersion().singleResult();
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
.processDefinitionId(processDefinition.getId())
.list();
list.forEach(hti -> {
System.out.println("hti.getExecutionId() = " + hti.getId());
FormInfo formInfo = taskService.getTaskFormModel(hti.getId());
System.out.println("formInfo = " + formInfo);
if (formInfo != null) {
SimpleFormModel formModel = (SimpleFormModel) formInfo.getFormModel();
List<FormField> fields = formModel.getFields();
fields.forEach(e -> System.out.println(e.getName() + " : " + e.getType() + " : " + e.getValue()));
}
System.out.println("===================================");
});
}
2. html表单
2.1 绘制流程
2.2 创建html表单
- leave_form.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="">
<table>
<tr>
<td>请假天数:</td>
<td><input type="text" name="days"></td>
</tr>
<tr>
<td>请假理由:</td>
<td><input type="text" name="reason"></td>
</tr>
<tr>
<td>起始时间:</td>
<td><input type="date" name="startTime"></td>
</tr>
<tr>
<td><input type="submit" value="提交"></td>
</tr>
</table>
</form>
</body>
</html>
- approval.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="">
<table>
<tr>
<td>请假天数:</td>
<td><input type="text" name="days" value="${days}"></td>
</tr>
<tr>
<td>请假理由:</td>
<td><input type="text" name="reason" value="${reason}"></td>
</tr>
<tr>
<td>起始时间:</td>
<td><input type="date" name="startTime" value="${startTime}"></td>
</tr>
<tr>
<td><input type="submit" value="提交"></td>
</tr>
</table>
</form>
</body>
</html>
2.3 测试案例
- 部署流程
@Test
void deploy() {
DeploymentBuilder builder = repositoryService.createDeployment()
.category("HX-FORM-TYPE")
.name("HTML-FORM")
.key(PROC_KEY)
.addClasspathResource("process/请假流程-html表单.bpmn20.xml");
InputStream is = this.getClass().getClassLoader().getResourceAsStream("templates/leave_form.html");
// 注意!这里必须指定名称与流程定义的名称相同!否则将会报错
// Form with formKey 'xxx.html' does not exist
builder.addInputStream("leave_form.html", is);
is = this.getClass().getClassLoader().getResourceAsStream("templates/approval.html");
builder.addInputStream("approval.html", is);
Deployment deployment = builder.deploy();
System.out.println("deployment.getId() = " + deployment.getId());
}
- 获取表单信息
@Test
void getStartFormContent() {
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
.latestVersion().processDefinitionKey(PROC_KEY).singleResult();
System.out.println("pd = " + pd);
String startFormKey = formService.getStartFormKey(pd.getId());
System.out.println("startFormKey = " + startFormKey);
String renderedStartForm = (String) formService.getRenderedStartForm(pd.getId());
System.out.println("renderedStartForm = " + renderedStartForm);
}
@Test
void getFormContent2() throws IOException {
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
.latestVersion().processDefinitionKey(PROC_KEY).singleResult();
List<String> deploymentResourceNames = repositoryService.getDeploymentResourceNames(pd.getDeploymentId());
byte[] bt = new byte[1024];
for (String name : deploymentResourceNames) {
System.out.println("deploymentResourceName = " + name);
InputStream is = repositoryService.getResourceAsStream(pd.getDeploymentId(), name);
while (is.read(bt) != -1) {
System.out.print(new String(bt, 0, bt.length));
}
}
}
- 启动流程
@Test
void startFlow() {
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(PROC_KEY)
.latestVersion().singleResult();
Map<String, String> vars = new HashMap<>();
vars.put("days", "3");
vars.put("reason", "去看《深海》");
vars.put("startTime", new Date().toString());
ProcessInstance pi = formService.submitStartFormData(pd.getId(), vars);
System.out.println("pi.getId() = " + pi.getId());
}
- 完成任务
@Test
void completeTask1() {
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(PROC_KEY).latestVersion().singleResult();
Task task = taskService.createTaskQuery()
.taskCandidateOrAssigned("whx")
.processDefinitionId(pd.getId()).singleResult();
Map<String, String> vars = new HashMap<>();
vars.put("days", "2");
vars.put("reason", "去看《深海》吧");
vars.put("startTime", new Date().toString());
formService.submitTaskFormData(task.getId(), vars);
}
- 获取任务表单信息
@Test
void getTaskFormContent() {
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(PROC_KEY).latestVersion().singleResult();
Task task = taskService.createTaskQuery()
.taskCandidateOrAssigned("dy")
.processDefinitionId(pd.getId()).singleResult();
String renderedTaskForm = (String) formService.getRenderedTaskForm(task.getId());
System.out.println("renderedTaskForm = " + renderedTaskForm);
}
2.4 踩坑记录 FlowableObjectNotFoundException: Form with formKey ‘leave_form.html’ does not exist
注意!这里必须指定名称与流程定义的名称相同!否则将会报错
FlowableObjectNotFoundException: Form with formKey 'leave_form.html' does not exist
正确示例:指定表单的名称与流程定义文件中的一致。
InputStream is = this.getClass().getClassLoader().getResourceAsStream("templates/leave_form.html");
builder.addInputStream("leave_form.html", is);
is = this.getClass().getClassLoader().getResourceAsStream("templates/approval.html");
builder.addInputStream("approval.html", is);
Deployment deployment = builder.deploy();
错误示例:未指定表单的名称。
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("process/请假流程-html表单.bpmn20.xml")
.addClasspathResource("templates/leave_form.html")
.addClasspathResource("templates/approval.html")
.category(PROC_KEY)
.name("ManualDeploy" + new Date())
.key(PROC_KEY).deploy();
三、任务回退
1. 串行任务回退
2. 并行任务回退
并行任务中的一个审批人员驳回到提交节点,后续提交的同样回要求并行中的所有人审批。
示例流程图与子流程回退一起。
3. 子流程回退
子流程回退,也是同样的步骤。
案例代码:
private final String PROC_KEY = "back-child-proc";
@Test
void deploy() {
Deployment deployment = repositoryService.createDeployment()
.name("MunualDeploy-" + new Date()).deploy();
System.out.println("deployment.getId() = " + deployment.getId());
System.out.println("deployment.getName() = " + deployment.getName());
}
@Test
void startFlow() {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(PROC_KEY);
System.out.println("processInstance.getId() = " + processInstance.getId());
}
/**
* 发起申报
*/
@Test
void completeTask() {
Task task = taskService.createTaskQuery()
.processDefinitionKey(PROC_KEY)
.taskCandidateOrAssigned("huathy")
.singleResult();
Map<String, Object> params = new HashMap<>();
params.put("num", 5);
taskService.complete(task.getId(), params);
System.out.println(task.getAssignee() + "-完成任务-" + task.getName() + task.getId());
}
@Test
void completeTask2() {
Task task = taskService.createTaskQuery()
.processDefinitionKey(PROC_KEY)
.taskCandidateOrAssigned("u1")
.singleResult();
taskService.complete(task.getId());
System.out.println(task.getAssignee() + "-完成任务-" + task.getName() + task.getId());
}
/**
* 转派、回退
*/
@Test
void task_back(){
Task task = taskService.createTaskQuery().processDefinitionKey(PROC_KEY)
.taskCandidateOrAssigned("dy").singleResult();
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(task.getProcessInstanceId())
.moveActivityIdTo(task.getTaskDefinitionKey(),"a2")
.changeState();
System.out.println("回退成功");
}
测试步骤:
- tjsq -> a1 -> a2 -> boss-sp[驳回a2] -> a2[驳回tjsq] -> tjsq
- tjsq -> b1 -> b2[审批通过]、b3[驳回tjsq]
观察表ACT_HI_TASKINST记录:
ID_ |REV_|PROC_DEF_ID_ |TASK_DEF_ID_|TASK_DEF_KEY_|PROC_INST_ID_ |EXECUTION_ID_ |SCOPE_ID_|SUB_SCOPE_ID_|SCOPE_TYPE_|SCOPE_DEFINITION_ID_|PROPAGATED_STAGE_INST_ID_|NAME_ |PARENT_TASK_ID_|DESCRIPTION_|OWNER_|ASSIGNEE_|START_TIME_ |CLAIM_TIME_|END_TIME_ |DURATION_|DELETE_REASON_ |PRIORITY_|DUE_DATE_|FORM_KEY_|CATEGORY_|TENANT_ID_|LAST_UPDATED_TIME_ |
------------------------------------+----+------------------------------------------------------+------------+-------------+------------------------------------+------------------------------------+---------+-------------+-----------+--------------------+-------------------------+-------+---------------+------------+------+---------+-----------------------+-----------+-----------------------+---------+-----------------------+---------+---------+---------+---------+----------+-----------------------+
42b004f4-a06b-11ed-b515-0a0027000003| 1|back-child-proc:1:695aece6-a069-11ed-859c-0a0027000003| |tjsq |a8040834-a069-11ed-9015-0a0027000003|42ade212-a06b-11ed-b515-0a0027000003| | | | | |提交申请 | | | |huathy |2023-01-30 14:56:45.904| | | | | 50| | | | |2023-01-30 14:56:45.916|
da9663eb-a06a-11ed-bbba-0a0027000003| 2|back-child-proc:1:695aece6-a069-11ed-859c-0a0027000003| |b2 |a8040834-a069-11ed-9015-0a0027000003|7aadee3a-a06a-11ed-bcb9-0a0027000003| | | | | |B流程U2审批| | | |u2 |2023-01-30 14:53:51.256| |2023-01-30 14:56:45.881| 174625|Change activity to tjsq| 50| | | | |2023-01-30 14:56:45.881|
da97755e-a06a-11ed-bbba-0a0027000003| 1|back-child-proc:1:695aece6-a069-11ed-859c-0a0027000003| |b3 |a8040834-a069-11ed-9015-0a0027000003|da950457-a06a-11ed-bbba-0a0027000003| | | | | |B流程U3审批| | | |u3 |2023-01-30 14:53:51.269| | | | | 50| | | | |2023-01-30 14:53:51.269|
b8bd625f-a06a-11ed-9851-0a0027000003| 2|back-child-proc:1:695aece6-a069-11ed-859c-0a0027000003| |b1 |a8040834-a069-11ed-9015-0a0027000003|7aadee3a-a06a-11ed-bcb9-0a0027000003| | | | | |B流程U1审批| | | |u1 |2023-01-30 14:52:54.472| |2023-01-30 14:53:51.205| 56733| | 50| | | | |2023-01-30 14:53:51.205|
7aaed89c-a06a-11ed-bcb9-0a0027000003| 2|back-child-proc:1:695aece6-a069-11ed-859c-0a0027000003| |tjsq |a8040834-a069-11ed-9015-0a0027000003|7aadee3a-a06a-11ed-bcb9-0a0027000003| | | | | |提交申请 | | | |huathy |2023-01-30 14:51:10.357| |2023-01-30 14:52:54.359| 104002| | 50| | | | |2023-01-30 14:52:54.359|
3c252cfa-a06a-11ed-bc9f-0a0027000003| 2|back-child-proc:1:695aece6-a069-11ed-859c-0a0027000003| |a2 |a8040834-a069-11ed-9015-0a0027000003|3c21d198-a06a-11ed-bc9f-0a0027000003| | | | | |A流程U2审批| | | |u2 |2023-01-30 14:49:25.424| |2023-01-30 14:51:10.331| 104907|Change activity to tjsq| 50| | | | |2023-01-30 14:51:10.331|
12ec58eb-a06a-11ed-846a-0a0027000003| 2|back-child-proc:1:695aece6-a069-11ed-859c-0a0027000003| |boss-sp |a8040834-a069-11ed-9015-0a0027000003|12e94ba6-a06a-11ed-846a-0a0027000003| | | | | |老大审批 | | | |dy |2023-01-30 14:48:16.275| |2023-01-30 14:49:25.392| 69117|Change activity to a2 | 50| | | | |2023-01-30 14:49:25.392|
f14c419e-a069-11ed-81e1-0a0027000003| 2|back-child-proc:1:695aece6-a069-11ed-859c-0a0027000003| |a2 |a8040834-a069-11ed-9015-0a0027000003|f14ba55a-a069-11ed-81e1-0a0027000003| | | | | |A流程U2审批| | | |u2 |2023-01-30 14:47:19.864| |2023-01-30 14:48:16.182| 56318| | 50| | | | |2023-01-30 14:48:16.182|
ddeefb07-a069-11ed-a7fa-0a0027000003| 2|back-child-proc:1:695aece6-a069-11ed-859c-0a0027000003| |a1 |a8040834-a069-11ed-9015-0a0027000003|a8042f45-a069-11ed-9015-0a0027000003| | | | | |A流程U1审批| | | |u1 |2023-01-30 14:46:47.378| |2023-01-30 14:47:19.828| 32450| | 50| | | | |2023-01-30 14:47:19.828|
a8073c89-a069-11ed-9015-0a0027000003| 2|back-child-proc:1:695aece6-a069-11ed-859c-0a0027000003| |tjsq |a8040834-a069-11ed-9015-0a0027000003|a8042f45-a069-11ed-9015-0a0027000003| | | | | |提交申请 | | | |huathy |2023-01-30 14:45:16.933| | 2023-01-30 14:46:47.32| 90387| | 50| | | | | 2023-01-30 14:46:47.32|
ACT_RU_TASK
ID_ |REV_|EXECUTION_ID_ |PROC_INST_ID_ |PROC_DEF_ID_ |TASK_DEF_ID_|SCOPE_ID_|SUB_SCOPE_ID_|SCOPE_TYPE_|SCOPE_DEFINITION_ID_|PROPAGATED_STAGE_INST_ID_|NAME_ |PARENT_TASK_ID_|DESCRIPTION_|TASK_DEF_KEY_|OWNER_|ASSIGNEE_|DELEGATION_|PRIORITY_|CREATE_TIME_ |DUE_DATE_|CATEGORY_|SUSPENSION_STATE_|TENANT_ID_|FORM_KEY_|CLAIM_TIME_|IS_COUNT_ENABLED_|VAR_COUNT_|ID_LINK_COUNT_|SUB_TASK_COUNT_|
------------------------------------+----+------------------------------------+------------------------------------+------------------------------------------------------+------------+---------+-------------+-----------+--------------------+-------------------------+-------+---------------+------------+-------------+------+---------+-----------+---------+-----------------------+---------+---------+-----------------+----------+---------+-----------+-----------------+----------+--------------+---------------+
0ce74a9a-a06c-11ed-823f-0a0027000003| 1|42ade212-a06b-11ed-b515-0a0027000003|a8040834-a069-11ed-9015-0a0027000003|back-child-proc:1:695aece6-a069-11ed-859c-0a0027000003| | | | | | |B流程U2审批| | |b2 | |u2 | | 50|2023-01-30 15:02:25.164| | | 1| | | | 1| 0| 0| 0|
630bc037-a06c-11ed-8632-0a0027000003| 1|630a3995-a06c-11ed-8632-0a0027000003|a8040834-a069-11ed-9015-0a0027000003|back-child-proc:1:695aece6-a069-11ed-859c-0a0027000003| | | | | | |A流程U2审批| | |a2 | |u2 | | 50|2023-01-30 15:04:49.692| | | 1| | | | 1| 0| 0| 0|