一套完整自定义工作流的实现

概述:

本工作流以一套金融软件业务处理流程为例,实现功能包括:流程自定义、步骤自定义、步骤重复次数、步骤类型(顺序/并行)、定义排序功能,完全使用数据库实现,本文将详细分析业务流程、系统设计及实现细节。

术语:

工作流(Workflow)[1],是对工作流程及其各操作步骤之间业务规则的抽象、概括、描述。工作流建模,即将工作流程中的工作如何前后组织在一起的逻辑和规则在计算机中以恰当的模型进行表示并对其实施计算。工作流要解决的主要问题是:为实现某个业务目标,在多个参与者之间,利用计算机,按某种预定规则自动传递文档、信息或者任务。工作流管理系统(Workflow Management System, WfMS)的主要功能是通过计算机技术的支持去定义、执行和管理工作流,协调工作流执行过程中工作之间以及群体成员之间的信息交互。工作流需要依靠工作流管理系统来实现。

流程:工作流包含多个工作流程,处理时可任选一种流程进行处理,其包含步骤信息;

步骤:流程中每一环节的名称,某一流程将包含多个步骤(其他工作流中也称为节点)。

 

正文:

  第一部分、业务逻辑分析

1、自定义工作流是指工作流各个环节及其参数完全自定义,常用于公文处理、业务流程签批处理等。本系统来源于本人参与开发的一套金融管理软件,业务处理人分不同角色拥有不同权限进行业务处理,将贷款数据库从贷款调查一直到贷款签批的完整流程。其中由于软件功能要求,需要将贷款调查固定置为第一步骤,将贷款签批置为最后一步骤。

其中数字表示当前步骤重复次数。

2、用户业务处理部分包括:

1)、通过:当前步骤处理通过,(选择下一处理人)进入当前流程中下一步骤,若为末步骤,则流程完成;

2)、退回:将步骤退回至上一步骤,即返回至上一处理人处,若为首步骤,则不可进行退回;

3)、否决:将步骤直接结束,不可再进行操作,或者回退至第一步骤;本系统中采用第二种方式;

4)、撤回:若当前步骤已处理,且在下一处理人未进行处理的情况下可进行撤回操作。

3、顺序与并行

顺序是指上一处理人指定某一处理人时,其他拥有此步骤权限的操作员不可进行查看和操作,必须当前处理人处理完毕后,流程才能继续;并行是由上一处理人指定固定多个处理人时,由任一员工处理即可,不分前后顺序,全部处理完成,进入下一步骤,此处理人数目由当前步骤重复次数确定。

两者之间对应关系如下。

  第二部分、系统设计

数据库设计如下:

1)、流程信息表:S_flow_info(flow_id,flow_name)

2)、步骤信息表:S_action_info(action_id,action_name)

3)、流程-步骤信息表:S_step_info(step_id,action_id,flow_id,step_repeat_no,step_order_no,step_type)

(其中step_repeat_no为重复次数,step_order_no为排序号,step_type为类型:0为顺序,1为并行)

4)、流程处理明细表:L_tranct_proc(proc_id,loan_id,step_id,step_action,step_emp_id)

(其中loan_id为数据主表主键,step_id关联S_action_info,step_action存储处理结果:0--不通过,1--通过,2--退回,3--否决,4--撤回,step_emp_id为当前处理员工编号)

其中流程表、步骤表、流程步骤关系表为核心数据表,它们三者确定工作流的完全自定义。流程处理明细表为重要数据表,查询数据主要通过此表进行连接查询。

其他相关表包括:

1)、数据主表L_loan_info(loan_id,loan_name,flow_id,step_id,...)

(flow_id为流程编号,step_id关联S_action_info)

2)、操作员工表E_emp_info(emp_id,emp_name,..)

3)、角色信息表E_role_info(role_id,role_name)

4)、员工-角色关系表E_emp_role(emp_role_id,emp_id,role_id)

(关联角色表与员工表)

5)、步骤角色关系表S_action_role(action_role_id,action_id,role_id)

(关联角色表与步骤表)

6)、下一处理人表L_loan_next_emp(loan_next_emp_id,loan_id,next_emp_id,step_id)

(其中next_emp_id关联操作员工表E_emp_info,step_id关联S_action_info)

其中数据流向如下:

1)业务最开始发生时,数据主表L_loan_info插入数据,其step_id为其所在流程的第一个步骤的编号,可根据下一步骤的重复次数来去将下一处理人的操作员编号插入到下一处理人表L_loan_next_emp中;插入流程处理明细表中数据,step_id为当前步骤编号;更新主表L_loan_info的step_id为下一步骤编号;

2)流程进入下一步骤;

3)下一处理人可查看当前待处理数据(以及本环节待处理数据),选定进行处理,将处理结果(0:不通过,1:通过,2:退回,3:否决)插入到流程处理明细表中,若为通过由更新主表L_loan_info的step_id为下一步骤编号,退回更新为上一步骤编号,否决则更新到第一个步骤编号;

4)在本人已处理数据中可查看已处理过的数据,若下一步骤中操作员还没有进行操作,则可对数据操作进行撤回,撤回时将处理结果(4:撤回)插入到流程处理明细表中,其中的next_emp_id为本人操作员工编号);更新主表L_loan_info的step_id为流程的第一步骤的编号;

5)下一处理人继续3)、4)的循环,直至流程的结束;

6)流程最后一个步骤,将处理结果中step_id值为0插入至流程处理明细中。

  第三部分、实现细节

1、第一步骤中获得数据列表代码如下: 

SELECT * FROM L_loan_info WHERE step_id=@step_id

其中@step_id使用代码得到当前数据的第一步骤编号

2、第一步骤获得已处理数据列表代码:

SELECT * FROM L_loan_info WHERE loan_id IN (SELECT DISTINCT loan_id FROM l_tranct_proc WHERE step_id=@step_id)

3、获得任一步骤数据列表(含未处理和已处理)

 

/// <summary>
/// 方法:获得审批(或签批)数据列表
/// 开发:王洪剑http://www.cnblogs.com/walkingp     http://www.51obj.cn/
/// 时间:2010-6-29
/// 最后修改时间:2010-6-29
/// 修改详情:
/// </summary>
/// <param name="step_emp_id">签批人(默认为当前操作员):0、本环节 其他、当前处理人</param>
/// <param name="action_id">步骤</param>
/// <param name="step_action">操作值:0:待审批/签批 1:已审批/签批 _:所有</param>
/// <param name="version">版本:3.0</param>
/// <returns></returns>
public List<Hope.Model.L_loan_info> GetModelByProcess(int step_emp_id, int action_id, string step_action, int version)
{
    #region
    string sql = "SELECT COUNT(*) FROM s_flow_info WHERE del_sign='1' and flow_id in(select flow_id from s_step_info where action_id=@action_id)";
    SqlParameter[] para ={ new SqlParameter("@action_id", SqlDbType.Int, 4) };
    para[0].Value = action_id;
    int count = int.Parse(DbHelperSQL.GetSingle(sql, para).ToString());
    string[] arrFlowId = new string[count];//流程信息
    sql = "SELECT flow_id FROM s_flow_info WHERE del_sign='1' and flow_id in(select flow_id from s_step_info where action_id=@action_id)";
    DataTable dt = DbHelperSQL.Query(sql, para).Tables[0];
    for (int i = 0; i < count; i++)
    {
        arrFlowId[i] = dt.Rows[i]["flow_id"].ToString();
    }

    string[] pre_action_id = new string[count];//当前流程中上一流程id
    string[] next_action_id = new string[count];//当前流程中下一流程id
    
    for (int i = 0; i < count; i++)
    {
        sql = "SELECT TOP 1 action_id FROM s_step_info WHERE step_order_no<(SELECT order_no FROM S_action_info WHERE action_id=@action_id) AND flow_id=" + arrFlowId[i] + " ORDER BY s_step_info.step_order_no DESC";
        SqlParameter[] paras ={ new SqlParameter("@action_id", SqlDbType.Int, 4) };
        paras[0].Value = action_id;
        dt.Clear();
        dt = DbHelperSQL.Query(sql, paras).Tables[0];
        if (dt.Rows.Count > 0)
            pre_action_id[i] = dt.Rows[0][0].ToString();

        sql = "SELECT TOP 1 action_id FROM s_step_info,l_loan_info WHERE step_order_no>(SELECT order_no FROM S_action_info WHERE action_id=@action_id) AND s_step_info.flow_id=" + arrFlowId[i] + " ORDER BY s_step_info.step_order_no";
        SqlParameter[] paras_ ={
                new SqlParameter("@action_id",SqlDbType.Int,4)
        };
        paras_[0].Value = action_id;
        dt.Clear();
        dt = DbHelperSQL.Query(sql, paras).Tables[0];
        if (dt.Rows.Count > 0)
            next_action_id[i] = dt.Rows[0][0].ToString();
    }

    DataSet ds = new DataSet();
    for (int k = 0; k < count; k++)
    {
        StringBuilder sbTmp = new StringBuilder();
        if (!string.IsNullOrEmpty(pre_action_id[k]))
            sbTmp.Append("(step_id=" + pre_action_id[k] + " AND step_action='1')");
        if (!string.IsNullOrEmpty(pre_action_id[k]) && !string.IsNullOrEmpty(next_action_id[k]))
            sbTmp.Append(" OR ");
        if (!string.IsNullOrEmpty(next_action_id[k]))
            sbTmp.Append("(step_id=" + next_action_id[k] + " AND step_action='2')");

        string strTemp = "1=1";
        if (!string.IsNullOrEmpty(sbTmp.ToString()))
            strTemp += " AND " + sbTmp.ToString();

        sql = "SELECT * FROM l_loan_info WHERE ";
        sql += "1=1";
        if (step_action == "1")//已审批(签批)
        {
            sql += " AND loan_id IN (SELECT loan_id FROM l_tranct_proc WHERE step_id=@action_id AND step_action='1'";
            if (step_emp_id == 0)
                sql += ")";
            else
                sql += " AND step_emp_id=@step_emp_id)";
        }
        else if (step_action == "0")//待审批(签批)
        {
            sql += " AND step_id=@action_id AND loan_id IN (SELECT loan_id FROM l_tranct_proc WHERE " + strTemp + ")";
            if (step_emp_id == 0)
                sql += "";
            else
                sql += " AND loan_id IN (select loan_id from l_loan_next_emp where next_emp_id=@step_emp_id)";
        }
        else if (step_action == "")//所有
        {
            sql += " AND loan_id IN (SELECT loan_id FROM l_tranct_proc WHERE step_id=@action_id AND step_action='1'";
            if (step_emp_id == 0)
                sql += ")";
            else
                sql += " AND step_emp_id=@step_emp_id)";//已审批(签批)
            sql += " UNION ";
            sql += " AND step_id=@action_id AND loan_id IN (SELECT loan_id FROM l_tranct_proc WHERE " + strTemp + ")";
            if (step_emp_id == 0)
                sql += "";
            else
                sql += " AND loan_id IN (select loan_id from l_loan_next_emp where next_emp_id=@step_emp_id)";//待审批(签批)
        }
        sql += " ORDER BY loan_id DESC";

        SqlParameter[] parameters ={
            new SqlParameter("@step_emp_id",SqlDbType.Int,4),
            new SqlParameter("@action_id",SqlDbType.Int,4)};
        parameters[0].Value = step_emp_id;
        parameters[1].Value = action_id;
        if (ds.Tables.Count == 0)
            ds = DbHelperSQL.Query(sql, parameters);
        else
        {
            ds.Merge(DbHelperSQL.Query(sql, parameters), true, MissingSchemaAction.AddWithKey);
        }
    }

    dt = ds.Tables[0];

    /*去除重复*/
    DataView dv = new DataView(dt);
    string[] strCol ={ "loan_id" };
    dt = dv.ToTable(true, strCol);

    Hope.Model.L_loan_info model;
    List<Hope.Model.L_loan_info> modelList = new List<Hope.Model.L_loan_info>();
    if (dt.Rows.Count > 0)
    {
        #region
        for (int i = 0; i < dt.Rows.Count; i++)
        {
            model = new Hope.Model.L_loan_info();
            model = GetModel(int.Parse(dt.Rows[i]["loan_id"].ToString()));
            modelList.Add(model);
        }
        #endregion
        return modelList;
    }
    else
    {
        return null;
    }
}

 

以上为核心代码,当然由于工作流的具体需求不同,可调整相应代码。

  第四部分、运行结果

1、系统参数配置

2、待处理列表

3、处理页面

(注:当前为最后一处理,下一处理人选择不可见)

4、撤回页面

结语:

完整自定义工作流由于应用广泛且业务逻辑复杂,要实现真正意义上通用的工作流还需要去做更多的分析和研究。

另本文不提供代码及其他资料下载,请勿留言索取。

抛砖引玉,欢迎拍砖!

参考文档:

[1]:维基百科:工作流技术 http://zh.wikipedia.org/zh-cn/工作流

posted @ 2010-08-09 15:09 walkingp 阅读(7226) 评论(49) 编辑 收藏

 回复 引用 查看   
#1楼 2010-08-09 15:31 | 夕颜      
挺好的,跟我们以前的公司自己做的一块小审批设计挺类似。非常感谢提供了具体的数据结构。有问题向您请教
 回复 引用 查看   
#2楼 2010-08-09 15:33 | 悟通      
很好,比嘎啦的那套强多了
 回复 引用 查看   
#3楼 2010-08-09 15:35 | 肖建      
NND,你又不提供源码,那你别写这篇文章撒,

难道我还要照着人铁思路把源码敲一篇嗦!
 回复 引用 查看   
#4楼 2010-08-09 15:55 | 陈梓瀚(vczh)      
@肖建
有思路不就行了吗,干嘛非得给你代码,你自己实现一遍好了,出了问题还知道修哪里。
 回复 引用 查看   
#5楼 2010-08-09 16:57 | javincoder      
up
 回复 引用 查看   
#6楼 2010-08-09 17:06 | chinaagan      
我对工作流不大熟悉。
请问楼主,流程和步骤等开发为什么不用工作流的工具来做呢?
 回复 引用 查看   
#7楼 2010-08-09 17:45 | jaygor      
似乎缺少一个图形化的流程展示界面?
虽然以表格的形式也可以展现,但似乎不如图形化的方式直观,毕竟并行和回转这些流程在表格上很难一眼看出来。
 回复 引用 查看   
#8楼 2010-08-09 17:52 | bidaas      
跟我一年以前搞的那个小玩意基本一样,不过我的那个还支持运行时update当前流程,地方政府就喜欢变态
 回复 引用 查看   
#9楼 2010-08-09 17:52 | Alex He      
引用肖建:
NND,你又不提供源码,那你别写这篇文章撒,

难道我还要照着人铁思路把源码敲一篇嗦!

我大吼一声:无耻
 回复 引用 查看   
#10楼 2010-08-09 17:55 | Alex He      
楼主这个估计只能实现比较死的业务逻辑。或者说某一个行业的某几个业务逻辑。可扩展性太差,目前也应该只实现了流程的运作功能,比如重启,指派,挂起。会签,邮件通知。。。。没有实现。
还有流程的设计最好有图形界面,可以拖拽的那种。。。。。。

呵呵,鼓励,楼主继续努力
 回复 引用 查看   
#11楼 2010-08-09 17:57 | CuiWenKe      
 回复 引用 查看   
#12楼 2010-08-09 18:41 | 孔国秋      
楼主加油啊!

可以有更加灵活的一点的流程设置方法。


可以关注一下E8.Net

E8.Net在银行方面有全国的应用范例,在某个分行使用和全国应用流程复杂度会高很多。

http://www.feifanit.com.cn/productFlow.htm




 回复 引用 查看   
#13楼 2010-08-09 19:05 | eng308      
引用Alex He:
引用肖建:
NND,你又不提供源码,那你别写这篇文章撒,

难道我还要照着人铁思路把源码敲一篇嗦!

我大吼一声:无耻


这种无耻之徒还有脸叫。。
知识是学来的不是"要"来的 SB 鄙视下。。
 回复 引用 查看   
#14楼[楼主] 2010-08-09 19:11 | 王洪剑      
@Alex He
看来还是有很多需要完善啊,你说的这个有哪个系统可以参考一下吗?
 回复 引用 查看   
#15楼 2010-08-09 19:32 | Junior Lau      
头像是谁?
 回复 引用 查看   
#16楼 2010-08-09 19:41 | 个人知识管理      
有没有试用版,发到fjwuyongzhi@gmail.com。谢谢!
 回复 引用 查看   
#17楼 2010-08-09 19:47 | Alex He      
引用王洪剑:
@Alex He
看来还是有很多需要完善啊,你说的这个有哪个系统可以参考一下吗?

这个倒没有,我只是知道这方面的东西,你可以慢慢实现的
 回复 引用 查看   
#18楼 2010-08-09 20:50 | 木乃伊      
头像我比较好奇。
 回复 引用 查看   
#19楼 2010-08-09 22:30 | 蓝蓝的天      
不错 , 正好学习下服务于项目,再三感谢,
 回复 引用 查看   
#20楼 2010-08-10 09:00 | 老湖      
@肖建
真是无耻!
 回复 引用 查看   
#21楼 2010-08-10 09:04 | BirchLee      
呵呵,思想的共鸣,楼主思路清晰啊
 回复 引用 查看   
#22楼 2010-08-10 09:29 | 架周      
对于办公的业务工作流来说,用3.5的WF或4.0的WF都不是很顺手的,很多东西实现起很烦人,还是自己写方便。
对于这些场景,其实关键点是下一关卡的审批人,以及流程流转的逻辑,很多流转都根据业务规则来的,而且规则再不断的变化。如果再集合了代理和授权,简直要死人啊。
而LZ的场景貌似只定义了简单的流程模型,还有些复杂的场景没涉及到,对节点的处理只处理了该节点是单人还是多人的情况,没有区分下一关卡是否是发散分支,收敛分支,条件分支,并发分支。
 回复 引用 查看   
#23楼 2010-08-10 09:32 | firewing      
关注工作流的话,看看http://www.cnblogs.com/firewing/
里面的介绍吧,起码比楼主的实现要强,提供试用版。
 回复 引用 查看   
#24楼[楼主] 2010-08-10 09:54 | 王洪剑      
@架周
请问“发散分支,收敛分支,条件分支,并发分支”这里是怎么回事,有相关资料吗?
 回复 引用 查看   
#25楼 2010-08-10 11:04 | 李贵庆      
引用王洪剑:
@架周
请问“发散分支,收敛分支,条件分支,并发分支”这里是怎么回事,有相关资料吗?

他说的这些是工作流中基本的元素,不过一般的说法是分流、合流、条件路由及并行等。
稍微完善点的工作流都有提供这些功能。
 回复 引用 查看   
#26楼 2010-08-10 11:35 | Alex He      
引用王洪剑:
@架周
请问“发散分支,收敛分支,条件分支,并发分支”这里是怎么回事,有相关资料吗?

我个人的理解:
发散分支:一个节点处理完之后一次性传给N多个节点
条件分支:就是按照条件来确定应该将流程走向那个节点
其他两个不知道这位仁兄说的是什么意思,还望说明白一点
 回复 引用 查看   
#27楼 2010-08-10 11:36 | Alex He      
引用李贵庆:
引用王洪剑:
@架周
请问“发散分支,收敛分支,条件分支,并发分支”这里是怎么回事,有相关资料吗?

他说的这些是工作流中基本的元素,不过一般的说法是分流、合流、条件路由及并行等。
稍微完善点的工作流都有提供这些功能。

恩,是这么回事。要不然复杂的业务实现不了,尤其那个条件
怎么跟我扯上关系了?

引用悟通:很好,比嘎啦的那套强多了

好用就是硬道理,推荐+1,支持一下楼主。
其实不管好坏,只要满足的客户的要求,平时用起来好用,有一定的重复利用的价值,那就非常棒了。
 回复 引用 查看   
#31楼[楼主] 2010-08-10 12:19 | 王洪剑      
引用吉日嘎拉 不仅权限管理:其实不管好坏,只要满足的客户的要求,平时用起来好用,有一定的重复利用的价值,那就非常棒了。

感谢支持,这是我在做项目中的一个重要功能,以后有时间会将其单独分离出来再扩展功能的!
 回复 引用 查看   
#32楼 2010-08-10 12:32 | 陈梓瀚(vczh)      
@王洪剑
参考.net的Windows Workflow Foundation及其编辑器。
 回复 引用 查看   
#33楼 2010-08-10 12:45 | 网络小虫      
引用肖建:
NND,你又不提供源码,那你别写这篇文章撒,

难道我还要照着人铁思路把源码敲一篇嗦!


人家写文章还碍着你,写就必须得给源码,这话放你自己头上,你接受不?作者愿意共享思路和数据结构就是很不错了,做人得知足
 回复 引用 查看   
#34楼 2010-08-10 13:44 | billrobin      
楼主能否开源 6935169@163.com
 回复 引用 查看   
#35楼 2010-08-10 15:31 | web报表      
不错,能实现自己项目中的需要,就可以了!

要实现真正意义上通用的工作流,可以看看我们的eworkflow,是真正意义的通用工作流系统,有流程引擎微内核,自定义表单,流程设计器,组织机构适配,任务管理,流程监控跟踪,动态会签,动态多路分支路由,回退流,自由流等等。

http://www.cnblogs.com/webreport
 回复 引用 查看   
#36楼 2010-08-10 16:56 | 月阳      
仅受教。
 回复 引用 查看   
#37楼 2010-08-10 18:11 | zjy      
按我以前做过的工作流,还可以补充如下需求,如:
会签,条件路由分支,动态流程,多情况适配器,各种审批结果的策略路由和适配器,对应表单的关联,对应表单子表的修改,历史轨迹的保留(包括子表痕迹)
当然,还需要将所有的SQL操作放到一个事务中处理.
 回复 引用 查看   
#38楼 2010-08-11 10:08 | 养心为上      
留个脚印!因为分享,值得推荐
 回复 引用 查看   
#39楼 2010-08-16 09:33 | ccjnet      
很不错;关注博主.
 回复 引用 查看   
#40楼 2010-08-16 16:19 | 兜兜里有糖      
楼主 你太牛了。

http://images.cnblogs.com/cnblogs_com/walkingp/230705/o_apth.png 这个都被你发现了。

http://news.cnblogs.com/n/62433/ 这个被删掉了。
 回复 引用 查看   
#41楼 2010-09-08 11:15 | zengxie      
博主,如果可能的话,还是给我一份源码,我很想研究一下,不是偷懒,而是水平太差,就算提示了这么多,我还是没想通怎么实现
zengxie1225@163.com
 回复 引用 查看   
#42楼 2010-09-17 10:58 | 013      
不错~~~谢谢分享
 回复 引用 查看   
#43楼 2011-03-22 16:07 | xiejuns      
透切,我正不知怎么做,正好有启发
 回复 引用 查看   
#44楼 2011-05-04 22:52 | dcy      
这种实现下一步操作员就按角色分配?这样如何处理组织架构和职能部门交叉审批呢?例如申请费用这个流程销售部门的员工和采购部门的员工大家都应该先提交至部门经理然后到财务成本,这个角色咋分配啊?盼复!
 回复 引用 查看   
#45楼[楼主] 2011-05-05 12:19 | walkingp      
引用dcy:这种实现下一步操作员就按角色分配?这样如何处理组织架构和职能部门交叉审批呢?例如申请费用这个流程销售部门的员工和采购部门的员工大家都应该先提交至部门经理然后到财务成本,这个角色咋分配啊?盼复!

选择下一步的前提是下一步的操作员必须有这个权限的角色,所以交叉审批就需要这多个人都要有这个处理的权限;你说的两个不同的操作并行然后汇总,这个其实有点复杂,间接一点吧,并入一个流程“费用采购流程”然后选择相应处理人,并行处理。

工作流实际情况比较复杂,依据业务需求来自定义,我们这个只是一个相对简单通用的模型,仅供参考,如果要做研究可以看下java平台一些开源项目。
 回复 引用 查看   
#46楼 2011-08-05 16:33 | 苏康胜      
非常好,非常有冲劲,好的产品都是一点点锤炼出来的

流程除了审批之外还需要一些跟业务配合的接口的。 工作流的设计的目标还可以考虑更远一些的。
工作流 不等于 OA
工作流 不等于 审批流

加油!

(E8.Net) http://www.feifanit.com.cn/productFlow.htm
 回复 引用 查看   
#47楼 2011-08-08 14:23 | 烂锄头      
初次 接触工作流 于我有益
 回复 引用 查看   
#48楼 2011-08-17 01:17 | 银光小子      
好东西 !!!
 回复 引用 查看   
#49楼 2011-08-17 16:53 | acer3680      
QQ:732611690
工作流 自定义表单 系统
工作流详情看
http://www.cnblogs.com/acer3680/archive/2011/03/24/1993810.html