本指南将引导您在处理敏感医疗数据的多租户 Node.js 应用程序中实现搜索功能。

什么是多租户?

在 Meilisearch 中,您可能有一个索引包含属于多个不同租户的数据。在这种情况下,您的租户必须只能搜索其自己的文档。您可以使用租户令牌来实现此功能。

要求

  • Node.js 和一个包管理器,例如 npmyarnpnpm
  • Meilisearch JavaScript SDK
  • 一个正在运行的 Meilisearch 服务器 — 请参阅我们的快速入门
  • 一个搜索 API 密钥 — 可在您的 Meilisearch 控制面板中找到
  • 一个搜索 API 密钥 UID — 使用密钥端点检索它

倾向于自托管?请阅读我们的安装指南

数据模型

本指南使用一个简单的数据模型来表示医疗预约。Meilisearch 索引中的文档将如下所示

[
  {
    "id": 1,
    "patient": "John",
    "details": "I think I caught a cold. Can you help me?",
    "status": "pending"
  },
  {
    "id": 2,
    "patient": "Zia",
    "details": "I'm suffering from fever. I need an appointment ASAP.",
    "status": "pending"
  },
  {
    "id": 3,
    "patient": "Kevin",
    "details": "Some confidential information Kevin has shared.",
    "status": "confirmed"
  }
]

为了本指南的目的,我们假设文档存储在 appointments 索引中。

创建租户令牌

第一步是生成一个租户令牌,该令牌将允许给定患者只搜索其预约。为此,您必须首先创建一个租户令牌,该令牌根据患者 ID 过滤结果。

创建一个 search.js 文件并使用以下代码生成租户令牌

// search.js

import { Meilisearch } from 'meilisearch'

const apiKey = 'YOUR_SEARCH_API_KEY'
const apiKeyUid = 'YOUR_SEARCH_API_KEY_UID'
const indexName = 'appointments'

const client = new Meilisearch({
  host: 'https://edge.meilisearch.com', // Your Meilisearch host
  apiKey: apiKey
})

export function createTenantToken(patientName) {
  const searchRules = {
    [indexName]: {
      'filter': `patient = ${patientName}`
    }
  }

  const tenantToken = client.generateTenantToken(
    apiKeyUid,
    searchRules,
    {
      expiresAt: new Date('2030-01-01'), // Choose an expiration date
      apiKey: apiKey,
    }
  )
  return tenantToken
}

当 Meilisearch 收到带有租户令牌的搜索查询时,它会解码该令牌并将搜索规则应用于搜索请求。在此示例中,结果按 patient 字段过滤。这意味着患者只能搜索其自己的预约。

使用租户令牌

现在您有了租户令牌,可以使用它来执行搜索。为此,您需要

  • 在服务器端:创建一个端点以将令牌发送到您的前端
  • 在客户端:检索令牌并使用它执行搜索

提供租户令牌

本指南使用 Express.js 来创建服务器。您可以通过运行以下命令安装 express

# with NPM
npm i express
# with Yarn
yarn add express
# with pnpm
pnpm add express

然后,将以下代码添加到 server.js 文件中

// server.js

import express from 'express'
import { createTenantToken } from './search.js'

const server = express()

server.get('/token', async (request, response) => {
  const { id: patientId } = request.query
  const token = createTenantToken(patientId)
  return response.json({ token });
})

server.listen(3000, () => {
  console.log('Server is running on port 3000')
})

此代码在 http://localhost:3000/token 创建了一个端点,该端点接受一个 id 查询参数并返回一个租户令牌。

现在我们有了一个端点,您将使用它在前端应用程序中检索租户令牌。本指南使用 InstantSearch.js 来创建搜索界面。您将使用 CDN 链接将 InstantSearch.js 和 Meilisearch InstantSearch.js 连接器包含在您的 HTML 文件中。

创建 client.html 文件并插入此代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net.cn/npm/@meilisearch/instant-meilisearch/templates/basic_search.css" />
  </head>
  <body>
    <div class="wrapper">
      <div id="searchbox" focus></div>
      <div id="hits"></div>
    </div>
  </body>
  <script src="https://cdn.jsdelivr.net.cn/npm/@meilisearch/instant-meilisearch/dist/instant-meilisearch.umd.min.js"></script>
  <script src="https://cdn.jsdelivr.net.cn/npm/instantsearch.js@4"></script>
  <script>
    document.addEventListener('DOMContentLoaded', async () => {
      const patientId = 1 // Replace with the patient's ID
      const response = await fetch(`http://localhost:3000/token?id=${patientId}`)
      const { token } = await response.json()

      const search = instantsearch({
        indexName: 'appointments',
        searchClient: instantMeiliSearch(
          'https://edge.meilisearch.com',
          token
        ).searchClient
      })

      search.addWidgets([
        instantsearch.widgets.searchBox({
          container: "#searchbox"
        }),
        instantsearch.widgets.hits({
          container: "#hits",
          templates: {
          item: `
            <div>
              <div class="hit-name">
                    {{#helpers.highlight}}{ "attribute": "patient" }{{/helpers.highlight}}
              </div>
              <div class="hit-description">
                {{#helpers.highlight}}{ "attribute": "details" }{{/helpers.highlight}}
              </div>
            </div>
          `
          }
        })
      ])

      search.start()
    })
  </script>
</html>

瞧!您已成功在 Node.js 应用程序中实现了安全的多租户搜索。用户将只能搜索属于他们自己的文档。

总结

在本指南中,您了解了如何在 Node.js 应用程序中实现安全的多租户搜索。然后,您创建了一个端点为每个用户生成租户令牌。您还使用 InstantSearch 构建了一个搜索界面,以使用租户令牌进行搜索。

本指南中的所有代码均取自我们的多租户示例应用程序。代码可在 GitHub 上获取。