快速入门:使用 REST API 在 Node.js 中创建 Azure 认知搜索索引Quickstart: Create an Azure Cognitive Search index in Node.js using REST APIs

创建一个可以创建、加载和查询 Azure 认知搜索索引的 Node.js 应用程序。Create a Node.js application that that creates, loads, and queries an Azure Cognitive Search index. 本文演示如何逐步创建应用程序。This article demonstrates how to create the application step-by-step. 或者,可以下载源代码和数据,并从命令行运行应用程序。Alternatively, you can download the source code and data and run the application from the command line.

如果没有 Azure 订阅,可在开始前创建一个试用帐户If you don't have an Azure subscription, create a trial account before you begin.

先决条件Prerequisites

我们使用了以下软件和服务来构建和测试本快速入门:We used the following software and services to build and test this quickstart:

建议:Recommended:

获取密钥和 URLGet keys and URLs

对服务的调用要求每个请求都有一个 URL 终结点和一个访问密钥。Calls to the service require a URL endpoint and an access key on every request. 搜索服务是使用这二者创建的,因此,如果向订阅添加了 Azure 认知搜索,则请按以下步骤获取必需信息:A search service is created with both, so if you added Azure Cognitive Search to your subscription, follow these steps to get the necessary information:

  1. 登录到 Azure 门户,在搜索服务的“概述”页中获取搜索服务的名称。 Sign in to the Azure portal, and in your search service Overview page, get the name of your search service. 可以通过查看终结点 URL 来确认服务名称。You can confirm your service name by reviewing the endpoint URL. 如果终结点 URL 为 https://mydemo.search.azure.cn,则服务名称为 mydemoIf your endpoint URL were https://mydemo.search.azure.cn, your service name would be mydemo.

  2. 在“设置” > “密钥”中,获取有关该服务的完全权限的管理员密钥 。In Settings > Keys, get an admin key for full rights on the service. 有两个可交换的管理员密钥,为保证业务连续性而提供,以防需要滚动一个密钥。There are two interchangeable admin keys, provided for business continuity in case you need to roll one over. 可以在请求中使用主要或辅助密钥来添加、修改和删除对象。You can use either the primary or secondary key on requests for adding, modifying, and deleting objects.

    此外,获取查询密钥。Get the query key as well. 最好使用只读权限发出查询请求。It's a best practice to issue query requests with read-only access.

获取服务名称以及管理密钥和查询密钥

所有请求要求在发送到服务的每个请求的标头中指定 API 密钥。All requests require an api-key in the header of every request sent to your service. 具有有效的密钥可以在发送请求的应用程序与处理请求的服务之间建立信任关系,这种信任关系以每个请求为基础。A valid key establishes trust, on a per request basis, between the application sending the request and the service that handles it.

设置你的环境Set up your environment

首先打开 Powershell 控制台或者安装了 Node.js 的其他环境。Begin by opening a Powershell console or other environment in which you've installed Node.js.

  1. 创建一个开发目录并将其命名为 quickstartCreate a development directory, giving it the name quickstart :

    mkdir quickstart
    cd quickstart
    
  2. 运行 npm init,使用 NPM 初始化一个空项目。Initialize an empty project with NPM by running npm init. 接受默认值,但“许可证”除外,其值应设置为“MIT”。Accept the default values, except for the License, which you should set to "MIT".

  3. 添加代码所依赖的并有助于开发的包:Add packages that will be depended on by the code and aid in development:

    npm install nconf node-fetch
    npm install --save-dev eslint eslint-config-prettier eslint-config-airbnb-base eslint-plugin-import prettier
    
  4. 检查如下所示的 package.json 文件,确认已配置项目及其依赖项:Confirm that you've configured the projects and its dependencies by checking that your package.json file looks similar to the following:

    {
      "name": "quickstart",
      "version": "1.0.0",
      "description": "Azure Cognitive Search Quickstart",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [
        "Azure",
        "Azure_Search"
      ],
      "author": "Your Name",
      "license": "MIT",
      "dependencies": {
        "nconf": "^0.10.0",
        "node-fetch": "^2.6.0"
      },
      "devDependencies": {
        "eslint": "^6.1.0",
        "eslint-config-airbnb-base": "^13.2.0",
        "eslint-config-prettier": "^6.0.0",
        "eslint-plugin-import": "^2.18.2",
        "prettier": "^1.18.2"
      }
    }
    
  5. 创建用于保存搜索服务数据的 azure_search_config.json 文件:Create a file azure_search_config.json to hold your search service data:

    {
        "serviceName" : "[SEARCH_SERVICE_NAME]",
        "adminKey" : "[ADMIN_KEY]",
        "queryKey" : "[QUERY_KEY]",
        "indexName" : "hotels-quickstart"
    }
    

请将 [SERVICE_NAME] 值替换为搜索服务的名称。Replace the [SERVICE_NAME] value with the name of your search service. [ADMIN_KEY][QUERY_KEY] 替换为前面记下的密钥值。Replace [ADMIN_KEY] and [QUERY_KEY] with the key values you recorded earlier.

1 - 创建索引1 - Create index

创建文件 hotels_quickstart_index.jsonCreate a file hotels_quickstart_index.json. 此文件定义 Azure 认知搜索如何处理要在下一步骤中加载的文档。This file defines how Azure Cognitive Search works with the documents you'll be loading in the next step. 每个字段由 name 标识,采用指定的 typeEach field will be identified by a name and have a specified type. 每个字段还包含一系列索引属性,这些属性指定 Azure 认知搜索是否可以根据字段进行搜索、筛选、排序和分面。Each field also has a series of index attributes that specify whether Azure Cognitive Search can search, filter, sort, and facet upon the field. 大多数字段采用简单数据类型,但有些字段(例如 AddressType)采用复杂类型,可让你在索引中创建丰富的数据结构。Most of the fields are simple data types, but some, like AddressType are complex types that allow you to create rich data structures in your index. 可以详细了解支持的数据类型索引属性You can read more about supported data types and index attributes.

将以下内容添加到 hotels_quickstart_index.json下载文件Add the following to hotels_quickstart_index.json or download the file.

{
    "name": "hotels-quickstart",
    "fields": [
        {
            "name": "HotelId",
            "type": "Edm.String",
            "key": true,
            "filterable": true
        },
        {
            "name": "HotelName",
            "type": "Edm.String",
            "searchable": true,
            "filterable": false,
            "sortable": true,
            "facetable": false
        },
        {
            "name": "Description",
            "type": "Edm.String",
            "searchable": true,
            "filterable": false,
            "sortable": false,
            "facetable": false,
            "analyzer": "en.lucene"
        },
        {
            "name": "Description_fr",
            "type": "Edm.String",
            "searchable": true,
            "filterable": false,
            "sortable": false,
            "facetable": false,
            "analyzer": "fr.lucene"
        },
        {
            "name": "Category",
            "type": "Edm.String",
            "searchable": true,
            "filterable": true,
            "sortable": true,
            "facetable": true
        },
        {
            "name": "Tags",
            "type": "Collection(Edm.String)",
            "searchable": true,
            "filterable": true,
            "sortable": false,
            "facetable": true
        },
        {
            "name": "ParkingIncluded",
            "type": "Edm.Boolean",
            "filterable": true,
            "sortable": true,
            "facetable": true
        },
        {
            "name": "LastRenovationDate",
            "type": "Edm.DateTimeOffset",
            "filterable": true,
            "sortable": true,
            "facetable": true
        },
        {
            "name": "Rating",
            "type": "Edm.Double",
            "filterable": true,
            "sortable": true,
            "facetable": true
        },
        {
            "name": "Address",
            "type": "Edm.ComplexType",
            "fields": [
                {
                    "name": "StreetAddress",
                    "type": "Edm.String",
                    "filterable": false,
                    "sortable": false,
                    "facetable": false,
                    "searchable": true
                },
                {
                    "name": "City",
                    "type": "Edm.String",
                    "searchable": true,
                    "filterable": true,
                    "sortable": true,
                    "facetable": true
                },
                {
                    "name": "StateProvince",
                    "type": "Edm.String",
                    "searchable": true,
                    "filterable": true,
                    "sortable": true,
                    "facetable": true
                },
                {
                    "name": "PostalCode",
                    "type": "Edm.String",
                    "searchable": true,
                    "filterable": true,
                    "sortable": true,
                    "facetable": true
                },
                {
                    "name": "Country",
                    "type": "Edm.String",
                    "searchable": true,
                    "filterable": true,
                    "sortable": true,
                    "facetable": true
                }
            ]
        }
    ],
    "suggesters": [
        {
            "name": "sg",
            "searchMode": "analyzingInfixMatching",
            "sourceFields": [
                "HotelName"
            ]
        }
    ]
}

最好是将特定方案的细节与广泛适用的代码区分开来。It's good practice to separate the specifics of a particular scenario from code that will be broadly applicable. AzureSearchClient.js 文件中定义的 AzureSearchClient 类知道如何构造请求 URL、使用提取 API 发出请求,并对响应的状态代码做出反应。The AzureSearchClient class defined in the file AzureSearchClient.js will know how to construct request URLs, make a request using the Fetch API, and react to the status code of the response.

开始处理 AzureSearchClient.js:导入 node-fetch 包并创建一个简单类。Begin working on AzureSearchClient.js by importing the node-fetch package and creating a simple class. 将各个配置值传递给 AzureSearchClient 的构造函数,以隔离该类的可更改部分:Isolate the changeable parts of the AzureSearchClient class by passing to its constructor the various configuration values:

const fetch = require('node-fetch');

class AzureSearchClient {
    constructor(searchServiceName, adminKey, queryKey, indexName) {
        this.searchServiceName = searchServiceName;
        this.adminKey = adminKey;
        // The query key is used for read-only requests and so can be distributed with less risk of abuse.
        this.queryKey = queryKey;
        this.indexName = indexName;
        this.apiVersion = '2020-06-30';
    }

    // All methods go inside class body here!
}

module.exports = AzureSearchClient;

该类的第一项责任是了解如何构造要将各种请求发送到的 URL。The first responsibility of the class is to know how to construct URLs to which to send the various requests. 通过使用传递给类构造函数的配置数据的实例方法生成这些 URL。Build these URLs with instance methods that use the configuration data passed to the class constructor. 请注意,这些方法构造的 URL 与特定的 API 版本相关,必须包含一个用于指定该版本(在此应用程序中为 2020-06-30)的参数。Notice that the URL they construct is specific to an API version and must have an argument specifying that version (in this application, 2020-06-30).

这些方法中的第一个方法将返回索引本身的 URL。The first of these methods will return the URL for the index itself. 将以下方法添加到类正文中:Add the following method inside the class body:

getIndexUrl() { return `https://${this.searchServiceName}.search.azure.cn/indexes/${this.indexName}?api-version=${this.apiVersion}`; }

AzureSearchClient 的第二项责任是使用提取 API 发出异步请求。The next responsibility of AzureSearchClient is making an asynchronous request with the Fetch API. 异步静态方法 request 采用一个 URL、一个指定 HTTP 方法的字符串(“GET”、“PUT”、“POST”、“DELETE”)、要在请求中使用的密钥,以及一个可选的 JSON 对象。The asynchronous static method request takes a URL, a string specifying the HTTP method ("GET", "PUT", "POST", "DELETE"), the key to be used in the request, and an optional JSON object. headers 变量将 queryKey(无论是管理密钥还是只读的查询密钥)映射到“api-key”HTTP 请求标头。The headers variable maps the queryKey (whether the admin key or the read-only query key) to the "api-key" HTTP request header. 请求选项始终包含要使用的 method,以及 headersThe request options always contain the method to be used and the headers. 如果 bodyJson 不是 null,则 HTTP 请求的正文将设置为 bodyJson 的字符串表示形式。If bodyJson isn't null, the body of the HTTP request is set to the string representation of bodyJson. request 方法返回用于执行 HTTP 请求的提取 API 约定。The request method returns the Fetch API's Promise to execute the HTTP request.

static async request(url, method, apiKey, bodyJson = null) {
    // Uncomment the following for request details:
    /*
    console.log(`\n${method} ${url}`);
    console.log(`\nKey ${apiKey}`);
    if (bodyJson !== null) {
        console.log(`\ncontent: ${JSON.stringify(bodyJson, null, 4)}`);
    }
    */

    const headers = {
        'content-type' : 'application/json',
        'api-key' : apiKey
    };
    const init = bodyJson === null ?
        { 
            method, 
            headers
        }
        : 
        {
            method, 
            headers,
            body : JSON.stringify(bodyJson)
        };
    return fetch(url, init);
}

为方便演示,当 HTTP 请求未成功时,只会引发异常。For demo purposes, just throw an exception if the HTTP request is not a success. 在实际应用程序中,你也许会对搜索服务请求的 response 中的 HTTP 状态代码进行一些日志记录和诊断。In a real application, you would probably do some logging and diagnosis of the HTTP status code in the response from the search service request.

static throwOnHttpError(response) {
    const statusCode = response.status;
    if (statusCode >= 300){
        console.log(`Request failed: ${JSON.stringify(response, null, 4)}`);
        throw new Error(`Failure in request. HTTP Status was ${statusCode}`);
    }
}

最后,添加用于检测、删除和创建 Azure 认知搜索索引的方法。Finally, add the methods to detect, delete, and create the Azure Cognitive Search index. 这些方法全部采用相同的结构:These methods all have the same structure:

  • 获取要对其发出请求的终结点。Get the endpoint to which the request will be made.
  • 使用相应的终结点、HTTP 谓词、API 密钥和 JSON 正文(如果适当)生成该请求。Generate the request with the appropriate endpoint, HTTP verb, API key, and, if appropriate, a JSON body. indexExistsAsync()deleteIndexAsync() 没有 JSON 正文,但 createIndexAsync(definition) 有。indexExistsAsync() and deleteIndexAsync() do not have a JSON body, but createIndexAsync(definition) does.
  • 执行 await 以等待返回请求的响应。await the response to the request.
  • 处理响应的状态代码。Act on the status code of the response.
  • 返回某个适当值(布尔值、this 或查询结果)的约定。Return a Promise of some appropriate value (a Boolean, this, or the query results).
async indexExistsAsync() { 
    console.log("\n Checking if index exists...");
    const endpoint = this.getIndexUrl();
    const response = await AzureSearchClient.request(endpoint, "GET", this.adminKey);
    // Success has a few likely status codes: 200 or 204 (No Content), but accept all in 200 range...
    const exists = response.status >= 200 && response.status < 300;
    return exists;
}

async deleteIndexAsync() {
    console.log("\n Deleting existing index...");
    const endpoint = this.getIndexUrl();
    const response = await AzureSearchClient.request(endpoint, "DELETE", this.adminKey);
    AzureSearchClient.throwOnHttpError(response);
    return this;
}

async createIndexAsync(definition) {
    console.log("\n Creating index...");
    const endpoint = this.getIndexUrl();
    const response = await AzureSearchClient.request(endpoint, "PUT", this.adminKey, definition);
    AzureSearchClient.throwOnHttpError(response);
    return this;
}

确认你的方法位于该类中,并且你正在导出该类。Confirm that your methods are inside the class and that you're exporting the class. AzureSearchClient.js 的最外层范围应是:The outermost scope of AzureSearchClient.js should be:

const fetch = require('node-fetch');

class AzureSearchClient {
    // ... code here ...
}

module.exports = AzureSearchClient;

面向对象的类非常适合用于潜在可重用的 AzureSearchClient.js 模块,但不一定适合用于要放入名为 index.js 的文件中的主程序。An object-oriented class was a good choice for the potentially reusable AzureSearchClient.js module, but isn't necessary for the main program, which you should put in a file called index.js.

创建 index.js,然后,先引入:Create index.js and begin by bringing in:

  • nconf 包:可让你灵活使用 JSON、环境变量或命令行参数指定配置。The nconf package, which gives you flexibility for specifying the configuration with JSON, environment variables, or command-line arguments.
  • hotels_quickstart_index.json 文件中的数据。The data from the hotels_quickstart_index.json file.
  • AzureSearchClient 模块。The AzureSearchClient module.
const nconf = require('nconf');

const indexDefinition = require('./hotels_quickstart_index.json');
const AzureSearchClient = require('./AzureSearchClient.js');

nconf可让你以多种格式(例如环境变量或命令行)指定配置数据。The nconf package allows you to specify configuration data in a variety of formats, such as environment variables or the command line. 本示例以基本方式使用 nconf,以读取文件 azure_search_config.json 并以字典形式返回该文件的内容。This sample uses nconf in a basic manner to read the file azure_search_config.json and return that file's contents as a dictionary. 使用 nconfget(key) 函数可以快速检查配置信息是否已正确自定义。Using nconf's get(key) function, you can do a quick check that the configuration information has been properly customized. 最后,函数将返回配置:Finally, the function returns the configuration:

function getAzureConfiguration() {
    const config = nconf.file({ file: 'azure_search_config.json' });
    if (config.get('serviceName') === '[SEARCH_SERVICE_NAME]' ) {
        throw new Error("You have not set the values in your azure_search_config.json file. Change them to match your search service's values.");
    }
    return config;
}

sleep 函数创建一个在指定的时间后得到解决的 PromiseThe sleep function creates a Promise that resolves after a specified amount of time. 应用可以使用此函数暂停,同时等待异步索引操作完成并可用。Using this function allows the app to pause while waiting for asynchronous index operations to complete and become available. 通常,仅在演示、测试和示例应用程序中才有必要添加此类延迟。Adding such a delay is typically only necessary in demos, tests, and sample applications.

function sleep(ms) {
    return(
        new Promise(function(resolve, reject) {
            setTimeout(function() { resolve(); }, ms);
        })
    );
}

最后,指定并调用主异步 run 函数。Finally, specify and call the main asynchronous run function. 此函数按顺序调用其他函数,并根据需要等待解决 PromiseThis function calls the other functions in order, awaiting as necessary to resolve Promises.

  • 使用前面编写的 getAzureConfiguration() 检索配置Retrieve the configuration with the getAzureConfiguration() you wrote previously
  • 创建一个新的 AzureSearchClient 实例,并传入配置中的值Create a new AzureSearchClient instance, passing in values from your configuration
  • 检查索引是否存在,如果存在,请将其删除Check if the index exists and, if it does, delete it
  • 使用从 hotels_quickstart_index.json 加载的 indexDefinition 创建索引Create an index using the indexDefinition loaded from hotels_quickstart_index.json
const run = async () => {
    try {
        const cfg = getAzureConfiguration();
        const client = new AzureSearchClient(cfg.get("serviceName"), cfg.get("adminKey"), cfg.get("queryKey"), cfg.get("indexName"));
        
        const exists = await client.indexExistsAsync();
        await exists ? client.deleteIndexAsync() : Promise.resolve();
        // Deleting index can take a few seconds
        await sleep(2000);
        await client.createIndexAsync(indexDefinition);
    } catch (x) {
        console.log(x);
    }
}

run();

最后,请不要忘记调用 run()Don't forget that final call to run()! 在下一步骤中运行 node index.js 时,该函数是程序的入口点。It's the entrance point to your program when you run node index.js in the next step.

请注意,AzureSearchClient.indexExistsAsync()AzureSearchClient.deleteIndexAsync() 不采用参数。Notice that AzureSearchClient.indexExistsAsync() and AzureSearchClient.deleteIndexAsync() do not take parameters. 这些函数调用不带 bodyJson 参数的 AzureSearchClient.request()These functions call AzureSearchClient.request() with no bodyJson argument. AzureSearchClient.request() 中,由于 bodyJson === nulltrue,因此 init 结构仅设置为 HTTP 谓词(对于 indexExistsAsync(),为“GET”;对于 deleteIndexAsync(),则为“DELETE”)和指定请求密钥的标头。Within AzureSearchClient.request(), since bodyJson === null is true, the init structure is set to be just the HTTP verb ("GET" for indexExistsAsync() and "DELETE" for deleteIndexAsync()) and the headers, which specify the request key.

相反,AzureSearchClient.createIndexAsync(indexDefinition) 方法采用一个参数。 In contrast, the AzureSearchClient.createIndexAsync(indexDefinition) method does take a parameter. index.js 中的 run 函数将文件 hotels_quickstart_index.json 的内容传递给 AzureSearchClient.createIndexAsync(indexDefinition) 方法。The run function in index.js, passes the contents of the file hotels_quickstart_index.json to the AzureSearchClient.createIndexAsync(indexDefinition) method. createIndexAsync() 方法将此定义传递给 AzureSearchClient.request()The createIndexAsync() method passes this definition to AzureSearchClient.request(). AzureSearchClient.request() 中,由于 bodyJson === null 现在为 falseinit 结构不仅包含 HTTP 谓词(“PUT”)和标头,而且还将 body 设置为索引定义数据。In AzureSearchClient.request(), since bodyJson === null is now false, the init structure includes not only the HTTP verb ("PUT") and the headers, but sets the body to the index definition data.

准备并运行示例Prepare and run the sample

使用终端窗口运行以下命令。Use a terminal window for the following commands.

  1. 导航到包含 package.json 文件和剩余代码的文件夹。Navigate to the folder that contains the package.json file and the rest of your code.
  2. 使用 npm install 安装示例的包。Install the packages for the sample with npm install. 此命令将下载代码所依赖的包。This command will download the packages upon which the code depends.
  3. 使用 node index.js 运行程序。Run your program with node index.js.

你应会看到一系列消息,其中描述了程序正在执行的操作。You should see a series of messages describing the actions being taken by the program. 若要查看请求的更多详细信息,可以取消注释 AzureSearchClient.js 中的 [AzureSearchClient.request() 方法开头的行]https://github.com/Azure-Samples/azure-search-javascript-samples/blob/master/quickstart/AzureSearchClient.js#L21-L27) 。If you want to see more detail of the requests, you can uncomment the [lines at the beginning of the AzureSearchClient.request() method]https://github.com/Azure-Samples/azure-search-javascript-samples/blob/master/quickstart/AzureSearchClient.js#L21-L27) in AzureSearchClient.js.

在 Azure 门户中打开搜索服务的“概述”。 Open the Overview of your search service in the Azure portal. 选择“索引”选项卡。 会看到下面这样的内容:Select the Indexes tab. You should see something like the following:

Azure 门户、搜索服务概述、“索引”选项卡的屏幕截图

在下一步骤中,你要向索引添加数据。In the next step, you'll add data to index.

2 - 加载文档2 - Load Documents

在 Azure 认知搜索中,文档这一数据结构既是索引输入,也是查询输出。In Azure Cognitive Search, documents are data structures that are both inputs to indexing and outputs from queries. 需要将此类数据发布到索引。You need to POST such data to the index. 这会使用不同的终结点,而不是上一步骤中执行操作时使用的终结点。This uses a different endpoint than the operations done in the previous step. 打开 AzureSearchClient.js,并在 getIndexUrl() 后面添加以下方法:Open AzureSearchClient.js and add the following method after getIndexUrl():

 getPostDataUrl() { return `https://${this.searchServiceName}.search.azure.cn/indexes/${this.indexName}/docs/index?api-version=${this.apiVersion}`;  }

AzureSearchClient.createIndexAsync(definition) 一样,需要使用一个调用 AzureSearchClient.request() 的函数,并传入酒店数据作为其正文。Like AzureSearchClient.createIndexAsync(definition), you need a function that calls AzureSearchClient.request() and passes in the hotel data to be its body. AzureSearchClient.js 中的 createIndexAsync(definition) 后面添加 postDataAsync(hotelsData)In AzureSearchClient.js add postDataAsync(hotelsData) after createIndexAsync(definition):

async postDataAsync(hotelsData) {
    console.log("\n Adding hotel data...");
    const endpoint = this.getPostDataUrl();
    const response = await AzureSearchClient.request(endpoint,"POST", this.adminKey, hotelsData);
    AzureSearchClient.throwOnHttpError(response);
    return this;
}

文档输入可以是数据库中的行、Blob 存储中的 Blob,或磁盘上的 JSON 文档(在本示例中为 JSON 文档)。Document inputs might be rows in a database, blobs in Blob storage, or, as in this sample, JSON documents on disk. 可以下载 hotels.json,或创建包含以下内容的 hotels.json 文件:You can either download hotels.json or create your own hotels.json file with the following content:

{
    "value": [
        {
            "HotelId": "1",
            "HotelName": "Secret Point Motel",
            "Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
            "Description_fr": "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.",
            "Category": "Boutique",
            "Tags": ["pool", "air conditioning", "concierge"],
            "ParkingIncluded": false,
            "LastRenovationDate": "1970-01-18T00:00:00Z",
            "Rating": 3.6,
            "Address": {
                "StreetAddress": "677 5th Ave",
                "City": "New York",
                "StateProvince": "NY",
                "PostalCode": "10022"
            }
        },
        {
            "HotelId": "2",
            "HotelName": "Twin Dome Motel",
            "Description": "The hotel is situated in a  nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.",
            "Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
            "Category": "Boutique",
            "Tags": ["pool", "free wifi", "concierge"],
            "ParkingIncluded": "false",
            "LastRenovationDate": "1979-02-18T00:00:00Z",
            "Rating": 3.6,
            "Address": {
                "StreetAddress": "140 University Town Center Dr",
                "City": "Sarasota",
                "StateProvince": "FL",
                "PostalCode": "34243"
            }
        },
        {
            "HotelId": "3",
            "HotelName": "Triple Landscape Hotel",
            "Description": "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.",
            "Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
            "Category": "Resort and Spa",
            "Tags": ["air conditioning", "bar", "continental breakfast"],
            "ParkingIncluded": "true",
            "LastRenovationDate": "2015-09-20T00:00:00Z",
            "Rating": 4.8,
            "Address": {
                "StreetAddress": "3393 Peachtree Rd",
                "City": "Atlanta",
                "StateProvince": "GA",
                "PostalCode": "30326"
            }
        },
        {
            "HotelId": "4",
            "HotelName": "Sublime Cliff Hotel",
            "Description": "Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.",
            "Description_fr": "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.",
            "Category": "Boutique",
            "Tags": ["concierge", "view", "24-hour front desk service"],
            "ParkingIncluded": true,
            "LastRenovationDate": "1960-02-06T00:00:00Z",
            "Rating": 4.6,
            "Address": {
                "StreetAddress": "7400 San Pedro Ave",
                "City": "San Antonio",
                "StateProvince": "TX",
                "PostalCode": "78216"
            }
        }
    ]
}

若要将此数据载入程序,请修改 index.js:在顶部附近添加引用 hotelData 的行:To load this data into your program, modify index.js by adding the line referring to hotelData near the top:

const nconf = require('nconf');

const hotelData = require('./hotels.json');
const indexDefinition = require('./hotels_quickstart_index.json');

现在,修改 index.js 中的 run() 函数。Now modify the run() function in index.js. 索引在几秒钟后才可用,因此,在调用 AzureSearchClient.postDataAsync(hotelData) 之前请添加 2 秒暂停:It can take a few seconds for the index to become available, so add a 2-second pause before calling AzureSearchClient.postDataAsync(hotelData):

const run = async () => {
    try {
        const cfg = getAzureConfiguration();
        const client = new AzureSearchClient(cfg.get("serviceName"), cfg.get("adminKey"), cfg.get("queryKey"), cfg.get("indexName"));
        
        const exists = await client.indexExistsAsync();
        await exists ? client.deleteIndexAsync() : Promise.resolve();
        // Deleting index can take a few seconds
        await sleep(2000);
        await client.createIndexAsync(indexDefinition);
        // Index availability can take a few seconds
        await sleep(2000);
        await client.postDataAsync(hotelData);
    } catch (x) {
        console.log(x);
    }
}

使用 node index.js 再次运行程序。Run the program again with node index.js. 应会看到与步骤 1 中显示的消息略有不同的一系列消息。You should see a slightly different set of messages from those you saw in Step 1. 这一次,索引确实存在,并且你会看到有关应用创建新索引并向其发布数据之前删除此索引的消息。 This time, the index does exist, and you should see message about deleting it before the app creates the new index and posts data to it.

3 - 搜索索引3 - Search an index

返回 Azure 门户上搜索服务“概述”中的“索引”选项卡。 Return to the Indexes tab in the Overview of your search service on the Azure portal. 索引现在包含四个文档并消耗了一定的存储量(UI 可能需要在几分钟后才能正确反映索引的基础状态)。Your index now contains four documents and consumes some amount of storage (it may take a few minutes for the UI to properly reflect the underlying state of the index). 单击索引名称转到“搜索资源管理器”。 Click on the index name to be taken to the Search Explorer. 在此页中可以体验数据查询。This page allows you to experiment with data queries. 尝试搜索 *&$count=true 的查询字符串,应会返回所有文档和结果数。Try searching on a query string of *&$count=true and you should get back all your documents and the number of results. 尝试使用查询字符串 historic&highlight=Description&$filter=Rating gt 4,应会返回单个文档,其 <em></em> 标记中包装了“historic”一词。Try with the query string historic&highlight=Description&$filter=Rating gt 4 and you should get back a single document, with the word "historic" wrapped in <em></em> tags. 详细了解如何在 Azure 认知搜索中撰写查询Read more about how to compose a query in Azure Cognitive Search.

打开 index.js 并在顶部附近添加以下代码,以在代码中重新生成这些查询:Reproduce these queries in code by opening index.js and adding this code near the top:

const queries = [
    "*&$count=true",
    "historic&highlight=Description&$filter=Rating gt 4&"
];

在同一 index.js 文件中,按如下所示编写 doQueriesAsync() 函数。In the same index.js file, write the doQueriesAsync() function shown below. 此函数采用 AzureSearchClient 对象,并将 AzureSearchClient.queryAsync 方法应用到 queries 数组中的每个值。This function takes an AzureSearchClient object and applies the AzureSearchClient.queryAsync method to each of the values in the queries array. 它使用 Promise.all() 函数返回仅在所有查询均已解决时才得到解决的单个 PromiseIt uses the Promise.all() function to return a single Promise that only resolves when all of the queries have resolved. JSON.stringify(body, null, 4) 的调用将设置查询结果的格式以提高可读性。The call to JSON.stringify(body, null, 4) formats the query result to be more readable.

async function doQueriesAsync(client) {
    return Promise.all(
        queries.map( async query => {
            const result = await client.queryAsync(query);
            const body = await result.json();
            const str = JSON.stringify( body, null, 4);
            console.log(`Query: ${query} \n ${str}`);
        })
    );
}

修改 run() 函数以暂停足够长的时间,使索引器正常工作,然后调用 doQueriesAsync(client) 函数:Modify the run() function to pause long enough for the indexer to work and then to call the doQueriesAsync(client) function:

const run = async () => {
    try {
        const cfg = getAzureConfiguration();
        const client = new AzureSearchClient(cfg.get("serviceName"), cfg.get("adminKey"), cfg.get("queryKey"), cfg.get("indexName"));
        
        const exists = await client.indexExistsAsync();
        await exists ? client.deleteIndexAsync() : Promise.resolve();
        // Deleting index can take a few seconds
        await sleep(2000);
        await client.createIndexAsync(indexDefinition);
        // Index availability can take a few seconds
        await sleep(2000);
        await client.postDataAsync(hotelData);
        // Data availability can take a few seconds
        await sleep(5000);
        await doQueriesAsync(client);
    } catch (x) {
        console.log(x);
    }
}

若要实现 AzureSearchClient.queryAsync(query),请编辑文件 AzureSearchClient.jsTo implement AzureSearchClient.queryAsync(query), edit the file AzureSearchClient.js. 搜索操作需要使用不同的终结点,搜索词将变为 URL 参数,因此,请结合已编写的 getIndexUrl()getPostDataUrl() 方法一起添加函数 getSearchUrl(searchTerm)Searching requires a different endpoint, and the search terms become URL arguments, so add the function getSearchUrl(searchTerm) alongside the getIndexUrl() and getPostDataUrl() methods you've already written.

getSearchUrl(searchTerm) { return `https://${this.searchServiceName}.search.azure.cn/indexes/${this.indexName}/docs?api-version=${this.apiVersion}&search=${searchTerm}&searchMode=all`; }

queryAsync(searchTerm) 函数还会访问 AzureSearchClient.js,并遵循与 postDataAsync(data) 和其他查询函数相同的结构:The queryAsync(searchTerm) function also goes in AzureSearchClient.js and follows the same structure as postDataAsync(data) and the other querying functions:

async queryAsync(searchTerm) {
    console.log("\n Querying...")
    const endpoint = this.getSearchUrl(searchTerm);
    const response = await AzureSearchClient.request(endpoint, "GET", this.queryKey);
    AzureSearchClient.throwOnHttpError(response);
    return response;
}

搜索是使用“GET”谓词(不使用正文)完成的,因为搜索词是 URL 的一部分。Search is done with the "GET" verb and no body, since the search term is part of the URL. 请注意,queryAsync(searchTerm) 使用 this.queryKey,这与使用管理密钥的其他函数不同。Notice that queryAsync(searchTerm) uses this.queryKey, unlike the other functions that used the admin key. 顾名思义,查询密钥只可用于查询索引,而不可用于以任何方式修改索引。Query keys, as the name implies, can only be used for querying the index and can't be used to modify the index in any way. 因此,可以更安全地将查询密钥分发到客户端应用程序。Query keys are therefore safer to distribute to client applications.

使用 node index.js 运行程序。Run the program with node index.js. 现在,除上述步骤以外,还会发送查询并将结果写入控制台。Now, in addition to the previous steps, the queries will be sent and the results written to the console.

关于本示例About the sample

该示例使用少量的酒店数据,但足以演示有关创建和查询 Azure 认知搜索索引的基础知识。The sample uses a small amount of hotel data, sufficient to demonstrate the basics of creating and querying an Azure Cognitive Search index.

AzureSearchClient 类封装搜索服务的配置、URL 和基本 HTTP 请求。The AzureSearchClient class encapsulates the configuration, URLs, and basic HTTP requests for the search service. index.js 文件加载 Azure 认知搜索服务的配置数据、要上传的用于编制索引的酒店数据、要在其 run 函数中指定的订单,并执行各种操作。The index.js file loads the configuration data for the Azure Cognitive Search service, the hotel data that will be uploaded for indexing, and, in its run function, orders, and executes the various operations.

run 函数的总体行为是删除 Azure 认知搜索索引(如果存在)、创建索引、添加一些数据,并执行一些查询。The overall behavior of the run function is to delete the Azure Cognitive Search index if it exists, create the index, add some data, and perform some queries.

清理资源Clean up resources

在自己的订阅中操作时,最好在项目结束时确定是否仍需要已创建的资源。When you're working in your own subscription, it's a good idea at the end of a project to identify whether you still need the resources you created. 持续运行资源可能会产生费用。Resources left running can cost you money. 可以逐个删除资源,也可以删除资源组以删除整个资源集。You can delete resources individually or delete the resource group to delete the entire set of resources.

可以使用左侧导航窗格中的“所有资源”或“资源组”链接 ,在门户中查找和管理资源。You can find and manage resources in the portal, using the All resources or Resource groups link in the left-navigation pane.

如果使用的是免费服务,请记住只能设置三个索引、索引器和数据源。If you are using a free service, remember that you are limited to three indexes, indexers, and data sources. 可以在门户中删除单个项目,以不超出此限制。You can delete individual items in the portal to stay under the limit.

后续步骤Next steps

在本 Node.js 快速入门中,我们已完成一系列任务,包括创建索引、使用文档加载索引并运行查询。In this Node.js quickstart, you worked through a series of tasks to create an index, load it with documents, and run queries. 我们以尽量简单的方法执行了一些步骤,例如读取配置和定义查询。We did certain steps, such as reading the configuration and defining the queries, in the simplest possible way. 在实际的应用程序中,你可能会在提供灵活性和封装功能的单独模块中解决这些问题。In a real application, you would want to put those concerns in separate modules that would provide flexibility and encapsulation.

如果已对 Azure 认知搜索有一定的了解,可以将此教程用作尝试使用建议器(提前键入或自动完成查询)、筛选器和分面导航的跳板。If you already have some background in Azure Cognitive Search, you can use this sample as a springboard for trying suggesters (type-ahead or autocomplete queries), filters, and faceted navigation. 如果你是 Azure 认知搜索的新手,我们建议尝试阅读其他教程,深入了解可以创建哪些内容。If you're new to Azure Cognitive Search, we recommend trying other tutorials to develop an understanding of what you can create. 请访问 文档页 查找更多资源。Visit our documentation page to find more resources.