示例手册:自定义服务和控制器
此页面是后端自定义示例手册的一部分。请确保您已阅读其简介。
从 FoodAdvisor 的前端网站,您可以浏览可通过 localhost:3000/restaurants
访问的餐厅列表。单击列表中的任何餐厅都将使用 /client
文件夹中包含的代码来显示有关该餐厅的其他信息。餐厅页面上显示的内容是在 Strapi 的内容管理器中创建的,并通过查询 Strapi 的 REST API 来检索,该 API 使用 /api
文件夹中包含的代码。
本页将讲授以下高级主题:
主题 | 部分 |
---|---|
创建与 Strapi 后端交互的组件 | 来自前端的 REST API 查询 |
了解服务和控制器如何协同工作 | 控制器与服务 |
创建自定义服务 | |
在控制器中使用服务 | 自定义控制器 |
来自前端的 REST API 查询
💭 上下文:
FoodAdvisor 前端网站上的餐厅页面包含一个只读的评论部分。添加评论需要登录 Strapi 的管理面板并通过 内容管理器 将内容添加到“评论”集合类型。
让我们在餐厅页面中添加一个小的前端组件。该组件将允许用户直接从前端网站撰写评论。
FoodAdvisor 前端网站餐厅页面上允许用户提交新评论的表单示例
🎯 目标:
- 添加表单以撰写评论。
- 在任何餐厅页面上显示表单。
- 提交表单时向 Strapi 的 REST API 发送 POST 请求。
- 使用 之前存储的 JWT 对请求进行身份验证。
有关内容类型端点的更多信息,请参阅 REST API 文档。
🧑💻 代码示例:
在 FoodAdvisor 项目的 /client
文件夹中,您可以使用以下代码示例:
- 创建一个新
pages/restaurant/RestaurantContent/Reviews/new-review.js
文件, - 并更新现有的
components/pages/restaurant/RestaurantContent/Reviews/reviews.js
。
添加用于撰写评论的组件并将其显示在餐厅页面上的示例前端代码:
控制器与服务
控制器可以包含客户端请求路由时要执行的任何业务逻辑。但是,随着代码变得越来越大并且变得更加结构化,最佳做法是将逻辑拆分为只做一件事的特定服务,然后从控制器调用服务。
为了说明服务的使用,在本文档中,自定义控制器不处理任何职责,并将所有业务逻辑委托给服务。
假设我们想自定义 FoodAdvisor 的后端以实现以下场景:在前端网站上提交 之前添加的评论表单 时,Strapi 将在后端创建评论并通过电子邮件通知餐厅老板。将其转换为 Strapi 后端定制意味着执行 3 个操作:
- 创建自定义服务以 创建评论。
- 创建自定义服务以 发送电子邮件。
- 自定义 Strapi 为 Review 内容类型提供的默认控制器 以使用 2 个新服务。
自定义服务:创建评论
💭 上下文:
默认情况下,Strapi 中的服务文件包含使用 createCoreService
工厂函数的基本样板代码。
让我们通过替换其代码来更新 FoodAdvisor 的“Reviews”集合类型的现有 review.js
服务文件以创建评论。
🎯 目标:
- 声明一个
create
方法。 - 从请求中获取上下文。
- 使用 EntityService API 中的
findMany()
方法查找餐厅。 - 使用 EntityService API 中的
create()
方法将数据附加到餐厅,填充餐厅老板。 - 返回新的评论数据。
更多信息可以在 请求上下文、服务 和 EntityService API 文档中找到。
🧑💻 代码示例:
要创建这样的服务,请在 FoodAdvisor 项目的 /api
文件夹中,将 src/api/review/services/review.js
文件的内容替换为以下代码:
const { createCoreService } = require('@strapi/strapi').factories
module.exports = createCoreService('api::review.review', ({ strapi }) => ({
async create(ctx) {
const user = ctx.state.user
const { body } = ctx.request
/**
* 查询 Restaurants 集合类型
* 使用 Entity Service API
* 检索有关餐厅的信息。
*/
const restaurants = await strapi.entityService.findMany(
'api::restaurant.restaurant',
{
filters: {
slug: body.restaurant,
},
}
)
/**
* 为 Reviews 集合类型创建一个新条目
* 并使用 Entity Service API 将餐厅所有者的相关信息填充到数据中。
*/
const newReview = await strapi.entityService.create('api::review.review', {
data: {
note: body.note,
content: body.content,
restaurant: restaurants[0].id,
author: user.id,
},
populate: ['restaurant.owner'],
})
return newReview
},
}))
自定义服务:向餐厅老板发送电子邮件
💭 上下文:
开箱即用,FoodAdvisor 不提供任何自动电子邮件服务功能。
让我们创建一个 email.js
服务文件来发送电子邮件。我们可以在自定义控制器中使用它,每当在前端网站上创建新评论时通知餐馆老板。
- 您已设置电子邮件插件的提供程序,例如Sendmail提供程序。
- 在 Strapi 的管理面板中,您已创建了一个
Email
单一类型,其中包含一个from
文本字段来定义发件人的电子邮件地址。
管理面板中已创建电子邮件单一类型。它包含一个“发件人”字段,用于定义电子邮件插件的发件人地址。
🎯 目标:
- 为“电子邮件”单一类型创建一个新的服务文件,
- 为该服务声明一个
send()
方法, - 使用实体服务 API 获取存储在电子邮件单一类型中的发件人地址,
- 使用调用服务的
send()
方法时传递的电子邮件详细信息(收件人地址、主题和电子邮件正文)使用电子邮件插件和先前配置的提供程序发送电子邮件。
更多信息请参阅 服务、实体服务 API、电子邮件插件 和 提供商 文档。
🧑💻 代码示例:
要创建这样的服务,请在 FoodAdvisor 项目的 /api
文件夹中,创建一个新的 src/api/email/services/email.js
文件,其中包含以下代码:
const { createCoreService } = require('@strapi/strapi').factories
module.exports = createCoreService('api::email.email', ({ strapi }) => ({
async send({ to, subject, html }) {
/**
* 使用实体服务 API 检索
* 存储在 Email 单一类型中
* 的电子邮件配置数据。
*/
const emailConfig = await strapi.entityService.findOne(
'api::email.email',
1
)
/**
* 使用以下方式发送电子邮件:
* - 调用服务时要传递的参数
* - 先前使用电子邮件配置检索到的“发件人”地址
*/
await strapi.plugins.email.services.email.send({
to,
subject,
html,
from: emailConfig.from,
})
},
}))
在控制器的代码中,可以使用 strapi.service('api::email.email).send(parameters)
调用此电子邮件服务的 send
方法,其中 parameters
是一个包含电子邮件相关信息(收件人地址、主题和电子邮件正文)的对象。
自定义控制器
💭 上下文:
默认情况下,Strapi 中的控制器文件包含使用 createCoreController
工厂函数的基本样板代码。这公开了在到达请求的端点时创建、检索、更新和删除内容的基本方法。可以自定义控制器的默认代码以执行任何业务逻辑。
让我们使用以下场景自定义 FoodAdvisor 的“Reviews”集合类型的默认控制器:在向 /reviews
端点发出 POST
请求后,控制器调用先前创建的服务来 创建评论 和 向餐厅老板发送电子邮件。
🎯 目标:
- 扩展现有的“Reviews”集合类型的控制器。
- 声明自定义
create()
方法。 - 调用先前创建的服务。
- 清理要返回的内容。
更多信息可以在 控制器 文档中找到。
🧑💻 代码示例:
在 FoodAdvisor 项目的 /api
文件夹中,将 src/api/review/controllers/review.js
文件的内容替换为以下代码示例之一,具体取决于您之前是只创建了 一个自定义服务 还是同时创建了评论创建和 电子邮件通知 的自定义服务:
不带电子邮件服务的自定义控制器
const { createCoreController } = require('@strapi/strapi').factories
module.exports = createCoreController('api::review.review', ({ strapi }) => ({
/**
* 由于控制器操作的名称
* 与核心控制器提供的原始“创建”操作完全相同,
* 它会覆盖它。
*/
async create(ctx) {
// Creates the new review using a service
const newReview = await strapi.service('api::review.review').create(ctx)
const sanitizedReview = await this.sanitizeOutput(newReview, ctx)
ctx.body = sanitizedReview
},
}))
具有电子邮件服务的自定义控制器
const { createCoreController } = require('@strapi/strapi').factories
module.exports = createCoreController('api::review.review', ({ strapi }) => ({
/**
* 由于控制器操作的名称
* 与核心控制器提供的原始“创建”操作完全相同,
* 它会覆盖它。
*/
async create(ctx) {
// 使用服务创建新评论
const newReview = await strapi.service('api::review.review').create(ctx)
// 使用另一项服务向餐厅老板发送电子邮件
if (newReview.restaurant?.owner) {
await strapi.service('api::email.email').send({
to: newReview.restaurant.owner.email,
subject: 'You have a new review!',
html: `You've received a ${newReview.note} star review: ${newReview.content}`,
})
}
const sanitizedReview = await this.sanitizeOutput(newReview, ctx)
ctx.body = sanitizedReview
},
}))
详细了解 自定义策略 如何帮助您调整基于 Strapi 的应用程序并根据特定条件限制对某些资源的访问。