Android中Retrofit调用Elasticsearch时中文参数编码问题求解
你遇到的情况是Retrofit默认会对URL中的查询参数做URL编码,导致中文变成了%E6%B5%B7%E5%BA%95%E6%8D%9E%E7%81%AB%E9%94%85这类编码字符串。其实Elasticsearch本身是可以识别这种编码后的参数并正常搜索的,但如果希望URL保持中文可读性,或者需要确保编码逻辑完全符合预期,这里有几种可行的解决方式:
方案一:修改Retrofit接口的参数编码规则
在定义ElasticSearchAPI接口时,给@Query注解添加encoded = true参数,告诉Retrofit这个参数不需要再做编码处理:
public interface ElasticSearchAPI { @GET("/elasticsearch/hashtags/hashtag/_search") Call<HitsObject> search( @HeaderMap HashMap<String, String> headerMap, @Query("default_operator") String operator, // 标记q参数不需要自动编码 @Query(value = "q", encoded = true) String searchString ); }
这样你传入的中文searchString会直接拼接到URL中,不会被Retrofit二次编码。不过要注意,OkHttp底层还是会处理成符合HTTP标准的编码(UTF-8),但最终URL会显示原始中文,而非编码后的字符串。
方案二:自定义OkHttp拦截器修正参数编码
如果不想修改接口定义,可以给Retrofit配置自定义的OkHttp拦截器,手动处理q参数的编码逻辑:
// 构建带拦截器的OkHttpClient OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); HttpUrl originalUrl = originalRequest.url(); HttpUrl.Builder urlBuilder = originalUrl.newBuilder(); // 遍历所有查询参数,找到q参数处理 for (String paramName : originalUrl.queryParameterNames()) { String paramValue = originalUrl.queryParameter(paramName); if ("q".equals(paramName) && paramValue != null) { // 移除原编码参数,添加解码后的中文参数 urlBuilder.removeAllQueryParameters(paramName); try { urlBuilder.addQueryParameter(paramName, URLDecoder.decode(paramValue, "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); urlBuilder.addQueryParameter(paramName, paramValue); } } } // 生成修改后的请求 Request modifiedRequest = originalRequest.newBuilder() .url(urlBuilder.build()) .build(); return chain.proceed(modifiedRequest); } }) .build(); // 给Retrofit配置这个自定义客户端 Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build();
方案三:用请求体传递搜索条件(推荐)
其实Elasticsearch官方更推荐用POST请求的请求体来传递搜索条件,这种方式不仅能彻底避免URL参数的编码问题,还能支持更复杂的查询DSL(比如多条件组合、分词设置、过滤条件等)。
首先修改接口定义:
public interface ElasticSearchAPI { @POST("/elasticsearch/hashtags/hashtag/_search") Call<HitsObject> search( @HeaderMap HashMap<String, String> headerMap, @Query("default_operator") String operator, @Body SearchRequest searchRequest ); } // 定义对应Elasticsearch查询结构的实体类 class SearchRequest { private Query query; public SearchRequest(Query query) { this.query = query; } static class Query { private Match match; public Query(Match match) { this.match = match; } static class Match { private String title; public Match(String title) { this.title = title; } } } }
然后修改调用逻辑:
// 构建中文搜索的请求体 SearchRequest.Query.Match matchCondition = new SearchRequest.Query.Match(title); SearchRequest.Query query = new SearchRequest.Query(matchCondition); SearchRequest searchRequest = new SearchRequest(query); Call<HitsObject> call = searchAPI.search(headerMap, "AND", searchRequest);
这种方式完全绕开了URL参数的编码问题,同时也更符合Elasticsearch的使用规范。
另外补充一句:如果你的搜索结果不符合预期,其实大概率不是编码的问题——Elasticsearch是可以正确解析URL编码后的中文参数的,这时候要检查你的索引是否配置了正确的中文分词器,或者查询语法是否有误。
内容的提问来源于stack exchange,提问作者user8795747

