您现在的位置是:首页 > 技术杂谈 > Laravel > php > elasticsearch elasticsearch
本篇正式在laravel项目中进行搜索查询
1、实现逻辑(分面搜索)
建立一个允许访问的查询api接口,路径:app/Modules/Api/Http/Controllers/BaseController/ceshi
use Illuminate\Http\Request; use App\Models\Order; use App\SearchBuilders\OrderSearchBuilder; class BaseController extends Controller { public function ceshi(Request $request){ $page = $request->input('page',1);//当前页 $per_page = $request->input('per_page',10);//每页条数 $keywords = $request->input('keywords');//搜索关键字,可以用空格隔开的多个关键词,如:手机 小米 $order_status = $request->input('order_status',0);//订单状态 (已支付,已发货,已取消,已完成等) $filters = $request->input('filters');//规格,商品规格查询 $searchBuilder = new OrderSearchBuilder();//自定义的订单搜索类 $builder = $searchBuilder->paginate($page,$per_page)->orderBy('order_amount','desc');//按订单总额倒序排列 if($keywords){ //将多个关键词重组成数组格式 $keywords = array_filter(explode(' ', $keywords)); $builder->keywords($keywords); } $result = app('es')->search($builder->getParams()); $orderIds = collect($result['hits']['hits'])->pluck('_id')->all(); $orders = Order::query()->whereIn('id',$orderIds)->orderByRaw(sprintf("FIND_IN_SET(id, '%s')", join(',', $orderIds)))->get(); dd($result); } }
查询后获取到订单id数组,然后在查询数据库,因为查询到的订单列表要以之前elasticsearch查询结果顺序为准,所以查询可以用到:
orderByRaw(sprintf("FIND_IN_SET(id, '%s')", join(',', $orderIds)))
订单搜索类文件内容:
<?php namespace App\SearchBuilders;//在app目录下新建了searchBuilders的搜索目录 class OrderSearchBuilder{ protected $params = [ 'index' => 'orders', 'type' => '_doc', 'body' => [ 'query' => [ 'bool' => [ 'filter' => [], 'must' => [], ], ], ], ]; // 添加分页查询 public function paginate($page=1,$perPage=10) { $this->params['body']['from'] = ($page - 1) * $perPage; $this->params['body']['size'] = $perPage; return $this; } public function orderBy($order="created_at",$sort="desc"){ if (!isset($this->params['body']['sort'])) { $this->params['body']['sort'] = []; } $this->params['body']['sort'][] = [$order => ['order'=>$sort]]; return $this; } public function orderStatus($order_status){ if($order_status){ $this->params['body']['query']['bool']['filter'][] = ['term' => ['order_status' => $order_status]]; } return $this; } // 添加搜索词 public function keywords($keywords) { // 如果参数不是数组则转为数组 $keywords = is_array($keywords) ? $keywords : [$keywords]; foreach ($keywords as $keyword) { $this->params['body']['query']['bool']['must'][] = [ 'multi_match' => [ 'query' => $keyword, 'fields' => [ 'user_name^2', 'consignee^2', 'store_name^2', 'addresses', 'goodsName', 'goodsSpec', 'properties_value' ], ], ]; } $this->params['body']['aggs'] = [ 'properties' => [ 'nested' => [ 'path' => 'properties', ], 'aggs' => [ 'properties' => [ 'terms' => [ 'field' => 'properties.name', ], // 第三层聚合 'aggs' => [ // 聚合的名称 'value' => [ 'terms' => [ 'field' => 'properties.value', ], ], ], ], ], ] ]; return $this; } // 添加一个按商品属性筛选的条件 public function propertyFilter($name, $value,$type="filter") { $this->params['body']['query']['bool'][$type][] = [ 'nested' => [ 'path' => 'properties', 'query' => [ ['term' => ['properties.search_value' => $name.':'.$value]], ], ], ]; return $this; } // 返回构造好的查询参数 public function getParams() { return $this->params; } }
在关键词搜索的方法中,给body加了aggs数组,这个是用于分面搜索的
什么是分面搜索呢,我们在京东去搜索商品时,如搜索【内存条】,如下图:
会出现当前搜索的商品的相关属性,搜索不同类型的商品,展示的商品属性是不同的,这个有利于用户更快捷的找到自己想要的商品,这就是分面搜索。
aggs数组内容说明如下:
'aggs' => [ // 这里的 properties 是我们给这个聚合操作的命名 // 可以是其他字符串,与商品结构里的 properties 没有必然联系 'properties' => [ // 由于我们要聚合的属性是在 nested 类型字段下的属性,需要在外面套一层 nested 聚合查询 'nested' => [ // 代表我们要查询的 nested 字段名为 properties 'path' => 'properties', ], // 在 nested 聚合下嵌套聚合 'aggs' => [ // 聚合的名称 'properties' => [ // terms 聚合,用于聚合相同的值 'terms' => [ // 我们要聚合的字段名 'field' => 'properties.name', ], ], ], ] ]
2、测试
http://myapp.com/api/ceshi?keywords=电视
执行结果:
如图,返回的数组中多了aggregations数组,把这个数组单独处理下,这块代码放到ceshi的方法中
$properties = []; if (isset($result['aggregations'])) { // 使用 collect 函数将返回值转为集合 $properties = collect($result['aggregations']['properties']['properties']['buckets']) ->map(function ($bucket) { // 通过 map 方法取出我们需要的字段 return [ 'key' => $bucket['key'], 'values' => collect($bucket['value']['buckets'])->pluck('key')->all(), ]; }); }
打印下$properties数组,结果如下图
当用户在前端去点击规格属性的时候,我们可以约定前端传的值,filters=传输类型:DDR4|内存容量:32GB,
属性之前用 | 隔开,属性名称和属性值之间用 :隔开,然后在查询的时候执行代码:
if($filters){ // 将获取到的字符串用符号 | 拆分成数组 $filterArray = explode('|', $filters); foreach ($filterArray as $filter) { // 将字符串用符号 : 拆分成两部分并且分别赋值给 $name 和 $value 两个变量 list($name, $value) = explode(':', $filter); $builder->propertyFilter($name,$value,'filter'); } }
3、查询相似产品
在电商商品详情页面都会有相似商品列表,相似商品相似的是属性,是同一类的商品,这个功能实现思路和属性查询是一样的。
在商品详情页可查询到当前商品的属性规格,然后取这个商品的属性规格去查询,属性规格查询相似多的排在前面,要实现需要了解布尔查询should条件,should 下的条件不需要全部满足,默认情况下只需要满足 should 下的一个条件即可,也可以通过 minimum_should_match 参数来改变需要满足的个数,满足的 should 条件越多,对应的文档的打分就越高,打分高的文档排序会靠前。
执行代码:
//获取相似商品的id public function getSimilarProductIds($product,$perPage=10){ $this->paginate(1,$perPage); foreach ($product->properties as $property) { $this->propertyFilter($property->name,$property->value,'should'); } $this->params['body']['query']['bool']['minimum_should_match'] = ceil(count($product->properties) / 2); $this->params['body']['query']['bool']['must_not'] = [['term' => ['_id' => $product->id]]]; $result = app('es')->search($this->getParams()); return collect($result['hits']['hits'])->pluck('_id')->all(); }
这段代码也是放到订单查询类里面,当然也可以新建一个商品查询类,放到商品类是最恰当的
文章评论

评论列表