基于activiti框架的工作流提交、审批以及撤销

基于activiti框架的工作流提交、审批以及撤销

背景:在一个企业中,大部分的事情都不不是一个人做决定的,往往需要经过几个层级的审批。这样就有一个流程,工作流就是方便这一个方面的需求。

1.认识工作流常用表

  • 历史节点表( act_hi_actinst )
    • 简要描述

      历史活动信息,这里记录流程流转过的所有节点。act_hi_taskinst表中只记录了历史节点表中节点类型为usertask的信息。
      
    • 表结构说明

      历史节点表
  • 运行时流程执行实例表( act_ru_execution )
    • 简要描述

      流程执行记录表。
      
    • 表结构说明

      运行时流程执行实例表
  • 运行时任务节点表( act_ru_task )
    • 简要描述

      运行时任务数据表。
      
    • 表结构说明

      运行时任务节点表

2.工作流的提交(以HGXP项目中的供应商提交为例)

  • 前台请求

    $.ajax({
        type: 'GET',
        url: "${base.contextPath}/cux/gxp/vendor/basic/pubapprove",//提交审批先走各自的入口,方便进行状态等的校验。然后再调用通入审批。
        success: function (json) {
            kendo.ui.showInfoDialog({
                message: json.message
            }).done(function () {
                if (json.success) {
                    window.location.reload();//如果提交成功,刷新状态
                }
            });
        },
        async: false,
        dataType: "json",
        data: {"docId": headerId, "approveCode": "GXP_VENDOR"} //提交需要审批的供应商头ID,以及在通用审批设置平台中维护的CODE(对应应该走哪条工作流)
    });
    

    如下图,HGXP中就是在这个块码中维护approveCode

    mark

    并在通用审批设置平台中维护:
    mark

    可以看出,在这个通用审批设置平台中,我们维护好了各工作流对应的头表、主键以及记录审批状态的字段status_Code

  • 后台走通用审批处理service (PubApproveServiceImpl.java)

    public ResponseData  pubApprove(IRequest request, String approveCode, Long docId) {
        PubApproveSetup pubApproveSetup = new PubApproveSetup();
        ResponseData responseData = null;
        String errMsg = "";
        int result = 0;
    
        //根据docId查找对应资质头信息并校验是否可以提交审批
        if("GXP_VENDOR".equals(approveCode)){
            GxpVendorBasic gxpVendorBasic =new GxpVendorBasic();
            gxpVendorBasic.setBasicId(docId);
            List<GxpVendorBasic> gxpVendorBasicList=gxpVendorBasicMapper.select(gxpVendorBasic);
            if(gxpVendorBasicList.size()==1){
                String status=gxpVendorBasicList.get(0).getStatusCode();
                responseData =returnResponseData(status);  //这一步就是校验该资质头是否可以提交(不为已提交和已审批状态)
                if(responseData !=null){
                    return responseData;
                }
    
            }
        }
    
        /*************************************************************************
         * 1.0 根据approveCode获取审批配置平台配置属性
         ************************************************************************ */
        pubApproveSetup.setApproveCode(approveCode);
        pubApproveSetup = pubApproveSetupMapper.selectOne(pubApproveSetup);
        if (pubApproveSetup == null) {
            errMsg += "审批代码" + approveCode + "未在通用审批平台定义!";
            responseData = new ResponseData(false);
            responseData.setMessage(errMsg);
            return responseData;
        }
    
        /*************************************************************************
         * 1.0 根据approveCode获取审批配置平台配置属性
         ************************************************************************ */
        if ("Y".equals(pubApproveSetup.getApproveByself()) && "Y".equals(pubApproveSetup.getApproveAlterFlag())) {
            //如果为自审批并且审批直接修改状态,则直接修改状态
            try {
                result = updateDocStatus(pubApproveSetup.getTableName(), pubApproveSetup.getTableKey(), pubApproveSetup.getStatusField(), pubApproveSetup.getApproveStatus(), docId);
            } catch (Exception e) {
                e.printStackTrace();
                errMsg += "自审批失败:" + ExceptionUtils.getRootCauseMessage(e);
            }
        } else if ("N".equals(pubApproveSetup.getApproveByself())) {
            //提交审批工作流进行审批
            //组合参数
            List<RestVariable> list=new ArrayList<>();
            RestVariable var1=new RestVariable();
            var1.setName("approveCode");
            var1.setValue(approveCode);
            RestVariable var2=new RestVariable();
            var2.setName("processDefinitionKey");
            var2.setValue(pubApproveSetup.getActivitiKey());
            RestVariable var3=new RestVariable();
            var3.setName("docId");
            var3.setValue(docId);
            RestVariable var4=new RestVariable();
            var4.setName("title");
            var4.setValue(pubApproveSetup.getFormTitle());
            RestVariable var5=new RestVariable();
            var5.setName("url");
            var5.setValue(pubApproveSetup.getFormUrl());
            list.add(var1);
            list.add(var2);
            list.add(var3);
            list.add(var4);
            list.add(var5);
            ProcessInstanceResponse response=startActivitiService.startActiviti(request,list);
            System.out.println(response==null);
            if(response!=null)
            {
                if ("Y".equals(pubApproveSetup.getSubmitAlterFlag())) {
                    //如果不为自审批,并且提交时自动修改状态,则修改为提交动作的状态
                    errMsg+=processStatus(request,approveCode,docId,"SUBMIT");
                }
                //工作流提交成功 向通用审批事务表表插入数据
                PubApproveTransaction pubApproveTransaction=new PubApproveTransaction();
                pubApproveTransaction.setActivitiKey(pubApproveSetup.getActivitiKey());
                pubApproveTransaction.setActivitiId(Long.parseLong(response.getId()));
                pubApproveTransaction.setApproveCode(approveCode);
                pubApproveTransaction.setDocId(docId);
                pubApproveTransaction.setObjectVersionNumber(1L);
                pubApproveTransactionService.insertSelective(request,pubApproveTransaction);
            }
            else
            {
                errMsg+="提交工作流出错!";
            }
        }
    
        if (errMsg==null||"".equals(errMsg)) {
            responseData = new ResponseData(true);
            responseData.setMessage("提交成功!");
        } else {
            responseData = new ResponseData(false);
            responseData.setMessage(errMsg);
        }
    
        return responseData;
    }
    

    上面的代码可以分割为:

    • 排除为已提交和已审批的状态(若为这两个状态就提示不能提交)
    • 获取通用审批配置平台上维护的数据
    • 判断是否为自审批
      • 若为自审批则直接修改资质头的状态不发起工作流
      • 若不为自审批状态,则发起工作流,List<RestVariable>中我们就将通用审批配置平台中的值全部带在工作流中

    其中startActivitiService.startActiviti(request,list)这个方法是发起工作流的方法

    mark

    实现类实现红色框中的接口,重写其中的startActiviti方法:

    @Autowired
    private IActivitiService activitiService;
    
    @Override
    public ProcessInstanceResponse startActiviti(IRequest irequest, List<RestVariable> list) {
        ProcessInstanceCreateRequest request=new ProcessInstanceCreateRequest();
        request.setBusinessKey("104");
       // request.setProcessDefinitionId(null);
        String key="";
        for (int i=0;i<list.size();i++)
        {
            System.out.println(list.get(i).getName());
            System.out.println(list.get(i).getName()=="processDefinitionKey");
            System.out.println("processDefinitionKey".equals(list.get(i).getName()));
            if(list.get(i).getName()=="processDefinitionKey"||"processDefinitionKey".equals(list.get(i).getName()))
            {
                key=list.get(i).getValue().toString();
            }
        }
        System.out.println(key);
        request.setProcessDefinitionKey(key);
        request.setReturnVariables(false);
        request.setTenantId(null);
        request.setVariables(list);
        request.setTransientVariables(null);
        return activitiService.startProcess(irequest, request);
    }
    

    上面的代码就可以理解为:

    • 模拟一个request请求
      • 其中 processDefinitionKey 就是通用配置平台中维护的工作流程字段,这个字段的作用就决定着我们是走的那一条工作流,这个字段就是与我们流程设计中的流程编码对应
        mark
      • variables 就是我们流程中的变量,这其中就是上一步骤中获取的通用审批配置平台中数据,存在 运行时流程变量数据表(act_ru_variable)中

    至此工作流的发起工作就完了,工作流程发起了之后我们剩下需要做的工作就是改变我们对应资质头的状态,并记录工作流程到 通用审批事务表(cux_pub_approve_transaction)中

    if ("Y".equals(pubApproveSetup.getSubmitAlterFlag())) {
        //如果不为自审批,并且提交时自动修改状态,则修改为提交动作的状态
        errMsg+=processStatus(request,approveCode,docId,"SUBMIT");
    }
    //工作流提交成功 向通用审批事务表表插入数据
    PubApproveTransaction pubApproveTransaction=new PubApproveTransaction();
    pubApproveTransaction.setActivitiKey(pubApproveSetup.getActivitiKey());
    pubApproveTransaction.setActivitiId(Long.parseLong(response.getId()));
    pubApproveTransaction.setApproveCode(approveCode);
    pubApproveTransaction.setDocId(docId);
    pubApproveTransaction.setObjectVersionNumber(1L);
    pubApproveTransactionService.insertSelective(request,pubApproveTransaction);
    
    • 第一步,就是改变资质头的状态为 SUBMITTED(已提交)

    • 第二部,就是将工作流程记录到通用审批事务表中

      • activitiKey 工作流程的标识即:流程设计页面中的流程编码
      • activitiId 发起工作流后的,产生的流程Id
      • approveCode 审批代码(块码中维护的值)
      • docId 资质头Id
      • objectversionnumber 版本号

3.工作流的审批(以HGXP项目中供应商资质审批为例)

  • 前台请求

    var currentTaskId = '${RequestParameters.taskId!taskId}';   //获取任务ID
    
    $('#btn-approved').click(function () {
        kendo.ui.showConfirmDialog({
            title: $l('hap.confirm'),
            message: $l('hap.confirm') + '<span class="action_ok">' + $('#text-approved').text() + '</span>?'
        }).done(function (e) {
            if (e.button == 'OK') {
                taskAction({approveResult: 'APPROVED'}); //传的参数为:`approveResult`属性值为`APPROVED`的对象
            }
        })
    });
    
    function taskAction(p) {
        p = p || {};         //传进的对象(会带有`approveResult`属性)
        p.action = p.action || 'complete'     //设置 `action` 属性为 `complete` (完成任务)
        if('pending' == currentTaskInfo.delegationState && 'delegate'!=p.action){ //如果有转交任务的操作
            p.action = 'resolve'  //设置 `action` 属性为 `resolve`(转交操作)
        }
        var variables = [];
        if (p.action != 'delegate') {
            var formVars = {};
            formVars.approveResult = p.approveResult;
            formVars.comment = $("#ta-comment").val();
            $.each(formVars, function (k, v) {
                variables.push({name: k, value: v});  //设置 `variables` 的 `approveResult` 和 `comment` 属性的值
            })
        }
    
        var param = {
            assignee: p.targetUser || null,
            action: p.action,  //任务的动作`complete`
            comment: $("#ta-comment").val(), // 审批意见
            variables: variables,
            jumpTarget: p.jumpTarget || null,
            carbonCopyUsers :null,
        };
        /**
        * p{approveResult: "APPROVED", action: "complete"}
        * variables = [{name: "approveResult", value: "APPROVED"}, {name: "comment", value: ""}]
        * param = {assignee: null, action: "complete", comment: "", variables: Array(2), jumpTarget: null, carbonCopyUsers:""}
        */
        $.ajax({
            url: contextPath_ + '/wfl/runtime/<#if isAdmin!false>admin/</#if>tasks/' + currentTaskId,
            type: 'POST',
            contentType: 'application/json',
            data: kendo.stringify(param),
            success: function (args) {
                if (args.success === false) {
                    kendo.ui.showErrorDialog({
                        title: $l('hap.error'),
                        message: args.message
                    });
                } else {
                    if (p.callback) {
                        p.callback(args);
                    } else {
                        kendo.ui.showInfoDialog({
                            title: $l('hap.tip.info'),
                            message: '操作完成!'
                        }).done(function () {
                            closeCurrentWin()
                        });
                    }
                }
            }, error: function (args) {
                kendo.ui.showInfoDialog({
                    title: $l('hap.error'),
                    message: kendo.stringify(args)
                });
            }
        })
    }
    
  • 后台走框架封装好的ActivitiController.class

    @RequestMapping(
        value = {"/runtime/tasks/{taskId}"},
        method = {RequestMethod.POST}
    )
    @ResponseStatus(HttpStatus.OK)
    public void executeTaskAction(@PathVariable String taskId, @RequestBody TaskActionRequestExt actionRequest, HttpServletRequest request, HttpServletResponse response) throws TaskActionException {
        IRequest iRequest = this.createRequestContext(request);
        this.activitiService.executeTaskAction(iRequest, taskId, actionRequest, false);
    }
    
    • 从目前来应用方面来讲,主要就是在前台设置好完成当前任务的几个参数,主要的参数
      • action:"complete"
      • variables中的 approveResult

4.工作流的撤销

  • 前台请求
    function cancelApproveData() {
        $.ajax({
            url:"${base.contextPath}/hmdmwfl/canclewf?id="+headerId+"&approveCode="+"GXP_VENDOR",
            dataType:"json",
            contentType:"application/json",
            type:"POST",
            success:function (data) {
                if(data.success==true){
                    Hap.submitForm({
                        url: '${base.contextPath}/cux/gxp/vendor/basic/ignore/submit',
                        formModel: viewModel.model,
                        success: function (json){
                            if(json.success){
                                kendo.ui.showInfoDialog({
                                    message: "成功"
                                }).done(function () {
                                    window.location.reload();
                                });
                            }else{
                                kendo.ui.showErrorDialog({
                                    message: "失败"
                                }).done(function () {
                                    window.location.reload();
                                });
                            }
                        }
                    });
                }else {
                    kendo.ui.showErrorDialog({
                        message: data.message
                    });
                }
            }
        });
    }
    
    工作流的撤销,前台请求和提交工作流类似,就是从前台传一个basicIdapproveCode到后台
  • 后台走HmdmActivitiController.java
    @RequestMapping(value="/canclewf")
    public ResponseData cancle(HttpServletRequest request, @RequestParam Long id,@RequestParam String approveCode){
        IRequest irequest = createRequestContext(request);
        ResponseData rDate = new ResponseData();
        List<PubApproveTransaction> transactionList=pubApproveTransactionService.selectAllByHeaderId(id,approveCode);
        if(transactionList.size()==0){
            rDate.setSuccess(false);
            rDate.setMessage("此流程为批量分配数据审批流程,正在审批中,不能撤销!");
            return rDate;
        }else {
            PubApproveTransaction approveTransaction=transactionList.get(transactionList.size()-1);
            String procId=approveTransaction.getActivitiId().toString();
    
            // 当前用户的ID
            Long userId = irequest.getUserId();
            User currentUser = userMapper.selectByPrimaryKey(userId);
            String currentUserName = currentUser.getUserName();
            String employeeCode = irequest.getEmployeeCode();
    
            // 根据流程Id,查找流程实例
            HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery();
            historicProcessInstanceQuery.processInstanceId(procId);
            // 流程实例
            HistoricProcessInstance historicProcessInstance = historicProcessInstanceQuery.singleResult();
            // 流程的启动人
            String startUser = historicProcessInstance.getStartUserId();
            String processInstanceId = historicProcessInstance.getId();
            String priorActType = pubApproveTransactionService.selectPriorNodeActType(processInstanceId);
            // 当前用户和流程启动是同一人
            if (employeeCode.equals(startUser)) {
                if(priorActType.equals("startEvent")){
                    // 根据流程实例,查找盖流程实例的所有任务
                    HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery();
                    historicTaskInstanceQuery.processInstanceId(processInstanceId);
                    historicTaskInstanceQuery.orderByTaskCreateTime().asc();
                    List<HistoricTaskInstance> historicTaskInstanceList = historicTaskInstanceQuery.list();
                    // 排除只有一个节点的流程,而且提交人就是审批人的情况
                    TaskQuery taskQuery = taskService.createTaskQuery();
                    List<Task> taskList = taskQuery.processInstanceId(processInstanceId).list();
                    HistoricTaskInstance instance = historicTaskInstanceList.get(0);
                    String taskId = instance.getId();
                    //增加撤销节点,并办理
                    Map<String, Object> variables = new HashMap<String, Object>();
                    variables.put("approveResult", "RETRACT");//REJECTED
                    taskService.addComment(taskId, null, "action", "RETRACT");
                    taskService.addComment(taskId, processInstanceId, "comment", "");  //添加评论
                    taskService.claim(taskId, currentUserName);
                    taskService.complete(taskId, variables); //办理任务,相当于将任务办理掉以此来达到撤销工作流的效果
                    rDate.setSuccess(true);
                    rDate.setMessage(id.toString());
                    return rDate;
                }else {
                    rDate.setSuccess(false);
                    rDate.setMessage("流程已有审批,不能撤回!");
                    return rDate;
                }
            }
        }
    
        rDate.setSuccess(false);
        rDate.setMessage("非发起人,不能撤回!");
        return rDate;
    }
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容