秘诀示例:自定义全局中间件
¥Examples cookbook: Custom global middlewares
此页面是后端定制示例手册的一部分。请确保你已阅读其 introduction。
¥This page is part of the back end customization examples cookbook. Please ensure you've read its introduction.
开箱即用,FoodAdvisor 不提供任何可以使用传入请求并在执行控制器代码之前执行一些附加逻辑的自定义中间件。
¥Out of the box, FoodAdvisor does not provide any custom middlewares that could use incoming requests and perform some additional logic before executing the controller code.
Strapi 有 2 种类型的中间件:路由中间件控制对路由的访问,而全局中间件具有更广泛的范围(请参阅 中间件定制 的参考文档)。
¥There are 2 types of middlewares in Strapi: route middlewares control access to a route while global middlewares have a wider scope (see reference documentation for middlewares customization).
可以使用自定义路由中间件代替策略来控制对端点的访问(请参阅 政策秘诀),并且可以在将上下文传递到 Strapi 服务器的进一步核心元素之前修改上下文。本页面不会介绍自定义路由中间件,而是说明自定义全局中间件的更详细用法。
¥Custom route middlewares could be used instead of policies to control access to an endpoint (see policies cookbook) and could modify the context before passing it down to further core elements of the Strapi server. This page will not cover custom route middlewares but rather illustrate a more elaborated usage for custom global middlewares.
使用自定义中间件填充 Google Sheets 中的分析仪表板
¥Populating an analytics dashboard in Google Sheets with a custom middleware
💭 上下文:
¥💭 Context:
本质上,中间件在到达服务器的请求和执行控制器功能之间执行。例如,中间件是执行某些分析的好地方。
¥In essence, a middleware gets executed between a request arriving at the server and the controller function getting executed. So, for instance, a middleware is a good place to perform some analytics.
让我们创建一个使用 Google Spreadsheets 制作的分析仪表板的基本示例,以深入了解 FoodAdvisor 的哪些餐厅页面访问量更大。
¥Let’s create a rudimentary example of an analytics dashboard made with Google Spreadsheets to have some insights on which restaurants pages of FoodAdvisor are more visited.
🎯 目标:
¥🎯 Goals:
-
创建一些与 Google Sheets 交互的实用函数。
¥Create some utility functions that interact with Google Sheets.
-
创建一个自定义 Strapi 中间件,每次我们收到对 FoodAdvisor 项目的餐厅页面的传入请求时,该中间件都会创建和/或更新现有的 Google Sheet 文档。
¥Create a custom Strapi middleware that will create and/or update an existing Google Sheet document every time we have an incoming request to a Restaurants page of the FoodAdvisor project.
-
将自定义中间件附加到我们希望执行它的路由。
¥Append the custom middleware to the route where we want it to get executed.
其他信息可在 中间件定制 文档中找到。
¥Additional information can be found in the middlewares customization documentation.
🧑💻 代码示例:
¥🧑💻 Code example:
-
在 FoodAdvisor 项目的
/api
文件夹中,创建一个/restaurant/middlewares/utils.js
文件,示例代码如下:¥In the
/api
folder of the FoodAdvisor project, create a/restaurant/middlewares/utils.js
file with the following example code:
Example utility functions that could be used to read, write and update a Google spreadsheet:
以下代码允许在给定从 JSON 文件读取的 API 密钥和从 URL 检索的电子表格 ID 的情况下读取、写入和更新 Google 电子表格:
¥The following code allows reading, writing, and updating a Google spreadsheet given an API Key read from a JSON file and a spreadsheet ID retrieved from the URL:
更多信息可以在官方 Google 表格 API 文档 中找到。
¥Additional information can be found in the official Google Sheets API documentation.
const { google } = require('googleapis');
const createGoogleSheetClient = async ({
keyFile,
sheetId,
tabName,
range,
}) => {
async function getGoogleSheetClient() {
const auth = new google.auth.GoogleAuth({
keyFile,
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
});
const authClient = await auth.getClient();
return google.sheets({
version: 'v4',
auth: authClient,
});
}
const googleSheetClient = await getGoogleSheetClient();
const writeGoogleSheet = async (data) => {
googleSheetClient.spreadsheets.values.append({
spreadsheetId: sheetId,
range: `${tabName}!${range}`,
valueInputOption: 'USER_ENTERED',
insertDataOption: 'INSERT_ROWS',
resource: {
majorDimension: 'ROWS',
values: data,
},
});
};
const updateoogleSheet = async (cell, data) => {
googleSheetClient.spreadsheets.values.update({
spreadsheetId: sheetId,
range: `${tabName}!${cell}`,
valueInputOption: 'USER_ENTERED',
resource: {
majorDimension: 'ROWS',
values: data,
},
});
};
const readGoogleSheet = async () => {
const res = await googleSheetClient.spreadsheets.values.get({
spreadsheetId: sheetId,
range: `${tabName}!${range}`,
});
return res.data.values;
};
return {
writeGoogleSheet,
updateoogleSheet,
readGoogleSheet,
};
};
module.exports = {
createGoogleSheetClient,
};
-
在 FoodAdvisor 项目的
/api
文件夹中,使用以下代码创建自定义analytics
中间件:¥In the
/api
folder of the FoodAdvisor project, create a customanalytics
middleware with the following code:
'use strict';
const { createGoogleSheetClient } = require('./utils');
const serviceAccountKeyFile = './gs-keys.json';
// Replace the sheetId value with the corresponding id found in your own URL
const sheetId = '1P7Oeh84c18NlHp1Zy-5kXD8zgpoA1WmvYL62T4GWpfk';
const tabName = 'Restaurants';
const range = 'A2:C';
const VIEWS_CELL = 'C';
const transformGSheetToObject = (response) =>
response.reduce(
(acc, restaurant) => ({
...acc,
[restaurant[0]]: {
id: restaurant[0],
name: restaurant[1],
views: restaurant[2],
cellNum: Object.keys(acc).length + 2 // + 2 because we need to consider the header and that the initial length is 0, so our first real row would be 2,
},
}),
{}
);
module.exports = (config, { strapi }) => {
return async (context, next) => {
// Generating google sheet client
const { readGoogleSheet, updateoogleSheet, writeGoogleSheet } =
await createGoogleSheetClient({
keyFile: serviceAccountKeyFile,
range,
sheetId,
tabName,
});
// Get the restaurant ID from the params in the URL
const restaurantId = context.params.id;
const restaurant = await strapi.entityService.findOne(
'api::restaurant.restaurant',
restaurantId
);
// Read the spreadsheet to get the current data
const restaurantAnalytics = await readGoogleSheet();
/**
* The returned data comes in the shape [1, "Mint Lounge", 23],
* and we need to transform it into an object: {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 {
/** If we don't have the restaurant in the spreadsheet already,
* we create it with 1 view.
*/
const newRestaurant = [[restaurant.id, restaurant.name, 1]];
await writeGoogleSheet(newRestaurant);
}
// Call next to continue with the flow and get to the controller
await next();
};
};
-
配置 "餐厅" 内容类型的路由,以便在查询餐厅页面时执行自定义
analytics
中间件。为此,请使用以下代码:¥Configure the routes for the "Restaurants" content-type to execute the custom
analytics
middleware whenever a restaurant page is queried. To do so, use the following code:
'use strict';
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::restaurant.restaurant', {
config: {
findOne: {
auth: false,
policies: [],
middlewares: ['api::restaurant.analytics'],
},
},
});