From a5fb4b69bd6d92f6ccfae23bae99fba6fc38d15d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E4=BB=81=E6=81=95?= <1051953562@qq.com> Date: Wed, 15 Jun 2022 19:19:35 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=AB=98=E4=BA=AE?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Engine.php | 58 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index 1234a0b..e352001 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -8,6 +8,7 @@ * @contact eric@zhu.email * @license https://github.com/hyperf-ext/scout/blob/master/LICENSE */ + namespace HyperfExt\Scout; use Elasticsearch\Client; @@ -52,18 +53,18 @@ public function update(ModelCollection $models) $body[] = [ 'update' => [ '_index' => $model->searchableAs(), - '_id' => $model->getScoutKey(), + '_id' => $model->getScoutKey(), ], ]; $body[] = [ - 'doc' => $doc, + 'doc' => $doc, 'doc_as_upsert' => true, ]; } $this->elasticsearch->bulk([ 'refresh' => config('scout.doc_refresh', true), - 'body' => $body, + 'body' => $body, ]); } @@ -82,14 +83,14 @@ public function delete(ModelCollection $models) $body[] = [ 'delete' => [ '_index' => $model->searchableAs(), - '_id' => $model->getScoutKey(), + '_id' => $model->getScoutKey(), ], ]; } $this->elasticsearch->bulk([ 'refresh' => config('scout.document_refresh', true), - 'body' => $body, + 'body' => $body, ]); } @@ -104,7 +105,7 @@ public function search(Builder $builder) $size = $builder->getSearch()->getSize(); return $this->performSearch( $builder, - ! is_null($from) + !is_null($from) ? ['size' => $size, 'from' => $from] : [] ); @@ -130,7 +131,7 @@ public function count(Builder $query): int { $result = $this->performCount($query); - return isset($result['count']) ? (int) $result['count'] : 0; + return isset($result['count']) ? (int)$result['count'] : 0; } /** @@ -143,12 +144,13 @@ public function mapIds($results): BaseCollection return collect($results['hits']['hits'])->pluck('_id'); } + /** * Map the given results to instances of the given model. * * @param mixed $results */ - public function map(Builder $builder, $results, Model $model): ModelCollection + public function map(Builder $builder, $results, Model $model) { if ($this->getTotalCount($results) === 0) { return $model->newCollection(); @@ -158,14 +160,26 @@ public function map(Builder $builder, $results, Model $model): ModelCollection $idPositions = array_flip($ids); - return $model->getScoutModelsByIds( + $res = $model->getScoutModelsByIds( $builder, $ids )->filter(function ($model) use ($ids) { return in_array($model->getScoutKey(), $ids); })->sortBy(function ($model) use ($idPositions) { return $idPositions[$model->getScoutKey()]; - })->values(); + })->keyBy($model->getKeyName()); + + + return collect($results['hits']['hits'])->map(function ($hit) use ($res) { + $one = $res[$hit['_id']]; + + if (isset($hit['highlight'])) { + foreach ($hit['highlight'] as $key => $value) { + $one->{$key} = $value[0]; + } + } + return $one; + }); } /** @@ -175,7 +189,7 @@ public function map(Builder $builder, $results, Model $model): ModelCollection */ public function getTotalCount($results): int { - return (int) $results['hits']['total']['value']; + return (int)$results['hits']['total']['value']; } /** @@ -185,8 +199,8 @@ public function flush(Model $model) { $this->elasticsearch->deleteByQuery([ 'refresh' => config('scout.document_refresh', true), - 'index' => $model->searchableAs(), - 'body' => [ + 'index' => $model->searchableAs(), + 'body' => [ 'query' => ['match_all' => []], ], ]); @@ -227,8 +241,8 @@ public function getClient(): Client protected function performCount(Builder $builder, array $options = []) { $query = [ - 'index' => $builder->index ?? $builder->model->searchableAs(), - 'body' => $builder->toArray(), + 'index' => $builder->index ?? $builder->model->searchableAs(), + 'body' => $builder->toArray(), 'ignore_throttled' => false, ]; @@ -243,8 +257,8 @@ protected function performCount(Builder $builder, array $options = []) protected function performSearch(Builder $builder, array $options = []) { $query = [ - 'index' => $builder->index ?: $builder->model->searchableAs(), - 'body' => $builder->toArray(), + 'index' => $builder->index ?: $builder->model->searchableAs(), + 'body' => $builder->toArray(), 'ignore_throttled' => false, ]; @@ -256,6 +270,16 @@ protected function performSearch(Builder $builder, array $options = []) $query['from'] = $options['from']; } + $scoutSettings = $builder->model->searchSettings ?? []; + + // 高亮 + if ($scoutSettings && isset($scoutSettings['attributesToHighlight'])) { + $attributes = $scoutSettings['attributesToHighlight']; + foreach ($attributes as $attribute) { + $query['body']['highlight']['fields'][$attribute] = new \stdClass(); + } + } + if ($builder->callback) { return call_user_func( $builder->callback, From be442d2cf375f767429da7057a1ae2f0e37a3396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E4=BB=81=E6=81=95?= <1051953562@qq.com> Date: Thu, 16 Jun 2022 09:52:30 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=AB=98=E4=BA=AE?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Engine.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index e352001..10df156 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -270,9 +270,9 @@ protected function performSearch(Builder $builder, array $options = []) $query['from'] = $options['from']; } - $scoutSettings = $builder->model->searchSettings ?? []; + $scoutSettings = $builder->model->getScoutSettings() ?? []; - // 高亮 + // highlight if ($scoutSettings && isset($scoutSettings['attributesToHighlight'])) { $attributes = $scoutSettings['attributesToHighlight']; foreach ($attributes as $attribute) { From 0a4f8049fd5f3f13f86c2e8133b2819ded5056b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E4=BB=81=E6=81=95?= <1051953562@qq.com> Date: Thu, 16 Jun 2022 10:52:35 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=AB=98=E4=BA=AE?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 41 ++++++++++++++++++++++++++++++++++++----- src/Engine.php | 17 ++++++++++------- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0b3e028..02066e5 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ [`hyperf-ext/scout`](https://github.com/hyperf-ext/scout) 组件为 Hyperf 模型的全文搜索提供了基于 Elasticsearch 的简单的解决方案。通过使用模型观察者,组件会自动同步模型记录的搜索索引。 -该组件移植自 [`Laravel Scout`](https://github.com/laravel/scout ),与 Laravel Scout 不同的是,为了提升使用体验并更好的与 Elasticsearch 结合,该组件移除了自定义驱动的特性,仅支持 Elasticsearch,同时针对 Elasticsearch 的特性在原来的基础上进行了诸多的优化,通过队列更新索引的方式也改为通过协程实现。 +该组件移植自 [`Laravel Scout`](https://github.com/laravel/scout ),与 Laravel Scout 不同的是,为了提升使用体验并更好的与 Elasticsearch 结合,该组件移除了自定义驱动的特性,仅支持 Elasticsearch,同时针对 +Elasticsearch 的特性在原来的基础上进行了诸多的优化,通过队列更新索引的方式也改为通过协程实现。 另外,组件在搜索查询构造器中通过 [`ongr/elasticsearch-dsl`](https://github.com/ongr-io/ElasticsearchDSL) 包来使用 Elasticsearch DSL 构建复杂的查询条件。 > 注意,组件依赖的 `elasticsearch/elasticsearch` 包版本为 `^7.9`,映射类型已被废弃并将在 `8.0` 中彻底移除,因此组件同样也不提供映射类型的支持,即一个模型对应一个索引。 -> +> > 使用独立的索引取代使用映射类型可以让数据更倾向于密集而非稀疏,并且由于同一个索引中的所有的文档表示为同一种实体,在通过全文搜索时打分的条件统计会更为精确。 - ## 安装 @@ -62,6 +62,35 @@ class Post extends Model ## 配置模型 +### 配置 Elasticsearch 搜索设置 + +搜索设置可以通过在模型中添加 `searchSetting` 属性来配置,目前只支持高亮: + +```php + [ + 'title' => ["pre_tags" => "", "post_tags" => ""], + 'content' + ], + ]; +} +``` + ### 配置 Elasticsearch 索引设置 索引设置可以通过在模型中添加 `scoutSettings` 属性或重写 `getScoutSettings` 方法来配置: @@ -529,13 +558,15 @@ $orders = App\Models\Order::search('Star Trek')->paginate(15); ### 软删除 -如果您索引的模型配置了[软删除](https://hyperf.wiki/2.0/#/zh-cn/db/model?id=%e8%bd%af%e5%88%a0%e9%99%a4 ),并且您需要搜索已删除的模型,请设置 `config/autoload/scout.php` 中的 `soft_delete` 选项为 `true`(默认值): +如果您索引的模型配置了[软删除](https://hyperf.wiki/2.0/#/zh-cn/db/model?id=%e8%bd%af%e5%88%a0%e9%99%a4 ),并且您需要搜索已删除的模型,请设置 `config/autoload/scout.php` +中的 `soft_delete` 选项为 `true`(默认值): ```php 'soft_delete' => true, ``` -当此配置选项为 `true` 时,Scout 将不会从搜索索引中删除软删除的模型。并且在查询时,其逻辑与传统的模型是一致的,因此请确保索引的数据中包含软删除的标识字段,通常是 `deleted_at`。然后,您可以在搜索时使用 `withTrashed` 或 `onlyTrashed` 方法来检索软删除的记录: +当此配置选项为 `true` 时,Scout 将不会从搜索索引中删除软删除的模型。并且在查询时,其逻辑与传统的模型是一致的,因此请确保索引的数据中包含软删除的标识字段,通常是 `deleted_at`。然后,您可以在搜索时使用 `withTrashed` 或 `onlyTrashed` +方法来检索软删除的记录: ```php // 检索结果时包含已删除记录... diff --git a/src/Engine.php b/src/Engine.php index 10df156..8ede826 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -169,7 +169,6 @@ public function map(Builder $builder, $results, Model $model) return $idPositions[$model->getScoutKey()]; })->keyBy($model->getKeyName()); - return collect($results['hits']['hits'])->map(function ($hit) use ($res) { $one = $res[$hit['_id']]; @@ -270,13 +269,17 @@ protected function performSearch(Builder $builder, array $options = []) $query['from'] = $options['from']; } - $scoutSettings = $builder->model->getScoutSettings() ?? []; + $searchSetting = $builder->model->searchSetting ?? []; - // highlight - if ($scoutSettings && isset($scoutSettings['attributesToHighlight'])) { - $attributes = $scoutSettings['attributesToHighlight']; - foreach ($attributes as $attribute) { - $query['body']['highlight']['fields'][$attribute] = new \stdClass(); + // 高亮 + if ($searchSetting && isset($searchSetting['attributesToHighlight'])) { + $attributes = $searchSetting['attributesToHighlight']; + foreach ($attributes as $key => $attribute) { + if (is_array($attribute)) { + $query['body']['highlight']['fields'][$key] = $attribute; + } else { + $query['body']['highlight']['fields'][$attribute] = new \stdClass(); + } } }