自定义中间件

通过我们的 FoodAdvisor 示例了解如何使用自定义中间件

示例手册:自定义全局中间件

此页面是后端自定义示例手册的一部分。请确保您已阅读其简介

开箱即用,FoodAdvisor 不提供任何可以使用传入请求并在执行控制器代码之前执行一些额外逻辑的自定义中间件。

Strapi 中有 2 种类型的中间件:路由中间件 控制对路由的访问,而 全局中间件 具有更广泛的范围(请参阅 中间件自定义 的参考文档)。

可以使用自定义路由中间件代替策略来控制对端点的访问(请参阅 策略手册),并且可以在将上下文传递给 Strapi 服务器的其他核心元素之前对其进行修改。本页面将不介绍自定义路由中间件,而是说明 自定义全局中间件 的更详细用法。

使用自定义中间件在 Google 表格中填充分析仪表板

💭 上下文:

本质上,中间件在到达服务器的请求和执行控制器函数之间执行。因此,例如,中间件是执行某些分析的好地方。

让我们创建一个使用 Google 电子表格制作的分析仪表板的基本示例,以了解 FoodAdvisor 中哪些餐厅页面访问量较大。

访问餐厅页面会更新 Google Sheets 电子表格 对餐厅页面的每个 GET 请求都会执行自定义中间件的代码,实时更新 Google Sheets 电子表格。

🎯 目标:

  • 创建一些与 Google Sheets 交互的实用函数。
  • 创建自定义 Strapi 中间件,每次我们收到对 FoodAdvisor 项目的 Restaurants 页面的传入请求时,该中间件都会创建和/或更新现有的 Google Sheet 文档。
  • 将自定义中间件附加到我们希望它执行的路由。

更多信息可以在 中间件自定义 文档中找到。

🧑‍💻 代码示例:

  1. FoodAdvisor 项目的 /api 文件夹中,创建一个 /restaurant/middlewares/utils.js 文件,其中包含以下示例代码:
可用于读取、写入和更新 Google 电子表格的示例实用函数:
详情
  1. 在FoodAdvisor项目的/api文件夹中,使用以下代码创建自定义analytics中间件:
src/api/restaurant/middlewares/analytics.js
jsx
'use strict'

const { createGoogleSheetClient } = require('./utils')

const serviceAccountKeyFile = './gs-keys.json'
// 将 sheetId 值替换为在您自己的 URL 中找到的相应 id
const sheetId = '1P7Oeh84c18NlHp1Zy-5kXD8zgpoA1WmvYL62T4GWpfk'
const tabName = 'Restaurants'
const range = 'A2:C'

const VIEWS_CELL = 'C'

function transformGSheetToObject(response) {
  return response.reduce(
    (acc, restaurant) => ({
      ...acc,
      [restaurant[0]]: {
        id: restaurant[0],
        name: restaurant[1],
        views: restaurant[2],
        cellNum: Object.keys(acc).length + 2
        // + 2 因为我们需要考虑标题,并且初始长度为 0,所以我们的第一个实际行将是 2,
      },
    }),
    {}
  )
}

module.exports = (config, { strapi }) => {
  return async (context, next) => {
    // 生成 google sheet 客户端
    const { readGoogleSheet, updateoogleSheet, writeGoogleSheet }
        = await createGoogleSheetClient({
          keyFile: serviceAccountKeyFile,
          range,
          sheetId,
          tabName,
        })

    // 从 URL 中的参数获取餐厅 ID
    const restaurantId = context.params.id
    const restaurant = await strapi.entityService.findOne(
      'api::restaurant.restaurant',
      restaurantId
    )

    // 读取电子表格以获取当前数据
    const restaurantAnalytics = await readGoogleSheet()

    /**
     * 返回的数据形状为 [1, "Mint Lounge", 23],
     * 我们需要将其转换为对象:{id: 1, name: "Mint Lounge", views: 23, cellNum: 2}
     */
    const requestedRestaurant
        = transformGSheetToObject(restaurantAnalytics)[restaurantId]

    if (requestedRestaurant) {
      await updateoogleSheet(
          `${VIEWS_CELL}${requestedRestaurant.cellNum}:${VIEWS_CELL}${requestedRestaurant.cellNum}`,
          [[Number(requestedRestaurant.views) + 1]]
      )
    }
    else {
      /**
       * 如果电子表格中还没有餐厅,
       * 我们会用 1 个视图来创建它。
       */
      const newRestaurant = [[restaurant.id, restaurant.name, 1]]
      await writeGoogleSheet(newRestaurant)
    }

    // 调用 next 继续流程并进入控制器
    await next()
  }
}
  1. 配置“餐厅”内容类型的路由,以便在查询餐厅页面时执行自定义“分析”中间件。为此,请使用以下代码:
src/api/restaurant/routes/restaurant.js
jsx
'use strict'

const { createCoreRouter } = require('@strapi/strapi').factories

module.exports = createCoreRouter('api::restaurant.restaurant', {
  config: {
    findOne: {
      auth: false,
      policies: [],
      middlewares: ['api::restaurant.analytics'],
    },
  },
})