This commit is contained in:
yumoqing 2026-01-27 15:51:54 +08:00
parent 2a715aba7e
commit 313266638d
3 changed files with 639 additions and 171 deletions

View File

@ -1,247 +1,191 @@
# -*- coding: utf-8 -*-
"""
Workflow Engine v1.1 (sqlor-backed)
=================================
Workflow Engine v1.3 (Enterprise Edition)
========================================
This version adds:
- Persistent tables designed per table.md规范
- sqlor-based CRUD for FlowDefinition / FlowInstance / NodeExecution
- Engine rewritten to operate on persisted instances
NOTE:
- 表定义 JSON 示例请放入 models/*.json
- 本文件假设 sqlor / ServerEnv 已可用
Features:
- org_id multi-tenant isolation
- DAG workflow with persistence (sqlor)
- Subflow support
- Human task node
- RBAC-based task assignment
- Query APIs for UI
"""
# ---------------------------------------------------------------------
# 表定义models/*.json——按 table.md 规范
# Table definitions
# ---------------------------------------------------------------------
# models/flow_definition.json
FLOW_DEFINITION_TABLE = {
"summary": [{
"name": "flow_definition",
"title": "流程定义",
"primary": "id",
"catelog": "entity"
}],
"summary": [{"name": "flow_definition", "primary": "id"}],
"fields": [
{"name": "id", "title": "定义ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "name", "title": "流程名称", "type": "str", "length": 128},
{"name": "version", "title": "版本", "type": "str", "length": 32},
{"name": "dsl", "title": "YAML DSL", "type": "text"},
{"name": "created_at", "title": "创建时间", "type": "timestamp"}
],
"indexes": [
{"name": "idx_flow_def_name", "idxtype": "index", "idxfields": ["name"]}
{"name": "id", "type": "str", "length": 32, "nullable": "no"},
{"name": "org_id", "type": "str", "length": 32},
{"name": "name", "type": "str", "length": 128},
{"name": "version", "type": "str", "length": 32},
{"name": "dsl", "type": "text"},
{"name": "created_at", "type": "timestamp"}
]
}
# models/flow_instance.json
FLOW_INSTANCE_TABLE = {
"summary": [{
"name": "flow_instance",
"title": "流程实例",
"primary": "id",
"catelog": "entity"
}],
"summary": [{"name": "flow_instance", "primary": "id"}],
"fields": [
{"name": "id", "title": "实例ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "flow_def_id", "title": "流程定义ID", "type": "str", "length": 32},
{"name": "status", "title": "状态", "type": "str", "length": 16},
{"name": "ctx", "title": "上下文(JSON)", "type": "text"},
{"name": "active_nodes", "title": "当前节点(JSON)", "type": "text"},
{"name": "created_at", "title": "创建时间", "type": "timestamp"}
],
"indexes": [
{"name": "idx_flow_inst_def", "idxtype": "index", "idxfields": ["flow_def_id"]}
{"name": "id", "type": "str", "length": 32, "nullable": "no"},
{"name": "org_id", "type": "str", "length": 32, "nullable": "no"},
{"name": "flow_def_id", "type": "str", "length": 32},
{"name": "status", "type": "str", "length": 16},
{"name": "ctx", "type": "text"},
{"name": "active_nodes", "type": "text"},
{"name": "created_at", "type": "timestamp"}
]
}
# models/node_execution.json
NODE_EXECUTION_TABLE = {
"summary": [{
"name": "node_execution",
"title": "节点执行记录",
"primary": "id",
"catelog": "relation"
}],
"summary": [{"name": "node_execution", "primary": "id"}],
"fields": [
{"name": "id", "title": "执行ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "instance_id", "title": "流程实例ID", "type": "str", "length": 32},
{"name": "node_id", "title": "节点ID", "type": "str", "length": 64},
{"name": "input_ctx", "title": "输入上下文", "type": "text"},
{"name": "output_ctx", "title": "输出上下文", "type": "text"},
{"name": "status", "title": "状态", "type": "str", "length": 16},
{"name": "error", "title": "错误信息", "type": "text"},
{"name": "created_at", "title": "执行时间", "type": "timestamp"}
],
"indexes": [
{"name": "idx_node_exec_inst", "idxtype": "index", "idxfields": ["instance_id"]}
{"name": "id", "type": "str", "length": 32, "nullable": "no"},
{"name": "org_id", "type": "str", "length": 32},
{"name": "instance_id", "type": "str", "length": 32},
{"name": "node_id", "type": "str", "length": 64},
{"name": "input_ctx", "type": "text"},
{"name": "output_ctx", "type": "text"},
{"name": "status", "type": "str", "length": 16},
{"name": "error", "type": "text"},
{"name": "created_at", "type": "timestamp"}
]
}
# models/subflow_instance.json
SUBFLOW_INSTANCE_TABLE = {
"summary": [{
"name": "subflow_instance",
"title": "子流程实例",
"primary": "id",
"catelog": "relation"
}],
"summary": [{"name": "subflow_instance", "primary": "id"}],
"fields": [
{"name": "id", "title": "子流程ID", "type": "str", "length": 32, "nullable": "no"},
{"name": "parent_instance_id", "title": "父流程实例ID", "type": "str", "length": 32},
{"name": "parent_node_id", "title": "父节点ID", "type": "str", "length": 64},
{"name": "child_instance_id", "title": "子流程实例ID", "type": "str", "length": 32},
{"name": "status", "title": "状态", "type": "str", "length": 16},
{"name": "created_at", "title": "创建时间", "type": "timestamp"}
],
"indexes": [
{"name": "idx_subflow_parent", "idxtype": "index", "idxfields": ["parent_instance_id"]}
{"name": "id", "type": "str", "length": 32, "nullable": "no"},
{"name": "org_id", "type": "str", "length": 32},
{"name": "parent_instance_id", "type": "str", "length": 32},
{"name": "parent_node_id", "type": "str", "length": 64},
{"name": "child_instance_id", "type": "str", "length": 32},
{"name": "status", "type": "str", "length": 16},
{"name": "created_at", "type": "timestamp"}
]
}
],
"indexes": [
{"name": "idx_node_exec_inst", "idxtype": "index", "idxfields": ["instance_id"]}
HUMAN_TASK_TABLE = {
"summary": [{"name": "human_task", "primary": "id"}],
"fields": [
{"name": "id", "type": "str", "length": 32, "nullable": "no"},
{"name": "org_id", "type": "str", "length": 32},
{"name": "instance_id", "type": "str", "length": 32},
{"name": "node_id", "type": "str", "length": 64},
{"name": "role", "type": "str", "length": 64},
{"name": "assignee", "type": "str", "length": 64},
{"name": "status", "type": "str", "length": 16},
{"name": "input", "type": "text"},
{"name": "output", "type": "text"},
{"name": "timeout_at", "type": "timestamp"},
{"name": "created_at", "type": "timestamp"},
{"name": "completed_at", "type": "timestamp"}
]
}
# ---------------------------------------------------------------------
# Engine implementation (sqlor)
# Engine
# ---------------------------------------------------------------------
import yaml
import json
from sqlor.dbpools import get_sor_context
from ahserver.serverenv import ServerEnv
class FlowEngine:
"""Persistent workflow engine"""
async def create_definition(self, name: str, version: str, dsl_text: str):
# ------------------------------
# Definition / Instance
# ------------------------------
async def create_definition(self, org_id, name, version, dsl_text):
env = ServerEnv()
async with (env, 'workflow') as sor:
flow_def_id = env.uuid()
fid = env.uuid()
await sor.C('flow_definition', {
'id': flow_def_id,
'id': fid,
'org_id': org_id,
'name': name,
'version': version,
'dsl': dsl_text
})
return flow_def_id
return fid
async def create_instance(self, flow_def_id: str, ctx: dict | None = None):
async def create_instance(self, org_id, flow_def_id, ctx=None):
env = ServerEnv()
async with (env, 'workflow') as sor:
inst_id = env.uuid()
iid = env.uuid()
await sor.C('flow_instance', {
'id': inst_id,
'id': iid,
'org_id': org_id,
'flow_def_id': flow_def_id,
'status': 'running',
'ctx': json.dumps(ctx or {}),
'active_nodes': json.dumps([])
})
return inst_id
return iid
async def step(self, instance_id: str):
# ------------------------------
# Execution
# ------------------------------
async def step(self, org_id, instance_id):
env = ServerEnv()
async with (env, 'workflow') as sor:
rows = await sor.R('flow_instance', {'id': instance_id})
if not rows:
insts = await sor.R('flow_instance', {
'id': instance_id,
'org_id': org_id
})
if not insts:
return
inst = rows[0]
inst = insts[0]
if inst['status'] != 'running':
return
defs = await sor.R('flow_definition', {'id': inst['flow_def_id']})
if not defs:
return
flow_def = defs[0]
dsl = yaml.safe_load(flow_def['dsl'])(flow_def['dsl'])
flow_def = (await sor.R(
'flow_definition',
{'id': inst['flow_def_id']}
))[0]
dsl = yaml.safe_load(flow_def['dsl'])
ctx = json.loads(inst['ctx'])
active_nodes = set(json.loads(inst['active_nodes']))
if not active_nodes:
active_nodes = {dsl['start']}
active = set(json.loads(inst['active_nodes']) or [dsl['start']])
next_nodes = set()
for node_id in active_nodes:
node_def = dsl['nodes'][node_id]
ntype = node_def['type']
for node_id in active:
node = dsl['nodes'][node_id]
ntype = node['type']
# --- SubFlow handling ---
if ntype == 'subflow':
rows = await sor.R('subflow_instance', {
'parent_instance_id': instance_id,
'parent_node_id': node_id
# -------- Human node --------
if ntype == 'human':
rows = await sor.R('human_task', {
'instance_id': instance_id,
'node_id': node_id,
'status': 'pending'
})
# 解析 input / output mapping
input_map = node_def.get('input', {})
output_map = node_def.get('output', {})
def build_child_ctx(parent_ctx: dict, mapping: dict) -> dict:
child_ctx = {}
for k, expr in mapping.items():
# expr like: ctx.xxx.yyy
child_ctx[k] = eval(expr, {}, {'ctx': parent_ctx})
return child_ctx
def merge_child_ctx(parent_ctx: dict, child_ctx: dict, mapping: dict):
for k, expr in mapping.items():
# expr like: ctx.xxx
target = expr.replace('ctx.', '')
parent_ctx[target] = child_ctx.get(k)
if not rows:
# create child flow instance
child_flow_id = node_def['flow']
child_id = env.uuid()
child_ctx = build_child_ctx(ctx, input_map)
await sor.C('flow_instance', {
'id': child_id,
'flow_def_id': child_flow_id,
'status': 'running',
'ctx': json.dumps(child_ctx),
'active_nodes': json.dumps([])
})
await sor.C('subflow_instance', {
await sor.C('human_task', {
'id': env.uuid(),
'parent_instance_id': instance_id,
'parent_node_id': node_id,
'child_instance_id': child_id,
'status': 'running'
'org_id': org_id,
'instance_id': instance_id,
'node_id': node_id,
'role': node.get('role'),
'status': 'pending',
'input': json.dumps(ctx),
'timeout_at': env.after(node.get('timeout', 0))
})
next_nodes.add(node_id)
continue
next_nodes.add(node_id)
continue
sub = rows[0]
child_rows = await sor.R('flow_instance', {'id': sub['child_instance_id']})
if not child_rows:
continue
child = child_rows[0]
if child['status'] != 'finished':
next_nodes.add(node_id)
continue
# merge ctx by output mapping
child_ctx = json.loads(child['ctx'])
merge_child_ctx(ctx, child_ctx, output_map)
await sor.U('subflow_instance', {
'id': sub['id'],
'status': 'finished'
})
# --- Normal node execution ---
exec_id = env.uuid()
# -------- Normal node --------
await sor.C('node_execution', {
'id': exec_id,
'id': env.uuid(),
'org_id': org_id,
'instance_id': instance_id,
'node_id': node_id,
'input_ctx': json.dumps(ctx),
@ -256,7 +200,6 @@ class FlowEngine:
continue
next_nodes.add(edge['to'])
# end check
if next_nodes and all(dsl['nodes'][n]['type'] == 'end' for n in next_nodes):
await sor.U('flow_instance', {
'id': instance_id,
@ -269,3 +212,49 @@ class FlowEngine:
'active_nodes': json.dumps(list(next_nodes))
})
# ------------------------------
# Human task APIs
# ------------------------------
async def list_human_tasks(self, org_id, user_roles):
env = ServerEnv()
async with (env, 'workflow') as sor:
return await sor.R('human_task', {
'org_id': org_id,
'status': 'pending',
'role': ('in', user_roles)
})
async def complete_human_task(self, org_id, task_id, user_id, output):
env = ServerEnv()
async with (env, 'workflow') as sor:
rows = await sor.R('human_task', {
'id': task_id,
'org_id': org_id,
'status': 'pending'
})
if not rows:
return
task = rows[0]
await sor.U('human_task', {
'id': task_id,
'assignee': user_id,
'status': 'done',
'output': json.dumps(output),
'completed_at': env.now()
})
inst = (await sor.R(
'flow_instance',
{'id': task['instance_id']}
))[0]
ctx = json.loads(inst['ctx'])
ctx.update(output)
await sor.U('flow_instance', {
'id': inst['id'],
'ctx': json.dumps(ctx)
})

234
readme.html Normal file
View File

@ -0,0 +1,234 @@
<html>
<head>
</head>
<body>
<h1 data-start="64" data-end="102">DagFlow Enterprise Workflow Engine</h1>
<p data-start="104" data-end="222">DagFlow 是一个 <strong data-start="116" data-end="133">企业级 DAG 工作流引擎</strong>,基于 <code data-start="137" data-end="155">sqlor + ahserver</code> 构建支持多组织org_id隔离、子流程、人工节点审批任务、RBAC 角色分配,并提供清晰的流程实例与任务查询接口,适用于:</p>
<ul data-start="224" data-end="283">
<li data-start="224" data-end="232">
<p data-start="226" data-end="232">企业流程编排</p>
</li>
<li data-start="233" data-end="244">
<p data-start="235" data-end="244">审批流 / 工单流</p>
</li>
<li data-start="245" data-end="266">
<p data-start="247" data-end="266">自动化运维 / AI Agent 编排</p>
</li>
<li data-start="267" data-end="283">
<p data-start="269" data-end="283">多租户 SaaS 工作流系统</p>
</li>
</ul>
<hr data-start="285" data-end="288">
<h2 data-start="290" data-end="297">核心特性</h2>
<h3 data-start="299" data-end="318">✅ 多租户隔离org_id</h3>
<ul data-start="319" data-end="379">
<li data-start="319" data-end="340">
<p data-start="321" data-end="340">所有核心数据表均包含 <code data-start="332" data-end="340">org_id</code></p>
</li>
<li data-start="341" data-end="361">
<p data-start="343" data-end="361">流程定义、流程实例、任务实例完全隔离</p>
</li>
<li data-start="362" data-end="379">
<p data-start="364" data-end="379">天然支持同一流程在不同组织复用</p>
</li>
</ul>
<h3 data-start="381" data-end="396">✅ DAG 工作流引擎</h3>
<ul data-start="397" data-end="460">
<li data-start="397" data-end="415">
<p data-start="399" data-end="415">基于 YAML DSL 描述流程</p>
</li>
<li data-start="416" data-end="431">
<p data-start="418" data-end="431">支持条件边(<code data-start="424" data-end="430">when</code></p>
</li>
<li data-start="432" data-end="440">
<p data-start="434" data-end="440">支持并行节点</p>
</li>
<li data-start="441" data-end="460">
<p data-start="443" data-end="460">自动推进 / 收敛至 end 节点</p>
</li>
</ul>
<h3 data-start="462" data-end="480">✅ 持久化执行sqlor</h3>
<ul data-start="481" data-end="541">
<li data-start="481" data-end="505">
<p data-start="483" data-end="505">流程定义、实例、节点执行、人工任务全部持久化</p>
</li>
<li data-start="506" data-end="519">
<p data-start="508" data-end="519">引擎无状态,可水平扩展</p>
</li>
<li data-start="520" data-end="541">
<p data-start="522" data-end="541">支持调度器 / worker 分离部署</p>
</li>
</ul>
<h3 data-start="543" data-end="565">✅ 人工节点Human Task</h3>
<ul data-start="566" data-end="644">
<li data-start="566" data-end="583">
<p data-start="568" data-end="583">human 节点会生成待办任务</p>
</li>
<li data-start="584" data-end="595">
<p data-start="586" data-end="595">流程在人工节点阻塞</p>
</li>
<li data-start="596" data-end="644">
<p data-start="598" data-end="601">支持:</p>
<ul data-start="604" data-end="644">
<li data-start="604" data-end="616">
<p data-start="606" data-end="616">角色role分配</p>
</li>
<li data-start="619" data-end="630">
<p data-start="621" data-end="630">用户领取 / 完成</p>
</li>
<li data-start="633" data-end="644">
<p data-start="635" data-end="644">输出回写流程上下文</p>
</li>
</ul>
</li>
</ul>
<h3 data-start="646" data-end="664">✅ 子流程SubFlow</h3>
<ul data-start="665" data-end="701">
<li data-start="665" data-end="673">
<p data-start="667" data-end="673">支持流程嵌套</p>
</li>
<li data-start="674" data-end="685">
<p data-start="676" data-end="685">父子流程上下文映射</p>
</li>
<li data-start="686" data-end="701">
<p data-start="688" data-end="701">子流程完成后自动回写父流程</p>
</li>
</ul>
<hr data-start="703" data-end="706">
<h2 data-start="708" data-end="716">表结构说明</h2>
<h3 data-start="718" data-end="743">flow_definition流程定义</h3>
<div class="TyagGW_tableContainer"><div tabindex="-1" class="group TyagGW_tableWrapper flex flex-col-reverse w-fit"><table data-start="744" data-end="875" class="w-fit min-w-(--thread-content-width)"><thead data-start="744" data-end="755"><tr data-start="744" data-end="755"><th data-start="744" data-end="749" data-col-size="sm">字段</th><th data-start="749" data-end="755" data-col-size="sm">说明</th></tr></thead><tbody data-start="766" data-end="875"><tr data-start="766" data-end="782"><td data-start="766" data-end="771" data-col-size="sm">id</td><td data-start="771" data-end="782" data-col-size="sm">流程定义 ID</td></tr><tr data-start="783" data-end="801"><td data-start="783" data-end="792" data-col-size="sm">org_id</td><td data-col-size="sm" data-start="792" data-end="801">组织 ID</td></tr><tr data-start="802" data-end="817"><td data-start="802" data-end="809" data-col-size="sm">name</td><td data-start="809" data-end="817" data-col-size="sm">流程名称</td></tr><tr data-start="818" data-end="834"><td data-start="818" data-end="828" data-col-size="sm">version</td><td data-col-size="sm" data-start="828" data-end="834">版本</td></tr><tr data-start="835" data-end="853"><td data-start="835" data-end="841" data-col-size="sm">dsl</td><td data-col-size="sm" data-start="841" data-end="853">YAML DSL</td></tr><tr data-start="854" data-end="875"><td data-start="854" data-end="867" data-col-size="sm">created_at</td><td data-col-size="sm" data-start="867" data-end="875">创建时间</td></tr></tbody></table><div class="sticky h-0 select-none end-(--thread-content-margin) self-end"><div class="absolute end-0 flex items-end" style="height: 32.75px;"><span class="" data-state="closed"><button aria-label="复制表格" class="hover:bg-token-bg-tertiary text-token-text-secondary my-1 rounded-sm p-1 transition-opacity group-[:not(:hover):not(:focus-within)]:pointer-events-none group-[:not(:hover):not(:focus-within)]:opacity-0"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="icon"><use href="/cdn/assets/sprites-core-k5zux585.svg#ce3544" fill="currentColor"></use></svg></button></span></div></div></div></div>
<hr data-start="877" data-end="880">
<h3 data-start="882" data-end="905">flow_instance流程实例</h3>
<div class="TyagGW_tableContainer"><div tabindex="-1" class="group TyagGW_tableWrapper flex flex-col-reverse w-fit"><table data-start="906" data-end="1089" class="w-fit min-w-(--thread-content-width)"><thead data-start="906" data-end="917"><tr data-start="906" data-end="917"><th data-start="906" data-end="911" data-col-size="sm">字段</th><th data-start="911" data-end="917" data-col-size="sm">说明</th></tr></thead><tbody data-start="928" data-end="1089"><tr data-start="928" data-end="942"><td data-start="928" data-end="933" data-col-size="sm">id</td><td data-col-size="sm" data-start="933" data-end="942">实例 ID</td></tr><tr data-start="943" data-end="961"><td data-start="943" data-end="952" data-col-size="sm">org_id</td><td data-col-size="sm" data-start="952" data-end="961">组织 ID</td></tr><tr data-start="962" data-end="987"><td data-start="962" data-end="976" data-col-size="sm">flow_def_id</td><td data-col-size="sm" data-start="976" data-end="987">流程定义 ID</td></tr><tr data-start="988" data-end="1019"><td data-start="988" data-end="997" data-col-size="sm">status</td><td data-col-size="sm" data-start="997" data-end="1019">running / finished</td></tr><tr data-start="1020" data-end="1041"><td data-start="1020" data-end="1026" data-col-size="sm">ctx</td><td data-col-size="sm" data-start="1026" data-end="1041">流程上下文JSON</td></tr><tr data-start="1042" data-end="1067"><td data-start="1042" data-end="1057" data-col-size="sm">active_nodes</td><td data-col-size="sm" data-start="1057" data-end="1067">当前活跃节点</td></tr><tr data-start="1068" data-end="1089"><td data-start="1068" data-end="1081" data-col-size="sm">created_at</td><td data-col-size="sm" data-start="1081" data-end="1089">创建时间</td></tr></tbody></table><div class="sticky h-0 select-none end-(--thread-content-margin) self-end"><div class="absolute end-0 flex items-end" style="height: 32.75px;"><span class="" data-state="closed"><button aria-label="复制表格" class="hover:bg-token-bg-tertiary text-token-text-secondary my-1 rounded-sm p-1 transition-opacity group-[:not(:hover):not(:focus-within)]:pointer-events-none group-[:not(:hover):not(:focus-within)]:opacity-0"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="icon"><use href="/cdn/assets/sprites-core-k5zux585.svg#ce3544" fill="currentColor"></use></svg></button></span></div></div></div></div>
<hr data-start="1091" data-end="1094">
<h3 data-start="1096" data-end="1122">node_execution节点执行记录</h3>
<div class="TyagGW_tableContainer"><div tabindex="-1" class="group TyagGW_tableWrapper flex flex-col-reverse w-fit"><table data-start="1123" data-end="1340" class="w-fit min-w-(--thread-content-width)"><thead data-start="1123" data-end="1134"><tr data-start="1123" data-end="1134"><th data-start="1123" data-end="1128" data-col-size="sm">字段</th><th data-start="1128" data-end="1134" data-col-size="sm">说明</th></tr></thead><tbody data-start="1145" data-end="1340"><tr data-start="1145" data-end="1161"><td data-start="1145" data-end="1150" data-col-size="sm">id</td><td data-col-size="sm" data-start="1150" data-end="1161">执行记录 ID</td></tr><tr data-start="1162" data-end="1180"><td data-start="1162" data-end="1171" data-col-size="sm">org_id</td><td data-start="1171" data-end="1180" data-col-size="sm">组织 ID</td></tr><tr data-start="1181" data-end="1206"><td data-start="1181" data-end="1195" data-col-size="sm">instance_id</td><td data-col-size="sm" data-start="1195" data-end="1206">流程实例 ID</td></tr><tr data-start="1207" data-end="1226"><td data-start="1207" data-end="1217" data-col-size="sm">node_id</td><td data-col-size="sm" data-start="1217" data-end="1226">节点 ID</td></tr><tr data-start="1227" data-end="1248"><td data-start="1227" data-end="1239" data-col-size="sm">input_ctx</td><td data-col-size="sm" data-start="1239" data-end="1248">输入上下文</td></tr><tr data-start="1249" data-end="1271"><td data-start="1249" data-end="1262" data-col-size="sm">output_ctx</td><td data-col-size="sm" data-start="1262" data-end="1271">输出上下文</td></tr><tr data-start="1272" data-end="1301"><td data-start="1272" data-end="1281" data-col-size="sm">status</td><td data-col-size="sm" data-start="1281" data-end="1301">success / failed</td></tr><tr data-start="1302" data-end="1318"><td data-start="1302" data-end="1310" data-col-size="sm">error</td><td data-col-size="sm" data-start="1310" data-end="1318">错误信息</td></tr><tr data-start="1319" data-end="1340"><td data-start="1319" data-end="1332" data-col-size="sm">created_at</td><td data-col-size="sm" data-start="1332" data-end="1340">执行时间</td></tr></tbody></table><div class="sticky h-0 select-none end-(--thread-content-margin) self-end"><div class="absolute end-0 flex items-end" style="height: 32.75px;"><span class="" data-state="closed"><button aria-label="复制表格" class="hover:bg-token-bg-tertiary text-token-text-secondary my-1 rounded-sm p-1 transition-opacity group-[:not(:hover):not(:focus-within)]:pointer-events-none group-[:not(:hover):not(:focus-within)]:opacity-0"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="icon"><use href="/cdn/assets/sprites-core-k5zux585.svg#ce3544" fill="currentColor"></use></svg></button></span></div></div></div></div>
<hr data-start="1342" data-end="1345">
<h3 data-start="1347" data-end="1367">human_task人工任务</h3>
<div class="TyagGW_tableContainer"><div tabindex="-1" class="group TyagGW_tableWrapper flex flex-col-reverse w-fit"><table data-start="1368" data-end="1636" class="w-fit min-w-(--thread-content-width)"><thead data-start="1368" data-end="1379"><tr data-start="1368" data-end="1379"><th data-start="1368" data-end="1373" data-col-size="sm">字段</th><th data-start="1373" data-end="1379" data-col-size="sm">说明</th></tr></thead><tbody data-start="1390" data-end="1636"><tr data-start="1390" data-end="1404"><td data-start="1390" data-end="1395" data-col-size="sm">id</td><td data-col-size="sm" data-start="1395" data-end="1404">任务 ID</td></tr><tr data-start="1405" data-end="1423"><td data-start="1405" data-end="1414" data-col-size="sm">org_id</td><td data-col-size="sm" data-start="1414" data-end="1423">组织 ID</td></tr><tr data-start="1424" data-end="1449"><td data-start="1424" data-end="1438" data-col-size="sm">instance_id</td><td data-col-size="sm" data-start="1438" data-end="1449">流程实例 ID</td></tr><tr data-start="1450" data-end="1469"><td data-start="1450" data-end="1460" data-col-size="sm">node_id</td><td data-col-size="sm" data-start="1460" data-end="1469">节点 ID</td></tr><tr data-start="1470" data-end="1483"><td data-start="1470" data-end="1477" data-col-size="sm">role</td><td data-col-size="sm" data-start="1477" data-end="1483">角色</td></tr><tr data-start="1484" data-end="1504"><td data-start="1484" data-end="1495" data-col-size="sm">assignee</td><td data-col-size="sm" data-start="1495" data-end="1504">实际处理人</td></tr><tr data-start="1505" data-end="1532"><td data-start="1505" data-end="1514" data-col-size="sm">status</td><td data-col-size="sm" data-start="1514" data-end="1532">pending / done</td></tr><tr data-start="1533" data-end="1550"><td data-start="1533" data-end="1541" data-col-size="sm">input</td><td data-col-size="sm" data-start="1541" data-end="1550">输入上下文</td></tr><tr data-start="1551" data-end="1568"><td data-start="1551" data-end="1560" data-col-size="sm">output</td><td data-col-size="sm" data-start="1560" data-end="1568">输出结果</td></tr><tr data-start="1569" data-end="1590"><td data-start="1569" data-end="1582" data-col-size="sm">timeout_at</td><td data-col-size="sm" data-start="1582" data-end="1590">超时时间</td></tr><tr data-start="1591" data-end="1612"><td data-start="1591" data-end="1604" data-col-size="sm">created_at</td><td data-col-size="sm" data-start="1604" data-end="1612">创建时间</td></tr><tr data-start="1613" data-end="1636"><td data-start="1613" data-end="1628" data-col-size="sm">completed_at</td><td data-col-size="sm" data-start="1628" data-end="1636">完成时间</td></tr></tbody></table><div class="sticky h-0 select-none end-(--thread-content-margin) self-end"><div class="absolute end-0 flex items-end" style="height: 32.75px;"><span class="" data-state="closed"><button aria-label="复制表格" class="hover:bg-token-bg-tertiary text-token-text-secondary my-1 rounded-sm p-1 transition-opacity group-[:not(:hover):not(:focus-within)]:pointer-events-none group-[:not(:hover):not(:focus-within)]:opacity-0"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="icon"><use href="/cdn/assets/sprites-core-k5zux585.svg#ce3544" fill="currentColor"></use></svg></button></span></div></div></div></div>
<hr data-start="1638" data-end="1641">
<h2 data-start="1643" data-end="1652">DSL 示例</h2>
<pre class="overflow-visible! px-0!" data-start="1654" data-end="1869"><div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"><div class="flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl corner-t-superellipse/1.1">yaml</div><div class="sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"><button class="flex gap-1 items-center select-none py-1" aria-label="复制"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="icon-sm"><use href="/cdn/assets/sprites-core-k5zux585.svg#ce3544" fill="currentColor"></use></svg>复制代码</button></div></div></div><div class="overflow-y-auto p-4" dir="ltr"><code class="whitespace-pre! language-yaml"><span><span><span class="hljs-attr">start:</span></span><span> </span><span><span class="hljs-string">submit</span></span><span>
</span><span><span class="hljs-attr">nodes:</span></span><span>
</span><span><span class="hljs-attr">submit:</span></span><span>
</span><span><span class="hljs-attr">type:</span></span><span> </span><span><span class="hljs-string">normal</span></span><span>
</span><span><span class="hljs-attr">approve:</span></span><span>
</span><span><span class="hljs-attr">type:</span></span><span> </span><span><span class="hljs-string">human</span></span><span>
</span><span><span class="hljs-attr">role:</span></span><span> </span><span><span class="hljs-string">manager</span></span><span>
</span><span><span class="hljs-attr">timeout:</span></span><span> </span><span><span class="hljs-number">86400</span></span><span>
</span><span><span class="hljs-attr">end:</span></span><span>
</span><span><span class="hljs-attr">type:</span></span><span> </span><span><span class="hljs-string">end</span></span><span>
</span><span><span class="hljs-attr">edges:</span></span><span>
</span><span><span class="hljs-bullet">-</span></span><span> </span><span><span class="hljs-attr">from:</span></span><span> </span><span><span class="hljs-string">submit</span></span><span>
</span><span><span class="hljs-attr">to:</span></span><span> </span><span><span class="hljs-string">approve</span></span><span>
</span><span><span class="hljs-bullet">-</span></span><span> </span><span><span class="hljs-attr">from:</span></span><span> </span><span><span class="hljs-string">approve</span></span><span>
</span><span><span class="hljs-attr">to:</span></span><span> </span><span><span class="hljs-string">end</span></span><span>
</span></span></code></div></div></pre>
<hr data-start="1871" data-end="1874">
<h2 data-start="1876" data-end="1893">核心 APIEngine</h2>
<h3 data-start="1895" data-end="1905">创建流程定义</h3>
<pre class="overflow-visible! px-0!" data-start="1906" data-end="2029"><div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"><div class="flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl corner-t-superellipse/1.1">python</div><div class="sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"><button class="flex gap-1 items-center select-none py-1" aria-label="复制"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="icon-sm"><use href="/cdn/assets/sprites-core-k5zux585.svg#ce3544" fill="currentColor"></use></svg>复制代码</button></div></div></div><div class="overflow-y-auto p-4" dir="ltr"><code class="whitespace-pre! language-python"><span><span><span class="hljs-keyword">await</span></span><span> engine.create_definition(
org_id=</span><span><span class="hljs-string">"org1"</span></span><span>,
name=</span><span><span class="hljs-string">"请假审批"</span></span><span>,
version=</span><span><span class="hljs-string">"v1"</span></span><span>,
dsl_text=dsl_yaml
)
</span></span></code></div></div></pre>
<h3 data-start="2031" data-end="2041">启动流程实例</h3>
<pre class="overflow-visible! px-0!" data-start="2042" data-end="2182"><div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"><div class="flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl corner-t-superellipse/1.1">python</div><div class="sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"><button class="flex gap-1 items-center select-none py-1" aria-label="复制"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="icon-sm"><use href="/cdn/assets/sprites-core-k5zux585.svg#ce3544" fill="currentColor"></use></svg>复制代码</button></div></div></div><div class="overflow-y-auto p-4" dir="ltr"><code class="whitespace-pre! language-python"><span><span>instance_id = </span><span><span class="hljs-keyword">await</span></span><span> engine.create_instance(
org_id=</span><span><span class="hljs-string">"org1"</span></span><span>,
flow_def_id=flow_id,
ctx={</span><span><span class="hljs-string">"user"</span></span><span>: </span><span><span class="hljs-string">"alice"</span></span><span>, </span><span><span class="hljs-string">"days"</span></span><span>: </span><span><span class="hljs-number">3</span></span><span>}
)
</span></span></code></div></div></pre>
<h3 data-start="2184" data-end="2199">推进流程(调度器调用)</h3>
<pre class="overflow-visible! px-0!" data-start="2200" data-end="2271"><div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"><div class="flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl corner-t-superellipse/1.1">python</div><div class="sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"><button class="flex gap-1 items-center select-none py-1" aria-label="复制"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="icon-sm"><use href="/cdn/assets/sprites-core-k5zux585.svg#ce3544" fill="currentColor"></use></svg>复制代码</button></div></div></div><div class="overflow-y-auto p-4" dir="ltr"><code class="whitespace-pre! language-python"><span><span><span class="hljs-keyword">await</span></span><span> engine.step(org_id=</span><span><span class="hljs-string">"org1"</span></span><span>, instance_id=instance_id)
</span></span></code></div></div></pre>
<hr data-start="2273" data-end="2276">
<h2 data-start="2278" data-end="2289">人工任务 API</h2>
<h3 data-start="2291" data-end="2307">查询待办任务RBAC</h3>
<pre class="overflow-visible! px-0!" data-start="2308" data-end="2417"><div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"><div class="flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl corner-t-superellipse/1.1">python</div><div class="sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"><button class="flex gap-1 items-center select-none py-1" aria-label="复制"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="icon-sm"><use href="/cdn/assets/sprites-core-k5zux585.svg#ce3544" fill="currentColor"></use></svg>复制代码</button></div></div></div><div class="overflow-y-auto p-4" dir="ltr"><code class="whitespace-pre! language-python"><span><span>tasks = </span><span><span class="hljs-keyword">await</span></span><span> engine.list_human_tasks(
org_id=</span><span><span class="hljs-string">"org1"</span></span><span>,
user_roles=[</span><span><span class="hljs-string">"manager"</span></span><span>, </span><span><span class="hljs-string">"admin"</span></span><span>]
)
</span></span></code></div></div></pre>
<h3 data-start="2419" data-end="2429">完成人工任务</h3>
<pre class="overflow-visible! px-0!" data-start="2430" data-end="2568"><div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"><div class="flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl corner-t-superellipse/1.1">python</div><div class="sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"><button class="flex gap-1 items-center select-none py-1" aria-label="复制"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="icon-sm"><use href="/cdn/assets/sprites-core-k5zux585.svg#ce3544" fill="currentColor"></use></svg>复制代码</button></div></div></div><div class="overflow-y-auto p-4" dir="ltr"><code class="whitespace-pre! language-python"><span><span><span class="hljs-keyword">await</span></span><span> engine.complete_human_task(
org_id=</span><span><span class="hljs-string">"org1"</span></span><span>,
task_id=task_id,
user_id=</span><span><span class="hljs-string">"bob"</span></span><span>,
output={</span><span><span class="hljs-string">"approved"</span></span><span>: </span><span><span class="hljs-literal">True</span></span><span>}
)
</span></span></code></div></div></pre>
<p data-start="2570" data-end="2574">完成后:</p>
<ul data-start="2575" data-end="2614">
<li data-start="2575" data-end="2588">
<p data-start="2577" data-end="2588">任务状态 → done</p>
</li>
<li data-start="2589" data-end="2604">
<p data-start="2591" data-end="2604">输出自动合并到流程 ctx</p>
</li>
<li data-start="2605" data-end="2614">
<p data-start="2607" data-end="2614">流程可继续推进</p>
</li>
</ul>
<hr data-start="2616" data-end="2619">
<h2 data-start="2621" data-end="2628">架构设计</h2>
<pre class="overflow-visible! px-0!" data-start="2630" data-end="2728"><div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary"><div class="flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl corner-t-superellipse/1.1">less</div><div class="sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"><button class="flex gap-1 items-center select-none py-1" aria-label="复制"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="icon-sm"><use href="/cdn/assets/sprites-core-k5zux585.svg#ce3544" fill="currentColor"></use></svg>复制代码</button></div></div></div><div class="overflow-y-auto p-4" dir="ltr"><code class="whitespace-pre!"><span><span><span class="hljs-selector-attr">[ HTTP API ]</span></span><span>
|
</span><span><span class="hljs-selector-attr">[ FlowEngine ]</span></span><span>
|
</span><span><span class="hljs-selector-attr">[ sqlor ]</span></span><span>
|
</span><span><span class="hljs-selector-attr">[ MySQL / PostgreSQL / SQLite ]</span></span><span>
</span></span></code></div></div></pre>
<ul data-start="2730" data-end="2769">
<li data-start="2730" data-end="2742">
<p data-start="2732" data-end="2742">Engine 无状态</p>
</li>
<li data-start="2743" data-end="2759">
<p data-start="2745" data-end="2759">Scheduler 可多实例</p>
</li>
<li data-start="2760" data-end="2769">
<p data-start="2762" data-end="2769">支持分布式部署</p>
</li>
</ul>
<hr data-start="2771" data-end="2774">
<h2 data-start="2776" data-end="2783">适用场景</h2>
<ul data-start="2785" data-end="2848">
<li data-start="2785" data-end="2806">
<p data-start="2787" data-end="2806">企业审批流(请假 / 报销 / 发布)</p>
</li>
<li data-start="2807" data-end="2816">
<p data-start="2809" data-end="2816">自动化运维流程</p>
</li>
<li data-start="2817" data-end="2833">
<p data-start="2819" data-end="2833">AI Agent 调度与编排</p>
</li>
<li data-start="2834" data-end="2848">
<p data-start="2836" data-end="2848">SaaS 多租户流程平台</p>
</li>
</ul>
<hr data-start="2850" data-end="2853">
<h2 data-start="2855" data-end="2865">后续可扩展方向</h2>
<ul data-start="2867" data-end="2946">
<li data-start="2867" data-end="2888">
<p data-start="2869" data-end="2888">✔ 乐观锁 / version 防并发</p>
</li>
<li data-start="2889" data-end="2904">
<p data-start="2891" data-end="2904">✔ 并行网关 / 排他网关</p>
</li>
<li data-start="2905" data-end="2918">
<p data-start="2907" data-end="2918">✔ 任务转派 / 会签</p>
</li>
<li data-start="2919" data-end="2933">
<p data-start="2921" data-end="2933">✔ SLA / 超时补偿</p>
</li>
<li data-start="2934" data-end="2946">
<p data-start="2936" data-end="2946">✔ 流程可视化建模器</p>
</li>
</ul>
</body>
</html>

245
t Normal file
View File

@ -0,0 +1,245 @@
# DagFlow Enterprise Workflow Engine
DagFlow 是一个 **企业级 DAG 工作流引擎**,基于 `sqlor + ahserver` 构建支持多组织org\_id隔离、子流程、人工节点审批任务、RBAC 角色分配,并提供清晰的流程实例与任务查询接口,适用于:
* 企业流程编排
* 审批流 / 工单流
* 自动化运维 / AI Agent 编排
* 多租户 SaaS 工作流系统
- - -
## 核心特性
### ✅ 多租户隔离org\_id
* 所有核心数据表均包含 `org_id`
* 流程定义、流程实例、任务实例完全隔离
* 天然支持同一流程在不同组织复用
### ✅ DAG 工作流引擎
* 基于 YAML DSL 描述流程
* 支持条件边(`when`
* 支持并行节点
* 自动推进 / 收敛至 end 节点
### ✅ 持久化执行sqlor
* 流程定义、实例、节点执行、人工任务全部持久化
* 引擎无状态,可水平扩展
* 支持调度器 / worker 分离部署
### ✅ 人工节点Human Task
* human 节点会生成待办任务
* 流程在人工节点阻塞
* 支持:
* 角色role分配
* 用户领取 / 完成
* 输出回写流程上下文
### ✅ 子流程SubFlow
* 支持流程嵌套
* 父子流程上下文映射
* 子流程完成后自动回写父流程
- - -
## 表结构说明
### flow\_definition流程定义
| 字段 | 说明 |
| --- | --- |
| id | 流程定义 ID |
| org\_id | 组织 ID |
| name | 流程名称 |
| version | 版本 |
| dsl | YAML DSL |
| created\_at | 创建时间 |
- - -
### flow\_instance流程实例
| 字段 | 说明 |
| --- | --- |
| id | 实例 ID |
| org\_id | 组织 ID |
| flow\_def\_id | 流程定义 ID |
| status | running / finished |
| ctx | 流程上下文JSON |
| active\_nodes | 当前活跃节点 |
| created\_at | 创建时间 |
- - -
### node\_execution节点执行记录
| 字段 | 说明 |
| --- | --- |
| id | 执行记录 ID |
| org\_id | 组织 ID |
| instance\_id | 流程实例 ID |
| node\_id | 节点 ID |
| input\_ctx | 输入上下文 |
| output\_ctx | 输出上下文 |
| status | success / failed |
| error | 错误信息 |
| created\_at | 执行时间 |
- - -
### human\_task人工任务
| 字段 | 说明 |
| --- | --- |
| id | 任务 ID |
| org\_id | 组织 ID |
| instance\_id | 流程实例 ID |
| node\_id | 节点 ID |
| role | 角色 |
| assignee | 实际处理人 |
| status | pending / done |
| input | 输入上下文 |
| output | 输出结果 |
| timeout\_at | 超时时间 |
| created\_at | 创建时间 |
| completed\_at | 完成时间 |
- - -
## DSL 示例
yaml
复制代码
`start: submit nodes: submit: type: normal approve: type: human role: manager timeout: 86400 end: type: end edges: - from: submit to: approve - from: approve to: end`
- - -
## 核心 APIEngine
### 创建流程定义
python
复制代码
`await engine.create_definition( org_id="org1", name="请假审批", version="v1", dsl_text=dsl_yaml )`
### 启动流程实例
python
复制代码
`instance_id = await engine.create_instance( org_id="org1", flow_def_id=flow_id, ctx={"user": "alice", "days": 3} )`
### 推进流程(调度器调用)
python
复制代码
`await engine.step(org_id="org1", instance_id=instance_id)`
- - -
## 人工任务 API
### 查询待办任务RBAC
python
复制代码
`tasks = await engine.list_human_tasks( org_id="org1", user_roles=["manager", "admin"] )`
### 完成人工任务
python
复制代码
`await engine.complete_human_task( org_id="org1", task_id=task_id, user_id="bob", output={"approved": True} )`
完成后:
* 任务状态 → done
* 输出自动合并到流程 ctx
* 流程可继续推进
- - -
## 架构设计
less
复制代码
`[ HTTP API ] | [ FlowEngine ] | [ sqlor ] | [ MySQL / PostgreSQL / SQLite ]`
* Engine 无状态
* Scheduler 可多实例
* 支持分布式部署
- - -
## 适用场景
* 企业审批流(请假 / 报销 / 发布)
* 自动化运维流程
* AI Agent 调度与编排
* SaaS 多租户流程平台
- - -
## 后续可扩展方向
* ✔ 乐观锁 / version 防并发
* ✔ 并行网关 / 排他网关
* ✔ 任务转派 / 会签
* ✔ SLA / 超时补偿
* ✔ 流程可视化建模器