laravel使用elasticsearch

laravel 中安装扩展包

composer安装elasticsearch扩展包

Elasticsearch 官方提供了 Composer 包,在引入时需要注意要指定版本,因为不同版本的 Elasticsearch 的 API 略有不同,因为我用的是 7.12.x,因此需使用 ~7.12.x 来指定包版本。

composer require elasticsearch/elasticsearch "7.12.x" --ignore-platform-reqs

laravel 配置 es

config/database.php

'elasticsearch' => [// Elasticsearch 支持多台服务器负载均衡,因此这里是一个数组'hosts' => explode(',',env('ES_HOSTS')),
]

.env 配置

ES_HOSTS=172.17.0.8

初始化 Elasticsearch 对象,并注入到 Laravel 容器中:

App/Providers/AppServiceProvider.php

注释:在laravel容器中自定义一个名为es的服务对象,通过ESClientBuilder以及配置文件中的信息连接
到es,我们可以通过app(‘es’)->info()查看连接之后的es对象信息。

<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Elasticsearch\ClientBuilder as ESClientBuilder;class AppServiceProvider extends ServiceProvider
{/*** Register any application services.** @return void*/public function register(){// 注册一个名为 es 的单例$this->app->singleton('es',function (){// 从配置文件读取 Elasticsearch 服务器列表$builder = ESClientBuilder::create()->setHosts(config('database.elasticsearch.hosts'));// 如果是开发环境if (app()->environment()==='local'){// 配置日志,Elasticsearch 的请求和返回数据将打印到日志文件中,方便我们调试$builder->setLogger(app('log')->driver());}return $builder->build();});}/*** Bootstrap any application services.** @return void*/public function boot(){//}
}
?>

注册完成后,进行测试

php artisan tinker>>>app('es')->info();

结果如下:


创建索引

PUT /products/
{"mappings": {"properties": {"name":{"type": "text","analyzer": "ik_smart"},"long_name":{"type": "text","analyzer": "ik_smart"},"brand_id":{"type": "integer"},"category_id":{"type":"integer"},"category":{"type": "keyword"},"category_path":{"type": "keyword"},"shop_id":{"type":"integer"},"price":{"type":"scaled_float","scaling_factor":100},"sold_count":{"type":"integer"},"review_count":{"type":"integer"},"status":{"type":"integer"},"create_time" : {"type" : "date"},"last_time" : {"type" : "date"},"skus":{"type": "nested","properties": {"name":{"type":"text","analyzer": "ik_smart"},"price":{"type":"scaled_float","scaling_factor":100}}},"attributes":{"type": "nested","properties": {"name": { "type": "keyword" },"value": { "type": "keyword"}}}}}
}

laravel中使用es

通过laravel把数据从mysql同步到es中

这里只是演示如何把数据从mysql同步进入es中

在之前的logstash操作中,通过jdbc的方式从mysql同步数据到es中,这次试用laravel的方式同步数据到es中

1 . 创建索引

注:这里存在一个 时间字段 create_time 格式为 date 的问题,laravel从mysql获取到的时间格式插入到es中会出现无法解析的情况,需要对时间格式进行 “format”: “yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis” 的操作

PUT /test/
{"mappings": {"properties": {"id":{"type": "integer"},"name":{"type": "text","analyzer": "ik_smart"},"long_name":{"type": "text","analyzer": "ik_smart"},"brand_id":{"type": "integer"},"shop_id":{"type":"integer"},"price":{"type":"scaled_float","scaling_factor":100},"sold_count":{"type":"integer"},"review_count":{"type":"integer"},"status":{"type":"integer"},"create_time" : {"type" : "date","format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"},"last_time" : {"type" : "date","format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"},"skus":{"type": "nested","properties": {"name":{"type":"text","analyzer": "ik_smart"},"price":{"type":"scaled_float","scaling_factor":100}}},"attributes":{"type": "nested","properties": {"name": { "type": "keyword" },"value": { "type": "keyword"}}}}}
}

2 . 创建代码脚本

php artisan make:command Elasticsearch/SyncProducts

app/Console/Commands/Elasticsearch/SyncProducts.php

<?phpnamespace App\Console\Commands\Elasticsearch;use App\Models\Product;
use Illuminate\Console\Command;class SyncProducts extends Command
{/*** The name and signature of the console command.** @var string*/protected $signature = 'es:sync-products';/*** The console command description.** @var string*/protected $description = '将商品数据同步到 Elasticsearch';/*** Create a new command instance.** @return void*/public function __construct(){parent::__construct();}/*** Execute the console command.** @return mixed*/public function handle(){// 获取 Elasticsearch 对象$es = app('es');$sql = 'id,name,long_name,brand_id,three_category_id,shop_id,price,sold_count,review_count,status,create_time,last_time';Product::query()->selectRaw($sql)// 使用 chunkById 避免一次性加载过多数据->chunkById(100, function ($products) use ($es) {$this->info(sprintf('正在同步 ID 范围为 %s 至 %s 的商品', $products->first()->id, $products->last()->id));// 初始化请求体$req = ['body' => []];// 遍历商品foreach ($products as $product) {// 将商品模型转为 Elasticsearch 所用的数组$data = $product->toESArray($product->id,$product->three_category_id);$req['body'][] = ['index' => ['_index' => 'test','_id'    => $data['id'],],];$req['body'][] = $data;}try {// 使用 bulk 方法批量创建$es->bulk($req);} catch (\Exception $e) {$this->info($e->getMessage());}});$this->info('同步完成');}
}

3 . 创建Product的Model Product.php、进行数据过滤

<?phpnamespace App\Models;use Illuminate\Support\Arr;
use Illuminate\Database\Eloquent\Model;class Product extends Model
{public $table = "products";/*** 格式化为es的数据* @param $product_id* @param $category_id* @return array*/public function toESArray($product_id,$category_id){// 只取出需要的字段$arr = Arr::only($this->toArray(), ['id','name','long_name','brand_id','shop_id','price','sold_count','review_count','status','create_time','last_time']);$productSkus = ProductSkus::query()->selectRaw('name,price')->where('product_id',$product_id)->get()->toArray();// skus在索引中是一个二维数组, 这里只取出需要的 SKU 字段$arr['skus'] = $productSkus;$sql = "lmrs_at.name as name,lmrs_at_val.name as value";$attributes = Attributes::query()->selectRaw($sql)->from('attributes as at')->leftJoin('attribute_values as at_val','at.id','at_val.attribute_id')->where('at.category_id',$category_id)->get()->toArray();// attributes 在索引中是一个二维数组, 这里只取出需要的商品属性字段$arr['attributes'] = $attributes;return $arr;}
}

我们在写入商品数据的时候用的是 bulk() 方法,这是 Elasticsearch 提供的一个批量操作接口。设想一下假如我们系统里有数百万条商品,如果每条商品都单独请求一次 Elasticsearch 的 API,那就是数百万次的请求,性能肯定是很差的,而 bulk() 方法可以让我们用一次 API 请求完成一批操作,从而减少请求次数的数量级,提高整体性能。

4 . 运行脚本

php artisan es:sync-products


插入数据后


laravel中es的查询操作

1 . 添加工具类 PublicService.php

<?php
namespace App\Services;use App\Models\ProductCategory;class PublicService
{/*** 多维数组转换一维数组* @param $input* @param $flatten* @return array*/public static function getDataByEs($input){$total = $input['hits']['total']['value'];$input = $input['hits']['hits'];$data  = collect($input)->pluck('_source');return [$data,$total];}/*** 获取分类信息* @param $category* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null*/public static function category($category){$category_array =   explode(',',$category);$category_id    =   array_pop($category_array);return ProductCategory::query()->where('id',$category_id)->first();}
}

2 . 封装一个用于商品搜索的 ElasticSeachService.php

<?php
namespace App\Services;class ElasticsearchService
{/*** 搜索结构体* @var array*/protected $params = ['type'  => '_doc','body'  => ['query' => ['bool' => ['filter'=> [],'must'  => []]]]];/*** 通过构造函数进行索引初始化* ElasticsearchService constructor.* @param $index*/public function __construct($index){//要搜索的索引名称$this->params['index'] = $index;return $this;}/*** 根据字段-条件搜索* @param $type .搜索类型:term精准搜索,match分词器模糊查询,prefix字段前缀* @param $key  .搜索的字段名称* @param $value.搜索字段值* @return $this*/public function  queryByFilter($type,$key,$value){$this->params['body']['query']['bool']['filter'][] = [$type => [$key => $value]];return $this;}/*** 关键词按照权重进行搜索* @param $keyWords* @return $this*/public function keyWords($keyWords){//如果不是数据则转换为数组$keyWords = is_array($keyWords) ? $keyWords : [$keyWords];foreach ($keyWords as $key => $value){$this->queryByMust($value);}return $this;}/*** 根据权重进行多字段搜索* @param $seach 搜索的值* @return $this*/public function  queryByMust($seach){$this->params['body']['query']['bool']['must'][] = ['multi_match' => ['query' => $seach,'fields'=> ['long_name^3','category^2']]];return $this;}/*** 获取指定字段* @param $keyWords  一维数组* @return $this*/public function source($keyWords){$keyWords = is_array($keyWords) ? $keyWords : [$keyWords];$this->params['body']['_source'] = $keyWords;return $this;}/*** 设置分页* @param $page* @param $pageSize* @return $this*/public function paginate($page,$pageSize){$this->params['body']['from'] = ($page - 1) * $pageSize;$this->params['body']['size'] = $pageSize;return $this;}/*** 排序* @param $filed        .排序字段* @param $direction    .排序值* @return $this*/public function orderBy($filed,$direction){if(!isset($this->params['body']['sort'])){$this->params['body']['sort'] = [];}$this->params['body']['sort'][] = [$filed => $direction];return  $this;}/*** 聚合查询 商品属性筛选的条件* @param $name     属性名称* @param $value    属性值* @return $this*/public function attributeFilter($name,$value){//attributes 为 索引中的  attributes  字段$this->params['body']['query']['bool']['filter'] = ['nested' => ['path' => 'attributes','query' => ['bool' => ['must' => [['term' => ['attributes.name' => $name]],['term' => ['attributes.value' => $value]]]]]]];return $this;}/*** 聚合查询(方式2) 商品属性筛选的条件* @param $name* @param $value* @return $this*//*public function attributeFilter($name,$value){$this->params['body']['query']['bool']['filter'][] = ['nested' => ['path'  => 'attributes','query' => [['term' => ['attributes.name' => $name]],['term' => ['attributes.value' => $value]]],],];return $this;}*//*** 返回结构体* @return array*/public function getParams(){return $this->params;}/*** 多维数组转换一维数组* @param $input* @param $flatten* @return array*/public static function getDataByEs($input){$total = $input['hits']['total']['value'];$input = $input['hits']['hits'];$data  = collect($input)->pluck('_source');return [$data,$total];}}

3 . 创建控制器,在控制器中调用 ProductController.php

<?phpnamespace App\Http\Controllers\Api\V1;use App\Http\Controllers\Controller;
use App\Models\Product;
use App\Models\ProductSkus;
use App\Services\PublicService;
use Illuminate\Http\Request;
use App\Http\Controllers\ApiResponse;
use App\Services\ElasticsearchService;class ProductController extends Controller
{use ApiResponse;/*** 商品首页信息* @param Request $request* @return \Illuminate\Http\JsonResponse*/public function index(Request $request){try{$page = $request->input('page');if(!$page) return $this->failed('页数不能为空');$search      = $request->input('search');$order       = $request->input('order');$attributes  = $request->input("attributes");$category_id = $request->input('category_id');$shop_id     = $request->input('shop_id',1);$pageSize    = $request->input('pageSize',20);$esService = new ElasticsearchService('products');//封装es类,增加商品状态为上架条件的分页查询结构$builder = $esService->queryByFilter('term','shop_id',$shop_id)->paginate($page,$pageSize);//分类搜索if ($category_id){//获取分类信息$category = PublicService::category($category_id);//根据字段-条件搜索if($category && $category->is_directory){$builder->queryByFilter('prefix','category_path',$category->path.$category->id.'-');}else{$builder->queryByFilter('term','category_id',$category->id);}}//关键词按照权重进行搜索if($search){$keywords = array_filter(explode(' ',$search));$builder->keyWords($keywords);}//排序if($order){if (preg_match('/^(.+)_(asc|desc)$/',$order,$m)){//只有当前三个字段才可以进行排序搜索if (in_array($m[1],['price','sold_count','review_count'])){$builder->orderBy($m[1],$m[2]);}}}$attributeFilter = [];//根据商品类型搜索if($attributes){$attrArray = explode("|",$attributes);foreach ($attrArray as $attr){list($name,$value) = explode(":",$attr);$attributeFilter[$name] = $value;$builder->attributeFilter($name,$value);}}//获取的字段$builder->source(['id','name','long_name','shop_id','skus','attributes','create_time']);//执行es搜索$restful = app('es')->search($builder->getParams());//多维数组转换一维数组list($data,$total) = $builder->getDataByEs($restful);return $this->success(compact('data','total','page'));}catch (\Exception $e){return $this->failed($e->getMessage().':'.$e->getLine());}}/*** 获取商品详情信息* @param Request $request* @return \Illuminate\Http\JsonResponse*/public function productInfo(Request $request){try{$product_id = $request->input('product_id');if(!$product_id) return $this->failed('缺少商品id');//通过id从es中获取数据$es = new ElasticsearchService('products');$builder = $es->queryByFilter('term','status',1)->queryByFilter('term','id',$product_id);$builder->source(['id','name','long_name','shop_id','create_time']);$restful = app('es')->search($builder->getParams());list($data,$total) = $es->getDataByEs($restful);return $this->success(compact('data','total'));}catch (\Exception $e){return $this->failed($e->getMessage());}}/*** 创建数据* @param Request $request* @return \Illuminate\Http\JsonResponse*/public function createProduct(Request $request){try{$input = $request->all();$shop_id = $input['shop_id'];$input['one_category_id']   =   425;$input['two_category_id']   =   438;$input['three_category_id'] =   440;$info = Product::query()->create($input);$id = $info->id;$sql = 'product_id,attribute_id,name,num,price,status,shop_id';$skus = ProductSkus::query()->selectRaw($sql)->where('product_id',18)->get()->each(function ($query) use ($id,$shop_id){$query->product_id = $id;$query->shop_id = $shop_id;});$skus = $skus->toArray();//添加新属性ProductSkus::query()->insert($skus);//格式化为es的数据$esArray = $info->toESArray($info->id,$info->three_category_id);//对于es来说,已有数据,则是编辑,没有则是新增app('es')->index(['id' => $esArray['id'], 'index' => 'test', 'body' => $esArray]);return $this->success('添加成功成功');}catch (\Exception $e){return $this->failed($e->getMessage());}}/*** 修改商品信息* @param Request $request* @return \Illuminate\Http\JsonResponse*/public function editProductInfo(Request $request){try{$product_id = $request->input('product_id');if(!$product_id) return $this->failed('缺少商品id');$name = $request->input('name');$long_name = $request->input('long_name');$price = $request->input('price');$sql = 'id,name,long_name,brand_id,shop_id,price,sold_count,review_count,status,create_time,last_time,three_category_id';$info = Product::query()->selectRaw($sql)->where('id',$product_id)->first();if(!$info) return $this->failed('暂无当前商品信息');//先修改mysql中的数据,在把数据同步到es中:如果有用logstash,则无需次操作,logstash会自动同步数据到es中$info->name = $name;$info->long_name = $long_name;$info->price = $price;$info->save();//格式化为es的数据$esArray = $info->toESArray($info->id,$info->three_category_id);//对于es来说,已有数据,则是编辑,没有则是新增app('es')->index(['id' => $esArray['id'], 'index' => 'test', 'body' => $esArray]);return $this->success('编辑成功');}catch (\Exception $e){return $this->failed($e->getMessage());}}
}

4 . 问题:laravel数据修改后对es数据影响的问题

在es中不能单独修改一个字段的数据,需要整条数据修改,因为es中无法修改一个字段的信息数据,es会删除掉原有的数据,重新生成只有修改时录入的数据。

解决方案: 在修改完数据库的数据后,需要重新生成当前es所需要的数据(在Model中封装好的toESArray方法),然后重新写入,或者通过第三方数据监听,如(go-mysql-es 、logstash) 实时监听并同步修改数据。

.
.
.
$esArray = Product::find($product_id)->toESArray();//对于es来说,已有数据,则是编辑,没有则是新增
app('es')->index(['id' => $arr['id'], 'index' => 'test', 'body' => $esArray]);
.
.
.

es 录入的 $arr 数据格式为:

array:13 ["id" => 1"name" => "HUAWEI Mate Book 11""long_name" => "HUAWEI Mate Book 11 16GB 512GB 触屏 集显""brand_id" => 11"shop_id" => 1"price" => "5299.00""sold_count" => 111"review_count" => 1111"status" => 2"create_time" => "2021-05-25 15:12:09""last_time" => "2021-05-25 15:12:14""skus" => array:6 [0 => array:2 ["name" => "皓月银 I5/16GB/512GB 触屏 集成显卡 官方标配""price" => "6299.00"]1 => array:2 ["name" => "皓月银 I7/16GB/512GB 触屏 集成显卡 官方标配""price" => "6599.00"]]"attributes" => array:12 [0 => array:2 ["name" => "颜色""value" => "星际蓝"]1 => array:2 ["name" => "颜色""value" => "冰霜银"]]
]

laravel使用elasticsearch相关推荐

  1. Laravel 以ElasticSearch的方式使用scout

    Laravel5.3已经发布了,这次版本多了一个官方工具包:scout.这个包的作用就是帮助大家更好的将全文检索工具和项目更好的结合,让开发者使用起来更方便.但是我发现了一个问题:Laravel官方文 ...

  2. es中修改某个字段值_搜索引擎之laravel中使用elasticsearch(一)

    一.概述 Elasticsearch官方提供了Composer包可直接引用就好,不过要注意:如果你使用的elastcsearch不是最新的那么我建议你指定合适的版本下载,而不要采用最新的,因为不同版本 ...

  3. laravel 的 scout elasticsearch ik laravel-scout-elastic 之间的关系与安装

    一 简介 laravel 的组件化使 laravel 的使用更加得心应手. Laravel Scout 为 Eloquent 模型 全文搜索提供了简单的,基于驱动的解决方案.通过使用模型观察者,Sco ...

  4. elastic学习笔记

    要点 不同工具之间版本匹配很重要 由点及面,先实践起来再学细节的原理和使用 技术栈 laravel5.5框架+scout组件+elasticsearch6.3.0搜索引擎 辅助 elasticsear ...

  5. mysql学习笔记(13)之mycat切分规则与es基础

    mycat切分规则与es基础 mycat基础配置 mycat切分规则 es,es-head,kibana简介与安装 Windows下安装 es分布式搜索引擎安装 elasticsearch-head安 ...

  6. Laravel 使用 scout 集成 elasticsearch 做全文搜索

    安装需要的组件 composer require tamayo/laravel-scout-elastic composer require laravel/scout 如果composer requ ...

  7. Laravel5.4中文分词搜索-使用 Laravel Scout,Elasticsearch,ik 分词(三)

    上一篇地址:https://blog.csdn.net/huangfenhu/article/details/94009241 创建一个文章表和文章模型: php artisan make:model ...

  8. Laravel5.4中文分词搜索-使用 Laravel Scout,Elasticsearch,ik 分词(二)

    上一篇环境搭建地址:https://blog.csdn.net/huangfenhu/article/details/94004316 安装Scout 进入laravel项目的根目录使用compose ...

  9. laravel Scout包在elasticsearch中的应用

    laravel Scout包在elasticsearch中的应用 2017-04-05 15:14 by 轩脉刃, ... 阅读, ... 评论, 收藏, 编辑 laravel Scout包在elas ...

最新文章

  1. 《Think Python》最新中文版火了,附完整PDF下载!
  2. 实现call和apply
  3. LeetCode之Missing Number
  4. VBA学习_3:对象、集合及对象的属性和方法
  5. HDU 2511二分+汉诺塔
  6. hp usb disk storage format tool
  7. Unity模拟行星轨道(一)
  8. 用acdsee制作html,ACDSee软件图像处理操作实用技巧
  9. Linux安装Flash脚本,linux 下安装adobe flash的关键。
  10. python openpyxl冻结首行单元格
  11. excel 计算复合增长率
  12. openwrt添加me909s-8201
  13. CKFinder baseDir 和 baseURL参数解释
  14. docker pull拉取镜像_Docker拉取镜像的完整步骤
  15. 建立量化交易趋势跟踪策略的五个指标
  16. 摔鸡蛋,摔手机问题。
  17. Week1.3-金融会计指标案例分析
  18. 副业搞钱的几个野路子:两个年入10万的零成本赚钱项目
  19. 高德地图:创建多个覆盖区域
  20. python实现FFT,IFFT

热门文章

  1. 抢红包软件背后的 Accessibility 服务及启动原理
  2. 森林火灾算法c语言,基于单片机的森林火灾报警器毕业论文.doc
  3. .DS_store 文件冲突
  4. 批量重命名利器——Bulk Rename Utility
  5. 初学者C语言练习题-结构体
  6. 实现web前端上传excel文件到flask服务器
  7. WIN10中MATLAB2014找不到编译器解决
  8. 管理为什么需要绩效考核系统
  9. Debian Linux使用小米球Ngrok内网穿透
  10. 2022届蓝桥杯b组省赛答案参考