前言
当工作流流程图发布完成之后,下一步就是启动工作流,也是工作流引擎的核心功能,本篇重点将要对工作流的启动和流程进行详细说明。
说明:所有项目配置均在系列第一篇文章中进行介绍,配置系列通用。
系列三内容
初始化参数、启动工作流、执行工作流
页面总览
功能详细说明
初始化参数
说明
在实际项目中,有可能会有如下需求:需要在启动工作流的时候,提前向工作流中传参(全局变量),这些参数往往在业务上具有重要意义,例如:xxId,status等。因此此工作流管理系统特加入此功能,在启动流程前对参数进行定义,并在启动时将这些参数赋值到流程实例中。
此处包含两个子功能:
打开参数列表功能、保存参数列表功能
页面
前端关键代码
TableData.vue
打开参数列表
<Button type="info" :disabled="row.defId === null" size="small" @click="initParams(row)">初始化参数</Button>
<Modal v-model="initParamsModal" width="540" :mask-closable="false" :closable="false" :footer-hide="true">
<input type="hidden" v-model="flowId"/>
请输入初始化参数:<Input v-model="paramsName" placeholder="各参数间以','隔开" style="width: 260px" />
<Button type="primary" size="small" @click="confirmParams">确认</Button>
<Button type="primary" size="small" @click="clearParams">清空</Button>
<Button type="primary" size="small" @click="cancelParams">取消</Button>
</Modal>
return {
initParamsModal:false,
paramsName:'',
flowId:''
}
methods: {
//初始化方法
initParams(row){
this.initParamsModal = true
const flowId = row.id ;
this.flowId = flowId ;
openParams({
flowId:flowId
}).then(res => {
if(res.data.responseCode === 1){
let paramsName = res.data.responseData.paramsName ;
this.paramsName = paramsName === null ? '':paramsName ;
}else{}
})
}
}
保存参数列表
//保存参数列表
confirmParams(){
const flowId = this.flowId
const paramsName = this.paramsName
this.initParamsModal = false
saveParams({
flowId:flowId,
paramsName:paramsName
}).then(res => {
if(res.data.responseCode === 1){
this.$Message.success('保存参数成功!');
}else{
this.$Message.error('保存参数失败!');
}
//清空数据
this.paramsName = ''
})
}
后端关键代码
ParamsRecordController
打开原参数列表
@Autowired
private ParamsRecordService paramsRecordService ;
/**
* 打开参数列表
* @return
*/
@RequestMapping("/open")
@Transactional
public JsonResult open(Long flowId){
JsonResult jr = new JsonResult() ;
resultMap = new HashMap<String,Object>() ;
try {
String paramsName = paramsRecordService.getParamsNameByFlowId(flowId);
//获取到原参数列表(以“,”隔开的字符串)
resultMap.put("paramsName",paramsName) ;
jr.setResponseData(resultMap);
}catch (Exception e){
e.printStackTrace();
}
return jr;
}
保存参数列表
/**
* 保存实体
* @return
*/
@RequestMapping("/save")
@Transactional
public JsonResult save(Long flowId,String paramsName){
JsonResult jr = new JsonResult() ;
try {
//先清空所有的参数
paramsRecordService.deleteBatchByFlowId(flowId);
if(StringUtils.isNotEmpty(paramsName)){
//进行保存,如果参数列表不为空,则执行保存操作,否则只清空,为了应对前端清除按钮的功能
List<ParamsRecord> list = strToObj(flowId,paramsName) ;
paramsRecordService.insertBatch(list) ;
}
jr.setResponseMessage(ResultEnum.SUCCESS);
jr.setResponseCode(1);
}catch (Exception e){
e.printStackTrace();
jr.setResponseMessage(ResultEnum.EXCEPTION);
jr.setResponseCode(0);
}
return jr;
}
/**
* 将前台获取到的str转化为ParamsRecord实体类
* str -> paramsRecord obj
* @param str
*/
private static List<ParamsRecord> strToObj(Long flowId,String str){
String[] paramsName = str.trim().split(",");
List<ParamsRecord> list = new ArrayList<ParamsRecord>() ;
for(String pname:paramsName){
ParamsRecord pr = new ParamsRecord() ;
pr.setFlowId(flowId);
pr.setIsInit(true);
pr.setParamName(pname);
list.add(pr) ;
}
return list ;
}
ParamsRecordServiceImpl
@Override
public List<ParamsRecord> getParamsListByFlowId(Long flowId) {
//根据flowId获取该流程定义下的原参数列表
List<ParamsRecord> list = mapper.getParamsListByFlowId(flowId) ;
if(list == null) return null ;
return list ;
}
启动工作流
说明
在本系统中,根据上文内容,启动工作流分为含参启动和无参启动,二者的区别即:含参启动是将上文中的参数列表进行罗列,并分别传初始参数值后启动;无参启动则不包含此过程。
页面展示
前端代码
TableData.vue
<Button type="success" :disabled="row.defId === null" size="small" @click="startProcess(row)">启动</Button>
<Modal :closable="false" :footer-hide="true" :mask-closable="false" v-model="startModal"
width="350">
<FormItemActiveAdd @change-modal="changeModal" ref="formItemActiveAdd"></FormItemActiveAdd>
</Modal>
components: {
FormItemActiveAdd
}
startProcess(row){
//流程定义ID
const processDefinitionId = row.defId ;
//流程定义业务主键ID
const flowId = row.id ;
let o = {}
getParamsListByFlowId({
flowId:flowId
}).then(res => {
this.startModal = true
let obj = {};
let arr = res.data.responseData.list ;
if(arr.length === 0){
//如果数组的长度为0,则不需要打开
this.$Message.warning('未获取到初始化参数列表,直接启动即可!');
}else{
for(let key in arr){
obj[key] = arr[key]
}
this.$refs.formItemActiveAdd.paramsRecord = obj ;
}
this.$refs.formItemActiveAdd.processDefinitionId = processDefinitionId ;
})
},
FormItemActiveAdd.vue
<template>
<Form :model="paramsRecord" style="width: 100%;text-align: left">
<FormItem
v-for="(item, index) in paramsRecord"
v-if="item.id"
:key="index"
:label="'参数'+(++index)+':'+item.paramName"
:prop="item.paramName" class="FormItem-class">
<Row>
<Col span="28">
<br><Input type="text" v-model="item.paramVal" placeholder="Enter something..."></Input>
</Col>
</Row>
</FormItem>
<FormItem>
<Button type="success" @click="handleSubmit" style="margin-left: 95px">启动</Button>
<Button type="primary" @click="handleReturn" style="margin-left: 8px">返回</Button>
</FormItem>
</Form>
</template>
<script>
import {
start
} from '../api/activityManagement'
export default {
name: 'FormItemActiveAdd',
data () {
return {
index: 1,
processDefinitionId:'',
paramsRecord:{
},
}
},
methods: {
handleSubmit () {
const processDefinitionId = this.processDefinitionId ;
const obj = Object.values(this.paramsRecord) ;
let o = {} ;
for(let i = 0,len=obj.length; i < len; i++) {
o[obj[i].paramName] = obj[i].paramVal ;
}
start({
processDefinitionId:processDefinitionId,
paramMap:o
}).then(res =>{
if(res.data.responseCode === 1){
this.$Message.success('启动成功!流程实例ID:'+res.data.responseData.pid);
}else {
this.$Message.error('启动失败!');
}
this.$emit('change-modal',false)
})
},
handleReturn () {
this.paramsRecord = {}
this.$emit('change-modal',false)
}
},
mounted () {
}
}
</script>
<style scoped>
</style>
注:参数列表的展示使用的是FormItem组件,具体可查看iview官方文档的4.x版本说明
activityManagement.js
/**启动工作流**/
export const start = (processDefinitionId,paramMap) => {
return axios.request({
url: 'workflow/start',
params: processDefinitionId,
data:paramMap,
method: 'post'
})
}
后端代码
WorkflowController
/**
* 启动工作流
* @param processDefinitionId,paramVal
* @return
*/
@RequestMapping("/start")
public JsonResult start(String processDefinitionId, String paramMap){
JsonResult jr = new JsonResult() ;
resultMap = new HashMap<String,Object>() ;
try {
Map<String, Object> map = JsonUtils.stringToMap(paramMap);
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId,map);
//启动成功后获取流程实例ID
String processInstanceId = processInstance.getId() ;
jr.setResponseCode(1);
jr.setResponseMessage(ResultEnum.SUCCESS);
resultMap.put("pid",processInstanceId) ;
jr.setResponseData(resultMap) ;
}catch (Exception e){
e.printStackTrace();
jr.setResponseCode(0);
jr.setResponseMessage(ResultEnum.EXCEPTION);
}
return jr ;
}
JsonUtils
/**
* String -> Map
* @param str
* @return
*/
public static Map<String ,Object> stringToMap(String str) {
if (StringUtils.isEmpty(str)) {
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
map = gson.fromJson(str, Map.class);
return map;
}
执行工作流
说明
执行工作流即当前实例继续向下执行,直至实例完成。
特殊说明:该篇内容中只介绍执行下一步的相关功能实现,但实际在此功能模块中涉及到其他内容,此模块莫心急,会在之后的系列篇中进行介绍
页面展示
亮点介绍
如上图所示,此功能包含下一步路由提醒功能。目的在于由于下一步可能存在互斥网关等情况,会在连接线上填写条件,但是在实际运转过程中往往会忘记这个条件,所以在此添加该功能,使得系统更加人性化~
前端代码
ActiveProcess.vue
<template>
<div class="div-class">
<Card style="height: 750px">
<p slot="title">
<Icon type="ios-film-outline"></Icon>
活动实例列表({{this.$route.params.flowKey}})
</p>
<Button type="primary" class="btn-class" @click="returnPage">返回</Button>
<ul>
<Table height="550" border ref="selection" :columns="columns12" :data="data6" style="margin-top: 50px">
<template slot-scope="{ row, index }" slot="action">
<div class="slot_class">
<Button type="info" size="small" @click="execute(row)">执行下一步</Button>
</div>
</template>
</Table>
</ul>
</Card>
</template>
<script>
import {
execute,
openExecute
} from '../../api/activityManagement'
import FormItemExecuteAdd from '../FormItemExecuteAdd'
export default {
name: 'ActiveProcess',
components: {
FormItemExecuteAdd
},
data () {
return {
flowId:'',
processInstanceId:'',
executeModal:false
}
},
methods: {
execute(row){
const processInstanceId = row.processInstanceId ;
const flowId = this.$route.params.flowId ;
if(processInstanceId === '' || flowId === ''){
this.$Message.warning('数据异常,无法加载!');
return ;
}
openExecute({
flowId:this.$route.params.flowId,
processInstanceId:processInstanceId
}).then(res => {
if(res.data.responseCode === 1){
const resArr = res.data.responseData.list ;
let message = ''
resArr.forEach(function(e){
message += '<p>' + e.toString() + '</p>'
});
console.log(message)
this.$Notice.open({
name:'executeNotice',
title: '下一步路由提醒',
duration:0,
desc: message
});
this.executeModal = true ;
this.$refs.paramList.processInstanceId = processInstanceId ;
}else{
this.$Message.warning('数据异常,无法加载!');
}
})
}
}
}
</script>
activityManagement.js
/**打开下一步Modal**/
export const openExecute = params => {
return axios.request({
url: 'workflow/openExecute',
params: params,
method: 'post'
})
}
FormItemExecuteAdd.vue
<template>
<Form ref="paramList" :model="paramList" :label-width="80" style="width: 300px">
<FormItem
v-for="(item, index) in paramList.items"
:key="index"
:label="'参数:'"
:prop="'items.' + index + '.value'">
<Row>
<Col span="18">
<Input style="width: 70px" type="text" v-model="item.key" placeholder="key"></Input> =
<Input style="width: 80px" type="text" v-model="item.value" placeholder="value"></Input>
</Col>
<Col span="4" offset="1">
<Button @click="handleRemove(index)">删除</Button>
</Col>
</Row>
</FormItem>
<FormItem>
<Row>
<Col span="12">
<Button type="dashed" long @click="handleAdd" icon="md-add">添加</Button>
</Col>
</Row>
</FormItem>
<FormItem>
<Button type="success" @click="handleSubmit('paramList')">执行</Button>
<Button type="primary" @click="handleReturn('paramList')" style="margin-left: 8px">返回</Button>
</FormItem>
</Form>
</template>
<script>
import {execute} from '../api/activityManagement'
export default {
data () {
return {
index: 1,
processInstanceId:'',
paramList: {
items: [
]
}
}
},
methods: {
handleSubmit (name) {
const obj = Object.values(this.paramList.items) ;
let o = {} ;
for(let i = 0,len=obj.length; i < len; i++) {
if((obj[i].key !== '' && obj[i].key !== undefined && obj[i].key !== null) &&
(obj[i].value !== '' && obj[i].value !== undefined && obj[i].value !== null)){
o[obj[i].key] = obj[i].value ;
}
}
execute({
processInstanceId:this.processInstanceId,
paramMap:o
}).then(res =>{
if(res.data.responseCode === 1){
this.$Message.success('执行成功!');
}else {
this.$Message.error('执行失败!');
}
this.$emit('change-modal',false)
})
},
handleReturn (name) {
// this.paramList = {items: [{}]}
this.paramList = {items:[]}
this.$emit('change-modal',false)
},
handleReset (name) {
this.$refs[name].resetFields();
},
handleAdd () {
this.index++;
this.paramList.items.push({
key:'',
value: '',
index: this.index
});
},
handleRemove (index) {
this.index-- ;
this.paramList.items.splice(index,1)
}
}
}
</script>
后端代码
WorkflowController
获取下一步路由的方法,前提是已经将任务环节信息以及各环节间逻辑关系保存到自己定义的业务表中,此系列该表为task_def,具体实现方案请参照系列第二篇~
/**
* 打开执行下一步操作时,查看下一步路由
* @param flowId
* @param processInstanceId
* @return
*/
@RequestMapping("/openExecute")
public JsonResult openExecute(Long flowId,String processInstanceId){
JsonResult jr = new JsonResult() ;
resultMap = new HashMap<String,Object>() ;
try {
//这里需要可以获取下一步路由
Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
List<TaskDef> nextTasks = taskDefService.getTaskNexts(flowId, task.getTaskDefinitionKey());
List<String> list = new ArrayList<String>() ;
String str = "" ;
for(TaskDef td:nextTasks){
String type = td.getType() ;
//只要不是任务类或者事件,那么应该继续向下查询
if(!type.contains("Task") && !type.contains("Event")){
getNextTasks(flowId,td.getTaskKey(),task.getName()+"->",list);
}else{
String expressionStr = td.getExpression() ;
str = task.getName() + "("+(StringUtils.isEmpty(expressionStr) ? "-" : expressionStr)+")"+ "->" + td.getName() ;
list.add(str) ;
}
}
resultMap.put("list",list) ;
jr.setResponseData(resultMap);
jr.setResponseCode(1);
jr.setResponseMessage(ResultEnum.SUCCESS);
}catch (Exception e){
e.printStackTrace();
jr.setResponseCode(0);
jr.setResponseMessage(ResultEnum.EXCEPTION);
}
return jr ;
}
/**
* private methods 01
* 获取下一步路由
* @param flowId
* @param taskKey
* @return
*/
private List<String> getNextTasks(Long flowId,String taskKey,String str,List<String> l){
//根据流程定义业务主键ID和该环节的key查找其下一步路由
List<TaskDef> taskNexts = taskDefService.getTaskNexts(flowId, taskKey);
StringBuffer tStr = new StringBuffer(str) ;
for(TaskDef td : taskNexts){
String type = td.getType() ;
String taskName = td.getName() ;
//只要不是任务类或者事件,那么应该继续向下查询
if(!tStr.toString().contains(taskName) && (!type.contains("Task") && !type.contains("Event"))){
if(!StringUtils.isEmpty(td.getExpression())){
//如果涉及多个网关条件,则需要一一列出
tStr.append("("+td.getExpression()+")") ;
}
getNextTasks(flowId,td.getTaskKey(),tStr.toString(),l) ;
}else{
String expressionStr = td.getExpression() ;
tStr.append("("+(StringUtils.isEmpty(expressionStr) ? "-" : expressionStr)+")"+td.getName()) ;
l.add(tStr.toString()) ;
//初始化tStr,让其=str
tStr = new StringBuffer(str) ;
}
}
return l ;
}
/**
* 执行下一步(dubbo)
* @param processInstanceId
* @param paramMap
* @return
*/
@RequestMapping("/execute")
public JsonResult execute(String processInstanceId, String paramMap){
JsonResult jr = new JsonResult() ;
resultMap = new HashMap<String,Object>() ;
try {
Map<String, Object> map = JsonUtils.stringToMap(paramMap);
Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
taskService.complete(task.getId(),map);
jr.setResponseCode(1);
jr.setResponseMessage(ResultEnum.SUCCESS);
}catch (Exception e){
e.printStackTrace();
jr.setResponseCode(0);
jr.setResponseMessage(ResultEnum.EXCEPTION);
}
return jr ;
}
总结
至此,有关于初始化参数、启动工作流、执行工作流的开发全部完成,此处涉及大量的业务端代码,由于本人对于前端掌握的并不是很好,所以可能途中会存在一些细节问题待完善,因此各位若对代码有任何不同的意见或建议欢迎下方留言,大家共同进步!
下篇预告
1.正在运行中实例和历史流程查看
2.工作流的挂起与激活
敬请期待~~~
第三篇完结~