基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试
浏览该文章,建议先食用 异常问题 这一节
相关软件及版本
软件/框架 | 版本 |
---|---|
jdk | 19.0.2 |
elasticsearch | 8.1.1 |
ik-analyzer | 8.1.1 |
laravel | 7.x-dev |
elasticsearch/elasticsearch | 7.17.1 |
tamayo/laravel-scout-elastic | 8.0.3 |
安装或升级jdk(版本:19.0.2)
-
下载jdk传送门
-
安装
下载:wget https://download.oracle.com/java/19/latest/jdk-19_linux-x64_bin.rpm
安装:rpm -ivh jdk-19_linux-x64_bin.rpm -
查看版本
java -version
安装es(版本:8.1.1)
-
下载传送门
-
安装
下载:wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.1.1-x86_64.rpm
安装:rpm -ivh elasticsearch-8.1.1-x86_64.rpm -
编辑配置项
vim /etc/elasticsearch/elasticsearch.yml
直接从最后一行添加如下内容:#bootstrap.memory_lock: true #network.host: localhost http.port: 9200 bootstrap.memory_lock: false network.host: 0.0.0.0 discovery.seed_hosts: ["127.0.0.1"] cluster.initial_master_nodes: ["node-1"] xpack.security.enabled: false #xpack.security.http.ssl: # enabled: false # keystore.path: certs/http.p12 #解决[.geoip_databases] index are active 问题 ingest.geoip.downloader.enabled: false ##允许跨域 http.cors.enabled: true http.cors.allow-origin: "*"
保存并退出。
-
启动es服务
设置开机启动:systemctl enable elasticsearch
启动服务:systemctl start elasticsearch
提示错误:
问题描述:内存空间不足
解决方案:
vim /etc/elasticsearch/jvm.options
找到-Xms4g 和 -Xmx4g 打开注释并修改为:
-Xms512m
-Xmx512m
保存并退出。
重新启动es服务:
systemctl restart elasticsearch提示错误:
问题描述:不能使用root用户,所以需要创建es专用用户和组
解决方案:
创建用户组:
groupadd elasticsearch
创建用户及所属组:
useradd -g elasticsearch elasticsearch
设置密码:
elasticsearch
设置 /opt/software/install/elasticsearch-8.6.2 目录权限为elasticsearch用户和组拥有:
chown -R elasticsearch:elasticsearch /opt/software/install/elasticsearch-8.6.2
切换到 elasticsearch 用户:
su elasticsearch
再次启动es服务:
systemctl restart elasticsearch
提示错误:
问题描述:exception during geoip databases updateorg.elasticsearch.ElasticsearchException: not all primary shards of [.geoip_databases] index are active
解决方案:
vim /opt/software/install/elasticsearch-8.6.2/config/elasticsearch.yml
在文件夹的最后一行添加:
#解决[.geoip_databases] index are active 问题
ingest.geoip.downloader.enabled: false
#允许跨域
http.cors.enabled: true
http.cors.allow-origin: “*”
再次启动es服务:
systemctl restart elasticsearch
不再提示错误,表示启动成功:
执行命令:
curl localhost:9200
说明es安装成功。
浏览器访问:ip:9200,也能得到上图结果: -
如果用的是云服务器(如阿里云),需要配置安全组开放9200端口。
-
如果服务器开启了防火墙,需要开放9200端口。
安装ik-analyzer(版本:8.1.1)
-
下载传送门
注意:为避免出现问题,版本需要和es完全一致。
-
安装
/usr/share/elasticsearch/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.1.1/elasticsearch-analysis-ik-8.1.1.zip
-
重启es
systemctl restart elasticsearch
-
测试分词效果
curl -H 'Content-Type: application/json' -XGET 'localhost:9200/_analyze?pretty' -d '{"analyzer":"ik_max_word","text":"张三丰创建了武当派"}'
可以看到 分词效果还是不错的,但是“张三丰”这个名字却被分成了3个,还好强大的 analysis-ik 支持自定义词库,增加自定义词库:
vim /etc/elasticsearch/analysis-ik/IKAnalyzer.cfg.xml
增加一个自定义词库,并向其中导入自定义内容,如通过名利写入:
echo ‘张三丰’ > /etc/elasticsearch/analysis-ik/custom.dic
再看下分词效果,明显好转:
laravel 7框架安装laravel-scout-elastic包
-
安装
composer.json文件中require对象加入elasticsearch包和laravel-scout-elastic
执行 composer update 完成相关包下载。需要注意的是:PHP7.2.* 版本安装的 elasticsearch/elasticsearch 版本为 7.11.0 ,php7.3.*安装的是 7.17.1,如果有多个。 -
在config/app.php 的 providers 数组中添加:
\Laravel\Scout\ScoutServiceProvider::class,
\Tamayo\LaravelScoutElastic\LaravelScoutElasticProvider::class, -
执行命令:
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider
config目录会生成一个scout.php配置文件。 -
修改scout.php配置文件
'driver' => env('SCOUT_DRIVER', 'elasticsearch'), //在最后添加 //配置elasticsearch引擎 'elasticsearch' => [ 'index' => env('ELASTICSEARCH_INDEX', 'laravel'),//laravel就是索引的名字,可以随便起 'hosts' => [ env('ELASTICSEARCH_HOST', 'http://127.0.0.1:9200'), ], ]
-
env配置如下:
#elasticsearch SCOUT_DRIVER=elasticsearch ELASTICSEARCH_INDEX=laravel7 ELASTICSEARCH_HOST=http://127.0.0.1:9200 ELASTIC_CLIENT_APIVERSIONING=1
在laravel中使用es进行中文分词及查询
-
自动更新索引
执行命令:php artisan make:command ESInit
-
在 app\Console\Kernel.php 里增加ESInit类
protected $commands = [ \App\Console\Commands\ESInit::class ];
-
编写ESInit类模板内容,完整代码如下:
<?php namespace App\Console\Commands; use GuzzleHttp\Client; use Illuminate\Console\Command; class ESInit extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'es:init'; /** * The console command description. * * @var string */ protected $description = 'init laravel es for post'; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } /** * Execute the console command. * * @return int */ public function handle() { $client = new Client(); // 创建模版 $url = config('scout.elasticsearch.hosts')[0] . '/_template/tmp'; try { $client->delete($url); } catch (\Exception $e) { $this->info("===delete模版出现错误===" . $e->getMessage()); } /* * 这个模板作用于我要做用的索引 * */ $param = [ 'json' => [ /* * 这句是取在scout.php(scout是驱动)里我们配置好elasticsearch引擎的 * index项。 * PS:其实都是取数组项,scout本身就是return一个数组, * scout.elasticsearch.index就是取 * scout[elasticsearch][index] * */ 'template' => config('scout.elasticsearch.index'), 'mappings' => [ '_default_' => [ 'dynamic_templates' => [ [ 'string' => [ 'match_mapping_type' => 'string',//传进来的是string 'mapping' => [ 'type' => 'text',//把传进来的string按text(文本)处理 'analyzer' => 'ik_smart',//用ik_smart进行解析(ik是专门解析中的插件) 'fields' => [ 'keyword' => [ 'type' => 'keyword' ] ] ] ] ] ] ] ], ], ]; try { $client->put($url, $param); } catch (\Exception $e) { $this->info("===put模版出现错误===" . $e->getMessage()); } $this->info('============create template success============'); //创建index $url = config('scout.elasticsearch.hosts')[0] . '/' . config('scout.elasticsearch.index'); try { $client->delete($url); } catch (\Exception $e) { $this->info("===delete索引出现错误===" . $e->getMessage()); } $param = [ 'json' => [ 'settings' => [ 'refresh_interval' => '5s', 'number_of_shards' => 1, 'number_of_replicas' => 0, ], 'mappings' => [ '_default_' => [ '_all' => [ 'enabled' => false ] ] ] ] ]; try { $client->put($url, $param); } catch (\Exception $e) { $this->info("===put索引出现错误===" . $e->getMessage()); } $this->info('============create index success============'); } }
-
启动 ES自动更新索引服务
php artisan es:init
-
修改你要搜索的 model,以 Project 为例:
模型类 Project 引入 Searchable 工具类:use Searchable;
-
模型类 Project 中重写 searchableAs () 方法 toSearchableArray () 方法
/** * @return string */ public function searchableAs() { return $this->getTable(); } /** * 索引的字段 * @return array */ public function toSearchableArray() { return [ 'project_name' => $this->project_name, 'nick_name' => $this->nick_name, ]; }
-
导入数据处理:
php artisan scout:import "App\Models\Project"
出现上图情况,表示导入数据成功。 -
编写查询代码
- 路由
//es测试 $route->get('es_project','ProjectController@esProject')->name('api_v1_esProject');
- 控制器
public function esProject(Request $request) { return $this->success( $this->homeProjectServ->esProjectServ($request->all()) ); }
- 服务层
public function esProjectServ(array $params) { $searchWord = $params['search_word'] ?? '义厂'; //DB::enableQueryLog(); $list = $this->model->search($searchWord)->get()->toArray(); //dd($searchWord, DB::getQueryLog()); return ['list' => $list]; }
- postman调用测试结果:
发现没查询到想要的结果,多方查找,终于找到问题所在,这篇文章 给出了答案,在文件 vendor/tamayo/laravel-scout-elastic/src/Engines/ElasticsearchEngine.php 中,第134行的前后两个 * 导致查询结果不达预期(可能是作者未考虑中文分词)。那么,找到问题就容易解决了,直接把前后 * 删掉即可
删除前:
删除后:
再次查询结果如下: 至此,es 安装、ik中文分词、在 laravel 中使用已经完成。
- 路由
代码优化
上面直接修改了 composer 下载的vendor包里的代码,是不优雅的,且在 composer install 或 composer update 后会被覆盖,那么有没有更好的处理方案呢?答案是有的,下面给出两种解决方案。两种方案都是基于重写包里对应方法的思想,方案一是通过AppServiceProvider的boot方法调用实现;方案二是通过自定义 provider 实现,下面是具体实现。
方案一
-
app/Libraries 目录下新增目录:CustomScoutElastic
-
创建类文件:app/Libraries/CustomScoutElastic/CustomElasticsearchEngine.php
该类为了重写 vendor 包内 vendor/tamayo/laravel-scout-elastic/src/Engines/ElasticsearchEngine.php 类内的 performSearch 方法。<?php namespace App\Libraries\CustomScoutElastic; use Laravel\Scout\Builder; use Tamayo\LaravelScoutElastic\Engines\ElasticsearchEngine; /** * 自定义es引擎,重写 performSearch 方法,解决134行(本文件28行)*导致跨字符查询无结果的问题 */ class CustomElasticsearchEngine extends ElasticsearchEngine { /** * Perform the given search on the engine. * * @param Builder $builder * @param array $options * @return mixed */ protected function performSearch(Builder $builder, array $options = []) { $params = [ 'index' => $builder->model->searchableAs(), 'type' => get_class($builder->model), 'body' => [ 'query' => [ 'bool' => [ 'must' => [['query_string' => ['query' => "{$builder->query}"]]] ] ] ] ]; if ($sort = $this->sort($builder)) { $params['body']['sort'] = $sort; } if (isset($options['from'])) { $params['body']['from'] = $options['from']; } if (isset($options['size'])) { $params['body']['size'] = $options['size']; } if (isset($options['numericFilters']) && count($options['numericFilters'])) { $params['body']['query']['bool']['must'] = array_merge( $params['body']['query']['bool']['must'], $options['numericFilters'] ); } if ($builder->callback) { return call_user_func( $builder->callback, $this->elastic, $builder->query, $params ); } return $this->elastic->search($params); } }
-
创建类文件:app/Libraries/CustomScoutElastic/CustomElasticsearch.php
该类继承并重写 vendor 包内 vendor/tamayo/laravel-scout-elastic/src/LaravelScoutElasticProvider.php 类内的 boot 方法<?php namespace App\Libraries\CustomScoutElastic; use Elasticsearch\ClientBuilder; use Laravel\Scout\EngineManager; use Tamayo\LaravelScoutElastic\LaravelScoutElasticProvider; class CustomElasticsearch extends LaravelScoutElasticProvider { /** * 重写 laravel-scout-elastic 包 LaravelScoutElasticProvider 类 的 boot 方法 * @throws \Exception */ public function customBootFromLaravelScoutElasticProvider() { try { $this->ensureElasticClientIsInstalled(); resolve(EngineManager::class)->extend('elasticsearch', function () { return new CustomElasticsearchEngine( ClientBuilder::create() ->setHosts(config('scout.elasticsearch.hosts')) ->build() ); }); } catch (\Exception $e) { throw new \Exception($e->getMessage(), $e->getCode()); } } }
-
编辑文件:app/Providers/AppServiceProvider.php
use相关类(new时会自动引入),此文件boot()方法中加入相关内容,如下:use App\Libraries\CustomScoutElastic\CustomElasticsearch; /** * Bootstrap any application services. * @throws \Exception */ public function boot() { try { //重写 laravel-scout-elastic 包 LaravelScoutElasticProvider 类 的 boot 方法 $customElasticsearch = new CustomElasticsearch($this->app); $customElasticsearch->customBootFromLaravelScoutElasticProvider(); } catch (\Exception $e) { throw new \Exception($e->getMessage(), $e->getCode()); } }
-
至此,不侵入vendor包代码的方案一就完成了,测试依然可用:
方案二
-
app/Libraries目录下新增目录:CustomScoutElastic
-
创建类文件:app/Libraries/CustomScoutElastic/CustomElasticsearchEngine.php
该类为了重写vendor包内 vendor/tamayo/laravel-scout-elastic/src/Engines/ElasticsearchEngine.php 类内的 performSearch 方法。<?php namespace App\Libraries\CustomScoutElastic; use Laravel\Scout\Builder; use Tamayo\LaravelScoutElastic\Engines\ElasticsearchEngine; /** * 自定义es引擎,重写 performSearch 方法,解决134行(本文件28行)*导致跨字符查询无结果的问题 */ class CustomElasticsearchEngine extends ElasticsearchEngine { /** * Perform the given search on the engine. * * @param Builder $builder * @param array $options * @return mixed */ protected function performSearch(Builder $builder, array $options = []) { $params = [ 'index' => $builder->model->searchableAs(), 'type' => get_class($builder->model), 'body' => [ 'query' => [ 'bool' => [ 'must' => [['query_string' => ['query' => "{$builder->query}"]]] ] ] ] ]; if ($sort = $this->sort($builder)) { $params['body']['sort'] = $sort; } if (isset($options['from'])) { $params['body']['from'] = $options['from']; } if (isset($options['size'])) { $params['body']['size'] = $options['size']; } if (isset($options['numericFilters']) && count($options['numericFilters'])) { $params['body']['query']['bool']['must'] = array_merge( $params['body']['query']['bool']['must'], $options['numericFilters'] ); } if ($builder->callback) { return call_user_func( $builder->callback, $this->elastic, $builder->query, $params ); } return $this->elastic->search($params); } }
-
App\Providers 目录下创建provider类文件:CustomLaravelScoutElasticProvider.php
具体路径为:app/Providers/CustomLaravelScoutElasticProvider.php
该类继承并重写 vendor 包内 vendor/tamayo/laravel-scout-elastic/src/LaravelScoutElasticProvider.php 类内的 boot 方法:<?php namespace App\Providers; use App\Libraries\CustomScoutElastic\CustomElasticsearchEngine; use Elasticsearch\ClientBuilder; use Laravel\Scout\EngineManager; use Tamayo\LaravelScoutElastic\LaravelScoutElasticProvider; /** * 自定义 LaravelScoutElastic 服务提供者,用于重写 LaravelScoutElasticProvider 类的boot方法 * 解决134行(本文件28行)*导致跨字符查询无结果的问题 */ //class CustomLaravelScoutElasticProvider extends LaravelScoutElasticProvider class CustomLaravelScoutElasticProvider extends LaravelScoutElasticProvider { /** * Bootstrap the application services. * @throws \Exception */ public function boot() { try { $this->ensureElasticClientIsInstalled(); resolve(EngineManager::class)->extend('elasticsearch', function () { return new CustomElasticsearchEngine( ClientBuilder::create() ->setHosts(config('scout.elasticsearch.hosts')) ->build() ); }); } catch (\Exception $e) { throw new \Exception($e->getMessage(), $e->getCode()); } } /** * Register any application services. * * @return void */ public function register() { // } }
-
config/app.php 文件 providers 加载创建的 CustomLaravelScoutElasticProvider 类
\App\Providers\CustomLaravelScoutElasticProvider::class,
-
至此,不侵入 vendor 包代码的方案二就完成了,测试依然可用:
异常问题
-
软件安装过程中遇到了各种各样的问题,最终确定了 es8.1.1 版本,注意 ik 版本需要和 es 版本绝对一致。文章来源:https://www.toymoban.com/news/detail-408105.html
-
使用方案二过程中,如果代码都写好了,还是无效,建议清除 bootstrap/cache 内文件后重新请求,可能是 provider 缓存导致的。文章来源地址https://www.toymoban.com/news/detail-408105.html
到了这里,关于基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!