springboot+mybatis+mysql
String newPwd = DigestUtils.md5DigestAsHex((UserBaseStr.md5Hex_sign + TestUser.getUserName() + TestUser.getPassword()).getBytes());
TestUser.setPassword(newPwd);
1,将用户录入的用户名密码,M5加密
2,根据用户名+加密后的密码,去数据库查询是否存在
3,不存在,给出提示
4,存在,生成token,并将token存入TokenDto,再将TokenDto存入tokenDb
5,接口返回 token
String tokenStr = DigestUtils.md5DigestAsHex((System.currentTimeMillis() + userDto.getName() + userDto.getPwd()).getBytes());
TokenDto tokenDto = new TokenDto();
tokenDto.setUserId(resultTestUser.getId());
tokenDto.setUserName(resultTestUser.getUserName());
tokenDto.setDefaultJenkinsId(resultTestUser.getDefaultJenkinsId());
tokenDto.setToken(tokenStr);
tokenDb.addUserInfo(tokenStr,tokenDto);
AitestToken aitestToken = new AitestToken();
aitestToken.setToken(tokenStr);
return ResultDto.success("成功",aitestToken);
1,从请求的Header获取客户端附加token
2,从tokenDb中取出tokenDto
3,返回tokenDto
String tokenStr = request.getHeader(UserBaseStr.LOGIN_TOKEN);
TokenDto tokenDto = tokenDb.getUserInfo(tokenStr);
return ResultDto.success("成功", tokenDto);
1,判断是否登录,未登录不用登出
2,已登录,则从tokenDb删除tokenDto
1,从请求的Header中获取客户端附加的token
2,判断如果是登录或注册接口,直接放行
3,如果请求中没有token,抛出异常“客户端没有传token”
4,如果请求中有token,根据token去tokenDb中查询是否已登录
5,查询成功,则放行;查询失败,则抛出异常用户未登录
@Component
@Slf4j
public class DemoInterceptor implements HandlerInterceptor {
@Autowired
private TokenDb tokenDb;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.info("=== preHandle ===");
log.info("=== request.getRequestURI() ===" + request.getRequestURI());
//1、从请求的Header中获取客户端附加的token
String token = request.getHeader("token");
String uri=request.getRequestURI();
if ("/TestUser/#".equals(uri)||"/TestUser/register".equals(uri) ){
return true;
}
//2、如果请求中无token,响应码设401,抛出业务异常:客户端没有传token
if (Objects.isNull(token)||token==""){
ServiceException.throwEx("客户端未传token");
}
//3、从token中根据token查询TokenDto
boolean loginFlag = tokenDb.isLogin(token);
//如果为空,则响应码设置401,抛出业务异常
//用户未登录
if (!loginFlag){
ServiceException.throwEx("用户未登录");
}
//否则,则允许通过
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
log.info("=== postHandle ===");
log.info("=== request.getRequestURI() ===" + request.getRequestURI());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
log.info("=== afterCompletion ===");
log.info("=== request.getRequestURI() ===" + request.getRequestURI());
}
}
拦截器设置
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Autowired
private DemoInterceptor demoInterceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(demoInterceptor).addPathPatterns("/**");
}
}
1,文本类型用例,调用/text接口,传入参数@RequestBody为 用例名称(必填)、测试数据(必填)、备注
2,文件类型用例,调用/file接口,传入参数@RequestParam为 测试文件(必填),用例名称,备注,测试数据不传值
1,将传入的参数 addTestCaseDto,复制到TestCase entity类;
2,header中取出token,从 tokenDb 中查出 userId
3,传入userId
4,setCreateTime、setUpdateTime
5,设置删除标志 setDelFlag 为 1(未删除)
注:其他对象存储过程到数据库的过程,也类似
1,调用/{caseId}接口,传入参数 @PathVariable 为 用例id
2,header中取出token,从 tokenDb 中查出 userId
3,TestCase entity 中设置 caseId、createUserId,设置删除标志位1
4,利用TestCaseMapper类查找数据库,是否存在 ”caseId、createUserId,删除标志位1“ 都相符的数据
5,没有查询到则抛出异常”未查到测试用例信息“
6,查询到了就 setDelFlag 标志位置为0,并删除该条记录
1,传入参数任务名称、任务备注、运行测试的Jenkins服务器id、创建者id(客户端传值无效,以token数据为准)、测试用例id(List)
2,校验传参:
(1)测试用例 id list 非空 (2)若传入的任务名称为空,则设置默认名称
4,从tokenDto中取出 UserId、DefaultJenkinsId
3,根据 DefaultJenkinsId 查询 Jenkisn 信息,并做非空校验
4,根据用户选择的测试用例id列表,查询测试用例信息列表
5,生成测试命令: 根据 Jenkins 信息、测试用例信息(可能不止一个)生成测试命令
- 从 TestJenkins(entity)对象中,取出commandRunCaseType
- 从 TestJenkins(entity)对象中,取出测试命令
- 从 TestCase(entity)对象中,取出测试数据
(1)若commandRunCaseType为1,则为文本类型,拼接后形如
mvn test Class1
mvn test Class2
mvn test Class3
(2)若commandRunCaseType为1,则为文件类型
- 先拼接下载文件的curl命令
- 再拼接命令+用例名称
拼接后形如
curl -o xxx.yml ${BaseUrl}/testCase/data/${id} -H \"token: ${token}\" || true\n\n
hrun --alluredir=${WORKSPACE}/target/allure-results xxx.yml || true \n\n
curl -o xxx.yml ${BaseUrl}/testCase/data/${id} -H \"token: ${token}\" || true\n\n
hrun --alluredir=${WORKSPACE}/target/allure-results xxx.yml || true \n\n
curl -o xxx.yml ${BaseUrl}/testCase/data/${id} -H \"token: ${token}\" || true\n\n
hrun --alluredir=${WORKSPACE}/target/allure-results xxx.yml || true \n\n
6,落库测试任务表
7,落库任务-用例关联表
1,传入参数 任务id、命令脚本
3,header 中获取 token ,利用tokenDto getUserId、getDefaultJenkinsId、getTestCommand、getTaskId ,可以拿到任务信息、测试命令等数据。
4,根据请求request,拿到 url
6,更新任务状态为执行中
7,以从数据库查询的数据为基础数据,组装调用Jenkins的参数
OperateJenkinsJobDto operateJenkinsJobDto = new OperateJenkinsJobDto();
operateJenkinsJobDto.setTokenDto(tokenDto);
operateJenkinsJobDto.setTestJenkins(resultTestJenkins);
operateJenkinsJobDto.setParams(map);
operateJenkinsJobDto.setTestUser(resultTestUser);
ResultDto<TestUser> resultDto = JenkinsUtil.build2(operateJenkinsJobDto);
8,获取Jenkins Job对应的配置文件
ClassPathResource classPathResource = new ClassPathResource("JenkinsConfigDir/test_mini_test_start.xml");
InputStream inputStream = classPathResource.getInputStream();
String jobConfigXml = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
9,通过Jenkins客户端连接Jenkins
String baseUrl = testJenkins.getUrl();
String userName = testJenkins.getUserName();
String password = testJenkins.getPassword();
String jobName = "test_mini_test_start_" + testUser.getId();
JenkinsHttpClient jenkinsHttpClient = new JenkinsHttpClient(new URI(baseUrl), userName, password);
10, 通过Jenkins客户端获取JenkinsServer对象
JenkinsServer jenkinsServer = new JenkinsServer(jenkinsHttpClient);
11, 通过JenkinsServer对象创建或者更新Jenkins Job
if (StringUtils.isEmpty(testUser.getStartTestJobName())) {
jenkinsServer.createJob(jobName, jobConfigXml, true);
testUser.setStartTestJobName(jobName);
} else {
jenkinsServer.updateJob(jobName, jobConfigXml, true);
}
12, 根据Job名称获取对应的Job
Map<String, Job> jobMap = jenkinsServer.getJobs();
Job job = jobMap.get(jobName);
13,通过Jenkins Job对象构建job
job.build(params, true);
14,数据返回给前端
注意,上面的执行过程,Jenkins 完成 job 构建后,还有1个重要的功能点:
-
需要设置完成状态为已完成
在Jenkins job中调用更改status接口
curl -X PUT "http://localhost:8081/task/status " -H "Content-Type: application/json " -H "token: acab4db17fe7f898c9f5dd2f726a7f3c" -d '{"buildUrl":"${BUILD_URL}","taskId":80,"status":3}'
TestJenkins entity 的 defaultJenkinsFlag 字段不存入数据库
是否设置为默认服务器 1 是 0 否
添加 Jenkisn 时,如果是否为默认Jenkins的标志位为1,则修改test_user表中的default_jenkins_id为当前 JenkinsId
resultTestUser.setDefaultJenkinsId(testJenkins.getId());
同时,记得修改tokenDto中的default_jenkins_id
tokenDto.setDefaultJenkinsId(testJenkins.getId());
任务已完成状态,需要取到报告
/allureReport/{taskId} 接口获取
主要逻辑:
public static String getAllureReportUrl(String buildUrl,TestJenkins testJenkins,boolean autoLoginJenkinsFlag) {
if (StringUtils.isEmpty(buildUrl) || !buildUrl.contains("/job")) {
return buildUrl;
}
String allureReportUrl = buildUrl;
if (autoLoginJenkinsFlag) {
allureReportUrl = getAllureReportUrlAndLogin(buildUrl, testJenkins);
}
return allureReportUrl + "allure/";
}
查询语句
<select id="getCaseCountByTask" resultType="TestTask">
select * FROM test_task where create_user_id = #{createUserId} ORDER BY id
<if test="start != null and end != null ">
limit #{start}, #{end}
</if>
</select>
@ApiModel(value = "基础返回类", description = "基础返回类")
public class ResultDto<T> implements Serializable {
private static final long serialVersionUID = -7472879865481412372L;
@ApiModelProperty(value = "返回结果码 1成功 0失败", required = true, example = "1", allowableValues = "1,0")
private Integer resultCode;
@ApiModelProperty(value = "提示信息", example = "成功", allowableValues = "成功,失败")
private String message = "";
@ApiModelProperty(value = "响应结果数据")
private T data = null;
public Integer getResultCode() {
return resultCode;
}
public static ResultDto newInstance() {
return new ResultDto();
}
/**
* 设置成功
*/
public void setAsSuccess() {
this.resultCode = 1;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public static ResultDto success(String message) {
ResultDto resultDto = new ResultDto();
resultDto.setAsSuccess();
resultDto.setMessage(message);
return resultDto;
}
public static <T> ResultDto<T> success(String message, T data) {
ResultDto<T> resultDto = new ResultDto<>();
resultDto.setAsSuccess();
resultDto.setMessage(message);
resultDto.setData(data);
return resultDto;
}
/**
* 设置失败
*/
public void setAsFailure() {
this.resultCode = 0;
}
public static <T> ResultDto<T> fail(String message) {
ResultDto<T> resultDto = new ResultDto<>();
resultDto.setAsFailure();
resultDto.setMessage(message);
return resultDto;
}
public static <T> ResultDto<T> fail(String message, T data) {
ResultDto<T> resultDto = new ResultDto<>();
resultDto.setAsFailure();
resultDto.setMessage(message);
resultDto.setData(data);
return resultDto;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static ResultDto main(String[] args) {
return ResultDto.fail("密码不对", new HashMap<>());
}
}
比如,添加用例-用例名称不能为空
此时,用统一大对象处理
if (StringUtils.isEmpty(addTestCaseDto.getCaseName())) {
return ResultDto.fail("测试用例名称不能为空");
}
if (Objects.isNull(testJenkins)) {
ServiceException.throwEx("组装测试命令时,Jenkins信息为空");
}
if (Objects.isNull(testCaseList) || testCaseList.size() == 0) {
throw new ServiceException("组装测试命令时,测试用例列表信息为空");
}
分为业务异常(自定义ServiceException)、非业务异常(Exception)、其他异常(Throwable,如Error)
- 打印出日志信息
- 业务异常返回401错误,非业务异常返回502错误
@Slf4j
@ResponseBody
@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler({ServiceException.class})
public ResultDto serviceExceptionHandler(ServiceException se){
log.error(se.getMessage());
return resultFormat(se) ;
}
@ResponseStatus(HttpStatus.BAD_GATEWAY)
@ExceptionHandler({Exception.class})
public ResultDto exceptionHandler(Exception e){
log.error(e.getMessage());
return resultFormat(e);
}
@ExceptionHandler({Throwable.class})
public ResultDto throwableHandler(Throwable t) {
log.error(t.getMessage());
return ResultDto.fail("系统错误 系统繁忙,请稍后重试");
}
public ResultDto resultFormat(Throwable t){
String tips="系统繁忙,请稍后重试";
if(t instanceof ServiceException){
return ResultDto.fail( "业务异常 "+t.getMessage());
}
if(t instanceof Exception){
return ResultDto.fail("非业务异常 "+t.getMessage());
}
return ResultDto.fail(tips);
}
}
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter(){
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.setMaxAge(18000L);
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
config.addAllowedMethod("GET");
source.registerCorsConfiguration("/**",config);
return new CorsFilter(source);
}
}
查询语句
<select id="count" resultType="int">
select count(1) from test_task htt where htt.create_user_id = #{params.createUserId}
<if test="params.name != null and params.name != '' ">
and htt.name = #{params.name}
</if>
</select>
<select id="list" resultType="TestTask">
select * from test_task htt where htt.create_user_id = #{params.createUserId}
<if test="params.name != null and params.name != '' ">
and htt.name = #{params.name}
</if>
order by htt.update_time desc
limit #{pageNum}, #{pageSize}
</select>
入参Dto
@ApiModel(value = "列表查询的分贝参数", description = "请求参数类")
@Data
public class PageTableRequest<Dto> implements Serializable {
private static final long serialVersionUID = 7328071045193618467L;
@ApiModelProperty(value = "页码", required = true, example = "1")
private Integer pageNum = 1;
@ApiModelProperty(value = "每页数据量", required = true, example = "10")
private Integer pageSize = 10;
@ApiModelProperty(value = "特定查询参数", required = true, example = "status=1")
private Map params;
}
出参Dto
@Data
public class PageTableResponse<T> implements Serializable {
private static final long serialVersionUID = -7472879865481412372L;
//返回的数据量
private Integer recordsTotal;
//返回的数据列表
private List<T> data;
}
参数解析
public class PageTableArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> cla = parameter.getParameterType();
return cla.isAssignableFrom(PageTableRequest.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Map<String, String[]> param = request.getParameterMap();
PageTableRequest tableRequest = new PageTableRequest();
if (param.containsKey("pageNum")) {
tableRequest.setPageNum(Integer.parseInt(request.getParameter("pageNum")));
}
if (param.containsKey("pageSize")) {
tableRequest.setPageSize(Integer.parseInt(request.getParameter("pageSize")));
}
Map<String,Object> map = new HashMap<>();
tableRequest.setParams(map);
param.forEach((k, v) -> {
if (v.length == 1) {
map.put(k, v[0]);
} else {
map.put(k, Arrays.asList(v));
}
});
return tableRequest;
}
}
1,insertUseGeneratedKeys:插入数据,限制为实体包含id
属性并且必须为自增列,实体配置的主键策略无效
2,insert:保存一个实体,null的属性也会保存,不会使用数据库默认值
3,insertList:批量插入,支持批量插入的数据库可以使用,例如MySQL,H2等,另外该接口限制实体包含id
属性并且必须为自增列
1,updateByPrimaryKey:根据主键更新实体全部字段,null值会被更新
2,updateByPrimaryKeySelective:根据主键更新属性不为null的值
1,selectOne:根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号
2,selectByIds:根据主键字符串进行查询,类中只有存在一个带有@Id注解的字段
3,select:根据实体中的属性值进行查询,查询条件使用等号
1,deleteByPrimaryKey:根据主键字段进行删除,方法参数必须包含完整的主键属性
2,delete:根据实体属性作为条件进行删除,查询条件使用等号