删除内容: - 前端: health/(67文件), ai/(2文件), Copilot, MediaPicker, 相关API/Store/Hook - 后端: wechat_handler, wechat_service, wechat_user entity, analytics handler, ai_workflow_seed - 配置: WechatConfig, AppConfig.wechat, AuthState wechat 字段 - 启动: 微信凭据检查块, ensure_ai_workflows() 调用 - 迁移: 新增 m20260613_000170_drop_wechat_users.rs - 脚本: api_test_health_alert.py, api_test_mp.py, mpsync.sh/ps1 - E2E: health-data page, flows/ 目录 保留: erp-core/auth/workflow/message/config/plugin + 基座前端 + 通用组件
253 lines
7.7 KiB
Rust
253 lines
7.7 KiB
Rust
use chrono::{DateTime, Utc};
|
||
use serde::{Deserialize, Serialize};
|
||
use utoipa::ToSchema;
|
||
use uuid::Uuid;
|
||
use validator::Validate;
|
||
|
||
// --- 流程图节点/边定义 ---
|
||
|
||
/// BPMN 节点类型
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, PartialEq)]
|
||
pub enum NodeType {
|
||
StartEvent,
|
||
EndEvent,
|
||
UserTask,
|
||
ServiceTask,
|
||
ExclusiveGateway,
|
||
ParallelGateway,
|
||
}
|
||
|
||
/// 流程图节点定义
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct NodeDef {
|
||
pub id: String,
|
||
#[serde(rename = "type")]
|
||
pub node_type: NodeType,
|
||
pub name: String,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub assignee_id: Option<Uuid>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub candidate_groups: Option<Vec<String>>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub service_type: Option<String>,
|
||
/// 服务任务 HTTP 调用配置
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub service_config: Option<ServiceTaskConfig>,
|
||
/// 前端渲染位置
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub position: Option<NodePosition>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct NodePosition {
|
||
pub x: f64,
|
||
pub y: f64,
|
||
}
|
||
|
||
/// ServiceTask HTTP 调用配置
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Validate, ToSchema)]
|
||
pub struct ServiceTaskConfig {
|
||
/// 请求 URL(仅允许 http/https 协议,禁止内网地址)
|
||
#[validate(length(min = 1, max = 2048), custom(function = "validate_service_url"))]
|
||
pub url: String,
|
||
/// HTTP 方法(GET / POST),默认 GET
|
||
#[serde(default = "default_method")]
|
||
#[validate(custom(function = "validate_http_method"))]
|
||
pub method: String,
|
||
/// POST body 模板(支持从流程变量替换 ${var_name})
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub body: Option<serde_json::Value>,
|
||
}
|
||
|
||
fn default_method() -> String {
|
||
"GET".to_string()
|
||
}
|
||
|
||
fn validate_service_url(value: &str) -> Result<(), validator::ValidationError> {
|
||
if !value.starts_with("https://") && !value.starts_with("http://") {
|
||
return Err(validator::ValidationError::new("invalid_url_scheme"));
|
||
}
|
||
if value.contains("127.0.0.1") || value.contains("localhost") || value.contains("0.0.0.0") {
|
||
return Err(validator::ValidationError::new("ssrf_blocked"));
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
fn validate_http_method(value: &str) -> Result<(), validator::ValidationError> {
|
||
match value {
|
||
"GET" | "POST" => Ok(()),
|
||
_ => Err(validator::ValidationError::new("invalid_http_method")),
|
||
}
|
||
}
|
||
|
||
/// 流程图连线定义
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct EdgeDef {
|
||
pub id: String,
|
||
pub source: String,
|
||
pub target: String,
|
||
/// 条件表达式(排他网关分支)
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub condition: Option<String>,
|
||
/// 前端渲染标签
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub label: Option<String>,
|
||
}
|
||
|
||
/// 完整流程图
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct FlowDiagram {
|
||
pub nodes: Vec<NodeDef>,
|
||
pub edges: Vec<EdgeDef>,
|
||
}
|
||
|
||
// --- 流程定义 DTOs ---
|
||
|
||
#[derive(Debug, Serialize, ToSchema)]
|
||
pub struct ProcessDefinitionResp {
|
||
pub id: Uuid,
|
||
pub name: String,
|
||
pub key: String,
|
||
pub version: i32,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub category: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub description: Option<String>,
|
||
pub nodes: serde_json::Value,
|
||
pub edges: serde_json::Value,
|
||
pub status: String,
|
||
pub created_at: DateTime<Utc>,
|
||
pub updated_at: DateTime<Utc>,
|
||
pub lock_version: i32,
|
||
}
|
||
|
||
#[derive(Debug, Deserialize, Validate, ToSchema)]
|
||
pub struct CreateProcessDefinitionReq {
|
||
#[validate(length(min = 1, max = 200, message = "流程名称不能为空"))]
|
||
pub name: String,
|
||
#[validate(length(min = 1, max = 100, message = "流程编码不能为空"))]
|
||
pub key: String,
|
||
pub category: Option<String>,
|
||
pub description: Option<String>,
|
||
pub nodes: Vec<NodeDef>,
|
||
pub edges: Vec<EdgeDef>,
|
||
}
|
||
|
||
#[derive(Debug, Deserialize, Validate, ToSchema)]
|
||
pub struct UpdateProcessDefinitionReq {
|
||
#[validate(length(max = 200, message = "流程名称过长"))]
|
||
pub name: Option<String>,
|
||
pub category: Option<String>,
|
||
pub description: Option<String>,
|
||
pub nodes: Option<Vec<NodeDef>>,
|
||
pub edges: Option<Vec<EdgeDef>>,
|
||
pub version: i32,
|
||
}
|
||
|
||
// --- 流程实例 DTOs ---
|
||
|
||
#[derive(Debug, Serialize, ToSchema)]
|
||
pub struct ProcessInstanceResp {
|
||
pub id: Uuid,
|
||
pub definition_id: Uuid,
|
||
pub definition_name: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub business_key: Option<String>,
|
||
pub status: String,
|
||
pub started_by: Uuid,
|
||
pub started_at: DateTime<Utc>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub completed_at: Option<DateTime<Utc>>,
|
||
pub created_at: DateTime<Utc>,
|
||
/// 当前活跃的 token 位置
|
||
pub active_tokens: Vec<TokenResp>,
|
||
pub version: i32,
|
||
}
|
||
|
||
#[derive(Debug, Deserialize, Validate, ToSchema)]
|
||
pub struct StartInstanceReq {
|
||
pub definition_id: Uuid,
|
||
pub business_key: Option<String>,
|
||
/// 初始流程变量
|
||
pub variables: Option<Vec<SetVariableReq>>,
|
||
}
|
||
|
||
// --- Token DTOs ---
|
||
|
||
#[derive(Debug, Serialize, ToSchema)]
|
||
pub struct TokenResp {
|
||
pub id: Uuid,
|
||
pub node_id: String,
|
||
pub status: String,
|
||
pub created_at: DateTime<Utc>,
|
||
}
|
||
|
||
// --- 任务 DTOs ---
|
||
|
||
#[derive(Debug, Serialize, ToSchema)]
|
||
pub struct TaskResp {
|
||
pub id: Uuid,
|
||
pub instance_id: Uuid,
|
||
pub token_id: Uuid,
|
||
pub node_id: String,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub node_name: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub assignee_id: Option<Uuid>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub candidate_groups: Option<serde_json::Value>,
|
||
pub status: String,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub outcome: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub form_data: Option<serde_json::Value>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub due_date: Option<DateTime<Utc>>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub completed_at: Option<DateTime<Utc>>,
|
||
pub created_at: DateTime<Utc>,
|
||
/// 流程定义名称(用于列表展示)
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub definition_name: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub business_key: Option<String>,
|
||
pub version: i32,
|
||
}
|
||
|
||
#[derive(Debug, Deserialize, Validate, ToSchema)]
|
||
pub struct CompleteTaskReq {
|
||
#[validate(length(min = 1, max = 50, message = "审批结果不能为空"))]
|
||
pub outcome: String,
|
||
pub form_data: Option<serde_json::Value>,
|
||
}
|
||
|
||
#[derive(Debug, Deserialize, Validate, ToSchema)]
|
||
pub struct DelegateTaskReq {
|
||
pub delegate_to: Uuid,
|
||
}
|
||
|
||
// --- 流程变量 DTOs ---
|
||
|
||
#[derive(Debug, Serialize, ToSchema)]
|
||
pub struct ProcessVariableResp {
|
||
pub id: Uuid,
|
||
pub name: String,
|
||
pub var_type: String,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub value_string: Option<String>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub value_number: Option<f64>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub value_boolean: Option<bool>,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub value_date: Option<DateTime<Utc>>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Deserialize, Validate, ToSchema)]
|
||
pub struct SetVariableReq {
|
||
#[validate(length(min = 1, max = 100, message = "变量名不能为空"))]
|
||
pub name: String,
|
||
pub var_type: Option<String>,
|
||
pub value: serde_json::Value,
|
||
}
|