├── slave ├── settings.gradle ├── src │ └── main │ │ ├── groovy │ │ └── com │ │ │ └── funtester │ │ │ └── slave │ │ │ ├── common │ │ │ ├── FunData.java │ │ │ ├── config │ │ │ │ ├── MasterApi.java │ │ │ │ ├── WebSocketConfig.java │ │ │ │ ├── ServerConfig.java │ │ │ │ └── SwaggerConfig.groovy │ │ │ ├── bean │ │ │ │ └── run │ │ │ │ │ ├── GroovyScript.groovy │ │ │ │ │ ├── LocalMethod.groovy │ │ │ │ │ ├── ManyRequest.groovy │ │ │ │ │ ├── HttpRequests.groovy │ │ │ │ │ ├── BaseRequest.groovy │ │ │ │ │ └── HttpRequest.groovy │ │ │ ├── StartRun.java │ │ │ ├── ScheduledConfig.java │ │ │ ├── Task.groovy │ │ │ ├── basedata │ │ │ │ └── DcsConstant.java │ │ │ └── wapper │ │ │ │ ├── CommonExceptionHandler.java │ │ │ │ ├── CommonInterceptor.java │ │ │ │ ├── ResponseWrapper.java │ │ │ │ ├── RequestWrapper.java │ │ │ │ └── WrappingFilter.java │ │ │ ├── service │ │ │ ├── impl │ │ │ │ └── IRunService.java │ │ │ └── RunServiceImpl.java │ │ │ ├── template │ │ │ └── ListRequestMode.groovy │ │ │ ├── SlaveApplication.java │ │ │ ├── manager │ │ │ ├── SlaveHttp.groovy │ │ │ └── SlaveManager.groovy │ │ │ └── controller │ │ │ ├── ConfigController.java │ │ │ └── RunController.java │ │ └── resources │ │ ├── application.properties │ │ └── logback.xml └── build.gradle ├── master ├── settings.gradle ├── src │ └── main │ │ ├── groovy │ │ └── com │ │ │ └── funtester │ │ │ └── master │ │ │ ├── common │ │ │ ├── StartRun.java │ │ │ ├── config │ │ │ │ ├── WebSocketConfig.java │ │ │ │ ├── ServerConfig.java │ │ │ │ ├── SlaveApi.java │ │ │ │ └── SwaggerConfig.groovy │ │ │ ├── bean │ │ │ │ └── manager │ │ │ │ │ ├── RunInfoBean.groovy │ │ │ │ │ └── RegisterBean.groovy │ │ │ ├── ScheduledConfig.java │ │ │ ├── Task.groovy │ │ │ ├── wapper │ │ │ │ ├── CommonExceptionHandler.java │ │ │ │ ├── CommonInterceptor.java │ │ │ │ ├── ResponseWrapper.java │ │ │ │ ├── RequestWrapper.java │ │ │ │ └── WrappingFilter.java │ │ │ └── basedata │ │ │ │ └── NodeData.java │ │ │ ├── service │ │ │ ├── IRunService.groovy │ │ │ └── impl │ │ │ │ └── RunService.groovy │ │ │ ├── MasterApplication.java │ │ │ ├── manaer │ │ │ ├── MasterHttp.groovy │ │ │ └── MasterManager.groovy │ │ │ └── controller │ │ │ ├── RunController.groovy │ │ │ └── Manager.groovy │ │ └── resources │ │ ├── application.properties │ │ └── logback.xml └── build.gradle ├── settings.gradle └── readme.markdown /slave/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'slave' -------------------------------------------------------------------------------- /master/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'master' 2 | includeFlat 'slave' -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'dcs_funtester' 2 | include 'slave' 3 | include 'master' -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/FunData.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common; 2 | 3 | import com.funtester.base.bean.PerformanceResultBean; 4 | 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | public class FunData { 8 | 9 | public static ConcurrentHashMap results = new ConcurrentHashMap(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/config/MasterApi.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.config; 2 | 3 | public class MasterApi { 4 | 5 | public static String GET_IP = "/test/ip"; 6 | 7 | public static String REGISTER = "/m/register"; 8 | 9 | public static String UPDATE = "/m/update"; 10 | 11 | public static String UPDATE_INFO = "/m/upinfo"; 12 | 13 | public static String UPDATE_RESULT = "/m/upresult/"; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/service/impl/IRunService.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.service.impl; 2 | 3 | import com.funtester.slave.common.bean.run.*; 4 | 5 | public interface IRunService { 6 | 7 | public void runRequest(HttpRequest request); 8 | 9 | public void runMany(ManyRequest request); 10 | 11 | public void runRequests(HttpRequests request); 12 | 13 | public void runMethod(LocalMethod method); 14 | 15 | public void runScript(GroovyScript script); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/StartRun.java: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class StartRun implements CommandLineRunner { 10 | 11 | private static Logger logger = LoggerFactory.getLogger(StartRun.class); 12 | 13 | @Override 14 | public void run(String... args) { 15 | logger.info("程序初始化运行方法执行完毕……"); 16 | } 17 | 18 | 19 | } -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/config/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.socket.config.annotation.EnableWebSocket; 6 | import org.springframework.web.socket.server.standard.ServerEndpointExporter; 7 | 8 | @Configuration 9 | @EnableWebSocket 10 | public class WebSocketConfig { 11 | @Bean 12 | public ServerEndpointExporter serverEndpoint() { 13 | return new ServerEndpointExporter(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/config/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.socket.config.annotation.EnableWebSocket; 6 | import org.springframework.web.socket.server.standard.ServerEndpointExporter; 7 | 8 | @Configuration 9 | @EnableWebSocket 10 | public class WebSocketConfig { 11 | @Bean 12 | public ServerEndpointExporter serverEndpoint() { 13 | return new ServerEndpointExporter(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/bean/manager/RunInfoBean.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common.bean.manager 2 | 3 | import com.funtester.base.bean.AbstractBean 4 | import io.swagger.annotations.ApiModel 5 | 6 | import javax.validation.constraints.NotBlank 7 | import javax.validation.constraints.NotEmpty 8 | 9 | @ApiModel(value = "更新运行进度bean") 10 | class RunInfoBean extends AbstractBean { 11 | 12 | private static final long serialVersionUID = 324085090345038940L; 13 | 14 | @NotBlank 15 | String host 16 | 17 | @NotEmpty 18 | String runinfo 19 | 20 | @NotEmpty 21 | String desc 22 | 23 | } 24 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/config/ServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common.config; 2 | 3 | import org.springframework.boot.web.context.WebServerInitializedEvent; 4 | import org.springframework.context.ApplicationListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class ServerConfig implements ApplicationListener { 9 | 10 | private int serverPort; 11 | 12 | @Override 13 | public void onApplicationEvent(WebServerInitializedEvent event) { 14 | this.serverPort = event.getWebServer().getPort(); 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/bean/run/GroovyScript.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.bean.run 2 | 3 | import com.funtester.base.bean.AbstractBean 4 | import io.swagger.annotations.ApiModel 5 | 6 | import javax.validation.constraints.NotEmpty 7 | import javax.validation.constraints.NotNull 8 | 9 | @ApiModel(value = "Groovy脚本性能测试参数") 10 | class GroovyScript extends AbstractBean implements Serializable{ 11 | 12 | private static final long serialVersionUID = 38033335615941L; 13 | 14 | @NotNull 15 | Integer mark 16 | 17 | @NotEmpty 18 | String script 19 | 20 | List params 21 | 22 | } 23 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/config/ServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.config; 2 | 3 | import org.springframework.boot.web.context.WebServerInitializedEvent; 4 | import org.springframework.context.ApplicationListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class ServerConfig implements ApplicationListener { 9 | 10 | public static int serverPort; 11 | 12 | @Override 13 | public void onApplicationEvent(WebServerInitializedEvent event) { 14 | serverPort = event.getWebServer().getPort(); 15 | 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/StartRun.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common; 2 | 3 | import com.funtester.slave.manager.SlaveManager; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.CommandLineRunner; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class StartRun implements CommandLineRunner { 11 | 12 | private static Logger logger = LoggerFactory.getLogger(StartRun.class); 13 | 14 | @Override 15 | public void run(String... args) { 16 | SlaveManager.getIP(); 17 | SlaveManager.register(); 18 | logger.info("程序初始化运行方法执行完毕……"); 19 | } 20 | 21 | 22 | } -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/ScheduledConfig.java: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.scheduling.TaskScheduler; 6 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 7 | 8 | @Configuration 9 | public class ScheduledConfig { 10 | 11 | @Bean 12 | public TaskScheduler taskScheduler() { 13 | ThreadPoolTaskScheduler scheduling = new ThreadPoolTaskScheduler(); 14 | scheduling.setPoolSize(10); 15 | scheduling.initialize(); 16 | return scheduling; 17 | } 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/config/SlaveApi.java: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common.config; 2 | 3 | public class SlaveApi { 4 | 5 | public static String RUN_REQUEST = "/run/request"; 6 | 7 | public static String RUN_REQUESTS = "/run/requests"; 8 | 9 | public static String RUN_METHOD = "/run/method"; 10 | 11 | public static String RUN_SCRIPT = "/run/script"; 12 | 13 | public static String RUN_MANY = "/run/many"; 14 | 15 | public static String PROGRESS = "/run/progress"; 16 | 17 | public static String STOP = "/test/stop"; 18 | 19 | public static String STATUS = "/test/s"; 20 | 21 | public static String ALIVE = "/test/alive"; 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/ScheduledConfig.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.scheduling.TaskScheduler; 6 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 7 | 8 | @Configuration 9 | public class ScheduledConfig { 10 | 11 | @Bean 12 | public TaskScheduler taskScheduler() { 13 | ThreadPoolTaskScheduler scheduling = new ThreadPoolTaskScheduler(); 14 | scheduling.setPoolSize(10); 15 | scheduling.initialize(); 16 | return scheduling; 17 | } 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/service/IRunService.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.master.service 2 | 3 | import com.funtester.slave.common.bean.run.GroovyScript 4 | import com.funtester.slave.common.bean.run.HttpRequest 5 | import com.funtester.slave.common.bean.run.HttpRequests 6 | import com.funtester.slave.common.bean.run.LocalMethod 7 | import com.funtester.slave.common.bean.run.ManyRequest 8 | 9 | interface IRunService { 10 | 11 | public void runRequest(ManyRequest request) 12 | 13 | public int runRequest(HttpRequest request) 14 | 15 | public int runRequests(HttpRequests request) 16 | 17 | public int runMethod(LocalMethod method) 18 | 19 | public int runScript(GroovyScript script) 20 | 21 | } -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/bean/manager/RegisterBean.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common.bean.manager 2 | 3 | import com.funtester.base.bean.AbstractBean 4 | import io.swagger.annotations.ApiModel 5 | 6 | import javax.validation.constraints.Pattern 7 | 8 | @ApiModel(value = "slave节点注册bean") 9 | class RegisterBean extends AbstractBean { 10 | 11 | private static final long serialVersionUID = 3240850903838940L; 12 | 13 | @Pattern(regexp = "http://((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])", message = "地址错误") 14 | String url 15 | 16 | Boolean status 17 | 18 | } 19 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/bean/run/LocalMethod.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.bean.run 2 | 3 | 4 | import com.funtester.base.bean.AbstractBean 5 | import io.swagger.annotations.ApiModel 6 | 7 | import javax.validation.constraints.NotEmpty 8 | import javax.validation.constraints.NotNull 9 | import javax.validation.constraints.Pattern 10 | 11 | @ApiModel(value = "本地方法性能测试参数") 12 | class LocalMethod extends AbstractBean implements Serializable{ 13 | 14 | private static final long serialVersionUID = 35205992379274029l; 15 | 16 | @NotNull 17 | Integer mark 18 | 19 | @NotEmpty 20 | @Pattern(regexp = "com\\..*main", message = "方法名错误") 21 | String methodName 22 | 23 | List params 24 | 25 | } 26 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/Task.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common 2 | 3 | 4 | import com.funtester.slave.manager.SlaveManager 5 | import com.funtester.utils.Time 6 | import org.apache.logging.log4j.LogManager 7 | import org.apache.logging.log4j.Logger 8 | import org.springframework.scheduling.annotation.Scheduled 9 | import org.springframework.stereotype.Component 10 | 11 | @Component 12 | class Task { 13 | 14 | private static Logger logger = LogManager.getLogger(Task.class); 15 | 16 | 17 | /** 18 | * 定时删除存的过期数据 19 | * @return 20 | */ 21 | @Scheduled(cron = "0/5 * * * * ?") 22 | def updateNodeInfo() { 23 | SlaveManager.update() 24 | SlaveManager.updateProgress() 25 | logger.info("更新节点信息执行完毕! 时间:{}", Time.getDate()) 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/template/ListRequestMode.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.template 2 | 3 | import com.funtester.base.constaint.FixedThread 4 | import com.funtester.base.constaint.ThreadBase 5 | import com.funtester.httpclient.FunLibrary 6 | import org.apache.http.client.methods.HttpRequestBase 7 | 8 | class ListRequestMode extends FixedThread { 9 | 10 | 11 | ListRequestMode(List res, int times) { 12 | super(res, times, true) 13 | } 14 | 15 | @Override 16 | protected void doing() throws Exception { 17 | // FunLibrary.executeSimlple(res.get(index.getAndDecrement() % res.size())) 18 | FunLibrary.executeSimlple(random(f)) 19 | } 20 | 21 | @Override 22 | ThreadBase clone() { 23 | return new ListRequestMode(f, limit); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/basedata/DcsConstant.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.basedata; 2 | 3 | import com.funtester.config.Constant; 4 | import com.funtester.config.PropertyUtils; 5 | import com.funtester.httpclient.FunLibrary; 6 | import org.apache.http.Header; 7 | 8 | /** 9 | * 常量配置类 10 | */ 11 | public class DcsConstant { 12 | 13 | private static PropertyUtils.Property property = PropertyUtils.getLocalProperties(Constant.WORK_SPACE + "fun.properties"); 14 | 15 | public static final String HEADER_KEY = "funtester"; 16 | 17 | public static final String HEADER_VALUE = property.getProperty("key"); 18 | 19 | public static final Header FUNTESTER = FunLibrary.getHeader(HEADER_KEY, HEADER_VALUE); 20 | 21 | public static String MASTER_HOST = property.getProperty("master.host"); 22 | 23 | public static String LOCAL_HOST; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/Task.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common 2 | 3 | import com.funtester.master.common.basedata.NodeData 4 | import com.funtester.utils.Time 5 | import org.apache.logging.log4j.LogManager 6 | import org.apache.logging.log4j.Logger 7 | import org.springframework.scheduling.annotation.Scheduled 8 | import org.springframework.stereotype.Component 9 | 10 | @Component 11 | class Task { 12 | 13 | private static Logger logger = LogManager.getLogger(Task.class); 14 | 15 | /** 16 | * 定时删除存的过期数据 17 | * @return 18 | */ 19 | @Scheduled(cron = "0 0 0/3 * * ?") 20 | def cleanNodeInfo() { 21 | NodeData.check() 22 | logger.info("清除过期节点信息定时任务执行完毕! 时间:{}", Time.getDate()) 23 | } 24 | 25 | @Scheduled(cron = "0/5 * * * * ?") 26 | def checkNode() { 27 | NodeData.checkNode() 28 | logger.info("清除过期节点信息定时任务执行完毕! 时间:{}", Time.getDate()) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/bean/run/ManyRequest.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.bean.run 2 | 3 | import com.alibaba.fastjson.JSONArray 4 | import com.alibaba.fastjson.JSONObject 5 | import com.funtester.base.bean.AbstractBean 6 | import io.swagger.annotations.ApiModel 7 | import org.hibernate.validator.constraints.Length 8 | 9 | import javax.validation.constraints.NotBlank 10 | import javax.validation.constraints.NotNull 11 | import javax.validation.constraints.Pattern 12 | 13 | @ApiModel(value = "单请求放大器参数") 14 | class ManyRequest extends AbstractBean { 15 | 16 | private static final long serialVersionUID = -49090532920398509L; 17 | 18 | @Pattern(regexp = "get|post|Get|Post|GET|POST", message = "请求方法错误!") 19 | String requestType 20 | 21 | @NotBlank 22 | @Pattern(regexp = "http.*", message = "请求url错误") 23 | String uri 24 | 25 | @NotNull 26 | JSONObject args 27 | 28 | @NotNull 29 | JSONObject params 30 | 31 | @NotNull 32 | JSONObject json 33 | 34 | @NotNull 35 | JSONArray headers 36 | 37 | @Length(min = 10, max = 100, message = "倍数范围错误") 38 | Integer times 39 | 40 | } 41 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/SlaveApplication.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; 6 | import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; 7 | import org.springframework.boot.web.servlet.ServletComponentScan; 8 | import org.springframework.scheduling.annotation.EnableAsync; 9 | import org.springframework.scheduling.annotation.EnableScheduling; 10 | import org.springframework.transaction.annotation.EnableTransactionManagement; 11 | import springfox.documentation.oas.annotations.EnableOpenApi; 12 | 13 | 14 | //@MapperScan(value = "com.okay.family.mapper") 15 | @ServletComponentScan 16 | @EnableAsync 17 | @EnableTransactionManagement 18 | @EnableScheduling 19 | @SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) 20 | @EnableOpenApi 21 | public class SlaveApplication { 22 | 23 | public static void main(String[] args) { 24 | SpringApplication.run(SlaveApplication.class, args); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/MasterApplication.java: -------------------------------------------------------------------------------- 1 | package com.funtester.master; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; 6 | import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; 7 | import org.springframework.boot.web.servlet.ServletComponentScan; 8 | import org.springframework.scheduling.annotation.EnableAsync; 9 | import org.springframework.scheduling.annotation.EnableScheduling; 10 | import org.springframework.transaction.annotation.EnableTransactionManagement; 11 | import springfox.documentation.oas.annotations.EnableOpenApi; 12 | 13 | 14 | //@MapperScan(value = "com.okay.family.mapper") 15 | @ServletComponentScan 16 | @EnableAsync 17 | @EnableTransactionManagement 18 | @EnableScheduling 19 | @SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) 20 | @EnableOpenApi 21 | public class MasterApplication { 22 | 23 | public static void main(String[] args) { 24 | SpringApplication.run(MasterApplication.class, args); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /master/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8080 2 | 3 | spring.datasource.url=jdbc:mysql://:3306/neworiental_family?useSSL=false&serverTimezone=GMT%2B8&useAffectedRows=true&characterEncoding=utf-8 4 | spring.groovy.template.check-template-location=false 5 | spring.datasource.username=root 6 | spring.datasource.password=123456 7 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 8 | spring.datasource.max-idle=10 9 | spring.datasource.max-wait=10000 10 | spring.datasource.min-idle=5 11 | spring.datasource.initial-size=5 12 | # 指定连接池中最大的活跃连接数. 13 | spring.datasource.max-active=50 14 | # 指定连接池最大的连接数,包括使用中的和空闲的连接. 15 | spring.datasource.maximum-pool-size=80 16 | mybatis.mapperLocations=classpath:/mapper/*.xml 17 | pagehelper.helperDialect=mysql 18 | pagehelper.reasonable=true 19 | pagehelper.supportMethodsArguments=true 20 | pagehelper.params=count=countSql 21 | pagehelper.returnPageInfo=check 22 | 23 | #配置mybaits自定义类型转换类所在的包 24 | mybatis.type-handlers-package=com.okay.family.common.typehandler 25 | 26 | spring.main.allow-bean-definition-overriding=true 27 | 28 | 29 | #设置超时时间 30 | rest.connection.connectionRequestTimeout=10000 31 | rest.connection.connectTimeout=10000 32 | rest.connection.readTimeout=10000 -------------------------------------------------------------------------------- /slave/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8000 2 | 3 | spring.datasource.url=jdbc:mysql://10.60.0.11:3306/neworiental_family?useSSL=false&serverTimezone=GMT%2B8&useAffectedRows=true&characterEncoding=utf-8 4 | spring.groovy.template.check-template-location=false 5 | spring.datasource.username=root 6 | spring.datasource.password=123456 7 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 8 | spring.datasource.max-idle=10 9 | spring.datasource.max-wait=10000 10 | spring.datasource.min-idle=5 11 | spring.datasource.initial-size=5 12 | # 指定连接池中最大的活跃连接数. 13 | spring.datasource.max-active=50 14 | # 指定连接池最大的连接数,包括使用中的和空闲的连接. 15 | spring.datasource.maximum-pool-size=80 16 | mybatis.mapperLocations=classpath:/mapper/*.xml 17 | pagehelper.helperDialect=mysql 18 | pagehelper.reasonable=true 19 | pagehelper.supportMethodsArguments=true 20 | pagehelper.params=count=countSql 21 | pagehelper.returnPageInfo=check 22 | 23 | #配置mybaits自定义类型转换类所在的包 24 | mybatis.type-handlers-package=com.okay.family.common.typehandler 25 | 26 | spring.main.allow-bean-definition-overriding=true 27 | 28 | 29 | #设置超时时间 30 | rest.connection.connectionRequestTimeout=10000 31 | rest.connection.connectTimeout=10000 32 | rest.connection.readTimeout=10000 -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/bean/run/HttpRequests.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.bean.run 2 | 3 | 4 | import com.funtester.base.bean.AbstractBean 5 | import io.swagger.annotations.ApiModel 6 | import io.swagger.annotations.ApiModelProperty 7 | import org.hibernate.validator.constraints.Length 8 | import org.hibernate.validator.constraints.Range 9 | 10 | import javax.validation.constraints.NotNull 11 | 12 | @ApiModel(value = "多请求测试参数") 13 | class HttpRequests extends AbstractBean implements Serializable { 14 | 15 | private static final long serialVersionUID = -324324327948379L; 16 | 17 | @NotNull 18 | Integer mark 19 | 20 | @ApiModelProperty(value = "http请求队列") 21 | @NotNull 22 | List requests 23 | 24 | @ApiModelProperty(value = "单线程请求次数") 25 | @Range(min = 1L, max = 2000L) 26 | Integer times 27 | 28 | @ApiModelProperty(value = "模式,内测阶段只支持ftt") 29 | String mode; 30 | 31 | @ApiModelProperty(value = "线程数") 32 | @Range(min = 1L, max = 10L) 33 | Integer thread 34 | 35 | @ApiModelProperty(value = "软启动时间") 36 | Integer runup 37 | 38 | @ApiModelProperty(value = "用例描述,1-100字符") 39 | @Length(min = 1, max = 100) 40 | String desc 41 | 42 | } 43 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/bean/run/BaseRequest.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.bean.run 2 | 3 | import com.alibaba.fastjson.JSONArray 4 | import com.alibaba.fastjson.JSONObject 5 | import com.funtester.base.bean.AbstractBean 6 | import io.swagger.annotations.ApiModel 7 | import io.swagger.annotations.ApiModelProperty 8 | 9 | import javax.validation.constraints.NotBlank 10 | import javax.validation.constraints.NotNull 11 | import javax.validation.constraints.Pattern 12 | 13 | @ApiModel(value = "基础HTTP请求对象") 14 | class BaseRequest extends AbstractBean { 15 | 16 | private static final long serialVersionUID = -490905323920398509L; 17 | 18 | @Pattern(regexp = "get|post|Get|Post|GET|POST", message = "请求方法错误!") 19 | String requestType 20 | 21 | @ApiModelProperty(value = "请求的URL") 22 | @NotBlank 23 | @Pattern(regexp = "http.*", message = "请求url错误") 24 | String uri 25 | 26 | @ApiModelProperty(value = "请求GET参数") 27 | @NotNull 28 | JSONObject args 29 | 30 | @ApiModelProperty(value = "POST和PUT请求表单参数") 31 | @NotNull 32 | JSONObject params 33 | 34 | @ApiModelProperty(value = "POST和PUT请求JSON参数") 35 | @NotNull 36 | JSONObject json 37 | 38 | @ApiModelProperty(value = "请求的header") 39 | @NotNull 40 | JSONArray headers 41 | 42 | } 43 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/bean/run/HttpRequest.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.bean.run 2 | 3 | 4 | import com.funtester.base.bean.AbstractBean 5 | import io.swagger.annotations.ApiModel 6 | import io.swagger.annotations.ApiModelProperty 7 | import org.hibernate.validator.constraints.Length 8 | import org.hibernate.validator.constraints.Range 9 | 10 | import javax.validation.constraints.NotNull 11 | 12 | @ApiModel(value = "单请求性能测试参数") 13 | class HttpRequest extends AbstractBean implements Serializable { 14 | 15 | private static final long serialVersionUID = 324324327948379L; 16 | 17 | /** 18 | * 单节点版本时候是标记,多节点版本是是执行节点数 19 | */ 20 | @NotNull 21 | Integer mark 22 | 23 | @ApiModelProperty(value = "http请求") 24 | @NotNull 25 | BaseRequest request 26 | 27 | @ApiModelProperty(value = "单线程请求次数") 28 | @Range(min = 1L, max = 2000L) 29 | Integer times 30 | 31 | @ApiModelProperty(value = "模式,内测阶段只支持ftt") 32 | String mode; 33 | //线程数,这里默认固定线程模式 34 | @ApiModelProperty(value = "线程数") 35 | @Range(min = 1L, max = 100L) 36 | Integer thread 37 | 38 | //软启动时间 39 | @ApiModelProperty(value = "软启动时间") 40 | Integer runup 41 | 42 | //用例描述 43 | @ApiModelProperty(value = "用例描述,1-100字符") 44 | @Length(min = 1, max = 100) 45 | String desc 46 | 47 | } 48 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/config/SwaggerConfig.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.config 2 | 3 | import io.swagger.annotations.ApiOperation 4 | import org.springframework.context.annotation.Bean 5 | import org.springframework.context.annotation.Configuration 6 | import springfox.documentation.builders.ApiInfoBuilder 7 | import springfox.documentation.builders.PathSelectors 8 | import springfox.documentation.builders.RequestHandlerSelectors 9 | import springfox.documentation.service.ApiInfo 10 | import springfox.documentation.service.Contact 11 | import springfox.documentation.spi.DocumentationType 12 | import springfox.documentation.spring.web.plugins.Docket 13 | 14 | @Configuration 15 | class SwaggerConfig { 16 | 17 | @Bean 18 | public Docket createRestApi() { 19 | return new Docket(DocumentationType.OAS_30) 20 | .apiInfo(apiInfo()) 21 | .select() 22 | .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) 23 | .paths(PathSelectors.any()) 24 | .build(); 25 | } 26 | 27 | private ApiInfo apiInfo() { 28 | return new ApiInfoBuilder() 29 | .title("Swagger3接口文档") 30 | .description("DCS_FunTester框架slave项目") 31 | .contact(new Contact("FunTester", "", "FunTester@88.com")) 32 | .version("1.0") 33 | .build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/config/SwaggerConfig.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common.config 2 | 3 | import io.swagger.annotations.ApiOperation 4 | import org.springframework.context.annotation.Bean 5 | import org.springframework.context.annotation.Configuration 6 | import springfox.documentation.builders.ApiInfoBuilder 7 | import springfox.documentation.builders.PathSelectors 8 | import springfox.documentation.builders.RequestHandlerSelectors 9 | import springfox.documentation.service.ApiInfo 10 | import springfox.documentation.service.Contact 11 | import springfox.documentation.spi.DocumentationType 12 | import springfox.documentation.spring.web.plugins.Docket 13 | 14 | @Configuration 15 | class SwaggerConfig { 16 | 17 | @Bean 18 | public Docket createRestApi() { 19 | return new Docket(DocumentationType.OAS_30) 20 | .apiInfo(apiInfo()) 21 | .select() 22 | .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) 23 | .paths(PathSelectors.any()) 24 | .build(); 25 | } 26 | 27 | private ApiInfo apiInfo() { 28 | return new ApiInfoBuilder() 29 | .title("Swagger3接口文档") 30 | .description("DCS_FunTester框架master项目") 31 | .contact(new Contact("FunTester", "", "FunTester@88.com")) 32 | .version("1.0") 33 | .build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/wapper/CommonExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.wapper; 2 | 3 | 4 | import com.funtester.base.bean.Result; 5 | import com.funtester.base.exception.FailException; 6 | import com.funtester.config.Constant; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.web.bind.MethodArgumentNotValidException; 10 | import org.springframework.web.bind.annotation.ControllerAdvice; 11 | import org.springframework.web.bind.annotation.ExceptionHandler; 12 | import org.springframework.web.bind.annotation.ResponseBody; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | 16 | @ControllerAdvice 17 | public class CommonExceptionHandler { 18 | 19 | public static Logger logger = LoggerFactory.getLogger(CommonExceptionHandler.class); 20 | 21 | @ExceptionHandler(value = Exception.class) 22 | @ResponseBody 23 | public Result defaultErrorHandler(HttpServletRequest req, Exception e) { 24 | /**参数错误*/ 25 | if (e instanceof MethodArgumentNotValidException) { 26 | String defaultMessage = ((MethodArgumentNotValidException) e).getBindingResult().getFieldError().getDefaultMessage(); 27 | logger.error("参数异常", e); 28 | return Result.fail(defaultMessage); 29 | } 30 | if (e instanceof FailException) { 31 | logger.error("捕获自定义异常", e); 32 | return Result.fail(e.getMessage()); 33 | } 34 | logger.warn("未记录异常类", e); 35 | return Result.fail(Constant.TEST_ERROR_CODE); 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/wapper/CommonExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common.wapper; 2 | 3 | 4 | import com.funtester.base.bean.Result; 5 | import com.funtester.base.exception.FailException; 6 | import com.funtester.config.Constant; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.web.bind.MethodArgumentNotValidException; 10 | import org.springframework.web.bind.annotation.ControllerAdvice; 11 | import org.springframework.web.bind.annotation.ExceptionHandler; 12 | import org.springframework.web.bind.annotation.ResponseBody; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | 16 | @ControllerAdvice 17 | public class CommonExceptionHandler { 18 | 19 | public static Logger logger = LoggerFactory.getLogger(CommonExceptionHandler.class); 20 | 21 | @ExceptionHandler(value = Exception.class) 22 | @ResponseBody 23 | public Result defaultErrorHandler(HttpServletRequest req, Exception e) { 24 | /**参数错误*/ 25 | if (e instanceof MethodArgumentNotValidException) { 26 | String defaultMessage = ((MethodArgumentNotValidException) e).getBindingResult().getFieldError().getDefaultMessage(); 27 | logger.error("参数异常", e); 28 | return Result.fail(defaultMessage); 29 | } 30 | if (e instanceof FailException) { 31 | logger.error("捕获自定义异常", e); 32 | return Result.fail(e.getMessage()); 33 | } 34 | logger.warn("未记录异常类", e); 35 | return Result.fail(Constant.TEST_ERROR_CODE); 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /slave/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.5.2' 4 | } 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 10 | } 11 | } 12 | 13 | apply plugin: 'org.springframework.boot' 14 | apply plugin: 'io.spring.dependency-management' 15 | apply plugin: 'idea' 16 | apply plugin: 'groovy' 17 | apply plugin: 'java' 18 | 19 | 20 | group = 'com.funtester' 21 | version = '0.1' 22 | sourceCompatibility = '1.8' 23 | 24 | repositories { 25 | // 本地maven仓库 26 | mavenLocal() 27 | maven {url = 'http://maven.aliyun.com/nexus/content/groups/public/'} 28 | //和maven中央仓库一样的另一个依赖管理仓库,主要是java 29 | jcenter() 30 | } 31 | 32 | dependencies { 33 | implementation group: 'org.codehaus.groovy', name: 'groovy-all', version: '3.0.8' 34 | implementation files('/Users/oker/Library/groovy-3.0.8/lib/funtester-1.0.jar') 35 | implementation 'org.springframework.boot:spring-boot-starter' 36 | implementation 'org.springframework.boot:spring-boot-starter-web' 37 | compile group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '2.1.1' 38 | compile group: 'org.springframework.boot', name: 'spring-boot-starter-validation', version: '2.2.5.RELEASE' 39 | compile group: 'org.springframework.boot', name: 'spring-boot-starter-websocket', version: '2.3.0.RELEASE' 40 | implementation group: 'io.springfox', name: 'springfox-boot-starter', version: '3.0.0' 41 | } 42 | 43 | test { 44 | useJUnitPlatform() 45 | } 46 | -------------------------------------------------------------------------------- /master/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.5.2' 4 | } 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 10 | } 11 | } 12 | 13 | apply plugin: 'org.springframework.boot' 14 | apply plugin: 'io.spring.dependency-management' 15 | apply plugin: 'idea' 16 | apply plugin: 'groovy' 17 | apply plugin: 'java' 18 | 19 | 20 | group = 'com.funtester' 21 | version = '0.1' 22 | sourceCompatibility = '1.8' 23 | 24 | repositories { 25 | // 本地maven仓库 26 | mavenLocal() 27 | maven { url = 'http://maven.aliyun.com/nexus/content/groups/public/' } 28 | //和maven中央仓库一样的另一个依赖管理仓库,主要是java 29 | jcenter()} 30 | 31 | dependencies { 32 | implementation project(':slave') 33 | compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '3.0.8' 34 | compile files('/Users/oker/Library/groovy-3.0.8/lib/funtester-1.0.jar') 35 | implementation 'org.springframework.boot:spring-boot-starter' 36 | implementation 'org.springframework.boot:spring-boot-starter-web' 37 | compile group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '2.1.1' 38 | compile group: 'org.springframework.boot', name: 'spring-boot-starter-validation', version: '2.2.5.RELEASE' 39 | compile group: 'org.springframework.boot', name: 'spring-boot-starter-websocket', version: '2.3.0.RELEASE' 40 | implementation group: 'io.springfox', name: 'springfox-boot-starter', version: '3.0.0' 41 | } 42 | 43 | test { 44 | useJUnitPlatform() 45 | } -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/wapper/CommonInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common.wapper; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.web.servlet.ModelAndView; 6 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | 11 | //@Component("commonInterceptor") 12 | public class CommonInterceptor extends HandlerInterceptorAdapter { 13 | 14 | private static Logger logger = LoggerFactory.getLogger(CommonInterceptor.class); 15 | 16 | @Override 17 | public boolean preHandle(HttpServletRequest request, HttpServletResponse httpServletResponse, Object o) throws Exception { 18 | try { 19 | RequestWrapper requestWrapper = new RequestWrapper(request); 20 | String url = requestWrapper.getRequestURL().toString(); 21 | String queryArgs = requestWrapper.getQueryString(); 22 | queryArgs = queryArgs == null ? requestWrapper.getBody() : queryArgs; 23 | logger.info("请求url:{},参数:{}", url, queryArgs); 24 | ResponseWrapper responseWrapper = new ResponseWrapper(httpServletResponse); 25 | byte[] content = responseWrapper.getContent(); 26 | String s = new String(content); 27 | logger.error(s); 28 | 29 | return true; 30 | } catch (Exception e) { 31 | logger.error("", e); 32 | } 33 | return false; 34 | } 35 | 36 | @Override 37 | public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { 38 | 39 | } 40 | 41 | @Override 42 | public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/wapper/CommonInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.wapper; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.web.servlet.ModelAndView; 6 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | 11 | //@Component("commonInterceptor") 12 | public class CommonInterceptor extends HandlerInterceptorAdapter { 13 | 14 | private static Logger logger = LoggerFactory.getLogger(CommonInterceptor.class); 15 | 16 | @Override 17 | public boolean preHandle(HttpServletRequest request, HttpServletResponse httpServletResponse, Object o) throws Exception { 18 | try { 19 | RequestWrapper requestWrapper = new RequestWrapper(request); 20 | String url = requestWrapper.getRequestURL().toString(); 21 | String queryArgs = requestWrapper.getQueryString(); 22 | queryArgs = queryArgs == null ? requestWrapper.getBody() : queryArgs; 23 | logger.info("请求url:{},参数:{}", url, queryArgs); 24 | ResponseWrapper responseWrapper = new ResponseWrapper(httpServletResponse); 25 | byte[] content = responseWrapper.getContent(); 26 | String s = new String(content); 27 | logger.error(s); 28 | 29 | return true; 30 | } catch (Exception e) { 31 | logger.error("", e); 32 | } 33 | return false; 34 | } 35 | 36 | @Override 37 | public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { 38 | 39 | } 40 | 41 | @Override 42 | public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/manager/SlaveHttp.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.manager 2 | 3 | import com.alibaba.fastjson.JSONObject 4 | import com.funtester.base.bean.AbstractBean 5 | import com.funtester.httpclient.FunLibrary 6 | import com.funtester.slave.common.basedata.DcsConstant 7 | import org.apache.commons.lang3.StringUtils 8 | import org.apache.logging.log4j.LogManager 9 | import org.apache.logging.log4j.Logger 10 | 11 | class SlaveHttp extends FunLibrary { 12 | 13 | private static final Logger logger = LogManager.getLogger(SlaveHttp.class); 14 | 15 | static JSONObject getGetResponse(String url, JSONObject args) { 16 | if (StringUtils.isAnyBlank(DcsConstant.MASTER_HOST, DcsConstant.LOCAL_HOST)) return null 17 | def get = getHttpGet(DcsConstant.MASTER_HOST + url, args) 18 | get.addHeader(DcsConstant.FUNTESTER) 19 | getHttpResponse(get) 20 | } 21 | 22 | static JSONObject getGetResponse(String url) { 23 | getGetResponse(url, null) 24 | } 25 | 26 | static JSONObject getPostResponse(String url, JSONObject params) { 27 | if (StringUtils.isAnyBlank(DcsConstant.MASTER_HOST, DcsConstant.LOCAL_HOST)) return null 28 | def post = getHttpPost(DcsConstant.MASTER_HOST + url, params.toString()) 29 | post.addHeader(DcsConstant.FUNTESTER) 30 | getHttpResponse(post) 31 | } 32 | 33 | static JSONObject getPostResponse(String url, AbstractBean bean) { 34 | if (StringUtils.isAnyBlank(DcsConstant.MASTER_HOST, DcsConstant.LOCAL_HOST)) return null 35 | def post = getHttpPost(DcsConstant.MASTER_HOST + url, bean.toString()) 36 | post.addHeader(DcsConstant.FUNTESTER) 37 | getHttpResponse(post) 38 | } 39 | 40 | static boolean isRight(JSONObject response) { 41 | try { 42 | return response.getIntValue("code") == 0 43 | } catch (e) { 44 | logger.warn("响应出错:{}", response.toString()) 45 | return false 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/wapper/ResponseWrapper.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.wapper; 2 | 3 | import javax.servlet.ServletOutputStream; 4 | import javax.servlet.WriteListener; 5 | import javax.servlet.http.HttpServletResponse; 6 | import javax.servlet.http.HttpServletResponseWrapper; 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.IOException; 9 | import java.io.PrintWriter; 10 | 11 | public class ResponseWrapper extends HttpServletResponseWrapper { 12 | private ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 13 | 14 | private PrintWriter printWriter = new PrintWriter(outputStream); 15 | 16 | public ResponseWrapper(HttpServletResponse response) { 17 | super(response); 18 | } 19 | 20 | @Override 21 | public ServletOutputStream getOutputStream() throws IOException { 22 | return new ServletOutputStream() { 23 | @Override 24 | public boolean isReady() { 25 | return false; 26 | } 27 | 28 | @Override 29 | public void setWriteListener(WriteListener writeListener) { 30 | 31 | } 32 | 33 | @Override 34 | public void write(int b) throws IOException { 35 | outputStream.write(b); 36 | } 37 | 38 | @Override 39 | public void write(byte[] b) throws IOException { 40 | outputStream.write(b); 41 | } 42 | 43 | @Override 44 | public void write(byte[] b, int off, int len) throws IOException { 45 | outputStream.write(b, off, len); 46 | } 47 | 48 | @Override 49 | public void flush() throws IOException { 50 | outputStream.flush(); 51 | } 52 | }; 53 | } 54 | 55 | @Override 56 | public PrintWriter getWriter() throws IOException { 57 | return printWriter; 58 | } 59 | 60 | public void flush(){ 61 | try { 62 | printWriter.flush(); 63 | printWriter.close(); 64 | outputStream.flush(); 65 | outputStream.close(); 66 | } catch (IOException e) { 67 | e.printStackTrace(); 68 | } 69 | } 70 | 71 | public byte[] getContent() { 72 | flush(); 73 | return outputStream.toByteArray(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/wapper/ResponseWrapper.java: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common.wapper; 2 | 3 | import javax.servlet.ServletOutputStream; 4 | import javax.servlet.WriteListener; 5 | import javax.servlet.http.HttpServletResponse; 6 | import javax.servlet.http.HttpServletResponseWrapper; 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.IOException; 9 | import java.io.PrintWriter; 10 | 11 | public class ResponseWrapper extends HttpServletResponseWrapper { 12 | private ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 13 | 14 | private PrintWriter printWriter = new PrintWriter(outputStream); 15 | 16 | public ResponseWrapper(HttpServletResponse response) { 17 | super(response); 18 | } 19 | 20 | @Override 21 | public ServletOutputStream getOutputStream() throws IOException { 22 | return new ServletOutputStream() { 23 | @Override 24 | public boolean isReady() { 25 | return false; 26 | } 27 | 28 | @Override 29 | public void setWriteListener(WriteListener writeListener) { 30 | 31 | } 32 | 33 | @Override 34 | public void write(int b) throws IOException { 35 | outputStream.write(b); 36 | } 37 | 38 | @Override 39 | public void write(byte[] b) throws IOException { 40 | outputStream.write(b); 41 | } 42 | 43 | @Override 44 | public void write(byte[] b, int off, int len) throws IOException { 45 | outputStream.write(b, off, len); 46 | } 47 | 48 | @Override 49 | public void flush() throws IOException { 50 | outputStream.flush(); 51 | } 52 | }; 53 | } 54 | 55 | @Override 56 | public PrintWriter getWriter() throws IOException { 57 | return printWriter; 58 | } 59 | 60 | public void flush(){ 61 | try { 62 | printWriter.flush(); 63 | printWriter.close(); 64 | outputStream.flush(); 65 | outputStream.close(); 66 | } catch (IOException e) { 67 | e.printStackTrace(); 68 | } 69 | } 70 | 71 | public byte[] getContent() { 72 | flush(); 73 | return outputStream.toByteArray(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/controller/ConfigController.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.controller; 2 | 3 | import com.funtester.base.bean.Result; 4 | import com.funtester.base.constaint.ThreadBase; 5 | import com.funtester.config.Constant; 6 | import com.funtester.slave.common.basedata.DcsConstant; 7 | import com.funtester.slave.manager.SlaveManager; 8 | import com.funtester.utils.Regex; 9 | import io.swagger.annotations.Api; 10 | import io.swagger.annotations.ApiOperation; 11 | import org.apache.logging.log4j.LogManager; 12 | import org.apache.logging.log4j.Logger; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | import java.util.Map; 16 | 17 | @Api(tags = "slave测试接口") 18 | @RestController 19 | @RequestMapping(value = "/test") 20 | public class ConfigController { 21 | 22 | private static Logger logger = LogManager.getLogger(ConfigController.class); 23 | 24 | 25 | @ApiOperation(value = "更新master节点host") 26 | @PostMapping(value = "/m") 27 | public Result findUsers(@RequestBody Map param) { 28 | String host = param.get("host").toString(); 29 | if (!Regex.isMatch(host, "http://" + Constant.HOST_REGEX)) Result.fail(); 30 | DcsConstant.MASTER_HOST = host; 31 | return Result.success(); 32 | } 33 | 34 | @ApiOperation(value = "获取远程host") 35 | @GetMapping(value = "/mh") 36 | public Result getLocalHost() { 37 | return Result.success(DcsConstant.MASTER_HOST); 38 | } 39 | 40 | @ApiOperation(value = "获取状态,提供master验证使用") 41 | @GetMapping(value = "/s") 42 | public Result nodeStatus() { 43 | return Result.success(ThreadBase.needAbort()); 44 | } 45 | 46 | @PostMapping(value = "/stop") 47 | public Result stop() { 48 | ThreadBase.stop(); 49 | return Result.success(); 50 | } 51 | 52 | @ApiOperation(value = "刷新节点IP") 53 | @GetMapping(value = "/ip") 54 | public Result refreshHost() { 55 | SlaveManager.getIP(); 56 | return Result.success(); 57 | } 58 | 59 | @ApiOperation(value = "刷新节点IP") 60 | @GetMapping(value = "/register") 61 | public Result register() { 62 | return Result.success(SlaveManager.register()); 63 | } 64 | 65 | @ApiOperation(value = "节点状态,是否存活") 66 | @GetMapping(value = "/alive") 67 | public Result alive() { 68 | return Result.success(); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/manaer/MasterHttp.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.master.manaer 2 | 3 | import com.alibaba.fastjson.JSONObject 4 | import com.funtester.base.bean.AbstractBean 5 | import com.funtester.httpclient.FunLibrary 6 | import com.funtester.slave.common.basedata.DcsConstant 7 | import org.apache.logging.log4j.LogManager 8 | import org.apache.logging.log4j.Logger 9 | 10 | /** 11 | * master节点HTTP功能类封装 12 | */ 13 | class MasterHttp extends FunLibrary { 14 | 15 | private static final Logger logger = LogManager.getLogger(MasterHttp.class); 16 | 17 | /** 18 | * 获取GET请求响应 19 | * @param slave 20 | * @param api 21 | * @param args 22 | * @return 23 | */ 24 | static JSONObject getGetResponse(String slave, String api, JSONObject args) { 25 | def get = getHttpGet(slave + api, args) 26 | get.addHeader(DcsConstant.FUNTESTER) 27 | getHttpResponse(get) 28 | } 29 | 30 | /** 31 | * 获取GET请求响应 32 | * @param slave 33 | * @param api 34 | * @return 35 | */ 36 | static JSONObject getGetResponse(String slave, String api) { 37 | def get = getHttpGet(slave + api) 38 | get.addHeader(DcsConstant.FUNTESTER) 39 | getHttpResponse(get) 40 | } 41 | 42 | /** 43 | * 获取POST请求响应 44 | * @param slave 45 | * @param api 46 | * @return 47 | */ 48 | static JSONObject getPostResponse(String slave, String api) { 49 | getPostResponse(slave, api, new JSONObject()) 50 | } 51 | 52 | /** 53 | * 获取POST请求响应 54 | * @param slave 55 | * @param api 56 | * @return 57 | */ 58 | static JSONObject getPostResponse(String slave, String api, JSONObject params) { 59 | def post = getHttpPost(slave + api, params.toString()) 60 | post.addHeader(DcsConstant.FUNTESTER) 61 | getHttpResponse(post) 62 | } 63 | 64 | /** 65 | * 获取POST请求响应 66 | * @param slave 67 | * @param api 68 | * @return 69 | */ 70 | static JSONObject getPostResponse(String slave, String url, AbstractBean bean) { 71 | def post = getHttpPost(slave + url, bean.toString()) 72 | post.addHeader(DcsConstant.FUNTESTER) 73 | getHttpResponse(post) 74 | } 75 | 76 | static boolean isRight(JSONObject response) { 77 | try { 78 | return response.getInteger("code") == 0 79 | } catch (Exception e) { 80 | logger.warn("响应出错:{}", response.toString()) 81 | return false 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/manager/SlaveManager.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.manager 2 | 3 | import com.alibaba.fastjson.JSONObject 4 | import com.funtester.base.bean.PerformanceResultBean 5 | import com.funtester.base.constaint.ThreadBase 6 | import com.funtester.slave.common.basedata.DcsConstant 7 | import com.funtester.slave.common.config.MasterApi 8 | import com.funtester.slave.common.config.ServerConfig 9 | import org.apache.commons.lang3.StringUtils 10 | 11 | /** 12 | * slave节点HTTP接口功能管理类 13 | */ 14 | class SlaveManager extends SlaveHttp { 15 | 16 | 17 | /** 18 | * 向master节点发请求获取本机IP 19 | */ 20 | static void getIP() { 21 | if (StringUtils.isBlank(DcsConstant.MASTER_HOST)) return 22 | String url = MasterApi.GET_IP 23 | def response = getHttpResponse(getHttpGet(DcsConstant.MASTER_HOST + url)) 24 | if (isRight(response)) { 25 | DcsConstant.LOCAL_HOST = "http://" + response.getString("data") + ":" + ServerConfig.serverPort 26 | } 27 | } 28 | 29 | /** 30 | * 注册slave节点 31 | * @return 32 | */ 33 | static boolean register() { 34 | String url = MasterApi.REGISTER 35 | def params = new JSONObject() 36 | params.url = DcsConstant.LOCAL_HOST 37 | def response = getPostResponse(url, params) 38 | isRight(response) 39 | } 40 | 41 | /** 42 | * 更新运行状态 43 | * @return 44 | */ 45 | static boolean update() { 46 | String url = MasterApi.UPDATE 47 | def params = new JSONObject() 48 | params.url = DcsConstant.LOCAL_HOST 49 | params.status = ThreadBase.needAbort() 50 | def response = getPostResponse(url, params) 51 | isRight(response) 52 | } 53 | 54 | /** 55 | * 更新进度 56 | * @return 57 | */ 58 | static boolean updateProgress() { 59 | String url = MasterApi.UPDATE_INFO 60 | def params = new JSONObject() 61 | params.runinfo = ThreadBase.needAbort() ? "空闲状态" : ThreadBase.progress.runInfo 62 | params.desc = ThreadBase.needAbort() ? DEFAULT_STRING : ThreadBase.progress.taskDesc 63 | params.host = DcsConstant.LOCAL_HOST 64 | def response = getPostResponse(url, params) 65 | isRight(response) 66 | } 67 | 68 | /** 69 | * 更新运行结果 70 | * @param bean 71 | * @param mark 72 | * @return 73 | */ 74 | static boolean updateResult(PerformanceResultBean bean, int mark) { 75 | String url = MasterApi.UPDATE_RESULT + mark; 76 | def response = getPostResponse(url, bean) 77 | isRight(response) 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /master/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | %level %class:%line %msg%n 14 | UTF-8 15 | 16 | 17 | 18 | 19 | 20 | 21 | ${LOG_HOME}/${SEVER_NAME}-info.log 22 | 23 | %d{yyyy-MM-dd HH:mm:ss} %level %class:%line [%thread] %msg%n 24 | UTF-8 25 | 26 | 27 | 28 | info 29 | 30 | 31 | 32 | ${LOG_HOME}/${SEVER_NAME}-info.log.%d{yyyy-MM-dd} 33 | 34 | 10 35 | 36 | 37 | 38 | 39 | 40 | ${LOG_HOME}/${SEVER_NAME}-error.log 41 | 42 | %d{yyyy-MM-dd HH:mm:ss} %level %class:%line [%thread] %msg%n 43 | UTF-8 44 | 45 | 46 | 47 | warn 48 | 50 | 51 | 52 | 53 | ${LOG_HOME}/${SEVER_NAME}-error.log.%d{yyyy-MM-dd} 54 | 55 | 10 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /slave/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | %level %class:%line %msg%n 14 | UTF-8 15 | 16 | 17 | 18 | 19 | 20 | 21 | ${LOG_HOME}/${SEVER_NAME}-info.log 22 | 23 | %d{yyyy-MM-dd HH:mm:ss} %level %class:%line [%thread] %msg%n 24 | UTF-8 25 | 26 | 27 | 28 | info 29 | 30 | 31 | 32 | ${LOG_HOME}/${SEVER_NAME}-info.log.%d{yyyy-MM-dd} 33 | 34 | 10 35 | 36 | 37 | 38 | 39 | 40 | ${LOG_HOME}/${SEVER_NAME}-error.log 41 | 42 | %d{yyyy-MM-dd HH:mm:ss} %level %class:%line [%thread] %msg%n 43 | UTF-8 44 | 45 | 46 | 47 | warn 48 | 50 | 51 | 52 | 53 | ${LOG_HOME}/${SEVER_NAME}-error.log.%d{yyyy-MM-dd} 54 | 55 | 10 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/controller/RunController.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.master.controller 2 | 3 | import com.funtester.base.bean.Result 4 | import com.funtester.master.common.basedata.NodeData 5 | import com.funtester.master.manaer.MasterManager 6 | import com.funtester.master.service.IRunService 7 | import com.funtester.slave.common.bean.run.ManyRequest 8 | import com.funtester.slave.common.bean.run.GroovyScript 9 | import com.funtester.slave.common.bean.run.HttpRequest 10 | import com.funtester.slave.common.bean.run.HttpRequests 11 | import com.funtester.slave.common.bean.run.LocalMethod 12 | import io.swagger.annotations.Api 13 | import io.swagger.annotations.ApiOperation 14 | import org.apache.logging.log4j.LogManager 15 | import org.apache.logging.log4j.Logger 16 | import org.springframework.beans.factory.annotation.Autowired 17 | import org.springframework.web.bind.annotation.* 18 | 19 | import javax.validation.Valid 20 | 21 | @Api(tags = "执行用例模块") 22 | @RestController 23 | @RequestMapping(value = "/run") 24 | class RunController { 25 | 26 | private static final Logger logger = LogManager.getLogger(RunController.class); 27 | 28 | 29 | IRunService runService 30 | 31 | @Autowired 32 | RunController(IRunService runService) { 33 | this.runService = runService 34 | } 35 | 36 | @ApiOperation(value = "请求放大器") 37 | @PostMapping(value = "/many") 38 | Result runMany(@Valid @RequestBody ManyRequest request) { 39 | Result.success(runService.runRequest(request)) 40 | } 41 | 42 | @ApiOperation(value = "多节点执行单请求") 43 | @PostMapping(value = "/req") 44 | Result runR(@Valid @RequestBody HttpRequest request) { 45 | Result.success(runService.runRequest(request)) 46 | } 47 | 48 | @ApiOperation(value = "多节点执行多请求") 49 | @PostMapping(value = "/reqs") 50 | Result runRs(@Valid @RequestBody HttpRequests requests) { 51 | Result.success(runService.runRequests(requests)) 52 | } 53 | 54 | @ApiOperation(value = "多节点执行测试用例") 55 | @PostMapping(value = "/method") 56 | Result runM(@Valid @RequestBody LocalMethod method) { 57 | Result.success(runService.runMethod(method)) 58 | } 59 | 60 | @ApiOperation(value = "多节点执行多Groovy脚本") 61 | @PostMapping(value = "/script") 62 | Result runS(@Valid @RequestBody GroovyScript script) { 63 | Result.success(runService.runScript(script)) 64 | } 65 | 66 | @ApiOperation(value = "终止某个任务的执行") 67 | @PostMapping(value = "/stop/{mark}") 68 | Result stop(@PathVariable(value = "mark", required = true) int mark) { 69 | NodeData.tasks.each { 70 | if (it.getValue() == mark) MasterManager.stop(it.key) 71 | } 72 | } 73 | 74 | @PostMapping(value = "/stopall") 75 | Result over() { 76 | NodeData.status.each { MasterManager.stop(it.getValue()) } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/controller/RunController.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.controller; 2 | 3 | import com.funtester.base.bean.Result; 4 | import com.funtester.base.constaint.ThreadBase; 5 | import com.funtester.slave.common.bean.run.*; 6 | import com.funtester.slave.service.impl.IRunService; 7 | import io.swagger.annotations.Api; 8 | import io.swagger.annotations.ApiImplicitParam; 9 | import io.swagger.annotations.ApiOperation; 10 | import org.apache.logging.log4j.LogManager; 11 | import org.apache.logging.log4j.Logger; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | import javax.validation.Valid; 16 | 17 | @Api(tags = "slave节点运行模块接口") 18 | @RestController 19 | @RequestMapping(value = "/run") 20 | public class RunController { 21 | 22 | private static Logger logger = LogManager.getLogger(RunController.class); 23 | 24 | IRunService runService; 25 | 26 | @Autowired 27 | public RunController(IRunService runService) { 28 | this.runService = runService; 29 | } 30 | 31 | /** 32 | * 获取运行状态 33 | * 34 | * @return 35 | */ 36 | @GetMapping(value = "/progress") 37 | @ApiOperation(value = "获取运行状态") 38 | public Result progeress() { 39 | String s = ThreadBase.progress == null ? "没有运行任务" : ThreadBase.progress.runInfo; 40 | return Result.success(s); 41 | } 42 | 43 | @ApiOperation(value = "单个请求性能测试") 44 | @ApiImplicitParam(name = "reqsut", value = "单请求参数", dataTypeClass = HttpRequest.class) 45 | @PostMapping(value = "/request") 46 | public Result tests(@Valid @RequestBody HttpRequest request) { 47 | runService.runRequest(request); 48 | return Result.success(); 49 | } 50 | 51 | @ApiOperation(value = "请求放大器") 52 | @ApiImplicitParam(name = "reqsut", value = "单请求参数", dataTypeClass = ManyRequest.class) 53 | @PostMapping(value = "/many") 54 | public Result tests2(@Valid @RequestBody ManyRequest request) { 55 | runService.runMany(request); 56 | return Result.success(); 57 | } 58 | 59 | @ApiOperation(value = "请求队列性能测试") 60 | @ApiImplicitParam(name = "reqsuts", value = "请求队列", dataTypeClass = HttpRequests.class) 61 | @PostMapping(value = "/requests") 62 | public Result tests2(@Valid @RequestBody HttpRequests requests) { 63 | runService.runRequests(requests); 64 | return Result.success(); 65 | } 66 | 67 | @ApiOperation(value = "请求自带方法性能测试") 68 | @ApiImplicitParam(name = "method", value = "请求执行本地方法", dataTypeClass = LocalMethod.class) 69 | @PostMapping(value = "/method") 70 | public Result tests3(@Valid @RequestBody LocalMethod method) { 71 | runService.runMethod(method); 72 | return Result.success(); 73 | } 74 | 75 | @ApiOperation(value = "请求自带方法性能测试") 76 | @ApiImplicitParam(name = "scrpit", value = "Groovy脚本", dataTypeClass = GroovyScript.class) 77 | @PostMapping(value = "/script") 78 | public Result tests4(@RequestBody GroovyScript scrpit) { 79 | runService.runScript(scrpit); 80 | return Result.success(); 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/wapper/RequestWrapper.java: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common.wapper; 2 | 3 | import com.funtester.config.Constant; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import javax.servlet.ReadListener; 8 | import javax.servlet.ServletInputStream; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletRequestWrapper; 11 | import java.io.*; 12 | 13 | public class RequestWrapper extends HttpServletRequestWrapper { 14 | 15 | private static Logger logger = LoggerFactory.getLogger(RequestWrapper.class); 16 | 17 | private final String body; 18 | 19 | public RequestWrapper(HttpServletRequest request) { 20 | super(request); 21 | StringBuilder stringBuilder = new StringBuilder(); 22 | BufferedReader bufferedReader = null; 23 | InputStream inputStream = null; 24 | try { 25 | inputStream = request.getInputStream(); 26 | if (inputStream != null) { 27 | bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); 28 | char[] charBuffer = new char[128]; 29 | int bytesRead = -1; 30 | while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { 31 | stringBuilder.append(charBuffer, 0, bytesRead); 32 | } 33 | } else { 34 | stringBuilder.append(Constant.EMPTY); 35 | } 36 | } catch (IOException ex) { 37 | logger.error("获取请求body发生错误", ex); 38 | } finally { 39 | if (inputStream != null) { 40 | try { 41 | inputStream.close(); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | if (bufferedReader != null) { 47 | try { 48 | bufferedReader.close(); 49 | } catch (IOException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | } 54 | body = stringBuilder.toString(); 55 | } 56 | 57 | @Override 58 | public ServletInputStream getInputStream() throws IOException { 59 | final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); 60 | ServletInputStream servletInputStream = new ServletInputStream() { 61 | @Override 62 | public boolean isFinished() { 63 | return false; 64 | } 65 | 66 | @Override 67 | public boolean isReady() { 68 | return false; 69 | } 70 | 71 | @Override 72 | public void setReadListener(ReadListener readListener) { 73 | } 74 | 75 | @Override 76 | public int read() throws IOException { 77 | return byteArrayInputStream.read(); 78 | } 79 | }; 80 | return servletInputStream; 81 | 82 | } 83 | 84 | @Override 85 | public BufferedReader getReader() throws IOException { 86 | return new BufferedReader(new InputStreamReader(this.getInputStream())); 87 | } 88 | 89 | public String getBody() { 90 | return this.body; 91 | } 92 | 93 | 94 | } 95 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/wapper/RequestWrapper.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.wapper; 2 | 3 | import com.funtester.config.Constant; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import javax.servlet.ReadListener; 8 | import javax.servlet.ServletInputStream; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletRequestWrapper; 11 | import java.io.*; 12 | 13 | public class RequestWrapper extends HttpServletRequestWrapper { 14 | 15 | private static Logger logger = LoggerFactory.getLogger(RequestWrapper.class); 16 | 17 | private final String body; 18 | 19 | public RequestWrapper(HttpServletRequest request) { 20 | super(request); 21 | StringBuilder stringBuilder = new StringBuilder(); 22 | BufferedReader bufferedReader = null; 23 | InputStream inputStream = null; 24 | try { 25 | inputStream = request.getInputStream(); 26 | if (inputStream != null) { 27 | bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); 28 | char[] charBuffer = new char[128]; 29 | int bytesRead = -1; 30 | while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { 31 | stringBuilder.append(charBuffer, 0, bytesRead); 32 | } 33 | } else { 34 | stringBuilder.append(Constant.EMPTY); 35 | } 36 | } catch (IOException ex) { 37 | logger.error("获取请求body发生错误", ex); 38 | } finally { 39 | if (inputStream != null) { 40 | try { 41 | inputStream.close(); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | if (bufferedReader != null) { 47 | try { 48 | bufferedReader.close(); 49 | } catch (IOException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | } 54 | body = stringBuilder.toString(); 55 | } 56 | 57 | @Override 58 | public ServletInputStream getInputStream() throws IOException { 59 | final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); 60 | ServletInputStream servletInputStream = new ServletInputStream() { 61 | @Override 62 | public boolean isFinished() { 63 | return false; 64 | } 65 | 66 | @Override 67 | public boolean isReady() { 68 | return false; 69 | } 70 | 71 | @Override 72 | public void setReadListener(ReadListener readListener) { 73 | } 74 | 75 | @Override 76 | public int read() throws IOException { 77 | return byteArrayInputStream.read(); 78 | } 79 | }; 80 | return servletInputStream; 81 | 82 | } 83 | 84 | @Override 85 | public BufferedReader getReader() throws IOException { 86 | return new BufferedReader(new InputStreamReader(this.getInputStream())); 87 | } 88 | 89 | public String getBody() { 90 | return this.body; 91 | } 92 | 93 | 94 | } 95 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/service/impl/RunService.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.master.service.impl 2 | 3 | import com.funtester.base.exception.FailException 4 | import com.funtester.frame.SourceCode 5 | import com.funtester.master.common.basedata.NodeData 6 | import com.funtester.master.manaer.MasterManager 7 | import com.funtester.master.service.IRunService 8 | import com.funtester.slave.common.bean.run.* 9 | import org.springframework.stereotype.Service 10 | 11 | /** 12 | * 服务器执行类,以后可以Redis锁减少出错 13 | */ 14 | @Service 15 | class RunService implements IRunService { 16 | 17 | @Override 18 | void runRequest(ManyRequest request) { 19 | def host = NodeData.getRunHost(1) 20 | MasterManager.runMany(host, request) 21 | } 22 | 23 | @Override 24 | int runRequest(HttpRequest request) { 25 | def num = request.getMark() 26 | def hosts = NodeData.getRunHost(num) 27 | def mark = SourceCode.getMark() 28 | request.setMark(mark) 29 | try { 30 | hosts.each { 31 | def re = MasterManager.runRequest(it, request) 32 | if (!re) FailException.fail() 33 | NodeData.addTask(it, mark) 34 | } 35 | } catch (FailException e) { 36 | hosts.each {f -> MasterManager.stop(f)} 37 | FailException.fail("多节点执行失败!") 38 | } 39 | mark 40 | } 41 | 42 | @Override 43 | int runRequests(HttpRequests requests) { 44 | def mark = SourceCode.getMark() 45 | def num = requests.getMark() 46 | def hosts = NodeData.getRunHost(num) 47 | requests.setMark(mark) 48 | try { 49 | hosts.each { 50 | def re = MasterManager.runRequests(it, requests) 51 | if (!re) FailException.fail() 52 | NodeData.addTask(it, mark) 53 | } 54 | } catch (FailException e) { 55 | hosts.each {f -> MasterManager.stop(f)} 56 | FailException.fail("多节点执行失败!") 57 | } 58 | mark 59 | } 60 | 61 | @Override 62 | int runMethod(LocalMethod method) { 63 | def mark = SourceCode.getMark() 64 | def num = method.getMark() 65 | def hosts = NodeData.getRunHost(num) 66 | method.setMark(mark) 67 | try { 68 | hosts.each { 69 | def re = MasterManager.runMethod(it, method) 70 | if (!re) FailException.fail() 71 | NodeData.addTask(it, mark) 72 | } 73 | } catch (FailException e) { 74 | hosts.each {f -> MasterManager.stop(f)} 75 | FailException.fail("多节点执行失败!") 76 | } 77 | mark 78 | } 79 | 80 | @Override 81 | int runScript(GroovyScript script) { 82 | def mark = SourceCode.getMark() 83 | def num = script.getMark() 84 | def hosts = NodeData.getRunHost(num) 85 | script.setMark(mark) 86 | try { 87 | hosts.each { 88 | def re = MasterManager.runRequest(it, script) 89 | if (!re) FailException.fail() 90 | NodeData.addTask(it, mark) 91 | } 92 | } catch (FailException e) { 93 | hosts.each {f -> MasterManager.stop(f)} 94 | FailException.fail("多节点执行失败!") 95 | } 96 | mark 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/manaer/MasterManager.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.master.manaer 2 | 3 | import com.funtester.master.common.config.SlaveApi 4 | import com.funtester.slave.common.bean.run.* 5 | import org.apache.logging.log4j.LogManager 6 | import org.apache.logging.log4j.Logger 7 | 8 | /** 9 | * master节点HTTP接口功能管理类 10 | */ 11 | class MasterManager extends MasterHttp { 12 | 13 | private static final Logger logger = LogManager.getLogger(MasterManager.class); 14 | 15 | /** 16 | * 运行单请求用例 17 | * @param host 18 | * @param request 19 | * @return 20 | */ 21 | static boolean runRequest(String host, HttpRequest request) { 22 | String url = SlaveApi.RUN_REQUEST; 23 | def response = getPostResponse(host, url, request) 24 | isRight(response) 25 | } 26 | 27 | /** 28 | * 运行多请求用例 29 | * @param host 30 | * @param requests 31 | * @return 32 | */ 33 | static boolean runRequests(String host, HttpRequests requests) { 34 | String url = SlaveApi.RUN_REQUESTS; 35 | def response = getPostResponse(host, url, requests) 36 | isRight(response) 37 | } 38 | 39 | /** 40 | * 运行本地方法用例 41 | * @param host 42 | * @param method 43 | * @return 44 | */ 45 | static boolean runMethod(String host, LocalMethod method) { 46 | String url = SlaveApi.RUN_METHOD; 47 | def response = getPostResponse(host, url, method) 48 | isRight(response) 49 | } 50 | 51 | /** 52 | * 运行Groovy脚本,或者Java脚本 53 | * @param host 54 | * @param script 55 | * @return 56 | */ 57 | static boolean runScript(String host, GroovyScript script) { 58 | String url = SlaveApi.RUN_SCRIPT; 59 | def response = getPostResponse(host, url, script) 60 | isRight(response) 61 | } 62 | 63 | /** 64 | * 运行放大器 65 | * @param host 66 | * @param request 67 | * @return 68 | */ 69 | static boolean runMany(String host, ManyRequest request) { 70 | String url = SlaveApi.RUN_MANY; 71 | def response = getPostResponse(host, url, request) 72 | isRight(response) 73 | } 74 | 75 | /** 76 | * 获取节点运行状态,以备第二种节点使用 77 | * @param host 78 | * @return 79 | */ 80 | static boolean progress(String host) { 81 | String url = SlaveApi.PROGRESS; 82 | def response = getGetResponse(host, url) 83 | isRight(response) 84 | } 85 | 86 | /** 87 | * 结束节点运行 88 | * @param host 89 | * @return 90 | */ 91 | static boolean stop(String host) { 92 | String url = SlaveApi.STOP; 93 | def response = getPostResponse(host, url) 94 | isRight(response) 95 | } 96 | 97 | /** 98 | * 获取节点运行状态 99 | * @param host 100 | * @return 101 | */ 102 | static boolean status(String host) { 103 | String url = SlaveApi.STATUS; 104 | def response = getGetResponse(host, url, null) 105 | isRight(response) && response.getBoolean("data") 106 | } 107 | 108 | /** 109 | * 节点是否存活 110 | * @param host 111 | * @return 112 | */ 113 | static boolean alive(String host) { 114 | try { 115 | String url = SlaveApi.ALIVE; 116 | return isRight(getGetResponse(host, url, null)) 117 | } catch (Exception e) { 118 | logger.warn("节点: {}探活失败!", host) 119 | return false 120 | } 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/service/RunServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.service; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.funtester.base.bean.PerformanceResultBean; 5 | import com.funtester.base.exception.FailException; 6 | import com.funtester.config.Constant; 7 | import com.funtester.frame.execute.Concurrent; 8 | import com.funtester.frame.execute.ExecuteGroovy; 9 | import com.funtester.frame.execute.ExecuteSource; 10 | import com.funtester.frame.thread.RequestThreadTimes; 11 | import com.funtester.httpclient.FunLibrary; 12 | import com.funtester.httpclient.FunRequest; 13 | import com.funtester.slave.common.bean.run.*; 14 | import com.funtester.slave.manager.SlaveManager; 15 | import com.funtester.slave.service.impl.IRunService; 16 | import com.funtester.slave.template.ListRequestMode; 17 | import org.apache.http.client.methods.HttpRequestBase; 18 | import org.springframework.scheduling.annotation.Async; 19 | import org.springframework.stereotype.Service; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | @Service 25 | public class RunServiceImpl implements IRunService { 26 | 27 | @Async 28 | @Override 29 | public void runRequest(HttpRequest request) { 30 | BaseRequest r = request.getRequest(); 31 | HttpRequestBase re = FunRequest.initFromJson(r.toJson()).getRequest(); 32 | Integer times = request.getTimes(); 33 | String mode = request.getMode(); 34 | Integer thread = request.getThread(); 35 | Integer runup = request.getRunup(); 36 | String desc = request.getDesc(); 37 | if (mode.equalsIgnoreCase("ftt")) { 38 | Constant.RUNUP_TIME = runup; 39 | RequestThreadTimes task = new RequestThreadTimes(re, times); 40 | Concurrent concurrent = new Concurrent(task, thread, desc); 41 | PerformanceResultBean resultBean = concurrent.start(); 42 | SlaveManager.updateResult(resultBean, request.getMark()); 43 | } 44 | } 45 | 46 | @Async 47 | @Override 48 | public void runMany(ManyRequest request) { 49 | FunRequest funRequest = FunRequest.initFromJson(request.toJson()); 50 | HttpRequestBase requestBase = funRequest.getRequest(); 51 | Integer times = request.getTimes(); 52 | try { 53 | for (int i = 0; i < times; i++) { 54 | FunLibrary.executeSimlple(requestBase); 55 | } 56 | } catch (Exception e) { 57 | FailException.fail(e.getMessage()); 58 | } 59 | } 60 | 61 | @Async 62 | @Override 63 | public void runRequests(HttpRequests request) { 64 | List requests = request.getRequests(); 65 | List res = new ArrayList<>(); 66 | requests.forEach(f -> { 67 | res.add(FunRequest.initFromString(JSON.toJSONString(f)).getRequest()); 68 | }); 69 | Integer times = request.getTimes(); 70 | String mode = request.getMode(); 71 | Integer thread = request.getThread(); 72 | Integer runup = request.getRunup(); 73 | String desc = request.getDesc(); 74 | if (mode.equalsIgnoreCase("ftt")) { 75 | Constant.RUNUP_TIME = runup; 76 | ListRequestMode task = new ListRequestMode(res, times); 77 | Concurrent concurrent = new Concurrent(task, thread, desc); 78 | PerformanceResultBean resultBean = concurrent.start(); 79 | SlaveManager.updateResult(resultBean, request.getMark()); 80 | } 81 | } 82 | 83 | @Async 84 | @Override 85 | public void runMethod(LocalMethod method) { 86 | String methodName = method.getMethodName(); 87 | List params = method.getParams(); 88 | ExecuteSource.executeMethod(methodName, params.toArray()); 89 | } 90 | 91 | @Override 92 | public void runScript(GroovyScript script) { 93 | //此处留意,以后支持传参 94 | ExecuteGroovy.executeScript(script.getScript()); 95 | } 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/controller/Manager.groovy: -------------------------------------------------------------------------------- 1 | package com.funtester.master.controller 2 | 3 | import com.funtester.base.bean.PerformanceResultBean 4 | import com.funtester.base.bean.Result 5 | import com.funtester.base.exception.FailException 6 | import com.funtester.master.common.basedata.NodeData 7 | import com.funtester.master.common.bean.manager.RegisterBean 8 | import com.funtester.master.common.bean.manager.RunInfoBean 9 | import com.funtester.master.manaer.MasterManager 10 | import io.swagger.annotations.Api 11 | import io.swagger.annotations.ApiImplicitParam 12 | import io.swagger.annotations.ApiOperation 13 | import org.apache.logging.log4j.LogManager 14 | import org.apache.logging.log4j.Logger 15 | import org.springframework.web.bind.annotation.* 16 | 17 | import javax.validation.Valid 18 | 19 | @Api(tags = "管理类接口") 20 | @RestController 21 | @RequestMapping(value = "/m") 22 | class Manager { 23 | 24 | private static final Logger logger = LogManager.getLogger(Manager.class); 25 | 26 | @ApiOperation(value = "节点状态") 27 | @GetMapping(value = "/status") 28 | public Result status() { 29 | def nodes = [] 30 | NodeData.status.each { 31 | nodes << it 32 | } 33 | Result.success(nodes) 34 | } 35 | 36 | @ApiOperation(value = "可用的节点数,对外") 37 | @GetMapping(value = "/alive") 38 | public Result alive() { 39 | Result.success(NodeData.available().size()) 40 | } 41 | 42 | @ApiOperation(value = "获取测试结果") 43 | @ApiImplicitParam(name = "mark", value = "标记时间戳", dataTypeClass = Integer.class) 44 | @GetMapping(value = "/re/{mark}") 45 | public Result re(@PathVariable(value = "mark", required = true) int mark) { 46 | Result.success(NodeData.results.get(mark)) 47 | } 48 | 49 | @ApiOperation(value = "获取测试结果集") 50 | @GetMapping(value = "/res") 51 | public Result res() { 52 | Result.success(NodeData.results) 53 | } 54 | 55 | @ApiOperation(value = "获取运行信息") 56 | @GetMapping(value = "/info/{desc}") 57 | public Result info(@PathVariable(value = "desc", required = true) String desc) { 58 | Result.success(NodeData.getRunInfo(desc)) 59 | } 60 | 61 | @ApiOperation(value = "获取运行信息集") 62 | @GetMapping(value = "/infos") 63 | public Result infos() { 64 | Result.success(NodeData.runInfos) 65 | } 66 | 67 | @ApiOperation(value = "注册接口") 68 | @PostMapping(value = "/register") 69 | public Result register(@Valid @RequestBody RegisterBean bean) { 70 | def url = bean.getUrl() 71 | def stop = MasterManager.alive(url) 72 | if (!stop) FailException.fail("注册失败!") 73 | NodeData.register(url, false) 74 | Result.success() 75 | } 76 | 77 | @ApiOperation(value = "更新状态接口") 78 | @ApiImplicitParam(name = "bean", value = "注册接口请求对象", dataTypeClass = RegisterBean.class) 79 | @PostMapping(value = "/update") 80 | public Result update(@Valid @RequestBody RegisterBean bean) { 81 | if (!NodeData.status.containsKey(bean.getUrl())) return Result.fail(bean.getUrl() + "没有注册!") 82 | NodeData.register(bean.getUrl(), bean.getStatus()) 83 | Result.success() 84 | } 85 | 86 | @ApiOperation(value = "更新运行状态") 87 | @ApiImplicitParam(name = "bean", value = "注册接口请求对象", dataTypeClass = RegisterBean.class) 88 | @PostMapping(value = "/upinfo") 89 | public Result updateProgress(@Valid @RequestBody RunInfoBean bean) { 90 | NodeData.addRunInfo(bean) 91 | Result.success() 92 | } 93 | 94 | @ApiOperation(value = "更新测试结果") 95 | @ApiImplicitParam(name = "bean", value = "测试结果对象", dataTypeClass = PerformanceResultBean.class) 96 | @PostMapping(value = "/upresult/{mark}") 97 | public Result updateResult(@PathVariable(value = "mark") int mark, @RequestBody PerformanceResultBean bean) { 98 | NodeData.addResult(mark, bean) 99 | Result.success() 100 | } 101 | 102 | 103 | } 104 | -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/wapper/WrappingFilter.java: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common.wapper; 2 | 3 | 4 | import com.funtester.base.bean.Result; 5 | import com.funtester.config.Constant; 6 | import com.funtester.slave.common.basedata.DcsConstant; 7 | import com.funtester.utils.DecodeEncode; 8 | import com.funtester.utils.Time; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.apache.http.client.methods.HttpPost; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.stereotype.Component; 14 | 15 | import javax.servlet.*; 16 | import javax.servlet.annotation.WebFilter; 17 | import javax.servlet.http.HttpServletRequest; 18 | import javax.servlet.http.HttpServletResponse; 19 | import java.io.IOException; 20 | import java.lang.invoke.MethodHandles; 21 | 22 | 23 | @Component 24 | @WebFilter(urlPatterns = "/*", filterName = "wrappingFilter") 25 | public class WrappingFilter implements Filter { 26 | 27 | private static Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); 28 | 29 | public void init(FilterConfig config) throws ServletException { 30 | } 31 | 32 | public void destroy() { 33 | } 34 | 35 | @Override 36 | public void doFilter(ServletRequest request, ServletResponse response, 37 | FilterChain chain) throws IOException, ServletException { 38 | 39 | HttpServletRequest req = (HttpServletRequest) request; 40 | String ipAddress = getIpAddress(req); 41 | HttpServletResponse resp = (HttpServletResponse) response; 42 | ResponseWrapper responseWrapper = new ResponseWrapper(resp); 43 | RequestWrapper requestWrapper = new RequestWrapper(req); 44 | String url = requestWrapper.getRequestURI(); 45 | String queryArgs = requestWrapper.getQueryString(); 46 | queryArgs = queryArgs == null ? DecodeEncode.unicodeToString(requestWrapper.getBody()) : queryArgs; 47 | String headerKey = req.getHeader(DcsConstant.HEADER_KEY); 48 | String method = requestWrapper.getMethod(); 49 | if (method.equalsIgnoreCase(HttpPost.METHOD_NAME)) { 50 | if (StringUtils.isEmpty(headerKey) || !headerKey.equalsIgnoreCase(DcsConstant.HEADER_VALUE)){ 51 | response.getOutputStream().write(Result.fail("验证失败!").toString().getBytes()); 52 | return; 53 | } 54 | } 55 | if (url.equalsIgnoreCase("/test/ip")) { 56 | response.getOutputStream().write(Result.success(ipAddress).toString().getBytes()); 57 | return; 58 | } 59 | long start = Time.getTimeStamp(); 60 | chain.doFilter(requestWrapper == null ? request : requestWrapper, responseWrapper); 61 | long end = Time.getTimeStamp(); 62 | byte[] bytes = responseWrapper.getContent(); 63 | String respContent = new String(bytes, Constant.UTF_8); 64 | // if (!(url.startsWith("/ws") || url.contains("swagger"))) { 65 | // logger.info("请求:{},耗时:{} ms,参数:{},响应:{},来源:{}", url, end - start, queryArgs, respContent, ipAddress); 66 | // try { 67 | // Output.showStr(queryArgs); 68 | // Output.showStr(respContent); 69 | // } catch (Exception e) { 70 | // 71 | // } 72 | // } 73 | response.getOutputStream().write(respContent.getBytes(Constant.UTF_8)); 74 | } 75 | 76 | public static String getIpAddress(HttpServletRequest request) { 77 | String ip = request.getHeader("x-forwarded-for"); 78 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 79 | ip = request.getHeader("Proxy-Client-IP"); 80 | } 81 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 82 | ip = request.getHeader("WL-Proxy-Client-IP"); 83 | } 84 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 85 | ip = request.getHeader("HTTP_CLIENT_IP"); 86 | } 87 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 88 | ip = request.getHeader("HTTP_X_FORWARDED_FOR"); 89 | } 90 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 91 | ip = request.getRemoteAddr(); 92 | } 93 | return ip; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /slave/src/main/groovy/com/funtester/slave/common/wapper/WrappingFilter.java: -------------------------------------------------------------------------------- 1 | package com.funtester.slave.common.wapper; 2 | 3 | 4 | import com.funtester.base.bean.Result; 5 | import com.funtester.base.constaint.ThreadBase; 6 | import com.funtester.config.Constant; 7 | import com.funtester.slave.common.basedata.DcsConstant; 8 | import com.funtester.utils.DecodeEncode; 9 | import com.funtester.utils.Time; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.apache.http.client.methods.HttpPost; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.stereotype.Component; 15 | 16 | import javax.servlet.*; 17 | import javax.servlet.annotation.WebFilter; 18 | import javax.servlet.http.HttpServletRequest; 19 | import javax.servlet.http.HttpServletResponse; 20 | import java.io.IOException; 21 | import java.lang.invoke.MethodHandles; 22 | 23 | 24 | @Component 25 | @WebFilter(urlPatterns = "/*", filterName = "wrappingFilter") 26 | public class WrappingFilter implements Filter { 27 | 28 | private static Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); 29 | 30 | public void init(FilterConfig config) throws ServletException { 31 | } 32 | 33 | public void destroy() { 34 | } 35 | 36 | @Override 37 | public void doFilter(ServletRequest request, ServletResponse response, 38 | FilterChain chain) throws IOException, ServletException { 39 | 40 | HttpServletRequest req = (HttpServletRequest) request; 41 | String ipAddress = getIpAddress(req); 42 | HttpServletResponse resp = (HttpServletResponse) response; 43 | ResponseWrapper responseWrapper = new ResponseWrapper(resp); 44 | RequestWrapper requestWrapper = new RequestWrapper(req); 45 | String url = requestWrapper.getRequestURI(); 46 | String queryArgs = requestWrapper.getQueryString(); 47 | queryArgs = queryArgs == null ? DecodeEncode.unicodeToString(requestWrapper.getBody()) : queryArgs; 48 | 49 | String headerKey = req.getHeader(DcsConstant.HEADER_KEY); 50 | String method = requestWrapper.getMethod(); 51 | if (method.equalsIgnoreCase(HttpPost.METHOD_NAME)) { 52 | if (StringUtils.isEmpty(headerKey) || !headerKey.equalsIgnoreCase(DcsConstant.HEADER_VALUE)) { 53 | response.getOutputStream().write(Result.fail("验证失败!").toString().getBytes()); 54 | return; 55 | } 56 | if (url.startsWith("/run/")) { 57 | if (!ThreadBase.needAbort()) { 58 | response.getOutputStream().write(Result.fail("正在运行其他用例").toString().getBytes()); 59 | return; 60 | } 61 | } 62 | } 63 | long start = Time.getTimeStamp(); 64 | chain.doFilter(requestWrapper == null ? request : requestWrapper, responseWrapper); 65 | long end = Time.getTimeStamp(); 66 | byte[] bytes = responseWrapper.getContent(); 67 | String respContent = new String(bytes, Constant.UTF_8); 68 | // if (!(url.startsWith("/ws") || url.contains("swagger"))) { 69 | // logger.info("请求:{},耗时:{} ms,参数:{},响应:{},来源:{}", url, end - start, queryArgs, respContent, ipAddress); 70 | // try { 71 | // Output.showStr(queryArgs); 72 | // Output.showStr(respContent); 73 | // } catch (Exception e) { 74 | // 75 | // } 76 | // } 77 | response.getOutputStream().write(respContent.getBytes(Constant.UTF_8)); 78 | } 79 | 80 | public static String getIpAddress(HttpServletRequest request) { 81 | String ip = request.getHeader("x-forwarded-for"); 82 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 83 | ip = request.getHeader("Proxy-Client-IP"); 84 | } 85 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 86 | ip = request.getHeader("WL-Proxy-Client-IP"); 87 | } 88 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 89 | ip = request.getHeader("HTTP_CLIENT_IP"); 90 | } 91 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 92 | ip = request.getHeader("HTTP_X_FORWARDED_FOR"); 93 | } 94 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 95 | ip = request.getRemoteAddr(); 96 | } 97 | return ip; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | ## 这是DCS_FunTester测试框架项目 2 | 3 | 相关文章如下 4 | 5 | ## 性能测试 6 | 7 | - [Linux性能监控软件netdata中文汉化版](https://mp.weixin.qq.com/s/7VG7gHx7FUvsuNtBTJpjWA) 8 | - [性能测试框架](https://mp.weixin.qq.com/s/3_09j7-5ex35u30HQRyWug) 9 | - [性能测试框架第二版](https://mp.weixin.qq.com/s/JPyGQ2DRC6EVBmZkxAoVWA) 10 | - [性能测试框架第三版](https://mp.weixin.qq.com/s/Mk3PoH7oJX7baFmbeLtl_w) 11 | - [一个时间计数器timewatch辅助性能测试](https://mp.weixin.qq.com/s/-YZ04n2kyfO0q2QaKHX_0Q) 12 | - [如何在Linux命令行界面愉快进行性能测试](https://mp.weixin.qq.com/s/fwGqBe1SpA2V0lPfAOd04Q) 13 | - [Mac+httpclient高并发配置实例](https://mp.weixin.qq.com/s/r4a-vGz0pxeZBPPH3phujw) 14 | - [单点登录性能测试方案](https://mp.weixin.qq.com/s/sv8FnvIq44dFEq63LpOD2A) 15 | - [如何对消息队列做性能测试](https://mp.weixin.qq.com/s/MNt22aW3Op9VQ5OoMzPwBw) 16 | - [如何对修改密码接口进行压测](https://mp.weixin.qq.com/s/9CL_6-uZOlAh7oeo7NOpag) 17 | - [如何对单行多次update接口进行压测](https://mp.weixin.qq.com/s/Ly1Y4iPGgL6FNRsbOTv0sg) 18 | - [如何对多行单次update接口进行压测](https://mp.weixin.qq.com/s/Fsqw7vlw6K9EKa_XJwGIgQ) 19 | - [如何获取JVM堆转储文件](https://mp.weixin.qq.com/s/qCg7nsXVvT1q-9yquQOfWA) 20 | - [性能测试中标记每个请求](https://mp.weixin.qq.com/s/PokvzoLdVf_y9inlVXHJHQ) 21 | - [如何对N个接口按比例压测](https://mp.weixin.qq.com/s/GZxbH4GjDkk4BLqnUj1_kw) 22 | - [如何性能测试中进行业务验证](https://mp.weixin.qq.com/s/OEvRy1bS2Yq_w1kGiidmng) 23 | - [性能测试中记录每一个耗时请求](https://mp.weixin.qq.com/s/VXcp4uIMm8mRgqe8fVhuCQ) 24 | - [线程安全类在性能测试中应用](https://mp.weixin.qq.com/s/0-Y63wXqIugVC8RiKldHvg) 25 | - [利用微基准测试修正压测结果](https://mp.weixin.qq.com/s/dmO33qhOBrTByw_NshS-uA) 26 | - [性能测试如何减少本机误差](https://mp.weixin.qq.com/s/S6b_wwSowVolp1Uu6sEIOA) 27 | - [服务端性能优化之异步查询转同步](https://mp.weixin.qq.com/s/okYP2aOPfkWj2FjZcAtQNA) 28 | - [服务端性能优化之双重检查锁](https://mp.weixin.qq.com/s/-bOyHBcqFlJY3c0PEZaWgQ) 29 | - [多种登录方式定量性能测试方案](https://mp.weixin.qq.com/s/WuZ2h2rr0rNBgEvQVioacA) 30 | - [性能测试中图形化输出测试数据](https://mp.weixin.qq.com/s/EMvpYIsszdwBJFPIxztTvA) 31 | - [压测中测量异步写入接口的延迟](https://mp.weixin.qq.com/s/odvK1iYgg4eRVtOOPbq15w) 32 | - [手机号验证码登录性能测试](https://mp.weixin.qq.com/s/i-j8fJAdcsJ7v8XPOnPDAw) 33 | - [绑定手机号性能测试](https://mp.weixin.qq.com/s/K5x1t1dKtIT2NKV6k4v5mw) 34 | - [终止性能测试并输出报告](https://mp.weixin.qq.com/s/II4-UbKDikctmS_vRT-xLg) 35 | - [CountDownLatch类在性能测试中应用](https://mp.weixin.qq.com/s/uYBPPOjauR2h81l2uKMANQ) 36 | - [CyclicBarrier类在性能测试中应用](https://mp.weixin.qq.com/s/kvEHX3t_2xpMke9vwOdWrg) 37 | - [Phaser类在性能测试中应用](https://mp.weixin.qq.com/s/plxNnQq7yNQvHYEGpyY4uA) 38 | - [如何同时压测创建和删除接口](https://mp.weixin.qq.com/s/NCeoEF3DkEtpdaaQ365I0Q) 39 | - [固定QPS压测模式探索](https://mp.weixin.qq.com/s/S2h-zEUoik_CWs60RL6g7Q) 40 | - [固定QPS压测初试](https://mp.weixin.qq.com/s/ySlJmDIH3fFB4qEnL-ueMg) 41 | - [命令行如何执行jar包里面的方法](https://mp.weixin.qq.com/s/50oMEmVEnv5Vzlm1HOxuFw) 42 | - [链路压测中如何记录每一个耗时的请求](https://mp.weixin.qq.com/s/8sb5QZcKbBjTxCaXK5ajXA) 43 | - [Socket接口异步验证实践](https://mp.weixin.qq.com/s/bnjHK3ZmEzHm3y-xaSVkTw) 44 | - [性能测试中集合点和多阶段同步问题初探](https://mp.weixin.qq.com/s/NlpD1WyMrcG1V5RYfY0Plg) 45 | - [性能测试中标记请求参数实践](https://mp.weixin.qq.com/s/2FNMU-k_En26FCqWkYpvhQ) 46 | - [测试模型中理解压力测试和负载测试](https://mp.weixin.qq.com/s/smNLx3malzM3avkrn3EJiA) 47 | - [重放浏览器单个请求性能测试实践](https://mp.weixin.qq.com/s/a10hxCrIzS4TV9JwmDSI3Q) 48 | - [重放浏览器多个请求性能测试实践](https://mp.weixin.qq.com/s/Hm1Kpp1PMrZ5rYFW8l2GlA) 49 | - [重放浏览器请求多链路性能测试实践](https://mp.weixin.qq.com/s/9YSBLAyHVw8Z6IfK-nJTpQ) 50 | - [性能测试中异步展示测试进度](https://mp.weixin.qq.com/s/AOERJbEc4ATJqhjvnxgQoA) 51 | - [ThreadLocal在链路性能测试中实践](https://mp.weixin.qq.com/s/3qhNdHHSStELzNraQSpcew) 52 | - [Socket接口固定QPS性能测试实践](https://mp.weixin.qq.com/s/I9-14L8THxvtX1NJY0KPfw) 53 | - [单链路性能测试实践](https://mp.weixin.qq.com/s/4xHLP-GZwrNu5cFKdfsB6g) 54 | - [链路性能测试中参数多样性方法分享](https://mp.weixin.qq.com/s/I1pm0fulNrj_S-YkNz-gEA) 55 | - [链路测试中参数流转图](https://mp.weixin.qq.com/s/xyo9HXBLoXgLW6MSFH3V6w) 56 | - [线程同步类CyclicBarrier在性能测试集合点应用](https://mp.weixin.qq.com/s/K2YySxX9T4v_rzbvIbIHJA) 57 | - [链路压测中各接口性能统计](https://mp.weixin.qq.com/s/Deyop0mMpHrRWj9JTHzayw) 58 | - [性能测试框架中QPS取样器实现](https://mp.weixin.qq.com/s/4-5WhwwE1oRQ7cMUDv7J2w) 59 | - [链路压测中的支路问题初探](https://mp.weixin.qq.com/s/9iN9XndRPH4vIgc0-jVpUA) 60 | - [性能测试误差分析文字版-上](https://mp.weixin.qq.com/s/QA1VmNiJGZbNKTUNkt_05w) 61 | - [性能测试误差分析文字版-下](https://mp.weixin.qq.com/s/JwT9G910gn3YGyjeBlafgg) 62 | - [性能测试误差统计实践](https://mp.weixin.qq.com/s/D0KAn8Ch4vOqXA_-pWXWhg) 63 | - [性能测试误差对比研究(一)](https://mp.weixin.qq.com/s/BC49_DLNbdbFGcGDFFuh6Q) 64 | - [性能测试软启动初探](https://mp.weixin.qq.com/s/fk7LA7GtqR7wiKRAw2IYFA) 65 | - [性能测试误差对比研究(二)](https://mp.weixin.qq.com/s/8oq9rSyCgxAiQAYrhHUCkg) 66 | - [性能测试误差对比研究(三)](https://mp.weixin.qq.com/s/RWfQxlcKnOb3Q2mG5PoITg) 67 | - [性能测试误差对比研究(四)](https://mp.weixin.qq.com/s/z3vnBfWynZ0knITWmgzEow) 68 | - [分布式性能测试框架用例方案设想(一)](https://mp.weixin.qq.com/s/qvUD8kRkQa2bWm6Cvk2BoQ) 69 | - [基于docker的分布式性能测试框架功能验证(一)](https://mp.weixin.qq.com/s/7aa4HN82As3Zmf4DIVwv1A) 70 | - [高QPS下的固定QPS模型](https://mp.weixin.qq.com/s/95ivpkGT2lvYEQpvSCpxEQ) 71 | - [分布式性能测试框架用例方案设想(二)](https://mp.weixin.qq.com/s/bs65VVvRoB0AGyyfwSgT0w) 72 | - [基于docker的分布式性能测试框架功能验证(二)](https://mp.weixin.qq.com/s/49fAcamsteo7yVjO8JmQrg) 73 | - [分布式性能测试框架单节点内测](https://mp.weixin.qq.com/s/IwEQGC2rOgcT7deCWW4wDw) 74 | - [分段随机实践—模拟线上流量](https://mp.weixin.qq.com/s/qUPhtG5lZIWx6oYrgwEuag) 75 | - [性能测试框架对比初探](https://mp.weixin.qq.com/s/w46QciCvh6dPswBA42oiIQ) 76 | - [性能框架哪家强—JMeter、K6、locust、FunTester横向对比](https://mp.weixin.qq.com/s/BGs3ImdkRGFB-h3fxUktmw) 77 | 78 | 文章会有延迟,感兴趣联系作者加入内测群 -------------------------------------------------------------------------------- /master/src/main/groovy/com/funtester/master/common/basedata/NodeData.java: -------------------------------------------------------------------------------- 1 | package com.funtester.master.common.basedata; 2 | 3 | import com.funtester.base.bean.PerformanceResultBean; 4 | import com.funtester.base.exception.FailException; 5 | import com.funtester.frame.SourceCode; 6 | import com.funtester.master.common.bean.manager.RunInfoBean; 7 | import com.funtester.master.manaer.MasterManager; 8 | import org.apache.logging.log4j.LogManager; 9 | import org.apache.logging.log4j.Logger; 10 | 11 | import java.util.ArrayList; 12 | import java.util.LinkedHashMap; 13 | import java.util.List; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | 16 | public class NodeData { 17 | 18 | private static final Logger logger = LogManager.getLogger(NodeData.class); 19 | 20 | /** 21 | * 节点状态 22 | */ 23 | public static ConcurrentHashMap status = new ConcurrentHashMap<>(); 24 | 25 | /** 26 | * 节点的运行信息,通过progress获取 27 | */ 28 | public static ConcurrentHashMap runInfos = new ConcurrentHashMap<>(); 29 | 30 | /** 31 | * 节点的运行结果 32 | */ 33 | public static LinkedHashMap> results = new LinkedHashMap<>(); 34 | 35 | /** 36 | * 节点运行的任务id 37 | */ 38 | public static ConcurrentHashMap tasks = new ConcurrentHashMap<>(); 39 | 40 | /** 41 | * 注册更新 42 | * 43 | * @param host 44 | * @param s 45 | */ 46 | public static void register(String host, boolean s) { 47 | synchronized (status) { 48 | status.put(host, s); 49 | } 50 | logger.info("节点: {} 注册成功!", host); 51 | } 52 | 53 | /** 54 | * 可用节点 55 | * 56 | * @return 57 | */ 58 | public static List available() { 59 | synchronized (status) { 60 | List availables = new ArrayList<>(); 61 | status.forEach((k, v) -> { 62 | if (v) availables.add(k); 63 | }); 64 | return availables; 65 | } 66 | } 67 | 68 | /** 69 | * 检查,删除过期节点和过期数据,提供定时任务执行 70 | */ 71 | public static void check() { 72 | int timeStamp = SourceCode.getMark(); 73 | synchronized (results) { 74 | List tkeys = new ArrayList<>(); 75 | results.forEach((k, v) -> { 76 | if (timeStamp - k > 3 * 3600) { 77 | tkeys.add(k); 78 | } 79 | }); 80 | tkeys.forEach(f -> results.remove(f)); 81 | } 82 | synchronized (tasks) { 83 | tasks.forEach((k, v) -> { 84 | if (timeStamp - v > 60 * 30) tasks.put(k, 0); 85 | }); 86 | } 87 | } 88 | 89 | /** 90 | * 单独设置检查节点 91 | */ 92 | public static void checkNode() { 93 | List hkeys = new ArrayList<>(); 94 | synchronized (status) { 95 | status.entrySet().forEach(f -> { 96 | boolean alive = MasterManager.alive(f.getKey()); 97 | if (!alive) hkeys.add(f.getKey()); 98 | }); 99 | hkeys.forEach(f -> { 100 | status.remove(f); 101 | logger.warn("节点失效: {}", f); 102 | }); 103 | } 104 | synchronized (runInfos) { 105 | hkeys.forEach(f -> runInfos.remove(f)); 106 | } 107 | synchronized (tasks) { 108 | hkeys.forEach(f -> tasks.remove(f)); 109 | } 110 | } 111 | 112 | /** 113 | * 添加运行信息 114 | * 115 | * @param bean 116 | */ 117 | public static void addRunInfo(RunInfoBean bean) { 118 | synchronized (runInfos) { 119 | logger.info("更新成功 {} , {}",bean.getHost(),bean.getRuninfo()); 120 | runInfos.put(bean.getHost(), bean.getRuninfo()); 121 | } 122 | } 123 | 124 | /** 125 | * 获取描述的的用例任务运行信息 126 | * 127 | * @param desc 任务描述信息 128 | * @return 129 | */ 130 | public static List getRunInfo(String desc) { 131 | synchronized (runInfos) { 132 | ArrayList infos = new ArrayList<>(); 133 | runInfos.forEach((k, v) -> { 134 | if (v.contains(desc)) { 135 | infos.add(v); 136 | } 137 | }); 138 | return infos; 139 | } 140 | } 141 | 142 | /** 143 | * 添加运行信息 144 | * 145 | * @param bean 146 | */ 147 | public static void addResult(int mark, PerformanceResultBean bean) { 148 | synchronized (results) { 149 | results.computeIfAbsent(mark, f -> new ArrayList()); 150 | results.get(mark).add(bean); 151 | } 152 | } 153 | 154 | /** 155 | * 添加节点运行任务id 156 | * 157 | * @param host 158 | * @param mark 159 | */ 160 | public static void addTask(String host, Integer mark) { 161 | synchronized (tasks) { 162 | if (status.get(host) != null && status.get(host) == false) { 163 | tasks.put(host, mark); 164 | } 165 | } 166 | } 167 | 168 | public static List getRunHost(int num) { 169 | synchronized (status) { 170 | List available = available(); 171 | if (num < 1 || num > available.size()) 172 | FailException.fail("没有足够节点执行任务"); 173 | List nods = new ArrayList<>(); 174 | for (int i = 0; i < num; i++) { 175 | String random = SourceCode.random(available); 176 | status.put(random, false); 177 | nods.add(random); 178 | } 179 | return nods; 180 | } 181 | 182 | 183 | } 184 | 185 | } 186 | --------------------------------------------------------------------------------