提交 f38e0b2e authored 作者: Matrix's avatar Matrix

feat(行为执行器): 增加了行为执行器的运行与行为变量解析

上级 acd1e56e
...@@ -3,6 +3,7 @@ package org.matrix.actuators.httpclient; ...@@ -3,6 +3,7 @@ package org.matrix.actuators.httpclient;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.matrix.actuators.usecase.BaseTestCaseResponseDetail; import org.matrix.actuators.usecase.BaseTestCaseResponseDetail;
...@@ -11,6 +12,7 @@ import org.springframework.http.HttpStatus; ...@@ -11,6 +12,7 @@ import org.springframework.http.HttpStatus;
/** /**
* @author huangxiahao * @author huangxiahao
*/ */
@EqualsAndHashCode(callSuper = true)
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
......
package org.matrix.actuators.move;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* MoveRegularObject.
*
* @author Matrix <xhyrzldf@gmail.com>
* @since 2022/3/7 at 6:24 PM
* Suffering is the most powerful teacher of life.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MoveRegularObject {
/**
* 在ThreadLocal里的key值
*/
private String key;
/**
* 要取的字段名
*/
private String colName;
}
package org.matrix.actuators.move;
/**
* MoveStrategy.
*
* @author Matrix <xhyrzldf@gmail.com>
* @since 2022/3/7 at 3:11 PM
* Suffering is the most powerful teacher of life.
*/
public enum MoveStrategy {
/**
* 前置动作
*/
PRE_MOVE,
/**
* 中间动作
*/
MID_MOVE,
/**
* 后置动作
*/
AFT_MOVE;
}
...@@ -45,7 +45,7 @@ import static org.springframework.util.CollectionUtils.isEmpty; ...@@ -45,7 +45,7 @@ import static org.springframework.util.CollectionUtils.isEmpty;
public class SqlExpActuator implements Actuator { public class SqlExpActuator implements Actuator {
/** /**
* 用于正则找出形如${id}这样的SQL变量 * 用于正则找出形如${id} 或者 ${id}[1] 这样的SQL变量
*/ */
public static final String DYNAMIC_VAR_EXP = "\\$\\{(\\w*)}\\[?(\\d*)]?"; public static final String DYNAMIC_VAR_EXP = "\\$\\{(\\w*)}\\[?(\\d*)]?";
...@@ -62,13 +62,51 @@ public class SqlExpActuator implements Actuator { ...@@ -62,13 +62,51 @@ public class SqlExpActuator implements Actuator {
private final IDynamicVariableService varService; private final IDynamicVariableService varService;
private final IConnectService connectService; private final IConnectService connectService;
private final IDataSourceService dataSourceService; private final IDataSourceService dataSourceService;
private final ITestCaseService caseService;
private final ITestDataService dataService; private final ITestDataService dataService;
/**
* 解析并运行SQL语句,可以包含别的SQL变量,例如 'select * from user where user.id = ${user_id} '
* @param sqlDetail sql的poolId与sql语句的对象
* @param envId 环境id
* @param projectId 项目id
* @return 运行SQL后取完数值的结果, 是一张数据表, 以LIST<MAP>的形式呈现
*/
public List<Map<String, Object>> parseSql(SqlExpDetail sqlDetail,Long envId,Long projectId){
// 找到第一层的SQL变量,递归替换
String sqlExp = sqlDetail.getSqlExp();
Long connectId = sqlDetail.getPoolId();
List<SqlRegularObject> varList = findDynamicVarList(sqlExp);
for (SqlRegularObject sqlReg : varList) {
sqlExp = sqlExp.replaceAll(sqlReg.getVarName(), parseVarByName(sqlReg.getVarName(), envId, projectId));
}
// 运行SQL
DataSourceDTO dataSourceDTO = Optional.of(connectService.getById(connectId))
.orElseThrow(() -> new GlobalException(String.format("没有找到id = %d 的连接池connect对象", connectId)))
.toDataSourceDTO();
// 替换环境共享变量
sqlExp = envActuator.replaceEnvVar(sqlExp, envId);
// 校验dynamicVar里的detail是否是可以直接执行的SQL
boolean allVarParsed = findDynamicVarList(sqlExp).size() == 0;
if (allVarParsed) {
// 切换数据源,执行SQL,获取数值
Set<String> dataSources = dataSourceService.switchDataSource(dataSourceDTO);
log.info("当前存在的数据源 {}", dataSources);
List<Map<String, Object>> resultMap = jdbcTemplate.queryForList(sqlExp);
dataSourceService.switchMainDataSource();
return resultMap;
} else {
throw new GlobalException(String.format("SQL解析异常,SQL语句为%s,连接池id为%d", sqlDetail.getSqlExp(), sqlDetail.getPoolId()));
}
}
/** /**
* 解析给定的动态变量ByName * 解析给定的动态变量ByName
* *
* @param varNameString 变量名,形如${id}这样的字符串 * @param varNameString 变量名,形如${id}或者是 ${id}[1]这样的字符串,默认下标为0
* @param projectId 该变量所在的项目ID * @param projectId 该变量所在的项目ID
* @return 变量递归解析后的值 * @return 变量递归解析后的值
*/ */
...@@ -170,7 +208,8 @@ public class SqlExpActuator implements Actuator { ...@@ -170,7 +208,8 @@ public class SqlExpActuator implements Actuator {
String sqlExp = dynamicVar.getSqlExpDetail().getSqlExp(); String sqlExp = dynamicVar.getSqlExpDetail().getSqlExp();
sqlExp = envActuator.replaceEnvVar(sqlExp, envId); sqlExp = envActuator.replaceEnvVar(sqlExp, envId);
List<SqlRegularObject> dynamicVarList = findDynamicVarList(dynamicVar.getDetail()); String detail = dynamicVar.getDetail();
List<SqlRegularObject> dynamicVarList = findDynamicVarList(detail);
// 解析SQL表达式,判断是可以直接执行的SQL还是需要再递归解析动态变量 // 解析SQL表达式,判断是可以直接执行的SQL还是需要再递归解析动态变量
if (!isEmpty(dynamicVarList)) { if (!isEmpty(dynamicVarList)) {
// 如果还存在动态变量,则继续递归解析 // 如果还存在动态变量,则继续递归解析
......
...@@ -7,6 +7,7 @@ import lombok.AllArgsConstructor; ...@@ -7,6 +7,7 @@ import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.matrix.enums.ActionType;
/** /**
* <p> * <p>
...@@ -37,8 +38,8 @@ public class Action extends BaseEntity { ...@@ -37,8 +38,8 @@ public class Action extends BaseEntity {
private String remark; private String remark;
@ApiModelProperty("类型 1为SQL,2为HTTP,3为CASE,4为WAIT_TIME") @ApiModelProperty("类型 1为SQL,2为HTTP,3为CASE,4为WAIT_TIME")
private Integer type; private ActionType type;
@ApiModelProperty("详细参数") @ApiModelProperty("详细参数")
private String detail; private String detail;
......
...@@ -3,6 +3,8 @@ package org.matrix.database.service; ...@@ -3,6 +3,8 @@ package org.matrix.database.service;
import org.matrix.database.entity.Action; import org.matrix.database.entity.Action;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Optional;
/** /**
* <p> * <p>
* 动作 服务类 * 动作 服务类
...@@ -13,4 +15,6 @@ import com.baomidou.mybatisplus.extension.service.IService; ...@@ -13,4 +15,6 @@ import com.baomidou.mybatisplus.extension.service.IService;
*/ */
public interface IActionService extends IService<Action> { public interface IActionService extends IService<Action> {
Optional<Action> findByMoveAndEnv(Long moveId, Long envId);
} }
package org.matrix.database.service.impl; package org.matrix.database.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.matrix.database.entity.Action; import org.matrix.database.entity.Action;
import org.matrix.database.mapper.ActionMapper; import org.matrix.database.mapper.ActionMapper;
import org.matrix.database.service.IActionService; import org.matrix.database.service.IActionService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Optional;
/** /**
* <p> * <p>
* 动作 服务实现类 * 动作 服务实现类
...@@ -17,4 +21,18 @@ import org.springframework.stereotype.Service; ...@@ -17,4 +21,18 @@ import org.springframework.stereotype.Service;
@Service @Service
public class ActionServiceImpl extends ServiceImpl<ActionMapper, Action> implements IActionService { public class ActionServiceImpl extends ServiceImpl<ActionMapper, Action> implements IActionService {
private final ActionMapper actionMapper;
public ActionServiceImpl(ActionMapper mapper) {
this.actionMapper = mapper;
}
@Override
public Optional<Action> findByMoveAndEnv(Long moveId, Long envId) {
LambdaQueryWrapper<Action> actionWrappers = Wrappers.lambdaQuery(Action.class);
Action action = actionMapper.selectOne(actionWrappers
.eq(Action::getMoveId, moveId)
.eq(Action::getEnvId, envId));
return Optional.ofNullable(action);
}
} }
...@@ -35,7 +35,7 @@ public class DynamicVariableServiceImpl extends ServiceImpl<DynamicVariableMappe ...@@ -35,7 +35,7 @@ public class DynamicVariableServiceImpl extends ServiceImpl<DynamicVariableMappe
*/ */
@Override @Override
public Optional<DynamicVariable> getByName(String name, Long projectId) { public Optional<DynamicVariable> getByName(String name, Long projectId) {
return Optional.ofNullable(mapper.selectOne(Wrappers.lambdaUpdate(DynamicVariable.class) return Optional.ofNullable(mapper.selectOne(Wrappers.lambdaQuery(DynamicVariable.class)
.eq(DynamicVariable::getName, name) .eq(DynamicVariable::getName, name)
.eq(DynamicVariable::getProjectId, projectId))); .eq(DynamicVariable::getProjectId, projectId)));
} }
......
package org.matrix.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* ActionType.
* 动作的类型,该变量决定了运行时使用哪一种执行器
*
* @author Matrix <xhyrzldf@gmail.com>
* @since 2022/2/25 at 5:18 PM
* Suffering is the most powerful teacher of life.
*/
@Getter
@AllArgsConstructor
public enum ActionType {
/**
* SQL类型动作,该动作执行SQL语句
*/
SQL_ACTION(1, "SQL类型动作"),
/**
* HTTP类型动作,该动作执行HTTP接口
*/
HTTP_ACTION(2, "HTTP类型动作"),
/**
* 用例类型动作,该动作执行另外的测试用例
*/
CASE_ACTION(3, "用例类型动作"),
/**
* 等待类动作,线程休眠指定的秒数
*/
WAIT_ACTION(4, "等待类动作");
/**
* 数据库里记录的字段使用该字段来记录
*/
@EnumValue
private final int code;
private final String des;
}
package org.matrix.util;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* BeanFlattener. javaBean 转对象
*
* @author Matrix <xhyrzldf@gmail.com>
* @since 2022/3/7 at 5:15 PM
* Suffering is the most powerful teacher of life.
*/
public final class BeanFlattener {
private BeanFlattener() {}
public static Map<String, Object> deepToMap(Object bean) {
Map<String, Object> map = new LinkedHashMap<>();
try {
putValues(bean, map, null);
} catch (IllegalAccessException x) {
throw new IllegalArgumentException(x);
}
return map;
}
private static void putValues(Object bean,
Map<String, Object> map,
String prefix)
throws IllegalAccessException {
Class<?> cls = bean.getClass();
for (Field field : cls.getDeclaredFields()) {
if (field.isSynthetic() || Modifier.isStatic(field.getModifiers())) {
continue;
}
field.setAccessible(true);
Object value = field.get(bean);
String key;
if (prefix == null) {
key = field.getName();
} else {
key = prefix + "." + field.getName();
}
if (isValue(value)) {
map.put(key, value);
} else {
putValues(value, map, key);
}
}
}
private static final Set<Class<?>> VALUE_CLASSES =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
Object.class, String.class, Boolean.class,
Character.class, Byte.class, Short.class,
Integer.class, Long.class, Float.class,
Double.class
// etc.
)));
private static boolean isValue(Object value) {
return value == null
|| value instanceof Enum<?>
|| VALUE_CLASSES.contains(value.getClass());
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论