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

wen java案例 1

本文目录导读:

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

  1. 核心设计思想:配置驱动 + 状态机
  2. 数据库设计(核心三张表)
  3. PHP核心逻辑实现
  4. 前端配置界面设计思路
  5. 扩展与最佳实践

这是一个很有价值的问题,用PHP实现用户自定义审批自动化,核心在于将审批流程的定义(元数据)与执行逻辑分离,用户可以动态配置流程,而PHP引擎负责解析和执行。

下面我将提供一个分层、可扩展的设计方案,包含核心原理、数据表设计和关键代码逻辑。

核心设计思想:配置驱动 + 状态机

  1. 配置驱动:审批流程不是写死在代码里的,而是存储在数据库或配置文件中。
  2. 状态机:每个审批节点(步骤)都是一个状态,用户的操作(同意/驳回)触发状态的流转。

数据库设计(核心三张表)

用户自定义审批需要动态的、非硬编码的流程结构,通常使用 单表不循环(Adjacency List)嵌套集 来存储流程节点。

approval_flows (审批流程定义表)

存储用户创建的每一个不同的审批流程模板。

字段 类型 说明
id INT (PK) 主键
name VARCHAR 流程名称,如“报销审批V2”
description TEXT 流程描述
created_by INT 创建者用户ID
status TINYINT 1=启用, 0=停用
created_at DATETIME

flow_nodes (流程节点表)

定义流程中的每一步。

字段 类型 说明
id INT (PK) 主键
flow_id INT (FK) 关联 approval_flows.id
node_name VARCHAR 节点名称,如“部门主管审批”
node_type TINYINT 1=单人审批, 2=会签(所有审批人同意), 3=或签(任一审批人同意)
approver_type VARCHAR 审批人类型:user(指定用户), role(指定角色), manager(申请人的上级)
approver_value VARCHAR 审批人值:用户ID、角色ID、或 manager
order_index INT 节点排序,数字越小越靠前
created_at DATETIME

approval_tickets (审批工单表)

记录一个具体的审批请求实例。

字段 类型 说明
id INT (PK) 主键
flow_id INT (FK) 使用的流程模板ID
applicant_id INT 申请人ID
content TEXT (JSON格式存储表单数据)
current_node_id INT 当前待审批的节点ID (空表示流程结束)
status TINYINT 0=待审批, 1=审批中, 2=已通过, 3=已驳回, 4=已撤销
created_at DATETIME

approval_logs (审批日志表)

记录每一步的操作历史。

字段 类型 说明
id INT (PK) 主键
ticket_id INT (FK) 关联 approval_tickets.id
node_id INT 操作的节点ID
operator_id INT 操作人ID
action TINYINT 1=同意, 2=驳回
comment TEXT 审批意见
created_at DATETIME

PHP核心逻辑实现

假设我们使用Laravel或ThinkPHP等主流框架,关键服务类如下:

创建审批工单(发起审批)

// ApprovalService.php
public function submitTicket($flowId, $applicantId, $title, $content) {
    // 1. 获取流程定义
    $flow = ApprovalFlow::findOrFail($flowId);
    // 2. 获取第一个节点
    $firstNode = FlowNode::where('flow_id', $flowId)
                         ->orderBy('order_index', 'asc')
                         ->first();
    if (!$firstNode) {
        throw new \Exception("流程未定义任何节点");
    }
    // 3. 创建工单,状态为“待审批”,current_node_id设为第一个节点
    $ticket = ApprovalTicket::create([
        'flow_id' => $flowId,
        'applicant_id' => $applicantId,
        'title' => $title,
        'content' => json_encode($content),
        'status' => 0, // 待审批
        'current_node_id' => $firstNode->id,
    ]);
    // 4. 创建日志
    ApprovalLog::create([
        'ticket_id' => $ticket->id,
        'node_id' => null,
        'operator_id' => $applicantId,
        'action' => 0, // 提交
        'comment' => '提交申请',
    ]);
    // 5. 发送通知(通知第一个节点的审批人)
    $this->notifyApprovers($ticket, $firstNode);
    return $ticket;
}

核心:审批操作(同意/驳回)

这是最关键的方法,实现了流程的流转。

public function processApproval($ticketId, $currentUserId, $action, $comment = '') {
    // 1. 获取工单和当前节点
    $ticket = ApprovalTicket::findOrFail($ticketId);
    $currentNode = FlowNode::findOrFail($ticket->current_node_id);
    // 2. 验证权限:当前用户是否是该节点的审批人?
    if (!$this->isApprover($currentUserId, $currentNode, $ticket)) {
        throw new \Exception("您不是当前节点的审批人");
    }
    // 3. 记录日志
    ApprovalLog::create([
        'ticket_id' => $ticket->id,
        'node_id' => $currentNode->id,
        'operator_id' => $currentUserId,
        'action' => $action, // 1=同意, 2=驳回
        'comment' => $comment,
    ]);
    // 4. 根据操作类型处理
    if ($action == 2) { // 驳回
        // 状态改为已驳回
        $ticket->status = 3;
        $ticket->current_node_id = null;
        $ticket->save();
        // 通知申请人
        $this->notifyApplicant($ticket);
        return ['status' => 'rejected'];
    }
    // 5. 同意:逻辑处理
    // 5.1 如果是“会签”节点,检查是否所有审批人都已批准
    if ($currentNode->node_type == 2) { // 会签
        // 获取该节点的所有审批人
        $allApprovers = $this->getNodeApprovers($currentNode, $ticket);
        // 获取已经同意的审批人
        $approvedUsers = ApprovalLog::where('ticket_id', $ticket->id)
                                     ->where('node_id', $currentNode->id)
                                     ->where('action', 1)
                                     ->pluck('operator_id')
                                     ->toArray();
        // 如果还有审批人未同意,则等待,不流转到下一节点
        if (count($approvedUsers) < count($allApprovers)) {
            return ['status' => 'waiting_for_other_approvers'];
        }
        // 否则,会签完成,继续向下流转
    }
    // 5.2 获取下一个节点
    $nextNode = FlowNode::where('flow_id', $ticket->flow_id)
                        ->where('order_index', '>', $currentNode->order_index)
                        ->orderBy('order_index', 'asc')
                        ->first();
    if ($nextNode) {
        // 有下一节点:更新工单状态,继续流转
        $ticket->current_node_id = $nextNode->id;
        $ticket->status = 1; // 审批中
        $ticket->save();
        // 通知下一节点的审批人
        $this->notifyApprovers($ticket, $nextNode);
        return ['status' => 'approved', 'next_node' => $nextNode->node_name];
    } else {
        // 没有下一节点:流程结束,最终通过
        $ticket->status = 2; // 已通过
        $ticket->current_node_id = null;
        $ticket->save();
        // 通知申请人流程结束
        $this->notifyApplicant($ticket, 'approved');
        return ['status' => 'final_approved'];
    }
}

辅助方法:获取审批人

private function getNodeApprovers($node, $ticket) {
    switch ($node->approver_type) {
        case 'user':
            // 直接指定用户列表(用逗号分隔)
            return explode(',', $node->approver_value);
        case 'role':
            // 根据角色ID查询用户
            return User::where('role_id', $node->approver_value)->pluck('id')->toArray();
        case 'manager':
            // 获取申请人的上级(假设 User 表有 manager_id 字段)
            $applicant = User::find($ticket->applicant_id);
            return [$applicant->manager_id];
        default:
            return [];
    }
}
private function isApprover($userId, $node, $ticket) {
    $approvers = $this->getNodeApprovers($node, $ticket);
    return in_array($userId, $approvers);
}

更灵活的方式:基于工作流引擎

对于复杂场景(条件分支、并行网关、循环),建议引入轻量级状态机库工作流引擎(PHP有 Symfony WorkflowProcessMaker 的开源组件)。

使用状态机库(symfony/workflow)的优势

  • 状态转移图可视化
  • 自动实现Guard(条件检查)
  • 事件监听(进入节点、离开节点等)
// 使用 Symfony Workflow 定义
$definition = new Definition(
    ['draft', 'dept_approve', 'hr_approve', 'final_approve', 'approved', 'rejected'],
    [
        new Transition('submit', 'draft', 'dept_approve'),
        new Transition('dept_approve', 'dept_approve', 'hr_approve', ['guard' => 'isDeptApproved']),
        new Transition('hr_reject', 'dept_approve', 'rejected'),
        // ...
    ]
);
$workflow = new Workflow($definition);
$workflow->apply($ticket, 'submit'); // 触发转换

前端配置界面设计思路

提供一个可视化拖拽或表单配置界面,让用户无需写代码就能搭建流程。

配置界面元素

  1. 节点类型:审批节点、抄送节点、条件分支(满足条件走A,否则走B)。
  2. 审批人设置
    • 指定用户(单选/多选)
    • 按角色(部门经理、HR)
    • 按组织架构(申请人上级、直属主管)
    • 动态公式({applicant.department.manager}
  3. 审批策略
    • 单人审批(一人同意即通过)
    • 会签(所有人同意)
    • 或签(任意一人同意)
  4. 节点属性:超时自动转交、是否允许加签、驳回方式(退回上一级/退回发起人)。

前端存储:配置完成后,将节点JSON结构保存到 approval_flows 表的 nodes_json 字段中,避免每次解析多表关联。


扩展与最佳实践

  1. 条件分支

    • flow_nodes 中添加 condition_expression 字段,如 {amount} > 5000
    • processApproval 中,获取下一节点前,解析条件表达式,决定走哪条分支。
  2. 超时自动处理

    • 使用Cron定时任务(如Laravel的Schedule):每分钟扫描 approval_tickets,查找 current_node_id 不为空且超过24小时未更新的工单,执行预定义动作(自动同意/转交/驳回)。
  3. 性能优化

    • 如果流程节点非常多(如超过20步),避免每次审批都重新查询整个流程树,可以将流程的“下个节点”信息缓存到Redis。
    • 使用队列(Queue)处理通知和日志写入,避免阻塞用户操作。
  4. 安全性

    • 严格校验 action 参数(只允许1或2)。
    • 使用事务(DB::transaction)包裹提交和审批逻辑,防止数据不一致。
    • 不要直接暴露 node_id 给前端,使用加密后的令牌。

实现用户自定义审批自动化,PHP项目建议采用 “配置(数据库/JSON)+ 状态机引擎” 的架构,重点在于:

  • 存储层:设计支持动态节点的数据表结构(flows + nodes)。
  • 逻辑层:实现 submitprocess 两个核心方法,关键点是解析 approver_type 动态获取审批人,并计算下一个节点。
  • 展示层:提供一个可视化配置界面,将流程定义转化为用户可理解的节点图。

这种方案灵活、可扩展,既能处理简单的“单人顺序审批”,也能支持会签、条件分支等复杂业务场景。

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