写这个的业务场景是需要呈现流程实例的 流程图同时要在环节节点呈现对应的处理日志以及表单信息。(应用于flowable远程服务,非嵌入式开发,其主要思路也只是把官方服务的相关数据进行二次封装)
目前还是个小白。如果有不对的地方请大家多多指教
先看下最终封装后的流程图对象,只封装了节点,不包含流线。若需要可根据后续介绍自行封装
[
{
"id": "startEvent1",
"name": "派单",
"current": false,
"completed": true,
"incomingElements": [],
"outgoingElements": [
{
"id": "point001",
"tips": null
}
],
"realgoingElements": [],
"type": "StartEvent"
},
{
"id": "point001",
"name": "受理",
"current": false,
"completed": true,
"incomingElements": [
"startEvent1"
],
"outgoingElements": [
{
"id": "sid-74327205-DEFC-4D60-B15D-606A65EF5B0D",
"tips": null
}
],
"realgoingElements": [],
"type": "UserTask"
},
{
"id": "sid-74327205-DEFC-4D60-B15D-606A65EF5B0D",
"name": null,
"current": false,
"completed": true,
"incomingElements": [
"point001"
],
"outgoingElements": [
{
"id": "point002",
"tips": "通过"
},
{
"id": "sid-38583216-0EB6-4E99-BFB9-F94A40738148",
"tips": "驳回"
}
],
"realgoingElements": [
"sid-38583216-0EB6-4E99-BFB9-F94A40738148"
],
"type": "ExclusiveGateway"
},
{
"id": "point002",
"name": "确认",
"current": false,
"completed": true,
"incomingElements": [
"sid-74327205-DEFC-4D60-B15D-606A65EF5B0D"
],
"outgoingElements": [
{
"id": "sid-38583216-0EB6-4E99-BFB9-F94A40738148",
"tips": null
}
],
"realgoingElements": [],
"type": "UserTask"
},
{
"id": "sid-38583216-0EB6-4E99-BFB9-F94A40738148",
"name": "归档",
"current": false,
"completed": true,
"incomingElements": [
"point002",
"sid-74327205-DEFC-4D60-B15D-606A65EF5B0D"
],
"outgoingElements": [],
"realgoingElements": [],
"type": "EndEvent"
}
]
1、首先流程比较简单,如果不会画流程图和部署可度娘上边应该有很多资源。
流程分配人也是写死的:
流程需要注意的地方:
启动节点默认未定义ID。因为官方回默认配置其ID为
startEvent1。其主要目的是为了最后前端解析后台数据(无序)画流程图,知道第一个节点数据是什么。其实也可通过节点类型 因为启动节点的类型为StartEvent
这里我随便启动了一个测试流程实例test_001,首先官方提供的flow-rest服务走流程,然后在flow-admin服务查看流程实例实际运行状态,当前测试流程我已经归档了。
2、同时来看下官方服务对流程实例画的流程图:
同时这个我们可以看到官方服务其实调了自身封装好流程图数据的接口(这个接口服务是封装我们实际流程对象的关键数据之一):
官方的请求地址为 : http://127.0.0.1:8087/flowable-admin/app/rest/admin/process-instances/0cbeb6f1-ccba-11ea-ac6a-8ee4c8e29e52/history-model-json?processDefinitionId=test_element:1:e2ff27d5-ccb9-11ea-852f-8ee4c8e29e52&nocaching=1595561437641
同时header中要带当前登录账户的 cookie
{
"elements":[
{
"completed":true,
"id":"startEvent1",
"name":"派单",
"incomingFlows":[
],
"x":165,
"y":150,
"width":30,
"height":30,
"type":"StartEvent"
},
{
"completed":true,
"id":"point001",
"name":"受理",
"incomingFlows":[
"sid-AF892F55-DC8E-4459-AC05-F3257EA819BF"
],
"x":240,
"y":125,
"width":100,
"height":80,
"type":"UserTask",
"properties":[
{
"name":"Assignee",
"value":"管理员"
}
]
},
{
"completed":true,
"id":"sid-74327205-DEFC-4D60-B15D-606A65EF5B0D",
"name":null,
"incomingFlows":[
"sid-E2F0CF0A-6350-4613-84F2-F3C182B1FA29"
],
"x":390,
"y":145,
"width":40,
"height":40,
"type":"ExclusiveGateway"
},
{
"completed":true,
"id":"point002",
"name":"确认",
"incomingFlows":[
"sid-44C6C3AD-B759-4BCB-916F-CCD06E818E7C"
],
"x":495,
"y":125,
"width":100,
"height":80,
"type":"UserTask",
"properties":[
{
"name":"Assignee",
"value":"班组人员"
},
{
"name":"Form key",
"value":"commonForm"
}
]
},
{
"completed":true,
"id":"sid-38583216-0EB6-4E99-BFB9-F94A40738148",
"name":"归档",
"incomingFlows":[
"sid-CF99371B-C4BE-4443-A7AA-5DE2828278E7",
"sid-0FEBE0D2-DF46-4CAC-898F-DD8778DC94BC"
],
"x":675,
"y":151,
"width":28,
"height":28,
"type":"EndEvent"
}
],
"flows":[
{
"completed":false,
"id":"sid-AF892F55-DC8E-4459-AC05-F3257EA819BF",
"type":"sequenceFlow",
"sourceRef":"startEvent1",
"targetRef":"point001",
"waypoints":[
{
"x":194,
"y":165
},
{
"x":240,
"y":165
}
]
},
{
"completed":false,
"id":"sid-CF99371B-C4BE-4443-A7AA-5DE2828278E7",
"type":"sequenceFlow",
"sourceRef":"point002",
"targetRef":"sid-38583216-0EB6-4E99-BFB9-F94A40738148",
"waypoints":[
{
"x":594,
"y":165
},
{
"x":675,
"y":165
}
]
},
{
"completed":false,
"id":"sid-44C6C3AD-B759-4BCB-916F-CCD06E818E7C",
"type":"sequenceFlow",
"sourceRef":"sid-74327205-DEFC-4D60-B15D-606A65EF5B0D",
"targetRef":"point002",
"waypoints":[
{
"x":429,
"y":165
},
{
"x":494,
"y":165
}
]
},
{
"completed":true,
"id":"sid-0FEBE0D2-DF46-4CAC-898F-DD8778DC94BC",
"type":"sequenceFlow",
"sourceRef":"sid-74327205-DEFC-4D60-B15D-606A65EF5B0D",
"targetRef":"sid-38583216-0EB6-4E99-BFB9-F94A40738148",
"waypoints":[
{
"x":410,
"y":145
},
{
"x":410,
"y":89
},
{
"x":689,
"y":89
},
{
"x":689,
"y":151
}
]
},
{
"completed":false,
"id":"sid-E2F0CF0A-6350-4613-84F2-F3C182B1FA29",
"type":"sequenceFlow",
"sourceRef":"point001",
"targetRef":"sid-74327205-DEFC-4D60-B15D-606A65EF5B0D",
"waypoints":[
{
"x":339,
"y":165
},
{
"x":390,
"y":165
}
]
}
],
"collapsed":[
],
"diagramBeginX":180,
"diagramBeginY":89,
"diagramWidth":703,
"diagramHeight":205,
"completedActivities":[
"point001",
"startEvent1",
"point002",
"sid-74327205-DEFC-4D60-B15D-606A65EF5B0D",
"sid-38583216-0EB6-4E99-BFB9-F94A40738148"
],
"completedSequenceFlows":[
"sid-0FEBE0D2-DF46-4CAC-898F-DD8778DC94BC"
]
}
官方服务封装好的流程图对象 elements:节点信息 flows:流线信息 completedActivities:已完成节点信息 completedSequenceFlows:已完成流线信息。这个数据说明了,节点与节点之间是通过线连接的。
@Data
@Builder
public class Element {
private String id; //节点Id
private String type; //节点类型 枚举:ExclusiveGateway 网关 UserTask 用户任务
private String name; //节点名称
private boolean current; //是否当前节点
private boolean completed; //是否完成
private int x; //横坐标
private int y; //纵坐标
private int width; //宽度
private int height; //高度
private List<String> incomingFlows; //入口节点
private List<Properties> properties; //配置变量
@Tolerate
public Element() {
}
}
@Data
public class Flows {
private String targetRef; //目标节点
private boolean current; //是否当前
private boolean completed; //是否完成
private String id; //流线ID
private String type; //类型
private String sourceRef; //来源节点
private List<Waypoints> waypoints; //流线坐标
@Data
public static class Waypoints {
private double x;
private double y;
}
}
调用flow-admin登录接口进行认证获取cookie:
public static final String bpmApiIdmAuthPath = "flowable-idm/app/authentication";
public static final String bpmApiIdmUser = "admin";
public static final String bpmApiIdmPassword = "test";
public static final String bpmApiProcessPath = "flowable-admin/app/rest/admin/process-instances";
/**
* flowable-admin登录认证
*
* @return cookie
* @throws IOException
*/
public static String getAuthenticationSession() {
// http协议头+服务器ip+端口 + flowable-idm/app/authentication(admin的登陆请求)
String baseUrl = FlowableRestClient.baseUrl;
String authenticationUrl = baseUrl.replace("flowable-rest", "") + 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;
}
根据 流程业务标识 businessKey,获取流程实例ID 和 流程定义ID,调用查询接口获取业务工单运行的流程图数据
public static String getDiagramHistory(String businessKey) {
String modelpath = "model-json";
String instanceInfo = queryHistoryInstanceByBusinessKey(businessKey);
JSONArray instanceInfoJson = JSONObject.parseObject(instanceInfo).getJSONArray("data");
Assert.isTrue(instanceInfoJson.size() > 0, "流程实例为空!");
if (instanceInfoJson.getJSONObject(0).getDate("endTime") != null) {
modelpath = "history-model-json";
}
URL url = null;
try {
// 获取响应的cookie 值
String responseCookie = getAuthenticationSession();
HttpURLConnection conn = null;
// 请求路径根路径
String baseUrl = FlowableRestClient.baseUrl;
// http://服务器ip:端口/flowable-admin/app/rest/admin/process-instances/流程实例id/model-json?processDefinitionId=流程定义id
String modelUrl = baseUrl.replace("flowable-rest", "") + bpmApiProcessPath + "/";
String processInstanceId = instanceInfoJson.getJSONObject(0).getString("id");
String processDefinitionId = instanceInfoJson.getJSONObject(0).getString("processDefinitionId");
url = new URL(modelUrl + processInstanceId + "/" + modelpath + "?processDefinitionId=" + processDefinitionId);
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();
}
return builder.toString();
} catch (IOException e) {
e.printStackTrace();
log.error(e.getMessage());
return null;
}
}
3、官方服务提供了一个接口查询部署的流程图数据
其返回的数据其实就是画流程图时官方生成的数据,不关联流程实际运行实例。具体的数据结构是干嘛就不详细介绍,无碍乎是关于流程图相关的。我们重点关心 process 中的 flowElements 对应的是定义流程图的数据
/**
* uri: GET /repository/process-definitions/{processDefinitionId}/model
* @param businessKey
* @return
*/
public static List<FlowElement> getProcModel(String businessKey) {
List<FlowElement> list = Lists.newArrayList();
Map<String, String> params = new HashMap<>();
params.put("processBusinessKey", businessKey);
JSONObject hisProcInst = JSON.parseObject(FlowableHisQueryClient.queryHisProcInst(JSONObject.toJSONString(params)));
JSONArray jsonArray = hisProcInst.getJSONArray("data");
if (jsonArray.isEmpty()) {
throw new RuntimeException("根据业务ID " + businessKey + " 未查到流程信息!");
}
String processDefinitionId = jsonArray.getJSONObject(0).getString("processDefinitionId");
//调用的flowable-rest中二次开发的功能
String uri = RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_MODEL, processDefinitionId);
ResponseEntity<String> respones = FlowableRestClient.callFlowRestApi(service.concat(uri), HttpMethod.GET, null, null);
if (respones.getStatusCode() != HttpStatus.OK) {
throw new RuntimeException("获取流程定义数据失败:" + respones.getBody());
}
if (respones.getStatusCode() == HttpStatus.OK) {
String res = respones.getBody();
JSONObject obj = JSON.parseObject(res);
JSONObject process = obj.getJSONArray("processes").getJSONObject(0);
JSONArray flowElements = process.getJSONArray("flowElements");
System.out.println("完整流程图数据: " + flowElements);
list = JSONObject.parseArray(flowElements.toJSONString(), FlowElement.class);
}
return list;
}
@Data
public class FlowElement {
private boolean interrupting;
private List<String> eventDefinitions;
private ExtensionElements extensionElements;
private List<IncomingFlows> incomingFlows;
private List<OutgoingFlows> outgoingFlows;
private int xmlColumnNumber;
private List<String> executionListeners;
private boolean notExclusive;
private boolean asynchronous;
private boolean exclusive;
private Attributes attributes;
private String id;
private int xmlRowNumber;
private List<String> formProperties;
}
最终将两个数据对象进行解析,封装想要的数据。其中心思想就是节点与节点之间是线。flowaable-admin 获取了流程实例运行的节点和线各自的状态。rest接口获取了流程定义的完整节点和线的流向。 所以 节点的出口是线,线的出口是节点。最后还发现官方有一个bug,两个节点的 completed 为true 但中间的线 为false,但还好不影响最终想要的流程图数据
/**
* 获取动态流程图数据 只包含节点 不包含线
*
* @param businessKey
* @return
*/
public static List<RunElement> getRunElements(String businessKey) {
String builder = getDiagramHistory(businessKey);
JSONObject processData = JSONObject.parseObject(builder);
if (processData == null) {
throw new RuntimeException("工单运行流程图实例获取异常");
}
JSONArray elements = (JSONArray) processData.get("elements");
JSONArray flows = (JSONArray) processData.get("flows");
//System.out.println("运行节点数据: " + elements.toJSONString());
//System.out.println("运行线数据: " + flows.toJSONString());
List<Flows> flowList = JSONObject.parseArray(flows.toJSONString(), Flows.class);
List<Element> elementList = JSONObject.parseArray(elements.toJSONString(), Element.class);
List<FlowElement> procElementList = FlowableProcDefClient.getProcModel(businessKey);
List<RunElement> runElementList = Lists.newArrayList();
if (elementList != null && !elementList.isEmpty()) {
elementList.forEach((element) -> {
String nodeId = element.getId();
String nodeName = element.getName();
boolean current = element.isCurrent();
boolean completed = element.isCompleted();
String type = element.getType();
List<String> incomingElements = Lists.newArrayList();
List<OutgoingElement> outgoingElements = Lists.newArrayList();
List<String> realgoingElements = Lists.newArrayList();
RunElement runElement = RunElement.builder()
.id(nodeId)
.name(nodeName)
.current(current)
.completed(completed)
.type(type)
.incomingElements(incomingElements)
.build();
//流程出口节点, 不包含线
if (procElementList != null && !procElementList.isEmpty()) {
for (FlowElement flowElement : procElementList) {
if (flowElement.getId().equals(nodeId)) {
List<OutgoingFlows> outgoingFlowsList = flowElement.getOutgoingFlows();
List<IncomingFlows> incomingFlowsList = flowElement.getIncomingFlows();
if (outgoingFlowsList != null && !outgoingFlowsList.isEmpty()) {
for (OutgoingFlows goingFlows : outgoingFlowsList) {
OutgoingElement outgoingElement = OutgoingElement.builder()
.id(goingFlows.getTargetRef())
.tips(goingFlows.getName())
.build();
outgoingElements.add(outgoingElement);
}
runElement.setOutgoingElements(outgoingElements);
}
if (incomingFlowsList != null && !incomingFlowsList.isEmpty()) {
for (IncomingFlows incomingFlows : incomingFlowsList) {
incomingElements.add(incomingFlows.getSourceRef());
}
runElement.setOutgoingElements(outgoingElements);
}
break;
}
}
}
//运行出口节点
if (flowList != null && !flowList.isEmpty()) {
for (Flows flow : flowList) {
if (flow.getSourceRef().equals(nodeId) && flow.isCompleted()) {
realgoingElements.add(flow.getTargetRef());
}
}
runElement.setRealgoingElements(realgoingElements);
}
runElementList.add(runElement);
});
}
//System.out.println("查询的流程图数据:" + JSON.toJSON(runElementList));
return runElementList;
}
/**
* 动态流程图数据 只包含节点 不包含线
*
* @param businessKey
* @return
*/
@Data
@Builder
public class RunElement {
private String id; //节点ID
private String name; //节点名称
private boolean current; //是否当前节点
private boolean completed; //是否完成
private List<String> incomingElements; //入口节点
private List<OutgoingElement> outgoingElements; //出口节点
private List<String> realgoingElements; //实际出口节点
private String type; //类型 枚举:ExclusiveGateway网关 UserTask用户任务 StartEvent开始节点
@Tolerate
public RunElement() {
}
}