PHP项目审批流与签核节点

wen PHP项目 4

本文目录导读:

PHP项目审批流与签核节点

  1. 核心概念
  2. 常见的审批场景
  3. 数据库表结构设计
  4. 核心PHP逻辑实现
  5. 推荐的开源PHP项目与库
  6. 关键开发难点与建议

在PHP项目中实现审批流(Approval Workflow)与签核节点(Sign-Off Nodes)是一个典型的中大型系统需求,下面我将从核心概念、流程设计场景、数据库表结构、PHP代码实现示例以及可用的PHP扩展库这几个方面为你详细拆解。


核心概念

概念 说明
工作流 一组按顺序或条件执行的审批步骤。
节点 工作流中的单个步骤(如“部门经理审批”)。
签核人 每个节点上具体负责审批的用户(可能是单个用户、角色或动态计算)。
动作 审批人执行的操作:同意、驳回、转签、加签等。
流转条件 决定节点走向的规则(金额 > 10000 需要总监审批)。

常见的审批场景

  1. 线性审批:A -> B -> C (如请假申请)。
  2. 会签:节点需要多人同时同意才能通过。
  3. 或签:节点只要有一人同意即可通过。
  4. 条件分支:满足特定条件跳转到不同节点。
  5. 动态指定:审批人由发起人指定或由上一个节点指定。

数据库表结构设计

这里采用“流程模板 + 流程实例 + 节点定义” 的设计模式,很通用且灵活。

-- 1. 审批流程模板定义 (定义请假、报销等不同流程)
CREATE TABLE `workflow_templates` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL COMMENT '流程名称',
  `description` text COMMENT '描述',
  `is_active` tinyint(1) DEFAULT '1',
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
);
-- 2. 审批节点定义 (模板下的具体步骤)
CREATE TABLE `workflow_nodes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `template_id` int(11) NOT NULL COMMENT '关联模板',
  `node_name` varchar(100) NOT NULL COMMENT '节点名称, 如经理审批',
  `node_order` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
  `approval_type` enum('single','countersign','or_sign') NOT NULL COMMENT '审批类型: 单人/会签/或签',
  `approver_type` enum('role','user','dynamic','leader') NOT NULL COMMENT '签核人来源',
  `approver_value` varchar(255) DEFAULT NULL COMMENT '签核人值 (角色ID/用户ID/字段)',
  `next_node_if_pass` int(11) DEFAULT NULL COMMENT '通过后下一节点ID',
  `next_node_if_reject` int(11) DEFAULT NULL COMMENT '驳回后下一节点ID',
  `condition_expression` text COMMENT '流向条件 (JSON格式)',
  PRIMARY KEY (`id`),
  INDEX (`template_id`)
);
-- 3. 流程实例 (发起一次申请)
CREATE TABLE `workflow_instances` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `template_id` int(11) NOT NULL,
  `business_type` varchar(50) DEFAULT NULL COMMENT '业务类型, 如Leave, Expense',
  `business_id` int(11) NOT NULL COMMENT '业务主键ID',
  `initiator_id` int(11) NOT NULL COMMENT '发起人',
  `status` enum('pending','approved','rejected','cancelled') NOT NULL DEFAULT 'pending',
  `current_node_id` int(11) DEFAULT NULL COMMENT '当前待处理节点',
  `started_at` datetime DEFAULT CURRENT_TIMESTAMP,
  `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
);
-- 4. 签核记录 (每个节点每次签核记录)
CREATE TABLE `workflow_sign_records` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `instance_id` int(11) NOT NULL,
  `node_id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL COMMENT '签核人',
  `action` enum('approve','reject','redispatch','add_sign') NOT NULL COMMENT '动作',
  `comment` text COMMENT '意见',
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  INDEX (`instance_id`, `node_id`)
);

核心PHP逻辑实现

提交申请并启动流程

<?php
class WorkflowService
{
    /**
     * 启动一个流程实例
     * @param int $templateId 模板ID
     * @param string $businessType 业务类型
     * @param int $businessId 业务ID
     * @param int $initiatorId 发起人
     * @return int 实例ID
     */
    public function startWorkflow($templateId, $businessType, $businessId, $initiatorId)
    {
        // DB::beginTransaction();
        // 1. 获取模板的第一个节点
        $firstNode = DB::select(
            "SELECT * FROM workflow_nodes WHERE template_id = ? ORDER BY node_order ASC LIMIT 1",
            [$templateId]
        );
        if (!$firstNode) {
            throw new Exception('流程模板无审批节点');
        }
        // 2. 创建流程实例
        $instanceId = DB::insert('workflow_instances', [
            'template_id' => $templateId,
            'business_type' => $businessType,
            'business_id' => $businessId,
            'initiator_id' => $initiatorId,
            'status' => 'pending',
            'current_node_id' => $firstNode->id,
        ]);
        // 3. 生成待办任务
        $this->createPendingTasks($instanceId, $firstNode);
        // DB::commit();
        return $instanceId;
    }
}

执行审批动作

public function processSign($instanceId, $userId, $action, $comment)
{
    // 1. 获取当前实例状态
    $instance = DB::selectOne("SELECT * FROM workflow_instances WHERE id = ?", [$instanceId]);
    if ($instance->status !== 'pending') {
        throw new Exception('流程已结束');
    }
    // 2. 获取当前节点信息
    $currentNode = DB::selectOne("SELECT * FROM workflow_nodes WHERE id = ?", [$instance->current_node_id]);
    // 3. 检查用户是否有权限签核此节点 (核心校验)
    if (!$this->checkUserIsApprover($instance->current_node_id, $userId, $instance)) {
        throw new Exception('您无权审批此节点');
    }
    // 4. 记录签核记录
    DB::insert('workflow_sign_records', [
        'instance_id' => $instanceId,
        'node_id' => $instance->current_node_id,
        'user_id' => $userId,
        'action' => $action,
        'comment' => $comment,
    ]);
    // 5. 处理审批逻辑
    if ($action === 'reject') {
        // 驳回:流程结束 or 退回上一节点(根据配置)
        if ($currentNode->next_node_if_reject) {
            // 若有指定驳回流向,则流转到那里
            $this->moveToNode($instanceId, $currentNode->next_node_if_reject);
        } else {
            // 默认驳回流程结束
            DB::update('workflow_instances', ['status' => 'rejected', 'current_node_id' => null], $instanceId);
        }
    } elseif ($action === 'approve') {
        // 处理会签逻辑
        if ($currentNode->approval_type === 'countersign') {
            // 检查是否所有签核人都已通过
            if ($this->isAllCountersignApproved($instanceId, $currentNode->id)) {
                $this->moveToNextNode($instanceId, $currentNode);
            } else {
                // 等待其他人签核
                // 不作处理,状态不变
            }
        } else {
            // 单人签核或或签
            $this->moveToNextNode($instanceId, $currentNode);
        }
    }
}

流转到下一节点

private function moveToNextNode($instanceId, $currentNode)
{
    $nextNodeId = $currentNode->next_node_if_pass;
    if (!$nextNodeId) {
        // 无下一节点 => 流程完成
        DB::update('workflow_instances', ['status' => 'approved', 'current_node_id' => null], $instanceId);
        // 触发业务回调:通知发起人,修改业务状态
        $this->triggerBusinessCallback($instanceId, 'approved');
        return;
    }
    // 移到下一节点
    DB::update('workflow_instances', ['current_node_id' => $nextNodeId], $instanceId);
    // 获取下一节点详情
    $nextNode = DB::selectOne("SELECT * FROM workflow_nodes WHERE id = ?", [$nextNodeId]);
    $this->createPendingTasks($instanceId, $nextNode);
}

动态计算签核人

/**
 * 获取某个节点的签核人列表
 */
public function getApproversForNode($node, $instance)
{
    switch ($node->approver_type) {
        case 'role':
            // 根据角色获取用户
            return DB::select("SELECT user_id FROM role_user WHERE role_id = ?", [$node->approver_value]);
        case 'user':
            // 直接指定
            return [['user_id' => $node->approver_value]];
        case 'leader':
            // 动态:发起人的直属上级
            $initiatorId = $instance->initiator_id;
            $leader = $this->getLeaderByUserId($initiatorId);
            return $leader ? [['user_id' => $leader->id]] : [];
        case 'dynamic':
            // 字段动态值(如业务表中的字段)
            $businessData = $this->getBusinessData($instance->business_type, $instance->business_id);
            return [['user_id' => $businessData->{$node->approver_value}]];
        default:
            return [];
    }
}

推荐的开源PHP项目与库

不建议完全从零开发,以下成熟方案可直接复用或二次开发:

方案 适用性 技术特点
Laravel Workflow 对简单线性、条件审批友好 基于Symfony的Workflow组件,图形化配置稍弱
Approvall 专注于审批流程 提供UI界面,支持动态审批人,可结合Laravel Permission
Camunda BPM 企业级、复杂BPMN2.0流程 有PHP REST Client,后端基于Java,流程模型化能力强
FlowBase 轻量、易于理解 适合业务不太复杂的场景

关键开发难点与建议

  1. 分支与条件判断:建议将条件表达式设计为JSON格式,内嵌简单的$and, $or, $field > value规则,配合轻量级的Symfony ExpressionLanguage解析。
  2. 并发处理:多人同时审批时,使用数据库行锁(SELECT ... FOR UPDATE 防止重复流转。
  3. 驳回(退回):区别退回上一节点还是退回发起人,可在workflow_nodes表中增加next_node_if_reject字段灵活扩展。
  4. 待办任务缓存:不要每次都从节点逻辑计算签核人,而是在创建/流转节点时提前生成待办任务表(UserTask)
  5. 可视化拖拽:前端可使用Vue + FlowableAnt Design Pro的流程图组件,后端对应解析JSON配置即可。

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