Skip to content

创建/自定义接口

一个接口大致由 route -> controller -> service(可选)组成

当在后台创建一个实体时,假设实体叫children,会自动生成下列文件:

bash
children
├── content-types
│   └── children
│       └── schema.json
├── controllers
│   └── children.js
├── routes
│   └── children.js
└── services
    └── children.js

这组文件提供了对实体基本的 5 个操作:findfindOnecreateupdatedelete

接下来针对routescontrollersservices分别介绍如何自定义和扩展

routes

children/routes/children.js的默认内容如下:

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

module.exports = createCoreRouter('api::children.children')

配置默认路由

createCoreRouter传第二个option参数,Configuring core routers

  • prefix 路由前缀
  • only 只加载指定的方法,如:only: ['find']
  • except 排除指定的方法
  • config 路由配置项,包括策略、中间件、公共可用性的配置。比如:config: { find: { auth: false, policies: [] }, findOne: {} }

完全自定义路由

children/routes/下可以存在 N 个 js 文件,同名的为默认路由(创建实体就能自动生成),还可以创建其它 js 文件自定义,自定义文件如下:

TIP

js 文件名也有讲究,一般01-开头。这是因为 strapi 会按字母表顺序加载routes/文件夹下的所有 js 文件,为了保证自定义路由的优先级,建议数字开头

javascript
module.exports = {
  routes: [
    {
      method: '',
      path: '',
      handler: '<controller-filename>.<method>',
      config: {},
    },
  ],
}

controllers

children/controllers/children.js的默认内容如下:

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

module.exports = createCoreController('api::children.children')

增加/覆盖 controller 方法

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

module.exports = createCoreController('api::children.children', ({ strapi }) => {
  async method1(ctx) {
    await this.validateQuery(ctx)
    const sanitizedQueryParams = await this.sanitizeQuery(ctx)
    const { results, pagination } = await strapi.service('api::children.children').find(sanitizedQueryParams)
    const sanitizedResults = await this.sanitizeOutput(results, ctx)

    // 在业务逻辑前,对输入进行验证是有必要的,上方是对query进行校验和消毒,也有针对data的方法
    // https://docs.strapi.io/dev-docs/backend-customization/controllers#sanitization-and-validation-in-controllers
    await this.validateInput(ctx.request.body, ctx)
    const sanitizedBody = await this.sanitizeInput(ctx.request.body, ctx)
  },
  // 和默认controller同名的,会覆盖
  async create(ctx) {
    // ctx的属性/方法:https://docs.strapi.io/dev-docs/backend-customization/requests-responses

    // 如果要覆盖默认的,建议通过super直接调用,再加额外的逻辑
    const { data, meta } = await super.find(ctx)
  }
})

也可以创建一个新的 js 文件,写自定义 controller,就和完全自定义路由一样:

javascript
module.exports = ({ strapi }) => ({
  async method1(ctx) {
    // ctx的属性/方法:https://docs.strapi.io/dev-docs/backend-customization/requests-responses
  },
})

services

children/services/children.js的默认内容如下:

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

module.exports = createCoreService('api::children.children')

增加/覆盖 services 方法

和 controller 如出一辙

final

本例以开发微信小程序相关自定义接口为例,集合名就叫miniprogram。因此首先创建一个src/api/miniprogram文件夹。

js
module.exports = {
  routes: [
    {
      method: 'GET',
      path: '/wx-login',
      handler: 'miniprogram.login',
      config: {
        auth: false,
      },
    },
  ],
}
js
// 这里可以直接返回一个对象字面量
// 但是更推荐返回一个函数,函数返回值是对象字面量。因为这样可以访问到strapi这个对象
module.exports = ({ strapi }) => ({
  async login(ctx) {
    // ctx的属性/方法:https://docs.strapi.io/dev-docs/backend-customization/requests-responses
    ctx.body = 'hello world'
  },
})

以上文件配置好,访问http://localhost:1337/api/wx-login即可看到 hello world。

完整的微信小程序登录

以上测试成功之后,将 route 的 method 改为 POST,更新 controller 的代码如下:

javascript
const axios = require('axios')
const APP_ID = ''
const APP_SECRET = ''
module.exports = ({ strapi }) => ({
  async login(ctx) {
    const { code, userInfo } = ctx.request.body || {}
    if (!code) {
      return ctx.badRequest('缺少code')
    }
    const resData = await axios.get(
      `https://api.weixin.qq.com/sns/jscode2session?appid=${APP_ID}&secret=${APP_SECRET}&js_code=${code}&grant_type=authorization_code`
    )
    if (resData.status !== 200)
      return ctx.internalServerError('请求微信服务失败', {
        msg: resData.statusText,
      })
    const { errcode, errmsg, openid } = resData.data
    if (errcode) return ctx.internalServerError(errmsg, resData.data)
    let user = await strapi.documents('plugin::users-permissions.user').findFirst({
      filters: {
        openid: {
          $eq: openid,
        },
      },
    })
    if (!user) {
      // https://github.com/strapi/strapi/blob/v5.6.0/packages/plugins/users-permissions/server/controllers/user.js
      const advanced = await strapi
        .store({ type: 'plugin', name: 'users-permissions', key: 'advanced' })
        .get()
      user = await strapi.documents('plugin::users-permissions.user').create({
        // 通过admin面板,编辑User的Content Type,添加openid字段,右上角点击保存
        // 此时,生成src/extensions/users-permissions/content-types/user/schema.json
        // 由于是面向微信用户的程序,编辑json文件
        // 1. 将username、email字段的required设置成false(不能删除,删除默认就是true)
        // 2. 暂不需要邮箱确认,confirmed默认值改为true
        // 这样创建时我们就不需要填那么多无用字段了
        // 该方式仅限于直接调create方法,在管理后台创建用户依然会对username和email做校验
        data: {
          openid,
          // 默认角色也是必须的,否则生成的用户token请求有权限的接口时会报401错误
          role: (
            await strapi
              .documents('plugin::users-permissions.role')
              .findFirst({ filter: { type: { $eq: advanced.default_role } } })
          ).id,
        },
      })
    }
    ctx.body = {
      token: strapi.service('plugin::users-permissions.jwt').issue({ id: user.id }),
      user: strapi.service('admin::user').sanitizeUser(user),
    }
  },
})