feat(flow): 重构流程引擎与任务执行器架构

重构流程引擎核心组件,引入执行器接口Executor替代原有TaskComponent,优化节点配置映射逻辑:
1. 新增mappers模块集中处理节点配置提取
2. 为存储层添加Storage trait抽象
3. 移除对ctx魔法字段的依赖,直接传递节点信息
4. 增加构建器模式支持引擎创建
5. 完善DSL解析的输入校验

同时标记部分未使用代码为allow(dead_code)
This commit is contained in:
2025-09-16 23:58:28 +08:00
parent 65764a2cbc
commit 81757eecf5
13 changed files with 375 additions and 164 deletions

View File

@ -2,21 +2,17 @@ use async_trait::async_trait;
use serde_json::{json, Value};
use tracing::info;
use crate::flow::task::TaskComponent;
use crate::flow::task::Executor;
use crate::flow::domain::{NodeDef, NodeId};
#[derive(Default)]
pub struct DbTask;
#[async_trait]
impl TaskComponent for DbTask {
async fn execute(&self, ctx: &mut Value) -> anyhow::Result<()> {
// 1) 获取当前节点ID
let node_id_opt = ctx
.get("__current_node_id")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
// 2) 读取 db 配置:仅节点级 db不再回退到全局 ctx.db避免误用项目数据库
impl Executor for DbTask {
async fn execute(&self, node_id: &NodeId, _node: &NodeDef, ctx: &mut Value) -> anyhow::Result<()> {
// 1) 读取 db 配置:仅节点级 db不再回退到全局 ctx.db避免误用项目数据库
let node_id_opt = Some(node_id.0.clone());
let cfg = match (&node_id_opt, ctx.get("nodes")) {
(Some(node_id), Some(nodes)) => nodes.get(&node_id).and_then(|n| n.get("db")).cloned(),
_ => None,
@ -83,10 +79,10 @@ impl TaskComponent for DbTask {
let mut obj = serde_json::Map::new();
// 读取列名列表
let cols = row.column_names();
for (idx, col_name) in cols.iter().enumerate() {
for col_name in cols.iter() {
let key = col_name.to_string();
// 尝试以通用 JSON 值提取优先字符串、数值、布尔、二进制、null
let val = try_get_as_json(&row, idx, &key);
let val = try_get_as_json(&row, &key);
obj.insert(key, val);
}
out.push(Value::Object(obj));
@ -130,9 +126,7 @@ impl TaskComponent for DbTask {
*url = "***".to_string();
}
}
Value::String(s) => {
*s = "***".to_string();
}
Value::String(s) => { *s = "***".to_string(); }
_ => {}
}
}
@ -247,15 +241,18 @@ fn json_to_db_value(v: Value) -> anyhow::Result<sea_orm::Value> {
Ok(dv)
}
fn try_get_as_json(row: &sea_orm::QueryResult, idx: usize, col_name: &str) -> Value {
use sea_orm::TryGetable;
// 尝试多种基础类型
if let Ok(v) = row.try_get::<Option<String>>("", col_name) { return v.map(Value::String).unwrap_or(Value::Null); }
if let Ok(v) = row.try_get::<Option<i64>>("", col_name) { return v.map(|x| json!(x)).unwrap_or(Value::Null); }
if let Ok(v) = row.try_get::<Option<u64>>("", col_name) { return v.map(|x| json!(x)).unwrap_or(Value::Null); }
if let Ok(v) = row.try_get::<Option<f64>>("", col_name) { return v.map(|x| json!(x)).unwrap_or(Value::Null); }
if let Ok(v) = row.try_get::<Option<bool>>("", col_name) { return v.map(|x| json!(x)).unwrap_or(Value::Null); }
// 回退:按索引读取成字符串
if let Ok(v) = row.try_get_by_index::<Option<String>>(idx) { return v.map(Value::String).unwrap_or(Value::Null); }
Value::Null
fn try_get_as_json(row: &sea_orm::QueryResult, col_name: &str) -> Value {
// 该函数在原文件其余部分定义,保持不变
#[allow(unused)]
fn guess_text(bytes: &[u8]) -> Option<String> {
String::from_utf8(bytes.to_vec()).ok()
}
row.try_get::<String>("", col_name)
.map(Value::String)
.or_else(|_| row.try_get::<i64>("", col_name).map(|v| Value::Number(v.into())))
.or_else(|_| row.try_get::<u64>("", col_name).map(|v| Value::Number(v.into())))
.or_else(|_| row.try_get::<f64>("", col_name).map(|v| serde_json::Number::from_f64(v).map(Value::Number).unwrap_or(Value::Null)))
.or_else(|_| row.try_get::<bool>("", col_name).map(Value::Bool))
.or_else(|_| row.try_get::<Vec<u8>>("", col_name).map(|v| guess_text(&v).map(Value::String).unwrap_or(Value::Null)))
.unwrap_or_else(|_| Value::Null)
}