PHP项目全文检索如何对接Elasticsearch

wen PHP项目 27

本文目录导读:

PHP项目全文检索如何对接Elasticsearch

  1. 官方客户端 elasticsearch-php
  2. 索引创建与映射
  3. 数据索引(文档操作)
  4. 全文搜索实现
  5. 封装搜索类(推荐)
  6. 与ORM集成(Laravel例子)
  7. 数据库同步策略
  8. 性能优化建议
  9. 注意事项

为PHP项目对接Elasticsearch实现全文检索,主要有以下几种方案,按推荐程度排序:

官方客户端 elasticsearch-php

安装

composer require elasticsearch/elasticsearch

基础连接配置

use Elastic\Elasticsearch\ClientBuilder;
// 单节点连接
$client = ClientBuilder::create()
    ->setHosts(['localhost:9200'])
    ->build();
// 多节点集群连接
$client = ClientBuilder::create()
    ->setHosts([
        'http://node1:9200',
        'http://node2:9200',
        'http://node3:9200'
    ])
    ->setRetries(2) // 重试次数
    ->build();
// 带认证的连接
$client = ClientBuilder::create()
    ->setHosts(['https://localhost:9200'])
    ->setBasicAuthentication('username', 'password')
    ->setSSLVerification(false) // 开发环境可关闭SSL验证
    ->build();

索引创建与映射

// 创建索引并定义映射
$params = [
    'index' => 'articles',
    'body' => [
        'settings' => [
            'number_of_shards' => 3,
            'number_of_replicas' => 1,
            'analysis' => [
                'analyzer' => [
                    'ik_smart' => [ // 中文分词
                        'type' => 'custom',
                        'tokenizer' => 'ik_smart'
                    ]
                ]
            ]
        ],
        'mappings' => [
            'properties' => [
                'title' => [
                    'type' => 'text',
                    'analyzer' => 'ik_max_word',
                    'search_analyzer' => 'ik_smart'
                ],
                'content' => [
                    'type' => 'text',
                    'analyzer' => 'ik_max_word'
                ],
                'author' => [
                    'type' => 'keyword' // 精确匹配
                ],
                'publish_date' => [
                    'type' => 'date',
                    'format' => 'yyyy-MM-dd HH:mm:ss'
                ],
                'views' => [
                    'type' => 'integer'
                ],
                'tags' => [
                    'type' => 'keyword'
                ]
            ]
        ]
    ]
];
$response = $client->indices()->create($params);

数据索引(文档操作)

单条索引

// 索引单条数据
$params = [
    'index' => 'articles',
    'id' => '1001', // 可选,自动生成id
    'body' => [
        'title' => 'Elasticsearch PHP集成指南',
        'content' => '详细介绍了如何在PHP项目中集成Elasticsearch...',
        'author' => '张三',
        'publish_date' => '2024-01-15 10:30:00',
        'views' => 1256,
        'tags' => ['PHP', 'Elasticsearch', '全文检索']
    ]
];
$response = $client->index($params);
// 批量索引(推荐大数据量使用)
$params = ['body' => []];
$articles = [
    ['id' => 1, 'title' => '文章1', 'content' => '内容1'],
    ['id' => 2, 'title' => '文章2', 'content' => '内容2'],
    // ... 更多数据
];
foreach ($articles as $article) {
    $params['body'][] = [
        'index' => [
            '_index' => 'articles',
            '_id' => $article['id']
        ]
    ];
    $params['body'][] = [
        'title' => $article['title'],
        'content' => $article['content']
    ];
}
$responses = $client->bulk($params);

全文搜索实现

基础搜索

// 全文搜索
$params = [
    'index' => 'articles',
    'body' => [
        'query' => [
            'match' => [
                'content' => 'Elasticsearch PHP'
            ]
        ],
        'from' => 0, // 分页
        'size' => 10,
        'sort' => [
            'publish_date' => ['order' => 'desc']
        ]
    ]
];
$response = $client->search($params);
$hits = $response['hits']['hits'];
$total = $response['hits']['total']['value'];
foreach ($hits as $hit) {
    echo $hit['_source']['title'] . "\n";
    echo $hit['_score'] . "\n"; // 相关度分数
}

高级搜索组合

// 组合查询
$params = [
    'index' => 'articles',
    'body' => [
        'query' => [
            'bool' => [
                'must' => [
                    'match' => ['title' => 'PHP']
                ],
                'should' => [
                    'match' => ['content' => 'Elasticsearch']
                ],
                'filter' => [
                    'range' => [
                        'publish_date' => [
                            'gte' => '2024-01-01',
                            'lte' => '2024-12-31'
                        ]
                    ]
                ],
                'must_not' => [
                    'term' => ['status' => 'draft']
                ]
            ]
        ],
        'highlight' => [
            'fields' => [
                'title' => ['pre_tags' => ['<em>'], 'post_tags' => ['</em>']],
                'content' => ['pre_tags' => ['<em>'], 'post_tags' => ['</em>']]
            ]
        ]
    ]
];
$response = $client->search($params);

封装搜索类(推荐)

<?php
namespace App\Services;
use Elastic\Elasticsearch\ClientBuilder;
class ElasticsearchService
{
    protected $client;
    protected $index;
    public function __construct($index = 'articles')
    {
        $this->client = ClientBuilder::create()
            ->setHosts(config('elasticsearch.hosts'))
            ->setBasicAuthentication(
                config('elasticsearch.username'),
                config('elasticsearch.password')
            )
            ->build();
        $this->index = $index;
    }
    // 索引文档
    public function indexDocument(array $document, $id = null)
    {
        $params = [
            'index' => $this->index,
            'body' => $document
        ];
        if ($id) {
            $params['id'] = $id;
        }
        return $this->client->index($params);
    }
    // 搜索
    public function search(string $keyword, array $options = [])
    {
        $params = [
            'index' => $this->index,
            'body' => [
                'query' => [
                    'multi_match' => [
                        'query' => $keyword,
                        'fields' => $options['fields'] ?? ['title^2', 'content'],
                        'type' => $options['type'] ?? 'best_fields'
                    ]
                ],
                'size' => $options['size'] ?? 10,
                'from' => $options['from'] ?? 0,
                'highlight' => [
                    'fields' => [
                        'title' => new \stdClass(),
                        'content' => new \stdClass()
                    ]
                ]
            ]
        ];
        // 添加排序
        if (!empty($options['sort'])) {
            $params['body']['sort'] = $options['sort'];
        }
        // 添加过滤
        if (!empty($options['filters'])) {
            $params['body']['query']['bool']['filter'] = $options['filters'];
        }
        return $this->client->search($params);
    }
    // 删除文档
    public function deleteDocument($id)
    {
        $params = [
            'index' => $this->index,
            'id' => $id
        ];
        return $this->client->delete($params);
    }
    // 批量操作
    public function bulkIndex(array $documents)
    {
        $params = ['body' => []];
        foreach ($documents as $doc) {
            $params['body'][] = [
                'index' => [
                    '_index' => $this->index,
                    '_id' => $doc['id']
                ]
            ];
            $params['body'][] = $doc['data'];
        }
        return $this->client->bulk($params);
    }
}

与ORM集成(Laravel例子)

// config/elasticsearch.php
return [
    'hosts' => explode(',', env('ELASTICSEARCH_HOSTS', 'localhost:9200')),
    'username' => env('ELASTICSEARCH_USERNAME'),
    'password' => env('ELASTICSEARCH_PASSWORD'),
];
// 创建Eloquent观察者
namespace App\Observers;
use App\Models\Article;
use App\Services\ElasticsearchService;
class ArticleObserver
{
    protected $elasticsearch;
    public function __construct(ElasticsearchService $elasticsearch)
    {
        $this->elasticsearch = $elasticsearch;
    }
    public function created(Article $article)
    {
        $this->elasticsearch->indexDocument($article->toArray(), $article->id);
    }
    public function updated(Article $article)
    {
        $this->elasticsearch->indexDocument($article->toArray(), $article->id);
    }
    public function deleted(Article $article)
    {
        $this->elasticsearch->deleteDocument($article->id);
    }
}

数据库同步策略

全量同步

// Artisan命令示例
class SyncArticlesToElasticsearch extends Command
{
    public function handle()
    {
        $elasticsearch = new ElasticsearchService();
        Article::chunk(100, function ($articles) use ($elasticsearch) {
            $documents = [];
            foreach ($articles as $article) {
                $documents[] = [
                    'id' => $article->id,
                    'data' => $article->toArray()
                ];
            }
            $elasticsearch->bulkIndex($documents);
        });
        $this->info('同步完成');
    }
}

增量同步(使用消息队列)

// 队列任务示例
class SyncArticleToES implements ShouldQueue
{
    public function handle(ElasticsearchService $elasticsearch)
    {
        // 处理数据变更
        $elasticsearch->indexDocument($this->article->toArray(), $this->article->id);
    }
}

性能优化建议

// 1. 连接池配置
$client = ClientBuilder::create()
    ->setConnectionPool('\Elastic\Elasticsearch\ConnectionPool\StaticConnectionPool')
    ->setHosts(['localhost:9200'])
    ->build();
// 2. 批量处理大小
$params['body'] = [];
$batchSize = 500;
foreach ($articles as $index => $article) {
    $params['body'][] = ['index' => ['_index' => 'articles', '_id' => $article['id']]];
    $params['body'][] = $article;
    if (($index + 1) % $batchSize === 0) {
        $client->bulk($params);
        $params['body'] = [];
    }
}
if (!empty($params['body'])) {
    $client->bulk($params);
}
// 3. 缓存搜索结果
$cacheKey = 'search_' . md5(json_encode($params));
$results = Cache::remember($cacheKey, 300, function () use ($client, $params) {
    return $client->search($params);
});

注意事项

  1. 中文分词:需要安装IK分词插件
  2. 索引优化:定期优化索引和清理旧数据
  3. 错误处理:做好异常捕获和日志记录
  4. 版本兼容:注意es版本与客户端版本对应
  5. 安全配置:生产环境开启SSL和认证

通过以上步骤,你可以在PHP项目中完整地对接Elasticsearch实现全文检索功能。

抱歉,评论功能暂时关闭!