端到端功能 图形节点分布相关代码提交

This commit is contained in:
qinoy 2023-05-12 17:34:43 +08:00
parent 69c949e6b0
commit ec180c2ecc
11 changed files with 407 additions and 30 deletions

View File

@ -90,6 +90,11 @@ public class SubProcessController {
}
}
@Mapping("com.actionsoft.apps.coe.method.process.subprocess.generator_end_to_end_model")
public String generatorEndToEndModel(UserContext uc, String processIdJsonArr, String locationId, String direction, String modelName){
SubProcessWeb processWeb = new SubProcessWeb(uc);
processWeb.generatorEndToEndModel(processIdJsonArr, locationId, direction, modelName);
return ResponseObject.newOkResponse().toString();
}
}

View File

@ -9,6 +9,9 @@ public interface SubProcessConst {
// 应用ID
String APP_ID = "com.actionsoft.apps.coe.method.process.subprocess";
String SUB_PROCESS_CATEGORY = "process";
String SUB_PROCESS_METHOD_ID = "process.subprocess";
// 端到端流程存放父节点 参数名
String SUB_PROCESS_MODEL_LOCATION = "SUB_PROCESS_MODEL_LOCATION";

View File

@ -0,0 +1,131 @@
package com.actionsoft.apps.coe.method.process.subprocess.graph;
import java.util.Random;
/**
* 图模型节点布局
* 采用力导向算法
* @author oYang
* @create 2023-05-12 14:58
*/
public class ForceDirectedGraphLayout {
private final int n; // 节点数
private final int[][] adjacency; // 邻接矩阵
private final double[][] position; // 节点位置
private final double[] charge; // 节点电荷
private final double[] forceX; // 节点受到的x方向力
private final double[] forceY; // 节点受到的y方向力
private final double[] displacementX; // 节点位移的x方向补偿
private final double[] displacementY; // 节点位移的y方向补偿
private double temperature = 100.0; // 温度
private double temperatureDecay = 0.1; // 温度衰减率
private final double maxDisplacement = 50.0; // 最大位移量
private final Random random = new Random();
private final double width; // 画布宽度
private final double height; // 画布高度
private final double shapeInterval = 80.0; // 图形节点在画布上的间隔
private final double nodeW = 100.0; // 图形节点默认宽度
private final double nodeH = 70.0; // 图形节点默认高度
public ForceDirectedGraphLayout(int[][] adjacency) {
this.n = adjacency.length;
this.adjacency = adjacency;
this.position = new double[n][2];
this.charge = new double[n];
this.forceX = new double[n];
this.forceY = new double[n];
this.displacementX = new double[n];
this.displacementY = new double[n];
this.width = n * (shapeInterval + nodeW);
this.height = n * (shapeInterval + nodeH);
// 初始化节点位置随机分布在一个2000x2000的矩形内
for (int i = 0; i < n; i++) {
position[i][0] = random.nextDouble() * width;
position[i][1] = random.nextDouble() * height;
charge[i] = 1.0; // 电荷为1
}
}
public void run() {
for (int i = 0; i < 1000; i++) { // 迭代1000次
calculateForces();
moveNodes();
cool(); // 降温
}
}
private void calculateForces() {
// 清空所有节点受到的力
for (int i = 0; i < n; i++) {
forceX[i] = 0;
forceY[i] = 0;
}
// 计算节点间的斥力
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i != j) {
double dx = position[j][0] - position[i][0];
double dy = position[j][1] - position[i][1];
double distance = Math.sqrt(dx * dx + dy * dy);
double repulsiveForce = charge[i] * charge[j] / distance * distance;
forceX[i] -= repulsiveForce * dx / distance;
forceY[i] -= repulsiveForce * dy / distance;
}
}
}
// 计算节点所在边的吸引力
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (adjacency[i][j] != 0) { // 代表节点i和节点j之间存在一条边
double dx = position[j][0] - position[i][0];
double dy = position[j][1] - position[i][1];
double distance = Math.sqrt(dx * dx + dy * dy);
double attractiveForce = distance * distance / charge[i];
forceX[i] += attractiveForce * dx / distance;
forceY[i] += attractiveForce * dy / distance;
}
}
}
}
private void moveNodes() {
for (int i = 0; i < n; i++) {
double displacement = Math.sqrt(forceX[i] * forceX[i] + forceY[i] * forceY[i]);
if (displacement == 0) { // 如果节点受到的力为0就加一点随机扰动防止节点过于静止
displacementX[i] = random.nextDouble() * maxDisplacement * 2 - maxDisplacement;
displacementY[i] = random.nextDouble() * maxDisplacement * 2 - maxDisplacement;
} else {
displacementX[i] = forceX[i] / displacement * Math.min(displacement, maxDisplacement); // 获得x方向的位移补偿
displacementY[i] = forceY[i] / displacement * Math.min(displacement, maxDisplacement); // 获得y方向的位移补偿
}
}
for (int i = 0; i < n; i++) {
position[i][0] += displacementX[i]; // 更新节点的x坐标
position[i][1] += displacementY[i]; // 更新节点的y坐标
}
}
private void cool() {
temperature *= 1 - temperatureDecay; // 温度指数级下降达到类似于退火的贪心优化效果
}
public double[][] getPosition() {
return position;
}
public static void main(String[] args) {
int[][] adjacency = new int[][]{{0, 1, 1, 0}, {0, 0, 1, 1}, {1, 0, 0, 1}, {1, 1, 0, 0}}; // 代表四个节点的有向图
ForceDirectedGraphLayout fdl = new ForceDirectedGraphLayout(adjacency);
fdl.run();
double[][] position = fdl.getPosition();
for (int i = 0; i < position.length; i++) {
System.out.println("Node " + i + ": (" + position[i][0] + ", " + position[i][1] + ")");
}
}
}

View File

@ -1,9 +1,12 @@
package com.actionsoft.apps.coe.method.process.subprocess.graph;
import com.actionsoft.apps.coe.method.process.subprocess.mode.Node;
import com.actionsoft.apps.coe.pal.pal.repository.designer.relation.model.DesignerShapeRelationModel;
import com.actionsoft.bpms.util.ConsolePrinter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 用邻接矩阵表示图模型 并实现相关的操作
@ -18,11 +21,11 @@ public class GraphAdjMatrix {
/**
* 构造函数 初始化邻接矩阵
* @param numVertices
* @param nodeList
*/
public GraphAdjMatrix(int numVertices) {
adjMatrix = new int[numVertices][numVertices];
vertexList = new ArrayList<>(numVertices);
public GraphAdjMatrix(List<Node> nodeList) {
adjMatrix = new int[nodeList.size()][nodeList.size()];
vertexList = nodeList;
numEdges = 0;
}
@ -53,4 +56,56 @@ public class GraphAdjMatrix {
public boolean hasEdge(int u, int v) {
return adjMatrix[u][v] == 1;
}
/**
* 获取边的树目
* @return
*/
public int getNumEdges(){
return numEdges;
}
/**
* 获取邻接矩阵
* @return
*/
public int[][] getAdjMatrix(){
return adjMatrix;
}
/**
* 构建邻接矩阵
* @param nodeIndexMap 索引映射 节点ID与节点在列表中索引的映射关系
*/
public void buildAdjMatrix(Map<String, Integer> nodeIndexMap){
for (int i = 0; i < vertexList.size(); i++) {
Node currentNode = vertexList.get(i);
// 处理当前节点到后置节点的边
if (currentNode.getRearModeList() != null && currentNode.getRearModeList().size() > 0){
List<DesignerShapeRelationModel> rearModeList = currentNode.getRearModeList();
for (DesignerShapeRelationModel rearModel : rearModeList) {
addEdge(i, nodeIndexMap.get(rearModel.getRelationFileId()).intValue());
}
}
// 处理当前节点与前置节点的边
if (currentNode.getLearModeList() != null && currentNode.getLearModeList().size() > 0){
List<DesignerShapeRelationModel> learModeList = currentNode.getLearModeList();
for (DesignerShapeRelationModel learModel : learModeList) {
addEdge(nodeIndexMap.get(learModel.getRelationFileId()).intValue(), i);
}
}
}
}
// 输出邻接矩阵
public void printAdjMatrix() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < vertexList.size(); i++) {
for (int j = 0; j < vertexList.size(); j++) {
sb.append(adjMatrix[i][j]).append(" ");
}
sb.append("\n");
}
System.out.println(sb.toString());
}
}

View File

@ -0,0 +1,55 @@
package com.actionsoft.apps.coe.method.process.subprocess.graph;
import com.actionsoft.apps.coe.method.process.subprocess.constant.SubProcessConst;
import com.actionsoft.apps.coe.pal.pal.repository.designer.manage.CoeDesignerAPIManager;
import com.actionsoft.apps.coe.pal.pal.repository.designer.model.BaseModel;
import com.actionsoft.apps.coe.pal.pal.repository.designer.util.CoeDesignerUtil;
import com.actionsoft.apps.coe.pal.pal.repository.designer.util.ShapeUtil;
import com.actionsoft.bpms.util.UUIDGener;
import com.alibaba.fastjson.JSONObject;
public class GraphRender {
private final int numVertex;
private final double width; // 画布宽度
private final double height; // 画布高度
private final double shapeInterval = 80.0; // 图形节点在画布上的间隔
private final double nodeW = 100.0; // 图形节点默认宽度
private final double nodeH = 70.0; // 图形节点默认高度
private final String definition;
private final String modelId;
public GraphRender(int numVertex, String modelId, String definition) {
this.numVertex = numVertex;
this.width = numVertex * (shapeInterval + nodeW);
this.height = numVertex * (shapeInterval + nodeH);
this.definition = definition;
this.modelId = modelId;
}
public void handleShapeNodeRender(double[][] position) {
JSONObject defineJsonObj = JSONObject.parseObject(definition);
JSONObject page = defineJsonObj.getJSONObject("page");
page.put("width", width);
page.put("height", height);
JSONObject elements = defineJsonObj.getJSONObject("elements");
for (int i = 0; i < numVertex; i++) {
JSONObject subProcessNode = ShapeUtil.getProcessShapeDefinition(SubProcessConst.SUB_PROCESS_METHOD_ID, "子流程");
String nodeId = UUIDGener.getObjectId();
subProcessNode.put("id", nodeId);
JSONObject subProcessNodeProps = subProcessNode.getJSONObject("props");
subProcessNodeProps.put("x", position[i][0]);
subProcessNodeProps.put("y", position[i][1]);
elements.put(nodeId, subProcessNode);
}
defineJsonObj.put("elements",elements);
BaseModel model = CoeDesignerAPIManager.getInstance().getDefinition(modelId, 0);
if (model == null) {
model = CoeDesignerUtil.createModel(modelId, 0);
}
model.setDefinition(JSONObject.toJSONString(defineJsonObj));
CoeDesignerAPIManager.getInstance().storeDefinition(model);
}
}

View File

@ -0,0 +1,65 @@
package com.actionsoft.apps.coe.method.process.subprocess.graph;
import com.actionsoft.apps.coe.method.process.subprocess.constant.SubProcessConst;
import com.actionsoft.apps.coe.method.process.subprocess.mode.Node;
import com.actionsoft.apps.coe.pal.pal.repository.designer.relation.cache.DesignerShapeRelationCache;
import com.actionsoft.apps.coe.pal.pal.repository.designer.relation.model.DesignerShapeRelationModel;
import com.actionsoft.exception.AWSException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 图模型节点预处理
* 选择的若干子流程 其中某些子流程配置的上下游流程并不在所选的若干子流程中
* 那么这些配置上的上下游流程怎么归属
* 是包含进总图中还是排除掉
* @author oYang
* @create 2023-05-12 15:30
*/
public class VertexPreHandle {
/**
* 包含进总图中
* @param processIdList 选择的若干子流程ID
* @param nodeIndexMap 存放子流程在集合中的索引
* @return
* @throws AWSException
*/
public List<Node> includeLearAndRearNode(List<String> processIdList, Map<String, Integer> nodeIndexMap) throws AWSException {
List<Node> nodeList = new ArrayList<>();
for (int i = 0; i < processIdList.size(); i++) {
Node node = new Node(processIdList.get(i));
// 前置流程
List<DesignerShapeRelationModel> learProcessList = DesignerShapeRelationCache.getByFileId(processIdList.get(i), SubProcessConst.LEAD_PROCESS_ATTR_ID);
learProcessList.stream().forEach(model -> {
if (!processIdList.contains(model.getRelationFileId())){
Node learNode = new Node(model.getRelationFileId());
nodeList.add(learNode);
nodeIndexMap.put(model.getRelationFileId(), nodeList.size() - 1);
}
});
node.setLearModeList(learProcessList);
// 后置流程
List<DesignerShapeRelationModel> rearProcessList = DesignerShapeRelationCache.getByFileId(processIdList.get(i), SubProcessConst.REAR_PROCESS_ATTR_ID);
rearProcessList.stream().forEach(model -> {
if (!processIdList.contains(model.getRelationFileId())){
Node rearNode = new Node(model.getRelationFileId());
nodeList.add(rearNode);
nodeIndexMap.put(model.getRelationFileId(), nodeList.size() - 1);
}
});
node.setRearModeList(rearProcessList);
nodeList.add(node);
nodeIndexMap.put(node.getId(), nodeList.size() - 1);
}
return nodeList;
}
@Deprecated
public void excludeLearAndRearNode(List<String> processIdList) throws AWSException {
}
}

View File

@ -1,5 +1,9 @@
package com.actionsoft.apps.coe.method.process.subprocess.mode;
import com.actionsoft.apps.coe.pal.pal.repository.designer.relation.model.DesignerShapeRelationModel;
import java.util.List;
/**
* @author oYang
* @create 2023-05-11 17:21
@ -9,8 +13,10 @@ public class Node {
private String id;
private double x;
private double y;
public double displaceX; // x方向移动位移
public double displaceY; // y方向移动位移
private List<DesignerShapeRelationModel> learModeList;
private List<DesignerShapeRelationModel> rearModeList;
public Node(String id) {
this.id = id;
@ -46,6 +52,23 @@ public class Node {
this.y = y;
}
public List<DesignerShapeRelationModel> getLearModeList() {
return learModeList;
}
public void setLearModeList(List<DesignerShapeRelationModel> learModeList) {
this.learModeList = learModeList;
}
public List<DesignerShapeRelationModel> getRearModeList() {
return rearModeList;
}
public void setRearModeList(List<DesignerShapeRelationModel> rearModeList) {
this.rearModeList = rearModeList;
}
@Override
public String toString() {
return "Node{" +

View File

@ -1,4 +1,4 @@
package com.actionsoft.apps.coe.method.process.subprocess.model.vo;
package com.actionsoft.apps.coe.method.process.subprocess.mode.vo;
/**
* @author oYang

View File

@ -1,19 +1,31 @@
package com.actionsoft.apps.coe.method.process.subprocess.web;
import com.actionsoft.apps.coe.method.process.subprocess.constant.SubProcessConst;
import com.actionsoft.apps.coe.method.process.subprocess.graph.ForceDirectedGraphLayout;
import com.actionsoft.apps.coe.method.process.subprocess.graph.GraphAdjMatrix;
import com.actionsoft.apps.coe.method.process.subprocess.graph.GraphRender;
import com.actionsoft.apps.coe.method.process.subprocess.graph.VertexPreHandle;
import com.actionsoft.apps.coe.method.process.subprocess.mode.Node;
import com.actionsoft.apps.coe.method.process.subprocess.model.vo.SubProcessTagVo;
import com.actionsoft.apps.coe.method.process.subprocess.mode.vo.SubProcessTagVo;
import com.actionsoft.apps.coe.pal.constant.CoEConstant;
import com.actionsoft.apps.coe.pal.pal.repository.PALRepositoryQueryAPIManager;
import com.actionsoft.apps.coe.pal.pal.repository.cache.PALRepositoryCache;
import com.actionsoft.apps.coe.pal.pal.repository.dao.CoeProcessLevelDaoFacotory;
import com.actionsoft.apps.coe.pal.pal.repository.dao.PALRepository;
import com.actionsoft.apps.coe.pal.pal.repository.designer.CoeDesignerShapeAPIManager;
import com.actionsoft.apps.coe.pal.pal.repository.designer.manage.CoeDesignerAPIManager;
import com.actionsoft.apps.coe.pal.pal.repository.designer.model.BaseModel;
import com.actionsoft.apps.coe.pal.pal.repository.designer.relation.cache.DesignerShapeRelationCache;
import com.actionsoft.apps.coe.pal.pal.repository.designer.relation.model.DesignerShapeRelationModel;
import com.actionsoft.apps.coe.pal.pal.repository.designer.util.CoeDesignerUtil;
import com.actionsoft.apps.coe.pal.pal.repository.model.PALRepositoryModel;
import com.actionsoft.apps.coe.pal.pal.repository.model.impl.PALRepositoryModelImpl;
import com.actionsoft.apps.coe.pal.pal.repository.util.CoeProcessLevelUtil;
import com.actionsoft.apps.coe.pal.pal.repository.web.CoeProcessLevelWeb;
import com.actionsoft.bpms.commons.mvc.view.ActionWeb;
import com.actionsoft.bpms.commons.mvc.view.ResponseObject;
import com.actionsoft.bpms.server.UserContext;
import com.actionsoft.bpms.util.UUIDGener;
import com.actionsoft.bpms.util.UtilString;
import com.actionsoft.exception.AWSException;
import com.actionsoft.i18n.I18nRes;
@ -22,6 +34,7 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.collections4.IteratorUtils;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;
@ -46,11 +59,10 @@ public class SubProcessWeb extends ActionWeb {
String[] locationIds = property.split("/");
StringBuffer dirName = new StringBuffer(I18nRes.findValue(CoEConstant.APP_ID, locationIds[0]) + "/");
for (int i = 1; i < locationIds.length; i++) {
PALRepositoryModel model = PALRepositoryCache.getCache().get(locationIds[i]);
dirName.append(model.getName()).append("/");
}
PALRepositoryModel model = PALRepositoryCache.getCache().get(locationIds[1]);
if (model == null)
throw new AWSException("应用参数【" + SubProcessConst.SUB_PROCESS_MODEL_LOCATION + "】中配置的一级目录在当前资产库中不存在");
dirName.append(model.getName()).append("/");
ResponseObject ro = ResponseObject.newOkResponse();
ro.put("dirRootName", dirName.toString());
ro.put("dirRootPath", property);
@ -198,35 +210,57 @@ public class SubProcessWeb extends ActionWeb {
.filter(item -> {
boolean flag = false;
JSONObject model = (JSONObject) item;
return flag = "default".equals(model.getString("plMethodId")) || "".equals(model.getString("plMethodId"));
return flag = "default".equals(model.getString("plMethodId")) || "process.framework".equals(model.getString("plMethodId"));
}).collect(Collectors.toCollection(JSONArray :: new));
return result;
}
public void generatorEndToEndModel(String processIdJsonArr){
public void generatorEndToEndModel(String processIdJsonArr, String locationId, String direction, String modelName) throws AWSException{
if (UtilString.isEmpty(processIdJsonArr))
throw new AWSException("参数异常");
List<String> processIdList = JSONArray.parseArray(processIdJsonArr, String.class);
// 1.校验
// 2.根据文件中前游流程与后游流程来确定关联关系
GraphAdjMatrix graphAdjMatrix = new GraphAdjMatrix(processIdList.size());
List<Node> nodeList = new ArrayList<>();
for (String processId : processIdList) {
Node node = new Node(processId);
// 前置流程
List<DesignerShapeRelationModel> learProcessList = DesignerShapeRelationCache.getByFileId(processId, SubProcessConst.LEAD_PROCESS_ATTR_ID);
// 后置流程
List<DesignerShapeRelationModel> rearProcessList = DesignerShapeRelationCache.getByFileId(processId, SubProcessConst.REAR_PROCESS_ATTR_ID);
// 节点预处理
Map<String, Integer> nodeIndexMap = new HashMap<>();
VertexPreHandle vertexPreHandle = new VertexPreHandle();
List<Node> nodeList = vertexPreHandle.includeLearAndRearNode(processIdList, nodeIndexMap);
}
// 构建有向图邻接矩阵
GraphAdjMatrix graphAdjMatrix = new GraphAdjMatrix(nodeList);
graphAdjMatrix.buildAdjMatrix(nodeIndexMap);
// graphAdjMatrix.printAdjMatrix();
// 3.根据关联关系数据模型来决定分布位置
// 获取节点分布
ForceDirectedGraphLayout graphLayout = new ForceDirectedGraphLayout(graphAdjMatrix.getAdjMatrix());
graphLayout.run();
double[][] position = graphLayout.getPosition();
// 3.1 每个子流程模型有一个 子流程模型的图形属性处理
// 新建模型
PALRepositoryModel parentModel = PALRepositoryCache.getCache().get(locationId);
PALRepository coeProcessLevel = CoeProcessLevelDaoFacotory.createCoeProcessLevel();
Timestamp nowTime = new Timestamp(System.currentTimeMillis());
String plRid = UUIDGener.getUUID();
String id = UUIDGener.getUUID();
int orderIndex = coeProcessLevel.getChildrenMaxOrderIndexByPidAndWsId(parentModel.getId(), parentModel.getWsId()) + 1;
PALRepositoryModelImpl model = CoeProcessLevelUtil.createPALRepositoryModel(id, plRid, parentModel.getWsId(), modelName, "", orderIndex, parentModel.getVersionId(),
SubProcessConst.SUB_PROCESS_CATEGORY, true, 1, id, false, SubProcessConst.SUB_PROCESS_METHOD_ID, "0", parentModel.getLevel() + 1, null, null,
uc.getUID(), uc.getUID(), nowTime, null, null, null, null, null, null, null, null, null,0);
// 4.在分布好的位置上根据关联关系数据模型连线
CoeProcessLevelDaoFacotory.createCoeProcessLevel().insert(model);
BaseModel baseModel = CoeDesignerAPIManager.getInstance().getDefinition(model.getId(), 0);
if (baseModel == null) baseModel = CoeDesignerUtil.createModel(model.getId(), 0);
//获取流程定义和排序
CoeDesignerShapeAPIManager manager = CoeDesignerShapeAPIManager.getInstance();
JSONObject object = manager.getCoeDefinitionAndSort(baseModel.getDefinition(), parentModel.getWsId(), SubProcessConst.SUB_PROCESS_METHOD_ID);
//处理流程节点形状的通用配置
JSONObject obj = manager.getCoeProcessShapeConfig(object.getString("define"), parentModel.getWsId(), SubProcessConst.SUB_PROCESS_METHOD_ID, model.getId());
baseModel.setDefinition(obj.getString("define"));
// 节点渲染
GraphRender graphRender = new GraphRender(nodeList.size(), model.getId(), baseModel.getDefinition());
graphRender.handleShapeNodeRender(position);
}
}

View File

@ -17,4 +17,10 @@
<param name="teamId"/>
<param name="pid"/>
</cmd-bean>
<cmd-bean name="com.actionsoft.apps.coe.method.process.subprocess.generator_end_to_end_model">
<param name="processIdJsonArr"/>
<param name="locationId"/>
<param name="direction"/>
<param name="modelName"/>
</cmd-bean>
</aws-actions>