ElasticSearch7.4.1 ,laravel ,scout,ik全文搜索实战

elasticsearch 安装:本实验使用 7.4.1

  • 需先安装 java , java -version
  • elasticsearch 7.4.1 内含了jdk 不用单独安装
wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-7.4.1.tar.gz
tar -xvf elasticsearch-7.4.1.tar.gz
cd elasticsearch-$version/bin
./elasticsearch #运行

deb安装:
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.4.1-amd64.deb  #下载
sudo dpkg -i elasticsearch-6.2.3.deb #安装
程序位置 `/usr/share/elasticsearch/`
配置文件 `/etc/elasticsearch/elasticsearch.yml`

启动:
$path/bin/elasticsearch -d  |  service elasticsearch start


  • 访问 curl 127.0.0.1:9200 返回
{
    "name": "homestead",
    "cluster_name": "elasticsearch",
    "cluster_uuid": "FC1WotHLTo6JKIsj-Y2wNg",
    "version": {
        "number": "7.4.1",
        "build_flavor": "default",
        "build_type": "deb",
        "build_hash": "fc0eeb6e2c25915d63d871d344e3d0b45ea0ea1e",
        "build_date": "2019-10-22T17:16:35.176724Z",
        "build_snapshot": false,
        "lucene_version": "8.2.0",
        "minimum_wire_compatibility_version": "6.8.0",
        "minimum_index_compatibility_version": "6.0.0-beta1"
    },
    "tagline": "You Know, for Search"
}

列出所有索引 `curl 127.0.0.1:9200/_cat/indices?v

laravel 中使用 , 需要用到的包

composer require laravel/scout
composer require tamayo/laravel-scout-elastic

  1. 配置
// config/app.php
'providers' => [
    // ...
    Laravel\Scout\ScoutServiceProvider::class,
    // ...
    ScoutEngines\Elasticsearch\ElasticsearchProvider::class,
],
  1. 生成scout配置文件 config/scout.php
    php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

  2. 在config/scout.php中加入elasticsearch的配置

 'elasticsearch' => [
        'index' => env('ELASTICSEARCH_INDEX', 'laravel'),
        'hosts' => [
            env('ELASTICSEARCH_HOST', 'http://localhost:9200'),
        ],
    ],
  1. 在 .env文件,加入scout和elasticsearch的配置
# scout配置
SCOUT_DRIVER=elasticsearch
SCOUT_PREFIX=

# elasticsearch 配置
ELASTICSEARCH_INDEX=esdemo
# elasticsearch 地址
ELASTICSEARCH_HOST=http://127.0.0.1:9200
  1. 在模型Orm中支持 索引
<?php
namespace App;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

/**
 * 学生模型
 */
class Student extends Model
{
    use Searchable;

    public function searchableAs()
    {
        return '_doc';  // 这里只能填_doc, 定义索引里面的类型,上文我们说过,可以把type理解成一个数据表。我们现在要做的就是把我们所有的要全文搜索的字段都存入到es中的一个叫'_doc'的表中。
    }

    /**
     * 可搜索的数据索引
     * @return array
     */
    public function toSearchableArray()
    {
        return [
              'indexName' => $this->field, // 与映射里定义的需要相同才能按照映射的分词方式处理;
        ];
    }
}
  1. 导入数据据到 elasticsearch 索引里
    php artisan scout:import "App\Student"
    删除索引数据
    php artisan scout:flush "App\Student"

  2. laravel中使用

$data = Sdudent::search("key word”)->get();
return $data;

  1. 中文无法搜索时的处理:
    修改 ErickTamayo/laravel-scout-elastic/src/ElasticsearchEngine.php
    protected function performSearch(Builder $builder, array $options = [])
    {
        $params = [
            'index' => $this->index,
            'type' => $builder->index ?: $builder->model->searchableAs(),
            'body' => [
                'query' => [
                    'bool' => [
                       // 'must' => [['query_string' => [ 'query' => "*{$builder->query}*"]]]
                        'must' => [['query_string' => [ 'query' => "{$builder->query}"]]]

                    ]
                ]
            ]
        ];


中文分词

默认使用的分词方式, 中文会切割成一个一个字符,搜索结果偏差大, ik分词可以很好的解决这个问题
ik插件安装:

$path/bin/elasticsearch-plugin list #查看

/usr/share/elasticsearch/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.1/elasticsearch-analysis-ik-7.4.1.zip #安装

$path/bin/elasticsearch-plugin remove pluginName #删除

分词实例: elasticseach 7.4.1

  1. 创建索引
    curl -X PUT "localhost:9200/test"

  2. 查看索引
    curl -X GET "localhost:9200/test"

  3. 创建映射 这个是决定php 使用时所使用的字段是否使用ik分词的关键

curl -X POST "localhost:9200/test/_mapping" -H 'Content-Type: application/json' -d'
{
    "properties": {
        "content": {  
            "type": "text",
            "analyzer": "ik_max_word",
            "search_analyzer": "ik_max_word"
        }
    }
}'
  1. 查看映射
    curl -X GET "localhost:9200/test/_mapping?pretty"

  2. 索引文档

curl -X PUT "localhost:9200/test/_doc/1" -H 'Content-Type: application/json' -d '
{ "content":"今天学习搜索分词"}'

curl -X PUT "localhost:9200/test/_doc/2" -H 'Content-Type: application/json' -d '
{ "content":"today study search explode words"}'

索引分词测试:

curl -H  'Content-Type: application/json' -XPOST 'http://localhost:9200/laravel/_analyze?pretty' -d'
{
  "text": "分词测试"
}'
response:
{
    "tokens": [
        {
            "token": "分",
            "start_offset": 0,
            "end_offset": 1,
            "type": "<IDEOGRAPHIC>",
            "position": 0
        },
        {
            "token": "词",
            "start_offset": 1,
            "end_offset": 2,
            "type": "<IDEOGRAPHIC>",
            "position": 1
        },
        {
            "token": "测",
            "start_offset": 2,
            "end_offset": 3,
            "type": "<IDEOGRAPHIC>",
            "position": 2
        },
        {
            "token": "试",
            "start_offset": 3,
            "end_offset": 4,
            "type": "<IDEOGRAPHIC>",
            "position": 3
        }
    ]
}

curl -H  'Content-Type: application/json' -XPOST 'http://localhost:9200/laravel/_analyze?pretty' -d'
{
  "analyzer":"ik_max_word",
  "text": "分词测试"
}'
response
{
    "tokens": [
        {
            "token": "分词",
            "start_offset": 0,
            "end_offset": 2,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "测试",
            "start_offset": 2,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 1
        }
    ]
}
  1. 查看索引列表
    curl -X GET localhost:9200/test/_search?q=*&pretty

  2. 搜索测试

curl -X POST "localhost:9200/test/_doc/_search" -H 'Content-Type: application/json' -d'
{
    "query": {
        "match": {
            "content": "搜索"
        }
    },
    "highlight": {
        "pre_tags": [
            "<tag1>",
            "<tag2>"
        ],
        "post_tags": [
            "</tag1>",
            "</tag2>"
        ],
        "fields": {
            "content": {}
        }
    }
}'

response

{
    "took": 3,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 1.4523084,
        "hits": [
            {
                "_index": "test",
                "_type": "_doc",
                "_id": "1",
                "_score": 1.4523084,
                "_source": {
                    "content": "今天学习搜索分词"
                },
                "highlight": {
                    "content": [
                        "今天学习<tag1>搜索</tag1><tag1>分词</tag1>"
                    ]
                }
            }
        ]
    }
}
  1. elasticsearch7 移除了type, 对于多表搜索 的处理模式是 每个表建立一个索引, scout 中索引是写在配置文件中固定的 config/scout.php
    'elasticsearch' => [
        'index' => env('ELASTICSEARCH_INDEX', 'laravel'),
        'hosts' => [
            env('ELASTICSEARCH_HOST', 'http://localhost:9200'),
        ],

scout 多个索引可以通过修改ORM 的__construnct(){} 重构 配置索引 index

class Orm extends Model
{
    use Searchable;
    protected $table = 'test';
    protected $fillable = [
        "code",
        "name_en",
        "name_zh",

        "is_delete",
        "created_at",
        "updated_at",
        ];

    protected $hidden = [
    ];

    public function __construct($attribute = [])
    {
        \Config::set('scout.elasticsearch.index',$this->getTable()); #使用表名做为索引
        parent::__construct($attribute);
    }

创建 command 创建索以及map , 导入数据
php artisan es:init modelOrm
php artisan scout:import modelOrm

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class EsInit extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'es:init {model}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'elasticsearch create mapping';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $class = $this->argument('model');

        $model = new $class;

        $client = new \GuzzleHttp\Client();
        $url = config('scout.elasticsearch.hosts')[0] . '/' . $model->getTable().'/_mapping';

        $client->delete(config('scout.elasticsearch.hosts')[0] . '/' . $model->getTable());
        $client->put(config('scout.elasticsearch.hosts')[0] . '/' . $model->getTable());

        $data = [];
        foreach ($model->toSearchableArray() as $key => $value){
            $data[$key] =  [
                "type" =>  "text",
                "analyzer" =>  "ik_max_word",
                "search_analyzer"=>  "ik_max_word"

            ];
        }

        $client->post($url, [
            \GuzzleHttp\RequestOptions::JSON => [
                "properties" => $data
            ]
        ]);
    }
}

关键词高亮

修改 ErickTamayo/laravel-scout-elastic/src/ElasticsearchEngine.php

protected function performSearch(Builder $builder, array $options = [])
{
    $fields = [];
    foreach ($builder->model->toSearchableArray() as $key => $value){
        $fields[$key] = (object)[];
    }

    $params = [
        'index' => $this->model->getTable(),
        'type' => $builder->index ?: $builder->model->searchableAs(),
        'body' => [
            'query' => [
                'bool' => [
                    'must' => [['query_string' => [ 'query' => "{$builder->query}"]]]
                ]
            ],
            "highlight" => [
                "fields" => $fields
            ]
        ]
    ];

public function map(Builder $builder, $results, $model)
{
    if ($results['hits']['total'] === 0) {
        return $model->newCollection();
    }

    $keys = collect($results['hits']['hits'])->pluck('_id')->values()->all();

    $source = [];

    foreach ($results['hits']['hits'] as $row){
        $source[$row['_id']] = $row['highlight'];
    }

    return $model->getScoutModelsByIds(
            $builder, $keys
        )->filter(function ($model) use ($keys) {
            return in_array($model->getScoutKey(), $keys);
        })->map(function ($item,$key) use($source,$model) {
            $item['highlight'] = $source[$item[$model->getKeyName()]];
            return $item;
        });
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容