[教程二] 寫一個搜尋:解決搜尋結果高亮問題,使用 Laravel Scout,Elasticsearch,ik 分詞

lijinma發表於2017-03-06

程式碼:https://github.com/lijinma/laravel-scout-e... ,歡迎 Star

接著第一篇:部落格:[教程一] 寫一個搜尋:使用 Laravel Scout,Elasticsearch,ik 分詞 ,在這篇文章中,我要實現“搜尋結果高亮“。

你先看看搜尋結果高亮的效果:

http://scout.lijinma.com/search?query=%E8%...

我們知道 Laravel Scout 的 search 結果直接是一個一個物件,並沒有提供搜尋結果高亮功能,這個時候我們有兩條路可以解決我們的問題:

  1. 搜尋的時候不使用 Scout 提供的 search 方法,直接呼叫原生的 ElasticSearch 介面來做,搜尋後自己組裝需要的屬性,這肯定是一條路,在我們沒有 Laravel Scout 的時候確實也是這麼做的,但這樣寫雖然帶來了靈活性,但是我們要自己寫的程式碼還是有點多。
  2. 第二條路,就是我們想辦法修改 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 協議》,轉載必須註明作者和本文連結
寫文字大部分時候是因為我希望能幫助到你,小部分時候是想做總結或做記錄。我的微信是 lijinma,希望和你交朋友。 以下是我的公眾賬號,會分享我的學習和成長。

相關文章