谷粒商城-高级-52 -检索服务-检索参数模型构建及 DSL 测试

一、检索查询参数模型分析抽取

检索条件分析
1、全文检索:skuTitle-》keyword
2、排序:saleCount(销量)、hotScore(热度分)、skuPrice(价格)
3、过滤:hasStock、skuPrice区间、brandId、catalog3Id、attrs
4、聚合:attrs

完整查询参数
keyword=小米 &sort=saleCount_desc/asc&hasStock=0/1&skuPrice=400_1900&brandId=1&catalog3Id=1&at trs=1_3G:4G:5G&attrs=2_骁龙845&attrs=4_高清屏

查询参数Vo封装:
gulimall-search/src/main/java/com/atguigu/gulimall/search/vo/SearchParam.java

package com.atguigu.gulimall.search.vo;

import lombok.Data;

import java.util.List;

/**
 * @Description: 封装页面所有可能传递过来的查询条件
 *
 * @example:
 * keyword=小米 &sort=saleCount_desc/asc&hasStock=0/1&skuPrice=400_1900&brandId=1&catalog3Id=1&at trs=1_3G:4G:5G&attrs=2_骁龙845&attrs=4_高清屏
 * @author: kaiyi
 * @create: 2020-09-03 01:27
 */
@Data
public class SearchParam {
  private String keyword;   // 页面传递过来的全文匹配关键字
  private Long catalog3Id;  // 三级分类ID

  /**
   * 排序条件:sort=price/salecount/hotscore_desc/asc
   */
  private String sort;

  /**
   * 过滤条件:hasStock、skuPrice区间、brandId、catalog3Id、attrs
   * hasStock=0/1
   * skuPrice=1_500/_500/500_1000
   * brandId=1
   * attrs=2_5寸:6寸 (2表示属性ID,后边:表示多选)
   */
  private Integer hasStock; // 是否只显示有货
  private String skuPrice; // 价格区间查询
  private List<Long> brandId; // 按照品牌进行查询,可以多选
  private List<String> attrs; // 按照属性进行分组

  private Integer pageNum; // 页码

  /**
   * 原生的所有查询条件
   */
  private String _queryString;
}

二、检索返回结果模型分析抽取

返回结果参数Vo封装:
gulimall-search/src/main/java/com/atguigu/gulimall/search/vo/SearchResponse.java

package com.atguigu.gulimall.search.vo;

import com.atguigu.common.to.es.SkuEsModel;
import lombok.Data;

import java.util.List;

/**
 * @author: kaiyi
 * @create: 2020-09-03 10:08
 */
public class SearchResponse {

  // 查询到的所有商品信息
  private List<SkuEsModel> products;

  /**
   * 当前页码
   */
  private Integer pageNum;

  /**
   * 总记录数
   */
  private Long total;

  /**
   * 总页码
   */
  private Integer totalPages;

  private List<Integer> pageNavs;

  /**
   * 当前查询到的结果,所有涉及到的品牌
   */
  private List<BrandVo> brands;

  /**
   * 当前查询到的结果,所有涉及到的所有属性
   */
  private List<AttrVo> attrs;

  /**
   * 当前查询到的结果,所有涉及到的所有分类
   */
  private List<CatalogVo> catalogs;

  //===========================以上是返回给页面的所有信息============================//

  /* 面包屑导航数据 */
  private List<NavVo> navs;

  @Data
  public static class NavVo {
    private String navName;
    private String navValue;
    private String link;
  }

  @Data
  public static class BrandVo{
    private Long brandId;
    private String brandName;
    private String brandImg;

  }

  @Data
  public static class AttrVo{
    private Long attrId;
    private String attrName;
    private String attrValue;
  }

  @Data
  public static class CatalogVo{
    private Long catalogId;
    private String catalogName;
  }
}

三、控制器调用

com/atguigu/gulimall/search/controller/SearchController.java

package com.atguigu.gulimall.search.controller;

import com.atguigu.gulimall.search.service.MallSearchService;
import com.atguigu.gulimall.search.vo.SearchParam;
import org.elasticsearch.action.search.SearchResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @author: kaiyi
 * @create: 2020-09-03 00:37
 */
@Controller
public class SearchController {

  @Autowired
  MallSearchService mallSearchService;

  /**
   * SpringMvc会自动将页面提交过来的所有请求参数封装成我们指定的对象
   * @param param
   * @return
   */
  @GetMapping("/list.html")
  public String listPage(SearchParam param, Model model) {

    // 1、根据传递过来的页面查询参数,去es中检索商品
    SearchResponse result = mallSearchService.search(param);
    model.addAttribute("result", result);
    return "list";
  }

}

四、DSL测试

GET gulimall_product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "skuTitle": "华为"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "catalogId": "225"
          }
        },
        {
          "terms": {
            "brandId": [
              "1",
              "2",
              "4",
              "9"
            ]
          }
        },
        {
          "term": {
            "attrs.attrId": 5
          }
        },
        {
          "nested": {
            "path": "attrs",
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "attrs.attrId": {
                        "value": 5
                      }
                    }
                  },
                  {
                    "terms": {
                      "attrs.attrValue": [
                        "麒麟800"
                      ]
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

nested 嵌套类型问题处理

在做 nested 嵌套类型测试时出现这样的错误:[nested] nested object under path [attrs] is not of nested type, 原因是这个字段 attrs 不是 nested类型,所以报错了:

GET gulimall_product/_search
{
  "query": {
    "bool": {
      "filter": {
        "nested": {
          "path": "attrs",
          "query": {
            "bool": {
              "must": [
                {
                  "term": {
                    "attrs.attrId": {
                      "value": 5
                    }
                  }
                },
                {
                  "terms": {
                    "attrs.attrValue": [
                      "麒麟800"
                    ]
                  }
                }
              ]
            }
          }
        }
      }
    }
  }
}

像这样的:

{
   "some_index": {
      "mappings": {
         "normal_type": {
            "properties": {
               "nested_type": {
                  "type": "nested",
                  "properties": {
                     "address": {
                        "type": "string"
                     },
                     "country": {
                        "type": "string"
                     }
                  }
               },
               "first_name": {
                  "type": "string"
               },
               "last_name": {
                  "type": "string"
               }
            }
         }
      }
   }
}

The "type": "nested", line is required for the nested queries to work which have "path": assigned to nested_type, like this:

GET /some_index/normal_type/_search
{
  "query": {
    "nested": {
      "query": {
        "bool": {}
      },
      "path": "nested_type"
    }
  }
}

先看看 gulimall_product 索引字段的属性:

//  查询指定索引的信息(settings,mappings)
GET gulimall_product

结果:

{
  "gulimall_product" : {
    "aliases" : { },
    "mappings" : {
      "properties" : {
        "attrs" : {
          "properties" : {
            "attrId" : {
              "type" : "long"
            },
            "attrName" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "attrValue" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        },
        "brandId" : {
          "type" : "long"
        },
        "brandImg" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "brandName" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "catalogId" : {
          "type" : "long"
        },
        "catalogName" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "hasStock" : {
          "type" : "boolean"
        },
        "hotScore" : {
          "type" : "long"
        },
        "saleCount" : {
          "type" : "long"
        },
        "skuId" : {
          "type" : "long"
        },
        "skuImg" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "skuPrice" : {
          "type" : "float"
        },
        "skuTitle" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "spuId" : {
          "type" : "long"
        }
      }
    },
    "settings" : {
      "index" : {
        "creation_date" : "1598598402124",
        "number_of_shards" : "1",
        "number_of_replicas" : "1",
        "uuid" : "w9qaZkMUTXa9DRbLSdAjCg",
        "version" : {
          "created" : "7040299"
        },
        "provided_name" : "gulimall_product"
      }
    }
  }
}

我们需要将 attrs 字段 改为 nested 类型, 那么Elasticsearch怎么修改索引字段类型?

由于ElasticSearch没有像mysql一样可以直接字段数据类型的方法,因此需要通过创建中间索引:data_index_1,备份数据到中间索引:data_index_1,然后删除原索引: data_index,重新创建正确数据类型索引:data_index,再把中间索引:data_index_1的数据备份到新创建索引:data_index。语句通过kibana的 dev_tools/console 执行。

操作步骤如下:
. 创建一个中间索引
. 向中间索引备份源索引的数据(mapping)
. 查询确认数据是否copy过去
. 删除有问题的索引
. 重新创建同名的索引(★字段类型修改正确★)
. 从中间索引还原到源索引的数据
. 删除中间索引

参考修改脚本,kibana执行:

# 1. 创建一个中间索引
#创建索引
PUT demo_metric_1/

# 创建Mapping
POST demo_metric_1/type/_mapping
{
    "type": {
      "properties": {
        "log_time_date": {
          "type": "date",
          "format": "epoch_millis"
        },
        .....
      }
    }
}

# 2. 向中间索引备份源索引的数据

# 重建索引
POST _reindex
{
  "source": {
    "index": "demo_metric"
  },
  "dest": {
    "index": "demo_metric_1"
  }
}

# 3.查询确认数据是否copy过去
GET /demo_metric/type/_search

GET /demo_metric_1/type/_search

# 4.删除有问题的索引
# 删除有问题的索引
DELETE demo_metric

# 5.重新创建同名的索引(★字段类型修改正确★)
#创建索引
PUT demo_metric/

# 创建Mapping
POST demo_metric/type/_mapping
{
    "type": {
      "properties": {
        "log_time_date": {
          "type": "date",
          "format": "epoch_millis"
        },
        .....
      }
    }
}

# 6. 从中间索引还原到源索引的数据
# 重建索引
POST _reindex
{
  "source": {
    "index": "demo_metric_1"
  },
  "dest": {
    "index": "demo_metric"
  }
}

# 7. 删除中间索引
DELETE demo_metric_1

在kibaba的索引管理中,找到我们要修改的索引 Mapping,然后复制,如下图:
file

或者也可以通过命令获取索引Mapping:

# 1、获取索引的映射
GET gulimall_product/_mapping

mapping

{
  "gulimall_product" : {
    "mappings" : {
      "properties" : {
        "attrs" : {
          "properties" : {
            "attrId" : {
              "type" : "long"
            },
            "attrName" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "attrValue" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        },
        "brandId" : {
          "type" : "long"
        },
        "brandImg" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "brandName" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "catalogId" : {
          "type" : "long"
        },
        "catalogName" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "hasStock" : {
          "type" : "boolean"
        },
        "hotScore" : {
          "type" : "long"
        },
        "saleCount" : {
          "type" : "long"
        },
        "skuId" : {
          "type" : "long"
        },
        "skuImg" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "skuPrice" : {
          "type" : "float"
        },
        "skuTitle" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "spuId" : {
          "type" : "long"
        }
      }
    }
  }
}

本项目操作步骤:

改映射数据,需要用数据迁移,原来的类型不能改动。

# 1、获取索引的映射
GET gulimall_product/_mapping

# 2、创建中间索引(直接使用下边的)
# PUT gulimall_product_1/

# 3、创建索引,并且同时设置索引Mapping
PUT gulimall_product_2
{
  "mappings": {
    "properties": {
      "attrs": {
        "type": "nested",
        "properties": {
          "attrId": {
            "type": "long"
          },
          "attrName": {
            "type": "keyword"
          },
          "attrValue": {
            "type": "keyword"
          }
        }
      },
      "brandId": {
        "type": "long"
      },
      "brandImg": {
        "type": "keyword"
      },
      "brandName": {
        "type": "keyword"
      },
      "catalogId": {
        "type": "long"
      },
      "catalogName": {
        "type": "keyword"
      },
      "hasStock": {
        "type": "boolean"
      },
      "hotScore": {
        "type": "long"
      },
      "saleCount": {
        "type": "long"
      },
      "skuId": {
        "type": "long"
      },
      "skuImg": {
        "type": "keyword"
      },
      "skuPrice": {
        "type": "keyword"
      },
      "skuTitle": {
        "type": "text"
      },
      "spuId": {
        "type": "keyword"
      }
    }
  }
}

# 4、查看创建的索引
GET gulimall_product_2

# 5、迁移数据
POST _reindex
{
  "source": {
    "index": "gulimall_product"
  },
  "dest": {
    "index": "gulimall_product_2"
  }
}

# 6、查询数据是否已迁移成功
GET gulimall_product_2/_search

# 7、上边迁移成功后,删除有问题的索引
DELETE gulimall_product

# 8、重新创建同名的索引(★字段类型修改正确★)
PUT gulimall_product
{
  "mappings": {
    "properties": {
      "attrs": {
        "type": "nested",
        "properties": {
          "attrId": {
            "type": "long"
          },
          "attrName": {
            "type": "keyword"
          },
          "attrValue": {
            "type": "keyword"
          }
        }
      },
      "brandId": {
        "type": "long"
      },
      "brandImg": {
        "type": "keyword"
      },
      "brandName": {
        "type": "keyword"
      },
      "catalogId": {
        "type": "long"
      },
      "catalogName": {
        "type": "keyword"
      },
      "hasStock": {
        "type": "boolean"
      },
      "hotScore": {
        "type": "long"
      },
      "saleCount": {
        "type": "long"
      },
      "skuId": {
        "type": "long"
      },
      "skuImg": {
        "type": "keyword"
      },
      "skuPrice": {
        "type": "keyword"
      },
      "skuTitle": {
        "type": "text"
      },
      "spuId": {
        "type": "keyword"
      }
    }
  }
}

# 9、从中间索引还原到源索引的数据
POST _reindex
{
  "source": {
    "index": "gulimall_product_2"
  },
  "dest": {
    "index": "gulimall_product"
  }
}

# 10、再次查询数据是否已迁移成功
GET gulimall_product/_search

# 11、上边迁移成功后,删除中间索引
DELETE gulimall_product_2

修改attrs为nested嵌套类型后,查询成功:
file


相关文章:
Elasticsearch官方文档Boolean queryed
Elasticsearch: nested object under path is not of
Elasticsearch怎么修改索引字段类型?
我用的ElasticSearch7+版本,使用插件postman的时候,PUT提交报错

为者常成,行者常至