laravel Es的封裝與使用

QQ糖的音符發表於2022-03-10

開啟Elasticsearch

elasticsearch -d

開啟Kibana

kibana的 bin 目錄下kibana.bat  雙擊

使用composer安裝:

composer require elasticsearch/elasticsearch

配置php.ini

配置php.ini的sys_temp_dir

laravel Es的封裝

;sys_temp_dir = "/tmp"
sys_temp_dir = "D:\phpstudy_pro\Extensions\tmp\tmp"

否則,使用過程中可能會出現以下報錯

laravel Es的封裝

建立索引

$es = \Elasticsearch\ClientBuilder::create()->setHosts(['127.0.0.1:9200'])->build();
$params = [
    'index' => 'test_index'
];
$r = $es->indices()->create($params);
dump($r);die;

預期結果:

array(3) {
  ["acknowledged"] => bool(true)
  ["shards_acknowledged"] => bool(true)
  ["index"] => string(10) "test_index"
}

新增文件(索引文件)

$es = \Elasticsearch\ClientBuilder::create()->setHosts(['127.0.0.1:9200'])->build();
$params = [
    'index' => 'test_index',
    'type' => 'test_type',
    'id' => 100,
    'body' => ['id'=>100, 'title'=>'PHP從入門到精通', 'author' => '張三']
];

$r = $es->index($params);
dump($r);die;

預期結果:

array(8) {
  ["_index"] => string(10) "test_index"
  ["_type"] => string(9) "test_type"
  ["_id"] => string(3) "100"
  ["_version"] => int(1)
  ["result"] => string(7) "created"
  ["_shards"] => array(3) {
    ["total"] => int(2)
    ["successful"] => int(1)
    ["failed"] => int(0)
  }
  ["_seq_no"] => int(0)
  ["_primary_term"] => int(1)
}

修改文件

$es = \Elasticsearch\ClientBuilder::create()->setHosts(['127.0.0.1:9200'])->build();
$params = [
    'index' => 'test_index',
    'type' => 'test_type',
    'id' => 100,
    'body' => [
        'doc' => ['id'=>100, 'title'=>'ES從入門到精通', 'author' => '張三']
    ]
];

$r = $es->update($params);
dump($r);die;

預期結果:

array(8) {
  ["_index"] => string(10) "test_index"
  ["_type"] => string(9) "test_type"
  ["_id"] => string(3) "100"
  ["_version"] => int(2)
  ["result"] => string(7) "updated"
  ["_shards"] => array(3) {
    ["total"] => int(2)
    ["successful"] => int(1)
    ["failed"] => int(0)
  }
  ["_seq_no"] => int(1)
  ["_primary_term"] => int(1)
}

刪除文件

$es = \Elasticsearch\ClientBuilder::create()->setHosts(['127.0.0.1:9200'])->build();
$params = [
    'index' => 'test_index',
    'type' => 'test_type',
    'id' => 100,
];

$r = $es->delete($params);
dump($r);die;

預期結果:

array(8) {
  ["_index"] => string(10) "test_index"
  ["_type"] => string(9) "test_type"
  ["_id"] => string(3) "100"
  ["_version"] => int(3)
  ["result"] => string(7) "deleted"
  ["_shards"] => array(3) {
    ["total"] => int(2)
    ["successful"] => int(1)
    ["failed"] => int(0)
  }
  ["_seq_no"] => int(2)
  ["_primary_term"] => int(1)
}

封裝工具類

<?php

namespace App\Http\tools;

use App\Models\Fang;
use Elasticsearch\ClientBuilder;
use Elasticsearch\Common\Exceptions\BadRequest400Exception;

class Es
{
    //ES客戶端連結
    private $client;

    /**
     * 建構函式
     * MyElasticsearch constructor.
     */
    public function __construct()
    {
        $params = array(
            '127.0.0.1:9200'
        );
        $this->client = ClientBuilder::create()->setHosts($params)->build();
    }

    /**
     * 判斷索引是否存在
     * @param string $index_name
     * @return bool|mixed|string
     */
    public function exists_index($index_name = 'test_ik')
    {
        $params = [
            'index' => $index_name
        ];

        try {
            return $this->client->indices()->exists($params);
        } catch (BadRequest400Exception $e) {
            $msg = $e->getMessage();
            $msg = json_decode($msg,true);
            return $msg;
        }
    }

    /**
     * 建立索引
     * @param string $index_name
     * @return array|mixed|string
     */
    public function create_index($index_name = 'test_ik') { // 只能建立一次
        $params = [
            'index' => $index_name,
            'body' => [
                'settings' => [
                    'number_of_shards' => 5,
                    'number_of_replicas' => 0
                ]
            ]
        ];

        try {
            return $this->client->indices()->create($params);
        } catch (BadRequest400Exception $e) {
            $msg = $e->getMessage();
            $msg = json_decode($msg,true);
            return $msg;
        }
    }

    /**
     * 刪除索引
     * @param string $index_name
     * @return array
     */
    public function delete_index($index_name = 'test_ik') {
        $params = ['index' => $index_name];
        $response = $this->client->indices()->delete($params);
        return $response;
    }

    /**
     * 新增文件
     * @param $id
     * @param $doc ['id'=>100, 'title'=>'phone']
     * @param string $index_name
     * @param string $type_name
     * @return array
     */
    public function add_doc($id,$doc,$index_name = 'test_ik',$type_name = 'goods') {
        $params = [
            'index' => $index_name,
            'type' => $type_name,
            'id' => $id,
            'body' => $doc
        ];

        $response = $this->client->index($params);
        return $response;
    }

    /**
     * 判斷文件存在
     * @param int $id
     * @param string $index_name
     * @param string $type_name
     * @return array|bool
     */
    public function exists_doc($id = 1,$index_name = 'test_ik',$type_name = 'goods') {
        $params = [
            'index' => $index_name,
            'type' => $type_name,
            'id' => $id
        ];

        $response = $this->client->exists($params);
        return $response;
    }

    /**
     * 獲取文件
     * @param int $id
     * @param string $index_name
     * @param string $type_name
     * @return array
     */
    public function get_doc($id = 1,$index_name = 'test_ik',$type_name = 'goods') {
        $params = [
            'index' => $index_name,
            'type' => $type_name,
            'id' => $id
        ];
        $response = $this->client->get($params);
        return $response;
    }

    /**
     * 更新文件
     * @param int $id
     * @param string $index_name
     * @param string $type_name
     * @param array $body ['doc' => ['title' => '蘋果手機iPhoneX']]
     * @return array
     */
    public function update_doc($id = 1,$body=[],$index_name = 'test_ik',$type_name = 'goods' ) {
        // 可以靈活新增新欄位,最好不要亂新增
        $params = [
            'index' => $index_name,
            'type' => $type_name,
            'id' => $id,
            'body' => $body
        ];

        $response = $this->client->index($params);
        return $response;
    }

    /**
     * 刪除文件
     * @param int $id
     * @param string $index_name
     * @param string $type_name
     * @return array
     */
    public function delete_doc($id = 1,$index_name = 'test_ik',$type_name = 'goods') {
        $params = [
            'index' => $index_name,
            'type' => $type_name,
            'id' => $id
        ];

        $response = $this->client->delete($params);
        return $response;
    }

    /**
     * 搜尋文件 (分頁,排序,權重,過濾)
     * @param string $index_name
     * @param string $type_name
     * @param array $body
     * $body = [
            'query' => [
                'bool' => [
                    'should' => [
                        [
                            'match' => [
                                'cate_name' => [
                                     'query' => $keywords,
                                     'boost' => 4, // 權重大
                                 ]
                            ]
                         ],
                          [
                               'match' => [
                                    'goods_name' => [
                                         'query' => $keywords,
                                         'boost' => 3,
                                    ]
                               ]
                          ],
                           [
                                 'match' => [
                                     'goods_introduce' => [
                                          'query' => $keywords,
                                          'boost' => 2,
                                     ]
                                 ]
                           ]
                        ],
                     ],
                 ],
                 'sort' => ['id'=>['order'=>'desc']],
                 'from' => $from,
                 'size' => $size
    ];
     * @return array
     */
    public function search_doc($body=[],$index_name = "test_ik",$type_name = "goods") {
        $params = [
            'index' => $index_name,
            'type' => $type_name,
            'body' => $body
        ];

        $results = $this->client->search($params);
        return $results;
    }

    /**
     * 查詢文件
     * @param $search
     * @param string $index_name
     * @param string $type_name
     * @return array
     */
    public function search_show($search,$index_name = "test_ik",$type_name = "goods"){
        if (empty($search)){
            $data=Fang::with(['fangLease','fangFangOwner','fangCity'])->get()->toArray();
            return $data;
        }
        $params=[
            'index' => $index_name,
            'type' => $type_name,
            'body'=>[
                'query'=>[
                    'match'=>[
                        'fang_name'=>[
                            'query'=>$search
                        ]
                    ]
                ],
                'highlight'=>[
                    'fields'=>[
                        'fang_name'=>[
                            'pre_tags'=>[
                                '<span style="color: red">'
                            ],
                            'post_tags'=>[
                                '</span>'
                            ]
                        ]
                    ]
                ]
            ]
        ];
        $results=$this->client->search($params);
        $data=[];
        foreach ($results['hits']['hits'] as $val){
            array_push($data,$val['_source']);
        }
        return $data;
    }
}

商品搜尋功能

搜尋規則

可根據關鍵詞對商品名稱、商品介紹、商品分類進行全文搜尋

建立商品全量索引

專案目錄下App\Http\Controllers\Es.php

<?php

namespace App\Http\Controllers;

use App\Model\User;
use Illuminate\Http\Request;

class Es extends Controller
{
    /**
     * 建立商品索引並匯入全部商品文件
     * cd public
     * php index.php /cli/Es/createAllGoodsDocs
     */
    public function createAllGoodsDocs()
    {
        try{
            //例項化ES工具類
            $es = new \App\Http\tools\Es();
            //建立索引
            if($es->exists_index('goods_index')) $es->delete_index('goods_index');

            $es->create_index('goods_index');
            $i = 0;
            while(true){
                //查詢商品資料 每次處理1000條
                $goods = User::with('category')->field('id,goods_name,goods_desc, goods_price,goods_logo,cate_id')->limit($i, 1000)->select();
                if(empty($goods)){
                    //查詢結果為空,則停止
                    break;
                }
                //新增文件
                foreach($goods as $v){
                    unset($v['cate_id']);
                    $es->add_doc($v['id'],$v, 'goods_index', 'goods_type');
                }
                $i += 1000;
            }
            die('success');
        }catch (\Exception $e){
            $msg = $e->getMessage();
            die($msg);
        }
    }
}

切換到public目錄 執行命令

php index.php  /Http/Controllers/Es/createAllGoodsDocs

注:其中,使用了封裝的ES工具類 : 專案目錄/App/Http/tools/Es.php

搜尋

頁面部分

html
<form action="{:url('home/goods/index')}" method="get" class="sui-form form-inline">
    <!--searchAutoComplete-->
    <div class="input-append">
        <input type="text" id="autocomplete" class="input-error input-xxlarge" name="keywords" value="{$Request.param.keywords}" />
        <button class="sui-btn btn-xlarge btn-danger" type="submit">搜尋</button>
    </div>
</form>

控制器部分

public function index($id=0)
    {
        //接收引數
        $keywords = input('keywords');
        if(empty($keywords)){
            //獲取指定分類下商品列表
            if(!preg_match('/^\d+$/', $id)){
                $this->error('引數錯誤');
            }
            //查詢分類下的商品
            $list = \app\common\model\Goods::where('cate_id', $id)->order('id desc')->paginate(10);
            //查詢分類名稱
            $category_info = \app\common\model\Category::find($id);
            $cate_name = $category_info['cate_name'];
        }else{
            try{
                //從ES中搜尋
                $list = \app\home\logic\GoodsLogic::search();
                $cate_name = $keywords;
            }catch (\Exception $e){
                $this->error('伺服器異常');
            }
        }
        return view('index', ['list' => $list, 'cate_name' => $cate_name]);
    }

搜尋邏輯部分

<?php

namespace app\home\logic;

use think\Controller;

class GoodsLogic extends Controller
{
    public static function search(){
        //例項化ES工具類
        $es = new \tools\es\MyElasticsearch();
        //計算分頁條件
        $keywords = input('keywords');
        $page = input('page', 1);
        $page = $page < 1 ? 1 : $page;
        $size = 10;
        $from = ($page - 1) * $size;
        //組裝搜尋引數體
        $body = [
            'query' => [
                'bool' => [
                    'should' => [
                        [ 'match' => [ 'cate_name' => [
                            'query' => $keywords,
                            'boost' => 4, // 權重大
                        ]]],
                        [ 'match' => [ 'goods_name' => [
                            'query' => $keywords,
                            'boost' => 3,
                        ]]],
                        [ 'match' => [ 'goods_desc' => [
                            'query' => $keywords,
                            'boost' => 2,
                        ]]],
                    ],
                ],
            ],
            'sort' => ['id'=>['order'=>'desc']],
            'from' => $from,
            'size' => $size
        ];
        //進行搜尋
        $results = $es->search_doc('goods_index', 'goods_type', $body);
        //獲取資料
        $data = array_column($results['hits']['hits'], '_source');
        $total = $results['hits']['total']['value'];
        //分頁處理
        $list = \tools\es\EsPage::paginate($data, $size, $total);
        return $list;
    }
}

ES分頁類

借鑑模型的分頁查詢方法,封裝用於ES搜尋的分頁類: 專案目錄/App/Http/tools/EsPage.php

<?php
namespace tools\es;

use think\Config;

class EsPage
{

    public static function paginate($results, $listRows = null, $simple = false, $config = [])
    {
        if (is_int($simple)) {
            $total  = $simple;
            $simple = false;
        }else{
            $total = null;
            $simple = true;
        }

        if (is_array($listRows)) {
            $config   = array_merge(Config::get('paginate'), $listRows);
            $listRows = $config['list_rows'];
        } else {
            $config   = array_merge(Config::get('paginate'), $config);
            $listRows = $listRows ?: $config['list_rows'];
        }

        /** @var Paginator $class */
        $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']);
        $page  = isset($config['page']) ? (int) $config['page'] : call_user_func([
            $class,
            'getCurrentPage',
        ], $config['var_page']);

        $page = $page < 1 ? 1 : $page;

        $config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, 'getCurrentPath']);

        return $class::make($results, $listRows, $page, $total, $simple, $config);
    }
}

商品列表頁 商品分類展示位置

laravel Es的封裝

商品文件維護

新增商品後,在ES中新增商品文件
更新商品後,在ES中修改商品文件
刪除商品後,在ES中刪除商品文件
使用MVC的後臺測試,則在app/Models/Fang.php中
使用前後端分離介面api測試,則寫在app/Models/Fang.php中

專案目錄/app/Models/Fang.php中,init方法程式碼如下:

protected static function init()
    {
        //例項化ES工具類
        $es = new \tools\es\MyElasticsearch();
        //設定新增回撥
        self::afterInsert(function($goods)use($es){
            //新增文件
            $doc = $goods->visible(['id', 'goods_name', 'goods_desc', 'goods_price'])->toArray();
            $doc['cate_name'] = $goods->category->cate_name;
            $es->add_doc($goods->id, $doc, 'goods_index', 'goods_type');
        });
        //設定更新回撥
        self::afterUpdate(function($goods)use($es){
            //修改文件
            $doc = $goods->visible(['id', 'goods_name', 'goods_desc', 'goods_price', 'cate_name'])->toArray();
            $doc['cate_name'] = $goods->category->cate_name;
            $body = ['doc' => $doc];
            $es->update_doc($goods->id, 'goods_index', 'goods_type', $body);
        });
        //設定刪除回撥
        self::afterDelete(function($goods)use($es){
            //刪除文件
            $es->delete_doc($goods->id, 'goods_index', 'goods_type');
        });
    }

另一種方法

框架內執行安裝擴充套件

composer require elasticsearch/elasticsearch

建立索引

public function createIndex(){
        // 建立索引
        $client = ClientBuilder::create()->setHosts(['127.0.0.1:9200'])->build();
        $params = [
            'index' => 'news',
            'body' => [
                'settings' => [
                    'number_of_shards' => 3,
                    'number_of_replicas' => 2
                ],
                'mappings' => [
                    '_source' => [
                        'enabled' => true
                    ],
                    'properties' => [
                        //  ‘title’ 欄位
                        'title' => [
                            'type' => 'text',
                            "analyzer" => "ik_max_word",
                            "search_analyzer" => "ik_max_word"
                        ],
                        'author' => [
                            'type' => 'text',
                            "analyzer" => "ik_max_word",
                            "search_analyzer" => "ik_max_word"
                        ]
                    ]
                ]
            ]
        ];
        // Create the index with mappings and settings now
        $response = $client->indices()->create($params);
        return $response;
    }

將資料同步到ES (這裡看你的邏輯是什麼)

 //封裝一個同步的方法,到時候直接呼叫該方法
public function  pushEsNews($data){
        $client = ClientBuilder::create()->setHosts(['127.0.0.1:9200'])->build();
            $params = [
                'index' => 'news',
                'type' => '_doc',
                'body' => $data
            ];
            $response = $client->index($params);
        return $response;
    }

搜尋後高亮顯示

public function show(request $request){
        //搜尋的值
        $seach = $request['seach'];
        $res = News::get()->toArray();
        //判斷使用者是否搜尋,如果沒有則跳過
        if($seach!=""){
            $client = ClientBuilder::create()->setHosts(['127.0.0.1:9200'])->build();
            $params = [
                'index' => 'news',
                'type' => '_doc',
                'body' => [
                    'query' => [
                        'bool' => [
                            'should' => [
                                [
                                    'match' => [
                                        'title' => "$seach",
                                    ]
                                ]
                            ]
                        ]
                    ],
                    'highlight' => [
                        'pre_tags' => ["<font color='red'>"],
                        'post_tags' => ["</font>"],
                        'fields' => [
                            "title" => new \stdClass()
                        ]
                    ]
                ]
            ];
            $response = $client->search($params);
            $data = $response['hits']['hits'];
            $res = [];
            foreach ($data as $v) {
                if (!empty($v['highlight']['title'][0])) {
                    $v['_source']['title'] = $v['highlight']['title'][0];
                }
                array_push($res, $v['_source']);
            }
        }
        return view('news_show',compact('res','seach'));
    }

最終效果:

laravel Es的封裝與使用

總結

1.分散式全文搜尋解決方案:是基於Mysql資料庫 、 Hadoop生態(可選)、 ElasticSearch搜尋引擎三大資料系統實現一個分散式全文搜尋系統。

2.Mysql資料庫用於結構化儲存專案資料。

3.Hadoop生態用於備份關係型資料庫的所有版本,還儲存使用者行為,點選,曝光,互動等海量日誌資料,用於資料分析處理。

4.ElasticSearch搜尋引擎用於對Mysql或者Hadoop提供的資料進行索引和全文搜尋。

5.其中核心功能,包括全量建立索引、增量建立索引、實時同步資料(文件的curd)、全文搜尋等。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章