Flowable 獲取,繪製流程圖 (流程中,已完成流程)

獲取流程圖的方式

  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;
    }
前端獲取效果圖:

在這裏插入圖片描述

非要後端繪製流程圖,又不能連數據庫,太坑了:)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章