前言
作为OA自动化办公系统,自动审批流可以说是必不可少的,整好本菜鸟在最近的项目中使用了工作流有关的技术,因此准备写一系列文章来完善自己所搭建的一套工作流管理系统。
系列说明:
由于本人对于前端技术十分薄弱,并且界面长相极为普通,因此在本系列中不详细说明前端技术,仅提供部分核心代码和简要截图说明,望各位大神见谅!
虽然工作流官方的表包含了工作流整个生命周期的完整数据内容,但是为了本系统能够方便展示以及在系统内部进行数据留痕,因此本人在该系统中除了使用工作流官方表(25张)外,还将自己创建业务表进行数据保存和前台展示。随着系列的深入,将不断放出所创建的表的表结构供大家参考,旨在抛砖引玉。下面开始进入正题:
系列一内容
本系列开篇主要介绍工作流web编辑器的使用,以及工作流与业务系统的对接关系和导入工作流定义文件的功能
环境搭建
前端:iview4.x+Vue、后端:springboot
核心技术:activiti工作流5.22.0版本
web编辑器:activiti-explorer
服务器:tomcat8.5.31(用于运行web编辑器)
注:实际项目开发使用的是springboot内置的tomcat
项目管理:git,Maven
数据库表结构(一)
工作流官方表:
共25张(自动生成)
自建数据业务表:
流程定义表:
flow_def(对应对象:FlowDef)
说明:流程bpmn文件发布成功后,将流程定义的信息保存到此表中在业务端进行管理和维护。
Domain:
private Long id;//主键
private String name;//流程名称
private String flowKey; //流程ID(KEY)
private String defId; //部署ID
private String flowType;//流程类别
private String version;//版本
private String bpmn; //bpmn文件url
private String status;//流程状态(0:禁用 1:启用)
private Date createDate; //创建日期
private Date publishDate; //发布日期
private transient Integer instanceQuantity; //正在运行实例数量
private transient Integer historyQuantity ; //历史实例数量
private String description;//流程描述
private String remark;//流程备注(同流程描述)
任务定义表:
task_def(对应对象:TaskDef)
说明:在bpmn文件部署完成后,会通过API生成该流程图内的节点关系和相关表达式,并保存在该表内部,方便业务系统进行调用。此表为业务系统中主要的节点信息获取渠道,因此必须保证各节点间关系正确无误!
Domain:
private Long id; //主键
private String name;//任务名称
private String taskKey;//任务编码
private Long flowId;//所属流程定义
private String type;//任务类型▼[单人处理、多人处理、多人会签、自动判定、会签判定]
private String description;//任务描述
private String remark;//任务备注(同任务描述)
private Long orderNum ;排序码
private String expression ;//el表达式
private String taskListener ; //监听器(包含的任务监听器)
private String parentKey ; //为表示节点间的关系,特加入parentKey
/*-------------------- 任务环节个性设置 --------------------*/
private Boolean canAudit = true; //可填写意见
private Boolean canAtch = false; //可添加附件
private Boolean canDelete = false; //可取消流程
流程参数表:
params_record(对应对象:ParamsRecord)
说明:此表为启动工作流时,所保存的必须传入的初始化参数,至于流程流转过程中的中间参数,则在当前版本中并未保存。
Domain:
private Long id; //主键
private String paramName;//参数名称
private String paramType;//参数类型(暂未用到)
private String paramVal;//参数值
private Long flowId;//所属流程定义ID
private Boolean isInit;//是否为初始化参数(true:是 false:否)
页面展示
Web编辑器
页面:
将activiti-explorer.war放到tomcat的webapps下,启动tomcat服务器即可。
在网址输入:http://localhost:8080/activiti-explorer/
Tips:用户名和密码均输入kermit(内置用户)即可登录。
Tips:可以选择新建或导入即可进入流程设计主界面。
使用方法介绍:
此处主要对流程定义配置选项进行说明:
☆Process identitier:流程定义的Key(业务端用来判断的唯一标志,对应FlowDef:flowKey字段)
Name:流程名称(对应FlowDef:name字段)
Documentation:流程说明(对应FlowDef:description和remark字段)
Process author:流程作者
Event listeners:事件监听器
Execution listerners:执行流监听
Message definitions:消息定义(可通过消息启动流程)
Signal definitions:信号定义(可通过信号启动流程)
点击选中任意一个任务环节,即可进入任务定义界面,以下将介绍各个配置项的含义
Id:任务定义的业务Key(对应TaskDef的taskKey字段)
Name:任务定义的名称(对应TaskDef的name字段)
Documentation:任务描述(对应TaskDef的description和remark字段)
Execution listeners:执行流监听器
Assignments:处理人(常用el表达式进行配置)
Task listeners:任务监听器
形如:
配置为:${usertask03_assignment}
则后台以map形式进行赋值:paramsMap.put("usertask03_assignment","zhangsan")
☆以下配置项中含(Multi-instance)内容的均为多人会签任务需要配置的选项
Multi-instance type:多人任务类型
主要分为:Parallel(并行关系:即所有参与会签的人员都处理完该任务节点后触发向下执行的执行流)
Sequential(按次序执行:即参与会签的人员依次进行处理,等待所有人处理完成后触发向下执行的执行流)
Cardinality(Multi-instance):暂时未涉及到,还请各位大神帮忙解释一下。。
Collection(Multi-instance):可做处理人列表使用(常用el表达式)
形如:
配置为:${usertask01_assignment}
则后台以map形式进行赋值:paramsMap.put("usertask01_assignment",new ArrayList<T>()); //map的key为List对象。
Element variable(Multi-instance):为上一配置项中list集合中的每一个对象,可进行自定义为user01_assignment(普通字符串,非el表达式),在工作流官方表中可以查看到list.size()条执行流。
☆本篇文章使用以下工作流定义进行说明:
Tips:使用工作流左侧的组件树即可绘制流程图,操作起来也是十分方便。
业务系统页面
导入新流程
本系统通过导入并解析xml的方式,将xml文件中的流程信息新增入系统中,这样简化了导入流程的操作,否则既要绘制流程图,又要将在绘制过程中已经填好的信息在重新填一遍。
操作页面如下图:
Tips:此前端页面为最简单的Modal对话框和一个Upload上传组件,直接上传文件即可。
业务判断:在上传时如果有正在运行的实例,那么则不允许上传,这样可以避免不同的运行流程同时走不同版本的流程定义文件(判断相同文件的标准为:两张bpmn文件的流程定义key相同,即FlowDef中的flowKey字段相同)此判断需根据工作中的实际需求进行自行修改。
代码
导入行流程
前端
TableData.vue
<template>
<div>
<div class="btn-class">
<Button type="primary" @click="createNewProcess">导入新流程</Button>
</div>
<ModalView ref="modal"></ModalView>
</div>
</template>
<script>
import ModalView from './ModalView'
export default {
name: 'TableData',
components: { ModalView },
data () {
return {
columns12: [],
data6: []
}
},
mounted(){
},
methods: {
createNewProcess(){
this.$refs.modal.modal1 = true
}
</script>
ModalView.vue
<template>
<div>
<Modal v-model="modal1" :title="title">
<Form ref="flowDef" :model="flowDef">
<input type="hidden" v-model="flowDef.id"/>
<input type="hidden" v-model="flowDef.bpmn"/>
<FormItem label="流程规范文件:" prop="bpmn">
<Upload :default-file-list="defaultList" accept=".xml" :on-remove="removeFile" :on-preview="downLoad" :on-error="handleError" :on-success="handleSuccess" :action="this.$store.state.globals.uploadUrl">
<Button icon="ios-cloud-upload-outline">上传文件</Button>
</Upload>
</FormItem>
</Form>
</Modal>
</div>
</template>
<script>
import {save} from '../api/activityManagement'
export default {
name:'ModalView',data () { return { modal1: false, title:'导入新流程', defaultList:[], flowDef:{} } },
methods:{
handleSuccess (response, file){
this.flowDef = []
this.flowDef = response.responseData.data
save(this.flowDef).then(res => {
//可自行处理逻辑,实际为保存到数据库后的回调方法
})
}
</script>
activityManagement.js
import axios from 'axios'
axios.defaults.baseURL = 'http://localhost:8088/activity_management/';
/**保存实体**/
export const save = flowDef => {
return axios.request({
url: 'flowDef/save',
data: flowDef,
method: 'post'
})
}
globals.js
export default {
state: {
uploadUrl:'http://localhost:8088/activity_management/common/upload'
},
mutations: { },
actions: { }
}
后端:
代码说明:
①JsonResult 为本人封装的返回对象,包括:int responseCode(结果码),Enum responseMessage(结果信息)和responseData(Map对象,key:常为data,value:需返回前台的数据)属性;
②本文及系列所有代码均展示关键代码,其余业务代码因项目不同而区别较大,本文仅对共性代码进行说明,望各位谅解;
③除非有特殊情况需要特别说明外,本文后端代码只展示Controller层代码,不展示service层和mapper.xml中的代码。
FlowDefController.java(保存实体功能)
public JsonResult save(@RequestBody FlowDef flowDef){
try {
String flowKey = flowDef.getFlowKey() ; //获取流程定义的唯一标识
FlowDef fd = flowDefService.selectByFlowKey(flowKey); //根据flowKey查询对象
//若对象为空则新增,反之则根据具体业务规则更新该实体
if(fd == null){
flowDefService.insert(flowDef) ;
}else{
//除了instanceQuantity和flowKey外,其余均应更新,且将publishDate和defId置空fd.set()方法。。。
flowDefService.update(fd) ;
//更新成功后,应将taskList删除(后续更新)
taskDefService.deleteBatchByFlowId(fd.getId());
}}catch (Exception e){e.printStackTrace();}
return jr;}
CommonController:(上传功能)
public JsonResult upload(@RequestParam MultipartFile file){
//解析 MultipartFile 对象,并返回对应的流程定义实体
FlowDef flowDef = XmlParseUtils.getObjects(FileUtils.multipartFileToFile(file));
//根据flowKey获取实体
FlowDef fd = flowDefService.selectByFlowKey( flowDef.getFlowKey() );
// ,若为空,则返回错误信息,因为原同flowKey版本的流程定义下还存在正在运行的实
//例时,则无法导入,反之返回成功信息。
if(fd != null && fd.getInstanceQuantity()>0){...}
}
XmlParseUtils(getObjects:解析xml文档,返回FlowDef对象)
// 使用SAX解析方式
public static FlowDef getObjects(File file) throws DocumentException{
FlowDef fd = new FlowDef() ;
SAXReader reader = new SAXReader();
Document document = reader.read(file);
Element processEle = document.getRootElement();
Iterator it = processEle.elementIterator();
while(it.hasNext()){
Element element = (Element) it.next();
if("process".equals(element.getName())) {
List<Attribute> attributes = element.attributes();
for(Attribute attribute : attributes){
//保存key字段
if(attribute.getName().equals("id")){
fd.setFlowKey( attribute.getValue() );
}else if(attribute.getName().equals("name")) {
//保存name字段
fd.setName(attribute.getValue());
}
Iterator itt = element.elementIterator();
while(itt.hasNext()){
Element child = (Element) itt.next();
String nodeName = child.getName();
//保存decription字段
if("documentation".equals(nodeName)) {
fd.setDescription(child.getTextTrim());
fd.setRemark(child.getTextTrim());
}}}}
String filePath = Globals.BPMN_DIR+file.getName() ;
fd.setBpmn(filePath);
fd.setCreateDate(new Date());
fd.setFlowType("CUSTOM");
fd.setStatus("-1"); // -1:待确认状态
return fd ;}
总结
至此Activiti工作流管理系统的系列一完成了,该文章介绍了项目基本架构设计,web编辑器的介绍及基本使用方法以及导入新流程功能。如果各路大神对此有什么建议或意见,还请不吝赐教,本人将在系列第二篇中进行完善。
由于篇幅的限制,本来想介绍发布功能的实现,但是写着写着发现内容太多了,因此将此功能放到系列第二篇中。并且本人对于简书并不是十分熟悉,因此文章可能会影响大家的观看,对此本人向各位说声抱歉,未来本人将不断熟悉这一平台,正确能为大家带来最好的观看体验,让大家能够方便的了解工作流的相关知识,谢谢大家!
下篇预告
发布功能的前台与后台实现
传递爱~
目前正处于新型冠状肺炎的隔离期间,在此希望祖国能够顺利渡过难关!武汉加油!中国加油!