feature #IBL9CK 增加bind关键字,能够在任何地方bind key和value

This commit is contained in:
everywhere.z
2025-02-21 16:27:23 +08:00
parent 5ea2bb2463
commit 96061dd5d3
26 changed files with 487 additions and 56 deletions

View File

@@ -114,4 +114,6 @@ public interface ChainConstant {
String USER_DIR = "user.dir";
String BIND = "bind";
String CONTEXT_SEARCH_REGEX = "^\\$\\{(.*?)\\}$";
}

View File

@@ -9,10 +9,13 @@ package com.yomahub.liteflow.core;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.yomahub.liteflow.common.ChainConstant;
import com.yomahub.liteflow.core.proxy.LiteFlowProxyUtil;
import com.yomahub.liteflow.enums.CmpStepTypeEnum;
import com.yomahub.liteflow.enums.NodeTypeEnum;
import com.yomahub.liteflow.exception.ObjectConvertException;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.flow.entity.CmpStep;
@@ -26,6 +29,7 @@ import com.yomahub.liteflow.slot.DataBus;
import com.yomahub.liteflow.slot.Slot;
import com.yomahub.liteflow.spi.holder.CmpAroundAspectHolder;
import com.yomahub.liteflow.util.JsonUtil;
import com.yomahub.liteflow.util.LiteflowContextRegexMatcher;
import java.lang.reflect.Method;
import java.util.Date;
@@ -446,10 +450,31 @@ public abstract class NodeComponent{
if (StrUtil.isBlank(bindData)) {
return null;
}
if (clazz.equals(String.class) || clazz.equals(Object.class)) {
return (T) bindData;
//如果bind的value是一个正则表达式说明要在上下文中进行搜索
if (ReUtil.isMatch(ChainConstant.CONTEXT_SEARCH_REGEX, bindData)) {
Object searchResult = LiteflowContextRegexMatcher.searchContext(
this.getSlot().getContextBeanList(),
ReUtil.getGroup1(ChainConstant.CONTEXT_SEARCH_REGEX, bindData)
);
if (searchResult == null){
return null;
}
//搜索到的对象一定要符合给定的clazz
if (clazz.isAssignableFrom(searchResult.getClass())) {
return (T) searchResult;
}else{
String errMsg = StrUtil.format("{} cannot convert to {}", searchResult.getClass().getName(), clazz.getName());
throw new ObjectConvertException(errMsg);
}
}else{
if (clazz.equals(String.class) || clazz.equals(Object.class)) {
return (T) bindData;
}
return JsonUtil.parseObject(bindData, clazz);
}
return JsonUtil.parseObject(bindData, clazz);
}
public <T> List<T> getBindDataList(Class<T> clazz) {
@@ -514,4 +539,10 @@ public abstract class NodeComponent{
Class<?> originalClass = LiteFlowProxyUtil.getUserClass(this.getClass());
return originalClass.getName();
}
public static void main(String[] args) {
boolean flag = ReUtil.isMatch(ChainConstant.CONTEXT_SEARCH_REGEX, "${user.name}");
System.out.println(ReUtil.getGroup1(ChainConstant.CONTEXT_SEARCH_REGEX, "${user.name}"));
}
}

View File

@@ -20,6 +20,7 @@ import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.log.LFLog;
import com.yomahub.liteflow.log.LFLoggerManager;
import com.yomahub.liteflow.slot.DataBus;
import com.yomahub.liteflow.util.LiteflowContextRegexMatcher;
import com.yomahub.liteflow.util.SerialsUtil;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
@@ -157,8 +158,6 @@ public class DeclComponentProxy {
}
private final ExpressRunner expressRunner = new ExpressRunner();
private Object[] loadMethodParameter(Object proxy, MethodWrapBean methodWrapBean, Object[] args){
NodeComponent thisNodeComponent = (NodeComponent) proxy;
@@ -181,38 +180,9 @@ public class DeclComponentProxy {
return null;
}
// 把上下文数据转换成map形式的key为别名value为上下文
Map<String, Object> contextMap = DataBus.getSlot(thisNodeComponent.getSlotIndex()).getContextBeanList().stream().collect(
Collectors.toMap(tuple -> tuple.get(0), tuple -> tuple.get(1))
);
List<String> errorList = new ArrayList<>();
Object result = null;
// 根据表达式去上下文里搜索相匹配的数据
for(Map.Entry<String, Object> entry : contextMap.entrySet()){
try{
InstructionSet instructionSet = expressRunner.getInstructionSetFromLocalCache(entry.getKey() + "." + parameterWrapBean.getFact().value());
DefaultContext<String, Object> context = new DefaultContext<>();
context.put(entry.getKey(), entry.getValue());
result = expressRunner.execute(instructionSet, context, errorList, false, false);
if (result != null){
break;
}
}catch (Exception ignore){}
}
if (result == null){
try{
// 如果没有搜到,那么尝试推断表达式是指定的上下文,按照指定上下文的方式去再获取
InstructionSet instructionSet = expressRunner.getInstructionSetFromLocalCache("contextMap." + parameterWrapBean.getFact().value());
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("contextMap", contextMap);
result = expressRunner.execute(instructionSet, context, errorList, false, false);
}catch (Exception ignore){}
}
return result;
return LiteflowContextRegexMatcher.searchContext(
DataBus.getSlot(thisNodeComponent.getSlotIndex()).getContextBeanList(),
parameterWrapBean.getFact().value());
}).toArray();
}
}

View File

@@ -0,0 +1,29 @@
package com.yomahub.liteflow.exception;
/**
* 对象转型异常
*
* @author Bryan.Zhang
* @since 2.13.0
*/
public class ObjectConvertException extends RuntimeException {
private static final long serialVersionUID = 1L;
/** 异常信息 */
private String message;
public ObjectConvertException(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@@ -0,0 +1,57 @@
package com.yomahub.liteflow.util;
import cn.hutool.core.lang.Tuple;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.InstructionSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* LiteFlow上下文正则表达式匹配器
* 用来根据正则表达式去寻找上下文中符合的对象
* @author Bryan.Zhang
* @since 2.13.0
*/
public class LiteflowContextRegexMatcher {
private static final ExpressRunner expressRunner = new ExpressRunner();
public static Object searchContext(List<Tuple> contextList, String regPattern){
// 把上下文数据转换成map形式的key为别名value为上下文
Map<String, Object> contextMap = contextList.stream().collect(
Collectors.toMap(tuple -> tuple.get(0), tuple -> tuple.get(1))
);
List<String> errorList = new ArrayList<>();
Object result = null;
// 根据表达式去上下文里搜索相匹配的数据
for(Map.Entry<String, Object> entry : contextMap.entrySet()){
try{
InstructionSet instructionSet = expressRunner.getInstructionSetFromLocalCache(entry.getKey() + "." + regPattern);
DefaultContext<String, Object> context = new DefaultContext<>();
context.put(entry.getKey(), entry.getValue());
result = expressRunner.execute(instructionSet, context, errorList, false, false);
if (result != null){
break;
}
}catch (Exception ignore){}
}
if (result == null){
try{
// 如果没有搜到,那么尝试推断表达式是指定的上下文,按照指定上下文的方式去再获取
InstructionSet instructionSet = expressRunner.getInstructionSetFromLocalCache("contextMap." + regPattern);
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("contextMap", contextMap);
result = expressRunner.execute(instructionSet, context, errorList, false, false);
}catch (Exception ignore){}
}
return result;
}
}

View File

@@ -24,6 +24,12 @@
<version>${revision}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-javax</artifactId>
<version>${revision}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -18,11 +18,11 @@ import javax.annotation.Resource;
*
* @author Bryan.Zhang
*/
@TestPropertySource(value = "classpath:/bindData/application.properties")
@SpringBootTest(classes = BindDataSpringbootTest.class)
@TestPropertySource(value = "classpath:/bindData/application1.properties")
@SpringBootTest(classes = BindDataSpringbootTest1.class)
@EnableAutoConfiguration
@ComponentScan({ "com.yomahub.liteflow.test.bindData.cmp" })
public class BindDataSpringbootTest extends BaseTest {
@ComponentScan({ "com.yomahub.liteflow.test.bindData.cmp1" })
public class BindDataSpringbootTest1 extends BaseTest {
@Resource
private FlowExecutor flowExecutor;

View File

@@ -0,0 +1,100 @@
package com.yomahub.liteflow.test.bindData;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.exception.ObjectConvertException;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.slot.DefaultContext;
import com.yomahub.liteflow.test.BaseTest;
import com.yomahub.liteflow.test.bindData.context.Member;
import com.yomahub.liteflow.test.bindData.context.MemberContext;
import com.yomahub.liteflow.test.bindData.context.OrderContext;
import com.yomahub.liteflow.test.bindData.context.UserInfoContext;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.TestPropertySource;
import javax.annotation.Resource;
/**
* springboot环境EL常规的例子测试
*
* @author Bryan.Zhang
*/
@TestPropertySource(value = "classpath:/bindData/application2.properties")
@SpringBootTest(classes = BindDataSpringbootTest2.class)
@EnableAutoConfiguration
@ComponentScan({"com.yomahub.liteflow.test.bindData.cmp2"})
public class BindDataSpringbootTest2 extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
// 测试动态bind最简单的情况2个上下文中搜索
@Test
public void testBindDynamic1() throws Exception {
MemberContext memberContext = new MemberContext();
memberContext.setId(31);
memberContext.setName("jack");
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg", memberContext, new DefaultContext());
Assertions.assertTrue(response.isSuccess());
DefaultContext context = response.getContextBean(DefaultContext.class);
Assertions.assertEquals("jack", context.getData("a1"));
}
// 测试动态bind多级取数据2个上下文中搜索
@Test
public void testBindDynamic2() throws Exception {
OrderContext orderContext = new OrderContext();
orderContext.setOrderCode("SO1234");
orderContext.setMember(new Member("M0001","jack"));
LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg", orderContext, new DefaultContext());
Assertions.assertTrue(response.isSuccess());
DefaultContext context = response.getContextBean(DefaultContext.class);
Assertions.assertEquals("M0001", context.getData("a2"));
}
// 测试动态bind多个上下文拥有相同的变量指定上下文
@Test
public void testBindDynamic3() throws Exception {
OrderContext orderContext = new OrderContext();
orderContext.setId(1000);
orderContext.setOrderCode("SO1234");
orderContext.setMember(new Member("M0001","jack"));
MemberContext memberContext = new MemberContext();
memberContext.setId(2000);
memberContext.setName("jack");
LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg", orderContext, memberContext, new DefaultContext());
Assertions.assertTrue(response.isSuccess());
DefaultContext context = response.getContextBean(DefaultContext.class);
Assertions.assertEquals(2000, (Integer) context.getData("a3"));
}
// 测试动态bind多个上下文结合@ContextBean测试
@Test
public void testBindDynamic4() throws Exception {
UserInfoContext userInfoContext = new UserInfoContext();
userInfoContext.setInfo("test info");
LiteflowResponse response = flowExecutor.execute2Resp("chain4", "arg", userInfoContext, new DefaultContext());
Assertions.assertTrue(response.isSuccess());
DefaultContext context = response.getContextBean(DefaultContext.class);
Assertions.assertEquals("test info", context.getData("a4"));
}
// 测试动态bindgetBindData中的class给错报错
@Test
public void testBindDynamic5() throws Exception {
MemberContext memberContext = new MemberContext();
memberContext.setId(31);
memberContext.setName("jack");
LiteflowResponse response = flowExecutor.execute2Resp("chain5", "arg", memberContext, new DefaultContext());
Assertions.assertFalse(response.isSuccess());
Assertions.assertEquals(ObjectConvertException.class, response.getCause().getClass());
}
}

View File

@@ -5,7 +5,7 @@
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.bindData.cmp;
package com.yomahub.liteflow.test.bindData.cmp1;
import cn.hutool.core.util.StrUtil;
import com.yomahub.liteflow.core.NodeComponent;

View File

@@ -5,7 +5,7 @@
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.bindData.cmp;
package com.yomahub.liteflow.test.bindData.cmp1;
import cn.hutool.core.util.StrUtil;
import com.yomahub.liteflow.core.NodeComponent;

View File

@@ -5,7 +5,7 @@
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.bindData.cmp;
package com.yomahub.liteflow.test.bindData.cmp1;
import cn.hutool.core.util.StrUtil;
import com.yomahub.liteflow.core.NodeComponent;

View File

@@ -5,7 +5,7 @@
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.bindData.cmp;
package com.yomahub.liteflow.test.bindData.cmp1;
import cn.hutool.core.util.StrUtil;
import com.yomahub.liteflow.core.NodeComponent;

View File

@@ -1,4 +1,4 @@
package com.yomahub.liteflow.test.bindData.cmp;
package com.yomahub.liteflow.test.bindData.cmp1;
import com.yomahub.liteflow.core.NodeBooleanComponent;
import com.yomahub.liteflow.slot.DefaultContext;

View File

@@ -1,4 +1,4 @@
package com.yomahub.liteflow.test.bindData.cmp;
package com.yomahub.liteflow.test.bindData.cmp1;
import com.yomahub.liteflow.core.NodeSwitchComponent;
import com.yomahub.liteflow.slot.DefaultContext;

View File

@@ -0,0 +1,27 @@
/**
* <p>Title: liteflow</p>
* <p>Description: 轻量级的组件式流程框架</p>
* @author Bryan.Zhang
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.bindData.cmp2;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.slot.DefaultContext;
import com.yomahub.liteflow.test.bindData.context.Member;
import org.springframework.stereotype.Component;
@Component("a1")
public class A1Cmp extends NodeComponent {
@Override
public void process() {
DefaultContext defaultContext = this.getContextBean(DefaultContext.class);
String bindValue = this.getBindData("k1", String.class);
if (bindValue != null) {
defaultContext.setData(this.getNodeId(), bindValue);
}
}
}

View File

@@ -0,0 +1,28 @@
/**
* <p>Title: liteflow</p>
* <p>Description: 轻量级的组件式流程框架</p>
* @author Bryan.Zhang
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.bindData.cmp2;
import cn.hutool.core.util.StrUtil;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.slot.DefaultContext;
import com.yomahub.liteflow.test.bindData.context.Member;
import org.springframework.stereotype.Component;
@Component("a2")
public class A2Cmp extends NodeComponent {
@Override
public void process() {
DefaultContext context = this.getContextBean(DefaultContext.class);
String bindValue = this.getBindData("k1", String.class);
if (bindValue != null) {
context.setData(this.getNodeId(), bindValue);
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* <p>Title: liteflow</p>
* <p>Description: 轻量级的组件式流程框架</p>
* @author Bryan.Zhang
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.bindData.cmp2;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.slot.DefaultContext;
import org.springframework.stereotype.Component;
@Component("a3")
public class A3Cmp extends NodeComponent {
@Override
public void process() {
DefaultContext context = this.getContextBean(DefaultContext.class);
Integer bindValue = this.getBindData("k1", Integer.class);
if (bindValue != null) {
context.setData(this.getNodeId(), bindValue);
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* <p>Title: liteflow</p>
* <p>Description: 轻量级的组件式流程框架</p>
* @author Bryan.Zhang
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.bindData.cmp2;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.slot.DefaultContext;
import org.springframework.stereotype.Component;
@Component("a4")
public class A4Cmp extends NodeComponent {
@Override
public void process() {
DefaultContext context = this.getContextBean(DefaultContext.class);
String bindValue = this.getBindData("k1", String.class);
if (bindValue != null) {
context.setData(this.getNodeId(), bindValue);
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* <p>Title: liteflow</p>
* <p>Description: 轻量级的组件式流程框架</p>
* @author Bryan.Zhang
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.bindData.cmp2;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.slot.DefaultContext;
import org.springframework.stereotype.Component;
@Component("a5")
public class A5Cmp extends NodeComponent {
@Override
public void process() {
DefaultContext context = this.getContextBean(DefaultContext.class);
String bindValue = this.getBindData("k1", String.class);
if (bindValue != null) {
context.setData(this.getNodeId(), bindValue);
}
}
}

View File

@@ -0,0 +1,28 @@
package com.yomahub.liteflow.test.bindData.context;
public class Member {
private String memberCode;
private String memberName;
public Member(String memberCode, String memberName) {
this.memberCode = memberCode;
this.memberName = memberName;
}
public String getMemberCode() {
return memberCode;
}
public void setMemberCode(String memberCode) {
this.memberCode = memberCode;
}
public String getMemberName() {
return memberName;
}
public void setMemberName(String memberName) {
this.memberName = memberName;
}
}

View File

@@ -0,0 +1,24 @@
package com.yomahub.liteflow.test.bindData.context;
public class MemberContext {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,34 @@
package com.yomahub.liteflow.test.bindData.context;
public class OrderContext {
private Integer id;
private String orderCode;
private Member member;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getOrderCode() {
return orderCode;
}
public void setOrderCode(String orderCode) {
this.orderCode = orderCode;
}
public Member getMember() {
return member;
}
public void setMember(Member member) {
this.member = member;
}
}

View File

@@ -0,0 +1,17 @@
package com.yomahub.liteflow.test.bindData.context;
import com.yomahub.liteflow.context.ContextBean;
@ContextBean("userCx")
public class UserInfoContext {
private String info;
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}

View File

@@ -1 +0,0 @@
liteflow.rule-source=bindData/flow.el.xml

View File

@@ -0,0 +1 @@
liteflow.rule-source=bindData/flow2.xml

View File

@@ -2,22 +2,22 @@
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
<flow>
<chain id="chain1">
THEN(a.bind("k1", "test"), b);
THEN(a1.bind("k1", "${name}"));
</chain>
<chain id="chain2">
THEN(a,b).bind("k1","test");
THEN(a2.bind("k1", "${member.memberCode}"));
</chain>
<chain id="chain3">
THEN(SWITCH(y).TO(d,c), WHEN(a, b), IF(x, c, d)).bind("k1", "test")
</chain>
<chain id="sub">
THEN(a,IF(NOT(x), b, c));
THEN(a3.bind("k1", "${memberContext.id}"));
</chain>
<chain id="chain4">
THEN(d, sub.bind("k1", "test2"))
THEN(a4.bind("k1", "${userCx.info}"));
</chain>
<chain id="chain5">
THEN(a5.bind("k1", "${id}"));
</chain>
</flow>