本文目录导读:

在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配置即可。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播