Easydozer
2005-6
1 什么是工作流
在WFMC(Workflow Management Coalition)中是这么定义的:
The automation of a business process, in whole or part, during which documents,information or tasks are passed from one participant to another for action, accordingto a set of procedural rules.
业务流程中的文档,信息或任务,根据事先预定的一套处理规则,自动地从一个参与者传递到另一个参与者的交互过程。(我翻译的,不知是否正确?)
根据上面的定义,我们可以从以下几个方面来理解:
第一、Workflow是用来实现业务流程的自动化;
第二、定义中说明一个业务流程的执行,必然会从一个参与者(participant[根据WFMC的定义参与都既可以是人也可以是系统或程序])传递到另一参与者;
第三、要被正确地自动执行,需要预定一套业务规则。
总结一下,Workflow就是用来解决在一个涉及多个参与者的业务流程中,根据预定的规则连接成完整的交互过程。可以用工业生产中的“流水线”来类比。
2 与工作流相关的概念
n 工作流引擎
为实现工作流正确自动的执行的一组计算机程序。
n 工作流管理系统(WFMS)
用来定义、执行和管理、监控工作流执行过程的一组计算机程序。
n 流程定义
根据工作流引擎定义的规则,制定的一套业务流程规则。
n 流程实例
在工作流引擎中,根据流程定义创建的一个实例。
3 工作流能做什么
n 审批流程,公文流转。
n 业务流程。
4 工作流带给我们什么
n 简化开发
在一个业务流程中可能包含多个功能点,在传统的开发过程中可能需要一方面关心功能点的功能实现,另一方面还需要关心它们之间的操作顺序关系等。使用工作流后,相当于把功能实现与功能间的关系分离,实现功能点时,只需要遵守工作流引擎提供的规则,专心实现功能,而功能间的关系由工作流引擎的配置文件进行配置并由工作流引擎实现流转。
n 随需而变
使用工作流系统的最大好处可能就体现在这了,在开发一个业务流程中,往往流程是经常变化的因素之一。
5 开源工作流引擎OSWorkflow
5.1 OSWorkflow概况
OSWorkflow是一个轻量级的开源工作流引擎,可以在http://www.opensymphony.com/osworkflow/中下载,目前发布的最新版本为2.7。OSWorkflow具有其它工作流引擎无法比拟的灵活性,一般的工作流引擎都有一套自身的用户、用户组管理,在使用时也必须使用它的用户管理机制,而OSWorkflow也提供了用户及用户组的管理,但它并非一定需要采用它的用户管理机制,你可以使用你自己的一套用户管理。OSWorkflow的流程配置文件采用xml文件进行配置,它虽然没有遵循WFMC流程定义(XPDL)规范,但它以简单,明了的配置,以及支持beanshell(即可以在xml配置文件中编写一些简单的Java语句,用于判断或处理。)独特的功能使用其流程定义变得更加灵活,在一般的工作流引擎中,GUI界面的流程定义工作是必不可少的,但对于OSWorkflow来说,它的流程定义好象更适合于”by hand”,即使用OSWorkflow也有一套比较简陋的流程定义工具。正是因为OSWorkflow的灵活,使用它作为业务系统开发的人在不断地增加,学习研究的人也在不断地增加。同样也因为它的灵活,使得哪些希望由业务人员来定义流程的人,正在徘徊,在国内也好,国外也好,许多人把引用工作流的最终目标是要实现流程的定义完全不需要开发人员的支持,而是由业务人员完成,因为业务人员是最精通业务流程的。如果你也希望OSWorkflow能达到这个目标,我想就目前而言你会失望的。
5.2 使用OSWorkflow
我们以一个简单的请假例子还介绍如何使用的。
当前的需求是这样的:
请假人提出申请,部门经理审批,根据请假时间决定是否交到公司领导审批,结束。其中审批不同意时,回到申请人处,由申请人修改后再提交或取消申请。
5.2.1 使用场景
5.2.2 配置流程
建文件leaveApply.xml
<?xml version="1.0" encoding="GB2312"?> <!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.7//EN" "http://www.opensymphony.com/osworkflow/workflow_2_7.dtd"> <workflow> <initial-actions> <action id="1" name="请假流程"> <pre-functions> <function type="class"> <arg name="class.name">com.opensymphony.workflow.util.Caller</arg> </function> <function type="beanshell"> <arg name="script"> propertySet.setString("proposer", "${caller}");//保存请假申请人 propertySet.setString("apply_day", "${apply_day}");//保存请假申请天数 </arg> </function> </pre-functions> <results> <unconditional-result old-status="Finished" status="applySubmit" step="1" owner="${caller}"/> </results> </action> </initial-actions>
<!-- ======================================= steps ================================================ --> <steps> <step id="1" name="请假申请"> <actions> <action id="2" name="申请提交"> <restrict-to> <conditions type="AND"> <condition type="class"> <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg> <arg name="status">applySubmit</arg> </condition> <condition type="class"> <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg> <arg name="stepId">1</arg> </condition> </conditions> </restrict-to> <pre-functions> <function type="class"> <arg name="class.name">com.opensymphony.workflow.util.Caller</arg> </function> </pre-functions> <results> <unconditional-result old-status="Finished" status="confirm" owner="${bmjl}" step="2"/> </results> </action> <action id="3" name="取消申请"> <restrict-to> <conditions type="AND"> <condition type="class"> <arg name="class.name">com.dragon.workflow.util.AllStatusCondition</arg> <arg name="status">Back</arg> <!-- <arg name="stepId">2</arg> --> </condition> <condition type="class"> <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg> <arg name="stepId">1</arg> </condition> </conditions> </restrict-to> <pre-functions> <function type="class"> <arg name="class.name">com.opensymphony.workflow.util.Caller</arg> </function> </pre-functions> <results> <unconditional-result old-status="Finished" status="Finished" step="4"/> </results> </action> </actions> </step> <step id="2" name="部门经理审批"> <actions> <action id="4" name="同意"> <restrict-to> <conditions type="AND"> <condition type="class"> <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg> <arg name="status">confirm</arg> </condition> <condition type="class"> <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg> <arg name="stepId">2</arg> </condition> </conditions> </restrict-to> <results> <result old-status="Finished" status="confirm" owner="${gsld}" step="3"> <conditions type="AND"> <condition type="beanshell"> <arg name="script"><![CDATA[ System.out.println("apply_day:${apply_day}"); System.out.println("standard_day:${standard_day}"); try{ return ((new Integer("${apply_day}")).intValue() >= (new Integer("${standard_day}")).intValue()); } catch(Exception ex){ throw new Exception("error!"); } ]]></arg> </condition> </conditions> </result> <unconditional-result old-status="Finished" status="Finished" step="4"/> </results> </action> <action id="5" name="不同意"> <restrict-to> <conditions type="AND"> <condition type="class"> <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg> <arg name="status">confirm</arg> </condition> <condition type="class"> <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg> <arg name="stepId">2</arg> </condition> </conditions> </restrict-to> <results> <unconditional-result old-status="Back" status="applySubmit" owner="${proposer}" step="1"/> </results> </action> </actions> </step> <step id="3" name="公司领导审批"> <actions> <action id="6" name="同意"> <restrict-to> <conditions type="AND"> <condition type="class"> <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg> <arg name="status">confirm</arg> </condition> <condition type="class"> <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg> <arg name="stepId">3</arg> </condition> </conditions> </restrict-to> <results> <unconditional-result old-status="Finished" status="Finished" step="4"/> </results> </action> <action id="7" name="不同意"> <restrict-to> <conditions type="AND"> <condition type="class"> <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg> <arg name="status">confirm</arg> </condition> <condition type="class"> <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg> <arg name="stepId">3</arg> </condition> </conditions> </restrict-to> <results> <unconditional-result old-status="Back" status="applySubmit" owner="${proposer}" step="1"/> </results> </action> </actions> </step> <step id="4" name="结束"> <!-- <actions> <action id="7" name="完成" finish="true"> <results> <unconditional-result old-status="Finished" status="Finished" step="4"/> </results> </action> </actions> --> </step> </steps>
</workflow> |
对上述流程进行部分说明:
${caller} 当前流程的调用者,即当在使用此流程实例的操作人员,此值由程序中传入。
${proposer} 申请人,流程中把流程的创建人,即第一次调用流程的caller。
${bmjl} 部门经理。
${gsld} 公司领导。
${apply_day} 申请请假天数。
${standard_day} 要求转交到公司领导请假审批的标准天数。
以上参数均由程序中设置,当然你也可以硬编码在流程定义文件中。
5.2.3 编码
1、编写申请的表单及保存申请数据的程序;
图表 1
2、编写审批的表单及保存的程序。
图表 2
3、加入流程
n 在图1的保存按钮请求的程序中加入如下语句
Workflow workflow = new BasicWorkflow(username);
workflow.initialize(“leaveApply”, 1,map);
--username为当前登录的用户,它也将与leaveApply.xml中的${caller}交换数据;
--leaveApply由配置文件定义的流程的名称;
--map可为null,如果你需要传参数给流程配置文件中的参数,可在此设置。
<workflows> <workflow name="leaveApply" type="resource" location="leaveApply.xml"/> </workflows> |
n 在审批界面的同意及不同意按钮请求的程序中加入如下语句
map.put("bmjl","bmjl");//部门经理用户号
map.put("gsld","gsld");//公司领导用户号
map.put("apply_day",apply_day);//请假天数
map.put("standard_day","7");//是否要公司领导审批天数
Workflow workflow = new BasicWorkflow(username);
workflow.doAction(workflowID, iActionID, map);
--workflowID流程实例ID号;
--iActionID操作步骤号,与leaveApply.xml中的action id 对应;
--map为java.util.Map实例,它存放数据与leaveApply.xml中交换,如前面提到的${standard_day}等都可以在此传入。
n 编写待处理任务(可根据你的需要取任意名称)
根据OSWorkflow的API对所有的流程,或对某一个流程名称查询当前用户的待处理任务。
在这一步骤中,我们如何编写出每一个待办任务列表中链接分别指向哪个处理页面呢?还记得在leaveApply.xml中出现的status=”Underway”或status=”Back”等类型的属性吧,我们可以指定它们的任意状态,然后在程序中根据这个状态就可以确定它当前是什么样的操作,也就是该去调用哪一个处理啦(这是我想的,不知这样使用有没有违背OSWorkflow的用意,不过我想因为OSWorkflow灵活,所以我也给它来个灵活,嘿嘿!)。还有另外一个细节问题,也是一个比较重要的参数,每一个流程实例存在一个唯一的ID号(workflowId,由workflow.initialize(“leaveApply”, 1,map)返回的long类型的数字),每一项业务的每一次办理(对应一次请假申请)也有一个关键字ID(例如为applyId),那么如何让它们关联起来呢?我这边提供几种方案:第一,把workflowId当前applyId来使用;第二,在工作流中保存数据applyId(OSWorkflow提供了一个可持久性的对象,类似于propertySet.setString(“applyId”,applyId));第三,把workflowId保存在业务表中,与applyId对应,这种方案在由流程作为切入点查询时比较麻烦,但由业务记录作为切入点时又比较方便;第四,即综合第二和第三。对于上述各种方案,第一种最简单也最方便,但在一定程序上使用业务数据中的applyId失去了意义(即不能代表一组有规则的信息),因此我们认为第四种方案比较通用。
n 其它事宜
编写已处理事情列表;
查询审批信息;
查询当前流程的轨迹;
等等。
5.2.4 与未引入工作流引擎比较
到目前为止,两种情况并没有很大的差异,从编码量也好,实现难易也好,两者所能达到的目的没什么差别。无非就是对于未引入工作流引擎之前,流程定义,信息的流转是耦合在业务处理中的,而引入工作流引擎后,把流程定义及信息流转部分抽取出来,由单独的工作流引擎来完成。
随着业务的发展,管理的规范,请假流程发生了变化:
第一:部门经理前面需要进行项目经理的审批;
第二:对于请假时间超过10天的,公司领导审批完成后,在全公司范围内进行电子邮件公告;
第三:对请假时间超过3天的,审批结束后,报行政部门备案;
第四:对于部门经理申请请假一定需要经过公司领导审批,但自己不再审批。
根据上述需求,在未引入工作流引擎作如此修改,就有可能要把原有的程序翻个底朝天。
而对于使用了工作流引擎的程序,只需要对原有的流程定义文件稍加处理就完成了,对于第一条增加一个step节点,对于第二条,OSWorkflow为action提供了post-function功能,即当一个操作完成时调用方法处理一些事情,对于第三条,只需要增加一个备案程序,然后再在流程定义文件中增加一个step,对于第四条也一样只需要对申请提交中增加条件判断,这个判断你可以在流程配置文件中用beanshell处理,也可以写一个类进行处理。在整个的修改过程中几乎不需要修改原有的程序。
5.2.5 安装配置OSWorkflow
参考实例
ftp://192.168.0.160/workflow/osworkflow-example