自学内容网 自学内容网

【黑马点评】使用RabbitMQ实现消息队列——3.使用Jmeter压力测试,导入批量token,测试异步秒杀下单

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")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
    String phone = loginForm.getPhone();
    String code = loginForm.getCode();
    if(phone == null){
        return Result.fail("手机号为空!");
    }
    //        if(code == null){
//            return Result.fail("验证码为空!");
//        }
        return userService.login(loginForm, session);
    }

修改实现类中的检验方法

主要注释掉校验验证码这部分的逻辑

        // @TODO 先不校验验证码
/*        if(cacheCode == null || !cacheCode.equals(code)){
            //3.不一致,报错
            return Result.fail("验证码错误");
        }*/
        //注释掉以上部分

impl类中方法如下

    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1.校验手机号
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 2.如果不符合,返回错误信息
            return Result.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, new HashMap<>(), //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.返回token
        return Result.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文件中。

编写测试类(即脚本):

在这里插入图片描述

测试类内容如下:

package com.hmdp;

import com.hmdp.entity.User;
import com.hmdp.service.IUserService;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;

import org.apache.http.util.EntityUtils;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.BufferedWriter;
import java.io.FileWriter;

import java.util.List;
@SpringBootTest
public class UserLoginBatch {

    @Autowired
    private IUserService userService;

    @Test
    public void function(){
        String loginUrl = "http://localhost:8080/api/user/login"; // 替换为实际的登录URL
        String tokenFilePath = "tokens.txt"; // 存储Token的文件路径

        try {
            HttpClient httpClient = HttpClients.createDefault();

            BufferedWriter writer = new BufferedWriter(new FileWriter(tokenFilePath));

            // 从数据库中获取用户手机号
            List<User> users = userService.list();

            for(User user : users) {
                String phoneNumber = user.getPhone();

                // 构建登录请求
                HttpPost httpPost = new HttpPost(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 = new JSONObject();
                jsonRequest.put("phone", phoneNumber);
                StringEntity requestEntity = new StringEntity(
                        jsonRequest.toString(),
                        ContentType.APPLICATION_JSON);
                httpPost.setEntity(requestEntity);

                // 发送登录请求
                HttpResponse response = httpClient.execute(httpPost);

                // 处理登录响应,获取token
                if (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的方法,这里只是示例,具体实现需要根据实际响应格式进行解析
    private static String parseTokenFromJson(String json) {
        try {
            // 将JSON字符串转换为JSONObject
            JSONObject jsonObject = new JSONObject(json);
            // 从JSONObject中获取名为"token"的字段的值
            String token = jsonObject.getString("data");
            return token;
        } catch (Exception e) {
            e.printStackTrace();
            return null; // 解析失败,返回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 将用户登录逻辑修改回去

取消验证码检测的注释。在这里插入图片描述


原文地址:https://blog.csdn.net/m0_52031708/article/details/142746795

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!