安装 Elasticsearch 可以参考 Laradock 最佳实践。本文大部分内容来自 scout-elasticsearch-driver 项目的 README 文档。

一、安装 Scout 扩展

$ # 安装 Scout
$ composer require laravel/scout
$ # 发布配置文件
$ php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

二、安装 ES 驱动

$ # 安装驱动
$ composer require babenkoivan/scout-elasticsearch-driver
$ # 发布配置文件
$ php artisan vendor:publish --provider="ScoutElastic\ScoutElasticServiceProvider"

三、修改配置文件

config/scout.php 文件中将驱动修改为 elastic

'driver' => env('SCOUT_DRIVER', 'elastic'),

config/scout_elastic.php 文件中配置驱动:

<?php

return [
    'client' => [
        'hosts' => [
            env('SCOUT_ELASTIC_HOST', 'elasticsearch:9200')
        ]
    ],
    'update_mapping' => env('SCOUT_ELASTIC_UPDATE_MAPPING', true),
    'indexer' => env('SCOUT_ELASTIC_INDEXER', 'single'),
    'document_refresh' => env('SCOUT_ELASTIC_DOCUMENT_REFRESH')
];
Option Description
client 客户端配置,更多信息查看这里。默认主机地址设置为:localhost:9200。如果使用的是 Laradock 开发环境,需要修改为 elasticsearch:9200
update_mapping 设置是否自动更新映射,默认为 true
indexer 设置为 single(默认)单独索引文档,设置为 buld 批量索引文档。
document_refresh 配置已更新的 Documents 什么时候出现在搜索结果里。可被设置为 truefalsewait_for 或者 null。默认是 null。更多信息查看这里

四、创建索引配置类

使用 Artisan 命令初始化一个索引配置类:

$ php artisan make:index-configurator IndexConfigurator/MyIndexConfigurator

这会在 app 目录下创建文件 IndexConfigurator/MyIndexConfigurator.php

<?php

namespace App\IndexConfigurator;

use ScoutElastic\IndexConfigurator;

class MyIndexConfigurator extends IndexConfigurator
{
    // 指定索引名不是强制性的,默认情况下是去掉 'IndexConfigurator' 部分的蛇形命名(snake case)
      // 在这个例子当中,如果不设置该属性,则索引名是 my
    protected $name = 'my_index';  

    // 你可以指定任何设置,例如:自定义分析器。 
    protected $settings = [
        'analysis' => [
            'analyzer' => [
                'es_std' => [
                    'type' => 'standard',
                    'stopwords' => '_spanish_'
                ]
            ]    
        ]
    ];
}

注意:每个模型都需要指定索引配置类,见下一节

更多关于索引设置的信息可以查看 Elasticsearch 的索引管理章节。

要创建与之对应的索引运行下面的 artisan 命令:

$ php artisan elastic:create-index App\\IndexConfigurator\\MyIndexConfigurator

运行上面的命令后索引会被创建,但是其映射(mapping)为空。

五、模型配置

模型中需要做的:

  • 继承 ScoutElastic\Searchable 这个 Trait。注意不是 Laravel\Scout\Searchable
  • 设置对应的索引类 $indexConfigurator
  • 设置搜索规则 $searchRules
  • 设置映射选项 $mapping

样例:

<?php

namespace App;

use ScoutElastic\Searchable;
use Illuminate\Database\Eloquent\Model;

class MyModel extends Model
{
    use Searchable;

    protected $indexConfigurator = MyIndexConfigurator::class;

    protected $searchRules = [
        //
    ];

    // 在这里为模型字段指定映射
    protected $mapping = [
        'properties' => [
            'text' => [
                'type' => 'text',
                'fields' => [
                    'raw' => [
                        'type' => 'keyword',
                    ]
                ]
            ],
        ]
    ];
}

每个可搜索(searchable)模型对应一个 Elasticsearch type。默认 type 的名称与表名相同,但是可以通过 searchableAs 方法进行修改。可以通过 toSearchableArray 方法指定被索引的字段。更多信息参考官方文档的 Scout 扩展

$searchRules 属性允许你对一个模型设置不同的算法。详见后面的搜索规则章节。

在设置完映射后,可以进行更新:

$ php artisan elastic:update-mapping App\\MyModel

导入数据到 ES 索引:

$ php artisan scout:import "App\Post"

从 ES 中移除数据(这个方法比较慢):

$ php artisan scout:flush "App\Post"

删除 ES 中的指定索引和数据:

$ curl -X DELETE 'localhost:9200/Index'

六、用法

一旦创建了索引配置类,ES 索引,和可搜索模型,就可以根据文档索引搜索数据。

基础用法如下:

// 设置查询字符串
App\MyModel::search('phone')
    // 指定选择的字段
    ->select(['title', 'price'])
    // 过滤
    ->where('color', 'red')
    // 排序
    ->orderBy('price', 'asc')
    // collapse by field
    ->collapse('brand')
    // 设置偏移
    ->from(0)
    // 设置间隔
    ->take(10)
    // 获取结果
    ->get();

如果只需要获取查询的匹配数量,使用 count 方法:

App\MyModel::search('phone') 
    ->with('makers')
    ->get();

如果需要加载关联关系,使用 with 方法:

App\MyModel::search('phone') 
    ->with('makers')
    ->get();

除了标准功能外,该包还提供了在 Elasticsearch 中过滤数据而无需指定查询字符串的可能性:

App\MyModel::search('*')
    ->where('id', 1)
    ->get();

平且可以覆盖搜索规则:

App\MyModel::search('Brazil')
    ->rule(App\MySearchRule::class)
    ->get();

使用多种 where 条件:

App\MyModel::search('*')
    ->whereRegexp('name.raw', 'A.+')
    ->where('age', '>=', 30)
    ->whereExists('unemployed')
    ->get();

如果要发送一个自定义的请求,可以使用 serachRaw 方法,这个请求返回原始响应。:

App\MyModel::searchRaw([
    'query' => [
        'bool' => [
            'must' => [
                'match' => [
                    '_all' => 'Brazil'
                ]
            ]
        ]
    ]
]);

七、终端命令

可用的终端命令如下:

命令 参数 描述
make:index-configurator name - 索引配置类名 Creates a new Elasticsearch index configurator.
make:searchable-model name - 模型类名 Creates a new searchable model.
make:search-rule name - 搜索规则类名 Creates a new search rule.
elastic:create-index index-configurator - 索引配置类 Creates an Elasticsearch index.
elastic:update-index index-configurator - 索引配置类 更新一个 ES 索引的设置和映射
elastic:drop-index index-configurator - 索引配置类 删除一个 ES 索引
elastic:update-mapping model - 模型类 更新一个模型的映射
elastic:migrate model - 模型类 target-index - 要迁移到的索引名称 迁移模型至另一个索引

通过执行命令 php artisan help [command] 获取更详细的描述和可用选项介绍。

八、搜索规则

搜索规则是描述搜索查询将如何执行的类。 要创建搜索规则,使用以下命令:

$ php artisan make:search-rule SearchRule\MySearchRule

app/MySearchRule.php 中你可以发现一个类定义:

<?php

namespace App\SearchRule;

use ScoutElastic\SearchRule;

class MySearchRule extends SearchRule
{
    // 该方法返回一个数组,描述如何高亮搜索结果。
    // 如果返回 null,则不高亮结果。
    public function buildHighlightPayload()
    {
        return [
            'fields' => [
                'name' => [
                    'type' => 'plain'
                ]
            ]
        ];
    }

    // 该方法返回一个数组,对应布尔查询。
    public function buildQueryPayload()
    {
        return [
            'must' => [
                'match' => [
                    'name' => $this->builder->query
                ]
            ]
        ];
    }
}

你可以在这里了解布尔查询,在这里了解高亮。

默认搜索规则返回如下负载:

return [
   'must' => [
       'query_string' => [
           'query' => $this->builder->query
       ]
   ]
];

这意味着默认情况下,当在模型上调用 search 方法时,它会尝试在任何字段中查找查询的字符串。

要指定模型的默认搜索规则,只需添加一个属性:

<?php

namespace App;

use ScoutElastic\Searchable;
use Illuminate\Database\Eloquent\Model;

class MyModel extends Model
{
    use Searchable;

    // 你可以为一个模型设置几个搜索规则,在这种情况下,将返回第一个非空结果。
    protected $searchRules = [
        MySearchRule::class
    ];
}

你也可以在查询构造器中设置搜索规则:

// 你可以设置一个搜索规则类
App\MyModel::search('Brazil')
    ->rule(App\MySearchRule::class)
    ->get();

// 或者是一个回调函数
App\MyModel::search('Brazil')
    ->rule(function($builder) {
        return [
            'must' => [
                'match' => [
                    'Country' => $builder->query
                ]
            ]
        ];
    })
    ->get();

要获取高亮,使用模型的 highlight 属性:

// Let's say we highlight field `name` of `MyModel`.
$model = App\MyModel::search('Brazil')
    ->rule(App\MySearchRule::class)
    ->first();

// Now you can get raw highlighted value:
$model->highlight->name;

// or string value:
 $model->highlight->nameAsString;

九、可用的过滤器

可以使用不同类型的过滤器:

方法 Example Description
where($field,$value) where(‘id’, 1) 检查是否等值
where($field,$operator,$value) where(‘id’, ‘>=’, 1) 根据给定的规则过滤,可用的操作符有: =, <, >, <=, >=, <>.
whereIn($field, $value) where(‘id’, [1, 2, 3]) Checks if a value is in a set of values.
whereNotIn($field, $value) whereNotIn(‘id’, [1, 2, 3]) 检查值是否不在一个集合中
whereBetween($field, $value) whereBetween(‘price’, [100, 200]) 检查值是否在一个范围内
whereNotBetween($field, $value) whereNotBetween(‘price’, [100, 200]) 检查值是否不在一个范围内
whereExists($field) whereExists(‘unemployed’) Checks if a value is defined.
whereNotExists($field) whereNotExists(‘unemployed’) 检查值是否没定义
whereRegexp($field, $value, $flags = ‘ALL’) whereRegexp(‘name.raw’, ‘A.+’) 根据给定的正则过滤, 这里 你可以找到更多语法。
whereGeoDistance($field, $value, $distance) whereGeoDistance(‘location’, [-70, 40], ‘1000m’) 根据给定的点和距离过滤, 这里 你可以找到更多语法。
whereGeoBoundingBox($field, array $value) whereGeoBoundingBox(‘location’, [‘top_left’ => [-74.1, 40.73], ‘bottom_right’ => [-71.12, 40.01]]) 根据跟定的边框过滤, 这里 你可以找到更多语法。
whereGeoPolygon($field, array $points) whereGeoPolygon(‘location’, [[-70, 40],[-80, 30],[-90, 20]]) 根据给定的多边形过滤, 这里 你可以找到更多语法。
whereGeoShape($field, array $shape) whereGeoShape(‘shape’, [‘type’ => ‘circle’, ‘radius’ => ‘1km’, ‘coordinates’ => [4, 52]]) 根据给定的形状过滤, 这里 你可以找到更多语法。

在大多数情况下,最好使用原始字段来过滤记录,即不分析字段。

十、零停机迁移

如你所知,你无法在 Elasticsearch 中更改已创建的字段的类型。在这种情况下,唯一的选择是创建一个具有必要映射的新索引,并将模型导入新索引。
迁移可能需要很长时间,因此为避免在此过程中停机,驱动程序将从旧索引读取并写入新索引。一旦迁移结束,它就会从新索引开始读取并删除旧索引。这就是 artisan elastic:migrate 命令的工作原理。

在运行命令之前,请确保索引配置类继承了 ScoutElastic\Migratable 这个 Trait。 如果没有,添加这个 Trait 并使用索引配置类名称作为参数运行 artisan elastic:update-index 命令:

$ php artisan elastic:update-index App\\IndexConfigurator\\MyIndexConfigurator

准备就绪后,更改你需要的模型的映射,并运行 elastic:migrate 命令,使用模型类作为第一个参数,并将所需的索引名称作为第二个参数:

$ php artisan elastic:migrate App\\MyModel my_index_v2

请注意,如果只需要在映射中添加新字段,请使用 elastic:update-mapping 命令。

十一、Debug

有两种方法可以帮助分析搜索查询的结果:

  • explain

    App\MyModel::search('Brazil')
        ->explain();
    
  • profile

    App\MyModel::search('Brazil')
        ->profile();
    

两种方法都从 ES 返回原始数据。

此外,可以通过调用 buildPayload 方法获取将发送到 ES 的查询体。

App\MyModel::search('Brazil')
    ->buildPayload();

请注意,此方法返回有效查询体的集合,因为可能在一个查询中使用多个搜索规则。

(完)