[教程二] 写一个搜索:解决搜索结果高亮问题,使用 Laravel Scout,Elasticsearch,ik 分词
代码:https://github.com/lijinma/laravel-scout-e... ,欢迎 Star
接着第一篇:博客:[教程一] 写一个搜索:使用 Laravel Scout,Elasticsearch,ik 分词 ,在这篇文章中,我要实现“搜索结果高亮“。
你先看看搜索结果高亮的效果:
http://scout.lijinma.com/search?query=%E8%...
我们知道 Laravel Scout 的 search 结果直接是一个一个对象,并没有提供搜索结果高亮功能,这个时候我们有两条路可以解决我们的问题:
- 搜索的时候不使用 Scout 提供的 search 方法,直接调用原生的 ElasticSearch 接口来做,搜索后自己组装需要的属性,这肯定是一条路,在我们没有 Laravel Scout 的时候确实也是这么做的,但这样写虽然带来了灵活性,但是我们要自己写的代码还是有点多。
- 第二条路,就是我们想办法修改 Scout ElasticSearch Engine,来满足我们的高亮需求。
我选择了第二条路:
1. 自定义 ElasticSearch Engine
首先,创建文件:app/Libraries/EsEngine.php
继承 ElasticsearchEngine,按照我们的需求添加或修改代码:
<?php namespace App\Libraries;
use Laravel\Scout\Builder;
use ScoutEngines\Elasticsearch\ElasticsearchEngine;
class EsEngine extends ElasticsearchEngine
{
public function search(Builder $builder)
{
$result = $this->performSearch($builder, array_filter([
'numericFilters' => $this->filters($builder),
'size' => $builder->limit,
]));
return $result;
}
/**
* Perform the given search on the engine.
*
* @param Builder $builder
* @return mixed
*/
protected function performSearch(Builder $builder, array $options = [])
{
$params = [
'index' => $this->index,
'type' => $builder->model->searchableAs(),
'body' => [
'query' => [
'bool' => [
'must' => [
[
'query_string' => [
'query' => "{$builder->query}",
]
]
]
]
],
]
];
/**
* 这里使用了 highlight 的配置
*/
if ($builder->model->searchSettings
&& isset($builder->model->searchSettings['attributesToHighlight'])
) {
$attributes = $builder->model->searchSettings['attributesToHighlight'];
foreach ($attributes as $attribute) {
$params['body']['highlight']['fields'][$attribute] = new \stdClass();
}
}
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']);
}
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 (count($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']];
/**
* 这里返回的数据,如果有 highlight,就把对应的 highlight 设置到对象上面
*/
if (isset($hit['highlight'])) {
$one->highlight = $hit['highlight'];
}
return $one;
});
}
}
2. 替换掉 Scout 的 Engine 为我们创建的 EsEngine
修改 app/Providers/AppServiceProvider.php
use App\Libraries\EsEngine;
use Laravel\Scout\EngineManager;
use Elasticsearch\ClientBuilder as ElasticBuilder;
public function boot()
{
resolve(EngineManager::class)->extend('es', function($app) {
return new EsEngine(ElasticBuilder::create()
->setHosts(config('scout.elasticsearch.hosts'))
->build(),
config('scout.elasticsearch.index')
);
});
}
3. 添加 Model 需要的属性
为了方便一个 Model 是否在搜索的时候使用高亮,我们把这些代码抽出来写一个 Trait,使用的时候再 use
创建 Trait app/Libraries/EsSearchable.php
<?php namespace App\Libraries;
trait EsSearchable
{
public $searchSettings = [
'attributesToHighlight' => [
'*'
]
];
public $highlight = [];
}
Post.php Model 中使用 trait:
use Searchable, EsSearchable;
4. 修改 view 满足我们的显示
修改 resources/views/search.blade.php ,查看高亮。
@extends('layouts.main')
@section('content')
<div class="row">
<div class="col-md-12">
<form action="/search">
<div class="input-group">
<input type="text" class="form-control h50" name="query" placeholder="关键字..." value="{{ $q }}">
<span class="input-group-btn"><button class="btn btn-default h50" type="submit" type="button"><span class="glyphicon glyphicon-search"></span></button></span>
</div>
</form>
</div>
</div>
@if($q)
<div class="row">
<div class="col-md-12">
<div class="panel panel-default list-panel search-results">
<div class="panel-heading">
<h3 class="panel-title ">
<i class="fa fa-search"></i> 关于 “<span class="highlight">{{ $q }}</span>” 的搜索结果, 共 {{ $paginator->total() }} 条
</h3>
</div>
<div class="panel-body ">
[@foreach](https://learnku.com/users/5651)($paginator as $post)
<div class="result">
<h2 class="title">
<a href="{{ $post->url }}" target="_blank">
@if (isset($post->highlight['title']))
[@foreach](https://learnku.com/users/5651) ($post->highlight['title'] as $item)
{!! $item !!}
@endforeach
@else
{{ $post->title }}
@endif
</a>
</h2>
<div class="info">
</div>
<div class="desc">
@if (isset($post->highlight['content']))
[@foreach](https://learnku.com/users/5651) ($post->highlight['content'] as $item)
......{!! $item !!}......
@endforeach
@else
{{ mb_substr($post->content, 0, 150) }}......
@endif
</div>
<hr>
</div>
@endforeach
</div>
{{ $paginator->links() }}
</div>
</div>
</div>
@else
<div class="row text-center">
<div class="col-md-12">
<br>
<h2>你会搜索到什么?</h2>
<br>
<p>学习学习再学习公众号所有文章</p>
</div>
</div>
@endif
@endsection
样式可以抄一抄 Laravel China 的搜索结果...
public/css/main.css
#app {
margin-top: 20px;
}
.h50 {
height: 50px;
}
.search-results {
margin-top: 20px;
padding: 20px;
line-height: 25px;
}
.search-results .panel-heading h3 {
color: #696969;
font-size: 15px;
margin-bottom: 12px;
}
.search-results a {
color: #333;
}
.search-results .result {
margin-bottom: 20px;
}
.search-results .user.result {
margin-top: 8px;
margin-bottom: 0px;
}
.search-results .result em {
color: #EB5424;
font-style: normal;
}
.search-results .result .title {
font-size: 18px;
}
.search-results .result .title .badge {
background: #EBEDEE;
color: #9A9DA0;
font-weight: normal;
font-size: 12px;
margin-left: 4px;
}
.search-results .result .info {
margin-bottom: 6px;
font-size: 14px;
}
.search-results .result .info .url a {
color: #23863F;
}
.search-results .result .info .date {
color: #999;
margin-left: 8px;
}
.search-results .result .desc {
color: #666;
font-size: 14px;
word-break: break-all;
}
.search-results .result .desc em {
color: #F86334;
}
.search-results .user .info {
margin-top: 4px;
font-size: 14px;
}
.search-results .user .info.number {
color: #666;
font-size: 13px;
}
.search-results em {
color: #e07b7a;
}
.search-results .role-label {
display: inline-block;
position: absolute;
}
.search-results .role-label a.label {
font-size: 85%;
font-weight: 100;
padding: 0.2em 1em .2em;
position: relative;
margin: 8px;
color: #fff;
}
.search-results .user-info {
padding-top: 8px;
padding-left: 8px;
}
.search-results hr {
margin-top: 15px;
margin-bottom: 15px;
}
.search-results .list-panel .panel-body {
padding: 0px;
}
好了,这样就解决了高亮的问题了。
别忘记 Star 我的 demo 哦,我要尽可能写的清楚。https://github.com/lijinma/laravel-scout-e...
我为什么这么做?
因为我看别人也这么做: https://github.com/laravel/scout/pull/19
本作品采用《CC 协议》,转载必须注明作者和本文链接
高产,要把一个月文章都写了吗
@Summer 哈哈,过年的时候就想写,无奈一直拖延,这次写完,还有一篇,等下发。
就是喜欢看着你折腾
@JokerLinly 因为这是我这样的小渣渣的进击之路。
虽然我没有认真看,还是点个赞,写文章这件事情坚持下来挺不容易的。
大学的时候坚持写,毕业工作之后基本一个月一篇了。
这个教程上没有说明 AppServiceProvider 里面的添加逻辑。
@RryLee 那也很厉害啊。
@jxlwqq 太感谢了。。谢谢你。。。已经改了。
@lijinma 哈哈哈,过奖了,你这个是高产似母猪,:smile:
@RryLee 哈哈,谢谢夸奖。
@lijinma 感觉还是漏了一点什么,我这边测试高亮无效果。
@jxlwqq 替换掉 Scout 的 Engine 为我们创建的 EsEngine了吗?
@lijinma AppServiceProvider 里面的逻辑已经加了。对了,我的版本是 lavarel 5.3 的,是否是版本的原因。
还有个建议。使用md5(url)来判断是否抓过比较好。长度能比url短,而且位数也统一
ElasticSearch默认的用户名和密码
用户名:elastic
密码:changeme
如果要使用本demo需要跑通.需要去掉授权的(实际场景不建议去掉api的auth验证)。也就是去掉x-pack.
命令:./bin/elasticsearch-plugin remove x-pack
然后重启elasticsearch
在提一个bug。如果在搜索中输入带有/,/hello就会报错
@jxlwqq 5.3应该没问题,你重新 index 下数据,flush 一下,再 index 一下?
@Will 非常感谢你。我修复一下。
@lijinma 不客气。也感谢你提供了这个demo。可以作为Laravel Scout学习的案例。
@lijinma 好的,谢谢,我再试试。
现在搜索可以了,按着这个教程一步步写完还是没有高亮显示,索引也flush了也重新import了,是不是漏掉了什么?
可以正常搜索,但是没有高亮显示,发现EsEngin中重写的方法都没有执行,是不是哪里少配置了?
@lijinma 我感觉替换你写的那个搜索引擎有问题啊,没有成功,高亮没效果
@JiberBoom 没有高亮效果是因为博主在
App\Providers\AppServiceProvider
里重新定义了搜索引擎为es
,那么在scout.php
里面的配置就要改成'driver' => env('SCOUT_DRIVER', 'es')
@JohnsonChung 你这样做成功了没
@JohnsonChung 对滴 完美运行
这个 total 本来是一个整型,应该去掉 count() 方法。
老铁,如何实现多表链接查询?
太感谢了,照着你的思路写,可以了 :smile: