本文目录导读:

在PHP项目中实现审批流(Approval Workflow)与签核节点(Sign-Off Nodes)是一个典型的中大型系统需求,下面我将从核心概念、流程设计场景、数据库表结构、PHP代码实现示例以及可用的PHP扩展库这几个方面为你详细拆解。
核心概念
| 概念 | 说明 |
|---|---|
| 工作流 | 一组按顺序或条件执行的审批步骤。 |
| 节点 | 工作流中的单个步骤(如“部门经理审批”)。 |
| 签核人 | 每个节点上具体负责审批的用户(可能是单个用户、角色或动态计算)。 |
| 动作 | 审批人执行的操作:同意、驳回、转签、加签等。 |
| 流转条件 | 决定节点走向的规则(金额 > 10000 需要总监审批)。 |
常见的审批场景
- 线性审批:A -> B -> C (如请假申请)。
- 会签:节点需要多人同时同意才能通过。
- 或签:节点只要有一人同意即可通过。
- 条件分支:满足特定条件跳转到不同节点。
- 动态指定:审批人由发起人指定或由上一个节点指定。
数据库表结构设计
这里采用“流程模板 + 流程实例 + 节点定义” 的设计模式,很通用且灵活。
-- 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 | 轻量、易于理解 | 适合业务不太复杂的场景 |
关键开发难点与建议
- 分支与条件判断:建议将条件表达式设计为JSON格式,内嵌简单的
$and,$or,$field > value规则,配合轻量级的Symfony ExpressionLanguage解析。 - 并发处理:多人同时审批时,使用数据库行锁(
SELECT ... FOR UPDATE) 防止重复流转。 - 驳回(退回):区别退回上一节点还是退回发起人,可在
workflow_nodes表中增加next_node_if_reject字段灵活扩展。 - 待办任务缓存:不要每次都从节点逻辑计算签核人,而是在创建/流转节点时提前生成待办任务表(UserTask)。
- 可视化拖拽:前端可使用Vue + Flowable或Ant Design Pro的流程图组件,后端对应解析JSON配置即可。