工作流很少有让人满意的,即便是国内用的比较多的jbpm,用起来也会觉得很便扭。再加上PHP中没有什么好用的工作流,于是干脆自己设计一个,设计的原则如下:
1 根据80/20原则,只使用wfmc模型中最符合自身应用的20%功能
2 充分吸收国内使用jbpm开发BOSS中遇到的问题,工作流引擎只负责参数的收集和流程的流转,具体和业务的控制,交给每个流程定制的控制类去实现。
3 表单采用简单的html+控制标签的方法实现
4 权限和模板引擎,以及其它辅助函数直接使用办公系统自带的框架
5 充分利用PHP语言的特点,流程设计是基于数据库的,程序上使用OO设计,但采用重对象的方法
6 不把可视化设计流程的工作交给最终客户,而且由设计时完成,因此不考虑流程版本更新的问题
一、 工作流数据表设计
tbl_workflow_defination :工作流定义表
defination_id | 流程 id | |
defination_name | 流程名称 | |
defination_handler | 流程处理辅助文件,每个工作流一个文件 | 自定义处理文件,及其对象。例如 workflow-proporsal-handler.php ,其中定义对象 proposal |
tbl_workflow_node :流程结点步骤表
node_id | 结点 id | |
defination_id | 流程 id | |
node_index | 结点序号 | 结点的 step |
node_name | 结点名称 | |
node_type | 结点类型 | 1 人为决策, 2 自动处理 ( 直接执行 execute_function) , 3 等待外部响应 (例如外部 WS 触发 ),4 分支, 5 汇总 6结束结点(此结点执行时候自动终止进程) |
init_function | 流程初始函数 | |
run_function | 流程运行函数 | |
save_function | 流程保存函数 | |
transit_function | 流程流转函数 | |
prev_node_index | 前结点序号 | 例如 1 。开始结点没有 执行前,通过此来校验一下流程 |
next_node_index | 后结点序号 | 例如 [ 同意 ]3,[ 不同意 ]4 。尾结点或要结束的结点没有,若没有,直接调用 end |
executor | 执行角色,组,人 | role[1,2] group[1,2] user[1,2],为空由运行时决定 |
execute_type | 执行类型 | 0 需所有人执行 1 只需一人执行 |
remind | 提醒 | 0 不提醒 1 邮件 2 短信 3 邮件和短信 |
field | 可编辑的字段 | name,content |
max_day | 最长时间 ( 天 ) |
tbl_workflow_process :流程执行进程表
process_id | 进程 id | |
defination_id | 流程 id | |
process_desc | 进程描述 | 显示在我的工作台中 |
context | 上下文 | 存放上下文变量 , 例如业务表的 id |
current_node_index | 当前结点序号 | |
start_time | 流程启动时间 | 如遇分支、汇合显示为: 1 =》 3,4 =》 3,5 =》 6 |
finish_time | 流程完成时间 | |
state | 状态 | 1 运行 2 结束 |
start_user | 发起人 | 发起人,用于显示自己的流程 |
tbl_workflow_thread :流程执行线程表
thread_id | 线程 id | |
process_id | 进程 id | |
process_desc | 进程描述 | |
node_id | 结点 id | |
node_name | 结点名称 | |
executor | 执行人 | |
start_time | 线程生成时间 | |
receive_time | 线程接收时间 | |
finish_time | 线程完成时间 | |
max_time | 结点规定的最长时间 | |
state | 状态 | 0 未接收 1 已接收 2已处理 |
二、 常见流程
人工决策
领导传阅
部门领导审批
填写表单
结束
放弃
提交
同意
重填(退回)
不同意
完成
外部响应
发送支付信息
接收支付成功响应(外部 WS 触发该流程)
三、PHP 设计
运行的函数由结点在设计时候决定,如果没有设定,就使用默认的函数。利用了 PHP 语言的以下特性
<?php
class Foo
{
function Variable ()
{
$name = 'Bar' ;
$this -> $name (); // This calls the Bar() method
}
function Bar ()
{
echo "This is Bar" ;
}
}
$foo = new Foo ();
$funcname = "Variable" ;
$foo -> $funcname (); // This calls $foo->Variable()
?>
使用前可以用 method_exists 来检查。
WorkflowService.php
WorkflowService
$defination
$process
$node
$thread
$input 用户输入的和流程有关的变量
list_defination()
{
}
init_process(defination_id)
{ global user;
取得 $defination ,得到业务的 handler, 例如 WorkflowProposalHandler
建立 $process 行记录
}
start_process()
{ 调用 WorkflowProposalHandler->start($process)// 新建业务对象,并把业务类的参数例如 proposal_id 放到 $process[‘context’] 里面
init_thread(1); // 默认调用第一个结点
}
list_ my_thread ()
{ global user;
}
init_thread(node_index)
{
取得 $node
取得 $process
修改 $process 为运行到当前结点
Switch($node[‘node_type’])
Case 1: 人工决策
建立 $thread
WorkflowProposalHandler-> init_function ($process,$node,$thread)
发送提醒
Case 2: 自动处理
建立 $thread
WorkflowProposalHandler-> init_function ($process,$node,$thread)
调用 run_thread(thread_id)
Case 3: 等待外部响应
建立 $thread
WorkflowProposalHandler-> init_function ($process,$node,$thread)
Case 4: 分支
取得所有分支的子结点
init_thread( 子结点 )
Case 5: 汇总:
取得所有前结点,如果所有前结点的 Thread 都结束了,调出下一结点
调用 init_thread( 子结点 )
Case 6: 结束:直接结束进程 process
end_process()
}
run_thread(thread_id)
{
取得 $node
取得 $process
取得 $thread
Switch($node[‘node_type’])
Case 1: 人工决策
修改 $thread 为已接收
WorkflowProposalHandler-> run_function ($process,$node,$thread) 显示表单
Case 2: 自动处理
修改 $thread 为已接收
$next_node_id=WorkflowProposalHandler-> run_function ($process,$node,$thread)
调用 transit_thread(thread_id, $next_node_id)
Case 3: 等待外部响应
修改 $thread 为已接收
$next_node_id=WorkflowProposalHandler-> run_function ($process,$node,$thread)
transit_thread(thread_id, $next_node_id)
Case 4: 分支
Case 5: 汇总:
Case 6: 结束:
}
save_thread(thread_id)
{ // 保存结点数据
取得 $node
取得 $process
取得 $thread
Switch($node[‘node_type’])
Case 1: 人工决策
WorkflowProposalHandler-> save_function ($process,$node,$thread) 保存表单
WorkflowProposalHandler-> run_function ($process,$node,$thread) 显示表单
Case 2: 自动处理
Case 3: 等待外部响应
Case 4: 分支
Case 5: 汇总:
Case 6: 结束:
}
transit_thread(thread_id, $next_node_id)
{ 取得 $node
取得 $process
取得 $thread
Switch($node[‘node_type’])
Case 1: 人工决策
WorkflowProposalHandler->transit_function($process,$node,$thread,$next_node_id)
修改 $thread 为已完成
If($next_node_id < $ cur_node_id) { // 回退
删除所有大于 $next_node_id 的 Thread
}
init_thread($next_node_id)
Case 2: 自动处理
修改 $thread 为已完成
If($next_node_id < $ cur_node_id) { // 回退
删除所有大于 $next_node_id 的 Thread
}
init _thread($next_node_id)
Case 3: 等待外部响应
修改 $thread 为已完成
If($next_node_id < $ cur_node_id) { // 回退
删除所有大于 $next_node_id 的 Thread
}
init _thread($next_node_id)
Case 4: 分支
Case 5: 汇总:
Case 6: 结束:
}
end_process()
list_my_process
view_process
workflow_proposal_handler.php
WorkflowProposalHandler
start()
prepare_input() 准备用户输入变量,从 $_POST 收集
init_function () 线程建立后调用的默认函数,当流程的执行者由程序生成时,在此函数内更改 $thread 的 executor ,例如直接赋值 user[2]
run_function () 线程运行化时候调用的默认函数
save_function () 保存运行信息
transit_function () 执行流转
sendmail 其它结点调用函数
workflow.php
switch(op)
case list_defination
参数:无
WorkflowService->list_defination()
case start_process : 启动
参数: defination_id
WorkflowService->init_process(defination_id)
WorkflowService->start_process()
case list_ my_thread : 待处理的列表
WorkflowService->list_ my_thread()
case run_thread :
参数: thread_id
WorkflowService->run_thread(thread_id)
case save_thread :
参数: thread_id
把 input 收集起来(所有的变量以 f_ 开头),赋给 WorkflowService 的 Input ,另外还要获得 thread_id
WorkflowService->save_thread(thread_id)
case transit_thread :
参数: thread_id
把 input 收集起来,赋给 WorkflowService 的 Input ,另外还要获得 thread_id
$next_node_id = 得到用户选择的下一结点 id
WorkflowService-> transit _thread(thread_id , $next_node_id)
case list_my_process: 所有我发起的流程
case list_all_process: 所有我发起的流程
case view_process :
在其它程序中初始化流程
1 先自行建立好业务表单
2WorkflowService->init_process(defination_id)
3 把建好的业务表单的 ID 放在 process 的 context 里面
4WorkflowService->init_thread(1)
WorkflowService->transit_thread(1 , 2) 通过手动调用把前面的流程过掉
外部服务继续流转流程(只用于自动流程)
1 把 input 收集起来,赋给 WorkflowService 的 Input ,另外还要获得 thread_id
2 WorkflowService->run_thread(thread_id)
参考文献: