只需五步 集成新版 Elasticsearch7.9 中文搜索 到你的 Laravel7 项目

ar414 5分钟 集成新版 Elasticsearch7.9 中文搜索 到你的 Laravel7 项目

只需五步骤:

  1. 启动 集成ik中文分词插件的Elasticsearch7.9 Docker镜像
  2. Laravel7 配置 Scout
  3. 配置 Model模型
  4. 导入数据
  5. 搜索

演示地址

ar414 5分钟 集成新版 Elasticsearch7.9 中文搜索 到你的 Laravel7 项目

www.ar414.com/search?query=php%E5%...

ar414

搜索范围

  • 文章内容
  • 标题
  • 标签

结果权重

  1. 出现关键词数量
  2. 出现关键词次数

搜索页面

  • 高亮显示
  • 分词显示
  • 结果分页

前言

主要是博客刚好想做个搜索,顺便就整理成文章

Laravel + Elasticsearch 很多前辈都写过教程和案例,但是随着Elasticsearch和laravel的版本升级 以前的文章很多都不适用新版本的,建议大家使用任何开源项目时应该过一遍文档以当前使用的版本文档为主,教程为辅

参考

使用集成ik中文分词插件的Elasticsearch

拉取docker

$ docker pull ar414/elasticsearch-7.9-ik-plugin

创建日志和数据存储目录

本地映射到docker容器内,防止docker重启数据丢失

$ mkdir -p /data/elasticsearch/data
$ mkdir -p /data/elasticsearch/log
$ chmod -R 777 /data/elasticsearch/data
$ chmod -R 777 /data/elasticsearch/log

运行

docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -v /data/elasticsearch/data:/var/lib/elasticsearch -v /data/elasticsearch/log:/var/log/elasticsearch ar414/elasticsearch-7.9-ik-plugin 

验证

$ curl http://localhost:9200
{
  "name" : "01ac21393985",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "h8L336qcRb2i1aydOv04Og",
  "version" : {
    "number" : "7.9.0",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "a479a2a7fce0389512d6a9361301708b92dff667",
    "build_date" : "2020-08-11T21:36:48.204330Z",
    "build_snapshot" : false,
    "lucene_version" : "8.6.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

测试中文分词

curl -X POST "http://localhost:9200/_analyze?pretty" -H 'Content-Type: application/json' -d'
{
  "analyzer": "ik_max_word",
  "text":     "laravel天下无敌"
}
'

{
  "tokens" : [
    {
      "token" : "laravel",
      "start_offset" : 0,
      "end_offset" : 7,
      "type" : "ENGLISH",
      "position" : 0
    },
    {
      "token" : "天下无敌",
      "start_offset" : 7,
      "end_offset" : 11,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "天下",
      "start_offset" : 7,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "无敌",
      "start_offset" : 9,
      "end_offset" : 11,
      "type" : "CN_WORD",
      "position" : 3
    }
  ]
}

Laravel 项目中使用 Elasticsearch

matchish/laravel-scout-elasticsearch
Elasticsearch官方有提供 SDK,在 Laravel 项目中可以更加优雅快速的接入 Elasticsearch,Laravel 本身有提供 Scout全文搜索 的解决方案,我们只需将默认的 Algolia 驱动 替换成ElasticSearch驱动

安装

配置

  1. 生成 Scout 配置文件(config/scout.php)

    $ php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
    Copied File [\vendor\laravel\scout\config\scout.php] To [\config\scout.php]
    Publishing complete.
  2. 指定 Scout 驱动

  • 第一种:在.env文件中指定(建议)
    SCOUT_DRIVER=Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine
  • 第二种:在config/scout.php直接修改默认驱动
    'driver' => env('SCOUT_DRIVER', 'algolia')
    改为
    'driver' => env('SCOUT_DRIVER', 'Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine')
  1. 指定Elasticsearch服务IP端口

    如果使用docker部署则使用docker0的IP,Linux通过ifconfig查看

    .env中配置

    ELASTICSEARCH_HOST=172.17.0.1:9200
  2. 注册服务
    config/app.php

    'providers' => [
     // Other Service Providers
     \Matchish\ScoutElasticSearch\ElasticSearchServiceProvider::class
    ],
  3. 清除配置缓存

    $ php artisan config:clear

至此 laravel 已经接入 Elasticsearch

实际业务中使用

需求

14分钟14秒 集成 Elasticsearch中文搜索 到你的 Laravel 项目

通过博客右上角的搜索框可以搜索到与关键词相关的文章,从以下几点匹配

  • 文章内容
  • 文章标题
  • 文章标签

涉及到2张 Mysql表 以及字段

  • article
    • title
    • tags
  • article_content
    • content

为文章配置 Elasticsearch 索引

  1. 创建索引配置文件(config/elasticsearch.php)

    $ touch config/elasticsearch.php
  2. elasticsearch.php 配置字段映射

    <?php
    return [
     'indices' => [
         'mappings' => [
             'blog-articles' => [
                 "properties"=>  [
                     "content"=>  [
                         "type"=>  "text",
                         "analyzer"=>  "ik_max_word",
                         "search_analyzer"=>  "ik_smart"
                     ],
                     "tags"=>  [
                         "type"=>  "text",
                         "analyzer"=>  "ik_max_word",
                         "search_analyzer"=>  "ik_smart"
                     ],
                     "title"=>  [
                         "type"=>  "text",
                         "analyzer"=>  "ik_max_word",
                         "search_analyzer"=>  "ik_smart"
                     ]
                 ]
             ]
         ]
     ],
    ];
  • analyzer:字段文本的分词器
    • search_analyzer:搜索词的分词器
    • 根据具体业务场景选择(颗粒小占用资源多,一般场景analyzer使用ik_max_word,search_analyzer使用ik_smart):
      • ik_max_word:ik中文分词插件提供,对文本进行最大数量分词
        laravel天下无敌 -> laravel天下无敌,天下,无敌
      • ik_smart: ik中文分词插件提供,对文本进行最小数量分词
        laravel天下无敌 -> laravel天下无敌

配置文章模型

建议先看一遍 Laravel Scout 使用文档

  1. 引入Laravel Scout

     namespace App\Models\Blog;
    
     use Laravel\Scout\Searchable;
    
     class Article extends BlogBaseModel
     {
         use Searchable;
     }
  2. 指定索引(刚刚配置文件中的elasticsearch.indices.mappings.blog-articles)

     /**
      * 指定索引
      * @return string
      */
     public function searchableAs()
     {
         return 'blog-articles';
     }
  3. 设置导入索引的数据字段

     /**
      * 设置导入索引的数据字段
      * @return array
      */
     public function toSearchableArray()
     {
         return [
             'content' => ArticleContent::query()
                 ->where('article_id',$this->id)
                 ->value('content'),
             'tags'    => implode(',',$this->tags),
             'title'   => $this->title
         ];
     }
  4. 指定 搜索索引中存储的唯一ID

     /**
      * 指定 搜索索引中存储的唯一ID
      * @return mixed
      */
     public function getScoutKey()
     {
         return $this->id;
     }
    
     /**
      * 指定 搜索索引中存储的唯一ID的键名
      * @return string
      */
     public function getScoutKeyName()
     {
         return 'id';
     }

数据导入

其实是将数据表中的数据通过Elasticsearch导入到Lucene
Elasticsearch 是 Lucene 的封装,提供了 REST API 的操作接口

  • 一键自动导入: php artisan scout:import
  • 导入指定模型: php artisan scout:import ${model}
$ php artisan scout:import "App\Models\Blog\Article"
Importing [App\Models\Blog\Article]
Switching to the new index
5/5 [⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬⚬] 100%
[OK] All [App\Models\Blog\Article] records have been imported.

导入失败,常见原因:

  • Unresolvable dependency resolving [Parameter #0 [ integer $retries ]] in class Elasticsearch\Transport
    • 解决: 修改配置后,没有清除配置缓存
  • invalid_index_name_exception
    • 解决: searchableAs配置错误,为索引创建别名后,指定别名

检查索引是否正确

$ curl -XGET http://localhost:9200/blog-articles/_mapping?pretty
{
  "blog-articles_1598362919" : {
    "mappings" : {
      "properties" : {
        "__class_name" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "content" : {
          "type" : "text",
          "analyzer" : "ik_max_word",
          "search_analyzer" : "ik_smart"
        },
        "tags" : {
          "type" : "text",
          "analyzer" : "ik_max_word",
          "search_analyzer" : "ik_smart"
        },
        "title" : {
          "type" : "text",
          "analyzer" : "ik_max_word",
          "search_analyzer" : "ik_smart"
        }
      }
    }
  }
}

测试

  1. 创建一个测试命令行

    $ php artisan make:command ElasticTest
  2. 代码

<?php

namespace App\Console\Commands;

use App\Models\Blog\Article;
use App\Models\Blog\ArticleContent;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;

class ElasticTest extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'elasticsearch {query}';

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

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

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        //
        $startTime = Carbon::now()->getPreciseTimestamp(3);
        $articles = Article::search($this->argument('query'))->get()->toArray();
        $userTime = Carbon::now()->getPreciseTimestamp(3) - $startTime;
        echo "耗时(毫秒):{$userTime} \n";

        //content在另外一张表中,方便观察测试 这里输出
        if(!empty($articles)) {
            foreach($articles as &$article) {
                $article = ArticleContent::query()->where('article_id',$article['id'])->value('content');
            }
        }

        var_dump($articles);

    }
}
  1. 测试
    $ php artisan elasticsearch 周杰伦

ar414 5分钟 集成新版 Elasticsearch7.9 中文搜索 到你的 Laravel7 项目

  1. 复杂查询
    例如:自定义高亮显示
//ONGR\ElasticsearchDSL\Highlight\Highlight 
ArticleModel::search($query,function($client,$body) {
            $higlight = new Highlight();
            $higlight->addField('content',['type' => 'plain']);
            $higlight->addField('title');
            $higlight->addField('tags');
            $body->addHighlight($higlight);
            $body->setSource(['title','tags']);
            return $client->search(['index' => (new ArticleModel())->searchableAs(), 'body' => $body->toArray()]);
        })->raw();        

复杂自定义查询回调中的$client和$body,可根据这两个包进行灵活操作

Support Author

coffee alipay wechat

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 3年前 自动加精
AR414
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 35

还是用原生包好点,这种做不了复杂的ES查询

3年前 评论
AR414 (楼主) 3年前
Double-Jin (作者) 3年前
AR414 (楼主) 3年前
Double-Jin (作者) 3年前
Double-Jin (作者) 3年前
Double-Jin (作者) 3年前
AR414 (楼主) 3年前
AR414 (楼主) 3年前
moxianzhe 2年前
Double-Jin (作者) 2年前

mark一下 我公司的项目使用原生包开发的中文搜索 确实比较麻烦 代码写的很臃肿

3年前 评论

你博客蛮好看的

3年前 评论
AR414 (楼主) 3年前

老哥你博客前端也是自己写的嘛,效果很不错,赞一个

2年前 评论
AR414 (楼主) 2年前

老哥 你的博客前端模板太喜欢了。可以分享嘛 :grin:

2年前 评论
AR414 (楼主) 2年前
JiroZhang (作者) 2年前

老哥换头像了

2年前 评论
AR414 (楼主) 2年前

楼主,我按照您的配置,都配置好了,但是检查索引的时候发现没使用ik分词器
{
"posts_index" : {
"mappings" : {
"properties" : {
"__class_name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"body" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"id" : {
"type" : "long"
},
"status" : {
"type" : "long"
},
"tag" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
还望楼主解答一下,下面是我的配置
<?php
return [
'indices' => [
'mappings' => [
'posts_index' => [
"properties"=> [
"body"=> [
"type"=> "text",
"analyzer"=> "ik_smart",
"search_analyzer"=> "ik_smart"
],
"tag"=> [
"type"=> "text",
"analyzer"=> "ik_smart",
"search_analyzer"=> "ik_smart"
],
"title"=> [
"type"=> "text",
"analyzer"=> "ik_smart",
"search_analyzer"=> "ik_smart"
]
]
]
]
],
];
我确定ik分词器已经安装好,curl测试的时候指定ik_smart都可以用,但是不指定就是默认的

2年前 评论
AR414 (楼主) 2年前
AR414 (楼主) 2年前
july1115 2年前
july1115 2年前

不配置中文分词可以导入,配置之后,导入只有index ,里面没内容

file

2年前 评论

楼主大大, 请教一个问题。 我的ik是号用的。 配置文件也做了修改 但是我发现运行 php artisan scout:import 并没有走配置文件

2年前 评论

最近公司需要用到搜索引擎,找到博主的这篇文章,一路操作下来,都通了,谢谢博主。这里有个问题哈,比如说我库中10条含有“名”的数据,但是搜索只搜索出部分数据,搜不全,网上找了一下,去掉模型中类似searchableAs方法,重新执行导入,即可查出所有数据。

2年前 评论

你好 导入model

file 帮看下呗 谢谢

1年前 评论
cm7788 1年前

请问下这个错应该怎么解决

file

9个月前 评论
rabbit_wl (作者) 9个月前

请问一下,项nested这种嵌套字段的,该怎么搜索

1个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!