一套完整自定义工作流的实现
本文来自于王洪剑
概述:
本工作流以一套金融软件业务处理流程为例,实现功能包括:流程自定义、步骤自定义、步骤重复次数、步骤类型(顺序/并行)、定义排序功能,完全使用数据库实现,本文将详细分析业务流程、系统设计及实现细节。
术语:
工作流(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、第一步骤中获得数据列表代码如下:
1
|
SELECT * FROM L_loan_info WHERE step_id=@step_id
|
其中@step_id使用代码得到当前数据的第一步骤编号
2、第一步骤获得已处理数据列表代码:
1
|
SELECT * FROM L_loan_info WHERE loan_id IN ( SELECT DISTINCT loan_id FROM l_tranct_proc WHERE step_id=@step_id)
|
3、获得任一步骤数据列表(含未处理和已处理)
001
|
/// <summary>
|
002
|
/// 方法:获得审批(或签批)数据列表
|
003
|
/// 开发:王洪剑http://www.cnblogs.com/walkingp http://www.51obj.cn/
|
004
|
/// 时间:2010-6-29
|
005
|
/// 最后修改时间:2010-6-29
|
006
|
/// 修改详情:
|
007
|
/// </summary>
|
008
|
/// <param name="step_emp_id">签批人(默认为当前操作员):0、本环节 其他、当前处理人</param>
|
009
|
/// <param name="action_id">步骤</param>
|
010
|
/// <param name="step_action">操作值:0:待审批/签批 1:已审批/签批 _:所有</param>
|
011
|
/// <param name="version">版本:3.0</param>
|
012
|
/// <returns></returns>
|
013
|
public List<Hope.Model.L_loan_info> GetModelByProcess( int step_emp_id, int action_id, string step_action, int version)
|
014
|
{
|
015
|
#region
|
016
|
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)" ;
|
017
|
SqlParameter[] para ={ new SqlParameter( "@action_id" , SqlDbType.Int, 4) };
|
018
|
para[0].Value = action_id;
|
019
|
int count = int .Parse(DbHelperSQL.GetSingle(sql, para).ToString());
|
020
|
string [] arrFlowId = new string [count]; //流程信息
|
021
|
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)" ;
|
022
|
DataTable dt = DbHelperSQL.Query(sql, para).Tables[0];
|
023
|
for ( int i = 0; i < count; i++)
|
024
|
{
|
025
|
arrFlowId[i] = dt.Rows[i][ "flow_id" ].ToString();
|
026
|
}
|
027
|
|
028
|
string [] pre_action_id = new string [count]; //当前流程中上一流程id
|
029
|
string [] next_action_id = new string [count]; //当前流程中下一流程id
|
030
|
|
031
|
for ( int i = 0; i < count; i++)
|
032
|
{
|
033
|
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" ;
|
034
|
SqlParameter[] paras ={ new SqlParameter( "@action_id" , SqlDbType.Int, 4) };
|
035
|
paras[0].Value = action_id;
|
036
|
dt.Clear();
|
037
|
dt = DbHelperSQL.Query(sql, paras).Tables[0];
|
038
|
if (dt.Rows.Count > 0)
|
039
|
pre_action_id[i] = dt.Rows[0][0].ToString();
|
040
|
|
041
|
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" ;
|
042
|
SqlParameter[] paras_ ={
|
043
|
new SqlParameter( "@action_id" ,SqlDbType.Int,4)
|
044
|
};
|
045
|
paras_[0].Value = action_id;
|
046
|
dt.Clear();
|
047
|
dt = DbHelperSQL.Query(sql, paras).Tables[0];
|
048
|
if (dt.Rows.Count > 0)
|
049
|
next_action_id[i] = dt.Rows[0][0].ToString();
|
050
|
}
|
051
|
|
052
|
DataSet ds = new DataSet();
|
053
|
for ( int k = 0; k < count; k++)
|
054
|
{
|
055
|
StringBuilder sbTmp = new StringBuilder();
|
056
|
if (! string .IsNullOrEmpty(pre_action_id[k]))
|
057
|
sbTmp.Append( "(step_id=" + pre_action_id[k] + " AND step_action='1')" );
|
058
|
if (! string .IsNullOrEmpty(pre_action_id[k]) && ! string .IsNullOrEmpty(next_action_id[k]))
|
059
|
sbTmp.Append( " OR " );
|
060
|
if (! string .IsNullOrEmpty(next_action_id[k]))
|
061
|
sbTmp.Append( "(step_id=" + next_action_id[k] + " AND step_action='2')" );
|
062
|
|
063
|
string strTemp = "1=1" ;
|
064
|
if (! string .IsNullOrEmpty(sbTmp.ToString()))
|
065
|
strTemp += " AND " + sbTmp.ToString();
|
066
|
|
067
|
sql = "SELECT * FROM l_loan_info WHERE " ;
|
068
|
sql += "1=1" ;
|
069
|
if (step_action == "1" ) //已审批(签批)
|
070
|
{
|
071
|
sql += " AND loan_id IN (SELECT loan_id FROM l_tranct_proc WHERE step_id=@action_id AND step_action='1'" ;
|
072
|
if (step_emp_id == 0)
|
073
|
sql += ")" ;
|
074
|
else
|
075
|
sql += " AND step_emp_id=@step_emp_id)" ;
|
076
|
}
|
077
|
else if (step_action == "0" ) //待审批(签批)
|
078
|
{
|
079
|
sql += " AND step_id=@action_id AND loan_id IN (SELECT loan_id FROM l_tranct_proc WHERE " + strTemp + ")" ;
|
080
|
if (step_emp_id == 0)
|
081
|
sql += "" ;
|
082
|
else
|
083
|
sql += " AND loan_id IN (select loan_id from l_loan_next_emp where next_emp_id=@step_emp_id)" ;
|
084
|
}
|
085
|
else if (step_action == "" ) //所有
|
086
|
{
|
087
|
sql += " AND loan_id IN (SELECT loan_id FROM l_tranct_proc WHERE step_id=@action_id AND step_action='1'" ;
|
088
|
if (step_emp_id == 0)
|
089
|
sql += ")" ;
|
090
|
else
|
091
|
sql += " AND step_emp_id=@step_emp_id)" ; //已审批(签批)
|
092
|
sql += " UNION " ;
|
093
|
sql += " AND step_id=@action_id AND loan_id IN (SELECT loan_id FROM l_tranct_proc WHERE " + strTemp + ")" ;
|
094
|
if (step_emp_id == 0)
|
095
|
sql += "" ;
|
096
|
else
|
097
|
sql += " AND loan_id IN (select loan_id from l_loan_next_emp where next_emp_id=@step_emp_id)" ; //待审批(签批)
|
098
|
}
|
099
|
sql += " ORDER BY loan_id DESC" ;
|
100
|
|
101
|
SqlParameter[] parameters ={
|
102
|
new SqlParameter( "@step_emp_id" ,SqlDbType.Int,4),
|
103
|
new SqlParameter( "@action_id" ,SqlDbType.Int,4)};
|
104
|
parameters[0].Value = step_emp_id;
|
105
|
parameters[1].Value = action_id;
|
106
|
if (ds.Tables.Count == 0)
|
107
|
ds = DbHelperSQL.Query(sql, parameters);
|
108
|
else
|
109
|
{
|
110
|
ds.Merge(DbHelperSQL.Query(sql, parameters), true , MissingSchemaAction.AddWithKey);
|
111
|
}
|
112
|
}
|
113
|
|
114
|
dt = ds.Tables[0];
|
115
|
|
116
|
/*去除重复*/
|
117
|
DataView dv = new DataView(dt);
|
118
|
string [] strCol ={ "loan_id" };
|
119
|
dt = dv.ToTable( true , strCol);
|
120
|
|
121
|
Hope.Model.L_loan_info model;
|
122
|
List<Hope.Model.L_loan_info> modelList = new List<Hope.Model.L_loan_info>();
|
123
|
if (dt.Rows.Count > 0)
|
124
|
{
|
125
|
#region
|
126
|
for ( int i = 0; i < dt.Rows.Count; i++)
|
127
|
{
|
128
|
model = new Hope.Model.L_loan_info();
|
129
|
model = GetModel( int .Parse(dt.Rows[i][ "loan_id" ].ToString()));
|
130
|
modelList.Add(model);
|
131
|
}
|
132
|
#endregion
|
133
|
return modelList;
|
134
|
}
|
135
|
else
|
136
|
{
|
137
|
return null ;
|
138
|
}
|
139
|
}
|
以上为核心代码,当然由于工作流的具体需求不同,可调整相应代码。
第四部分、运行结果
1、系统参数配置
2、待处理列表
3、处理页面
(注:当前为最后一处理,下一处理人选择不可见)
4、撤回页面
结语:
完整自定义工作流由于应用广泛且业务逻辑复杂,要实现真正意义上通用的工作流还需要去做更多的分析和研究。
另本文不提供代码及其他资料下载,请勿留言索取。
抛砖引玉,欢迎拍砖!
转载于:https://www.cnblogs.com/crazylogin/archive/2010/08/10/1796565.html
一套完整自定义工作流的实现相关推荐
- 自定义工作流相关思路
本工作流以一套金融软件业务处理流程为例,实现功能包括:流程自定义.步骤自定义.步骤重复次数.步骤类型(顺序/并行).定义排序功能,完全使用数据库实现,本文将详细分析业务流程.系统设计及实现细节. 术语 ...
- [译] Google Interview University 一套完整的学习手册帮助自己准备 Google 的面试
[译] Google Interview University 一套完整的学习手册帮助自己准备 Google 的面试 十一七天乐,看池博的github,发现这个markdown,转过来mark一下 原 ...
- 一套完整的数字无线监控系统需要哪些设备和材料?
无线网桥监控系统的搭建有效的解决了各种场所涉及范围广.距离远,有线敷设难度大,对环境不破坏.投资高等问题.通过使用大功率无线网桥监控设备,能够适应各种环境远距离传输替代光纤和专线,降低网络建设成本,减 ...
- 自定义工作流任务控件
读moss sdk中的自定义工作流任务控件. 自定义工作流任务控件:任务的创建,修改,删除,完成于一体,同时定义了这四个动作的历史纪录. 自定义时封装属性: 1. 封装任务属性 IsTaskComp ...
- 一套完整的防火墙系统通常是由屏蔽路由器和代理服务器组成
1.什么是防火墙? 我们知道,原是指古代人们房屋之间修建的那道墙,这道墙可以防止火灾发生的时候蔓延到别的房屋. 而这里所说的防火墙当然不是指物理上的防火墙,而是指隔离在本地网络与外界网络之间的一道防御 ...
- Jira 自定义工作流
一.添加修改工作流 打开 设置--问题--工作流 复制一个工作流,然后进去编辑页面 添加状态 增加转换动作 切换到文本,设置跳转过程中的事件 针对Stop Progress事件,修改跳转界面(界面需先 ...
- 方法论:如何从0到1搭建一套完整的邀请体系
最近对邀请好友做任务类的产品功能思考还是挺多的,有一些思考分享给大家.写文章前,把网上的邀请好友类文章,刷了大半,有很多都挺不错:有深度.有案例.有数据.有实操建议,贴部分好文如下:大部分文章都基本会 ...
- 如何搭建一套完整的深度学习系统?
假期总是过的很快,刷刷抖音,说没就没了. 说到抖音,就不得不提它的推荐系统,太 NB 了.刷了啥,立刻记住你的偏好,推荐相似内容,一不小心 2 小时就过去了,让人欲罢不能,要么日活 6 亿呢. 其实& ...
- 一套完整java项目 后台+管理+前端
分享一套完整的项目: 一.项目功能 此项目是一套完整的小商场系统,主要包括商场后台系统.前端管理页面,管理后台系统.是不是很完善呀.作者对于项目有着详细的介绍,从技术栈到系统的搭建. 从用户登陆,商品 ...
- 基于SpringBoot开发一套完整的项目(一)准备工作
基于SpringBoot开发一套完整的项目(一)准备工作 1.1 SpringBoot简介 ① 为所有Spring 开发提供一个更快更广泛的人门体验. ② 零配置.无冗余代码生成和XML 强制配置,遵 ...
最新文章
- 强化学习与3D视觉结合新突破:高效能在线码垛机器人
- mysql事务隔离级别与设置
- 文件服务器换个用登录,文件服务器迁移 登录
- NLPIR RuntimeError: NLPIR function 'NLPIR_Init' failed 解决方案
- 体验最火的敏捷——SCRUM(厦门,2014.1.4)
- rpm -e --nodeps_微课 | rpm的思维导图
- Redis 持久化方式
- android mvc使用方法,详细学习android mvc设计模式教程
- linux怎么查看内核定义的结构体,Linux如何查找一个结构体的原始定义
- Nand Flash数据存储单元的整体架构
- Linux多线程编程[精]
- windows | RDPWrap 远程桌面登录增强工具,长期提供多版本 rdpwrap.ini配置文件 [可灵活设置多人同时登录、一键改变配置]
- navicat中文破解版,navicat for mysql10.0.11简体中文破解版
- 2020 计蒜客蓝桥杯省赛 B 组模拟赛(一)题解1.有趣的数字
- PHP中 逗号,和句号.的区别
- SVN版本控制器使用攻略
- 某内容管理系统最最最详细的代码审计
- 几款常用的高质量web前端框架
- ios-弹窗输入六位密码
- Ubuntu16.04安装qq和微信(亲测 可用)附安装包下载链接
热门文章
- AirServer for mac如何实现无线投屏
- 如何查找识别苹果无线鼠标/无线键盘/触控板的设备序列号
- 前端文档汇总(觉得对您有用的话,别忘了给点个赞哦 ^_^ !) 1
- 路透:在美投资遇阻 中国科技资金转向以色列
- MySQL中char、varchar和text的区别
- c#调用c++ dll的一个例子
- 使用Boostrap,左侧菜单栏固定宽度,右侧自适应宽度。
- 用.htaccess 禁止IP访问
- java中map的使用和排序使用
- 您无权查看或编辑目前的权限设置;但是,您可以取得所有权或更改审核设置