feat(接口测试): 处理body参数的jmx转换

--task=1013377 --user=陈建星 接口测试-接口调试-后台调试执行 https://www.tapd.cn/55049933/s/1447114
This commit is contained in:
AgAngle 2023-12-21 17:27:41 +08:00 committed by Craftsman
parent a587b7bcb5
commit b55ba39b58
43 changed files with 890 additions and 108 deletions

View File

@ -50,7 +50,7 @@ public class DefaultRepositoryDir {
private static final String PROJECT_FILE_MANAGEMENT_PREVIEW_DIR = PROJECT_DIR + "/file-management/preview"; private static final String PROJECT_FILE_MANAGEMENT_PREVIEW_DIR = PROJECT_DIR + "/file-management/preview";
/** /**
* 接口调试相关文件的存储目录 * 接口调试相关文件的存储目录
* project/{projectId}/apiCase/{apiDebugId} * project/{projectId}/api-debug/{apiDebugId}
*/ */
private static final String PROJECT_API_DEBUG_DIR = PROJECT_DIR + "/api-debug/%s"; private static final String PROJECT_API_DEBUG_DIR = PROJECT_DIR + "/api-debug/%s";
private static final String PROJECT_BUG_DIR = PROJECT_DIR + "/bug/%s"; private static final String PROJECT_BUG_DIR = PROJECT_DIR + "/bug/%s";

View File

@ -29,14 +29,29 @@ public class LocalRepositoryDir {
* 插件存储目录 * 插件存储目录
*/ */
private static final String SYSTEM_PLUGIN_DIR = SYSTEM_ROOT_DIR + "/plugin"; private static final String SYSTEM_PLUGIN_DIR = SYSTEM_ROOT_DIR + "/plugin";
private static final String SYSTEM_BODY_ENVIRONMENT_TEM_DIR = SYSTEM_ROOT_DIR + "/body/environment/tmp"; /**
* 系统临时文件的存放目录
* system/temp
* 会定时清理
*/
private static final String SYSTEM_TEMP_DIR = SYSTEM_ROOT_DIR + "/temp";
/**
* 系统缓存文件存放目录
* 目前仅执行机缓存执行文件使用到
* system/cache
*/
private static final String SYSTEM_CACHE_DIR = SYSTEM_ROOT_DIR + "/cache";
/*------ end: 系统下资源目录 --------*/ /*------ end: 系统下资源目录 --------*/
public static String getPluginDir() { public static String getPluginDir() {
return SYSTEM_PLUGIN_DIR; return SYSTEM_PLUGIN_DIR;
} }
public static String getBodyEnvironmentTmpDir() { public static String getSystemTempDir() {
return SYSTEM_BODY_ENVIRONMENT_TEM_DIR; return SYSTEM_TEMP_DIR;
}
public static String getSystemCacheDir() {
return SYSTEM_CACHE_DIR;
} }
} }

View File

@ -1,5 +1,12 @@
package io.metersphere.sdk.constants; package io.metersphere.sdk.constants;
import org.apache.commons.lang3.StringUtils;
public enum StorageType { public enum StorageType {
MINIO, GIT, LOCAL
MINIO, GIT, LOCAL;
public static boolean isGit(String storage) {
return StringUtils.equalsIgnoreCase(GIT.name(), storage);
}
} }

View File

@ -0,0 +1,36 @@
package io.metersphere.sdk.dto.api.task;
import io.metersphere.sdk.dto.FileMetadataRepositoryDTO;
import io.metersphere.sdk.dto.FileModuleRepositoryDTO;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-12-15 16:59
*/
@Data
public class ApiExecuteFileInfo {
private String fileId;
private String fileName;
/**
* 文件存储方式
*/
private String storage;
/**
* 项目ID
*/
private String projectId;
/**
* 资源ID
*/
private String resourceId;
/**
* git文件的版本信息
*/
private FileMetadataRepositoryDTO fileMetadataRepositoryDTO;
/**
* 文件的仓库信息
*/
private FileModuleRepositoryDTO fileModuleRepositoryDTO;
}

View File

@ -28,19 +28,28 @@ public class TaskRequest implements Serializable {
* 执行的资源ID * 执行的资源ID
*/ */
private String testId; private String testId;
/**
* 点击调试时尚未保存的文件ID列表
*/
private List<String> tempFileIds;
/** /**
* 执行模式 * 执行模式
*/ */
private String runMode; private String runMode;
/** /**
* 资源类型 * 资源类型
* ApiResourceType
*/ */
private String resourceType; private String resourceType;
/**
* 点击调试时尚未保存的本地上传的文件列表
*/
private List<ApiExecuteFileInfo> localTempFiles;
/**
* 通过本地上传的文件ID列表
*/
private List<ApiExecuteFileInfo> localFiles;
/**
* 关联文件管理的文件列表
* 这里记录文件名mino存的文件名是id
* 执行时下载文件后按原文件命名
*/
private List<ApiExecuteFileInfo> refFiles;
// TODO 其它执行参数 // TODO 其它执行参数
} }

View File

@ -1,17 +1,22 @@
package io.metersphere.api.dto.debug; package io.metersphere.api.dto.debug;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import java.util.List; import java.util.List;
@Data @Data
public class ApiDebugRunRequest { public class ApiDebugRunRequest {
@Schema(description = "接口ID")
@NotNull
private String id; private String id;
@Schema(description = "环境ID")
private String environmentId; private String environmentId;
@Schema(description = "点击调试时尚未保存的文件ID列表") @Schema(description = "点击调试时尚未保存的文件ID列表")
private List<String> tempFileIds; private List<String> tempFileIds;
@NotNull
@Schema(description = "请求内容")
private String request; private String request;
} }

View File

@ -1,7 +1,5 @@
package io.metersphere.api.dto.debug; package io.metersphere.api.dto.debug;
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
@ -20,13 +18,12 @@ public class ApiDebugUpdateRequest implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Schema(description = "接口pk", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "接口pk", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.id.not_blank}", groups = {Updated.class}) @NotBlank(message = "{api_debug.id.not_blank}")
@Size(min = 1, max = 50, message = "{api_debug.id.length_range}", groups = {Created.class, Updated.class}) @Size(max = 50, message = "{api_debug.id.length_range}")
private String id; private String id;
@Schema(description = "接口名称", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "接口名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.name.not_blank}", groups = {Created.class}) @Size(max = 255, message = "{api_debug.name.length_range}")
@Size(min = 1, max = 255, message = "{api_debug.name.length_range}", groups = {Created.class, Updated.class})
private String name; private String name;
@Schema(description = "http协议类型post/get/其它协议则是协议名(mqtt)") @Schema(description = "http协议类型post/get/其它协议则是协议名(mqtt)")
@ -36,12 +33,10 @@ public class ApiDebugUpdateRequest implements Serializable {
private String path; private String path;
@Schema(description = "模块fk", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "模块fk", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.module_id.not_blank}", groups = {Created.class}) @Size(max = 50, message = "{api_debug.module_id.length_range}")
@Size(min = 1, max = 50, message = "{api_debug.module_id.length_range}", groups = {Created.class, Updated.class})
private String moduleId; private String moduleId;
@Schema(description = "请求内容") @Schema(description = "请求内容")
@NotBlank
private String request; private String request;
/** /**

View File

@ -1,6 +1,5 @@
package io.metersphere.api.dto.debug; package io.metersphere.api.dto.debug;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.util.List; import java.util.List;
@ -8,9 +7,21 @@ import java.util.List;
@Data @Data
public class ApiResourceRunRequest { public class ApiResourceRunRequest {
private String id; private String id;
/**
* 项目ID
*/
private String projectId; private String projectId;
/**
* 资源ID
*/
private String testId; private String testId;
/**
* 测试报告ID
*/
private String reportId; private String reportId;
/**
* 环境ID
*/
private String environmentId; private String environmentId;
/** /**
* 执行模式 * 执行模式
@ -18,9 +29,15 @@ public class ApiResourceRunRequest {
private String runMode; private String runMode;
/** /**
* 资源类型 * 资源类型
* @see io.metersphere.api.constants.ApiResourceType
*/ */
private String resourceType; private String resourceType;
@Schema(description = "点击调试时尚未保存的文件ID列表") /**
private List<String> tempFileIds; * 请求内容
*/
private String request; private String request;
/**
* 点击调试时尚未保存的文件列表
*/
private List<String> tempFileIds;
} }

View File

@ -1,6 +1,7 @@
package io.metersphere.api.dto.request.http; package io.metersphere.api.dto.request.http;
import lombok.Data; import lombok.Data;
import org.apache.commons.lang3.StringUtils;
/** /**
* @Author: jianxing * @Author: jianxing
@ -8,6 +9,10 @@ import lombok.Data;
*/ */
@Data @Data
public class KeyValueParam { public class KeyValueParam {
/**
* 参数ID
*/
private String id;
/** /**
* *
*/ */
@ -16,4 +21,8 @@ public class KeyValueParam {
* *
*/ */
private String value; private String value;
public boolean isValid() {
return StringUtils.isNotBlank(key);
}
} }

View File

@ -8,7 +8,6 @@ import lombok.Data;
*/ */
@Data @Data
public class BinaryBody { public class BinaryBody {
private String fileId; private BodyFile bodyFile;
private String fileName;
private String description; private String description;
} }

View File

@ -2,6 +2,9 @@ package io.metersphere.api.dto.request.http.body;
import lombok.Data; import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/** /**
* @Author: jianxing * @Author: jianxing
* @CreateTime: 2023-11-06 16:59 * @CreateTime: 2023-11-06 16:59
@ -22,6 +25,35 @@ public class Body {
private RawBody rawBody; private RawBody rawBody;
private BinaryBody binaryBody; private BinaryBody binaryBody;
private static Map<BodyType, Class> bodyTypeClassMap = new HashMap<>();
static {
bodyTypeClassMap.put(BodyType.NONE, NoneBody.class);
bodyTypeClassMap.put(BodyType.FORM_DATA, FormDataBody.class);
bodyTypeClassMap.put(BodyType.WWW_FORM, WWWFormBody.class);
bodyTypeClassMap.put(BodyType.JSON, JsonBody.class);
bodyTypeClassMap.put(BodyType.XML, XmlBody.class);
bodyTypeClassMap.put(BodyType.RAW, RawBody.class);
bodyTypeClassMap.put(BodyType.BINARY, BinaryBody.class);
}
public Class getBodyClassByType() {
return bodyTypeClassMap.get(BodyType.valueOf(bodyType));
}
public Object getBodyDataByType() {
Map<BodyType, Object> boadyDataMap = new HashMap<>();
boadyDataMap.put(BodyType.NONE, noneBody);
boadyDataMap.put(BodyType.FORM_DATA, formDataBody);
boadyDataMap.put(BodyType.WWW_FORM, wwwFormBody);
boadyDataMap.put(BodyType.JSON, jsonBody);
boadyDataMap.put(BodyType.XML, xmlBody);
boadyDataMap.put(BodyType.RAW, rawBody);
boadyDataMap.put(BodyType.BINARY, binaryBody);
return boadyDataMap.get(BodyType.valueOf(bodyType));
}
public enum BodyType { public enum BodyType {
BINARY, BINARY,
FORM_DATA, FORM_DATA,

View File

@ -0,0 +1,16 @@
package io.metersphere.api.dto.request.http.body;
import lombok.Data;
@Data
public class BodyFile {
/**
* 记录文件的ID防止重名
* 生成脚本时通过 fileId + value(文件名) 获取文件路径
*/
private String fileId;
/**
* 文件名称
*/
private String fileName;
}

View File

@ -1,28 +1,20 @@
package io.metersphere.api.dto.request.http.body; package io.metersphere.api.dto.request.http.body;
import io.metersphere.api.dto.request.http.KeyValueEnableParam;
import lombok.Data; import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
/** /**
* @Author: jianxing * @Author: jianxing
* @CreateTime: 2023-11-06 18:11 * @CreateTime: 2023-11-06 18:11
*/ */
@Data @Data
public class FormDataKV extends KeyValueEnableParam { public class FormDataKV extends WWWFormKV {
private String paramType;
private Boolean required = false; private List<BodyFile> files;
private Integer minLength;
private Integer maxLength; public boolean isFile() {
private String contentType; return StringUtils.equalsIgnoreCase(getParamType(), WWWFormParamType.FILE.name());
private Boolean encode = false; }
/**
* 记录文件的ID防止重名
* 生成脚本时通过 fileId + value(文件名) 获取文件路径
*/
private String fileId;
/**
* 文件存储方式
* 对象存储和引用(FILE_REF)
*/
private String storage;
} }

View File

@ -11,7 +11,7 @@ public class JsonBody {
/** /**
* 是否启用 json-schema * 是否启用 json-schema
*/ */
private Boolean enableJsonSchema; private Boolean enableJsonSchema = false;
/** /**
* 没有启用 json-schema 时的 json 参数值 * 没有启用 json-schema 时的 json 参数值
*/ */

View File

@ -0,0 +1,38 @@
package io.metersphere.api.dto.request.http.body;
import io.metersphere.api.dto.request.http.KeyValueEnableParam;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 18:11
*/
@Data
public class WWWFormKV extends KeyValueEnableParam {
/**
* 参数类型
*
* @see WWWFormParamType
*/
private String paramType;
private Boolean required = false;
private Integer minLength;
private Integer maxLength;
private String contentType;
private Boolean encode = false;
enum WWWFormParamType {
/**
* 默认 application/text
*/
STRING, INTEGER, NUMBER, ARRAY,
/**
* 默认 application/octet-stream
*/
FILE,
/**
* 默认 application/json
*/
JSON
}
}

View File

@ -28,7 +28,6 @@ public class JmeterTestElementParser implements TestElementParser {
private Boolean onSampleError; private Boolean onSampleError;
private String name; private String name;
private Boolean enable = true;
private ParameterConfig config; private ParameterConfig config;
private boolean displayJMeterProperties = false; private boolean displayJMeterProperties = false;
@ -36,13 +35,18 @@ public class JmeterTestElementParser implements TestElementParser {
private boolean displaySystemProperties = false; private boolean displaySystemProperties = false;
/**
* 解析生成 jmx 脚本
* @param msTestElement
* @param config
* @return
*/
@Override @Override
public String parse(AbstractMsTestElement msTestElement, ParameterConfig config) { public String parse(AbstractMsTestElement msTestElement, ParameterConfig config) {
this.config = config; this.config = config;
HashTree hashTree = new ListedHashTree(); HashTree hashTree = new ListedHashTree();
TestPlan testPlan = getPlan(); TestPlan testPlan = getPlan();
name = msTestElement.getName(); name = msTestElement.getName();
enable = msTestElement.getEnable();
final HashTree testPlanTree = hashTree.add(testPlan); final HashTree testPlanTree = hashTree.add(testPlan);
final HashTree groupTree = testPlanTree.add(getThreadGroup()); final HashTree groupTree = testPlanTree.add(getThreadGroup());
// 添加 debugSampler // 添加 debugSampler
@ -82,11 +86,11 @@ public class JmeterTestElementParser implements TestElementParser {
loopController.setName("LoopController"); loopController.setName("LoopController");
loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName()); loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
loopController.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("LoopControlPanel")); loopController.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("LoopControlPanel"));
loopController.setEnabled(this.enable); loopController.setEnabled(true);
loopController.setLoops(1); loopController.setLoops(1);
ThreadGroup threadGroup = new ThreadGroup(); ThreadGroup threadGroup = new ThreadGroup();
threadGroup.setEnabled(this.enable); threadGroup.setEnabled(true);
threadGroup.setName(config.getReportId()); threadGroup.setName(config.getReportId());
threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName()); threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName());
threadGroup.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ThreadGroupGui")); threadGroup.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ThreadGroupGui"));

View File

@ -2,31 +2,68 @@ package io.metersphere.api.parser.jmeter;
import io.metersphere.api.dto.request.http.MsHTTPElement; import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.plugin.api.spi.AbstractJmeterElementConverter; import io.metersphere.api.dto.request.http.body.Body;
import io.metersphere.api.parser.jmeter.body.MsBodyConverter;
import io.metersphere.api.parser.jmeter.body.MsBodyConverterFactory;
import io.metersphere.api.parser.jmeter.body.MsFormDataBodyConverter;
import io.metersphere.api.parser.jmeter.body.MsWWWFormBodyConverter;
import io.metersphere.plugin.api.dto.ParameterConfig; import io.metersphere.plugin.api.dto.ParameterConfig;
import io.metersphere.plugin.api.spi.AbstractJmeterElementConverter;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import org.apache.jmeter.save.SaveService; import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement; import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree; import org.apache.jorphan.collections.HashTree;
import org.springframework.http.HttpMethod;
/** /**
* @Author: jianxing * @Author: jianxing
* @CreateTime: 2023-10-27 10:07 * @CreateTime: 2023-10-27 10:07
* * <p>
* 脚本解析器 * 脚本解析器
*/ */
public class MsHTTPElementConverter extends AbstractJmeterElementConverter<MsHTTPElement> { public class MsHTTPElementConverter extends AbstractJmeterElementConverter<MsHTTPElement> {
@Override private ParameterConfig config;
public void toHashTree(HashTree tree, MsHTTPElement msHTTPElement, ParameterConfig msParameter) {
ParameterConfig config = msParameter;
@Override
public void toHashTree(HashTree tree, MsHTTPElement msHTTPElement, ParameterConfig config) {
this.config = config;
HTTPSamplerProxy sampler = new HTTPSamplerProxy(); HTTPSamplerProxy sampler = new HTTPSamplerProxy();
sampler.setName(msHTTPElement.getName()); sampler.setName(msHTTPElement.getName());
sampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName()); sampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName());
sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("HttpTestSampleGui")); sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("HttpTestSampleGui"));
sampler.setMethod(msHTTPElement.getMethod());
// todo 根据环境设置
sampler.setDomain(msHTTPElement.getUrl());
sampler.setPath(msHTTPElement.getPath());
handleBody(sampler, msHTTPElement);
HashTree httpTree = tree.add(sampler); HashTree httpTree = tree.add(sampler);
parseChild(httpTree, msHTTPElement, config); parseChild(httpTree, msHTTPElement, config);
} }
/**
* 解析body参数
*
* @param sampler
* @param msHTTPElement
*/
private void handleBody(HTTPSamplerProxy sampler, MsHTTPElement msHTTPElement) {
Body body = msHTTPElement.getBody();
// 请求体处理
if (body != null) {
Class bodyClass = body.getBodyClassByType();
MsBodyConverter converter = MsBodyConverterFactory.getConverter(bodyClass);
// 这里get请求不处理 form-date www-form-urlencoded 类型的参数
// 否则会被 jmeter 作为 query 参数
if (StringUtils.equalsIgnoreCase(msHTTPElement.getMethod(), HttpMethod.GET.name())
&& (converter instanceof MsWWWFormBodyConverter || converter instanceof MsFormDataBodyConverter)) {
return;
}
converter.parse(sampler, body.getBodyDataByType(), config);
}
}
} }

View File

@ -0,0 +1,21 @@
package io.metersphere.api.parser.jmeter.body;
import io.metersphere.api.dto.request.http.body.BinaryBody;
import io.metersphere.api.dto.request.http.body.BodyFile;
import io.metersphere.plugin.api.dto.ParameterConfig;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import org.apache.jmeter.protocol.http.util.HTTPFileArg;
/**
* 处理 Binary 参数
* @Author: jianxing
* @CreateTime: 2023-12-14 19:55
*/
public class MsBinaryBodyConverter extends MsBodyConverter<BinaryBody> {
@Override
public void parse(HTTPSamplerProxy sampler, BinaryBody body, ParameterConfig config) {
BodyFile bodyFile = body.getBodyFile();
HTTPFileArg httpFileArg = getHttpFileArg(bodyFile);
sampler.setHTTPFiles(new HTTPFileArg[]{httpFileArg});
}
}

View File

@ -0,0 +1,111 @@
package io.metersphere.api.parser.jmeter.body;
import io.metersphere.api.dto.request.http.body.BodyFile;
import io.metersphere.api.dto.request.http.body.WWWFormKV;
import io.metersphere.jmeter.mock.Mock;
import io.metersphere.plugin.api.dto.ParameterConfig;
import io.metersphere.sdk.constants.LocalRepositoryDir;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.ContentType;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import org.apache.jmeter.protocol.http.util.HTTPArgument;
import org.apache.jmeter.protocol.http.util.HTTPFileArg;
import java.io.File;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @Author: jianxing
* @CreateTime: 2023-10-27 10:07
* <p>
* body 解析器
*/
public abstract class MsBodyConverter<T> {
/**
* 解析对应的请求体参数
* @param sampler
* @param body
* @param config
*/
public abstract void parse(HTTPSamplerProxy sampler, T body, ParameterConfig config);
/**
* 解析文本类型的 kv 参数
* @param textFromValues
* @return
*/
protected Arguments getArguments(List<? extends WWWFormKV> textFromValues) {
Arguments arguments = new Arguments();
textFromValues.forEach(formDataKV -> {
// 处理 mock 函数
String value = Mock.buildFunctionCallString(formDataKV.getValue());
if (value == null) {
value = StringUtils.EMPTY;
}
HTTPArgument httpArgument = new HTTPArgument(formDataKV.getKey(), value);
httpArgument.setAlwaysEncoded(formDataKV.getEncode());
if (StringUtils.isNotBlank(formDataKV.getContentType())) {
httpArgument.setContentType(formDataKV.getContentType());
}
arguments.addArgument(httpArgument);
});
return arguments;
}
/**
* form-data binary 类型的文件转换为 jmeter HTTPFileArg
*
* @param file
* @return
*/
protected HTTPFileArg getHttpFileArg(BodyFile file) {
String fileId = file.getFileId();
String fileName = file.getFileName();
// 在对应目录下创建文件ID目录将文件放入
String path = LocalRepositoryDir.getSystemCacheDir() + '/' + fileId + '/' + fileName;
if (!StringUtils.equals(File.separator, "/")) {
// windows 系统下运行 / 转换为 \否则jmeter报错
path = path.replace("/", File.separator);
}
String mimetype = ContentType.APPLICATION_OCTET_STREAM.getMimeType();
HTTPFileArg fileArg = new HTTPFileArg(path, StringUtils.EMPTY, mimetype);
return fileArg;
}
/**
* 将文本中的 @xxx 转换成 ${__Mock(@xxx)}
* @param text
* @return
*/
protected String parseTextMock(String text) {
String pattern = "@[a-zA-Z]*";
Pattern regex = Pattern.compile(pattern);
Matcher matcher = regex.matcher(text);
while (matcher.find()) {
text = text.replace(matcher.group(), "${__Mock(" + matcher.group() + ")}");
}
return text;
}
/**
* 处理raw格式参数
* 包含了 json 等格式
* @param sampler
* @param raw
*/
protected static void handleRowBody(HTTPSamplerProxy sampler, String raw) {
Arguments arguments = new Arguments();
HTTPArgument httpArgument = new HTTPArgument();
httpArgument.setValue(raw);
httpArgument.setAlwaysEncoded(false);
httpArgument.setEnabled(true);
arguments.addArgument(httpArgument);
sampler.setPostBodyRaw(true);
sampler.setArguments(arguments);
}
}

View File

@ -0,0 +1,29 @@
package io.metersphere.api.parser.jmeter.body;
import io.metersphere.api.dto.request.http.body.*;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: jianxing
* @CreateTime: 2023-12-21 19:19
*/
public class MsBodyConverterFactory {
private static Map<Class, MsBodyConverter> converterMap = new HashMap<>();
static {
converterMap.put(RawBody.class, new MsRawBodyConverter());
converterMap.put(NoneBody.class, new MsNoneBodyConverter());
converterMap.put(FormDataBody.class, new MsFormDataBodyConverter());
converterMap.put(WWWFormBody.class, new MsWWWFormBodyConverter());
converterMap.put(JsonBody.class, new MsJsonBodyConverter());
converterMap.put(XmlBody.class, new MsXmlBodyConverter());
converterMap.put(BinaryBody.class, new MsBinaryBodyConverter());
}
public static MsBodyConverter getConverter(Class bodyClassByType) {
return converterMap.get(bodyClassByType);
}
}

View File

@ -0,0 +1,60 @@
package io.metersphere.api.parser.jmeter.body;
import io.metersphere.api.dto.request.http.body.FormDataBody;
import io.metersphere.api.dto.request.http.body.FormDataKV;
import io.metersphere.plugin.api.dto.ParameterConfig;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import org.apache.jmeter.protocol.http.util.HTTPFileArg;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 处理 form-data 类型的请求体
* @Author: jianxing
* @CreateTime: 2023-12-14 15:18
*/
public class MsFormDataBodyConverter extends MsBodyConverter<FormDataBody> {
@Override
public void parse(HTTPSamplerProxy sampler, FormDataBody body, ParameterConfig config) {
List<FormDataKV> fromValues = body.getFromValues();
List<FormDataKV> validFromValues = fromValues.stream().filter(FormDataKV::isValid).collect(Collectors.toList());
List<FormDataKV> fileFromValues = validFromValues.stream().filter(FormDataKV::isFile).collect(Collectors.toList());
List<FormDataKV> textFromValues = validFromValues.stream().filter(kv -> !kv.isFile()).collect(Collectors.toList());
sampler.setDoMultipart(true);
sampler.setHTTPFiles(getHttpFileArg(fileFromValues));
sampler.setArguments(getArguments(textFromValues));
}
/**
* 解析文件类型的参数
* @param fileFromValues
* @return
*/
private HTTPFileArg[] getHttpFileArg(List<FormDataKV> fileFromValues) {
if (CollectionUtils.isEmpty(fileFromValues)) {
return new HTTPFileArg[0];
}
List<HTTPFileArg> list = new ArrayList<>();
if (fileFromValues != null) {
fileFromValues.forEach(formDataKV -> {
String paramName = formDataKV.getKey();
formDataKV.getFiles().forEach(file -> {
HTTPFileArg fileArg = getHttpFileArg(file);
fileArg.setParamName(paramName);
String mimetype = formDataKV.getContentType();
if (StringUtils.isBlank(mimetype)) {
fileArg.setMimeType(mimetype);
}
list.add(fileArg);
});
});
}
return list.toArray(new HTTPFileArg[0]);
}
}

View File

@ -0,0 +1,100 @@
package io.metersphere.api.parser.jmeter.body;
import io.metersphere.api.dto.request.http.body.JsonBody;
import io.metersphere.jmeter.mock.Mock;
import io.metersphere.plugin.api.dto.ParameterConfig;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author: jianxing
* @CreateTime: 2023-12-14 21:15
*/
public class MsJsonBodyConverter extends MsBodyConverter<JsonBody> {
@Override
public void parse(HTTPSamplerProxy sampler, JsonBody body, ParameterConfig config) {
sampler.setPostBodyRaw(true);
try {
String raw = null;
if (body.getEnableJsonSchema()) {
// todo jsonSchema
// JSONSchemaBuilder.generator(JSONUtil.toJSONString(this.getJsonSchema()))
// raw = StringEscapeUtils.unescapeJava();
} else {
raw = parseJsonMock(body.getJsonValue());
}
handleRowBody(sampler, raw);
} catch (Exception e) {
LogUtils.error("json mock value is abnormal", e);
}
}
/**
* 将json中的 @xxx 转换成 ${__Mock(@xxx)}
* @param jsonStr
* @return
*/
private String parseJsonMock(String jsonStr) {
if (StringUtils.isNotEmpty(jsonStr)) {
String value = StringUtils.chomp(jsonStr.trim());
try {
if (StringUtils.startsWith(value, "[") && StringUtils.endsWith(value, "]")) {
List list = JSON.parseArray(jsonStr);
parseMock(list);
return JSON.toJSONString(list);
} else {
Map<String, Object> map = JSON.parseObject(jsonStr, Map.class);
parseMock(map);
return JSON.toJSONString(map);
}
} catch (Exception e) {
// 如果json串中的格式不是标准的json格式正则替换变量
return parseTextMock(jsonStr);
}
}
return jsonStr;
}
private void parseMock(List list) {
Map<Integer, String> replaceDataMap = new HashMap<>();
for (int index = 0; index < list.size(); index++) {
Object obj = list.get(index);
if (obj instanceof Map) {
parseMock((Map) obj);
} else if (obj instanceof String) {
if (StringUtils.isNotBlank((String) obj)) {
String str = Mock.buildFunctionCallString((String) obj);
replaceDataMap.put(index, str);
}
}
}
for (Map.Entry<Integer, String> entry : replaceDataMap.entrySet()) {
int replaceIndex = entry.getKey();
String replaceStr = entry.getValue();
list.set(replaceIndex, replaceStr);
}
}
private void parseMock(Map map) {
for (Object key : map.keySet()) {
Object value = map.get(key);
if (value instanceof List) {
parseMock((List) value);
} else if (value instanceof Map) {
parseMock((Map) value);
} else if (value instanceof String) {
if (StringUtils.isNotBlank((String) value)) {
value = Mock.buildFunctionCallString((String) value);
}
map.put(key, value);
}
}
}
}

View File

@ -0,0 +1,16 @@
package io.metersphere.api.parser.jmeter.body;
import io.metersphere.api.dto.request.http.body.NoneBody;
import io.metersphere.plugin.api.dto.ParameterConfig;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
/**
* @Author: jianxing
* @CreateTime: 2023-12-14 21:15
*/
public class MsNoneBodyConverter extends MsBodyConverter<NoneBody> {
@Override
public void parse(HTTPSamplerProxy sampler, NoneBody body, ParameterConfig config) {
// do nothing
}
}

View File

@ -0,0 +1,17 @@
package io.metersphere.api.parser.jmeter.body;
import io.metersphere.api.dto.request.http.body.RawBody;
import io.metersphere.plugin.api.dto.ParameterConfig;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
/**
* 处理 Raw 格式参数
* @Author: jianxing
* @CreateTime: 2023-12-14 21:15
*/
public class MsRawBodyConverter extends MsBodyConverter<RawBody> {
@Override
public void parse(HTTPSamplerProxy sampler, RawBody body, ParameterConfig config) {
handleRowBody(sampler, body.getValue());
}
}

View File

@ -0,0 +1,22 @@
package io.metersphere.api.parser.jmeter.body;
import io.metersphere.api.dto.request.http.body.FormDataKV;
import io.metersphere.api.dto.request.http.body.WWWFormBody;
import io.metersphere.plugin.api.dto.ParameterConfig;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author: jianxing
* @CreateTime: 2023-12-14 20:34
*/
public class MsWWWFormBodyConverter extends MsBodyConverter<WWWFormBody> {
@Override
public void parse(HTTPSamplerProxy sampler, WWWFormBody body, ParameterConfig config) {
List<FormDataKV> fromValues = body.getFromValues();
List<FormDataKV> validFromValues = fromValues.stream().filter(FormDataKV::isValid).collect(Collectors.toList());
sampler.setArguments(getArguments(validFromValues));
}
}

View File

@ -0,0 +1,16 @@
package io.metersphere.api.parser.jmeter.body;
import io.metersphere.api.dto.request.http.body.XmlBody;
import io.metersphere.plugin.api.dto.ParameterConfig;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
/**
* @Author: jianxing
* @CreateTime: 2023-12-14 21:15
*/
public class MsXmlBodyConverter extends MsBodyConverter<XmlBody> {
@Override
public void parse(HTTPSamplerProxy sampler, XmlBody body, ParameterConfig config) {
handleRowBody(sampler, body.getValue());
}
}

View File

@ -7,11 +7,16 @@ import io.metersphere.api.dto.debug.ApiResourceRunRequest;
import io.metersphere.api.parser.TestElementParser; import io.metersphere.api.parser.TestElementParser;
import io.metersphere.api.parser.TestElementParserFactory; import io.metersphere.api.parser.TestElementParserFactory;
import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.plugin.api.dto.ParameterConfig; import io.metersphere.plugin.api.dto.ParameterConfig;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.domain.ProjectApplication; import io.metersphere.project.domain.ProjectApplication;
import io.metersphere.project.service.FileAssociationService;
import io.metersphere.project.service.FileManagementService;
import io.metersphere.project.service.FileMetadataService;
import io.metersphere.project.service.ProjectApplicationService; import io.metersphere.project.service.ProjectApplicationService;
import io.metersphere.sdk.constants.ProjectApplicationType; import io.metersphere.sdk.constants.ProjectApplicationType;
import io.metersphere.sdk.constants.StorageType;
import io.metersphere.sdk.dto.api.task.ApiExecuteFileInfo;
import io.metersphere.sdk.dto.api.task.TaskRequest; import io.metersphere.sdk.dto.api.task.TaskRequest;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.*; import io.metersphere.sdk.util.*;
@ -25,6 +30,7 @@ import io.metersphere.system.service.TestResourcePoolService;
import io.metersphere.system.utils.TaskRunnerClient; import io.metersphere.system.utils.TaskRunnerClient;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.util.JMeterUtils;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
@ -37,6 +43,8 @@ import java.security.SecureRandom;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static io.metersphere.api.controller.result.ApiResultCode.RESOURCE_POOL_EXECUTE_ERROR; import static io.metersphere.api.controller.result.ApiResultCode.RESOURCE_POOL_EXECUTE_ERROR;
@ -59,6 +67,15 @@ public class ApiExecuteService {
private StringRedisTemplate stringRedisTemplate; private StringRedisTemplate stringRedisTemplate;
@Resource @Resource
private JmeterProperties jmeterProperties; private JmeterProperties jmeterProperties;
@Resource
private ApiFileResourceService apiFileResourceService;
@Resource
private FileAssociationService fileAssociationService;
@Resource
private FileMetadataService fileMetadataService;
@Resource
private FileManagementService fileManagementService;
@PostConstruct @PostConstruct
private void init() { private void init() {
@ -89,24 +106,24 @@ public class ApiExecuteService {
public void debug(ApiResourceRunRequest request) { public void debug(ApiResourceRunRequest request) {
TestResourceDTO resourcePoolDTO = getAvailableResourcePoolDTO(request.getProjectId()); TestResourceDTO resourcePoolDTO = getAvailableResourcePoolDTO(request.getProjectId());
String reportId = request.getReportId(); String reportId = request.getReportId();
String testId = request.getTestId(); String testId = request.getTestId();
TaskRequest taskRequest = new TaskRequest(); TaskRequest taskRequest = new TaskRequest();
BeanUtils.copyBean(taskRequest, request); BeanUtils.copyBean(taskRequest, request);
String msUrl = systemParameterService.getBaseInfo().getUrl();
taskRequest.setKafkaConfig(EncryptUtils.aesEncrypt(JSON.toJSONString(KafkaConfig.getKafkaConfig()))); taskRequest.setKafkaConfig(EncryptUtils.aesEncrypt(JSON.toJSONString(KafkaConfig.getKafkaConfig())));
taskRequest.setMinioConfig(EncryptUtils.aesEncrypt(JSON.toJSONString(getMinio()))); taskRequest.setMinioConfig(EncryptUtils.aesEncrypt(JSON.toJSONString(getMinio())));
taskRequest.setMsUrl(msUrl); taskRequest.setMsUrl(systemParameterService.getBaseInfo().getUrl());
taskRequest.setReportId(reportId);
taskRequest.setRealTime(true); taskRequest.setRealTime(true);
// todo 环境配置 // 设置执行文件参数
// EnvironmentRequest environmentRequest = environmentService.get(request.getEnvironmentId()); setTaskFileParam(request, taskRequest);
// todo 误报
// todo 获取接口插件和jar包
// todo 处理公共脚本
// todo 环境配置
// EnvironmentInfoDTO environmentInfoDTO = environmentService.get(request.getEnvironmentId());
// todo 误报
// todo 获取接口插件和jar包
// todo 处理公共脚本
// todo 接口用例 method 获取定义中的数据库字段
ParameterConfig parameterConfig = new ParameterConfig(); ParameterConfig parameterConfig = new ParameterConfig();
parameterConfig.setReportId(reportId); parameterConfig.setReportId(reportId);
String executeScript = parseExecuteScript(request.getRequest(), parameterConfig); String executeScript = parseExecuteScript(request.getRequest(), parameterConfig);
@ -130,8 +147,94 @@ public class ApiExecuteService {
} }
} }
/**
* taskRequest 设置文件相关参数
*
* @param request
* @param taskRequest
*/
private void setTaskFileParam(ApiResourceRunRequest request, TaskRequest taskRequest) {
// 查询通过本地上传的文件
List<ApiExecuteFileInfo> localFiles = apiFileResourceService.getByResourceId(request.getId()).
stream()
.map(file -> {
ApiExecuteFileInfo apiExecuteFileInfo = getApiExecuteFileInfo(file.getFileId(), file.getFileName(), file.getProjectId());
// 本地上传的文件需要 resourceId 查询对应的目录
apiExecuteFileInfo.setResourceId(request.getId());
return apiExecuteFileInfo;
})
.collect(Collectors.toList());
taskRequest.setLocalFiles(localFiles);
// 查询关联的文件管理的文件
List<ApiExecuteFileInfo> refFiles = fileAssociationService.getFiles(request.getId()).
stream()
.map(file -> {
ApiExecuteFileInfo refFileInfo = getApiExecuteFileInfo(file.getFileId(), file.getFileName(),
file.getProjectId(), file.getStorage());
if (StorageType.isGit(file.getStorage())) {
// 设置Git信息
refFileInfo.setFileMetadataRepositoryDTO(fileManagementService.getFileMetadataRepositoryDTO(file.getMetadataId()));
refFileInfo.setFileModuleRepositoryDTO(fileManagementService.getFileModuleRepositoryDTO(file.getModuleId()));
}
return refFileInfo;
}).collect(Collectors.toList());
// 处理没有保存的临时文件
List<String> tempFileIds = request.getTempFileIds();
if (CollectionUtils.isNotEmpty(tempFileIds)) {
// 查询这些文件有哪些是关联文件管理的文件
List<ApiExecuteFileInfo> refTempFiles = fileMetadataService.getByFileIds(tempFileIds)
.stream()
.map(file -> {
String fileName = file.getName();
if (StringUtils.isNotBlank(file.getType())) {
fileName += "." + file.getType();
}
ApiExecuteFileInfo tempFileInfo = getApiExecuteFileInfo(file.getId(), fileName,
file.getProjectId(), file.getStorage());
if (StorageType.isGit(file.getStorage())) {
// 设置Git信息
tempFileInfo.setFileMetadataRepositoryDTO(fileManagementService.getFileMetadataRepositoryDTO(file.getId()));
tempFileInfo.setFileModuleRepositoryDTO(fileManagementService.getFileModuleRepositoryDTO(file.getModuleId()));
}
return tempFileInfo;
}).collect(Collectors.toList());
// 添加临时的文件管理的文件
refFiles.addAll(refTempFiles);
Set<String> refTempFileIds = refTempFiles.stream().map(ApiExecuteFileInfo::getFileId).collect(Collectors.toSet());
// 去掉文件管理的文件即通过本地上传的临时文件
List<ApiExecuteFileInfo> localTempFiles = tempFileIds.stream()
.filter(tempFileId -> !refTempFileIds.contains(tempFileId))
.map(tempFileId -> {
String fileName = apiFileResourceService.getTempFileNameByFileId(tempFileId);
ApiExecuteFileInfo apiExecuteFileInfo = getApiExecuteFileInfo(tempFileId, fileName, request.getProjectId());
return apiExecuteFileInfo;
})
.collect(Collectors.toList());
taskRequest.setLocalTempFiles(localTempFiles);
}
taskRequest.setRefFiles(refFiles);
}
private static ApiExecuteFileInfo getApiExecuteFileInfo(String fileId, String fileName, String projectId) {
return getApiExecuteFileInfo(fileId, fileName, projectId, StorageType.MINIO.name());
}
private static ApiExecuteFileInfo getApiExecuteFileInfo(String fileId, String fileName, String projectId, String storage) {
ApiExecuteFileInfo apiExecuteFileInfo = new ApiExecuteFileInfo();
apiExecuteFileInfo.setStorage(storage);
apiExecuteFileInfo.setFileName(fileName);
apiExecuteFileInfo.setFileId(fileId);
apiExecuteFileInfo.setProjectId(projectId);
return apiExecuteFileInfo;
}
/** /**
* 生成执行脚本 * 生成执行脚本
*
* @param testElementStr * @param testElementStr
* @param msParameter * @param msParameter
* @return * @return
@ -155,6 +258,7 @@ public class ApiExecuteService {
/** /**
* 获取当前项目配置的接口默认资源池 * 获取当前项目配置的接口默认资源池
*
* @param projectId * @param projectId
* @param * @param
*/ */

View File

@ -113,10 +113,12 @@ public class ApiDebugService {
apiDebugMapper.updateByPrimaryKeySelective(apiDebug); apiDebugMapper.updateByPrimaryKeySelective(apiDebug);
// todo 校验 moduleId // todo 校验 moduleId
ApiDebugBlob apiDebugBlob = new ApiDebugBlob(); if (StringUtils.isNotBlank(request.getRequest())) {
apiDebugBlob.setId(request.getId()); ApiDebugBlob apiDebugBlob = new ApiDebugBlob();
apiDebugBlob.setRequest(request.getRequest().getBytes()); apiDebugBlob.setId(request.getId());
apiDebugBlobMapper.updateByPrimaryKeySelective(apiDebugBlob); apiDebugBlob.setRequest(request.getRequest().getBytes());
apiDebugBlobMapper.updateByPrimaryKeySelective(apiDebugBlob);
}
ApiFileResourceUpdateRequest resourceUpdateRequest = getApiFileResourceUpdateRequest(originApiDebug.getId(), originApiDebug.getProjectId(), updateUser); ApiFileResourceUpdateRequest resourceUpdateRequest = getApiFileResourceUpdateRequest(originApiDebug.getId(), originApiDebug.getProjectId(), updateUser);
resourceUpdateRequest.setUploadFileIds(request.getUploadFileIds()); resourceUpdateRequest.setUploadFileIds(request.getUploadFileIds());
@ -159,6 +161,7 @@ public class ApiDebugService {
throw new MSException(API_DEBUG_EXIST); throw new MSException(API_DEBUG_EXIST);
} }
} }
private ApiDebug checkResourceExist(String id) { private ApiDebug checkResourceExist(String id) {
return ServiceUtils.checkResourceExist(apiDebugMapper.selectByPrimaryKey(id), "permission.system_api_debug.name"); return ServiceUtils.checkResourceExist(apiDebugMapper.selectByPrimaryKey(id), "permission.system_api_debug.name");
} }

View File

@ -7,6 +7,7 @@ import io.metersphere.api.domain.ApiFileResource;
import io.metersphere.api.dto.debug.*; import io.metersphere.api.dto.debug.*;
import io.metersphere.api.dto.request.MsScenario; import io.metersphere.api.dto.request.MsScenario;
import io.metersphere.api.dto.request.http.MsHTTPElement; import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.dto.request.http.body.Body;
import io.metersphere.api.mapper.ApiDebugBlobMapper; import io.metersphere.api.mapper.ApiDebugBlobMapper;
import io.metersphere.api.mapper.ApiDebugMapper; import io.metersphere.api.mapper.ApiDebugMapper;
import io.metersphere.api.parser.ImportParserFactory; import io.metersphere.api.parser.ImportParserFactory;
@ -29,9 +30,10 @@ import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.ProjectApplicationType; import io.metersphere.sdk.constants.ProjectApplicationType;
import io.metersphere.sdk.constants.ResourcePoolTypeEnum; import io.metersphere.sdk.constants.ResourcePoolTypeEnum;
import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.CommonBeanFactory; import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.FileAssociationSourceUtil;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest; import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder; import io.metersphere.system.controller.handler.ResultHolder;
@ -42,8 +44,6 @@ import io.metersphere.system.domain.TestResourcePoolOrganizationExample;
import io.metersphere.system.dto.pool.TestResourceDTO; import io.metersphere.system.dto.pool.TestResourceDTO;
import io.metersphere.system.dto.pool.TestResourceNodeDTO; import io.metersphere.system.dto.pool.TestResourceNodeDTO;
import io.metersphere.system.dto.pool.TestResourcePoolDTO; import io.metersphere.system.dto.pool.TestResourcePoolDTO;
import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.mapper.TestResourcePoolBlobMapper; import io.metersphere.system.mapper.TestResourcePoolBlobMapper;
import io.metersphere.system.mapper.TestResourcePoolMapper; import io.metersphere.system.mapper.TestResourcePoolMapper;
@ -339,7 +339,7 @@ public class ApiDebugControllerTests extends BaseTest {
*/ */
private static void assertLinkFile(String id, List<String> fileIds) { private static void assertLinkFile(String id, List<String> fileIds) {
FileAssociationService fileAssociationService = CommonBeanFactory.getBean(FileAssociationService.class); FileAssociationService fileAssociationService = CommonBeanFactory.getBean(FileAssociationService.class);
List<String> linkFileIds = fileAssociationService.getFiles(id, FileAssociationSourceUtil.SOURCE_TYPE_API_DEBUG) List<String> linkFileIds = fileAssociationService.getFiles(id)
.stream() .stream()
.map(FileInfo::getFileId) .map(FileInfo::getFileId)
.toList(); .toList();
@ -408,6 +408,42 @@ public class ApiDebugControllerTests extends BaseTest {
// @@请求成功 // @@请求成功
this.requestPostWithOk(DEBUG, request); this.requestPostWithOk(DEBUG, request);
// 测试 FORM_DATA
MockMultipartFile file = getMockMultipartFile();
String fileId = doUploadTempFile(file);
request.setTempFileIds(List.of(fileId, fileMetadataId));
msHTTPElement = MsHTTPElementTest.getMsHttpElement();
Body generalBody = MsHTTPElementTest.getGeneralBody();
generalBody.setBodyType(Body.BodyType.FORM_DATA.name());
msHTTPElement.setBody(generalBody);
request.setRequest(ApiDataUtils.toJSONString(msHTTPElement));
this.requestPostWithOk(DEBUG, request);
// 测试 WWW_FORM
generalBody.setBodyType(Body.BodyType.WWW_FORM.name());
request.setRequest(ApiDataUtils.toJSONString(msHTTPElement));
this.requestPostWithOk(DEBUG, request);
// 测试 BINARY
generalBody.setBodyType(Body.BodyType.BINARY.name());
request.setRequest(ApiDataUtils.toJSONString(msHTTPElement));
this.requestPostWithOk(DEBUG, request);
// 测试 JSON
generalBody.setBodyType(Body.BodyType.JSON.name());
request.setRequest(ApiDataUtils.toJSONString(msHTTPElement));
this.requestPostWithOk(DEBUG, request);
// 测试 XML
generalBody.setBodyType(Body.BodyType.XML.name());
request.setRequest(ApiDataUtils.toJSONString(msHTTPElement));
this.requestPostWithOk(DEBUG, request);
// 测试 XML
generalBody.setBodyType(Body.BodyType.RAW.name());
request.setRequest(ApiDataUtils.toJSONString(msHTTPElement));
this.requestPostWithOk(DEBUG, request);
// 增加覆盖率 // 增加覆盖率
new TestElementParserFactory(); new TestElementParserFactory();
new ImportParserFactory(); new ImportParserFactory();

View File

@ -415,7 +415,7 @@ public class ApiDefinitionControllerTests extends BaseTest {
*/ */
private static void assertLinkFile(String id, List<String> fileIds) { private static void assertLinkFile(String id, List<String> fileIds) {
FileAssociationService fileAssociationService = CommonBeanFactory.getBean(FileAssociationService.class); FileAssociationService fileAssociationService = CommonBeanFactory.getBean(FileAssociationService.class);
List<String> linkFileIds = fileAssociationService.getFiles(id, FileAssociationSourceUtil.SOURCE_TYPE_API_DEFINITION) List<String> linkFileIds = fileAssociationService.getFiles(id)
.stream() .stream()
.map(FileInfo::getFileId) .map(FileInfo::getFileId)
.toList(); .toList();

View File

@ -3,6 +3,7 @@ package io.metersphere.api.controller;
import io.metersphere.api.controller.param.ApiTestCaseAddRequestDefinition; import io.metersphere.api.controller.param.ApiTestCaseAddRequestDefinition;
import io.metersphere.api.domain.*; import io.metersphere.api.domain.*;
import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.mapper.*; import io.metersphere.api.mapper.*;
import io.metersphere.api.service.ApiFileResourceService; import io.metersphere.api.service.ApiFileResourceService;
import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDataUtils;
@ -17,18 +18,16 @@ import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.SessionConstants; import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.domain.Environment; import io.metersphere.sdk.domain.Environment;
import io.metersphere.sdk.domain.EnvironmentExample; import io.metersphere.sdk.domain.EnvironmentExample;
import io.metersphere.api.dto.request.http.MsHTTPElement; import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.file.MinioRepository;
import io.metersphere.sdk.mapper.EnvironmentMapper; import io.metersphere.sdk.mapper.EnvironmentMapper;
import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.CommonBeanFactory; import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.FileAssociationSourceUtil;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest; import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder; import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.dto.sdk.request.PosRequest; import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.file.MinioRepository;
import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.uid.NumGenerator; import io.metersphere.system.uid.NumGenerator;
import io.metersphere.system.utils.Pager; import io.metersphere.system.utils.Pager;
@ -240,7 +239,7 @@ public class ApiTestCaseControllerTests extends BaseTest {
*/ */
private static void assertLinkFile(String id, List<String> fileIds) { private static void assertLinkFile(String id, List<String> fileIds) {
FileAssociationService fileAssociationService = CommonBeanFactory.getBean(FileAssociationService.class); FileAssociationService fileAssociationService = CommonBeanFactory.getBean(FileAssociationService.class);
List<String> linkFileIds = fileAssociationService.getFiles(id, FileAssociationSourceUtil.SOURCE_TYPE_API_TEST_CASE) List<String> linkFileIds = fileAssociationService.getFiles(id)
.stream() .stream()
.map(FileInfo::getFileId) .map(FileInfo::getFileId)
.toList(); .toList();

View File

@ -1,9 +1,6 @@
package io.metersphere.api.controller; package io.metersphere.api.controller;
import io.metersphere.api.dto.definition.HttpResponse; import io.metersphere.api.dto.definition.HttpResponse;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.sdk.constants.MsAssertionCondition;
import io.metersphere.api.dto.request.assertion.*; import io.metersphere.api.dto.request.assertion.*;
import io.metersphere.api.dto.request.assertion.body.*; import io.metersphere.api.dto.request.assertion.body.*;
import io.metersphere.api.dto.request.http.*; import io.metersphere.api.dto.request.http.*;
@ -16,6 +13,9 @@ import io.metersphere.api.dto.request.processors.*;
import io.metersphere.api.dto.request.processors.extract.JSONPathExtract; import io.metersphere.api.dto.request.processors.extract.JSONPathExtract;
import io.metersphere.api.dto.request.processors.extract.RegexExtract; import io.metersphere.api.dto.request.processors.extract.RegexExtract;
import io.metersphere.api.dto.request.processors.extract.XPathExtract; import io.metersphere.api.dto.request.processors.extract.XPathExtract;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.sdk.constants.MsAssertionCondition;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -33,9 +33,14 @@ public class MsHTTPElementTest {
@Test @Test
public void bodyTest() { public void bodyTest() {
MsHTTPElement msHTTPElement = getMsHttpElement(); MsHTTPElement msHTTPElement = getMsHttpElement();
msHTTPElement.setBody(getGeneralBody());
String json = ApiDataUtils.toJSONString(msHTTPElement);
Assertions.assertNotNull(json);
Assertions.assertEquals(ApiDataUtils.parseObject(json, AbstractMsTestElement.class), msHTTPElement);
}
public static Body getGeneralBody() {
Body body = new Body(); Body body = new Body();
body.setBodyType(Body.BodyType.FORM_DATA.name()); body.setBodyType(Body.BodyType.FORM_DATA.name());
@ -49,8 +54,14 @@ public class MsHTTPElementTest {
formDataKV.setParamType("text"); formDataKV.setParamType("text");
formDataKV.setDescription("test"); formDataKV.setDescription("test");
formDataKV.setRequired(true); formDataKV.setRequired(true);
formDataKV.setValue("value"); formDataKV.setValue("@email");
formDataKV.setKey("key"); formDataKV.setKey("key");
FormDataKV formDataFileKV = new FormDataKV();
BodyFile bodyFile = new BodyFile();
bodyFile.setFileId("aaa");
bodyFile.setFileName("aaa");
formDataFileKV.setFiles(List.of(bodyFile));
formDataFileKV.setKey("fileKey");
formDataBody.setFromValues(List.of(formDataKV)); formDataBody.setFromValues(List.of(formDataKV));
body.setFormDataBody(formDataBody); body.setFormDataBody(formDataBody);
@ -60,6 +71,7 @@ public class MsHTTPElementTest {
JsonBody jsonBody = new JsonBody(); JsonBody jsonBody = new JsonBody();
jsonBody.setJsonSchema("{}"); jsonBody.setJsonSchema("{}");
jsonBody.setEnableJsonSchema(false);
body.setJsonBody(jsonBody); body.setJsonBody(jsonBody);
body.setNoneBody(new NoneBody()); body.setNoneBody(new NoneBody());
@ -73,14 +85,10 @@ public class MsHTTPElementTest {
body.setXmlBody(xmlBody); body.setXmlBody(xmlBody);
BinaryBody binaryBody = new BinaryBody(); BinaryBody binaryBody = new BinaryBody();
binaryBody.setFileId("sdfsf2222"); binaryBody.setBodyFile(bodyFile);
binaryBody.setFileName("a.png");
body.setBinaryBody(binaryBody); body.setBinaryBody(binaryBody);
msHTTPElement.setBody(body); return body;
String json = ApiDataUtils.toJSONString(msHTTPElement);
Assertions.assertNotNull(json);
Assertions.assertEquals(ApiDataUtils.parseObject(json, AbstractMsTestElement.class), msHTTPElement);
} }
@Test @Test

View File

@ -128,7 +128,7 @@ public class FunctionalCaseAttachmentService {
})); }));
//获取关联的附件信息 //获取关联的附件信息
List<FileInfo> files = fileAssociationService.getFiles(functionalCaseDetailDTO.getId(), FileAssociationSourceUtil.SOURCE_TYPE_FUNCTIONAL_CASE); List<FileInfo> files = fileAssociationService.getFiles(functionalCaseDetailDTO.getId());
List<FunctionalCaseAttachmentDTO> filesDTOs = new ArrayList<>(Lists.transform(files, (fileInfo) -> { List<FunctionalCaseAttachmentDTO> filesDTOs = new ArrayList<>(Lists.transform(files, (fileInfo) -> {
FunctionalCaseAttachmentDTO attachmentDTO = new FunctionalCaseAttachmentDTO(); FunctionalCaseAttachmentDTO attachmentDTO = new FunctionalCaseAttachmentDTO();
BeanUtils.copyBean(attachmentDTO, fileInfo); BeanUtils.copyBean(attachmentDTO, fileInfo);

View File

@ -1,5 +1,6 @@
package io.metersphere.project.controller; package io.metersphere.project.controller;
import io.metersphere.project.dto.environment.EnvironmentInfoDTO;
import io.metersphere.project.dto.environment.*; import io.metersphere.project.dto.environment.*;
import io.metersphere.project.dto.environment.datasource.DataSource; import io.metersphere.project.dto.environment.datasource.DataSource;
import io.metersphere.project.dto.environment.ssl.KeyStoreEntry; import io.metersphere.project.dto.environment.ssl.KeyStoreEntry;

View File

@ -36,4 +36,15 @@ public class FileInfo implements Serializable {
@Schema(description = "创建时间") @Schema(description = "创建时间")
private Long createTime; private Long createTime;
@Schema(description = "文件存储方式")
private String storage;
@Schema(description = "项目ID")
private String projectId;
@Schema(description = "模块ID")
private String moduleId;
@Schema(description = "文件资源ID")
private String metadataId;
} }

View File

@ -15,7 +15,7 @@ public interface ExtFileAssociationMapper {
FileAssociationSource selectNameBySourceTableAndId(@Param("querySql") String querySql, @Param("sourceId") String sourceId); FileAssociationSource selectNameBySourceTableAndId(@Param("querySql") String querySql, @Param("sourceId") String sourceId);
List<FileAssociationSource> selectAssociationSourceBySourceTableAndIdList(@Param("querySql") String querySql, @Param("idList") List<String> sourceIdList); List<FileAssociationSource> selectAssociationSourceBySourceTableAndIdList(@Param("querySql") String querySql, @Param("idList") List<String> sourceIdList);
List<FileInfo> selectAssociationFileInfo(@Param("sourceId") String sourceId, @Param("sourceType") String sourceType); List<FileInfo> selectAssociationFileInfo(@Param("sourceId") String sourceId);
List<FileAssociation> selectFileIdsBySourceId(@Param("sourceIds")List<String> sourceIds, @Param("sourceType")String sourceType); List<FileAssociation> selectFileIdsBySourceId(@Param("sourceIds")List<String> sourceIds, @Param("sourceType")String sourceType);
} }

View File

@ -19,8 +19,12 @@
SELECT SELECT
file_association.id AS id, file_association.id AS id,
file_association.file_id AS fileId, file_association.file_id AS fileId,
CONCAT( file_metadata.`name`, '.', file_metadata.type ) AS fileName, CONCAT( file_metadata.`name`, IF(LENGTH(file_metadata.type) = 0, '', '.'), file_metadata.type ) AS fileName,
file_metadata.size AS size, file_metadata.size AS size,
file_metadata.storage,
file_metadata.project_id,
file_metadata.module_id,
file_metadata.id as metadataId,
'false' AS local, 'false' AS local,
file_association.create_user AS createUser, file_association.create_user AS createUser,
file_association.create_time AS createTime file_association.create_time AS createTime
@ -29,7 +33,6 @@
LEFT JOIN file_metadata ON file_association.file_id = file_metadata.id LEFT JOIN file_metadata ON file_association.file_id = file_metadata.id
WHERE WHERE
file_association.source_id = #{sourceId} file_association.source_id = #{sourceId}
AND file_association.source_type = #{sourceType}
</select> </select>
<select id="selectFileIdsBySourceId" resultType="io.metersphere.project.domain.FileAssociation"> <select id="selectFileIdsBySourceId" resultType="io.metersphere.project.domain.FileAssociation">

View File

@ -25,7 +25,7 @@ public class CommandService {
public static String createFile(MultipartFile bodyFile) { public static String createFile(MultipartFile bodyFile) {
MsFileUtils.validateFileName(bodyFile.getOriginalFilename()); MsFileUtils.validateFileName(bodyFile.getOriginalFilename());
String dir = LocalRepositoryDir.getBodyEnvironmentTmpDir(); String dir = LocalRepositoryDir.getSystemTempDir();
File fileDir = new File(dir); File fileDir = new File(dir);
if (!fileDir.exists()) { if (!fileDir.exists()) {
fileDir.mkdirs(); fileDir.mkdirs();

View File

@ -337,11 +337,10 @@ public class FileAssociationService {
* 获取文件列表接口 * 获取文件列表接口
* *
* @param sourceId * @param sourceId
* @param sourceType
* @return * @return
*/ */
public List<FileInfo> getFiles(String sourceId, String sourceType) { public List<FileInfo> getFiles(String sourceId) {
return extFileAssociationMapper.selectAssociationFileInfo(sourceId, sourceType); return extFileAssociationMapper.selectAssociationFileInfo(sourceId);
} }
public List<FileAssociation> getFileAssociations(List<String> sourceIds, String sourceType) { public List<FileAssociation> getFileAssociations(List<String> sourceIds, String sourceType) {

View File

@ -172,19 +172,27 @@ public class FileManagementService {
fileRequest.setStorage(fileMetadata.getStorage()); fileRequest.setStorage(fileMetadata.getStorage());
//获取git文件下载 //获取git文件下载
if (StringUtils.equals(fileMetadata.getStorage(), StorageType.GIT.name())) { if (StringUtils.equals(fileMetadata.getStorage(), StorageType.GIT.name())) {
FileModuleRepository fileModuleRepository = fileModuleRepositoryMapper.selectByPrimaryKey(fileMetadata.getModuleId()); FileModuleRepositoryDTO repositoryDTO = getFileModuleRepositoryDTO(fileMetadata.getModuleId());
FileMetadataRepository fileMetadataRepository = fileMetadataRepositoryMapper.selectByPrimaryKey(fileMetadata.getId()); FileMetadataRepositoryDTO metadataRepositoryDTO = getFileMetadataRepositoryDTO(fileMetadata.getId());
FileModuleRepositoryDTO repositoryDTO = new FileModuleRepositoryDTO();
BeanUtils.copyBean(repositoryDTO, fileModuleRepository);
FileMetadataRepositoryDTO metadataRepositoryDTO = new FileMetadataRepositoryDTO();
BeanUtils.copyBean(metadataRepositoryDTO, fileMetadataRepository);
fileRequest.setGitFileRequest(repositoryDTO, metadataRepositoryDTO); fileRequest.setGitFileRequest(repositoryDTO, metadataRepositoryDTO);
} }
return fileService.download(fileRequest); return fileService.download(fileRequest);
} }
public FileMetadataRepositoryDTO getFileMetadataRepositoryDTO(String fileMetadataId) {
FileMetadataRepository fileMetadataRepository = fileMetadataRepositoryMapper.selectByPrimaryKey(fileMetadataId);
FileMetadataRepositoryDTO metadataRepositoryDTO = new FileMetadataRepositoryDTO();
BeanUtils.copyBean(metadataRepositoryDTO, fileMetadataRepository);
return metadataRepositoryDTO;
}
public FileModuleRepositoryDTO getFileModuleRepositoryDTO(String moduleId) {
FileModuleRepository fileModuleRepository = fileModuleRepositoryMapper.selectByPrimaryKey(moduleId);
FileModuleRepositoryDTO repositoryDTO = new FileModuleRepositoryDTO();
BeanUtils.copyBean(repositoryDTO, fileModuleRepository);
return repositoryDTO;
}
public byte[] getPreviewImg(FileMetadata fileMetadata) { public byte[] getPreviewImg(FileMetadata fileMetadata) {
FileRequest previewRequest = new FileRequest(); FileRequest previewRequest = new FileRequest();

View File

@ -669,4 +669,10 @@ public class FileMetadataService {
private String generateMinIOFilePath(String projectId) { private String generateMinIOFilePath(String projectId) {
return DefaultRepositoryDir.getFileManagementDir(projectId); return DefaultRepositoryDir.getFileManagementDir(projectId);
} }
public List<FileMetadata> getByFileIds(List<String> tempFileIds) {
FileMetadataExample example = new FileMetadataExample();
example.createCriteria().andIdIn(tempFileIds);
return fileMetadataMapper.selectByExample(example);
}
} }

View File

@ -1,6 +1,7 @@
package io.metersphere.project.controller; package io.metersphere.project.controller;
import io.metersphere.project.dto.environment.EnvironmentInfoDTO;
import io.metersphere.project.dto.environment.*; import io.metersphere.project.dto.environment.*;
import io.metersphere.project.dto.environment.assertions.*; import io.metersphere.project.dto.environment.assertions.*;
import io.metersphere.project.dto.environment.auth.AuthConfig; import io.metersphere.project.dto.environment.auth.AuthConfig;

View File

@ -2421,7 +2421,7 @@ public class FileManagementControllerTests extends BaseTest {
@Test @Test
@Order(91) @Order(91)
public void testQuery() throws Exception { public void testQuery() throws Exception {
fileAssociationService.getFiles("TEST", FileAssociationSourceUtil.SOURCE_TYPE_FUNCTIONAL_CASE); fileAssociationService.getFiles("TEST");
fileAssociationService.getFileAssociations(Collections.singletonList("TEST"), FileAssociationSourceUtil.SOURCE_TYPE_FUNCTIONAL_CASE); fileAssociationService.getFileAssociations(Collections.singletonList("TEST"), FileAssociationSourceUtil.SOURCE_TYPE_FUNCTIONAL_CASE);