PHP项目怎么实现用户自定义审批机器人?

wen java案例 1

本文目录导读:

PHP项目怎么实现用户自定义审批机器人?

  1. 核心概念:从“硬编码”到“流程引擎”
  2. 总体架构方案
  3. 步骤一:流程设计器(前端实现)
  4. 步骤二:流程定义存储(后端数据库)
  5. 步骤三:流程引擎(核心PHP逻辑)
  6. 步骤四:集成机器人(钉钉/飞书/企业微信)
  7. 安全性、性能与扩展性考量
  8. 简化实现:基于“角色/用户”的硬编码 + 配置表
  9. 总结建议

这是一个比较复杂的工程问题,在PHP项目中实现“用户自定义审批机器人”,核心在于将审批流程的“逻辑”与“代码”解耦,让用户通过图形化界面或配置来定义流程,而不是让开发人员为每个流程写死代码。

下面我为你梳理一个完整的技术实现思路和架构方案,分为几个核心环节。

核心概念:从“硬编码”到“流程引擎”

传统方式:if (请假天数 > 3) { 找总监审批; } else { 找经理审批; } 用户自定义方式:用户自己画一个流程图:“请假 -> 条件判断天数 > 3 -> 总监审批 -> 结束” 或 “请假 -> 经理审批 -> 结束”。

总体架构方案

一个完整的审批机器人系统通常包含以下模块:

  1. 流程设计器(前端):用户画流程图的界面。
  2. 流程定义存储(后端):存储用户设计的流程图数据(JSON/XML)。
  3. 流程引擎(核心后端):解析流程图,驱动流程流转,调用通知、审批等动作。
  4. 机器人/通知模块(集成):连接钉钉、飞书、企业微信或邮件,发送审批任务和通知。

流程设计器(前端实现)

用户需要能“画”出审批流程,这通常使用可视化拖拽库实现。

  • 技术选型:推荐使用 bpmn-js (集成了BPMN 2.0标准,比较专业) 或国内更开箱即用的 LogicFlowGGEditor
  • 要存储的数据结构:设计器最终会生成一个JSON对象,用于描述整个流程,一个简单的自定义格式例子如下:
// 一个简单的请假流程定义
{
  "id": "leave_flow_001",
  "name": "普通员工请假流程",
  "nodes": [
    { "id": "start", "type": "start", "name": "发起申请", "assignee": "user:{{initiator}}" },
    { "id": "seq_1", "type": "condition", "name": "判断天数", "expression": "{{leave_days}} <= 3" },
    { "id": "task_manager", "type": "approval", "name": "经理审批", "assignee": "role:manager" },
    { "id": "task_director", "type": "approval", "name": "总监审批", "assignee": "role:director" },
    { "id": "end", "type": "end", "name": "结束" }
  ],
  "edges": [
    { "from": "start", "to": "seq_1" },
    { "from": "seq_1", "to": "task_manager", "condition": true },
    { "from": "seq_1", "to": "task_director", "condition": false },
    { "from": "task_manager", "to": "end" },
    { "from": "task_director", "to": "end" }
  ]
}

Node节点类型startendcondition (条件/排他网关), approval (审批人节点), notify (通知节点) 等。

流程定义存储(后端数据库)

将用户设计的流程图JSON存入数据库。

  • 表结构设计
    • workflow_definitions 表:
      • id name description definition_json (TEXT, 存储上面的JSON) status (draft/active/archived) created_at updated_at created_by
    • workflow_instances 表: (每发起一次审批,生成一条记录)
      • id definition_id initiator_user_id form_data (TEXT, 表单填写的数据) status (pending/approved/rejected/cancelled) created_at updated_at
    • workflow_tasks 表: (审批节点实例)
      • id instance_id node_id (对应流程定义里的node id) name assignee_user_id status (pending/approved/rejected) comment operated_at

流程引擎(核心PHP逻辑)

这是最难啃的骨头,你需要一个PHP服务来解析流程定义JSON,并根据当前状态驱动流程到下一个节点。

核心逻辑(伪代码):

<?php
class WorkflowEngine
{
    public function advanceWorkflow($instanceId, $action, $userId, $comment)
    {
        // 1. 加载实例和工作流定义
        $instance = $this->getWorkflowInstance($instanceId);
        $definition = $this->getWorkflowDefinition($instance->definition_id);
        $flow = json_decode($definition->definition_json, true);
        // 2. 找到当前在等待的审批任务 (pending状态的task)
        $currentTask = $this->getCurrentPendingTask($instanceId);
        if ($currentTask == null) { /* 流程已结束 */ return; }
        // 3. 处理当前节点的动作 (通过/拒绝)
        if ($action == 'approve') {
            // 更新当前task为approved
            $this->updateTaskStatus($currentTask->id, 'approved', $userId, $comment);
            // 4. 寻找下一个节点
            $nextNodes = $this->findNextNodes($flow, $currentTask->node_id, $instance->form_data);
            foreach ($nextNodes as $nextNode) {
                if ($nextNode['type'] == 'end') {
                    // 流程结束,更新instance状态为approved
                    $this->updateInstanceStatus($instanceId, 'approved');
                    $this->sendNotification('审批通过', $instance);
                } else if ($nextNode['type'] == 'approval') {
                    // 创建新的审批任务 (pending)
                    $assignee = $this->resolveAssignee($nextNode['assignee'], $instance->form_data);
                    $this->createTask($instanceId, $nextNode['id'], $nextNode['name'], $assignee);
                    $this->sendNotification('新审批任务', $task);
                } else if ($nextNode['type'] == 'condition') {
                    // 条件节点自动判断,继续向前走
                    $this->advanceToNextNodeAfterCondition($instance, $flow, $nextNode, $instance->form_data);
                }
            }
        } else if ($action == 'reject') {
            // 拒绝逻辑:更新当前task为rejected,然后实例状态变为rejected。
            $this->updateTaskStatus($currentTask->id, 'rejected', $userId, $comment);
            $this->updateInstanceStatus($instanceId, 'rejected');
            $this->sendNotification('审批被拒绝', $instance);
        }
    }
    private function findNextNodes($flow, $currentNodeId, $formData)
    {
        $nextNodes = [];
        foreach ($flow['edges'] as $edge) {
            if ($edge['from'] == $currentNodeId) {
                // 如果是条件边,需要计算表达式的值
                if ($edge['condition'] !== null) {
                    $conditionResult = $this->evaluateExpression($edge['expression'], $formData);
                    if ($conditionResult == $edge['condition']) {
                        $nextNodes[] = $this->getNodeById($flow, $edge['to']);
                    }
                } else {
                    $nextNodes[] = $this->getNodeById($flow, $edge['to']);
                }
            }
        }
        return $nextNodes;
    }
    // 该函数解析用户定义的表达式,"{{leave_days}} <= 3"
    private function evaluateExpression($expression, $formData)
    {
        // 简单实现:用正则替换占位符,eval (有安全风险,需严格控制输入)
        // 生产环境建议使用 symfony/expression-language 或自定义沙盒解析器
        $search = ['{{leave_days}}'];
        $replace = [$formData['leave_days']];
        $resolvedExpression = str_replace($search, $replace, $expression);
        // 注意:这里使用了eval,需要确保expression来源安全且不能执行任意PHP代码
        return eval("return $resolvedExpression;");
    }
}

集成机器人(钉钉/飞书/企业微信)

机器人负责推送消息给审批人,并在消息中携带操作按钮(同意/拒绝)。

  1. 构建消息卡片(Rich Text / 消息卡片)
    • “您有一个审批任务待处理”
    • 申请人、申请事由、表单数据摘要。
    • 按钮同意拒绝
  2. 处理按钮回调
    • 用户在机器人消息中点击“同意”或“拒绝”。
    • 平台(钉钉/飞书)会发一个 HTTP回调请求 到你的后端。
    • 你定义的回调URL需要接收这个请求,解析出 实例ID任务ID操作用户ID
    • 调用 步骤三 中的 WorkflowEngine::advanceWorkflow() 方法。

安全性、性能与扩展性考量

  1. 表达式安全eval() 是危险的,对于用户自定义的条件(如 {{days}} > 3),建议使用专门的数学表达式库(如 php-math-expression)或写一个简单的解析器来安全计算,绝对不要直接用 eval 执行用户输入的字符串
  2. 错误处理:流程引擎必须有完善的日志和异常处理,如通知发送失败,需有重试机制。
  3. 并发锁:处理回调时(特别是异步消息),必须对实例ID加锁(如Redis锁),防止重复操作或状态错乱。
  4. 可视化设计器:建议直接使用成熟的BPMN设计器库,不要自己从头造轮子,成本极高且易用性差。
  5. 低代码方案:如果项目复杂度高,可考虑集成现成的流程引擎(如:Camunda BPMFlowable 的PHP客户端,但不推荐,因为复杂)或使用国内云厂商的低代码平台(如微搭、简道云等,但这会脱离PHP生态)。

简化实现:基于“角色/用户”的硬编码 + 配置表

如果不想实现通用的流程引擎,只想让用户“自定义”审批人,那么可以简化方案:

  • 用户配置:在后台创建一个审批规则表:approval_rules ( trigger_type , trigger_value , approver_roles , level )。
  • trigger_type='leave_days', trigger_value='>3', approver_roles='["director"]'
  • 代码中:用户提交表单后,根据规则表查找到对应的审批人,生成任务。
  • 优点:实现简单,适合80%的固定审批场景。
  • 缺点:无法实现并发、会签、条件分支、动态路由等复杂流程。

总结建议

  1. 小步快跑:如果业务不太复杂,先采用简化实现(配置表),快速上线验证。
  2. 确定核心:如果确需通用引擎,先确保 流程设计器流程引擎 这两个核心模块的接口和数据结构设计清晰,不急于接入机器人。
  3. 复用生态:PHP不是这类复杂工作流引擎的首选语言(Java有Activiti等),如果团队没有大坑经验,建议考虑使用Node.js或Go实现核心引擎,PHP只做API的胶水层和后台管理,或者使用BPMN标准的第三方服务(如Zeebe/Flowable Cloud),PHP调用其API。
  4. 测试先行:流程引擎的状态机逻辑非常容易出错,务必为每个分支、每个回调、每个节点类型编写充分的单元测试和集成测试。

如果你能提供更具体的场景(只需要一个简单的“接力审批”还是需要“会签、或签、分支”),我可以给出更针对性的代码结构和示例。

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