PHP项目如何实现流程会签?从架构设计到代码实战全解析
目录导读
什么是流程会签?核心场景与难点
会签是指在一个审批流程中,需要多个审批人共同审批,且所有审批人(或指定比例)同意后,流程才能进入下一环节。

典型场景:
- 合同审批:法务、财务、业务负责人三个人必须全部通过。
- 采购报销:部门经理、财务总监、总经理中任意两人同意即可。
实现难点:
- 需要管理“多人同时审批”的状态(独立投票 vs 累计投票)。
- 结果计算逻辑复杂(一票否决”“半数通过”)。
- 在前端需要展示“谁已批、谁待批、谁拒绝”的状态。
会签的四种常见模式与选择策略
| 模式名称 | 规则描述 | 应用场景 |
|---|---|---|
| 全部通过 | 所有审批人必须同意,否则驳回 | 高合规性流程(如财务报销) |
| 任意通过 | 任意一人同意即通过 | 快速审批(如请假) |
| 比例通过 | 超过指定比例(如三分之二)同意 | 集体决策(如评标) |
| 一票否决 | 只要一人拒绝,流程结束 | 安全审核(如代码上线) |
选择建议:
- 如果流程要求“责任共担”,使用“全部通过”。
- 如果流程追求效率,使用“任意通过”或“比例通过”。
数据库表设计:支撑灵活会签的基石
会签需要的核心数据表如下(仅展示关键字段):
表1:wf_node(节点定义表)
CREATE TABLE `wf_node` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `node_name` VARCHAR(50) COMMENT '节点名称(如“部门会签”)', `strategy` TINYINT COMMENT '1:全部通过 2:任意通过 3:比例通过', `pass_ratio` DECIMAL(3,2) COMMENT '当strategy=3时,通过比例(如0.5)', `required_voters` TEXT COMMENT '审批人列表(JSON格式:[1,2,3])' );
表2:wf_task(每个审批人的投票记录)
CREATE TABLE `wf_task` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `node_id` INT COMMENT '关联节点ID', `voter_id` INT COMMENT '审批人ID', `vote_action` TINYINT COMMENT '0:待审批 1:同意 2:拒绝', `vote_time` DATETIME );
表3:wf_process(流程实例状态)
CREATE TABLE `wf_process` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `current_node_id` INT COMMENT '当前节点ID', `status` TINYINT COMMENT '0:进行中 1:通过 2:驳回', `create_time` DATETIME );
设计要点:
required_voters存储审批人ID列表(JSON)而非分表记录,减少JOIN。wf_task的vote_action用整数表示状态,便于快速统计。
PHP实现会签流转的核心逻辑
假设使用ThinkPHP 6框架,以下代码演示“全部通过”模式的会签处理:
<?php
namespace app\workflow\logic;
class SignLogic
{
/**
* 处理审批人的投票
* @param int $processId 流程ID
* @param int $voterId 审批人ID
* @param int $action 1:同意 2:拒绝
*/
public function vote($processId, $voterId, $action)
{
// 1. 创建投票记录
$task = WfTask::create([
'process_id' => $processId,
'voter_id' => $voterId,
'vote_action' => $action,
'vote_time' => date('Y-m-d H:i:s')
]);
// 2. 获取当前节点配置
$process = WfProcess::find($processId);
$node = WfNode::find($process->current_node_id);
// 3. 统计当前节点所有投票
$votes = WfTask::where([
'process_id' => $processId,
'node_id' => $process->current_node_id
])->select();
$totalVoters = count(json_decode($node->required_voters, true));
$agreeCount = 0;
$rejectCount = 0;
foreach ($votes as $v) {
switch ($v->vote_action) {
case 1: $agreeCount++; break;
case 2: $rejectCount++; break;
}
}
// 4. 根据策略判断节点是否结束
switch ($node->strategy) {
// 全部通过:所有投票完成后,必须全部同意
case 1:
if (count($votes) < $totalVoters) return '等待其他审批人投票';
if ($rejectCount > 0) {
$process->status = 2; // 驳回
} else {
$process->status = 1; // 通过
}
break;
// 任意通过:只要有1人同意即通过
case 2:
if ($agreeCount > 0) {
$process->status = 1;
} elseif ($rejectCount > 0) {
$process->status = 2;
}
break;
// 比例通过:计算是否达到比例
case 3:
$currentRate = $agreeCount / $totalVoters;
if ($rejectCount > 0) {
// 拒绝优先?按业务定,这里设拒绝即否决
$process->status = 2;
} elseif ($currentRate >= $node->pass_ratio) {
$process->status = 1;
}
break;
}
// 5. 更新流程状态
if (in_array($process->status, [1,2])) {
$process->save();
// 进入下一节点或结束流程...
}
return '投票完成';
}
}
关键点解析:
- 使用
count($votes)判断是否已集齐所有审批人投票。 - 根据
strategy选择不同的通过逻辑,便于扩展。 - 拒绝动作的优先级可以根据业务调整(例如拒绝票数达到一定数量才驳回)。
高频问答:手把手解决你的实施困惑
Q1:会签过程中,用户如何看到“谁已审批、谁等待审批”?
A:查询当前节点的 wf_task 表,关联用户表后,将结果分组:
vote_action=0的为待审批。vote_action=1的为同意。vote_action=2的为拒绝。
返回前端后,用不同颜色图标展示即可。
Q2:如果审批人离职,正在进行的会签如何处理?
A:方案一(推荐):在节点配置 required_voters 时,使用角色ID而非用户ID,流程运行时动态查询角色下的人员,角色人员变更后,未投票的自动转为新人员处理。
方案二:管理员手动跳转节点(预留“转交”功能)。
Q3:比例会签中,如果投票人数未达到总人数,怎么处理?
A:常见的策略是:
- 等待所有投票:必须全部投完才计算(适合10人以下)。
- 超时中断:设置超时时间(如48小时),超时未投的按“弃权”处理(弃权票可算作同意或否决,按业务定义)。
Q4:PHP处理高并发会签投票,会不会出现数据重复?
A:需要在 wf_task 表对 (process_id, node_id, voter_id) 加唯一索引,防止同一审批人重复投票,同时数据库操作使用事务(Db::transaction())。
流程会签的核心在于“多票状态计算”与“灵活的策略支持”,通过合理的表结构设计,以及在PHP代码中将“策略”作为抽离的配置参数,你可以轻松实现支持全部通过、一票否决、比例通过等模式的会签系统。
实践中,建议先从小规模的“全部通过”模式开始,再逐步扩展其他策略,如需完整源码案例,可以在本地搭建一个测试库模拟流程执行,验证上述逻辑的准确性。
提示:本文所使用的域名
example.org仅作示例,实际部署时请替换为你自己的项目域名。