Flowable开发--流程审批(五)

一、流程审批后涉及到的表

表名 描述
act_fo_form_instance 存储用户填充后表单实例信息,FORM_DEFINITION_ID_字段 2bb4ecac-cfb8-11e9-9f13-1a1dea14efe7
act_fo_form_resource NAME_字段 form-2bb4ecac-cfb8-11e9-9f13-1a1dea14efe7
act_hi_actinst 历史的流程实例 插入多条数据
act_hi_identitylink 历史的流程运行过程中用户关系 插入多条数据
act_hi_procinst 历史的流程实例 REV_ 1未完成 2已完成
act_hi_taskinst 历史的任务实例 REV_ 1待认领 2等审批 3已审批
act_hi_varinst 历史的流程运行中的变量信息
act_ru_actinst 存储运行时节点信息 与act_hi_actinst同时存储
act_ru_execution 执行实例表和act_run_task表,一起控制了用户任务的产生与完成等,当在并行网关和会签多实例时,它是会产生多个执行实例,IS_ACTIVE_这个字段的值都是为1,即激活状态,当每完成一个执行实例时,它会把IS_ACTIVE设为0,非激活状态,当所有执行实例完成后,它才会转移到历史,把这个多实例自动删除
act_ru_identitylink 运行时用户关系
act_ru_task 运行时任务表
act_ru_variable 运行的流程中的变量信息

当流程全部走完后,act_ru_表的数据清空了,全部移到了act_hi_

二、流程示例

  1. 创建流程图
    创建OrderApproval.bpmn20.xml文件


    创建文件

    创建文件

    改更扩展名

    流程图设计
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
    <!--订单审批,必须定义ID-->
    <process id="OrderApproval" name="订单审批" isExecutable="true">
        <!--开始事件-->
        <startEvent id="startEvent" name="采购订单"></startEvent>
        <!--流程-->
        <sequenceFlow id="sequenceFlow-3" sourceRef="startEvent" targetRef="approveTask"></sequenceFlow>
        <!--定义任务 flowable:assignee 指定用户ID-->
        <userTask id="approveTask" name="订单审批" flowable:assignee="${userId}">
            <extensionElements>
                <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
            </extensionElements>
        </userTask>
        <!--定义网关-->
        <exclusiveGateway id="decision"></exclusiveGateway>
        <!--定义流程条件-->
        <sequenceFlow id="sequenceFlow-9" sourceRef="decision" targetRef="fail">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!approved}]]></conditionExpression>
        </sequenceFlow>
        <!--定义任务->同意-->
        <serviceTask id="success" name="通过" flowable:class="com.xtsz.workflow.delegate.ReviewApprove"></serviceTask>
        <!--定义结束事件-->
        <endEvent id="approveEnd"></endEvent>
        <endEvent id="rejectEnd"></endEvent>
        <sequenceFlow id="sequenceFlow-e" sourceRef="success" targetRef="approveEnd"></sequenceFlow>
        <sequenceFlow id="sequenceFlow-1" sourceRef="fail" targetRef="rejectEnd"></sequenceFlow>
        <!--定义任务->拒绝-->
        <serviceTask id="fail" name="拒绝" flowable:class="com.xtsz.workflow.delegate.ReviewNoApprove"></serviceTask>
        <sequenceFlow id="sequenceFlow-5" sourceRef="approveTask" targetRef="decision"></sequenceFlow>
        <sequenceFlow id="sequenceFlow-c" sourceRef="decision" targetRef="success">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${approved}]]></conditionExpression>
        </sequenceFlow>
    </process>
    <bpmndi:BPMNDiagram id="BPMNDiagram_OrderApproval">
        <bpmndi:BPMNPlane bpmnElement="OrderApproval" id="BPMNPlane_OrderApproval">
            <bpmndi:BPMNShape bpmnElement="startEvent" id="BPMNShape_startEvent">
                <omgdc:Bounds height="30.0" width="30.0" x="0.0" y="95.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="approveTask" id="BPMNShape_approveTask">
                <omgdc:Bounds height="60.0" width="100.0" x="75.0" y="75.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="decision" id="BPMNShape_decision">
                <omgdc:Bounds height="40.0" width="40.0" x="230.0" y="90.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="success" id="BPMNShape_success">
                <omgdc:Bounds height="60.0" width="100.0" x="320.0" y="0.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="approveEnd" id="BPMNShape_approveEnd">
                <omgdc:Bounds height="28.0" width="28.0" x="620.0" y="16.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="rejectEnd" id="BPMNShape_rejectEnd">
                <omgdc:Bounds height="28.0" width="28.0" x="570.0" y="175.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="fail" id="BPMNShape_false">
                <omgdc:Bounds height="60.0" width="100.0" x="315.0" y="150.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNEdge bpmnElement="sequenceFlow-5" id="BPMNEdge_sequenceFlow-5">
                <omgdi:waypoint x="174.95" y="105.0"></omgdi:waypoint>
                <omgdi:waypoint x="202.5" y="105.0"></omgdi:waypoint>
                <omgdi:waypoint x="202.5" y="110.0"></omgdi:waypoint>
                <omgdi:waypoint x="230.0" y="110.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sequenceFlow-c" id="BPMNEdge_sequenceFlow-c">
                <omgdi:waypoint x="269.9189252336448" y="110.0"></omgdi:waypoint>
                <omgdi:waypoint x="282.0" y="110.0"></omgdi:waypoint>
                <omgdi:waypoint x="282.0" y="30.000000000000004"></omgdi:waypoint>
                <omgdi:waypoint x="319.999999999994" y="30.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sequenceFlow-1" id="BPMNEdge_sequenceFlow-1">
                <omgdi:waypoint x="414.95000000000005" y="180.0"></omgdi:waypoint>
                <omgdi:waypoint x="460.0" y="180.0"></omgdi:waypoint>
                <omgdi:waypoint x="460.0" y="189.0"></omgdi:waypoint>
                <omgdi:waypoint x="570.0" y="189.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sequenceFlow-e" id="BPMNEdge_sequenceFlow-e">
                <omgdi:waypoint x="419.94999999998697" y="30.0"></omgdi:waypoint>
                <omgdi:waypoint x="620.0" y="30.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sequenceFlow-3" id="BPMNEdge_sequenceFlow-3">
                <omgdi:waypoint x="29.949987029268733" y="110.0"></omgdi:waypoint>
                <omgdi:waypoint x="52.5" y="110.0"></omgdi:waypoint>
                <omgdi:waypoint x="52.5" y="105.0"></omgdi:waypoint>
                <omgdi:waypoint x="74.99999999999241" y="105.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sequenceFlow-9" id="BPMNEdge_sequenceFlow-9">
                <omgdi:waypoint x="269.9189252336448" y="110.0"></omgdi:waypoint>
                <omgdi:waypoint x="282.0" y="110.0"></omgdi:waypoint>
                <omgdi:waypoint x="282.0" y="180.0"></omgdi:waypoint>
                <omgdi:waypoint x="314.9999999999916" y="180.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
        </bpmndi:BPMNPlane>
    </bpmndi:BPMNDiagram>
</definitions>
流程图
  1. 创建委托
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;

/**
 * 批准委托类
 */
@Slf4j
public class ReviewApprove implements JavaDelegate {

    @Override
    public void execute(DelegateExecution delegateExecution) {
        //可以发送消息给某人
        log.info("通过,userId是:{}",delegateExecution.getVariable("userId"));
    }
}

import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;

/**
 * 驳回委托类
 */
@Slf4j
public class ReviewNoApprove implements JavaDelegate {
    @Override
    public void execute(DelegateExecution delegateExecution) {
        //可以发送消息给某人
        log.info("拒绝,userId是:{}",delegateExecution.getVariable("userId"));
    }
}


  1. 创建控制器
@RestController
@RequestMapping("/orderFlow")
@Slf4j
public class OrderFlowController {

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private HistoryService historyService;
    @Resource
    private ProcessEngine processEngine;

    /**
     * 1.提交采购订单的审批请求
     *
     * @param userId 用户id
     */
    @PostMapping("/start/{userId}/{purchaseOrderId}")
    public Result startFlow(@PathVariable String userId, @PathVariable String purchaseOrderId) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("userId", userId);
        map.put("purchaseOrderId", purchaseOrderId);
        // 流程ID->OrderApproval
        ProcessInstance processInstance =
                runtimeService.startProcessInstanceByKey("OrderApproval", map);
        String processId = processInstance.getId();
        // 名称由布署时指定
        String name = processInstance.getName();
        log.info(processId + ":" + name);
        return Result.ok(processId + ":" + name);
    }

    /**
     * 2.获取用户的任务
     *
     * @param userId 用户id
     */
    @GetMapping("/getTasks/{userId}")
    public Result getTasks(@PathVariable String userId) {
        List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list();
        return Result.ok(tasks.toString());
    }

    /**
     * 3.审批通过
     */
    @PostMapping("/success/{taskId}")
    public Result success(@PathVariable String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            return Result.error("流程不存在");
        }
        //通过审核
        HashMap<String, Object> map = new HashMap<>();
        // 在流程图中获取进行处理
        map.put("approved", true);
        taskService.complete(taskId, map);
        return Result.ok("流程审核通过!");
    }

    /**
     * 4.审批不通过
     */
    @PostMapping("/fail/{taskId}")
    public Result fail(@PathVariable String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            return Result.error("流程不存在");
        }
        //通过审核
        HashMap<String, Object> map = new HashMap<>();
        // 在流程图中获取进行处理
        map.put("approved", false);
        taskService.complete(taskId, map);
        return Result.ok();
    }

    /**
     * 5. 生成流程图
     *
     * @param httpServletResponse
     * @param processId
     * @throws Exception
     */
    @PostMapping(value = "processDiagram")
    public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) {
        /**
         * 获得当前活动的节点
         */
        String processDefinitionId = "";
        boolean isFinish = historyService.createHistoricProcessInstanceQuery().finished()
                .processInstanceId(processId).count() > 0;
        if (isFinish) {// 如果流程已经结束,则得到结束节点
            HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(processId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        } else {// 如果流程没有结束,则取当前活动节点
            // 根据流程实例ID获得当前处于活动状态的ActivityId合集
            ProcessInstance pi = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(processId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        }
        List<String> highLightedActivitis = new ArrayList<String>();

        /**
         * 获得活动的节点
         */
        List<HistoricActivityInstance> highLightedActivitList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processId).orderByHistoricActivityInstanceStartTime().asc().list();

        for (HistoricActivityInstance tempActivity : highLightedActivitList) {
            String activityId = tempActivity.getActivityId();
            highLightedActivitis.add(activityId);
        }

        List<String> flows = new ArrayList<>();
        //获取流程图
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();

        ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
        // 注意:如果是PNG格式那么输出的是背景色是黑色,如果连接线上有字不容易看清楚。可以使用bmp
        InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitis, flows, engconf.getActivityFontName(),
                engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0, true);
        OutputStream out = null;
        byte[] buf = new byte[1024];
        int legth = 0;
        try {
            out = httpServletResponse.getOutputStream();
            while ((legth = in.read(buf)) != -1) {
                out.write(buf, 0, legth);
            }
        } catch (IOException e) {
            log.error("操作异常", e);
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
        }
    }
}

三、运行测试

  1. 提交采购订单的审批请求
    http://localhost:7001/orderFlow/start/1/1
    提交采购订单的审批请求

    返回:
{
    "msg": "3c6dc4b2-f30a-11e9-bc68-005056c00008:null",
    "code": 0
}
  1. 获取用户的任务
    http://localhost:7001/orderFlow/getTasks/1
    获取用户的任务

    返回:
{
    "msg": "[Task[id=3c6dc4b2-f30a-11e9-bc68-005056c00008, name=订单审批]]",
    "code": 0
}
  1. 审批通过
    http://localhost:7001/orderFlow/success/3c6dc4b2-f30a-11e9-bc68-005056c00008
    审批通过

    返回:
{
    "msg": "流程审核通过!",
    "code": 0
}
  1. 审批驳回
    http://localhost:7001/orderFlow/fail/fac2256e-f30e-11e9-a987-005056c00008
    任务ID: fac2256e-f30e-11e9-a987-005056c00008
    审批驳回

    返回:
{
    "msg": "success",
    "code": 0
}
  1. 生成流程图
    http://localhost:7001/orderFlow/processDiagram?processId=4fdcbade-f311-11e9-acac-005056c00008
    processId: 为流程实例ID
    生成流程图

四、事件监听器实现

事件监听器的唯一要求是实现org.flowable.engine.delegate.event.FlowableEventListener。
一个事件侦听器基类,可用于侦听特定类型的实体或所有实体的实体相关事件。它隐藏掉类型检查,并提供4种方法应覆盖:onCreate(..),onUpdate(..)并onDelete(..)创建实体时,更新或删除。

五、常见问题

  1. "code":401,"error":"Unauthorized."
    排除配置
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
  1. 流程文档部署时没生成流程图片
    如果流程文档部署时没生成流程图片,且流程定义中包含必要的“图形交换(diagram interchange)”信息,Flowable引擎会生成流程图。
    如果由于某种原因,不需要或不希望在部署时生成流程图,可以在流程引擎配置中设置isCreateDiagramOnDeploy参数:
<property name="createDiagramOnDeploy" value="false" />
  1. Waiting for changelog lock....
    数据库中执行:
SELECT `LOCKED` FROM workflow_flowable.ACT_DMN_DATABASECHANGELOGLOCK WHERE ID=1

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

推荐阅读更多精彩内容