插件扩展

页面摘要: 现有插件可以通过将代码放置在 /src/extensions 中,或使用全局的 register/bootstrap hook 来进行覆盖。本文档中的说明涵盖了重塑插件内容类型 schema 或服务端逻辑 —— 尽管上游更新可能会破坏这些扩展。

Strapi 自带了一些插件,可以从 Marketplace 或作为 npm 包安装。你也可以创建自己的插件(见 插件开发)或扩展现有插件。

WARNING
  • 任何插件更新都可能破坏该插件的扩展。
  • 必要时,Strapi 的新版本会随迁移指南一起发布,但这些指南从不会涵盖插件扩展。如果需要大量自定义,请考虑 fork 一个插件。
  • 目前,插件的 admin 面板部分只能使用 patch-package 进行扩展,但请注意,这样做可能会在你的 Strapi 未来版本中破坏你的插件。

插件扩展代码位于 ./src/extensions 文件夹中(见 项目结构)。一些插件会自动在那里创建可供修改的文件。

extensions 文件夹结构示例

/extensions
  /some-plugin-to-extend
    strapi-server.js|ts
    /content-types
      /some-content-type-to-extend
        schema.json
      /another-content-type-to-extend
        schema.json
  /another-plugin-to-extend
    strapi-server.js|ts

插件可以通过 2 种方式扩展:

扩展插件的内容类型

插件的内容类型可以通过 2 种方式扩展:使用 strapi-server.js|ts 中的编程接口,以及覆盖内容类型 schema。

内容类型的最终 schema 取决于以下加载顺序:

  1. 原始插件的内容类型,
  2. 在 ./src/extensions/plugin-name/content-types/content-type-name/schema.json 中定义的 schema 声明所覆盖的内容类型
  3. 来自 strapi-server.js|ts 的 contentTypes 导出 中的内容类型声明
  4. Strapi 应用的 register() 函数 中的内容类型声明

要覆盖插件的 内容类型:

  1. (可选) 在应用根目录创建 ./src/extensions 文件夹(如果该文件夹尚不存在)。
  2. 创建一个与要扩展的插件同名的子文件夹。
  3. 创建一个 content-types 子文件夹。
  4. 在 content-types 子文件夹内,创建一个与要覆盖的内容类型的 singularName 同名的子文件夹。
  5. 在此 content-types/name-of-content-type 子文件夹内,在 schema.json 文件中定义内容类型的新 schema(见 schema 文档)。
  6. (可选) 对每个要覆盖的内容类型重复步骤 4 和 5。

扩展插件的接口

当 Strapi 应用初始化时,插件、扩展和全局生命周期函数事件按以下顺序发生:

  1. 插件被加载,其接口被暴露。
  2. ./src/extensions 中的文件被加载。
  3. ./src/index.js|ts 中的 register() 和 bootstrap() 函数被调用。

插件的接口可以在第 2 步(即在 ./src/extensions 内)或第 3 步(即在 ./src/index.js|ts 内)进行扩展。

NOTE

如果你的 Strapi 项目基于 TypeScript,请确保 index 文件具有 TypeScript 扩展名(即 src/index.ts),否则它不会被编译。

在 extensions 文件夹内

要使用 ./src/extensions 文件夹扩展插件的服务端接口:

  1. (可选) 在应用根目录创建 ./src/extensions 文件夹(如果该文件夹尚不存在)。
  2. 创建一个与要扩展的插件同名的子文件夹。
  3. 创建一个 strapi-server.js|ts 文件,使用 Server API 扩展插件的后端。
  4. 在此文件中,定义并导出一个函数。该函数接收 plugin 接口作为参数,以便对其进行扩展。

后端扩展示例


module.exports = (plugin) => {
  plugin.controllers.controllerA.find = (ctx) => {};

  plugin.policies[newPolicy] = (ctx) => {};

  plugin.routes['content-api'].routes.push({
    method: 'GET',
    path: '/route-path',
    handler: 'controller.action',
  });

  return plugin;
};
基于工厂的控制器

某些插件控制器(例如 Users & Permissions 插件中的 auth 控制器)使用工厂模式(即它们被导出为函数:({ strapi }) => ({ ... }))。

尝试直接覆盖这些控制器上的操作(例如 plugin.controllers.auth.callback = ...)将不起作用,因为在你的扩展代码运行时,工厂尚未被解析。

要覆盖基于工厂的控制器操作,请包裹工厂函数本身:

module.exports = (plugin) => {
  const originalAuthFactory = plugin.controllers.auth;

  plugin.controllers.auth = ({ strapi }) => {
    // Resolve the original factory to get the controller methods
    const originalAuth = originalAuthFactory({ strapi });

    // Store the original action to avoid recursion
    const originalCallback = originalAuth.callback;

    // Override the action
    originalAuth.callback = async (ctx) => {
      // Custom pre-auth logic
      await originalCallback(ctx);
      // Custom post-auth logic
    };

    return originalAuth;
  };

  return plugin;
};

更多信息,请参阅 自定义 Users & Permissions 插件路由指南。

NOTE

strapi-server.js|ts 文件也是你可以覆盖 image 函数的地方,方法是替换 Upload 插件的 generateFileName() 函数,使其生成自定义图片名称。

自定义文件命名逻辑示例


module.exports = (plugin) => {
  plugin.services['image-manipulation'].generateFileName = (file) => {
    // Example: prefix a timestamp before the generated base name
    return `${Date.now()}_${name}`;
  };

  return plugin;
};

generateFileName() 属于 Upload 插件的 image-manipulation 服务,并期望接收单个 name: string 参数。

WARNING

此自定义依赖于 Upload 插件的内部服务(image-manipulation)。内部扩展点不属于 Strapi 稳定的公共 API,并可能在不同版本之间发生变化。

在 register 和 bootstrap 函数内

要在 ./src/index.js|ts 内扩展插件的接口,请使用整个项目的 bootstrap() 和 register() 函数,并通过 getters 以编程方式访问接口。

在 ./src/index.js|ts 内扩展插件内容类型的示例


module.exports = {
  register({ strapi }) {
    const contentTypeName = strapi.contentType('plugin::my-plugin.content-type-name')  
    contentTypeName.attributes = {
      // Spread previous defined attributes
      ...contentTypeName.attributes,
      // Add new, or override attributes
      'toto': {
        type: 'string',
      }
    }
  },
  bootstrap({ strapi }) {},
};