Laravel-Docker 环境下使用 Elasticsearch 总结

Elasticsearch提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

参考

环境需求

  • PHP >= 7.0
  • ext-json >=1.3.7
  • docker下lnmp + Elasticsearch

继续阅读需具备以下知识、能力

  • composer
  • laravel迁移
  • docker(不会需手动安装java环境)
  • 动手能力

Begin!

准备工作

  • 假设你laravel环境已经搭建好,访问 / 看到laravel默认页面。
  • 本地lnmp + Elasticsearch 已经搭建并运行。
  • curl本地Elasticsearch响应正常。

安装官方提供的扩展包

composer require elasticsearch/elasticsearch:~6.0
生成控制器、迁移文件
php artisan make:model Article -mc 
表字段&生成表
public function up()
{
 Schema::create('articles', function (Blueprint $table) {
  $table->bigIncrements('id');
  $table->string('author')->comment('作者;')->nullable();
  $table->string('title')->comment('书名')->nullable();
  $table->text('body')->comment('内容')->nullable();
  $table->string('countries')->comment('国家')->nullable();
  $table->string('desc')->comment('简介')->nullable();
  $table->timestamps();
 });
 }

 // 生成表
php artisan migrate
填充数据
php artisan make:seeder ArticleTableSeeder
    public function run()
    {
        \Illuminate\Support\Facades\DB::table('articles')->insert([
            [
                'author'    =>  '尼古拉·奥斯特洛夫斯基',
                'title'     =>  '钢铁是怎么练成的',
                'body'      =>  '',
                'countries' =>  '苏联',
                'desc'      =>  '本书是一部闪烁着崇高理想主义光芒的半自传体长篇小说,小说成功地塑造了保尔·柯察金这一无产阶级英雄形象,以生动而又富于生活气息的语言、震撼人心的精神力量,展现了保尔·柯察金的生活和情感世界,成为了'
            ]
        ...more
        ]);
    }

调用 Seeders

DatabaseSeeder 类中,你可以使用 call 方法来运行其他的 seed 类。使用 call 方法可以将数据填充拆分成多个文件,这样就不会使单个 seeder 变得非常大。只需简单传递要运行的 seeder 类名称即可:

public function run()
{
    $this->call([
        ArticleTableSeeder::class,
    ]);
}

运行 Seeders

完成 seeder 类的编写之后,你可能需要使用 dump-autoload 命令重新生成 Composer 的自动加载器:

composer dump-autoload

接着就可以使用 Artisan 命令 db:seed 来填充数据库了。默认情况下,db:seed 命令将运行 DatabaseSeeder 类,这个类可以用来调用其它 Seed 类。不过,你也可以使用 --class 选项来指定一个特定的 seeder 类:

php artisan db:seed

php artisan db:seed --class=ArticleTableSeeder

数据检查

开始干货

绑定别名,方便后面使用
// app/Providers/AppServiceProvider.php
use Elasticsearch\ClientBuilder;
class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $this->registerEsService();
    }

    protected function registerEsService()
    {
        $this->app->singleton('es',function(){
            $host = [
                // 容器内部通信端口,docker知识
                'elasticsearch:9200'
            ];

            return ClientBuilder::create()->setHosts($host)->build();
        });
    }
}
创建索引(和我们使用mysql一样创建表结构) es 官方文档字段
   protected $index = 'article_index';  // 索引名字

    protected $type = 'article_type';   // 索引类型

    public function createIndex()
    {
        $result = ((new EsService)->createIndex());

        dd($result);

//        array:3 [▼
//          "acknowledged" => true
//          "shards_acknowledged" => true
//          "index" => "article_index"
//        ]
    }


    public function createIndex()
    {
        //  创建索引
        $params = [
            'index' =>  $this->index,
            'body'  =>  [
                'settings' => [
                    'number_of_shards' => 1,
                    'number_of_replicas' => 2
                ],
                'mappings'  =>  [
                    $this->type   =>  [
                        '_source' => [
                            'enabled' => true
                        ],
                        'properties' => [
                            'created_at' => [
                                'type' => 'date',
                                "format" => "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||yyyy-MM||yyyy/MM/dd||yyyy/MM||strict_date_optional_time||epoch_millis"
                            ],
                            'author'    =>  [
                                'type'  =>  'text',
                            ],
                            'body'    =>  [
                                'type'  =>  'text',
                            ],
                            'countries'    =>  [
                                'type'  =>  'text',
                            ],
                            'desc'    =>  [
                                'type'  =>  'text',
                            ],
                        ]
                    ]
                ]
            ]
        ];

        return app('es')->indices()->create($params);
    }
向es写入数据
    public function run()
    {
        $ids = [4,5,6];

        $data = Article::whereIn('id',$ids)->get();

        $result = (new EsService)->add($data,['created_at']);

        dd($result);  // 只需关注 errors

//        array:3 [▼
//          "took" => 15
//          "errors" => false
//          "items" => array:3 [▼
//            0 => array:1 [▼
//              "index" => array:9 [▼
//                "_index" => "article_index"
//                "_type" => "article_type"
//                "_id" => "8yik83EBzOhuf1N0cyBT"
//                "_version" => 1
//                "result" => "created"
//                "_shards" => array:3 [▼
//                  "total" => 3
//                  "successful" => 1
//                  "failed" => 0
//                ]
//                "_seq_no" => 0
//                "_primary_term" => 1
//                "status" => 201
//              ]
//            ]
//            1 => array:1 [▼
//              "index" => array:9 [▶]
//            ]
//            2 => array:1 [▶]
//          ]
//        ]
    }


    public function add($data,$field = [])
    {

        foreach ($data as $k => $v){

            $temp = [];
            foreach ($field as $kk => $vv){
                $temp[$vv] = $v->$vv;
            }

            //  不传入_id会帮我们自动生成
            $params['body'][] = [
                'index' => [
                    '_index'    =>  $this->index,
                    '_type'     =>  $this->type
                ]
            ];
            $params['body'][] = $temp;
        }

        $responses = app('es')->bulk($params);

        dd($responses);
    }
搜索数据
1.搜索全部数据
    public function search()
    {
        $type = 'all';

        $data = (new EsService)->search($type);

        dd($data);
    }

    public function search($type,$conditions = [])
    {
        switch ($type){
            case 'all':

                $params = [
                    'index' => $this->index,
                    'type'  => $this->type,
                ];

                $response =  app('es')->search($params);
                return $response;
        }
    }

2.搜索某个字段的数据(针对某个字段)

注意:这个时候如果有个作者名字是:林林汉达也会被搜索到。
手动添加一条数据作者为:林林汉达,调用上面到run()方法写入es,查看下结果。
try

    public function search()
    {
        $type = 'q';

        $q = [
            'query' =>  [
                'match' =>  [
                    //  搜索作者字段包含林汉达的
                    'author'    =>  '林汉达'
                ]
            ]
        ];

        $data = (new EsService)->search($type,$q);

        dd($data);
    }

    public function search($type,$conditions = [])
    {
        $params = [
            'index' => $this->index,
            'type'  => $this->type,
        ];

        switch ($type){
            case 'q':
                $params['body'] = $conditions;
                return  app('es')->search($params);

        }
    }

3.搜索国家为中国的数据

注意生成索引的时候用的keyword,解决上面林林汉达问题。

4. 搜索排序

这个时候我们想按照时间越大大排在前面(没有_score的影响下 )(倒序)

    public function search()
    {
        /*按时间倒序排序*/
        $type = 'sort';
        $q = [
            "sort"  =>  [
                "created_at"    =>  'desc'
            ]
        ];
        $data = (new EsService)->search($type,$q);
        dd($data);
    }

    public function search($type,$conditions = [])
    {
        $params = [
            'index' => $this->index,
            'type'  => $this->type,
        ];

        switch ($type){
            case 'sort':
                $params['body'] = $conditions;
                return  app('es')->search($params);
        }
    }

可以看到,es都搜索非常灵活,也可以带条件组合搜索。es中文文档

分页
方式1. size搜索数量,from跳过第几个
    public function search()
    {

        $type = 'page';
        $q = [
            'size'      =>  1,
            'from'      =>  1,
            "body" => [ // 条件,可以去掉
                "query" => [
                    "match_all" => new \stdClass()
                ]
            ]
        ];

        $data = (new EsService)->search($type,$q);

        dd($data);
    }

    public function search($type,$conditions = [])
    {
        $params = [
            'index' => $this->index,
            'type'  => $this->type,
        ];

        switch ($type){
            case 'page':
                $c = array_merge($params,$conditions);
                return  app('es')->search($c);
        }
    }

方式2

使用游标,比较简单,复制官方例子就可以跑起来,不在示范。


删除索引(危险,不是单条删除,类似mysql删库)

      public function deleteIndex()
     { 
         $result = ((new EsService)->deleteIndex());

         dd($result);
        //        array:1 [▼
        //          "acknowledged" => true
        //        ]
      }

    public function deleteIndex($index = '')
    {
        $deleteParams = [
            'index' => $index == '' ? $this->index : $index,
        ];

        return app('es')->indices()->delete($deleteParams);
    }

总结: Elasticsearch PHP Sdk对使用者非常友好,强烈建议使用者 先快速看下es中文版文档(1-2个小时),在看下Elasticsearch PHP Sdk(20分钟)即可上手使用。

最后特别谢谢 DNMP(Docker + Nginx + MySQL + PHP7/5 + Redis)是一款全功能的LNMP一键安装程序, 让使用如此简单。

核心代码码云

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!