use std::collections::HashMap; use rhai::Engine; use tracing::info; use super::{context::{DriveOptions, ExecutionMode}, domain::{ChainDef, NodeKind}, task::TaskRegistry}; pub struct FlowEngine { pub tasks: TaskRegistry, } impl FlowEngine { pub fn new(tasks: TaskRegistry) -> Self { Self { tasks } } pub fn builder() -> FlowEngineBuilder { FlowEngineBuilder::default() } pub async fn drive(&self, chain: &ChainDef, mut ctx: serde_json::Value, opts: DriveOptions) -> anyhow::Result<(serde_json::Value, Vec)> { let mut logs = Vec::new(); // 查找 start:优先 Start 节点;否则选择入度为 0 的第一个节点;再否则回退第一个节点 let start = if let Some(n) = chain .nodes .iter() .find(|n| matches!(n.kind, NodeKind::Start)) { n.id.0.clone() } else { // 计算入度 let mut indeg: HashMap<&str, usize> = HashMap::new(); for n in &chain.nodes { indeg.entry(n.id.0.as_str()).or_insert(0); } for l in &chain.links { *indeg.entry(l.to.0.as_str()).or_insert(0) += 1; } if let Some(n) = chain.nodes.iter().find(|n| indeg.get(n.id.0.as_str()).copied().unwrap_or(0) == 0) { n.id.0.clone() } else { chain .nodes .first() .ok_or_else(|| anyhow::anyhow!("empty chain"))? .id .0 .clone() } }; // 邻接表(按 links 的原始顺序保序) let mut adj: HashMap<&str, Vec<&super::domain::LinkDef>> = HashMap::new(); for l in &chain.links { adj.entry(&l.from.0).or_default().push(l); } let node_map: HashMap<&str, &super::domain::NodeDef> = chain.nodes.iter().map(|n| (n.id.0.as_str(), n)).collect(); let mut current = start; let mut steps = 0usize; while steps < opts.max_steps { steps += 1; let node = node_map.get(current.as_str()).ok_or_else(|| anyhow::anyhow!("node not found"))?; logs.push(format!("enter node: {}", node.id.0)); info!(target: "udmin.flow", "enter node: {}", node.id.0); // 任务执行 if let Some(task_name) = &node.task { if let Some(task) = self.tasks.get(task_name) { match opts.execution_mode { ExecutionMode::Sync => { // 直接传入 node_id 与 node,避免对 ctx 魔法字段的依赖 task.execute(&node.id, node, &mut ctx).await?; logs.push(format!("exec task: {} (sync)", task_name)); info!(target: "udmin.flow", "exec task: {} (sync)", task_name); } ExecutionMode::AsyncFireAndForget => { // fire-and-forget: 复制一份上下文供该任务使用,主流程不等待 let mut task_ctx = ctx.clone(); let task_arc = task.clone(); let name_for_log = task_name.clone(); let node_id = node.id.clone(); let node_def = (*node).clone(); tokio::spawn(async move { let _ = task_arc.execute(&node_id, &node_def, &mut task_ctx).await; info!(target: "udmin.flow", "exec task done (async): {}", name_for_log); }); logs.push(format!("spawn task: {} (async)", task_name)); info!(target: "udmin.flow", "spawn task: {} (async)", task_name); } } } else { logs.push(format!("task not found: {} (skip)", task_name)); info!(target: "udmin.flow", "task not found: {} (skip)", task_name); } } if matches!(node.kind, NodeKind::End) { break; } // 选择下一条 link:优先有条件的且为真;否则保序选择第一条无条件边 let mut next: Option = None; if let Some(links) = adj.get(node.id.0.as_str()) { // 先检测条件边 for link in links.iter() { if let Some(cond_str) = &link.condition { // 两种情况: // 1) 前端序列化的 JSON,形如 { left: {type, content}, operator, right? } // 2) 直接是 rhai 表达式字符串 let ok = if cond_str.trim_start().starts_with('{') { match serde_json::from_str::(cond_str) { Ok(v) => eval_condition_json(&ctx, &v).unwrap_or(false), Err(_) => false, } } else { let mut scope = rhai::Scope::new(); scope.push("ctx", rhai::serde::to_dynamic(ctx.clone()).map_err(|e| anyhow::anyhow!(e.to_string()))?); let engine = Engine::new(); engine.eval_with_scope::(&mut scope, cond_str).unwrap_or(false) }; if ok { next = Some(link.to.0.clone()); break; } } } // 若没有命中条件边,则取第一条无条件边 if next.is_none() { for link in links.iter() { if link.condition.is_none() { next = Some(link.to.0.clone()); break; } } } } match next { Some(n) => current = n, None => break } } Ok((ctx, logs)) } } #[derive(Default)] pub struct FlowEngineBuilder { tasks: Option, } impl FlowEngineBuilder { pub fn tasks(mut self, reg: TaskRegistry) -> Self { self.tasks = Some(reg); self } pub fn build(self) -> FlowEngine { let tasks = self.tasks.unwrap_or_else(|| crate::flow::task::default_registry()); FlowEngine { tasks } } } impl Default for FlowEngine { fn default() -> Self { Self { tasks: crate::flow::task::default_registry() } } } fn eval_condition_json(ctx: &serde_json::Value, cond: &serde_json::Value) -> anyhow::Result { // 目前支持前端 Condition 组件导出的: { left:{type, content}, operator, right? } let left = cond.get("left").ok_or_else(|| anyhow::anyhow!("missing left"))?; let op = cond.get("operator").and_then(|v| v.as_str()).unwrap_or(""); let right = cond.get("right"); let lval = resolve_value(ctx, left)?; let rval = match right { Some(v) => Some(resolve_value(ctx, v)?), None => None }; use serde_json::Value as V; let res = match (op, &lval, &rval) { ("contains", V::String(s), Some(V::String(t))) => s.contains(t), ("equals", V::String(s), Some(V::String(t))) => s == t, ("equals", V::Number(a), Some(V::Number(b))) => a == b, ("is_true", V::Bool(b), _) => *b, ("is_false", V::Bool(b), _) => !*b, ("gt", V::Number(a), Some(V::Number(b))) => a.as_f64().unwrap_or(0.0) > b.as_f64().unwrap_or(0.0), ("lt", V::Number(a), Some(V::Number(b))) => a.as_f64().unwrap_or(0.0) < b.as_f64().unwrap_or(0.0), _ => false, }; Ok(res) } fn resolve_value(ctx: &serde_json::Value, v: &serde_json::Value) -> anyhow::Result { use serde_json::Value as V; let t = v.get("type").and_then(|v| v.as_str()).unwrap_or(""); match t { "constant" => Ok(v.get("content").cloned().unwrap_or(V::Null)), "ref" => { // content: [nodeId, field] if let Some(arr) = v.get("content").and_then(|v| v.as_array()) { if arr.len() >= 2 { if let (Some(node), Some(field)) = (arr[0].as_str(), arr[1].as_str()) { let val = ctx .get("nodes") .and_then(|n| n.get(node)) .and_then(|m| m.get(field)) .cloned() .or_else(|| ctx.get(field).cloned()) .unwrap_or(V::Null); return Ok(val); } } } Ok(V::Null) } _ => Ok(V::Null), } }