创建内容连接器

内容连接器是一种软件程序,用于遍历企业代码库中的数据并填充数据源。针对内容连接器的开发,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 创建内容连接器。

设置依赖项

您必须在 build 文件中包含某些依赖项才能使用 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 声明为企业 Google Workspace 帐号的唯一 ID。

除非您想要替换其他 Google 提供的参数的默认值,否则您无需在配置文件中声明它们。如需详细了解 Google 提供的配置参数,例如如何生成特定 ID 和密钥,请参阅 Google 提供的配置参数

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

将配置文件传递给连接器

设置系统属性 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();
}

在后台,SDK 会在连接器的 main() 方法调用 Application.build 后调用 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 是一种 ApiOperation,用于执行实际 IndexingService.indexItem() 请求。

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

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

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

getAllDocs() 方法会返回 RepositoryDoc 对象的 Iterator,具体而言就是 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,请参阅 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();
}

在后台,SDK 会在连接器的 main() 方法调用 Application.build 后调用 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() 方法返回 nullNullPointerException. 中返回 null 的结果

处理已删除的项

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

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 是一种 ApiOperation,用于执行实际 IndexingService.indexItem() 请求。

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

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

后续步骤

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

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

Cloud Search Indexing Queue 用于保存代码库中每一项的 ID 和可选哈希值。图表遍历连接器将项 ID 推送到 Google Cloud Search Indexing Queue,并一次检索一个项以将其编入索引。Google Cloud Search 维护队列并比较队列内容以确定项的状态,例如是否已从代码库中删除项。如需进一步了解 Cloud 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();
}

在后台,SDK 会在连接器的 main() 方法调用 Application.build 后调用 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() 方法接受 Cloud 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 是一种 ApiOperation,用于执行实际 IndexingService.indexItem() 请求。

您还可以使用 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();

后续步骤

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

使用 REST API 创建内容连接器

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

确定您的遍历策略

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

完全遍历策略

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

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

列表遍历策略

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

当代码库难以执行或不支持更改检测、您具有非分层数据,并且您要处理庞大的数据集时,此遍历策略非常适用。

图形遍历

图形遍历策略扫描整个父节点,确定每一项的状态。然后,连接器进行第二次遍历,仅将根节点中自上次编入索引后的新项或已更新的项编入索引。最后,连接器会传递所有子 ID,然后将子节点中新增或更新的项编入索引。连接器以递归方式继续遍历所有子节点,直到处理完所有项。这一遍历方法通常用于分层代码库,在此种代码库中,很难列出所有 ID。

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

实现遍历策略和索引项

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

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

  1. (可选)使用 items.upload 上传大于 100 KiB 的文件以编入索引。如果文件较小,请使用 items.indexinlineContent 形式嵌入内容。

  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

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