修改
修改一处代码,之前写的时候没注意,写了个低级bug:
这岂不是每个组件都会往上下文里放一个新的map。。。只需改为要么在业务代码前手动构造,要么这个上下文自动就会创建一个map,然后组件最后putAll即可,具体的改动在正文。
回顾
接上篇v1.1的总结,存在一个很大的bug:v1.1已经实现了用户可以持久化存储他们预设的组件流,之后只需要调用运行接口即可,而在测试中填写两个组件的入参list时,由于经历了转为json并持久化的操作,使得运行的时候是运行了两个全新的list。
为了解决这个问题,需要引入一个上下文的概念,可以是一个map,它会把组件的结果存起来,然后其他组件在需要的时候从上下文中获取想要的结果即可。
代码部分
core.framework.ComponentContext
根据这个思路创建一个类如下
package com.example.lowcode.core.framework;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* flow上下文
*
* @author llxzdmd
* @version Context.java, v 0.1 2023年12月26日 13:14 llxzdmd
*/
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ComponentContext {
@Builder.Default // 确保构建器使用这个默认值
private Map<String, Object> contextMap = new HashMap<>();
}
然后还有一个问题,用户在前端怎么控制上下文的读取?常见的方式有两种,为了方便展示,我这里用plantUML的图作为示例,有兴趣的也可以自行下载这个插件。
@startuml
'https://plantuml.com/class-diagram
class DistinctFilter {
List list: ['a','b','c','d','e','f','g','c','a'];
output() result: result1
}
class PageFilter {
List list: ${result1}、DistinctFilter.result1
Integer pageNum: 2
Integer pageSize: 3
output() result: result2
}
DistinctFilter --|> PageFilter
@enduml
之后的图都可以按照这种画法展示:把每个类看作是用户创建的组件;把泛化关系的箭头看作是组件运行的指向;冒号左边是定义、右边是实际填写的值;output()也是新引入的概念,让用户可以对组件的输出值进行控制,重命名后放入上下文里。
可以看到这里把 DistinctFilter 组件的输出值定义为result1,PageFilter想要获取这个值有两种常见的方式:${result1}、DistinctFilter.result1 。前者的实现是比较简单的,目前先用前者的实现方式,后者等以后有机会的话可能会编辑到这一篇中。
core.util.RegexUtils
创建一个工具类用于判断用户的输入是否是${}的EL表达式,以及对其处理去掉特殊字符。
package com.example.lowcode.util;
import org.apache.commons.lang3.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author llxzdmd
* @version RegexUtils.java, 2024年01月11日 18:56 llxzdmd
*/
public class RegexUtils {
/**
* 判断字符串中是否有EL表达式
*
* @param val
* @return
*/
public static boolean hasEl(String val) {
if(StringUtils.isEmpty(val)) {
return false;
}
String regex = "(\\$\\{[\\w\\.]+})";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(val);
return matcher.find();
}
/**
* 去掉${}
*
* @param val
* @return
*/
public static String dealEl(String val){
if (val == null || val.length() <= 3) {
return "";
}
// 去掉前两个字符,然后再去掉最后一个字符
return val.substring(2, val.length() - 1);
}
}
为了让组件和上下文结合,在公共接口类和抽象类中增加重载的接口方法:
Map<String, Object> execute(ComponentContext context, ComponentInfo componentInfo) throws Exception;
然后分别修改去重组件类和分页组件类,重写这个方法:
package com.example.lowcode.component;
import com.example.lowcode.core.model.*;
import com.example.lowcode.core.dto.ComponentInfo;
import com.example.lowcode.core.framework.*;
import com.google.common.collect.Sets;
import java.util.*;
/**
* 去重过滤器
*
* @author llxzdmd
* @version DistinctFilter.java, v 0.1 2024年01月02日 19:39 llxzdmd
*/
@ComponentDefinition(name = "DistinctFilter", type = ComponentTypeEnum.FILTER, desc = "去重过滤器")
@InputParamDefinition({
@Param(name = "list", desc = "需要去重的集合", type = ParamTypeEnum.LIST, required = true),
@Param(name = "params", desc = "对象集合的去重字段", type = ParamTypeEnum.LIST, required = false),
@Param(name = "paramTypes", desc = "去重字段的类型", type = ParamTypeEnum.LIST, required = false)
})
@OutputParamDefinition({@Param(name = "result", desc = "去重过滤器返回结果", required = true)})
public class DistinctFilter extends AbstractComponent {
@Override
public Map<String, Object> execute(ComponentInfo componentInfo) throws Exception {
// 先随便写,只考虑简单类型
List list = (List) componentInfo.getInputs().get("list").getValue();
Set set = Sets.newHashSet(list);
List difference = findListDifference(list, set);
System.out.println("被去重的元素有:" + difference);
HashMap<String, Object> result = new HashMap<>();
result.put("result", set);
return result;
}
@Override
public Map<String, Object> execute(ComponentContext context, ComponentInfo componentInfo) throws Exception {
// 先随便写,只考虑简单类型
List list = (List) componentInfo.getInputs().get("list").getValue();
String output = componentInfo.getOutputs().get("result").getValue().toString();
Set uniqueElements = new HashSet<>();
List duplicates = new ArrayList<>();
List deduplicatedList = new ArrayList<>();
for (Object element : list) {
if (!uniqueElements.add(element)) {
// 如果元素已经存在于集合中,则添加到duplicates列表中
duplicates.add(element);
} else {
// 否则,添加到去重后的列表中
deduplicatedList.add(element);
}
}
// 打印被删除的元素
System.out.println("Removed elements: " + duplicates);
HashMap<String, Object> result = new HashMap<>();
result.put(output, deduplicatedList);
context.getContextMap().putAll(result);
return result;
}
public static List findListDifference(List list, Set set) {
// 创建List的一个副本
List listCopy = new ArrayList<>(list);
// 遍历Set,尝试从List副本中移除元素
for (Object element : set) {
listCopy.remove(element);
}
// 返回剩余的元素,即为多出的元素
return listCopy;
}
}
package com.example.lowcode.component;
import com.example.lowcode.core.model.*;
import com.example.lowcode.core.dto.ComponentInfo;
import com.example.lowcode.core.framework.*;
import com.example.lowcode.util.RegexUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 分页过滤器
*
* @author llxzdmd
* @version PageFilter.java, v 0.1 2024年01月02日 19:39 llxzdmd
*/
@ComponentDefinition(name = "PageFilter", type = ComponentTypeEnum.FILTER, desc = "分页过滤器")
@InputParamDefinition({
@Param(name = "list", desc = "需要分页的集合", type = ParamTypeEnum.LIST, required = true),
@Param(name = "pageNum", desc = "起始在第几页,默认1", type = ParamTypeEnum.INT, required = false),
@Param(name = "pageSize", desc = "分页大小,默认10", type = ParamTypeEnum.INT, required = false)
})
@OutputParamDefinition({@Param(name = "result", desc = "分页过滤器返回结果", required = true)})
public class PageFilter extends AbstractComponent {
@Override
public Map<String, Object> execute(ComponentInfo componentInfo) throws Exception {
List list = (List) componentInfo.getInputs().get("list").getValue();
int pageNum = componentInfo.getInputs().get("pageNum") == null ?
1 : (int) componentInfo.getInputs().get("pageNum").getValue();
int pageSize = componentInfo.getInputs().get("pageSize") == null ?
10 : (int) componentInfo.getInputs().get("pageSize").getValue();
HashMap<String, Object> result = new HashMap<>();
if (list == null || pageNum <= 0 || pageSize <= 0) {
result.put("result", Collections.emptyList());
return result;
}
int fromIndex = (pageNum - 1) * pageSize;
if (fromIndex >= list.size()) {
// 请求的页码超出了列表的范围,返回空列表
result.put("result", Collections.emptyList());
return result;
}
int toIndex = Math.min(fromIndex + pageSize, list.size());
result.put("result", list.subList(fromIndex, toIndex));
return result;
}
@Override
public Map<String, Object> execute(ComponentContext context, ComponentInfo componentInfo) throws Exception {
Object o = componentInfo.getInputs().get("list").getValue();
List list;
if (RegexUtils.hasEl(o.toString())) {
Object realValue = context.getContextMap().get(RegexUtils.dealEl(o.toString()));
if (realValue instanceof List<?>) {
list = (List) realValue;
} else {
throw new RuntimeException();
}
} else {
list = (List) componentInfo.getInputs().get("list").getValue();
}
int pageNum = componentInfo.getInputs().get("pageNum") == null ?
1 : (int) componentInfo.getInputs().get("pageNum").getValue();
int pageSize = componentInfo.getInputs().get("pageSize") == null ?
10 : (int) componentInfo.getInputs().get("pageSize").getValue();
HashMap<String, Object> result = new HashMap<>();
if (list == null || pageNum <= 0 || pageSize <= 0) {
result.put("result", Collections.emptyList());
return result;
}
int fromIndex = (pageNum - 1) * pageSize;
if (fromIndex >= list.size()) {
// 请求的页码超出了列表的范围,返回空列表
result.put("result", Collections.emptyList());
return result;
}
int toIndex = Math.min(fromIndex + pageSize, list.size());
result.put("result", list.subList(fromIndex, toIndex));
return result;
}
}
再修改运行组件流的接口,改为运行最新的接口:
测试
然后修改上一篇中的 saveFlowSnapshotTest 方法
@Test
public void saveFlowSnapshotTest() {
FlowNode flowNode1 = new FlowNode();
flowNode1.setNodeName("去重组件(前端展示的组件名)");
flowNode1.setOrder(1);
flowNode1.setComponentId(1L);
flowNode1.setComponentName("DistinctFilter");
flowNode1.setType(ComponentTypeEnum.FILTER);
// 去重过滤器填参数
ComponentInfo componentInfo1 = new ComponentInfo();
componentInfo1.setComponentId(1L);
componentInfo1.setFlowId(1L);
List<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("b");
stringList.add("c");
stringList.add("d");
stringList.add("e");
stringList.add("f");
stringList.add("g");
stringList.add("c");
stringList.add("a");
Map<String, ComponentParam> inputMap1 = new HashMap<>();
inputMap1.put("list", buildComponentParam(
ParamTypeEnum.LIST, "list123", "需要去重的集合", stringList, true
));
Map<String, ComponentParam> outputMap1 = new HashMap<>();
outputMap1.put("result", buildComponentParam(
ParamTypeEnum.STRING, "result", "执行结果,放进上下文", "result1", true
));
componentInfo1.setInputs(inputMap1);
componentInfo1.setOutputs(outputMap1);
flowNode1.setComponentInfo(componentInfo1);
flowNode1.setComponentMetaInfo(ComponentMetaInfoDO.mockData().get(0));
FlowNode flowNode2 = new FlowNode();
flowNode2.setNodeName("分页组件(前端展示的组件名)");
flowNode2.setOrder(2);
flowNode2.setComponentId(2L);
flowNode2.setComponentName("PageFilter");
flowNode2.setType(ComponentTypeEnum.FILTER);
// 分页过滤器填参数
ComponentInfo componentInfo2 = new ComponentInfo();
componentInfo2.setComponentId(2L);
componentInfo2.setFlowId(1L);
Map<String, ComponentParam> inputMap2 = new HashMap<>();
inputMap2.put("list", buildComponentParam(
ParamTypeEnum.LIST, "list456", "需要分页的集合", "${result1}", true
));
inputMap2.put("pageNum", buildComponentParam(
ParamTypeEnum.INT, "pageNum111", "起始在第几页,默认1", 2, false
));
inputMap2.put("pageSize", buildComponentParam(
ParamTypeEnum.INT, "pageSize111", "分页大小,默认10", 3, false
));
componentInfo2.setInputs(inputMap2);
flowNode2.setComponentInfo(componentInfo2);
flowNode2.setComponentMetaInfo(ComponentMetaInfoDO.mockData().get(1));
List<FlowNode> flowNodeList = new ArrayList<>();
flowNodeList.add(flowNode1);
flowNodeList.add(flowNode2);
// 调用保存组件流快照的接口,模拟入库
FlowSnapshotDO.insertData(1L, JSON.toJSONString(flowNodeList));
}
实现上下文功能
总结
到此本项目终于有点正规的感觉了,后续的1.x应该是增加一些组件代码和完善生产中的一些必备能力,但目前代码只敲到这里了,因此更新速度可能会慢一点。