ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

乐优购物学习笔记(7):用 TaoToken 统一 Key 打通 Elasticsearch 聚合检索链路

乐优购物学习笔记(7):用 TaoToken 统一 Key 打通 Elasticsearch 聚合检索链路 1. 乐优购物商品搜索为什么需要统一 Key 管理做乐优购物商品搜索模块时我遇到的第一类问题不是 Elasticsearch 语法不会写而是 Key 和地址散落在各处。商品索引创建脚本里写一份地址Spring Data Elasticsearch 的application.yml里写一份Kibana 控制台里又填一份聚合调试时再复制一份。改一次环境四处都要动漏一处就报连接失败。Elasticsearch 在乐优购物里承担的是商品全文检索和聚合统计用户搜“小米手机”要能按品牌、分类、价格区间做桶聚合前端筛选栏才能显示“小米 12 款、华为 8 款”这类数据。这条链路涉及索引创建、映射配置、文档写入、聚合查询每一环都要发 HTTP 请求。如果 Key 和 endpoint 不统一调试成本会成倍上升。TaoToken 在这里的作用是提供一个统一的 Key 和统一入口把模型对话、编码辅助、API 调用这些能力收敛到一套凭证体系里。你可以把它理解成“一个 Key 管多个下游服务”的网关层。对乐优购物这种既要写 Java 代码、又要调 Rest API、还要在 Kibana 里验证聚合结果的项目来说统一 Key 能省掉大量重复配置。这篇笔记面向正在做乐优购物搜索模块、或者刚接触 Spring Data Elasticsearch 的同学。我会给出 settings.json 配置骨架、Rest API 调用示例、索引创建与聚合验证步骤以及常见报错排查。你跟着做能跑通从配置到检索的完整链路。2. TaoToken 前置准备Key 与 settings.json 骨架在开始写 Elasticsearch 代码之前先把 TaoToken 的 Key 准备好。访问官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后进入控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 列表页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。拿到 Key 之后不要直接硬编码到 Java 类里。我建议用一个统一的settings.json来管理这样本地调试、CI 环境、不同机器之间切换时只改一个文件。下面是我在乐优购物项目里用的配置骨架{ taotoken: { api_key: sk-你的Key, base_url: https://taotoken.net/api, timeout_ms: 30000, retry: { max_attempts: 3, backoff_ms: 500 } }, elasticsearch: { host: 192.168.56.101, port: 9200, scheme: http, index_name: leyou_goods, type_name: goods }, spring: { data: { elasticsearch: { cluster_name: leyou-cluster, cluster_nodes: 192.168.56.101:9300 } } } }这个文件里taotoken.api_key是统一凭证base_url指向 API 入口 https://taotoken.net/api 。elasticsearch段放 ES 的连接信息spring.data.elasticsearch段对应 Spring Boot 的application.yml配置。注意settings.json不要提交到 Git 仓库。可以在.gitignore里加上settings.local.json团队协作时用环境变量覆盖。如果你用的是 Spring Boot可以把settings.json放在src/main/resources下启动时用PropertySource加载。也可以直接用环境变量注入比如TAOTOKEN_API_KEY然后在代码里读取。Key 准备好之后先别急着写业务代码。用一条最简单的 Rest API 请求验证 Key 是否可用这一步能帮你排除掉大部分“配置看起来对但就是连不上”的问题。3. 可复制配置Rest API 调用与 Spring Data Elasticsearch 衔接3.1 用 Rest API 创建乐优购物商品索引Elasticsearch 的索引创建走 PUT 请求。乐优购物商品索引需要支持全文检索和聚合所以title字段用text类型配 ik 分词器brand、category用keyword类型以便聚合。curl -X PUT http://192.168.56.101:9200/leyou_goods -H Content-Type: application/json -d { settings: { number_of_shards: 3, number_of_replicas: 1 }, mappings: { goods: { properties: { id: { type: long }, title: { type: text, analyzer: ik_max_word }, category: { type: keyword }, brand: { type: keyword }, price: { type: double }, images: { type: keyword, index: false }, stock: { type: long }, saleable: { type: boolean } } } } }执行成功会返回{acknowledged:true,shards_acknowledged:true,index:leyou_goods}。如果返回resource_already_exists_exception说明索引已存在先删掉再建或者换个索引名。3.2 批量导入商品数据用 bulk API 批量写入比一条条 POST 快很多。注意 bulk 请求体最后要换行。curl -X POST http://192.168.56.101:9200/leyou_goods/goods/_bulk -H Content-Type: application/json -d {index:{_id:1}} {id:1,title:小米手机7,category:手机,brand:小米,price:3499.00,images:http://image.leyou.com/1.jpg,stock:200,saleable:true} {index:{_id:2}} {id:2,title:坚果手机R1,category:手机,brand:锤子,price:3699.00,images:http://image.leyou.com/2.jpg,stock:150,saleable:true} {index:{_id:3}} {id:3,title:华为META10,category:手机,brand:华为,price:4499.00,images:http://image.leyou.com/3.jpg,stock:100,saleable:true} {index:{_id:4}} {id:4,title:小米电视4A,category:电视,brand:小米,price:3899.00,images:http://image.leyou.com/4.jpg,stock:80,saleable:true} {index:{_id:5}} {id:5,title:荣耀V10,category:手机,brand:华为,price:2799.00,images:http://image.leyou.com/5.jpg,stock:300,saleable:true} 3.3 Spring Data Elasticsearch 实体类与配置Java 侧用 Spring Data Elasticsearch 做对象映射。实体类加Document、Id、Field注解Document(indexName leyou_goods, type goods, shards 3, replicas 1) public class Goods { Id private Long id; Field(type FieldType.Text, analyzer ik_max_word) private String title; Field(type FieldType.Keyword) private String category; Field(type FieldType.Keyword) private String brand; Field(type FieldType.Double) private Double price; Field(index false, type FieldType.Keyword) private String images; Field(type FieldType.Long) private Long stock; Field(type FieldType.Boolean) private Boolean saleable; // getter/setter 省略 }application.yml里配置 ES 连接spring: data: elasticsearch: cluster-name: leyou-cluster cluster-nodes: 192.168.56.101:9300Repository 接口继承ElasticsearchRepositorypublic interface GoodsRepository extends ElasticsearchRepositoryGoods, Long { ListGoods findByPriceBetween(double price1, double price2); ListGoods findByBrandAndCategory(String brand, String category); }3.4 聚合查询按品牌分桶并求平均价格乐优购物前端筛选栏需要“每个品牌有多少款商品、平均价格多少”。用NativeSearchQueryBuilder构建聚合Test public void testBrandAggregation() { NativeSearchQueryBuilder queryBuilder new NativeSearchQueryBuilder(); queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null)); queryBuilder.addAggregation( AggregationBuilders.terms(brands).field(brand) .subAggregation(AggregationBuilders.avg(avg_price).field(price)) ); AggregatedPageGoods aggPage (AggregatedPageGoods) goodsRepository.search(queryBuilder.build()); StringTerms brandTerms (StringTerms) aggPage.getAggregation(brands); for (StringTerms.Bucket bucket : brandTerms.getBuckets()) { String brand bucket.getKeyAsString(); long count bucket.getDocCount(); InternalAvg avg (InternalAvg) bucket.getAggregations().asMap().get(avg_price); System.out.println(brand 共 count 款平均价格 avg.getValue()); } }这段代码跑通后你会看到类似“小米 共 2 款平均价格 3699.0”的输出。如果聚合结果为空先检查brand字段是不是keyword类型text类型默认不参与聚合。4. 验证请求与成功结果对照配置写完怎么确认链路真的通了我一般分三步验证。第一步验证 ES 服务本身。浏览器或 curl 访问http://192.168.56.101:9200返回带cluster_name、version.number的 JSON 就说明 ES 活着。第二步验证索引和映射。执行curl -X GET http://192.168.56.101:9200/leyou_goods/_mapping?pretty返回里应该能看到title是textik_max_wordbrand是keyword。如果brand显示成text聚合会报Fielddata is disabled on text fields by default。第三步验证聚合查询。用 Rest API 直接发聚合请求curl -X GET http://192.168.56.101:9200/leyou_goods/_search?pretty -H Content-Type: application/json -d { size: 0, aggs: { brands: { terms: { field: brand }, aggs: { avg_price: { avg: { field: price } } } } } }成功返回的aggregations.brands.buckets里每个桶有key品牌名、doc_count数量、avg_price.value平均价。对照 Java 侧的输出两边数字一致就说明 Spring Data Elasticsearch 和 Rest API 走的是同一套数据。验证项请求方式成功标志ES 服务GET /返回 cluster_name 和 version索引映射GET /leyou_goods/_mappingbrand 为 keywordtitle 为 text聚合查询GET /leyou_goods/_searchbuckets 含 key、doc_count、avg_priceJava 聚合单元测试控制台输出品牌、数量、均价5. 本篇常见错误排查5.1 启动报 max virtual memory areas vm.max_map_count 过低这是 ES 在 Linux 上最常见的启动错误。解决方式sudo vim /etc/sysctl.conf # 添加 vm.max_map_count655360 sudo sysctl -p改完必须重新登录终端否则配置不生效。5.2 聚合报 Fielddata is disabled on text fields原因是你对text类型字段做了聚合。text会分词聚合需要完整值所以要用keyword。检查映射curl -X GET http://192.168.56.101:9200/leyou_goods/_mapping?pretty如果brand是text需要重建索引把brand改成keyword。已经写入的数据要重新导入。5.3 Spring Data Elasticsearch 连不上 9300 端口cluster-nodes配的是 9300这是集群节点间通信端口不是 9200 的 HTTP 端口。确认 ES 启动日志里 9300 已监听。如果 ES 绑定了network.host: 0.0.0.09300 也会对外暴露。防火墙要放行 9200 和 9300。5.4 批量导入报 bulk 请求体格式错误bulk API 要求每个 action 行和文档行都以换行结尾最后一行也要有换行。用 curl 的-d传多行 JSON 时注意 shell 的引号处理。建议把请求体写到文件里用--data-binary bulk.json发送。5.5 TaoToken Key 调用返回 401先确认 Key 没有多余空格再确认请求头格式是Authorization: Bearer sk-xxx。如果用的是 settings.json检查加载路径是否正确。可以在代码里打印一下实际读到的 Key 前几位排除配置文件没被加载的情况。6. 统一 Key 之后编码与检索怎么配合乐优购物搜索模块的调试很多时候不只是改 ES 查询语句还要同时改 Java 实体类、Repository 方法、聚合构建代码。这种跨文件改动用模型对话来辅助会快很多。你可以把报错信息、映射结构、聚合 JSON 一起贴进去让它帮你定位是映射问题还是查询构建问题。模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。如果是要长期做乐优购物这类项目反复写 Repository、写聚合、写映射可以考虑用 Coding Plan 把编码辅助能力固定下来入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面写了不同语言怎么配 base_url 和 Key。我自己的习惯是ES 的 Rest API 调试用 curl 或 KibanaJava 侧用 Spring Data Elasticsearch两边结果对不上时先把 Rest API 的 JSON 结果和 Java 打印的对象都贴出来逐字段比对。聚合这块最容易出问题的是字段类型keyword和text混用会导致桶数量对不上。把映射先固定死再写查询能省掉很多来回改的时间。
返回列表