一分一毛也是爱

微信

微信

支付宝

支付宝

观海听潮

观海听潮博客

登录
还没有账号?去注册
观海听潮

观海听潮博客

注册
×

我的名片

网名:观海听潮

职业:PHP开发工程师

现居:山东省-青岛市

Email:[email protected]

网站统计

  • 观海听潮•博客
  • 77篇
  • 149条
  • 110361次
  • 254次
  • 美国弗吉尼亚州

您现在的位置是:首页  > 技术杂谈  > php  > elasticsearch  > Laravel Laravel

观海听潮

laravel 引入elasticsearch7.8.1搜索引擎(完)

摘要
本篇主要讲在laravel项目中如何实现复杂的查询,包括电商项目中经常用到的分面查询和推荐相似商品,本文将这些搜索都整理成一个相对简单的方法,可以方便调用和理解。

本篇正式在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数组,这个是用于分面搜索的

什么是分面搜索呢,我们在京东去搜索商品时,如搜索【内存条】,如下图:

rdoJQj4wjO.png

会出现当前搜索的商品的相关属性,搜索不同类型的商品,展示的商品属性是不同的,这个有利于用户更快捷的找到自己想要的商品,这就是分面搜索。

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=电视

执行结果:

截图.png

如图,返回的数组中多了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数组,结果如下图

截图 (1).png

当用户在前端去点击规格属性的时候,我们可以约定前端传的值,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();
}

这段代码也是放到订单查询类里面,当然也可以新建一个商品查询类,放到商品类是最恰当的


讨厌 (0)
微博logo QQ空间logo QQlogo 豆瓣logo 人人logo 百度贴吧logo 有道云笔记logo

文章评论

表情表情
×
图片图片

评论列表