Files
udmin/backend/src/flow/log_handler.rs
ayou 8c06849254 feat(调度任务): 实现调度任务管理功能
新增调度任务模块,支持任务的增删改查、启停及手动执行
- 后端添加 schedule_job 模型、服务、路由及调度器工具类
- 前端新增调度任务管理页面
- 修改 flow 相关接口将 id 类型从 String 改为 i64
- 添加 tokio-cron-scheduler 依赖实现定时任务调度
- 初始化时加载已启用任务并注册到调度器
2025-09-24 00:21:30 +08:00

219 lines
10 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(())
}
}