如何用PHP项目实现用户自定义工作流?

wen java案例 2

本文目录导读:

如何用PHP项目实现用户自定义工作流?

  1. 目录导读
  2. 为什么需要自定义工作流?
  3. 核心设计思路:用“节点+连线”模拟流程
  4. 数据库表设计:一张表搞定所有流程
  5. 代码实战:PHP实现流程解析器
  6. 前端可视化:让用户拖拽配置工作流
  7. 常见问题与避坑指南(问答形式)

PHP项目实现用户自定义工作流的完整指南

目录导读

  1. 为什么需要自定义工作流?
  2. 核心设计思路:用“节点+连线”模拟流程
  3. 数据库表设计:一张表搞定所有流程
  4. 代码实战:PHP实现流程解析器
  5. 前端可视化:让用户拖拽配置工作流
  6. 常见问题与避坑指南(问答形式)

为什么需要自定义工作流?

传统OA系统里,审批流通常是写死在代码里的——要改流程就得改代码、重部署,但现实业务中,不同部门、不同场景的流程差异巨大。

  • 请假:普通员工→经理→HR(少于3天)
  • 报销:员工→财务→总监(超过5000元需总监)

用户自定义工作流的价值就在于:允许业务人员通过界面拖拽、配置规则,自由定义流程走向,无需开发介入


核心设计思路:用“节点+连线”模拟流程

一个工作流本质上是一个 有向图,我们将其抽象为三个对象:

对象 含义 例子
节点(Node) 流程中的一个步骤 开始、部门经理审批、结束
连线(Edge) 节点的跳转关系 经理通过→跳到财务审批
条件(Condition) 连线上的逻辑规则 金额>1000走A线,否则走B线

关键点:每个连线可以挂载一组条件(PHP数组存储),系统通过解析条件决定下一个节点是哪个。


数据库表设计:一张表搞定所有流程

为了适应任意流程,建议采用主子表结构(以下是MySQL示例):

-- 流程定义主表:每个业务流程对应一条记录
CREATE TABLE `workflow_definition` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL COMMENT '流程名称(如请假流程)',
  `config` json DEFAULT NULL COMMENT '存储完整的节点与连线结构',
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
-- 流程实例表:每一次发起的申请对应一条记录
CREATE TABLE `workflow_instance` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `def_id` int(11) NOT NULL COMMENT '关联流程定义',
  `current_node` varchar(50) NOT NULL COMMENT '当前所在节点ID',
  `data` json DEFAULT NULL COMMENT '表单提交的数据(如申请人、金额)',
  `status` tinyint(1) DEFAULT '0' COMMENT '0进行中 1通过 2驳回',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

设计亮点

  • 使用config字段存储完整的流程结构(JSON格式),读取后直接还原整个流程图。
  • current_node记录当前走到哪个节点,再配合configdata判断下一步。

代码实战:PHP实现流程解析器

核心逻辑在 “前进”方法中——当用户审核通过后,系统需要自动找到下一个节点,以下是伪代码示例(基于ThinkPHP 8):

class WorkflowEngine {
    /**
     * 执行流程前进
     * @param int $instanceId 流程实例ID
     * @param string $decision 审批结果(pass/reject)
     * @return mixed 下一个节点ID或错误
     */
    public function proceed($instanceId, $decision) {
        $instance = WorkflowInstance::find($instanceId);
        $def = WorkflowDefinition::find($instance->def_id);
        $config = json_decode($def->config, true);
        // 当前节点信息
        $currentNodeId = $instance->current_node;
        $edges = $config['edges'];  // 所有连线
        $nodes = $config['nodes'];  // 所有节点
        // 找出当前节点出发的所有连线
        $nextEdges = array_filter($edges, function($e) use ($currentNodeId) {
            return $e['from'] === $currentNodeId;
        });
        foreach ($nextEdges as $edge) {
            // 解析连线上的条件
            $condition = $edge['condition'] ?? [];
            if ($this->evaluateCondition($condition, $instance->data, $decision)) {
                // 条件满足,更新为下一个节点
                $instance->current_node = $edge['to'];
                $instance->save();
                return ['next_node' => $edge['to']];
            }
        }
        // 无满足条件的连线 → 流程结束
        return ['status' => 'finished'];
    }
    /**
     * 条件解析器:支持 > < == in 等运算符
     */
    private function evaluateCondition($condition, $formData, $decision) {
        if (empty($condition)) return true; // 无条件默认通过
        $field = $condition['field'];    // 如 amount
        $operator = $condition['op'];    // 如 >
        $value = $condition['value'];    // 如 1000
        $actual = $formData[$field] ?? null;
        switch ($operator) {
            case '>': return $actual > $value;
            case '==': return $actual == $value;
            case 'in': return in_array($actual, explode(',', $value));
            case 'decision': return $actual === $value; // 审批决策
            default: return false;
        }
    }
}

进阶技巧

  • 条件字段支持decision(审批结果)和任意表单字段(如amount)。
  • 可组合多个条件(AND / OR),只需在condition中增加logic字段。

前端可视化:让用户拖拽配置工作流

如果时间紧张,推荐集成开源前端库 FlowiseDrawflow(纯JavaScript,轻量级)。
实现思路:

  1. 管理员打开配置页面,通过拖拽面板添加“审批节点”“抄送节点”“条件节点”。
  2. 点击连线,在弹出框里填写条件规则(如“请假天数 > 3”)。
  3. 保存时,将前端产生的JSON对象直接存入workflow_definition.config字段。

示例JSON结构

{
  "nodes": [
    {"id": "start", "label": "开始", "type": "start"},
    {"id": "manager_approve", "label": "经理审批", "type": "approve"},
    {"id": "hr_approve", "label": "HR审批", "type": "approve"},
    {"id": "end", "label": "结束", "type": "end"}
  ],
  "edges": [
    {"from": "start", "to": "manager_approve", "label": "", "condition": {}},
    {"from": "manager_approve", "to": "hr_approve", "label": "通过", "condition": {"field": "decision", "op": "==", "value": "pass"}},
    {"from": "manager_approve", "to": "end", "label": "驳回", "condition": {"field": "decision", "op": "==", "value": "reject"}}
  ]
}

常见问题与避坑指南(问答形式)

Q1:如何防止用户配置死循环流程?
A:在保存流程配置时,后端做一个简单的图检测:判断是否存在从某个节点出发,经过若干连线又能回到该节点的路径(深度优先搜索),如果发现循环,返回错误提示“流程中存在循环依赖,请修正”。

Q2:同一个节点需要多人依次审批怎么办?
A:我们可以在节点定义中增加assignees字段(数组),例如["user1","user2"],当流程到达该节点时,先分配给user1;处理完成后,检查是否还有未处理的审批人,有则继续分配给下一个人,同时current_node保持不变(但记录当前已审批人数),完全通过后再跳转下一节点。

Q3:支持子流程(嵌套流程)吗?
A:可以,在节点类型中添加sub_process类型,并在节点配置中指向另一个workflow_definition的ID,后端遇到该类节点时,创建一个新的流程实例,并等待子流程结束后再继续主流程(通过回调或轮询状态实现)。

Q4:性能会不会很差?
A:核心性能瓶颈在于每次前进都需要解析JSON并遍历查找连线,对于中小型项目(日均处理500次以内)完全够用,如果需高并发,建议将流程配置缓存到Redis(使用def_id作为key),并采用MPM(消息队列)异步处理流程推进。

Q5:如何与现有PHP框架(如Laravel、Yii)集成?
A:只需将上述WorkflowEngine类移植到项目中,依赖注入数据库连接,前端可视化部分可独立成一个项目/插件,通过API与后端通信。

  • 路由POST /api/workflow/forward → 调用proceed()方法
  • 路由POST /api/workflow/start → 创建新实例,初始化current_node为开始节点(start


通过“节点+连线+条件”的抽象,PHP项目完全可以支撑灵活的用户自定义工作流,关键在于合理设计JSON存储结构,并编写健壮的条件解析引擎,建议先从最简单的线性流程做起,再逐步增加分支、会签、子流程等高级功能,这样既能快速上线,又能保持代码的可维护性。

抱歉,评论功能暂时关闭!