x

Elasticsearch 商品搜索实战:五金门店应用场景

五金门店的核心痛点:同名不同规、同规不同牌、规格参数极多。本文详细展开 Elasticsearch 如何解决这些问题。

一、数据结构设计

// 五金商品索引 mapping 核心字段
{
  "mappings": {
    "properties": {
      "product_id":    { "type": "keyword" },
      "name":          { "type": "text", "analyzer": "ik_max_word", "fields": { "raw": { "type": "keyword" }}},
      "brand":         { "type": "keyword" },
      "category_l1":   { "type": "keyword" },
      "category_l2":   { "type": "keyword" },
      "category_l3":   { "type": "keyword" },

      // ★ 规格参数(核心,同名不同规的关键)
      "specs": {
        "type": "nested",
        "properties": {
          "spec_name":  { "type": "keyword" },
          "spec_value": { "type": "keyword" },
          "spec_unit":  { "type": "keyword" }
        }
      },

      // 扁平规格(方便直接 filter)
      "size_mm":       { "type": "keyword" },
      "material":      { "type": "keyword" },
      "model":         { "type": "keyword" },
      "standard":      { "type": "keyword" },

      "unit":          { "type": "keyword" },
      "min_unit":      { "type": "keyword" },

      "price_cost":    { "type": "float" },
      "price_retail":  { "type": "float" },
      "price_trade":   { "type": "float" },
      "price_project": { "type": "float" },

      "stock_qty":     { "type": "float" },
      "stock_safe":    { "type": "float" },
      "stock_unit":    { "type": "keyword" },

      "supplier_id":   { "type": "keyword" },
      "supplier_name": { "type": "keyword" },
      "customer_ids":  { "type": "keyword" },

      "tags":          { "type": "keyword" },
      "pinyin_name":   { "type": "text" },
      "aliases":       { "type": "text" },
      "suggest":       { "type": "completion" }
    }
  }
}

关键设计说明

设计点 方案 原因
规格用 nested 嵌套对象 同类参数有多值,避免交叉污染
尺寸用 keyword 字符串 10mm 和 10.0mm 是同一规格,精确匹配
品牌用 keyword 不分词 "世达"就是"世达",不拆
名称用 text+ik 中文分词 "开口扳手"拆为"开口/扳手"
拼音单建字段 pinyin_name 支持拼音首字母搜索 sld → 世达
别名字段 aliases "老虎钳"="卡簧钳"="钢丝钳"
客户-商品绑定 customer_ids 赊销客户快速查到自己的历史价

二、核心搜索场景

场景1:同名不同规 — "这个牌子我要30的,另一个牌子我要19的"

典型对话: 客户要"世达的 10mm 开口扳手"和"史丹利的 19mm 梅花扳手"

// 搜索:世达 开口扳手 10mm
GET /hardware/_search
{
  "query": {
    "bool": {
      "must": [
        { "term": { "brand": "世达" }},
        { "match": { "name": "开口扳手" }}
      ],
      "filter": [
        { "term": { "size_mm": "10" }},
        { "term": { "in_stock": true }}
      ]
    }
  },
  "sort": [{ "price_retail": "asc" }]
}
// 搜索:史丹利 19mm 梅花扳手
GET /hardware/_search
{
  "query": {
    "bool": {
      "must": [
        { "term": { "brand": "史丹利" }},
        { "match": { "name": "梅花扳手" }}
      ],
      "filter": [
        { "term": { "size_mm": "19" }}
      ]
    }
  }
}

关键设计: size_mm 作为 keyword 而非数值类型,因为 10mm 和 10.0mm 在五金里是同一个东西,keyword 精确匹配避免浮点误差。

场景2:规格自由组合搜索 — "我要一台能切割 30mm 钢管的切割机"

GET /hardware/_search
{
  "query": {
    "bool": {
      "must": { "match": { "name": "切割机" }},
      "filter": [
        { "range": { "spec_max_cut": { "gte": 30 }}}
      ]
    }
  },
  "highlight": {
    "fields": {
      "name": {},
      "specs.spec_value": {}
    }
  }
}

场景3:多规格联合查询(Nested)— "304不锈钢 M10 牙距6mm 螺栓"

// ★ nested 查询 — 必须同时满足多个规格条件
GET /hardware/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "螺栓" }},
        {
          "nested": {
            "path": "specs",
            "query": {
              "bool": {
                "must": [
                  { "term": { "specs.spec_name": "材质" }},
                  { "term": { "specs.spec_value": "304不锈钢" }}
                ]
              }
            }
          }
        },
        {
          "nested": {
            "path": "specs",
            "query": {
              "bool": {
                "must": [
                  { "term": { "specs.spec_name": "牙距" }},
                  { "term": { "specs.spec_value": "6mm" }}
                ]
              }
            }
          }
        },
        {
          "nested": {
            "path": "specs",
            "query": {
              "bool": {
                "must": [
                  { "term": { "specs.spec_name": "规格" }},
                  { "term": { "specs.spec_value": "M10" }}
                ]
              }
            }
          }
        }
      ]
    }
  }
}

nested vs object: 五金商品同一个参数名会出现多次(如一颗螺栓同时有"材质"、"规格"、"牙距"三个参数),必须用 nested 才能正确交叉查询,用 object 会串数据。

场景4:同款多品牌横向对比 — "所有品牌的 M10 膨胀螺栓,列出价格和库存"

GET /hardware/_search
{
  "query": {
    "bool": {
      "must": { "match": { "name": "膨胀螺栓" }},
      "filter": [
        { "term": { "category_l2": "螺栓" }},
        {
          "nested": {
            "path": "specs",
            "query": {
              "bool": {
                "must": [
                  { "term": { "specs.spec_name": "规格" }},
                  { "term": { "specs.spec_value": "M10" }}
                ]
              }
            }
          }
        }
      ]
    },
    "aggs": {
      "品牌及价格": {
        "terms": { "field": "brand", "size": 20 },
        "aggs": {
          "最低价":   { "min": { "field": "price_retail" }},
          "最高价":   { "max": { "field": "price_retail" }},
          "平均价":   { "avg": { "field": "price_retail" }},
          "库存总量": { "sum": { "field": "stock_qty" }},
          "在售品牌": { "top_hits": { "size": 1, "_source": ["name", "price_retail", "stock_qty"] }}
        }
      }
    }
  }
}

输出结果:各品牌的 M10 膨胀螺栓最低价/最高价/库存量,一目了然方便客户比价。

场景5:工程报价单查询 — "帮我查一下这个工地的材料清单"

// 客户是某工程方,有专属工程价
GET /hardware/_search
{
  "query": {
    "bool": {
      "must": [
        { "terms": { "product_id": ["P001", "P023", "P088", "P104"] }}
      ]
    }
  },
  "_source": ["product_id", "name", "brand", "model",
              "price_cost", "price_project", "stock_qty"],
  "sort": [{ "price_project": "asc" }]
}

场景6:赊销客户历史采购查询 — "刘总上次拿的货是什么价"

// 查找某个客户历史采购过哪些商品(赊销场景)
GET /hardware/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "customer_ids": "CUST_20240001" }}
      ]
    }
  },
  "sort": [{ "_timestamp": "desc" }],
  "size": 50,
  "_source": ["product_id", "name", "brand", "model",
              "price_trade", "last_purchase_date"]
}

场景7:模糊搜索 + 品牌纠错 — "sld 的 S2 型螺丝刀"

// 拼音首字母 + 型号 + 同义词
GET /hardware/_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "pinyin_name": "sld" }},
        { "match": { "aliases": "sld" }},
        { "term": { "brand": "世达" }},
        { "term": { "model": "S2" }}
      ],
      "minimum_should_match": 2
    }
  }
}

场景8:库存告警 + 智能补货建议

// 聚合:库存低于安全库存的商品,按类别统计
GET /hardware/_search
{
  "query": {
    "bool": {
      "filter": [
        { "script": { "script": "doc['stock_qty'].value < doc['stock_safe'].value" }}
      ]
    }
  },
  "aggs": {
    "急需补货": {
      "terms": { "field": "category_l2", "size": 20 },
      "aggs": {
        "缺货数量":  { "sum": { "script": "doc['stock_safe'].value - doc['stock_qty'].value" }},
        "平均成本":  { "avg": { "field": "price_cost" }},
        "TOP3缺货":  { "top_hits": { "size": 3, "_source": ["name", "brand", "supplier_name"] }}
      }
    }
  }
}

场景9:开店进货套餐推荐 — "开五金店要进一批货,帮我推荐一个工具箱套装"

GET /hardware/_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "name": "工具箱套装" }},
        { "match": { "name": "汽修工具套装" }},
        { "match": { "tags": "开店必备" }}
      ],
      "filter": [
        { "range": { "price_trade": { "lte": 500 }}},
        { "term": { "in_stock": true }}
      ]
    }
  },
  "sort": [
    { "_score": "desc" },
    { "sales_count": "desc" }
  ],
  "aggs": {
    "价格区间分布": { "histogram": { "field": "price_trade", "interval": 100 }}
  }
}

场景10:快速批量查找 — "这 20 个型号有没有货"

// mget 批量查询(多 ID 并行,性能极高)
GET /hardware/_mget
{
  "docs": [
    { "_id": "P001" },
    { "_id": "P023" },
    { "_id": "P088" },
    { "_id": "P104" }
  ]
}
// 返回每个 ID 的 name, brand, model, stock_qty, price_retail

三、数据示例:同一"螺丝刀"的不同规格

[
  { "name": "螺丝刀", "brand": "世达",   "model": "S2",   "size_mm": "6", "material": "S2合金钢",   "price_retail": 12.5, "price_trade": 8.0  },
  { "name": "螺丝刀", "brand": "史丹利", "model": "CR-V", "size_mm": "6", "material": "CR-V铬钒钢","price_retail": 18.0, "price_trade": 13.5 },
  { "name": "螺丝刀", "brand": "得力",   "model": "普通",  "size_mm": "6", "material": "碳钢",       "price_retail": 4.5,  "price_trade": 2.8  },
  { "name": "螺丝刀", "brand": "博世",   "model": "S4",   "size_mm": "4", "material": "S4工具钢",   "price_retail": 22.0, "price_trade": 16.0 },
  { "name": "螺丝刀", "brand": "史努博", "model": "S2",   "size_mm": "5", "material": "S2合金钢",   "price_retail": 9.8,  "price_trade": 6.5  }
]

这 5 个商品 name 完全相同,靠 brand + model + material + size_mm 四个字段组合才能唯一区分。

四、基础关键词搜索(补充)

match — 全文检索

GET /products/_search
{
  "query": {
    "match": {
      "name": "运动鞋"
    }
  }
}

multi_match — 多字段搜索,提高召回率

GET /products/_search
{
  "query": {
    "multi_match": {
      "query": "耐克跑步鞋",
      "fields": ["name^3", "category^2", "description"]
    }
  }
}

query_string — 支持 AND/OR 语法

GET /products/_search
{
  "query": {
    "query_string": {
      "default_field": "name",
      "query": "(运动鞋 OR 跑步鞋) AND NOT 儿童"
    }
  }
}

filter 不评分,性能优于 query

GET /products/_search
{
  "query": {
    "bool": {
      "must": { "match": { "name": "手机" }},
      "filter": [
        { "term": { "brand": "华为" }},
        { "term": { "in_stock": true }},
        { "range": { "price": { "gte": 2000, "lte": 5000 }}}
      ]
    }
  }
}

五、聚合统计(Facets)

GET /products/_search
{
  "query": { "match": { "name": "扳手" }},
  "aggs": {
    "品牌分布": { "terms": { "field": "brand", "size": 10 }},
    "价格区间": {
      "histogram": { "field": "price", "interval": 50 }
    },
    "平均价格": { "avg": { "field": "price" }}
  }
}

六、搜索建议 / 自动补全(Completion Suggester)

索引定义

PUT /products/_mapping
{
  "properties": {
    "suggest": {
      "type": "completion",
      "analyzer": "ik_smart"
    }
  }
}

搜索时前缀补全

GET /products/_search
{
  "suggest": {
    "前缀建议": {
      "prefix": "运动",
      "completion": { "field": "suggest", "size": 5 }
    }
  }
}

七、拼音搜索 + 纠错

// 拼音分词器:输入 "dq" 匹配 "电器"
GET /products/_search
{
  "query": {
    "match": {
      "pinyin_name": {
        "query": "shubiag",
        "analyzer": "pinyin"
      }
    }
  }
}
// Term Suggester — 拼写纠错
GET /products/_search
{
  "suggest": {
    "纠错": {
      "text": "运动鞋",
      "term": { "field": "name", "size": 2 }
    }
  }
}

八、同义词搜索

// 索引用同义词过滤器,搜索 "酒店" 也能匹配 "旅馆"
GET /products/_search
{
  "query": {
    "match": {
      "name": {
        "query": "旅馆",
        "synonym_analyzer": "my_synonym"
      }
    }
  }
}

例:T恤 → 短袖手机 → 移动电话螺丝刀 → 改锥

九、相关商品推荐(More Like This)

// 根据当前商品,推荐相似商品
GET /products/_search
{
  "query": {
    "more_like_this": {
      "fields": ["name", "category", "description"],
      "like": [
        { "_index": "products", "_id": "12345" }
      ],
      "min_term_freq": 1,
      "min_doc_freq": 1
    }
  }
}

十、智能排序(Function Score)

// 综合评分:关键词匹配度 × 销量权重 × 评分权重
GET /products/_search
{
  "query": {
    "function_score": {
      "query": { "match": { "name": "显示器" }},
      "functions": [
        {
          "field_value_factor": {
            "field": "sales_count",
            "factor": 1.2,
            "modifier": "log1p"
          }
        },
        {
          "gauss": {
            "rating": { "origin": 5, "scale": 2 }
          }
        },
        { "filter": { "term": { "in_stock": true }}},
        "score_mode": "sum",
        "boost_mode": "multiply"
      ]
    }
  }
}

十一、组合搜索 + 高亮

GET /products/_search
{
  "query": {
    "bool": {
      "must": {
        "multi_match": {
          "query": "黑色 512G 手机",
          "fields": ["name^3", "description"],
          "type": "best_fields"
        }
      },
      "filter": [
        { "terms": { "color": ["黑色", "深灰"]}},
        { "range": { "storage": { "gte": 512 }}}
      ],
      "should": [
        { "term": { "in_stock": true, "boost": 2 }}
      ]
    }
  },
  "highlight": {
    "fields": {
      "name": { "pre_tags": "<em>", "post_tags": "</em>" }
    }
  }
}

十二、额外场景速查

场景 示例
地理搜索 查找"附近5公里的五金店" — geo_distance 查询
规格属性搜索 attributes.规格: M码 AND attributes.颜色: 红色
爆款加权 销量超过 1000 的商品搜索排名 ×2
季节性推荐 夏天自动提升"风扇/凉席",冬天提升"取暖器"
搜索日志分析 聚合用户搜索词,分析"热搜榜"和"无结果搜索词"

十三、关联文档

Left-click: follow link, Right-click: select node, Scroll: zoom
x