feat: 审计修复 Phase 6-7 — SSE 推送/工作流补全/消息群发/前端收尾
Phase 6 功能补全: - P1-3: 消息 SSE 实时推送端点 + 前端 EventSource 连接 - P1-6: ServiceTask HTTP 调用能力 (reqwest GET/POST) - P1-7: user.deleted 事件处理 — 终止相关流程实例 - P1-8: 任务认领 (claim) 端点 + handler - P1-9: 超时检查器发布 task.timeout 事件 - P1-15: 组织/部门名称唯一性校验 (create + update) - P1-18: 消息群发 fan-out (role/department/all 批量投递) Phase 7 P3-P4 收尾: - PluginAdmin purge 按钮状态修复 - ChangePassword 最小 8 字符 + 新旧密码不同验证 - AuditLogViewer 用户名缓存 + 扩展资源类型 - InstanceMonitor 通过 definition 缓存解析 node_name - NotificationPreferences DND 时间范围校验
This commit is contained in:
@@ -249,8 +249,7 @@ impl FlowExecutor {
|
||||
.await
|
||||
}
|
||||
NodeType::ServiceTask => {
|
||||
// ServiceTask 自动执行:当前阶段自动跳过(直接推进到后继节点)
|
||||
// 创建一个立即消费的 token 记录(用于审计追踪)
|
||||
// ServiceTask 自动执行 HTTP 调用
|
||||
let now = Utc::now();
|
||||
let system_user = uuid::Uuid::nil();
|
||||
let auto_token_id = Uuid::now_v7();
|
||||
@@ -274,7 +273,18 @@ impl FlowExecutor {
|
||||
.await
|
||||
.map_err(|e| WorkflowError::Validation(e.to_string()))?;
|
||||
|
||||
tracing::info!(node_id = node_id, node_name = %node.name, "ServiceTask 自动跳过(尚未实现 HTTP 调用)");
|
||||
// 执行 HTTP 调用(如果配置了 service_config)
|
||||
let var_name = format!("service_task_{node_id}_result");
|
||||
let result_value = Self::execute_service_task(node, variables).await;
|
||||
// 将结果存储为流程变量
|
||||
Self::set_process_variable(
|
||||
instance_id,
|
||||
tenant_id,
|
||||
&var_name,
|
||||
&result_value,
|
||||
txn,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 沿出边继续推进
|
||||
let outgoing = graph.get_outgoing_edges(node_id);
|
||||
@@ -444,6 +454,125 @@ impl FlowExecutor {
|
||||
Ok(new_tokens)
|
||||
}
|
||||
|
||||
/// 执行 ServiceTask HTTP 调用。
|
||||
///
|
||||
/// 根据 `service_config` 中的 url/method/body 发起 HTTP 请求。
|
||||
/// 如果没有配置 `service_config` 或调用失败,返回错误信息 JSON 而不是阻塞流程。
|
||||
async fn execute_service_task(
|
||||
node: &crate::engine::model::FlowNode,
|
||||
variables: &HashMap<String, serde_json::Value>,
|
||||
) -> serde_json::Value {
|
||||
let config = match &node.service_config {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
tracing::warn!(
|
||||
node_id = &node.id,
|
||||
node_name = %node.name,
|
||||
"ServiceTask 没有 service_config 配置,跳过 HTTP 调用"
|
||||
);
|
||||
return serde_json::json!({
|
||||
"status": "skipped",
|
||||
"reason": "未配置 service_config"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let method = config.method.to_uppercase();
|
||||
let url = &config.url;
|
||||
|
||||
tracing::info!(
|
||||
node_id = &node.id,
|
||||
node_name = %node.name,
|
||||
method = %method,
|
||||
url = %url,
|
||||
"ServiceTask 开始 HTTP 调用"
|
||||
);
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let result = match method.as_str() {
|
||||
"POST" => {
|
||||
let body = config.body.as_ref().map(|b| {
|
||||
// 简单变量替换:${var_name} → variables 中的值
|
||||
let mut body_str = b.to_string();
|
||||
for (key, val) in variables {
|
||||
let placeholder = format!("${{{key}}}");
|
||||
body_str = body_str.replace(&placeholder, &val.to_string());
|
||||
}
|
||||
body_str
|
||||
});
|
||||
client.post(url).body(body.unwrap_or_default()).send().await
|
||||
}
|
||||
_ => {
|
||||
// 默认 GET
|
||||
client.get(url).send().await
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(resp) => {
|
||||
let status = resp.status().as_u16();
|
||||
let body = resp.text().await.unwrap_or_default();
|
||||
tracing::info!(
|
||||
node_id = &node.id,
|
||||
status = status,
|
||||
"ServiceTask HTTP 调用完成"
|
||||
);
|
||||
serde_json::json!({
|
||||
"status": "success",
|
||||
"http_status": status,
|
||||
"body": body,
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
node_id = &node.id,
|
||||
error = %e,
|
||||
"ServiceTask HTTP 调用失败(流程继续推进)"
|
||||
);
|
||||
serde_json::json!({
|
||||
"status": "error",
|
||||
"error": e.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 将流程变量写入 process_variables 表。
|
||||
async fn set_process_variable(
|
||||
instance_id: Uuid,
|
||||
tenant_id: Uuid,
|
||||
name: &str,
|
||||
value: &serde_json::Value,
|
||||
txn: &impl ConnectionTrait,
|
||||
) -> WorkflowResult<()> {
|
||||
use crate::entity::process_variable;
|
||||
|
||||
let now = Utc::now();
|
||||
let system_user = Uuid::nil();
|
||||
let var_model = process_variable::ActiveModel {
|
||||
id: Set(Uuid::now_v7()),
|
||||
tenant_id: Set(tenant_id),
|
||||
instance_id: Set(instance_id),
|
||||
name: Set(name.to_string()),
|
||||
var_type: Set("json".to_string()),
|
||||
value_string: Set(Some(value.to_string())),
|
||||
value_number: Set(None),
|
||||
value_boolean: Set(None),
|
||||
value_date: Set(None),
|
||||
created_at: Set(now),
|
||||
updated_at: Set(now),
|
||||
created_by: Set(system_user),
|
||||
updated_by: Set(system_user),
|
||||
deleted_at: Set(None),
|
||||
version: Set(1),
|
||||
};
|
||||
var_model
|
||||
.insert(txn)
|
||||
.await
|
||||
.map_err(|e| WorkflowError::Validation(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 检查实例是否所有 token 都已完成,如果是则完成实例。
|
||||
async fn check_instance_completion(
|
||||
instance_id: Uuid,
|
||||
|
||||
Reference in New Issue
Block a user