獲取流程圖的方式
- 通過flowable提供的jar包,直接連接flowable數據庫,調用flowable的api(diagram)生成流程圖(直接連接了flowable數據庫,微服務中最好不要這樣)
- 運行flowable提供的rest-api的war包,調用restful api接口返回流圖
runtime/process-instances/{processInstanceId}/diagram
這種方式,不能返回已經完成流程的流程圖(官方規定不返回 :)) - 運行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 繪製流程圖,(可以直接使用,路由繪製未添加判斷,後續添加即可)
考慮點:
- 節點的圖形,通過節點類型判斷;位置通過json數據解析
- 線條,位置通過json數據解析,動作名稱統一設置在第二個點上,如果只有兩個點,就放在兩點之間
- 箭頭指向(最煩畫這個),這個需要判斷方向,如果是斜着指向,需要通過旋轉某一點來推斷箭頭三個點的位置;第一個點爲線段的最後一個節點
/**
* 解析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;
}