JeecgBoot BPM 流程自动生成器
将自然语言的流程描述转换为 Flowable BPMN 2.0 XML,并通过 API 在 JeecgBoot 系统中自动创建流程。
临时配置文件规则(强制)
所有传给脚本的 --config <xxx.json> 必须写到 {系统临时目录}/{SKILL_NAME}/ 下,由操作系统自动清理;skill 与脚本均不主动删除该目录或文件。
import tempfile, os, json
SKILL_NAME = "<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),无需重复技能前缀,因路径已包含技能名称,便于排错。
** 禁止:**
- 写到
<skill>/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 目录。
介绍组件时的完整性要求
重要: 当用户要求介绍流程设计器各组件时,必须包含以下内容,不可遗漏:
- 会签节点:串行/并行两种模式;全部通过/一人通过/半数通过/按比例/自定义 5种通过规则;指定人员/角色/审批角色/部门/岗位/职级/表单字段/流程变量 8种审批人类型
- 条件表达式:系统内置流程变量(
result、applyUserId、applyDate等);13种条件运算符;多条件组合用法(AND/OR)- 监听器:执行监听器/任务监听器/全局事件监听器三种类型;系统预置监听器(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"]),嵌套结构需递归提取: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):
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,正确写法:def fix_node_form_edit(api_