3 批量获取用户token,使用jmeter压力测试
3 批量获取用户token,使用jmeter压力测试
3.1 需求
优惠券秒杀的接口,需要模拟1000个不同登录用户下的秒杀场景,测试这个接口的性能。
分析
1.如何模拟这1000个用户?
我们可以使用for循环在数据库中批量添加这1000个用户,然后需要对这1000个用户进行登录以获取这1000个用户的token,以便在jmeter发起的请求头中携带这1000个token模拟1000个用户。
2.如何批量获取token?
编写脚本发起1000个登录请求,并将响应的token写入txt文件中。
3.2 实现
3.2.1 环境配置
1.修改pom.xml文件,导入apache包
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.6</version></dependency>
3.2.2 修改登录接口UserController和实现类
1.登录接口:修改UserController.java中的login方法为以下内容
本项目使用的是手机号和验证码登录方式,这两个参数携带在请求体(requestbody)中,而不是请求参数中(url路径中),如果根据手机号登录,需要将验证验证码的代码注释掉(即注释掉验证逻辑),以便直接根据手机号登录而无需验证。
/**
* 登录功能
* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
*/@PostMapping("/login")publicResultlogin(@RequestBodyLoginFormDTO loginForm,HttpSession session){String phone = loginForm.getPhone();String code = loginForm.getCode();if(phone ==null){returnResult.fail("手机号为空!");}// if(code == null){// return Result.fail("验证码为空!");// }return userService.login(loginForm, session);}
修改实现类中的检验方法
主要注释掉校验验证码这部分的逻辑
// @TODO 先不校验验证码/* if(cacheCode == null || !cacheCode.equals(code)){
//3.不一致,报错
return Result.fail("验证码错误");
}*///注释掉以上部分
impl类中方法如下
@OverridepublicResultlogin(LoginFormDTO loginForm,HttpSession session){// 1.校验手机号String phone = loginForm.getPhone();if(RegexUtils.isPhoneInvalid(phone)){// 2.如果不符合,返回错误信息returnResult.fail("手机号格式错误!");}// // 3.校验验证码// Object cacheCode = session.getAttribute("code");// 3.从redis获取验证码并校验String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();// @TODO 先不校验验证码/* if(cacheCode == null || !cacheCode.equals(code)){
//3.不一致,报错
return Result.fail("验证码错误");
}*///注释掉以上部分//一致,根据手机号查询用户User user =query().eq("phone", phone).one();//5.判断用户是否存在if(user ==null){//不存在,则创建
user =createUserWithPhone(phone);}// 7.保存用户信息到 redis中// 7.1.随机生成token,作为登录令牌String token = UUID.randomUUID().toString(true);// 7.2.将User对象转为HashMap存储UserDTO userDTO =BeanUtil.copyProperties(user,UserDTO.class);Map<String,Object> userMap =BeanUtil.beanToMap(userDTO,newHashMap<>(),//beanToMap方法执行了对象到Map的转换CopyOptions.create().setIgnoreNullValue(true)//BeanUtil在转换过程中忽略所有null值的属性.setFieldValueEditor((fieldName, fieldValue)-> fieldValue.toString()));//对于每个字段值,它简单地调用toString()方法,将字段值转换为字符串。// 7.3.存储String tokenKey = LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.设置token有效期
stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL,TimeUnit.MINUTES);// 8.返回tokenreturnResult.ok(token);}
3.2.3 测试类
2.登录流程:
用户登录成功后,服务端会将token作为data数据返回给客户端,并将token存储到Redis中。之后客户端将token添加到请求头Authorization中,每次发起请求都需要携带该请求头,后端拦截器会根据请求头进行用户身份验证。
3.响应格式:
用户登录成功服务端响应格式
{“success”:true,“data”:“301130fd-7e25-4c93-8a79-9eb7d54c6fed”}//响应体
批量获取token脚本(Java)
思路:使用userService从数据库中获取用户集合(这里使用的是Mybatis-plus),遍历集合中的每个用户,将用户的手机号添加到请求体中,使用Java的Http客户端发起请求。之后从json响应体中获取token并写入txt文件中。
编写测试类(即脚本):
测试类内容如下:
packagecom.hmdp;importcom.hmdp.entity.User;importcom.hmdp.service.IUserService;importorg.apache.http.HttpEntity;importorg.apache.http.HttpResponse;importorg.apache.http.client.HttpClient;importorg.apache.http.client.methods.HttpPost;importorg.apache.http.entity.ContentType;importorg.apache.http.entity.StringEntity;importorg.apache.http.impl.client.HttpClients;importorg.apache.http.util.EntityUtils;importorg.json.JSONObject;importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importjava.io.BufferedWriter;importjava.io.FileWriter;importjava.util.List;@SpringBootTestpublicclassUserLoginBatch{@AutowiredprivateIUserService userService;@Testpublicvoidfunction(){String loginUrl ="http://localhost:8080/api/user/login";// 替换为实际的登录URLString tokenFilePath ="tokens.txt";// 存储Token的文件路径try{HttpClient httpClient =HttpClients.createDefault();BufferedWriter writer =newBufferedWriter(newFileWriter(tokenFilePath));// 从数据库中获取用户手机号List<User> users = userService.list();for(User user : users){String phoneNumber = user.getPhone();// 构建登录请求HttpPost httpPost =newHttpPost(loginUrl);//(1.如果作为请求参数传递)//List<NameValuePair> params = new ArrayList<>();//params.add(new BasicNameValuePair("phone", phoneNumber));// 如果登录需要提供密码,也可以添加密码参数// params.add(new BasicNameValuePair("password", "user_password"));//httpPost.setEntity(new UrlEncodedFormEntity(params));// (2.如果作为请求体传递)构建请求体JSON对象JSONObject jsonRequest =newJSONObject();
jsonRequest.put("phone", phoneNumber);StringEntity requestEntity =newStringEntity(
jsonRequest.toString(),ContentType.APPLICATION_JSON);
httpPost.setEntity(requestEntity);// 发送登录请求HttpResponse response = httpClient.execute(httpPost);// 处理登录响应,获取tokenif(response.getStatusLine().getStatusCode()==200){HttpEntity entity = response.getEntity();String responseString =EntityUtils.toString(entity);System.out.println(responseString);// 解析响应,获取token,这里假设响应是JSON格式的// 根据实际情况使用合适的JSON库进行解析String token =parseTokenFromJson(responseString);System.out.println("手机号 "+ phoneNumber +" 登录成功,Token: "+ token);// 将token写入txt文件
writer.write(token);
writer.newLine();}else{System.out.println("手机号 "+ phoneNumber +" 登录失败");}}
writer.close();}catch(Exception e){
e.printStackTrace();}}// 解析JSON响应获取token的方法,这里只是示例,具体实现需要根据实际响应格式进行解析privatestaticStringparseTokenFromJson(String json){try{// 将JSON字符串转换为JSONObjectJSONObject jsonObject =newJSONObject(json);// 从JSONObject中获取名为"token"的字段的值String token = jsonObject.getString("data");return token;}catch(Exception e){
e.printStackTrace();returnnull;// 解析失败,返回null或者抛出异常,具体根据实际需求处理}}}
先运行HmDianPingApplication,之后运行测试类可得到存储了1000个用户token的txt文件
3.3 使用jmeter进行测试
1.设置线程数为1000
2.导入tokens文件
3.设置HTTP信息头管理器
4.设置HTTP请求
注意,Path为/api/voucher-order/seckill/13
13记得修改为自己的秒杀商品id
5.运行并得到结果
运行前,13的商品库存为100
3.4 测试结果
Redis和数据库中库存减为0
订单数量为100
3.5 将用户登录逻辑修改回去
取消验证码检测的注释。
版权归原作者 -$_$- 所有, 如有侵权,请联系我们删除。