3. 定义商品索引的显式 Mapping 并写入文档
商品索引的字段类型设计
商品数据里,同一个“名称”字段既可能用于全文检索,也可能用于精确聚合;同一个“品牌”字段通常只做精确过滤和聚合。Elasticsearch 默认的动态映射会把字符串同时映射为 text 和 keyword 子字段,看似方便,但字段类型、分词器、日期格式都不可控。显式 Mapping 的价值在于:在写入第一条文档之前,就把每个字段的检索行为固定下来。
本例设计一个 products 索引,字段规划如下:
product_id:keyword,商品唯一编号,用于精确查询和去重。name:text,使用ik_max_word分词器,用于商品名称全文检索。brand:keyword,品牌名,用于精确过滤与聚合。price:integer,以“分”为单位的整数价格,避免浮点误差。stock:integer,库存数量。on_sale:boolean,是否在售。release_date:date,格式yyyy-MM-dd。tags:keyword,标签数组,用于多值精确过滤。
若集群未安装
ik 分词插件,可将 name 的 analyzer 改为 standard,其余字段设计不变,仍可完成本例的写入与查询验证。创建索引与显式 Mapping
使用 PUT /products 一次性提交 settings 与 mappings。显式声明 dynamic: strict 后,写入未定义字段会直接报错,能尽早暴露字段拼写或结构问题。
PUT /products
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"dynamic": "strict",
"properties": {
"product_id": { "type": "keyword" },
"name": { "type": "text", "analyzer": "ik_max_word" },
"brand": { "type": "keyword" },
"price": { "type": "integer" },
"stock": { "type": "integer" },
"on_sale": { "type": "boolean" },
"release_date": { "type": "date", "format": "yyyy-MM-dd" },
"tags": { "type": "keyword" }
}
}
}
返回结果中 "acknowledged": true 表示索引与 Mapping 创建成功。随后可用 GET /products/_mapping 确认字段类型与分词器已按预期生效。
写入文档并查询验证
逐条写入 3 条商品文档,注意 price 用整数分、release_date 用 yyyy-MM-dd、tags 用数组:
POST /products/_doc/1
{ "product_id": "P1001", "name": "无线蓝牙降噪耳机",
"brand": "AudioX", "price": 49900, "stock": 120,
"on_sale": true, "release_date": "2024-03-15",
"tags": ["耳机", "蓝牙", "降噪"] }
POST /products/_doc/2
{ "product_id": "P1002", "name": "机械键盘 87 键 青轴",
"brand": "KeyPro", "price": 32900, "stock": 45,
"on_sale": true, "release_date": "2023-11-02",
"tags": ["键盘", "机械", "有线"] }
POST /products/_doc/3
{ "product_id": "P1003", "name": "便携蓝牙音箱 防水",
"brand": "AudioX", "price": 19900, "stock": 0,
"on_sale": false, "release_date": "2022-06-20",
"tags": ["音箱", "蓝牙", "防水"] }
写入后执行 GET /products/_refresh 使文档立即可搜,然后分别验证三类查询:
# 1. 全文检索:name 字段分词后匹配“蓝牙”
GET /products/_search
{ "query": { "match": { "name": "蓝牙" } } }
# 2. 精确过滤 + 范围:品牌为 AudioX 且价格低于 30000 分
GET /products/_search
{ "query": { "bool": { "filter": [
{ "term": { "brand": "AudioX" } },
{ "range": { "price": { "lt": 30000 } } }
] } } }
# 3. 日期范围:2023 年之后发布
GET /products/_search
{ "query": { "range": { "release_date": { "gte": "2023-01-01" } } } }
预期结果:查询 1 命中文档 1 和 3(名称含“蓝牙”);查询 2 只命中文档 3(AudioX 且 19900 < 30000);查询 3 命中文档 1 和 2。若查询 1 未命中,优先检查 name 的 analyzer 是否与写入时一致;若查询 2 报字段类型错误,检查 brand 是否被误建为 text。
结果判读与常见问题
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
写入报 strict_dynamic_mapping_exception | 文档含未在 Mapping 中定义的字段 | 补全 Mapping 字段或修正文档字段名 |
match 查询无结果 | name 分词器与预期不符 | 用 _analyze 查看分词结果,确认 analyzer |
term 查询 brand 无结果 | brand 被建为 text 而非 keyword | 重建索引并修正字段类型 |
| 日期范围查询报解析错误 | 写入日期格式与 Mapping 的 format 不一致 | 统一为 yyyy-MM-dd 或扩展 format |
验证通过标准:
GET /products/_mapping 中 8 个字段类型与设计一致;3 条文档写入均返回 result: created;上述三类查询命中结果与预期完全一致。