创建内容连接器

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

Maven

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

Gradle

groovy 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 提供的适用于所有连接器的参数。您必须在配置文件中声明以下内容:

  • 内容连接器:声明 api.sourceIdapi.serviceAccountPrivateKeyFile。这些参数用于标识您的代码库以及访问所需的私钥。
  • 身份连接器:声明 api.identitySourceId 以标识您的外部身份源。对于用户同步,还要声明 api.customerId(您的 Google Workspace 账号的唯一 ID)。

仅声明其他由 Google 提供的参数,以替换其默认值。 如需详细了解如何生成 ID 和密钥,请参阅 Google 提供的参数

您还可以在配置文件中定义代码库专属参数。

将配置文件传递给连接器

设置 config 系统属性以传递配置文件。启动连接器时使用 -D 实参。例如:

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

如果您省略此实参,SDK 会尝试使用本地目录中名为 connector-config.properties 的文件。

确定您的遍历策略

内容连接器的主要功能是遍历代码库并为其数据编制索引。您必须根据代码库的大小和布局实现相应策略。您可以设计自己的策略,也可以从 SDK 中选择一种策略:

完全遍历策略
扫描整个代码库,并将每一项都编入索引。此策略最适合小型代码库,因为您能够负担得起每次编制索引时进行完全遍历的开销。此策略适用于大部分数据都处于静态且不分层的小型存储库,或者难以进行更改检测的情况。
列表遍历策略
扫描整个代码库以确定每一项的状态,然后仅对新项或更新后的项编制索引。当不支持更改检测时,使用此策略对大型非分层索引进行增量更新。
图遍历
扫描父节点以确定其项的状态,然后对该节点中的新项或更新项编制索引。然后,它会以递归方式处理子节点。对于列出所有 ID 不切实际的分层代码库(例如目录结构或网站),请使用此方法。

SDK 在模板连接器类中实现了这些策略。这些模板可以加快您的开发速度。如需使用模板,请参阅相应部分:

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

本部分引用了 FullTraversalSample 中的代码。

实现连接器入口点

入口点是 main() 方法。它会创建一个 Application 实例并调用 start() 来运行连接器。

在调用 application.start() 之前,请使用 IndexingApplication.Builder 类实例化 FullTraversalConnector 模板。此模板接受 Repository 对象。

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. 确保 Configuration 尚未初始化。
  2. 使用 Google 提供的键值对初始化 Configuration 对象。

实现 Repository 接口

Repository 对象用于遍历和索引代码库项。使用模板时,您只需替换 Repository 接口中的某些方法。对于 FullTraversalConnector,请替换:

  • init():用于代码库设置和初始化。
  • getAllDocs():遍历并为所有内容编制索引。每次执行计划遍历时,系统都会调用此方法一次。
  • (可选)getChanges():如果您的代码库支持更改检测,请替换此方法以检索和索引已修改的项。
  • (可选)close():用于在关机期间清理代码库。

每个方法都会返回一个 ApiOperation 对象,该对象使用 IndexingService.indexItem() 执行索引。

获取自定义配置参数

为了处理连接器的配置,您必须从 Configuration 对象检索所有自定义参数。在 Repository 类的 init() 方法中执行此任务。

Configuration 类包含用于检索不同数据类型的方法。 每个方法都会返回一个 ConfigValue 对象。使用 ConfigValue 对象的 get() 方法检索值。以下代码段来自 FullTraversalSample,展示了如何检索自定义整数值:

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() 以执行完全遍历。此方法接受检查点,以便在中断后恢复索引编制。对于每个商品:

  1. 设置权限。
  2. 设置元数据。
  3. 将它们合并为一个 RepositoryDoc
  4. 将每个项打包到 getAllDocs() 返回的迭代器中。

如果商品集太大,无法在一次调用中处理,请使用检查点并调用 hasMore(true)

设置项的权限

代码库使用访问控制列表 (ACL) 来标识对某一项有访问权限的用户或组。ACL 列出了已获授权的用户或群组的 ID。

为确保用户只能看到他们有权访问的搜索结果,您必须复制代码库的 ACL。在将项编入索引时,请包含 ACL,以便 Google Cloud Search 可以提供正确的访问权限级别。

内容连接器 SDK 包含用于对大多数代码库的 ACL 进行建模的类和方法。在编制索引期间,分析代码库的 ACL 并为 Cloud Search 创建相应的 ACL。对复杂的 ACL(例如使用继承的 ACL)进行建模需要仔细规划。如需了解详情,请参阅 Cloud Search ACL

使用 Acl.Builder 类设置访问权限。以下代码段来自完整遍历示例,可让所有网域用户 (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)建模,需要参阅 Cloud Search ACL 中的信息。

设置项的元数据

元数据存储在 Item 对象中。如需创建 Item,您需要提供唯一 ID、商品类型、ACL、网址和版本。使用 IndexingItemBuilder 辅助类。

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() 方法会返回一个 RepositoryDoc 对象的 CheckpointCloseableIterable。使用 CheckpointCloseableIterableImpl.Builder 类。

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

后续步骤

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

Cloud Search Indexing Queue 用于保存代码库项的 ID 和可选哈希值。列表遍历连接器将 ID 推送到此队列,并检索这些 ID 以进行索引。Cloud Search 会维护这些队列,以确定项的状态,例如是否已删除。请参阅 Cloud Search Indexing Queue

本部分涉及 ListTraversalSample

实现连接器入口点

main() 方法会创建一个 Application 实例并调用 start()。使用 IndexingApplication.Builder 实例化 ListingConnector 模板。

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();
}

实现 Repository 接口

ListingConnector 替换以下方法:

  • init():用于设置代码库。
  • getIds():检索所有记录的 ID 和哈希。
  • getDoc():用于在索引中添加、更新或删除项。
  • (可选)getChanges():用于使用更改检测进行增量更新。
  • (可选)close():用于清理代码库。

执行列表遍历

替换 getIds() 以检索 ID 和哈希。重写 getDoc() 以处理 Cloud Search Indexing Queue 中的每一项。

推送项 ID 和哈希值

替换 getIds() 以提取 ID 和内容哈希值。将它们打包到向索引队列发送的 PushItems 请求中。

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 和哈希打包。

ListTraversalSample.java
ApiOperation pushOperation = allIds.build();
CheckpointCloseableIterable<ApiOperation> iterator =
  new CheckpointCloseableIterableImpl.Builder<>(
      Collections.singletonList(pushOperation))
  .build();
return iterator;
检索并处理每一项

替换 getDoc() 以处理 Indexing Queue 中的项。项可以是新添加的、已修改的、未更改的或已删除的。

  1. 检查商品 ID 是否存在于代码库中。如果不是,请将其删除。
  2. 轮询索引以获取状态。如果未更改 (ACCEPTED),则不执行任何操作。
  3. 将已更改或新添加的项编入索引:设置权限、设置元数据、合并到 RepositoryDoc 中并返回。
处理已删除的项

此代码段展示了如何确定某一项是否存在,如果不存在则将其删除。

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);
}
处理未更改的项

轮询索引队列以处理未更改的项。

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 并为 Cloud Search 创建相应的 ACL。对复杂的 ACL(例如使用继承的 ACL)进行建模需要仔细规划。如需了解详情,请参阅 Cloud Search ACL

使用 Acl.Builder 类设置访问权限。以下代码段来自完整遍历示例,可让所有网域用户 (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)建模,需要参阅 Cloud Search ACL 中的信息。

设置项的元数据
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();
创建可索引项
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.Builder 类的 setRequestMode() 方法将索引编制请求设置为 ASYNCHRONOUSSYNCHRONOUS

ASYNCHRONOUS
此模式的从索引到服务的延迟时间较长,但可容纳更大的吞吐量配额。在对整个代码库执行首次索引编制(回填)时,请使用异步模式。
SYNCHRONOUS
此模式的索引到服务的延迟时间较短,但吞吐量配额较小。使用同步模式对代码库更新和变更进行索引。如果未指定,请求模式默认为 SYNCHRONOUS

后续步骤

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

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

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

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,直到处理完所有项。

实现连接器的入口点

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

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

实现 Repository 接口

替换 init()getIds()getDoc(),并可选择性地替换 getChanges()close()

执行图形遍历

替换 getIds() 以检索初始 ID,并替换 getDoc() 以处理项并将子 ID 推送到队列。

推送项 ID 和哈希值
GraphTraversalSample.java
PushItems.Builder allIds = new PushItems.Builder();
PushItem item = new PushItem();
allIds.addPushItem("root", item);
检索并处理每一项
  1. 检查相应 ID 是否存在于代码库中。如果没有,请删除相应项。
  2. 对于现有商品,设置权限和元数据,并将它们合并到 RepositoryDoc 中。
  3. 将子 ID 推送到索引队列。
  4. 返回 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);
设置元数据并创建商品
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();
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);
将子 ID 放入到 Indexing Queue 中
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 创建内容连接器。

确定您的遍历策略

这些策略(完整、列表和图表)在概念上与 SDK 的策略相同。使用 REST API 实现您选择的策略。

实现遍历策略和索引项

注册架构,然后使用以下方法填充索引:

  1. (可选)对于大于 100 KiB 的文件,请使用 items.upload
  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/",
        "objectType": "movie"
      },
      "structuredData": {
        "object": {
          "properties": [
            {
              "name": "movieTitle",
              "textValues": { "values": ["Titanic"] }
            }
          ]
        }
      },
      "content": {
        "inlineContent": "A seventeen-year-old aristocrat falls in love...",
        "contentFormat": "TEXT"
      },
      "version": "01",
      "itemType": "CONTENT_ITEM"
    }
    
  4. (可选)使用 items.get 验证索引编制。

处理存储库更改

定期将整个代码库重新编入索引,以进行完全索引。对于列表或图遍历,请使用 Google Cloud Indexing Queue 跟踪更改,并仅将已更改的内容编入索引。使用 items.push 将项目添加到队列。