本文目录导读:

实现用户自定义审批人功能是工作流系统的核心,在 PHP 项目中,通常需要结合动态表单、角色/用户体系和流程引擎来实现。
以下是几种从简单到复杂的设计方案,以及核心代码思路:
核心设计思路
核心在于“确定”这个动作,即:谁来决定下一个节点的审批人?
- 由申请人指定(最常见的需求):表单提交时,有一个“选择审批人”的下拉框。
- 由上一级审批人指定(转审/加签):当前审批人可以选择指定下一步的审批人。
- 基于规则引擎(高级):根据表单金额、部门等字段,自动匹配一个审批人。
- 动态选择:在流程中,审批人需要从组织架构中选择若干人(会签或或签)。
数据库驱动 + 简单流程节点(适用于中小型项目)
这是最通用、最容易实现的方式,将审批人的选择和数据存储绑定。
数据库表设计
-- 审批流程定义表
CREATE TABLE `approval_flow` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(255) COMMENT '流程名称,如请假审批',
`status` TINYINT COMMENT '状态'
);
-- 流程节点表 (存储每一步的配置和“谁来选”的逻辑)
CREATE TABLE `approval_node` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`flow_id` INT,
`node_name` VARCHAR(100) COMMENT '节点名称,如 直属上级审批',
`node_order` INT COMMENT '节点顺序',
`assign_type` ENUM('fixed', 'applicant_self', 'last_user', 'role_based', 'dynamic_selector')
COMMENT '指定方式: fixed=固定人, applicant_self=申请人自选, last_user=上级指定, dynamic_selector=动态选择',
`assign_value` TEXT COMMENT '根据assign_type存储值,如角色ID或用户ID列表',
`is_countersign` TINYINT DEFAULT 0 COMMENT '是否为会签(需要所有人同意)'
);
-- 审批实例表 (发起一个申请)
CREATE TABLE `approval_request` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`flow_id` INT,
`applicant_id` INT, VARCHAR(255),
`status` TINYINT,
`form_data` JSON COMMENT '表单提交的数据',
`created_at` DATETIME
);
-- 审批记录表 (记录每一步的审批人、状态)
CREATE TABLE `approval_record` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`request_id` INT,
`node_id` INT,
`assign_type` VARCHAR(20),
`assign_value` TEXT,
`actual_approver_id` INT COMMENT '实际指定的审批人ID',
`status` TINYINT DEFAULT 0 COMMENT '0待处理 1同意 2驳回',
`comment` TEXT,
`created_at` DATETIME
);
-- 用户与角色表 (假设已有)
CREATE TABLE `user` ( `id` INT, `name` VARCHAR(50), `role_id` INT, `manager_id` INT );
CREATE TABLE `role` ( `id` INT, `role_name` VARCHAR(50) );
PHP 核心逻辑代码
步骤 A:前端表单(申请人视角)
当assign_type = applicant_self时,前端加载该节点的选项。
// html/approval_form.php
<?php
// 假设当前流程节点ID是 $current_node_id
$node = getNodeById($current_node_id);
if ($node['assign_type'] == 'applicant_self') {
// 读取 assign_value,可能是 "role_id:2" 表示角色ID为2的用户
// 或者 "department" 表示同部门用户
$possible_users = getUsersByAssignConfig($node['assign_value']);
echo '<select name="approver_id">';
foreach ($possible_users as $user) {
echo "<option value=\"{$user['id']}\">{$user['name']}</option>";
}
echo '</select>';
}
?>
步骤 B:后端逻辑(处理提交流程) 当申请人提交表单时,保存自定义的审批人。
// process_start.php
<?php
$request_id = createApprovalRequest($_POST);
$flow_id = $_POST['flow_id'];
$applicant_id = $_SESSION['user_id'];
// 获取流程的第一个节点
$node = getFirstNode($flow_id);
// 关键逻辑:根据节点配置,决定谁是审批人
$approver_id = null;
switch ($node['assign_type']) {
case 'fixed':
$approver_id = $node['assign_value']; // 直接取配置的固定用户ID
break;
case 'applicant_self':
// 从表单提交的数据中获取用户选择的审批人
$approver_id = $_POST['approver_id'];
break;
case 'role_based':
// 根据表单数据动态查询,金额>1000 选经理,否则选主管
$current_data = json_decode($request['form_data'], true);
if ($current_data['amount'] > 1000) {
$role_id = $node['assign_value']; // 可能是经理角色ID
$approver_id = getRoleManager($role_id, $applicant_id);
}
break;
case 'last_user': // 上级指定:这个节点由上一个审批人接力选择
// 此时不在提交时处理,而是在上一节点审批通过时处理
break;
default:
throw new Exception("未知的指定类型");
}
// 创建审批记录
createApprovalRecord($request_id, $node['id'], $node['assign_type'], $approver_id, 'pending');
echo "提交成功,等待审批";
?>
基于角色的动态指派(更灵活)
如果不想让用户直接选择某人,而是希望根据发起人的信息自动匹配,可以用此方案。
场景:用户提交请假单,系统自动将审批人指定为“用户的直属上级”。
代码实现示例
// process_auto_assign.php
<?php
function getDynamicApprover($node_config, $applicant_id) {
// 假设 node_config 中定义了 rule: 'manager', 'department_head', 'role_id'
switch ($node_config['rule']) {
case 'manager':
// 查询申请人的上级
$user = getUserById($applicant_id);
return $user['manager_id'];
break;
case 'department_head':
// 查询申请人的部门负责人
$user = getUserById($applicant_id);
$dept_id = $user['department_id'];
return getDeptHead($dept_id);
break;
case 'role':
// 查询具有特定角色的用户(如"财务总监")
// 可能还需要根据部门维度筛选
$role_id = $node_config['role_id'];
$user = getUserById($applicant_id);
return getRoleUserInDepartment($role_id, $user['department_id']);
break;
case 'last_approver_select':
// 这个不在这里处理,由上个节点的审批人在UI界面选择
return null;
break;
}
}
?>
灵活转审与加签(高级)
很多情况下,审批人需要能够转交给其他人(转审)或增加额外审批人(加签)。
实现思路:
- 转审:当前审批人A在操作界面选择“转审”,并选择B,后端在
approval_record中,将A的状态置为“已转审”,并新创建一条记录,actual_approver_id = B,status = pending。 - 加签:当前审批流程结束后,额外新增一个审批节点,需要修改
approval_node表,新增一个临时节点ID,并插入新的approval_record。
转审代码片段
// transfer_approve.php
<?php
$current_record_id = $_POST['record_id'];
$new_approver_id = $_POST['new_approver_id'];
// 1. 将当前记录标记为已转审
updateRecord($current_record_id, ['status' => 'transferred']);
// 2. 创建一条新记录,指向新的审批人
$current_record = getRecordById($current_record_id);
createNewRecord([
'request_id' => $current_record['request_id'],
'node_id' => $current_record['node_id'], // 节点保持不变
'actual_approver_id' => $new_approver_id,
'status' => 'pending',
'comment' => '来自: ' . getCurrentUserName() . ' 的转审'
]);
echo json_encode(['success' => true]);
?>
使用现成的流程引擎(推荐复杂项目)
如果项目很复杂(多分支、会签、条件路由、催办、驳回重审),建议不要自己手写,而是集成一个轻量级的工作流引擎。
推荐 PHP 工作流引擎:
- WorkflowCore PHP (仿 .NET WorkflowCore):可定义步骤、事件、复杂路由。
- Symfony Workflow + Guard:适合状态机类型的审批流。
- Ruro (第三方):轻量级,数据库驱动。
- 自己封装一个简单的状态机类:如果流程节点不超10个,且分支少,用状态机模式结合工厂模式会更清晰。
使用状态机模式的 Demo:
<?php
// 简单状态机示例
class ApprovalStateMachine {
private $transitions = [
'pending' => ['approve' => 'manager_approved', 'reject' => 'rejected', 'transfer' => 'pending'],
'manager_approved' => ['approve' => 'hr_approved', 'reject' => 'rejected'],
'hr_approved' => ['approve' => 'completed', 'reject' => 'rejected'],
'rejected' => []
];
public function apply($current_state, $action, $params = []) {
if (!isset($this->transitions[$current_state][$action])) {
throw new Exception("不允许的状态转换: {$current_state} -> {$action}");
}
$next_state = $this->transitions[$current_state][$action];
// 在执行转换前,处理"谁来审批"的逻辑
if ($action == 'approve' && $current_state == 'pending') {
// 检查是否需要从表单中提取下一个审批人
$this->assignNextApprover($params);
}
return $next_state;
}
private function assignNextApprover($params) {
// 根据 $params 或表单数据,动态设定下一个审批人
// 写入数据库
}
}
?>
总结建议
| 项目复杂度 | 推荐方案 | 优点 | 缺点 |
|---|---|---|---|
| 简单 (5个节点内,固定审批人+申请人自选) | 方案一 (数据库驱动+表单字段) | 实现快,代码量少,逻辑直观 | 扩展性差,分支路由写死在代码里 |
| 中等 (需根据部门/角色/金额自动匹配) | 方案二 (角色动态指派) | 自动化程度高,无需用户选择 | 需要维护良好的组织架构/角色数据 |
| 复杂 (会签、加签、子流程、驳回任意节点) | 方案四 (集成流程引擎) | 稳定、功能全面(如催办、超时处理) | 学习成本高,可能过于重量级 |
| 临时需求 (如某个审批人想转给别人) | 方案三 (转审/加签) | 灵活处理异常情况 | 作为补充功能,不能作为主流选择 |
最佳实践: 如果你的项目是给内部员工用的(如OA、ERP),方案一 + 方案二的组合通常是最好的起点:
- 80%的固定流程用方案二自动派发。
- 20%需要特殊处理(如请假代班人审批)用方案一让申请人自选。
如果是SaaS产品给不同客户用(他们自定义流程),则必须使用流程引擎(如设计一个拖拽式流程设计器,后端用状态机或WorkflowCore驱动)。