Activiti是当前流行并重要的业务流程管理框架. 获得Apache许可, 它具有轻量级、可嵌入的优点, 同时还被设计适用于可扩展的云架构. Activiti具有以下特点: 一是开源特性, 它是一个开源项目[1], 适用者可以阅读源码理解工作原理, 也可以修改源代码实现自定义功能. 二是使用MyBatis框架, MyBatis具有轻量化的特点, 利于上手和后期优化, 使用该框架可加快数据的持久化. 三是可融合Spring, 它提供了便捷的Spring接入机制, 可充分利用Spring优势, 快速实现项目开发[2,3]. 四是支持BPMN2.0规范, BPMN2.0用标准定义了描述业务的图元和规范的执行语义, 保证在不同的流程引擎得到的结果一致[4]. 五是支持广泛的数据库, 它支持当前主流数据库如: Oracle、MySQL、PostgreSQL、DB2等.
MongoDB是一个目前流行使用的非结构化数据库, 广泛应用于大数据应用的后台, 是非关系型数据库中最像关系数据库和功能最丰富的一款. 它支持松散的数据结构, 类似于JSON的BSON格式, 用来存储较为复杂的数据类型. 具有以下显著特点: 一是面向集合存储, 集合类似于关系数据库中的表, 一个集合中可包括任意多的文档; 二是模式自由, 集合中存储的数据是无模式的, 这是区别于关系数据库的重要特征; 三是在支持索引、查询等传统关系数据库的功能之外, 具有强大聚合工具, 如count, group等, 支持采用MapReduce完成的聚合任务; 四是文件存储采用BSON格式, 它是二进制格式JSON的缩写, 支持文档与数组的嵌套[5,6].
后勤业务涵盖面比较广泛, 例如物资请领与采购、财务管理、营房管理、公车使用、人员管理、办公办文等等方面. 一个较大单位的后勤保障必然涉及到上述的诸多方面, 这些方面的信息化管理最常用的需求就是流程审批与管理, 不夸张地说, 后勤业务系统一刻也离不开流程使用[7, 8]. 现有系统或使用标记位定义的流程, 随意性较大, 修改复杂; 或使用BPMN1.0的内核, 较为陈旧; 或使用已完全封装好的流程软件, 不利于修改流程. Activiti具有开源的优势, 且可以自定义流程, 通过开发可实现自由流程的源代码, 利于后勤业务系统.
同时, 后勤业务涵盖面广, 产生的数据量也大, 是最容易产生大数据的领域[9]. 现有的关系型数据库在非结构性数据的存储处理上捉襟见肘, 限制了快速高效地操作后勤数据. 适应新需求新前沿, 引入主流非关系型数据库MongoDB, 可以更好满足大数据的要求. 将Activiti实现的自由流程和MongoDB结合起来, 可以实现一种全新的后勤自由流程框架, 带来独特的实现效果. 文献[2]中使用在线审批和Activiti相绑定, 形成了实用在线审批流程算法, 本文则侧重于利用Activiti实现自由流程流转. 文献[5]中利用Node.js和MongoDB搭建的重点在实现高性能、维护成本低Web框架, 本文则重点利用MongoDB服务于流程的高效性.
1 自由流程框架传统带流程的系统, 常常事先画好复杂工作流程图, 写死逻辑代码, 一旦人员机构发生变化带来轻微的修改, 都给程序员带来大量的工作. 不指定流程流转的下一个人、不绑死流程流转的业务表, 在系统中实现一个基本的自由流程框架很有意义.
图1显示了利用Activiti实现自由流程的示意图. 从左至右, 流程启动后, 用户填写相应业务申请表单, 并手动指定呈批对象, 流程会自动流转到该审批对象. 审批对象完成相应批示后, 既可以继续呈批到更高级的审批人, 也可以返回申请人修改申请. 如果是继续向上呈批, 将循环前述审批步骤; 如果是返回申请人修改, 修改后可以自行决定是否重新呈批. 最终审批人完成审批意见后可直接返回申请人确认, 如果申请人认为工作完成则选择结束流程, 如果认为流程还需其他人员协助完成, 还可继续流转到下一审批人, 直至任务完成后再由本人结束.
该自由流程框架有如下3个特点: (1) 不自动指定流程流转的下一人, 由用户根据单位线下实际工作的流程自行指定下一人, 此举利于适应单位组织人员的变化; (2) 不绑定流程流转的业务表, 在流程启动前, 由用户选择业务表, 这样利于流程与业务表独立出来, 高效实现各自功能; (3) 给予申请人充分自主权, 流程形成闭环. 流程由申请人发起, 最后由同一人结束, 符合信息流程闭环的要求, 申请当事人可及时充分地知悉办事过程, 能起到一定的监督作用.
2 关键接口与代码逻辑表1给出了该流程框架实现的代码接口和相应意义. 其中接口1、2和4的意义最为典型, 接口1的意义为: 在起始申请和中间节点时, 用户填写完相应表单后, 提交给流程处理. 接口2为用户作为中间节点的处理人, 处理待办事件时的逻辑功能. 接口4是某个流程的详细历史记录. 接口3为撤回发出的任务. 而其他接口则是接口4功能的延伸, 为实现较完备功能而准备, 实现原理类似.
算法1. 开启新流程逻辑伪代码
输入: 当前用户id—userId
01 startUserId = userId;//初始用户id预留好.
02 map.put("leader", startUserId); //编辑节点都是由申请者完成.
03 String processDefinitionKey = "generalduty";//流程名
04 ProcessDefinition processDefinition = repositoryService
.createProcessDefinitionQuery()
.processDefinitionKey(processDefinitionKey)
.latestVersion().singleResult();
05 String processDefinitionId = processDefinition.getId();
06 String uuidBussness = this.get32UUID();
07 String bussness = uuidBussness+","+tableId;
08 identityService.setAuthenticatedUserId(userId);
//以businessKey(包含多种信息--bId与tableId)方式启动流程
09 ProcessInstance processInstance = runtimeService
.startProcessInstanceById (processDefinitionId, bussness, map);
10 String pid = processInstance.getProcessInstanceId();
//存储流程实例信息, 放入表universe
11 String uuidUniverse = this.get32UUID();
12 PageData pdUniverse = new PageData();
13 pdUniverse.put("ID", uuidUniverse);
14 pdUniverse.put("PID",pid);
15 pdUniverse.put("CREATETIME",createTime);
16 try{ universeService.save(pdUniverse);
17 } catch (Exception e4) e4.printStackTrace();
//得到初始编辑节点的taskId
18 List<Task> tasks = new ArrayList<Task>();
19 tasks = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.taskCandidateOrAssigned(userId)
.active().orderByTaskCreateTime()
.desc().listPage(0, 10);
20 Task firstTask = tasks.get(0);
21 taskId = firstTask.getId()
算法2. 中间节点流转逻辑伪代码
输入: 流转用户名—nextUserName, 当前任务Id—taskId, 流转标记—approval, 当前任务—task
01 Task task=taskService.createTaskQuery().taskId(taskId)
.singleResult();
02 String pId =task.getProcessInstanceId();
//对各个任务节点需要分别进行处理
03 switch(task.getTaskDefinitionKey()) {
04 case "usertask1":
//0-在流程图里转向下一个审批人/转办(14)
05 if (approval.equals("0") || approval.equals("14")){
06 if(nextUserName.equals(username)){
07 msg="请提交给正确的业务审核人员!";
08 errInfo="false";
09 map1.put("msg", msg);
10 map1.put("result", errInfo);
11 return AppUtil.returnObject(new PageData(), map1,true);
12 } else {
13 pdUserId.put("USERNAME", nextUserName);
14 nextUserId = userService.findByUId(pdUserId)
.getString("USER_ID");
15 map.put("leader", nextUserId);
16 message = "请尽快完成审批! ";
17 bussMessageService.sendMessage(sendSmsUser, nextUserName, processDefinitionKey,message); }}
//1-转给申请人修改
18 else if(approval.equals("1")){
19 startUserId =mapData.getString("userId");
20 map.put("leader",startUserId);
21 PageData apllypd = new PageData();
22 applypd.put("USER_ID", startUserId);
23 apllypd= userService.findByUiId(apllypd);
24 String recieveUser = apllypd.getString("USERNAME");
25 String message =null;
26 nextUserName=recieveUser;
27 message="请申请人修改申请表! ";
28 bussMessageService.sendMessage(sendSmsUser, recieveUser, processDefinitionKey,massege);
}
//11-转向申请人确认
29 else if(approval.equals("11") || approval.equals("17")){
...
//以请假流程为例, 进行表的更新.
30 if(tableId2.equals("leave")){
31 mapData.put("isPass", "1");
32 freeModel.setMap(mapData);
33 freeModel.setId(bId2); //update时, 需要Id关键字
34 freeDao.update(freeModel,tableId2);
}
35 Message = "请申请人确认! ";
36 bussMessageService.sendMessage(sendSmsUser, recieveUser, processDefinitionKey,message);
37 }
38 break;
39 case "usertask2":…
40 break;
41 default:…
}
//根据流程分支条件, 往下一节点流转
42 taskService.complete(taskId, map)
梳理这些接口的核心代码, 其中两部分关键代码逻辑值得特别展示. 算法1和算法2给出了两段关键原理代码逻辑, 它们分别实现: 开始一个新流程和流程的中间节点流转. 此两部分代码构成了自由流程正常流转的核心部分. 需要说明的是: 一个流程(process), 在每一个节点处都有一个任务(task), 即一个process包含多个task. 此外, 此两表只给出核心代码的原理逻辑, 其中用不少伪代码示意.
3 数据表设计关系图2给出了本文自由流程框架的数据库表设计关系图, 表中的箭头方向为一对多的关系. 其中浅灰色表为关系型数据库表, 深灰色表为非关系型数据库表[10]. 表act_hi_procinst和act_ru_task是Activiti工作自带的关键表, 前者存储了已完成流程的详细记录, 后者存储了已启动、尚未结束流程的记录. 表log则不是Activiti的保留表, 它用来存储流程中各节点的处理意见, 需要说明的是, 它并不是业务表, 而且配合流程使用的流程日志表.
图2中虚线框部分给出了非结构化自由业务表原理. 深灰色标注表leave为举例业务表——请销假表, 它存储在MongoDB中, 表中列出了请销假需要的主要字段. 这种业务表可以根据用户的需要自由添加, 并可修改对应字段, 当用户添加了一个新业务表时, 关系型表free_bt会记录下该表的相关信息. 当用户对新表字段进行添加或修改时, 关系型表free_nr又会对相应字段信息进行登记. 在前端配合的情况下, 后端可以自如地添加修改表及表的字段结构, 具体实现效果在第4部分中展示.
除了单一流程, 还会存在如下情形: 在某个流程尚未结束时, 用户需要启动其他流程并在其中流转. 图3给出了延伸为多流程的数据表设计关系图, 该示意图对应需求为: 在用户请假的同时需要申请派车送站. 其中灰色部分仍为关系型表, 深灰色部分为非关系型表. 深灰色部分表leave和applyCar对应请假和派车的业务表, 表act_hi_procinst和act_ru_task同图2的意义, 表multiProcess用来管理多个流程. 在设计中, 后台视之为实质是同一个流程的扩展, 流程实例和id无需变化, 只需变换相应业务表的对应关系, 让携带新业务表的流程继续在自由流程框架中流转, 直至结束, 后再切换回前一流程对应的业务表继续流转. 当然, 也可以做到两个业务表不切换, 让它们同时流转, 以利于后一流程处理人更加直观方便知悉相关情况.
4 实现效果
(1) 自由流程的应用场景. 自由流程可以应用到一个单位很多的后勤事务工作中去, 详见表2.
(2) 以请销假流程为例说明自由流程使用达到的效果.
图4至图6用实例给出了请销假自由流程的过程, 图4中的承接人可以让用户选择任意人完成对请假的审批工作. 对于承接人, 在完成了相应审批(图5)时, 他的上级审批人同样由他选择, 从而实现申请-审批工作的自由灵活, 最后结果可在图6的历史详情页中获悉.
另外, 用户通过该系统能获取参与但尚未结束的流程的各步骤详情; 还能在每次进入个人主页时, 看到需要待办的事务. 由于篇幅所限, 并不在此一一展示. 对于通用事务处理, 系统内设了一些业务处理单, 它们对应着不同的流程业务类型, 可以为不同的后勤管理进行自由流转, 从而满足相应的工作需要.
(3) 灵活添加业务表单. 如图7所示, 点击业务类型右边的星型按钮可以添加新表, 点击字段后的向下箭头按钮可以让用户添加新字段. 从而实现了表和字段的自由添加, 在申请之初为用户提供方便. 由于业务表和自由流程之间的保持相对独立, 这种设计非常有利于开展新业务, 并使之进行业务流转.
5 结语本文的主要贡献如下:
(1)本文充分利用了流程框架Activiti和非关系型数据库MongoDB的优点, 提出了适用于大型单位后勤管理的自由流程信息系统设计流程框架、系统重要接口、关键逻辑代码和两类数据库结合的数据结构协同设计方法.
(2)本文设计的系统支持多个流程同时并行进行, 或者多个流程串行运行, 支持针对不同的业务类型根据需求自由添加表和字段.
实现效果显示本文设计的自由流程管理系统实用、灵活和高效, 最大限度地支持业务工作和减少研发人员的工作量, 对于单位后勤管理信息化具有较好的设计参考价值.
[1] |
Activiti项目. https://www.activiti.org/. [2019-06-01].
|
[2] |
李修云. 基于Activiti框架的在线审批流程应用研究. 计算机科学, 2016, 43(6A): 555-557. DOI:10.11896/j.issn.1002-137X.2016.6A.132 |
[3] |
梁继刚, 郭凌, 刘凌. 基于SOA的数字后勤日常业务信息系统设计. 四川兵工学报, 2015, 36(4): 92-95. |
[4] |
张毅, 孙加光, 喻波, 等. 一种基于Activiti的流程创建部署方法及系统: 201810430062.4. [2018-10-19].
|
[5] |
朱爱华, 付曹政, 曹钟, 等. 基于Node.js框架和MongoDB数据库的物流信息服务系统设计. 北京建筑大学学报, 2018, 34(4): 41-46. |
[6] |
Holemans A, Kasprzyk JP, Donnay JP. Coupling an unstructured NoSQL database with a geographic information system. Proceedings of the 10th International Conference on Advanced Geographic Information Systems, Applications, and Services GEOProcessing 2018. Rome, Italy. 2018. 23–28.
|
[7] |
于晓芸. 民远学院后勤管理系统设计与实现[硕士学位论文]. 成都: 电子科技大学, 2015.
|
[8] |
Casanova H, Pandey S, Oeth J, et al. WRENCH: A framework for simulating workflow management systems. Proceedings of 2018 IEEE/ACM Workflows in Support of Large-Scale Science. Dallas, TX, USA. 2018. 74–85.
|
[9] |
牟杨. 大数据在后勤库存物资智能化管理系统中的应用. 自动化与仪器仪表, 2019(4): 233-236. |
[10] |
潘明明, 李丁丁, 汤庸, 等. 一种基于中间件的异构数据库融合访问方法及系统. 计算机科学, 2018, 45(5): 163-167. |