今天聊聊 Elasticsearch 在PHP项目中的使用。

最近开发一个项目,因为涉及到关键字及 多分类多标签查询,鉴于日后数据可能会比较多,而多标签分类查询对mysql 来讲必定要全表扫描,所以就请出搜索神器 Elasticsearch ,Elasticsearch我是在大学上课学的,有一些也是很模糊的了解·, 并且对于PHPer 来讲初次使用可能会觉得很复杂,但是熟悉以后可以解决项目中很多查询问题,建议了解并使用,为此特写一篇文章来做一个笔记吧。在这里插入图片描述

  1. 先安装 Elasticsearch (ES 下文中ES 表示为Elasticsearch)

  2. 安装 Elasticsearch 之前请保证你已经安装了 jdk 并设置好了jdk 环境变量。

JDK下载

下载对应系统的版本进行安装,这里我下载了zip 版本。
解压以后 进入 bin 目录(linux 用户 不要用 root 运行 下载存放的目录也不要放在 root 权限下的目录,比如 我放在了 home/wj008/Elasticsearch 目录下面)

2 运行 ES (如果没有权限 先用chmod添加执行权限)

sudo chmod +x ./elasticsearch
./elasticsearch

运行成功以后,我们可以在浏览器中打开地址 http://127.0.0.1:9200/
如果没有什么问题,将显示如下信息。
在这里插入图片描述
因为 我们需要用到 关键字搜索,所以我们需要再安装一个中文分词插件。
这里我使用的分词插件是 ik 分词插件

下载地址:
https://github.com/medcl/elasticsearch-analysis-ik/

请对应你安装的 ES 安装对应的 ik 插件,我这里是 6.3.0
如果发现你的版本太高 或者太低,你就得重新安装 Elasticsearch 哈
在这里插入图片描述

OK ,我们先 ctl+c 先退出 ES,并使用命令行安装插件,进入 bin 目录 执行如下代码:

./elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.0/elasticsearch-analysis-ik-6.3.0.zip

插件安装完成后 再次启动 ES

到这里 我们的ES 已经准备完成,接下来我们要使用php 开始设置我们的索引了,因为从 ES 6.3 开始 不在区分不同的 文档type(类似表名)所以我们只能使用一个 mappings 全局设置

首先 使用 composer 安装 ES 包(不知道 composer 是什么和怎么使用的 请自行百度了解)。

composer require elasticsearch/elasticsearch

这里我们需要一个在 cli 模式下运行的初始化脚本,当程序运行之前我们需要初始化es 的 mappings 设置。

创建一个php文件,名字随意,我叫inites.php

<?php
//inites.php
require('../vendor/autoload.php');
use Elasticsearch\ClientBuilder;

//只能以命令行模式运行
if (PHP_SAPI != 'cli') {
    die('该命令需要在命令行模式下运行');
}

$hosts = ['127.0.0.1:9200'];
$client = ClientBuilder::create()->setHosts($hosts)->build();

//这里的代码适用于实时输出,不使用缓存区
ob_implicit_flush(1);

$params = ['index' => 'my_index'];

//先判断索引是否存在
if ($client->indices()->exists($params)) {
    echo '删除索引...' . PHP_EOL;
    $response = $client->indices()->delete($params);
    echo json_encode($response, JSON_UNESCAPED_UNICODE) . PHP_EOL;
}

echo '创建索引...' . PHP_EOL;

$params = [
    'index' => 'my_index', //要创建的索引
    'body' => [
        'mappings' => [
            //文档
            'doc' => [
                '_source' => [
                    'enabled' => true
                ],
                //文档属性
                'properties' => [
                    //这个字段 用于存储对应的表名,如果有多个的话,因为 自 ES 6.3 开始 已经不
                    //支持多个 type(类似数据库表) 设置属性,所以这里不同表 用 tbname 区分
                    "tbname" => ["type" => "keyword"],
                    //关键字 字段设置
                    'keyword' => [
                        'type' => 'text',
                        'analyzer' => 'ik_max_word', // 存入时使用 ik 分词
                        'search_analyzer' => 'ik_max_word', //搜索时使用 ik 分词
                    ],
                ]
            ],
        ]
    ]
];
$response = $client->indices()->create($params);
echo json_encode($response, JSON_UNESCAPED_UNICODE) . PHP_EOL;
//查看 mappings
echo '查看 mappings...' . PHP_EOL;

$params = [
    'index' => 'my_index',
    'type' => 'doc'
];
$response = $client->indices()->getMapping($params);
echo json_encode($response, JSON_UNESCAPED_UNICODE) . PHP_EOL;

//如果 你数据已经有数据,那么 你可以接下去 循环插入索引数据,这里就先不插入了....

在命令行中运行 php inites.php初始化设置。

接下来我写一个例子来演示 ES 的数据插入

<?php
//insertes.php
require('../vendor/autoload.php');

use Elasticsearch\ClientBuilder;

$hosts = ['127.0.0.1:9200'];
$client = ClientBuilder::create()->setHosts($hosts)->build();
/*
 数据表:
DROP TABLE IF EXISTS `course`;
CREATE TABLE `course` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(250) DEFAULT NULL COMMENT '课程名称',
  `createTime` datetime DEFAULT NULL COMMENT '创建时间',
  `allow` tinyint(1) DEFAULT NULL COMMENT '是否启用',
  `tags` text COMMENT '选择标签',
  `subColumn` text COMMENT '子栏目',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=156 DEFAULT CHARSET=utf8;
INSERT INTO `course` VALUES ('148', '美国本科留学案例', '2018-06-04 03:30:45', '1', '[33,34,32,31]', '[{\"navBid\": 27, \"navMid\": 29, \"columnId\": 1}, {\"navBid\": 1, \"navMid\": 7, \"columnId\": 2}]');
INSERT INTO `course` VALUES ('150', '测试——如风过境', '2018-06-22 17:18:45', '1', '[40]', '[{\"navBid\": 1, \"navMid\": 7, \"columnId\": 2}]');
INSERT INTO `course` VALUES ('151', '斗罗大陆之绝世唐门1', '2018-06-22 18:42:37', '1', '[1,2,3,4,5,6,7,8,9]', '[{\"navBid\": 1, \"navMid\": 4, \"columnId\": 2}, {\"navBid\": 57, \"navMid\": 66, \"columnId\": 3}, {\"navBid\": 68, \"navMid\": 70, \"columnId\": 4}, {\"navBid\": 75, \"navMid\": 77, \"columnId\": 5}]');
INSERT INTO `course` VALUES ('152', '学长分享-考试英语-全部', '2018-06-23 17:09:39', '1', '[41,39,37,24]', '[{\"navBid\": 54, \"navMid\": 58, \"columnId\": 3}, {\"navBid\": 68, \"navMid\": 70, \"columnId\": 4}, {\"navBid\": 75, \"navMid\": 77, \"columnId\": 5}]');
INSERT INTO `course` VALUES ('153', '玉兰花,未来的会计师', '2018-06-23 17:13:54', '1', '[1,2,3,4,5,6,7,8,9]', '[{\"navBid\": 54, \"navMid\": 59, \"columnId\": 3}, {\"navBid\": 68, \"navMid\": 71, \"columnId\": 4}, {\"navBid\": 75, \"navMid\": 77, \"columnId\": 5}, {\"navBid\": 2, \"navMid\": 8, \"columnId\": 1}]');
INSERT INTO `course` VALUES ('154', '水母~海洋生物。', '2018-06-23 17:17:26', '1', '[1,2,3,4,5,6,7,8,9]', '[{\"navBid\": 1, \"navMid\": 7, \"columnId\": 2}, {\"navBid\": 54, \"navMid\": 60, \"columnId\": 3}, {\"navBid\": 68, \"navMid\": 72, \"columnId\": 4}, {\"navBid\": 76, \"navMid\": 79, \"columnId\": 5}]');
 */

$sqli = new mysqli('127.0.0.1', 'root', '123456', 'mytest1', 3306);
$sqli->query("SET NAMES utf8");
$rows = $sqli->query('select * from course')->fetch_all(MYSQLI_ASSOC);
foreach ($rows as $row) {
    $row['createTime'] = strtotime($row['createTime']);//日期转为时间戳 以便可以查询区间
    $row['tags'] = json_decode($row['tags'], true);//标签转为数组 以便可以查询数组内的值
    $row['subColumn'] = json_decode($row['subColumn'], true);
    $row['keyword'] = $row['name'];//我们要搜索的关键字,只有 keyword 我们设置了 ik 分词
    $row['tbname'] = 'course';// 表名要给上,用于以后区分不同的表查询

    $_id = 'course_' . $row['id']; //带上表前缀,以后用于更新的时候区分不同表
    //如果存在 就更新 否则插入
    if ($client->exists(['index' => 'my_index', 'type' => 'doc', 'id' => $_id])) {
        $client->update(['index' => 'my_index', 'type' => 'doc', 'id' => $_id, 'body' => ['doc' => $row]]);
    } else {
        $client->index(['index' => 'my_index', 'type' => 'doc', 'id' => $_id, 'body' => $row]);
    }
}

//显示所有已插入数据
print_r($client->search(['index' => 'my_index', 'type' => 'doc']));

这里我是一次性插入,如果在项目中,每次更新或者插入数据时 需要同时 插入 ES 或者更新 ES.

<?php
//search.php
require('../vendor/autoload.php');

use Elasticsearch\ClientBuilder;

function search($data)
{

    $hosts = ['127.0.0.1:9200'];
    $client = ClientBuilder::create()->setHosts($hosts)->build();

    $must = []; //建一个数组用来装查询条件
    $must[] = ['term' => ['allow' => 1]]; //这里查 allow =1 的条件 term 要求全等

    //栏目id  需要查询 subColumn 中的 数组的 columnId match为查找匹配
    $columnId = intval(empty($data['columnId']) ? '0' : $data['columnId']);
    if ($columnId) {
        $must[] = ['match' => ['subColumn.columnId' => $columnId]];
    }

    //导航Bid  需要查询 subColumn 中的 数组的 navBid
    $navBid = intval(empty($data['navBid']) ? '0' : $data['navBid']);
    if ($navBid) {
        $must[] = ['match' => ['subColumn.navBid' => $navBid]];
    }

    //导航Mid  需要查询 subColumn 中的 数组的 navMid
    $navMid = intval(empty($data['navMid']) ? '0' : $data['navMid']);
    if ($navMid) {
        $must[] = ['match' => ['subColumn.navMid' => $navMid]];
    }
    //标签id  需要查询 tags 数组中查找tagId
    $tagId = intval(empty($data['tagId']) ? '0' : $data['tagId']);
    if ($tagId) {
        $must[] = ['match' => ['tags' => $tagId]];
    }

    $keyword = empty($data['keyword']) ? '' : $data['keyword'];
    if (!empty($keyword)) {
        $must[] = ['match' => ['keyword' => $keyword]];
    }

    //拼接查询数组
    $sort = []; //排序
    $sort[] = ['createTime' => ["order" => "desc"]]; //先按时间排序
    $sort[] = ['_score' => ["order" => "desc"]]; //按得分排序

    //数据分页
    $from = 0;
    $size = 4;

    $params = [
        'index' => 'my_index',
        'type' => 'doc',
        'body' => [
            'query' => [
                'bool' => [
                    'must' => $must,
                    //使用filter 过滤表名,如果不过滤 这会显示其他所有表的数据 tbname 之前设置过滤表的字段
                    'filter' => [
                        'match' => [
                            'tbname' => 'course'
                        ]
                    ]
                ]
            ],
            'sort' => $sort,
        ],
        'from' => $from,
        'size' => $size
    ];
    return $client->search($params);
}


echo '按类搜索=========' . PHP_EOL;
print_r(search([
    'columnId' => 3,
    'navBid' => 68,
    'navMid' => 66,
]));

echo '按tagId搜索=========' . PHP_EOL;
//按tagId搜索
print_r(search([
    'tagId' => 3
]));

echo '按关键字搜索=========' . PHP_EOL;
print_r(search([
    'keyword' => '玉兰花,未来的会计师 考试英语'
]));

//从 $_GET 获取
echo '按$_GET搜索=========' . PHP_EOL;
print_r(search($_GET));

至此,ES 的PHP 搜索已经完成,如果需要更复杂的搜索 可以参考 官方帮助文档。

这里要注意的情况是,不要把表中所有不需要搜索的数据加入搜索引擎,比如 图片路径 文章内容 等加入到ES 搜索引擎中,这会导致索引相当大,而且索引效率下降,如需要其他字段你可以使用搜索后的 id 在二次查询mysql 数据库。 mysql 使用 id 主键查询是相当快的。

感谢大家阅读,关注评论什么的,我最喜欢啦·
在这里插入图片描述

  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Spring Boot项目使用Elasticsearch,需要在pom.xml文件添加相关依赖。以下是一个使用Spring Boot和Elasticsearch的简单示例: 1.添加依赖 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> ``` 2.配置Elasticsearch属性 在application.properties文件添加Elasticsearch的配置属性,例如: ```properties spring.data.elasticsearch.cluster-name=my-application spring.data.elasticsearch.cluster-nodes=localhost:9300 ``` 3.定义实体类 在Spring Boot应用程序,可以使用Spring Data Elasticsearch来定义Elasticsearch实体类。例如: ```java @Document(indexName="blog",type="article") public class Article { @Id private String id; private String title; private String content; // getters and setters } ``` 4.定义Elasticsearch存储库 定义一个Elasticsearch存储库接口,例如: ```java public interface ArticleRepository extends ElasticsearchRepository<Article, String> { List<Article> findByTitle(String title); } ``` 5.在服务使用Elasticsearch存储库 在服务注入Elasticsearch存储库,并使用它执行Elasticsearch操作,例如: ```java @Service public class ArticleService { @Autowired private ArticleRepository articleRepository; public List<Article> search(String title) { return articleRepository.findByTitle(title); } public void save(Article article) { articleRepository.save(article); } } ``` 这是一个简单的示例,演示了如何在Spring Boot应用程序使用Elasticsearch。你可以使用这个示例作为起点,根据自己的需求进行修改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值