feat(flow): 新增流式执行模式与SSE支持

新增流式执行模式,通过SSE实时推送节点执行事件与日志
重构HTTP执行器与中间件,提取通用HTTP客户端组件
优化前端测试面板,支持流式模式切换与实时日志展示
更新依赖版本并修复密码哈希的随机数生成器问题
修复前端节点类型映射问题,确保Code节点表单可用
This commit is contained in:
2025-09-21 01:48:24 +08:00
parent 296f0ae9f6
commit dd7857940f
24 changed files with 1695 additions and 885 deletions

View File

@ -1,3 +1,12 @@
//! 模块:流程 DSL 与自由布局 Design JSON 的解析、校验与构建。
//! 主要内容:
//! - FlowDSL/NodeDSL/EdgeDSL较为“表述性”的简化 DSL 结构(用于外部接口/入库)。
//! - DesignSyntax/NodeSyntax/EdgeSyntax与前端自由布局 JSON 对齐的结构(含 source_port_id 等)。
//! - validate_design基础校验节点 ID 唯一、至少包含一个 start 与一个 end、边的引用合法
//! - build_chain_from_design将自由布局 JSON 转换为内部 ChainDef含条件节点 AND 组装等启发式与兼容逻辑)。
//! - chain_from_design_json统一入口支持字符串/对象两种输入,做兼容字段回填后再校验并构建。
//! 说明:尽量保持向后兼容;在条件节点的出边组装上采用启发式(例如:单出边 + 多条件 => 组装为 AND 条件组)。
use serde::{Deserialize, Serialize};
use serde_json::Value;
use anyhow::bail;
@ -5,36 +14,53 @@ use anyhow::bail;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FlowDSL {
#[serde(default)]
/// 流程名称(可选)
pub name: String,
#[serde(default, alias = "executionMode")]
/// 执行模式(兼容前端 executionModesync/async目前仅占位
pub execution_mode: Option<String>,
/// 节点列表(按声明顺序)
pub nodes: Vec<NodeDSL>,
#[serde(default)]
/// 边列表from -> to可选 condition
pub edges: Vec<EdgeDSL>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeDSL {
/// 节点唯一 ID字符串
pub id: String,
#[serde(default)]
/// 节点类型start / end / task / condition开始/结束/任务/条件)
pub kind: String, // 节点类型start / end / task / condition开始/结束/任务/条件)
#[serde(default)]
/// 节点显示名称(可选)
pub name: String,
#[serde(default)]
/// 任务标识(绑定执行器),如 http/db/variable/script_*(可选)
pub task: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EdgeDSL {
/// 起点节点 ID别名source/from
#[serde(alias = "source", alias = "from", rename = "from")]
pub from: String,
/// 终点节点 ID别名target/to
#[serde(alias = "target", alias = "to", rename = "to")]
pub to: String,
#[serde(default)]
/// 条件表达式(字符串):
/// - 若为 JSON 字符串(以 { 或 [ 开头),则按 JSON 条件集合进行求值;
/// - 否则按 Rhai 表达式求值;
/// - 空字符串/None 表示无条件。
pub condition: Option<String>,
}
impl From<FlowDSL> for super::domain::ChainDef {
/// 将简化 DSL 转换为内部 ChainDef
/// - kind 映射start/end/condition/其他->task支持 decision 别名 -> condition。
/// - 直接搬运 edges 的 from/to/condition。
fn from(v: FlowDSL) -> Self {
super::domain::ChainDef {
name: v.name,
@ -71,34 +97,47 @@ impl From<FlowDSL> for super::domain::ChainDef {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DesignSyntax {
#[serde(default)]
/// 设计名称(可选)
pub name: String,
#[serde(default)]
/// 节点集合(自由布局)
pub nodes: Vec<NodeSyntax>,
#[serde(default)]
/// 边集合(自由布局)
pub edges: Vec<EdgeSyntax>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeSyntax {
/// 节点 ID
pub id: String,
#[serde(rename = "type", default)]
/// 前端类型start | end | condition | http | db | task | script_*(用于推断具体执行器)
pub kind: String, // 取值: start | end | condition | http | db | task开始/结束/条件/HTTP/数据库/通用任务)
#[serde(default)]
/// 节点附加数据title/conditions/scripts 等
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EdgeSyntax {
/// 起点(兼容 sourceNodeID/source/from
#[serde(alias = "sourceNodeID", alias = "source", alias = "from")]
pub from: String,
/// 终点(兼容 targetNodeID/target/to
#[serde(alias = "targetNodeID", alias = "target", alias = "to")]
pub to: String,
#[serde(default)]
/// 源端口 ID用于条件节点端口到条件 key 的兼容映射;
/// 特殊值 and/all/group/true 表示将节点内所有 conditions 的 value 组装为 AND 组。
pub source_port_id: Option<String>,
}
/// 从 design_json前端流程 JSON构建 ChainDef
/// 设计级别校验:
/// - 节点 ID 唯一且非空;
/// - 至少一个 start 与一个 end
/// - 边的 from/to 必须指向已知节点。
fn validate_design(design: &DesignSyntax) -> anyhow::Result<()> {
use std::collections::HashSet;
let mut ids = HashSet::new();
@ -129,6 +168,13 @@ fn validate_design(design: &DesignSyntax) -> anyhow::Result<()> {
Ok(())
}
/// 将自由布局 DesignSyntax 转换为内部 ChainDef
/// - 节点:推断 kind/name/task含 scripts 与 inline script/expr 的兼容);
/// - 边:
/// * 条件节点:支持 source_port_id 到 data.conditions 的旧版映射;
/// * 当 source_port_id 为空或为 and/all/group/true取 conditions 的 value 组成 AND 组;
/// * 启发式:若条件节点仅一条出边且包含多个 conditions即便 source_port_id 指向具体 key也回退为 AND 组;
/// * 非条件节点:不处理条件。
fn build_chain_from_design(design: &DesignSyntax) -> anyhow::Result<super::domain::ChainDef> {
use super::domain::{ChainDef, NodeDef, NodeId, NodeKind, LinkDef};