获取流程图的方式
  1. 通过flowable提供的jar包,直接连接flowable数据库,调用flowable的api(diagram)生成流程图(直接连接了flowable数据库,微服务中最好不要这样)
  2. 运行flowable提供的rest-api的war包,调用restful api接口返回流图runtime/process-instances/{processInstanceId}/diagram 这种方式,不能返回已经完成流程的流程图(官方规定不返回 :))
  3. 运行flowable-admin的war包,登录,并调用app/rest/admin/process-instances/{processInstanceId}/model-json(或history-model-json)?processDefinitionId={processDefinitionId}获取流程节点信息,再调用前端或后台进行节点绘制(由于业务要求,我只能用这种方式实现 …)
获取流程节点数据,后台java绘制

1、由于不是直接使用flowable提供的rest-api,而是调用上面第三点的api进行节点数据获取,会直接跳转到admin的登陆界面,不是普通的页面验证,于是先后台模仿登录,获取需要的cookie值:

/**
     * flowable-admin登录认证
     *
     * @return cookie
     * @throws IOException
     */
    public String getAuthenticationSession() {
		// http协议头+服务器ip+端口 + flowable-idm/app/authentication(admin的登陆请求)
        String authenticationUrl = bpmApiProtocol + bpmApiHost + ":" + bpmApiPort + "/" + bpmApiIdmAuthPath;

        URL url = null;
        String responseCookie= "";
        try {
            url = new URL(authenticationUrl);

            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            // 允许连接提交信息
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");
            // 请求参数
            String content = "j_username=" + bpmApiIdmUser + "&j_password=" + bpmApiIdmPassword + "&_spring_security_remember_me=true&submit=Login";

            // 提交请求数据
            OutputStream os = connection.getOutputStream();
            os.write(content.getBytes("utf8"));
            os.close();

            // 取到所用的Cookie, 认证
            responseCookie = connection.getHeaderField("Set-Cookie");
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return responseCookie;
    }

2、通过获取的cookie,作为admin中的提供的api的cookie,进行数据获取

String modelpath = "model-json";
        if (判断该流程实例是否已经结束) {
            modelpath = "history-model-json";
        }
		
		URL url = null;
        try {

            // 获取响应的cookie 值
            String responseCookie = getAuthenticationSession();

            HttpURLConnection conn = null;

            // 请求路径根路径
            // http://服务器ip:端口/flowable-admin/app/rest/admin/process-instances/流程实例id/model-json?processDefinitionId=流程定义id
            String modelUrl = bpmApiProtocol + bpmApiHost + ":" + bpmApiPort + "/" + bpmApiProcessPath;

            url = new URL(modelUrl + processId + "/" + modelpath + "?processDefinitionId=" + instanceList.get(0).getProcessDefinitionId());
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Accept", "application/json");
            // 设置为之前登录获取的cookie
            conn.setRequestProperty("Cookie", responseCookie);
            conn.connect();

            StringBuilder builder = new StringBuilder();
            // 将数据读入StringBuilder;
            int successCode = 200;
            if (conn.getResponseCode() == successCode) {
                InputStream inputStream = conn.getInputStream();
                byte[] data = new byte[1024];
                StringBuffer sb1 = new StringBuffer();
                int length = 0;
                while ((length = inputStream.read(data)) != -1) {
                    String s = new String(data, 0, length);
                    sb1.append(s);
                }
                builder.append(sb1.toString());
                inputStream.close();
            }
            // 将图片以流的形式返回response
            writeImage(builder.toString(), response);

        } catch (IOException e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }

此处读取到的json数据
elements: 每个节点的信息(图片位置,节点类型,是否完成,当前节点)
flows:每条线的位置及线上对应的内容
其他相关信息

{
    "elements": [
        {
            "completed": true, 
            "current": false, 
            "id": "startEvent1", 
            "name": null, 
            "incomingFlows": [ ], 
            "x": 170, 
            "y": 240, 
            "width": 30, 
            "height": 30, 
            "type": "StartEvent"
        }, 
        {
            "completed": true, 
            "current": true, 
            "id": "sid-2DEC37F4-6DDA-4F9C-851E-253FEF5E2AEA", 
            "name": "业务主管审核", 
            "incomingFlows": [
                "sid-B6657CA2-B886-4F41-84E3-144DB506732D", 
                "sid-CF900A33-E208-494D-BE2B-5DF6E9B98CEE", 
                "sid-09ACE55D-6012-4EB9-82B4-E2D82530A30F"
            ], 
            "x": 135, 
            "y": 330, 
            "width": 100, 
            "height": 80, 
            "type": "UserTask", 
            "properties": [ ]
        }, 
       ...............................................
    ], 
    "flows": [
        {
            "completed": false, 
            "current": false, 
            "id": "sid-496BE5E8-5817-4DE0-ADF2-BA9DE52007BF", 
            "type": "sequenceFlow", 
            "sourceRef": "sid-2DEC37F4-6DDA-4F9C-851E-253FEF5E2AEA", 
            "targetRef": "sid-24C983CE-C446-455B-B4FC-90D5E7736F89", 
            "name": "放弃", 
            "waypoints": [
                {
                    "x": 134, 
                    "y": 370
                }, 
                {
                    "x": 27, 
                    "y": 370
                }, 
                {
                    "x": 27, 
                    "y": 655
                }, 
                {
                    "x": 180, 
                    "y": 655
                }
            ]
        }, 
        .............................................
    ], 
    "collapsed": [ ], 
    "diagramBeginX": 27, 
    "diagramBeginY": 240, 
    "diagramWidth": 460, 
    "diagramHeight": 695, 
    "completedActivities": [
        "startEvent1", 
        "sid-B6657CA2-B886-4F41-84E3-144DB506732D", 
        "sid-2DEC37F4-6DDA-4F9C-851E-253FEF5E2AEA", 
        "sid-C50A9276-D20D-4278-9B76-F6A233ACB284", 
        "sid-CEB4D4E0-259A-47CB-BC70-C4D679794D59", 
        "sid-09ACE55D-6012-4EB9-82B4-E2D82530A30F", 
        "sid-2DEC37F4-6DDA-4F9C-851E-253FEF5E2AEA"
    ], 
    "currentActivities": [
        "sid-2DEC37F4-6DDA-4F9C-851E-253FEF5E2AEA"
    ], 
    "completedSequenceFlows": [ ]
}

3、调用java 的 Graphics2D 绘制流程图,(可以直接使用,路由绘制未添加判断,后续添加即可)
考虑点:

  1. 节点的图形,通过节点类型判断;位置通过json数据解析
  2. 线条,位置通过json数据解析,动作名称统一设置在第二个点上,如果只有两个点,就放在两点之间
  3. 箭头指向(最烦画这个),这个需要判断方向,如果是斜着指向,需要通过旋转某一点来推断箭头三个点的位置;第一个点为线段的最后一个节点
/**
     * 解析json数据,绘制流程图,并返回response
     * @param procData 流程数据
     * @param response response
     * @return 是否成功
     */
    public static boolean writeImage(String procData, HttpServletResponse response) {

        JSONObject processData = JSONObject.parseObject(procData);

        BufferedImage bi = new BufferedImage(460 + 20, 695 + 20, BufferedImage.TYPE_INT_BGR);

        Graphics2D g = bi.createGraphics();
//        生成透明背景的画板,再重新生成画板
        bi = g.getDeviceConfiguration().createCompatibleImage(460 + 20, 695 + 20, Transparency.TRANSLUCENT);
        g.dispose();
        g = bi.createGraphics();

//		节点信息位置
        JSONArray elements = (JSONArray) processData.get("elements");
//		连线坐标
        JSONArray flows = (JSONArray) processData.get("flows");

        g.setColor(new Color(0, 0, 0));

//		画线
        for (int i = 0; i < flows.size(); i++) {
            JSONObject el = (JSONObject) flows.get(i);
            System.out.println(el);
            String name = (String) el.get("name");
            JSONArray points = (JSONArray) el.get("waypoints");
            for (int j = 0; j < points.size() - 1; j++) {
                JSONObject po1 = (JSONObject) points.get(j);
                int x1 = ((BigDecimal) po1.get("x")).intValue();
                int y1 = ((BigDecimal) po1.get("y")).intValue();
                JSONObject po2 = (JSONObject) points.get(j + 1);
                int x2 = ((BigDecimal) po2.get("x")).intValue();
                int y2 = ((BigDecimal) po2.get("y")).intValue();

                g.setColor(new Color(105, 105, 105));
                g.setStroke(new BasicStroke(2.0f));
                g.drawLine(x1, y1, x2, y2);

                g.setStroke(new BasicStroke(5.0f));
				// 箭头绘制
				// 1、先求出最后一条线的长度
                double pow = Math.pow(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2), 1.0f / 2.0f);
                // 2、设置箭头的长度,占该线的长度比
                double rate = 9.0 / pow;
				// 3、按比例获取该线上,长度为箭头长度的点,该处的nx,ny取矢量值,即保留正负
                int nx = (int) ((x2 - x1) * rate);
                int ny = (int) ((y2 - y1) * rate);
                int mx = x2 - nx;
                int my = y2 - ny;
                // 4、以上面获取的矢量值,进行90°旋转;这里以最后一个点为原点进行旋转
                Double vx = (nx) * Math.cos(1.57079632679489661923f) - (ny) * Math.sin(1.57079632679489661923f);
                Double vy = (ny) * Math.cos(1.57079632679489661923f) + (nx) * Math.sin(1.57079632679489661923f);
				// 5、获取旋转的点
                Double px1 = mx + vx / 2;
                Double py1 = my + vy / 2;
				// 6、通过上面获取的点,推出最后一个点
                Double px2 = mx + mx - px1;
                Double py2 = my + my - py1;
				// 7、对不同方向的箭头进行判断;由于有些位置有画笔粗细导致偏移问题,单独进行了位置的偏移
                if (j == (points.size() - 2)) {
                    // 左上
                    if (x2 < x1 && y2 < y1) {

                        int[] xPoints = {x2, px1.intValue(), px2.intValue()};
                        int[] yPoints = {y2, py1.intValue(), py2.intValue()};
                        g.drawPolygon(xPoints, yPoints, 3);
                    }
                    // 左下
                    if (x2 < x1 && y2 > y1) {
                        int[] xPoints = {x2 + 2, px1.intValue() + 2, px2.intValue() + 2};
                        int[] yPoints = {y2 - 2, py1.intValue() - 2, py2.intValue() - 2};

                        g.drawPolygon(xPoints, yPoints, 3);
                    }

                    //正左
                    if (x2 < x1 && y2 == y1) {
                        int[] xPoints = {x2 + 5, x2 + 15, x2 + 15};
                        int[] yPoints = {y2, y2 - 5, y2 + 5};
                        g.drawPolygon(xPoints, yPoints, 3);
                    }
                    // 右上
                    if (x2 > x1 && y2 < y1) {
                        int[] xPoints = {x2, px1.intValue(), px2.intValue()};
                        int[] yPoints = {y2, py1.intValue(), py2.intValue()};
                        g.drawPolygon(xPoints, yPoints, 3);
                    }
                    // 右下
                    if (x2 > x1 && y2 > y1) {
                        int[] xPoints = {x2, px1.intValue(), px2.intValue()};
                        int[] yPoints = {y2, py1.intValue(), py2.intValue()};
                        g.drawPolygon(xPoints, yPoints, 3);
                    }
                    // 正右
                    if (x2 > x1 && y2 == y1) {
                        int[] xPoints = {x2 - 5, x2 - 15, x2 - 15};
                        int[] yPoints = {y2, y2 - 5, y2 + 5};
                        g.drawPolygon(xPoints, yPoints, 3);
                    }
                    // 正上
                    if (x2 == x1 && y2 < y1) {
                        int[] xPoints = {x2, x2 - 5, x2 + 5};
                        int[] yPoints = {y2 + 5, y2 + 15, y2 + 15};
                        g.drawPolygon(xPoints, yPoints, 3);
                    }
                    // 正下
                    if (x2 == x1 && y2 > y1) {
                        int[] xPoints = {x2, x2 - 5, x2 + 5};
                        int[] yPoints = {y2 - 5, y2 - 15, y2 - 15};
                        g.drawPolygon(xPoints, yPoints, 3);
                    }

                }
                // 执行动作
                if (j == 0 && name != null) {

                    g.setColor(new Color(95, 158, 160));
                    g.setFont(new Font("SansSerif", Font.ITALIC, 15));
                    if (points.size() == 2) {
                        g.drawString(name, (x2 + x1) / 2, (y2 + y1) / 2);
                    } else {
                        g.drawString(name, x2, y2);
                    }

                }

            }

        }

//		画图
        for (int i = 0; i < elements.size(); i++) {
            JSONObject el = (JSONObject) elements.get(i);
            boolean completed = (Boolean) (el.get("completed"));
            boolean current = (Boolean) (el.get("current") == null ? false : el.get("current"));
            String name = (String) el.get("name");
            int x = ((BigDecimal) el.get("x")).intValue();
            int y = ((BigDecimal) el.get("y")).intValue();
            int width = ((BigDecimal) el.get("width")).intValue();
            int height = ((BigDecimal) el.get("height")).intValue();
            String type = (String) el.get("type");
            System.out.println("=========================");
			
			// 运行到当前节点
            if (current) {
                g.setColor(new Color(220, 20, 60));
                g.setStroke(new BasicStroke(5.0f));
            } else if (completed) { 
            // 当前节点已完成
                g.setColor(new Color(102, 170, 102));
                g.setStroke(new BasicStroke(3.0f));
            } else {
            // 默认未执行的节点
                g.setColor(new Color(0, 0, 0));
                g.setStroke(new BasicStroke(3.0f));
            }
            // 正方形
            if ("UserTask".equals(type)) {
                RoundRectangle2D roundedRectangle = new RoundRectangle2D.Float(x, y, width, height, 10, 10);
                g.draw(roundedRectangle);
            }
            // 粗圆
            if ("StartEvent".equals(type)) {
                g.drawOval(x, y, width, height);
            }
            // 细圆
            if ("EndEvent".equals(type)) {
                g.setStroke(new BasicStroke(5.0f));
                g.drawOval(x, y, width, height);
            }
            // 后续添加路由相关的判断
            if (name != null) {
                g.setFont(new Font("Serif", Font.BOLD, 12));
                g.drawString(name, x + 10, y + (height) / 2);
            }

        }

        g.dispose();
        boolean val = false;

        try (
                ServletOutputStream out = response.getOutputStream();
        ) {
            val = ImageIO.write(bi, "png", out);
        } catch (IOException e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }

        return val;
    }
前端获取效果图:

flowable结束全部流程 java flowable获取流程节点_流程图

非要后端绘制流程图,又不能连数据库,太坑了:)