├── settings.gradle ├── long ├── 1 ├── poster.markdown └── sql │ ├── request.sql │ └── performance.sql ├── src └── main │ ├── resources │ ├── redis.properties │ ├── mysql.properties │ ├── http.properties │ └── log4j2.xml │ └── groovy │ └── com │ └── funtester │ ├── base │ ├── interfaces │ │ ├── ReturnCode.java │ │ ├── MarkRequest.java │ │ ├── MarkThread.java │ │ ├── IMessage.java │ │ ├── ISocketVerify.java │ │ ├── IMySqlBasic.java │ │ ├── ISocketClient.java │ │ └── IBase.java │ ├── exception │ │ ├── ParamException.java │ │ ├── LoginException.java │ │ ├── RequestException.java │ │ ├── FailException.java │ │ └── VerifyException.java │ ├── constaint │ │ ├── CaseBase.java │ │ ├── FixedQpsThread.java │ │ ├── ThreadLimitTimesCount.java │ │ ├── ThreadLimitTimeCount.java │ │ └── ThreadBase.java │ └── bean │ │ ├── AbstractBean.groovy │ │ ├── Result.groovy │ │ ├── PerformanceResultBean.groovy │ │ ├── RecordBean.groovy │ │ ├── VerifyBean.groovy │ │ └── RequestInfo.groovy │ ├── dubbo │ ├── DubboParamBase.groovy │ ├── DubboInvokeParams.groovy │ ├── DubboUtil.java │ └── DubboBase.java │ ├── utils │ ├── xml │ │ ├── NodeInfo.groovy │ │ ├── Attr.groovy │ │ ├── XMLUtil2.groovy │ │ └── XMLUtil.groovy │ ├── CountUtil.groovy │ ├── ArgsUtil.java │ ├── HeapDumper.java │ ├── Join.java │ ├── WriteHtml.java │ ├── request │ │ ├── RequestFile.java │ │ └── Swagger.java │ ├── FileUtil.groovy │ ├── message │ │ ├── EmailUtil.java │ │ └── AlertOver.java │ ├── CurlUtil.groovy │ ├── Regex.java │ ├── CMD.java │ ├── JsonUtil.groovy │ └── Time.java │ ├── main │ ├── ExecuteMethod.java │ └── PerformanceFromFile.groovy │ ├── config │ ├── EmailConstant.java │ ├── SysInit.java │ ├── SocketConstant.java │ ├── RequestType.java │ ├── VerifyType.groovy │ ├── SqlConstant.java │ ├── PropertyUtils.groovy │ ├── HttpClientConstant.java │ └── Constant.java │ ├── db │ ├── mysql │ │ ├── AidThread.java │ │ ├── MySqlObject.java │ │ ├── MySqlFun.java │ │ ├── SqlBase.java │ │ └── TestConnectionManage.java │ ├── mongodb │ │ ├── MongoObject.java │ │ └── MongoBase.java │ └── redis │ │ ├── RedisPool.java │ │ └── RedisUtil.java │ ├── frame │ ├── thread │ │ ├── UpdateSqlThread.java │ │ ├── FixedQpsParamMark.java │ │ ├── ParamMark.java │ │ ├── QuerySqlThread.java │ │ ├── RequestTimeFixedQps.java │ │ ├── RequestTimesFixedQps.java │ │ ├── FixedQpsHeaderMark.groovy │ │ ├── RequestThreadTime.java │ │ ├── RequestThreadTimes.java │ │ └── HeaderMark.java │ ├── execute │ │ ├── ThreadPoolUtil.groovy │ │ ├── ExecuteGroovy.java │ │ ├── Progress.java │ │ └── Concurrent.java │ └── Save.java │ ├── httpclient │ └── GCThread.java │ └── socket │ └── WebSocketFunClient.java ├── document └── directory.markdown └── readme.markdown /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'funtester' -------------------------------------------------------------------------------- /long/1: -------------------------------------------------------------------------------- 1 | id=324,23,4,234,2,4,32,4,23 2 | size1=9 3 | size2=5 4 | c=5,10,15 5 | a=3,6,9 6 | b=4,8,12 -------------------------------------------------------------------------------- /src/main/resources/redis.properties: -------------------------------------------------------------------------------- 1 | ip=58.87.70.151 2 | port=6379 3 | max_total=10 4 | max_idle=5 5 | min_idle=2 6 | max_wait=60000 7 | timeout=60000 -------------------------------------------------------------------------------- /src/main/resources/mysql.properties: -------------------------------------------------------------------------------- 1 | test_mysql_url=jdbc:mysql://172.18.4.55:3306/okayapi 2 | user=root 3 | password=dsjw2016 4 | mysql_server_path=http://172.18.4.55:8888/mysql 5 | flag=false -------------------------------------------------------------------------------- /document/directory.markdown: -------------------------------------------------------------------------------- 1 | * 由于文章链接存在敏感词问题,请各位看官移步 2 | 3 | ## [GitHub地址](https://github.com/JunManYuanLong/FunTester/blob/okay/document/article.markdown) 4 | 5 | 6 | ### 当然也可以关注**FunTester**公众号 -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/interfaces/ReturnCode.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.interfaces; 2 | 3 | public interface ReturnCode { 4 | 5 | int getCode(); 6 | 7 | String getDesc(); 8 | 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/http.properties: -------------------------------------------------------------------------------- 1 | ssl_v=TLSv1.2 2 | User-Agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36 3 | Connection=keep-alive 4 | black_host=wblçog.fv1314.xyz:8082,172.18.4.55:8888 5 | TIMEOUT=10 6 | TRY_TIMES=3 7 | MAX_ACCEPT_TIME=5 -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/dubbo/DubboParamBase.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.dubbo 2 | 3 | class DubboParamBase { 4 | // 5 | // String type 6 | // 7 | // Object value 8 | // 9 | // DubboParamBase(Class type, Object value) { 10 | // this.type = type.getTypeName() 11 | // this.value = value 12 | // } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/dubbo/DubboInvokeParams.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.dubbo 2 | 3 | 4 | class DubboInvokeParams { 5 | // 6 | // int length 7 | // 8 | // String[] types = new String[length] 9 | // 10 | // Object[] values = new Object[length] 11 | // 12 | // DubboInvokeParams(int length) { 13 | // this.length = length; 14 | // } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/xml/NodeInfo.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.utils.xml 2 | /** 3 | * 节点信息 4 | */ 5 | class NodeInfo { 6 | //class NodeInfo extends AbstractBean implements Serializable{ 7 | 8 | // private static final long serialVersionUID = 568896512159847L 9 | // 10 | // List attrs 11 | // 12 | // List children 13 | 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/interfaces/MarkRequest.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.interfaces; 2 | 3 | import org.apache.http.client.methods.HttpRequestBase; 4 | 5 | /** 6 | * 专门用来标记HTTP请求的接口 7 | */ 8 | public interface MarkRequest extends MarkThread { 9 | 10 | /** 11 | * 标记请求对象 12 | * 13 | * @param requestBase 14 | * @return 15 | */ 16 | public String mark(HttpRequestBase requestBase); 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/interfaces/MarkThread.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.interfaces; 2 | 3 | import com.funtester.base.constaint.ThreadBase; 4 | 5 | /** 6 | * 用来标记thread,为了记录超时的请求 7 | */ 8 | public interface MarkThread { 9 | 10 | /** 11 | * 标记线程任务 12 | * 13 | * @param threadBase 14 | * @return 15 | */ 16 | public String mark(ThreadBase threadBase); 17 | 18 | public MarkThread clone(); 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/xml/Attr.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.utils.xml 2 | /** 3 | * 节点属性信息 4 | */ 5 | class Attr { 6 | //class Attr extends AbstractBean implements Serializable{ 7 | 8 | // private static final long serialVersionUID = -35484487563215649L 9 | // 10 | // String name 11 | // 12 | // String value 13 | // 14 | // Attr(String name, String value) { 15 | // this.name = name 16 | // this.value = value 17 | // } 18 | // 19 | 20 | } -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/main/ExecuteMethod.java: -------------------------------------------------------------------------------- 1 | package com.funtester.main; 2 | 3 | import com.funtester.frame.SourceCode; 4 | import com.funtester.frame.execute.ExecuteSource; 5 | import org.apache.commons.lang3.ArrayUtils; 6 | 7 | public class ExecuteMethod extends SourceCode { 8 | 9 | public static void main(String[] args) { 10 | if (ArrayUtils.isEmpty(args)) args = new String[]{"com.funtester.ztest.java.T.test", "java.lang.Integer", "1"}; 11 | ExecuteSource.executeMethod(args); 12 | } 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/interfaces/IMessage.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.interfaces; 2 | 3 | public interface IMessage { 4 | /** 5 | * 发送系统异常 6 | */ 7 | public void sendSystemMessage(); 8 | 9 | /** 10 | * 发送功能异常 11 | */ 12 | public void sendFunctionMessage(); 13 | 14 | /** 15 | * 发送业务异常 16 | */ 17 | public void sendBusinessMessage(); 18 | 19 | /** 20 | * 发送程序异常 21 | */ 22 | public void sendCodeMessage(); 23 | 24 | /** 25 | * 提醒推送 26 | */ 27 | public void sendRemindMessage(); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/config/EmailConstant.java: -------------------------------------------------------------------------------- 1 | package com.funtester.config; 2 | 3 | public class EmailConstant { 4 | 5 | /** 6 | * QQ邮箱发件服务器 7 | */ 8 | public static final String QQ_HOST = "smtp.qq.com"; 9 | 10 | /** 11 | * QQ发件邮箱 12 | */ 13 | public static final String QQ_USERNAME = "1009329307@qq.com"; 14 | 15 | /** 16 | * 授权码 17 | */ 18 | public static final String QQ_PASSWORD = "nhkmsrcucjpgbbcj"; 19 | 20 | /** 21 | * email加密传输类型 22 | */ 23 | public static final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /long/poster.markdown: -------------------------------------------------------------------------------- 1 | # FunTester广告位分享 2 | 3 | 由于公众号资源有限,所以暂定每周一篇头条软文投放,为了方便特写此文档. 4 | 5 | 日期周一代表当周. 6 | 7 | ps:有兴趣可以一起聊一聊文末和次条. 8 | 9 | 转载事宜直接微信联系 10 | 11 | [原创汇总,每周更新](https://gitee.com/fanapi/tester/blob/okay/document/directory.markdown) 12 | 13 | |日期(周一)|状态|代号|付款| 14 | |----|----|----|-----| 15 | |12.7|已预订| L+C|已付款| 16 | |12.14|已预订|L|已付款| 17 | |12.21|已预订|N|已付款| 18 | |12.28|已预订|L|已付款| 19 | |1.4|已预订|N|被鸽| 20 | |1.4|已预订|C|未付款| 21 | |1.11|已预订|M|被鸽| 22 | |1.11|已预订|M|已付款| 23 | |1.18|未预定||| 24 | |1.25|未预定||| 25 | |2.22|已预定|七七八十一|已付款| 26 | |3.1|未预定|虚位以待|| 27 | |3.8|已预定|花卷|已付款| 28 | |3.15|虚位以待|| 29 | |3.22|已预定|花卷|未付款 30 | |3.29|未预定|虚位以待|| 31 | |4.5|未预定|虚位以待|| 32 | |4.12|已预订|花卷|未付款| 33 | |4.20|已预订|花卷|未付款| 34 | |4.26|未预定|虚位以待|未付款| 35 | 36 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/exception/ParamException.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.exception; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | /** 6 | * 参数错误运行异常类 7 | */ 8 | public class ParamException extends FailException { 9 | 10 | private static final long serialVersionUID = -5079364420579956243L; 11 | 12 | private ParamException() { 13 | super(); 14 | } 15 | 16 | private ParamException(String message) { 17 | super(message); 18 | } 19 | 20 | public static void fail(String message) { 21 | throw new ParamException(message); 22 | } 23 | 24 | public static void fail(JSONObject message) { 25 | throw new ParamException(message.toJSONString()); 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/config/SysInit.java: -------------------------------------------------------------------------------- 1 | package com.funtester.config; 2 | 3 | import com.funtester.frame.SourceCode; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | 7 | /** 8 | * 存放一些系统初始化的方法,可被外部调用 9 | */ 10 | public class SysInit extends SourceCode{ 11 | 12 | private static Logger logger = LogManager.getLogger(SysInit.class); 13 | 14 | /** 15 | * 是否是黑名单的host 16 | *

先检验fv1314和本地local还有10.10.的地址,然后校验配置文件中的host name

17 | * 18 | * @param name 19 | * @return 20 | */ 21 | public static boolean isBlack(String name) { 22 | return name.contains("10.10") || name.contains("local") || HttpClientConstant.BLACK_HOSTS.contains(name); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | # 分支简介 2 | 3 | 该分支为主分支,其他分支停止更新. 4 | 5 | > **FunTester**,[腾讯云年度作者,优秀讲师 | 腾讯云+社区权威认证](https://mp.weixin.qq.com/s/oeTeJZs6h4jJJMRyUunurw),非著名测试开发,欢迎关注。 6 | 7 | 8 | ## [GitHub地址](https://github.com/JunManYuanLong/FunTester) 9 | 10 | 联系地址:FunTester@88.com 11 | 12 | # [**570+原创文章**](/document/directory.markdown) 13 | # [**接口篇**](/document/api.markdown) 14 | # [**基础篇**](/document/base.markdown) 15 | # [**升级篇**](/document/update.markdown) 16 | # [**7788篇**](/document/7788.markdown) 17 | 18 | * 文章链接可能无法访问,各位看官移步GitHub地址即可. 19 | 20 | [FunTester测试框架架构图](http://pic.automancloud.com/structure.html) 21 | 22 | [FunTester测试项目架构图](http://pic.automancloud.com/project.html) 23 | 24 | ![FunTester测试框架架构图](http://pic.automancloud.com/structure.png) 25 | 26 | ![FunTester测试项目架构图](http://pic.automancloud.com/project.png) -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/exception/LoginException.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.exception; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | /** 6 | * 处理项目中的登录异常 7 | */ 8 | public class LoginException extends FailException { 9 | 10 | private static final long serialVersionUID = 8674617502387938483L; 11 | 12 | private LoginException() { 13 | super(); 14 | } 15 | 16 | private LoginException(String message) { 17 | super(message); 18 | } 19 | 20 | public static void fail(String message) { 21 | throw new LoginException(message); 22 | } 23 | 24 | /** 25 | * 用于处理记录登录响应结果的抛异常方法 26 | * 27 | * @param response 登录接口响应结果 28 | */ 29 | public static void fail(JSONObject response) { 30 | fail(response.toJSONString()); 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/exception/RequestException.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.exception; 2 | 3 | import org.apache.http.client.methods.HttpRequestBase; 4 | 5 | /** 6 | * 用于处理请求异常 7 | */ 8 | public class RequestException extends FailException { 9 | 10 | private static final long serialVersionUID = 7916010541762451964L; 11 | 12 | private RequestException() { 13 | super(); 14 | } 15 | 16 | private RequestException(HttpRequestBase request) { 17 | super(request.toString()); 18 | } 19 | 20 | public RequestException(String message) { 21 | super(message); 22 | } 23 | 24 | public static void fail(HttpRequestBase base) { 25 | throw new RequestException(base); 26 | } 27 | 28 | public static void fail(String message) { 29 | throw new RequestException(message); 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/config/SocketConstant.java: -------------------------------------------------------------------------------- 1 | package com.funtester.config; 2 | 3 | 4 | /** 5 | * socket测试相关的配置 6 | */ 7 | public class SocketConstant { 8 | 9 | /* WebSocket独享配置 */ 10 | 11 | /** 12 | * 最大等待次数,超过次数*时间就是连接失败 13 | */ 14 | public static int MAX_WATI_TIMES = 3; 15 | 16 | /* 共享配置 */ 17 | 18 | /** 19 | * 默认连接间隔 20 | */ 21 | public static int WAIT_INTERVAL = 3; 22 | 23 | /** 24 | * 默认最大的保存响应消息的数量 25 | */ 26 | public static int MAX_MSG_SIZE = 200; 27 | 28 | /*Socket.IO独享配置*/ 29 | 30 | /** 31 | * Socket.IO独享配置 32 | */ 33 | public static int MAX_RETRY = 3; 34 | 35 | /** 36 | * 重试延迟 37 | */ 38 | public static int RETRY_DELAY = 1000; 39 | 40 | /** 41 | * 请求超时 42 | */ 43 | public static int TIMEOUT = 10000; 44 | 45 | public static String[] transports = {"websocket"}; 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/constaint/CaseBase.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.constaint; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.funtester.db.mysql.MySqlTest; 5 | import com.funtester.frame.SourceCode; 6 | 7 | /** 8 | * 用例虚拟类 9 | */ 10 | public abstract class CaseBase extends SourceCode { 11 | 12 | /** 13 | * 保存测试用例的执行结果 14 | * 15 | * @param label 测试用例的标签 16 | * @param result 测试用例结果 17 | */ 18 | public void saveResult(String label, JSONObject result) { 19 | MySqlTest.saveTestResult(label, result); 20 | } 21 | 22 | /** 23 | * 前置处理 24 | * 25 | * @return 26 | */ 27 | public abstract boolean before(); 28 | 29 | /** 30 | * 后置处理 31 | * 32 | * @return 33 | */ 34 | public abstract boolean after(); 35 | 36 | /** 37 | * 初始化 38 | * 39 | * @return 40 | */ 41 | public abstract boolean init(); 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/exception/FailException.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.exception; 2 | 3 | import com.funtester.config.Constant; 4 | 5 | /** 6 | * 自定义异常基类 7 | */ 8 | public class FailException extends RuntimeException { 9 | 10 | private static final long serialVersionUID = -7041169491254546905L; 11 | 12 | public FailException() { 13 | super(Constant.DEFAULT_STRING); 14 | } 15 | 16 | protected FailException(String message) { 17 | super(message); 18 | } 19 | 20 | public static void fail(String message) { 21 | throw new FailException(message); 22 | } 23 | 24 | /** 25 | * 默认抛异常,多用于调试 26 | */ 27 | public static void fail() { 28 | throw new FailException(); 29 | } 30 | 31 | /** 32 | * 将检查异常修改为运行异常 33 | * 34 | * @param e 35 | */ 36 | public static void fail(Exception e) { 37 | throw new FailException(e.getMessage()); 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/config/RequestType.java: -------------------------------------------------------------------------------- 1 | package com.funtester.config; 2 | 3 | 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | 7 | /** 8 | * 请求枚举类,fun备用,暂时无用,通过其他方式区分了post请求的参数格式 9 | */ 10 | public enum RequestType { 11 | 12 | GET("get"), POST("post"), FUN("fun"); 13 | 14 | static Logger logger = LogManager.getLogger(RequestType.class); 15 | 16 | String name; 17 | 18 | private RequestType(String name) { 19 | this.name = name; 20 | } 21 | 22 | public static RequestType getRequestType(String name) { 23 | logger.debug("验证请求方式:{}", name); 24 | for (RequestType requestType : RequestType.values()) { 25 | if (requestType.name.equalsIgnoreCase(name)) { 26 | return requestType; 27 | } 28 | } 29 | return FUN; 30 | } 31 | 32 | /** 33 | * 获取名字 34 | * 35 | * @return 36 | */ 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/db/mysql/AidThread.java: -------------------------------------------------------------------------------- 1 | package com.funtester.db.mysql; 2 | 3 | import com.funtester.frame.SourceCode; 4 | 5 | /** 6 | * mysql辅助线程,当任务数太满的时候启用 7 | *

已经启用,单独写了基于springboot的sql存储服务

8 | */ 9 | @Deprecated 10 | public class AidThread extends SourceCode { 11 | //public class AidThread extends SourceCode implements Runnable { 12 | 13 | // private static Logger logger = LogManager.getLogger(AidThread.class); 14 | // 15 | // @Override 16 | // public void run() { 17 | // MySqlObject object = new MySqlObject(); 18 | // MySqlObject.threadNum.incrementAndGet(); 19 | // while (true) { 20 | // if (object.statement == null || MySqlTest.getWaitWorkNum() < SqlConstant.MYSQL_WORK_PER_THREAD) break; 21 | // String sql = MySqlTest.getWork(); 22 | // if (sql == null) break; 23 | // logger.info("辅助线程执行SQL:{}", sql); 24 | // object.executeUpdateSql(sql); 25 | // } 26 | // MySqlObject.threadNum.decrementAndGet(); 27 | // object.close(); 28 | // } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/interfaces/ISocketVerify.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.interfaces; 2 | 3 | import com.funtester.base.bean.VerifyBean; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Socket接口通用验证接口,暂时无用 9 | */ 10 | public interface ISocketVerify extends Runnable { 11 | 12 | /** 13 | * 初始化消息,某些场景下需要将消息转成固定对象,进行验证,如json 14 | * 15 | * @param msg 16 | */ 17 | public void initMsg(List msg); 18 | 19 | /** 20 | * 执行一次现有消息的全部验证,是否有匹配 21 | * 22 | * @return 23 | */ 24 | public boolean verify(); 25 | 26 | /** 27 | * 往验证队列中添加verify对象 28 | * 29 | * @param bean 30 | */ 31 | public void addVerify(VerifyBean bean); 32 | 33 | /** 34 | * 清除verify,验证通过的verify可以从队列中清除 35 | * 36 | * @param bean 37 | */ 38 | public void remoreVerify(VerifyBean bean); 39 | 40 | /** 41 | * 清除所有验证对象,通常是未验证通过,可以区分未通过和已通过 42 | */ 43 | public void removeAllVerify(); 44 | 45 | /** 46 | * 保存verify队列的测试结果 47 | */ 48 | public void saveResult(); 49 | 50 | 51 | } -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/interfaces/IMySqlBasic.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.interfaces; 2 | 3 | import java.sql.ResultSet; 4 | 5 | /** 6 | * 项目数据库执行类接口 7 | */ 8 | public interface IMySqlBasic { 9 | /** 10 | * 执行查询sql 11 | * 12 | * @param sql 13 | * @return 14 | */ 15 | ResultSet executeQuerySql(String sql); 16 | 17 | /** 18 | * 执行查询sql 19 | * 20 | * @param database 21 | * @param sql 22 | * @return 23 | */ 24 | ResultSet executeQuerySql(String database, String sql); 25 | 26 | /** 27 | * 执行修改sql 28 | * 29 | * @param sql 30 | */ 31 | void executeUpdateSql(String sql); 32 | 33 | /** 34 | * 执行查询sql 35 | * 36 | * @param database 37 | * @param sql 38 | */ 39 | void executeUpdateSql(String database, String sql); 40 | 41 | /** 42 | * 关闭数据库连接 43 | */ 44 | void mySqlOver(); 45 | 46 | /** 47 | * 初始化数据库连接 48 | * 49 | * @param database 50 | */ 51 | void getConnection(String database); 52 | 53 | /** 54 | * 初始化数据库连接 55 | */ 56 | void getConnection(); 57 | } -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/thread/UpdateSqlThread.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.thread; 2 | 3 | import com.funtester.base.constaint.ThreadBase; 4 | import com.funtester.base.constaint.ThreadLimitTimesCount; 5 | import com.funtester.base.interfaces.IMySqlBasic; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | /** 10 | * 数据库多线程类,update方法类,区别于querythread 11 | */ 12 | public class UpdateSqlThread extends ThreadLimitTimesCount { 13 | 14 | private static final long serialVersionUID = 5808571085138930143L; 15 | 16 | private static Logger logger = LogManager.getLogger(UpdateSqlThread.class); 17 | 18 | IMySqlBasic base; 19 | 20 | public UpdateSqlThread(IMySqlBasic base, String sql, int times) { 21 | this.times = times; 22 | this.f = sql; 23 | this.base = base; 24 | } 25 | 26 | @Override 27 | protected void doing() { 28 | base.executeUpdateSql(f); 29 | } 30 | 31 | @Override 32 | protected void after() { 33 | super.after(); 34 | base.mySqlOver(); 35 | } 36 | 37 | @Override 38 | public ThreadBase clone() { 39 | return null; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /long/sql/request.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : test 5 | Source Server Type : MySQL 6 | Source Server Version : 50640 7 | Source Host : 172.18.4.55:3306 8 | Source Schema : okayapi 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50640 12 | File Encoding : 65001 13 | 14 | Date: 02/01/2020 18:14:01 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for request 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `request`; 24 | CREATE TABLE `request` ( 25 | `id` int(10) NOT NULL AUTO_INCREMENT, 26 | `type` varchar(6) DEFAULT NULL, 27 | `method` varchar(6) DEFAULT NULL, 28 | `domain` varchar(100) DEFAULT NULL, 29 | `api` varchar(100) DEFAULT NULL, 30 | `status` int(10) DEFAULT NULL, 31 | `code` int(10) DEFAULT NULL, 32 | `expend_time` double(10,2) DEFAULT NULL, 33 | `data_size` int(6) DEFAULT NULL, 34 | `local_ip` varchar(20) DEFAULT NULL, 35 | `local_name` varchar(20) DEFAULT NULL, 36 | `create_time` varchar(100) DEFAULT NULL, 37 | PRIMARY KEY (`id`) USING BTREE 38 | ) ENGINE=InnoDB AUTO_INCREMENT=122167 DEFAULT CHARSET=utf8; 39 | 40 | SET FOREIGN_KEY_CHECKS = 1; 41 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/exception/VerifyException.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.exception; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.funtester.frame.SourceCode; 5 | import com.funtester.httpclient.FunRequest; 6 | import org.apache.http.client.methods.HttpRequestBase; 7 | 8 | /** 9 | * 用于处理验证过程中的异常 10 | */ 11 | public class VerifyException extends FailException { 12 | 13 | private static final long serialVersionUID = 7916010541762451964L; 14 | 15 | private VerifyException() { 16 | super(); 17 | } 18 | 19 | private VerifyException(HttpRequestBase request) { 20 | super(request.toString()); 21 | } 22 | 23 | private VerifyException(String message) { 24 | super(message); 25 | } 26 | 27 | 28 | public static void fail(String message) { 29 | SourceCode.getiMessage().sendBusinessMessage(); 30 | throw new VerifyException(message); 31 | } 32 | 33 | public static void fail(JSONObject message) { 34 | SourceCode.getiMessage().sendBusinessMessage(); 35 | fail(message.toJSONString()); 36 | } 37 | 38 | public static void fail(HttpRequestBase request) { 39 | SourceCode.getiMessage().sendBusinessMessage(); 40 | fail(FunRequest.initFromRequest(request).toString()); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/thread/FixedQpsParamMark.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.thread; 2 | 3 | import com.funtester.base.constaint.ThreadBase; 4 | import com.funtester.base.interfaces.MarkThread; 5 | import com.funtester.frame.SourceCode; 6 | 7 | import java.io.Serializable; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | /** 11 | * 用于非单纯的http请求以及非HTTP请求,没有httprequestbase对象的标记方法,自己实现的虚拟类,可用户标记header固定字段或者随机参数,使用T作为参数载体,目前只能使用在T为string类才行 12 | */ 13 | public class FixedQpsParamMark extends SourceCode implements MarkThread, Cloneable, Serializable { 14 | 15 | private static final long serialVersionUID = 2135701056209833015L; 16 | 17 | public static AtomicInteger num = new AtomicInteger(10000); 18 | 19 | /** 20 | * 用于标记执行线程 21 | */ 22 | String name; 23 | 24 | @Override 25 | public String mark(ThreadBase threadBase) { 26 | return name + num.getAndIncrement(); 27 | } 28 | 29 | @Override 30 | public FixedQpsParamMark clone() { 31 | FixedQpsParamMark paramMark = new FixedQpsParamMark(this.name); 32 | return paramMark; 33 | } 34 | 35 | private FixedQpsParamMark() { 36 | name = EMPTY; 37 | } 38 | 39 | public FixedQpsParamMark(String name) { 40 | this(); 41 | this.name = name; 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/thread/ParamMark.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.thread; 2 | 3 | import com.funtester.base.constaint.ThreadBase; 4 | import com.funtester.base.interfaces.MarkThread; 5 | import com.funtester.frame.SourceCode; 6 | 7 | import java.io.Serializable; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | /** 11 | * 用于非单纯的http请求以及非HTTP请求,没有httprequestbase对象的标记方法,自己实现的虚拟类,可用户标记header固定字段或者随机参数,使用T作为参数载体,目前只能使用在T为string类才行 12 | */ 13 | public class ParamMark extends SourceCode implements MarkThread, Cloneable, Serializable { 14 | 15 | private static final long serialVersionUID = -5532592151245141262L; 16 | 17 | public static AtomicInteger threadName = new AtomicInteger(getRandomIntRange(1000, 9000)); 18 | 19 | /** 20 | * 用于标记执行线程 21 | */ 22 | String name; 23 | 24 | int num = getRandomIntRange(100, 999) * 1000; 25 | 26 | @Override 27 | public String mark(ThreadBase threadBase) { 28 | return name + num++; 29 | } 30 | 31 | @Override 32 | public ParamMark clone() { 33 | ParamMark paramMark = new ParamMark(); 34 | return paramMark; 35 | } 36 | 37 | public ParamMark() { 38 | this.name = threadName.getAndIncrement() + EMPTY; 39 | } 40 | 41 | public ParamMark(String name) { 42 | this(); 43 | this.name = name; 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/httpclient/GCThread.java: -------------------------------------------------------------------------------- 1 | package com.funtester.httpclient; 2 | 3 | import com.funtester.config.HttpClientConstant; 4 | 5 | import static com.funtester.frame.SourceCode.sleep; 6 | 7 | /** 8 | * 从连接池中回收连接的多线程类 9 | */ 10 | public class GCThread implements Runnable { 11 | 12 | /** 13 | * 资源回收线程 14 | */ 15 | private static volatile Thread gc = init(); 16 | 17 | /** 18 | * 增加了线程状态的判断,同一进程多次运行HTTP请求的压测功能 19 | */ 20 | public synchronized static void starts() { 21 | if (gc.getState() == Thread.State.NEW) gc.start(); 22 | else if (gc.getState() == Thread.State.TERMINATED) gc = init(); 23 | } 24 | 25 | /** 26 | * 初始化方法,获取新的gc线程对象 27 | * 28 | * @return 29 | */ 30 | public static synchronized Thread init() { 31 | FLAG = true; 32 | return new Thread(new GCThread()); 33 | } 34 | 35 | private GCThread() { 36 | } 37 | 38 | /** 39 | * 线程结束标志 40 | */ 41 | private static boolean FLAG = true; 42 | 43 | @Override 44 | public void run() { 45 | while (FLAG) { 46 | sleep(HttpClientConstant.LOOP_INTERVAL); 47 | ClientManage.recyclingConnection(); 48 | } 49 | } 50 | 51 | /** 52 | * 结束线程方法 53 | */ 54 | public static void stop() { 55 | FLAG = false; 56 | } 57 | 58 | 59 | } -------------------------------------------------------------------------------- /long/sql/performance.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : test 5 | Source Server Type : MySQL 6 | Source Server Version : 50640 7 | Source Host : 172.18.4.55:3306 8 | Source Schema : okayapi 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50640 12 | File Encoding : 65001 13 | 14 | Date: 02/01/2020 18:13:16 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for performance 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `performance`; 24 | CREATE TABLE `performance` ( 25 | `id` int(10) NOT NULL AUTO_INCREMENT, 26 | `threads` int(4) DEFAULT NULL COMMENT '线程数', 27 | `rt` int(5) DEFAULT NULL COMMENT '平均响应时间,ms', 28 | `qps` double(10,4) DEFAULT NULL COMMENT 'QPS处理能力 /s', 29 | `error` double(10,4) DEFAULT NULL COMMENT '错误率', 30 | `fail` double(10,4) DEFAULT NULL COMMENT '失败率', 31 | `des` varchar(1000) DEFAULT NULL COMMENT '任务描述', 32 | `total` int(10) DEFAULT NULL COMMENT '总请求次数', 33 | `start_time` timestamp NULL DEFAULT NULL, 34 | `end_time` timestamp NULL DEFAULT NULL, 35 | `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 36 | PRIMARY KEY (`id`) USING BTREE 37 | ) ENGINE=InnoDB AUTO_INCREMENT=91 DEFAULT CHARSET=utf8; 38 | 39 | SET FOREIGN_KEY_CHECKS = 1; 40 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/interfaces/ISocketClient.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.interfaces; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.funtester.socket.ScoketIOFunClient; 5 | import com.funtester.socket.WebSocketFunClient; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 对于基类base拓展Socket功能,暂时分成WebSocket和Socket.IO 11 | * {@link ScoketIOFunClient} 12 | * {@link WebSocketFunClient} 13 | */ 14 | public interface ISocketClient { 15 | 16 | /** 17 | * 连接 18 | */ 19 | void connect(); 20 | 21 | /** 22 | * 初始化 23 | */ 24 | void init(); 25 | 26 | /** 27 | * 发送消息 28 | * 29 | * @param mgs 30 | */ 31 | void send(JSONObject mgs); 32 | 33 | /** 34 | * 发送消息 35 | * 36 | * @param mgs 37 | */ 38 | void send(String mgs); 39 | 40 | /** 41 | * 关闭 42 | */ 43 | void close(); 44 | 45 | /** 46 | * 克隆对象,性能测试中需要 47 | */ 48 | ISocketClient clone(); 49 | 50 | /** 51 | * 是否已连接 52 | * 53 | * @return 54 | */ 55 | boolean isConnect(); 56 | 57 | /** 58 | * 获取记录的消息,用于验证响应,请注意需要返回副本 59 | * 60 | * @return 61 | */ 62 | List getMsgs(); 63 | 64 | /** 65 | * 用于保存收到的信息,不同于Client的saveMsg,此方法需要将对象存储的消息全都存到long_path目录下,是否需要清空Client对象中的msgs信息,需要视情况而定. 66 | */ 67 | void savaMsg(); 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/thread/QuerySqlThread.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.thread; 2 | 3 | import com.funtester.base.constaint.ThreadBase; 4 | import com.funtester.base.constaint.ThreadLimitTimesCount; 5 | import com.funtester.base.interfaces.IMySqlBasic; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | import java.sql.SQLException; 10 | 11 | /** 12 | * 数据库多线程类,query方法类,区别于updatethread 13 | */ 14 | public class QuerySqlThread extends ThreadLimitTimesCount { 15 | 16 | private static final long serialVersionUID = 879371247008746883L; 17 | 18 | private static Logger logger = LogManager.getLogger(QuerySqlThread.class); 19 | 20 | String sql; 21 | 22 | IMySqlBasic base; 23 | 24 | public QuerySqlThread(IMySqlBasic base, String sql, int times) { 25 | this.times = times; 26 | this.sql = sql; 27 | this.base = base; 28 | } 29 | 30 | @Override 31 | public void before() { 32 | base.getConnection(); 33 | } 34 | 35 | @Override 36 | protected void doing() throws SQLException { 37 | base.executeQuerySql(sql); 38 | } 39 | 40 | @Override 41 | protected void after() { 42 | super.after(); 43 | base.mySqlOver(); 44 | } 45 | 46 | @Override 47 | public ThreadBase clone() { 48 | return null; 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/config/VerifyType.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.config; 2 | 3 | import com.funtester.base.exception.ParamException; 4 | import org.apache.commons.lang3.StringUtils 5 | import org.apache.logging.log4j.LogManager 6 | import org.apache.logging.log4j.Logger; 7 | 8 | /** 9 | * 通用验证类型,包含,正则,JsonPath,handle四项 10 | */ 11 | enum VerifyType { 12 | 13 | CONTAIN("contain"), REGEX("regex"), JSONPATH("jsonpath"), HANDLE("handle"); 14 | 15 | String vname; 16 | 17 | VerifyType(String vname) { 18 | this.vname = vname; 19 | } 20 | 21 | private static Logger logger = LogManager.getLogger(VerifyType.class); 22 | 23 | /** 24 | * 获取验证类型,不区分大小写 25 | * 26 | * @param name 27 | * @return 28 | */ 29 | static VerifyType getRequestType(String name) { 30 | logger.debug("验证校验方式方式:{}", name); 31 | if (StringUtils.isEmpty(name)) ParamException.fail("参数不能为空!"); 32 | name = name.toLowerCase(); 33 | switch (name) { 34 | case CONTAIN.getVname(): 35 | return CONTAIN; 36 | case REGEX.getVname(): 37 | return REGEX; 38 | case JSONPATH.getVname(): 39 | return JSONPATH; 40 | case HANDLE.getVname(): 41 | return HANDLE; 42 | default: 43 | ParamException.fail(name + "参数错误!"); 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/main/PerformanceFromFile.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.main 2 | 3 | import com.funtester.frame.SourceCode 4 | import com.funtester.frame.execute.Concurrent 5 | import com.funtester.frame.thread.RequestThreadTimes 6 | import com.funtester.httpclient.FunLibrary 7 | import com.funtester.utils.request.RequestFile 8 | import org.apache.http.client.methods.HttpRequestBase 9 | /** 10 | * 从文本配置中读取request,进行压测的类 11 | */ 12 | class PerformanceFromFile extends SourceCode { 13 | static void main(String[] args) { 14 | FunLibrary.setSocketTimeOut(30) 15 | def size = args.size(); 16 | List list = new ArrayList<>() 17 | for (int i = 0; i < size - 1; i += 2) { 18 | def name = args[i] 19 | int thread = changeStringToInt(args[i + 1]) 20 | def request = new RequestFile(name).getRequest() 21 | for (int j = 0; j < thread; j++) { 22 | list.add(request) 23 | } 24 | } 25 | int perTimes = changeStringToInt(args[size - 1]) 26 | List thread = new ArrayList<>() 27 | for (int i = 0; i < list.size(); i++) { 28 | def get = list.get(i) 29 | def thread1 = new RequestThreadTimes(get, perTimes) 30 | thread.add(thread1) 31 | } 32 | def concurrent = new Concurrent(thread) 33 | concurrent.start() 34 | FunLibrary.testOver() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/CountUtil.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.utils 2 | 3 | import org.apache.logging.log4j.LogManager 4 | import org.apache.logging.log4j.Logger 5 | 6 | import java.util.stream.Collectors 7 | 8 | /** 9 | * 统计出现次数相关类 10 | */ 11 | class CountUtil { 12 | 13 | private static Logger logger = LogManager.getLogger(CountUtil.class) 14 | 15 | /** 16 | * 统计数据出现的次数 17 | * 18 | * @param counts 统计的 jsonobject 对象 19 | * @param object 需要统计的数据 20 | */ 21 | static def count(Map counts, Object object) { 22 | count(counts, object, 1) 23 | } 24 | 25 | /** 26 | * 统计数据出现的次数 27 | * 28 | * @param counts 统计的 jsonobject 对象 29 | * @param object 需要统计的数据 30 | * @param num 增加值 31 | */ 32 | static def count(Map counts, Object object, int num) { 33 | counts.put(object, Integer.valueOf(counts.getOrDefault(object, 0) + num)) 34 | } 35 | 36 | /** 37 | * 统计某个list里面某个元素出现的次数 38 | * @param list 39 | * @param str 40 | * @return 41 | */ 42 | static def count(List list, def str) { 43 | list.count {s -> s.toString().equals(str.toString())} 44 | } 45 | 46 | /** 47 | * 统计某个list里面各个元素出现的次数 48 | * collect,是一个map对象 49 | * @param list 50 | * @return 51 | */ 52 | static def count(List list) { 53 | list.stream().collect(Collectors.groupingBy {x -> x}).each { 54 | it.setValue(it.value.size()) 55 | logger.info("元素:${it.key},次数:${it.value}") 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/ArgsUtil.java: -------------------------------------------------------------------------------- 1 | package com.funtester.utils; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.funtester.frame.SourceCode; 6 | 7 | import java.io.File; 8 | 9 | public class ArgsUtil extends SourceCode { 10 | 11 | String[] all; 12 | 13 | public ArgsUtil(String[] args) { 14 | all = (String[]) args.clone(); 15 | } 16 | 17 | /** 18 | * 获取int参数 19 | * 20 | * @param i 获取的参数索引 21 | * @param k 默认值 22 | * @return 23 | */ 24 | public int getIntOrdefault(int i, int k) { 25 | return i >= all.length ? k : changeStringToInt(all[i]); 26 | } 27 | 28 | /** 29 | * 获取boolean参数 30 | * 31 | * @param i 32 | * @param k 33 | * @return 34 | */ 35 | public boolean getBooleanOrdefault(int i, boolean k) { 36 | return i >= all.length ? k : changeStringToBoolean(all[i]); 37 | } 38 | 39 | 40 | /** 41 | * @param i 42 | * @param k 43 | * @return 44 | */ 45 | public String getStringOrdefault(int i, String k) { 46 | return i >= all.length ? k : all[i]; 47 | } 48 | 49 | 50 | /** 51 | * @param i 52 | * @param path 53 | * @return 54 | */ 55 | public File getFileOrDefault(int i, String path) { 56 | return i >= all.length ? new File(path) : new File(all[i]); 57 | } 58 | 59 | /** 60 | * @param i 61 | * @param json 62 | * @return 63 | */ 64 | public JSONObject getJsonOrDefault(int i, String json) { 65 | return i >= all.length ? JSON.parseObject(json) : JSON.parseObject(all[i]); 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/thread/RequestTimeFixedQps.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.thread; 2 | 3 | import com.funtester.base.constaint.FixedQpsThread; 4 | import com.funtester.base.interfaces.MarkRequest; 5 | import com.funtester.httpclient.FunLibrary; 6 | import com.funtester.httpclient.FunRequest; 7 | import com.funtester.httpclient.GCThread; 8 | import org.apache.http.client.methods.HttpRequestBase; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.Logger; 11 | 12 | public class RequestTimeFixedQps extends FixedQpsThread { 13 | 14 | private static final long serialVersionUID = -64206522585960792L; 15 | 16 | private static Logger logger = LogManager.getLogger(RequestTimeFixedQps.class); 17 | 18 | private RequestTimeFixedQps() { 19 | 20 | } 21 | 22 | public RequestTimeFixedQps(int qps, int time, MarkRequest markRequest, HttpRequestBase request) { 23 | super(request, time, qps, markRequest, false); 24 | } 25 | 26 | @Override 27 | public void before() { 28 | super.before(); 29 | GCThread.starts(); 30 | } 31 | 32 | @Override 33 | protected void doing() throws Exception { 34 | FunLibrary.executeSimlple(f); 35 | } 36 | 37 | @Override 38 | public RequestTimeFixedQps clone() { 39 | RequestTimeFixedQps newone = new RequestTimeFixedQps(); 40 | newone.f = FunRequest.cloneRequest(this.f); 41 | newone.mark = this.mark == null ? null : this.mark.clone(); 42 | newone.qps = this.qps; 43 | newone.isTimesMode = this.isTimesMode; 44 | newone.limit = this.limit; 45 | return newone; 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/thread/RequestTimesFixedQps.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.thread; 2 | 3 | import com.funtester.base.constaint.FixedQpsThread; 4 | import com.funtester.base.interfaces.MarkRequest; 5 | import com.funtester.httpclient.FunLibrary; 6 | import com.funtester.httpclient.FunRequest; 7 | import com.funtester.httpclient.GCThread; 8 | import org.apache.http.client.methods.HttpRequestBase; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.Logger; 11 | 12 | public class RequestTimesFixedQps extends FixedQpsThread { 13 | 14 | private static final long serialVersionUID = 679065222134424087L; 15 | 16 | private static Logger logger = LogManager.getLogger(RequestTimesFixedQps.class); 17 | 18 | private RequestTimesFixedQps() { 19 | 20 | } 21 | 22 | public RequestTimesFixedQps(int qps, int times, MarkRequest markRequest, HttpRequestBase request) { 23 | super(request, times, qps, markRequest, true); 24 | } 25 | 26 | @Override 27 | public void before() { 28 | super.before(); 29 | GCThread.starts(); 30 | } 31 | 32 | @Override 33 | protected void doing() throws Exception { 34 | FunLibrary.executeSimlple(f); 35 | } 36 | 37 | @Override 38 | public RequestTimesFixedQps clone() { 39 | RequestTimesFixedQps newone = new RequestTimesFixedQps(); 40 | newone.f = FunRequest.cloneRequest(this.f); 41 | newone.mark = this.mark == null ? null : this.mark.clone(); 42 | newone.qps = this.qps; 43 | newone.isTimesMode = this.isTimesMode; 44 | newone.limit = this.limit; 45 | return newone; 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/bean/AbstractBean.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.base.bean 2 | 3 | import com.alibaba.fastjson.JSON 4 | import com.alibaba.fastjson.JSONObject 5 | import com.funtester.config.Constant 6 | import com.funtester.frame.Save 7 | import com.funtester.frame.SourceCode 8 | import org.apache.logging.log4j.LogManager 9 | import org.apache.logging.log4j.Logger 10 | 11 | /** 12 | * bean的基类 13 | */ 14 | abstract class AbstractBean extends Constant{ 15 | 16 | static final Logger logger = LogManager.getLogger(AbstractBean.class) 17 | 18 | /** 19 | * 将bean转化为json,为了进行数据处理和打印 20 | * 21 | * @return 22 | */ 23 | JSONObject toJson() { 24 | JSONObject.parseObject(JSONObject.toJSONString(this)) 25 | } 26 | 27 | /** 28 | * 文本形式保存 29 | */ 30 | def save() { 31 | Save.saveJson(this.toJson(), this.getClass().toString() + SourceCode.getMark()); 32 | } 33 | 34 | /** 35 | * 控制台打印,使用WARN记录,以便查看 36 | */ 37 | def print() { 38 | logger.warn(this.getClass().toString() + ":" + this.toString()); 39 | } 40 | 41 | def initFrom(String str) { 42 | JSONObject.parseObject(str, this.getClass()) 43 | } 44 | 45 | def initFrom(Object str) { 46 | initFrom(JSON.toJSONString(str)) 47 | } 48 | 49 | def copyFrom(AbstractBean source) { 50 | JSON.parseObject(JSON.toJSONString(source), source.class) 51 | } 52 | 53 | def copyTo(AbstractBean target) { 54 | JSON.parseObject(JSON.toJSONString(this, target.class)) 55 | } 56 | 57 | /** 58 | * 这里bean的属性必需是可以访问的,不然会返回空json串 59 | * @return 60 | */ 61 | @Override 62 | String toString() { 63 | JSONObject.toJSONString(this) 64 | } 65 | 66 | @Override 67 | protected Object clone() { 68 | initFrom(this) 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/dubbo/DubboUtil.java: -------------------------------------------------------------------------------- 1 | package com.funtester.dubbo; 2 | 3 | import com.funtester.frame.SourceCode; 4 | 5 | 6 | /** 7 | * dubbo泛化调用的方法 8 | */ 9 | public class DubboUtil extends SourceCode { 10 | // 11 | // public static Logger logger = LogManager.getLogger(DubboUtil.class); 12 | // 13 | // public static DubboInvokeParams initDubboInvokeParams(DubboParamBase... params) { 14 | // DubboInvokeParams invokeParams = new DubboInvokeParams(params.length); 15 | // range(invokeParams.getLength()).forEach(x -> 16 | // { 17 | // DubboParamBase param = params[x]; 18 | // invokeParams.getTypes()[x] = param.getType(); 19 | // invokeParams.getValues()[x] = param.getValue(); 20 | // } 21 | // ); 22 | // return invokeParams; 23 | // } 24 | // 25 | // public static Object getResponse(GenericService genericService, String methodName, DubboInvokeParams params) { 26 | // return genericService.$invoke(methodName, params.getTypes(), params.getValues()); 27 | // } 28 | // 29 | // public static void main(String[] args) { 30 | // Map getDataRequest = new HashMap(); 31 | // getDataRequest.put("orgId", "119"); 32 | // getDataRequest.put("orgType", "2"); 33 | // getDataRequest.put("reqId", "123456789"); 34 | // DubboInvokeParams dubboInvokeParams = initDubboInvokeParams(new DubboParamBase(Object.class, getDataRequest)); 35 | // DubboBase dubbo = new DubboBase("dubbo"); 36 | // GenericService genericService = dubbo.getGenericService("com.noriental.usersvr.service.okuser.SchoolYearService"); 37 | // 38 | // Object response = getResponse(genericService, "findFutureYear", dubboInvokeParams); 39 | // output(response.toString()); 40 | // 41 | // } 42 | 43 | 44 | } -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/xml/XMLUtil2.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.utils.xml 2 | /** 3 | * 基于dom4j解析xml工具类 4 | */ 5 | class XMLUtil2 { 6 | 7 | // private static Logger logger = LogManager.getLogger(XMLUtil2.class) 8 | // 9 | // /** 10 | // * 解析xml文件 11 | // * @param path 绝对路径或者URL 12 | // * @return 13 | // */ 14 | // static List parse(String path) { 15 | // SAXReader reader = new SAXReader(); 16 | // try { 17 | // Document document = reader.read(path.startsWith("http") ? new URL(path) : new File(path)); 18 | // Element rootElement = document.getRootElement(); 19 | // def iterator = rootElement.elementIterator() 20 | // List info = new ArrayList<>() 21 | // while (iterator.hasNext()) { 22 | // info << parseNode(iterator.next() as Element) 23 | // } 24 | // return info; 25 | // } catch (DocumentException e) { 26 | // logger.error("解析文件${path}失败!", e) 27 | // } 28 | // FailException.fail("解析文件${path}失败!") 29 | // } 30 | // 31 | // /** 32 | // * 解析节点信息 33 | // * @param e 34 | // * @return 35 | // */ 36 | // static NodeInfo parseNode(Element e) { 37 | // if (e.getNodeType() != Node.ELEMENT_NODE) return null; 38 | // def info = new NodeInfo() 39 | // List attributes = e.attributes(); 40 | // List attrs = new ArrayList<>() 41 | // attributes.each { 42 | // attrs << new Attr(it.name, it.value) 43 | // } 44 | // info.setAttrs(attrs) 45 | // List children = new ArrayList<>() 46 | // def iterator = e.elementIterator() 47 | // if (iterator.hasNext()) { 48 | // children << parseNode(iterator.next() as Element) 49 | // } 50 | // info.setChildren(children) 51 | // return info; 52 | // } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/HeapDumper.java: -------------------------------------------------------------------------------- 1 | package com.funtester.utils; 2 | 3 | import com.sun.management.HotSpotDiagnosticMXBean; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | 7 | import javax.management.MBeanServer; 8 | import java.lang.management.ManagementFactory; 9 | 10 | /** 11 | * 获取JVM内存转储文件的工具类 12 | */ 13 | public class HeapDumper { 14 | 15 | private static Logger logger = LogManager.getLogger(HeapDumper.class); 16 | 17 | /** 18 | * 这是HotSpot Diagnostic MBean的名称 19 | */ 20 | private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic"; 21 | 22 | /** 23 | * 用于存储热点诊断MBean的字段 24 | */ 25 | private static volatile HotSpotDiagnosticMXBean hotspotMBean; 26 | 27 | /** 28 | * 下载内存转储文件 29 | * 30 | * @param fileName 文件名,例如:heap.bin,不兼容路径,会在当前目录下生成 31 | * @param live 32 | */ 33 | public static void dumpHeap(String fileName, boolean live) { 34 | initHotspotMBean(); 35 | try { 36 | hotspotMBean.dumpHeap(fileName, live); 37 | } catch (Exception e) { 38 | logger.error("生成内存转储文件失败!", e); 39 | } 40 | } 41 | 42 | /** 43 | * 初始化热点诊断MBean 44 | */ 45 | private static void initHotspotMBean() { 46 | if (hotspotMBean == null) { 47 | synchronized (HeapDumper.class) { 48 | if (hotspotMBean == null) { 49 | try { 50 | MBeanServer server = ManagementFactory.getPlatformMBeanServer(); 51 | hotspotMBean = ManagementFactory.newPlatformMXBeanProxy(server, HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean.class); 52 | } catch (Exception e) { 53 | logger.error("初始化mbean失败!", e); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | 61 | } -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/bean/Result.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.base.bean 2 | 3 | import com.alibaba.fastjson.JSONObject 4 | import com.funtester.base.interfaces.ReturnCode 5 | import com.funtester.config.Constant 6 | 7 | /** 8 | * 通用的返回体 9 | * 配合moco框架使用 10 | * @param < T > 11 | */ 12 | class Result extends AbstractBean implements Serializable{ 13 | 14 | private static final long serialVersionUID = -196371159847L; 15 | 16 | /** 17 | * code码 18 | */ 19 | int code 20 | /** 21 | * 返回信息 22 | */ 23 | T data 24 | 25 | Result(int code, T data) { 26 | this.code = code 27 | this.data = data 28 | } 29 | /** 30 | * 返回简单的响应 31 | * @param c 32 | */ 33 | 34 | Result(ReturnCode errorCode) { 35 | this(errorCode.getCode(), errorCode.getDesc()) 36 | } 37 | 38 | def Result() { 39 | } 40 | /** 41 | * 返回成功响应内容 42 | * @param data 43 | * @return 44 | */ 45 | static Result success(T data) { 46 | new Result(0, data) 47 | } 48 | 49 | static Result success() { 50 | new Result() 51 | } 52 | 53 | static Result build(ReturnCode errorCode) { 54 | new Result(errorCode) 55 | } 56 | 57 | static Result build(int code, String msg) { 58 | new Result(code, msg) 59 | } 60 | 61 | static Result build(List listData) { 62 | success([list: listData] as JSONObject) 63 | } 64 | 65 | /** 66 | * 返回通用失败的响应内容 67 | * @param data 68 | * @return 69 | */ 70 | static Result fail(T data) { 71 | new Result(Constant.TEST_ERROR_CODE, data) 72 | } 73 | 74 | static Result fail() { 75 | new Result(Constant.TEST_ERROR_CODE) 76 | } 77 | 78 | static Result fail(ReturnCode errorCode) { 79 | new Result(errorCode) 80 | } 81 | 82 | /** 83 | * 是否成功响应 84 | * @return 85 | */ 86 | boolean isSuccess() { 87 | code == 0 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/thread/FixedQpsHeaderMark.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.thread 2 | 3 | import com.funtester.base.constaint.ThreadBase 4 | import com.funtester.base.exception.ParamException 5 | import com.funtester.base.interfaces.MarkRequest 6 | import com.funtester.frame.SourceCode 7 | import org.apache.http.client.methods.HttpRequestBase 8 | 9 | import java.util.concurrent.atomic.AtomicInteger 10 | 11 | /** 12 | * 针对固定QPS模式的多线程对象的标记类 13 | */ 14 | class FixedQpsHeaderMark extends SourceCode implements MarkRequest, Cloneable, Serializable { 15 | 16 | private static final long serialVersionUID = -158942567078477L; 17 | 18 | private static volatile AtomicInteger num = new AtomicInteger(10000); 19 | 20 | String headerName; 21 | 22 | @Override 23 | String mark(ThreadBase threadBase) { 24 | if (threadBase instanceof RequestTimesFixedQps) { 25 | RequestTimesFixedQps req = (RequestTimesFixedQps) threadBase; 26 | return mark(req.t); 27 | } else if (threadBase instanceof RequestTimeFixedQps) { 28 | RequestThreadTimes req = (RequestTimeFixedQps) threadBase; 29 | return mark(req.t); 30 | } else { 31 | ParamException.fail(threadBase.getClass().toString()); 32 | } 33 | EMPTY; 34 | } 35 | 36 | /** 37 | * 标记请求 38 | * 39 | * @param base 40 | * @return 41 | */ 42 | @Override 43 | String mark(HttpRequestBase base) { 44 | base.removeHeaders(headerName); 45 | String value = 8 + EMPTY + num.getAndIncrement(); 46 | base.addHeader(headerName, value); 47 | value; 48 | } 49 | 50 | @Override 51 | FixedQpsHeaderMark clone() { 52 | new FixedQpsHeaderMark(headerName); 53 | } 54 | 55 | FixedQpsHeaderMark(String headerName) { 56 | this.headerName = headerName; 57 | } 58 | 59 | private FixedQpsHeaderMark() { 60 | 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/thread/RequestThreadTime.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.thread; 2 | 3 | import com.funtester.base.constaint.ThreadLimitTimeCount; 4 | import com.funtester.base.interfaces.MarkThread; 5 | import com.funtester.httpclient.FunLibrary; 6 | import com.funtester.httpclient.FunRequest; 7 | import com.funtester.httpclient.GCThread; 8 | import org.apache.http.client.methods.HttpRequestBase; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.Logger; 11 | 12 | /** 13 | * http请求多线程类 14 | */ 15 | public class RequestThreadTime extends ThreadLimitTimeCount { 16 | 17 | private static final long serialVersionUID = -6554503654885966097L; 18 | 19 | private static Logger logger = LogManager.getLogger(RequestThreadTime.class); 20 | 21 | /** 22 | * 单请求多线程多次任务构造方法 23 | * 24 | * @param request 被执行的请求 25 | * @param time 每个线程运行的次数 26 | */ 27 | public RequestThreadTime(HttpRequestBase request, int time) { 28 | super(request, time, null); 29 | } 30 | 31 | /** 32 | * @param request 被执行的请求 33 | * @param time 执行时间 34 | * @param mark 标记类对象 35 | */ 36 | public RequestThreadTime(HttpRequestBase request, int time, MarkThread mark) { 37 | super(request, time, mark); 38 | } 39 | 40 | protected RequestThreadTime() { 41 | super(); 42 | } 43 | 44 | @Override 45 | public void before() { 46 | super.before(); 47 | GCThread.starts(); 48 | } 49 | 50 | @Override 51 | protected void doing() throws Exception { 52 | FunLibrary.executeSimlple(f); 53 | } 54 | 55 | 56 | @Override 57 | public RequestThreadTime clone() { 58 | RequestThreadTime threadTime = new RequestThreadTime(); 59 | threadTime.time = this.time; 60 | threadTime.f = FunRequest.cloneRequest(f); 61 | threadTime.mark = mark == null ? null : mark.clone(); 62 | return threadTime; 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/constaint/FixedQpsThread.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.constaint; 2 | 3 | import com.funtester.base.interfaces.MarkThread; 4 | import com.funtester.config.HttpClientConstant; 5 | import com.funtester.frame.execute.FixedQpsConcurrent; 6 | import com.funtester.utils.Time; 7 | import org.apache.logging.log4j.LogManager; 8 | import org.apache.logging.log4j.Logger; 9 | 10 | public abstract class FixedQpsThread extends ThreadBase { 11 | 12 | private static Logger logger = LogManager.getLogger(FixedQpsThread.class); 13 | 14 | public int qps; 15 | 16 | /** 17 | * 根据属性isTimesMode判断,次数或者时间(单位ms) 18 | */ 19 | public int limit; 20 | 21 | public boolean isTimesMode; 22 | 23 | public FixedQpsThread(F f, int limit, int qps, MarkThread markThread, boolean isTimesMode) { 24 | this.limit = limit; 25 | this.qps = qps; 26 | this.mark = markThread; 27 | this.f = f; 28 | this.isTimesMode = isTimesMode; 29 | } 30 | 31 | 32 | protected FixedQpsThread() { 33 | super(); 34 | } 35 | 36 | @Override 37 | public void run() { 38 | try { 39 | before(); 40 | threadmark = this.mark == null ? EMPTY : this.mark.mark(this); 41 | long s = Time.getTimeStamp(); 42 | doing(); 43 | long e = Time.getTimeStamp(); 44 | FixedQpsConcurrent.executeTimes.getAndIncrement(); 45 | int diff = (int) (e - s); 46 | FixedQpsConcurrent.allTimes.add(diff); 47 | if (diff > HttpClientConstant.MAX_ACCEPT_TIME) 48 | FixedQpsConcurrent.marks.add(diff + CONNECTOR + threadmark + CONNECTOR + Time.getNow()); 49 | } catch (Exception e) { 50 | FixedQpsConcurrent.errorTimes.getAndIncrement(); 51 | logger.warn("执行任务失败!,标记:{}", threadmark, e); 52 | } finally { 53 | after(); 54 | } 55 | } 56 | 57 | @Override 58 | public void before() { 59 | 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/db/mysql/MySqlObject.java: -------------------------------------------------------------------------------- 1 | package com.funtester.db.mysql; 2 | 3 | /** 4 | * 辅助线程,处理sql任务 5 | *

不再使用该方式存储数据库数据

6 | */ 7 | @Deprecated 8 | public class MySqlObject { 9 | // /** 10 | // * 标记多少辅助线程存活数量 11 | // */ 12 | // public static AtomicInteger threadNum = new AtomicInteger(0); 13 | // /** 14 | // * 标记连接使用 15 | // */ 16 | // int updateTime; 17 | // Connection connection; 18 | // Statement statement; 19 | // 20 | // /** 21 | // * 初始化连接方法 22 | // */ 23 | // public MySqlObject() { 24 | // getConnection(); 25 | // } 26 | // 27 | // /** 28 | // * 获取当前辅助线程数 29 | // * 30 | // * @return 31 | // */ 32 | // public static int getThreadNum() { 33 | // return threadNum.get(); 34 | // } 35 | // 36 | // /** 37 | // * 更新连接使用标记 38 | // */ 39 | // void updateLastUpdate() { 40 | // updateTime = SourceCode.getMark(); 41 | // } 42 | // 43 | // /** 44 | // * 执行sql方法 45 | // * 46 | // * @param sql 47 | // */ 48 | // void executeUpdateSql(String sql) { 49 | // getConnection(); 50 | // SqlBase.executeUpdateSql(connection, statement, sql); 51 | // updateLastUpdate(); 52 | // } 53 | // 54 | // /** 55 | // * 获取数据库连接 56 | // */ 57 | // void getConnection() { 58 | // try { 59 | // if (SourceCode.getMark() - updateTime > SqlConstant.MYSQL_RECONNECTION_GAP || connection == null || connection.isClosed()) { 60 | // connection = TestConnectionManage.getConnection(SqlConstant.TEST_SQL_URL, SqlConstant.TEST_USER, SqlConstant.TEST_PASS_WORD); 61 | // statement = TestConnectionManage.getStatement(connection); 62 | // } 63 | // } catch (SQLException e) { 64 | // Output.output("数据库连接获取失败!", e); 65 | // } finally { 66 | // updateLastUpdate(); 67 | // } 68 | // } 69 | // 70 | // /** 71 | // * 关闭对象方法 72 | // */ 73 | // void close() { 74 | // SqlBase.mySqlOver(connection, statement); 75 | // } 76 | } -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/thread/RequestThreadTimes.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.thread; 2 | 3 | import com.funtester.base.constaint.ThreadLimitTimesCount; 4 | import com.funtester.base.interfaces.MarkThread; 5 | import com.funtester.httpclient.FunLibrary; 6 | import com.funtester.httpclient.FunRequest; 7 | import com.funtester.httpclient.GCThread; 8 | import org.apache.http.client.methods.HttpRequestBase; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.Logger; 11 | 12 | /** 13 | * http请求多线程类 14 | */ 15 | public class RequestThreadTimes extends ThreadLimitTimesCount { 16 | 17 | private static final long serialVersionUID = 84690314667174004L; 18 | 19 | private static Logger logger = LogManager.getLogger(RequestThreadTimes.class); 20 | 21 | /** 22 | * 单请求多线程多次任务构造方法 23 | * 24 | * @param request 被执行的请求 25 | * @param times 每个线程运行的次数 26 | */ 27 | public RequestThreadTimes(HttpRequestBase request, int times) { 28 | super(request, times, null); 29 | } 30 | 31 | /** 32 | * 应对对每个请求进行标记的情况 33 | * 34 | * @param request 35 | * @param times 36 | * @param mark 37 | */ 38 | public RequestThreadTimes(HttpRequestBase request, int times, MarkThread mark) { 39 | super(request, times, mark); 40 | } 41 | 42 | protected RequestThreadTimes() { 43 | super(); 44 | } 45 | 46 | @Override 47 | public void before() { 48 | super.before(); 49 | GCThread.starts(); 50 | } 51 | 52 | /** 53 | * @throws Exception 54 | */ 55 | @Override 56 | protected void doing() throws Exception { 57 | FunLibrary.executeSimlple(f); 58 | } 59 | 60 | @Override 61 | public RequestThreadTimes clone() { 62 | RequestThreadTimes threadTimes = new RequestThreadTimes(); 63 | threadTimes.times = this.times; 64 | threadTimes.f = FunRequest.cloneRequest(f); 65 | threadTimes.mark = mark == null ? null : mark.clone(); 66 | return threadTimes; 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/Join.java: -------------------------------------------------------------------------------- 1 | package com.funtester.utils; 2 | 3 | import org.apache.commons.lang3.ArrayUtils; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | public class Join { 7 | 8 | /** 9 | * 把字符串每个字符用分隔器连接起来 10 | * 11 | * @param text 12 | * @param separator 13 | * @return 14 | */ 15 | public static String join(String text, String separator) { 16 | return StringUtils.join(ArrayUtils.toObject(text.toCharArray()), separator); 17 | } 18 | 19 | /** 20 | * 把字符串每个字符用分隔器连接起来 21 | * 22 | * @param text 23 | * @param separator 24 | * @return 25 | */ 26 | public static String join(String text, String separator, String prefix, String suffix) { 27 | return prefix + join(text, separator) + suffix; 28 | } 29 | 30 | /** 31 | * 把Iterable用分隔器连接起来 32 | * 33 | * @param iterable 34 | * @param separator 35 | * @param prefix 36 | * @param suffix 37 | * @return 38 | */ 39 | public static String join(Iterable iterable, String separator, String prefix, String suffix) { 40 | return prefix + join(iterable, separator) + suffix; 41 | } 42 | 43 | /** 44 | * 把Iterable用分隔器连接起来,没有前后缀 45 | * 46 | * @param iterable 47 | * @param separator 48 | * @return 49 | */ 50 | public static String join(Iterable iterable, String separator) { 51 | return StringUtils.join(iterable, separator); 52 | } 53 | 54 | /** 55 | * 把数组添加用间隔符连接 56 | * 57 | * @param objects 58 | * @param separator 59 | * @param prefix 前缀 60 | * @param suffix 后缀 61 | * @return 62 | */ 63 | public static String join(Object[] objects, String separator, String prefix, String suffix) { 64 | return prefix + join(objects, separator) + suffix; 65 | } 66 | 67 | /** 68 | * 把数组添加用间隔符连接 69 | * 70 | * @param objects 71 | * @param separator 间隔 72 | * @return 73 | */ 74 | public static String join(Object[] objects, String separator) { 75 | return StringUtils.join(objects, separator); 76 | } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/thread/HeaderMark.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.thread; 2 | 3 | import com.funtester.base.constaint.ThreadBase; 4 | import com.funtester.base.exception.ParamException; 5 | import com.funtester.base.interfaces.MarkRequest; 6 | import com.funtester.frame.SourceCode; 7 | import com.funtester.utils.StringUtil; 8 | import org.apache.http.client.methods.HttpRequestBase; 9 | 10 | import java.io.Serializable; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | 13 | public class HeaderMark extends SourceCode implements MarkRequest, Cloneable, Serializable { 14 | 15 | private static final long serialVersionUID = -1595942567071153477L; 16 | 17 | public static AtomicInteger threadName = new AtomicInteger(getRandomIntRange(1000, 9000)); 18 | 19 | String headerName; 20 | 21 | private String m; 22 | 23 | int num = getRandomIntRange(100, 999) * 1000; 24 | 25 | @Override 26 | public String mark(ThreadBase threadBase) { 27 | if (threadBase instanceof RequestThreadTime) { 28 | RequestThreadTime req = (RequestThreadTime) threadBase; 29 | return mark(req.f); 30 | } else if (threadBase instanceof RequestThreadTimes) { 31 | RequestThreadTimes req = (RequestThreadTimes) threadBase; 32 | return mark(req.f); 33 | } else { 34 | ParamException.fail(threadBase.getClass().toString()); 35 | } 36 | return EMPTY; 37 | } 38 | 39 | /** 40 | * 标记请求 41 | * 42 | * @param base 43 | * @return 44 | */ 45 | @Override 46 | public String mark(HttpRequestBase base) { 47 | base.removeHeaders(headerName); 48 | String value = m + num++; 49 | base.addHeader(headerName, value); 50 | return value; 51 | } 52 | 53 | @Override 54 | public HeaderMark clone() { 55 | return new HeaderMark(headerName); 56 | } 57 | 58 | public HeaderMark(String headerName) { 59 | this.headerName = headerName; 60 | this.m = DEFAULT_STRING + StringUtil.getStringWithoutNum(4) + threadName.getAndIncrement(); 61 | } 62 | 63 | public HeaderMark() { 64 | 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/xml/XMLUtil.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.utils.xml 2 | /** 3 | * 基于dom解析xml文件工具类 4 | */ 5 | class XMLUtil { 6 | 7 | // private static Logger logger = LogManager.getLogger(XMLUtil.class) 8 | // 9 | // /** 10 | // * 解析某个节点(根节点)信息 11 | // * @param path 绝对路径或者URL 12 | // * @param root 13 | // * @return 14 | // */ 15 | // static List parseRoot(String path, String root) { 16 | // DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance() 17 | // try { 18 | // DocumentBuilder db = dbf.newDocumentBuilder() 19 | // Document document = db.parse(path.startsWith("http") ? new URI(path) : new File(path)) 20 | // NodeList nodeList = document.getElementsByTagName(root) 21 | // return range(nodeList.getLength()).mapToObj {x -> parseNode(nodeList.item(x))}.collect() as List 22 | // } catch (ParserConfigurationException e) { 23 | // logger.error("解析配置错误!", e) 24 | // } catch (IOException e) { 25 | // logger.error("IO错误!", e) 26 | // } catch (SAXException e) { 27 | // logger.error("SAX错误!", e) 28 | // } 29 | // FailException.fail("解析文件:${path}中${root}节点出错!") 30 | // } 31 | // 32 | // /** 33 | // * 解析某个节点信息 34 | // * @param node 35 | // * @return 36 | // */ 37 | // static NodeInfo parseNode(Node node) { 38 | // if (node.getNodeType() != Node.ELEMENT_NODE) return null 39 | // NodeInfo nodeInfo = new NodeInfo() 40 | // NamedNodeMap attrs = node.getAttributes() 41 | // List nodeAttr = new ArrayList<>() 42 | // range(attrs.getLength()).each { 43 | // Node attr = attrs.item(it) 44 | // nodeAttr << new Attr(attr.getNodeName(), attr.getNodeValue()) 45 | // } 46 | // nodeInfo.attrs = nodeAttr 47 | // NodeList childNodes = node.getChildNodes() 48 | // List children = new ArrayList<>() 49 | // range(childNodes.getLength()).each {children.add(parseNode(childNodes.item(it)))} 50 | // nodeInfo.children = children.findAll {it != null} 51 | // return nodeInfo 52 | // } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/bean/PerformanceResultBean.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.base.bean 2 | 3 | import com.funtester.db.mysql.MySqlTest 4 | import com.funtester.frame.Output 5 | import com.funtester.utils.DecodeEncode 6 | 7 | /** 8 | * 性能测试结果集 9 | */ 10 | class PerformanceResultBean extends AbstractBean implements Serializable { 11 | 12 | private static final long serialVersionUID = -1595942562342357L; 13 | 14 | /** 15 | * 测试用例描述 16 | */ 17 | String mark 18 | 19 | /** 20 | * 开始时间 21 | */ 22 | String startTime 23 | 24 | /** 25 | * 结束时间 26 | */ 27 | String endTime 28 | 29 | /** 30 | * 表格信息 31 | */ 32 | String table 33 | 34 | /** 35 | * 线程数 36 | */ 37 | int threads 38 | 39 | /** 40 | * 总请求次数 41 | */ 42 | int total 43 | 44 | /** 45 | * 平均响应时间 46 | */ 47 | int rt 48 | 49 | /** 50 | * 吞吐量,公式为QPS=Thead/avg(time) 51 | */ 52 | double qps 53 | 54 | /** 55 | * 通过QPS=count(r)/T公式计算得到的QPS,在固定QPS模式中,这个值来源于预设QPS 56 | */ 57 | double qps2 58 | 59 | /** 60 | * 理论误差,两种统计模式 61 | */ 62 | String deviation 63 | 64 | /** 65 | * 错误率 66 | */ 67 | double errorRate 68 | 69 | /** 70 | * 失败率 71 | */ 72 | double failRate 73 | 74 | /** 75 | * 执行总数 76 | */ 77 | int executeTotal 78 | 79 | PerformanceResultBean(String mark, String startTime, String endTime, int threads, int total, int rt, double qps, double qps2, double errorRate, double failRate, int executeTotal, String table) { 80 | this.mark = mark 81 | this.startTime = startTime 82 | this.endTime = endTime 83 | this.threads = threads 84 | this.total = total 85 | this.rt = rt 86 | this.qps = qps 87 | this.qps2 = qps2 88 | this.errorRate = errorRate 89 | this.failRate = failRate 90 | this.executeTotal = executeTotal 91 | this.table = DecodeEncode.zipBase64(table) 92 | this.deviation = com.funtester.frame.SourceCode.getPercent(Math.abs(qps - qps2) * 100 / Math.max(qps, qps2)) 93 | Output.output(this.toJson()) 94 | Output.output(table) 95 | MySqlTest.savePerformanceBean(this) 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/bean/RecordBean.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.base.bean 2 | 3 | import org.apache.logging.log4j.LogManager 4 | import org.apache.logging.log4j.Logger 5 | 6 | /** 7 | * 测试记录的bean 8 | */ 9 | class RecordBean extends AbstractBean implements Serializable{ 10 | 11 | private static final long serialVersionUID = -159594234325649847L; 12 | 13 | static Logger logger = LogManager.getLogger(RecordBean.class) 14 | 15 | String domain; 16 | 17 | String type; 18 | 19 | String api; 20 | 21 | long expend_time; 22 | 23 | int data_size; 24 | 25 | int status; 26 | 27 | int code; 28 | 29 | String method; 30 | 31 | String local_ip; 32 | 33 | String local_name; 34 | 35 | String create_time; 36 | 37 | static RecordBean get() { 38 | new RecordBean() 39 | } 40 | 41 | RecordBean setDomain(String domain) { 42 | this.domain = domain 43 | this 44 | } 45 | 46 | RecordBean setType(String type) { 47 | this.type = type 48 | this 49 | } 50 | 51 | RecordBean setApi(String api) { 52 | this.api = api 53 | this 54 | } 55 | 56 | RecordBean setExpend_time(long expend_time) { 57 | this.expend_time = expend_time 58 | this 59 | } 60 | 61 | RecordBean setData_size(int data_size) { 62 | this.data_size = data_size 63 | this 64 | } 65 | 66 | RecordBean setStatus(int status) { 67 | this.status = status 68 | this 69 | } 70 | 71 | RecordBean setCode(int code) { 72 | this.code = code 73 | this 74 | } 75 | 76 | RecordBean setMethod(String method) { 77 | this.method = method 78 | this 79 | } 80 | 81 | RecordBean setLocal_ip(String local_ip) { 82 | this.local_ip = local_ip 83 | this 84 | } 85 | 86 | RecordBean setLocal_name(String local_name) { 87 | this.local_name = local_name 88 | this 89 | } 90 | 91 | RecordBean setCreate_time(String create_time) { 92 | this.create_time = create_time 93 | this 94 | } 95 | 96 | @Override 97 | def print() { 98 | logger.info "接口:{},响应时间{}", api, expend_time 99 | } 100 | } -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/WriteHtml.java: -------------------------------------------------------------------------------- 1 | package com.funtester.utils; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 生成表格封装类 7 | */ 8 | public class WriteHtml { 9 | 10 | /** 11 | * 获取表格头部信息 12 | * 13 | * @param list 14 | * @return 15 | */ 16 | private static String getTable(List list) { 17 | StringBuffer start = new StringBuffer(""); 18 | start.append(""); 19 | for (int i = 0; i < list.size(); i++) { 20 | start.append(""); 21 | } 22 | String end = ""; 23 | return start + end; 24 | } 25 | 26 | /** 27 | * 拼接整个页面 28 | * 29 | * @param result 30 | * @param title 31 | * @return 32 | */ 33 | public static String createWebReport(List> result, String title) { 34 | String starttext = "

" + title + "

" + getTable(result.get(0)); 35 | String endtext = "
序号" + list.get(i).toString() + "
"; 36 | StringBuffer sheet = new StringBuffer(starttext); 37 | for (int i = 1; i < result.size(); i++) { 38 | List objects = result.get(i); 39 | sheet.append(""); 40 | sheet.append("" + i + ""); 41 | sheet.append(getTr(objects)); 42 | sheet.append(""); 43 | } 44 | sheet.append(endtext); 45 | return sheet.toString(); 46 | } 47 | 48 | /** 49 | * 获取行信息 50 | * 51 | * @param tr 52 | * @return 53 | */ 54 | private static StringBuffer getTr(List tr) { 55 | StringBuffer body = new StringBuffer(); 56 | for (int i = 0; i < tr.size(); i++) { 57 | body.append("" + tr.get(i).toString() + ""); 58 | } 59 | return body; 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/config/SqlConstant.java: -------------------------------------------------------------------------------- 1 | package com.funtester.config; 2 | 3 | 4 | /** 5 | * 6 | */ 7 | public class SqlConstant { 8 | 9 | static PropertyUtils.Property propertyUtils = PropertyUtils.getProperties("mysql"); 10 | 11 | static String getProperty(String name) { 12 | return propertyUtils.getProperty(name); 13 | } 14 | 15 | /** 16 | * 驱动名称 17 | */ 18 | public static final String DRIVE = "com.mysql.cj.jdbc.Driver"; 19 | 20 | /** 21 | * 数据库默认连接设置 22 | */ 23 | public static final String SQLARGS = "?useUnicode=true&characterEncoding=utf-8&useOldAliasMetadataBehavior=true"; 24 | 25 | /** 26 | * 测试数据库 27 | */ 28 | public static final String TEST_SQL_URL = getProperty("test_mysql_url") + SQLARGS; 29 | 30 | public static final String TEST_USER = getProperty("user"); 31 | 32 | public static final String TEST_PASS_WORD = getProperty("password"); 33 | 34 | /** 35 | * 数据库账号 36 | */ 37 | public static final String FUN_SQL_URL = "jdbc:mysql://ip/database" + SQLARGS; 38 | 39 | /** 40 | * 数据库存储服务接口地址 41 | */ 42 | public static final String MYSQL_SERVER_PATH = getProperty("mysql_server_path"); 43 | 44 | /** 45 | * 数据库连接重连间隔 46 | */ 47 | public static final int MYSQL_RECONNECTION_GAP = 250; 48 | 49 | /** 50 | * 数据库存储任务每个线程最大等待数量 51 | */ 52 | public static final int MYSQL_WORK_PER_THREAD = 30; 53 | 54 | /** 55 | * 最大等待数量,超过上限不再创建新的线程 56 | */ 57 | public static final int MYSQL_MAX_WAIT_WORK = MYSQL_WORK_PER_THREAD * 50; 58 | 59 | /** 60 | * 获取数据库存储任务的超时时间,单位毫秒 61 | */ 62 | public static final int MYSQLWORK_TIMEOUT = 200; 63 | 64 | /** 65 | * 默认request表名 66 | */ 67 | public static String REQUEST_TABLE; 68 | 69 | /** 70 | * 默认result表名 71 | */ 72 | public static String RESULT_TABLE; 73 | 74 | /** 75 | * 默认class表名 76 | */ 77 | public static String CLASS_TABLE; 78 | 79 | /** 80 | * 默认性能测试表名 81 | */ 82 | public static String PERFORMANCE_TABLE; 83 | 84 | /** 85 | * 默认的alertover表格 86 | */ 87 | public static String ALERTOVER_TABLE; 88 | 89 | /** 90 | * 是否保存所有的请求到数据库 91 | */ 92 | public static boolean flag = propertyUtils.getPropertyBoolean("flag"); 93 | } 94 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/db/mongodb/MongoObject.java: -------------------------------------------------------------------------------- 1 | package com.funtester.db.mongodb; 2 | 3 | 4 | /** 5 | * mongo数据库配置对象,针对单个数据服务,单个身份验证 6 | */ 7 | public class MongoObject extends MongoBase { 8 | // 9 | // String host; 10 | // 11 | // int port; 12 | // 13 | // String user; 14 | // 15 | // String password; 16 | // 17 | // String database; 18 | // 19 | // MongoClient mongoClient; 20 | // 21 | // /** 22 | // * 创建测试数据连接 23 | // * 24 | // * @param host 25 | // * @param port 26 | // * @param user 27 | // * @param password 28 | // * @param database 29 | // */ 30 | // public MongoObject(String host, int port, String user, String password, String database) { 31 | // this.host = host; 32 | // this.port = port; 33 | // this.user = user; 34 | // this.password = password; 35 | // this.database = database; 36 | // this.mongoClient = getMongoClient(this); 37 | // } 38 | // 39 | // /** 40 | // * 创建线上数据库连接 41 | // * 42 | // * @param port 43 | // * @param host 44 | // * @param user 45 | // * @param password 46 | // * @param database 47 | // */ 48 | // public MongoObject(int port, String host, String user, String password, String database) { 49 | // this.host = host; 50 | // this.port = port; 51 | // this.user = user; 52 | // this.password = password; 53 | // this.database = database; 54 | // this.mongoClient = getMongoClientOnline(this); 55 | // } 56 | // 57 | // /** 58 | // * 获取colletion对象 59 | // * 60 | // * @param collectionName 61 | // * @return 62 | // */ 63 | // public MongoCollection getMongoCollection(String collectionName) { 64 | // MongoClient mongoClientOnline = getMongoClientOnline(this); 65 | // return mongoClientOnline.getDatabase(database).getCollection(collectionName); 66 | // } 67 | // 68 | // 69 | // /** 70 | // * 关闭连接 71 | // */ 72 | // public void over() { 73 | // over(this.mongoClient); 74 | // } 75 | // 76 | // @Override 77 | // public MongoObject clone() { 78 | // return new MongoObject(this.host, this.port, this.user, this.password, this.database); 79 | // } 80 | // 81 | // public MongoObject clone2() { 82 | // return new MongoObject(this.port, this.host, this.user, this.password, this.database); 83 | // } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/db/mysql/MySqlFun.java: -------------------------------------------------------------------------------- 1 | package com.funtester.db.mysql; 2 | 3 | /** 4 | * mysql操作的基础类 5 | *

用于存储数据,多用于爬虫

6 | */ 7 | @Deprecated 8 | public class MySqlFun extends SqlBase { 9 | //public class MySqlFun extends SqlBase implements IMySqlBasic { 10 | 11 | // String url; 12 | // 13 | // String database; 14 | // 15 | // String user; 16 | // 17 | // String password; 18 | // 19 | // Connection connection; 20 | // 21 | // Statement statement; 22 | // 23 | // /** 24 | // * 私有构造方法 25 | // */ 26 | // public MySqlFun(String url, String database, String user, String password) { 27 | // this.url = url; 28 | // this.database = database; 29 | // this.user = user; 30 | // this.password = password; 31 | // getConnection(database); 32 | // } 33 | // 34 | // /** 35 | // * 初始化连接 36 | // */ 37 | // @Override 38 | // public void getConnection() { 39 | // getConnection(EMPTY); 40 | // } 41 | // 42 | // /** 43 | // * 执行sql语句,非query语句,并不关闭连接 44 | // * 45 | // * @param sql 46 | // */ 47 | // @Override 48 | // public void executeUpdateSql(String sql) { 49 | // executeUpdateSql(EMPTY, sql); 50 | // } 51 | // 52 | // /** 53 | // * 执行sql语句,非query语句,并不关闭连接 54 | // * 55 | // * @param database 56 | // * @param sql 57 | // */ 58 | // @Override 59 | // public void executeUpdateSql(String database, String sql) { 60 | // getConnection(database); 61 | // SqlBase.executeUpdateSql(connection, statement, sql); 62 | // } 63 | // 64 | // /** 65 | // * 查询功能 66 | // * 67 | // * @param sql 68 | // * @return 69 | // */ 70 | // @Override 71 | // public ResultSet executeQuerySql(String sql) { 72 | // return SqlBase.executeQuerySql(connection, statement, sql); 73 | // } 74 | // 75 | // @Override 76 | // public ResultSet executeQuerySql(String database, String sql) { 77 | // getConnection(database); 78 | // return executeQuerySql(sql); 79 | // } 80 | // 81 | // /** 82 | // * 关闭query连接 83 | // */ 84 | // @Override 85 | // public void mySqlOver() { 86 | // SqlBase.mySqlOver(connection, statement); 87 | // } 88 | // 89 | // @Override 90 | // public void getConnection(String database) { 91 | // connection = SqlBase.getConnection(SqlConstant.FUN_SQL_URL.replace("ip", url).replace("database", database), user, password); 92 | // statement = SqlBase.getStatement(connection); 93 | // } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/dubbo/DubboBase.java: -------------------------------------------------------------------------------- 1 | package com.funtester.dubbo; 2 | 3 | public class DubboBase { 4 | 5 | // private ApplicationConfig applicationConfig = new ApplicationConfig(); 6 | // 7 | // private RegistryConfig registryConfig = new RegistryConfig(); 8 | // 9 | // private String version; 10 | // 11 | // private String registryAddress; 12 | // 13 | // ReferenceConfig referenceConfig; 14 | // 15 | // ReferenceConfigCache configCache; 16 | // 17 | // public DubboBase(String propertyName) { 18 | // PropertyUtils.Property properties = PropertyUtils.getProperties(propertyName); 19 | // this.registryAddress = properties.getProperty("address"); 20 | // this.version = properties.getProperty("version"); 21 | // RegistryConfig registryConfig = new RegistryConfig(); 22 | // registryConfig.setAddress(registryAddress); 23 | // applicationConfig.setName(properties.getProperty("name")); 24 | // } 25 | // 26 | // /** 27 | // * 不依赖配置文件 28 | // * 29 | // * @param adress 30 | // * @param version 31 | // * @param name 32 | // */ 33 | // public DubboBase(String adress, String version, String name) { 34 | // this.registryAddress = adress; 35 | // this.version = version; 36 | // RegistryConfig registryConfig = new RegistryConfig(); 37 | // registryConfig.setAddress(registryAddress); 38 | // applicationConfig.setName(name); 39 | // } 40 | // 41 | // /** 42 | // * ReferenceConfig实例很重,封装了与注册中心的连接以及与提供者的连接, 43 | // * 需要缓存,否则重复生成ReferenceConfig可能造成性能问题并且会有内存和连接泄漏。 44 | // * API方式编程时,容易忽略此问题。 45 | // * 这里使用dubbo内置的简单缓存工具类进行缓存 46 | // * 47 | // * @param interfaceClass 48 | // * @return 49 | // */ 50 | // public GenericService getGenericService(String interfaceClass) { 51 | // if (referenceConfig == null) { 52 | // referenceConfig = new ReferenceConfig(); 53 | // referenceConfig.setApplication(applicationConfig); 54 | // referenceConfig.setRegistry(registryConfig); 55 | // referenceConfig.setVersion(version); 56 | // // 弱类型接口名 57 | // referenceConfig.setInterface(interfaceClass); 58 | // // 声明为泛化接口 59 | // referenceConfig.setGeneric(true); 60 | // } 61 | // configCache = ReferenceConfigCache.getCache(StringUtil.getChinese(5)); 62 | // return configCache.get(referenceConfig); 63 | // } 64 | // 65 | // /** 66 | // * 释放资源 67 | // */ 68 | // public void over() { 69 | // if (null != configCache) configCache.destroy(referenceConfig); 70 | // } 71 | // 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/request/RequestFile.java: -------------------------------------------------------------------------------- 1 | package com.funtester.utils.request; 2 | 3 | import com.funtester.config.Constant; 4 | import com.funtester.config.RequestType; 5 | import com.funtester.httpclient.FunLibrary; 6 | import com.funtester.utils.RWUtil; 7 | import com.alibaba.fastjson.JSONObject; 8 | import org.apache.http.client.methods.HttpRequestBase; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.Logger; 11 | 12 | 13 | /** 14 | * 从文件中读取接口相关参数,用来发送请求,实现接口请求的配置化 15 | *

从当前路径下获取后缀为.log的文件,以文件名为准读取文件内容

16 | */ 17 | public class RequestFile extends FunLibrary { 18 | 19 | private static Logger logger = LogManager.getLogger(RequestFile.class); 20 | 21 | String url; 22 | 23 | /** 24 | * get对应get请求,post对应post请求表单参数,其他对应post请求json参数 25 | */ 26 | JSONObject headers; 27 | 28 | RequestType requestType; 29 | 30 | String name; 31 | 32 | JSONObject info; 33 | 34 | JSONObject params; 35 | 36 | /** 37 | * @param name 38 | */ 39 | public RequestFile(String name) { 40 | this.name = name; 41 | getInfo(); 42 | this.url = this.info.getString("url"); 43 | requestType = RequestType.getRequestType(this.info.getString("requestType")); 44 | getParams(); 45 | headers = JSONObject.parseObject(this.info.getString("headers")); 46 | } 47 | 48 | /** 49 | * 获取当前目录下的配置文件,以数字开头,后缀是.log的 50 | * 51 | * @param i 52 | */ 53 | public RequestFile(int i) { 54 | this(i + Constant.EMPTY); 55 | } 56 | 57 | /** 58 | * 从配置文件中读取信息,组成一个json对象 59 | */ 60 | private void getInfo() { 61 | String filePath = Constant.WORK_SPACE + this.name; 62 | logger.info("配置文件地址:" + filePath); 63 | this.info = RWUtil.readTxtByJson(filePath); 64 | } 65 | 66 | /** 67 | * 获取请求参数 68 | */ 69 | private void getParams() { 70 | params = JSONObject.parseObject(info.getString("params")); 71 | } 72 | 73 | 74 | /** 75 | * 根据info组成请求 76 | * 77 | * @return 78 | */ 79 | public HttpRequestBase getRequest() { 80 | HttpRequestBase requestBase; 81 | switch (this.requestType) { 82 | case GET: 83 | requestBase = getHttpGet(this.url, this.params); 84 | break; 85 | case POST: 86 | requestBase = getHttpPost(this.url, this.params); 87 | break; 88 | default: 89 | requestBase = getHttpPost(this.url, this.params.toString()); 90 | break; 91 | } 92 | this.headers.keySet().forEach(x -> requestBase.addHeader(getHeader(x.toString(), headers.getString(x.toString())))); 93 | output(getHttpResponse(requestBase)); 94 | return requestBase; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/FileUtil.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.utils 2 | 3 | import com.funtester.config.Constant 4 | import org.apache.logging.log4j.LogManager 5 | import org.apache.logging.log4j.Logger 6 | 7 | /** 8 | * 文件读写类,与{@link RWUtil}有功能上的重合,原因在与Java和Groovy的不兼容问题. 9 | */ 10 | class FileUtil extends Constant { 11 | 12 | private static Logger logger = LogManager.getLogger(FileUtil.class) 13 | 14 | /** 15 | * 拷贝文件 16 | * @param source 17 | * @param target 18 | * @return 19 | */ 20 | static def copy(String source, String target) { 21 | def s = new File(source) 22 | def t = new File(target) 23 | if (s.exists() && s.isFile()) t.newOutputStream() << s.newInputStream() 24 | } 25 | 26 | /** 27 | * 重命名一个文件 28 | * @param oldPath 29 | * @param newPath 30 | * @return 31 | */ 32 | static def rename(String oldPath, String newPath) { 33 | if (new File(oldPath).renameTo(newPath)) logger.error("rename file error!,old:{},new:{}", oldPath, newPath) 34 | } 35 | 36 | /** 37 | * 从url下载文件 38 | * @param url 39 | * @param name 40 | * @return 41 | */ 42 | static def down(String url, String name) { 43 | new File(name) << new URL(url).openStream() 44 | } 45 | 46 | /** 47 | * 下载文件,目前只要针对图片 48 | * @param url 49 | * @return 50 | */ 51 | static def down(String url) { 52 | def tuple = handlePicName(url) 53 | down(tuple.first, tuple.second); 54 | } 55 | 56 | /** 57 | * 获取文件夹下所有文件的绝对路径的方法,递归,排除了Linux系统的隐藏文件 58 | * 59 | * @param path 60 | * @return 61 | */ 62 | static List getAllFile(String path) { 63 | List list = new ArrayList<>() 64 | File file = new File(path) 65 | if (!file.exists() || file.isFile()) return list 66 | File[] files = file.listFiles() 67 | int length = files.length 68 | if (length == 0) return list 69 | for (int i in 0..length - 1) { 70 | File file1 = files[i] 71 | if (file1.isDirectory()) { 72 | List allFile = getAllFile(file1.getAbsolutePath()) 73 | list.addAll(allFile) 74 | continue 75 | } 76 | String path1 = file1.getAbsolutePath() 77 | if (path1.contains("/.")) continue 78 | list.add(path1) 79 | } 80 | return list 81 | } 82 | 83 | /** 84 | * 处理下载网络图片的时候明文件的问题 85 | * @param name 86 | * @return 87 | */ 88 | static Tuple2 handlePicName(String url) { 89 | url -= ".webp" 90 | String name = url.substring(url.lastIndexOf("/") + 1); 91 | if (name.contains(UNKNOW)) name = name.substring(0, name.indexOf(UNKNOW)) 92 | return new Tuple2(url, name) 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/message/EmailUtil.java: -------------------------------------------------------------------------------- 1 | package com.funtester.utils.message; 2 | 3 | import com.funtester.frame.SourceCode; 4 | 5 | /** 6 | * 发送邮件的类,暂时使用静态方法,默认使用QQ邮箱发送 7 | */ 8 | public class EmailUtil extends SourceCode { 9 | 10 | // private static Logger logger = LogManager.getLogger(EmailUtil.class); 11 | // 12 | // private static Session session; 13 | // 14 | // private static void instance() { 15 | // Security.addProvider(new Provider()); 16 | // //设置邮件会话参数 17 | // Properties props = new Properties(); 18 | // //邮箱的发送服务器地址 19 | // props.setProperty("mail.smtp.host", EmailConstant.QQ_HOST); 20 | // props.setProperty("mail.smtp.socketFactory.class", EmailConstant.SSL_FACTORY); 21 | // props.setProperty("mail.smtp.socketFactory.fallback", "false"); 22 | // //邮箱发送服务器端口,这里设置为465端口 23 | // props.setProperty("mail.smtp.port", "465"); 24 | // props.setProperty("mail.smtp.socketFactory.port", "465"); 25 | // props.put("mail.smtp.auth", "true"); 26 | // //获取到邮箱会话,利用匿名内部类的方式,将发送者邮箱用户名和密码授权给jvm 27 | // session = Session.getDefaultInstance(props, new Authenticator() { 28 | // protected PasswordAuthentication getPasswordAuthentication() { 29 | // return new PasswordAuthentication(EmailConstant.QQ_USERNAME, EmailConstant.QQ_PASSWORD); 30 | // } 31 | // }); 32 | // } 33 | // 34 | // /** 35 | // * 向邮箱发送邮件 36 | // * 37 | // * @param email 对方的邮件地址 38 | // * @param title 邮件的标题 39 | // * @param content 邮件的内容 40 | // * @return 41 | // */ 42 | // public static boolean sendEmail(String email, String title, String content) { 43 | // //多线程优化 44 | // if (session == null) { 45 | // synchronized (EmailUtil.class) { 46 | // if (session == null) 47 | // instance(); 48 | // } 49 | // } 50 | // try { 51 | // Message msg = new MimeMessage(session); 52 | // //设置发件人 53 | // msg.setFrom(new InternetAddress(EmailConstant.QQ_USERNAME)); 54 | // //设置收件人,to为收件人,cc为抄送,bcc为密送 55 | // msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(email, false)); 56 | // msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(email, false)); 57 | // msg.setRecipients(Message.RecipientType.BCC, InternetAddress.parse(email, false)); 58 | // msg.setSubject(title); 59 | // //设置邮件消息 60 | // msg.setText(content); 61 | // //设置发送的日期 62 | // msg.setSentDate(new Date()); 63 | // //调用Transport的send方法去发送邮件 64 | // Transport.send(msg); 65 | // return true; 66 | // } catch (MessagingException e) { 67 | // logger.error(e.getMessage()); 68 | // return false; 69 | // } 70 | // } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/constaint/ThreadLimitTimesCount.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.constaint; 2 | 3 | import com.funtester.base.interfaces.MarkThread; 4 | import com.funtester.config.HttpClientConstant; 5 | import com.funtester.frame.execute.Concurrent; 6 | import com.funtester.httpclient.GCThread; 7 | import com.funtester.utils.Time; 8 | import org.apache.logging.log4j.LogManager; 9 | import org.apache.logging.log4j.Logger; 10 | 11 | /** 12 | * 请求时间限制的多线程类,限制每个线程执行的次数 13 | * 14 | *

15 | * 通常在测试某项用例固定时间的场景下使用,可以提前终止测试用例 16 | *

17 | * 18 | * @param 闭包参数传递使用,Groovy脚本会有一些兼容问题,部分对象需要tostring获取参数值 19 | */ 20 | public abstract class ThreadLimitTimesCount extends ThreadBase { 21 | 22 | private static final long serialVersionUID = -4617192188292407063L; 23 | 24 | private static final Logger logger = LogManager.getLogger(ThreadLimitTimesCount.class); 25 | 26 | 27 | /** 28 | * 任务请求执行次数 29 | */ 30 | public int times; 31 | 32 | public ThreadLimitTimesCount(F f, int times, MarkThread markThread) { 33 | this.times = times; 34 | this.f = f; 35 | this.mark = markThread; 36 | } 37 | 38 | protected ThreadLimitTimesCount() { 39 | super(); 40 | } 41 | 42 | @Override 43 | public void run() { 44 | try { 45 | before(); 46 | long ss = Time.getTimeStamp(); 47 | for (int i = 0; i < times; i++) { 48 | try { 49 | threadmark = mark == null ? EMPTY : this.mark.mark(this); 50 | long s = Time.getTimeStamp(); 51 | doing(); 52 | long e = Time.getTimeStamp(); 53 | executeNum++; 54 | int diff =(int) (e - s); 55 | costs.add(diff); 56 | if (diff > HttpClientConstant.MAX_ACCEPT_TIME) 57 | marks.add(diff + CONNECTOR + threadmark + CONNECTOR + Time.getNow()); 58 | if (status() || ThreadBase.needAbort()) break; 59 | } catch (Exception e) { 60 | logger.warn("执行任务失败!", e); 61 | logger.warn("执行失败对象的标记:{}", threadmark); 62 | errorNum++; 63 | } 64 | } 65 | long ee = Time.getTimeStamp(); 66 | logger.info("线程:{},执行次数:{},错误次数: {},总耗时:{} s", threadName, times, errorNum, (ee - ss) / 1000.0); 67 | Concurrent.allTimes.addAll(costs); 68 | Concurrent.requestMark.addAll(marks); 69 | } catch (Exception e) { 70 | logger.warn("执行任务失败!", e); 71 | } finally { 72 | after(); 73 | } 74 | } 75 | 76 | /** 77 | * 运行待测方法的之前的准备 78 | */ 79 | @Override 80 | public void before() { 81 | super.before(); 82 | } 83 | 84 | @Override 85 | public boolean status() { 86 | return errorNum > 10; 87 | } 88 | 89 | 90 | @Override 91 | protected void after() { 92 | super.after(); 93 | GCThread.stop(); 94 | } 95 | 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/CurlUtil.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.utils 2 | 3 | import com.alibaba.fastjson.JSONObject 4 | import com.funtester.config.Constant 5 | import com.funtester.config.RequestType 6 | import com.funtester.frame.SourceCode 7 | import com.funtester.httpclient.FunLibrary 8 | import com.funtester.httpclient.FunRequest 9 | import org.apache.http.Header 10 | import org.apache.http.client.methods.HttpRequestBase 11 | 12 | /** 13 | * 通过将浏览器中复制的curl文本信息转化成HTTPrequestbase对象工具类 14 | */ 15 | class CurlUtil { 16 | 17 | private static def filterWords = [".js", ".png", ".gif", ".css", ".ico", "list_unread", ".svg", ".htm", ".jpeg", ".ashx"] 18 | 19 | /** 20 | * 从curl复制结果中获取请求 21 | * @param path 22 | * @return 23 | */ 24 | static List getRequests(String path) { 25 | def fileinfo = RWUtil.readTxtFileByLine(path.contains(Constant.OR) ? path : Constant.LONG_Path + path).stream().map {it.trim()} 26 | List requests = [] 27 | def base = new CurlRequestBase() 28 | fileinfo.each { 29 | if (it.startsWith("curl")) { 30 | def split = it.split(" ", 2) 31 | def type = split[0] 32 | def value = split[1] 33 | base.url = value.substring(value.indexOf('h'), value.lastIndexOf("'")) 34 | } else if (it.startsWith("-H")) { 35 | def split = it.split(" ", 2)[1].split(": ") 36 | base.headers << FunLibrary.getHeader(split[0].substring(1), split[1].substring(0, split[1].lastIndexOf("'"))) 37 | } else if (it.startsWith("--data-raw")) { 38 | base.params = SourceCode.getJson(it.substring(it.indexOf("'") + 1, it.lastIndexOf("'")).split("&")) 39 | base.type = RequestType.POST 40 | } else if (it.startsWith("--compressed")) { 41 | requests << getRequest(base) 42 | base = new CurlRequestBase() 43 | } 44 | } 45 | requests.findAll { 46 | it != null && it.getFirstHeader("accept").getValue().contains("application/json") 47 | } 48 | } 49 | 50 | /** 51 | * 将curlrequestbase对象转换成HTTPrequestbase 52 | * @param base 53 | * @return 54 | */ 55 | static HttpRequestBase getRequest(CurlRequestBase base) { 56 | if (filterWords.any { 57 | base.url.contains(it) 58 | }) return 59 | base.type == RequestType.GET ? FunRequest.isGet().setUri(base.url).addHeader(base.headers).getRequest() : FunRequest.isPost().setUri(base.url).addHeader(base.headers).addParams(base.params).getRequest() 60 | } 61 | 62 | /** 63 | * 添加URL过滤词汇 64 | * @param w 65 | */ 66 | static void addFilterWord(String w) { 67 | filterWords << w 68 | } 69 | 70 | /** 71 | * 用于存储每一个请求的详情 72 | */ 73 | static class CurlRequestBase { 74 | 75 | String url 76 | 77 | RequestType type = RequestType.GET 78 | 79 | List
headers = new ArrayList<>() 80 | 81 | JSONObject params = new JSONObject() 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/db/redis/RedisPool.java: -------------------------------------------------------------------------------- 1 | package com.funtester.db.redis; 2 | 3 | import com.funtester.frame.SourceCode; 4 | 5 | /** 6 | * redis连接池 7 | */ 8 | public class RedisPool extends SourceCode { 9 | 10 | // static Logger logger = LogManager.getLogger(RedisPool.class); 11 | // 12 | // static PropertyUtils.Property property = PropertyUtils.getProperties("redis"); 13 | // 14 | // private static String IP = property.getProperty("ip"); 15 | // 16 | // private static int PORT = property.getPropertyInt("port"); 17 | // 18 | // /** 19 | // * 最大连接数 20 | // */ 21 | // private static int MAX_TOTAL = property.getPropertyInt("max_total"); 22 | // 23 | // /** 24 | // * 在jedispool中最大的idle状态(空闲的)的jedis实例的个数 25 | // */ 26 | // private static int MAX_IDLE = property.getPropertyInt("max_idle"); 27 | // 28 | // /** 29 | // * 在jedispool中最小的idle状态(空闲的)的jedis实例的个数 30 | // */ 31 | // private static int MIN_IDLE = property.getPropertyInt("min_idle"); 32 | // 33 | // /** 34 | // * 获取实例的最大等待时间 35 | // */ 36 | // private static long MAX_WAIT = property.getPropertyLong("max_wait"); 37 | // 38 | // /** 39 | // * redis连接的超时时间 40 | // */ 41 | // private static int TIMEOUT = property.getPropertyInt("timeout"); 42 | // 43 | // /** 44 | // * 在borrow一个jedis实例的时候,是否要进行验证操作,如果赋值true。则得到的jedis实例肯定是可以用的 45 | // */ 46 | // private static boolean testOnBorrow = true; 47 | // 48 | // /** 49 | // * 在return一个jedis实例的时候,是否要进行验证操作,如果赋值true。则放回jedispool的jedis实例肯定是可以用的。 50 | // */ 51 | // private static boolean testOnReturn = true; 52 | // 53 | // /** 54 | // * 连接耗尽的时候,是否阻塞,false会抛出异常,true阻塞直到超时。默认为true 55 | // */ 56 | // private static boolean blockWhenExhausted = true; 57 | // 58 | // private static JedisPoolConfig config = getConfig(); 59 | // 60 | // private static JedisPool pool = initPool(); 61 | // 62 | // /** 63 | // * 初始化连接池 64 | // */ 65 | // private static JedisPool initPool() { 66 | // logger.debug("redis连接池IP:{},端口:{},超时设置:{}", IP, PORT, TIMEOUT); 67 | // return new JedisPool(config, IP, PORT, TIMEOUT); 68 | // } 69 | // 70 | // /** 71 | // * 默认连接池配置 72 | // * 73 | // * @return 74 | // */ 75 | // private static JedisPoolConfig getConfig() { 76 | // JedisPoolConfig config = new JedisPoolConfig(); 77 | // config.setMaxTotal(MAX_TOTAL); 78 | // config.setMaxIdle(MAX_IDLE); 79 | // config.setMinIdle(MIN_IDLE); 80 | // config.setTestOnBorrow(testOnBorrow); 81 | // config.setTestOnReturn(testOnReturn); 82 | // config.setBlockWhenExhausted(blockWhenExhausted); 83 | // config.setMaxWaitMillis(MAX_WAIT); 84 | // logger.debug("连接redis配置:{}", JSONObject.toJSONString(config)); 85 | // return config; 86 | // } 87 | // 88 | // /** 89 | // * 获取连接池 90 | // * 91 | // * @return 92 | // */ 93 | // public static JedisPool getPool() { 94 | // return pool; 95 | // } 96 | // 97 | // /** 98 | // * 关闭连接池资源 99 | // */ 100 | // public static void close() { 101 | // pool.close(); 102 | // } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/constaint/ThreadLimitTimeCount.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.constaint; 2 | 3 | import com.funtester.base.interfaces.MarkThread; 4 | import com.funtester.config.HttpClientConstant; 5 | import com.funtester.frame.execute.Concurrent; 6 | import com.funtester.httpclient.GCThread; 7 | import com.funtester.utils.Time; 8 | import org.apache.logging.log4j.LogManager; 9 | import org.apache.logging.log4j.Logger; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * 请求时间限制的多线程类,限制每个线程执行的时间 16 | *

17 | * 通常在测试某项用例固定时间的场景下使用,可以提前终止测试用例 18 | *

19 | * 20 | * @param 闭包参数传递使用,Groovy脚本会有一些兼容问题,部分对象需要tostring获取参数值 21 | */ 22 | public abstract class ThreadLimitTimeCount extends ThreadBase { 23 | 24 | private static final long serialVersionUID = -7017995186493855741L; 25 | 26 | private static final Logger logger = LogManager.getLogger(ThreadLimitTimeCount.class); 27 | 28 | public List marks = new ArrayList<>(); 29 | 30 | /** 31 | * 任务请求执行时间,单位是ms秒 32 | */ 33 | public int time; 34 | 35 | public ThreadLimitTimeCount(F f, int time, MarkThread markThread) { 36 | this.time = time * 1000; 37 | this.f = f; 38 | this.mark = markThread; 39 | } 40 | 41 | protected ThreadLimitTimeCount() { 42 | super(); 43 | } 44 | 45 | @Override 46 | public void run() { 47 | try { 48 | before(); 49 | long ss = Time.getTimeStamp(); 50 | while (true) { 51 | try { 52 | threadmark = mark == null ? EMPTY : this.mark.mark(this); 53 | long s = Time.getTimeStamp(); 54 | doing(); 55 | long et = Time.getTimeStamp(); 56 | executeNum++; 57 | int diff =(int) (et - s); 58 | costs.add(diff); 59 | if (diff > HttpClientConstant.MAX_ACCEPT_TIME) 60 | marks.add(diff + CONNECTOR + threadmark + CONNECTOR + Time.getNow()); 61 | if ((et - ss) > time || status() || ThreadBase.needAbort()) break; 62 | } catch (Exception e) { 63 | logger.warn("执行任务失败!", e); 64 | logger.warn("执行失败对象的标记:{}", threadmark); 65 | errorNum++; 66 | } 67 | } 68 | long ee = Time.getTimeStamp(); 69 | logger.info("线程:{},执行次数:{}, 失败次数: {},总耗时: {} s", threadName, executeNum, errorNum, (ee - ss) / 1000.0); 70 | Concurrent.allTimes.addAll(costs); 71 | Concurrent.requestMark.addAll(marks); 72 | } catch (Exception e) { 73 | logger.warn("执行任务失败!", e); 74 | } finally { 75 | after(); 76 | } 77 | 78 | } 79 | 80 | 81 | 82 | public boolean status() { 83 | return errorNum > 10; 84 | } 85 | 86 | /** 87 | * 运行待测方法的之前的准备 88 | */ 89 | @Override 90 | public void before() { 91 | super.before(); 92 | } 93 | 94 | @Override 95 | protected void after() { 96 | super.after(); 97 | GCThread.stop(); 98 | } 99 | 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/Regex.java: -------------------------------------------------------------------------------- 1 | package com.funtester.utils; 2 | 3 | import com.funtester.base.exception.ParamException; 4 | import com.funtester.frame.SourceCode; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.regex.Matcher; 12 | import java.util.regex.Pattern; 13 | 14 | /** 15 | * 正则验证的封装 16 | */ 17 | public class Regex extends SourceCode { 18 | 19 | private static Logger logger = LogManager.getLogger(Regex.class); 20 | 21 | /** 22 | * 正则校验文本是否匹配 23 | * 24 | * @param text 需要匹配的文本 25 | * @param regex 正则表达式 26 | * @return 27 | */ 28 | public static boolean isRegex(String text, String regex) { 29 | return matcher(text, regex).find(); 30 | } 31 | 32 | /** 33 | * 正则校验文本是否完全匹配,不包含其他杂项,相当于加上了^和$ 34 | * 35 | * @param text 需要匹配的文本 36 | * @param regex 正则表达式 37 | * @return 38 | */ 39 | public static boolean isMatch(String text, String regex) { 40 | return matcher(text, regex).matches(); 41 | } 42 | 43 | /** 44 | * 获取匹配对象 45 | * 46 | * @param text 47 | * @param regex 48 | * @return 49 | */ 50 | private static Matcher matcher(String text, String regex) { 51 | if (StringUtils.isAnyBlank(text, regex)) ParamException.fail("正则参数错误!"); 52 | return Pattern.compile(regex).matcher(text); 53 | } 54 | 55 | /** 56 | * 返回所有匹配项 57 | * 58 | * @param text 需要匹配的文本 59 | * @param regex 正则表达式 60 | * @return 61 | */ 62 | public static List regexAll(String text, String regex) { 63 | Matcher matcher = matcher(text, regex); 64 | List result = new ArrayList<>(); 65 | while (matcher.find()) { 66 | result.add(matcher.group()); 67 | } 68 | return result; 69 | } 70 | 71 | /** 72 | * 获取第一个匹配对象 73 | * 74 | * @param text 75 | * @param regex 76 | * @return 77 | */ 78 | public static String findFirst(String text, String regex) { 79 | Matcher matcher = matcher(text, regex); 80 | if (matcher.find()) return matcher.group(); 81 | return EMPTY; 82 | } 83 | 84 | /** 85 | * 获取匹配项,不包含文字信息,会删除regex的内容 86 | *

不保证完全正确

87 | * 88 | * @param text 89 | * @param regex 90 | * @return 91 | */ 92 | @Deprecated 93 | public static String getRegex(String text, String regex) { 94 | if (StringUtils.isAnyBlank(text, regex)) ParamException.fail("正则参数错误!"); 95 | String result = EMPTY; 96 | try { 97 | result = regexAll(text, regex).get(0); 98 | String[] split = regex.split("(\\.|\\+|\\*|\\?)"); 99 | for (int i = 0; i < split.length; i++) { 100 | String s1 = split[i]; 101 | if (!s1.isEmpty()) 102 | result = result.replaceAll(s1, EMPTY); 103 | } 104 | } catch (Exception e) { 105 | logger.warn("获取匹配对象失败!", e); 106 | } finally { 107 | return result; 108 | } 109 | } 110 | 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/bean/VerifyBean.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.base.bean 2 | 3 | import com.alibaba.fastjson.JSON 4 | import com.funtester.base.exception.ParamException 5 | import com.funtester.config.VerifyType 6 | import com.funtester.utils.JsonUtil 7 | import com.funtester.utils.Regex 8 | import org.apache.logging.log4j.LogManager 9 | import org.apache.logging.log4j.Logger 10 | 11 | /** 12 | * 验证对象类 13 | */ 14 | class VerifyBean extends AbstractBean implements Serializable, Cloneable { 15 | 16 | private static Logger logger = LogManager.getLogger(VerifyBean.class) 17 | 18 | private static final long serialVersionUID = -1595942567071153982L; 19 | 20 | VerifyType type 21 | 22 | /** 23 | * 验证语法 24 | */ 25 | String verify 26 | /** 27 | * 待验证内容 28 | */ 29 | String value 30 | 31 | String des 32 | 33 | boolean isVerify 34 | 35 | boolean result; 36 | 37 | VerifyBean(String verify, String value, String des) { 38 | this.value = value 39 | this.des = des 40 | def split = verify.split(REG_PART, 2) 41 | this.verify = split[1] 42 | this.type = VerifyType.getRequestType(split[0]) 43 | } 44 | 45 | /** 46 | * 用于进行自身验证,会进行isverify和result记录 47 | * @return 48 | */ 49 | boolean verify() { 50 | if (isVerify && result) return result 51 | isVerify = true 52 | result = verify(value) 53 | result 54 | } 55 | 56 | /** 57 | * 用于进行对象外String验证,不会修改对象属性 58 | * @param val 59 | * @return 60 | */ 61 | boolean verify(String val) { 62 | boolean res 63 | try { 64 | switch (type) { 65 | case VerifyType.CONTAIN: 66 | res = val.contains(verify) 67 | return res 68 | case VerifyType.REGEX: 69 | res = Regex.isRegex(val, verify) 70 | return res 71 | case VerifyType.JSONPATH: 72 | def split = verify.split(REG_PART, 2) 73 | def path = split[0] 74 | def v = split[1] 75 | def instance = JsonUtil.getInstance(JSON.parseObject(val)) 76 | res = instance.getVerify(path).fit(v) 77 | return res 78 | case VerifyType.HANDLE: 79 | def sp = verify.split(REG_PART, 2) 80 | def path = sp[0] 81 | def ve = sp[1] 82 | def instance = JsonUtil.getInstance(JSON.parseObject(val)) 83 | res = instance.getVerify(path).fitFun(ve) 84 | return res 85 | default: 86 | ParamException.fail("验证类型参数错误!") 87 | } 88 | } catch (Exception e) { 89 | logger.warn("验证出现问题: {}", e.getMessage()) 90 | res = false 91 | } finally { 92 | /*这里Groovy可以这么写,但是Java不能这么写,因为需要有返回值*/ 93 | logger.info("verify对象 {} ,验证结果: {}", verify, res) 94 | } 95 | } 96 | 97 | @Override 98 | def print() { 99 | logger.info("{} 验证结果: {}", des, result) 100 | } 101 | 102 | @Override 103 | VerifyBean clone() { 104 | new VerifyBean(this.verify, this.value, this.des) 105 | } 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/db/mysql/SqlBase.java: -------------------------------------------------------------------------------- 1 | package com.funtester.db.mysql; 2 | 3 | import com.funtester.frame.SourceCode; 4 | 5 | /** 6 | * 数据库基础类,主要公共的获取连接和操作对象 7 | */ 8 | public class SqlBase extends SourceCode { 9 | 10 | // private static Logger logger = LogManager.getLogger(SqlBase.class); 11 | // 12 | // /** 13 | // * 获取数据库连接 14 | // * 15 | // * @param url 地址,包括端口 16 | // * @param user 用户名 17 | // * @param passowrd 密码 18 | // * @return 19 | // */ 20 | // public static Connection getConnection(String url, String user, String passowrd) { 21 | // logger.debug("连接数据库url:{},user:{},password:{}", url, user, passowrd); 22 | // try { 23 | // Class.forName(SqlConstant.DRIVE); 24 | // } catch (ClassNotFoundException e) { 25 | // logger.warn("加载驱动程序失败!", e); 26 | // } 27 | // try { 28 | // return DriverManager.getConnection(url, user, passowrd); 29 | // } catch (SQLException e) { 30 | // logger.warn("数据库连接失败!", e); 31 | // } 32 | // return null; 33 | // } 34 | // 35 | // /** 36 | // * 获取statement对象 37 | // * 38 | // * @param connection 39 | // * @return 40 | // */ 41 | // public static Statement getStatement(Connection connection) { 42 | // try { 43 | // return connection.createStatement(); 44 | // } catch (SQLException e) { 45 | // logger.warn("获取数据库连接失败!", e); 46 | // } catch (ExceptionInInitializerError e) { 47 | // logger.warn("初始化失败!", e); 48 | // } 49 | // return null; 50 | // } 51 | // 52 | // /** 53 | // * 执行sql语句,查询语句,返回ResultSet,并不关闭连接 54 | // * 55 | // * @param connection 56 | // * @param statement 57 | // * @param sql 58 | // * @return 59 | // */ 60 | // public static ResultSet executeQuerySql(Connection connection, Statement statement, String sql) { 61 | // logger.debug("执行的SQL:{}", sql); 62 | // try { 63 | // if (connection != null && !connection.isClosed()) { 64 | // ResultSet resultSet = statement.executeQuery(sql); 65 | // return resultSet; 66 | // } 67 | // } catch (SQLException e) { 68 | // logger.warn(sql, e); 69 | // } 70 | // return null; 71 | // } 72 | // 73 | // /** 74 | // * 执行sql语句,非query语句,不关闭连接 75 | // * 76 | // * @param connection 77 | // * @param statement 78 | // * @param sql 79 | // */ 80 | // public static void executeUpdateSql(Connection connection, Statement statement, String sql) { 81 | // logger.debug("执行的SQL:{}", sql); 82 | // try { 83 | // if (!connection.isClosed()) statement.executeUpdate(sql); 84 | // } catch (SQLException e) { 85 | // logger.warn(sql, e); 86 | // } 87 | // } 88 | // 89 | // /** 90 | // * 关闭数据库资源 91 | // * 92 | // * @param connection 93 | // * @param statement 94 | // */ 95 | // public static void mySqlOver(Connection connection, Statement statement) { 96 | // try { 97 | // if (connection == null || connection.isClosed()) return; 98 | // statement.close(); 99 | // connection.close(); 100 | // } catch (SQLException e) { 101 | // logger.warn("关闭数据库链接失败!", e); 102 | // } 103 | // } 104 | 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/execute/ThreadPoolUtil.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.execute; 2 | 3 | import java.util.concurrent.*; 4 | 5 | /** 6 | * Java线程池Demo 7 | */ 8 | class ThreadPoolUtil { 9 | 10 | 11 | /** 12 | * 重建可变线程池 13 | * corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中; 14 | * maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程; 15 | * keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0; 16 | * unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性: 17 | * workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue;LinkedBlockingQueue; SynchronousQueue; 18 | *   ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。 19 | * threadFactory:线程工厂,主要用来创建线程; 20 | * handler:表示当拒绝处理任务时的策略,有以下四种取值: 21 | * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 22 | * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 23 | * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) 24 | * ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 25 | * 26 | * @param core 核心线程数 27 | * @param max 最大线程数 28 | * @param liveTime 空闲时间 29 | * @return 30 | */ 31 | static ThreadPoolExecutor createPool(int core = 5, int max = 20, int liveTime = 5) { 32 | return new ThreadPoolExecutor(core, max, liveTime, TimeUnit.SECONDS, new LinkedBlockingDeque(1000)); 33 | 34 | } 35 | 36 | /** 37 | * 定长的线程池 38 | * 39 | * @param size 40 | * @return 41 | */ 42 | static ExecutorService createFixedPool(int size = 10) { 43 | return Executors.newFixedThreadPool(size); 44 | } 45 | 46 | /** 47 | * 缓存线程池,无限长度 48 | * 49 | * @return 50 | */ 51 | static ExecutorService createCachePool() { 52 | return Executors.newCachedThreadPool(); 53 | } 54 | 55 | /*获取线程安全的单例的线程池 56 | static ThreadPoolExecutor getSingleThreadPoolExecutor(AtomicInteger atomicInteger) { 57 | if (singleThreadPoolExecutor == null){ 58 | synchronized (objectLock){ 59 | if (singleThreadPoolExecutor == null){ 60 | singleThreadPoolExecutor = new ThreadPoolExecutor(1, 1, 5, 61 | TimeUnit.SECONDS, new LinkedBlockingQueue(100), 62 | new ThreadFactory() { 63 | @Override 64 | Thread newThread(Runnable runnable) { 65 | Thread thread = new Thread(runnable); 66 | thread.setName("UserCenter-business-" + atomicInteger.getAndIncrement()); 67 | return thread; 68 | } 69 | }, 70 | new ThreadPoolExecutor.CallerRunsPolicy()); 71 | } 72 | } 73 | } 74 | return singleThreadPoolExecutor; 75 | } 76 | */ 77 | } 78 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/Save.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame; 2 | 3 | import com.funtester.base.exception.FailException; 4 | import com.funtester.utils.RWUtil; 5 | import com.alibaba.fastjson.JSONObject; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.apache.logging.log4j.LogManager; 8 | import org.apache.logging.log4j.Logger; 9 | 10 | import java.io.File; 11 | import java.util.ArrayList; 12 | import java.util.Collection; 13 | import java.util.List; 14 | 15 | import static com.funtester.config.Constant.*; 16 | 17 | /** 18 | * 用来保存数据的类,如果文件已经存在会删除原来的文件 19 | */ 20 | public class Save { 21 | 22 | private static Logger logger = LogManager.getLogger(Save.class); 23 | 24 | /** 25 | * 保存信息,每次回删除文件,默认当前工作空间 26 | * 27 | * @param content 内容 28 | */ 29 | public static void info(String content) { 30 | info("long", content); 31 | } 32 | 33 | public static void info(String name, String content) { 34 | File dirFile = new File(LONG_Path + name); 35 | if (dirFile.exists()) { 36 | boolean delete = dirFile.delete(); 37 | if (!delete) FailException.fail("删除文件失败!" + name); 38 | } 39 | RWUtil.writeText(dirFile, content); 40 | logger.info("数据保存成功!文件名:{}{}", LONG_Path, name); 41 | } 42 | 43 | /** 44 | * 保存list数据到本地文件 45 | */ 46 | public static void saveLongList(Collection data, Object name) { 47 | List list = new ArrayList<>(); 48 | data.forEach(num -> list.add(num.toString())); 49 | saveStringList(list, name.toString()); 50 | } 51 | 52 | /** 53 | * 保存list数据到本地文件 54 | */ 55 | public static void saveIntegerList(Collection data, String name) { 56 | List list = new ArrayList<>(); 57 | data.forEach(num -> list.add(num.toString())); 58 | saveStringList(list, name); 59 | } 60 | 61 | /** 62 | * 保存list数据到本地文件 63 | */ 64 | public static void saveDoubleList(Collection data, String name) { 65 | List list = new ArrayList<>(); 66 | data.forEach(num -> list.add(num.toString())); 67 | saveStringList(list, name); 68 | } 69 | 70 | /** 71 | * 保存list数据,long类型无法覆盖 72 | * 73 | * @param data 74 | * @param name 75 | */ 76 | public static void saveList(Collection data, String name) { 77 | List list = new ArrayList<>(); 78 | data.forEach(num -> list.add(num.toString())); 79 | saveStringList(list, name); 80 | } 81 | 82 | /** 83 | * 保存list数据到本地文件 84 | */ 85 | public static void saveStringList(Collection data, String name) { 86 | String join = StringUtils.join(data, LINE); 87 | info(name, join); 88 | } 89 | 90 | /** 91 | * 保存json数据到本地文件 92 | */ 93 | public static void saveJson(JSONObject data, String name) { 94 | StringBuffer buffer = new StringBuffer(); 95 | data.keySet().forEach(x -> buffer.append(LINE + x.toString() + PART + data.getString(x.toString()))); 96 | /*处理\n\t(LINE)*/ 97 | if (buffer.length() > 2) info(name, buffer.substring(2)); 98 | } 99 | 100 | /** 101 | * 同步save数据,用于匿名类多线程保存测试数据 102 | * 103 | * @param data 104 | * @param name 105 | */ 106 | public static void saveStringListSync(Collection data, String name) { 107 | synchronized (Save.class) { 108 | if (data.isEmpty()) return; 109 | saveStringList(data, name); 110 | } 111 | } 112 | 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/message/AlertOver.java: -------------------------------------------------------------------------------- 1 | package com.funtester.utils.message; 2 | 3 | import com.funtester.base.bean.RequestInfo; 4 | import com.funtester.base.interfaces.IMessage; 5 | import com.funtester.httpclient.FunLibrary; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | public class AlertOver extends FunLibrary implements IMessage { 10 | 11 | private static Logger logger = LogManager.getLogger(AlertOver.class); 12 | 13 | String title; 14 | 15 | String content; 16 | 17 | String murl; 18 | 19 | RequestInfo requestInfo; 20 | 21 | private static String system = "s-7e93ec02-1308-480c-bc11-a7260c14";//系统异常 22 | 23 | private static String function = "s-7e3b7ea5-b4b0-4479-a0e3-bce6c830";//功能异常 24 | 25 | private static String business = "s-466a191a-cbb8-4164-b8be-9779bb88";//业务异常 26 | 27 | private static String remind = "s-f49ac5bc-008b-4b11-890e-6715ef89";//提醒推送 28 | 29 | private static String code = "s-490d0fc6-35cc-4430-9f87-09cdeb05";//程序异常 30 | 31 | private static final String testGroup = "g-4eefc0ad-19af-4b1c-9d0b-ef87be15"; 32 | 33 | public AlertOver() { 34 | this("test title", "test content!"); 35 | } 36 | 37 | public AlertOver(String title, String content) { 38 | this.title = title; 39 | this.content = content + LINE + "发送源:" + COMPUTER_USER_NAME; 40 | } 41 | 42 | public AlertOver(String title, String content, String url) { 43 | this(title, content); 44 | this.murl = url; 45 | } 46 | 47 | public AlertOver(String title, String content, String url, RequestInfo requestInfo) { 48 | this(title, content); 49 | this.murl = url; 50 | this.requestInfo = requestInfo; 51 | } 52 | 53 | /** 54 | * 发送系统异常 55 | */ 56 | public void sendSystemMessage() { 57 | // if (SysInit.isBlack(murl)) return; 58 | // sendMessage(system); 59 | // MySqlTest.saveAlertOverMessage(requestInfo, "system", title, LOCAL_IP, COMPUTER_USER_NAME); 60 | // logger.info("发送系统错误提醒,title:{},ip:{},computer:{}", title, LOCAL_IP, COMPUTER_USER_NAME); 61 | } 62 | 63 | /** 64 | * 发送功能异常 65 | */ 66 | public void sendFunctionMessage() { 67 | sendMessage(function); 68 | } 69 | 70 | /** 71 | * 发送业务异常 72 | */ 73 | public void sendBusinessMessage() { 74 | sendMessage(business); 75 | } 76 | 77 | /** 78 | * 发送程序异常 79 | */ 80 | public void sendCodeMessage() { 81 | sendMessage(code); 82 | } 83 | 84 | /** 85 | * 提醒推送 86 | */ 87 | public void sendRemindMessage() { 88 | sendMessage(remind); 89 | } 90 | 91 | /** 92 | * 发送消息 93 | * 94 | * @return 95 | */ 96 | public void sendMessage(String source) { 97 | // if (SysInit.isBlack(murl)) return; 98 | // String url = "https://api.alertover.com/v1/alert"; 99 | // String receiver = testGroup;//测试组ID 100 | // JSONObject jsonObject = new JSONObject();// 新建json数组 101 | // jsonObject.put("frame", source);// 添加发送源id 102 | // jsonObject.put("receiver", receiver);// 添加接收组id 103 | // jsonObject.put("content", content);// 发送内容 104 | // jsonObject.put("title", title);// 发送标题 105 | // jsonObject.put("url", murl);// 发送标题 106 | // jsonObject.put("sound", "pianobar");// 发送声音 107 | // logger.debug("消息详情:{}", jsonObject.toString()); 108 | // HttpPost httpPost = getHttpPost(url, jsonObject); 109 | /*取消发送*/ 110 | // getHttpResponse(httpPost); 111 | } 112 | 113 | 114 | } -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/CMD.java: -------------------------------------------------------------------------------- 1 | package com.funtester.utils; 2 | 3 | import com.funtester.config.Constant; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | 8 | import java.io.*; 9 | import java.nio.charset.Charset; 10 | 11 | /** 12 | * 执行命令的类 13 | */ 14 | public class CMD extends Constant { 15 | 16 | private static Logger logger = LogManager.getLogger(CMD.class); 17 | 18 | /** 19 | * 执行cmd命令,控制台信息编码方式 20 | * 21 | * @param cmd 需要执行的命令 22 | */ 23 | public static int execCmd(String cmd) { 24 | return execCmd(cmd, DEFAULT_CHARSET); 25 | } 26 | 27 | /** 28 | * 执行cmd命令,注意Mac 系统添加环境路径 29 | * 30 | * @param cmd 需要执行的命令 31 | */ 32 | public static int execCmd(String cmd, Charset charset) { 33 | return execCmd(cmd, charset, false, EMPTY); 34 | } 35 | 36 | public static int execCmd(String cmd, boolean filter, String mark) { 37 | return execCmd(cmd, DEFAULT_CHARSET, false, EMPTY); 38 | } 39 | 40 | public static int execCmd(String cmd, Charset charset, boolean filter, String mark) { 41 | logger.info("执行命令:{}", cmd); 42 | Process p = null;// 通过runtime类执行cmd命令 43 | try { 44 | p = Runtime.getRuntime().exec(cmd); 45 | } catch (IOException e) { 46 | logger.error("cmd:{}命令错误", e); 47 | return 1; 48 | } 49 | try (InputStream input = p.getInputStream(); 50 | InputStreamReader inputStreamReader = new InputStreamReader(input, charset); 51 | BufferedReader reader = new BufferedReader(inputStreamReader); 52 | InputStream errorInput = p.getErrorStream(); 53 | InputStreamReader streamReader = new InputStreamReader(errorInput, charset.name()); 54 | BufferedReader errorReader = new BufferedReader(streamReader)) { 55 | String line = EMPTY; 56 | while ((line = reader.readLine()) != null) {// 循环读取 57 | if (!filter || (line.contains(mark))) logger.info(line); 58 | } 59 | String eline = EMPTY; 60 | while ((eline = errorReader.readLine()) != null) {// 循环读取 61 | logger.info(eline);// 输出 62 | } 63 | return 0; 64 | } catch (IOException e) { 65 | logger.warn("执行命令:{}失败!", cmd, e); 66 | p.destroy(); 67 | return 1; 68 | } 69 | } 70 | 71 | /** 72 | * 获取文本信息的最后几行,用户查看日志 73 | * 74 | * @param path 75 | * @param num 76 | * @return 77 | */ 78 | public static String catFile(String path, int num) { 79 | logger.info("查询的文件:{}", path); 80 | if (StringUtils.isEmpty(path)) return EMPTY; 81 | File file = new File(path); 82 | if (!file.exists() || file.isDirectory()) return EMPTY; 83 | StringBuffer stringBuffer = new StringBuffer(); 84 | String command = "tail -n " + num + SPACE_1 + path; 85 | logger.debug("执行命令:{}", command); 86 | try (InputStream input = Runtime.getRuntime().exec(command).getInputStream(); 87 | InputStreamReader inputStreamReader = new InputStreamReader(input, DEFAULT_CHARSET); 88 | BufferedReader reader = new BufferedReader(inputStreamReader);) { 89 | String line = EMPTY; 90 | while ((line = reader.readLine()) != null) {// 循环读取 91 | stringBuffer.append(line + LINE); 92 | } 93 | } catch (IOException e) { 94 | logger.error("获取:{}文件信息失败!", path, e); 95 | } finally { 96 | return stringBuffer.toString(); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/execute/ExecuteGroovy.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.execute; 2 | 3 | import com.funtester.frame.SourceCode; 4 | import com.funtester.utils.FileUtil; 5 | import groovy.lang.GroovyClassLoader; 6 | import groovy.lang.GroovyObject; 7 | import org.apache.logging.log4j.LogManager; 8 | import org.apache.logging.log4j.Logger; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.lang.reflect.Method; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * groovy脚本执行类,用户执行上传的groovy脚本,功能简单,使用未做封装,将就用一下 18 | */ 19 | public class ExecuteGroovy extends SourceCode { 20 | 21 | private static Logger logger = LogManager.getLogger(ExecuteSource.class); 22 | 23 | /** 24 | * 路径 25 | */ 26 | private String path; 27 | 28 | /** 29 | * 文件名 30 | */ 31 | private String name; 32 | 33 | /** 34 | * 所有的脚本文件 35 | */ 36 | private List files = new ArrayList<>(); 37 | 38 | /** 39 | * Groovy类加载器 40 | */ 41 | private GroovyClassLoader loader = new GroovyClassLoader(getClass().getClassLoader()); 42 | 43 | /** 44 | * Groovy对象 45 | */ 46 | private GroovyObject groovyObject; 47 | 48 | /** 49 | * 加载类 50 | */ 51 | private Class groovyClass; 52 | 53 | 54 | public ExecuteGroovy(String path, String name) { 55 | this.path = path; 56 | this.name = name; 57 | getGroovyObject(); 58 | } 59 | 60 | /** 61 | * 执行一个类的所有方法 62 | */ 63 | public void executeAllMethod(String path) { 64 | FileUtil.getAllFile(path); 65 | if (files == null) 66 | return; 67 | files.forEach((file) -> new ExecuteGroovy(file, EMPTY).executeMethodByPath()); 68 | } 69 | 70 | /** 71 | * 执行某个类的方法,需要做过滤 72 | * 73 | * @return 74 | */ 75 | public void executeMethodByName() { 76 | if (new File(path).isDirectory()) { 77 | logger.warn("文件类型错误!"); 78 | } 79 | try { 80 | groovyObject.invokeMethod(name, null); 81 | } catch (Exception e) { 82 | logger.warn("执行" + name + "失败!", e); 83 | } 84 | } 85 | 86 | /** 87 | * 根据path执行相关方法 88 | */ 89 | public void executeMethodByPath() { 90 | Method[] methods = groovyClass.getDeclaredMethods();//获取类方法,此处方法比较多,需过滤 91 | for (Method method : methods) { 92 | String methodName = method.getName(); 93 | if (methodName.contains("test") || methodName.equals("main")) { 94 | groovyObject.invokeMethod(methodName, null); 95 | } 96 | } 97 | } 98 | 99 | /** 100 | * 获取groovy对象和执行类 101 | */ 102 | public void getGroovyObject() { 103 | try { 104 | groovyClass = loader.parseClass(new File(path));//创建类 105 | } catch (IOException e) { 106 | logger.warn("groovy类加载失败!", e); 107 | } 108 | try { 109 | groovyObject = (GroovyObject) groovyClass.newInstance();//创建类对象 110 | } catch (InstantiationException e) { 111 | logger.warn("创建对象失败!", e); 112 | } catch (IllegalAccessException e) { 113 | logger.warn("非法异常!", e); 114 | } 115 | } 116 | 117 | /** 118 | * 获取文件下所有的groovy脚本,不支持递归查询 119 | * 120 | * @return 121 | */ 122 | public List getAllGroovyFile(String path) { 123 | File file = new File(path); 124 | if (file.isFile()) { 125 | files.add(path); 126 | return files; 127 | } 128 | File[] files1 = file.listFiles(); 129 | int size = files1.length; 130 | for (int i = 0; i < size; i++) { 131 | String name = files1[i].getAbsolutePath(); 132 | if (name.endsWith(".groovy")) 133 | files.add(name); 134 | } 135 | return files; 136 | } 137 | 138 | } -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/constaint/ThreadBase.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.constaint; 2 | 3 | import com.funtester.base.interfaces.MarkThread; 4 | import com.funtester.frame.SourceCode; 5 | import com.funtester.httpclient.FunLibrary; 6 | 7 | import java.io.Serializable; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.concurrent.CountDownLatch; 11 | import java.util.stream.Collectors; 12 | 13 | /** 14 | * 多线程任务基类,可单独使用 15 | * 16 | * @param 必需实现Serializable 17 | */ 18 | public abstract class ThreadBase extends SourceCode implements Runnable, Serializable { 19 | 20 | private static final long serialVersionUID = -1282879464717720145L; 21 | 22 | /** 23 | * 全局的时间终止开关,true表示终止,false表示不终止. 24 | */ 25 | private static boolean ABORT = false; 26 | 27 | /** 28 | * 线程的名字 29 | */ 30 | public String threadName; 31 | 32 | /** 33 | * 线程标记对象,用户标记请求或者单次执行任务的 34 | */ 35 | public String threadmark; 36 | 37 | /** 38 | * 错误数 39 | *

这里注意使用{@link FunLibrary#getHttpResponse(org.apache.http.client.methods.HttpRequestBase)}方法获取响应的功能封装方法,即使报错也不会抛异常.这样会导致errorNum错误数为零

40 | */ 41 | public int errorNum; 42 | 43 | /** 44 | * 执行数,一般与响应时间记录数量相同 45 | */ 46 | public int executeNum; 47 | 48 | /** 49 | * 计数锁 50 | *

51 | * 会在concurrent类里面根据线程数自动设定 52 | *

53 | */ 54 | protected CountDownLatch countDownLatch; 55 | 56 | /** 57 | * 标记对象 58 | */ 59 | public MarkThread mark; 60 | 61 | /** 62 | * 用于设置访问资源,用于闭包中无法访问包外实例对象的情况,这里还有一个用处就是在标记线程对象的时候,用到了这个t(参数标记模式中) 63 | * 64 | * @since 2020年10月19日, 统一用来设置HTTPrequestbase对象.同样可以用于执行SQL和redis查询语句或者对象, 暂未使用dubbo尝试 65 | */ 66 | public F f; 67 | 68 | protected ThreadBase() { 69 | } 70 | 71 | /** 72 | * 记录所有超时的请求标记 73 | */ 74 | public List marks = new ArrayList<>(); 75 | 76 | /** 77 | * 用于存储请求耗时集合 78 | * 2021年03月16日,将统计集合提取为对象属性,用于外部访问,可用于取样器实现 79 | */ 80 | public List costs = new ArrayList<>(); 81 | 82 | /** 83 | * 运行待测方法的之前的准备 84 | */ 85 | public void before() { 86 | ABORT = false; 87 | } 88 | 89 | /** 90 | * 待测方法 91 | * 92 | * @throws Exception 抛出异常后记录错误次数,一般在性能测试的时候重置重试控制器不再重试 93 | */ 94 | protected abstract void doing() throws Exception; 95 | 96 | /** 97 | * 运行待测方法后的处理 98 | */ 99 | protected void after() { 100 | costs = new ArrayList<>(); 101 | marks = new ArrayList<>(); 102 | if (countDownLatch != null) 103 | countDownLatch.countDown(); 104 | } 105 | 106 | /** 107 | * 设置计数器 108 | * 109 | * @param countDownLatch 110 | */ 111 | public void setCountDownLatch(CountDownLatch countDownLatch) { 112 | this.countDownLatch = countDownLatch; 113 | } 114 | 115 | /** 116 | * 拷贝对象方法,用于统计单一对象多线程调用时候的请求数和成功数,对于的复杂情况,需要将T类型也重写clone方法 117 | * 118 | *

119 | * 此处若具体实现类而非虚拟类建议自己写clone方法,子类重写需注意{@link ThreadBase#initBase()}方法调用 120 | *

121 | * 122 | * @return 123 | */ 124 | @Override 125 | public abstract ThreadBase clone(); 126 | 127 | /** 128 | * 用于对象拷贝之后,清空存储列表 129 | */ 130 | public void initBase() { 131 | this.costs = new ArrayList<>(); 132 | this.marks = new ArrayList<>(); 133 | } 134 | 135 | /** 136 | * 线程任务是否需要提前关闭,默认返回false 137 | *

138 | * 一般用于单线程错误率过高的情况 139 | *

140 | * 141 | * @return 142 | */ 143 | public boolean status() { 144 | return false; 145 | } 146 | 147 | /** 148 | * Groovy乘法调用方法 149 | * 150 | * @param num 151 | * @return 152 | */ 153 | public List multiply(int num) { 154 | return range(num).mapToObj(x -> this.clone()).collect(Collectors.toList()); 155 | } 156 | 157 | 158 | /** 159 | * 用于在某些情况下提前终止测试 160 | */ 161 | public static void stop() { 162 | ABORT = true; 163 | } 164 | 165 | /** 166 | * true表示终止,false表示不终止. 167 | * 168 | * @return 169 | */ 170 | public static boolean needAbort() { 171 | return ABORT; 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/bean/RequestInfo.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.base.bean 2 | 3 | import com.alibaba.fastjson.JSONObject 4 | import com.funtester.base.interfaces.MarkRequest 5 | import com.funtester.config.Constant 6 | import com.funtester.config.RequestType 7 | import com.funtester.config.SysInit 8 | import org.apache.http.Header 9 | import org.apache.http.HttpEntity 10 | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase 11 | import org.apache.http.client.methods.HttpRequestBase 12 | import org.apache.http.util.EntityUtils 13 | import org.apache.logging.log4j.LogManager 14 | import org.apache.logging.log4j.Logger 15 | 16 | /** 17 | * 请求信息封装类 18 | */ 19 | class RequestInfo extends AbstractBean implements Serializable { 20 | 21 | private static final long serialVersionUID = 5942566988949859847L; 22 | 23 | private static Logger logger = LogManager.getLogger(RequestInfo.class) 24 | 25 | /** 26 | * 请求信息的标记字段,用于日志记录请求 27 | */ 28 | private static MarkRequest mark; 29 | 30 | static void initMark(MarkRequest markRequest) { 31 | mark = markRequest; 32 | } 33 | 34 | /** 35 | * 接口地址 36 | */ 37 | String apiName 38 | 39 | /** 40 | * 请求的url 41 | */ 42 | String url 43 | 44 | /** 45 | * 请求的uri 46 | */ 47 | String uri 48 | 49 | /** 50 | * 方法,get/post 51 | */ 52 | RequestType method 53 | 54 | /** 55 | * 域名 56 | */ 57 | String host 58 | 59 | /** 60 | * 协议类型 61 | */ 62 | String type 63 | 64 | /** 65 | * 参数 66 | */ 67 | String params 68 | 69 | /** 70 | * host是否是黑名单 71 | */ 72 | boolean isBlack; 73 | 74 | /** 75 | * 所有的请求header,会去重 76 | */ 77 | JSONObject headers 78 | 79 | /** 80 | * 存一下 81 | */ 82 | HttpRequestBase request 83 | 84 | /** 85 | * 通过request获取请求的相关信息,并输出部分信息 86 | * 87 | * @param request 88 | */ 89 | RequestInfo(HttpRequestBase request) { 90 | this.request = request 91 | getRequestInfo() 92 | } 93 | 94 | /** 95 | * 封装获取请求的各种信息的方法 96 | * 97 | * @param request 传入请求对象 98 | * @return 返回一个map,包含api_name,host_name,type,method,params 99 | */ 100 | private void getRequestInfo() { 101 | method = RequestType.getRequestType request.getMethod() 102 | uri = request.getURI().toString()// 获取uri 103 | getRequestUrl(uri) 104 | String one = url.substring(url.indexOf("//") + 2)// 删除掉http:// 105 | apiName = one.substring(one.indexOf("/"))// 获取接口名 106 | host = one.substring(0, one.indexOf("/"))// 获取host地址 107 | isBlack = SysInit.isBlack(host) 108 | type = url.substring(0, url.indexOf("//") - 1)// 获取协议类型 109 | if (method == RequestType.GET) { 110 | if (!uri.contains(UNKNOW)) return 111 | params = uri.substring(uri.indexOf(UNKNOW) + 1) 112 | } else if (method == RequestType.POST) { 113 | getPostRequestParams(request) 114 | } 115 | List
list = Arrays.asList(request.getAllHeaders()) 116 | headers = new JSONObject() { 117 | 118 | { 119 | list.each { 120 | put(it.name, it.value) 121 | } 122 | } 123 | } 124 | 125 | } 126 | 127 | /** 128 | * 获取请求url,遇到get请求,先截取 129 | * 130 | * @param uri 131 | */ 132 | private void getRequestUrl(String uri) { 133 | url = uri.contains(UNKNOW) ? uri.substring(0, uri.indexOf(UNKNOW)) : uri 134 | } 135 | 136 | /** 137 | * 获取响应实体,post path,put方法适用 138 | * 139 | * @param request 140 | */ 141 | private void getPostRequestParams(HttpEntityEnclosingRequestBase request) { 142 | HttpEntity entity = request.getEntity()// 获取实体 143 | if (entity == null) return 144 | try { 145 | params = EntityUtils.toString(entity)// 解析实体 146 | EntityUtils.consume(entity)// 确保实体消耗 147 | } catch (Exception e) { 148 | logger.warn("获取post请求参数时异常!") 149 | params = "entity类型:" + entity.getClass() 150 | } 151 | } 152 | 153 | boolean isBlack() { 154 | isBlack 155 | } 156 | 157 | String mark() { 158 | mark == null ? Constant.EMPTY : mark.mark(request) 159 | } 160 | 161 | 162 | @Override 163 | String toString() { 164 | this.toJson().toString() 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/db/mongodb/MongoBase.java: -------------------------------------------------------------------------------- 1 | package com.funtester.db.mongodb; 2 | 3 | import com.funtester.frame.SourceCode; 4 | 5 | /** 6 | * mongo操作类的基础类 7 | */ 8 | @SuppressWarnings("all") 9 | public class MongoBase extends SourceCode { 10 | // 11 | // /** 12 | // * 获取服务地址list 13 | // * 14 | // * @param addresses 15 | // * @return 16 | // */ 17 | // public static List getServers(ServerAddress... addresses) { 18 | // return Arrays.asList(addresses); 19 | // } 20 | // 21 | // /** 22 | // * 获取服务地址 23 | // * 24 | // * @param host 25 | // * @param port 26 | // * @return 27 | // */ 28 | // public static ServerAddress getServerAdress(String host, int port) { 29 | // return new ServerAddress(host, port); 30 | // } 31 | // 32 | // /** 33 | // * 获取认证list 34 | // * 35 | // * @param credentials 36 | // * @return 37 | // */ 38 | // public static List getCredentials(MongoCredential... credentials) { 39 | // return Arrays.asList(credentials); 40 | // } 41 | // 42 | // /** 43 | // * 获取验证 44 | // * 45 | // * @param userName 46 | // * @param database 47 | // * @param password 48 | // * @return 49 | // */ 50 | // public static MongoCredential getMongoCredential(String userName, String database, String password) { 51 | // return MongoCredential.createCredential(userName, database, password.toCharArray()); 52 | // } 53 | // 54 | // /** 55 | // * 获取mongo客户端 56 | // * 57 | // * @param addresses 58 | // * @param credentials 59 | // * @return 60 | // */ 61 | // public static MongoClient getMongoClient(List addresses, List credentials) { 62 | // return new MongoClient(addresses, credentials); 63 | // } 64 | // 65 | // /** 66 | // * 连接mongo数据库 67 | // * 68 | // * @param mongoClient 69 | // * @param databaseName 70 | // * @return 71 | // */ 72 | // public static MongoDatabase getMongoDatabase(MongoClient mongoClient, String databaseName) { 73 | // return mongoClient.getDatabase(databaseName); 74 | // } 75 | // 76 | // /** 77 | // * 连接mongo集 78 | // * 79 | // * @param mongoDatabase 80 | // * @param collectionName 81 | // * @return 82 | // */ 83 | // public static MongoCollection getMongoCollection(MongoDatabase mongoDatabase, String collectionName) { 84 | // return mongoDatabase.getCollection(collectionName); 85 | // } 86 | // 87 | // /** 88 | // * 关闭数据库连接 89 | // * 90 | // * @param mongoClient 91 | // */ 92 | // public static void over(MongoClient mongoClient) { 93 | // mongoClient.close(); 94 | // } 95 | // 96 | // /** 97 | // * 获取mongo客户端对象,通过servers和credentials对象创建 98 | // * 99 | // * @param mongoObject 100 | // * @return 101 | // */ 102 | // public static MongoClient getMongoClient(MongoObject mongoObject) { 103 | // MongoClient mongoClient = new MongoClient(getServers(getServerAdress(mongoObject.host, mongoObject.port)), getCredentials(getMongoCredential(mongoObject.user, mongoObject.database, mongoObject.password))); 104 | // return mongoClient; 105 | // } 106 | // 107 | // /** 108 | // * 获取mongo客户端对象,通过uri方式连接 109 | // * 110 | // * @param mongoObject 111 | // * @return 112 | // */ 113 | // public static MongoClient getMongoClientOnline(MongoObject mongoObject) { 114 | // String format = String.format("mongodb://%s:%s@%s:%d/%s", mongoObject.user, mongoObject.password, mongoObject.host, mongoObject.port, mongoObject.database); 115 | // return new MongoClient(new MongoClientURI(format)); 116 | // } 117 | // 118 | // /** 119 | // * 获取collection对象 120 | // * 121 | // * @param mongoObject 122 | // * @return 123 | // */ 124 | // public static MongoCollection getCollection(MongoObject mongoObject, String collectionName) { 125 | // return getMongoClient(mongoObject).getDatabase(mongoObject.database).getCollection(collectionName); 126 | // } 127 | // 128 | // /** 129 | // * 获取collection对象 130 | // * 131 | // * @param mongoObject 132 | // * @return 133 | // */ 134 | // public static MongoCollection getCollectionOnline(MongoObject mongoObject, String collectionName) { 135 | // return getMongoClientOnline(mongoObject).getDatabase(mongoObject.database).getCollection(collectionName); 136 | // } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/base/interfaces/IBase.java: -------------------------------------------------------------------------------- 1 | package com.funtester.base.interfaces; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.funtester.base.bean.RequestInfo; 5 | import org.apache.http.client.methods.CloseableHttpResponse; 6 | import org.apache.http.client.methods.HttpGet; 7 | import org.apache.http.client.methods.HttpPost; 8 | import org.apache.http.client.methods.HttpRequestBase; 9 | 10 | import java.io.File; 11 | 12 | /** 13 | * 每个项目需要重写的方法 14 | */ 15 | public interface IBase { 16 | 17 | /** 18 | * 获取get请求对象 19 | * 20 | * @param url 21 | * @return 22 | */ 23 | HttpGet getGet(String url); 24 | 25 | /** 26 | * 获取get请求对象 27 | * 28 | * @param url 29 | * @param arg 30 | * @return 31 | */ 32 | HttpGet getGet(String url, JSONObject arg); 33 | 34 | /** 35 | * 获取post请求对象 36 | * 37 | * @param url 38 | * @return 39 | */ 40 | HttpPost getPost(String url); 41 | 42 | /** 43 | * 获取post请求对象 44 | * 45 | * @param url 46 | * @param params 47 | * @return 48 | */ 49 | HttpPost getPost(String url, JSONObject params); 50 | 51 | /** 52 | * 获取post请求对象 53 | * 54 | * @param url 55 | * @param params 56 | * @param file 57 | * @return 58 | */ 59 | HttpPost getPost(String url, JSONObject params, File file); 60 | 61 | /** 62 | * 获取响应 63 | * 64 | * @param request 65 | * @return 66 | */ 67 | JSONObject getResponse(HttpRequestBase request); 68 | 69 | /** 70 | * 获取响应 71 | * 72 | * @param url 73 | * @return 74 | */ 75 | JSONObject getGetResponse(String url); 76 | 77 | /** 78 | * 获取响应 79 | * 80 | * @param url 81 | * @param args 82 | * @return 83 | */ 84 | JSONObject getGetResponse(String url, JSONObject args); 85 | 86 | /** 87 | * 获取响应 88 | * 89 | * @param url 90 | * @return 91 | */ 92 | JSONObject getPostResponse(String url); 93 | 94 | /** 95 | * 获取响应 96 | * 97 | * @param url 98 | * @param params 99 | * @return 100 | */ 101 | JSONObject getPostResponse(String url, JSONObject params); 102 | 103 | /** 104 | * 获取响应 105 | * 106 | * @param url 107 | * @param params 108 | * @param file 109 | * @return 110 | */ 111 | JSONObject getPostResponse(String url, JSONObject params, File file); 112 | 113 | /** 114 | * 校验响应正确性 115 | *

116 | * 用于处理响应结果,一般校验json的必要层级和响应码 117 | *

118 | * 119 | * @param response 120 | * @return 121 | */ 122 | boolean isRight(JSONObject response); 123 | 124 | /** 125 | * 检查响应是否符合标准 126 | *

127 | * 会在FunLibrary类使用,如果没有ibase对象,会默认返回test_error_code 128 | * requestinfo主要用于校验该请求是否需要校验,黑名单有配置black_host提供 129 | *

130 | * 131 | * @param response 响应json 132 | * @param requestInfo 请求info 133 | * @return 134 | */ 135 | int checkCode(JSONObject response, RequestInfo requestInfo); 136 | 137 | /** 138 | * 登录 139 | */ 140 | void login(); 141 | 142 | /** 143 | * 设置header 144 | */ 145 | void setHeaders(HttpRequestBase request); 146 | 147 | /** 148 | * 处理响应结果 149 | * 150 | * @param response 151 | */ 152 | void handleResponseHeader(JSONObject response); 153 | 154 | /** 155 | * 获取公共的登录参数 156 | * 157 | * @return 158 | */ 159 | JSONObject getParams(); 160 | 161 | /** 162 | * 初始化对象,从json数据中,一般指cookie 163 | *

164 | * 主要用于new了新的对象之后,然后赋值的操作,场景是从另外一个服务的对象拷贝到现在的对象,区别于clone,因为可能还会涉及其他的验证,所以单独写出一个方法,极少用到 165 | *

166 | */ 167 | void init(JSONObject info); 168 | 169 | 170 | /** 171 | * 记录请求 172 | */ 173 | void recordRequest(HttpRequestBase base); 174 | 175 | /** 176 | * 获取请求,用于并发 177 | * 178 | * @return 179 | */ 180 | HttpRequestBase getRequest(); 181 | 182 | /** 183 | * 输出JSON格式的响应结果,用于统一屏蔽打印或者不打印响应内容 184 | * 185 | * @param response 186 | */ 187 | public void print(JSONObject response); 188 | 189 | 190 | /** 191 | * 打印所有的请求header,此处功能与print响应类似,需要用一个开关控制 192 | * 193 | * @param request 194 | */ 195 | public void printHeader(HttpRequestBase request); 196 | 197 | /** 198 | * 打印所有的响应header,此处功能与print响应类似,需要用一个开关控制 199 | * 200 | * @param response 201 | */ 202 | public void printHeader(CloseableHttpResponse response); 203 | 204 | } 205 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/db/mysql/TestConnectionManage.java: -------------------------------------------------------------------------------- 1 | package com.funtester.db.mysql; 2 | 3 | /** 4 | * 测试数据存储数据库连接管理类 5 | *

放弃使用该方式存储,换成springboot数据库服务

6 | */ 7 | @Deprecated 8 | public class TestConnectionManage extends SqlBase { 9 | 10 | // static Logger logger = LogManager.getLogger(TestConnectionManage.class); 11 | // 12 | // public static ExecuteThread executeThread1 = new ExecuteThread(true); 13 | // 14 | // 15 | // public static ExecuteThread executeThread2 = new ExecuteThread(false); 16 | // 17 | // 18 | // /** 19 | // * 记录query的最后调用时间时间 20 | // */ 21 | // private static int lastQuery; 22 | // 23 | // /** 24 | // * 记录update的最后调用时间 25 | // */ 26 | // private static int lastUpdate1; 27 | // 28 | // /** 29 | // * 记录update的最后调用时间 30 | // */ 31 | // private static int lastUpdate2; 32 | // 33 | // public static void start() { 34 | // getUpdateConnection1(); 35 | // getUpdateConnection2(); 36 | // executeThread1.start(); 37 | // executeThread2.start(); 38 | // } 39 | // 40 | // static void getQueryConnection() { 41 | // try { 42 | // if (getMark() - lastQuery > SqlConstant.MYSQL_RECONNECTION_GAP || MySqlTest.connection0 == null || MySqlTest.connection0.isClosed()) 43 | // MySqlTestInitQuery(); 44 | // } catch (SQLException e) { 45 | // logger.warn("数据库连接获取失败!", e); 46 | // } 47 | // } 48 | // 49 | // public static void getUpdateConnection1() { 50 | // try { 51 | // if (getMark() - lastUpdate1 > SqlConstant.MYSQL_RECONNECTION_GAP || MySqlTest.connection1 == null || MySqlTest.connection1.isClosed()) 52 | // MySqlTestInitUpdate(true); 53 | // } catch (SQLException e) { 54 | // logger.warn("数据库连接获取失败!", e); 55 | // } 56 | // } 57 | // 58 | // public static void getUpdateConnection2() { 59 | // try { 60 | // if (getMark() - lastUpdate2 > SqlConstant.MYSQL_RECONNECTION_GAP || MySqlTest.connection2 == null || MySqlTest.connection2.isClosed()) 61 | // MySqlTestInitUpdate(false); 62 | // } catch (SQLException e) { 63 | // logger.warn("数据库连接获取失败!", e); 64 | // } 65 | // } 66 | // 67 | // static void updateLastQuery() { 68 | // lastQuery = getMark(); 69 | // } 70 | // 71 | // static void updateLastUpdate1() { 72 | // lastUpdate1 = getMark(); 73 | // } 74 | // 75 | // static void updateLastUpdate2() { 76 | // lastUpdate2 = getMark(); 77 | // } 78 | // 79 | // /** 80 | // * 连接初始化,last自动赋值 81 | // */ 82 | // private static void MySqlTestInitQuery() { 83 | // updateLastQuery(); 84 | // MySqlTest.mySqlQueryOver(); 85 | // MySqlTest.connection0 = getConnection(SqlConstant.TEST_SQL_URL, SqlConstant.TEST_USER, SqlConstant.TEST_PASS_WORD); 86 | // MySqlTest.statement0 = getStatement(MySqlTest.connection0); 87 | // } 88 | // 89 | // 90 | // /** 91 | // * 连接初始化,last自动赋值 92 | // */ 93 | // private static void MySqlTestInitUpdate(boolean key) { 94 | // if (key) { 95 | // updateLastUpdate1(); 96 | // MySqlTest.connection1 = getConnection(SqlConstant.TEST_SQL_URL, SqlConstant.TEST_USER, SqlConstant.TEST_PASS_WORD); 97 | // MySqlTest.statement1 = getStatement(MySqlTest.connection1); 98 | // } else { 99 | // updateLastUpdate2(); 100 | // MySqlTest.connection2 = getConnection(SqlConstant.TEST_SQL_URL, SqlConstant.TEST_USER, SqlConstant.TEST_PASS_WORD); 101 | // MySqlTest.statement2 = getStatement(MySqlTest.connection2); 102 | // } 103 | // } 104 | // 105 | // 106 | // /** 107 | // * 结束所有sql任务线程 108 | // */ 109 | // public static void stopAllThread() { 110 | // ExecuteThread.threadKey = true; 111 | // } 112 | // 113 | //} 114 | // 115 | ///** 116 | // * 多线程类,用于消耗mysqltest里sqls中的数据库任务 117 | // */ 118 | //@Deprecated 119 | //class ExecuteThread extends Thread { 120 | // 121 | // /** 122 | // * 分配连接 123 | // */ 124 | // boolean key; 125 | // 126 | // /** 127 | // * 结束标志 128 | // */ 129 | // static boolean threadKey = false; 130 | // 131 | // ExecuteThread(boolean key) { 132 | // this.key = key; 133 | // } 134 | // 135 | // @Override 136 | // public void run() { 137 | // while (true) { 138 | // if (threadKey) break; 139 | // String sql = MySqlTest.getWork(); 140 | // if (sql == null) continue; 141 | // TestConnectionManage.logger.info("辅助线程执行SQL:{}", sql); 142 | // MySqlTest.executeUpdateSql(sql, key); 143 | // } 144 | // } 145 | 146 | } -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/config/PropertyUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.config 2 | 3 | import com.alibaba.fastjson.JSONObject 4 | import com.funtester.utils.RWUtil 5 | import com.funtester.frame.SourceCode 6 | import org.apache.logging.log4j.LogManager 7 | import org.apache.logging.log4j.Logger 8 | 9 | import java.util.stream.Stream 10 | 11 | /** 12 | * 读取配置工具 13 | */ 14 | class PropertyUtils extends SourceCode { 15 | 16 | private static Logger logger = LogManager.getLogger(PropertyUtils.class) 17 | 18 | /** 19 | * 获取指定.properties配置文件中所以的数据 20 | * @param propertyName 21 | * 调用方式: 22 | * 1.配置文件放在resource源包下,不用加后缀 23 | * PropertiesUtil.getAllMessage("message") 24 | * 2.放在包里面的 25 | * PropertiesUtil.getAllMessage("com.test.message") 26 | * @return 27 | */ 28 | static Property getProperties(String propertyName) { 29 | logger.debug("读取配置文件:{}", propertyName) 30 | try { 31 | new Property(ResourceBundle.getBundle(propertyName.trim())) 32 | } catch (MissingResourceException e) { 33 | getLocalProperties(WORK_SPACE + propertyName + ".properties") 34 | } 35 | } 36 | 37 | /** 38 | * 获取指定路径下的文件配置,过滤掉{@link com.funtester.config.Constant#FILTER} 39 | * @param filePath 40 | * @return 41 | */ 42 | static Property getLocalProperties(String filePath) { 43 | logger.debug("读取配置文件:{}", filePath) 44 | try { 45 | new Property(RWUtil.readTxtByJson(filePath, FILTER)) 46 | } catch (MissingResourceException e) { 47 | logger.warn("找不到配置文件", e) 48 | new Property() 49 | } 50 | } 51 | 52 | /** 53 | * 获取当前项目下的文件配置,过滤掉{@link com.funtester.config.Constant#FILTER} 54 | * @param propertyName 55 | * @return 56 | */ 57 | static Property getPropertiesByFile(String propertyName) { 58 | getLocalProperties(WORK_SPACE + propertyName) 59 | } 60 | 61 | /** 62 | * 配置项 63 | */ 64 | static class Property { 65 | 66 | Map properties = new HashMap<>() 67 | 68 | def Property(ResourceBundle resourceBundle) { 69 | def set = resourceBundle.keySet() 70 | for (def key in set) { 71 | properties.put key, resourceBundle.getString(key) 72 | } 73 | } 74 | 75 | def Property(JSONObject json) { 76 | properties.putAll(json) 77 | } 78 | 79 | /** 80 | * 获取string类型 81 | * @param name 82 | * @return 83 | */ 84 | String getProperty(String name) { 85 | PropertyUtils.logger.debug("获取配置项:{}", name) 86 | if (contain(name)) properties.get(name) 87 | } 88 | 89 | /** 90 | * 获取int值 91 | * @param name 92 | * @return 93 | */ 94 | int getPropertyInt(String name) { 95 | changeStringToInt(properties.get(name)) 96 | } 97 | /** 98 | * 获取long值 99 | * @param name 100 | * @return 101 | */ 102 | int getPropertyLong(String name) { 103 | Long.valueOf(properties.get(name)) 104 | } 105 | 106 | /** 107 | * 获取boolean值 108 | * @param name 109 | * @return 110 | */ 111 | boolean getPropertyBoolean(String name) { 112 | changeStringToBoolean(properties.get(name)) 113 | } 114 | 115 | /** 116 | * 获取数组 117 | * @param name 118 | * @return 119 | */ 120 | String[] getArrays(String name) { 121 | getProperty(name).split(COMMA) 122 | } 123 | 124 | /** 125 | * 获取数字类型的数组 126 | * @param name 127 | * @return 128 | */ 129 | Integer[] getIntArray(String name) { 130 | def split = getProperty(name).split(COMMA) 131 | Stream.of(split).map { x -> x as Integer }.toArray() 132 | } 133 | 134 | /** 135 | * 返回配置文件的配置项的大小 136 | * @return 137 | */ 138 | int size() { 139 | properties.size() 140 | } 141 | 142 | /** 143 | * 输出所以配置项 144 | * @return 145 | */ 146 | def printAll() { 147 | output properties 148 | } 149 | 150 | /** 151 | * 是否有配置项 152 | * @param key 153 | * @return 154 | */ 155 | boolean contain(def key) { 156 | boolean var = properties.containsKey key asBoolean() 157 | if (!var) PropertyUtils.logger.error("配置{}未发现!", key) 158 | var 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/config/HttpClientConstant.java: -------------------------------------------------------------------------------- 1 | package com.funtester.config; 2 | 3 | 4 | import org.apache.http.Header; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | import static com.funtester.config.Constant.DEFAULT_CHARSET; 11 | import static com.funtester.httpclient.FunLibrary.getHeader; 12 | 13 | /** 14 | * 15 | */ 16 | public class HttpClientConstant { 17 | 18 | static PropertyUtils.Property propertyUtils = PropertyUtils.getProperties("http"); 19 | 20 | static String getProperty(String name) { 21 | return propertyUtils.getProperty(name); 22 | } 23 | 24 | /** 25 | * 默认user_agent 26 | */ 27 | public static Header USER_AGENT = getHeader("User-Agent", getProperty("User-Agent")); 28 | 29 | /** 30 | * 从连接目标url最大超时 单位:毫秒 31 | */ 32 | public static int CONNECT_REQUEST_TIMEOUT = propertyUtils.getPropertyInt("TIMEOUT") * 1000; 33 | 34 | /** 35 | * 连接池中获取可用连接最大超时时间 单位:毫秒 36 | */ 37 | public static int CONNECT_TIMEOUT = CONNECT_REQUEST_TIMEOUT; 38 | 39 | /** 40 | * 等待响应(读数据)最大超时 单位:毫秒 41 | */ 42 | public static int SOCKET_TIMEOUT = CONNECT_REQUEST_TIMEOUT; 43 | 44 | /** 45 | * 记录 46 | */ 47 | public static int MAX_ACCEPT_TIME = propertyUtils.getPropertyInt("MAX_ACCEPT_TIME") * 1000; 48 | 49 | /** 50 | * 连接池最大连接数 51 | */ 52 | public static int MAX_TOTAL_CONNECTION = 5000; 53 | 54 | /** 55 | * 每个路由最大连接数 56 | */ 57 | public static int MAX_PER_ROUTE_CONNECTION = 2000; 58 | 59 | /** 60 | * 最大header数 61 | */ 62 | public static int MAX_HEADER_COUNT = 100; 63 | 64 | /** 65 | * 消息最大长度 66 | */ 67 | public static int MAX_LINE_LENGTH = 10000; 68 | 69 | /** 70 | * 设置的本机ip 71 | */ 72 | public static String IP = SysInit.getRandomIP(); 73 | 74 | /** 75 | * 连接header设置 76 | */ 77 | public static Header CONNECTION = getHeader("Connection", getProperty("Connection")); 78 | 79 | public static Header CLIENT_IP = getHeader("Client-Ip", IP); 80 | 81 | public static Header HTTP_X_FORWARDED_FOR = getHeader("HTTP_X_FORWARDED_FOR", IP); 82 | 83 | public static Header WL_Proxy_Client_IP = getHeader("WL-Proxy-Client-IP", IP); 84 | 85 | public static Header Proxy_Client_IP = getHeader("Proxy-Client-IP", IP); 86 | 87 | public static Header X_FORWARDED_FOR = getHeader("X-FORWARDED-FOR", IP); 88 | 89 | public static Header ContentType_JSON = getHeader("Content-Type", "application/json; charset=" + DEFAULT_CHARSET.toString()); 90 | 91 | public static Header ContentType_FORM = getHeader("Content-Type", "application/x-www-form-urlencoded; charset=" + DEFAULT_CHARSET.toString()); 92 | 93 | public static Header ContentType_TEXT = getHeader("Content-Type", "text/plain; charset=" + DEFAULT_CHARSET.toString()); 94 | 95 | public static Header X_Requested_KWith = getHeader("X-Requested-With", "XMLHttpRequest"); 96 | 97 | /** 98 | * 重试次数 99 | */ 100 | public static int TRY_TIMES = propertyUtils.getPropertyInt("TRY_TIMES"); 101 | 102 | /** 103 | * 关闭超时的链接 104 | */ 105 | public static int IDLE_TIMEOUT = 5; 106 | 107 | /** 108 | * 在设置请求contenttype参数,表示请求以io流发送数据 109 | */ 110 | public static String CONTENTTYPE_MULTIPART_FORM = "multipart/form-data"; 111 | 112 | /** 113 | * 在设置请求contenttype参数,表示请求以文本发送数据 114 | */ 115 | public static String CONTENTTYPE_TEXT = "text/plain"; 116 | 117 | /** 118 | * 请求头,cookie 119 | */ 120 | public static String COOKIE = "cookie"; 121 | 122 | /** 123 | * SSL版本 124 | */ 125 | public static String SSL_VERSION = getProperty("ssl_v"); 126 | 127 | /** 128 | * 域名黑名单 129 | */ 130 | public static List BLACK_HOSTS = new ArrayList<>(); 131 | 132 | /** 133 | * 通用循环间隔时间,单位s 134 | */ 135 | public static final int LOOP_INTERVAL = 5; 136 | 137 | /** 138 | * 线程池,线程最大空闲时间 139 | */ 140 | public static final int THREAD_ALIVE_TIME = 3; 141 | 142 | /** 143 | * 线程池核心线程数 144 | */ 145 | public static final int THREADPOOL_CORE = 20; 146 | 147 | /** 148 | * 线程池最大线程数 149 | */ 150 | public static final int THREADPOOL_MAX = 500; 151 | 152 | /** 153 | * 关闭线程池最大等待时间 154 | */ 155 | public static final int WAIT_TERMINATION_TIMEOUT = 10; 156 | 157 | /** 158 | * 添加黑名单 159 | * 160 | * @param host 161 | */ 162 | public static void addBlackHost(String host) { 163 | BLACK_HOSTS.add(host); 164 | } 165 | 166 | static { 167 | BLACK_HOSTS.addAll(Arrays.asList(getProperty("black_host").split(","))); 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/db/redis/RedisUtil.java: -------------------------------------------------------------------------------- 1 | package com.funtester.db.redis; 2 | 3 | public class RedisUtil extends RedisPool { 4 | 5 | // public static int getIndex() { 6 | // return index; 7 | // } 8 | // 9 | // public static void setIndex(int index) { 10 | // RedisUtil.index = index; 11 | // } 12 | // 13 | // /** 14 | // * redis数据库索引,默认0 15 | // */ 16 | // private static int index; 17 | // 18 | // private static Logger logger = LogManager.getLogger(RedisUtil.class); 19 | // 20 | // /** 21 | // * 获取jedis操作对象,回收资源方法close,3.0以后废弃了其他方法,默认连接第一个数据库 22 | // * 23 | // * @return 24 | // */ 25 | // public static Jedis getJedis() { 26 | // return RedisPool.getPool().getResource(); 27 | // } 28 | // 29 | // /** 30 | // * 获取某一个database的操作连接 31 | // * 32 | // * @param index 33 | // * @return 34 | // */ 35 | // public static Jedis getJedis(int index) { 36 | // Jedis jedis = getJedis(); 37 | // jedis.select(index); 38 | // return jedis; 39 | // } 40 | // 41 | // /** 42 | // * 设置key的有效期,单位是秒 43 | // * 44 | // * @param key 45 | // * @param exTime 46 | // * @return 47 | // */ 48 | // public static boolean expire(String key, int exTime) { 49 | // try (Jedis jedis = getJedis()) { 50 | // return jedis.expire(key, exTime) == 1; 51 | // } catch (Exception e) { 52 | // logger.error("expire key:{} error", key, e); 53 | // return false; 54 | // } 55 | // } 56 | // 57 | // /** 58 | // * 设置key-value,过期时间 59 | // * 60 | // * @param key 61 | // * @param value 62 | // * @param expiredTime 63 | // * @return 64 | // */ 65 | // public static String set(String key, String value, int expiredTime) { 66 | // try (Jedis jedis = getJedis()) { 67 | // return jedis.setex(key, expiredTime, value); 68 | // } catch (Exception e) { 69 | // logger.error("setex key:{} value:{} error", key, value, e); 70 | // return EMPTY; 71 | // } 72 | // } 73 | // 74 | // /** 75 | // * 设置redis内容 76 | // * 77 | // * @param key 78 | // * @param value 79 | // * @return 80 | // */ 81 | // public static String set(String key, String value) { 82 | // try (Jedis jedis = getJedis()) { 83 | // return jedis.set(key, value); 84 | // } catch (Exception e) { 85 | // logger.error("set key:{} value:{} error", key, value, e); 86 | // return EMPTY; 87 | // } 88 | // } 89 | // 90 | // /** 91 | // * 获取value 92 | // * 93 | // * @param key 94 | // * @return 95 | // */ 96 | // public static String get(String key) { 97 | // try (Jedis jedis = getJedis()) { 98 | // return jedis.get(key); 99 | // } catch (Exception e) { 100 | // logger.error("get key:{} error", key, e); 101 | // return EMPTY; 102 | // } 103 | // } 104 | // 105 | // /** 106 | // * 是否存在key 107 | // * 108 | // * @param key 109 | // * @return 110 | // */ 111 | // public static boolean exists(String key) { 112 | // try (Jedis jedis = getJedis()) { 113 | // return jedis.exists(key); 114 | // } catch (Exception e) { 115 | // logger.error("exists key:{} error", key, e); 116 | // return false; 117 | // } 118 | // } 119 | // 120 | // /** 121 | // * 删除key 122 | // * jedis返回值1表示成功,0表示失败,可能是不存在的key 123 | // * 124 | // * @param key 125 | // * @return 126 | // */ 127 | // public static boolean del(String key) { 128 | // try (Jedis jedis = getJedis()) { 129 | // return jedis.del(key) == 1; 130 | // } catch (Exception e) { 131 | // logger.error("del key:{} error", key, e); 132 | // return false; 133 | // } 134 | // } 135 | // 136 | // /** 137 | // * 获取key对应value的类型 138 | // * 139 | // * @param key 140 | // * @return 141 | // */ 142 | // public static String type(String key) { 143 | // try (Jedis jedis = getJedis()) { 144 | // return jedis.type(key); 145 | // } catch (Exception e) { 146 | // logger.error("type key:{} error", key, e); 147 | // return EMPTY; 148 | // } 149 | // } 150 | // 151 | // /** 152 | // * 获取符合条件的key集合 153 | // * 154 | // * @param pattern 155 | // * @return 156 | // */ 157 | // public static Set getKeys(String pattern) { 158 | // try (Jedis jedis = getJedis()) { 159 | // return jedis.keys(pattern); 160 | // } catch (Exception e) { 161 | // logger.error("type key:{} error", pattern, e); 162 | // return new HashSet(); 163 | // } 164 | // } 165 | // 166 | // /** 167 | // * 获取符合条件的key集合 168 | // * 169 | // * @param key 170 | // * @param content 171 | // * @return 172 | // */ 173 | // public static boolean append(String key, String content) { 174 | // try (Jedis jedis = getJedis()) { 175 | // return jedis.append(key, content) > 0; 176 | // } catch (Exception e) { 177 | // logger.error("append key:{} ,content:{},error", key, content, e); 178 | // return false; 179 | // } 180 | // } 181 | } 182 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/JsonUtil.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.utils 2 | /**下面是例子,官方文档地址:https://github.com/json-path/JsonPath/blob/master/README.md 3 | * $.store.book[*].author The authors of all books 4 | * $..author All authors 5 | * $.store.* All things, both books and bicycles 6 | * $.store..price The price of everything 7 | * $..book[2] The third book 8 | * $..book[-2] The second to last book 9 | * $..book[0,1] The first two books 10 | * $..book[:2] All books from index 0 (inclusive) until index 2 (exclusive) 11 | * $..book[1:2] All books from index 1 (inclusive) until index 2 (exclusive) 12 | * $..book[-2:] Last two books 13 | * $..book[2:] Book number two from tail 14 | * $..book[?(@.isbn)] All books with an ISBN number 15 | * $.store.book[?(@.price < 10)] All books in store cheaper than 10 16 | * $..book[?(@.price <= $['expensive'])] All books in store that are not "expensive" 17 | * $..book[?(@.author =~ /.*REES/i)] All books matching regex (ignore case) 18 | * $..* Give me every thing 19 | * $..book.length() The number of books 20 | * 21 | * 22 | * min() Provides the min value of an array of numbers Double 23 | * max() Provides the max value of an array of numbers Double 24 | * avg() Provides the average value of an array of numbers Double 25 | * stddev() Provides the standard deviation value of an array of numbers Double 26 | * length() Provides the length of an array Integer 27 | * sum() Provides the sum value of an array of numbers Double 28 | * min() 最小值 Double 29 | * max() 最大值 Double 30 | * avg() 平均值 Double 31 | * stddev() 标准差 Double 32 | * length() 数组长度 Integer 33 | * sum() 数组之和 Double 34 | * == left is equal to right (note that 1 is not equal to '1') 35 | * != left is not equal to right 36 | * < left is less than right 37 | * <= left is less or equal to right 38 | * > left is greater than right 39 | * >= left is greater than or equal to right 40 | * =~ left matches regular expression [?(@.name =~ /foo.*?/i)] 41 | * in left exists in right [?(@.size in ['S', 'M'])] 42 | * nin left does not exists in right 43 | * subsetof 子集 [?(@.sizes subsetof ['S', 'M', 'L'])] 44 | * anyof left has an intersection with right [?(@.sizes anyof ['M', 'L'])] 45 | * noneof left has no intersection with right [?(@.sizes noneof ['M', 'L'])] 46 | * size size of left (array or string) should match right 47 | * empty left (array or string) should be empty 48 | * 49 | * groovy需要 \$ Java不需要直接 $ 50 | * 51 | */ 52 | class JsonUtil { 53 | 54 | // private static Logger logger = LogManager.getLogger(JsonUtil.class) 55 | // 56 | // /** 57 | // * 用户构建对象,获取verify对象 58 | // */ 59 | // private JSONObject json 60 | // 61 | // private JsonUtil(JSONObject json) { 62 | // this.json = json 63 | // } 64 | // 65 | // static JsonUtil getInstance(JSONObject json) { 66 | // new JsonUtil(json) 67 | // } 68 | // 69 | // JsonVerify getVerify(String path) { 70 | // JsonVerify.getInstance(this.json, path) 71 | // } 72 | // 73 | // /** 74 | // * 获取string对象 75 | // * @param path 76 | // * @return 77 | // */ 78 | // String getString(String path) { 79 | // def object = get(path) 80 | // object == null ? Constant.EMPTY : object.toString() 81 | // } 82 | // 83 | // 84 | // /** 85 | // * 获取int类型 86 | // * @param path 87 | // * @return 88 | // */ 89 | // int getInt(String path) { 90 | // SourceCode.changeStringToInt(getString ( path)) 91 | // } 92 | // 93 | // /** 94 | // * 获取boolean类型 95 | // * @param path 96 | // * @return 97 | // */ 98 | // int getBoolean(String path) { 99 | // SourceCode.changeStringToBoolean(getString(path)) 100 | // } 101 | // 102 | // /** 103 | // * 获取long类型 104 | // * @param path 105 | // * @return 106 | // */ 107 | // int getLong(String path) { 108 | // SourceCode.changeStringToLong(getString(path)) 109 | // } 110 | // 111 | // /** 112 | // * 获取double类型 113 | // * @param path 114 | // * @return 115 | // */ 116 | // double getDouble(String path) { 117 | // SourceCode.changeStringToDouble(getString(path)) 118 | // } 119 | // 120 | // /** 121 | // * 获取list对象 122 | // * @param path 123 | // * @return 124 | // */ 125 | // List getList(String path) { 126 | // get(path) as List 127 | // } 128 | // 129 | // /** 130 | // * 获取匹配对象,类型传参 131 | // * 这里不加public IDE会报错 132 | // * @param path 133 | // * @param tClass 134 | // * @return 135 | // */ 136 | // public T getT(String path, Class tClass) { 137 | // try { 138 | // get(path) as T 139 | // } catch (ClassCastException e) { 140 | // logger.warn("类型转换失败!", e) 141 | // } 142 | // } 143 | // 144 | // /** 145 | // * 获取匹配对象,这里如果使用模糊路径匹配失败,返回"[]",如果绝对路径报错 146 | // * @param path 147 | // * @return 148 | // */ 149 | // def get(String path) { 150 | // logger.debug("匹配对象:{},表达式:{}", json.toString(), path) 151 | // if (json == null || json.isEmpty()) ParamException.fail("json为空或者null,参数错误!") 152 | // try { 153 | // JsonPath.read(this.json, path) 154 | // } catch (JsonPathException e) { 155 | // logger.warn("json: {} 解析失败,path: {}", json.toString(), path, e) 156 | // } 157 | // } 158 | // 159 | 160 | } 161 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/request/Swagger.java: -------------------------------------------------------------------------------- 1 | package com.funtester.utils.request; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.funtester.config.RequestType; 6 | import com.funtester.httpclient.FunLibrary; 7 | import com.funtester.utils.Regex; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | /** 14 | * swagger文档解析类 15 | */ 16 | public class Swagger extends FunLibrary { 17 | 18 | /** 19 | * 关键字,用于url前 20 | */ 21 | String key; 22 | 23 | /** 24 | * swagger文档地址 25 | */ 26 | String swaggerPath; 27 | 28 | /** 29 | * 构造方法中接口地址类别 30 | */ 31 | String name; 32 | 33 | /** 34 | * swagger地址所有类别 35 | */ 36 | List names = new ArrayList<>(); 37 | 38 | /** 39 | * 某类别所有接口地址 40 | */ 41 | List urls = new ArrayList<>(); 42 | 43 | /** 44 | * swagger文档转换成的json对象 45 | */ 46 | JSONObject swagger = new JSONObject(); 47 | 48 | /** 49 | * 所有接口地址的json对象 50 | */ 51 | JSONObject paths = new JSONObject(); 52 | 53 | /** 54 | * 对应构造方法中url的request对象 55 | */ 56 | Request request = new Request(); 57 | 58 | public Request getRequest() { 59 | return request; 60 | } 61 | 62 | public List getAllRequests() { 63 | return allRequests; 64 | } 65 | 66 | /** 67 | * 对应构造方法中name的所有request对象 68 | */ 69 | List allRequests = new ArrayList<>(); 70 | 71 | /** 72 | * 获取某一类的接口的request对象 73 | * 74 | * @param swaggerPath 75 | * @param name 76 | */ 77 | public Swagger(String swaggerPath, String name) { 78 | this.swaggerPath = swaggerPath; 79 | this.name = name; 80 | build(); 81 | } 82 | 83 | /** 84 | * 获取某一类的某一个接口的request对象 85 | * 86 | * @param swaggerPath 87 | * @param name 88 | * @param url 89 | */ 90 | public Swagger(String swaggerPath, String name, String url) { 91 | this.swaggerPath = swaggerPath; 92 | this.name = name; 93 | build(); 94 | request = getRequest(url); 95 | } 96 | 97 | 98 | public String getKey() { 99 | this.key = Regex.regexAll(this.swaggerPath, "/((?!/).)*/swagger.json").get(0); 100 | this.key = this.key.replace(OR, EMPTY).replace("swagger.json", EMPTY); 101 | if (this.key.contains(":")) this.key = EMPTY; 102 | return this.key; 103 | } 104 | 105 | /** 106 | * 获取name下所有接口的request对象 107 | */ 108 | private void getRequests() { 109 | this.urls.forEach(url -> { 110 | Request request = getRequest(url); 111 | if (request != null) allRequests.add(request); 112 | }); 113 | } 114 | 115 | /** 116 | * 初始化处理方法 117 | */ 118 | public void build() { 119 | swagger = getHttpResponse(getHttpGet(this.swaggerPath)); 120 | getKey(); 121 | getNames(); 122 | getPaths(); 123 | getUrls(); 124 | getRequests(); 125 | 126 | } 127 | 128 | /** 129 | * 获取某一个url地址的请求request对象 130 | * 131 | * @param url 接口地址 132 | * @return 133 | */ 134 | private Request getRequest(String url) { 135 | Request request = new Request(); 136 | request.setUrl((OR + key + url).replace("//", "/")); 137 | JSONObject json1 = paths.getJSONObject(url); 138 | JSONObject json2 = new JSONObject(); 139 | if (json1.containsKey("get")) { 140 | request.setType(RequestType.GET); 141 | json2 = json1.getJSONObject("get"); 142 | } else if (json1.containsKey("post")) { 143 | request.setType(RequestType.POST); 144 | json2 = json1.getJSONObject("post"); 145 | } 146 | String tags = json2.get("tags").toString(); 147 | if (!tags.contains(name)) return null; 148 | String apiName = json2.getString("operationId"); 149 | request.setApiName(apiName); 150 | String desc = json2.getString("summary"); 151 | request.setDesc(desc); 152 | JSONArray json3 = json2.getJSONArray("parameters"); 153 | JSONObject json5 = new JSONObject(); 154 | JSONObject json6 = new JSONObject(); 155 | json3.forEach(json -> {//获取参数,区分query和formdata 156 | JSONObject json4 = (JSONObject) json; 157 | String in = json4.getString("in"); 158 | if (in.equals("query")) { 159 | boolean required = json4.getBoolean("required"); 160 | if (required) { 161 | String format = json4.getString("type"); 162 | String name = json4.getString("name"); 163 | json5.put(name, format); 164 | } 165 | } else if (in.equals("formData")) { 166 | boolean required = json4.getBoolean("required"); 167 | if (required) { 168 | String format = json4.getString("type"); 169 | String name = json4.getString("name"); 170 | json6.put(name, format); 171 | } 172 | } 173 | }); 174 | request.setArgs(json5); 175 | request.setParams(json6); 176 | return request; 177 | } 178 | 179 | /** 180 | * 获取name下所有接口的地址 181 | */ 182 | private void getUrls() { 183 | Set keySet = paths.keySet(); 184 | keySet.forEach(key -> urls.add(key.toString())); 185 | } 186 | 187 | 188 | /** 189 | * 获取所有name 190 | */ 191 | private void getNames() { 192 | JSONArray tags = swagger.getJSONArray("tags"); 193 | tags.forEach(info -> { 194 | JSONObject name = (JSONObject) info; 195 | names.add(name.getString("name")); 196 | }); 197 | } 198 | 199 | /** 200 | * 获取所有的接口地址 201 | */ 202 | private void getPaths() { 203 | paths = swagger.getJSONObject("paths"); 204 | } 205 | 206 | 207 | } 208 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/execute/Progress.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.execute; 2 | 3 | import com.funtester.base.constaint.FixedQpsThread; 4 | import com.funtester.base.constaint.ThreadBase; 5 | import com.funtester.base.constaint.ThreadLimitTimeCount; 6 | import com.funtester.base.constaint.ThreadLimitTimesCount; 7 | import com.funtester.base.exception.ParamException; 8 | import com.funtester.config.HttpClientConstant; 9 | import com.funtester.frame.SourceCode; 10 | import com.funtester.utils.StringUtil; 11 | import com.funtester.utils.Time; 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.concurrent.atomic.AtomicInteger; 18 | import java.util.stream.Collectors; 19 | 20 | /** 21 | * 用于异步展示性能测试进度的多线程类 22 | * 23 | * @param 多线程任务{@link ThreadBase}对象的实现子类 24 | */ 25 | public class Progress extends SourceCode implements Runnable { 26 | 27 | private static Logger logger = LogManager.getLogger(Progress.class); 28 | 29 | /** 30 | * 会长 31 | */ 32 | private static final String SUFFIX = "QPS变化曲线"; 33 | 34 | /** 35 | * 记录每一次获取QPS的值,可能用于结果展示 36 | */ 37 | public List qs = new ArrayList<>(); 38 | 39 | /** 40 | * 多线程任务类对象 41 | */ 42 | private List threads; 43 | 44 | /** 45 | * 线程数,用于计算实时QPS 46 | */ 47 | private int threadNum; 48 | 49 | /** 50 | * 进度条的长度 51 | */ 52 | private static final int LENGTH = 67; 53 | 54 | /** 55 | * 标志符号 56 | */ 57 | private static final String ONE = getPart(3); 58 | 59 | /** 60 | * 总开关,是否运行,默认true 61 | */ 62 | private boolean st = true; 63 | 64 | /** 65 | * 是否次数模型 66 | */ 67 | private boolean isTimesMode; 68 | 69 | /** 70 | * 用于区分固定QPS请求模型,这里不计算固定QPS模型中的实时QPS 71 | */ 72 | private boolean canCount; 73 | 74 | /** 75 | * 多线程任务基类对象,本类中不处理,只用来获取值,若使用的话请调用clone()方法 76 | */ 77 | private F base; 78 | 79 | /** 80 | * 在固定QPS模式中使用 81 | */ 82 | private AtomicInteger excuteNum; 83 | 84 | /** 85 | * 限制条件 86 | */ 87 | private int limit; 88 | 89 | /** 90 | * 非精确时间,误差可以忽略 91 | */ 92 | private long startTime = Time.getTimeStamp(); 93 | 94 | /** 95 | * 描述 96 | */ 97 | private String taskDesc; 98 | 99 | /** 100 | * 固定线程模型 101 | * 102 | * @param threads 103 | * @param desc 104 | */ 105 | public Progress(final List threads, String desc) { 106 | this.threads = threads; 107 | this.threadNum = threads.size(); 108 | this.taskDesc = desc; 109 | this.base = threads.get(0); 110 | init(); 111 | } 112 | 113 | /** 114 | * 适配固定QPS模型 115 | * 116 | * @param threads 117 | * @param desc 118 | * @param excuteNum 119 | */ 120 | public Progress(final List threads, String desc, final AtomicInteger excuteNum) { 121 | this.threads = threads; 122 | this.threadNum = threads.size(); 123 | this.taskDesc = desc; 124 | this.base = threads.get(0); 125 | init(); 126 | } 127 | 128 | /** 129 | * 初始化对象,对istimesMode和limit赋值 130 | */ 131 | private void init() { 132 | if (base instanceof ThreadLimitTimeCount) { 133 | this.isTimesMode = false; 134 | this.canCount = true; 135 | this.limit = ((ThreadLimitTimeCount) base).time; 136 | } else if (base instanceof ThreadLimitTimesCount) { 137 | this.isTimesMode = true; 138 | this.canCount = true; 139 | this.limit = ((ThreadLimitTimesCount) base).times; 140 | } else if (base instanceof FixedQpsThread) { 141 | FixedQpsThread fix = (FixedQpsThread) base; 142 | this.canCount = false; 143 | this.isTimesMode = fix.isTimesMode; 144 | this.limit = fix.limit; 145 | } else { 146 | ParamException.fail("创建进度条对象失败!"); 147 | } 148 | } 149 | 150 | @Override 151 | public void run() { 152 | double pro = 0; 153 | while (st) { 154 | sleep(HttpClientConstant.LOOP_INTERVAL); 155 | pro = isTimesMode ? base.executeNum == 0 ? FixedQpsConcurrent.executeTimes.get() * 1.0 / limit : base.executeNum * 1.0 / limit : (Time.getTimeStamp() - startTime) * 1.0 / limit; 156 | if (pro > 0.95) break; 157 | if (st) 158 | logger.info("{}进度:{} {} ,当前QPS: {}", taskDesc, getManyString(ONE, (int) (pro * LENGTH)), getPercent(pro * 100), getQPS()); 159 | } 160 | } 161 | 162 | /** 163 | * 获取某一个时刻的QPS 164 | * 165 | * @return 166 | */ 167 | private int getQPS() { 168 | int qps = 0; 169 | if (canCount) { 170 | List times = new ArrayList<>(); 171 | for (int i = 0; i < threadNum; i++) { 172 | List costs = threads.get(i).costs; 173 | int size = costs.size(); 174 | if (size < 3) continue; 175 | times.add(costs.get(size - 1)); 176 | times.add(costs.get(size - 2)); 177 | } 178 | qps = times.isEmpty() ? 0 : (int) (1000 * threadNum / (times.stream().collect(Collectors.summarizingInt(x -> x)).getAverage())); 179 | } else { 180 | qps = excuteNum.get() / (int) (Time.getTimeStamp() - startTime); 181 | } 182 | qs.add(qps); 183 | return qps; 184 | } 185 | 186 | /** 187 | * 关闭线程,防止死循环 188 | */ 189 | public void stop() { 190 | st = false; 191 | logger.info("{}进度:{} {}", taskDesc, getManyString(ONE, LENGTH), "100%"); 192 | printQPS(); 193 | } 194 | 195 | /** 196 | * 打印QPS变化曲线 197 | */ 198 | private void printQPS() { 199 | int size = qs.size(); 200 | if (size < 5) return; 201 | if (size <= BUCKET_SIZE) { 202 | output(StatisticsUtil.draw(qs, StringUtil.center(taskDesc + SUFFIX, size * 3))); 203 | } else { 204 | double v = size * 1.0 / BUCKET_SIZE; 205 | List qpss = range(BUCKET_SIZE).mapToObj(x -> qs.get((int) (x * v))).collect(Collectors.toList()); 206 | output(StatisticsUtil.draw(qpss, StringUtil.center(taskDesc + SUFFIX, BUCKET_SIZE * 3))); 207 | } 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/socket/WebSocketFunClient.java: -------------------------------------------------------------------------------- 1 | package com.funtester.socket; 2 | 3 | /** 4 | * socket客户端代码,限于WebSocket协议的测试 5 | */ 6 | public class WebSocketFunClient { 7 | //public class WebSocketFunClient extends WebSocketClient { 8 | 9 | // private static Logger logger = LogManager.getLogger(WebSocketFunClient.class); 10 | // 11 | // public static Vector clients = new Vector<>(); 12 | // 13 | // /** 14 | // * 存储收到的消息 15 | // */ 16 | // public LinkedList msgs = new LinkedList<>(); 17 | // 18 | // /** 19 | // * 连接的url 20 | // */ 21 | // private String url; 22 | // 23 | // /** 24 | // * 客户端名称 25 | // */ 26 | // private String cname; 27 | // 28 | // private WebSocketFunClient(String url, String cname) throws URISyntaxException { 29 | // super(new URI(url)); 30 | // this.cname = cname; 31 | // this.url = url; 32 | // clients.add(this); 33 | // } 34 | // 35 | // /** 36 | // * 获取socketclient实例 37 | // * 38 | // * @param url 39 | // * @return 40 | // */ 41 | // public static WebSocketFunClient getInstance(String url) { 42 | // return getInstance(url, Constant.DEFAULT_STRING + StringUtil.getString(4)); 43 | // } 44 | // 45 | // /** 46 | // * 获取socketclient实例 47 | // * 48 | // * @param url 49 | // * @param cname 50 | // * @return 51 | // */ 52 | // public static WebSocketFunClient getInstance(String url, String cname) { 53 | // WebSocketFunClient client = null; 54 | // try { 55 | // client = new WebSocketFunClient(url, cname); 56 | // } catch (URISyntaxException e) { 57 | // ParamException.fail(cname + "创建socket client 失败! 原因:" + e.getMessage()); 58 | // } 59 | // return client; 60 | // } 61 | // 62 | // @Override 63 | // public void onOpen(ServerHandshake handshakedata) { 64 | // logger.info("{} 正在建立socket连接...", cname); 65 | // handshakedata.iterateHttpFields().forEachRemaining(x -> logger.info("握手信息key: {} ,value: {}", x, handshakedata.getFieldValue(x))); 66 | // } 67 | // 68 | // /** 69 | // * 收到消息时候调用的方法 70 | // * 71 | // * @param message 72 | // */ 73 | // @Override 74 | // public void onMessage(String message) { 75 | // saveMsg(message); 76 | // logger.info("{}收到: {}", cname, message); 77 | // } 78 | // 79 | // /** 80 | // * 关闭 81 | // * 82 | // * @param code 关闭code码,详情查看 {@link org.java_websocket.framing.CloseFrame} 83 | // * @param reason 关闭原因 84 | // * @param remote 85 | // */ 86 | // @Override 87 | // public void onClose(int code, String reason, boolean remote) { 88 | // logger.info("{} socket 连接关闭,URL: {} ,code码:{},原因:{},是否由远程服务关闭:{}", cname, url, code, reason, remote); 89 | // } 90 | // 91 | // /** 92 | // * 关闭socketclient 93 | // */ 94 | // @Override 95 | // public void close() { 96 | // logger.warn("{}:socket连接关闭!", cname); 97 | // super.close(); 98 | // } 99 | // 100 | // /** 101 | // * 出错时候调用 102 | // * 103 | // * @param e 104 | // */ 105 | // @Override 106 | // public void onError(Exception e) { 107 | // logger.error("{} socket异常,URL: {}", cname, url, e); 108 | // } 109 | // 110 | // /** 111 | // * 发送消息 112 | // * 113 | // * @param text 114 | // */ 115 | // @Override 116 | // public void send(String text) { 117 | // logger.debug("{} 发送:{}", cname, text); 118 | // super.send(text); 119 | // } 120 | // 121 | // /** 122 | // * 简历socket连接 123 | // */ 124 | // @Override 125 | // public void connect() { 126 | // logger.info("{} 开始连接...", cname); 127 | // super.connect(); 128 | // int a = 0; 129 | // while (true) { 130 | // if (this.getReadyState() == ReadyState.OPEN) break; 131 | // if ((a++ > SocketConstant.MAX_WATI_TIMES)) FailException.fail(cname + "连接重试失败!"); 132 | // SourceCode.sleep(SocketConstant.WAIT_INTERVAL); 133 | // } 134 | // logger.info("{} 连接成功!", cname); 135 | // } 136 | // 137 | // /** 138 | // * 发送非默认编码格式的文字 139 | // * 140 | // * @param text 141 | // * @param charset 142 | // */ 143 | // public void send(String text, Charset charset) { 144 | // send(new String(text.getBytes(), charset)); 145 | // } 146 | // 147 | // /** 148 | // * 发送json信息 149 | // * 150 | // * @param json 151 | // */ 152 | // public void send(JSONObject json) { 153 | // send(json.toJSONString()); 154 | // } 155 | // 156 | // /** 157 | // * 发送bean 158 | // * 159 | // * @param bean 160 | // */ 161 | // public void send(AbstractBean bean) { 162 | // send(bean.toString()); 163 | // } 164 | // 165 | // /** 166 | // * 重置连接 167 | // */ 168 | // @Override 169 | // public void reconnect() { 170 | // logger.info("{}重置连接并尝试重新连接!", cname); 171 | // super.reconnect(); 172 | // } 173 | // 174 | // /** 175 | // * 设置cname,多用于性能测试clone()之后 176 | // * 177 | // * @param cname 178 | // */ 179 | // public void setCname(String cname) { 180 | // this.cname = cname; 181 | // } 182 | // 183 | // public String getCname() { 184 | // return cname; 185 | // } 186 | // 187 | // public String getUrl() { 188 | // return url; 189 | // } 190 | // 191 | // public void setUrl(String url) { 192 | // this.url = url; 193 | // } 194 | // 195 | // /** 196 | // * 该方法用于性能测试中,clone多线程对象 197 | // * 198 | // * @return 199 | // */ 200 | // @Override 201 | // public WebSocketFunClient clone() { 202 | // return getInstance(this.url, this.cname + StringUtil.getString(4)); 203 | // } 204 | // 205 | // /** 206 | // * 保存收到的信息,只保留最近的{@link SocketConstant}条 207 | // * 208 | // * @param msg 209 | // */ 210 | // public void saveMsg(String msg) { 211 | // synchronized (msgs) { 212 | // if (msgs.size() > SocketConstant.MAX_MSG_SIZE) msgs.remove(); 213 | // msgs.add(msg); 214 | // } 215 | // } 216 | // 217 | // /** 218 | // * 关闭所有socketclient 219 | // */ 220 | // public static void closeAll() { 221 | // clients.forEach(x -> 222 | // { 223 | // if (x != null && !x.isClosed()) x.close(); 224 | // } 225 | // ); 226 | // clients.clear(); 227 | // logger.info("关闭所有Socket客户端!"); 228 | // } 229 | 230 | 231 | } 232 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/frame/execute/Concurrent.java: -------------------------------------------------------------------------------- 1 | package com.funtester.frame.execute; 2 | 3 | import com.funtester.base.bean.PerformanceResultBean; 4 | import com.funtester.base.constaint.ThreadBase; 5 | import com.funtester.config.Constant; 6 | import com.funtester.frame.Save; 7 | import com.funtester.frame.SourceCode; 8 | import com.funtester.utils.Time; 9 | import com.funtester.utils.RWUtil; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.apache.logging.log4j.LogManager; 12 | import org.apache.logging.log4j.Logger; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Vector; 17 | import java.util.concurrent.CountDownLatch; 18 | import java.util.concurrent.ExecutorService; 19 | 20 | import static java.util.stream.Collectors.toList; 21 | 22 | /** 23 | * 并发类,用于启动压力脚本 24 | */ 25 | public class Concurrent extends SourceCode { 26 | 27 | private static Logger logger = LogManager.getLogger(Concurrent.class); 28 | 29 | /** 30 | * 开始时间 31 | */ 32 | private long startTime; 33 | 34 | /** 35 | * 结束时间 36 | */ 37 | private long endTime; 38 | 39 | /** 40 | * 任务描述 41 | */ 42 | public String desc; 43 | 44 | /** 45 | * 任务集 46 | */ 47 | public List threads = new ArrayList<>(); 48 | 49 | /** 50 | * 线程数 51 | */ 52 | public int threadNum; 53 | 54 | /** 55 | * 执行失败总数 56 | */ 57 | private int errorTotal; 58 | 59 | /** 60 | * 任务执行失败总数 61 | */ 62 | private int failTotal; 63 | 64 | /** 65 | * 执行总数 66 | */ 67 | private int executeTotal; 68 | 69 | /** 70 | * 用于记录所有请求时间 71 | */ 72 | public static Vector allTimes = new Vector<>(); 73 | 74 | /** 75 | * 记录所有markrequest的信息 76 | */ 77 | public static Vector requestMark = new Vector<>(); 78 | 79 | /** 80 | * 线程池 81 | */ 82 | ExecutorService executorService; 83 | 84 | /** 85 | * 计数器 86 | */ 87 | CountDownLatch countDownLatch; 88 | 89 | /** 90 | * @param thread 线程任务 91 | * @param threadNum 线程数 92 | * @param desc 任务描述 93 | */ 94 | public Concurrent(ThreadBase thread, int threadNum, String desc) { 95 | this(threadNum, desc); 96 | range(threadNum).forEach(x -> threads.add(thread.clone())); 97 | } 98 | 99 | /** 100 | * @param threads 线程组 101 | * @param desc 任务描述 102 | */ 103 | public Concurrent(List threads, String desc) { 104 | this(threads.size(), desc); 105 | this.threads = threads; 106 | } 107 | 108 | private Concurrent(int threadNum, String desc) { 109 | this.threadNum = threadNum; 110 | this.desc = StatisticsUtil.getFileName(desc); 111 | executorService = ThreadPoolUtil.createFixedPool(threadNum); 112 | countDownLatch = new CountDownLatch(threadNum); 113 | } 114 | 115 | private Concurrent() { 116 | 117 | } 118 | 119 | /** 120 | * 执行多线程任务 121 | * 默认取list中thread对象,丢入线程池,完成多线程执行,如果没有threadname,name默认采用desc+线程数作为threadname,去除末尾的日期 122 | */ 123 | public PerformanceResultBean start() { 124 | Progress progress = new Progress(threads, StatisticsUtil.getTrueName(desc)); 125 | new Thread(progress).start(); 126 | startTime = Time.getTimeStamp(); 127 | for (int i = 0; i < threadNum; i++) { 128 | ThreadBase thread = threads.get(i); 129 | if (StringUtils.isBlank(thread.threadName)) thread.threadName = StatisticsUtil.getTrueName(desc) + i; 130 | thread.setCountDownLatch(countDownLatch); 131 | executorService.execute(thread); 132 | } 133 | shutdownService(executorService, countDownLatch); 134 | endTime = Time.getTimeStamp(); 135 | progress.stop(); 136 | threads.forEach(x -> { 137 | if (x.status()) failTotal++; 138 | errorTotal += x.errorNum; 139 | executeTotal += x.executeNum; 140 | }); 141 | logger.info("总计{}个线程,共用时:{} s,执行总数:{},错误数:{},失败数:{}", threadNum, Time.getTimeDiffer(startTime, endTime), executeTotal, errorTotal, failTotal); 142 | return over(); 143 | } 144 | 145 | /** 146 | * 关闭任务相关资源 147 | * 148 | * @param executorService 线程池 149 | * @param countDownLatch 计数器 150 | */ 151 | private static void shutdownService(ExecutorService executorService, CountDownLatch countDownLatch) { 152 | try { 153 | countDownLatch.await(); 154 | executorService.shutdown(); 155 | } catch (InterruptedException e) { 156 | logger.warn("线程池关闭失败!", e); 157 | } 158 | } 159 | 160 | private PerformanceResultBean over() { 161 | Save.saveIntegerList(allTimes, DATA_Path.replace(LONG_Path, EMPTY) + StatisticsUtil.getFileName(threadNum, desc)); 162 | Save.saveStringListSync(Concurrent.requestMark, MARK_Path.replace(LONG_Path, EMPTY) + desc); 163 | allTimes = new Vector<>(); 164 | requestMark = new Vector<>(); 165 | return countQPS(threadNum, desc, Time.getTimeByTimestamp(startTime), Time.getTimeByTimestamp(endTime)); 166 | } 167 | 168 | 169 | /** 170 | * 计算结果 171 | *

此结果仅供参考

172 | * 此处因为start和end的不准确问题,所以采用改计算方法,与fixQPS有区别 173 | * 174 | * @param name 线程数 175 | */ 176 | public PerformanceResultBean countQPS(int name, String desc, String start, String end) { 177 | List strings = RWUtil.readTxtFileByLine(Constant.DATA_Path + StatisticsUtil.getFileName(name, desc)); 178 | int size = strings.size(); 179 | List data = strings.stream().map(x -> changeStringToInt(x)).collect(toList()); 180 | int sum = data.stream().mapToInt(x -> x).sum(); 181 | String statistics = StatisticsUtil.statistics(data, desc, threadNum); 182 | int rt = sum / size; 183 | double qps = 1000.0 * name / rt; 184 | double qps2 = (executeTotal + errorTotal) * 1000.0 / (endTime - startTime); 185 | return new PerformanceResultBean(desc, start, end, name, size, rt, qps, qps2, getPercent(executeTotal, errorTotal), getPercent(threadNum, failTotal), executeTotal, statistics); 186 | } 187 | 188 | 189 | /** 190 | * 用于做后期的计算 191 | * 192 | * @param name 193 | * @param desc 194 | * @return 195 | */ 196 | public PerformanceResultBean countQPS(int name, String desc) { 197 | return countQPS(name, desc, Time.getDate(), Time.getDate()); 198 | } 199 | 200 | 201 | } -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/utils/Time.java: -------------------------------------------------------------------------------- 1 | package com.funtester.utils; 2 | 3 | import com.funtester.config.Constant; 4 | import com.funtester.frame.SourceCode; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | 8 | import java.text.ParseException; 9 | import java.text.SimpleDateFormat; 10 | import java.util.Calendar; 11 | import java.util.Date; 12 | 13 | /** 14 | * 时间相关功能工具类 15 | */ 16 | public class Time extends SourceCode { 17 | 18 | private static Logger logger = LogManager.getLogger(Time.class); 19 | 20 | /** 21 | * 默认的日志显示格式 22 | */ 23 | private static ThreadLocal DEFAULT_FORMAT = new ThreadLocal() { 24 | @Override 25 | protected SimpleDateFormat initialValue() { 26 | return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 27 | } 28 | }; 29 | 30 | /** 31 | * 纯数字的日期格式 32 | */ 33 | private static ThreadLocal NUM_FORMAT = new ThreadLocal() { 34 | @Override 35 | protected SimpleDateFormat initialValue() { 36 | return new SimpleDateFormat("yyyyMMddHHmmss"); 37 | } 38 | }; 39 | 40 | /** 41 | * 标记日期格式,选用ddHHmm 42 | */ 43 | private static ThreadLocal MARK_FORMAT = new ThreadLocal() { 44 | @Override 45 | protected SimpleDateFormat initialValue() { 46 | return new SimpleDateFormat("ddHHmm"); 47 | } 48 | }; 49 | 50 | /** 51 | * 获取calendar类对象,默认UTC时间 52 | * 53 | * @return 54 | */ 55 | private static ThreadLocal calendar = new ThreadLocal() { 56 | @Override 57 | protected Calendar initialValue() { 58 | Calendar calendar = Calendar.getInstance(); 59 | calendar.setTime(new Date()); 60 | return calendar; 61 | } 62 | }; 63 | 64 | /** 65 | * 获取时间戳,13位long类型 66 | * 67 | * @return 68 | */ 69 | public static long getTimeStamp() { 70 | return System.currentTimeMillis(); 71 | } 72 | 73 | /** 74 | * 获取一天开始,utc 75 | * 76 | * @return 77 | */ 78 | public static String getStartOfDay() { 79 | return getUtcDate() + " 00:00:00"; 80 | } 81 | 82 | /** 83 | * 获取一天结束,utc 84 | * 85 | * @return 86 | */ 87 | public static String getEndOfDay() { 88 | return getUtcDate() + " 23:55:55"; 89 | } 90 | 91 | /** 92 | * 获取当天日期,utc 93 | * 94 | * @return 95 | */ 96 | public static String getUtcDate() { 97 | int month = getMonth(); 98 | int day = getDay(); 99 | return getYear() + "-" + (month < 10 ? "0" + month : month) + "-" + (day < 10 ? "0" + day : day); 100 | } 101 | 102 | /** 103 | * 获取时间戳 104 | * 105 | * @param time 传入时间,纯数字 106 | * @return 返回时间戳,毫秒 107 | */ 108 | public static long getUtcTimestamp(String time) { 109 | long timestamp = getTimeStamp(time); 110 | long utc = timestamp - Calendar.getInstance().getTimeZone().getRawOffset(); 111 | return utc; 112 | } 113 | 114 | /** 115 | * 获取UTC时间戳 116 | * 117 | * @param time 纯数字日期 118 | * @return 119 | */ 120 | public static long getUtcTimestamp(long time) { 121 | return getUtcTimestamp(time + EMPTY); 122 | } 123 | 124 | /** 125 | * 获取当前星期数(按年) 126 | * 127 | * @return 128 | */ 129 | public static int getWeeksNum() { 130 | return calendar.get().get(Calendar.WEEK_OF_YEAR); 131 | } 132 | 133 | /** 134 | * 获取月份,获取值+1,索引从0开始的 135 | * 136 | * @return 137 | */ 138 | public static int getMonth() { 139 | return calendar.get().get(Calendar.MONTH) + 1; 140 | } 141 | 142 | /** 143 | * 获取当前是当月的第几天 144 | * 145 | * @return 146 | */ 147 | public static int getDay() { 148 | return calendar.get().get(Calendar.DAY_OF_MONTH); 149 | } 150 | 151 | /** 152 | * 获取年份 153 | * 154 | * @return 155 | */ 156 | public static int getYear() { 157 | return calendar.get().get(Calendar.YEAR); 158 | } 159 | 160 | public static int getHour() { 161 | return calendar.get().get(Calendar.HOUR_OF_DAY); 162 | } 163 | 164 | public static int getMinute() { 165 | return calendar.get().get(Calendar.MINUTE); 166 | } 167 | 168 | public static int getSecond() { 169 | return calendar.get().get(Calendar.SECOND); 170 | } 171 | 172 | /** 173 | * 获取当前时间 174 | * 175 | * @return 返回当前时间 176 | */ 177 | public static String getNow() { 178 | return getNow(NUM_FORMAT.get()); 179 | } 180 | 181 | public static String markDate() { 182 | return getNow(MARK_FORMAT.get()); 183 | } 184 | 185 | public static String getNow(String format) { 186 | return getNow(new SimpleDateFormat(format)); 187 | } 188 | 189 | public static String getNow(SimpleDateFormat now) { 190 | return now.format(new Date()); 191 | } 192 | 193 | /** 194 | * 获取时间戳,会替换掉所有非数字的字符 195 | * 默认返回{@link Constant#DEFAULT_LONG} 196 | * 197 | * @param time 传入时间,纯数字组成的时间 198 | * @return 返回时间戳,毫秒 199 | */ 200 | public static long getTimeStamp(String time) { 201 | time = time.replaceAll("\\D*", EMPTY); 202 | try { 203 | return NUM_FORMAT.get().parse(time).getTime(); 204 | } catch (ParseException e) { 205 | logger.warn("时间格式错误!", e); 206 | } 207 | return DEFAULT_LONG; 208 | } 209 | 210 | /** 211 | * 根据时间戳返回对应的时间,并且输出 212 | * 213 | * @param time long 时间戳 214 | * @return 返回时间 215 | */ 216 | public static String getTimeByTimestamp(long time) { 217 | Date now = new Date(time); 218 | String nowTime = DEFAULT_FORMAT.get().format(now); 219 | return nowTime; 220 | } 221 | 222 | /** 223 | * 获取时间差,以秒为单位 224 | * 225 | * @param start 开始时间 226 | * @param end 结束时间 227 | * @return 228 | */ 229 | @Deprecated 230 | public static double getTimeDiffer(Date start, Date end) { 231 | return getTimeDiffer(start.getTime(), end.getTime()); 232 | } 233 | 234 | /** 235 | * 重载,用long类型取代date 236 | * 237 | * @param start 238 | * @param end 239 | * @return 240 | */ 241 | public static double getTimeDiffer(long start, long end) { 242 | return (end - start) / 1000.0; 243 | } 244 | 245 | /** 246 | * 获取当前时间,返回date类型 247 | * 248 | * @return 249 | */ 250 | public static String getDate() { 251 | return getNow(DEFAULT_FORMAT.get()); 252 | } 253 | 254 | 255 | } 256 | -------------------------------------------------------------------------------- /src/main/groovy/com/funtester/config/Constant.java: -------------------------------------------------------------------------------- 1 | package com.funtester.config; 2 | 3 | import com.funtester.utils.FileUtil; 4 | import com.funtester.utils.Time; 5 | import org.apache.http.Consts; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | import java.io.File; 10 | import java.net.InetAddress; 11 | import java.net.UnknownHostException; 12 | import java.nio.charset.Charset; 13 | import java.util.List; 14 | import java.util.Properties; 15 | 16 | /** 17 | * 常量类 18 | */ 19 | public class Constant { 20 | 21 | private static Logger logger = LogManager.getLogger(Constant.class); 22 | 23 | /*常用的常量*/ 24 | public static final String LINE = "\r\n"; 25 | 26 | public static final String TAB = "\t"; 27 | 28 | public static final String EMPTY = ""; 29 | 30 | public static final String COMMA = ","; 31 | 32 | public static final String UNKNOW = "?"; 33 | 34 | public static final String OR = "/"; 35 | 36 | public static final String PART = "|"; 37 | 38 | /** 39 | * 正则表达式中用到的{@link Constant#PART} 40 | */ 41 | public static final String REG_PART = "\\|"; 42 | 43 | public static final String SPACE_1 = " "; 44 | 45 | public static final String CONNECTOR = "_"; 46 | 47 | public static final String QUOTE_DOUBLE = "\""; 48 | 49 | public static final String QUOTE_SINGLE = "\'"; 50 | 51 | private static final String[] PERCENT = {SPACE_1, "▁", "▂", "▃", "▄", "▅", "▅", "▇", "█"}; 52 | 53 | /** 54 | * 此处前七处等高,第八个元素不等高,不能正常使用 55 | */ 56 | private static final String[] PARTS = {SPACE_1, "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"}; 57 | 58 | /** 59 | * 统计性能数据的分桶数 60 | */ 61 | public static final int BUCKET_SIZE = 32; 62 | 63 | /** 64 | * 读写配置文件过滤的文本 65 | */ 66 | public static final String FILTER = "##"; 67 | 68 | public static final String DEFAULT_STRING = "FunTester"; 69 | 70 | public static final String RESPONSE_CODE = "code"; 71 | 72 | public static final String RESPONSE_CONTENT = "content"; 73 | 74 | public static final int TEST_ERROR_CODE = -2; 75 | 76 | public static final long DEFAULT_LONG = 0L; 77 | 78 | public static final Properties SYSTEM_INFO = getSysInfo(); 79 | 80 | private static Properties getSysInfo() { 81 | return System.getProperties(); 82 | } 83 | 84 | /** 85 | * 校验IP+port的正确性 86 | */ 87 | public static final String HOST_REGEX = "((25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))\\.){3}(25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d))):([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-4]\\d{4}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])"; 88 | 89 | /** 90 | * UTF-8字符编码格式 91 | */ 92 | public static final Charset UTF_8 = Consts.UTF_8; 93 | 94 | /** 95 | * gb2312编码格式 96 | */ 97 | public static final Charset GB2312 = Charset.forName("gb2312"); 98 | 99 | /** 100 | * Unicode编码格式 101 | */ 102 | public static final Charset UNICODE = Charset.forName("Unicode"); 103 | 104 | /** 105 | * utf-16字符集 106 | */ 107 | public static final Charset UTF_16 = Charset.forName("UTF-16"); 108 | 109 | /** 110 | * ISO-8859-1编码格式 111 | */ 112 | public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); 113 | 114 | /** 115 | * GBK编码格式 116 | */ 117 | public static final Charset GBK = Charset.forName("GBK"); 118 | 119 | /** 120 | * 默认字符集 121 | */ 122 | public static Charset DEFAULT_CHARSET = UTF_8; 123 | 124 | /** 125 | * 当前工作目录 126 | */ 127 | public static final String WORK_SPACE = new File(EMPTY).getAbsolutePath() + "/"; 128 | 129 | /** 130 | * 测试数据存储目录 131 | */ 132 | public static final String LONG_Path = WORK_SPACE + "long/"; 133 | 134 | /** 135 | * 日志存存储目录 136 | */ 137 | public static final String LOG_Path = WORK_SPACE + "log/"; 138 | 139 | /** 140 | * request日志记录目录 141 | */ 142 | public static final String REQUEST_Path = LONG_Path + "request/"; 143 | 144 | /** 145 | * 标记请求地址 146 | */ 147 | public static final String MARK_Path = LONG_Path + "mark/"; 148 | 149 | /** 150 | * 压测数据存放地址 151 | */ 152 | public static final String DATA_Path = LONG_Path + "data/"; 153 | 154 | /** 155 | * 毫秒数 156 | */ 157 | public static final long DAY = 86400000; 158 | 159 | /** 160 | * 反射方法执行用例时间间隔 161 | */ 162 | public static final int EXECUTE_GAP_TIME = 10; 163 | 164 | /** 165 | * 本机ip,程序初始化会赋值 166 | */ 167 | public static final String LOCAL_IP = getLocalIp(); 168 | 169 | /** 170 | * 本机用户名,程序初始化会赋值 171 | */ 172 | public static final String COMPUTER_USER_NAME = SYSTEM_INFO.getOrDefault("user.name", DEFAULT_STRING).toString(); 173 | 174 | public static final String JAVA_VERSION = SYSTEM_INFO.get("java.version").toString(); 175 | 176 | public static final String SYS_ENCODING = SYSTEM_INFO.get("file.encoding").toString(); 177 | 178 | public static final String SYS_VERSION = SYSTEM_INFO.get("os.version").toString(); 179 | 180 | public static final String SYS_NAME = SYSTEM_INFO.get("os.name").toString(); 181 | 182 | 183 | /** 184 | * 获取本机IP 185 | * 186 | * @return 187 | */ 188 | public static String getLocalIp() { 189 | try { 190 | return InetAddress.getLocalHost().getHostAddress(); 191 | } catch (UnknownHostException e) { 192 | logger.warn("获取本机IP失败!", e); 193 | return EMPTY; 194 | } 195 | } 196 | 197 | /** 198 | * 直接获取long目录下的文件 199 | * 200 | * @param fileName 201 | * @return 202 | */ 203 | public static String getLongFile(String fileName) { 204 | return LONG_Path + fileName; 205 | } 206 | 207 | public static String getPercent(int i) { 208 | return PERCENT[i % 9]; 209 | } 210 | 211 | public static String getPart(int i) { 212 | return PARTS[i % 9]; 213 | } 214 | 215 | /** 216 | * 创建日志文件夹和数据存储文件夹 217 | */ 218 | static { 219 | new File(LOG_Path).mkdir(); 220 | new File(LONG_Path).mkdir(); 221 | File file = new File(REQUEST_Path); 222 | File mark = new File(MARK_Path); 223 | File data = new File(DATA_Path); 224 | file.mkdir(); 225 | mark.mkdir(); 226 | data.mkdir(); 227 | List allFile = FileUtil.getAllFile(DATA_Path); 228 | allFile.addAll(FileUtil.getAllFile(MARK_Path)); 229 | allFile.addAll(FileUtil.getAllFile(REQUEST_Path)); 230 | allFile.stream().map(y -> new File(y)).forEach(x -> { 231 | if (Time.getTimeStamp() - x.lastModified() > 3 * DAY) x.delete(); 232 | }); 233 | logger.info("当前用户:{},IP:{},工作目录:{},系统编码格式:{},系统{}版本:{}", COMPUTER_USER_NAME, LOCAL_IP, WORK_SPACE, SYS_ENCODING, SYS_NAME, SYS_VERSION); 234 | } 235 | 236 | } 237 | --------------------------------------------------------------------------------