提交 7e61e683 authored 作者: Matrix's avatar Matrix

fix(SQL执行器): 结构优化|BUG修复

- 优化了项目结构 - 修复了各类执行器的BUG - 完成了环境变量替换,递归SQL变量的测试
上级 826e8fa5
......@@ -95,7 +95,6 @@
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
......@@ -159,6 +158,12 @@
<version>5.5.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package org.matrix;
import lombok.extern.slf4j.Slf4j;
import org.matrix.actuators.datasource.DataSourceDTO;
import org.matrix.actuators.datasource.IDataSourceService;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Hello world!
*
* @author matrix
*/
@Slf4j
@SpringBootApplication
public class BaseBootApplication {
@MapperScan("org.matrix.database.mapper")
public class BaseBootApplication implements CommandLineRunner {
@Value("${spring.datasource.dynamic.datasource.master.url}")
private String url;
@Value("${spring.datasource.dynamic.datasource.master.username}")
private String username;
@Value("${spring.datasource.dynamic.datasource.master.password}")
private String password;
@Value("${spring.datasource.dynamic.datasource.master.driverClassName}")
private String driverClassName;
private final IDataSourceService dataSourceService;
public BaseBootApplication(IDataSourceService dataSourceService) {
this.dataSourceService = dataSourceService;
}
public static void main(String[] args) {
SpringApplication.run(BaseBootApplication.class, args);
}
/**
* Callback used to run the bean.
*
* @param args incoming main method arguments
* @throws Exception on error
*/
@Override
public void run(String... args) throws Exception {
log.info("[初始化] 初始化数据源" + driverClassName);
dataSourceService.add(new DataSourceDTO("初始数据源",driverClassName,url,username,password));
}
}
......@@ -20,12 +20,16 @@ import java.util.Set;
* Suffering is the most powerful teacher of life.
*/
@Service
@RequiredArgsConstructor(onConstructor = @__(@Lazy))
public class IDataSourceServiceImpl implements IDataSourceService {
private final DataSource dataSource;
private final DefaultDataSourceCreator dataSourceCreator;
public IDataSourceServiceImpl(DataSource dataSource, DefaultDataSourceCreator dataSourceCreator) {
this.dataSource = dataSource;
this.dataSourceCreator = dataSourceCreator;
}
/**
* 将当前使用的数据替换为参数提供的数据源,如果还没有,则会添加,如果已经有了,则不会添加
*
......
......@@ -2,8 +2,8 @@ package org.matrix.actuators.sql;
import cn.hutool.core.util.ReUtil;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.matrix.actuators.Actuator;
import org.matrix.actuators.datasource.DataSourceDTO;
import org.matrix.actuators.datasource.IDataSourceService;
......@@ -21,14 +21,10 @@ import org.matrix.database.service.IExampleService;
import org.matrix.database.service.ITestCaseService;
import org.matrix.enums.DynamicVarType;
import org.matrix.exception.GlobalException;
import org.springframework.context.annotation.Lazy;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -45,18 +41,23 @@ import static org.springframework.util.CollectionUtils.isEmpty;
*/
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Lazy))
public class SqlExpActuator implements Actuator {
/**
* 用于正则找出形如${id}这样的SQL变量
*/
public static final String DYNAMIC_VAR_EXP = "(\\$\\{.*})[\\[]?(.*)[]]?";
public static final String DYNAMIC_VAR_EXP = "\\$\\{(\\w*)}\\[?(\\d*)]?";
//language=RegExp
/**
* 用于replaceAll的正则,目的是替换掉形如${id} 或者是 ${id}[1] 这样的SQL变量
*/
public static final String REPLACE_VAR_EXP = "\\$\\{%s}(\\[\\w*])?";
/**
* 用于正则找出形如#{id}这样的环境
*/
public static final String ENV_VAR_EXP = "(#\\{.*})";
public static final String ENV_VAR_EXP = "(#\\{(\\w*)})";
private final JdbcTemplate jdbcTemplate;
private final IDynamicVariableService varService;
......@@ -65,6 +66,15 @@ public class SqlExpActuator implements Actuator {
private final IDataSourceService dataSourceService;
private final ITestCaseService caseService;
public SqlExpActuator(JdbcTemplate jdbcTemplate, IDynamicVariableService varService, IExampleService envService, IConnectService connectService, IDataSourceService dataSourceService, ITestCaseService caseService) {
this.jdbcTemplate = jdbcTemplate;
this.varService = varService;
this.envService = envService;
this.connectService = connectService;
this.dataSourceService = dataSourceService;
this.caseService = caseService;
}
/**
* 解析给定的动态变量ByName
*
......@@ -73,11 +83,12 @@ public class SqlExpActuator implements Actuator {
* @return 变量递归解析后的值
*/
public String parseVarByName(String varNameString, Long envId, Long projectId) {
List<SqlRegObject> varList = findDynamicVarList(varNameString);
List<SqlRegularObject> varList = findDynamicVarList(varNameString);
if (varList.size() == 1) {
SqlRegObject sqlReg = varList.get(0);
SqlRegularObject sqlReg = varList.get(0);
DynamicVariable variable = varService.getByName(sqlReg.getVarName(), projectId)
.orElseThrow(() -> new GlobalException("没有找到name = " + sqlReg.getVarName() + " 的动态变量"));
.orElseThrow(() -> new GlobalException("没有找到name = " + sqlReg.getVarName() + " 的动态变量"))
.parseSqlDetail();
return parseVar(envId, sqlReg.getIndex(), projectId, variable);
} else {
throw new GlobalException("varNameString 参数请一次只输入一个动态变量! 你的提供的参数为: " + varNameString);
......@@ -116,6 +127,14 @@ public class SqlExpActuator implements Actuator {
}
}
/**
* 用例处理器
*
* @param envId 环境ID - 环境共享变量需要
* @param projectId 项目id,防止SQL表达式中带的嵌合动态变量名字重复
* @param dynamicVar {@link DynamicVariable}
* @return 解析后的字符串
*/
private String handleCaseVar(Long envId, Long projectId, DynamicVariable dynamicVar) {
CaseActuator caseActuator = new CaseActuator(envId, projectId);
long caseId = Long.parseLong(dynamicVar.getDetail());
......@@ -125,35 +144,51 @@ public class SqlExpActuator implements Actuator {
return responseDetail.getResponseBody();
}
/**
* HTTP请求处理器
*
* @param envId 环境ID - 环境共享变量需要
* @param projectId 项目id,防止SQL表达式中带的嵌合动态变量名字重复
* @param dynamicVar {@link DynamicVariable}
* @return 解析后的字符串
*/
private String handleHttpVar(Long envId, Long projectId, DynamicVariable dynamicVar) {
HttpClientActuator httpClient = new HttpClientActuator(new HttpRequestConfig(), envId, projectId);
HttpRequestDetail httpRequestDetail = JSON.parseObject(dynamicVar.getDetail(), HttpRequestDetail.class);
return httpClient.sendHttpRequest(httpRequestDetail).getResponseBody();
}
/**
* SQL表达式处理器
*
* @param envId 环境ID - 环境共享变量需要
* @param projectId 项目id,防止SQL表达式中带的嵌合动态变量名字重复
* @param dynamicVar {@link DynamicVariable}
* @return 解析后的字符串
*/
private String handleSqlVar(Long envId, Integer takenIndex, Long projectId, DynamicVariable dynamicVar) {
// 首先替换掉#{id}这类的共享变量(如果有的话)
String sqlExp = dynamicVar.getSqlExpDetail().getSqlExp();
sqlExp = replaceEnvVar(sqlExp, envId);
List<SqlRegObject> dynamicVarList = findDynamicVarList(dynamicVar.getDetail());
List<SqlRegularObject> dynamicVarList = findDynamicVarList(dynamicVar.getDetail());
// 解析SQL表达式,判断是可以直接执行的SQL还是需要再递归解析动态变量
if (isEmpty(dynamicVarList)) {
// 如果查到的动态变量列表为空,则直接执行SQL
List<Map<String, Object>> resultList = runSql(dynamicVar, envId);
return takenResultString(resultList, dynamicVar.getTakenField(), Math.toIntExact(takenIndex));
} else {
if (!isEmpty(dynamicVarList)) {
// 如果还存在动态变量,则继续递归解析
for (SqlRegObject sqlReg : dynamicVarList) {
DynamicVariable recVar = varService.getByName(sqlReg.getVarName(), projectId)
for (SqlRegularObject sqlRegular : dynamicVarList) {
DynamicVariable recVar = varService.getByName(sqlRegular.getVarName(), projectId)
.orElseThrow(() -> new GlobalException(
String.format("没有找到项目id = %d 下,name = %s的变量", projectId, sqlReg.getVarName())));
String calculatedValue = parseVar(envId, sqlReg.getIndex(), projectId, recVar);
sqlExp = sqlExp.replaceAll(sqlReg.getVarName(), calculatedValue);
String.format("没有找到项目id = %d 下,name = %s的变量", projectId, sqlRegular.getVarName())))
.parseSqlDetail();
String calculatedValue = parseVar(envId, sqlRegular.getIndex(), projectId, recVar);
sqlExp = sqlExp.replaceAll(String.format(REPLACE_VAR_EXP, sqlRegular.getVarName()), "'" + calculatedValue + "'");
dynamicVar.getSqlExpDetail().setSqlExp(sqlExp);
}
// 将里面的动态变量全部计算完替换之后返回出去
return sqlExp;
}
// 执行SQL
List<Map<String, Object>> resultList = runSql(dynamicVar, envId);
//正则
return takenResultString(resultList, dynamicVar.getTakenField(), Math.toIntExact(takenIndex));
}
/**
......@@ -182,15 +217,16 @@ public class SqlExpActuator implements Actuator {
* @return 替换后的字符串
*/
private String replaceEnvVar(String sqlExp, Long envId) {
// SQL表达式首先尝试替换掉形如#{name}的共享环境变量
// SQL表达式首先尝试替换掉形如#{name}的共享环境变量 todo 这里的env的map应该做一个缓存
Example env = Optional.of(envService.getById(envId))
.orElseThrow(() -> new GlobalException("没有找到对应ID的example(env)对象,id = " + envId));
Map<String, String> envMap = env.getVariable();
List<String> envList = ReUtil.findAll(ENV_VAR_EXP, sqlExp, 0, new ArrayList<>());
List<String> envList = ReUtil.findAll(ENV_VAR_EXP, sqlExp, 2, new ArrayList<>());
// 到对应env的变量池里找到值替换掉
for (String key : envList) {
if (envMap.containsKey(key)) {
sqlExp = sqlExp.replaceAll(key, envMap.get(key));
log.info("[变量替换] 将环境变量 {} 替换为 {}", String.format("#{%s}", key), envMap.get(key));
sqlExp = sqlExp.replaceAll(String.format("#\\{%s}", key), envMap.get(key));
} else {
throw new GlobalException(String.format("id = %d 的环境(用例)表里没有key = %s 的数值,当前环境的变量池 = %s",
envId, key, envMap));
......@@ -221,7 +257,8 @@ public class SqlExpActuator implements Actuator {
// 校验dynamicVar里的detail是否是可以直接执行的SQL
if (dynamicVar.getType() == SQL_VARIABLE && findDynamicVarList(sqlExp).size() == 0) {
// 切换数据源,执行SQL,获取数值
dataSourceService.switchDataSource(dataSourceDTO);
Set<String> dataSources = dataSourceService.switchDataSource(dataSourceDTO);
log.info("当前存在的数据源 {}", dataSources);
return jdbcTemplate.queryForList(sqlExp);
} else {
......@@ -238,19 +275,18 @@ public class SqlExpActuator implements Actuator {
* @param dynamicString SQL字符串或者动态变量
* @return SQL动态变量对象, 分别找出其中的变量名与对应的下标序列值(默认为0)
*/
private List<SqlRegObject> findDynamicVarList(String dynamicString) {
private List<SqlRegularObject> findDynamicVarList(String dynamicString) {
Pattern pattern = Pattern.compile(DYNAMIC_VAR_EXP);
Matcher matcher = pattern.matcher(dynamicString);
List<SqlRegObject> sqlRegObjects = new ArrayList<>();
List<SqlRegularObject> sqlRegObjects = new ArrayList<>();
// 如果使用者写了诸如{$id}[1]的下标,则会使用下标值,否则使用默认值0
while (matcher.find()) {
if (matcher.groupCount() == 1) {
sqlRegObjects.add(new SqlRegObject(matcher.group(0), 0));
} else if (matcher.groupCount() == 2) {
sqlRegObjects.add(new SqlRegObject(matcher.group(0), Integer.parseInt(matcher.group(1))));
} else {
if (matcher.groupCount() != 2) {
throw new GlobalException("给定的动态变量/SQL表达式 不符合语法,给定的值是: " + dynamicString);
}
String varName = matcher.group(1);
int index = StringUtils.isEmpty(matcher.group(2)) ? 0 : Integer.parseInt(matcher.group(2));
sqlRegObjects.add(new SqlRegularObject(varName, index));
}
return sqlRegObjects;
}
......
......@@ -15,7 +15,7 @@ import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SqlRegObject {
public class SqlRegularObject {
/**
* 动态变量名
......
package org.matrix.database.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
......
package org.matrix.database.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.*;
import org.matrix.actuators.datasource.DataSourceDTO;
/**
......@@ -19,6 +14,7 @@ import org.matrix.actuators.datasource.DataSourceDTO;
* @since 2022-01-15
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
......@@ -46,7 +42,7 @@ public class Connect extends BaseEntity {
* @return {@link DataSourceDTO}
*/
public DataSourceDTO toDataSourceDTO() {
return new DataSourceDTO("name", driver, url, username, password);
return new DataSourceDTO(name, driver, url, username, password);
}
......
......@@ -5,10 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.*;
import org.matrix.actuators.sql.SqlExpDetail;
import org.matrix.enums.DynamicVarType;
import org.matrix.exception.GlobalException;
......@@ -22,6 +19,7 @@ import org.matrix.exception.GlobalException;
* @since 2022-01-15
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
......
package org.matrix.database.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import io.swagger.annotations.ApiModel;
......
package org.matrix.database.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import org.matrix.database.entity.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
......
package org.matrix.database.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import org.matrix.database.entity.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
......
package org.matrix.database.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.matrix.database.entity.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.matrix.database.mapper.ActionMapper">
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.matrix.database.mapper.ConnectMapper">
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.matrix.database.mapper.DynamicVariableMapper">
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.matrix.database.mapper.ExampleMapper">
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.matrix.database.mapper.MoveMapper">
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.matrix.database.mapper.ProjectMapper">
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.matrix.database.mapper.TestCaseMapper">
</mapper>
# config mysql database
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=ldf3291369
spring:
application:
name: keyStone
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: true #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master: #增加默认数据源
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: ldf3291369
mybatis-plus:
type-enums-package: org.matrix.enums
\ No newline at end of file
package org.matrix.actuators.sql;
import com.alibaba.fastjson.JSON;
import org.junit.Before;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.runner.RunWith;
import org.matrix.database.entity.Connect;
import org.matrix.database.entity.DynamicVariable;
import org.matrix.database.service.IConnectService;
import org.matrix.database.service.IDynamicVariableService;
import org.matrix.enums.DynamicVarType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* SqlExpActuatorTest.
......@@ -11,10 +20,25 @@ import static org.junit.jupiter.api.Assertions.*;
* @since 2022/1/20 at 3:08 PM
* Suffering is the most powerful teacher of life.
*/
@RunWith(SpringRunner.class)
@SpringBootTest
class SqlExpActuatorTest {
@Autowired
private SqlExpActuator sqlExpActuator;
@Autowired
private IDynamicVariableService variableService;
@Autowired
private IConnectService connectService;
@Test
void parseVarByName() {
String reuslt = sqlExpActuator.parseVarByName("${componentName}[2]", 1L, 1L);
System.out.println(reuslt);
}
@Test
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论