├── .gitignore ├── README.md ├── assembly.xml ├── beanshell ├── README.md ├── assert.bsh └── sql.bsh ├── common-util ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── apache │ └── jmeter │ ├── common │ ├── cli │ │ └── CliOptions.java │ ├── encryption │ │ └── Signature.java │ ├── exceptions │ │ ├── JBDCException.java │ │ └── ServiceException.java │ ├── google │ │ └── GoogleAuthenticator.java │ ├── jmeter │ │ ├── JMeterGuiUtil.java │ │ ├── JMeterVariablesUtil.java │ │ └── ValueReplaceUtil.java │ ├── json │ │ ├── DoubleTypeAdapter.java │ │ ├── JsonFileUtil.java │ │ ├── JsonPathUtil.java │ │ ├── JsonUtil.java │ │ └── MapTypeAdapter.java │ ├── office │ │ └── ExcelUtil.java │ ├── random │ │ ├── AreaCode.java │ │ ├── CambodiaMobilePhone.java │ │ ├── IDCard.java │ │ ├── MobilePhone.java │ │ └── Randoms.java │ ├── redis │ │ ├── Redis.java │ │ └── RedisCluster.java │ ├── ssh │ │ ├── GoogleAuthUserInfo.java │ │ ├── SSHTelnetClient.java │ │ └── SSHUtil.java │ ├── telnet │ │ └── TelnetUtil.java │ └── utils │ │ ├── DesktopUtil.java │ │ ├── ExceptionUtil.java │ │ ├── FileUtil.java │ │ ├── JsonConfigUtil.java │ │ ├── NetworkUtil.java │ │ ├── PathUtil.java │ │ ├── ReflectUtil.java │ │ ├── RuntimeUtil.java │ │ ├── StringUtil.java │ │ ├── TimeUtil.java │ │ └── YamlUtil.java │ └── engine │ └── util │ ├── SimpleValueReplacer.java │ └── SimpleVariableNoContext.java ├── configs ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── apache │ └── jmeter │ └── config │ ├── EnvDataSet.java │ ├── FailureResultSaver.java │ ├── HTTPHeaderReader.java │ ├── SSHConfiguration.java │ ├── ScriptArgumentsDescriptor.java │ ├── TraversalDataSet.java │ ├── TraversalEmptyValue.java │ └── gui │ ├── EnvDataSetGui.java │ ├── FailureResultSaverGui.java │ ├── HTTPHeaderReaderGui.java │ ├── SSHConfigurationGui.java │ ├── ScriptArgumentsDescriptorGui.java │ ├── TraversalDataSetGui.java │ └── TraversalEmptyValueGui.java ├── docs ├── JMeter+Jenkins+Python接口自动化方案.excalidraw ├── images │ ├── DubboTelnetSampler_001.png │ ├── EnvDataSet_001.png │ ├── FailureResultSaver_001.png │ ├── HTTPHeaderReader_001.png │ ├── JMeterScriptSampler_001.png │ ├── JMeter_Jenkins_Python_Interface_Automation_Testplan.png │ ├── LocalHtmlReport_001.png │ ├── LocalHtmlReport_002.png │ ├── LocalHtmlReport_003.png │ ├── LocalHtmlReport_004.png │ ├── SSHConfiguration_001.png │ ├── TraversalDataSet_001.png │ ├── TraversalDataSet_002.png │ ├── TraversalDataSet_003.png │ ├── TraversalEmptyValue_001.png │ ├── TraversalEmptyValue_002.png │ └── TraversalEmptyValue_003.png └── 常用函数.md ├── functions ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── apache │ └── jmeter │ └── functions │ ├── ExtractPrevResponse.java │ ├── ExtractSQLValue.java │ ├── GoogleAuth.java │ ├── JmeterHome.java │ ├── MD5.java │ ├── RCambodiaMobile.java │ ├── RIdCard.java │ ├── RMobile.java │ ├── RNumber.java │ └── ScriptAbsPath.java ├── infautomator └── pom.xml ├── pom.xml ├── samplers ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── apache │ │ └── jmeter │ │ └── samplers │ │ ├── DubboTelnetSampler.java │ │ ├── JMeterScriptDataForwarder.java │ │ ├── JMeterScriptSampler.java │ │ └── gui │ │ ├── DubboTelnetSamplerGui.java │ │ └── JMeterScriptSamplerGui.java │ └── resources │ └── log4j2.xml └── visualizers ├── README.md ├── pom.xml └── src └── main ├── java └── org │ └── apache │ └── jmeter │ └── visualizers │ ├── ReportCollector.java │ ├── ReportManager.java │ ├── gui │ └── LocalHtmlReportGui.java │ ├── utils │ ├── FreemarkerUtil.java │ ├── JavaScriptUtil.java │ └── JsoupUtil.java │ └── vo │ ├── OverviewInfoVO.java │ ├── ReportInfoVO.java │ ├── TestCaseVO.java │ ├── TestDataSet.java │ ├── TestStepVO.java │ └── TestSuiteVO.java └── resources └── org └── apache └── jmeter └── visualizers └── utils └── template └── report.ftl /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 24 | hs_err_pid* 25 | 26 | .idea/ 27 | *.iml 28 | target/ 29 | 30 | **/dependency-reduced-pom.xml 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jmeter-plugins 2 | ## 一、项目介绍 3 | 一些自用的JMeter插件,可提高脚本的可维护性,可复用性,可读性和编写效率。 4 | 下图是JMeter + Jenkins + Python的接口自动化测试方案。 5 | ![DubboTelnetSampler](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/JMeter_Jenkins_Python_Interface_Automation_Testplan.png) 6 | 7 | ## 二、打包和安装 8 | 1. 编译打包:`mvn clean assembly:assembly`。 9 | 2. 将编译好的jar包放至 `jmeterHome/lib/ext`目录下。 10 | 11 | ## 三、JMeter版本说明 12 | 所有插件均在JMeter-5.1.1版本上开发,理论上5.x版本均可正常使用,如使用过程中出现问题请使用5.1.1版本或联系我 13 | 14 | ## 四、插件介绍 15 | ### Configs [→](https://github.com/YeKelvin/jmeter-plugins/tree/master/configs) 16 | - **环境变量配置器(EnvDataSet):** 根据`.yaml`配置文件加载测试环境变量,脚本中通过`${keyName}`占位符引用 17 | - **失败请求保存器(FailureResultSaver):** 用于性能测试时,把失败的请求数据单独保存下来,方便定位问题 18 | - **HTTP请求头读取器(HTTPHeaderReader):** HTTP信息头管理器的文件版,根据`.yaml`配置文件加载请求头 19 | - **SSH配置器(SSHConfiguration):** 配置ssh,多用于内网跳板机端口转发,目的是本地直连跳板机后的内部服务 20 | - **数据遍历配置器(TraversalDataSet):** 用于枚举遍历测试,完成遍历后线程自动停止线程 21 | - **空值遍历配置器(TraversalEmptyValue):** 用于遍历报文字段做非空校验,完成遍历后线程自动停止线程 22 | 23 | ### Samplers [→](https://github.com/YeKelvin/jmeter-plugins/tree/master/samplers) 24 | - **DubboTelnet取样器(DubboTelnetSampler):** Dubbo接口插件,通过Telnet方式调用Dubbo接口 25 | - **JMeterScript取样器(JMeterScriptSampler):** 可在当前脚本中执行指定位置的JMeter脚本并获取执行结果,当前脚本和目标脚本可传递变量,该插件是提高脚本复用性的大杀器 26 | 27 | ### Visualizers [→](https://github.com/YeKelvin/jmeter-plugins/tree/master/visualizers) 28 | - **HTML报告(LocalHtmlReport):** 收集所有sampler数据保存至html文件中 29 | 30 | ### Functions [→](https://github.com/YeKelvin/jmeter-plugins/tree/master/functions) 31 | - **{__ExtractPrevResponse()}:** 根据JsonPath表达式提取上一个SamplerResponse的Json值 32 | - **{__ExtractSQLValue()}:** 根据列名提取数据库表第一行的值 33 | - **{__GoogleAuth()}:** 谷歌动态认证码 34 | - **{__JmeterHome()}:** 获取JMeter根目录的绝对路径 35 | - **{__MD5()}:** MD5加密 36 | - **{__RIdCard()}:** 随机生成国内身份证号 37 | - **{__RMobile()}:** 随机生成国内手机号 38 | - **{__RNumber()}:** 随机生成数字 39 | - **{__ScriptAbsPath()}:** 获取JMeter脚本所在目录的绝对路径 40 | 41 | ### common-util [→](https://github.com/YeKelvin/jmeter-plugins/tree/master/common-util) 42 | - 平时写脚本时常用的方法 43 | 44 | ### beanshell [→](https://github.com/YeKelvin/jmeter-plugins/tree/master/beanshell) 45 | - 平时写脚本时常用的bsh脚本 46 | - **assert.bsh:** 常用断言方法 47 | - **sql.bsh:** 对获取数据库表结果的方法封装 48 | -------------------------------------------------------------------------------- /assembly.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | jar 6 | 7 | 8 | dir 9 | 10 | 11 | false 12 | 13 | 14 | 15 | ${basedir}/infautomator/target 16 | 17 | jmeter-plugins-*.jar 18 | 19 | / 20 | 21 | 22 | -------------------------------------------------------------------------------- /beanshell/README.md: -------------------------------------------------------------------------------- 1 | # BeanShell 2 | ## beanshell脚本依赖bsh脚本的方法 3 | ```java 4 | // bsh路径:xx/jmeterHome/beanshell/xxx.bsh 5 | 6 | source("${__JmeterHome(beanshell, assert.bsh)}"); 7 | source("${__JmeterHome(beanshell, sql.bsh)}"); 8 | ``` 9 | 10 | 11 | ## assert.bsh 12 | - 常用断言方法 13 | ```java 14 | source("${__JmeterHome(beanshell, assert.bsh)}"); 15 | 16 | assertSQLResultSize("TABLE_NAME", 1); 17 | assertEquals(source, expection); 18 | assertNotEquals(source, expection); 19 | assertTrue(boolean); 20 | assertFalse(boolean); 21 | assertNotNull(source); 22 | assertNull(source); 23 | 24 | String errorMsg = "测试失败了"; 25 | assertSQLResultSize("TABLE_NAME", 1, errorMsg); 26 | assertEquals(source, expection, errorMsg); 27 | assertNotEquals(source, expection, errorMsg); 28 | assertTrue(boolean, errorMsg); 29 | assertFalse(boolean, errorMsg); 30 | assertNotNull(source, errorMsg); 31 | assertNull(source, errorMsg); 32 | ``` 33 | 34 | 35 | ## sql.bsh 36 | - 对获取数据库表结果的方法封装 37 | ```java 38 | source("${__JmeterHome(beanshell, sql.bsh)}"); 39 | 40 | String value = getTableValue("tableName", "columnName"); 41 | String valueWithQuotes = getTableValue("tableName", "columnName", "defualtValue", true); 42 | String count = getTableCount("tableName"); 43 | ``` 44 | 45 | 46 | ## beanshell调试技巧 47 | - 有时beanshell报错信息就只有一条异常,完全不知道哪里出错了,可以把脚本内容套一层try-catch ,能输出更多异常堆栈信息 48 | ```java 49 | try { 50 | // 脚本内容写这里 51 | }catch (Throwable ex) { 52 | log.error("", ex); 53 | } 54 | ``` 55 | 56 | ## bsh脚本source依赖 57 | ```java 58 | import org.apache.jmeter.util.JMeterUtils; 59 | import java.io.File; 60 | 61 | String bshHome = JMeterUtils.getJMeterHome() + File.separator + "beanshell"; 62 | source(bshHome + File.separator + "xxx.bsh"); 63 | ``` -------------------------------------------------------------------------------- /beanshell/assert.bsh: -------------------------------------------------------------------------------- 1 | import org.apache.commons.lang3.StringUtils; 2 | 3 | 4 | /** 5 | * 根据判断条件,condition=true时,停止当前 sample,输出错误信息(不知道为啥中文乱码,暂时用英文提示) 6 | */ 7 | void stopThreadWithCondition(boolean condition, String testResult, String expectionResult) { 8 | if( condition ) { 9 | Failure = true; 10 | FailureMessage = "Test failed!! TestResult=" + testResult + ", ExpectionResult=" + expectionResult; 11 | Response.setStopThread(true); 12 | } 13 | } 14 | 15 | /** 16 | * 根据判断条件,condition=true时,停止当前 sample,输出自定义错误信息 17 | */ 18 | void stopThreadWithCondition(boolean condition, String errorMsg) { 19 | if( condition ) { 20 | Failure = true; 21 | FailureMessage = errorMsg; 22 | Response.setStopThread(true); 23 | } 24 | } 25 | 26 | /** 27 | * 断言 SQL结果数 28 | */ 29 | void assertSQLResultsSize(String tableName, int resultsSize) { 30 | Object tableObj = vars.getObject(tableName); 31 | // 判断JDBC Request中是否有正确设置表结果变量 32 | if(tableObj != null) { 33 | int tableResultSize = tableObj.size(); 34 | String testResult = String.valueOf(tableResultSize); 35 | String expectionResult = String.valueOf(resultsSize); 36 | stopThreadWithCondition(tableResultSize != resultsSize, testResult, expectionResult); 37 | }else { 38 | String errorMsg = "tableName variable does not exist."; 39 | log.info(errorMsg); 40 | stopThreadWithCondition(true, errorMsg); 41 | } 42 | } 43 | 44 | /** 45 | * 断言 SQL结果数,带自定义错误信息 46 | */ 47 | void assertSQLResultsSize(String tableName, int resultsSize, String errorMsg) { 48 | Object tableObj = vars.getObject(tableName); 49 | // 判断JDBC Request中是否有正确设置表结果变量 50 | if(tableObj != null) { 51 | int tableResultSize = tableObj.size(); 52 | String testResult = String.valueOf(tableResultSize); 53 | String expectionResult = String.valueOf(resultsSize); 54 | stopThreadWithCondition(tableResultSize != resultsSize, errorMsg); 55 | }else { 56 | String errorMsg = "tableName variable does not exist."; 57 | log.info(errorMsg); 58 | stopThreadWithCondition(true, errorMsg); 59 | } 60 | } 61 | 62 | /** 63 | * 断言 String是否相等 64 | */ 65 | void assertEquals(String source, String expection) { 66 | stopThreadWithCondition(!source.equals(expection), source, expection); 67 | } 68 | 69 | /** 70 | * 断言 String是否相等,带自定义错误信息 71 | */ 72 | void assertEquals(String source, String expection, String errorMsg) { 73 | stopThreadWithCondition(!source.equals(expection), errorMsg); 74 | } 75 | 76 | /** 77 | * 断言 int是否相等 78 | */ 79 | void assertEquals(int source, int expection) { 80 | stopThreadWithCondition(!(source==expection), 81 | String.valueOf(source), 82 | String.valueOf(expection)); 83 | } 84 | 85 | /** 86 | * 断言 int是否相等,带自定义错误信息 87 | */ 88 | void assertEquals(int source, int expection, String errorMsg) { 89 | stopThreadWithCondition(!(source==expection), errorMsg); 90 | } 91 | 92 | /** 93 | * 断言 float是否相等 94 | */ 95 | void assertEquals(float source, float expection) { 96 | stopThreadWithCondition(!(source==expection), 97 | String.valueOf(source), 98 | String.valueOf(expection)); 99 | } 100 | 101 | /** 102 | * 断言 float是否相等,带自定义错误信息 103 | */ 104 | void assertEquals(float source, float expection, String errorMsg) { 105 | stopThreadWithCondition(!(source==expection), errorMsg); 106 | } 107 | 108 | /** 109 | * 断言 String是否不相等 110 | */ 111 | void assertNotEquals(String source, String expection) { 112 | stopThreadWithCondition(source.equals(expection), source, expection); 113 | } 114 | 115 | /** 116 | * 断言 String是否不相等,带自定义错误信息 117 | */ 118 | void assertNotEquals(String source, String expection, String errorMsg) { 119 | stopThreadWithCondition(source.equals(expection), errorMsg); 120 | } 121 | 122 | /** 123 | * 断言 int是否不相等 124 | */ 125 | void assertNotEquals(int source, int expection) { 126 | stopThreadWithCondition((source==expection), 127 | String.valueOf(source), 128 | String.valueOf(expection)); 129 | } 130 | 131 | /** 132 | * 断言 int是否不相等,带自定义错误信息 133 | */ 134 | void assertNotEquals(int source, int expection, String errorMsg) { 135 | stopThreadWithCondition((source==expection), errorMsg); 136 | } 137 | 138 | /** 139 | * 断言 float是否不相等 140 | */ 141 | void assertNotEquals(float source, float expection) { 142 | stopThreadWithCondition((source==expection), 143 | String.valueOf(source), 144 | String.valueOf(expection)); 145 | } 146 | 147 | /** 148 | * 断言 float是否不相等,带自定义错误信息 149 | */ 150 | void assertNotEquals(float source, float expection, String errorMsg) { 151 | stopThreadWithCondition((source==expection), errorMsg); 152 | } 153 | 154 | /** 155 | * 断言是否为 true 156 | */ 157 | void assertTrue(boolean source) { 158 | stopThreadWithCondition(!(source && true), String.valueOf(source), "true"); 159 | } 160 | 161 | /** 162 | * 断言是否为 false 163 | */ 164 | void assertFalse(boolean source) { 165 | stopThreadWithCondition(!(!source && true), String.valueOf(source), "false"); 166 | } 167 | 168 | /** 169 | * 断言是否为 true,带自定义错误信息 170 | */ 171 | void assertTrue(boolean source, String errorMsg) { 172 | stopThreadWithCondition(!(source && true), errorMsg); 173 | } 174 | 175 | /** 176 | * 断言是否为 false,带自定义错误信息 177 | */ 178 | void assertFalse(boolean source, String errorMsg) { 179 | stopThreadWithCondition(!(!source && true), errorMsg); 180 | } 181 | 182 | /** 183 | * 断言 String是否非 null 184 | */ 185 | void assertNotNull(String source) { 186 | String sourceStr = source == null ? "null" : source; 187 | stopThreadWithCondition((source == null), sourceStr, "null"); 188 | } 189 | 190 | /** 191 | * 断言 String是否非 null,带自定义错误信息 192 | */ 193 | void assertNotNull(String source, String errorMsg) { 194 | stopThreadWithCondition((source == null), errorMsg); 195 | } 196 | 197 | /** 198 | * 断言 String是否为 null 199 | */ 200 | void assertNull(String source) { 201 | String sourceStr = source == null ? "null" : source; 202 | stopThreadWithCondition((source != null), sourceStr, "not null"); 203 | } 204 | 205 | /** 206 | * 断言 String是否为 null,带自定义错误信息 207 | */ 208 | void assertNull(String source, String errorMsg) { 209 | stopThreadWithCondition((source != null), errorMsg); 210 | } 211 | 212 | -------------------------------------------------------------------------------- /beanshell/sql.bsh: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 获取String类型的SQL值 4 | */ 5 | String getTableValue(String tableName, String columnName) { 6 | return String.valueOf(vars.getObject(tableName).get(0).get(columnName)); 7 | } 8 | 9 | /** 10 | * 获取String类型的SQL值,如null则赋默认值defaultValue,withQuotes为true时,columnValue带双引号返回 11 | */ 12 | String getTableValue(String tableName, String columnName, String defaultValue, boolean withQuotes) { 13 | String columnValue = vars.getObject(tableName).get(0).get(columnName); 14 | if(columnValue == null || columnValue.isEmpty()) { 15 | return defaultValue; 16 | } 17 | if(withQuotes) { 18 | return "\"" + String.valueOf(columnValue) + "\""; 19 | } 20 | return String.valueOf(columnValue); 21 | } 22 | 23 | /** 24 | * select count(*) from tableName; 25 | * 获取String类型的数据统计 26 | */ 27 | String getTableCount(String tableName) { 28 | return vars.getObject(tableName).get(0).get("COUNT(*)").toString(); 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /common-util/README.md: -------------------------------------------------------------------------------- 1 | # common-util 2 | ## 项目说明 3 | - 平时写脚本时常用的方法 4 | 5 | ## 方法说明 6 | ### `org.apache.jmeter.common.random.Randoms` 7 | #### `getNumber(int length)` 8 | #### `getNumber(String str, int length)` 9 | #### `getNumber(int length, String str)` 10 | #### `getNumber(int length1, String str, int length2)` 11 | #### `getNumber(String str1, int length, String str2)` 12 | - 获取随机数 13 | 14 | 15 | #### `getIDCard()` 16 | #### `getHKIDCard()` 17 | #### `getMacaoIDCard()` 18 | #### `getTWIDCard()` 19 | - 获取身份证ID随机数 20 | 21 | #### `getIDCard15()` 22 | - 获取15位身份证ID随机数 23 | 24 | #### `getBankCard(String cardBin, int cardLength)` 25 | - 根据卡bin和卡长度随机生成银行卡卡号,卡号无需减去卡bin长度 26 | 27 | #### `getMobileNumber()` 28 | #### `getCMCCMobileNumber()` 29 | #### `getCUCCMobileNumber()` 30 | #### `getTelecomMobileNumber()` 31 | - 获取 移动/联通/电信 手机号码随机数 32 | 33 | ... -------------------------------------------------------------------------------- /common-util/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jmeter-plugins 7 | jmeter-plugins 8 | 5.1.1-v3 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | common-util 14 | 15 | 16 | 17 | org.apache.jmeter 18 | ApacheJMeter_core 19 | 20 | 21 | org.apache.jmeter 22 | ApacheJMeter_http 23 | 24 | 25 | com.google.code.gson 26 | gson 27 | 28 | 29 | redis.clients 30 | jedis 31 | 32 | 33 | com.jcraft 34 | jsch 35 | 36 | 37 | com.jayway.jsonpath 38 | json-path 39 | 40 | 41 | org.apache.commons 42 | commons-lang3 43 | 44 | 45 | org.apache.httpcomponents 46 | httpclient 47 | 48 | 49 | org.apache.httpcomponents 50 | httpcore 51 | 52 | 53 | org.apache.commons 54 | commons-text 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | 60 | 61 | org.apache.poi 62 | poi 63 | 64 | 65 | org.apache.poi 66 | poi-ooxml 67 | 68 | 69 | org.yaml 70 | snakeyaml 71 | 72 | 73 | commons-codec 74 | commons-codec 75 | 76 | 77 | org.apache.logging.log4j 78 | log4j-api 79 | 80 | 81 | org.apache.logging.log4j 82 | log4j-core 83 | 84 | 85 | org.slf4j 86 | slf4j-api 87 | 88 | 89 | 90 | 91 | ${project.groupId}-${project.artifactId}-${project.version} 92 | 93 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/cli/CliOptions.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.cli; 2 | 3 | /** 4 | * @author Kaiwen.Ye 5 | */ 6 | public class CliOptions { 7 | public static final String REPORT_DIR = "reportDir"; 8 | public static final String REPORT_NAME = "reportName"; 9 | public static final String IS_APPEND = "isAppend"; 10 | public static final String CONFIG_NAME = "configName"; 11 | } 12 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/encryption/Signature.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.encryption; 2 | 3 | import com.google.gson.JsonSyntaxException; 4 | import org.apache.commons.collections4.CollectionUtils; 5 | import org.apache.commons.collections4.MapUtils; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.apache.jmeter.common.json.JsonUtil; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.nio.charset.StandardCharsets; 12 | import java.security.MessageDigest; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.util.Comparator; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.TreeMap; 18 | 19 | /** 20 | * 报文加签工具类 21 | * Json报文按照 key首字母排序后用MD5加密 22 | * 23 | * @author Kelvin.Ye 24 | */ 25 | public class Signature { 26 | 27 | private static final Logger log = LoggerFactory.getLogger(Signature.class); 28 | 29 | /** 30 | * 报文加签 31 | * 32 | * @param json json报文 33 | * @param prefix 加签前缀 34 | * @return 报文加签md5密文 35 | */ 36 | public static String sign(String json, String prefix) throws JsonSyntaxException, NoSuchAlgorithmException { 37 | if (StringUtils.isBlank(json)) { 38 | return ""; 39 | } 40 | 41 | try { 42 | StringBuffer sortedSb = new StringBuffer(); 43 | 44 | // 排序Json 45 | Map resultMap = sortMapByKey(JsonUtil.fromJson(json, JsonUtil.mapType)); 46 | if (resultMap != null) { 47 | resultMap.forEach((key, value) -> traverse(sortedSb, key, value)); 48 | } 49 | String sign = sortedSb.substring(0, sortedSb.length() - 1); 50 | 51 | // 拼接前缀 52 | if (StringUtils.isNotBlank(prefix)) { 53 | sign = prefix + "&" + sign; 54 | } 55 | 56 | log.debug("sign={}", sign); 57 | // md5加密 58 | if (StringUtils.isNotBlank(sign)) { 59 | sign = md5(sign); 60 | } 61 | log.debug("md5sign={}", sign); 62 | return sign; 63 | } catch (JsonSyntaxException e) { 64 | log.error("Sign函数目前仅支持Json格式报文"); 65 | throw e; 66 | } 67 | } 68 | 69 | /** 70 | * 递归遍历报文并排序,排序完成后拼接字段 71 | */ 72 | @SuppressWarnings("unchecked") 73 | private static void traverse(StringBuffer sb, Object key, Object value) { 74 | if (value instanceof Map) { 75 | sb.append(key).append("=").append(traverseMap((Map) value)).append("&"); 76 | } else if (value instanceof List) { 77 | sb.append(key).append("=").append(traverseList((List) value)).append("&"); 78 | } else { 79 | sb.append(key).append("=").append(value).append("&"); 80 | } 81 | } 82 | 83 | /** 84 | * 排序 Map并拼接字段 85 | */ 86 | private static String traverseMap(Map map) { 87 | StringBuffer sb = new StringBuffer(); 88 | if (MapUtils.isEmpty(map)) { 89 | return sb.append("{}").toString(); 90 | } 91 | 92 | Map sortedMap = sortMapByKey(map); 93 | if (MapUtils.isEmpty(sortedMap)) { 94 | return sb.append("{}").toString(); 95 | } 96 | 97 | sb.append("{"); 98 | sortedMap.forEach((key, value) -> traverse(sb, key, value)); 99 | return sb.substring(0, sb.length() - 1) + "}"; 100 | } 101 | 102 | /** 103 | * 排序 List并拼接字段 104 | */ 105 | @SuppressWarnings("unchecked") 106 | private static String traverseList(List list) { 107 | StringBuffer sb = new StringBuffer(); 108 | if (CollectionUtils.isEmpty(list)) { 109 | return sb.append("[]").toString(); 110 | } 111 | 112 | sb.append("["); 113 | list.forEach(item -> { 114 | if (item instanceof Map) { 115 | sb.append(traverseMap((Map) item)); 116 | } else if (item instanceof List) { 117 | sb.append(traverseList((List) item)); 118 | } else { 119 | sb.append(item); 120 | } 121 | sb.append(","); 122 | }); 123 | return sb.substring(0, sb.length() - 1) + "]"; 124 | } 125 | 126 | /** 127 | * 根据 key排序 Map 128 | */ 129 | private static Map sortMapByKey(Map map) { 130 | if (MapUtils.isEmpty(map)) { 131 | return null; 132 | } 133 | 134 | // 降序排序 135 | Map sortMap = new TreeMap<>(Comparator.comparing(Object::toString)); 136 | sortMap.putAll(map); 137 | return sortMap; 138 | } 139 | 140 | /** 141 | * md5加密 142 | */ 143 | private static String md5(String value) throws NoSuchAlgorithmException { 144 | MessageDigest md5 = MessageDigest.getInstance("MD5"); 145 | byte[] data; 146 | data = md5.digest(value.getBytes(StandardCharsets.UTF_8)); 147 | String strDigest = bytesToHexString(data).toLowerCase(); 148 | log.debug("md5str:[ {} ]", strDigest); 149 | return strDigest; 150 | } 151 | 152 | private static String bytesToHexString(byte[] src) { 153 | if (src == null || src.length <= 0) { 154 | return ""; 155 | } 156 | 157 | StringBuffer sb = new StringBuffer(); 158 | for (byte b : src) { 159 | int v = b & 0xFF; 160 | String hv = Integer.toHexString(v); 161 | if (hv.length() < 2) { 162 | sb.append(0); 163 | } 164 | sb.append(hv); 165 | } 166 | return sb.toString(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/exceptions/JBDCException.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.exceptions; 2 | 3 | 4 | /** 5 | * @author Kelvin.Ye 6 | */ 7 | public class JBDCException extends RuntimeException { 8 | 9 | private String errorMessage; 10 | 11 | public JBDCException(String errorMessage) { 12 | super(errorMessage); 13 | this.errorMessage = errorMessage; 14 | } 15 | 16 | public String getErrorMessage() { 17 | return errorMessage; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/exceptions/ServiceException.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.exceptions; 2 | 3 | /** 4 | * @author Kelvin.Ye 5 | * @date 2019-02-28 14:53 6 | */ 7 | public class ServiceException extends RuntimeException { 8 | 9 | private String errorMessage; 10 | 11 | public ServiceException(String errorMessage) { 12 | super(errorMessage); 13 | this.errorMessage = errorMessage; 14 | } 15 | 16 | public String getErrorMessage() { 17 | return errorMessage; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/google/GoogleAuthenticator.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.google; 2 | 3 | 4 | import org.apache.commons.codec.binary.Base32; 5 | 6 | import javax.crypto.Mac; 7 | import javax.crypto.spec.SecretKeySpec; 8 | import java.security.InvalidKeyException; 9 | import java.security.NoSuchAlgorithmException; 10 | import java.util.Calendar; 11 | 12 | /** 13 | * @author Kaiwen.Ye 14 | */ 15 | public class GoogleAuthenticator { 16 | 17 | private static final Base32 BASE32 = new Base32(); 18 | 19 | /** 20 | * 获取谷歌动态认证码 21 | */ 22 | public static String getCode(String secretkey) throws NoSuchAlgorithmException, InvalidKeyException { 23 | byte[] key = BASE32.decode(secretkey); 24 | long timeMsec = Calendar.getInstance().getTimeInMillis(); 25 | long t = (timeMsec / 1000L) / 30L; 26 | byte[] data = new byte[8]; 27 | long value = t; 28 | for (int i = 8; i-- > 0; value >>>= 8) { 29 | data[i] = (byte) value; 30 | } 31 | SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1"); 32 | Mac mac = Mac.getInstance("HmacSHA1"); 33 | mac.init(signKey); 34 | byte[] hash = mac.doFinal(data); 35 | int offset = hash[20 - 1] & 0xF; 36 | long truncatedHash = 0; 37 | for (int i = 0; i < 4; ++i) { 38 | truncatedHash <<= 8; 39 | truncatedHash |= (hash[offset + i] & 0xFF); 40 | } 41 | truncatedHash &= 0x7FFFFFFF; 42 | truncatedHash %= 1000000; 43 | 44 | String code = String.valueOf(truncatedHash); 45 | int strLen = code.length(); 46 | while (strLen < 6) { 47 | StringBuffer sb = new StringBuffer(); 48 | //左补0 49 | sb.append("0").append(code); 50 | code = sb.toString(); 51 | strLen = code.length(); 52 | } 53 | return code; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/jmeter/JMeterVariablesUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.jmeter; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.jmeter.threads.JMeterContextService; 5 | 6 | /** 7 | * @author Kaiwen.Ye 8 | */ 9 | public class JMeterVariablesUtil { 10 | 11 | public static String get(String varName) { 12 | return JMeterContextService.getContext().getVariables().get(varName); 13 | } 14 | 15 | public static String getDefault(String varName) { 16 | return getDefault(varName, ""); 17 | } 18 | 19 | public static String getDefault(String varName, String defaultValue) { 20 | String value = get(varName); 21 | return StringUtils.isNotBlank(value) ? value : defaultValue; 22 | } 23 | 24 | public static boolean getDefaultAsBoolean(String varName) { 25 | String value = get(varName); 26 | return StringUtils.isNotBlank(value) && Boolean.parseBoolean(value); 27 | } 28 | 29 | public static boolean getDefaultAsBoolean(String varName, boolean defaultValue) { 30 | String value = get(varName); 31 | return StringUtils.isNotBlank(value) ? Boolean.parseBoolean(value) : defaultValue; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/jmeter/ValueReplaceUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.jmeter; 2 | 3 | import org.apache.jmeter.common.utils.ExceptionUtil; 4 | import org.apache.jmeter.engine.util.SimpleValueReplacer; 5 | import org.apache.jmeter.functions.InvalidVariableException; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.Map; 10 | 11 | /** 12 | * @author Kelvin.Ye 13 | * @date 2021-05-15 14:08 14 | */ 15 | public class ValueReplaceUtil { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(ValueReplaceUtil.class); 18 | 19 | public static String replace(String value, Map variables) { 20 | try { 21 | SimpleValueReplacer replacer = new SimpleValueReplacer(variables); 22 | replacer.setParameters(value); 23 | return replacer.replace(); 24 | } catch (InvalidVariableException e) { 25 | log.error(ExceptionUtil.getStackTrace(e)); 26 | } 27 | return value; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/json/DoubleTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.json; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonPrimitive; 5 | import com.google.gson.JsonSerializationContext; 6 | import com.google.gson.JsonSerializer; 7 | 8 | import java.lang.reflect.Type; 9 | 10 | /** 11 | * @author Kelvin.Ye 12 | * @date 2019-06-22 10:52 13 | */ 14 | public class DoubleTypeAdapter implements JsonSerializer { 15 | @Override 16 | public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) { 17 | if (src == src.longValue()) { 18 | return new JsonPrimitive(src.longValue()); 19 | } 20 | return new JsonPrimitive(src); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/json/JsonFileUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.json; 2 | 3 | 4 | import org.apache.jmeter.common.utils.JsonConfigUtil; 5 | import org.apache.jmeter.common.exceptions.ServiceException; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.IOException; 13 | import java.io.InputStreamReader; 14 | import java.nio.charset.StandardCharsets; 15 | import java.util.ArrayList; 16 | 17 | /** 18 | * @author Kelvin.Ye 19 | */ 20 | public class JsonFileUtil { 21 | 22 | private static final Logger log = LoggerFactory.getLogger(JsonFileUtil.class); 23 | 24 | /** 25 | * 读取json文件内容 26 | * 27 | * @param configFilePath 配置文件路径 28 | * @param interfaceName json文件名 29 | * @return json模版 30 | */ 31 | public static String readJsonFile(String configFilePath, String interfaceName) throws IOException { 32 | // 获取配置文件中的json模版存放目录 33 | String templateJsonDir = JsonConfigUtil.get(configFilePath).get("templateJsonDir"); 34 | // 根据入參interfaceName去templateJsonDir递归搜索获取绝对路径 35 | String interfacePath = JsonFileUtil.findInterfacePathByKeywords(templateJsonDir, interfaceName); 36 | if (interfacePath == null) { 37 | throw new ServiceException(String.format("\"%s\" json模版不存在", interfaceName)); 38 | } 39 | // 根据绝对路径获取json模版内容 40 | return JsonFileUtil.readJsonFileToString(interfacePath); 41 | } 42 | 43 | /** 44 | * 在系统名称的目录下查找json模版 45 | * 46 | * @param configFilePath 配置文件路径 47 | * @param systemName 接口所属系统的目录名 48 | * @param interfaceName json文件名 49 | * @return json模版 50 | */ 51 | public static String readJsonFile(String configFilePath, String systemName, String interfaceName) throws IOException { 52 | // 获取配置文件中的json模版存放目录 53 | String templateJsonDir = JsonConfigUtil.get(configFilePath).get("templateJsonDir"); 54 | // 根据入參interfaceName去templateJsonDir递归搜索获取绝对路径 55 | String interfacePath = JsonFileUtil.findInterfacePathByKeywords( 56 | templateJsonDir + File.separator + systemName, interfaceName); 57 | if (interfacePath == null) { 58 | throw new ServiceException(String.format("\"%s\" json模版不存在", interfaceName)); 59 | } 60 | // 根据绝对路径获取json模版内容 61 | return JsonFileUtil.readJsonFileToString(interfacePath); 62 | } 63 | 64 | 65 | public static ArrayList getJsonFileList(String rootDir) { 66 | ArrayList fileList = new ArrayList<>(); 67 | File dir = new File(rootDir); 68 | File[] files = dir.listFiles(); 69 | if (files != null) { 70 | for (File file : files) { 71 | if (file.isDirectory()) { 72 | fileList.addAll(getJsonFileList(file.getAbsolutePath())); 73 | } else if (file.getName().endsWith("json")) { 74 | fileList.add(file); 75 | } 76 | } 77 | } 78 | return fileList; 79 | } 80 | 81 | 82 | public static String findInterfacePathByKeywords(String rootDir, String interfaceName) { 83 | log.debug("接口搜索路径={}", rootDir); 84 | ArrayList fileList = getJsonFileList(rootDir); 85 | for (File file : fileList) { 86 | if (file.getName().contains(interfaceName + ".json")) { 87 | return file.getAbsolutePath(); 88 | } 89 | } 90 | log.warn(String.format( 91 | "%s%s...%s%s.json 接口模板不存在", rootDir, File.separator, File.separator, interfaceName)); 92 | // 搜索不到路径时返回null 93 | return null; 94 | } 95 | 96 | 97 | public static String readJsonFileToString(String filePath) throws IOException { 98 | StringBuffer content = new StringBuffer(); 99 | File file = new File(filePath); 100 | if (file.exists()) { 101 | InputStreamReader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8); 102 | BufferedReader bufferedReader = new BufferedReader(reader); 103 | String lineTxt; 104 | while ((lineTxt = bufferedReader.readLine()) != null) { 105 | content.append(lineTxt); 106 | } 107 | reader.close(); 108 | return content.toString(); 109 | } 110 | // 文件不存在时返回null 111 | return null; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/json/JsonPathUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.json; 2 | 3 | import com.jayway.jsonpath.Configuration; 4 | import com.jayway.jsonpath.DocumentContext; 5 | import com.jayway.jsonpath.JsonPath; 6 | import com.jayway.jsonpath.Option; 7 | import com.jayway.jsonpath.spi.json.GsonJsonProvider; 8 | import com.jayway.jsonpath.spi.mapper.GsonMappingProvider; 9 | import net.minidev.json.JSONArray; 10 | import net.minidev.json.JSONObject; 11 | import org.apache.jmeter.common.utils.ExceptionUtil; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.util.EnumSet; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | /** 20 | * @author Kelvin.Ye 21 | * @date 2019-02-14 10:16 22 | */ 23 | public class JsonPathUtil { 24 | 25 | private static final Logger log = LoggerFactory.getLogger(JsonUtil.class); 26 | 27 | private static final Configuration config = Configuration.builder() 28 | .jsonProvider(new GsonJsonProvider(JsonUtil.getGson())) 29 | .mappingProvider(new GsonMappingProvider(JsonUtil.getGson())) 30 | .options(EnumSet.noneOf(Option.class)) 31 | .build(); 32 | 33 | /** 34 | * @param json json报文 35 | * @param jsonPath json节点路径 36 | * @return json值 37 | */ 38 | @SuppressWarnings("unchecked") 39 | public static String extractAsString(String json, String jsonPath) { 40 | String result = ""; 41 | try { 42 | Object obj = JsonPath.read(json, jsonPath); 43 | if (obj instanceof Map) { 44 | return new JSONObject((Map) obj).toJSONString(); 45 | } 46 | if (obj instanceof JSONArray) { 47 | return ((JSONArray) obj).toJSONString(); 48 | } 49 | result = obj == null ? "null" : String.valueOf(obj); 50 | } catch (Exception e) { 51 | log.error(ExceptionUtil.getStackTrace(e)); 52 | } 53 | return result; 54 | } 55 | 56 | public static int extractAsInt(String json, String jsonPath) { 57 | int result = 0; 58 | try { 59 | Object obj = JsonPath.read(json, jsonPath); 60 | result = obj == null ? 0 : Integer.parseInt(String.valueOf(obj)); 61 | } catch (Exception e) { 62 | log.error(ExceptionUtil.getStackTrace(e)); 63 | } 64 | return result; 65 | } 66 | 67 | public static float extractAsFloat(String json, String jsonPath) { 68 | float result = 0; 69 | try { 70 | Object obj = JsonPath.read(json, jsonPath); 71 | result = obj == null ? 0 : Float.parseFloat(obj.toString()); 72 | } catch (Exception e) { 73 | log.error(ExceptionUtil.getStackTrace(e)); 74 | } 75 | return result; 76 | } 77 | 78 | /** 79 | * 解析json串 80 | */ 81 | public static DocumentContext jsonParse(String json) { 82 | return JsonPath.using(config).parse(json); 83 | } 84 | 85 | /** 86 | * 获取json报文所有可遍历的JsonPath地址 87 | */ 88 | public static List getJsonPathList(String json) { 89 | Configuration conf = Configuration.builder().options(Option.AS_PATH_LIST).build(); 90 | return JsonPath.using(conf).parse(json).read("$..*"); 91 | } 92 | 93 | /** 94 | * 获取json list的长度 95 | * 96 | * @param json json字符串 97 | * @param jsonPath e.g.: $..book.length() 98 | * @return length 99 | */ 100 | public static int getArrayLength(String json, String jsonPath) { 101 | try { 102 | List size = JsonPath.read(json, jsonPath); 103 | return size.get(0); 104 | } catch (Exception e) { 105 | log.error(ExceptionUtil.getStackTrace(e)); 106 | return 0; 107 | } 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/json/MapTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.json; 2 | 3 | import com.google.gson.TypeAdapter; 4 | import com.google.gson.internal.LinkedTreeMap; 5 | import com.google.gson.stream.JsonReader; 6 | import com.google.gson.stream.JsonToken; 7 | import com.google.gson.stream.JsonWriter; 8 | 9 | import java.io.IOException; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * @author Kelvin.Ye 16 | * @date 2019-06-22 10:41 17 | */ 18 | public class MapTypeAdapter extends TypeAdapter { 19 | 20 | @Override 21 | public Object read(JsonReader in) throws IOException { 22 | JsonToken token = in.peek(); 23 | switch (token) { 24 | case BEGIN_ARRAY: 25 | List list = new ArrayList(); 26 | in.beginArray(); 27 | while (in.hasNext()) { 28 | list.add(read(in)); 29 | } 30 | in.endArray(); 31 | return list; 32 | 33 | case BEGIN_OBJECT: 34 | Map map = new LinkedTreeMap(); 35 | in.beginObject(); 36 | while (in.hasNext()) { 37 | map.put(in.nextName(), read(in)); 38 | } 39 | in.endObject(); 40 | return map; 41 | 42 | case STRING: 43 | return in.nextString(); 44 | 45 | case NUMBER: 46 | // 改写数字的处理逻辑,将数字值分为整型与浮点型。 47 | String numberStr = in.nextString(); 48 | if (numberStr.contains(".") || numberStr.contains("e") 49 | || numberStr.contains("E")) { 50 | return Double.parseDouble(numberStr); 51 | } 52 | if (Long.parseLong(numberStr) <= Integer.MAX_VALUE) { 53 | return Integer.parseInt(numberStr); 54 | } 55 | return Long.parseLong(numberStr); 56 | 57 | case BOOLEAN: 58 | return in.nextBoolean(); 59 | 60 | case NULL: 61 | in.nextNull(); 62 | return null; 63 | 64 | default: 65 | throw new IllegalStateException(); 66 | } 67 | } 68 | 69 | @Override 70 | public void write(JsonWriter out, Object value) { 71 | // 序列化无需实现 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/random/CambodiaMobilePhone.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.random; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Random; 5 | 6 | /** 7 | * 柬埔寨手机号随机数 8 | * 9 | * @author Kelvin.Ye 10 | */ 11 | public class CambodiaMobilePhone { 12 | private static final ArrayList PHONE_RULE = new ArrayList<>(); 13 | 14 | static { 15 | // cellcard 16 | PHONE_RULE.add(new String[]{"11","6"}); 17 | PHONE_RULE.add(new String[]{"12","6"}); 18 | PHONE_RULE.add(new String[]{"12","7"}); 19 | PHONE_RULE.add(new String[]{"14","6"}); 20 | PHONE_RULE.add(new String[]{"17","6"}); 21 | PHONE_RULE.add(new String[]{"61","6"}); 22 | PHONE_RULE.add(new String[]{"76","7"}); 23 | PHONE_RULE.add(new String[]{"77","6"}); 24 | PHONE_RULE.add(new String[]{"78","6"}); 25 | PHONE_RULE.add(new String[]{"79","6"}); 26 | PHONE_RULE.add(new String[]{"85","6"}); 27 | PHONE_RULE.add(new String[]{"89","6"}); 28 | PHONE_RULE.add(new String[]{"92","6"}); 29 | PHONE_RULE.add(new String[]{"95","6"}); 30 | PHONE_RULE.add(new String[]{"99","6"}); 31 | 32 | // smart 33 | PHONE_RULE.add(new String[]{"10","6"}); 34 | PHONE_RULE.add(new String[]{"15","6"}); 35 | PHONE_RULE.add(new String[]{"16","6"}); 36 | PHONE_RULE.add(new String[]{"69","6"}); 37 | PHONE_RULE.add(new String[]{"70","6"}); 38 | PHONE_RULE.add(new String[]{"81","6"}); 39 | PHONE_RULE.add(new String[]{"86","6"}); 40 | PHONE_RULE.add(new String[]{"87","6"}); 41 | PHONE_RULE.add(new String[]{"93","6"}); 42 | PHONE_RULE.add(new String[]{"96","7"}); 43 | PHONE_RULE.add(new String[]{"98","6"}); 44 | 45 | // metfone 46 | PHONE_RULE.add(new String[]{"31","7"}); 47 | PHONE_RULE.add(new String[]{"60","6"}); 48 | PHONE_RULE.add(new String[]{"66","6"}); 49 | PHONE_RULE.add(new String[]{"67","6"}); 50 | PHONE_RULE.add(new String[]{"68","6"}); 51 | PHONE_RULE.add(new String[]{"71","7"}); 52 | PHONE_RULE.add(new String[]{"88","7"}); 53 | PHONE_RULE.add(new String[]{"90","6"}); 54 | PHONE_RULE.add(new String[]{"97","7"}); 55 | 56 | // qb 57 | PHONE_RULE.add(new String[]{"13","6"}); 58 | PHONE_RULE.add(new String[]{"80","6"}); 59 | PHONE_RULE.add(new String[]{"83","6"}); 60 | PHONE_RULE.add(new String[]{"84","6"}); 61 | 62 | // cootel 63 | PHONE_RULE.add(new String[]{"38","7"}); 64 | 65 | // seatel 66 | PHONE_RULE.add(new String[]{"18","7"}); 67 | 68 | } 69 | 70 | /** 71 | * 随机生成柬埔寨手机号规则(前缀 + 长度) 72 | */ 73 | public static String[] getRandomPhoneCode() { 74 | return PHONE_RULE.get(new Random().nextInt(PHONE_RULE.size())); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/random/IDCard.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.random; 2 | 3 | import java.util.Calendar; 4 | import java.util.Random; 5 | import java.util.Set; 6 | 7 | /** 8 | * 生成随机身份证号 9 | * 10 | * @author Kelvin.Ye 11 | */ 12 | public class IDCard { 13 | /** 14 | * 随机生成大陆身份证号码 15 | */ 16 | public static String idGenerate() { 17 | StringBuffer idCard = new StringBuffer(); 18 | idCard.append(randomAreaCode()); 19 | idCard.append(randomBirthday()); 20 | idCard.append(randomCode()); 21 | idCard.append(calcTrailingNumber(idCard.toString().toCharArray())); 22 | return idCard.toString(); 23 | } 24 | 25 | /** 26 | * 随机生成港澳台身份证号码,810000 | 820000 | 830000 27 | */ 28 | public static String idGenerate(String prefix) { 29 | StringBuffer idCard = new StringBuffer(); 30 | idCard.append(prefix); 31 | idCard.append(randomBirthday()); 32 | idCard.append(randomCode()); 33 | idCard.append(calcTrailingNumber(idCard.toString().toCharArray())); 34 | return idCard.toString(); 35 | } 36 | 37 | /** 38 | * 随机地区码 39 | */ 40 | private static int randomAreaCode() { 41 | Set ac = AreaCode.code.keySet(); 42 | return AreaCode.code.get(ac.toArray()[new Random().nextInt(ac.size() - 1)].toString()); 43 | } 44 | 45 | /** 46 | * 随机出生日期 47 | */ 48 | private static String randomBirthday() { 49 | Calendar birthday = Calendar.getInstance(); 50 | birthday.set(Calendar.YEAR, (int) (Math.random() * 60) + 1950); 51 | birthday.set(Calendar.MONTH, (int) (Math.random() * 12)); 52 | birthday.set(Calendar.DATE, (int) (Math.random() * 31)); 53 | 54 | StringBuffer randomBirthday = new StringBuffer(); 55 | randomBirthday.append(birthday.get(Calendar.YEAR)); 56 | long month = birthday.get(Calendar.MONTH) + 1; 57 | if (month < 10) { 58 | randomBirthday.append("0"); 59 | } 60 | randomBirthday.append(month); 61 | long date = birthday.get(Calendar.DATE); 62 | if (date < 10) { 63 | randomBirthday.append("0"); 64 | } 65 | randomBirthday.append(date); 66 | return randomBirthday.toString(); 67 | } 68 | 69 | /** 70 | * 18位身份证验证 71 | * 根据〖中华人民共和国国家标准 GB 11643-1999〗中有关公民身份号码的规定,公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成。 72 | * 排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。 73 | * 第十八位数字(校验码)的计算方法为: 74 | * 1.将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2 75 | * 2.将这17位数字和系数相乘的结果相加。 76 | * 3.用加出来和除以11,看余数是多少? 77 | * 4.余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2。 78 | * 5.通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10,身份证的最后一位号码就是2。 79 | */ 80 | private static char calcTrailingNumber(char[] chars) { 81 | if (chars.length < 17) { 82 | return ' '; 83 | } 84 | int[] c = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}; 85 | char[] r = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'}; 86 | int[] n = new int[17]; 87 | int result = 0; 88 | for (int i = 0; i < n.length; i++) { 89 | n[i] = Integer.parseInt(chars[i] + ""); 90 | } 91 | for (int i = 0; i < n.length; i++) { 92 | result += c[i] * n[i]; 93 | } 94 | return r[result % 11]; 95 | } 96 | 97 | /** 98 | * 随机产生3位数 99 | */ 100 | private static String randomCode() { 101 | int code = (int) (Math.random() * 1000); 102 | if (code < 10) { 103 | return "00" + code; 104 | } else if (code < 100) { 105 | return "0" + code; 106 | } else { 107 | return "" + code; 108 | } 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/random/MobilePhone.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.random; 2 | 3 | 4 | import java.util.ArrayList; 5 | import java.util.Random; 6 | 7 | /** 8 | * @author Kelvin.Ye 9 | */ 10 | public class MobilePhone { 11 | private static final ArrayList PHONE_CODE = new ArrayList<>(); 12 | private static final ArrayList CMCC_CODE = new ArrayList<>(); 13 | private static final ArrayList CUCC_CODE = new ArrayList<>(); 14 | private static final ArrayList TELECOM_CODE = new ArrayList<>(); 15 | 16 | static { 17 | //移动 18 | CMCC_CODE.add("134"); 19 | CMCC_CODE.add("135"); 20 | CMCC_CODE.add("136"); 21 | CMCC_CODE.add("137"); 22 | CMCC_CODE.add("138"); 23 | CMCC_CODE.add("139"); 24 | CMCC_CODE.add("147"); 25 | CMCC_CODE.add("150"); 26 | CMCC_CODE.add("151"); 27 | CMCC_CODE.add("152"); 28 | CMCC_CODE.add("157"); 29 | CMCC_CODE.add("158"); 30 | CMCC_CODE.add("159"); 31 | CMCC_CODE.add("170"); 32 | CMCC_CODE.add("172"); 33 | CMCC_CODE.add("178"); 34 | CMCC_CODE.add("182"); 35 | CMCC_CODE.add("183"); 36 | CMCC_CODE.add("184"); 37 | CMCC_CODE.add("187"); 38 | CMCC_CODE.add("188"); 39 | 40 | //联通 41 | CUCC_CODE.add("130"); 42 | CUCC_CODE.add("131"); 43 | CUCC_CODE.add("132"); 44 | CUCC_CODE.add("145"); 45 | CUCC_CODE.add("155"); 46 | CUCC_CODE.add("156"); 47 | CUCC_CODE.add("170"); 48 | CUCC_CODE.add("171"); 49 | CUCC_CODE.add("175"); 50 | CUCC_CODE.add("176"); 51 | CUCC_CODE.add("185"); 52 | CUCC_CODE.add("186"); 53 | 54 | //电信 55 | TELECOM_CODE.add("133"); 56 | TELECOM_CODE.add("149"); 57 | TELECOM_CODE.add("153"); 58 | TELECOM_CODE.add("158"); 59 | TELECOM_CODE.add("170"); 60 | TELECOM_CODE.add("173"); 61 | TELECOM_CODE.add("177"); 62 | TELECOM_CODE.add("178"); 63 | TELECOM_CODE.add("180"); 64 | TELECOM_CODE.add("181"); 65 | TELECOM_CODE.add("182"); 66 | TELECOM_CODE.add("189"); 67 | TELECOM_CODE.add("199"); 68 | 69 | PHONE_CODE.addAll(CMCC_CODE); 70 | PHONE_CODE.addAll(CUCC_CODE); 71 | PHONE_CODE.addAll(TELECOM_CODE); 72 | } 73 | 74 | /** 75 | * 随机生成 移动/联通/电信手机号码前缀三位数 76 | */ 77 | public static String getRandomPhoneCode() { 78 | return PHONE_CODE.get(new Random().nextInt(PHONE_CODE.size())); 79 | } 80 | 81 | /** 82 | * 随机生成 移动手机号码前缀三位数 83 | */ 84 | public static String getRandomCMCCCode() { 85 | return CMCC_CODE.get(new Random().nextInt(CMCC_CODE.size())); 86 | } 87 | 88 | /** 89 | * 随机生成 联通手机号码前缀三位数 90 | */ 91 | public static String getRandomCUCCCode() { 92 | return CUCC_CODE.get(new Random().nextInt(CUCC_CODE.size())); 93 | } 94 | 95 | /** 96 | * 随机生成 电信手机号码前缀三位数 97 | */ 98 | public static String getRandomTelecomCode() { 99 | return TELECOM_CODE.get(new Random().nextInt(TELECOM_CODE.size())); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/random/Randoms.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.random; 2 | 3 | import org.apache.jmeter.common.random.CambodiaMobilePhone; 4 | import org.apache.jmeter.common.random.IDCard; 5 | import org.apache.jmeter.common.random.MobilePhone; 6 | 7 | import java.util.Random; 8 | 9 | /** 10 | * @author Kelvin.Ye 11 | */ 12 | public class Randoms { 13 | /** 14 | * 获取随机数 15 | * 16 | * @param length 随机数的长度 17 | */ 18 | public static String getNumber(int length) { 19 | StringBuffer sb = new StringBuffer(); 20 | Random random = new Random(); 21 | for (int i = 0; i < length; i++) { 22 | sb.append(random.nextInt(9)); 23 | } 24 | return sb.toString(); 25 | } 26 | 27 | /** 28 | * 获取字符串 + 随机数的组合 29 | */ 30 | public static String getNumber(String str, int length) { 31 | return str + getNumber(length); 32 | } 33 | 34 | /** 35 | * 获取随机数 + 字符串的组合 36 | */ 37 | public static String getNumber(int length, String str) { 38 | return getNumber(length) + str; 39 | } 40 | 41 | /** 42 | * 获取字符串 + 随机数 + 字符串的组合 43 | */ 44 | public static String getNumber(int length1, String str, int length2) { 45 | return getNumber(length1) + str + getNumber(length2); 46 | } 47 | 48 | /** 49 | * 获取字符串 + 随机数 + 字符串的组合 50 | */ 51 | public static String getNumber(String str1, int length, String str2) { 52 | return str1 + getNumber(length) + str2; 53 | } 54 | 55 | 56 | /** 57 | * 获取随机字符串 58 | */ 59 | public static String getString(int length) { 60 | String stringModel = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 61 | StringBuffer sb = new StringBuffer(); 62 | Random random = new Random(); 63 | for (int i = 0; i < length; i++) { 64 | int index = random.nextInt(stringModel.length()); 65 | char ch = stringModel.charAt(index); 66 | sb.append(ch); 67 | } 68 | return sb.toString(); 69 | } 70 | 71 | /** 72 | * 获取字符串 + 随机字符串的组合 73 | */ 74 | public static String getString(String str, int length) { 75 | return str + getString(length); 76 | } 77 | 78 | /** 79 | * 获取随机字符串 + 字符串的组合 80 | */ 81 | public static String getString(int length, String str) { 82 | return getString(length) + str; 83 | } 84 | 85 | /** 86 | * 获取字符串 + 随机字符串 + 字符串的组合 87 | */ 88 | public static String getString(int length1, String str, int length2) { 89 | return getString(length1) + str + getString(length2); 90 | } 91 | 92 | /** 93 | * 获取字符串 + 随机字符串 + 字符串的组合 94 | */ 95 | public static String getString(String str1, int length, String str2) { 96 | return str1 + getString(length) + str2; 97 | } 98 | 99 | /** 100 | * 获取身份证ID 101 | */ 102 | public static String getIdCard() { 103 | return IDCard.idGenerate(); 104 | } 105 | 106 | /** 107 | * 获取香港身份证ID 108 | */ 109 | public static String getHKIdCard() { 110 | return IDCard.idGenerate("810000"); 111 | } 112 | 113 | /** 114 | * 获取澳门身份证ID 115 | */ 116 | public static String getMacaoIdCard() { 117 | return IDCard.idGenerate("820000"); 118 | } 119 | 120 | /** 121 | * 获取台湾身份证ID 122 | */ 123 | public static String getTWIdCard() { 124 | return IDCard.idGenerate("830000"); 125 | } 126 | 127 | /** 128 | * 获取15位身份证ID 129 | */ 130 | public static String getIdCard15() { 131 | String idCard = IDCard.idGenerate(); 132 | return idCard.substring(0, 6) + idCard.substring(8, 17); 133 | } 134 | 135 | /** 136 | * 根据卡bin和卡长度随机生成银行卡卡号,卡号无需减去卡bin长度(代码自动扣减) 137 | * 138 | * @param cardBin 卡bin 139 | * @param cardLength 卡号长度 140 | * @return 随机银行卡卡号 141 | */ 142 | public static String getBankCard(String cardBin, int cardLength) { 143 | return getNumber(cardBin, cardLength - cardBin.length()); 144 | } 145 | 146 | /** 147 | * 获取 移动/联通/电信 手机号码 148 | */ 149 | public static String getMobileNumber() { 150 | return MobilePhone.getRandomPhoneCode() + getNumber(8); 151 | } 152 | 153 | /** 154 | * 获取移动手机号码 155 | */ 156 | public static String getCMCCMobileNumber() { 157 | return MobilePhone.getRandomCMCCCode() + getNumber(8); 158 | } 159 | 160 | /** 161 | * 获取联通手机号码 162 | */ 163 | public static String getCUCCMobileNumber() { 164 | return MobilePhone.getRandomCUCCCode() + getNumber(8); 165 | } 166 | 167 | /** 168 | * 获取电信手机号码 169 | */ 170 | public static String getTelecomMobileNumber() { 171 | return MobilePhone.getRandomTelecomCode() + getNumber(8); 172 | } 173 | 174 | /** 175 | * 获取柬埔寨手机号码 176 | */ 177 | public static String getCambodiaMobileNumber() { 178 | String[] mobileRule = CambodiaMobilePhone.getRandomPhoneCode(); 179 | return getNumber("855" + mobileRule[0],Integer.parseInt(mobileRule[1])); 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/redis/Redis.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.redis; 2 | 3 | 4 | import redis.clients.jedis.Jedis; 5 | 6 | /** 7 | * @author Kelvin.Ye 8 | */ 9 | public class Redis { 10 | /** 11 | * @param nodeAddress redis地址,格式为 ip:port 12 | * @return Jedis实例 13 | */ 14 | public static Jedis redisConnect(String nodeAddress) { 15 | String[] addressArray = nodeAddress.split(":"); 16 | Jedis redis = new Jedis(addressArray[0], Integer.parseInt(addressArray[1])); 17 | redis.connect(); 18 | return redis; 19 | } 20 | 21 | public static void flushDB(String host, int port) { 22 | Jedis redis = new Jedis(host, port); 23 | redis.connect(); 24 | redis.flushDB(); 25 | redis.close(); 26 | } 27 | 28 | public static void set(String host, int port, String key, String value) { 29 | Jedis redis = new Jedis(host, port); 30 | redis.connect(); 31 | redis.set(key, value); 32 | redis.close(); 33 | } 34 | 35 | public static String get(String host, int port, String key) { 36 | Jedis redis = new Jedis(host, port); 37 | redis.connect(); 38 | String value = redis.get(key); 39 | redis.close(); 40 | return value; 41 | } 42 | 43 | public static String hget(String host, int port, String key, String field) { 44 | Jedis redis = new Jedis(host, port); 45 | redis.connect(); 46 | String value = redis.hget(key, field); 47 | redis.close(); 48 | return value; 49 | } 50 | 51 | public static boolean exists(String host, int port, String key) { 52 | Jedis redis = new Jedis(host, port); 53 | redis.connect(); 54 | boolean isExisted = redis.exists(key); 55 | redis.close(); 56 | return isExisted; 57 | } 58 | 59 | public static String del(String host, int port, String key) { 60 | Jedis redis = new Jedis(host, port); 61 | redis.connect(); 62 | long delResult = redis.del(key); 63 | redis.close(); 64 | return String.valueOf(delResult); 65 | } 66 | 67 | public static String append(String host, int port, String key, String value) { 68 | Jedis redis = new Jedis(host, port); 69 | redis.connect(); 70 | long appendResult = redis.append(key, value); 71 | redis.close(); 72 | return String.valueOf(appendResult); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/redis/RedisCluster.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.redis; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import redis.clients.jedis.HostAndPort; 6 | import redis.clients.jedis.JedisCluster; 7 | 8 | import java.io.FileNotFoundException; 9 | import java.io.IOException; 10 | import java.util.Arrays; 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | 14 | /** 15 | * @author Kelvin.Ye 16 | */ 17 | public class RedisCluster { 18 | 19 | private static final Logger log = LoggerFactory.getLogger(RedisCluster.class); 20 | 21 | private static JedisCluster redisConnect(String nodes) throws FileNotFoundException { 22 | String[] nodeArray = nodes.split(","); 23 | Set jedisClusterNode = new HashSet<>(); 24 | for (String node : nodeArray) { 25 | String[] address = node.split(":"); 26 | String ip = address[0]; 27 | int port = address.length == 1 ? 0 : Integer.parseInt(address[1]); 28 | jedisClusterNode.add(new HostAndPort(ip, port)); 29 | 30 | } 31 | return new JedisCluster(jedisClusterNode); 32 | } 33 | 34 | public static String get(String nodes, String key) { 35 | String value; 36 | try { 37 | JedisCluster redis = redisConnect(nodes); 38 | value = redis.get(key); 39 | redis.close(); 40 | } catch (IOException e) { 41 | value = String.format("%s\n%s", e.getMessage(), Arrays.toString(e.getStackTrace())); 42 | } 43 | return value; 44 | } 45 | 46 | public static String set(String nodes, String key, String value) { 47 | String setResult = "0"; 48 | try { 49 | JedisCluster redis = redisConnect(nodes); 50 | setResult = redis.set(key, value); 51 | redis.close(); 52 | } catch (IOException e) { 53 | log.error("{}\n{}", e.getMessage(), Arrays.toString(e.getStackTrace())); 54 | } 55 | return setResult; 56 | } 57 | 58 | public static boolean exists(String nodes, String key) { 59 | boolean isExisted = false; 60 | try { 61 | JedisCluster redis = redisConnect(nodes); 62 | isExisted = redis.exists(key); 63 | redis.close(); 64 | } catch (IOException e) { 65 | log.error("{}\n{}", e.getMessage(), Arrays.toString(e.getStackTrace())); 66 | } 67 | return isExisted; 68 | } 69 | 70 | public static String del(String nodes, String key) { 71 | long delResult = 0; 72 | try { 73 | JedisCluster redis = redisConnect(nodes); 74 | delResult = redis.del(key); 75 | redis.close(); 76 | } catch (IOException e) { 77 | log.error("{}\n{}", e.getMessage(), Arrays.toString(e.getStackTrace())); 78 | } 79 | return String.valueOf(delResult); 80 | 81 | } 82 | 83 | public static String append(String nodes, String key, String value) { 84 | long appendResult = 0; 85 | try { 86 | JedisCluster redis = redisConnect(nodes); 87 | redis.append(key, value); 88 | redis.close(); 89 | } catch (IOException e) { 90 | log.error("{}\n{}", e.getMessage(), Arrays.toString(e.getStackTrace())); 91 | } 92 | return String.valueOf(appendResult); 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/ssh/GoogleAuthUserInfo.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.ssh; 2 | 3 | import com.jcraft.jsch.UIKeyboardInteractive; 4 | import com.jcraft.jsch.UserInfo; 5 | import org.apache.jmeter.common.utils.ExceptionUtil; 6 | import org.apache.jmeter.common.google.GoogleAuthenticator; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.security.InvalidKeyException; 11 | import java.security.NoSuchAlgorithmException; 12 | import java.util.Arrays; 13 | 14 | 15 | /** 16 | * @author Kaiwen.Ye 17 | */ 18 | public class GoogleAuthUserInfo implements UserInfo, UIKeyboardInteractive { 19 | 20 | private static final Logger log = LoggerFactory.getLogger(GoogleAuthUserInfo.class); 21 | 22 | private String password; 23 | private String googleSecretKey; 24 | 25 | public void setPassword(String password) { 26 | this.password = password; 27 | } 28 | 29 | public void setGoogleSecretKey(String secretKey) { 30 | this.googleSecretKey = secretKey; 31 | } 32 | 33 | @Override 34 | public String getPassword() { 35 | return password; 36 | } 37 | 38 | @Override 39 | public boolean promptYesNo(String str) { 40 | return true; 41 | } 42 | 43 | @Override 44 | public String getPassphrase() { 45 | return null; 46 | } 47 | 48 | @Override 49 | public boolean promptPassphrase(String message) { 50 | return false; 51 | } 52 | 53 | @Override 54 | public boolean promptPassword(String message) { 55 | return false; 56 | } 57 | 58 | @Override 59 | public void showMessage(String message) { 60 | } 61 | 62 | @Override 63 | public String[] promptKeyboardInteractive(String destination, 64 | String name, 65 | String instruction, 66 | String[] prompt, 67 | boolean[] echo) { 68 | log.debug("destination={}", destination); 69 | log.debug("name={}", name); 70 | log.debug("instruction={}", instruction); 71 | log.debug("prompt={}", Arrays.toString(prompt)); 72 | log.debug("echo={}", Arrays.toString(echo)); 73 | 74 | String[] response = new String[prompt.length]; 75 | if (prompt[0].contains("Verification code:")) { 76 | response[0] = getGoogleCode(); 77 | } else if (prompt[0].contains("Password:")) { 78 | response[0] = getPassword(); 79 | } else { 80 | log.error("超出预期的密码校验"); 81 | response[0] = ""; 82 | } 83 | return response; 84 | } 85 | 86 | /** 87 | * 获取谷歌动态验证码 88 | */ 89 | private String getGoogleCode() { 90 | String code = ""; 91 | try { 92 | code = GoogleAuthenticator.getCode(googleSecretKey); 93 | } catch (NoSuchAlgorithmException | InvalidKeyException e) { 94 | log.error(ExceptionUtil.getStackTrace(e)); 95 | } 96 | return code; 97 | } 98 | } 99 | 100 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/ssh/SSHUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.ssh; 2 | 3 | 4 | import com.jcraft.jsch.ChannelExec; 5 | import com.jcraft.jsch.JSch; 6 | import com.jcraft.jsch.JSchException; 7 | import com.jcraft.jsch.Session; 8 | import org.apache.logging.log4j.core.util.IOUtils; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.nio.charset.Charset; 14 | import java.nio.charset.StandardCharsets; 15 | 16 | /** 17 | * @author Kelvin.Ye 18 | */ 19 | public class SSHUtil { 20 | /** 21 | * ssh远程连接 22 | * 23 | * @param host 地址 24 | * @param port 端口号 25 | * @param userName 登录用户名 26 | * @param password 登录密码 27 | * @return ssh会话对象 28 | */ 29 | public static Session getSession(String host, int port, String userName, String password) throws JSchException { 30 | Session session = new JSch().getSession(userName, host, port); 31 | session.setPassword(password); 32 | // SSH客户端是否接受SSH服务端的hostkey 33 | session.setConfig("StrictHostKeyChecking", "no"); 34 | session.setTimeout(5000); 35 | session.connect(); 36 | return session; 37 | } 38 | 39 | /** 40 | * 执行命令 41 | * 42 | * @param session ssh会话对象 43 | * @param command 命令 44 | * @return 执行结果 45 | */ 46 | public static String executeCommand(Session session, String command) throws IOException, JSchException { 47 | return executeCommand(session, command, StandardCharsets.UTF_8.name()); 48 | } 49 | 50 | /** 51 | * 执行命令 52 | * 53 | * @param session ssh会话对象 54 | * @param command 命令 55 | * @param resultEncoding 结果文本编码 56 | * @return 执行结果 57 | */ 58 | public static String executeCommand(Session session, String command, String resultEncoding) 59 | throws IOException, JSchException { 60 | ChannelExec channelExec = (ChannelExec) session.openChannel("exec"); 61 | InputStream in = channelExec.getInputStream(); 62 | channelExec.setCommand(command); 63 | channelExec.setErrStream(System.err); 64 | channelExec.connect(); 65 | String result = IOUtils.toString(new InputStreamReader(in, Charset.forName(resultEncoding))); 66 | channelExec.disconnect(); 67 | return result; 68 | } 69 | 70 | public static void disConnect(Session session) { 71 | if (session != null && session.isConnected()) { 72 | session.disconnect(); 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/telnet/TelnetUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.telnet; 2 | 3 | 4 | import org.apache.commons.net.telnet.TelnetClient; 5 | import org.apache.jmeter.common.utils.ExceptionUtil; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.io.PrintStream; 12 | import java.nio.charset.Charset; 13 | import java.nio.charset.StandardCharsets; 14 | 15 | 16 | /** 17 | * @author Kelvin.Ye 18 | */ 19 | public class TelnetUtil { 20 | 21 | private static final Logger log = LoggerFactory.getLogger(TelnetUtil.class); 22 | 23 | public static final String WINDOWS = "VT220"; 24 | public static final String UNIX = "VT100"; 25 | public static final String LINUX = "VT100"; 26 | 27 | private final TelnetClient telnet = new TelnetClient(WINDOWS); 28 | private InputStreamReader in; 29 | private PrintStream out; 30 | private int timeout; 31 | 32 | public TelnetUtil(String host, String port) throws IOException { 33 | initTelnet(host, port, StandardCharsets.UTF_8.name(), 5000); 34 | } 35 | 36 | public TelnetUtil(String host, String port, String charset) throws IOException { 37 | initTelnet(host, port, charset, 5000); 38 | } 39 | 40 | public TelnetUtil(String host, String port, String charset, int timeout) throws IOException { 41 | initTelnet(host, port, charset, timeout); 42 | } 43 | 44 | private void initTelnet(String host, String port, String charset, int timeout) throws IOException { 45 | this.timeout = timeout; 46 | // 设置连接超时时间ms 47 | telnet.setConnectTimeout(2000); 48 | telnet.connect(host, Integer.parseInt(port)); 49 | in = new InputStreamReader(telnet.getInputStream(), Charset.forName(charset)); 50 | out = new PrintStream(telnet.getOutputStream(), true, charset); 51 | } 52 | 53 | /** 54 | * 调用dubbo接口 55 | * 56 | * @param interfaceName 接口名 57 | * @param request 请求报文 58 | * @return 响应报文 59 | */ 60 | public String invokeDubbo(String interfaceName, String request) throws IOException { 61 | String result = sendCommand("invoke " + interfaceName + "(" + request + ")"); 62 | log.debug("invoke result={}", result); 63 | return result; 64 | } 65 | 66 | /** 67 | * 关闭连接 68 | */ 69 | public void disconnect() { 70 | try { 71 | if (in != null) { 72 | in.close(); 73 | } 74 | } catch (Exception e) { 75 | log.error(ExceptionUtil.getStackTrace(e)); 76 | } 77 | 78 | try { 79 | telnet.disconnect(); 80 | } catch (Exception e) { 81 | log.error(ExceptionUtil.getStackTrace(e)); 82 | } 83 | } 84 | 85 | /** 86 | * 发送命令并返回结果 87 | * 88 | * @param command 命令值 89 | * @return 响应 90 | */ 91 | private String sendCommand(String command) throws IOException { 92 | write(command); 93 | return readUntil(">"); 94 | } 95 | 96 | /** 97 | * 写命令并发送 98 | * 99 | * @param value 命令值 100 | */ 101 | private void write(String value) { 102 | //写命令 103 | out.println(value); 104 | //发送命令 105 | out.flush(); 106 | } 107 | 108 | /** 109 | * 读消息,直到读到指定字符串中的其中一个才返回,超时则直接返回 110 | * 111 | * @param pattern 匹配到该字符串时返回结果 112 | * @return 返回筛选后的结果 113 | */ 114 | private String readUntil(String pattern) throws IOException { 115 | StringBuffer sb = new StringBuffer(); 116 | boolean flag = pattern != null && pattern.length() > 0; 117 | char lastChar = (char) -1; 118 | if (flag) { 119 | lastChar = pattern.charAt(pattern.length() - 1); 120 | } 121 | int charCode = -1; 122 | long startTime = System.currentTimeMillis(); 123 | // read()返回-1时表示input stream已无数据 124 | while ((charCode = in.read()) != -1) { 125 | // 超时判断 126 | long currentTime = System.currentTimeMillis(); 127 | if (currentTime - startTime > timeout) { 128 | log.debug("readUntil 等待超时"); 129 | break; 130 | } 131 | char ch = (char) charCode; 132 | sb.append(ch); 133 | if (flag) { 134 | if (ch == lastChar && sb.toString().endsWith(pattern)) { 135 | return sb.substring(0, sb.length() - 7); 136 | } 137 | } else { 138 | //如果没指定结束标识,匹配到默认结束标识字符时返回结果 139 | if (ch == '>') { 140 | return sb.toString(); 141 | } 142 | } 143 | } 144 | return sb.toString(); 145 | } 146 | } 147 | 148 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/utils/DesktopUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.utils; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.awt.*; 8 | import java.io.File; 9 | import java.io.IOException; 10 | 11 | /** 12 | * @author Kelvin.Ye 13 | * @date 2021-05-15 13:30 14 | */ 15 | public class DesktopUtil { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(DesktopUtil.class); 18 | 19 | public static void openFile(String filePath) { 20 | if (StringUtils.isBlank(filePath)){ 21 | log.warn("打开文件或目录失败,路径为空"); 22 | return; 23 | } 24 | 25 | File file = new File(filePath); 26 | if (!file.exists()) { 27 | log.warn("打开文件或目录失败,路径不存在,路径:[ {} ]", filePath); 28 | } 29 | openFile(file); 30 | } 31 | 32 | public static void openFile(File file){ 33 | try { 34 | Desktop.getDesktop().open(file); 35 | } catch (IOException ex) { 36 | log.warn(ex.getMessage()); 37 | log.debug(ExceptionUtil.getStackTrace(ex)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/utils/ExceptionUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.utils; 2 | 3 | import java.io.PrintWriter; 4 | import java.io.StringWriter; 5 | 6 | /** 7 | * 异常工具类 8 | * 9 | * @author Kelvin.Ye 10 | * @date 2018-09-30 10:25 11 | */ 12 | public class ExceptionUtil { 13 | /** 14 | * 获取异常堆栈信息 15 | * @param throwable e 16 | * @return 异常堆栈信息 17 | */ 18 | public static String getStackTrace(Throwable throwable) { 19 | StringWriter sw = new StringWriter(); 20 | 21 | try (PrintWriter pw = new PrintWriter(sw)) { 22 | throwable.printStackTrace(pw); 23 | return sw.toString(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/utils/FileUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.utils; 2 | 3 | import org.apache.jmeter.common.exceptions.ServiceException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.BufferedWriter; 9 | import java.io.File; 10 | import java.io.FileInputStream; 11 | import java.io.FileNotFoundException; 12 | import java.io.FileOutputStream; 13 | import java.io.IOException; 14 | import java.io.InputStreamReader; 15 | import java.io.OutputStreamWriter; 16 | import java.nio.charset.StandardCharsets; 17 | 18 | /** 19 | * @author Kelvin.Ye 20 | * @date 2019-02-14 14:53 21 | */ 22 | public class FileUtil { 23 | 24 | private static final Logger log = LoggerFactory.getLogger(FileUtil.class); 25 | 26 | /** 27 | * 获取当前系统换行符 28 | */ 29 | public static final String LINE_SEPARATOR = System.getProperty("line.separator"); 30 | 31 | /** 32 | * 判断文件父目录是否存在,不存在则新建 33 | * 34 | * @param file File对象 35 | */ 36 | public static void createParentDir(File file) { 37 | if (file.getParentFile() != null && !file.getParentFile().exists()) { 38 | if (!file.getParentFile().mkdirs()) { 39 | throw new ServiceException("创建文件 " + file.getParentFile().getAbsolutePath() + "的父目录失败"); 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * 删除文件 46 | * 47 | * @param filePath 文件路径 48 | */ 49 | public static void deleteFile(String filePath) throws FileNotFoundException { 50 | File file = new File(filePath); 51 | if (file.exists() && file.isFile()) { 52 | if (!file.delete()) { 53 | throw new ServiceException("删除文件 " + filePath + "失败"); 54 | } 55 | } else { 56 | throw new FileNotFoundException("删除文件失败,文件 " + filePath + "不存在"); 57 | } 58 | } 59 | 60 | /** 61 | * 判断文件是否存在 62 | */ 63 | public static boolean exists(String filePath) { 64 | return new File(filePath).exists(); 65 | } 66 | 67 | /** 68 | * 写文件 69 | * 70 | * @param filePath 文件路径 71 | * @param content 写入内容 72 | */ 73 | public static void outputFile(String filePath, String content) { 74 | File file = new File(filePath); 75 | outputFile(file, content); 76 | } 77 | 78 | /** 79 | * 写文件 80 | * 81 | * @param file 文件对象 82 | * @param content 写入内容 83 | */ 84 | public static void outputFile(File file, String content) { 85 | try { 86 | FileUtil.createParentDir(file); 87 | BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( 88 | new FileOutputStream(file, false), StandardCharsets.UTF_8)); 89 | writer.write(content); 90 | writer.flush(); 91 | writer.close(); 92 | } catch (IOException e) { 93 | log.error(ExceptionUtil.getStackTrace(e)); 94 | } 95 | } 96 | 97 | /** 98 | * 追加写文件 99 | * 100 | * @param filePath 文件路径 101 | * @param newLine 追加内容 102 | */ 103 | public static void appendFile(String filePath, String newLine) { 104 | File file = new File(filePath); 105 | appendFile(file, newLine); 106 | } 107 | 108 | /** 109 | * 追加写文件 110 | * 111 | * @param file 文件对象 112 | * @param newLine 追加内容 113 | */ 114 | public static void appendFile(File file, String newLine) { 115 | try { 116 | FileUtil.createParentDir(file); 117 | BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( 118 | new FileOutputStream(file, true), StandardCharsets.UTF_8)); 119 | writer.write(newLine); 120 | writer.flush(); 121 | writer.close(); 122 | } catch (IOException e) { 123 | log.error(ExceptionUtil.getStackTrace(e)); 124 | } 125 | } 126 | 127 | /** 128 | * 读文件 129 | * 130 | * @param filePath 文件路径 131 | * @return 文件内容 132 | */ 133 | public static String readFile(String filePath) { 134 | StringBuffer sb = new StringBuffer(); 135 | try ( 136 | BufferedReader reader = new BufferedReader( 137 | new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8)) 138 | ) { 139 | String line; 140 | while ((line = reader.readLine()) != null) { 141 | sb.append(line); 142 | } 143 | } catch (IOException e) { 144 | log.error(ExceptionUtil.getStackTrace(e)); 145 | } 146 | return sb.toString(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/utils/JsonConfigUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.utils; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.reflect.TypeToken; 5 | 6 | import java.io.FileInputStream; 7 | import java.io.FileNotFoundException; 8 | import java.io.InputStreamReader; 9 | import java.lang.reflect.Type; 10 | import java.nio.charset.StandardCharsets; 11 | import java.util.HashMap; 12 | 13 | /** 14 | * @author Kelvin.Ye 15 | */ 16 | public class JsonConfigUtil { 17 | /** 18 | * 读取本地Json配置文件 19 | * 20 | * @return HashMap 21 | */ 22 | public static HashMap get(String configFilePath) throws FileNotFoundException { 23 | InputStreamReader reader = new InputStreamReader(new FileInputStream(configFilePath), StandardCharsets.UTF_8); 24 | Type hashMap = new TypeToken>() { 25 | }.getType(); 26 | return new Gson().fromJson(reader, hashMap); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/utils/NetworkUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.utils; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.InetSocketAddress; 6 | import java.net.Socket; 7 | 8 | /** 9 | * @author Kaiwen.Ye 10 | */ 11 | public class NetworkUtil { 12 | 13 | /** 14 | * 判断ip、端口是否可连接,主要的原理是如果对该主机的特定端口号能建立一个socket,则说明该主机的该端口在使用。 15 | */ 16 | public static boolean isHostConnectable(String host, int port) { 17 | Socket socket = new Socket(); 18 | try { 19 | socket.connect(new InetSocketAddress(host, port)); 20 | } catch (IOException e) { 21 | return false; 22 | } finally { 23 | try { 24 | socket.close(); 25 | } catch (IOException e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | return true; 30 | } 31 | 32 | /** 33 | * 判断ip是否可以连接 34 | */ 35 | public static boolean isHostReachable(String host, int timeOut) { 36 | try { 37 | return InetAddress.getByName(host).isReachable(timeOut); 38 | } catch (IOException e) { 39 | e.printStackTrace(); 40 | return false; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/utils/PathUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.utils; 2 | 3 | import java.io.File; 4 | import java.util.regex.Pattern; 5 | 6 | /** 7 | * @author Kaiwen.Ye 8 | */ 9 | public class PathUtil { 10 | private static final String WIN_SEP = "\\"; 11 | private static final String UNIX_SEP = "/"; 12 | 13 | private static Pattern separatorPattern = Pattern.compile("\\|/"); 14 | 15 | /** 16 | * 目录路径拼接 17 | * 18 | * @param firstPath 父路径 19 | * @param secondPath 子路径 20 | * @return 拼接路径 21 | */ 22 | public static String join(String firstPath, String secondPath) { 23 | if (firstPath.endsWith(WIN_SEP) || firstPath.endsWith(UNIX_SEP)) { 24 | firstPath = firstPath.substring(0, firstPath.length() - 1); 25 | } 26 | if (secondPath.startsWith(WIN_SEP) || secondPath.startsWith(UNIX_SEP)) { 27 | secondPath = secondPath.substring(1); 28 | } 29 | if (secondPath.endsWith(WIN_SEP) || secondPath.endsWith(UNIX_SEP)) { 30 | secondPath = secondPath.substring(0, secondPath.length() - 1); 31 | } 32 | return firstPath + File.separator + secondPath; 33 | } 34 | 35 | public static String join(String path, String... names) { 36 | StringBuffer sb = new StringBuffer(path); 37 | for (String name : names) { 38 | sb.append(File.separator).append(name); 39 | } 40 | path = sb.toString(); 41 | return separatorPattern.matcher(path).replaceAll(File.separator); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/utils/ReflectUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.utils; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.lang.reflect.Field; 7 | import java.lang.reflect.Method; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class ReflectUtil { 14 | 15 | private static final Logger log = LoggerFactory.getLogger(ReflectUtil.class); 16 | 17 | /** 18 | * 获取对象的属性名数组 19 | */ 20 | public static String[] getFiledName(Object obj) { 21 | Field[] fields = obj.getClass().getDeclaredFields(); 22 | String[] fieldNames = new String[fields.length]; 23 | 24 | for (int i = 0; i < fields.length; i++) { 25 | fieldNames[i] = fields[i].getName(); 26 | } 27 | return fieldNames; 28 | } 29 | 30 | /** 31 | * 获取属性类型(type),属性名(name)的map组成的list 32 | */ 33 | public static List> getFiledsInfo(Object obj) { 34 | Field[] fields = obj.getClass().getDeclaredFields(); 35 | List> list = new ArrayList<>(); 36 | Map infoMap; 37 | 38 | for (Field field : fields) { 39 | infoMap = new HashMap<>(); 40 | infoMap.put("type", field.getType().toString()); 41 | infoMap.put("name", field.getName()); 42 | list.add(infoMap); 43 | } 44 | return list; 45 | } 46 | 47 | /** 48 | * 获取方法 49 | * 50 | * @param className 类名 51 | * @param methodName 方法名 52 | * @return Method对象 53 | * @throws ClassNotFoundException 异常 54 | */ 55 | public static Method getMethod(String className, String methodName) throws ClassNotFoundException { 56 | return getMethod(Class.forName(className), methodName); 57 | } 58 | 59 | /** 60 | * 获取方法 61 | * 62 | * @param tclass 类对象 63 | * @param methodName 方法名 64 | * @return Method对象 65 | */ 66 | public static Method getMethod(Class tclass, String methodName) { 67 | Method method = null; 68 | for (Method m : tclass.getDeclaredMethods()) { 69 | if (methodName.equals(m.getName())) { 70 | method = m; 71 | } 72 | } 73 | return method; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/utils/RuntimeUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.utils; 2 | 3 | import com.google.common.base.Joiner; 4 | import org.apache.commons.lang3.ArrayUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | 11 | /** 12 | * @author Kelvin.Ye 13 | * @date 2021-05-16 21:30 14 | */ 15 | public class RuntimeUtil { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(RuntimeUtil.class); 18 | 19 | private static final String OS_WINDOWS = "windows"; 20 | private static final String OS_MAC = "mac"; 21 | private static final String OS_LINUX = "linux"; 22 | 23 | private static final String CMD = "c:\\Windows\\System32\\cmd.exe"; 24 | private static final String TERMINAL = "/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal"; 25 | private static final String ITERM2 = "/Applications/iTerm.app/Contents/MacOS/iTerm2"; 26 | 27 | 28 | public static void exec(String command, String dirPath) throws IOException { 29 | exec(command, new File(dirPath)); 30 | } 31 | 32 | public static void exec(String command, File dir) throws IOException { 33 | String osName = System.getProperty("os.name"); 34 | if (osName.toLowerCase().contains(OS_WINDOWS)) { 35 | exec4Windows(command, dir); 36 | } else if (osName.toLowerCase().contains(OS_MAC)) { 37 | exec4Mac(command, dir); 38 | } else if (osName.toLowerCase().contains(OS_LINUX)) { 39 | exec4Linux(command, dir); 40 | } 41 | } 42 | 43 | /** 44 | * cmd /c dir 是执行完dir命令后关闭命令窗口 45 | * cmd /k dir 是执行完dir命令后不关闭命令窗口 46 | * cmd /c start dir 会打开一个新窗口后执行dir指令,原窗口会关闭 47 | * cmd /k start dir 会打开一个新窗口后执行dir指令,原窗口不会关闭 48 | */ 49 | public static void exec4Windows(String command, String dir) throws IOException { 50 | exec4Windows(command, new File(dir)); 51 | } 52 | 53 | public static void exec4Windows(String command, File dir) throws IOException { 54 | String[] fullCmd = new String[]{"cmd", "/c", command}; 55 | log.info("workspace:[ {} ] execute:[ {} ]", dir.getPath(), Joiner.on(" ").join(fullCmd)); 56 | Runtime.getRuntime().exec(fullCmd, null, dir); 57 | } 58 | 59 | public static void exec4Mac(String command, String dir) throws IOException { 60 | exec4Mac(command, new File(dir)); 61 | } 62 | 63 | public static void exec4Mac(String command, File dir) throws IOException { 64 | String[] cmdArray = command.split(" "); 65 | exec4Mac(cmdArray, dir); 66 | } 67 | 68 | public static void exec4Mac(String[] cmdArray, String dir) throws IOException { 69 | exec4Mac(cmdArray, new File(dir)); 70 | } 71 | 72 | public static void exec4Mac(String[] cmdArray, File dir) throws IOException { 73 | String[] fullCmd = ArrayUtils.addAll(new String[]{"/bin/sh"}, cmdArray); 74 | log.info("workspace:[ {} ] execute:[ {} ]", dir.getPath(), Joiner.on(" ").join(fullCmd)); 75 | Runtime.getRuntime().exec(fullCmd, null, dir); 76 | } 77 | 78 | public static void exec4Linux(String command, String dir) throws IOException { 79 | exec4Linux(command, new File(dir)); 80 | } 81 | 82 | public static void exec4Linux(String command, File dir) throws IOException { 83 | String[] fullCmd = new String[]{"/bin/sh", "-c", "xterm -e " + command}; 84 | log.info("workspace:[ {} ] execute:[ {} ]", dir.getPath(), Joiner.on(" ").join(fullCmd)); 85 | Runtime.getRuntime().exec(fullCmd, null, dir); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/utils/StringUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.utils; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.commons.text.StringSubstitutor; 5 | 6 | import java.util.Map; 7 | import java.util.regex.Pattern; 8 | 9 | /** 10 | * @author Kelvin.Ye 11 | * @date 2018-03-27 10:30 12 | */ 13 | public class StringUtil { 14 | 15 | private static final Pattern SPACES_AND_LINE_BREAKS_PATTERN = Pattern.compile("\\s*|\t|\r|\n"); 16 | 17 | private static final Pattern LINE_BREAKS_PATTERN = Pattern.compile("[\r\n]"); 18 | 19 | public static String replace(String source, Map valuesMap) { 20 | if (StringUtils.isNotEmpty(source)) { 21 | StringSubstitutor substitutor = new StringSubstitutor(valuesMap); 22 | return substitutor.replace(source); 23 | } 24 | return source; 25 | } 26 | 27 | /** 28 | * 去除空格和换行符 29 | */ 30 | public static String removeSpacesAndLineBreaks(String str) { 31 | if (StringUtils.isBlank(str)) { 32 | return str; 33 | } 34 | return SPACES_AND_LINE_BREAKS_PATTERN.matcher(str).replaceAll(""); 35 | } 36 | 37 | /** 38 | * 去除换行符 39 | */ 40 | public static String removeLineBreaks(String str) { 41 | if (StringUtils.isBlank(str)) { 42 | return str; 43 | } 44 | return LINE_BREAKS_PATTERN.matcher(str).replaceAll(""); 45 | } 46 | 47 | public static String joinNewline(String... lines) { 48 | StringBuffer sb = new StringBuffer(); 49 | for (String line : lines) { 50 | sb.append(line).append("\n"); 51 | } 52 | return sb.toString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/common/utils/YamlUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.common.utils; 2 | 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.yaml.snakeyaml.Yaml; 7 | 8 | import java.io.File; 9 | import java.io.FileInputStream; 10 | import java.io.FileNotFoundException; 11 | import java.io.InputStreamReader; 12 | import java.io.UnsupportedEncodingException; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author Kaiwen.Ye 18 | */ 19 | public class YamlUtil { 20 | 21 | private static final Logger log = LoggerFactory.getLogger(YamlUtil.class); 22 | 23 | private static final Yaml YAML = new Yaml(); 24 | 25 | public static final String YAML_SUFFIX = ".yaml"; 26 | 27 | public static Object parseYaml(File file) { 28 | try ( 29 | FileInputStream input = new FileInputStream(file); 30 | InputStreamReader reader = new InputStreamReader(input, StandardCharsets.UTF_8.name()) 31 | ) { 32 | return YAML.load(reader); 33 | } catch (Exception e) { 34 | log.error(ExceptionUtil.getStackTrace(e)); 35 | return null; 36 | } 37 | } 38 | 39 | public static Map parseYamlAsMap(String filePath) 40 | throws FileNotFoundException, UnsupportedEncodingException { 41 | File file = new File(filePath); 42 | if (!file.exists() || !file.isFile() || !filePath.endsWith(YAML_SUFFIX)) { 43 | throw new FileNotFoundException(String.format("文件不存在或非.yaml文件,filePath:[ %s ]", filePath)); 44 | } 45 | return parseYamlAsMap(file); 46 | } 47 | 48 | public static Map parseYamlAsMap(File file) 49 | throws FileNotFoundException, UnsupportedEncodingException { 50 | FileInputStream input = new FileInputStream(file); 51 | InputStreamReader reader = new InputStreamReader(input, StandardCharsets.UTF_8.name()); 52 | return YAML.loadAs(reader, Map.class); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/engine/util/SimpleValueReplacer.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.engine.util; 2 | 3 | import org.apache.jmeter.functions.InvalidVariableException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.util.LinkedList; 8 | import java.util.Map; 9 | 10 | /** 11 | * @author Kaiwen.Ye 12 | */ 13 | public class SimpleValueReplacer { 14 | 15 | private static final Logger log = LoggerFactory.getLogger(SimpleValueReplacer.class); 16 | 17 | private static final FunctionParser FUNCTION_PARSER = new FunctionParser(); 18 | 19 | private final Map variables; 20 | 21 | private LinkedList compiledComponents; 22 | 23 | public SimpleValueReplacer(Map variables) { 24 | this.variables = variables; 25 | } 26 | 27 | public String replace() { 28 | if (compiledComponents == null || compiledComponents.isEmpty()) { 29 | return ""; 30 | } 31 | 32 | StringBuilder results = new StringBuilder(); 33 | for (Object item : compiledComponents) { 34 | results.append(item); 35 | } 36 | return results.toString(); 37 | } 38 | 39 | public void setParameters(String parameters) throws InvalidVariableException { 40 | if (parameters == null || parameters.length() == 0) { 41 | return; 42 | } 43 | 44 | LinkedList newCompiledComponents = new LinkedList<>(); 45 | compiledComponents = FUNCTION_PARSER.compileString(parameters); 46 | for (Object item : compiledComponents) { 47 | if (item instanceof SimpleVariable) { 48 | SimpleVariable simpleVar = (SimpleVariable) item; 49 | SimpleVariableNoContext newSimpleVar = new SimpleVariableNoContext(this.variables); 50 | newSimpleVar.setName(simpleVar.getName()); 51 | newCompiledComponents.add(newSimpleVar); 52 | } else { 53 | newCompiledComponents.add(item); 54 | } 55 | } 56 | compiledComponents = newCompiledComponents; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /common-util/src/main/java/org/apache/jmeter/engine/util/SimpleVariableNoContext.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.engine.util; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * @author Kaiwen.Ye 7 | */ 8 | public class SimpleVariableNoContext extends SimpleVariable { 9 | 10 | private final Map variables; 11 | 12 | public SimpleVariableNoContext(Map variables) { 13 | this.variables = variables; 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | String ret = null; 19 | 20 | if (variables != null) { 21 | ret = variables.get(getName()); 22 | } 23 | 24 | if (ret == null) { 25 | return "${" + getName() + "}"; 26 | } 27 | 28 | return ret; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /configs/README.md: -------------------------------------------------------------------------------- 1 | # Configs 2 | ## 1、环境变量配置器(EnvDataSet) 3 | ### 1.1、插件说明 4 | - 根据`.yaml`配置文件加载测试环境变量,脚本中通过${key}占位符引用 5 | - 将测试环境变量的键值对写入配置文件中,如服务器地址、数据库配置等;目的是提高脚本的可移植性和可维护性 6 | - 如果存在多个测试环境,只需增加多个对应环境的配置文件(键名一致),执行时只需选择对应名称的配置文件即可 7 | 8 | ### 1.2、使用说明 9 | - 在`JMeter根目录`下新建`config`文件夹 10 | - 在`config文件夹`下新建`.yaml`配置文件 11 | - 在`.yaml`配置文件中添加变量键值对,e.g.: 12 | ```yaml 13 | # uat.yaml 14 | http.host: xxx.xxx 15 | http.port: 443 16 | db.url: jdbc 17 | db.username: xxx 18 | db.password: xxx 19 | ... 20 | ``` 21 | - 在`测试计划`下添加`环境变量配置器`(配置元件,与线程组同级,建议添加至线程组前面) 22 | - 在`环境变量配置器`中选择对应的配置文件 23 | - 在需要使用环境变量的地方通过占位符 `${keyName}`引用即可 24 | 25 | ### 1.3、截图 26 | ![EnvDataSet](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/EnvDataSet_001.png) 27 | 28 | 29 | ## 2、失败请求保存器(FailureResultSaver) 30 | ### 2.1、插件说明 31 | - 用于性能测试时,把失败的请求数据单独保存下来,方便定位问题 32 | 33 | ### 2.2、使用说明 34 | - 在`测试计划`下添加`失败请求保存器`(配置元件,与线程组同级,建议添加至线程组前面) 35 | - 脚本执行过程中,如果取样器断言失败,则会把取样器的请求和响应数据保存至指定路径的日志文件中 36 | 37 | ### 2.3、参数说明 38 | - `日志路径`: 自定义日志文件路径 39 | - `错误分类`: 仅适用于Json报文,通过JsonPath表达式获取ResponseData的错误码,把错误码分组为不同的日志文件,日志文件名称以错误码命名,目的是方便统计和分类错误类型,为空时不做分类 40 | - `排除指定错误`: 指定需要排除的错误码,如果ResponseData包含该错误码,则不输出至日志文件中 41 | 42 | ### 2.4、截图 43 | ![FailureResultSaver](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/FailureResultSaver_001.png) 44 | 45 | 46 | ## 3、HTTP请求头读取器(HTTPHeaderReader) 47 | ### 3.1、插件说明 48 | - `HTTP信息头管理器`的文件版,根据`.yaml`配置文件加载请求头 49 | - 当存在多个`HTTP请求头读取器`且有父子关系时,读取优先级和继承逻辑与`HTTP信息头管理器`一致,不同的是数据来源于配置文件 50 | - 开发该插件的目的是为了提高请求头的可复用性和可维护性,系统在迭代的过程中或多或少会增删改请求头,JMeter内建的`HTTP信息头管理器`只能在脚本中维护请求头,如果已经维护了大量的脚本,此时系统发展需要增删改请求头,那么修改脚本需要花费大量的时间,但是改用该插件后,只需修改配置文件即可轻松达到上述效果,节省大量时间和精力且不易出错 51 | 52 | ### 3.2、使用说明 53 | - 在`JMeter根目录`下新建`header`文件夹 54 | - 在`header文件夹`下新建`.yaml`请求头配置文件 55 | - 在`.yaml`配置文件中添加请求头键值对,e.g.: 56 | ```yaml 57 | # headers.yaml 58 | Accept-Language: zh-CN,zh; 59 | Content-Type: application/json;charset=UTF-8 60 | User-Agent: AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36 61 | ... 62 | ``` 63 | - 可在`测试计划`、`线程组`或`HTTP取样器`下添加`HTTP请求头读取器`(配置元件) 64 | - 在`HTTP请求头读取器`中选择对应的请求头配置文件 65 | - 在执行HTTP请求前会把所有请求头合并后添加至当前HTTP的请求头中 66 | 67 | ### 3.3、截图 68 | ![HTTPHeaderReader](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/HTTPHeaderReader_001.png) 69 | 70 | 71 | ## 4、SSH配置器(SSHConfiguration) 72 | ### 4.1、插件说明 73 | - 配置ssh,多用于内网跳板机端口转发,目的是本地直连跳板机后的内部服务 74 | 75 | ### 4.2、使用说明 76 | - 在`测试计划`下添加`SSH配置器`(配置元件,与线程组同级,建议添加至线程组前面) 77 | 78 | ### 4.3、参数说明 79 | - `SSH地址`: host:port 80 | - `SSH用户名称`: username 81 | - `SSH密码`: password 82 | - `启用本地端口转发`: true or false 83 | - `本地转发端口`: local port 84 | - `远程地址`: remote host:port 85 | 86 | ### 4.4、截图 87 | ![SSHConfiguration](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/SSHConfiguration_001.png) 88 | 89 | 90 | ## 5、数据遍历配置器(TraversalDataSet) 91 | ### 5.1、插件说明 92 | - 用于枚举遍历测试,完成遍历后线程自动停止线程 93 | 94 | ### 5.2、使用说明 95 | - 将`线程组`的`循环次数`设置为`永远`,当完成遍历后会自动停止,无需手动计算循环次数 96 | - 在`线程组`下添加`数据遍历配置器`(配置元件,建议排在线程组下的第一位) 97 | - 在使用枚举的地方把枚举值替换为自定义的`变量名称`的占位符 98 | 99 | ### 5.3、参数说明 100 | - `变量名称`: 变量名称,有多个时用“,”逗号分隔 101 | - `数据集`: 变量值,与CSV格式一致 102 | 103 | ### 5.4、截图 104 | ![TraversalDataSet001](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/TraversalDataSet_001.png) 105 | ![TraversalDataSet002](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/TraversalDataSet_002.png) 106 | ![TraversalDataSet003](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/TraversalDataSet_003.png) 107 | 108 | 109 | ## 6、空值遍历配置器(TraversalEmptyValue) 110 | ### 6.1、插件说明 111 | - 用于遍历报文字段做非空校验,完成遍历后线程自动停止线程 112 | 113 | ### 6.2、使用说明 114 | - 将`线程组`的`循环次数`设置为`永远`,当完成遍历后会自动停止,无需手动计算循环次数 115 | - 在`线程组`下添加`空值遍历配置器`(配置元件,建议排在线程组下的第一位) 116 | - 请求执行前会把当次请求报文赋值给`params`变量,单次空值校验预期结果赋值给`expression`变量,在脚本请求报文的位置使用函数`${__eval(${params})}`替换原来的请求内容,在请求断言处使用占位符`${expression}`替换原来的断言 117 | 118 | ### 6.3、参数说明 119 | - `空类型`: 空值是`null`还是`""` 120 | - `请求报文`: 请求报文 121 | - `预期结果`: 结果表达式 122 | 123 | ### 6.4、截图 124 | ![TraversalEmptyValue001](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/TraversalEmptyValue_001.png) 125 | ![TraversalEmptyValue002](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/TraversalEmptyValue_002.png) 126 | ![TraversalEmptyValue003](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/TraversalEmptyValue_003.png) 127 | -------------------------------------------------------------------------------- /configs/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jmeter-plugins 7 | jmeter-plugins 8 | 5.1.1-v3 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | configs 14 | 15 | 16 | 17 | jmeter-plugins 18 | common-util 19 | 20 | 21 | org.apache.jmeter 22 | ApacheJMeter_core 23 | 24 | 25 | org.apache.jmeter 26 | ApacheJMeter_components 27 | 28 | 29 | org.apache.jmeter 30 | ApacheJMeter_jdbc 31 | 32 | 33 | org.testng 34 | testng 35 | test 36 | 37 | 38 | com.jayway.jsonpath 39 | json-path 40 | 41 | 42 | com.jayway.jsonpath 43 | json-path-assert 44 | 45 | 46 | org.apache.logging.log4j 47 | log4j-core 48 | 49 | 50 | org.slf4j 51 | slf4j-api 52 | 53 | 54 | 55 | 56 | ${project.groupId}-${project.artifactId}-${project.version} 57 | 58 | -------------------------------------------------------------------------------- /configs/src/main/java/org/apache/jmeter/config/EnvDataSet.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.config; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.jmeter.common.cli.CliOptions; 5 | import org.apache.jmeter.common.utils.ExceptionUtil; 6 | import org.apache.jmeter.common.utils.YamlUtil; 7 | import org.apache.jmeter.engine.util.NoConfigMerge; 8 | import org.apache.jmeter.engine.util.NoThreadClone; 9 | import org.apache.jmeter.testelement.TestStateListener; 10 | import org.apache.jmeter.threads.JMeterContextService; 11 | import org.apache.jmeter.util.JMeterUtils; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.io.File; 16 | import java.io.FileNotFoundException; 17 | import java.io.UnsupportedEncodingException; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | /** 22 | * @author Kelvin.Ye 23 | * @date 2018-04-08 17:11 24 | */ 25 | public class EnvDataSet extends ConfigTestElement implements TestStateListener, NoThreadClone, NoConfigMerge { 26 | 27 | private static final Logger log = LoggerFactory.getLogger(EnvDataSet.class); 28 | 29 | public static final String CONFIG_NAME = "EnvDataSet.configName"; 30 | 31 | public EnvDataSet() { 32 | super(); 33 | } 34 | 35 | /** 36 | * 获取环境变量配置文件名称 37 | */ 38 | public String getConfigName() { 39 | String configName = JMeterUtils.getPropDefault(CliOptions.CONFIG_NAME, getPropertyAsString(CONFIG_NAME)); 40 | log.debug("configName:[ {} ]", configName); 41 | return configName; 42 | } 43 | 44 | /** 45 | * 获取环境变量配置文件路径 46 | */ 47 | public String getConfigPath() { 48 | String configPath = JMeterUtils.getJMeterHome() + File.separator + "config" + File.separator + getConfigName(); 49 | log.debug("configPath:[ {} ]", configPath); 50 | return configPath; 51 | } 52 | 53 | /** 54 | * 反序列化配置文件 55 | */ 56 | private Map getEnvironmentVariables(String filePath) { 57 | Map variables = new HashMap<>(); 58 | try { 59 | YamlUtil.parseYamlAsMap(filePath).forEach((key, value) ->{ 60 | if (StringUtils.isBlank(key)) { 61 | return; 62 | } 63 | if (value != null) { 64 | variables.put(key, value.toString()); 65 | } else { 66 | variables.put(key, ""); 67 | } 68 | }); 69 | } catch (FileNotFoundException | UnsupportedEncodingException e) { 70 | log.error(ExceptionUtil.getStackTrace(e)); 71 | } 72 | return variables; 73 | } 74 | 75 | @Override 76 | public void testStarted() { 77 | testStarted("localhost"); 78 | } 79 | 80 | @Override 81 | public void testStarted(String s) { 82 | // 添加配置文件名到JMeter变量中 83 | JMeterContextService.getContext().getVariables().put(CONFIG_NAME, getConfigName()); 84 | // 反序列化配置文件,添加所有配置变量到JMeter变量中 85 | Map envVariables = getEnvironmentVariables(getConfigPath()); 86 | envVariables.forEach((key, value) -> getThreadContext().getVariables().put(key, value)); 87 | } 88 | 89 | @Override 90 | public void testEnded() { 91 | testEnded("localhost"); 92 | } 93 | 94 | @Override 95 | public void testEnded(String s) { 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /configs/src/main/java/org/apache/jmeter/config/FailureResultSaver.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.config; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.jmeter.common.utils.FileUtil; 5 | import org.apache.jmeter.common.utils.TimeUtil; 6 | import org.apache.jmeter.samplers.SampleEvent; 7 | import org.apache.jmeter.samplers.SampleListener; 8 | import org.apache.jmeter.samplers.SampleResult; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.File; 13 | 14 | 15 | /** 16 | * @author Kelvin.Ye 17 | */ 18 | public class FailureResultSaver extends ConfigTestElement implements SampleListener { 19 | 20 | private static final Logger log = LoggerFactory.getLogger(FailureResultSaver.class); 21 | 22 | private static final String LINE_SEP = FileUtil.LINE_SEPARATOR; 23 | 24 | public static final String LOG_PATH = "FailureResultSaver.logPath"; 25 | 26 | public static final String ERROR_CLASSIFICATION = "FailureResultSaver.errorClassification"; 27 | 28 | public static final String EXCLUDE = "FailureResultSaver.exclude"; 29 | 30 | private File failureLog; 31 | 32 | private String getLogPath() { 33 | return getPropertyAsString(LOG_PATH); 34 | } 35 | 36 | private String getErrorClassification() { 37 | return getPropertyAsString(ERROR_CLASSIFICATION); 38 | } 39 | 40 | private String getExclude() { 41 | return getPropertyAsString(EXCLUDE); 42 | } 43 | 44 | private File getFailureLog() { 45 | String logPath = getLogPath(); 46 | log.debug("LogPath:[ {} ]", logPath); 47 | if (failureLog == null) { 48 | failureLog = new File(logPath); 49 | } 50 | return failureLog; 51 | } 52 | 53 | @Override 54 | public void sampleOccurred(SampleEvent e) { 55 | SampleResult result = e.getResult(); 56 | // sampler失败时记录测试数据到日志文件 57 | if (!result.isSuccessful()) { 58 | // 排除指定的错误请求数据 59 | if (isExclude(result)) { 60 | return; 61 | } 62 | String content = ""; 63 | // 判断是否在事务控制器下 64 | if (e.isTransactionSampleEvent()) { 65 | // 排除 not Generate parent sample(事务)的数据(事务的空数据) 66 | return; 67 | } else { 68 | // 判断 sampler是否为 Generate parent sample(事务) 69 | // 根据 SampleResult下是否存在 subResults 70 | SampleResult[] transactionResults = result.getSubResults(); 71 | // Generate parent sample(事务) 72 | if (transactionResults.length != 0) { 73 | for (SampleResult transactionResult : transactionResults) { 74 | if (!transactionResult.isSuccessful()) { 75 | content = getResultContent(transactionResult); 76 | break; // 跳出for循环 77 | } 78 | } 79 | } else { // 一般sampler 80 | content = getResultContent(result); 81 | } 82 | } 83 | outputFile(content); 84 | } 85 | } 86 | 87 | @Override 88 | public void sampleStarted(SampleEvent e) { 89 | } 90 | 91 | @Override 92 | public void sampleStopped(SampleEvent e) { 93 | } 94 | 95 | /** 96 | * 当前错误的请求是否为需要排除的指定请求 97 | */ 98 | private boolean isExclude(SampleResult result) { 99 | String excludeText = getExclude(); 100 | if (StringUtils.isNotBlank(excludeText)) { 101 | String responseData = result.getResponseDataAsString(); 102 | String[] excludes = excludeText.split(","); 103 | for (String exclude : excludes) { 104 | if (responseData.contains(exclude)) { 105 | return true; 106 | } 107 | } 108 | } 109 | return false; 110 | } 111 | 112 | private String getResultContent(SampleResult result) { 113 | StringBuffer resultContent = new StringBuffer(); 114 | resultContent 115 | .append("【Start Time】: ") 116 | .append(LINE_SEP) 117 | .append(TimeUtil.timestampToStrtime(result.getStartTime(), "yyyy.MM.dd HH:mm:ss")); 118 | 119 | String requestHeaders = result.getRequestHeaders(); 120 | if (requestHeaders != null && !requestHeaders.isEmpty()) { 121 | resultContent.append("【Request Headers】: ").append(LINE_SEP).append(requestHeaders); 122 | } 123 | 124 | resultContent.append("【Request Data】: ").append(LINE_SEP).append(result.getSamplerData()); 125 | 126 | String responseHeaders = result.getResponseHeaders(); 127 | if (responseHeaders != null && !responseHeaders.isEmpty()) { 128 | resultContent.append("【Response Headers】: ").append(LINE_SEP).append(responseHeaders); 129 | } 130 | 131 | resultContent.append("【Response Data】: ").append(LINE_SEP).append(result.getResponseDataAsString()).append(LINE_SEP); 132 | resultContent.append("【elapsed】: ").append(result.getEndTime() - result.getStartTime()).append(" ms") 133 | .append(LINE_SEP).append(LINE_SEP).append(LINE_SEP); 134 | return resultContent.toString(); 135 | } 136 | 137 | private void outputFile(String content) { 138 | // 根据错误码分类错误日志文件 139 | String errorClassification = getErrorClassification(); 140 | if (StringUtils.isNotBlank(errorClassification)) { 141 | File failureLog = getFailureLog(); 142 | String[] failureLogs = failureLog.getName().split("\\."); 143 | String logName = failureLogs[0] + "-" + errorClassification + "." + failureLogs[1]; 144 | String logPath = failureLog.getParent() + File.separator + logName; 145 | FileUtil.appendFile(logPath, content); 146 | } else { 147 | FileUtil.appendFile(getFailureLog(), content); 148 | } 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /configs/src/main/java/org/apache/jmeter/config/HTTPHeaderReader.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.config; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.jmeter.common.utils.ExceptionUtil; 5 | import org.apache.jmeter.common.utils.YamlUtil; 6 | import org.apache.jmeter.config.gui.HTTPHeaderReaderGui; 7 | import org.apache.jmeter.engine.util.ValueReplacer; 8 | import org.apache.jmeter.protocol.http.control.Header; 9 | import org.apache.jmeter.protocol.http.control.HeaderManager; 10 | import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; 11 | import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; 12 | import org.apache.jmeter.testelement.TestStateListener; 13 | import org.apache.jmeter.testelement.property.CollectionProperty; 14 | import org.apache.jmeter.util.JMeterUtils; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import java.io.File; 19 | import java.io.FileNotFoundException; 20 | import java.io.UnsupportedEncodingException; 21 | import java.lang.reflect.Field; 22 | import java.lang.reflect.Modifier; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | import java.util.Set; 26 | 27 | /** 28 | * HTTP请求头文件读取器 29 | * 30 | * @author Kelvin.Ye 31 | */ 32 | public class HTTPHeaderReader extends HeaderManager implements TestStateListener { 33 | 34 | private static final Logger log = LoggerFactory.getLogger(HTTPHeaderReader.class); 35 | 36 | public static final String HEADER_FILE_NAME = "HTTPHeaderReader.headerFileName"; 37 | 38 | private static final ValueReplacer REPLACER = new ValueReplacer(); 39 | 40 | private static boolean alreadyAddedGui; 41 | 42 | private boolean alreadyRead; 43 | 44 | public HTTPHeaderReader() { 45 | super(); 46 | } 47 | 48 | public void init() { 49 | if (!alreadyRead) { 50 | try { 51 | Map headerMap = getHeaderVariables(getHeaderFilePath()); 52 | for (Map.Entry entry : headerMap.entrySet()) { 53 | Header header = new Header(entry.getKey(), entry.getValue()); 54 | REPLACER.replaceValues(header); 55 | header.setRunningVersion(true); 56 | super.getHeaders().addItem(header); 57 | } 58 | alreadyRead = true; 59 | } catch (Exception e) { 60 | log.error(ExceptionUtil.getStackTrace(e)); 61 | } 62 | } 63 | } 64 | 65 | @Override 66 | public Object clone() { 67 | HTTPHeaderReader clone = (HTTPHeaderReader) super.clone(); 68 | clone.alreadyRead = alreadyRead; 69 | return clone; 70 | } 71 | 72 | @Override 73 | public CollectionProperty getHeaders() { 74 | init(); 75 | return super.getHeaders(); 76 | } 77 | 78 | public String getHeadersFileName() { 79 | return getPropertyAsString(HEADER_FILE_NAME); 80 | } 81 | 82 | public String getHeaderFilePath() { 83 | return JMeterUtils.getJMeterHome() + File.separator + "header" + File.separator + getHeadersFileName(); 84 | } 85 | 86 | /** 87 | * 反序列化配置文件 88 | */ 89 | private Map getHeaderVariables(String filePath) { 90 | Map variables = new HashMap<>(); 91 | try { 92 | YamlUtil.parseYamlAsMap(filePath).forEach((key, value) -> { 93 | if (StringUtils.isBlank(key)) { 94 | return; 95 | } 96 | if (value != null) { 97 | variables.put(key, value.toString()); 98 | } else { 99 | variables.put(key, ""); 100 | } 101 | }); 102 | } catch (FileNotFoundException | UnsupportedEncodingException e) { 103 | log.error(ExceptionUtil.getStackTrace(e)); 104 | } 105 | return variables; 106 | } 107 | 108 | @Override 109 | public void testStarted() { 110 | testStarted("localhost"); 111 | } 112 | 113 | @Override 114 | public void testStarted(String host) { 115 | if (!alreadyAddedGui) { 116 | try { 117 | modifyAppliableConfigClassesField(); 118 | alreadyAddedGui = true; 119 | } catch (Exception e) { 120 | log.error(ExceptionUtil.getStackTrace(e)); 121 | } 122 | } 123 | } 124 | 125 | @Override 126 | public void testEnded() { 127 | testEnded("localhost"); 128 | } 129 | 130 | @Override 131 | public void testEnded(String host) { 132 | alreadyAddedGui = false; 133 | } 134 | 135 | @Override 136 | public int replace(String regex, String replaceBy, boolean caseSensitive) { 137 | return 0; 138 | } 139 | 140 | /** 141 | * 修改HTTPSampler的APPLIABLE_CONFIG_CLASSES属性 142 | */ 143 | @SuppressWarnings("unchecked") 144 | private void modifyAppliableConfigClassesField() throws NoSuchFieldException, IllegalAccessException { 145 | Class clazz = HTTPSamplerBase.class; 146 | HTTPSamplerProxy httpSampler = new HTTPSamplerProxy(); 147 | 148 | Field appliableConfigClassesField = clazz.getDeclaredField("APPLIABLE_CONFIG_CLASSES"); 149 | appliableConfigClassesField.setAccessible(true); 150 | 151 | Field modifiers = Field.class.getDeclaredField("modifiers"); 152 | modifiers.setAccessible(true); 153 | modifiers.setInt(appliableConfigClassesField, appliableConfigClassesField.getModifiers() & ~Modifier.FINAL); 154 | 155 | Set APPLIABLE_CONFIG_CLASSES = (Set) appliableConfigClassesField.get(httpSampler); 156 | APPLIABLE_CONFIG_CLASSES.add(HTTPHeaderReaderGui.class.getName()); 157 | appliableConfigClassesField.set(httpSampler, APPLIABLE_CONFIG_CLASSES); 158 | 159 | modifiers.setInt(appliableConfigClassesField, appliableConfigClassesField.getModifiers() & ~Modifier.FINAL); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /configs/src/main/java/org/apache/jmeter/config/SSHConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.config; 2 | 3 | 4 | import com.jcraft.jsch.JSch; 5 | import com.jcraft.jsch.JSchException; 6 | import com.jcraft.jsch.Session; 7 | import org.apache.jmeter.common.utils.ExceptionUtil; 8 | import org.apache.jmeter.common.jmeter.JMeterVariablesUtil; 9 | import org.apache.jmeter.samplers.Interruptible; 10 | import org.apache.jmeter.testelement.TestStateListener; 11 | import org.apache.jmeter.util.JMeterUtils; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | /** 16 | * SSH配置器 17 | * 18 | * @author Kelvin.Ye 19 | */ 20 | public class SSHConfiguration extends ConfigTestElement implements TestStateListener, Interruptible { 21 | 22 | private static final Logger log = LoggerFactory.getLogger(SSHConfiguration.class); 23 | 24 | public static final String SSH_ADDRESS = "SSHConfiguration.address"; 25 | public static final String SSH_USER_NAME = "SSHConfiguration.userName"; 26 | public static final String SSH_PASSWORD = "SSHConfiguration.password"; 27 | public static final String SSH_PORT_FORWARDING = "SSHConfiguration.sshPortForwarding"; 28 | public static final String LOCAL_FORWARDING_PORT = "SSHConfiguration.localForwardingPort"; 29 | public static final String REMOTE_ADDRESS = "SSHConfiguration.remoteAddress"; 30 | 31 | 32 | private Session session; 33 | 34 | @Override 35 | public void testStarted() { 36 | testStarted("local"); 37 | } 38 | 39 | /** 40 | * 测试开始时,ssh连接跳板机做本地端口转发 41 | */ 42 | @Override 43 | public void testStarted(String s) { 44 | try { 45 | if (isSSHPortForwarding()) { 46 | log.info("开始端口转发"); 47 | String username = getSSHUserName(); 48 | String password = getSSHPassword(); 49 | int localForwardingPort = getLocalForwardingPort(); 50 | 51 | // 拆分ssh address 52 | String[] sshAddres = getSSHAddress().split(":"); 53 | String sshHost = sshAddres[0]; 54 | int sshPort = Integer.parseInt(sshAddres.length == 1 ? "22" : sshAddres[1]); 55 | 56 | // 拆分remote address 57 | String[] remoteAddres = getRemoteAddress().split(":"); 58 | String remoteHost = remoteAddres[0]; 59 | int remotePort = Integer.parseInt(remoteAddres.length == 1 ? "22" : remoteAddres[1]); 60 | 61 | // ssh连接 62 | JSch jsch = new JSch(); 63 | session = jsch.getSession(username, sshHost, sshPort); 64 | session.setPassword(password); 65 | session.setConfig("StrictHostKeyChecking", "no"); 66 | 67 | // 本地端口转发 68 | log.info("本地转发端口={}", localForwardingPort); 69 | session.setPortForwardingL(localForwardingPort, remoteHost, remotePort); 70 | session.connect(); 71 | } 72 | } catch (Exception e) { 73 | log.error(ExceptionUtil.getStackTrace(e)); 74 | } 75 | 76 | } 77 | 78 | @Override 79 | public void testEnded() { 80 | testEnded("localhost"); 81 | } 82 | 83 | /** 84 | * 测试结束前删除本地转发的端口 85 | */ 86 | @Override 87 | public void testEnded(String s) { 88 | if (isSSHPortForwarding() && session != null) { 89 | disconnect(); 90 | } 91 | } 92 | 93 | private void disconnect() { 94 | try { 95 | int localForwardingPort = getLocalForwardingPort(); 96 | log.info("停用端口转发,端口号={}", localForwardingPort); 97 | session.delPortForwardingL(localForwardingPort); 98 | } catch (JSchException e) { 99 | log.error(ExceptionUtil.getStackTrace(e)); 100 | } finally { 101 | if (session != null) { 102 | session.disconnect(); 103 | } 104 | } 105 | } 106 | 107 | private String getSSHAddress() { 108 | return getPropertyAsString(SSH_ADDRESS); 109 | } 110 | 111 | private String getSSHUserName() { 112 | return getPropertyAsString(SSH_USER_NAME); 113 | } 114 | 115 | private String getSSHPassword() { 116 | return getPropertyAsString(SSH_PASSWORD); 117 | } 118 | 119 | private int getLocalForwardingPort() { 120 | return getPropertyAsInt(LOCAL_FORWARDING_PORT, 22); 121 | } 122 | 123 | private String getRemoteAddress() { 124 | return getPropertyAsString(REMOTE_ADDRESS); 125 | } 126 | 127 | private boolean isSSHPortForwarding() { 128 | return JMeterUtils.getPropDefault( 129 | "sshPortForwarding", JMeterVariablesUtil.getDefaultAsBoolean(SSH_PORT_FORWARDING, false)); 130 | } 131 | 132 | @Override 133 | public boolean interrupt() { 134 | testEnded(); 135 | return false; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /configs/src/main/java/org/apache/jmeter/config/ScriptArgumentsDescriptor.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.config; 2 | 3 | import org.apache.jmeter.engine.util.CompoundVariable; 4 | import org.apache.jmeter.testelement.TestStateListener; 5 | import org.apache.jmeter.testelement.property.JMeterProperty; 6 | import org.apache.jmeter.threads.JMeterContextService; 7 | import org.apache.jmeter.threads.JMeterVariables; 8 | 9 | /** 10 | * @author Kelvin.Ye 11 | * @date 2021-05-13 00:20 12 | */ 13 | public class ScriptArgumentsDescriptor extends Arguments implements TestStateListener { 14 | @Override 15 | public void testStarted() { 16 | testStarted("localhost"); 17 | } 18 | 19 | @Override 20 | public void testStarted(String host) { 21 | JMeterVariables variables = JMeterContextService.getContext().getVariables(); 22 | 23 | for (JMeterProperty prop : this) { 24 | Argument arg = (Argument) prop.getObjectValue(); 25 | Object value = arg.getProperty(Argument.VALUE).getObjectValue(); 26 | if (value instanceof CompoundVariable) { 27 | variables.put(arg.getName(), ((CompoundVariable) value).execute()); 28 | } 29 | } 30 | } 31 | 32 | @Override 33 | public void testEnded() { 34 | testEnded("localhost"); 35 | } 36 | 37 | @Override 38 | public void testEnded(String host) { 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /configs/src/main/java/org/apache/jmeter/config/TraversalDataSet.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.config; 2 | 3 | import org.apache.jmeter.engine.event.LoopIterationEvent; 4 | import org.apache.jmeter.engine.event.LoopIterationListener; 5 | import org.apache.jmeter.engine.util.NoConfigMerge; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.Arrays; 10 | import java.util.Iterator; 11 | import java.util.List; 12 | 13 | /** 14 | * @author Kelvin.Ye 15 | * @date 2018-04-17 11:10 16 | */ 17 | public class TraversalDataSet extends ConfigTestElement implements LoopIterationListener, NoConfigMerge { 18 | 19 | private static final Logger log = LoggerFactory.getLogger(TraversalDataSet.class); 20 | 21 | public static final String VARIABLE_NAMES = "TraversalDataSet.variableNames"; 22 | 23 | public static final String DATA_SET = "TraversalDataSet.dataSet"; 24 | 25 | private String[] varNames = null; 26 | 27 | private int varNamesLength = 0; 28 | 29 | private Iterator lineIter = null; 30 | 31 | 32 | @Override 33 | public void iterationStart(LoopIterationEvent event) { 34 | Iterator iter = getLineIter(); 35 | if (iter.hasNext()) { 36 | putVarsFromLine(iter.next()); 37 | } else { 38 | log.info("CSV Data循环结束,线程组停止循环"); 39 | getThreadContext().getThreadGroup().stop(); 40 | } 41 | 42 | } 43 | 44 | private void putVarsFromLine(String line) { 45 | String[] lineDatas = line.split(","); 46 | for (int i = 0; i < getVarNamesLength(); i++) { 47 | getThreadContext().getVariables().put(getVarNames()[i], lineDatas[i]); 48 | } 49 | } 50 | 51 | private Iterator getLineIter() { 52 | if (lineIter == null) { 53 | lineIter = readData().iterator(); 54 | } 55 | return lineIter; 56 | } 57 | 58 | private String[] getVarNames() { 59 | if (varNames == null) { 60 | varNames = getVariableNames().split(","); 61 | } 62 | return varNames; 63 | } 64 | 65 | private int getVarNamesLength() { 66 | if (varNamesLength == 0) { 67 | varNamesLength = getVarNames().length; 68 | } 69 | return varNamesLength; 70 | } 71 | 72 | private List readData() { 73 | return Arrays.asList(getData().split("\n")); 74 | } 75 | 76 | public String getVariableNames() { 77 | return getPropertyAsString(TraversalDataSet.VARIABLE_NAMES); 78 | } 79 | 80 | public String getData() { 81 | return getPropertyAsString(TraversalDataSet.DATA_SET); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /configs/src/main/java/org/apache/jmeter/config/TraversalEmptyValue.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.config; 2 | 3 | import com.jayway.jsonpath.DocumentContext; 4 | import com.jayway.jsonpath.JsonPath; 5 | import org.apache.jmeter.common.utils.ExceptionUtil; 6 | import org.apache.jmeter.common.json.JsonPathUtil; 7 | import org.apache.jmeter.common.json.JsonUtil; 8 | import org.apache.jmeter.engine.event.LoopIterationEvent; 9 | import org.apache.jmeter.engine.event.LoopIterationListener; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.IOException; 14 | import java.util.Iterator; 15 | 16 | /** 17 | * Json报文自动遍历非空校验 18 | * @author Kelvin.Ye 19 | * Date: 2018-04-17 20 | * Time: 11:10 21 | */ 22 | public class TraversalEmptyValue extends ConfigTestElement implements LoopIterationListener { 23 | 24 | private static final Logger log = LoggerFactory.getLogger(TraversalEmptyValue.class); 25 | 26 | public static final String BLANK_TYPE = "TraversalEmptyValue.blankType"; 27 | public static final String PATAMS = "TraversalEmptyValue.patams"; 28 | public static final String EMPTY_CHECK_EXPRESSION = "TraversalEmptyValue.emptyCheckExpression"; 29 | 30 | private Iterator jsonPathIterator = null; 31 | 32 | 33 | @Override 34 | public void iterationStart(LoopIterationEvent event) { 35 | setVariables(); 36 | } 37 | 38 | 39 | /** 40 | * 循环获取jsonPath、expression 和 params,并放入vars变量中 41 | */ 42 | private void setVariables() { 43 | Iterator iter = getJsonPathIterator(); 44 | if (iter.hasNext()) { 45 | String jsonPath = iter.next(); 46 | Object isSuccess = JsonPath.read(JsonUtil.toArrayJson(getEmptyCheckExpection()), jsonPath); 47 | if (isSuccess instanceof Boolean) { 48 | String expection = String.valueOf(isSuccess); 49 | getThreadContext().getVariables().put("jsonPath", jsonPath); 50 | getThreadContext().getVariables().put("expression", expection); 51 | putParams(jsonPath); 52 | } else { 53 | setVariables(); 54 | } 55 | } else { 56 | log.info("Traverse Empty Check循环结束,线程组停止"); 57 | getThreadContext().getThreadGroup().stop(); 58 | } 59 | } 60 | 61 | /** 62 | * 根据JsonPath更新对应的值为 null或 "",然后将json字符串并放入变量名为params的jmeter变量中 63 | */ 64 | private void putParams(String jsonPath) { 65 | try { 66 | DocumentContext ctx = JsonPathUtil.jsonParse(JsonUtil.toArrayJson(getPatams())); 67 | if ("null".equals(getBlankType())) { 68 | ctx.set(jsonPath, null); 69 | } else { 70 | ctx.set(jsonPath, ""); 71 | } 72 | String jsonStr = ctx.jsonString(); 73 | getThreadContext().getVariables().put("params", jsonStr.substring(1, jsonStr.length() - 1)); 74 | } catch (Exception e) { 75 | log.error(ExceptionUtil.getStackTrace(e)); 76 | } 77 | } 78 | 79 | /** 80 | * JsonPathList的迭代器 81 | */ 82 | private Iterator getJsonPathIterator() { 83 | if (jsonPathIterator == null) { 84 | jsonPathIterator = JsonPathUtil.getJsonPathList(JsonUtil.toArrayJson(getEmptyCheckExpection())).iterator(); 85 | } 86 | return jsonPathIterator; 87 | } 88 | 89 | /** 90 | * 获取脚本中的值 91 | */ 92 | public String getPatams() throws IOException { 93 | //使testEL元素只读,即不能参数化 94 | setRunningVersion(false); 95 | return getPropertyAsString(TraversalEmptyValue.PATAMS); 96 | } 97 | 98 | /** 99 | * 获取脚本中的值 100 | */ 101 | public String getEmptyCheckExpection() { 102 | //使testEL元素只读,即不能参数化 103 | setRunningVersion(false); 104 | return getPropertyAsString(TraversalEmptyValue.EMPTY_CHECK_EXPRESSION); 105 | } 106 | 107 | private String getBlankType() { 108 | return getPropertyAsString(BLANK_TYPE); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /configs/src/main/java/org/apache/jmeter/config/gui/FailureResultSaverGui.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.config.gui; 2 | 3 | import org.apache.jmeter.common.jmeter.JMeterGuiUtil; 4 | import org.apache.jmeter.config.FailureResultSaver; 5 | import org.apache.jmeter.gui.util.VerticalPanel; 6 | import org.apache.jmeter.testelement.TestElement; 7 | 8 | import javax.swing.*; 9 | import java.awt.*; 10 | 11 | 12 | /** 13 | * @author Kelvin.Ye 14 | */ 15 | public class FailureResultSaverGui extends AbstractConfigGui { 16 | 17 | private JTextField logPathTextField; 18 | private JTextField errorClassificationTextField; 19 | private JTextField excludeTextField; 20 | 21 | public FailureResultSaverGui() { 22 | init(); 23 | } 24 | 25 | private void init() { 26 | setLayout(new BorderLayout()); 27 | setBorder(makeBorder()); 28 | add(makeTitlePanel(), BorderLayout.NORTH); 29 | add(createBodyPanel(), BorderLayout.CENTER); 30 | } 31 | 32 | @Override 33 | public String getStaticLabel() { 34 | return "失败请求保存器"; 35 | } 36 | 37 | 38 | @Override 39 | public String getLabelResource() { 40 | return null; 41 | } 42 | 43 | 44 | @Override 45 | public TestElement createTestElement() { 46 | FailureResultSaver el = new FailureResultSaver(); 47 | modifyTestElement(el); 48 | return el; 49 | } 50 | 51 | /** 52 | * GUI -> TestElement 53 | */ 54 | @Override 55 | public void modifyTestElement(TestElement el) { 56 | super.configureTestElement(el); 57 | el.setProperty(FailureResultSaver.LOG_PATH, logPathTextField.getText()); 58 | el.setProperty(FailureResultSaver.ERROR_CLASSIFICATION, errorClassificationTextField.getText()); 59 | el.setProperty(FailureResultSaver.EXCLUDE, excludeTextField.getText()); 60 | } 61 | 62 | /** 63 | * TestElement -> GUI 64 | */ 65 | @Override 66 | public void configure(TestElement el) { 67 | super.configure(el); 68 | logPathTextField.setText(el.getPropertyAsString(FailureResultSaver.LOG_PATH)); 69 | errorClassificationTextField.setText(el.getPropertyAsString(FailureResultSaver.ERROR_CLASSIFICATION)); 70 | excludeTextField.setText(el.getPropertyAsString(FailureResultSaver.EXCLUDE)); 71 | } 72 | 73 | @Override 74 | public void clearGui() { 75 | super.clearGui(); 76 | logPathTextField.setText(""); 77 | errorClassificationTextField.setText(""); 78 | excludeTextField.setText(""); 79 | } 80 | 81 | private Component createLogPathTextField() { 82 | if (logPathTextField == null) { 83 | logPathTextField = JMeterGuiUtil.createTextField(FailureResultSaver.LOG_PATH); 84 | } 85 | return logPathTextField; 86 | } 87 | 88 | private Component createLogPathLabel() { 89 | return JMeterGuiUtil.createLabel("日志路径:", createLogPathTextField()); 90 | } 91 | 92 | private Component createErrorClassificationTextField() { 93 | if (errorClassificationTextField == null) { 94 | errorClassificationTextField = JMeterGuiUtil.createTextField(FailureResultSaver.ERROR_CLASSIFICATION); 95 | } 96 | return errorClassificationTextField; 97 | } 98 | 99 | private Component createErrorClassificationLabel() { 100 | return JMeterGuiUtil.createLabel("错误分类:", createErrorClassificationTextField()); 101 | } 102 | 103 | private Component createExcludeTextField() { 104 | if (excludeTextField == null) { 105 | excludeTextField = JMeterGuiUtil.createTextField(FailureResultSaver.EXCLUDE); 106 | } 107 | return excludeTextField; 108 | } 109 | 110 | private Component createExcludeLabel() { 111 | return JMeterGuiUtil.createLabel("排除指定错误(逗号分隔):", createExcludeTextField()); 112 | } 113 | 114 | private Component createBodyPanel() { 115 | JPanel bodyPanel = new JPanel(new GridBagLayout()); 116 | bodyPanel.setBorder(JMeterGuiUtil.createTitledBorder("配置错误日志信息")); 117 | bodyPanel.add(createLogPathLabel(), JMeterGuiUtil.GridBag.labelConstraints); 118 | bodyPanel.add(createLogPathTextField(), JMeterGuiUtil.GridBag.editorConstraints); 119 | bodyPanel.add(createErrorClassificationLabel(), JMeterGuiUtil.GridBag.labelConstraints); 120 | bodyPanel.add(createErrorClassificationTextField(), JMeterGuiUtil.GridBag.editorConstraints); 121 | bodyPanel.add(createExcludeLabel(), JMeterGuiUtil.GridBag.labelConstraints); 122 | bodyPanel.add(createExcludeTextField(), JMeterGuiUtil.GridBag.editorConstraints); 123 | 124 | VerticalPanel mainPanel = new VerticalPanel(); 125 | mainPanel.add(bodyPanel); 126 | return mainPanel; 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /configs/src/main/java/org/apache/jmeter/config/gui/ScriptArgumentsDescriptorGui.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.config.gui; 2 | 3 | import org.apache.jmeter.config.Argument; 4 | import org.apache.jmeter.config.ScriptArgumentsDescriptor; 5 | import org.apache.jmeter.testelement.TestElement; 6 | import org.apache.jorphan.gui.ObjectTableModel; 7 | import org.apache.jorphan.reflect.Functor; 8 | 9 | /** 10 | * @author Kelvin.Ye 11 | * @date 2021-05-13 00:20 12 | */ 13 | public class ScriptArgumentsDescriptorGui extends ArgumentsPanel { 14 | 15 | public ScriptArgumentsDescriptorGui() { 16 | super("定义脚本入参", null, true, true, null, false); 17 | } 18 | 19 | @Override 20 | public String getStaticLabel() { 21 | return "脚本参数描述器"; 22 | } 23 | 24 | @Override 25 | public String getLabelResource() { 26 | return null; 27 | } 28 | 29 | @Override 30 | public TestElement createTestElement() { 31 | ScriptArgumentsDescriptor args = new ScriptArgumentsDescriptor(); 32 | modifyTestElement(args); 33 | return args; 34 | } 35 | 36 | @Override 37 | protected void initializeTableModel() { 38 | if (tableModel == null) { 39 | tableModel = new ObjectTableModel(new String[]{"参数名称", "默认值", "参数描述"}, 40 | Argument.class, 41 | new Functor[]{ 42 | new Functor("getName"), 43 | new Functor("getValue"), 44 | new Functor("getDescription")}, 45 | new Functor[]{ 46 | new Functor("setName"), 47 | new Functor("setValue"), 48 | new Functor("setDescription")}, 49 | new Class[]{String.class, String.class, String.class}); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /configs/src/main/java/org/apache/jmeter/config/gui/TraversalDataSetGui.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.config.gui; 2 | 3 | import org.apache.jmeter.common.jmeter.JMeterGuiUtil; 4 | import org.apache.jmeter.config.TraversalDataSet; 5 | import org.apache.jmeter.gui.util.JSyntaxTextArea; 6 | import org.apache.jmeter.gui.util.JTextScrollPane; 7 | import org.apache.jmeter.testelement.TestElement; 8 | 9 | import javax.swing.*; 10 | import java.awt.*; 11 | 12 | /** 13 | * @author KelvinYe 14 | * @date 2018-04-17 11:10 15 | */ 16 | public class TraversalDataSetGui extends AbstractConfigGui { 17 | 18 | private JTextField variableNamesTextField; 19 | private JSyntaxTextArea dataTextArea; 20 | 21 | /** 22 | * 插件说明 23 | */ 24 | private static final String NOTE = 25 | "1、以 “,” 逗号作为引用名和数据的分隔符\n" + 26 | "2、请将线程组设置为无限循环,数据遍历完毕时线程组将自动停止循环"; 27 | 28 | public TraversalDataSetGui() { 29 | init(); 30 | } 31 | 32 | private void init() { 33 | setLayout(new BorderLayout()); 34 | setBorder(makeBorder()); 35 | add(makeTitlePanel(), BorderLayout.NORTH); 36 | add(createBodyPanel(), BorderLayout.CENTER); 37 | add(createNoteArea(), BorderLayout.SOUTH); 38 | } 39 | 40 | @Override 41 | public String getStaticLabel() { 42 | return "数据遍历配置器"; 43 | } 44 | 45 | 46 | @Override 47 | public String getLabelResource() { 48 | return null; 49 | } 50 | 51 | 52 | @Override 53 | public TestElement createTestElement() { 54 | TraversalDataSet dataSet = new TraversalDataSet(); 55 | modifyTestElement(dataSet); 56 | return dataSet; 57 | } 58 | 59 | /** 60 | * GUI -> TestElement 61 | */ 62 | @Override 63 | public void modifyTestElement(TestElement el) { 64 | super.configureTestElement(el); 65 | el.setProperty(TraversalDataSet.VARIABLE_NAMES, variableNamesTextField.getText()); 66 | el.setProperty(TraversalDataSet.DATA_SET, dataTextArea.getText()); 67 | } 68 | 69 | /** 70 | * TestElement -> GUI 71 | */ 72 | @Override 73 | public void configure(TestElement el) { 74 | super.configure(el); 75 | variableNamesTextField.setText(el.getPropertyAsString(TraversalDataSet.VARIABLE_NAMES)); 76 | dataTextArea.setInitialText(el.getPropertyAsString(TraversalDataSet.DATA_SET)); 77 | dataTextArea.setCaretPosition(0); 78 | } 79 | 80 | @Override 81 | public void clearGui() { 82 | super.clearGui(); 83 | variableNamesTextField.setText(""); 84 | dataTextArea.setInitialText(""); 85 | } 86 | 87 | private Component createVariableNamesTextField() { 88 | if (variableNamesTextField == null) { 89 | variableNamesTextField = JMeterGuiUtil.createTextField(TraversalDataSet.VARIABLE_NAMES); 90 | } 91 | return variableNamesTextField; 92 | } 93 | 94 | private Component createVariableNamesLabel() { 95 | return JMeterGuiUtil.createLabel("变量名称:", createVariableNamesTextField()); 96 | } 97 | 98 | private Component createDataTextArea() { 99 | if (dataTextArea == null) { 100 | dataTextArea = JMeterGuiUtil.createTextArea(TraversalDataSet.DATA_SET, 20); 101 | } 102 | return dataTextArea; 103 | } 104 | 105 | private Component createDataLabel() { 106 | return JMeterGuiUtil.createLabel("数据集:", createDataTextArea()); 107 | } 108 | 109 | 110 | private Component createDataPanel() { 111 | return JTextScrollPane.getInstance((JSyntaxTextArea) createDataTextArea()); 112 | } 113 | 114 | private Component createBodyPanel() { 115 | JPanel bodyPanel = new JPanel(new GridBagLayout()); 116 | bodyPanel.setBorder(JMeterGuiUtil.createTitledBorder("配置CSV数据")); 117 | 118 | bodyPanel.add(createVariableNamesLabel(), JMeterGuiUtil.GridBag.labelConstraints); 119 | bodyPanel.add(createVariableNamesTextField(), JMeterGuiUtil.GridBag.editorConstraints); 120 | 121 | bodyPanel.add(createDataLabel(), JMeterGuiUtil.GridBag.labelConstraints); 122 | bodyPanel.add(JMeterGuiUtil.createBlankPanel(), JMeterGuiUtil.GridBag.editorConstraints); 123 | bodyPanel.add(createDataPanel(), JMeterGuiUtil.GridBag.fillBottomConstraints); 124 | return bodyPanel; 125 | } 126 | 127 | private Component createNoteArea() { 128 | return JMeterGuiUtil.createNoteArea(NOTE, this.getBackground()); 129 | } 130 | 131 | } 132 | 133 | 134 | -------------------------------------------------------------------------------- /configs/src/main/java/org/apache/jmeter/config/gui/TraversalEmptyValueGui.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.config.gui; 2 | 3 | import org.apache.jmeter.common.jmeter.JMeterGuiUtil; 4 | import org.apache.jmeter.config.TraversalEmptyValue; 5 | import org.apache.jmeter.gui.util.JSyntaxTextArea; 6 | import org.apache.jmeter.gui.util.JTextScrollPane; 7 | import org.apache.jmeter.testelement.TestElement; 8 | 9 | import javax.swing.*; 10 | import java.awt.*; 11 | 12 | /** 13 | * @author Kelvin.Ye 14 | * @date 2018-04-17 11:10 15 | */ 16 | public class TraversalEmptyValueGui extends AbstractConfigGui { 17 | 18 | private JComboBox blankTypeComboBox; 19 | private JSyntaxTextArea paramsTextArea; 20 | private JSyntaxTextArea emptyCheckExpressionTextArea; 21 | 22 | /** 23 | * 插件说明 24 | */ 25 | private static final String NOTE = 26 | "1、请将线程组设置为无限循环,数据遍历完毕时线程组将自动停止循环\n" + 27 | "2、请求报文变量名=params,预期结果变量名=expression,当前 JsonPath变量名=jsonPath\n" + 28 | "3、该插件中数据引用变量或函数不会替换为具体的值,请在使用的位置利用 ${__eval(${params})} 函数替换"; 29 | 30 | public TraversalEmptyValueGui() { 31 | init(); 32 | } 33 | 34 | private void init() { 35 | setLayout(new BorderLayout()); 36 | setBorder(makeBorder()); 37 | add(makeTitlePanel(), BorderLayout.NORTH); 38 | add(createJTabbedPane(), BorderLayout.CENTER); 39 | } 40 | 41 | @Override 42 | public String getStaticLabel() { 43 | return "空值遍历配置器"; 44 | } 45 | 46 | 47 | @Override 48 | public String getLabelResource() { 49 | return null; 50 | } 51 | 52 | 53 | @Override 54 | public TestElement createTestElement() { 55 | TraversalEmptyValue dataSet = new TraversalEmptyValue(); 56 | modifyTestElement(dataSet); 57 | return dataSet; 58 | } 59 | 60 | /** 61 | * GUI -> TestElement 62 | */ 63 | @Override 64 | public void modifyTestElement(TestElement el) { 65 | super.configureTestElement(el); 66 | el.setProperty(TraversalEmptyValue.BLANK_TYPE, (String) blankTypeComboBox.getSelectedItem()); 67 | el.setProperty(TraversalEmptyValue.PATAMS, paramsTextArea.getText()); 68 | el.setProperty(TraversalEmptyValue.EMPTY_CHECK_EXPRESSION, emptyCheckExpressionTextArea.getText()); 69 | } 70 | 71 | /** 72 | * TestElement -> GUI 73 | */ 74 | @Override 75 | public void configure(TestElement el) { 76 | super.configure(el); 77 | blankTypeComboBox.setSelectedItem(el.getPropertyAsString(TraversalEmptyValue.BLANK_TYPE)); 78 | paramsTextArea.setInitialText(el.getPropertyAsString(TraversalEmptyValue.PATAMS)); 79 | paramsTextArea.setCaretPosition(0); 80 | emptyCheckExpressionTextArea.setInitialText(el.getPropertyAsString(TraversalEmptyValue.EMPTY_CHECK_EXPRESSION)); 81 | emptyCheckExpressionTextArea.setCaretPosition(0); 82 | } 83 | 84 | @Override 85 | public void clearGui() { 86 | super.clearGui(); 87 | blankTypeComboBox.setSelectedItem(""); 88 | paramsTextArea.setInitialText(""); 89 | emptyCheckExpressionTextArea.setInitialText(""); 90 | } 91 | 92 | private Component createBlankTypeComboBox() { 93 | if (blankTypeComboBox == null) { 94 | blankTypeComboBox = JMeterGuiUtil.createComboBox(TraversalEmptyValue.BLANK_TYPE); 95 | blankTypeComboBox.addItem("null"); 96 | blankTypeComboBox.addItem("\"\""); 97 | } 98 | return blankTypeComboBox; 99 | } 100 | 101 | private Component createBlankTypeLabel() { 102 | return JMeterGuiUtil.createLabel("空类型:", createBlankTypeComboBox()); 103 | } 104 | 105 | private Component createParamsTextArea() { 106 | if (paramsTextArea == null) { 107 | paramsTextArea = JMeterGuiUtil.createTextArea(TraversalEmptyValue.PATAMS, 20); 108 | } 109 | return paramsTextArea; 110 | } 111 | 112 | private Component createParamsLabel() { 113 | return JMeterGuiUtil.createLabel("请求报文:", createParamsTextArea()); 114 | } 115 | 116 | private Component createParamsPanel() { 117 | return JTextScrollPane.getInstance((JSyntaxTextArea) createParamsTextArea()); 118 | } 119 | 120 | private Component createEmptyCheckExpressionTextArea() { 121 | if (emptyCheckExpressionTextArea == null) { 122 | emptyCheckExpressionTextArea = JMeterGuiUtil.createTextArea(TraversalEmptyValue.EMPTY_CHECK_EXPRESSION, 20); 123 | } 124 | return emptyCheckExpressionTextArea; 125 | 126 | } 127 | 128 | private Component createEmptyCheckExpressionLabel() { 129 | return JMeterGuiUtil.createLabel("预期结果:", createEmptyCheckExpressionTextArea()); 130 | } 131 | 132 | private Component createEmptyCheckExpressionPanel() { 133 | return JTextScrollPane.getInstance((JSyntaxTextArea) createEmptyCheckExpressionTextArea()); 134 | } 135 | 136 | private Component createJTabbedPane() { 137 | JPanel interfacePanel = new JPanel(new GridBagLayout()); 138 | interfacePanel.setBorder(JMeterGuiUtil.createTitledBorder("配置非空校验信息")); 139 | interfacePanel.add(createBlankTypeLabel(), JMeterGuiUtil.GridBag.labelConstraints); 140 | interfacePanel.add(createBlankTypeComboBox(), JMeterGuiUtil.GridBag.editorConstraints); 141 | interfacePanel.add(createParamsLabel(), JMeterGuiUtil.GridBag.labelConstraints); 142 | interfacePanel.add(JMeterGuiUtil.createBlankPanel(), JMeterGuiUtil.GridBag.editorConstraints); 143 | interfacePanel.add(createParamsPanel(), JMeterGuiUtil.GridBag.multiLineEditorConstraints); 144 | interfacePanel.add(createEmptyCheckExpressionLabel(), JMeterGuiUtil.GridBag.labelConstraints); 145 | interfacePanel.add(JMeterGuiUtil.createBlankPanel(), JMeterGuiUtil.GridBag.editorConstraints); 146 | interfacePanel.add(createEmptyCheckExpressionPanel(), JMeterGuiUtil.GridBag.multiLineEditorConstraints); 147 | 148 | JTabbedPane tabbedPane = new JTabbedPane(); 149 | tabbedPane.add("非空校验配置", interfacePanel); 150 | tabbedPane.add("说明", createNoteArea()); 151 | 152 | return tabbedPane; 153 | } 154 | 155 | private Component createNoteArea() { 156 | return JMeterGuiUtil.createNoteArea(NOTE, this.getBackground()); 157 | } 158 | 159 | } 160 | 161 | 162 | -------------------------------------------------------------------------------- /docs/images/DubboTelnetSampler_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/DubboTelnetSampler_001.png -------------------------------------------------------------------------------- /docs/images/EnvDataSet_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/EnvDataSet_001.png -------------------------------------------------------------------------------- /docs/images/FailureResultSaver_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/FailureResultSaver_001.png -------------------------------------------------------------------------------- /docs/images/HTTPHeaderReader_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/HTTPHeaderReader_001.png -------------------------------------------------------------------------------- /docs/images/JMeterScriptSampler_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/JMeterScriptSampler_001.png -------------------------------------------------------------------------------- /docs/images/JMeter_Jenkins_Python_Interface_Automation_Testplan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/JMeter_Jenkins_Python_Interface_Automation_Testplan.png -------------------------------------------------------------------------------- /docs/images/LocalHtmlReport_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/LocalHtmlReport_001.png -------------------------------------------------------------------------------- /docs/images/LocalHtmlReport_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/LocalHtmlReport_002.png -------------------------------------------------------------------------------- /docs/images/LocalHtmlReport_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/LocalHtmlReport_003.png -------------------------------------------------------------------------------- /docs/images/LocalHtmlReport_004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/LocalHtmlReport_004.png -------------------------------------------------------------------------------- /docs/images/SSHConfiguration_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/SSHConfiguration_001.png -------------------------------------------------------------------------------- /docs/images/TraversalDataSet_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/TraversalDataSet_001.png -------------------------------------------------------------------------------- /docs/images/TraversalDataSet_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/TraversalDataSet_002.png -------------------------------------------------------------------------------- /docs/images/TraversalDataSet_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/TraversalDataSet_003.png -------------------------------------------------------------------------------- /docs/images/TraversalEmptyValue_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/TraversalEmptyValue_001.png -------------------------------------------------------------------------------- /docs/images/TraversalEmptyValue_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/TraversalEmptyValue_002.png -------------------------------------------------------------------------------- /docs/images/TraversalEmptyValue_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinye-lab/jmeter-plugins/2761772bfc1e951099415d2d30c39edca859b89d/docs/images/TraversalEmptyValue_003.png -------------------------------------------------------------------------------- /docs/常用函数.md: -------------------------------------------------------------------------------- 1 | # 常用函数 2 | ### 随机数 3 | ``` 4 | ${__time(yyyyMMddHHmmssSS)}${__Random(1,9999)} 5 | ``` 6 | 7 | ### 随机字符串 8 | ``` 9 | ${__RandomString(256,1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ)} 10 | ``` 11 | 12 | ### 当前时间 13 | ``` 14 | ${__time(yyyy-MM-dd HH:mm:ss)} 15 | ``` 16 | -------------------------------------------------------------------------------- /functions/README.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | ## 1、ExtractSQLValue 3 | ### 1.1、函数说明 4 | - 根据列名提取数据库表第一行的值 5 | 6 | ### 1.2、使用说明 7 | ``` 8 | /** 9 | * @param tableName 必填,表名 10 | * @param columnName 必填,列名 11 | * @param defaultValue 必填,默认值 12 | * @param variable 选填,存在时把结果存入vars变量中 13 | * 14 | * @return sql值 15 | */ 16 | ${__ExtractSQLValue(tableName, columnName, defaultValue, variable)} 17 | ``` 18 | 19 | 20 | ## 2、GoogleAuth 21 | ### 2.1、函数说明 22 | - 谷歌动态认证码 23 | 24 | ### 2.2、使用说明 25 | ``` 26 | /** 27 | * @param secretKey 必填,秘钥 28 | * 29 | * @return 认证码 30 | */ 31 | ${__GoogleAuth(secretKey)} 32 | ``` 33 | 34 | 35 | ## 3、JmeterHome 36 | ### 3.1、函数说明 37 | - 获取JMeter根目录的绝对路径 38 | - 如果有入参则把参数拼接在路径后 39 | 40 | ### 3.2、使用说明 41 | ``` 42 | ${__JmeterHome} 43 | ${__JmeterHome()} 44 | ${__JmeterHome(pathA)} 45 | ${__JmeterHome(pathA, pathB, fileName)} 46 | 47 | // 在BeanShell中引入bsh文件 48 | source("${__JmeterHome(beanshell, xxx.bsh)}"); 49 | ``` 50 | 51 | 52 | ## 4、MD5 53 | ### 4.1、函数说明 54 | - MD5加密 55 | 56 | ### 4.2、使用说明 57 | ``` 58 | /** 59 | * @param plaintext 必填,明文 60 | * @param md5Key 选填,秘钥 61 | * @param encode 选填,编码 62 | * @param variable 选填,存在时把结果存入vars变量中 63 | * 64 | * @return md5加密字符串 65 | */ 66 | ${__MD5(plaintext, md5Key, encode, variable)} 67 | ``` 68 | 69 | 70 | ## 5、ExtractPrevResponse 71 | ### 5.1、函数说明 72 | - 根据JsonPath表达式提取上一个SamplerResponse的Json值 73 | 74 | ### 5.2、使用说明 75 | ``` 76 | /** 77 | * @param jsonPath 必填,JsonPath表达式 78 | * @param variable 选填,存在时把结果存入vars变量中 79 | * 80 | * @return json值 81 | */ 82 | ${__ExtractPrevResponse(jsonPath, variable)} 83 | ``` 84 | 85 | 86 | ## 6、RIdCard 87 | ### 6.1、函数说明 88 | - 随机生成国内身份证号 89 | 90 | ### 6.2、使用说明 91 | ``` 92 | /** 93 | * @param variable 选填,存在时把结果存入vars变量中 94 | * 95 | * @return 随机国内身份证号 96 | */ 97 | ${__RIdCard(variable)} 98 | ``` 99 | 100 | 101 | ## 7、RMobile 102 | ### 7.1、函数说明 103 | - 随机生成国内手机号 104 | 105 | ### 7.2、使用说明 106 | ``` 107 | /** 108 | * @param variable 选填,存在时把结果存入vars变量中 109 | * 110 | * @return 随机国内手机号 111 | */ 112 | ${__RMobile()} 113 | ``` 114 | 115 | 116 | ## 8、RNumber 117 | ### 8.1、函数说明 118 | - 随机生成数字 119 | 120 | ### 8.2、使用说明 121 | ``` 122 | /** 123 | * @param pattern 必填,随机数长度或随机数生成规则 124 | * @param variable 选填,存在时把结果存入vars变量中 125 | * 126 | * @return 随机数 127 | */ 128 | ${__RNumber(pattern, variable)} 129 | ${__RNumber(8, variable)} // 123456789 130 | ${__RNumber(str:8)} // str12345678 131 | ${__RNumber(8:str)} // 12345678str 132 | ${__RNumber(str:8:str)} // str12345678str 133 | ${__RNumber(8:str:8)} // 12345678str12345678 134 | ${__RNumber(\8:8)} // 812345678 135 | ``` 136 | 137 | 138 | ## 9、ScriptAbsPath 139 | ### 9.1、函数说明 140 | - 获取JMeter脚本所在目录的绝对路径 141 | - 如果有入参则把参数拼接在路径后 142 | 143 | ### 9.2、使用说明 144 | ``` 145 | ${__ScriptAbsPath} 146 | ${__ScriptAbsPath()} 147 | ${__ScriptAbsPath(pathA)} 148 | ${__ScriptAbsPath(pathA, pathB, fileName)} 149 | ``` 150 | -------------------------------------------------------------------------------- /functions/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jmeter-plugins 7 | jmeter-plugins 8 | 5.1.1-v3 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | functions 14 | 15 | 16 | 17 | jmeter-plugins 18 | common-util 19 | 20 | 21 | org.apache.jmeter 22 | ApacheJMeter_core 23 | 24 | 25 | org.apache.jmeter 26 | ApacheJMeter_http 27 | 28 | 29 | org.apache.commons 30 | commons-lang3 31 | 32 | 33 | 34 | 35 | ${project.groupId}-${project.artifactId}-${project.version} 36 | 37 | -------------------------------------------------------------------------------- /functions/src/main/java/org/apache/jmeter/functions/ExtractPrevResponse.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.functions; 2 | 3 | import org.apache.jmeter.common.utils.ExceptionUtil; 4 | import org.apache.jmeter.common.json.JsonPathUtil; 5 | import org.apache.jmeter.engine.util.CompoundVariable; 6 | import org.apache.jmeter.samplers.SampleResult; 7 | import org.apache.jmeter.samplers.Sampler; 8 | import org.apache.jmeter.threads.JMeterContext; 9 | import org.apache.jmeter.threads.JMeterContextService; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.Collection; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | 17 | /** 18 | * 根据 jsonpath提取上一个 Sampler Response 19 | * 20 | * @author Kelvin.Ye 21 | * @date 2020-04-20 10:08 22 | */ 23 | public class ExtractPrevResponse extends AbstractFunction { 24 | 25 | private static final Logger log = LoggerFactory.getLogger(ExtractPrevResponse.class); 26 | 27 | private static final List DESC = new LinkedList<>(); 28 | 29 | static { 30 | DESC.add("根据JsonPath表达式提取上一个SamplerResponse的Json值"); 31 | } 32 | 33 | private static final String KEY = "__ExtractPrevResponse"; 34 | 35 | private CompoundVariable jsonPath = null; 36 | private CompoundVariable variable = null; 37 | 38 | 39 | @Override 40 | public String getReferenceKey() { 41 | return KEY; 42 | } 43 | 44 | @Override 45 | public List getArgumentDesc() { 46 | return DESC; 47 | } 48 | 49 | @Override 50 | public void setParameters(Collection parameters) throws InvalidVariableException { 51 | checkParameterCount(parameters, 1, 2); 52 | 53 | Object[] params = parameters.toArray(); 54 | int count = params.length; 55 | 56 | if (count > 0) { 57 | jsonPath = (CompoundVariable) params[0]; 58 | } 59 | 60 | if (count > 1) { 61 | variable = (CompoundVariable) params[1]; 62 | } 63 | } 64 | 65 | @Override 66 | public String execute(SampleResult sampleResult, Sampler sampler) { 67 | String result = ""; 68 | try { 69 | JMeterContext context = JMeterContextService.getContext(); 70 | String previousResponse = context.getPreviousResult().getResponseDataAsString(); 71 | String jsonPath = this.jsonPath.execute().trim(); 72 | result = JsonPathUtil.extractAsString(previousResponse, jsonPath); 73 | 74 | if (this.variable != null) { 75 | String variable = this.variable.execute().trim(); 76 | context.getVariables().put(variable, result); 77 | } 78 | } catch (Exception e) { 79 | log.error(ExceptionUtil.getStackTrace(e)); 80 | } 81 | return result; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /functions/src/main/java/org/apache/jmeter/functions/ExtractSQLValue.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.functions; 2 | 3 | import org.apache.jmeter.common.utils.ExceptionUtil; 4 | import org.apache.jmeter.engine.util.CompoundVariable; 5 | import org.apache.jmeter.samplers.SampleResult; 6 | import org.apache.jmeter.samplers.Sampler; 7 | import org.apache.jmeter.threads.JMeterContextService; 8 | import org.apache.jmeter.threads.JMeterVariables; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.Collection; 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | /** 18 | * @author Kelvin.Ye 19 | * @date 2020-04-20 19:38 20 | */ 21 | public class ExtractSQLValue extends AbstractFunction { 22 | 23 | private static final Logger log = LoggerFactory.getLogger(ExtractSQLValue.class); 24 | 25 | private static final List DESC = new LinkedList<>(); 26 | 27 | static { 28 | DESC.add("根据列名提取数据库表第一行的值"); 29 | } 30 | 31 | private static final String KEY = "__ExtractSQLValue"; 32 | 33 | private CompoundVariable tableName = null; 34 | private CompoundVariable columnName = null; 35 | private CompoundVariable defaultValue = null; 36 | private CompoundVariable variable = null; 37 | 38 | 39 | @Override 40 | public String getReferenceKey() { 41 | return KEY; 42 | } 43 | 44 | @Override 45 | public List getArgumentDesc() { 46 | return DESC; 47 | } 48 | 49 | @Override 50 | public void setParameters(Collection parameters) throws InvalidVariableException { 51 | checkParameterCount(parameters, 3, 4); 52 | 53 | Object[] params = parameters.toArray(); 54 | int count = params.length; 55 | 56 | tableName = (CompoundVariable) params[0]; 57 | columnName = (CompoundVariable) params[1]; 58 | defaultValue = (CompoundVariable) params[2]; 59 | 60 | 61 | if (count > 3) { 62 | variable = (CompoundVariable) params[3]; 63 | } 64 | } 65 | 66 | @Override 67 | public String execute(SampleResult sampleResult, Sampler sampler) { 68 | String result = ""; 69 | try { 70 | String tableName = this.tableName.execute().trim(); 71 | String columnName = this.columnName.execute().trim(); 72 | String defaultValue = this.defaultValue.execute().trim(); 73 | JMeterVariables variables = JMeterContextService.getContext().getVariables(); 74 | @SuppressWarnings("unchecked") 75 | String columnValue = String.valueOf( 76 | ((List>) variables.getObject(tableName)).get(0).get(columnName) 77 | ); 78 | if (columnValue == null || columnValue.isEmpty()) { 79 | result = defaultValue; 80 | } 81 | result = columnValue; 82 | 83 | if (this.variable != null) { 84 | String variable = this.variable.execute().trim(); 85 | variables.put(variable, result); 86 | } 87 | } catch (Exception e) { 88 | log.error(ExceptionUtil.getStackTrace(e)); 89 | } 90 | return result; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /functions/src/main/java/org/apache/jmeter/functions/GoogleAuth.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.functions; 2 | 3 | import org.apache.jmeter.common.utils.ExceptionUtil; 4 | import org.apache.jmeter.common.google.GoogleAuthenticator; 5 | import org.apache.jmeter.engine.util.CompoundVariable; 6 | import org.apache.jmeter.samplers.SampleResult; 7 | import org.apache.jmeter.samplers.Sampler; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.Collection; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | 15 | /** 16 | * 获取谷歌动态认证码 17 | * 18 | * @author Kelvin.Ye 19 | * @date 2019-06-19 19:42 20 | */ 21 | public class GoogleAuth extends AbstractFunction { 22 | 23 | private static final Logger log = LoggerFactory.getLogger(GoogleAuth.class); 24 | 25 | /** 26 | * 自定义function的描述 27 | */ 28 | private static final List DESC = new LinkedList<>(); 29 | 30 | static { 31 | DESC.add("谷歌动态认证码"); 32 | } 33 | 34 | /** 35 | * function名称 36 | */ 37 | private static final String KEY = "__GoogleAuth"; 38 | 39 | /** 40 | * function传入的参数 41 | */ 42 | private CompoundVariable secretKey; 43 | 44 | /** 45 | * function的引用关键字 46 | */ 47 | @Override 48 | public String getReferenceKey() { 49 | return KEY; 50 | } 51 | 52 | /** 53 | * function的描述 54 | */ 55 | @Override 56 | public List getArgumentDesc() { 57 | return DESC; 58 | } 59 | 60 | /** 61 | * 设置function的入参 62 | */ 63 | @Override 64 | public void setParameters(Collection parameters) throws InvalidVariableException { 65 | // 检查参数个数 66 | checkParameterCount(parameters, 1, 1); 67 | secretKey = (CompoundVariable) parameters.toArray()[0]; 68 | } 69 | 70 | /** 71 | * function的执行主体 72 | */ 73 | @Override 74 | public synchronized String execute(SampleResult sampleResult, Sampler sampler) { 75 | String result = ""; 76 | String secret = secretKey.execute().trim(); 77 | log.debug("Google Secret Key={}", secret); 78 | try { 79 | result = GoogleAuthenticator.getCode(secret); 80 | } catch (Exception e) { 81 | log.error(ExceptionUtil.getStackTrace(e)); 82 | } 83 | return result; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /functions/src/main/java/org/apache/jmeter/functions/JmeterHome.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.functions; 2 | 3 | import org.apache.commons.collections4.CollectionUtils; 4 | import org.apache.jmeter.common.utils.ExceptionUtil; 5 | import org.apache.jmeter.common.utils.PathUtil; 6 | import org.apache.jmeter.engine.util.CompoundVariable; 7 | import org.apache.jmeter.samplers.SampleResult; 8 | import org.apache.jmeter.samplers.Sampler; 9 | import org.apache.jmeter.util.JMeterUtils; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.Collection; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | 17 | /** 18 | * 获取JmeterHome 19 | * 20 | * @author Kelvin.Ye 21 | * @date 2018-08-22 17:11 22 | */ 23 | public class JmeterHome extends AbstractFunction { 24 | 25 | private static final Logger log = LoggerFactory.getLogger(JmeterHome.class); 26 | 27 | /** 28 | * 自定义function的描述 29 | */ 30 | private static final List DESC = new LinkedList<>(); 31 | 32 | static { 33 | DESC.add("获取JMeter所在目录路径"); 34 | } 35 | 36 | /** 37 | * function名称 38 | */ 39 | private static final String KEY = "__JmeterHome"; 40 | 41 | /** 42 | * function传入的参数的值 43 | */ 44 | private Collection parameters = null; 45 | 46 | /** 47 | * function引用关键字 48 | */ 49 | @Override 50 | public String getReferenceKey() { 51 | return KEY; 52 | } 53 | 54 | /** 55 | * function描述 56 | */ 57 | @Override 58 | public List getArgumentDesc() { 59 | return DESC; 60 | } 61 | 62 | /** 63 | * 设置function参数 64 | */ 65 | @Override 66 | public void setParameters(Collection parameters) { 67 | this.parameters = parameters; 68 | } 69 | 70 | /** 71 | * function执行 72 | */ 73 | @Override 74 | public String execute(SampleResult sampleResult, Sampler sampler) { 75 | String result = ""; 76 | try { 77 | String path = JMeterUtils.getJMeterHome(); 78 | if (CollectionUtils.isNotEmpty(parameters)) { 79 | for (CompoundVariable parameter : parameters) { 80 | String childPath = parameter.execute().trim(); 81 | path = PathUtil.join(path, childPath); 82 | } 83 | } 84 | result = path.replace("\\", "/"); 85 | } catch (Exception e) { 86 | log.error(ExceptionUtil.getStackTrace(e)); 87 | } 88 | return result; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /functions/src/main/java/org/apache/jmeter/functions/MD5.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.functions; 2 | 3 | import org.apache.commons.codec.digest.DigestUtils; 4 | import org.apache.jmeter.common.utils.ExceptionUtil; 5 | import org.apache.jmeter.engine.util.CompoundVariable; 6 | import org.apache.jmeter.samplers.SampleResult; 7 | import org.apache.jmeter.samplers.Sampler; 8 | import org.apache.jmeter.threads.JMeterContextService; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.nio.charset.Charset; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.Collection; 15 | import java.util.LinkedList; 16 | import java.util.List; 17 | 18 | /** 19 | * @author Kelvin.Ye 20 | * @date 2020-04-21 17:14 21 | */ 22 | public class MD5 extends AbstractFunction { 23 | 24 | private static final Logger log = LoggerFactory.getLogger(MD5.class); 25 | 26 | private static final List DESC = new LinkedList<>(); 27 | 28 | static { 29 | DESC.add("MD5加密"); 30 | } 31 | 32 | private static final String KEY = "__MD5"; 33 | 34 | 35 | private CompoundVariable plaintext = null; 36 | private CompoundVariable md5Key = null; 37 | private CompoundVariable encode = null; 38 | private CompoundVariable variable = null; 39 | 40 | 41 | @Override 42 | public String getReferenceKey() { 43 | return KEY; 44 | } 45 | 46 | @Override 47 | public List getArgumentDesc() { 48 | return DESC; 49 | } 50 | 51 | @Override 52 | public void setParameters(Collection parameters) throws InvalidVariableException { 53 | checkParameterCount(parameters, 1, 4); 54 | 55 | Object[] params = parameters.toArray(); 56 | int count = params.length; 57 | 58 | plaintext = (CompoundVariable) params[0]; 59 | 60 | if (count > 1) { 61 | md5Key = (CompoundVariable) params[1]; 62 | } 63 | if (count > 2) { 64 | encode = (CompoundVariable) params[2]; 65 | } 66 | if (count > 3) { 67 | variable = (CompoundVariable) params[3]; 68 | } 69 | } 70 | 71 | @Override 72 | public String execute(SampleResult sampleResult, Sampler sampler) { 73 | String result = ""; 74 | try { 75 | String data = this.plaintext.execute().trim(); 76 | Charset charset = StandardCharsets.UTF_8; 77 | 78 | if (this.md5Key != null) { 79 | data += this.md5Key.execute().trim(); 80 | } 81 | 82 | if (this.encode != null) { 83 | charset = Charset.forName(this.encode.execute().trim()); 84 | } 85 | 86 | result = DigestUtils.md5Hex(data.getBytes(charset)); 87 | 88 | if (this.variable != null) { 89 | String variable = this.variable.execute().trim(); 90 | JMeterContextService.getContext().getVariables().put(variable, result); 91 | } 92 | } catch (Exception e) { 93 | log.error(ExceptionUtil.getStackTrace(e)); 94 | } 95 | return result; 96 | } 97 | } -------------------------------------------------------------------------------- /functions/src/main/java/org/apache/jmeter/functions/RCambodiaMobile.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.functions; 2 | 3 | import org.apache.jmeter.common.utils.ExceptionUtil; 4 | import org.apache.jmeter.common.random.Randoms; 5 | import org.apache.jmeter.engine.util.CompoundVariable; 6 | import org.apache.jmeter.samplers.SampleResult; 7 | import org.apache.jmeter.samplers.Sampler; 8 | import org.apache.jmeter.threads.JMeterContextService; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.Collection; 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | 16 | /** 17 | * @author Kelvin.Ye 18 | * @date 2020-04-20 19:38 19 | */ 20 | public class RCambodiaMobile extends AbstractFunction { 21 | 22 | private static final Logger log = LoggerFactory.getLogger(RCambodiaMobile.class); 23 | 24 | private static final List DESC = new LinkedList<>(); 25 | 26 | static { 27 | DESC.add("随机柬埔寨手机号"); 28 | } 29 | 30 | private static final String KEY = "__RCambodiaMobile"; 31 | 32 | private CompoundVariable variable = null; 33 | 34 | 35 | @Override 36 | public String getReferenceKey() { 37 | return KEY; 38 | } 39 | 40 | @Override 41 | public List getArgumentDesc() { 42 | return DESC; 43 | } 44 | 45 | @Override 46 | public void setParameters(Collection parameters) throws InvalidVariableException { 47 | checkParameterCount(parameters, 0, 1); 48 | 49 | Object[] params = parameters.toArray(); 50 | int count = params.length; 51 | 52 | if (count > 0) { 53 | variable = (CompoundVariable) params[0]; 54 | } 55 | } 56 | 57 | @Override 58 | public String execute(SampleResult sampleResult, Sampler sampler) { 59 | String result = ""; 60 | try { 61 | result = Randoms.getCambodiaMobileNumber(); 62 | 63 | if (this.variable != null) { 64 | String variable = this.variable.execute().trim(); 65 | JMeterContextService.getContext().getVariables().put(variable, result); 66 | } 67 | } catch (Exception e) { 68 | log.error(ExceptionUtil.getStackTrace(e)); 69 | } 70 | return result; 71 | } 72 | } -------------------------------------------------------------------------------- /functions/src/main/java/org/apache/jmeter/functions/RIdCard.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.functions; 2 | 3 | import org.apache.jmeter.common.utils.ExceptionUtil; 4 | import org.apache.jmeter.common.random.Randoms; 5 | import org.apache.jmeter.engine.util.CompoundVariable; 6 | import org.apache.jmeter.samplers.SampleResult; 7 | import org.apache.jmeter.samplers.Sampler; 8 | import org.apache.jmeter.threads.JMeterContextService; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.Collection; 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | 16 | /** 17 | * @author Kelvin.Ye 18 | * @date 2020-04-20 19:38 19 | */ 20 | public class RIdCard extends AbstractFunction { 21 | 22 | private static final Logger log = LoggerFactory.getLogger(RIdCard.class); 23 | 24 | private static final List DESC = new LinkedList<>(); 25 | 26 | static { 27 | DESC.add("随机生成国内身份证号"); 28 | } 29 | 30 | private static final String KEY = "__RIdCard"; 31 | 32 | private CompoundVariable variable = null; 33 | 34 | 35 | @Override 36 | public String getReferenceKey() { 37 | return KEY; 38 | } 39 | 40 | @Override 41 | public List getArgumentDesc() { 42 | return DESC; 43 | } 44 | 45 | @Override 46 | public void setParameters(Collection parameters) throws InvalidVariableException { 47 | checkParameterCount(parameters, 0, 1); 48 | 49 | Object[] params = parameters.toArray(); 50 | int count = params.length; 51 | 52 | if (count > 0) { 53 | variable = (CompoundVariable) params[0]; 54 | } 55 | } 56 | 57 | @Override 58 | public String execute(SampleResult sampleResult, Sampler sampler) { 59 | String result = ""; 60 | try { 61 | result = Randoms.getIdCard(); 62 | 63 | if (this.variable != null) { 64 | String variable = this.variable.execute().trim(); 65 | JMeterContextService.getContext().getVariables().put(variable, result); 66 | } 67 | } catch (Exception e) { 68 | log.error(ExceptionUtil.getStackTrace(e)); 69 | } 70 | return result; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /functions/src/main/java/org/apache/jmeter/functions/RMobile.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.functions; 2 | 3 | import org.apache.jmeter.common.utils.ExceptionUtil; 4 | import org.apache.jmeter.common.random.Randoms; 5 | import org.apache.jmeter.engine.util.CompoundVariable; 6 | import org.apache.jmeter.samplers.SampleResult; 7 | import org.apache.jmeter.samplers.Sampler; 8 | import org.apache.jmeter.threads.JMeterContextService; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.Collection; 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | 16 | /** 17 | * @author Kelvin.Ye 18 | * @date 2020-04-20 19:38 19 | */ 20 | public class RMobile extends AbstractFunction { 21 | 22 | private static final Logger log = LoggerFactory.getLogger(RMobile.class); 23 | 24 | private static final List DESC = new LinkedList<>(); 25 | 26 | static { 27 | DESC.add("随机生成国内手机号"); 28 | } 29 | 30 | private static final String KEY = "__RMobile"; 31 | 32 | private CompoundVariable variable = null; 33 | 34 | 35 | @Override 36 | public String getReferenceKey() { 37 | return KEY; 38 | } 39 | 40 | @Override 41 | public List getArgumentDesc() { 42 | return DESC; 43 | } 44 | 45 | @Override 46 | public void setParameters(Collection parameters) throws InvalidVariableException { 47 | checkParameterCount(parameters, 0, 1); 48 | 49 | Object[] params = parameters.toArray(); 50 | int count = params.length; 51 | 52 | if (count > 0) { 53 | variable = (CompoundVariable) params[0]; 54 | } 55 | } 56 | 57 | @Override 58 | public String execute(SampleResult sampleResult, Sampler sampler) { 59 | String result = ""; 60 | try { 61 | result = Randoms.getMobileNumber(); 62 | 63 | if (this.variable != null) { 64 | String variable = this.variable.execute().trim(); 65 | JMeterContextService.getContext().getVariables().put(variable, result); 66 | } 67 | } catch (Exception e) { 68 | log.error(ExceptionUtil.getStackTrace(e)); 69 | } 70 | return result; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /functions/src/main/java/org/apache/jmeter/functions/RNumber.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.functions; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.jmeter.common.utils.ExceptionUtil; 5 | import org.apache.jmeter.common.random.Randoms; 6 | import org.apache.jmeter.engine.util.CompoundVariable; 7 | import org.apache.jmeter.samplers.SampleResult; 8 | import org.apache.jmeter.samplers.Sampler; 9 | import org.apache.jmeter.threads.JMeterContextService; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Collection; 15 | import java.util.LinkedList; 16 | import java.util.List; 17 | 18 | /** 19 | * @author Kelvin.Ye 20 | * @date 2020-04-20 19:38 21 | */ 22 | public class RNumber extends AbstractFunction { 23 | 24 | private static final Logger log = LoggerFactory.getLogger(RNumber.class); 25 | 26 | private static final List DESC = new LinkedList<>(); 27 | 28 | static { 29 | DESC.add("随机生成数字"); 30 | } 31 | 32 | private static final String KEY = "__RNumber"; 33 | 34 | private static final String ERROR_MSG = ( 35 | "格式错误,例如: ${__RNumber(8)}, ${__RNumber(str:8)}, ${__RNumber(8:str)}, ${__RNumber(str:8:str)}, " + 36 | "${__RNumber(8:str:8)}, ${__RNumber(\\8:8)}, ${__RNumber(8, variable)}" 37 | ); 38 | 39 | private CompoundVariable pattern = null; 40 | private CompoundVariable variable = null; 41 | 42 | 43 | @Override 44 | public String getReferenceKey() { 45 | return KEY; 46 | } 47 | 48 | @Override 49 | public List getArgumentDesc() { 50 | return DESC; 51 | } 52 | 53 | @Override 54 | public void setParameters(Collection parameters) throws InvalidVariableException { 55 | checkParameterCount(parameters, 0, 2); 56 | 57 | Object[] params = parameters.toArray(); 58 | int count = params.length; 59 | 60 | if (count > 0) { 61 | pattern = (CompoundVariable) params[0]; 62 | } 63 | if (count > 1) { 64 | variable = (CompoundVariable) params[1]; 65 | } 66 | } 67 | 68 | @Override 69 | public String execute(SampleResult sampleResult, Sampler sampler) { 70 | String result = ""; 71 | try { 72 | result = getNumber(); 73 | 74 | if (this.variable != null) { 75 | String variable = this.variable.execute().trim(); 76 | JMeterContextService.getContext().getVariables().put(variable, result); 77 | } 78 | } catch (Exception e) { 79 | log.error(ExceptionUtil.getStackTrace(e)); 80 | } 81 | return result; 82 | } 83 | 84 | private List getParams() throws InvalidVariableException { 85 | List params = new ArrayList<>(); 86 | String paramPattern = this.pattern.execute().trim(); 87 | String[] paramPatterns = paramPattern.split(":"); 88 | int parameterSize = paramPatterns.length; 89 | for (String pattern : paramPatterns) { 90 | if (parameterSize == 1) { 91 | if (StringUtils.isNumeric(pattern)) { 92 | params.add(Integer.parseInt(pattern)); 93 | break; 94 | } else { 95 | throw new InvalidVariableException(ERROR_MSG); 96 | } 97 | } 98 | 99 | if (StringUtils.isNumeric(pattern)) { 100 | if (pattern.startsWith("\\")) { 101 | params.add(pattern.substring(1)); 102 | } else { 103 | params.add(Integer.parseInt(pattern)); 104 | } 105 | } else { 106 | params.add(pattern); 107 | } 108 | } 109 | return params; 110 | } 111 | 112 | private String getNumber() throws InvalidVariableException { 113 | List params = getParams(); 114 | int parameterSize = params.size(); 115 | 116 | if (parameterSize > 3) { 117 | throw new InvalidVariableException(ERROR_MSG); 118 | } 119 | 120 | int intTypeCount = 0; 121 | for (Object param : params) { 122 | if (param instanceof Integer) { 123 | intTypeCount++; 124 | } 125 | } 126 | if (intTypeCount == 0) { 127 | throw new InvalidVariableException(ERROR_MSG); 128 | } 129 | 130 | if (parameterSize == 3) { 131 | if (params.get(0) instanceof String) { 132 | return Randoms.getNumber((String) params.get(0), (int) params.get(1), (String) params.get(2)); 133 | } else { 134 | return Randoms.getNumber((int) params.get(0), (String) params.get(1), (int) params.get(2)); 135 | } 136 | } 137 | 138 | if (parameterSize == 2) { 139 | if (params.get(0) instanceof String) { 140 | return Randoms.getNumber((String) params.get(0), (int) params.get(1)); 141 | } else { 142 | return Randoms.getNumber((int) params.get(0), (String) params.get(1)); 143 | } 144 | } 145 | 146 | return Randoms.getNumber((int) params.get(0)); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /functions/src/main/java/org/apache/jmeter/functions/ScriptAbsPath.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.functions; 2 | 3 | import org.apache.commons.collections4.CollectionUtils; 4 | import org.apache.jmeter.common.utils.ExceptionUtil; 5 | import org.apache.jmeter.common.utils.PathUtil; 6 | import org.apache.jmeter.engine.util.CompoundVariable; 7 | import org.apache.jmeter.samplers.SampleResult; 8 | import org.apache.jmeter.samplers.Sampler; 9 | import org.apache.jmeter.services.FileServer; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.Collection; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | 17 | /** 18 | * 获取JmeterHome 19 | * 20 | * @author Kelvin.Ye 21 | * @date 2018-08-22 17:11 22 | */ 23 | public class ScriptAbsPath extends AbstractFunction { 24 | 25 | private static final Logger log = LoggerFactory.getLogger(ScriptAbsPath.class); 26 | 27 | /** 28 | * 自定义function的描述 29 | */ 30 | private static final List DESC = new LinkedList<>(); 31 | 32 | static { 33 | DESC.add("获取JMeter脚本所在的目录路径"); 34 | } 35 | 36 | /** 37 | * function名称 38 | */ 39 | private static final String KEY = "__ScriptAbsPath"; 40 | 41 | /** 42 | * function传入的参数的值 43 | */ 44 | private Collection parameters = null; 45 | 46 | /** 47 | * function引用关键字 48 | */ 49 | @Override 50 | public String getReferenceKey() { 51 | return KEY; 52 | } 53 | 54 | /** 55 | * function描述 56 | */ 57 | @Override 58 | public List getArgumentDesc() { 59 | return DESC; 60 | } 61 | 62 | /** 63 | * 设置function参数 64 | */ 65 | @Override 66 | public void setParameters(Collection parameters) { 67 | this.parameters = parameters; 68 | } 69 | 70 | /** 71 | * function执行 72 | */ 73 | @Override 74 | public String execute(SampleResult sampleResult, Sampler sampler) { 75 | String result = ""; 76 | try { 77 | String scriptAbsPath = FileServer.getFileServer().getBaseDir(); 78 | if (CollectionUtils.isNotEmpty(parameters)) { 79 | for (CompoundVariable parameter : parameters) { 80 | String childPath = parameter.execute().trim(); 81 | scriptAbsPath = PathUtil.join(scriptAbsPath, childPath); 82 | } 83 | } 84 | result = scriptAbsPath.replace("\\", "/"); 85 | } catch (Exception e) { 86 | log.error(ExceptionUtil.getStackTrace(e)); 87 | } 88 | return result; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /infautomator/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jmeter-plugins 7 | jmeter-plugins 8 | 5.1.1-v3 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | infautomator 14 | 15 | 16 | 17 | jmeter-plugins 18 | common-util 19 | 20 | 21 | jmeter-plugins 22 | configs 23 | 24 | 25 | jmeter-plugins 26 | functions 27 | 28 | 29 | jmeter-plugins 30 | samplers 31 | 32 | 33 | jmeter-plugins 34 | visualizers 35 | 36 | 37 | 38 | 39 | ${project.groupId}-${project.artifactId}-${project.version} 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-shade-plugin 45 | 3.2.2 46 | 47 | 48 | package 49 | 50 | shade 51 | 52 | 53 | 54 | 55 | jmeter-plugins:common-util 56 | jmeter-plugins:configs 57 | jmeter-plugins:functions 58 | jmeter-plugins:samplers 59 | jmeter-plugins:visualizers 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /samplers/README.md: -------------------------------------------------------------------------------- 1 | # Samplers 2 | ## 1、DubboTelnet取样器(DubboTelnetSampler) 3 | ### 1.1、插件说明 4 | - Dubbo接口插件,通过Telnet方式调用Dubbo接口 5 | - Dubbo泛化调用推荐官方插件: `https://github.com/thubbo/jmeter-plugins-for-apache-dubbo` 6 | 7 | ### 1.2、使用说明 8 | - 在`线程组`下添加`DubboTelnet取样器` 9 | 10 | ### 1.3、参数说明 11 | - `服务器地址`: 必填,Dubbo服务地址 host:port 12 | - `接口名称`: 必填,接口名称 package.class.interface 13 | - `预期结果`: true | false 14 | - `字符编码`: 选填,默认utf-8 15 | - `请求报文`: 必填,json 16 | - `SSH`: 是否使用ssh端口转发 17 | 18 | ### 1.4、截图 19 | ![DubboTelnetSampler](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/DubboTelnetSampler_001.png) 20 | 21 | 22 | ## 2、JMeterScript取样器(JMeterScriptSampler) 23 | ### 2.1、插件说明 24 | - 可在当前脚本中执行指定位置的JMeter脚本并获取执行结果,当前脚本和目标脚本可传递变量 25 | 26 | ### 2.2、使用说明 27 | - 在`线程组`下添加`JMeterScript取样器` 28 | 29 | ### 2.3、参数说明 30 | - `脚本目录`: JMeter脚本所在目录路径 31 | - `脚本名称`: JMeter脚本名称 32 | - `同步增量vars至props`: 将目标脚本中新增的局部变量同步至全局变量中 33 | - `同步vars至目标脚本`: 将调用者的局部变量同步至目标脚本中(不会覆盖目标脚本中已存在的key),执行结束时将目标脚本新增的局部变量返回给调用者 34 | 35 | ### 2.4、截图 36 | ![JMeterScriptSampler](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/JMeterScriptSampler_001.png) 37 | -------------------------------------------------------------------------------- /samplers/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jmeter-plugins 7 | jmeter-plugins 8 | 5.1.1-v3 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | samplers 14 | 15 | 16 | 17 | jmeter-plugins 18 | common-util 19 | 20 | 21 | jmeter-plugins 22 | configs 23 | 24 | 25 | jmeter-plugins 26 | visualizers 27 | 28 | 29 | org.apache.jmeter 30 | ApacheJMeter_core 31 | 32 | 33 | org.apache.jmeter 34 | ApacheJMeter_java 35 | 36 | 37 | commons-net 38 | commons-net 39 | 40 | 41 | org.apache.logging.log4j 42 | log4j-api 43 | 44 | 45 | org.apache.logging.log4j 46 | log4j-core 47 | 48 | 49 | org.slf4j 50 | slf4j-api 51 | 52 | 53 | 54 | 55 | ${project.groupId}-${project.artifactId}-${project.version} 56 | 57 | 58 | src/main/resources 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /samplers/src/main/java/org/apache/jmeter/samplers/JMeterScriptDataForwarder.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.samplers; 2 | 3 | import org.apache.commons.collections4.CollectionUtils; 4 | import org.apache.jmeter.engine.util.NoThreadClone; 5 | import org.apache.jmeter.protocol.jdbc.config.DataSourceElement; 6 | import org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler; 7 | import org.apache.jmeter.testelement.AbstractTestElement; 8 | import org.apache.jmeter.testelement.ThreadListener; 9 | import org.apache.jmeter.threads.JMeterVariables; 10 | import org.apache.jmeter.util.JMeterUtils; 11 | import org.apache.jorphan.collections.ListedHashTree; 12 | import org.apache.jorphan.collections.SearchByClass; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Collection; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | import java.util.Properties; 21 | 22 | 23 | /** 24 | * @author Kelvin.Ye 25 | */ 26 | public class JMeterScriptDataForwarder extends AbstractTestElement 27 | implements ThreadListener, SampleListener, NoThreadClone { 28 | 29 | private static final Logger log = LoggerFactory.getLogger(JMeterScriptDataForwarder.class); 30 | 31 | public static final String CALLER_VARIABLES = "JMeterScriptDataTransfer.callerVariables"; 32 | public static final String INCREMENTAL_VARIABLES = "JMeterScriptDataTransfer.incrementalVariables"; 33 | 34 | private final Properties props = JMeterUtils.getJMeterProperties(); 35 | 36 | private Map clonedVars; 37 | 38 | private Map incrementalVariables; 39 | 40 | private SampleResult parentResult; 41 | 42 | public JMeterScriptDataForwarder(SampleResult parentResult) { 43 | super(); 44 | this.incrementalVariables = new HashMap<>(); 45 | this.clonedVars = new HashMap<>(); 46 | this.parentResult = parentResult; 47 | } 48 | 49 | @Override 50 | public void sampleOccurred(SampleEvent e) { 51 | SampleResult result = e.getResult(); 52 | parentResult.addSubResult(result, false); 53 | 54 | if (!result.isSuccessful()) { 55 | parentResult.setSuccessful(false); 56 | } 57 | 58 | // 获取 Sampler运行前后局部变量的差集 59 | Collection> differenceSet = getThreadVariablesDifferenceSet(); 60 | 61 | if (!differenceSet.isEmpty()) { 62 | // 存储差集 63 | differenceSet.forEach(entry -> incrementalVariables.put(entry.getKey(), entry.getValue())); 64 | // 删除不必要的key 65 | removeUnwantedKey(incrementalVariables); 66 | } 67 | 68 | } 69 | 70 | @Override 71 | public void sampleStarted(SampleEvent e) { 72 | } 73 | 74 | @Override 75 | public void sampleStopped(SampleEvent e) { 76 | } 77 | 78 | @Override 79 | public void threadStarted() { 80 | if (props.containsKey(CALLER_VARIABLES)) { 81 | // 把调用者的局部变量复制到当前线程的局部变量中,同名 key不覆盖 82 | JMeterVariables callerVars = (JMeterVariables) props.get(CALLER_VARIABLES); 83 | JMeterVariables currentVars = getThreadContext().getVariables(); 84 | 85 | // 获取调用者的局部变量和当前线程的局部变量的差集 86 | Collection> differenceSet = 87 | CollectionUtils.subtract(callerVars.entrySet(), currentVars.entrySet()); 88 | 89 | if (!differenceSet.isEmpty()) { 90 | differenceSet.forEach(e -> { 91 | if (e.getValue() instanceof String) { 92 | currentVars.put(e.getKey(), (String) e.getValue()); 93 | } else { 94 | currentVars.putObject(e.getKey(), e.getValue()); 95 | } 96 | }); 97 | } 98 | } 99 | 100 | // 克隆当前线程的局部变量作为副本 101 | getThreadContext().getVariables().entrySet().forEach(e -> clonedVars.put(e.getKey(), e.getValue())); 102 | } 103 | 104 | @Override 105 | public void threadFinished() { 106 | props.put(INCREMENTAL_VARIABLES, incrementalVariables); 107 | } 108 | 109 | private Collection> getThreadVariablesDifferenceSet() { 110 | return CollectionUtils.subtract(getThreadContext().getVariables().entrySet(), clonedVars.entrySet()); 111 | } 112 | 113 | /** 114 | * 获取JDBC Request中的 resultVariable变量名称,用于删除该变量 115 | */ 116 | private ArrayList getKeyNameInJDBCRequest(ListedHashTree testTree) { 117 | ArrayList jdbcKeyNameList = new ArrayList<>(); 118 | SearchByClass searcher = new SearchByClass<>(JDBCSampler.class); 119 | testTree.traverse(searcher); 120 | for (JDBCSampler jdbcSampler : searcher.getSearchResults()) { 121 | jdbcKeyNameList.add(jdbcSampler.getResultVariable()); 122 | } 123 | return jdbcKeyNameList; 124 | } 125 | 126 | /** 127 | * 获取JDBC DataSource中的 DataSource变量名称,用于删除该变量 128 | */ 129 | private ArrayList getKeyNameInJDBCDataSource(ListedHashTree testTree) { 130 | ArrayList jdbcDataSourceNameList = new ArrayList<>(); 131 | SearchByClass searcher = new SearchByClass<>(DataSourceElement.class); 132 | testTree.traverse(searcher); 133 | for (DataSourceElement sourceElement : searcher.getSearchResults()) { 134 | jdbcDataSourceNameList.add(sourceElement.getDataSource()); 135 | } 136 | return jdbcDataSourceNameList; 137 | } 138 | 139 | /** 140 | * 删除不需要的key 141 | */ 142 | private void removeUnwantedKey(Map givenMap) { 143 | givenMap.remove("START.MS"); 144 | givenMap.remove("START.YMD"); 145 | givenMap.remove("START.HMS"); 146 | givenMap.remove("TESTSTART.MS"); 147 | givenMap.remove("JMeterThread.pack"); 148 | givenMap.remove("__jm__" + getThreadContext().getThreadGroup().getName() + "__idx"); 149 | givenMap.remove("__jmeter.U_T__"); 150 | getKeyNameInJDBCRequest(getThreadContext().getThread().getTestTree()).forEach(givenMap::remove); 151 | getKeyNameInJDBCDataSource(getThreadContext().getThread().getTestTree()).forEach(givenMap::remove); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /samplers/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /visualizers/README.md: -------------------------------------------------------------------------------- 1 | # Visualizers 2 | ## 1、HTML报告(LocalHtmlReport) 3 | ### 1.1、插件说明 4 | - 收集所有sampler数据保存至html文件中 5 | - 批量执行多个脚本时可以把不同脚本的的数据保存至同一个html文件,方便查看所有测试数据 6 | 7 | ### 1.2、使用说明 8 | - 在`测试计划`下添加`HTML 报告`(监听器,与线程组同级,建议添加至线程组前面) 9 | 10 | ### 1.3、参数说明 11 | - `报告名称`: html文件名称 12 | - `追加写报告`: 是否追加写报告(文件名一致的前提下),如果选择true,每次写报告都添加在原报告的最后添加数据,如果选择false则每次都覆盖原报告 13 | 14 | ### 1.4、截图 15 | ![LocalHtmlReport001](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/LocalHtmlReport_001.png) 16 | ![LocalHtmlReport002](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/LocalHtmlReport_002.png) 17 | ![LocalHtmlReport003](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/LocalHtmlReport_003.png) 18 | ![LocalHtmlReport004](https://github.com/YeKelvin/jmeter-plugins/blob/master/docs/images/LocalHtmlReport_004.png) 19 | -------------------------------------------------------------------------------- /visualizers/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jmeter-plugins 7 | jmeter-plugins 8 | 5.1.1-v3 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | visualizers 14 | 15 | 16 | 17 | jmeter-plugins 18 | common-util 19 | 20 | 21 | org.apache.jmeter 22 | ApacheJMeter_core 23 | 24 | 25 | org.apache.jmeter 26 | ApacheJMeter_components 27 | 28 | 29 | org.freemarker 30 | freemarker 31 | 32 | 33 | org.jsoup 34 | jsoup 35 | 36 | 37 | org.projectlombok 38 | lombok 39 | 40 | 41 | com.jayway.jsonpath 42 | json-path 43 | 44 | 45 | 46 | 47 | ${project.groupId}-${project.artifactId}-${project.version} 48 | 49 | -------------------------------------------------------------------------------- /visualizers/src/main/java/org/apache/jmeter/visualizers/utils/FreemarkerUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.visualizers.utils; 2 | 3 | 4 | import freemarker.template.Configuration; 5 | import freemarker.template.Template; 6 | import freemarker.template.TemplateException; 7 | import org.apache.jmeter.common.utils.ExceptionUtil; 8 | import org.apache.jmeter.common.utils.FileUtil; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.BufferedWriter; 13 | import java.io.File; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.io.OutputStreamWriter; 17 | import java.nio.charset.StandardCharsets; 18 | import java.util.Map; 19 | 20 | /** 21 | * @author Kelvin.Ye 22 | * @date 2019-01-23 14:47 23 | */ 24 | public class FreemarkerUtil { 25 | 26 | private static final Logger log = LoggerFactory.getLogger(FreemarkerUtil.class); 27 | 28 | private static final String TEMPLATE_LOCATION = "template"; 29 | 30 | /** 31 | * 获取 freemarker html模版 32 | * 33 | * @param name 模版名称 34 | * @return Template对象 35 | * @throws IOException 文件不存在 36 | */ 37 | public static Template getTemplate(String name) throws IOException { 38 | // 通过Freemaker的Configuration读取相应的ftl 39 | Configuration cfg = new Configuration(Configuration.VERSION_2_3_28); 40 | // 设定去哪里读取相应的ftl模板文件 41 | cfg.setClassForTemplateLoading(FreemarkerUtil.class, TEMPLATE_LOCATION); 42 | // 在模板文件目录中找到名称为name的文件 43 | return cfg.getTemplate(name); 44 | } 45 | 46 | 47 | /** 48 | * 输出HTML文件 49 | */ 50 | public static void outputFile(String templateName, Map root, String outputFilePath) { 51 | try { 52 | File file = new File(outputFilePath); 53 | FileUtil.createParentDir(file); 54 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( 55 | new FileOutputStream(file, false), StandardCharsets.UTF_8)); 56 | Template temp = getTemplate(templateName); 57 | temp.process(root, bw); 58 | bw.flush(); 59 | bw.close(); 60 | } catch (IOException | TemplateException e) { 61 | log.error(ExceptionUtil.getStackTrace(e)); 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /visualizers/src/main/java/org/apache/jmeter/visualizers/utils/JavaScriptUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.visualizers.utils; 2 | 3 | import com.jayway.jsonpath.DocumentContext; 4 | import org.apache.jmeter.common.json.JsonPathUtil; 5 | import org.apache.jmeter.common.json.JsonUtil; 6 | import org.apache.jmeter.visualizers.vo.OverviewInfoVO; 7 | 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | /** 12 | * @author Kelvin.Ye 13 | * @date 2019-01-30 17:45 14 | */ 15 | public class JavaScriptUtil { 16 | 17 | private static final String TEST_SUITE_LIST_NAME = "testSuiteList: "; 18 | private static final String REPORT_INFO_NAME = "reportInfo: "; 19 | private static final String OVERVIEW_INFO_NAME = "overviewInfo: "; 20 | 21 | private static final String TEST_SUITE_LIST_VALUE_PATTERN = "testSuiteList: .*"; 22 | private static final String REPORT_INFO_VALUE_PATTERN = "reportInfo: .*"; 23 | private static final String OVERVIEW_INFO_VALUE_PATTERN = "overviewInfo: .*"; 24 | 25 | private static final Pattern TEST_SUITE_LIST_REGEX = Pattern.compile(TEST_SUITE_LIST_VALUE_PATTERN); 26 | private static final Pattern REPORT_INFO_REGEX = Pattern.compile(REPORT_INFO_VALUE_PATTERN); 27 | private static final Pattern OVERVIEW_INFO_REGEX = Pattern.compile(OVERVIEW_INFO_VALUE_PATTERN); 28 | 29 | /** 30 | * 提取js脚本中 testSuiteList的值 31 | * 32 | * @param jsContent js脚本 33 | * @return str 34 | */ 35 | public static String extractTestSuiteList(String jsContent) { 36 | Matcher matcher = TEST_SUITE_LIST_REGEX.matcher(jsContent); 37 | if (matcher.find()) { 38 | return matcher.group(0).substring(TEST_SUITE_LIST_NAME.length()); 39 | } 40 | return null; 41 | } 42 | 43 | /** 44 | * 提取js脚本中 reportInfo的值 45 | * 46 | * @param jsContent js脚本 47 | * @return str 48 | */ 49 | public static String extractReportInfo(String jsContent) { 50 | Matcher matcher = REPORT_INFO_REGEX.matcher(jsContent); 51 | if (matcher.find()) { 52 | String result = matcher.group(0); 53 | return result.substring(REPORT_INFO_NAME.length(), result.length() - 1); 54 | } 55 | return null; 56 | } 57 | 58 | /** 59 | * 提取js脚本中 overviewInfo的值 60 | * 61 | * @param jsContent js脚本 62 | * @return str 63 | */ 64 | public static String extractOverviewInfo(String jsContent) { 65 | Matcher matcher = OVERVIEW_INFO_REGEX.matcher(jsContent); 66 | if (matcher.find()) { 67 | String result = matcher.group(0); 68 | return result.substring(OVERVIEW_INFO_NAME.length(), result.length() - 1); 69 | } 70 | return null; 71 | } 72 | 73 | /** 74 | * 以替换文本的方式更新 js脚本中的 testSuiteList的值 75 | * 76 | * @param jsContent js脚本 77 | * @param testSuiteJson 新值 78 | * @return str 79 | */ 80 | public static String updateTestSuiteList(String jsContent, String testSuiteJson) { 81 | Matcher matcher = TEST_SUITE_LIST_REGEX.matcher(jsContent); 82 | return matcher.replaceAll(Matcher.quoteReplacement(TEST_SUITE_LIST_NAME + testSuiteJson)); 83 | } 84 | 85 | /** 86 | * 以替换文本的方式更新 js脚本中的 reportInfo的值 87 | * 88 | * @param jsContent js脚本 89 | * @param previousValue 旧值 90 | * @param lastUpdateTime 最后更新时间 91 | * @return str 92 | */ 93 | public static String updateReportInfo(String jsContent, String previousValue, Object lastUpdateTime) { 94 | String newValue = updateLastUpdateTime(previousValue, lastUpdateTime); 95 | Matcher matcher = REPORT_INFO_REGEX.matcher(jsContent); 96 | return matcher.replaceAll(Matcher.quoteReplacement(REPORT_INFO_NAME + newValue + ",")); 97 | } 98 | 99 | /** 100 | * 以替换文本的方式更新 js脚本中的 overviewInfo的值 101 | * 102 | * @param jsContent js脚本 103 | * @param previousValue 旧值 104 | * @param currentOverview 当前的 OverviewInfo对象 105 | * @return str 106 | */ 107 | public static String updateOverviewInfo(String jsContent, String previousValue, OverviewInfoVO currentOverview) { 108 | OverviewInfoVO overviewInfo = JsonUtil.fromJson(previousValue, OverviewInfoVO.class); 109 | overviewInfo.add(currentOverview); 110 | Matcher matcher = OVERVIEW_INFO_REGEX.matcher(jsContent); 111 | return matcher.replaceAll(Matcher.quoteReplacement(OVERVIEW_INFO_NAME + JsonUtil.toJson(overviewInfo) + ",")); 112 | } 113 | 114 | public static String updateOverviewInfo(String jsContent, OverviewInfoVO previousOverview, OverviewInfoVO currentOverview) { 115 | previousOverview.add(currentOverview); 116 | Matcher matcher = OVERVIEW_INFO_REGEX.matcher(jsContent); 117 | return matcher.replaceAll(Matcher.quoteReplacement(OVERVIEW_INFO_NAME + JsonUtil.toJson(previousOverview) + ",")); 118 | } 119 | 120 | /** 121 | * 更新 reportInfo中的lastUpdateTime的值 122 | * 123 | * @param reportInfo reportInfo的json串 124 | * @param lastUpdateTime String型的时间 125 | * @return str 126 | */ 127 | private static String updateLastUpdateTime(String reportInfo, Object lastUpdateTime) { 128 | DocumentContext ctx = JsonPathUtil.jsonParse(reportInfo); 129 | ctx.set("$.lastUpdateTime", lastUpdateTime); 130 | return ctx.jsonString(); 131 | } 132 | 133 | /** 134 | * 向 js脚本中的 testSuiteList的添加数据 135 | * 136 | * @param testSuiteList testSuiteList值 137 | * @param testSuite 新testSuite 138 | * @return str 139 | */ 140 | public static String appendTestSuiteList(String testSuiteList, Object testSuite) { 141 | DocumentContext ctx = JsonPathUtil.jsonParse(testSuiteList); 142 | ctx.add("$", testSuite); 143 | return ctx.jsonString(); 144 | } 145 | 146 | } 147 | 148 | -------------------------------------------------------------------------------- /visualizers/src/main/java/org/apache/jmeter/visualizers/utils/JsoupUtil.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.visualizers.utils; 2 | 3 | import org.jsoup.Jsoup; 4 | import org.jsoup.nodes.Document; 5 | import org.jsoup.select.Elements; 6 | 7 | import java.io.BufferedWriter; 8 | import java.io.File; 9 | import java.io.FileOutputStream; 10 | import java.io.IOException; 11 | import java.io.OutputStreamWriter; 12 | import java.nio.charset.StandardCharsets; 13 | 14 | /** 15 | * @author Kelvin.Ye 16 | * @date 2019-01-29 11:27 17 | */ 18 | public class JsoupUtil { 19 | 20 | /** 21 | * 获取 html文档对象 22 | * 23 | * @param filePath 文件路径 24 | * @return Document对象 25 | * @throws IOException 文件不存在 26 | */ 27 | public static Document getDocument(String filePath) throws IOException { 28 | return getDocument(new File(filePath)); 29 | } 30 | 31 | public static Document getDocument(File file) throws IOException { 32 | return Jsoup.parse(file, StandardCharsets.UTF_8.name()); 33 | } 34 | 35 | /** 36 | * 解析 HTML文件,获取最后一个