Files
DevFlow/src-tauri/src/commands/workflow.rs
绝尘 22964a2e20 修复: 工作流审批闭环(execution_id 下沉 + 共享状态机 + HumanNode 真审批)
- B-06: DagExecutor 接收 execution_id 下沉到 NodeContext,消除 dummy-execution-id 硬编码
- B-07: NodeContext.node_status 共享 self.state_machine.clone()(is_cancelled 可工作)
- B-03a: HumanNode execute 重写为 subscribe→send→select! 循环(响应/超时/取消 + execution_id+node_id 双键 + Lagged 容忍),删假返回"同意"
- eventbus.rs 删 try_recv_human_approval 死代码
- 新增 human_node 7 单测(正常/双键过滤/超时/非法决策/自由文本)

照 B-03-人工审批响应机制.md 设计。cargo check 0 error,df-nodes 14 + df-workflow 11 test 全过
2026-06-14 14:27:08 +08:00

188 lines
6.0 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.
//! 工作流相关命令 — 触发执行、查询执行记录
//!
//! 事件转发run_workflow 在执行前订阅事件总线,
//! 将 WorkflowEvent 包装为 `{ execution_id, event }` 通过 `workflow-event` 事件发给前端。
use serde::Serialize;
use tauri::{AppHandle, Emitter, State};
use tokio::sync::broadcast::error::RecvError;
use df_core::events::{WorkflowEvent, HumanApprovalResponse};
use df_core::types::new_id;
use df_storage::crud::WorkflowRepo;
use df_storage::models::WorkflowRecord;
use df_workflow::dag_def::DagDef;
use df_workflow::executor::DagExecutor;
use crate::state::AppState;
use super::now_millis;
/// 转发到前端的事件载荷
#[derive(Debug, Clone, Serialize)]
struct WorkflowEventPayload {
/// 工作流执行记录 ID
execution_id: String,
/// 原始工作流事件serde tag = "type"
event: WorkflowEvent,
}
/// 触发工作流执行(核心命令)
///
/// 流程build_dag 校验 → 写入执行记录(status=running) → 后台异步执行 →
/// 事件经 EventBus 转发到前端 → 完成后更新执行记录状态。
/// 立即返回执行记录 ID前端凭此关联后续事件。
#[tauri::command]
pub async fn run_workflow(
app: AppHandle,
state: State<'_, AppState>,
name: String,
dag: DagDef,
config: serde_json::Value,
) -> Result<String, String> {
// 1. 先构建运行时 DAG校验失败直接返回不落库
let runtime_dag = state.registry.build_dag(&dag).map_err(|e| e.to_string())?;
// 2. 写入执行记录status=running
let execution_id = new_id();
let dag_json = serde_json::to_string(&dag).map_err(|e| e.to_string())?;
let record = WorkflowRecord {
id: execution_id.clone(),
name,
dag_json,
status: "running".to_string(),
triggered_by: Some("manual".to_string()),
project_id: None,
task_id: None,
created_at: now_millis(),
completed_at: None,
};
state
.workflows
.insert(record)
.await
.map_err(|e| e.to_string())?;
// 3. 订阅事件总线,把事件转发给前端(收到完成/失败事件后退出)
let mut rx = state.event_bus.subscribe();
let forward_app = app.clone();
let forward_exec_id = execution_id.clone();
tauri::async_runtime::spawn(async move {
loop {
match rx.recv().await {
Ok(event) => {
let finished = matches!(
event,
WorkflowEvent::WorkflowCompleted { .. }
| WorkflowEvent::WorkflowFailed { .. }
);
let payload = WorkflowEventPayload {
execution_id: forward_exec_id.clone(),
event,
};
if let Err(e) = forward_app.emit("workflow-event", &payload) {
tracing::warn!("工作流事件转发失败: {}", e);
}
if finished {
break;
}
}
// 消费过慢丢失部分事件时继续接收
Err(RecvError::Lagged(n)) => {
tracing::warn!("工作流事件消费滞后,丢失 {} 条", n);
}
Err(RecvError::Closed) => break,
}
}
});
// 4. 后台异步执行 DAG完成后更新执行记录状态
let event_bus = state.event_bus.clone();
let db = state.db.clone();
let exec_id = execution_id.clone();
tauri::async_runtime::spawn(async move {
let mut executor = DagExecutor::new(event_bus.clone(), exec_id.clone());
let result = executor.run(&runtime_dag, config).await;
let (status, error) = match &result {
Ok(_) => ("completed", None),
Err(e) => ("failed", Some(format!("{:#}", e))),
};
// 更新执行记录Repo 内部仅持有 Arc 连接,重建开销可忽略)
let workflows = WorkflowRepo::new(&db);
if let Err(e) = workflows.update_field(&exec_id, "status", status).await {
tracing::error!("更新工作流状态失败: {}", e);
}
if let Err(e) = workflows
.update_field(&exec_id, "completed_at", &now_millis())
.await
{
tracing::error!("更新工作流完成时间失败: {}", e);
}
// 执行失败时补发 WorkflowFailed执行器内部只发 NodeFailed
if let Some(error) = error {
event_bus
.send(WorkflowEvent::WorkflowFailed {
error,
failed_node: String::new(),
})
.await;
}
});
Ok(execution_id)
}
/// 列出全部工作流执行记录
#[tauri::command]
pub async fn list_workflow_executions(
state: State<'_, AppState>,
) -> Result<Vec<WorkflowRecord>, String> {
state.workflows.list_all().await.map_err(|e| e.to_string())
}
/// 按 ID 查询工作流执行记录
#[tauri::command]
pub async fn get_workflow_execution(
state: State<'_, AppState>,
id: String,
) -> Result<Option<WorkflowRecord>, String> {
state
.workflows
.get_by_id(&id)
.await
.map_err(|e| e.to_string())
}
/// 发送人工审批响应
#[tauri::command]
pub async fn approve_human_approval(
app: AppHandle,
state: State<'_, AppState>,
execution_id: String,
node_id: String,
decision: String,
comment: Option<String>,
) -> Result<(), String> {
// 发送审批响应到事件总线
let response = HumanApprovalResponse {
execution_id: execution_id.clone(),
node_id: node_id.clone(),
decision,
comment,
};
let event = WorkflowEvent::HumanApprovalResponse {
execution_id: response.execution_id,
node_id: response.node_id,
decision: response.decision,
comment: response.comment,
};
// 直接发送到全局事件总线
state.event_bus.send(event).await;
Ok(())
}