Install
$ agentstack add skill-jeecgboot-skills-jeecg-bpmn ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ● Filesystem access Used
- ● Shell / process execution Used
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
JeecgBoot BPM 流程自动生成器
将自然语言的流程描述转换为 Flowable BPMN 2.0 XML,并通过 API 在 JeecgBoot 系统中自动创建流程。
临时配置文件规则(强制)
所有传给脚本的 --config 必须写到 {系统临时目录}/{SKILL_NAME}/ 下,由操作系统自动清理;skill 与脚本均不主动删除该目录或文件。
import tempfile, os, json
SKILL_NAME = "" # 请替换为实际的技能名称
skill_dir = os.path.join(tempfile.gettempdir(), SKILL_NAME)
os.makedirs(skill_dir, exist_ok=True) # 确保目录存在,不主动检查
config_path = os.path.join(skill_dir, 'sk_audit_create.json') # 示例文件名
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(cfg, f, ensure_ascii=False, indent=2)
tempfile.gettempdir() 自动适配:Windows %TEMP%、Linux /tmp、macOS /var/folders/.../T(注意 macOS 并非 /tmp)。 文件名建议使用 _.json(如 sk_audit_create.json),无需重复技能前缀,因路径已包含技能名称,便于排错。
禁止:
- 写到
/tmp/或当前工作目录(污染 skill / 用户项目) - 硬编码
/tmp、C:\Temp或任何固定路径(不跨平台) - 每步完成后主动
rm/Remove-Item(操作系统会清理,属多余 tool call) - 主动
os.path.exists()检查(其本身即为一次 tool call)
(使用 os.makedirs(…, exist_ok=True) 满足需求,不算主动检查)
临时文件可能被操作系统异步清理,但仍遵循 乐观调用 + 报错补救:仅当脚本返回 FileNotFoundError 或 配置文件不存在 时,使用相同内容、在相同的 {系统临时目录}/{SKILL名称}/ 路径下重写(重写前仍需 os.makedirs(skill_dir, exist_ok=True) 确保目录存在),切勿更换路径或回退至 skill 目录。
介绍组件时的完整性要求
> 重要: 当用户要求介绍流程设计器各组件时,必须包含以下内容,不可遗漏: > > 1. 会签节点:串行/并行两种模式;全部通过/一人通过/半数通过/按比例/自定义 5种通过规则;指定人员/角色/审批角色/部门/岗位/职级/表单字段/流程变量 8种审批人类型 > 2. 条件表达式:系统内置流程变量(result、applyUserId、applyDate 等);13种条件运算符;多条件组合用法(AND/OR) > 3. 监听器:执行监听器/任务监听器/全局事件监听器三种类型;系统预置监听器(ProcessEndListener必需、TaskSkipApprovalListener、TaskCreatedAutoSubmitListener等);taskExtendJson 节点行为控制字段说明
性能规范与已验证规律
> ⚠️ 禁止预防性读取参考文档。 执行任务前不要为了"以防万用"而读取 references/ 下的文档。只在遇到具体问题时按需读取,且使用 offset/limit 指定行范围。 > > ⚠️ 对外部 API 响应结构,先用小脚本探测,再写主逻辑。 但下方速查表中已验证的数据不需要重新探测。 > > ⚠️ 用不熟悉的 Python 模块前,必须先 dir() 查 exports。 但下方速查表中已验证的模块不需要重新 dir()。 > > ⚠️ 禁止对 API 响应的 result 直接做 [:] 切片。 JeecgBoot API 的 result 格式不统一:分页接口返回 dict {"records": [...], "total": N},全量接口返回 list [...],写操作返回 string。对 dict 做切片 → KeyError: slice(None, 5, None)。强制规则:取值前必须根据「API 响应速查」表确定 result 类型,分页接口统一用 .get('result', {}).get('records', []),全量接口用 isinstance(result, list) 判后再切片。
模块导入(固定模式,直接复用)
import os, pathlib, sys
_SKILLS_DIR = pathlib.Path.home() / '.claude' / 'skills'
sys.path.insert(0, str(_SKILLS_DIR / 'jeecg-desform' / 'scripts')) # desform_creator, desform_utils
sys.path.insert(0, str(_SKILLS_DIR / 'jeecg-bpmn' / 'scripts')) # bpmn_creator, bpmn_oa
sys.path.insert(0, str(_SKILLS_DIR / 'jeecg-system' / 'scripts')) # system_utils
os.chdir(str(_SKILLS_DIR / 'jeecg-bpmn' / 'scripts'))
import desform_utils as du; du.init_api(API_BASE, TOKEN) # ⚠ 必须初始化,否则 ValueError: unknown url type
import desform_creator as dc # 无需初始化
import bpmn_creator as bc # 无需初始化,各函数直接传 api_base/token
# system_utils 需要: from system_utils import init_api, ...; init_api(API_BASE, TOKEN)
函数返回值速查
| 函数 | 返回类型 | 正确取值 | |------|---------|---------| | dc.create_form(...) | tuple (form_id, title_field_model) | result[0] | | dc.get_form_id(code) | tuple (form_id, index) | result[0] | | bc.get_desform_fields(api_base, token, code) | dict {label: {model, key, type}} | fields.get('薪资', {}).get('model') | | bc.authorize_form(...) | dict(不是 tuple) | r = bc.authorize_form(...) | | du.get_form_fields(code) | list [{name, model, type}] | 返回表单字段列表。注意:不存在 du.get_form_detail() |
API 响应速查
| API / 操作 | 返回值 | 正确取值 | |-----------|--------|---------| | saveProcess | dict | result['obj'] 含新ID(编辑时可能 null,按 processKey 查)。路径:/act/designer/api/saveProcess,Content-Type:application/x-www-form-urlencoded | | extActProcess/queryById | dict | result 含流程全字段;result['processXml'] 为 base64 编码的 XML,需 base64.b64decode().decode('utf-8') | | sys/sysDepart/add | result=null | 新建后用 queryDepartAndPostTreeSync 全量查找 | | approvalRole/rootList | result.records[](不是裸数组) | r['result']['records'],每条 {id, name, type, pid} | | approvalRole/childList?pid=xxx | result.records[](不是裸数组) | r['result']['records'] | | approvalRole/group/add | result="添加成功!"(字符串,不是 ID) | 创建后调 rootList 按 name 查 ID | | approvalRole/role/add | result="添加成功!"(字符串,不是 ID) | 创建后调 childList 按 name 查 ID | | sys/position/list | result.records[] | 每条 {id, name, code},用于 deptPosition 审批人 | | query_approval_roles() | {'roles': [...], 'persons': [...]} | 用 find_approval_role(keyword) | | query_dept_positions() | depart 树节点(departName 不是 name) | 过滤 orgCategory=='3' |
关键函数签名
| 函数 | 签名 | |------|------| | du.create_form | (name, code, widgets, title_index=0, layout='auto', ...) | | bc.edit_node_config | (api_base, token, process_id, node_code, node_settings) | | bc.set_node_field_permissions | (api_base, token, process_id, node_code, form_code, field_permissions, form_type='2') |
其他关键规律
dc.DIVIDER/USER/MONEY等常量是函数不是字符串,创建 widget 用dc.build_widget({'type':'money', 'name':'金额', 'required': True})build_widget对所有控件类型都强制要求name字段(含 divider:{'type': 'divider', 'name': '---', 'text': '标题'})build_widget合法type清单:基础input textarea number integer money date time switch slider rate color/ 选择radio select checkbox/ 系统select-user select-depart select-depart-post phone email area-linkage org-role/ 文件file-upload imgupload hand-sign/ 高级auto-number formula barcode location table-dict select-tree link-record link-field capital-money text-compose ocr map summary editor markdown/ OAoa-approval-comments/ 布局tabs grid card divider text buttons- DesForm 字段在
design["list"]下(不是design["fields"]),嵌套结构需递归提取:
``python def find_fields(node, results): if isinstance(node, dict): if node.get('type') not in ('grid','text','') and node.get('model'): results.append(node) for v in node.values(): find_fields(v, results) elif isinstance(node, list): for item in node: find_fields(item, results) fields = []; find_fields(design, fields) ``
userTask含会签时 XML 子元素顺序:extensionElements→incoming/outgoing→multiInstanceLoopCharacteristics(顺序错报cvc-complex-type.2.4.a)- 条件表达式必须调
bc.build_condition_b64(),手写格式:外层数组[{"logic":"and","conditions":[...]}],flowUtil.evaluateExpression需三参数(execution, 'b64', 'and') - 手工分支 + 网关组合 → 自动使用水平多行布局(
_detect_horizontal_multirow),W_GAP=60, MAIN_CY=330, LOWER_CY=540 - 包含网关(inclusiveGateway)带 default flow 时:default flow 从 split 直达 join(无中间节点),
_detect_parallel_blocks已支持空链检测,calc_layout只对非空分支做水平展开(已修复,此前空链导致检测失败、分支垂直堆叠重叠) - 子流程必须先于表单创建,否则表单关联冲突(修复:DELETE 子流程 formId 再重新 link_form)
bpmn_oa.py支持subprocess键一键创建子流程,自动填充calledElement- 手写子流程必须加
"isSubProcess": True(bpmn_oa.py的_setup_oa_subprocess已自动设置) - system_utils 函数:查岗位
query_dept_positions(dept_id=None)/ 查角色find_approval_role(keyword)返回 dict 或 None / 岗位列表GET /sys/position/list/ 不存在/sys/position/rank/list/sys/duty/list - 不存在的 API(禁止尝试):
queryDepartTreeSync?pid=xxxqueryIdTreequeryTreeListqueryMyDeptloadNodeGroupData?groupType=deptPositionqueryByKeywordssysDepart/listrecycleBin/* - 审批角色查找或创建模式(防重复 + 获取真实 ID):
``python def find_or_create_approval_role(name, grp_id): def query_id(): r = api_get(f'/sys/approvalRole/childList?pid={grp_id}') return next((c['id'] for c in r.get('result',{}).get('records',[]) if c['name']==name), None) rid = query_id() if not rid: api_post('/sys/approvalRole/role/add', {'name': name, 'pid': grp_id}) rid = query_id() return rid ``
bc.edit_node_config不更新nodeConfigJson(已踩坑):该函数只做node.update(settings)后 PUT,不同步nodeConfigJson字段。前端读nodeConfigJson.formEditStatus时仍为 false,导致可编辑节点实际不可编辑。凡需设置formEditStatus=1的节点,必须手动同步更新nodeConfigJson,正确写法:
``python def fix_node_form_edit(api_base, token, process_id, node_code, url): """设 formEditStatus=1 并同步 nodeConfigJson(edit_node_config 不做这步)""" r = bc.api_request(api_base, token, f'/act/process/extActProcessNode/list?processId={process_id}&pageNo=1&pageSize=50', method='GET') for node in (r.get('result') or {}).get('records', []): if node.get('processNodeCode') == node_code: node['formEditStatus'] = '1' node['modelAndView'] = url node['modelAndViewMobile'] = url try: cfg = json.loads(node.get('nodeConfigJson') or '{}') except Exception: cfg = {} cfg['formEditStatus'] = True # ← 关键:必须同步 node['nodeConfigJson'] = json.dumps(cfg, ensure_ascii=False) return bc.api_request(api_base, token, '/act/process/extActProcessNode/edit', data=node, method='PUT') ` > setdraftnodeseditable 已内置此逻辑;只有直接调 editnode_config` 设 formEditStatus 时需要用上述替代函数。
- 子流程节点禁止使用
draft=True(已踩坑):在被callActivity调用的子流程中,任何节点都不能设draft=True。原因:draft=True会为节点添加TaskCreatedAutoSubmitListener,callActivity 启动子流程时该监听器立即自动提交任务,此时子流程 execution 仍处于中间态,写入ACT_RU_VARIABLE时EXECUTION_ID_无效,触发 FK 约束失败(ACT_FK_VAR_EXE)。子流程中需要表单可编辑的节点,改用fix_node_form_edit显式设置formEditStatus=1即可。
规则3:URL 中含中文参数必须用 urllib.parse.quote 编码(⚠️ 强制)
# ✅ 正确
import urllib.parse
keyword = urllib.parse.quote('安全评审')
url = f'{API_BASE}/sys/approvalRole/search?keyword={keyword}'
# 或用 urlencode:params = urllib.parse.urlencode({'keyword': '安全评审'})
规则4:独立的系统数据查询必须合并到单个脚本一次执行(⚠️ 强制)
不要分多轮 Bash 调用执行独立查询,合并到一个脚本里一次运行。
规则5:部门/岗位查询只能用 queryDepartAndPostTreeSync(⚠️ 强制)
req = urllib.request.Request(f'{API_BASE}/sys/sysDepart/queryDepartAndPostTreeSync', headers=HEADERS)
result = json.loads(urllib.request.urlopen(req).read().decode())['result'] or []
def flatten(nodes, acc=None):
if acc is None: acc = []
for n in (nodes or []):
if isinstance(n, dict):
acc.append(n)
flatten(n.get('children', []), acc)
return acc
all_nodes = flatten(result)
depts = [n for n in all_nodes if str(n.get('orgCategory','')) == '2']
positions = [n for n in all_nodes if str(n.get('orgCategory','')) == '3']
规则6:DesForm 表单编码被回收站占用时直接换编码(⚠️ 强制)
desform/add 返回 "该code已存在" 但 desform/list 查不到 → 回收站占用。直接加后缀 _v2,禁止尝试 recycleBin API(均 404)。
规则7:含 ${...} 的 Python 脚本禁止用 python -c "..." 执行(⚠️ 强制)
现象: bash: bad substitution,Python 根本没启动。
根因: bash 双引号内的 ${...} 会被当作 shell 变量展开。Python f-string 中的 f'${{{model}}}'(生成 DesForm URL 占位符如 ${BPM_DES_DATA_ID})触发 bash 的非法变量名错误。
强制规则:凡是脚本含 ${ 的,必须写入 .py 文件再执行,不得用 -c "..."。
# ❌ 错误 —— bash 会展开 ${...},报 bad substitution
python -X utf8 -c "
...
f'${{{model}}}提交的申请'
"
# ✅ 正确 —— 写文件,bash 不解析文件内容
# Write tool 写入 C:\Users\25067\tmp_script.py,然后:
powershell -Command "& python -X utf8 C:\Users\25067\tmp_script.py"
powershell -Command "Remove-Item 'C:\Users\25067\tmp_script.py'"
规则8:Scenario A 子流程创建后必须立即删除其表单绑定(⚠️ 强制)
现象: 主流程 link_form 报 "编码重复或表名已被授权流程!"。
根因: bpmn_oa._setup_oa_subprocess 内部会将主流程的 form_code 关联到子流程(extActProcessForm),后端对 relationCode 有唯一约束,导致主流程随后绑定同一表单失败。
强制规则: 调用 _setup_oa_subprocess 后,主流程 link_form 前,必须先删除子流程的表单绑定:
# _setup_oa_subprocess 执行完之后立即执行:
import urllib.parse as _up
q = _up.urlencode({'processId': sub_pid, 'pageNo': 1, 'pageSize': 10})
sub_forms = bc.api_request(API_BASE, TOKEN,
f'/act/process/extActProcessForm/list?{q}', method='GET')
for rec in (sub_forms.get('result') or {}).get('records', []):
bc.api_request(API_BASE, TOKEN,
f'/act/process/extActProcessForm/delete?id={rec["id"]}', method='DELETE')
# 之后再 link_form 到主流程
> 此步骤不影响子流程运行——Scenario A 子流程通过 JG_SUB_MAIN_PROCESS_ID 共享主流程数据,无需自己独立绑定表单。
规则9:API 响应遍历前必须做类型检查(⚠️ 强制)
现象: 'str' object has no attribute 'get' 或 KeyError: 0,程序崩溃。
根因: 部分 API 的 result 字段结构不固定,可能是 dict(含 records 键)、裸 list、或 str。直接用 [0] 或 .get() 导致类型错误。
强制规则:凡是遍历 API 响应 result 的,必须先 isinstance 检查和 print(type(result)) 确认结构。
# ❌ 错误 —— 假设 result 一定是 list
for item in r['result']: # 实际是 dict,抛出 KeyError
print(item['name'])
# ✅ 正确 —— 先查验结构再遍历
result = r.get('result', [])
if isinstance(result, dict):
records = result.get('records', [])
elif isinstance(result, list):
records = result
else:
records = []
for item in records:
print(item.get('name'))
规则10:复杂流程(含网关/会签/多种审批人)先 dry-run 验证 XML(⚠️ 推荐)
现象: 直接调用 API 创建复杂流程后,发现 taskExtendJson 配置不正确或布局错乱,需要删除重建。
推荐流程:
# 第一步:dry-run 只生成 XML,不调 API
python "/scripts/bpmn_creator.py" \
--api-base --token --config --dry-run
# 第二步:人工或脚本检查 XML 中的关键元素
# - taskExtendJson 的 sameMode/skipOne 值是否正确
# - assignee/candidateUsers/candidateGroups 属性是否存在
# - countersign/multiInstance/timer 等是否正确生成
# - 条件表达式的 field 字段 model 是否匹配 DesForm 实际字段
# 第三步:确认无误后再正式创建(去掉 --dry-run)
python "/scripts/bpmn_creator.py" \
--api-base --token --config --link-form
规则11:编辑已有流程 XML 时,正则必须兼容 bpmn2: 命名空间前缀(⚠️ 强制)
现象: 用 ` pattern = rf'(]\bid="{node_code}"[^>]>)'
✅ 正确 —— 兼容有/无 bpmn2: 前缀两种写法
pattern = rf'(]\bid="{re.escape(node_code)}"[^>]>)'
### 规则12:`candidateUsersExpression` 类型在 XML 中不加任何 `groupType`(⚠️ 强制)
**现象:** 写了 `groupType="candidateUsersExpression"`(无 `flowable:` 前缀)导致发布失败:
cvc-complex-type.3.2.2: 元素 'bpmn2:userTask' 中不允许出现属性 'groupType'
**根因:** BPMN 标准不允许无命名空间的自定义属性。只有带 `flowable:` 前缀的属性(`flowable:groupType`)才合法。而 `candidateUsersExpression` 类型(`bpmn_creator.py` 368行)**本身就不生成任何 groupType 属性**。
**各审批人类型 groupType 规则(来自 `bpmn_creator.py`):**
| 类型 | XML 属性 | groupType |
|------|---------|-----------|
| `expression` / `assignee` / `candidateUsers` | `flowable:assignee` / `flowable:candidateUsers` | **无** |
| `candidateUsersExpression` | `flowable:candidateUsers="${表达式}"` | **无** |
| `role` | `flowable:candidateGroups` | `flowable:groupType="role"` |
| `approvalRole` | `flowable:candidateUsers="${flowUtil...}"` | `flowable:groupType="approvalRole"` |
| `dept` | `flowable:candidateGroups` | `flowable:groupType="dept"` |
| `deptPosition` | `flowable:candidateGroups` | `flowable:groupType="deptPosition"` |
| `position` | `flowable:candidateUsers="${oaFlowExpression...}"` | `flowable:groupType="position"` |
**强制规则:手写 XML 审批人属性前,必须先查 `bpmn_creator.py` 中对应 type 的生成代码(约 363-395 行),禁止凭记忆猜测。**
### 规则13:手动调用 `saveProcess` 必须用 form-urlencoded + 正确路径(⚠️ 强制)
**现象:** 调用 `/act/process/extActProcess/saveProcess` 报"路径不存在"。
**根因:** `saveProcess` 的正确路径是 `/act/designer/api/saveProcess`,且必须用 `application/x-www-form-urlencoded` 编码,不能用 JSON。
**强制规则:手动调用 saveProcess 必须严格按以下模板,禁止猜测路径或 Content-Type。**
```python
import urllib.parse
save_data = {
'processDefinitionId': process_id, # 已有流程ID(新建传 '0')
'processName': process_detail['processName'],
'processkey': process_detail['processKey'], # ⚠ 字段名是 processkey(全小写)
'typeid': process_detail.get('processType', 'oa'), # ⚠ 字段名是 typeid(全小写)
'lowAppId': '',
'params': '',
'nodes': nodes_str, # 见下方 nodes_str 构建方式
'processDescriptor': xml, # 原始 XML 字符串(非 base64)
'realProcDefId': '',
'startType': process_detail.get('startType', 'manual'),
}
# nodes_str 构建:只含 userTask 节点,格式 id=xxx###nodeName=xxx@@@
nodes_str = ''.join(
f'id={n["processNodeCode"]}###nodeName={n["processNodeName"]}@@@'
for n in node_records # 来自 extActProcessNode/list
)
form_body = urllib.parse.urlencode(save_data).encode('utf-8')
req = urllib.request.Request(
f'{API_BASE}/act/designer/api/saveProcess',
data=form_body,
headers={**HEADERS, 'Content-Type': 'application/x-www-form-urlencoded'},
method='POST'
)
r = json.loads(urllib.request.urlopen(req).read().decode('utf-8'))
> 查询流程 XML:`GET /act/process/extActProcess/queryById?id={
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: jeecgboot
- Source: jeecgboot/skills
- License: Apache-2.0
- Homepage: https://jeecg.com
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.