全文搜索

大多数应用允许用户搜索应用内容。例如,您可能想要搜索包含某个特定字词的帖子,或者搜索您撰写的关于特定主题的笔记。

Cloud Firestore 不支持原生索引,也不支持搜索文档中的文本字段。另外,下载整个集合并在客户端搜索字段也不现实。

解决方案:Algolia

要对 Cloud Firestore 数据进行全文搜索,您可使用 Algolia 等第三方搜索服务。以一个笔记应用(它的每条笔记都是一个文档)为例:

// /notes/${ID}
{
  owner: "{UID}", // Firebase Authentication's User ID of note owner
  text: "This is my first note!"
}

您可以将 Algolia 与 Cloud Functions 结合使用,以每条笔记的内容填充索引并实现搜索功能。首先,使用您的应用 ID 和 API 密钥配置 Algolia 客户端:

Node.js

// Initialize Algolia, requires installing Algolia dependencies:
// https://www.algolia.com/doc/api-client/javascript/getting-started/#install
//
// App ID and API Key are stored in functions config variables
const ALGOLIA_ID = functions.config().algolia.app_id;
const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key;
const ALGOLIA_SEARCH_KEY = functions.config().algolia.search_key;

const ALGOLIA_INDEX_NAME = 'notes';
const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);

接下来,添加一个函数,该函数会在每次写入备注时更新索引:

Node.js

// Update the search index every time a blog post is written.
exports.onNoteCreated = functions.firestore.document('notes/{noteId}').onCreate((snap, context) => {
  // Get the note document
  const note = snap.data();

  // Add an 'objectID' field which Algolia requires
  note.objectID = context.params.noteId;

  // Write to the algolia index
  const index = client.initIndex(ALGOLIA_INDEX_NAME);
  return index.saveObject(note);
});

将数据编入索引后,您可以使用 Algolia 针对 iOS、Android 或网页的集成方案来搜索数据。

网页

var client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_KEY);
var index = client.initIndex('notes');

// Perform an Algolia search:
// https://www.algolia.com/doc/api-reference/api-methods/search/
index
  .seach({
    query
  })
  .then(function(responses) {
    // Response from Algolia:
    // https://www.algolia.com/doc/api-reference/api-methods/search/#response-format
    console.log(responses.hits);
  });

添加保护闸

如果所有笔记都可供所有用户搜索,那么原始解决方案就完全够用了。但是,许多使用情形需要以安全的方式限制搜索结果的范围。对于这些使用情形,您可以添加一个 HTTP Cloud Functions 函数来生成安全的 API 密钥,从而为用户执行的每个查询增加特定的过滤条件。

Node.js

// This complex HTTP function will be created as an ExpressJS app:
// https://expressjs.com/en/4x/api.html
const app = require('express')();

// We'll enable CORS support to allow the function to be invoked
// from our app client-side.
app.use(require('cors')({origin: true}));

// Then we'll also use a special 'getFirebaseUser' middleware which
// verifies the Authorization header and adds a `user` field to the
// incoming request:
// https://gist.github.com/abehaskins/832d6f8665454d0cd99ef08c229afb42
app.use(getFirebaseUser);

// Add a route handler to the app to generate the secured key
app.get('/', (req, res) => {
  // Create the params object as described in the Algolia documentation:
  // https://www.algolia.com/doc/guides/security/api-keys/#generating-api-keys
  const params = {
    // This filter ensures that only documents where author == user_id will be readable
    filters: `author:${req.user.user_id}`,
    // We also proxy the user_id as a unique token for this key.
    userToken: req.user.user_id,
  };

  // Call the Algolia API to generate a unique key based on our search key
  const key = client.generateSecuredApiKey(ALGOLIA_SEARCH_KEY, params);

  // Then return this key as {key: '...key'}
  res.json({key});
});

// Finally, pass our ExpressJS app to Cloud Functions as a function
// called 'getSearchKey';
exports.getSearchKey = functions.https.onRequest(app);

如果您使用此函数来提供 Algolia 搜索密钥,用户将只能搜索 author 字段与其用户 ID 完全相同的笔记。

网页

// Use Firebase Authentication to request the underlying token
return firebase.auth().currentUser.getIdToken()
  .then(function(token) {
    // The token is then passed to our getSearchKey Cloud Function
    return fetch('https://us-central1-' + PROJECT_ID + '.cloudfunctions.net/getSearchKey/', {
        headers: { Authorization: 'Bearer ' + token }
    });
  })
  .then(function(response) {
    // The Fetch API returns a stream, which we convert into a JSON object.
    return response.json();
  })
  .then(function(data) {
    // Data will contain the restricted key in the `key` field.
    client = algoliasearch(ALGOLIA_APP_ID, data.key);
    index = client.initIndex('notes');

    // Perform the search as usual.
    return index.search({query});
  })
  .then(function(responses) {
    // Finally, use the search 'hits' returned from Algolia.
    return responses.hits;
  });

您无需为每个查询提取搜索密钥。您应该缓存提取的密钥或 Algolia 客户端,以加快搜索速度。

限制

上述解决方案是为 Cloud Firestore 数据提供全文搜索功能的一种简单方法。但请注意,Algolia 不是唯一的第三方搜索服务提供商。您不妨考虑使用 ElasticSearch 等其他提供方,然后选择最合适的解决方案部署到生产环境中。

发送以下问题的反馈:

此网页
需要帮助?请访问我们的支持页面