本文目录导读:

PHP项目实现用户自定义工作流的完整指南
目录导读
为什么需要自定义工作流?
传统OA系统里,审批流通常是写死在代码里的——要改流程就得改代码、重部署,但现实业务中,不同部门、不同场景的流程差异巨大。
- 请假:普通员工→经理→HR(少于3天)
- 报销:员工→财务→总监(超过5000元需总监)
用户自定义工作流的价值就在于:允许业务人员通过界面拖拽、配置规则,自由定义流程走向,无需开发介入。
核心设计思路:用“节点+连线”模拟流程
一个工作流本质上是一个 有向图,我们将其抽象为三个对象:
| 对象 | 含义 | 例子 |
|---|---|---|
| 节点(Node) | 流程中的一个步骤 | 开始、部门经理审批、结束 |
| 连线(Edge) | 节点的跳转关系 | 经理通过→跳到财务审批 |
| 条件(Condition) | 连线上的逻辑规则 | 金额>1000走A线,否则走B线 |
关键点:每个连线可以挂载一组条件(PHP数组存储),系统通过解析条件决定下一个节点是哪个。
数据库表设计:一张表搞定所有流程
为了适应任意流程,建议采用主子表结构(以下是MySQL示例):
-- 流程定义主表:每个业务流程对应一条记录 CREATE TABLE `workflow_definition` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '流程名称(如请假流程)', `config` json DEFAULT NULL COMMENT '存储完整的节点与连线结构', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB; -- 流程实例表:每一次发起的申请对应一条记录 CREATE TABLE `workflow_instance` ( `id` int(11) NOT NULL AUTO_INCREMENT, `def_id` int(11) NOT NULL COMMENT '关联流程定义', `current_node` varchar(50) NOT NULL COMMENT '当前所在节点ID', `data` json DEFAULT NULL COMMENT '表单提交的数据(如申请人、金额)', `status` tinyint(1) DEFAULT '0' COMMENT '0进行中 1通过 2驳回', PRIMARY KEY (`id`) ) ENGINE=InnoDB;
设计亮点:
- 使用
config字段存储完整的流程结构(JSON格式),读取后直接还原整个流程图。 - 用
current_node记录当前走到哪个节点,再配合config和data判断下一步。
代码实战:PHP实现流程解析器
核心逻辑在 “前进”方法中——当用户审核通过后,系统需要自动找到下一个节点,以下是伪代码示例(基于ThinkPHP 8):
class WorkflowEngine {
/**
* 执行流程前进
* @param int $instanceId 流程实例ID
* @param string $decision 审批结果(pass/reject)
* @return mixed 下一个节点ID或错误
*/
public function proceed($instanceId, $decision) {
$instance = WorkflowInstance::find($instanceId);
$def = WorkflowDefinition::find($instance->def_id);
$config = json_decode($def->config, true);
// 当前节点信息
$currentNodeId = $instance->current_node;
$edges = $config['edges']; // 所有连线
$nodes = $config['nodes']; // 所有节点
// 找出当前节点出发的所有连线
$nextEdges = array_filter($edges, function($e) use ($currentNodeId) {
return $e['from'] === $currentNodeId;
});
foreach ($nextEdges as $edge) {
// 解析连线上的条件
$condition = $edge['condition'] ?? [];
if ($this->evaluateCondition($condition, $instance->data, $decision)) {
// 条件满足,更新为下一个节点
$instance->current_node = $edge['to'];
$instance->save();
return ['next_node' => $edge['to']];
}
}
// 无满足条件的连线 → 流程结束
return ['status' => 'finished'];
}
/**
* 条件解析器:支持 > < == in 等运算符
*/
private function evaluateCondition($condition, $formData, $decision) {
if (empty($condition)) return true; // 无条件默认通过
$field = $condition['field']; // 如 amount
$operator = $condition['op']; // 如 >
$value = $condition['value']; // 如 1000
$actual = $formData[$field] ?? null;
switch ($operator) {
case '>': return $actual > $value;
case '==': return $actual == $value;
case 'in': return in_array($actual, explode(',', $value));
case 'decision': return $actual === $value; // 审批决策
default: return false;
}
}
}
进阶技巧:
- 条件字段支持
decision(审批结果)和任意表单字段(如amount)。 - 可组合多个条件(AND / OR),只需在
condition中增加logic字段。
前端可视化:让用户拖拽配置工作流
如果时间紧张,推荐集成开源前端库 Flowise 或 Drawflow(纯JavaScript,轻量级)。
实现思路:
- 管理员打开配置页面,通过拖拽面板添加“审批节点”“抄送节点”“条件节点”。
- 点击连线,在弹出框里填写条件规则(如“请假天数 > 3”)。
- 保存时,将前端产生的JSON对象直接存入
workflow_definition.config字段。
示例JSON结构:
{
"nodes": [
{"id": "start", "label": "开始", "type": "start"},
{"id": "manager_approve", "label": "经理审批", "type": "approve"},
{"id": "hr_approve", "label": "HR审批", "type": "approve"},
{"id": "end", "label": "结束", "type": "end"}
],
"edges": [
{"from": "start", "to": "manager_approve", "label": "", "condition": {}},
{"from": "manager_approve", "to": "hr_approve", "label": "通过", "condition": {"field": "decision", "op": "==", "value": "pass"}},
{"from": "manager_approve", "to": "end", "label": "驳回", "condition": {"field": "decision", "op": "==", "value": "reject"}}
]
}
常见问题与避坑指南(问答形式)
Q1:如何防止用户配置死循环流程?
A:在保存流程配置时,后端做一个简单的图检测:判断是否存在从某个节点出发,经过若干连线又能回到该节点的路径(深度优先搜索),如果发现循环,返回错误提示“流程中存在循环依赖,请修正”。
Q2:同一个节点需要多人依次审批怎么办?
A:我们可以在节点定义中增加assignees字段(数组),例如["user1","user2"],当流程到达该节点时,先分配给user1;处理完成后,检查是否还有未处理的审批人,有则继续分配给下一个人,同时current_node保持不变(但记录当前已审批人数),完全通过后再跳转下一节点。
Q3:支持子流程(嵌套流程)吗?
A:可以,在节点类型中添加sub_process类型,并在节点配置中指向另一个workflow_definition的ID,后端遇到该类节点时,创建一个新的流程实例,并等待子流程结束后再继续主流程(通过回调或轮询状态实现)。
Q4:性能会不会很差?
A:核心性能瓶颈在于每次前进都需要解析JSON并遍历查找连线,对于中小型项目(日均处理500次以内)完全够用,如果需高并发,建议将流程配置缓存到Redis(使用def_id作为key),并采用MPM(消息队列)异步处理流程推进。
Q5:如何与现有PHP框架(如Laravel、Yii)集成?
A:只需将上述WorkflowEngine类移植到项目中,依赖注入数据库连接,前端可视化部分可独立成一个项目/插件,通过API与后端通信。
- 路由
POST /api/workflow/forward→ 调用proceed()方法 - 路由
POST /api/workflow/start→ 创建新实例,初始化current_node为开始节点(start)
通过“节点+连线+条件”的抽象,PHP项目完全可以支撑灵活的用户自定义工作流,关键在于合理设计JSON存储结构,并编写健壮的条件解析引擎,建议先从最简单的线性流程做起,再逐步增加分支、会签、子流程等高级功能,这样既能快速上线,又能保持代码的可维护性。