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

Micronaut HttpClient自动编码URL中&与=符号问题求助

问题描述

简述

查看HttpClient日志后,发现部分字符被URL编码,但不希望出现这种情况。

详述

调用的URL由<base url>+<path>+<apitoken>+<query>+<channel>组成。其中query包含<tcins>和<store info>,这两部分中包含=和&,但这些字符在URL中被不一致地编码为%26和%3D。例如,API密钥部分?key=...正常,但后续的&tcins=82691535...被编码为%26tcins%3D82691535...。

Get方法定义如下:

@Get("/product_summary_with_fulfillment_v1?key=${redsky-api.token}&{tcins}&{targetStore}&channel=WEB")
HttpResponse<List<?>> fetchProducts(String tcins, String targetStore);

日志片段

11:58:04.258 [default-nioEventLoopGroup-1-1] DEBUG io.micronaut.http.client.netty.DefaultHttpClient - Sending HTTP GET to  https://redsky.target.com/redsky_aggregations/v1/web/product_summary_with_fulfillment_v1?key=9f36aeafbe60771e321a7cc95a78140772ab3e96&%26tcins%3D82691535&store_id%3D1750%26zip%3D84014-1752%26state%3DUT%26latitude%3D40.91825%26longitude%3D-111.887%26scheduled_delivery_store_id%3D1750%26required_store_id%3D1750%26has_required_store_id%3Dtrue&channel=WEB
11:58:04.265 [default-nioEventLoopGroup-1-1] TRACE io.micronaut.http.client.netty.DefaultHttpClient - User-Agent: Micronaut HTTP Client
11:58:04.265 [default-nioEventLoopGroup-1-1] TRACE io.micronaut.http.client.netty.DefaultHttpClient - Accept: application/vnd.github.v3+json, application/json
11:58:04.266 [default-nioEventLoopGroup-1-1] TRACE io.micronaut.http.client.netty.DefaultHttpClient - host: redsky.target.com

控制器代码

package com.github.peanutbutter.unicorn.tarvester;

import com.github.peanutbutter.unicorn.tarvester.model.TargetStore;
import com.github.peanutbutter.unicorn.tarvester.model.TcinList;
import io.micronaut.core.async.annotation.SingleResult;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

import java.util.List;

@Controller("products")
public class TarvesterController {

    final TarvesterClient tarvesterClient;

    public TarvesterController(TarvesterClient tarvesterClient) {
        this.tarvesterClient = tarvesterClient;
    }

    @Get("by-tcin")
    @SingleResult
    HttpResponse<List<?>> fetchProducts(TcinList tcinlist, TargetStore targetStore) {
        return tarvesterClient.fetchProducts(tcinlist.toString(), targetStore.toString());
    }
}

客户端代码

package com.github.peanutbutter.unicorn.tarvester;

import com.github.peanutbutter.unicorn.tarvester.model.Product;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Header;
import io.micronaut.http.client.annotation.Client;

import java.util.List;

import static io.micronaut.http.HttpHeaders.ACCEPT;
import static io.micronaut.http.HttpHeaders.USER_AGENT;

@Client(id = "redsky-api")
@Header(name = USER_AGENT, value = "Micronaut HTTP Client")
@Header(name = ACCEPT, value = "application/vnd.github.v3+json, application/json")
public interface TarvesterClient {

    @Get("/product_summary_with_fulfillment_v1?key=${redsky-api.token}&{targetStore}&{tcins}&channel=WEB")
    HttpResponse<List<?>> fetchProducts(String tcins, String targetStore);
}

ToString方法

//tcins片段
@Override
public String toString() {
    return "&tcins=" + Arrays.stream(id)
            .mapToObj(String::valueOf)
            .collect(Collectors.joining("%2C"));
}
...
// TargetStore片段
@Override
public String toString() {
    return String.join("&", new String[]{
            "store_id=" + locationId,
            "zip=" + postalCode,
            "state=" + region,
            "latitude=" + latitude,
            "longitude=" + longitude,
            "scheduled_delivery_store_id=" + locationId,
            "required_store_id=" + locationId,
            "has_required_store_id=true"
    });
}

application.properties配置

micronaut.application.name=Tarvester
micronaut.application.default-charset=UTF-8
micronaut.http.services.redsky-api.url=https://redsky.target.com/
micronaut.http.services.redsky-api.path=redsky_aggregations/v1/web
redsky-api.token=9f36aeafbe60771e321a7cc95a78140772ab3e96

已尝试的解决方法

  • 在控制器和@Get注解中添加produces = MediaType.PLAINTEXT,未解决URL编码问题。
  • 在客户端或控制器中转换Java对象为字符串,无效。
  • 未找到HttpClient禁用URL编码的配置参数。
  • 修改默认应用字符集为UTF-8,无效。

怀疑这是一个Bug,因为URL中的编码行为不一致。


解决方案

问题根源在于你用{tcins}和{targetStore}这类路径参数的方式传递查询参数,Micronaut会自动对这类参数内容做URL编码,而你又在toString()里手动拼接了&和=,导致这些字符被二次编码。

方案1:利用Micronaut查询参数绑定(推荐)

放弃手动拼接字符串,直接让框架处理查询参数:

@Client(id = "redsky-api")
@Header(name = USER_AGENT, value = "Micronaut HTTP Client")
@Header(name = ACCEPT, value = "application/vnd.github.v3+json, application/json")
public interface TarvesterClient {

    @Get("/product_summary_with_fulfillment_v1?key=${redsky-api.token}&channel=WEB")
    HttpResponse<List<?>> fetchProducts(
            @QueryValue("tcins") String tcins,
            @QueryValue("store_id") String storeId,
            @QueryValue("zip") String zip,
            @QueryValue("state") String state,
            @QueryValue("latitude") String latitude,
            @QueryValue("longitude") String longitude,
            @QueryValue("scheduled_delivery_store_id") String scheduledDeliveryStoreId,
            @QueryValue("required_store_id") String requiredStoreId,
            @QueryValue("has_required_store_id") boolean hasRequiredStoreId
    );
}

控制器调用时直接传入对应字段值即可,框架会自动拼接合法的查询参数,无需手动处理&和=。

方案2:用DTO对象绑定查询参数

如果参数较多,可以封装成DTO类:

public class StoreQueryParams {
    private String store_id;
    private String zip;
    private String state;
    private String latitude;
    private String longitude;
    private String scheduled_delivery_store_id;
    private String required_store_id;
    private boolean has_required_store_id;

    // Getters & Setters
}

修改客户端接口:

@Client(id = "redsky-api")
@Header(name = USER_AGENT, value = "Micronaut HTTP Client")
@Header(name = ACCEPT, value = "application/vnd.github.v3+json, application/json")
public interface TarvesterClient {

    @Get("/product_summary_with_fulfillment_v1?key=${redsky-api.token}&channel=WEB")
    HttpResponse<List<?>> fetchProducts(
            @QueryValue("tcins") String tcins,
            @QueryValue StoreQueryParams storeParams
    );
}

控制器中把TargetStore转换成StoreQueryParams对象传入即可。

方案3:禁用参数编码(不推荐)

如果坚持手动拼接字符串,可以通过@QueryValue的encode属性关闭编码:

@Client(id = "redsky-api")
@Header(name = USER_AGENT, value = "Micronaut HTTP Client")
@Header(name = ACCEPT, value = "application/vnd.github.v3+json, application/json")
public interface TarvesterClient {

    @Get("/product_summary_with_fulfillment_v1?key=${redsky-api.token}&channel=WEB")
    HttpResponse<List<?>> fetchProducts(
            @QueryValue(value = "", encode = false) String tcins,
            @QueryValue(value = "", encode = false) String targetStore
    );
}

注意:此方式需确保手动拼接的内容是合法URL格式,避免出现安全问题。

内容的提问来源于stack exchange,提问作者Jonathan-Zollinger

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:37:04