You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Gatling场景中仅执行一次登录并复用认证令牌?

问题原因排查与解决方案

一、csrfToken多次生成的原因

  1. 全局变量线程不安全:你定义的csrfToken是类级别的全局变量,Gatling多虚拟用户并发运行时会共享该变量,导致值被频繁覆盖;同时当前场景每个用户都会完整执行「获取csrf→登录」流程,自然每个用户都会生成独立的csrfToken,不符合“仅一次登录复用令牌”的需求。
  2. 未实现全局复用登录会话:现有逻辑没有提前完成单次登录并共享会话,而是让每个用户重复执行登录步骤,必然导致csrfToken多次生成。

二、listingID为空的原因

  1. 房源未实际创建:当前流程仅调用GET /addProperty进入添加页面,没有提交创建房源的POST请求,页面不会生成对应房源的编辑链接,自然无法捕获listingID。
  2. 正则匹配失效:即使页面存在编辑链接,正则表达式<a class="" href="https://dev.com/update/(\\d+)">可能与实际HTML结构不匹配(比如class属性值、链接格式有差异),导致无法提取ID。
  3. 响应未验证:未检查GET /addProperty的响应内容,无法确认目标链接是否存在。

三、解决方案

1. 实现单登录全局复用会话

通过Gatling的before钩子提前执行一次登录,保存会话信息(Cookie、csrfToken等),所有虚拟用户直接复用该会话:

  • 在Simulation类中添加before方法,单独执行登录流程并存储会话。
  • 后续虚拟用户启动时直接继承该会话的所有数据,避免重复登录。

2. 修复listingID为空问题

  • 补充房源创建步骤:添加提交房源信息的POST请求,完成实际创建操作,才能生成对应的listingID。
  • 优化ID提取方式:从创建成功后的跳转响应头(如Location)提取ID,比从HTML页面匹配更可靠;若需从页面提取,改用CSS选择器替代正则(稳定性更高)。
  • 添加响应检查:保存页面响应内容,用于排查匹配失败的原因。

3. 移除不安全的全局变量

放弃类级别的csrfToken变量,改用Gatling Session存储变量,避免并发冲突。

四、修正后的代码示例

public class PerfCopy extends Simulation {
    // 全局存储登录后的会话信息
    private Session sharedSession;

    @Override
    public void before() {
        // 提前执行单次登录,获取共享会话
        HttpProtocolBuilder httpProtocol = http
                .baseUrl("https://dev.com/")
                .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
                .acceptLanguageHeader("en-US,en;q=0.5")
                .acceptEncodingHeader("gzip, deflate, br")
                .userAgentHeader("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0");

        ScenarioBuilder loginScn = scenario("PreLogin")
                .exec(http("CSRF_TOKEN")
                        .get("/login")
                        .check(status().is(200))
                        .check(regex("<meta name=\"csrf-token\" content=\"(.*?)\"").saveAs("csrfToken")))
                .exec(http("Login")
                        .post("/login")
                        .headers(Map.of("Content-Type", "application/x-www-form-urlencoded"))
                        .formParam("_token", "#{csrfToken}")
                        .formParam("email", "email@email.com")
                        .formParam("password", "test@1234")
                        .check(status().is(200)));

        // 执行登录场景并保存会话
        sharedSession = loginScn.injectOpen(atOnceUsers(1)).protocols(httpProtocol).awaitResult();
    }

    public PerfCopy() {
        HttpProtocolBuilder httpProtocol = http
                .baseUrl("https://dev.com/")
                .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
                .acceptLanguageHeader("en-US,en;q=0.5")
                .acceptEncodingHeader("gzip, deflate, br")
                .userAgentHeader("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0");

        ScenarioBuilder scn = scenario("MainScenario")
                // 复用提前登录的会话数据
                .exec(session -> session.setAll(sharedSession.getAll()))
                // 提交房源创建请求(需根据实际接口调整参数)
                .exec(http("POST Add Listing")
                        .post("/addProperty")
                        .headers(Map.of("Content-Type", "application/x-www-form-urlencoded"))
                        .formParam("_token", "#{csrfToken}")
                        .formParam("listing_owner", "152")
                        .formParam("address-autocomplete-street-number", "123")
                        .formParam("address-autocomplete-street-name", "Main St")
                        .formParam("choose_mls[]", "Georgia")
                        // 其他必填房源参数
                        .check(status().is(302)) // 创建成功通常会跳转
                        .check(header("Location").saveAs("listingUrl")))
                // 从跳转链接提取listingID
                .exec(session -> {
                    String listingUrl = session.getString("listingUrl");
                    String listingID = listingUrl.split("/")[listingUrl.split("/").length - 1];
                    return session.set("listingID", listingID);
                })
                // 验证listingID
                .exec(session -> {
                    String listingID = session.getString("listingID");
                    System.out.println("Listing ID: " + listingID);
                    return session;
                })
                // 更新房源信息
                .exec(http("POST Info")
                        .post("/update/${listingID}")
                        .headers(Map.of(
                                "Content-Type", "application/json",
                                "X-CSRF-TOKEN", "#{csrfToken}"
                        ))
                        .body(ElFileBody("property_info.json"))
                        .check(status().is(200))
                        .check(bodyString().saveAs("updateListingResponse")))
                .exec(session -> {
                    String responseBody = session.getString("updateListingResponse");
                    System.out.println("POST updateListingResponse Listing Media Response Body: " + responseBody);
                    return session;
                });

        // 配置虚拟用户数
        int numUsers = 10;
        setUp(scn.injectOpen(rampUsers(numUsers).during(1)).protocols(httpProtocol));
    }
}

关键说明

  • 全局会话复用:通过before钩子提前完成单次登录,所有虚拟用户直接继承登录后的Cookie和csrfToken,彻底避免重复登录和csrfToken多次生成的问题。
  • 房源创建逻辑:补充了POST提交房源的步骤,确保实际生成房源后再提取listingID。
  • ID提取优化:从跳转响应头提取ID,比HTML页面匹配更可靠,避免正则匹配失效的问题。

内容的提问来源于stack exchange,提问作者Harshit Soni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 13:37:05