laravel Es的封装与使用
开启Elasticsearch
elasticsearch -d
开启Kibana
kibana的 bin 目录下kibana.bat 双击
使用composer
安装:
composer require elasticsearch/elasticsearch
配置php.ini
配置php.ini的sys_temp_dir
;sys_temp_dir = "/tmp"
sys_temp_dir = "D:\phpstudy_pro\Extensions\tmp\tmp"
否则,使用过程中可能会出现以下报错
创建索引
$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);
}
}
商品列表页 商品分类展示位置
商品文档维护
新增商品后,在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'));
}
最终效果:
总结
1.分布式全文搜索解决方案:是基于Mysql数据库 、 Hadoop生态(可选)、 ElasticSearch搜索引擎三大数据系统实现一个分布式全文搜索系统。
2.Mysql数据库用于结构化存储项目数据。
3.Hadoop生态用于备份关系型数据库的所有版本,还存储用户行为,点击,曝光,互动等海量日志数据,用于数据分析处理。
4.ElasticSearch搜索引擎用于对Mysql或者Hadoop提供的数据进行索引和全文搜索。
5.其中核心功能,包括全量创建索引、增量创建索引、实时同步数据(文档的curd)、全文搜索等。
本作品采用《CC 协议》,转载必须注明作者和本文链接