Elasticsearch 是一个分布式的搜索和分析引擎,可以用于全文检索、结构化检索和分析,并能将这三者结合起来。Elasticsearch 基于 Lucene 开发,是 Lucene 的封装,提供了 REST API 的操作接口,开箱即用。现在是使用最广的开源搜索引擎之一,Wikipedia、Stack Overflow、GitHub 等都基于 Elasticsearch 来构建他们的搜索引擎。
安装elasticsearch
https://github.com/medcl/elasticsearch-rtf
当前的版本是 Elasticsearch 5.1.1,ik 插件也是直接自带了。安装好 ElasticSearch,跑起来服务,测试服务安装是否正确:
http://127.0.0.1:9200
安装laravel以及插件
使用的是laravel5.5框架,composer安装或者官网下载
composer create-project laravel/laravel=5.5 blog
composer安装scout
composer require laravel/scout
安装elasticsearch驱动
composer require tamayo/laravel-scout-elastic
引入GuzzleHttp包
composer require Guzzlehttp/guzzle
将类引到app.php里面
'providers' => [
...
...
Laravel\Scout\ScoutServiceProvider::class,
ScoutEngines\Elasticsearch\ElasticsearchProvider::class,
]
vendor:publish生成scout配置文件
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
config/scout.php设置elasticsearch环境
return [
'driver' => env('SCOUT_DRIVER', 'elasticsearch'),
...
'elasticsearch' => [
'index' => env('ELASTICSEARCH_INDEX', 'laravel'),
'hosts' => [
env('ELASTICSEARCH_HOST', '127.0.0.1'),
],
],
];
数据部分
migration创建article表,数据方面可以自己填充
php artisan make:migration create_article_table
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateArticleTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('article', function (Blueprint $table) {
$table->increments('id');
$table->text('url');
$table->string('author', 64)->nullable()->default(null);
$table->text('title');
$table->longText('content');
$table->dateTime('post_date')->nullable()->default(null);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('article');
}
}
代码部分
创建article的模板和索引
php artisan make:command InitEs
app/console/command/InitEs.php
namespace App\Console\Commands;
use GuzzleHttp\Client;
use Illuminate\Console\Command;
class InitEs 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 es to create index';
/**
* Create a new command instance.
*
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$client = new Client();
$this->createTemplate($client);
$this->createIndex($client);
}
protected function createIndex(Client $client)
{
$url = config('scout.elasticsearch.hosts')[0] . ':9200/' . config('scout.elasticsearch.index');
$client->put($url, [
'json' => [
'settings' => [
'refresh_interval' => '5s',
'number_of_shards' => 1,
'number_of_replicas' => 0,
],
'mappings' => [
'_default_' => [
'_all' => [
'enabled' => false
]
]
]
]
]);
}
protected function createTemplate(Client $client)
{
$url = config('scout.elasticsearch.hosts')[0] . ':9200/' . '_template/rtf';
$client->put($url, [
'json' => [
'template' => '*',
'settings' => [
'number_of_shards' => 1
],
'mappings' => [
'_default_' => [
'_all' => [
'enabled' => true
],
'dynamic_templates' => [
[
'strings' => [
'match_mapping_type' => 'string',
'mapping' => [
'type' => 'text',
'analyzer' => 'ik_smart',
'ignore_above' => 256,
'fields' => [
'keyword' => [
'type' => 'keyword'
]
]
]
]
]
]
]
]
]
]);
}
}
调用es脚本
php artisan es:init
创建Article模型
app/Model/Article.php
<?php
namespace App\Model;
//use App\Libraries\EsSearchable;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
/**
* Class Post
* @package App
* @property string $url
* @property string $author
* @property string $content
* @property string $title
* @property string $post_date
* @property string $created_at
* @property string $updated_at
*/
class Article extends Model
{
use Searchable;
protected $table = 'article';
protected $fillable = [
'url',
'author',
'title',
'content',
'post_date'
];
public function toSearchableArray()
{
return [
'title' => $this->title,
'content' => $this->content
];
}
}
导入数据模型
php artisan scout:import "App\Model\Article"
新建访问页面的控制器 PostController
app\Http\Controllers\PostController
<?php
namespace App\Http\Controllers;
use App\Model\Article;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function search(Request $request)
{
$q = $request->get('keyword');
$paginator = [];
if ($q) {
$paginator = Article::search($q)->paginate();
}
return view('search', compact('paginator', 'q'));
}
}
搜索关键词照顾,变量dump出来的样子
<pre class="sf-dump" id="sf-dump-609623676" >LengthAwarePaginator</abbr> {#338 ▼ <samp data-depth="1" class="sf-dump-expanded">#total: 1
#lastPage: 1
#items: <abbr title="Illuminate\Database\Eloquent\Collection" class="sf-dump-note" style="text-decoration: none; border: none; cursor: help; color: rgb(167, 29, 93);">Collection</abbr> {#340 ▼ <samp data-depth="2" >
#attributes: array:9 [▼ <samp data-depth="5" class="sf-dump-expanded">"id" => 52
"url" => "http://www.baidu.com"
"author" => "johnny"
"title" => "红包"
"content" => "谁人曾照顾过我的感受"
"post_date" => "2018-07-19 10:58:35"
"updated_at" => "2018-07-19 10:58:40"
"created_at" => "2018-07-19 10:58:42"
"highlight" => array:2 [▼ <samp data-depth="6" class="sf-dump-expanded">"content.keyword" => array:1 [▼ <samp data-depth="7" class="sf-dump-expanded">0 => "<em>谁人曾照顾过我的感受</em>"</samp> ]
"content" => array:1 [▼ <samp data-depth="7" class="sf-dump-expanded">0 => "谁人曾<em>照顾</em>过我的感受"</samp> ]</samp> ]</samp> ]
#original: array:8 [▶]
#changes: []
#casts: []
#dates: []
#dateFormat: null
#appends: []
#dispatchesEvents: []
#observables: []
#relations: []
#touches: []
+timestamps: true
#hidden: []
#visible: []
#guarded: array:1 [▶]
#scoutMetadata: []</samp> }</samp> ]</samp> }
#perPage: 15
#currentPage: 1
#path: "http://blog.com/search"
#query: array:1 [▶]
#fragment: null
#pageName: "page"</samp> }</pre>
highlight,也就是高亮需要修改源代码,把highlight的json加进去
vendor\tamayo\laravel-scout-elastic\src\ElasticsearchEngine
<?php
namespace ScoutEngines\Elasticsearch;
use Laravel\Scout\Builder;
use Laravel\Scout\Engines\Engine;
use Elasticsearch\Client as Elastic;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as BaseCollection;
class ElasticsearchEngine extends Engine
{
...
...
...
/**
* Perform the given search on the engine.
*
* @param Builder $builder
* @param array $options
* @return mixed
*/
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}*"]]]
]
]
]
];
$params['body']['highlight']['fields']['*'] = new \stdClass();
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);
}
...
...
/**
* Map the given results to instances of the given model.
*
* @param mixed $results
* @param \Illuminate\Database\Eloquent\Model $model
* @return Collection
*/
public function map($results, $model)
{
if ($results['hits']['total'] === 0) {
return Collection::make();
}
$keys = collect($results['hits']['hits'])
->pluck('_id')->values()->all();
$models = $model->whereIn(
$model->getKeyName(), $keys
)->get()->keyBy($model->getKeyName());
return collect($results['hits']['hits'])->map(function ($hit) use ($model, $models) {
$one = $models[$hit['_id']];
if (isset($hit['highlight'])) {
$one->highlight = $hit['highlight'];
}
return $one;
})->filter()->values();
}
...
...
}