PHP项目如何实现用户自定义审批优化?

wen java案例 1

本文目录导读:

PHP项目如何实现用户自定义审批优化?

  1. 核心设计理念:从“代码逻辑”到“数据驱动”
  2. 数据库表结构设计(核心)
  3. PHP审批引擎核心实现
  4. 性能优化方案
  5. 前端配置界面思路
  6. 安全与最佳实践
  7. 可选的第三方库(PHP生态)

在PHP项目中实现用户自定义审批流程(工作流)的优化,核心在于将审批逻辑从硬编码中抽离,转变为可配置、可动态解析的数据结构

以下是实现和优化的完整思路,包含架构设计、数据库设计、代码实现及性能优化方案。


核心设计理念:从“代码逻辑”到“数据驱动”

  1. 规则配置化:不修改PHP代码,仅通过后台界面配置审批节点、条件、处理人。
  2. 引擎化:编写一个独立的“审批引擎”,负责解析配置、推进流程、处理分支。
  3. 事件驱动:使用钩子或事件系统触发审批,解耦业务代码与审批代码。

数据库表结构设计(核心)

需要几张核心表来承载自定义流程:

approval_flow (流程模版表)

字段 类型 说明
id int 主键
name varchar 流程名称(如:请假审批)
trigger_event varchar 触发事件(如:leave.create)
status tinyint 启用/禁用

approval_node (节点配置表)

字段 类型 说明
id int 主键
flow_id int 关联流程ID
node_name varchar 节点名称(如:直属上级审批)
node_type enum approver, condition, cc
assignee_type enum user, role, department_head, variable
assignee_value varchar 具体值(如用户ID、角色ID或变量名{creator.dept_leader}
order int 排序
timeout_hours int 超时时间(可空)

approval_node_condition (条件分支表 - 用于node_type=condition)

字段 类型 说明
id int
node_id int 关联父节点(条件节点)
condition_expression text 条件表达式,如 {amount} > 5000
next_node_id int 满足条件时跳转到哪个节点
default_next tinyint 是否默认分支(无满足条件时)

approval_instance (审批实例表 - 每一次申请)

字段 类型 说明
id int
flow_id int
business_type varchar 业务类型(如 leave
business_id int 业务数据ID
creator_id int 发起人
current_node_id int 当前等待的节点ID
status enum pending, approved, rejected, cancelled

approval_record (审批记录表)

字段 类型 说明
id int
instance_id int
node_id int
operator_id int 操作人
action enum approve, reject, transfer
comment text
operated_at datetime

PHP审批引擎核心实现

流程解析器

<?php
class ApprovalEngine
{
    // 获取当前节点
    public function getCurrentNode($instance) {
        $node = ApprovalNode::find($instance->current_node_id);
        return $node;
    }
    // 执行审批动作
    public function processAction($instance, $action, $user, $comment) {
        $this->addRecord($instance, $instance->current_node_id, $user, $action, $comment);
        if ($action == 'reject') {
            $instance->status = 'rejected';
            $instance->save();
            // 触发驳回事件
            Event::dispatch('approval.rejected', $instance);
            return;
        }
        // 查找下一个节点
        $nextNode = $this->resolveNextNode($instance);
        if ($nextNode == null) {
            // 流程结束
            $instance->status = 'approved';
            $instance->current_node_id = null;
            $instance->save();
            // 触发完成事件
            Event::dispatch('approval.completed', $instance);
        } else {
            // 推进到下一个节点
            $instance->current_node_id = $nextNode->id;
            $instance->save();
            // 通知下一节点审批人
            $this->notifyApprovers($nextNode, $instance);
        }
    }
    // 解析下一个节点(核心)
    private function resolveNextNode($instance) {
        $currentNode = ApprovalNode::find($instance->current_node_id);
        $nextNodes = ApprovalNode::where('flow_id', $instance->flow_id)
                                   ->where('order', '>', $currentNode->order)
                                   ->orderBy('order')
                                   ->get();
        foreach ($nextNodes as $node) {
            if ($node->node_type == 'condition') {
                // 检查条件分支
                $matched = $this->evaluateConditions($node, $instance);
                if ($matched) {
                    return $matched; // 返回条件跳转的节点
                }
                // 没有匹配则继续下一个节点
            } else {
                return $node; // 普通审批节点
            }
        }
        return null; // 流程结束
    }
    // 条件评估
    private function evaluateConditions($conditionNode, $instance) {
        // 获取业务数据(如请假金额)
        $businessData = $this->getBusinessData($instance->business_type, $instance->business_id);
        foreach ($conditionNode->branches as $branch) {
            // 使用表达式解析,建议用Symfony ExpressionLanguage
            $expression = $this->parseExpression($branch->condition_expression, $businessData);
            if ($expression) {
                return $branch->nextNode;
            }
        }
        // 没有匹配,如果有默认分支则走默认
        foreach ($conditionNode->branches as $branch) {
            if ($branch->default_next) {
                return $branch->nextNode;
            }
        }
        return null;
    }
}

条件表达式解析(安全方案)

避免使用eval(),推荐使用Symfony ExpressionLanguage组件:

composer require symfony/expression-language
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
$language = new ExpressionLanguage();
$data = ['amount' => 8000, 'department' => '研发部'];
$result = $language->evaluate('amount > 5000 and department == "研发部"', $data);
// $result = true

审批人动态解析

处理assignee_typevariable的情况:

public function getApprovers($node, $instance) {
    switch ($node->assignee_type) {
        case 'user':
            return User::whereIn('id', explode(',', $node->assignee_value))->get();
        case 'role':
            return User::where('role_id', $node->assignee_value)->get();
        case 'department_head':
            $creator = User::find($instance->creator_id);
            return User::where('department_id', $creator->department_id)
                       ->where('is_head', 1)
                       ->get();
        case 'variable':
            // 从业务数据中动态获取,如 {project.manager_id}
            $businessData = $this->getBusinessData($instance->business_type, $instance->business_id);
            $uid = $this->resolveVariable($node->assignee_value, $businessData);
            return User::find($uid);
        default:
            return collect();
    }
}

性能优化方案

缓存策略

  • 缓存流程配置:将approval_flowapproval_nodeflow_id缓存到Redis,减少数据库查询。
    // 获取流程配置
    public function getFlowConfig($flowId) {
        $cacheKey = "approval_flow:{$flowId}";
        return Cache::remember($cacheKey, 3600, function() use ($flowId) {
            return ApprovalFlow::with('nodes.branches')->find($flowId);
        });
    }

消息队列处理

  • 审批提交后,将processAction放入队列,避免同步阻塞。
  • 使用Laravel队列或RabbitMQ:
    dispatch(new ProcessApprovalJob($instanceId, $action, $userId));

索引优化

  • approval_record表的instance_idoperator_id添加复合索引。
  • approval_node表的flow_id+order添加唯一索引。

懒加载与预加载

  • 加载审批实例时,使用with('records', 'records.operator')避免N+1查询。

条件表达式预编译

  • 对于高频使用的流程,可以将表达式预编译(ExpressionLanguage->compile()),减少每次运行时的解释开销。

前端配置界面思路

提供一个可视化拖拽式流程设计器:

  1. 节点类型面板:审核节点、条件节点、抄送节点。
  2. 审批人设置UI:支持选择人员、角色、部门负责人、表单变量。
  3. 条件编辑器:自动根据业务字段生成下拉选择,然后拼接成表达式。
  4. 实时保存:每次拖拽或修改后自动保存至数据库(通过API)。

安全与最佳实践

  1. 权限隔离:不要让用户自由写PHP表达式,使用ExpressionLanguageallowed functions白名单限制。
  2. SQL注入防护:表达式中的值应从业务数据中安全提取,不要直接拼接。
  3. 超时与死锁:审批流程出现循环(A->B->A)时,要在引擎中设置最大深度检测。
  4. 审计日志:所有配置修改、审批操作记录到日志表。
  5. 并发控制:审批操作使用乐观锁(version字段)防止重复提交。

可选的第三方库(PHP生态)

特点 适用场景
symfony/workflow Symfony官方工作流组件 有状态机,适合简单固定流程
petru/workflow 轻量级,支持DSL 小型项目快速集成
自研引擎 完全定制 复杂业务、多分支、动态审批人

实现用户自定义审批优化的核心

  1. 数据驱动:把流程结构存入数据库。
  2. 引擎解耦:写一个纯逻辑的审批引擎,不依赖任何业务数据。
  3. 表达式安全解析:用表达式库处理条件分支。
  4. 缓存+队列:保证高并发下性能稳定。

如果你能提供更具体的业务需求(如审批节点数上限、是否需要会签/或签、是否需要并行审批等),我可以进一步细化方案。

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