From cb7a7c947c3d13f7b49b2621b08edc3ebd914286 Mon Sep 17 00:00:00 2001 From: liangdaliang Date: Tue, 15 Apr 2025 11:57:12 +0800 Subject: [PATCH] =?UTF-8?q?jmeter=E6=80=A7=E8=83=BD=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=8F=8A=E6=8A=A5=E5=91=8A=E5=88=9D=E6=AD=A5=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/model/JmeterGroupRequest.java | 119 ++++ .../test/common/utils/JMeterGroupUtil.java | 601 ++++++++++++++++++ .../PerformanceReportController.java | 14 + .../domain/PerformanceTestCaseReport.java | 241 +++++++ .../PerformanceTestCaseReportMapper.java | 62 ++ .../IPerformanceTestCaseReportService.java | 62 ++ .../PerformanceTestCaseReportServiceImpl.java | 97 +++ .../test/PerformanceTestCaseReportMapper.xml | 136 ++++ 8 files changed, 1332 insertions(+) create mode 100644 test-common/src/main/java/com/test/common/core/domain/model/JmeterGroupRequest.java create mode 100644 test-common/src/main/java/com/test/common/utils/JMeterGroupUtil.java create mode 100644 test-test/src/main/java/com/test/test/controller/PerformanceReportController.java create mode 100644 test-test/src/main/java/com/test/test/domain/PerformanceTestCaseReport.java create mode 100644 test-test/src/main/java/com/test/test/mapper/PerformanceTestCaseReportMapper.java create mode 100644 test-test/src/main/java/com/test/test/service/IPerformanceTestCaseReportService.java create mode 100644 test-test/src/main/java/com/test/test/service/impl/PerformanceTestCaseReportServiceImpl.java create mode 100644 test-test/src/main/resources/mapper/test/PerformanceTestCaseReportMapper.xml diff --git a/test-common/src/main/java/com/test/common/core/domain/model/JmeterGroupRequest.java b/test-common/src/main/java/com/test/common/core/domain/model/JmeterGroupRequest.java new file mode 100644 index 0000000..cf8532f --- /dev/null +++ b/test-common/src/main/java/com/test/common/core/domain/model/JmeterGroupRequest.java @@ -0,0 +1,119 @@ +package com.test.common.core.domain.model; + +import java.util.List; + +/** + * @author liangdaliang + * @Description:Jmeter线程组调用参数 + * @date 2025-04-14 18:04 + */ +public class JmeterGroupRequest { + private Long testCaseId; + private Long sid; + private Integer concurrentThreads; + private Integer errorOperType; + private Integer executeType; + private Integer rampUpSeconds; + private Integer pressureSecond; + private Integer loopCount; + private Integer rpsStatus; + private Integer rpsLimit; + private String jmeterHomePath; + private List jmeterRequestList; + + public String getJmeterHomePath() { + return jmeterHomePath; + } + + public void setJmeterHomePath(String jmeterHomePath) { + this.jmeterHomePath = jmeterHomePath; + } + + public Integer getConcurrentThreads() { + return concurrentThreads; + } + + public void setConcurrentThreads(Integer concurrentThreads) { + this.concurrentThreads = concurrentThreads; + } + + public Integer getErrorOperType() { + return errorOperType; + } + + public void setErrorOperType(Integer errorOperType) { + this.errorOperType = errorOperType; + } + + public Integer getExecuteType() { + return executeType; + } + + public void setExecuteType(Integer executeType) { + this.executeType = executeType; + } + + public Integer getRampUpSeconds() { + return rampUpSeconds; + } + + public void setRampUpSeconds(Integer rampUpSeconds) { + this.rampUpSeconds = rampUpSeconds; + } + + public Integer getPressureSecond() { + return pressureSecond; + } + + public void setPressureSecond(Integer pressureSecond) { + this.pressureSecond = pressureSecond; + } + + public Integer getLoopCount() { + return loopCount; + } + + public void setLoopCount(Integer loopCount) { + this.loopCount = loopCount; + } + + public Integer getRpsStatus() { + return rpsStatus; + } + + public void setRpsStatus(Integer rpsStatus) { + this.rpsStatus = rpsStatus; + } + + public Integer getRpsLimit() { + return rpsLimit; + } + + public void setRpsLimit(Integer rpsLimit) { + this.rpsLimit = rpsLimit; + } + + public Long getTestCaseId() { + return testCaseId; + } + + public void setTestCaseId(Long testCaseId) { + this.testCaseId = testCaseId; + } + + public Long getSid() { + return sid; + } + + public void setSid(Long sid) { + this.sid = sid; + } + + public List getJmeterRequestList() { + return jmeterRequestList; + } + + public void setJmeterRequestList(List jmeterRequestList) { + this.jmeterRequestList = jmeterRequestList; + } +} diff --git a/test-common/src/main/java/com/test/common/utils/JMeterGroupUtil.java b/test-common/src/main/java/com/test/common/utils/JMeterGroupUtil.java new file mode 100644 index 0000000..f5b0a04 --- /dev/null +++ b/test-common/src/main/java/com/test/common/utils/JMeterGroupUtil.java @@ -0,0 +1,601 @@ +package com.test.common.utils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.PathNotFoundException; +import com.test.common.core.domain.model.JmeterGroupRequest; +import com.test.common.core.domain.model.JmeterRequest; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.config.gui.ArgumentsPanel; +import org.apache.jmeter.control.LoopController; +import org.apache.jmeter.control.gui.LoopControlPanel; +import org.apache.jmeter.control.gui.TestPlanGui; +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jmeter.protocol.http.control.Header; +import org.apache.jmeter.protocol.http.control.HeaderManager; +import org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui; +import org.apache.jmeter.protocol.http.gui.HeaderPanel; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; +import org.apache.jmeter.reporters.ResultCollector; +import org.apache.jmeter.reporters.Summariser; +import org.apache.jmeter.samplers.SampleSaveConfiguration; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testbeans.gui.TestBeanGUI; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.*; +import org.apache.jmeter.threads.ThreadGroup; +import org.apache.jmeter.threads.gui.ThreadGroupGui; +import org.apache.jmeter.timers.ConstantThroughputTimer; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.File; +import java.io.FileOutputStream; +import java.lang.reflect.Type; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author liangdaliang + * @Description:JMeterter线程组工具类 + * @date 2025-04-14 17:44 + */ +public class JMeterGroupUtil { + + private static final Logger logger = LoggerFactory.getLogger(JMeterGroupUtil.class); + + /** + * 生成jmeter测试执行计划并获取执行结果 + * @param jmeterGroupRequest + * @return + */ + public static Map getJmeterResult(JmeterGroupRequest jmeterGroupRequest) { + Long id = jmeterGroupRequest.getTestCaseId(); + Integer concurrentThreads = jmeterGroupRequest.getConcurrentThreads(); + Integer loopCount = jmeterGroupRequest.getLoopCount(); + Integer rpsStatus = jmeterGroupRequest.getRpsStatus(); + Integer rpsLimit = jmeterGroupRequest.getRpsLimit(); + String jmeterHomePath = jmeterGroupRequest.getJmeterHomePath(); + Map result = null; + try { + // 1. 初始化 JMeter + JMeterUtils.loadJMeterProperties(jmeterHomePath + "/bin/jmeter.properties"); + JMeterUtils.setJMeterHome(jmeterHomePath); + JMeterUtils.initLocale(); + + // 2. 创建测试计划 + TestPlan testPlan = createTestPlan("Dynamic Test Plan"); + + // 3. 创建线程组 + org.apache.jmeter.threads.ThreadGroup threadGroup = createThreadGroup(concurrentThreads, loopCount); + + // 4. 获取结果:如汇总报告、查看结果树 + String replayLogPath = jmeterHomePath + "/p_replay_result"+ id +".log"; + String summaryReportPath = jmeterHomePath + "/p_summary_report"+ id +".log"; + List resultCollector = getResultCollector(replayLogPath, summaryReportPath); + + // 5. 构建测试树,新增每个用例下的http请求节点 + HashTree firstTreeTestPlan = new HashTree(); + HashTree secondHashTree = new HashTree(); + HashTree thirdHashTree = new HashTree(); + dealHttpHashTreeWithRequestList(thirdHashTree, jmeterGroupRequest.getJmeterRequestList()); + // 6. 获取设置吞吐量 + ConstantThroughputTimer constantThroughputTimer = null; + thirdHashTree.add(resultCollector); + + if (rpsStatus != null && rpsStatus == 1) { + constantThroughputTimer = getConstantThroughputTimer(rpsLimit); + thirdHashTree.add(constantThroughputTimer); + } + secondHashTree.add(threadGroup, thirdHashTree); + firstTreeTestPlan.add(testPlan, secondHashTree); + + // 7. 保存测试计划为 JMX 文件 + File jmxFile = new File(jmeterHomePath + "/p_dynamic_test_plan"+ id +".jmx"); + SaveService.saveTree(firstTreeTestPlan, new FileOutputStream(jmxFile)); + + // 8. 运行 JMeter 测试 + File resultFile = new File(jmeterHomePath + "/p_replay_result"+ id +".log"); + if (resultFile.exists()) { + resultFile.delete(); + } + StandardJMeterEngine jmeter = new StandardJMeterEngine(); + jmeter.configure(firstTreeTestPlan); + jmeter.run(); + while (jmeter.isActive()) { + System.out.println("JMeter isActive!"); + Thread.sleep(1000); // 每隔 1 秒检查一次 + } + // 9. 获取响应结果 +// result = getResultMessageFromFile(jmeterHomePath + "/p_replay_result"+ id +".log"); +// if (result != null) { +// result.put("requestHeader", requestHeaderJson); +// result.put("requestBody", requestBody); +// } + + System.out.println("JMeter 测试执行完成!"); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + /** + * 处理线程组下新增每个用例下的http请求节点 + * @param thirdHashTree + * @param requestList + */ + private static void dealHttpHashTreeWithRequestList(HashTree thirdHashTree, List requestList) { + for (JmeterRequest jmeterRequest : requestList) { + try { + HashTree fourHashTree = new HashTree(); + // 创建 HTTP 请求 + String url = jmeterRequest.getUrl(); + int port = jmeterRequest.getPort(); + String method = jmeterRequest.getMethod().toUpperCase(); + String requestBody = jmeterRequest.getRequestBody(); + String requestParams = jmeterRequest.getRequestParams(); + String requestHeaderJson = ""; + Map requestParamsMap = convertJsonStringToMap(requestParams); + String requestHeader = jmeterRequest.getRequestHeader(); + HTTPSamplerProxy httpSampler = null; + if ("POST".equals(method)) { + httpSampler = createPostHttpSampler( + url, // 请求地址 + port, + requestBody, // 请求体 + requestParamsMap // 查询参数 + ); + } else { + httpSampler = createGetHttpSampler( + url, // 请求地址 + port, + requestParamsMap // 查询参数 + ); + } + // 创建请求头管理器 + Map headerMap = new HashMap<>(); + if (!StringUtils.isEmpty(requestHeader) && !requestHeader.equals("[]")) { + Gson gson = new Gson(); + // 定义类型 + Type type = new TypeToken>>() { + }.getType(); + // 解析 JSON 数组 + List> headerList = null; + try { + headerList = gson.fromJson(requestHeader, type); + // 将 List> 转换为 Map + for (Map entry : headerList) { + String key = entry.get("key"); + String value = entry.get("value"); + headerMap.put(key, value); + } +// requestHeaderJson = gson.toJson(headerMap); + } catch (Exception e) { + logger.error("requestHeader gson.fromJson异常!requestHeader:" + requestHeader, e); + } + } + HeaderManager headerManager = createHeaderManager(headerMap); + fourHashTree.add(headerManager); + thirdHashTree.add(httpSampler, fourHashTree); + } catch (Exception e) { + String info = "url:" + jmeterRequest.getUrl() + ", port:" + jmeterRequest.getPort(); + logger.error(info + " dealHttpHashTreeWithRequest异常", e); + } + } + } + + /** + * 创建测试计划 + * @param name + * @return + */ + private static TestPlan createTestPlan(String name) { + TestPlan testPlan = new TestPlan(name); + testPlan.setProperty(TestElement.TEST_CLASS, TestPlan.class.getName()); + testPlan.setProperty(TestElement.GUI_CLASS, TestPlanGui.class.getName()); + testPlan.setUserDefinedVariables((Arguments) new ArgumentsPanel().createTestElement()); + return testPlan; + } + + /** + * 创建线程组 + * @param numThreads + * @param loops + * @return + */ + private static org.apache.jmeter.threads.ThreadGroup createThreadGroup(int numThreads, int loops) { + org.apache.jmeter.threads.ThreadGroup threadGroup = new org.apache.jmeter.threads.ThreadGroup(); + threadGroup.setName("Thread Group"); + threadGroup.setNumThreads(numThreads); + threadGroup.setRampUp(1); + threadGroup.setSamplerController(createLoopController(loops)); + threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName()); + threadGroup.setProperty(TestElement.GUI_CLASS, ThreadGroupGui.class.getName()); + return threadGroup; + } + + /** + * 创建轮询控制器 + * @param loops + * @return + */ + private static LoopController createLoopController(int loops) { + LoopController loopController = new LoopController(); + loopController.setLoops(loops); + loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName()); + loopController.setProperty(TestElement.GUI_CLASS, LoopControlPanel.class.getName()); + loopController.initialize(); + return loopController; + } + + /** + * 创建get请求方式 + * @param urlString + * @param port + * @param requestParams + * @return + * @throws MalformedURLException + */ + private static HTTPSamplerProxy createGetHttpSampler(String urlString, int port, Map requestParams) throws MalformedURLException { + URL url = new URL(urlString); + // 提取域名(不包括端口号) + String domain = url.getHost(); + // 提取路径(不包括查询参数和片段标识符) + String path = url.getPath(); + HTTPSamplerProxy httpSampler = new HTTPSamplerProxy(); + httpSampler.setName("HTTP Request Test"); + httpSampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName()); + httpSampler.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName()); + httpSampler.setDomain(domain); // 提取域名 + if (requestParams != null) { + path += "?"; + path += mapToQueryString(requestParams); + } + httpSampler.setPath(path); // 提取路径 + httpSampler.setPort(port); + httpSampler.setMethod("GET"); + httpSampler.setConnectTimeout("5000"); + httpSampler.setUseKeepAlive(true); + + return httpSampler; + } + + /** + * 构建get请求查询参数 + * @param params + * @return + */ + public static String mapToQueryString(Map params) { + StringBuilder queryString = new StringBuilder(); + for (Map.Entry entry : params.entrySet()) { + if (!queryString.isEmpty()) { + queryString.append("&"); + } + // 对键和值进行 URL 编码 + String encodedKey = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8); + String encodedValue = URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8); + queryString.append(encodedKey).append("=").append(encodedValue); + } + return queryString.toString(); + } + + /** + * 转换[{"key":"authorization","value":"tttt"},{"key":"name","value":"123"}]为Map结构 + * @param jsonString + * @return + */ + private static Map convertJsonStringToMap(String jsonString) { + if (StringUtils.isEmpty(jsonString) || jsonString.equals("[]")) { + return null; + } + Gson gson = new Gson(); + // 定义类型,用于告诉Gson我们期望的结果类型是一个包含键值对的对象列表 + Type listType = new TypeToken>>(){}.getType(); + + // 将JSON字符串转换为List> + List> keyValuePairs = null; + try { +// jsonString = jsonString.replaceAll(", \\[\\{\\}]", ""); +// jsonString = jsonString.replaceAll(",\\[\\{\\}]", ""); + keyValuePairs = gson.fromJson(jsonString, listType); + } catch (Exception e) { + logger.error("gson.fromJson发生异常!jsonString:" + jsonString, e); + return null; + } + + // 创建一个新的Map用于存储结果 + Map resultMap = new HashMap<>(); + for (Map pair : keyValuePairs) { + // 确保每个map都有"key"和"value"两个字段 + if (pair.containsKey("key") && pair.containsKey("value")) { + resultMap.put(pair.get("key"), pair.get("value")); + } + } + + return resultMap; + } + + /** + * 创建post请求方式 + * @param urlString + * @param port + * @param requestBody + * @param requestParams + * @return + * @throws MalformedURLException + */ + private static HTTPSamplerProxy createPostHttpSampler(String urlString, int port, String requestBody, Map requestParams) throws MalformedURLException { + URL url = new URL(urlString); + // 提取域名(不包括端口号) + String domain = url.getHost(); + // 提取路径(不包括查询参数和片段标识符) + String path = url.getPath(); +// int portTest = url.getPort(); + HTTPSamplerProxy httpSampler = new HTTPSamplerProxy(); + httpSampler.setName("POST JSON Request"); + httpSampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName()); + httpSampler.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName()); + httpSampler.setDomain(domain); + if (requestParams != null) { + path += "?"; + path += mapToQueryString(requestParams); + } + httpSampler.setPath(path); + httpSampler.setPort(port); + httpSampler.setMethod("POST"); + httpSampler.setConnectTimeout("5000"); + httpSampler.setUseKeepAlive(true); + + // 设置请求体 + httpSampler.setPostBodyRaw(true); // 使用原始请求体 + httpSampler.addNonEncodedArgument("", requestBody, ""); + + return httpSampler; + } + + /** + * 创建http请求头管理器 + * @param headerMap + * @return + */ + private static HeaderManager createHeaderManager(Map headerMap) { + HeaderManager headerManager = new HeaderManager(); + headerManager.setName("HTTP Header Manager"); + headerManager.setProperty(TestElement.TEST_CLASS, HeaderManager.class.getName()); + headerManager.setProperty(TestElement.GUI_CLASS, HeaderPanel.class.getName()); + + // 添加默认 Content-Type 请求头 + if (!headerMap.containsKey("Content-Type")) { + Header contentTypeHeader = new Header(); + contentTypeHeader.setName("Content-Type"); + contentTypeHeader.setValue("application/json; charset=UTF-8"); + headerManager.add(contentTypeHeader); + } + + // 遍历 headerMap,动态添加请求头 + for (Map.Entry entry : headerMap.entrySet()) { + Header header = new Header(); + header.setName(entry.getKey()); // 请求头名称 + header.setValue(entry.getValue()); // 请求头值 + headerManager.add(header); + } + + return headerManager; + } + + /*** + * 监听结果树 + * @param replayLogPath 将结果保存到文件中,这个是文件的路径 + * @param summaryReportPath 将汇总报告保存到文件中,这个是文件的路径 + * @return + */ + private static List getResultCollector(String replayLogPath, String summaryReportPath) { + // 察看结果数 + List resultCollectors = new ArrayList<>(); + Summariser summariser = new Summariser("速度"); + ResultCollector resultCollector = new ResultCollector(summariser); + resultCollector.setProperty(new BooleanProperty("ResultCollector.error_logging", true)); + resultCollector.setProperty(new ObjectProperty("saveConfig", getSampleSaveConfig())); + resultCollector.setProperty(new StringProperty("TestElement.gui_class", "org.apache.jmeter.visualizers.ViewResultsFullVisualizer")); + resultCollector.setProperty(new StringProperty("TestElement.name", "察看结果树")); + resultCollector.setProperty(new StringProperty("TestElement.enabled", "true")); + resultCollector.setProperty(new StringProperty("filename", replayLogPath)); + resultCollectors.add(resultCollector); + + // 结果汇总 + ResultCollector resultTotalCollector = new ResultCollector(); + resultTotalCollector.setProperty(new BooleanProperty("ResultCollector.error_logging", true)); + resultTotalCollector.setProperty(new ObjectProperty("saveConfig", getSampleSaveConfig())); + resultTotalCollector.setProperty(new StringProperty("TestElement.gui_class", "org.apache.jmeter.visualizers.SummaryReport")); + resultTotalCollector.setProperty(new StringProperty("TestElement.name", "汇总报告")); + resultTotalCollector.setProperty(new StringProperty("TestElement.enabled", "true")); + resultTotalCollector.setProperty(new StringProperty("filename", summaryReportPath)); + resultCollectors.add(resultTotalCollector); + + return resultCollectors; + } + + /** + * 配置采样结果信息 + * @return + */ + private static SampleSaveConfiguration getSampleSaveConfig() { + SampleSaveConfiguration sampleSaveConfiguration = new SampleSaveConfiguration(); + sampleSaveConfiguration.setTime(true); + sampleSaveConfiguration.setLatency(true); + sampleSaveConfiguration.setTimestamp(true); + sampleSaveConfiguration.setSuccess(true); + sampleSaveConfiguration.setLabel(true); + sampleSaveConfiguration.setCode(true); + sampleSaveConfiguration.setMessage(true); + sampleSaveConfiguration.setThreadName(true); + sampleSaveConfiguration.setDataType(true); + sampleSaveConfiguration.setEncoding(true); + sampleSaveConfiguration.setAssertions(true); + sampleSaveConfiguration.setSubresults(true); + sampleSaveConfiguration.setResponseData(true); + sampleSaveConfiguration.setSamplerData(true); + sampleSaveConfiguration.setAsXml(true); + sampleSaveConfiguration.setFieldNames(true); + sampleSaveConfiguration.setResponseHeaders(true); + sampleSaveConfiguration.setRequestHeaders(false); + sampleSaveConfiguration.setAssertionResultsFailureMessage(true); + //sampleSaveConfiguration.setsserAtionsResultsToSave(0); assertionsResultsToSave + sampleSaveConfiguration.setBytes(true); + sampleSaveConfiguration.setSentBytes(true); + sampleSaveConfiguration.setUrl(true); + sampleSaveConfiguration.setThreadCounts(true); + sampleSaveConfiguration.setIdleTime(true); + sampleSaveConfiguration.setConnectTime(true); + return sampleSaveConfiguration; + } + + + + /*** + * 限制QPS设置 + * @param throughputTimer + * @return + */ + private static ConstantThroughputTimer getConstantThroughputTimer(int throughputTimer) { + ConstantThroughputTimer constantThroughputTimer = new ConstantThroughputTimer(); + constantThroughputTimer.setEnabled(true); + constantThroughputTimer.setName("常数吞吐量定时器"); + constantThroughputTimer.setProperty(TestElement.TEST_CLASS, ConstantThroughputTimer.class.getName()); + constantThroughputTimer.setProperty(TestElement.GUI_CLASS, TestBeanGUI.class.getName()); + constantThroughputTimer.setCalcMode(ConstantThroughputTimer.Mode.AllActiveThreads.ordinal()); + + constantThroughputTimer.setProperty(new IntegerProperty("calcMode", ConstantThroughputTimer.Mode.AllActiveThreads.ordinal())); + DoubleProperty doubleProperty = new DoubleProperty(); + doubleProperty.setName("throughput"); + doubleProperty.setValue(throughputTimer * 60f); + constantThroughputTimer.setProperty(doubleProperty); + return constantThroughputTimer; + } + + /** + * 从结果文件读取响应内容 + * @param filePath + * @return + */ + private static Map getResultMessageFromFile(String filePath) { + File file = new File(filePath); + if (!file.exists()) { + return null; + } + try { + // 1. 创建DocumentBuilderFactory对象 + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + // 2. 创建DocumentBuilder对象 + DocumentBuilder builder = factory.newDocumentBuilder(); + // 3. 解析XML文件,获取Document对象 + Document document = builder.parse(new File(filePath)); + // 4. 获取所有节点 + NodeList httpSampleList = document.getElementsByTagName("httpSample"); + Node httpSampleNode = httpSampleList.item(0); + if (httpSampleNode.getNodeType() == Node.ELEMENT_NODE) { + Map resultMap = new HashMap<>(); + Element httpSampleElement = (Element) httpSampleNode; + String costMiliseconds = httpSampleElement.getAttribute("t"); + String responseCode = httpSampleElement.getAttribute("rc"); + resultMap.put("responseCode", responseCode); + resultMap.put("costMiliseconds", costMiliseconds); + // 提取子节点的内容 + NodeList responseDataList = httpSampleElement.getElementsByTagName("responseData"); + if (responseDataList.getLength() > 0) { + Element responseDataElement = (Element) responseDataList.item(0); + resultMap.put("responseBody", responseDataElement.getTextContent()); + } + // 提取子节点的内容 + NodeList responseHeaderList = httpSampleElement.getElementsByTagName("responseHeader"); + if (responseHeaderList.getLength() > 0) { + Element responseHeaderElement = (Element) responseHeaderList.item(0); + resultMap.put("responseHeader", responseHeaderElement.getTextContent()); + } + return resultMap; + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 从响应头中读取Map信息结构 + * @param responseHeader + * @return + */ + public static Map parseHeaders(String responseHeader) { + Map headerMap = new HashMap<>(); + if (responseHeader == null) { + return headerMap; + } + // 按行拆分 + String[] lines = responseHeader.split("\n"); + // 遍历每一行 + for (String line : lines) { + // 忽略空行和 HTTP 状态行 + if (line.trim().isEmpty() || line.startsWith("HTTP/")) { + continue; + } + // 按冒号拆分键值对 + int colonIndex = line.indexOf(":"); + if (colonIndex > 0) { + String key = line.substring(0, colonIndex).trim(); + String value = line.substring(colonIndex + 1).trim(); + headerMap.put(key, value); + } + } + return headerMap; + } + + /** + * 根据 JSONPath 表达式从 JSON 中提取字段值 + * + * @param json JSON 字符串 + * @param expression JSONPath 表达式 + * @return 字段值,如果字段不存在则返回 null + */ + public static String extractFieldValue(String json, String expression) { + try { + Object result = JsonPath.read(json, expression); + return result != null ? result.toString() : null; + } catch (PathNotFoundException e) { + // 如果字段不存在,返回 null + return null; + } + } + + public static void main(String[] args) { +// String s = "[{\"key\":\"authorization\",\"value\":\"Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjhkZTZlMWYzLTU0YjMtNGM5MC05YTczLTA0NDkxYmE2NTZlNiJ9.oepUXspCPm6Gjjz3pXbs4O87p3ByemxxhRh5HXUFGK2x-lDb2uxdSRfITiE9LeDYQQNTD6UPYKPda2PWLyAUoA\"},[{\"key\":\"\",\"value\":\"\"}]]"; +// s = s.replaceAll(",\\s*\\[\\{\"key\":\"\",\"value\":\"\"\\}]", ""); +// Gson gson = new Gson(); +// // 定义类型 +// Type type = new TypeToken>>() {}.getType(); +// gson.fromJson(s, type); + String paramJson = "{\"md5Value\":\"value1\",\"fileName\":\"value2\"}"; + paramJson = "{\"name\":\"John\",\"city\":\"New York\",\"age\":30}"; + String requestHeader = "{\"Content-Type\":\"application/json; charset=UTF-8\",\"Authorization\":\"Bearer token123\"}"; + String jmeterHomePath = "D:/apache-jmeter-5.4.3"; +// String result = getJmeterResult("http://127.0.0.1:3000/api/items/add", 3000, "POST", paramJson, requestHeader, jmeterHomePath); +// getJmeterResult(123L, "http://127.0.0.1:3000/api/items/user", 3000, "GET", paramJson, requestHeader, jmeterHomePath); +// System.out.println(result); + } +} diff --git a/test-test/src/main/java/com/test/test/controller/PerformanceReportController.java b/test-test/src/main/java/com/test/test/controller/PerformanceReportController.java new file mode 100644 index 0000000..c01ad98 --- /dev/null +++ b/test-test/src/main/java/com/test/test/controller/PerformanceReportController.java @@ -0,0 +1,14 @@ +package com.test.test.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author liangdaliang + * @Description: + * @date 2025-04-14 16:59 + */ +@RestController +@RequestMapping("/test/performanceReport") +public class PerformanceReportController { +} diff --git a/test-test/src/main/java/com/test/test/domain/PerformanceTestCaseReport.java b/test-test/src/main/java/com/test/test/domain/PerformanceTestCaseReport.java new file mode 100644 index 0000000..8de7659 --- /dev/null +++ b/test-test/src/main/java/com/test/test/domain/PerformanceTestCaseReport.java @@ -0,0 +1,241 @@ +package com.test.test.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.test.common.annotation.Excel; +import com.test.common.core.domain.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.util.Date; + +/** + * 性能测试用例报告对象 performance_test_case_report + * + * @author test + * @date 2025-04-14 + */ +public class PerformanceTestCaseReport extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + private Long id; + + /** 性能测试id(外键) */ + @Excel(name = "性能测试id", readConverterExp = "外=键") + private Long performanceId; + + /** 测试用例id(外键) */ + @Excel(name = "测试用例id", readConverterExp = "外=键") + private Long testCaseId; + + /** 性能测试执行批次id,取当前系统时间戳 */ + @Excel(name = "性能测试执行批次id,取当前系统时间戳") + private Long sid; + + /** 并发线程数 */ + @Excel(name = "并发线程数") + private Long concurrentThreads; + + /** 平均响应时间(毫秒) */ + @Excel(name = "平均响应时间", readConverterExp = "毫=秒") + private Long average; + + /** tps */ + @Excel(name = "tps") + private String tps; + + /** 开始时间 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "开始时间", width = 30, dateFormat = "yyyy-MM-dd") + private Date startTime; + + /** 结束时间 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd") + private Date endTime; + + /** 耗时(毫秒) */ + @Excel(name = "耗时", readConverterExp = "毫=秒") + private Long costTime; + + /** 触发方式:1-定时任务;2-手动 */ + @Excel(name = "触发方式:1-定时任务;2-手动") + private Long triggerType; + + /** 性能测试汇总报告json数组 */ + @Excel(name = "性能测试汇总报告json数组") + private String summaryReport; + + /** 申请人姓名 */ + @Excel(name = "申请人姓名") + private String applyUser; + + /** 是否已执行完成状态:0否,1是,默认0 */ + @Excel(name = "是否已执行完成状态:0否,1是,默认0") + private String status; + + /** 删除标记:0:正常;1:删除 */ + private String delFlag; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setPerformanceId(Long performanceId) + { + this.performanceId = performanceId; + } + + public Long getPerformanceId() + { + return performanceId; + } + public void setTestCaseId(Long testCaseId) + { + this.testCaseId = testCaseId; + } + + public Long getTestCaseId() + { + return testCaseId; + } + public void setSid(Long sid) + { + this.sid = sid; + } + + public Long getSid() + { + return sid; + } + public void setConcurrentThreads(Long concurrentThreads) + { + this.concurrentThreads = concurrentThreads; + } + + public Long getConcurrentThreads() + { + return concurrentThreads; + } + public void setAverage(Long average) + { + this.average = average; + } + + public Long getAverage() + { + return average; + } + public void setTps(String tps) + { + this.tps = tps; + } + + public String getTps() + { + return tps; + } + public void setStartTime(Date startTime) + { + this.startTime = startTime; + } + + public Date getStartTime() + { + return startTime; + } + public void setEndTime(Date endTime) + { + this.endTime = endTime; + } + + public Date getEndTime() + { + return endTime; + } + public void setCostTime(Long costTime) + { + this.costTime = costTime; + } + + public Long getCostTime() + { + return costTime; + } + public void setTriggerType(Long triggerType) + { + this.triggerType = triggerType; + } + + public Long getTriggerType() + { + return triggerType; + } + public void setSummaryReport(String summaryReport) + { + this.summaryReport = summaryReport; + } + + public String getSummaryReport() + { + return summaryReport; + } + public void setApplyUser(String applyUser) + { + this.applyUser = applyUser; + } + + public String getApplyUser() + { + return applyUser; + } + public void setStatus(String status) + { + this.status = status; + } + + public String getStatus() + { + return status; + } + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("performanceId", getPerformanceId()) + .append("testCaseId", getTestCaseId()) + .append("sid", getSid()) + .append("concurrentThreads", getConcurrentThreads()) + .append("average", getAverage()) + .append("tps", getTps()) + .append("startTime", getStartTime()) + .append("endTime", getEndTime()) + .append("costTime", getCostTime()) + .append("triggerType", getTriggerType()) + .append("summaryReport", getSummaryReport()) + .append("applyUser", getApplyUser()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("updateBy", getUpdateBy()) + .append("createTime", getCreateTime()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} diff --git a/test-test/src/main/java/com/test/test/mapper/PerformanceTestCaseReportMapper.java b/test-test/src/main/java/com/test/test/mapper/PerformanceTestCaseReportMapper.java new file mode 100644 index 0000000..6b7a87b --- /dev/null +++ b/test-test/src/main/java/com/test/test/mapper/PerformanceTestCaseReportMapper.java @@ -0,0 +1,62 @@ +package com.test.test.mapper; + +import com.test.test.domain.PerformanceTestCaseReport; + +import java.util.List; + +/** + * 性能测试用例报告Mapper接口 + * + * @author test + * @date 2025-04-14 + */ +public interface PerformanceTestCaseReportMapper +{ + /** + * 查询性能测试用例报告 + * + * @param id 性能测试用例报告主键 + * @return 性能测试用例报告 + */ + public PerformanceTestCaseReport selectPerformanceTestCaseReportById(Long id); + + /** + * 查询性能测试用例报告列表 + * + * @param performanceTestCaseReport 性能测试用例报告 + * @return 性能测试用例报告集合 + */ + public List selectPerformanceTestCaseReportList(PerformanceTestCaseReport performanceTestCaseReport); + + /** + * 新增性能测试用例报告 + * + * @param performanceTestCaseReport 性能测试用例报告 + * @return 结果 + */ + public int insertPerformanceTestCaseReport(PerformanceTestCaseReport performanceTestCaseReport); + + /** + * 修改性能测试用例报告 + * + * @param performanceTestCaseReport 性能测试用例报告 + * @return 结果 + */ + public int updatePerformanceTestCaseReport(PerformanceTestCaseReport performanceTestCaseReport); + + /** + * 删除性能测试用例报告 + * + * @param id 性能测试用例报告主键 + * @return 结果 + */ + public int deletePerformanceTestCaseReportById(Long id); + + /** + * 批量删除性能测试用例报告 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deletePerformanceTestCaseReportByIds(Long[] ids); +} diff --git a/test-test/src/main/java/com/test/test/service/IPerformanceTestCaseReportService.java b/test-test/src/main/java/com/test/test/service/IPerformanceTestCaseReportService.java new file mode 100644 index 0000000..07d5883 --- /dev/null +++ b/test-test/src/main/java/com/test/test/service/IPerformanceTestCaseReportService.java @@ -0,0 +1,62 @@ +package com.test.test.service; + +import com.test.test.domain.PerformanceTestCaseReport; + +import java.util.List; + +/** + * 性能测试用例报告Service接口 + * + * @author test + * @date 2025-04-14 + */ +public interface IPerformanceTestCaseReportService +{ + /** + * 查询性能测试用例报告 + * + * @param id 性能测试用例报告主键 + * @return 性能测试用例报告 + */ + public PerformanceTestCaseReport selectPerformanceTestCaseReportById(Long id); + + /** + * 查询性能测试用例报告列表 + * + * @param performanceTestCaseReport 性能测试用例报告 + * @return 性能测试用例报告集合 + */ + public List selectPerformanceTestCaseReportList(PerformanceTestCaseReport performanceTestCaseReport); + + /** + * 新增性能测试用例报告 + * + * @param performanceTestCaseReport 性能测试用例报告 + * @return 结果 + */ + public int insertPerformanceTestCaseReport(PerformanceTestCaseReport performanceTestCaseReport); + + /** + * 修改性能测试用例报告 + * + * @param performanceTestCaseReport 性能测试用例报告 + * @return 结果 + */ + public int updatePerformanceTestCaseReport(PerformanceTestCaseReport performanceTestCaseReport); + + /** + * 批量删除性能测试用例报告 + * + * @param ids 需要删除的性能测试用例报告主键集合 + * @return 结果 + */ + public int deletePerformanceTestCaseReportByIds(Long[] ids); + + /** + * 删除性能测试用例报告信息 + * + * @param id 性能测试用例报告主键 + * @return 结果 + */ + public int deletePerformanceTestCaseReportById(Long id); +} diff --git a/test-test/src/main/java/com/test/test/service/impl/PerformanceTestCaseReportServiceImpl.java b/test-test/src/main/java/com/test/test/service/impl/PerformanceTestCaseReportServiceImpl.java new file mode 100644 index 0000000..751a271 --- /dev/null +++ b/test-test/src/main/java/com/test/test/service/impl/PerformanceTestCaseReportServiceImpl.java @@ -0,0 +1,97 @@ +package com.test.test.service.impl; + +import com.test.common.utils.DateUtils; +import com.test.test.domain.PerformanceTestCaseReport; +import com.test.test.mapper.PerformanceTestCaseReportMapper; +import com.test.test.service.IPerformanceTestCaseReportService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 性能测试用例报告Service业务层处理 + * + * @author test + * @date 2025-04-14 + */ +@Service +public class PerformanceTestCaseReportServiceImpl implements IPerformanceTestCaseReportService +{ + @Resource + private PerformanceTestCaseReportMapper performanceTestCaseReportMapper; + + /** + * 查询性能测试用例报告 + * + * @param id 性能测试用例报告主键 + * @return 性能测试用例报告 + */ + @Override + public PerformanceTestCaseReport selectPerformanceTestCaseReportById(Long id) + { + return performanceTestCaseReportMapper.selectPerformanceTestCaseReportById(id); + } + + /** + * 查询性能测试用例报告列表 + * + * @param performanceTestCaseReport 性能测试用例报告 + * @return 性能测试用例报告 + */ + @Override + public List selectPerformanceTestCaseReportList(PerformanceTestCaseReport performanceTestCaseReport) + { + return performanceTestCaseReportMapper.selectPerformanceTestCaseReportList(performanceTestCaseReport); + } + + /** + * 新增性能测试用例报告 + * + * @param performanceTestCaseReport 性能测试用例报告 + * @return 结果 + */ + @Override + public int insertPerformanceTestCaseReport(PerformanceTestCaseReport performanceTestCaseReport) + { + performanceTestCaseReport.setCreateTime(DateUtils.getNowDate()); + return performanceTestCaseReportMapper.insertPerformanceTestCaseReport(performanceTestCaseReport); + } + + /** + * 修改性能测试用例报告 + * + * @param performanceTestCaseReport 性能测试用例报告 + * @return 结果 + */ + @Override + public int updatePerformanceTestCaseReport(PerformanceTestCaseReport performanceTestCaseReport) + { + performanceTestCaseReport.setUpdateTime(DateUtils.getNowDate()); + return performanceTestCaseReportMapper.updatePerformanceTestCaseReport(performanceTestCaseReport); + } + + /** + * 批量删除性能测试用例报告 + * + * @param ids 需要删除的性能测试用例报告主键 + * @return 结果 + */ + @Override + public int deletePerformanceTestCaseReportByIds(Long[] ids) + { + return performanceTestCaseReportMapper.deletePerformanceTestCaseReportByIds(ids); + } + + /** + * 删除性能测试用例报告信息 + * + * @param id 性能测试用例报告主键 + * @return 结果 + */ + @Override + public int deletePerformanceTestCaseReportById(Long id) + { + return performanceTestCaseReportMapper.deletePerformanceTestCaseReportById(id); + } +} diff --git a/test-test/src/main/resources/mapper/test/PerformanceTestCaseReportMapper.xml b/test-test/src/main/resources/mapper/test/PerformanceTestCaseReportMapper.xml new file mode 100644 index 0000000..2c3f08b --- /dev/null +++ b/test-test/src/main/resources/mapper/test/PerformanceTestCaseReportMapper.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, performance_id, test_case_id, sid, concurrent_threads, average, tps, start_time, end_time, cost_time, trigger_type, summary_report, apply_user, status, del_flag, create_by, update_by, create_time, update_time from performance_test_case_report + + + + + + + + insert into performance_test_case_report + + performance_id, + test_case_id, + sid, + concurrent_threads, + average, + tps, + start_time, + end_time, + cost_time, + trigger_type, + summary_report, + apply_user, + status, + del_flag, + create_by, + update_by, + create_time, + update_time, + + + #{performanceId}, + #{testCaseId}, + #{sid}, + #{concurrentThreads}, + #{average}, + #{tps}, + #{startTime}, + #{endTime}, + #{costTime}, + #{triggerType}, + #{summaryReport}, + #{applyUser}, + #{status}, + #{delFlag}, + #{createBy}, + #{updateBy}, + #{createTime}, + #{updateTime}, + + + + + update performance_test_case_report + + performance_id = #{performanceId}, + test_case_id = #{testCaseId}, + sid = #{sid}, + concurrent_threads = #{concurrentThreads}, + average = #{average}, + tps = #{tps}, + start_time = #{startTime}, + end_time = #{endTime}, + cost_time = #{costTime}, + trigger_type = #{triggerType}, + summary_report = #{summaryReport}, + apply_user = #{applyUser}, + status = #{status}, + del_flag = #{delFlag}, + create_by = #{createBy}, + update_by = #{updateBy}, + create_time = #{createTime}, + update_time = #{updateTime}, + + where id = #{id} + + + + delete from performance_test_case_report where id = #{id} + + + + delete from performance_test_case_report where id in + + #{id} + + +