PHP项目如何实现用户自定义审批节点:从零构建灵活工作流引擎
📚 目录导读
- 为什么需要自定义审批节点?
- 核心设计思路与数据库架构
- 节点类型与规则引擎设计
- 前端交互与可视化配置
- 审批流转与状态机实现
- 权限控制与安全注意事项
- 性能优化与扩展性方案
- 常见问题与实战问答
为什么需要自定义审批节点?
在传统OA系统中,审批流程往往是固定的,请假3天内直属经理审批,超3天需总监审批”,但实际业务中,不同部门、不同职级、不同金额的报销、采购、合同审批流程差异极大。如果每次流程变更都需要开发修改代码,不仅周期长,更会带来运维灾难。

用户自定义审批节点的核心价值在于:
- 业务人员无需编程即可动态调整流程
- 适应多变的组织架构与业务规则
- 降低开发团队的重复工作与BUG风险
据Workflow Management Coalition统计,企业中约63%的审批流程需要定期调整,而自定义设计能力可将流程上线时间从数天缩短至几分钟。
核心设计思路与数据库架构
1 设计模式选型
PHP实现推荐采用 “责任链模式 + 状态机” 组合设计,每个审批节点视为一个处理单元,通过配置表定义链式顺序。
2 数据库核心表设计
-- 流程模板表
CREATE TABLE `workflow_template` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(100) COMMENT '模板名称',
`description` TEXT,
`status` TINYINT(1) DEFAULT 1 COMMENT '0禁用 1启用',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 节点配置表
CREATE TABLE `workflow_node` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`template_id` INT NOT NULL,
`node_name` VARCHAR(50) COMMENT '节点名称',
`node_order` SMALLINT COMMENT '排序序号',
`approver_type` ENUM('role','user','department','condition') DEFAULT 'role',
`approver_value` VARCHAR(500) COMMENT '审批者配置JSON',
`next_if_approved` INT DEFAULT NULL COMMENT '通过后下一节点ID',
`next_if_rejected` INT DEFAULT NULL COMMENT '驳回后下一节点ID',
`time_limit` INT DEFAULT 0 COMMENT '审批时限(小时)',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`template_id`) REFERENCES `workflow_template`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键设计说明:
approver_type支持角色(Role)、用户(User)、部门(Department)、条件(Condition)四种类型,赋予高度灵活性approver_value采用JSON存储,{"roles":[3,5]}表示需要角色ID为3和5的用户共同审批- 通过
next_if_approved / rejected支持分支流转(金额>10000走总监节点)
3 条件节点示例
{
"conditions": [
{"field": "amount", "operator": ">", "value": 5000, "action": "next_node_5"},
{"field": "amount", "operator": "<=", "value": 5000, "action": "next_node_3"}
],
"default_next": 4
}
节点类型与规则引擎设计
1 内置节点类型
| 节点类型 | 用途说明 | 典型场景 |
|---|---|---|
| 普通审批节点 | 单人/多人会签审批 | 部门经理审批 |
| 条件分支节点 | 根据表单字段自动路由 | 根据金额判断审批层级 |
| 抄送节点 | 仅通知不审批 | 知会财务总监 |
| 自动节点 | 系统自动通过(如无违规则跳过) | 预算合规检查 |
| 并行节点 | 同时发送给多人,全部通过才继续 | 跨部门联合审批 |
2 规则引擎实现
采用 “策略模式”,定义 RuleInterface:
interface RuleInterface {
public function evaluate(array $formData, array $config): bool;
}
class AmountRule implements RuleInterface {
public function evaluate(array $formData, array $config): bool {
return $formData['amount'] > $config['threshold'];
}
}
前端配置界面 允许用户通过下拉菜单选择规则类型,并输入参数(阈值、字段名等),后端序列化为JSON存储。
前端交互与可视化配置
1 拖拽式设计器
使用 GooFlow 或 vue-flow 实现可视化节点组装,用户可直接拖拽节点到画布,关联箭头方向,无需理解代码逻辑。
2 核心交互流程
- 左侧节点库选择节点类型(审批、条件、抄送)
- 中间画布拖拽放置,鼠标悬停显示配置按钮
- 右侧属性面板动态加载配置项(审批人选择/条件设置/超时时间)
- 保存时生成JSON结构,传给后端解析
示例JSON结构:
{
"nodes": [
{"id": "n1", "type": "start", "label": "发起申请"},
{"id": "n2", "type": "approval", "config": {"approverType":"role","roles":[2]}},
{"id": "n3", "type": "condition", "config": {"field":"amount","operator":">","value":10000}}
],
"edges": [
{"from": "n1", "to": "n2"},
{"from": "n2", "to": "n3"},
{"from": "n3", "to": "n4", "label": "通过(>10000)"},
{"from": "n3", "to": "n5", "label": "通过(≤10000)"}
]
}
审批流转与状态机实现
1 状态机设计
采用有限状态机(FSM),记录每个审批实例的当前状态:
class ApprovalFSM {
const INIT = 'init';
const PENDING = 'pending'; // 等待审批
const APPROVED = 'approved'; // 已通过
const REJECTED = 'rejected'; // 已驳回
const TERMINATED = 'terminated';// 流程终止
public function transition(string $currentState, string $action): string {
$transitions = [
self::INIT => ['submit' => self::PENDING],
self::PENDING => ['approve' => self::APPROVED, 'reject' => self::REJECTED],
self::APPROVED => ['continue' => self::PENDING], // 转到下一节点
];
return $transitions[$currentState][$action] ?? throw new \Exception('非法状态转换');
}
}
2 流转处理逻辑
当用户“通过”当前节点:
- 更新当前节点状态为
approved - 查询
next_if_approved获取下一节点ID - 如果是条件节点,执行规则引擎判断走哪个分支
- 创建新的待办任务分配给下一节点审批人
- 触发通知(邮件/站内信)
驳回处理:
支持两种模式:
- 退回发起人:直接重置状态为
init,发起人可修改后再次提交 - 退回上一节点:状态回到上一节点,上一节点审批人需重新处理
权限控制与安全注意事项
1 审批人权限校验
- 每个节点配置的审批人必须与后台用户/角色表关联
- 使用
Policy模式检查当前登录用户是否属于节点的审批人集合 - 关键安全措施:禁止普通用户通过篡改参数越权审批
2 数据隔离与防篡改
- 审批记录使用
不可变日志模式:一旦创建写入就无法修改 - 节点配置变更应记录版本号,审批中的流程引用旧版本配置
- 敏感字段(如金额)在审批过程中应建立快照以防止表单数据后期被修改
3 超时自动处理
配置 计划任务(CRON) 定期扫描超时的审批节点,根据配置自动转交或通过。
性能优化与扩展性方案
1 高并发场景应对
- 使用 队列(Redis + RabbitMQ) 处理通知发送与状态更新
- 审批表单关键字段建立索引(如
template_id,current_node_id) - 热点模板配置使用本地缓存+redis,减少MySQL查询
2 扩展性设计
- 插件机制:支持自定义审批策略(如会签比例计算规则)
- Webhook:在每个节点流转时触发HTTP回调,对接第三方系统(如ERP、钉钉)
- API 开放:提供RESTful API,允许外部系统提交审批或查询进度
3 监控与日志
- 记录每一个节点流转的完整日志:
node_id, action, user_id, timestamp, form_snapshot - 使用 ELK 或 阿里云SLS 实时分析审批效率瓶颈
常见问题与实战问答
Q1: 如何实现“会签”(多人需全部通过)?
A: 在节点配置中设置 approver_type=roles,表添加字段 pass_type(enum: 'all','any'),后端在 approved 前检查已通过人数是否达到预设比例(like 超半数),可使用 Redis计数器 记录当前节点已审批人数。
Q2: 节点配置更改后,已有审批流程是否受影响?
A: 最佳实践是 “版本化设计”,每个审批实例记录创建时引用的模板版本号(加 workflow_template_version 表),更新配置时生成新版本,旧流程继续按旧版本执行,新发起流程使用新版本。
Q3: 如何实现“加签”或“转审”?
A: 加签:在原节点后临时插入一个节点,配置额外审批人,转审:修改当前节点的 approver_value 为转审人,两者都需记录操作日志并通知原审批人。
Q4: 性能瓶颈通常出现在哪里?
A: 最常见的是 “审批人动态计算”,例如每笔报销的直属领导不同,如果每次流转都实时查询组织架构树,会造成慢查询,解决方案:在流程启动时预计算每个节点审批人并缓存到 workflow_instance_approver 表。
Q5: 如何与钉钉/飞书等办公系统集成?
A: 在节点流转时触发 webhook,发送审批卡片到指定群,可使用这些平台的开放API(如钉钉dingtalk.com→oapi.dingtalk.com),通过OAuth2.0获取调用凭证并推送消息,注意:需用户授权开放平台权限。
实现 PHP 项目的用户自定义审批节点,核心在于:灵活的数据库设计 + 可视化配置界面 + 健壮的状态机引擎,从实际项目经验看,建议优先考虑使用已有的开源工作流引擎(如 jflow),但若需深度定制,基于责任链模式的自主实现更为可控。
关键成功因素:
- 配置与执行分离(版本化管理)
- 支持条件分支与动态审批人计算
- 完善的审计日志与回滚机制
- 前端体验优先于后端复杂度
记住一点:用户自定义的程度越高,后台的约束逻辑就需要越精细,合理运用角色、部门、条件三种审批人类型,配合队列与缓存手段,即可在 PHP 体系中构建出媲美大型BPM系统的审批能力。