wilr/silverstripe-algolia

Algolia 索引器和搜索功能

安装次数: 14,824

依赖: 0

建议者: 0

安全性: 0

星级: 13

关注者: 6

分支: 16

开放问题: 5

类型:silverstripe-vendormodule

1.7.5 2024-09-16 22:54 UTC

README

codecov Version License

维护者联系方式

安装

composer require "wilr/silverstripe-algolia"

特性

☑️ 支持多个索引,并将记录保存到多个索引中。

☑️ 集成到现有版本化工作流程中。

☑️ 不依赖于CMS,支持任何DataObject子类。

☑️ 支持队列作业,将操作卸载到Algolia。

☑️ 通过YAML和PHP轻松配置搜索配置和索引。

☑️ 索引网页模板,因此支持Elemental和自定义字段。

文档

Algolia的搜索即服务及其完整的API套件允许团队轻松开发定制、快速搜索和发现体验,令人愉悦并促进转化。

此模块添加了将Silverstripe页面同步到Algolia索引的能力。

对任何继承自SiteTree的对象或应用了Wilr\SilverStripe\Algolia\Extensions\AlgoliaObjectExtension到您的DataObjects的对象,索引和删除文档都是透明的。

🛠️ 设置

首先,在Algolia.com注册账户并安装此模块。安装后,通过YAML配置API密钥(建议使用环境变量)。

app/_config/algolia.yml

---
Name: algolia
After: silverstripe-algolia
---
SilverStripe\Core\Injector\Injector:
    Wilr\SilverStripe\Algolia\Service\AlgoliaService:
        properties:
            adminApiKey: "`ALGOLIA_ADMIN_API_KEY`"
            searchApiKey: "`ALGOLIA_SEARCH_API_KEY`"
            applicationId: "`ALGOLIA_SEARCH_APP_ID`"
            indexes:
                IndexName:
                    includeClasses:
                        - SilverStripe\CMS\Model\SiteTree
                    indexSettings:
                        attributesForFaceting:
                            - "filterOnly(objectClassName)"

一旦配置了索引和API密钥,运行dev/build以更新数据库并刷新索引设置。或者,您也可以运行AlgoliaConfigure来手动重建索引设置。

配置索引名称

此模块将假设您的索引设置为dev_{IndexName}test_{IndexName}live_{IndexName},其中环境类型的结果作为前缀添加到主YAML配置中列出的名称。

如果您明确想要禁用环境前缀(或使用自定义方法),请使用ALGOLIA_PREFIX_INDEX_NAME环境变量。

ALGOLIA_PREFIX_INDEX_NAME='dev_will'

或者,要使用测试数据在开发中使用ALGOLIA_PREFIX_INDEX_NAME='live'

定义副本索引

如果您的搜索表单提供排序选项(例如最新或相关性),则将使用副本索引(https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/how-to/creating-replicas/

可以使用相同的YAML配置定义这些。

---
Name: algolia
After: silverstripe-algolia
---
SilverStripe\Core\Injector\Injector:
    Wilr\SilverStripe\Algolia\Service\AlgoliaService:
        properties:
            adminApiKey: "`ALGOLIA_ADMIN_API_KEY`"
            searchApiKey: "`ALGOLIA_SEARCH_API_KEY`"
            applicationId: "`ALGOLIA_SEARCH_APP_ID`"
            indexes:
                IndexName:
                    includeClasses:
                        - SilverStripe\CMS\Model\SiteTree
                    indexSettings:
                        attributesForFaceting:
                            - "filterOnly(ObjectClassName)"
                        replicas:
                            - IndexName_Latest
                IndexName_Latest:
                    indexSettings:
                        ranking:
                            - "desc(objectCreated)"
                            - "typo"
                            - "words"
                            - "filters"
                            - "proximity"
                            - "attribute"
                            - "exact"
                            - "custom"

索引

如果要在现有网站上安装,请运行AlgoliaReindex任务(通过CLI)以导入现有数据。这将批量将数据库中的所有记录导入上述配置的索引中。

./vendor/bin/sake dev/tasks/AlgoliaReindex "flush=1"

从现在起,将通过onAfterPublish钩子自动索引单个记录,并通过在发布或取消发布文档时调用的onAfterUnpublish钩子删除记录。如果您的DataObject没有实现Versioned扩展,您需要通过调用$item->indexInAlgolia()$item->removeFromAlgolia()自行管理此状态。

AlgoliaReindex接受多个参数以允许自定义批量索引。例如,如果您有大量职位空缺要批量导入,但只需要活跃的,则可以按以下方式触发任务

/vendor/bin/sake dev/tasks/AlgoliaReindex "onlyClass=Vacancy&filter=ExpiryDate>NOW()"

如果您没有访问CLI(即Silverstripe Cloud),则也可以通过队列作业AlgoliaReindexAllJob批量重新索引。

可选

forceAll 强制重新同步所有 Silverstripe 记录。

./vendor/bin/sake dev/tasks/AlgoliaReindex "flush=1&forceAll=1"

clearAll 在重新索引之前截断搜索索引。

./vendor/bin/sake dev/tasks/AlgoliaReindex "flush=1&clearAll=1&forceAll=1"

自定义索引属性(字段)

默认情况下,只有 IDTitleLinkLastEdited 将从每个记录中索引。要指定其他字段,请定义一个 algolia_index_fields 配置变量。

class MyPage extends Page {
    // ..
    private static $algolia_index_fields = [
        'Content',
        'MyCustomColumn',
        'RelationshipName'
    ];
}

或者,你可以在你的对象上定义一个 exportObjectToAlgolia 方法。这个方法接收默认索引字段,然后允许你根据需要添加或删除字段。

use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\Map;

class MyPage extends Page {

    public function exportObjectToAlgolia($data)
    {
        $data = array_merge($data, [
            'MyCustomField' => $this->MyCustomField()
        ]);

        $map = new Map(ArrayList::create());

        foreach ($data as $k => $v) {
            $map->push($k, $v);
        }

        return $map;
    }
}

自定义索引关系

默认情况下,将任何关系($has_one$has_many$many_many)的 ID 和 Title 字段推送到字段 relation{name} 中,记录 ID 和 Title 与记录的行为一致。

可以通过 PHP 函数索引关系的其他字段。

public function updateAlgoliaRelationshipAttributes(\SilverStripe\ORM\Map $attributes, $related)
{
    $attributes->push('CategoryName', $related->CategoryName);
}

排除对象索引

对象可以定义一个 canIndexInAlgolia 方法,如果对象不应该在 algolia 中索引,则该方法应返回 false。

public function canIndexInAlgolia(): bool
{
    return ($this->Expired) ? false : true;
}

队列索引

为了减少在发布更改时等待第三方服务的影响,此模块利用 queued-jobs 模块上传索引操作。可以通过 Config YAML 禁用排队功能。

Wilr\SilverStripe\Algolia\Extensions\AlgoliaObjectExtension:
    use_queued_indexing: false

显示和检索结果

对于您的网站前端,如果您愿意,可以使用 InstantSearch.js 库,或者要从 Algolia 获取结果的 PaginatedList,在您的 Controller 子类中创建一个方法来调用 Wilr\SilverStripe\Algolia\Service\AlgoliaQuerier

<?php

use SilverStripe\Core\Injector\Injector;
use Wilr\SilverStripe\Algolia\Service\AlgoliaQuerier;

class PageController extends ContentController
{
    public function results()
    {
        $hitsPerPage = 25;
        $paginatedPageNum = floor($this->request->getVar('start') / $hitsPerPage);

        $results = Injector::inst()->get(AlgoliaQuerier::class)->fetchResults(
            'indexName',
            $this->request->getVar('search'), [
                'page' => $this->request->getVar('start') ? $paginatedPageNum : 0,
                'hitsPerPage' => $hitsPerPage
            ]
        );

        return [
            'Title' => 'Search Results',
            'Results' => $results
        ];
    }
}

或者,您还可以使用 JS Search SDK(https://www.algolia.com/doc/api-client/getting-started/install/javascript/)。

🔍 检查对象字段

为了帮助调试要推送到 Algolia 的字段以及查看已在 Algolia 中的信息,请使用 AlgoliaInspect BuildTask。这可以通过 CLI 运行。

./vendor/bin/sake dev/tasks/AlgoliaInspect "class=Page&id=1"

将输出 ID 为 '1' 的页面的 Algolia 数据结构。

Elemental 支持

默认情况下,此模块抓取网页的 main HTML 部分,并将其存储在 Algolia 的 objectForTemplate 字段中。此内容通过 AlgoliaPageCrawler 类解析。

<main>
    $ElementalArea
    <!-- will be indexed via Algolia -->
</main>

如果此行为不受欢迎,则可以通过 YAML 禁用它。

Wilr\SilverStripe\Algolia\Service\AlgoliaIndexer:
  include_page_content: false

或者,您可以使用 YAML 指定要索引的 HTML 选择器。例如,要索引具有 data-index 属性的任何元素。

Wilr\SilverStripe\Algolia\Service\AlgoliaPageCrawler:
  content_xpath_selector: '//[data-index]'

子站支持

如果您使用 Silverstripe Subsite 模块运行多个网站,您可以通过几种方式处理索引。

  • 使用每个站点的独立索引。
  • 使用单个索引,但在 Algolia 中添加 SubsiteID 字段。

决定选择哪一种取决于网站的性质以及它们之间的相关性,但强烈建议使用独立的索引以防止网站之间信息泄露以及搞乱分析和查询建议。

使用单个索引的子站支持

如果子站经常被创建,则您可能更喜欢单个索引,因为索引名称需要通过 YAML 控制,因此任何新的子站都需要代码更改。

此方法的关键是在属性中添加 SubsiteID 以进行细分,并在查询时。

步骤 1. 将字段添加到 Algolia

SilverStripe\Core\Injector\Injector:
  Wilr\SilverStripe\Algolia\Service\AlgoliaService:
    properties:
      adminApiKey: "`ALGOLIA_ADMIN_API_KEY`"
      searchApiKey: "`ALGOLIA_SEARCH_API_KEY`"
      applicationId: "`ALGOLIA_SEARCH_APP_ID`"
      indexes:
        index_main_site:
          includeClasses:
            - SilverStripe\CMS\Model\SiteTree
          indexSettings:
            distinct: true
            attributeForDistinct: "objectLink"
            searchableAttributes:
              - objectTitle
              - objectContent
              - objectLink
              - Summary
              - objectForTemplate
            attributesForFaceting:
              - "filterOnly(objectClassName)"
              ***- "filterOnly(SubsiteID)"***

步骤 2. 通过数据扩展在 SiteTree 上公开字段(确保应用扩展)

<?php

class SiteTreeExtension extends DataExtension
{
    private static $algolia_index_fields = [
        'SubsiteID'
    ];
}

步骤 3. 在结果中根据子站 ID 进行筛选

<?php

use SilverStripe\Core\Injector\Injector;
use Wilr\SilverStripe\Algolia\Service\AlgoliaQuerier;

class PageController extends ContentController
{
    public function results()
    {
        $hitsPerPage = 25;
        $paginatedPageNum = floor($this->request->getVar('start') / $hitsPerPage);

        $results = Injector::inst()->get(AlgoliaQuerier::class)->fetchResults(
            'indexName',
            $this->request->getVar('search'), [
                'page' => $this->request->getVar('start') ? $paginatedPageNum : 0,
                'hitsPerPage' => $hitsPerPage,
                'facetFilters' => [
                    'SubsiteID' => SubsiteState::singleton()->getSubsiteId()
                ]
            ]
        );

        return [
            'Title' => 'Search Results',
            'Results' => $results
        ];
    }
}

使用独立索引的子站支持

在您的配置中创建多个索引,并使用 includeFilter 参数过滤每个索引的记录。

includeFilter 应该采用以下格式:{$Class}: {$WhereQuery} 其中 $WhereQuery 是ORM在给定类上执行的基本SQL语句。

SilverStripe\Core\Injector\Injector:
  Wilr\SilverStripe\Algolia\Service\AlgoliaService:
    properties:
      adminApiKey: "`ALGOLIA_ADMIN_API_KEY`"
      searchApiKey: "`ALGOLIA_SEARCH_API_KEY`"
      applicationId: "`ALGOLIA_SEARCH_APP_ID`"
      indexes:
        index_main_site:
          includeClasses:
            - SilverStripe\CMS\Model\SiteTree
          includeFilter:
            "SilverStripe\\CMS\\Model\\SiteTree": "SubsiteID = 0"
          indexSettings:
            distinct: true
            attributeForDistinct: "objectLink"
            searchableAttributes:
              - objectTitle
              - objectContent
              - objectLink
              - Summary
              - objectForTemplate
            attributesForFaceting:
              - "filterOnly(objectClassName)"
        index_subsite_pages:
          includeClasses:
            - SilverStripe\CMS\Model\SiteTree
          includeFilter:
            "SilverStripe\\CMS\\Model\\SiteTree": "SubsiteID > 0"
          indexSettings:
            distinct: true
            attributeForDistinct: "objectLink"
            searchableAttributes:
              - objectTitle
              - objectContent
              - objectLink
              - Summary
              - objectForTemplate
            attributesForFaceting:
              - "filterOnly(objectClassName)"