自定义服务和控制器

使用我们的 FoodAdvisor 示例了解如何使用自定义服务和控制器进行身份验证

示例手册:自定义服务和控制器

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

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 个操作:

  1. 创建自定义服务以 创建评论
  2. 创建自定义服务以 发送电子邮件
  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 文件的内容替换为以下代码:

src/api/review/services/review.js
jsx
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
  },
}))
  • 在控制器的代码中,可以使用 strapi.service('api::review.review').create(ctx) 调用此服务的 create 方法,其中 ctx 是请求的 context
  • 提供的示例代码不包括错误处理。您应该考虑处理错误,例如当餐厅不存在时。更多信息可以在 错误处理 文档中找到。

自定义服务:向餐厅老板发送电子邮件

💭 上下文:

开箱即用,FoodAdvisor 不提供任何自动电子邮件服务功能。

让我们创建一个 email.js 服务文件来发送电子邮件。我们可以在自定义控制器中使用它,每当在前端网站上创建新评论时通知餐馆老板。

🤗 可选服务

此服务是使用电子邮件插件的高级代码示例,需要了解插件提供程序如何与Strapi配合使用。如果您不需要电子邮件服务来通知餐馆老板,您可以跳过此部分并跳转到自定义控制器示例。

管理面板中的电子邮件单一类型 管理面板中已创建电子邮件单一类型。它包含一个“发件人”字段,用于定义电子邮件插件的发件人地址。

🎯 目标

  • 为“电子邮件”单一类型创建一个新的服务文件,
  • 为该服务声明一个 send() 方法,
  • 使用实体服务 API 获取存储在电子邮件单一类型中的发件人地址,
  • 使用调用服务的 send() 方法时传递的电子邮件详细信息(收件人地址、主题和电子邮件正文)使用电子邮件插件和先前配置的提供程序发送电子邮件。

更多信息请参阅 服务实体服务 API电子邮件插件提供商 文档。

🧑‍💻 代码示例:

要创建这样的服务,请在 FoodAdvisor 项目的 /api 文件夹中,创建一个新的 src/api/email/services/email.js 文件,其中包含以下代码:

src/api/email/services/email.js
jsx
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 文件的内容替换为以下代码示例之一,具体取决于您之前是只创建了 一个自定义服务 还是同时创建了评论创建和 电子邮件通知 的自定义服务:

不带电子邮件服务的自定义控制器

src/api/review/controllers/review.js
jsx
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
  },
}))

具有电子邮件服务的自定义控制器

src/api/review/controllers/review.js
jsx
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 的应用程序并根据特定条件限制对某些资源的访问。