论坛首页 Java企业应用论坛

基于开源工作流引擎OSWorkflow的业务系统实例——请假审批系统

浏览 114503 次
该帖已经被评为良好帖
作者 正文
   发表时间:2007-07-12  
最近工作中一个项目打算采用工作流技术,所以对工作流特别是OSWorkflow进行了一些学习和研究,为了向项目组其他成员演示和进一步进行应用,做了这个小业务系统,现在经过整理把它拿出来,希望对想要了解OSWorkflow的朋友有一点帮助。我接触的时间也不长,所以哪个地方有问题还希望大家多多指点,对工作流技术共同提高。

这个小业务系统的主要目的
演示如何基于OSWorkflow构建业务系统,即如何将原来程序内硬编码的业务过程抽取出来,放到底层的工作流引擎中,上层业务模块只进行具体业务动作的执行,同时演示着两层如何“集成”。
本系统演示三个方面:业务处理和OSWorkflow的基本“集成”、工作流引擎根据工作流定义调用业务处理逻辑、基于业务数据的工作流跳转

业务用例描述
核心工作流:
1、员工填写请假申请单,包括“请假原因”和“请假天数”,填写后进行提交;
2、部门主管对员工请假申请进行审批,同意员工请假;
3、人力资源主管对员工请假申请进行审批,同意员工请假;
4、系统发送邮件通知员工请假申请已获得批准;
5、用例结束;

备选工作流1:
在第一步中,如果员工请假天数小于等于3天,那么不需要部门主管审批,直接从第一步跳到第三步,又人力资源审批;

备选工作流2:
在第二步中,如果部门主管不同意请假申请,那么系统将给请假员工发送请假没有通过的邮件通知,用例结束;

备选工作流3:
在第三步中,如果人力资源主管不同意请假申请,那么系统将给请假员工发送请假没有通过的邮件通知,用例结束;

系统运行/开发环境
数据库:ms sqlserver2000
应用服务器:Jboss-4.0.2
开发工具:Jbuilder2006

系统搭建
1、创建数据库,我命名为osworkflow_app,也可命名为任意名字,只要在数据源配置中正确配置即可;
2、创建数据表并初始化用户及用户组数据,执行附件中的db_leaveApprove_20070712.sql即可;
3、在jboss下配置数据源,我的配置如附件mssql-ds.xml
4、将osworkflow及log4j的配置文件leave_apply.xml、osuser.xml、osworkflow.xml、propertyset.xml、workflows.xml、log4j.properties拷贝到工程目录src下面;
可直接将附件的leaveApprove.rar解到项目中,里面包括所有的源程序和资源包。

工作流定义文件说明
依据本业务需求的工作流定义文件如下
定义中包括6个step

step1是员工请假申请的步骤,本步骤的action在执行时将回调业务方法类ApplyFunction,将申请单数据插入到数据库中,同时处理结果将根据请假申请天数dayCount进行判断,如果请假申请天数大于3天,将跳到step2让部门主管审批,如果不大于3天,将直接跳到step3让人力资源主管审批;

step2 是部门主管审批,如果审批同意(opinion!=2)将跳到step3再由人力资源主管审批,如果审批不同意(opinion==2)将跳到step5自动发“申请未批准”邮件通知步骤;

step3 是人力资源主管审批,如果审批同意(opinion!=2)将跳到step4自动发“申请批准”邮件通知步骤,如果审批不同意(opinion==2)将跳到step5自动发“申请未批准”邮件通知步骤;

step4和step5都是自动发送邮件通知步骤,执行完之后跳到空步骤step6结束该工作流实例。

工作流定义文件
<?xml version="1.0" encoding="GBK"?>
<!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.6//EN" "http://www.opensymphony.com/osworkflow/workflow_2_8.dtd">
<workflow>
	<initial-actions>
		<action id="100" name="启动请假申请工作流">
			<results>
				<unconditional-result old-status="Finished" status="Underway" step="1"/>
			</results>
		</action>
	</initial-actions>
	<steps>
		<step id="1" name="请假申请">
			<actions>
				<action id="1" name="提交需求申请">
					<restrict-to>
						<conditions type="AND">
							<condition type="class">
								<arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
								<arg name="group">employee</arg>
							</condition>
						</conditions>
					</restrict-to>
					<pre-functions>
						<function type="class">
							<arg name="class.name">com.qiny.leave.ApplyFunction</arg>
						</function>
					</pre-functions>
					<results>
						<result old-status="Finished" status="Underway" step="2" owner="manager1">
							<conditions type="AND">
								<condition type="beanshell">
									<arg name="script">
									propertySet.getInt("dayCount")>3
									</arg>
								</condition>
							</conditions>
							<post-functions>
								<function type="beanshell">
									<arg name="script">
                                        System.out.println("步骤 1 提交需求申请 满足条件结果 需部门经理审批...");
                                    </arg>
								</function>
							</post-functions>
						</result>
						<unconditional-result old-status="Finished" status="Underway" step="3" owner="hr1"/>
					</results>
				</action>
			</actions>
		</step>
		<step id="2" name="请假申请审核">
			<actions>
				<action id="2" name="部门主管审批请假申请">
					<restrict-to>
						<conditions type="AND">
							<condition type="beanshell">
								<arg name="script">true</arg>
							</condition>
							<condition type="class">
								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
								<arg name="status">Underway</arg>
							</condition>
							<condition type="class">
								<arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
								<arg name="group">manager</arg>
							</condition>
						</conditions>
					</restrict-to>
					<pre-functions>
						<function type="class">
							<arg name="class.name">com.qiny.leave.ApproveFunction</arg>
						</function>
					</pre-functions>
					<results>
						<result old-status="Finished" status="Underway" step="5">
							<conditions type="AND">
								<condition type="beanshell">
									<arg name="script">
									propertySet.getInt("opinion")==2
									</arg>
								</condition>
							</conditions>
							<post-functions>
								<function type="beanshell">
									<arg name="script">
                                        System.out.println("步骤 2 请假申请审核 部门经理审批没有通过 ...");
                                    </arg>
								</function>
							</post-functions>
						</result>
						<unconditional-result old-status="Finished" status="Underway" step="3" owner="hr1"/>
					</results>
				</action>
			</actions>
		</step>
		<step id="3" name="请假申请审核">
			<actions>
				<action id="3" name="人力资源主管审批请假申请">
					<pre-functions>
						<function type="class">
							<arg name="class.name">com.qiny.leave.ApproveFunction</arg>
						</function>
					</pre-functions>
					<results>
						<result old-status="Finished" status="Underway" step="5">
							<conditions type="AND">
								<condition type="beanshell">
									<arg name="script">
									propertySet.getInt("opinion")==2
									</arg>
								</condition>
							</conditions>
						</result>
						<unconditional-result old-status="Finished" status="Underway" step="5"/>
					</results>
				</action>
			</actions>
		</step>
		<step id="4" name="请假申请结果通知">
			<actions>
				<action id="4" auto="true" name="请假申请获准邮件通知">
					<pre-functions>
						<function type="beanshell">
							<arg name="script">
                                System.out.println("步骤 4 自动动作 请假申请获准邮件通知 Send mail 祝贺你$$$$$$$");
                            </arg>
						</function>
					</pre-functions>
					<results>
						<unconditional-result old-status="Finished" status="Finished" step="6"/>
					</results>
				</action>
			</actions>
		</step>
		<step id="5" name="请假申请结果通知">
			<actions>
				<action id="5" auto="true" name="请假申请没能获准邮件通知">
					<pre-functions>
						<function type="beanshell">
							<arg name="script">
                                System.out.println("步骤 5 自动动作 请假申请没能获准邮件通知 Send mail 很抱歉$$$$$$$");
                            </arg>
						</function>
					</pre-functions>
					<results>
						<unconditional-result old-status="Finished" status="Finished" step="6"/>
					</results>
				</action>
			</actions>
		</step>
		<step id="6" name="flow over">
		</step>
	</steps>
</workflow>


回家了,明天再对一些程序进行说明
可以下载附件,建立工程后执行,数据库中有三个用户:employee1表示员工,可以提交请假申请;manager1表示部门主管;hr1表示人力资源主管,密码都是test
  • leaveApprove.rar (2.7 MB)
  • 描述: 程序源文件和需要的lib
  • 下载次数: 13282
  • databaseAndConfig.rar (2.3 KB)
  • 描述: 数据库表创建及数据初始化文件 数据源配置文件
  • 下载次数: 5181
   发表时间:2007-07-13  
希望楼主能把这个例子在eclipse下发布下,很多人都没装JBuilder.
0 请登录后投票
   发表时间:2007-07-13  
这是eclipse3.2.2下的项目工程
  • os_leave.rar (2.7 MB)
  • 描述: eclipse3.2.2下的项目工程
  • 下载次数: 6520
1 请登录后投票
   发表时间:2007-07-13  
还没用过这个...下一个试试 谢谢哈
0 请登录后投票
   发表时间:2007-07-13  
工作流引擎调用业务逻辑
在基于工作流进行业务系统开发时,通常需要在某些事件发生时或某些情况下,由低层工作流引擎调用上层的业务处理逻辑,比如在示例业务需求中,当部门主管审批员工请假申请时,审批事件发生时需要将审批意见插入到数据库表中,并进入后续相应的步骤,这里的将审批意见插入数据库,可以在业务程序中审批处理代码中直接插入,也可以只让审批处理代码只负责调用工作流引擎的“审批处理”步骤的doAction,而在doAction的过程中由引擎调用插入数据的操作;又例如在某些动作执行后向用户发送邮件通知。

这样当采用由引擎调用业务处理后,就可以在不改变程序代码的情况下调整业务过程及处理方式,比如去掉插入审批意见到数据库、取消动作后发送邮件通知等。业务过程的可配置性大大提高。

在工作流管理系统参考模型中,工作流引擎调用业务逻辑就是指的Interface 3,即如下图所示


在示例中,由引擎调用业务处理是通过Function的回调实现的,这个Function需要实现com.opensymphony.workflow.FunctionProvider,然后实现其中的execute(Map transientVars, Map args, PropertySet ps)方法。参数说明:

Map transientVars表示临时变量,这些变量可以通过doAction(long id, int actionId, Map inputs)的inputs传入,也可以通过initialize(String workflowName, int initialAction, Map inputs)的inputs传入,此外还包含一些全局变量,如entry、context、actionId等;

Map args表示调用Function的属性,这些属性在过程定义文件xml中设定;

PropertySet ps 持久变量,这些变量设置后将存储到数据库或其他存储介质,在本示例中通过在doAction中设置该变量,然后在流程跳转时根据该变量进行相应跳转,即实现业务流程的不同流向。

示例代码
在部门主管审批时调用的ApproveFunction,这个Function只是将审批结果插入数据库中
  public void execute(Map transientVars, Map args, PropertySet ps) {
    logger.info("execute ......");
    WorkflowEntry entry = (WorkflowEntry) transientVars.get("entry");
    long wfid = entry.getId();
    int actionId = ((Integer) transientVars.get("actionId")).intValue();
    logger.info("execute actionId=" + actionId);
    int applyID = ((Integer) transientVars.get("applyID")).intValue();
    String approver = (String) transientVars.get("approver");
    int opinion = ((Integer) transientVars.get("opinion")).intValue();
    LeaveApprove LeaveApprove = new LeaveApprove(applyID, approver, opinion);
    LeaveDAO leaveDao = new LeaveDAO();
    leaveDao.addLeaveApprove(LeaveApprove);
    ps.setInt("opinion", opinion);
    logger.info("execute leaveDao has added leaveApprove!");
  }


过程定义配置文件中相关的回调
		<step id="2" name="请假申请审核">
			<actions>
				<action id="2" name="部门主管审批请假申请">
					<restrict-to>
						<conditions type="AND">
							<condition type="beanshell">
								<arg name="script">true</arg>
							</condition>
							<condition type="class">
								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
								<arg name="status">Underway</arg>
							</condition>
							<condition type="class">
								<arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
								<arg name="group">manager</arg>
							</condition>
						</conditions>
					</restrict-to>
					<pre-functions>
						[color=red]<function type="class">
							<arg name="class.name">com.qiny.leave.ApproveFunction</arg>
						</function>[/color]
						<function type="beanshell">
							<arg name="script">
                                System.out.println("步骤 2 请假申请审核 pre-functions $$$$$$$");
                            </arg>
						</function>
					</pre-functions>
					<results>
						<result old-status="Finished" status="Underway" step="5">
							<conditions type="AND">
								<condition type="beanshell">
									<arg name="script">
									propertySet.getInt("opinion")==2
									</arg>
								</condition>
							</conditions>
							<post-functions>
								<function type="beanshell">
									<arg name="script">
                                        System.out.println("步骤 2 请假申请审核 部门经理审批没有通过 ...");
                                    </arg>
								</function>
							</post-functions>
						</result>
						<unconditional-result old-status="Finished" status="Underway" step="3" owner="hr1"/>
					</results>
					<post-functions>
						<function type="beanshell">
							<arg name="script">
                                System.out.println("步骤 2 请假申请审核 post-functions .......");
                            </arg>
						</function>
					</post-functions>
				</action>
			</actions>
		</step>


0 请登录后投票
   发表时间:2007-07-13  
业务过程根据业务数据而变化

在上面的示例中,只有在员工请假申请的天数大于3天时,才需要部门主管审批,若不大于3天则直接由人力资源主管审批,这样业务过程就由业务数据决定,不同的数据有不同的过程。

这个实现是通过action的条件结果完成的,配置如下
<results>
  <result old-status="Finished" status="Underway" step="2" owner="manager1">
    <conditions type="AND">
      <condition type="beanshell">
        <arg name="script">
        propertySet.getInt("dayCount")>3
        </arg>
      </condition>
    </conditions>
    <post-functions>
      <function type="beanshell">
        <arg name="script">
          System.out.println("步骤 1 提交需求申请 满足条件结果 需部门经理审批...");
        </arg>
      </function>
    </post-functions>
  </result>
  <unconditional-result old-status="Finished" status="Underway" step="3" owner="hr1"/>
</results>


这里当条件propertySet.getInt("dayCount")>3满足时,导向结果<result old-status="Finished" status="Underway" step="2" owner="manager1">,由部门主管审批
如果条件不满足,则导向默认结果unconditional-result ,由人力资源直接审批

这里的值(propertySet.getInt("dayCount"))从哪里来的呢?是从定义的pre-functions:com.qiny.leave.ApproveFunction来的,在ApproveFunction中我们将数据插入库后,调用了ps.setInt("dayCount", dayCount);
0 请登录后投票
   发表时间:2007-07-15  
为什么用JSQL这个驱动啊,找了好久都没找到,现在的SQLSERVER的驱动不行吗?
0 请登录后投票
   发表时间:2007-07-15  
不明白作者在这里要说明什么,是分析业务流程还是说明OSWorkflow解决问题的方法,而且无论是你的配置文件还是代码从规范上都显得过于的粗糙,这样的代码还是最好不要拿出来的好。
7 请登录后投票
   发表时间:2007-07-15  
killvin 写道
不明白作者在这里要说明什么,是分析业务流程还是说明OSWorkflow解决问题的方法,而且无论是你的配置文件还是代码从规范上都显得过于的粗糙,这样的代码还是最好不要拿出来的好。

正如标题,要说明的就是:基于开源工作流引擎OSWorkflow的业务系统实例,具体内容在上面说得都很明白

至于代码粗糙,因为程序的目标不在程序设计和编码规范上,而在用最简单的程序代码——尽量少的设计概念表达os的使用方法。
示例中jsp基本上都是从OSWorkflow的演示(参见osworkflow-2.8.0-example.war)而来;java代码除了一个dao和两个值对象,另两个就是Function的实现类,代码都非常的简单
0 请登录后投票
   发表时间:2007-07-15  
说些题外话,看了很多的workflow,撇开实现好坏不管,就workflow的schema来说--这也是最重要的--还是jbpm最适合阅读和理解,更DSL些. 其它的开源实现都实在不宜阅读和理解.
7 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics