前面分享了Xunserch 和 ElasticSearch的安裝,本來是要用Xunserch 的因爲官網項目部需要太強大的插件,但是安裝不給力,各種錯誤,最後用ElasticSearch。。。其實ElasticSearch並不太難,,, 不說了 直接上代碼
先說下我的設計模式:
後臺添加數據的同時王es裏面插入索引, es6.0版本後一個索引只能插入一個type,所以es每個索引對應數據表, es存放前端要顯示的字段即可,沒必要全部字段都存在裏面。 插入或者更新是調用一個公共方法。。。 搜索調用搜索接口
優化點 如果你的集羣只有一個節點,這不要設置副本數,不然健康值會變成yellow,設置爲0即可
阿里雲的dts可以自動把mysql數據庫的數據添加和更新索引到ES 但是需要付費。。。這個不做過多的說明
1.在 項目配置文件 web.php (高級模板main.php) 中的components中添加配置:
//現使用 es
'elasticsearch' => [
'class' => 'yii\elasticsearch\Connection',
//默認爲true 如果單節點 設爲false 不然會連接錯誤
'autodetectCluster' => false,
'nodes' => [
['http_address' => '127.0.0.1:9200'],
],
],
2.搜索接口
/**
* auth - rk
* 獲取全站搜索數據
* 支持分詞搜索和 多條件+多字段搜索
*/
public function actionSearch(){
if(Yii::$app->request->isPost){
$searchData = Yii::$app->request->post('name','');
$pageSize = Yii::$app->request->post('pageSize',5);
$page = Yii::$app->request->post('page',1);
$cate = Yii::$app->request->post('cate','');
$offset = ($page-1)*$pageSize;
$searchData =trim($searchData);
// $searchData = str_replace(" ","",$searchData);
if(empty($searchData)){
$data['res_code']='200';
$data['res_msg']='success';
$data['res_data']=[];
return $data;
}
//ElasticSearch
$es = new ElasticSearch();
//高亮數據
$highlight = [
#左標籤,配合前端.highlight這個類來實現高亮
"pre_tags"=>['<span class="highlight">'],
"post_tags"=>['</span>'],
#在原生api中寫的是{}表示空對象,因此使用php的stdClass來表示空對象
"fields"=>[
"title"=>new \stdClass(),
"subtitle"=>new \stdClass(),
]
];
$cat = ['product','cases','news','about_us'];
if(!empty($cate)){
if(in_array($cate,$cat)){
$cat = [$cate];
}
}
$resArr = [];
foreach ($cat as $val){
$es::$index_db_name = $val;
$es::$type_tb_name = $val;
$where = ["is_delete"=>0,"status"=>1];
if($val == 'about_us'){
$where = ["is_delete"=>0];
}
$must[] = [
"multi_match" => [//分詞多字段搜索
'query' => $searchData,//['fuzzy'=>$searchData]
'fields' => ['title','subtitle'],//搜索的字段
],
];
$query = [
'bool'=>[
'must' => $must,
'should' => [
['match'=> ['is_delete' => 0]]
],
'minimum_should_match' => 1,
]
];
$sort = ['sort' => ['order' => 'desc']];
//->query($query)->orderBy($sort)->asArray()->all() 'terms'=> $where
$esQuery = $es::find()->query($query);//->offset($offset)->limit($pageSize)->orderBy($sort)->highlight($highlight)->asArray()->all()
$count = $es::find()->query($query)->search();
$res = $esQuery->offset($offset)->limit($pageSize)->orderBy($sort)->highlight($highlight)->asArray()->all();
$resArr[$val]['count'] = $count['hits']['total']['value'] ??0;
$resArr[$val]['pageSize'] = $pageSize;
$resArr[$val]['data'] = $res??[];
}
//搜索數據存儲到後臺
$searchModel = new Search();
//先判斷是否存在搜索數據 存在更新次數
$querySql = $searchModel::find()->where(['like','content',$searchData]);
$count = $querySql->count();
if($count !==0){
$queryData = $querySql->asArray()->all();
foreach ($queryData as $key => &$val){
$num = $val['num'] +=1;
$searchModel->updateAll(['num'=>$num],['id'=>$val['id']]);
}
}else{
$searchModel->ip = Yii::$app->request->getUserIP();
$searchModel->content = $searchData;
$searchModel->save();
}
//xunsearch
// $db = \Yii::$app->xunsearch->getDatabase('mmc_uav',true);
// $xs = $db->xs;
// $search = $db->getSearch();
// $index = $db->getIndex();
// var_dump($search);die;
$data['res_code']='200';
$data['res_msg']='success';
$data['host'] = Yii::$app->request->hostInfo;
$data['res_data']=$resArr;
return $data;
}
$data['res_code']='400';
$data['res_msg']='請求方式錯誤';
return $data;
}
3.ElasticSearch模型
<?php
/**
* Created by PhpStorm.
* User: renkun
* Date: 2020/4/9
* Time: 10:09
*/
namespace app\models;
use yii\elasticsearch\ActiveRecord;
class ElasticSearch extends ActiveRecord
{
public static $index_db_name = '';
public static $type_tb_name = '';
// # 定義db鏈接
public static function getDb()
{
return \Yii::$app->get('elasticsearch');
}
// 索引名相當於庫名
public static function index(){
return self::$index_db_name;
}
// 類別名相當於表名
public static function type(){
return self::$type_tb_name;
}
# 屬性
public function attributes()
{
$mapConfig = self::mapConfig();
return array_keys($mapConfig[self::$type_tb_name]);
}
# mapping配置
public static function mapConfig(){
return [
'news' => [
'id' => ['type' => 'int'],
'userId' => ['type' => 'int'],
'categoryId' => ['type' => 'int'],
'title' => ['type' => 'string'],//默認分詞
'subtitle' => ['type' => 'string'],
'content' => ['type' => 'string'],
'recommend' => ['type' => 'int'],
'publishTime' => ['type' => 'int'],
'auth' => ['type' => 'string'],
'cover_img' => ['type' => 'string'],
'origin' => ['type' => 'string'],
'origin_url' => ['type' => 'string'],
'status' => ['type' => 'int'],
'sort' => ['type' => 'int'],
'is_delete' => ['type' => 'int'],
],
'about_us' => [
'id' => ['type' => 'int'],
'content' => ['type' => 'string'],
'addtime' => ['type' => 'int'],
'title' => ['type' => 'string'],//默認分詞
'description' => ['type' => 'string'],
'type' => ['type' => 'int'],
'sort' => ['type' => 'int'],
'is_delete' => ['type' => 'int'],
],
'cases' => [
'id' => ['type' => 'int'],
'userId' => ['type' => 'int'],
'categoryId' => ['type' => 'int'],
'title' => ['type' => 'string'],//默認分詞
'subtitle' => ['type' => 'string'],
'auth' => ['type' => 'string'],
'device' => ['type' => 'string'],
'content' => ['type' => 'string'],
'recommend' => ['type' => 'int'],
'status' => ['type' => 'int'],
'cover_img' => ['type' => 'string'],
'create_at' => ['type' => 'int'],
'sort' => ['type' => 'int'],
'is_delete' => ['type' => 'int'],
],
'product' => [
'id' => ['type' => 'int'],
'product_type_id' => ['type' => 'int'],
'title' => ['type' => 'string'],//默認分詞
'description' => ['type' => 'string'],
'cover_img_url' => ['type' => 'string'],
'product_introduction' => ['type' => 'string'],
'extension' => ['type' => 'string'],
'images' => ['type' => 'long'],
'recommend' => ['type' => 'string'],
'addtime' => ['type' => 'int'],
'is_recommend' => ['type' => 'int'],
'user_id' => ['type' => 'int'],
'status' => ['type' => 'int'],
'sort' => ['type' => 'int'],
'is_delete' => ['type' => 'int'],
]
];
}
// 映射
public static function mapping(){
$mapConfig = self::mapConfig();
return [
static::type() => $mapConfig[self::$type_tb_name]
];
}
// 更新映射
public static function updateMapping(){
$db = self::getDb();
$command = $db->createCommand();
if(!$command->indexExists(self::index())){
$command->createIndex(self::index());
}
$res = $command->setMapping(self::index(), self::type(), self::mapping());
return $res;
}
// 創建索引
public static function createIndex(){
$db = static::getDb();
$command = $db->createCommand();
if(!$command->indexExists(self::index())){
$command->createIndex(static::index(), [
'settings' => [
'index' => [
// 指定新索引的時候不會立即生效,1s之後刷新生效 默認是1s
'refresh_interval' => '1s',
// 索引分片個數 默認5片
'number_of_shards' => 5,
// 每個分片副本個數 默認1個 單節點集羣如果副本不設置爲0 集羣健康值會變成黃色 因爲單點集羣無法存放副本
'number_of_replicas' => 0,
]
],
// 'mappings' => static::mapping(),
//'warmers' => [ /* ... */ ],
//'aliases' => [ /* ... */ ],
//'creation_date' => '...'
]);
}
}
// 刪除索引 (謹慎使用)
public static function deleteIndex(){
$db = static::getDb();
$command = $db->createCommand();
$res = $command->deleteIndex(static::index(), static::type());
return $res;
}
//創建es索引 並賦值
public static function es($index,$data=[],$action = 0){ //0 添加 1 創建
$es = new self();
$es::$index_db_name = $index;
$es::$type_tb_name = $index;
$attr = $es::mapping();
$indexKey = array_keys($attr[$index]);
// var_dump($data);die;
$tmpData = $data;
//只獲取主要字段
if(!empty($data)){
if($action == 1){
$es = $es::findOne($data['id']); //更新
if(empty($es)){
self::es($index,$tmpData,0);
}
}else{
$es::createIndex();
$es->primaryKey = $data['id'];
}
foreach ($data as $key => $val){
// var_dump($indexKey);
// var_dump($data);die;
if(in_array($key,$indexKey)){
$es->$key = $val;
}
}
if ($es->save() == false) {
die("es數據出現錯誤,請聯繫管理員");
}
}
}
}
- 手動添加索引和更新
if( $model->save()){
//同步到es
ElasticSearch::es('product',$model->attributes);
return $this->redirect(['view', 'id' => $model->id]);
}
//最終效果