新增调度任务模块,支持任务的增删改查、启停及手动执行 - 后端添加 schedule_job 模型、服务、路由及调度器工具类 - 前端新增调度任务管理页面 - 修改 flow 相关接口将 id 类型从 String 改为 i64 - 添加 tokio-cron-scheduler 依赖实现定时任务调度 - 初始化时加载已启用任务并注册到调度器
219 lines
10 KiB
Rust
219 lines
10 KiB
Rust
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: i64, flow_code: Option<&str>, input: &Value, operator: Option<(i64, String)>) -> anyhow::Result<()>;
|
||
|
||
/// 记录流程执行失败(仅包含错误信息)
|
||
async fn log_error(&self, flow_id: i64, 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: i64, _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: i64, 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: i64, _flow_code: Option<&str>, _input: &Value, _operator: Option<(i64, String)>) -> anyhow::Result<()> {
|
||
// 数据库日志处理器不需要记录开始事件,只在结束时记录
|
||
Ok(())
|
||
}
|
||
|
||
async fn log_error(&self, flow_id: i64, 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_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: i64, 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_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: i64, 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_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: i64, _flow_code: Option<&str>, _input: &Value, _operator: Option<(i64, String)>) -> anyhow::Result<()> {
|
||
// SSE处理器也不需要记录开始事件
|
||
Ok(())
|
||
}
|
||
|
||
async fn log_error(&self, flow_id: i64, 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_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: i64, 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_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: i64, 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_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(())
|
||
}
|
||
} |