本文目录导读:

用PHP项目高效实现流程会签与或签的完整指南
目录导读
- 核心概念解析:什么是会签与或签?
- 为什么要用PHP实现流程审批?
- 数据库表结构设计(关键步骤)
- 核心逻辑代码实现(含流程图)
- 常见问题问答(Q&A)
- 性能优化与安全建议
- 扩展至微服务架构的思考
核心概念解析:什么是会签与或签?
在业务流程审批中,会签(Parallel Approval)指多个审批人必须全部同意,流程才能进入下一节点;而或签(Any Approval)则只需任意一人通过即可,这两者是企业OA、财务报销、合同审批中最常见的节点逻辑。
真实案例:某公司采购订单审批中,部门经理和财务总监采用“会签”确保双重把关;而普通差旅报销中的主管审批采用“或签”,只要直属或上级通过即生效。
为什么要用PHP实现流程审批?
- 轻量级部署:PHP无需额外JVM环境,适合中小型企业快速搭建。
- 生态丰富:结合Laravel、ThinkPHP等框架,自带ORM、队列、事件系统,可快速实现复杂状态机。
- 成本可控:相比Activiti(Java)或Camunda,PHP版本更易维护。
但需注意:PHP强项不在超高并发引擎,若需支撑数万级节点并行,建议底层使用Go或Java引擎,PHP仅作为API层。
数据库表结构设计(必须掌握)
假设我们采用 “流程实例-节点-审批记录” 三层模型,关键表如下(以MySQL为例):
CREATE TABLE `wf_process` ( `id` int(11) NOT NULL AUTO_INCREMENT, `process_name` varchar(100) NOT NULL COMMENT '流程名称', `creator_id` int(11) NOT NULL, `status` tinyint(1) DEFAULT 0 COMMENT '0:待开始 1:运行中 2:结束 3:作废', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB; CREATE TABLE `wf_node` ( `id` int(11) NOT NULL AUTO_INCREMENT, `process_id` int(11) NOT NULL, `node_name` varchar(50) NOT NULL COMMENT '节点名称', `node_type` tinyint(1) DEFAULT 0 COMMENT '0:普通节点 1:会签 2:或签', `related_user_ids` text COMMENT '审批人ID列表,逗号分隔', `sort_order` int(3) DEFAULT 0 COMMENT '节点顺序', `status` tinyint(1) DEFAULT 0 COMMENT '0:待处理 1:已完成', PRIMARY KEY (`id`), KEY `idx_process` (`process_id`) ) ENGINE=InnoDB; CREATE TABLE `wf_approve_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `node_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, `action` tinyint(1) DEFAULT 0 COMMENT '0:待审核 1:通过 2:驳回', `remark` text, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_node_user` (`node_id`,`user_id`) ) ENGINE=InnoDB;
设计精华:
node_type字段决定了节点审批模式。- 会签需统计“全部用户已通过”;或签只需“至少一人通过”。
- 驳回操作通常直接终止流程实例(具体业务可调整)。
核心逻辑代码实现
我们将采用 PHP + Laravel 框架实现一个简洁的审批调度器,核心方法如下:
<?php
namespace App\Services;
use App\Models\WfNode;
use App\Models\WfApproveRecord;
use App\Models\WfProcess;
use App\Exceptions\FlowException;
class FlowService
{
/**
* 处理审批动作
* @param int $nodeId 节点ID
* @param int $userId 审批人ID
* @param int $action 1:通过 2:驳回
* @param string $remark 备注
* @return bool
* @throws FlowException
*/
public function handleAction(int $nodeId, int $userId, int $action, string $remark = ''): bool
{
\DB::beginTransaction();
try {
$node = WfNode::lockForUpdate()->findOrFail($nodeId);
$process = $node->process;
// 校验节点状态
if ($node->status != 0) {
throw new FlowException('该节点已处理完毕');
}
// 写入审批记录
$record = new WfApproveRecord([
'node_id' => $nodeId,
'user_id' => $userId,
'action' => $action,
'remark' => $remark
]);
$record->save();
// 判断是否需要跳过此节点
if ($this->checkNodeComplete($node)) {
// 标记节点完成
$node->status = 1;
$node->save();
// 推进到下一节点或完成流程
$this->advanceToNextNode($process);
}
\DB::commit();
return true;
} catch (\Exception $e) {
\DB::rollBack();
throw new FlowException('审批处理失败:' . $e->getMessage());
}
}
/**
* 检查节点是否完成(核心判断逻辑)
*/
private function checkNodeComplete(WfNode $node): bool
{
$actionCount = WfApproveRecord::where('node_id', $node->id)
->whereIn('action', [1, 2])
->count();
$totalUsers = count(explode(',', $node->related_user_ids));
// 或签:至少一人通过
if ($node->node_type == 2) { // 或签
$approvedCount = WfApproveRecord::where('node_id', $node->id)
->where('action', 1)
->count();
return $approvedCount >= 1 || $actionCount >= $totalUsers;
}
// 会签:全部用户均已操作
if ($node->node_type == 1) { // 会签
return $actionCount >= $totalUsers;
}
// 普通节点:单人审批
return $actionCount >= 1;
}
/**
* 推进到下一节点
*/
private function advanceToNextNode(WfProcess $process): void
{
$nextNodes = WfNode::where('process_id', $process->id)
->where('status', 0)
->orderBy('sort_order')
->get();
if ($nextNodes->isEmpty()) {
// 流程结束
$process->status = 2;
$process->save();
}
// 否则由外部调度器处理通知下一节点审批人
}
}
核心思想:通过 checkNodeComplete() 方法,根据 node_type 动态判断节点是否满足触发条件,而不是硬编码if-else。
常见问题问答(Q&A)
Q1:会签时,若有人“驳回”,是否需要立即终止流程?
A:这取决于业务需求,建议配置化:可在节点表增加reject_break字段,值为1时立即终止流程,0则等待全部人完成后再判断最终结果(如按多数原则)。
Q2:PHP处理高并发审批(如秒杀场景)需要注意什么?
A:务必使用行级锁(如代码中lockForUpdate()),防止同一节点多次触发推进逻辑,同时可引入Redis队列异步处理通知,避免数据库瓶颈。
Q3:当审批人数量动态变化(如跨部门会签)如何设计表结构?
A:将related_user_ids改为关联表wf_node_users,支持动态增删用户,并在checkNodeComplete中查询wf_approve_record的action状态,灵活判断。
Q4:如何实现“条件路由”(比如金额超过5000需会签)?
A:在节点表增加condition_expression字段,存储JSON格式条件,调度器先解析条件表达式,若匹配则启用会签,否则走普通节点,条件 {"amount_gt": 5000}。
性能优化与安全建议
- 索引优化:为
node_id、user_id、process_id建立联合索引,提升查询速度。 - 避免长事务:
handleAction方法中事务尽量短,仅包裹核心写操作,读取操作可放到事务外。 - 凭证校验:审批接口必须验证用户身份与节点权限,防止越权操作(如未授权用户通过会签)。
- 使用观察者模式:在节点完成后触发事件(如通知、邮件),确保解耦。
扩展至微服务架构的思考
当前PHP实现适合独立系统,若需集成企业级平台,建议将流程引擎抽象为独立服务(如通过REST API暴露),上游应用只需调用POST /api/flow/submit、POST /api/flow/approve等接口,此时PHP可作为网关,而核心引擎使用Go或Java处理高并发节点。
请记住:流程引擎核心不是代码,而是逻辑的抽象,无论会签或或签,都遵循“状态机+事件驱动”模型,上述PHP实现已为扩展成通用引擎打下了扎实基础。
本文由资深PHP工程师基于真实项目经验编写,涵盖数据库设计、核心逻辑、性能优化及常见陷阱,自动符合搜索引擎SEO语义化结构。