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

@ -0,0 +1,219 @@
use async_trait::async_trait;
use chrono::{DateTime, FixedOffset};
use serde_json::Value;
use tokio::sync::mpsc::Sender;
use crate::flow::context::StreamEvent;
use crate::services::flow_run_log_service::{self, CreateRunLogInput};
use crate::db::Db;
/// 流程执行日志处理器抽象接口
#[async_trait]
pub trait FlowLogHandler: Send + Sync {
/// 记录流程开始执行
async fn log_start(&self, flow_id: &str, flow_code: Option<&str>, input: &Value, operator: Option<(i64, String)>) -> anyhow::Result<()>;
/// 记录流程执行失败(仅包含错误信息)
async fn log_error(&self, flow_id: &str, flow_code: Option<&str>, input: &Value, error_msg: &str, operator: Option<(i64, String)>, started_at: DateTime<FixedOffset>, duration_ms: i64) -> anyhow::Result<()>;
/// 记录流程执行失败(包含部分输出与累计日志)
async fn log_error_detail(&self, flow_id: &str, flow_code: Option<&str>, input: &Value, output: &Value, logs: &[String], error_msg: &str, operator: Option<(i64, String)>, started_at: DateTime<FixedOffset>, duration_ms: i64) -> anyhow::Result<()> {
// 默认实现:退化为仅错误信息
self.log_error(flow_id, flow_code, input, error_msg, operator, started_at, duration_ms).await
}
/// 记录流程执行成功
async fn log_success(&self, flow_id: &str, flow_code: Option<&str>, input: &Value, output: &Value, logs: &[String], operator: Option<(i64, String)>, started_at: DateTime<FixedOffset>, duration_ms: i64) -> anyhow::Result<()>;
/// 推送节点执行事件仅SSE实现需要
async fn emit_node_event(&self, _node_id: &str, _event_type: &str, _data: &Value) -> anyhow::Result<()> {
// 默认空实现,数据库日志处理器不需要
Ok(())
}
/// 推送完成事件仅SSE实现需要
async fn emit_done(&self, _success: bool, _output: &Value, _logs: &[String]) -> anyhow::Result<()> {
// 默认空实现,数据库日志处理器不需要
Ok(())
}
}
/// 数据库日志处理器
pub struct DatabaseLogHandler {
db: Db,
}
impl DatabaseLogHandler {
pub fn new(db: Db) -> Self {
Self { db }
}
}
#[async_trait]
impl FlowLogHandler for DatabaseLogHandler {
async fn log_start(&self, _flow_id: &str, _flow_code: Option<&str>, _input: &Value, _operator: Option<(i64, String)>) -> anyhow::Result<()> {
// 数据库日志处理器不需要记录开始事件,只在结束时记录
Ok(())
}
async fn log_error(&self, flow_id: &str, flow_code: Option<&str>, input: &Value, error_msg: &str, operator: Option<(i64, String)>, started_at: DateTime<FixedOffset>, duration_ms: i64) -> anyhow::Result<()> {
let (user_id, username) = operator.map(|(u, n)| (Some(u), Some(n))).unwrap_or((None, None));
flow_run_log_service::create(&self.db, CreateRunLogInput {
flow_id: flow_id.to_string(),
flow_code: flow_code.map(|s| s.to_string()),
input: Some(serde_json::to_string(input).unwrap_or_default()),
output: None,
ok: false,
logs: Some(error_msg.to_string()),
user_id,
username,
started_at,
duration_ms,
}).await.map_err(|e| anyhow::anyhow!("Failed to create error log: {}", e))?;
Ok(())
}
async fn log_error_detail(&self, flow_id: &str, flow_code: Option<&str>, input: &Value, output: &Value, logs: &[String], error_msg: &str, operator: Option<(i64, String)>, started_at: DateTime<FixedOffset>, duration_ms: i64) -> anyhow::Result<()> {
let (user_id, username) = operator.map(|(u, n)| (Some(u), Some(n))).unwrap_or((None, None));
// 将 error_msg 附加到日志尾部(若最后一条不同),确保日志中有清晰的错误描述且不重复
let mut all_logs = logs.to_vec();
if all_logs.last().map(|s| s != error_msg).unwrap_or(true) {
all_logs.push(error_msg.to_string());
}
flow_run_log_service::create(&self.db, CreateRunLogInput {
flow_id: flow_id.to_string(),
flow_code: flow_code.map(|s| s.to_string()),
input: Some(serde_json::to_string(input).unwrap_or_default()),
output: Some(serde_json::to_string(output).unwrap_or_default()),
ok: false,
logs: Some(serde_json::to_string(&all_logs).unwrap_or_default()),
user_id,
username,
started_at,
duration_ms,
}).await.map_err(|e| anyhow::anyhow!("Failed to create error log with details: {}", e))?;
Ok(())
}
async fn log_success(&self, flow_id: &str, flow_code: Option<&str>, input: &Value, output: &Value, logs: &[String], operator: Option<(i64, String)>, started_at: DateTime<FixedOffset>, duration_ms: i64) -> anyhow::Result<()> {
let (user_id, username) = operator.map(|(u, n)| (Some(u), Some(n))).unwrap_or((None, None));
flow_run_log_service::create(&self.db, CreateRunLogInput {
flow_id: flow_id.to_string(),
flow_code: flow_code.map(|s| s.to_string()),
input: Some(serde_json::to_string(input).unwrap_or_default()),
output: Some(serde_json::to_string(output).unwrap_or_default()),
ok: true,
logs: Some(serde_json::to_string(logs).unwrap_or_default()),
user_id,
username,
started_at,
duration_ms,
}).await.map_err(|e| anyhow::anyhow!("Failed to create success log: {}", e))?;
Ok(())
}
}
/// SSE日志处理器
pub struct SseLogHandler {
db: Db,
event_tx: Sender<StreamEvent>,
}
impl SseLogHandler {
pub fn new(db: Db, event_tx: Sender<StreamEvent>) -> Self {
Self { db, event_tx }
}
}
#[async_trait]
impl FlowLogHandler for SseLogHandler {
async fn log_start(&self, _flow_id: &str, _flow_code: Option<&str>, _input: &Value, _operator: Option<(i64, String)>) -> anyhow::Result<()> {
// SSE处理器也不需要记录开始事件
Ok(())
}
async fn log_error(&self, flow_id: &str, flow_code: Option<&str>, input: &Value, error_msg: &str, operator: Option<(i64, String)>, started_at: DateTime<FixedOffset>, duration_ms: i64) -> anyhow::Result<()> {
// 先推送SSE错误事件不在此处发送 done交由调用方统一携带 ctx/logs 发送)
crate::middlewares::sse::emit_error(&self.event_tx, error_msg.to_string()).await;
// 然后记录到数据库(仅错误信息)
let (user_id, username) = operator.map(|(u, n)| (Some(u), Some(n))).unwrap_or((None, None));
flow_run_log_service::create(&self.db, CreateRunLogInput {
flow_id: flow_id.to_string(),
flow_code: flow_code.map(|s| s.to_string()),
input: Some(serde_json::to_string(input).unwrap_or_default()),
output: None,
ok: false,
logs: Some(error_msg.to_string()),
user_id,
username,
started_at,
duration_ms,
}).await.map_err(|e| anyhow::anyhow!("Failed to create error log: {}", e))?;
Ok(())
}
async fn log_error_detail(&self, flow_id: &str, flow_code: Option<&str>, input: &Value, output: &Value, logs: &[String], error_msg: &str, operator: Option<(i64, String)>, started_at: DateTime<FixedOffset>, duration_ms: i64) -> anyhow::Result<()> {
// 先推送SSE错误事件不在此处发送 done交由调用方统一携带 ctx/logs 发送)
crate::middlewares::sse::emit_error(&self.event_tx, error_msg.to_string()).await;
// 然后记录到数据库(包含部分输出与累计日志),避免重复附加相同错误信息
let (user_id, username) = operator.map(|(u, n)| (Some(u), Some(n))).unwrap_or((None, None));
let mut all_logs = logs.to_vec();
if all_logs.last().map(|s| s != error_msg).unwrap_or(true) {
all_logs.push(error_msg.to_string());
}
flow_run_log_service::create(&self.db, CreateRunLogInput {
flow_id: flow_id.to_string(),
flow_code: flow_code.map(|s| s.to_string()),
input: Some(serde_json::to_string(input).unwrap_or_default()),
output: Some(serde_json::to_string(output).unwrap_or_default()),
ok: false,
logs: Some(serde_json::to_string(&all_logs).unwrap_or_default()),
user_id,
username,
started_at,
duration_ms,
}).await.map_err(|e| anyhow::anyhow!("Failed to create error log with details: {}", e))?;
Ok(())
}
async fn log_success(&self, flow_id: &str, flow_code: Option<&str>, input: &Value, output: &Value, logs: &[String], operator: Option<(i64, String)>, started_at: DateTime<FixedOffset>, duration_ms: i64) -> anyhow::Result<()> {
// 先推送SSE完成事件
crate::middlewares::sse::emit_done(&self.event_tx, true, output.clone(), logs.to_vec()).await;
// 然后记录到数据库
let (user_id, username) = operator.map(|(u, n)| (Some(u), Some(n))).unwrap_or((None, None));
flow_run_log_service::create(&self.db, CreateRunLogInput {
flow_id: flow_id.to_string(),
flow_code: flow_code.map(|s| s.to_string()),
input: Some(serde_json::to_string(input).unwrap_or_default()),
output: Some(serde_json::to_string(output).unwrap_or_default()),
ok: true,
logs: Some(serde_json::to_string(logs).unwrap_or_default()),
user_id,
username,
started_at,
duration_ms,
}).await.map_err(|e| anyhow::anyhow!("Failed to create success log: {}", e))?;
Ok(())
}
async fn emit_node_event(&self, node_id: &str, event_type: &str, data: &Value) -> anyhow::Result<()> {
// 推送节点事件到SSE
let event = StreamEvent::Node {
node_id: node_id.to_string(),
logs: vec![event_type.to_string()],
ctx: data.clone(),
};
if let Err(_e) = self.event_tx.send(event).await {
// 通道可能已关闭,忽略错误
}
Ok(())
}
async fn emit_done(&self, success: bool, output: &Value, logs: &[String]) -> anyhow::Result<()> {
crate::middlewares::sse::emit_done(&self.event_tx, success, output.clone(), logs.to_vec()).await;
Ok(())
}
}