feat(flow): 新增流式执行模式与SSE支持
新增流式执行模式,通过SSE实时推送节点执行事件与日志 重构HTTP执行器与中间件,提取通用HTTP客户端组件 优化前端测试面板,支持流式模式切换与实时日志展示 更新依赖版本并修复密码哈希的随机数生成器问题 修复前端节点类型映射问题,确保Code节点表单可用
This commit is contained in:
219
backend/src/flow/log_handler.rs
Normal file
219
backend/src/flow/log_handler.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user