Files
opsxsql/backend/app/models/workflow.py
T

363 lines
15 KiB
Python

"""审批流程模型 — 支持多级、并行、条件审批、版本控制与执行日志。"""
import json
from sqlalchemy import (
Column, Integer, String, Text, Boolean, DateTime, ForeignKey, JSON, func, UniqueConstraint
)
from sqlalchemy.orm import relationship
from app.core.database import Base
class WorkflowDefinition(Base):
"""审批流程定义模板。"""
__tablename__ = "workflow_definitions"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(100), nullable=False)
description = Column(Text, nullable=True)
category = Column(String(50), default="ticket") # ticket / query / instance / general
is_default = Column(Boolean, default=False)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
created_by = Column(Integer, ForeignKey("users.id"), nullable=True)
# ═══════════════════════════════════════════════════════════
# 流程级配置(JSON格式,灵活扩展)
# ═══════════════════════════════════════════════════════════
# 全局流程配置:
# {
# "timeout_strategy": "auto_approve|auto_reject|escalate|notify",
# "timeout_hours": 48,
# "escalation_user_id": 1,
# "allow_transfer": true,
# "allow_skip": false,
# "require_comment": true,
# "parallel_mode": "all|any", // 并行审批模式:全部通过/任一通过
# "notify_channels": ["email", "sms", "webhook"]
# }
flow_config = Column(JSON, nullable=True, default=dict)
# 触发条件配置(支持多条件组合):
# {
# "conditions": [
# {"field": "ticket.type", "operator": "in", "value": ["DDL", "DML"]},
# {"field": "ticket.risk_level", "operator": ">=", "value": "high"},
# {"field": "database.instance_id", "operator": "==", "value": 1}
# ],
# "logic": "AND|OR"
# }
trigger_conditions = Column(JSON, nullable=True)
# 步骤定义关联
steps = relationship(
"WorkflowStep",
back_populates="workflow",
order_by="WorkflowStep.step_number",
cascade="all, delete-orphan"
)
# 版本关联
versions = relationship(
"WorkflowVersion",
back_populates="workflow",
order_by="desc(WorkflowVersion.version_number)",
cascade="all, delete-orphan"
)
# 审批实例关联
approvals = relationship(
"TicketApproval",
back_populates="workflow_def",
lazy="dynamic"
)
# 执行日志关联
execution_logs = relationship(
"WorkflowExecutionLog",
back_populates="workflow",
lazy="dynamic",
order_by="desc(WorkflowExecutionLog.created_at)"
)
def get_current_version(self):
"""获取当前激活的版本。"""
return next((v for v in self.versions if v.is_active), None)
def to_dict(self, include_steps: bool = False) -> dict:
data = {
"id": self.id,
"name": self.name,
"description": self.description,
"category": self.category,
"is_default": self.is_default,
"is_active": self.is_active,
"flow_config": self.flow_config,
"trigger_conditions": self.trigger_conditions,
"step_count": len(self.steps) if self.steps else 0,
"version_count": len(self.versions) if self.versions else 0,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
if include_steps:
data["steps"] = [s.to_dict() for s in (self.steps or [])]
return data
class WorkflowVersion(Base):
"""审批流程版本控制 — 支持流程的版本管理与回溯。"""
__tablename__ = "workflow_versions"
__table_args__ = (UniqueConstraint("workflow_id", "version_number", name="uix_workflow_version"),)
id = Column(Integer, primary_key=True, autoincrement=True)
workflow_id = Column(Integer, ForeignKey("workflow_definitions.id", ondelete="CASCADE"), nullable=False)
version_number = Column(Integer, nullable=False) # 版本号,递增
version_name = Column(String(100), nullable=True) # 版本名称,如 "v2.0-添加并行审批"
is_active = Column(Boolean, default=False) # 是否为当前激活版本
# 版本快照 — 存储此版本完整的流程定义快照
steps_snapshot = Column(JSON, nullable=False)
flow_config_snapshot = Column(JSON, nullable=True)
# 变更说明
change_summary = Column(Text, nullable=True)
created_by = Column(Integer, ForeignKey("users.id"), nullable=True)
created_at = Column(DateTime, server_default=func.now())
# 关联
workflow = relationship("WorkflowDefinition", back_populates="versions")
def to_dict(self) -> dict:
return {
"id": self.id,
"workflow_id": self.workflow_id,
"version_number": self.version_number,
"version_name": self.version_name,
"is_active": self.is_active,
"change_summary": self.change_summary,
"steps_count": len(self.steps_snapshot) if self.steps_snapshot else 0,
"created_at": self.created_at.isoformat() if self.created_at else None,
}
class WorkflowStep(Base):
"""审批步骤定义 — 支持灵活的条件判断与动态审批人分配。"""
__tablename__ = "workflow_steps"
id = Column(Integer, primary_key=True, autoincrement=True)
workflow_id = Column(Integer, ForeignKey("workflow_definitions.id", ondelete="CASCADE"), nullable=False)
step_number = Column(Integer, nullable=False)
name = Column(String(100), nullable=False)
description = Column(Text, nullable=True)
# 步骤类型: serial(串行) / parallel(并行-会签) / any_one(或签) / condition(条件分支)
step_type = Column(String(20), nullable=False, default="serial")
# 审批人角色
approver_role = Column(String(20), nullable=True) # admin / dba / manager / creator
# 指定审批人ID列表(JSON数组)
approver_ids = Column(JSON, nullable=True)
# 审批人分配策略:
# {
# "type": "role|specific|leader|load_balance|creator|expression",
# "expression": "${ticket.creator.manager_id}", // 动态表达式
# "fallback_user_id": 1, // 无法解析时的 fallback
# "department_id": 2 // 指定部门
# }
approver_assignment = Column(JSON, nullable=True, default=dict)
is_required = Column(Boolean, default=True)
timeout_hours = Column(Integer, default=24)
auto_approve_on_timeout = Column(Boolean, default=False)
# ═══════════════════════════════════════════════════════════
# 条件判断配置(条件步骤使用)
# ═══════════════════════════════════════════════════════════
# 步骤准入条件(满足条件才进入此步骤):
# {
# "conditions": [
# {"field": "ticket.sql_type", "operator": "in", "value": ["ALTER", "DROP"]},
# {"field": "affected_rows", "operator": ">", "value": 1000}
# ],
# "logic": "AND"
# }
entry_conditions = Column(JSON, nullable=True)
# 步骤流转条件(满足条件才进入下一步,否则跳过后续步骤):
# {
# "conditions": [
# {"field": "current.result", "operator": "==", "value": "approved"}
# ],
# "on_false_action": "skip|reject|goto_step",
# "goto_step_number": 3
# }
transition_conditions = Column(JSON, nullable=True)
# ═══════════════════════════════════════════════════════════
# 字段权限配置
# ═══════════════════════════════════════════════════════════
# 此步骤中可查看/编辑的字段:
# {
# "visible_fields": ["ticket.title", "ticket.sql", "ticket.description"],
# "editable_fields": ["ticket.comment"],
# "masked_fields": ["ticket.password", "ticket.secret_key"]
# }
field_permissions = Column(JSON, nullable=True)
workflow = relationship("WorkflowDefinition", back_populates="steps")
def to_dict(self) -> dict:
return {
"id": self.id,
"workflow_id": self.workflow_id,
"step_number": self.step_number,
"name": self.name,
"description": self.description,
"step_type": self.step_type,
"approver_role": self.approver_role,
"approver_ids": self.approver_ids,
"approver_assignment": self.approver_assignment,
"is_required": self.is_required,
"timeout_hours": self.timeout_hours,
"auto_approve_on_timeout": self.auto_approve_on_timeout,
"entry_conditions": self.entry_conditions,
"transition_conditions": self.transition_conditions,
"field_permissions": self.field_permissions,
}
class TicketApproval(Base):
"""工单审批实例(运行时记录)。"""
__tablename__ = "ticket_approvals"
id = Column(Integer, primary_key=True, autoincrement=True)
ticket_id = Column(Integer, ForeignKey("tickets.id"), nullable=False, index=True)
# 关联的流程定义和版本
workflow_id = Column(Integer, ForeignKey("workflow_definitions.id"), nullable=True)
version_id = Column(Integer, ForeignKey("workflow_versions.id"), nullable=True)
step_number = Column(Integer, nullable=False)
step_name = Column(String(100), nullable=True)
step_type = Column(String(20), default="serial") # serial / parallel / any_one / condition
# 审批人信息
approver_id = Column(Integer, ForeignKey("users.id"), nullable=True)
approver_role = Column(String(20), nullable=True)
# 实际执行人(转交场景)
actual_approver_id = Column(Integer, ForeignKey("users.id"), nullable=True)
# 审批动作: pending / approved / rejected / transferred / skipped / auto_approved
action = Column(String(20), default="pending", index=True)
comment = Column(Text, nullable=True)
# 转交信息
transfer_from = Column(Integer, ForeignKey("users.id"), nullable=True)
transfer_reason = Column(Text, nullable=True)
# 审批时间记录
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
completed_at = Column(DateTime, nullable=True)
# 超时标记
is_timeout = Column(Boolean, default=False)
timeout_handled_at = Column(DateTime, nullable=True)
# 关联
workflow_def = relationship("WorkflowDefinition", back_populates="approvals")
approver = relationship("User", foreign_keys=[approver_id])
actual_approver = relationship("User", foreign_keys=[actual_approver_id])
def to_dict(self) -> dict:
return {
"id": self.id,
"ticket_id": self.ticket_id,
"workflow_id": self.workflow_id,
"step_number": self.step_number,
"step_name": self.step_name,
"step_type": self.step_type,
"approver_id": self.approver_id,
"approver_role": self.approver_role,
"approver_name": self.approver.display_name if self.approver else None,
"actual_approver_id": self.actual_approver_id,
"action": self.action,
"comment": self.comment,
"transfer_from": self.transfer_from,
"transfer_reason": self.transfer_reason,
"is_timeout": self.is_timeout,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
}
class WorkflowExecutionLog(Base):
"""流程执行日志 — 记录审批流程的完整执行轨迹,用于审计和问题排查。"""
__tablename__ = "workflow_execution_logs"
id = Column(Integer, primary_key=True, autoincrement=True)
workflow_id = Column(Integer, ForeignKey("workflow_definitions.id"), nullable=False, index=True)
ticket_id = Column(Integer, ForeignKey("tickets.id"), nullable=True, index=True)
step_id = Column(Integer, ForeignKey("workflow_steps.id"), nullable=True)
approval_id = Column(Integer, ForeignKey("ticket_approvals.id"), nullable=True)
# 事件类型:
# workflow_started / step_entered / step_completed / approved / rejected /
# transferred / skipped / timeout / escalated / workflow_completed / workflow_cancelled
event_type = Column(String(50), nullable=False, index=True)
# 事件详情
event_description = Column(Text, nullable=True)
# 执行人
actor_id = Column(Integer, ForeignKey("users.id"), nullable=True)
actor_name = Column(String(100), nullable=True)
# 上下文数据(快照):
# {
# "step_snapshot": {...},
# "approval_snapshot": {...},
# "decision_context": {...},
# "condition_results": [...]
# }
context_data = Column(JSON, nullable=True)
created_at = Column(DateTime, server_default=func.now(), index=True)
# 关联
workflow = relationship("WorkflowDefinition", back_populates="execution_logs")
def to_dict(self) -> dict:
return {
"id": self.id,
"workflow_id": self.workflow_id,
"ticket_id": self.ticket_id,
"step_id": self.step_id,
"approval_id": self.approval_id,
"event_type": self.event_type,
"event_description": self.event_description,
"actor_id": self.actor_id,
"actor_name": self.actor_name,
"context_data": self.context_data,
"created_at": self.created_at.isoformat() if self.created_at else None,
}
def __repr__(self) -> str:
return (
f"<WorkflowExecutionLog("
f"workflow={self.workflow_id}, ticket={self.ticket_id}, "
f"event='{self.event_type}', actor='{self.actor_name}')>"
)