Files
qhmes/.trae/skills/jeecg-bpmn/references/bpmn-examples.md

395 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 完整示例、Python 脚本与流程模式
## 1. 完整示例 — 请假审批流程
**流程描述:** 开始 → 员工提交 → 经理审批 → 排他网关(通过/拒绝) → HR审批 → 结束
### 1.1 节点定义
```xml
<bpmn2:startEvent id="start" name="开始" flowable:initiator="applyUserId" />
<bpmn2:userTask id="task_apply" name="员工提交申请" flowable:assignee="${applyUserId}" />
<bpmn2:userTask id="task_manager" name="部门经理审批" flowable:assignee="manager" />
<bpmn2:exclusiveGateway id="gateway_result" name="审批结果" />
<bpmn2:userTask id="task_hr" name="HR审批" flowable:assignee="hr" />
<bpmn2:endEvent id="end" name="结束" />
```
### 1.2 连线定义
```xml
<bpmn2:sequenceFlow id="flow_1" name="" sourceRef="start" targetRef="task_apply" />
<bpmn2:sequenceFlow id="flow_2" name="" sourceRef="task_apply" targetRef="task_manager" />
<bpmn2:sequenceFlow id="flow_3" name="" sourceRef="task_manager" targetRef="gateway_result" />
<bpmn2:sequenceFlow id="flow_approve" name="通过" sourceRef="gateway_result" targetRef="task_hr">
<bpmn2:conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 1}]]></bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="flow_reject" name="拒绝" sourceRef="gateway_result" targetRef="end">
<bpmn2:conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 0}]]></bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="flow_4" name="" sourceRef="task_hr" targetRef="end" />
```
### 1.3 布局计算
```
节点列表(按垂直顺序):
start: type=startEvent, y=30, h=36 → bottom=66
task_apply: type=userTask, y=106, h=60 → bottom=166
task_manager: type=userTask, y=206, h=60 → bottom=266
gateway_result: type=exclusiveGateway, y=306, h=50 → bottom=356
task_hr: type=userTask, y=396, h=60 → bottom=456
end: type=endEvent, y=496, h=36 → bottom=532
```
### 1.4 nodes 参数
```
id=task_apply###nodeName=员工提交申请@@@id=task_manager###nodeName=部门经理审批@@@id=task_hr###nodeName=HR审批@@@
```
## 2. Python 调用脚本模板
```python
import urllib.request
import urllib.parse
import json
import time
def create_bpm_process(api_base, token, process_name, process_type, bpmn_xml, nodes_str, tenant_id="1"):
"""
创建 JeecgBoot BPM 流程
Args:
api_base: 后端地址,如 "https://api3.boot.jeecg.com"
token: X-Access-Token
process_name: 流程名称
process_type: 流程类型(如 "oa"
bpmn_xml: 完整 BPMN XML 字符串
nodes_str: nodes 参数字符串
tenant_id: 租户ID默认 "1"
Returns:
dict: API 返回结果
"""
ts = str(int(time.time() * 1000))
process_key = f"process_{ts}"
# 替换 XML 中的占位符
bpmn_xml = bpmn_xml.replace("${PROCESS_KEY}", process_key)
bpmn_xml = bpmn_xml.replace("${PROCESS_NAME}", process_name)
data = {
"processDefinitionId": "0",
"processName": process_name,
"processkey": process_key,
"typeid": process_type,
"lowAppId": "",
"params": "",
"nodes": nodes_str,
"processDescriptor": bpmn_xml,
"realProcDefId": "",
"startType": "manual"
}
encoded_data = urllib.parse.urlencode(data).encode('utf-8')
req = urllib.request.Request(
f"{api_base}/act/designer/api/saveProcess",
data=encoded_data,
headers={
"X-Access-Token": token,
"X-Sign": "00000000000000000000000000000000",
"X-Tenant-Id": tenant_id,
"X-Timestamp": ts,
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
},
method="POST"
)
resp = urllib.request.urlopen(req)
result = json.loads(resp.read().decode('utf-8'))
return result, process_key
def update_bpm_process(api_base, token, process_id, process_key, process_name, process_type, bpmn_xml, nodes_str, tenant_id="1"):
"""
更新已有 JeecgBoot BPM 流程
Args:
api_base: 后端地址
token: X-Access-Token
process_id: 流程数据表ID新建时返回的 obj
process_key: 流程定义ID
process_name: 流程名称
process_type: 流程类型
bpmn_xml: 完整 BPMN XML 字符串
nodes_str: nodes 参数字符串
tenant_id: 租户ID默认 "1"
Returns:
dict: API 返回结果
"""
ts = str(int(time.time() * 1000))
data = {
"processDefinitionId": process_id,
"processName": process_name,
"processkey": process_key,
"typeid": process_type,
"lowAppId": "",
"params": "",
"nodes": nodes_str,
"processDescriptor": bpmn_xml,
"startType": "manual"
}
encoded_data = urllib.parse.urlencode(data).encode('utf-8')
req = urllib.request.Request(
f"{api_base}/act/designer/api/saveProcess",
data=encoded_data,
headers={
"X-Access-Token": token,
"X-Sign": "00000000000000000000000000000000",
"X-Tenant-Id": tenant_id,
"X-Timestamp": ts,
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
},
method="POST"
)
resp = urllib.request.urlopen(req)
result = json.loads(resp.read().decode('utf-8'))
return result
```
## 3. 常见流程模式速查
### 模式A简单审批线性
```
开始 → 提交 → 审批 → 结束
```
### 模式B多级审批线性
```
开始 → 提交 → 经理审批 → 总监审批 → HR审批 → 结束
```
### 模式C条件分支排他网关
```
开始 → 提交 → 审批 → 网关
├─ 通过 → 下一步 → 结束
└─ 拒绝 → 结束
```
### 模式D金额条件分支
```
开始 → 提交 → 网关(金额判断)
├─ ≤1000 → 经理审批 → 结束
├─ ≤10000 → 总监审批 → 结束
└─ >10000 → CEO审批 → 结束
```
### 模式E并行会签
```
开始 → 提交 → 并行网关(fork)
├─ 部门A审批
└─ 部门B审批
并行网关(join) → 结束
```
### 模式F审批+驳回到发起人(草稿节点模式)
```
开始 → 草稿(自动跳过) → 审批 → 网关
├─ 通过 → 结束
└─ 驳回 → 草稿(发起人修改后重新提交)
```
**关键节点 XML**
```xml
<!-- 草稿节点:首次自动跳过,驳回后发起人手动操作 -->
<bpmn2:userTask id="task_draft" name="草稿" flowable:assignee="${applyUserId}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value="{&quot;sameMode&quot;:2,&quot;isSkipAssigneeEmpty&quot;:false,&quot;isSkipAssigneeOnePersion&quot;:false,&quot;isSkipApproval&quot;:false,&quot;isAssignedByPreviousNode&quot;:false,&quot;isEmptyAssignedByPreviousNode&quot;:false}" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener" event="create" id="9c3064baa7074eab62e3c5b3b5458691" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<!-- 驳回连线:从网关回到草稿节点 -->
<bpmn2:sequenceFlow id="flow_reject" name="驳回" sourceRef="gateway_result" targetRef="task_draft">
<bpmn2:conditionExpression xsi:type="tFormalExpression"><![CDATA[${result == 0}]]></bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
```
### 模式G多级审批 + 表达式审批人 + 上一节点指派(实战验证)
```
开始 → 申请人填写(草稿) → 部门负责人审批(表达式) → 分管领导审批(表达式) → 指派确认(上一节点指派) → 结束
```
**已验证成功的完整 Python 脚本f-string 方式构造 XML**
```python
import urllib.request
import urllib.parse
import json
import time
API_BASE = '{后端地址}'
TOKEN = '{X-Access-Token}'
ts = str(int(time.time() * 1000))
process_key = f'process_{ts}'
process_name = '车辆出差申请流程'
# 注意f-string 中 ${xxx} 写作 ${{xxx}}JSON 花括号也需 {{}}
bpmn_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:flowable="http://flowable.org/bpmn"
id="sample-diagram"
targetNamespace="http://bpmn.io/schema/bpmn"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="{process_key}" name="{process_name}">
<bpmn2:extensionElements>
<flowable:executionListener class="org.jeecg.modules.extbpm.listener.execution.ProcessEndListener" event="end" />
<flowable:eventListener class="org.jeecg.modules.listener.tasktip.TaskCreateGlobalListener" />
</bpmn2:extensionElements>
<bpmn2:startEvent id="start" name="开始" flowable:initiator="applyUserId" />
<bpmn2:userTask id="task_apply" name="申请人填写" flowable:assignee="${{applyUserId}}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value='{{"sameMode":2,"isSkipAssigneeEmpty":false,"isSkipAssigneeOnePersion":false,"isSkipApproval":false,"isAssignedByPreviousNode":false,"isEmptyAssignedByPreviousNode":false}}' />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskCreatedAutoSubmitListener" event="create" id="auto_submit_1" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<bpmn2:userTask id="task_dept_leader" name="部门负责人审批"
flowable:candidateUsers="${{flowNodeExecution.getDepartLeaders(execution)}}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value='{{"sameMode":0,"isSkipAssigneeEmpty":false,"isSkipAssigneeOnePersion":true,"isSkipApproval":false,"isAssignedByPreviousNode":false,"isEmptyAssignedByPreviousNode":true}}' />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<bpmn2:userTask id="task_leader" name="分管领导审批"
flowable:candidateUsers="${{flowNodeExecution.getLevel1DepartLeaders(execution)}}">
<bpmn2:extensionElements>
<flowable:taskExtendJson value='{{"sameMode":0,"isSkipAssigneeEmpty":false,"isSkipAssigneeOnePersion":true,"isSkipApproval":false,"isAssignedByPreviousNode":false,"isEmptyAssignedByPreviousNode":true}}' />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<bpmn2:userTask id="task_dispatch" name="车辆调度确认">
<bpmn2:extensionElements>
<flowable:taskExtendJson value='{{"sameMode":0,"isSkipAssigneeEmpty":false,"isSkipAssigneeOnePersion":false,"isSkipApproval":false,"isAssignedByPreviousNode":true,"isEmptyAssignedByPreviousNode":false}}' />
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
</bpmn2:extensionElements>
</bpmn2:userTask>
<bpmn2:endEvent id="end" name="结束" />
<bpmn2:sequenceFlow id="flow_1" name="" sourceRef="start" targetRef="task_apply" />
<bpmn2:sequenceFlow id="flow_2" name="" sourceRef="task_apply" targetRef="task_dept_leader" />
<bpmn2:sequenceFlow id="flow_3" name="" sourceRef="task_dept_leader" targetRef="task_leader" />
<bpmn2:sequenceFlow id="flow_4" name="" sourceRef="task_leader" targetRef="task_dispatch" />
<bpmn2:sequenceFlow id="flow_5" name="" sourceRef="task_dispatch" targetRef="end" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="{process_key}">
<bpmndi:BPMNShape id="shape_start" bpmnElement="start">
<dc:Bounds x="200" y="30" width="36" height="36" />
<bpmndi:BPMNLabel><dc:Bounds x="207" y="73" width="22" height="14" /></bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_task_apply" bpmnElement="task_apply">
<dc:Bounds x="168" y="106" width="100" height="60" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_task_dept_leader" bpmnElement="task_dept_leader">
<dc:Bounds x="168" y="206" width="100" height="60" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_task_leader" bpmnElement="task_leader">
<dc:Bounds x="168" y="306" width="100" height="60" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_task_dispatch" bpmnElement="task_dispatch">
<dc:Bounds x="168" y="406" width="100" height="60" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_end" bpmnElement="end">
<dc:Bounds x="200" y="506" width="36" height="36" />
<bpmndi:BPMNLabel><dc:Bounds x="207" y="549" width="22" height="14" /></bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge_flow_1" bpmnElement="flow_1">
<di:waypoint x="218" y="66" /><di:waypoint x="218" y="106" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_2" bpmnElement="flow_2">
<di:waypoint x="218" y="166" /><di:waypoint x="218" y="206" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_3" bpmnElement="flow_3">
<di:waypoint x="218" y="266" /><di:waypoint x="218" y="306" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_4" bpmnElement="flow_4">
<di:waypoint x="218" y="366" /><di:waypoint x="218" y="406" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_5" bpmnElement="flow_5">
<di:waypoint x="218" y="466" /><di:waypoint x="218" y="506" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>'''
nodes_str = 'id=task_apply###nodeName=申请人填写@@@id=task_dept_leader###nodeName=部门负责人审批@@@id=task_leader###nodeName=分管领导审批@@@id=task_dispatch###nodeName=车辆调度确认@@@'
data = {
'processDefinitionId': '0',
'processName': process_name,
'processkey': process_key,
'typeid': 'oa',
'lowAppId': '',
'params': '',
'nodes': nodes_str,
'processDescriptor': bpmn_xml,
'realProcDefId': '',
'startType': 'manual'
}
encoded_data = urllib.parse.urlencode(data).encode('utf-8')
req = urllib.request.Request(
f'{API_BASE}/act/designer/api/saveProcess',
data=encoded_data,
headers={
'X-Access-Token': TOKEN,
'X-Sign': '00000000000000000000000000000000',
'X-Tenant-Id': '1',
'X-Timestamp': str(int(time.time() * 1000)),
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
method='POST'
)
resp = urllib.request.urlopen(req)
result = json.loads(resp.read().decode('utf-8'))
print(json.dumps(result, ensure_ascii=False, indent=2))
print(f'\\nProcess Key: {process_key}')
```
**实战要点:**
1. 此脚本必须先写入 `.py` 文件再执行(不能 `python3 -c` 内联bash 会展开 `${}` 导致报错)
2. f-string 中所有 `${xxx}` 都写作 `${{xxx}}`
3. taskExtendJson 的 `value` 用单引号包裹 JSONJSON 花括号用 `{{}}` 转义
4. 执行完毕后删除临时 `.py` 文件