Activiti工作流管理系统(三)

前言

当工作流流程图发布完成之后,下一步就是启动工作流,也是工作流引擎的核心功能,本篇重点将要对工作流的启动和流程进行详细说明。
说明:所有项目配置均在系列第一篇文章中进行介绍,配置系列通用。

系列三内容

初始化参数、启动工作流、执行工作流

页面总览

页面总览.png

功能详细说明

初始化参数

说明

在实际项目中,有可能会有如下需求:需要在启动工作流的时候,提前向工作流中传参(全局变量),这些参数往往在业务上具有重要意义,例如:xxId,status等。因此此工作流管理系统特加入此功能,在启动流程前对参数进行定义,并在启动时将这些参数赋值到流程实例中。
此处包含两个子功能:
打开参数列表功能、保存参数列表功能

页面

初始化参数.png

前端关键代码

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 ;
    }

启动工作流

说明

在本系统中,根据上文内容,启动工作流分为含参启动和无参启动,二者的区别即:含参启动是将上文中的参数列表进行罗列,并分别传初始参数值后启动;无参启动则不包含此过程。

页面展示

无参启动.png

含参启动.png

前端代码

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;
    }

执行工作流

说明

执行工作流即当前实例继续向下执行,直至实例完成。

特殊说明:该篇内容中只介绍执行下一步的相关功能实现,但实际在此功能模块中涉及到其他内容,此模块莫心急,会在之后的系列篇中进行介绍

页面展示

下一步列表.png

执行下一步+路由提醒.png

带参执行下一步.png

亮点介绍

如上图所示,此功能包含下一步路由提醒功能。目的在于由于下一步可能存在互斥网关等情况,会在连接线上填写条件,但是在实际运转过程中往往会忘记这个条件,所以在此添加该功能,使得系统更加人性化~

前端代码

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.工作流的挂起与激活

敬请期待~~~
第三篇完结~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容