您的位置:首页 > 编程语言 > PHP开发

一个真正符合中国国情的工作流设计参考(包括PHP实现)

2008-01-03 09:50 489 查看
开源的工作流很少有让人满意的,即便是国内用的比较多的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语言的以下特性

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_processview_process workflow_proposal_handler.phpWorkflowProposalHandler  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 = 得到用户选择的下一结点idWorkflowService-> 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_id2 WorkflowService->run_thread(thread_id)顺便把和daniel的聊天也附在下面:♂蒓玥♀ 12:09:25
execute_type的作用是表示这个流程由多少人完成,例如对于会签,就需要大家一起完成。如果是客户报修,只要有一个客服接收了这次报修,流程就继续
 
是的,我写这个文的目的,就是想说明,自己完全可以开发一个符合实际需要的流程引擎。如果总是用那些现成的引擎,常常会束手束脚
 
-=Daniel=- 12:11:39
是的啊, 我知道, 但是我认为execute_type本身就是为了executer存在的. executer又是来自外部的, 比如用户管理模块啊什么的. 外部就可以生成一个executer的id给流程使用了, 为什么这里还需要描述这个呢. 比如说外部生成一个id, 这个id是描述一个人, 那就是一个人的操作, 如果是一个组, 那就是一个组的操作. 在流程内部标明这个id的含义有什么作用呢?♂蒓玥♀ 12:11:46
remind就是一个提醒功能,设计的时候,自己定义那些结点需要提醒,把他放进去,其实是为了利用空间换时间的原理,减少定义在其他地方的时候浪费读取的时间
 
-=Daniel=- 12:13:01
其实我的意思是remind可能有很多种, 不可能在流程内部给定义完. 如果留接口而不是由流程给定, 可能会更加全面点. ♂蒓玥♀ 12:14:07
我的executor是这样定义的,可以定义user[1]表示id为1的用户,group[1]表示组id为1,role[1]表示角色1,这几个组合是可以并的,最终根据定义把thread分到所有的用户,但是execute_type的含义是,只要有一个人执行了这个thread,我根据设定判断,如果不需要所有人做,就把其他的thread结束
 
恩,我把所有业务的类都交给handler,例如一个提案流程
就是workflow-proposal-handler类,里面定义所有结点需要的处理方法,当然也可以发送短信
 
我把一次完整的流程看作process,其中的每一条分支,都是thread
所有的thread都是可以直接由外部调用的,这比jpbm这些要好多了
 
-=Daniel=- 12:17:02
我不同意, 我认为executer不管一个人完成或者多个人完成, 都是只有有其中一个人完成就立即判定为执行了流程. 如果一个流程需要多个人有次序的完成, 那么就说明这个流程不够详细, 需要再细分.  
♂蒓玥♀ 12:18:08
哦,我先定义了两种情况:0 所有人处理,就是会签
1 只需一个人处理,就是客服模式,
这两个是最长用的模式
 
-=Daniel=- 12:18:53
哦, 这可能是你定义流程的模式跟我的有一定的差别, 或者是我们的客户不同类型吧. -=Daniel=- 12:19:51
对了, 有一种流程你怎么处理的, 比如一个流程中有一个步骤, 它按A这条路走是对的, 按B走也是可以的, 只要走了子流程中的任意一个流程, 都可以. ♂蒓玥♀ 12:20:48
就是分支啊,随便用户选哪一条路径 -=Daniel=- 12:22:35
是啊, 我这边在这个分支上遇到了意外, 因为现在要做事务功能, 加上了分支很麻烦. ♂蒓玥♀ 12:23:03
哦,我的这个客户本身就是用SAP的,他们一个子公司要上提案系统,是基于Web的。
 
♂蒓玥♀ 12:24:26
你的事务处理具体想干什么?
 
-=Daniel=- 12:26:47
事务嘛, 最主要的就是回溯啊, 原先在纪录的保存上做的都是当前状态的值, 回溯就是返回到上一个值, 要求处理分支的回溯, 那就要求将当前的操作本身也当作一个值, 进行回溯. ♂蒓玥♀ 12:27:15
我的回退作为流程分支处理
 
-=Daniel=- 12:27:50
如果一个事务跨越了一个分支倒可以, 问题就是怕事务从分支的一部分开始, 到另外一个分支的某一部分结束, 那就会弄的很乱. -=Daniel=- 12:28:39
我看了, 这是一个方法, 就是所有的节点都有一个回退功能, 做起来比较烦. ♂蒓玥♀ 12:29:39
这个问题我想过,包括所有结点的回退,我的结论是:纯粹图论上的功能对实际业务没有任何作用,只要在流程设计的时候设计好,不要有这些问题就行了
 
♂蒓玥♀ 12:30:18
对于银行这种单位,流程里面有很多自动划帐的结点,能让人随便回退么
 

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1824947
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐