创建内容连接器

内容连接器是一种软件程序,用于遍历企业存储库中的数据并填充数据源。针对内容连接器的开发,Google 提供了以下选项:

  • 内容连接器 SDK。如果您使用 Java 编程,这是一个不错的选择。内容连接器 SDK 是 REST API 的封装容器,可让您快速创建连接器。要使用此 SDK 创建内容连接器,请参阅使用内容连接器 SDK 创建内容连接器

  • 低层级 REST API 或 API 库。如果您不用 Java 编程,或者您的存储库更适合 REST API 或库,请使用这些选项。要使用 REST API 创建内容连接器,请参阅使用 REST API 创建内容连接器

一个典型的内容连接器会执行以下任务:

  1. 读取和处理配置参数。
  2. 从第三方内容存储库中提取离散的可索引数据块,即“项”
  3. 将 ACL、元数据和内容数据合并到可索引项中。
  4. 将项编入 Cloud Search 数据源的索引中。
  5. (可选)侦听来自第三方内容存储库的更改通知。更改通知将转换为索引请求,使 Cloud Search 数据源与第三方存储库保持同步。连接器仅在存储库支持更改检测的情况下执行此任务。

使用内容连接器 SDK 创建内容连接器

以下部分介绍如何使用内容连接器 SDK 创建内容连接器。

设置依赖项

您必须在构建文件中加入特定的依赖项才能使用 SDK。请点击下面的标签查看对应于您的构建环境的依赖项:

Maven

<dependency>
    <groupId>com.google.enterprise.cloudsearch</groupId>
    <artifactId>google-cloudsearch-indexing-connector-sdk</artifactId>
    <version>v1-0.0.3</version>
    </dependency>
    

Gradle

compile group: 'com.google.enterprise.cloudsearch',
            name: 'google-cloudsearch-indexing-connector-sdk',
            version: 'v1-0.0.3'
    

创建连接器配置

每个连接器都有一个配置文件,其中包含连接器使用的参数,例如存储库的 ID。这些参数以键值对的形式进行定义,例如

api.sourceId=1234567890abcdef

Google Cloud Search SDK 包含 Google 提供的若干个配置参数,可供所有连接器使用。您必须在配置文件中声明以下由 Google 提供的参数:

  • 对于内容连接器,您必须声明 api.sourceIdapi.serviceAccountPrivateKeyFile,因为这些参数标识了存储库的位置和访问存储库所需的私钥。
  • 对于身份连接器,您必须声明 api.identitySourceId,因为此参数标识了外部身份源的位置。如果您要同步用户,则还必须将 api.customerId 声明为企业 G Suite 帐号的唯一 ID。

对于其他由 Google 提供的参数,如果不需要替换其默认值,您就不需要在配置文件中声明这些参数。如需了解 Google 提供的配置参数的其他信息,例如如何生成特定的 ID 和密钥,请参阅 Google 提供的配置参数

此外,您还可以定义存储库的专属参数,以便在配置文件中使用。

注意:虽然连接器属性文件没有严格的命名要求,但我们建议使用 .properties.config 扩展名保存文件。

将配置文件传递给连接器

设置系统属性 config 以将配置文件传递给连接器。您可以在启动连接器时使用 -D 参数来设置属性。例如,以下命令会使用 MyConfig.properties 配置文件启动连接器:

java -classpath myconnector.jar;... -Dconfig=MyConfig.properties MyConnector
    

如果缺少此参数,SDK 将尝试访问名为 connector-config.properties 的默认配置文件。

确定您的遍历策略

内容连接器的主要功能是遍历存储库并为其中的数据编制索引。您必须根据存储库中数据的大小和布局实现遍历策略。您可以设计自己的专属策略,也可以从 SDK 中实现的以下策略中进行选择:

完全遍历策略

完全遍历策略会扫描整个存储库,并不加分辨地将每一项都编入索引。如果您的存储库规模较小,并且能够负担得起每次编制索引都执行完全遍历的开销,通常可以使用此策略。

这一遍历策略适用于大部分数据都处于静态且不分层的小型存储库。当存储库难以执行或完全不支持更改检测时,您也可以使用此遍历策略。

列表遍历策略

列表遍历策略扫描整个存储库,包括所有子节点,来确定每一项的状态。然后,连接器进行第二次遍历,仅将自上次编制索引以来添加的新项或已更新的项编入索引。此策略通常用于对现有索引执行增量更新(无需在每次更新索引时都执行完全遍历)。

如果存储库难以执行或完全不支持更改检测,您的数据不分层,或者需要处理庞大的数据集时,此遍历策略非常适用。

图形遍历

图形遍历策略扫描整个父节点,确定每一项的状态。然后,连接器进行第二次遍历,仅将根节点中自上次编制索引以来添加的新项或已更新的项编入索引。最后,连接器遍历所有子 ID,然后将子节点中添加的新项或已更新的项编入索引。连接器以递归方式继续遍历所有子节点,直到处理完所有项。这一遍历方法通常用于分层存储库,列出这种存储库中的所有 ID 往往不切实际。

如果您拥有需要抓取的分层数据,例如一系列目录或网页,则此策略非常适用。

这些遍历策略中的每一项策略都由 SDK 中的模板连接器类实现。虽然您可以实现自己的遍历策略,但这些模板可以大大加快连接器的开发速度。要使用模板创建连接器,请转到与您的遍历策略对应的部分:

使用模板类创建完全遍历连接器

本文档的这一部分引用了 FullTraversalSample 示例中的代码段。

实现连接器的入口点

连接器的入口点采用 main() 方法。此方法的主要任务是创建 Application 类的实例,并调用其 start() 方法来运行连接器。

在调用 application.start() 之前,请使用 IndexingApplication.Builder 类对 FullTraversalConnector 模板进行实例化。FullTraversalConnector 接受您实现其方法的 Repository 对象。以下代码段显示了如何实现 main() 方法:

FullTraversalSample.java
/**
     * This sample connector uses the Cloud Search SDK template class for a full
     * traversal connector.
     *
     * @param args program command line arguments
     * @throws InterruptedException thrown if an abort is issued during initialization
     */
    public static void main(String[] args) throws InterruptedException {
      Repository repository = new SampleRepository();
      IndexingConnector connector = new FullTraversalConnector(repository);
      IndexingApplication application = new IndexingApplication.Builder(connector, args).build();
      application.start();
    }

在后台,在连接器的 main() 方法调用 Application.build 之后,SDK 才会调用 initConfig() 方法。initConfig() 方法执行以下任务:

  1. 调用 Configuation.isInitialized() 方法以确保 Configuration 尚未初始化。
  2. 使用 Google 提供的键值对初始化 Configuration 对象。每个键值对都存储在 Configuration 对象内的 ConfigValue 对象中。

实现 Repository 接口

Repository 对象的唯一目的是对存储库项执行遍历和索引操作。使用模板时,您只需替换 Repository 接口中的某些方法即可创建内容连接器。您替换的方法取决于您使用的模板和遍历策略。对于 FullTraversalConnector,请替换以下方法:

  • init() 方法。要执行任何数据存储库设置和初始化,请替换 init() 方法。

  • getAllDocs() 方法。要遍历数据存储库中的所有项并将其编入索引,请替换 getAllDocs() 方法。每次执行计划遍历(由您的配置定义)时,系统都将调用一次此方法。

  • (可选)getChanges() 方法。如果您的存储库支持更改检测,请替换 getChanges() 方法。每次执行计划增量遍历(由您的配置定义)时,系统都将调用一次此方法,以检索经修改的项并将其编入索引。

  • (可选)close() 方法。如果需要清理存储库,请替换 close() 方法。连接器关闭期间会调用一次此方法。

Repository 对象的每个方法都返回某种类型的 ApiOperation 对象。ApiOperation 对象以单次或多次 IndexingService.indexItem() 调用的形式来执行操作,在实际上以此形式对存储库进行索引编制。

获取自定义配置参数

作为处理连接器配置流程的一环,您需要从 Configuration 对象获取所有自定义参数。此任务通常在 Repository 类的 init() 方法中执行。

Configuration 类有几种方法供您使用,以从配置中获取不同的数据类型,并且每个方法都会返回一个 ConfigValue 对象。然后,您将使用 ConfigValue 对象的 get() 方法来检索实际值。FullTraversalSample 的以下代码段显示了如何从 Configuration 对象检索单个自定义整数值:

FullTraversalSample.java
@Override
    public void init(RepositoryContext context) {
      log.info("Initializing repository");
      numberOfDocuments = Configuration.getInteger("sample.documentCount", 10).get();
    }

要获取和解析包含多个值的参数,请使用 Configuration 类的一个类型解析器将数据解析为离散区块。以下是一段取自示例教程中的连接器的代码段,该连接器使用 getMultiValue 方法来获取一系列 GitHub 存储库的名称,并生成相应的列表:

GithubRepository.java
ConfigValue<List<String>> repos = Configuration.getMultiValue(
        "github.repos",
        Collections.emptyList(),
        Configuration.STRING_PARSER);

执行完全遍历

重写 getAllDocs() 以执行完全遍历,并为您的存储库编制索引。getAllDocs() 方法接受检查点。如果进程被中断,可使用该检查点在特定项处恢复索引。请在 getAllDocs() 方法中对您存储库中的每一项执行以下步骤:

  1. 设置权限。
  2. 为要编入索引的项设置元数据。
  3. 将元数据和该项合并到一个可索引的 RepositoryDoc 中。
  4. 将每个可索引项打包到 getAllDocs() 方法返回的迭代器中。请注意,getAllDocs() 实际上返回一个 CheckpointCloseableIterable,它是 ApiOperation 对象的迭代,每个对象表示在 RepositoryDoc 上执行的一个 API 请求,例如将其编入索引。

如果项的数量太多以至于无法在单次调用中处理,请添加一个检查点并设置 hasMore(true) 以指明有更多可编入索引的项。

设置项的权限

您的存储库使用访问控制列表 (ACL) 来标识对某一项有访问权限的用户或组。ACL 是可以访问该项的组或用户的 ID 列表。

您必须在数据源中复制此 ACL,以确保只有具有该项访问权限的用户才能在搜索结果中看到该项。因此,在将该项编入索引时,必须包含该项的 ACL,以使得 Google Cloud Search 具有必要信息来为该项设置正确的访问级别。

内容连接器 SDK 提供丰富的 ACL 类和方法,可对大多数存储库的 ACL 进行建模。您必须分析存储库中每一项的 ACL,并在将项编入索引时为 Google Cloud Search 创建相应的 ACL。如果您存储库的 ACL 使用 ACL 继承等概念,则可能难以对此类 ACL 进行建模。如需进一步了解 Google Cloud Search ACL,请参阅 Google Cloud Search ACL

注意:Cloud Search Indexing API 支持单网域 ACL,但不支持跨网域 ACL。从根本上说,您将使用 Acl.Builder 类,通过 ACL 设置对每一项的访问权限。以下代码段截取自完全遍历示例,允许所有用户或“主帐号”(getCustomerPrincipal()) 在执行搜索时可以“读取”所有项 (.setReaders())。

FullTraversalSample.java
// Make the document publicly readable within the domain
    Acl acl = new Acl.Builder()
        .setReaders(Collections.singletonList(Acl.getCustomerPrincipal()))
        .build();

您需要了解 ACL 才能对数据源的 ACL 进行正确建模。例如,您可能在使用某种继承模型的文件系统中将文件编入了索引,其中子文件夹从父文件夹继承权限。为 ACL 继承建模需要参阅 Google Cloud Search ACL 中涵盖的其他信息

设置项的元数据

元数据存储在 Item 对象中。要创建一个 Item,您至少需要该项的唯一字符串 ID、项类型、ACL、网址和版本。以下代码段显示了如何使用 IndexingItemBuilder 辅助类构建 Item

FullTraversalSample.java
// Url is required. Use google.com as a placeholder for this sample.
    String viewUrl = "https://www.google.com";

    // Version is required, set to current timestamp.
    byte[] version = Longs.toByteArray(System.currentTimeMillis());

    // Using the SDK item builder class to create the document with appropriate attributes
    // (this can be expanded to include metadata fields etc.)
    Item item = IndexingItemBuilder.fromConfiguration(Integer.toString(id))
        .setItemType(IndexingItemBuilder.ItemType.CONTENT_ITEM)
        .setAcl(acl)
        .setSourceRepositoryUrl(IndexingItemBuilder.FieldOrValue.withValue(viewUrl))
        .setVersion(version)
        .build();

创建可索引项

设置完项的元数据后,您可以使用 RepositoryDoc.Builder 类创建实际的可索引项。以下示例显示了如何创建单个可索引项。

FullTraversalSample.java
// For this sample, content is just plain text
    String content = String.format("Hello world from sample doc %d", id);
    ByteArrayContent byteContent = ByteArrayContent.fromString("text/plain", content);

    // Create the fully formed document
    RepositoryDoc doc = new RepositoryDoc.Builder()
        .setItem(item)
        .setContent(byteContent, IndexingService.ContentFormat.TEXT)
        .build();

RepositoryDoc 是一种执行实际 IndexingService.indexItem() 请求的 ApiOperation

您还可以使用 RepositoryDoc.Builder 类的 setRequestMode() 方法将索引请求标识为 ASYNCHRONOUSSYNCHRONOUS

ASYNCHRONOUS
异步模式会导致从索引到服务的延迟时间变长,并造成索引请求占用大量吞吐量配额。建议在对整个存储库执行首次索引编制(回填)时使用异步模式。
SYNCHRONOUS
同步模式可缩短索引到服务的延迟时间,并占用有限的吞吐量配额。建议在对存储库的更新和变更执行编入索引时使用同步模式。如果未指定,请求模式默认为 SYNCHRONOUS

在迭代器中打包每个可索引项

getAllDocs() 方法返回一个 Iterator,具体地说是 RepositoryDoc 对象的 CheckpointCloseableIterable。您可以使用 CheckpointClosableIterableImpl.Builder 类来构建和返回迭代器。以下代码段显示了如何构建和返回迭代器。

FullTraversalSample.java
CheckpointCloseableIterable<ApiOperation> iterator =
      new CheckpointCloseableIterableImpl.Builder<>(allDocs).build();

SDK 执行迭代器中包含的每个索引调用。

后续步骤

您可以执行以下几个后续步骤:

使用模板类创建列表遍历连接器

Cloud Search Indexing Queue 用于保存存储库中每一项的 ID 和可选哈希值。列表遍历连接器将项 ID 推送到 Google Cloud Search Indexing Queue,并以每次检索一个的形式以将其编入索引。Google Cloud Search 维护队列并比较队列内容以确定项的状态,例如是否已从存储库中删除项。如需进一步了解 Cloud Search Indexing Queue,请参阅 Google Cloud Search Indexing Queue

本文档的这一部分引用了 ListTraversalSample 示例中的代码段。

实现连接器的入口点

连接器的入口点采用 main() 方法。此方法的主要任务是创建 Application 类的实例,并调用其 start() 方法来运行连接器。

在调用 application.start() 之前,请使用 IndexingApplication.Builder 类对 ListingConnector 模板进行实例化。ListingConnector 接受您实现其方法的 Repository 对象。以下代码段显示了如何实例化 ListingConnector 及与其关联的 Repository

ListTraversalSample.java
/**
     * This sample connector uses the Cloud Search SDK template class for a
     * list traversal connector.
     *
     * @param args program command line arguments
     * @throws InterruptedException thrown if an abort is issued during initialization
     */
    public static void main(String[] args) throws InterruptedException {
      Repository repository = new SampleRepository();
      IndexingConnector connector = new ListingConnector(repository);
      IndexingApplication application = new IndexingApplication.Builder(connector, args).build();
      application.start();
    }

在后台,在连接器的 main() 方法调用 Application.build 之后,SDK 才会调用 initConfig() 方法。 initConfig() 方法执行以下任务:

  1. 调用 Configuation.isInitialized() 方法以确保 Configuration 尚未初始化。
  2. 使用 Google 提供的键值对初始化 Configuration 对象。每个键值对都存储在 Configuration 对象内的 ConfigValue 对象中。

实现 Repository 接口

Repository 对象的唯一目的是对存储库项执行遍历和索引操作。使用模板时,您只需替换 Repository 接口中的某些方法即可创建内容连接器。 您替换的方法取决于您使用的模板和遍历策略。 ListingConnector对于 ,请替换以下方法:

  • init() 方法。要执行任何数据存储库设置和初始化,请替换 init() 方法。

  • getIds() 方法。要检索存储库中所有记录的 ID 和哈希值,请替换 getIds() 方法。

  • getDoc() 方法。要添加项、更新项、修改项或从索引删除项,请替换 getDoc() 方法。

  • (可选)getChanges() 方法。如果您的存储库支持更改检测,请替换 getChanges() 方法。每次执行计划增量遍历(由您的配置定义)时,系统都将调用一次此方法,以检索经修改的项并将其编入索引。

  • (可选)close() 方法。如果需要清理存储库,请替换 close() 方法。连接器关闭期间会调用一次此方法。

Repository 对象的每个方法都返回某种类型的 ApiOperation 对象。ApiOperation 对象以单次或多次 IndexingService.indexItem() 调用的形式来执行操作,在实际上以此形式对存储库进行索引编制。

获取自定义配置参数

作为处理连接器配置流程的一环,您需要从 Configuration 对象获取所有自定义参数。此任务通常在 Repository 类的 init() 方法中执行。

Configuration 类有几种方法供您使用,以从配置中获取不同的数据类型,并且每个方法都会返回一个 ConfigValue 对象。然后,您将使用 ConfigValue 对象的 get() 方法来检索实际值。FullTraversalSample 的以下代码段显示了如何从 Configuration 对象检索单个自定义整数值:

FullTraversalSample.java
@Override
    public void init(RepositoryContext context) {
      log.info("Initializing repository");
      numberOfDocuments = Configuration.getInteger("sample.documentCount", 10).get();
    }

要获取和解析包含多个值的参数,请使用 Configuration 类的一个类型解析器将数据解析为离散区块。以下是一段取自示例教程中的连接器的代码段,该连接器使用 getMultiValue 方法来获取一系列 GitHub 存储库的名称,并生成相应的列表:

GithubRepository.java
ConfigValue<List<String>> repos = Configuration.getMultiValue(
        "github.repos",
        Collections.emptyList(),
        Configuration.STRING_PARSER);

执行列表遍历

替换 getIds() 方法以检索存储库中所有记录的 ID 和哈希值。getIds() 方法接受一个检查点。如果进程被中断,可使用该检查点在特定项处恢复索引。

接下来,替换 getDoc() 方法以处理 Cloud Search Indexing Queue 中的每一项。

推送项 ID 和哈希值

替换 getIds() 以从存储库中提取项 ID 及与其关联的内容哈希值。然后将 ID 和哈希值对打包到指向 Cloud Search Indexing Queue 的推送操作请求中。一般来说,首先会推送根 ID 或父 ID,然后是子 ID,直到处理完整个项的层次结构。

getIds() 方法接受代表要编入索引的最后一项的检查点,如果进程被中断,可使用该检查点在特定项处恢复索引。请在 getIds() 方法中对您存储库中的每一项执行以下步骤:

  • 从存储库中获取每个项 ID 和关联的哈希值。
  • 将每个 ID 和哈希值对打包到 PushItems
  • 将每个 PushItems 合并到 getIds() 方法返回的迭代器中。请注意,getIds() 实际上返回一个 CheckpointCloseableIterable,它是 ApiOperation 对象的迭代,每个对象表示在 RepositoryDoc 上执行的一个 API 请求,例如将各项推送到队列中。

以下代码段显示了如何获取每个项 ID 和哈希值并将它们插入 PushItemsPushItems 是一个 ApiOperation 请求,用于将项推送到 Cloud Search Indexing Queue。

ListTraversalSample.java
PushItems.Builder allIds = new PushItems.Builder();
    for (Map.Entry<Integer, Long> entry : this.documents.entrySet()) {
      String documentId = Integer.toString(entry.getKey());
      String hash = this.calculateMetadataHash(entry.getKey());
      PushItem item = new PushItem().setMetadataHash(hash);
      log.info("Pushing " + documentId);
      allIds.addPushItem(documentId, item);
    }

以下代码段显示了如何使用 PushItems.Builder 类将 ID 和哈希值打包到单个推送 ApiOperation 中。

ListTraversalSample.java
ApiOperation pushOperation = allIds.build();
    CheckpointCloseableIterable<ApiOperation> iterator =
      new CheckpointCloseableIterableImpl.Builder<>(
          Collections.singletonList(pushOperation))
      .build();
    return iterator;

各项将被推送到 Cloud Search Indexing Queue 以进一步处理。

检索并处理每一项

替换 getDoc() 以处理 Cloud Search Indexing Queue 中的每一项。项可以是新添加的、已修改的、未更改的,或者不再存在于源存储库中的。检索新的或已修改的各个项,并将其编入索引。从索引中移除不再存在于源存储库中的项。

getDoc() 方法接受 Google Cloud Search Indexing Queue 中的项。请在 getDoc() 方法中对队列中的每一项执行以下步骤:

  1. 检查 Cloud Search Indexing Queue 中的项 ID 是否存在于存储库中。如果没有,请从索引中删除相应项。

  2. 轮询索引以获取项的状态,如果项未更改 (ACCEPTED),则不执行任何操作。

  3. 将已更改或新添加的项编入索引:

    1. 设置权限。
    2. 为要编入索引的项设置元数据。
    3. 将元数据和该项合并到一个可索引的 RepositoryDoc 中。
    4. 返回 RepositoryDoc

注意ListingConnector 模板不支持在 getDoc() 方法中返回 null。返回 null 将导致 NullPointerException.

处理已删除的项

以下代码段显示了如何确定某一项是否存在于存储库中,并且如果不存在则将其删除。

ListTraversalSample.java
String resourceName = item.getName();
    int documentId = Integer.parseInt(resourceName);

    if (!documents.containsKey(documentId)) {
      // Document no longer exists -- delete it
      log.info(() -> String.format("Deleting document %s", item.getName()));
      return ApiOperations.deleteItem(resourceName);
    }

请注意,documents 是表示存储库的数据结构。如果未在 documents 中找到 documentID,则返回 APIOperations.deleteItem(resourceName) 以从索引中删除该项。

处理未更改的项

以下代码段显示了如何在 Cloud Search Indexing Queue 中轮询项的状态并处理未更改的项。

ListTraversalSample.java
String currentHash = this.calculateMetadataHash(documentId);
    if (this.canSkipIndexing(item, currentHash)) {
      // Document neither modified nor deleted, ack the push
      log.info(() -> String.format("Document %s not modified", item.getName()));
      PushItem pushItem = new PushItem().setType("NOT_MODIFIED");
      return new PushItems.Builder().addPushItem(resourceName, pushItem).build();
    }

要确定项是否未修改,请检查项的状态以及可能指明更改的其他元数据。在本示例中,元数据哈希用于确定项是否已更改。

ListTraversalSample.java
/**
     * Checks to see if an item is already up to date
     *
     * @param previousItem Polled item
     * @param currentHash  Metadata hash of the current github object
     * @return PushItem operation
     */
    private boolean canSkipIndexing(Item previousItem, String currentHash) {
      if (previousItem.getStatus() == null || previousItem.getMetadata() == null) {
        return false;
      }
      String status = previousItem.getStatus().getCode();
      String previousHash = previousItem.getMetadata().getHash();
      return "ACCEPTED".equals(status)
          && previousHash != null
          && previousHash.equals(currentHash);
    }

设置项的权限

您的存储库使用访问控制列表 (ACL) 来标识对某一项有访问权限的用户或组。ACL 是可以访问该项的组或用户的 ID 列表。

您必须在数据源中复制此 ACL,以确保只有具有该项访问权限的用户才能在搜索结果中看到该项。因此,在将该项编入索引时,必须包含该项的 ACL,以使得 Google Cloud Search 具有必要信息来为该项设置正确的访问级别。

内容连接器 SDK 提供丰富的 ACL 类和方法,可对大多数存储库的 ACL 进行建模。您必须分析存储库中每一项的 ACL,并在将项编入索引时为 Google Cloud Search 创建相应的 ACL。如果您存储库的 ACL 使用 ACL 继承等概念,则可能难以对此类 ACL 进行建模。如需进一步了解 Google Cloud Search ACL,请参阅 Google Cloud Search ACL

注意:Cloud Search Indexing API 支持单网域 ACL,但不支持跨网域 ACL。从根本上说,您将使用 Acl.Builder 类,通过 ACL 设置对每一项的访问权限。以下代码段截取自完全遍历示例,允许所有用户或“主帐号”(getCustomerPrincipal()) 在执行搜索时可以“读取”所有项 (.setReaders())。

FullTraversalSample.java
// Make the document publicly readable within the domain
    Acl acl = new Acl.Builder()
        .setReaders(Collections.singletonList(Acl.getCustomerPrincipal()))
        .build();

您需要了解 ACL 才能对数据源的 ACL 进行正确建模。例如,您可能在使用某种继承模型的文件系统中将文件编入了索引,其中子文件夹从父文件夹继承权限。为 ACL 继承建模需要参阅 Google Cloud Search ACL 中涵盖的其他信息

设置项的元数据

元数据存储在 Item 对象中。要创建一个 Item,您至少需要该项的唯一字符串 ID、项类型、ACL、网址和版本。以下代码段显示了如何使用 IndexingItemBuilder 辅助类构建 Item

ListTraversalSample.java
// Url is required. Use google.com as a placeholder for this sample.
    String viewUrl = "https://www.google.com";

    // Version is required, set to current timestamp.
    byte[] version = Longs.toByteArray(System.currentTimeMillis());

    // Set metadata hash so queue can detect changes
    String metadataHash = this.calculateMetadataHash(documentId);

    // Using the SDK item builder class to create the document with
    // appropriate attributes. This can be expanded to include metadata
    // fields etc.
    Item item = IndexingItemBuilder.fromConfiguration(Integer.toString(documentId))
        .setItemType(IndexingItemBuilder.ItemType.CONTENT_ITEM)
        .setAcl(acl)
        .setSourceRepositoryUrl(IndexingItemBuilder.FieldOrValue.withValue(viewUrl))
        .setVersion(version)
        .setHash(metadataHash)
        .build();

创建可索引项

设置完项的元数据后,您可以使用 RepositoryDoc.Builder 创建实际的可索引项。 以下示例显示了如何创建单个可索引项。

ListTraversalSample.java
// For this sample, content is just plain text
    String content = String.format("Hello world from sample doc %d", documentId);
    ByteArrayContent byteContent = ByteArrayContent.fromString("text/plain", content);

    // Create the fully formed document
    RepositoryDoc doc = new RepositoryDoc.Builder()
        .setItem(item)
        .setContent(byteContent, IndexingService.ContentFormat.TEXT)
        .build();

RepositoryDoc 是一种执行实际 IndexingService.indexItem() 请求的 ApiOperation

您还可以使用 RepositoryDoc.Builder 类的 setRequestMode() 方法将索引请求标识为 ASYNCHRONOUSSYNCHRONOUS

ASYNCHRONOUS
异步模式会导致从索引到服务的延迟时间变长,并造成索引请求占用大量吞吐量配额。建议在对整个存储库执行首次索引编制(回填)时使用异步模式。
SYNCHRONOUS
同步模式可缩短索引到服务的延迟时间,并占用有限的吞吐量配额。建议在对存储库的更新和变更执行编入索引时使用同步模式。如果未指定,请求模式默认为 SYNCHRONOUS

后续步骤

您可以执行以下几个后续步骤:

  • (可选)实现 close() 方法以在运行结束前释放所有资源。
  • (可选)使用内容连接器 SDK 创建身份连接器

使用模板类创建图形遍历连接器

Cloud Search Indexing Queue 用于保存存储库中每一项的 ID 和可选哈希值。图形遍历连接器将项 ID 推送到 Googe Cloud Search Indexing Queue,并以每次检索一个的形式以将其编入索引。Google Cloud Search 维护队列并比较队列内容以确定项的状态,例如是否已从存储库中删除项。如需进一步了解 Google Search Indexing Queue,请参阅 Google Cloud Search Indexing Queue

在编入索引期间,将从数据存储库中提取项的内容,并将任何子项 ID 推送到队列。连接器以递归的方式处理父 ID 和子 ID,直到处理完所有项。

本文档的这一部分引用了 GraphTraversalSample 示例中的代码段。

实现连接器的入口点

连接器的入口点采用 main() 方法。此方法的主要任务是创建 Application 类的实例,并调用其 start() 方法来运行连接器。

在调用 application.start() 之前,请使用 IndexingApplication.Builder 类对 ListingConnector 模板进行实例化。ListingConnector 接受您实现其方法的 Repository 对象。

以下代码段显示了如何实例化 ListingConnector 及与其关联的 Repository

GraphTraversalSample.java
/**
     * This sample connector uses the Cloud Search SDK template class for a graph
     * traversal connector.
     *
     * @param args program command line arguments
     * @throws InterruptedException thrown if an abort is issued during initialization
     */
    public static void main(String[] args) throws InterruptedException {
      Repository repository = new SampleRepository();
      IndexingConnector connector = new ListingConnector(repository);
      IndexingApplication application = new IndexingApplication.Builder(connector, args).build();
      application.start();
    }

在后台,在连接器的 main() 方法调用 Application.build 之后,SDK 才会调用 initConfig() 方法。 initConfig() 方法执行以下任务:

  1. 调用 Configuation.isInitialized() 方法以确保 Configuration 尚未初始化。
  2. 使用 Google 提供的键值对初始化 Configuration 对象。每个键值对都存储在 Configuration 对象内的 ConfigValue 对象中。

实现 Repository 接口

Repository 对象的唯一目的是对存储库项执行遍历和索引操作。使用模板时,您只需替换 Repository 接口中的某些方法即可创建内容连接器。具体替换方法取决于您使用的模板和遍历策略。对于 ListingConnector,您可以替换以下方法:

  • init() 方法。要执行任何数据存储库设置和初始化,请替换 init() 方法。

  • getIds() 方法。要检索存储库中所有记录的 ID 和哈希值,请替换 getIds() 方法。

  • getDoc() 方法。要添加项、更新项、修改项或从索引删除项,请替换 getDoc() 方法。

  • (可选)getChanges() 方法。如果您的存储库支持更改检测,请替换 getChanges() 方法。每次执行计划增量遍历(由您的配置定义)时,系统都将调用一次此方法,以检索经修改的项并将其编入索引。

  • (可选)close() 方法。如果需要清理存储库,请替换 close() 方法。连接器关闭期间会调用一次此方法。

Repository 对象的每个方法都返回某种类型的 ApiOperation 对象。ApiOperation 对象以单次或多次 IndexingService.indexItem() 调用的形式来执行操作,在实际上以此形式对存储库进行索引编制。

获取自定义配置参数

作为处理连接器配置流程的一环,您需要从 Configuration 对象获取所有自定义参数。此任务通常在 Repository 类的 init() 方法中执行。

Configuration 类有几种方法供您使用,以从配置中获取不同的数据类型,并且每个方法都会返回一个 ConfigValue 对象。然后,您将使用 ConfigValue 对象的 get() 方法来检索实际值。FullTraversalSample 的以下代码段显示了如何从 Configuration 对象检索单个自定义整数值:

FullTraversalSample.java
@Override
    public void init(RepositoryContext context) {
      log.info("Initializing repository");
      numberOfDocuments = Configuration.getInteger("sample.documentCount", 10).get();
    }

要获取和解析包含多个值的参数,请使用 Configuration 类的一个类型解析器将数据解析为离散区块。以下是一段取自示例教程中的连接器的代码段,该连接器使用 getMultiValue 方法来获取一系列 GitHub 存储库的名称,并生成相应的列表:

GithubRepository.java
ConfigValue<List<String>> repos = Configuration.getMultiValue(
        "github.repos",
        Collections.emptyList(),
        Configuration.STRING_PARSER);

执行图形遍历

替换 getIds() 方法以检索存储库中所有记录的 ID 和哈希值。getIds() 方法接受一个检查点。如果进程被中断,可使用该检查点在特定项处恢复索引。

接下来,替换 getDoc() 方法以处理 Cloud Search Indexing Queue 中的每一项。

推送项 ID 和哈希值

替换 getIds() 以从存储库中提取项 ID 及与其关联的内容哈希值。然后将 ID 和哈希值对打包到指向 Cloud Search Indexing Queue 的推送操作请求中。一般来说,首先会推送根 ID 或父 ID,然后是子 ID,直到处理完整个项的层次结构。

getIds() 方法接受代表要编入索引的最后一项的检查点,如果进程被中断,可使用该检查点在特定项处恢复索引。请在 getIds() 方法中对您存储库中的每一项执行以下步骤:

  • 从存储库中获取每个项 ID 和关联的哈希值。
  • 将每个 ID 和哈希值对打包到 PushItems
  • 将每个 PushItems 合并到 getIds() 方法返回的迭代器中。请注意,getIds() 实际上返回一个 CheckpointCloseableIterable,它是 ApiOperation 对象的迭代,每个对象表示在 RepositoryDoc 上执行的一个 API 请求,例如将各项推送到队列中。

以下代码段显示了如何获取每个项 ID 和哈希值并将它们插入 PushItemsPushItems 是一个 ApiOperation 请求,用于将项推送到 Cloud Search Indexing Queue。

GraphTraversalSample.java
PushItems.Builder allIds = new PushItems.Builder();
    PushItem item = new PushItem();
    allIds.addPushItem("root", item);

以下代码段显示了如何使用 PushItems.Builder 类将 ID 和哈希值打包到单个推送 ApiOperation 中。

GraphTraversalSample.java
ApiOperation pushOperation = allIds.build();
    CheckpointCloseableIterable<ApiOperation> iterator =
      new CheckpointCloseableIterableImpl.Builder<>(
          Collections.singletonList(pushOperation))
      .build();

各项将被推送到 Cloud Search Indexing Queue 以进一步处理。

检索并处理每一项

替换 getDoc() 以处理 Cloud Search Indexing Queue 中的每一项。项可以是新添加的、已修改的、未更改的,或者不再存在于源存储库中的。检索新的或已修改的各个项,并将其编入索引。从索引中移除不再存在于源存储库中的项。

getDoc() 方法接受 Google Search Indexing Queue 中的项。请在 getDoc() 方法中对队列中的每一项执行以下步骤:

  1. 检查 Cloud Search Indexing Queue 中的项 ID 是否存在于存储库中。如果没有,请从索引中删除相应项。如果该项存在,请继续执行下一步操作。

  2. 将已更改或新添加的项编入索引:

    1. 设置权限。
    2. 为要编入索引的项设置元数据。
    3. 将元数据和该项合并到一个可索引的 RepositoryDoc 中。
    4. 将子 ID 放入到 Cloud Search Indexing Queue 中以进一步处理。
    5. 返回 RepositoryDoc

处理已删除的项

以下代码段显示了如何确定某一项是否存在于索引中,并且如果不存在则将其删除。

GraphTraversalSample.java
String resourceName = item.getName();
    if (documentExists(resourceName)) {
      return buildDocumentAndChildren(resourceName);
    }
    // Document doesn't exist, delete it
    log.info(() -> String.format("Deleting document %s", resourceName));
    return ApiOperations.deleteItem(resourceName);

设置项的权限

您的存储库使用访问控制列表 (ACL) 来标识对某一项有访问权限的用户或组。ACL 是可以访问该项的组或用户的 ID 列表。

您必须在数据源中复制此 ACL,以确保只有具有该项访问权限的用户才能在搜索结果中看到该项。因此,在将该项编入索引时,必须包含该项的 ACL,以使得 Google Cloud Search 具有必要信息来为该项设置正确的访问级别。

内容连接器 SDK 提供丰富的 ACL 类和方法,可对大多数存储库的 ACL 进行建模。您必须分析存储库中每一项的 ACL,并在将项编入索引时为 Google Cloud Search 创建相应的 ACL。如果您存储库的 ACL 使用 ACL 继承等概念,则可能难以对此类 ACL 进行建模。如需进一步了解 Google Cloud Search ACL,请参阅 Google Cloud Search ACL

注意:Cloud Search Indexing API 支持单网域 ACL,但不支持跨网域 ACL。从根本上说,您将使用 Acl.Builder 类,通过 ACL 设置对每一项的访问权限。以下代码段截取自完全遍历示例,允许所有用户或“主帐号”(getCustomerPrincipal()) 在执行搜索时可以“读取”所有项 (.setReaders())。

FullTraversalSample.java
// Make the document publicly readable within the domain
    Acl acl = new Acl.Builder()
        .setReaders(Collections.singletonList(Acl.getCustomerPrincipal()))
        .build();

您需要了解 ACL 才能对数据源的 ACL 进行正确建模。例如,您可能在使用某种继承模型的文件系统中将文件编入了索引,其中子文件夹从父文件夹继承权限。为 ACL 继承建模需要参阅 Google Cloud Search ACL 中涵盖的其他信息

设置项的元数据

元数据存储在 Item 对象中。要创建一个 Item,您至少需要该项的唯一字符串 ID、项类型、ACL、网址和版本。以下代码段显示了如何使用 IndexingItemBuilder 辅助类构建 Item

GraphTraversalSample.java
// Url is required. Use google.com as a placeholder for this sample.
    String viewUrl = "https://www.google.com";

    // Version is required, set to current timestamp.
    byte[] version = Longs.toByteArray(System.currentTimeMillis());

    // Using the SDK item builder class to create the document with
    // appropriate attributes. This can be expanded to include metadata
    // fields etc.
    Item item = IndexingItemBuilder.fromConfiguration(documentId)
        .setItemType(IndexingItemBuilder.ItemType.CONTENT_ITEM)
        .setAcl(acl)
        .setSourceRepositoryUrl(IndexingItemBuilder.FieldOrValue.withValue(viewUrl))
        .setVersion(version)
        .build();

创建可索引项

设置完项的元数据后,您可以使用 RepositoryDoc.Builder 创建实际的可索引项。 以下示例显示了如何创建单个可索引项。

GraphTraversalSample.java
// For this sample, content is just plain text
    String content = String.format("Hello world from sample doc %s", documentId);
    ByteArrayContent byteContent = ByteArrayContent.fromString("text/plain", content);

    RepositoryDoc.Builder docBuilder = new RepositoryDoc.Builder()
        .setItem(item)
        .setContent(byteContent, IndexingService.ContentFormat.TEXT);

RepositoryDoc 是一种执行实际 IndexingService.indexItem() 请求的 ApiOperation

您还可以使用 RepositoryDoc.Builder 类的 setRequestMode() 方法将索引请求标识为 ASYNCHRONOUSSYNCHRONOUS

ASYNCHRONOUS
异步模式会导致从索引到服务的延迟时间变长,并造成索引请求占用大量吞吐量配额。建议在对整个存储库执行首次索引编制(回填)时使用异步模式。
SYNCHRONOUS
同步模式可缩短索引到服务的延迟时间,并占用有限的吞吐量配额。建议在对存储库的更新和变更执行编入索引时使用同步模式。如果未指定,请求模式默认为 SYNCHRONOUS

将子 ID 放入到 Cloud Search Indexing Queue 中

以下代码段显示了如何将当前处理的父项的子 ID 包含到队列中以进行处理。系统将父项编入索引后便会处理这些 ID。

GraphTraversalSample.java
// Queue the child nodes to visit after indexing this document
    Set<String> childIds = getChildItemNames(documentId);
    for (String id : childIds) {
      log.info(() -> String.format("Pushing child node %s", id));
      PushItem pushItem = new PushItem();
      docBuilder.addChildId(id, pushItem);
    }

    RepositoryDoc doc = docBuilder.build();

后续步骤

您可以执行以下几个后续步骤:

  • (可选)实现 close() 方法以在运行结束前释放所有资源。
  • (可选)使用身份连接器 SDK 创建身份连接器

使用 REST API 创建内容连接器

以下部分介绍如何使用 REST API 创建内容连接器。

确定您的遍历策略

内容连接器的主要功能是遍历存储库并为其中的数据编制索引。您必须根据存储库中数据的大小和布局实现遍历策略。以下是三种常见的遍历策略:

完全遍历策略

完全遍历策略会扫描整个存储库,并不加分辨地将每一项都编入索引。如果您的存储库规模较小,并且能够负担得起每次编制索引都执行完全遍历的开销,通常可以使用此策略。

这一遍历策略适用于大部分数据都处于静态且不分层的小型存储库。当存储库难以执行或完全不支持更改检测时,您也可以使用此遍历策略。

列表遍历策略

列表遍历策略扫描整个存储库,包括所有子节点,来确定每一项的状态。然后,连接器进行第二次遍历,仅将自上次编制索引以来添加的新项或已更新的项编入索引。此策略通常用于对现有索引执行增量更新(无需在每次更新索引时都执行完全遍历)。

如果存储库难以执行或完全不支持更改检测,您的数据不分层,或者需要处理庞大的数据集时,此遍历策略非常适用。

图形遍历

图形遍历策略扫描整个父节点,确定每一项的状态。然后,连接器进行第二次遍历,仅将根节点中自上次编制索引以来添加的新项或已更新的项编入索引。最后,连接器遍历所有子 ID,然后将子节点中添加的新项或已更新的项编入索引。连接器以递归方式继续遍历所有子节点,直到处理完所有项。这一遍历方法通常用于分层存储库,列出这种存储库中的所有 ID 往往不切实际。

如果您拥有需要抓取的分层数据,例如一系列目录或网页,则此策略非常适用。

实现遍历策略和索引项

在 Cloud Search API 中,Google Cloud Search 的每个可索引元素都称为一项。一项可以是一个文件、一个文件夹、一个 CSV 文件中的一行,或者一条数据库记录。

注册架构后,您可以通过以下方式填充索引:

  1. (可选)使用 items.upload 上传大于 100KiB 的文件以编入索引。对于较小的文件,可使用 items.index 将内容作为 inlineContent 嵌入。

  2. (可选)使用 media.upload 上传媒体文件以编入索引。

  3. 使用 items.index 将项编入索引。例如,如果您的架构使用影片架构中的对象定义,则单个项的索引请求将类似于如下所示:

    {
          "name": "datasource/<data_source_id>/items/titanic",
          "acl": {
            "readers": [
              {
                "gsuitePrincipal": {
                  "gsuiteDomain": true
                }
              }
            ]
          },
          "metadata": {
            "title": "Titanic",
            "viewUrl": "http://www.imdb.com/title/tt2234155/?ref_=nv_sr_1",
            "objectType": "movie"
          },
          "structuredData": {
            "object": {
              "properties": [
                {
                  "name": "movieTitle",
                  "textValues": {
                    "values": [
                      "Titanic"
                    ]
                  }
                },
                {
                  "name": "releaseDate",
                  "dateValues": {
                    "values": [
                      {
                        "year": 1997,
                        "month": 12,
                        "day": 19
                      }
                    ]
                  }
                },
                {
                  "name": "actorName",
                  "textValues": {
                    "values": [
                      "Leonardo DiCaprio",
                      "Kate Winslet",
                      "Billy Zane"
                    ]
                  }
                },
                {
                  "name": "genre",
                  "enumValues": {
                    "values": [
                      "Drama",
                      "Action"
                    ]
                  }
                },
                {
                  "name": "userRating",
                  "integerValues": {
                    "values": [
                      8
                    ]
                  }
                },
                {
                  "name": "mpaaRating",
                  "textValues": {
                    "values": [
                      "PG-13"
                    ]
                  }
                },
                {
                  "name": "duration",
                  "textValues": {
                    "values": [
                      "3 h 14 min"
                    ]
                  }
                }
              ]
            }
          },
          "content": {
            "inlineContent": "A seventeen-year-old aristocrat falls in love with a kind but poor artist aboard the luxurious, ill-fated R.M.S. Titanic.",
            "contentFormat": "TEXT"
          },
          "version": "01",
          "itemType": "CONTENT_ITEM"
        }
        
  4. (可选)使用 items.get 调用来验证是否已编入索引。

要执行完全遍历,您需要定期将整个存储库重新编入索引。如需执行列表或图形遍历,您需要实现代码来处理存储库更改

处理存储库更改

您可以定期收集存储库中的每一项并将其编入索引,以执行完全索引。虽然完全索引能够有效确保您的索引是最新的,但在处理较大存储库或分层存储库时,完全索引的费用可能非常高昂。

您可以使用 Google Cloud Indexing Queue 作为跟踪更改的机制,并仅将已更改的项编入索引,而不是时常使用索引调用为整个存储库编制索引。您可以使用 items.push 请求将项推送到队列中,以便日后进行轮询和更新。如需详细了解 Google Cloud Indexing Queue,请参阅 Google Cloud Indexing Queue

如需进一步了解 Cloud Search REST API,请参阅 Cloud Search API