# 插件扩展

> Source: https://strapi.nodejs.cn/cms/plugins-development/plugins-extension

🌐 Plugins extension

现有插件可以通过将代码放置在 `/src/extensions` 或使用全局 `register/bootstrap` 钩子来覆盖。本手册中的说明涵盖了重塑插件内容类型模式或服务器逻辑 —— 尽管上游更新可能会破坏扩展。

Strapi 带有可以从 [Marketplace](/cms/plugins/installing-plugins-via-marketplace#installing-marketplace-plugins-and-providers) 安装的插件，也可以作为 npm 包安装。你也可以创建自己的插件（参见 [插件开发](/cms/plugins-development/developing-plugins)）或扩展现有插件。

🌐 Strapi comes with plugins that can be installed from the [Marketplace](/cms/plugins/installing-plugins-via-marketplace#installing-marketplace-plugins-and-providers) or as npm packages. You can also create your own plugins (see [plugins development](/cms/plugins-development/developing-plugins)) or extend the existing ones.

:::warning

* 任何插件更新都可能会破坏该插件的扩展。
* 当需要时，Strapi 的新版本将会发布迁移指南，但这些指南从不涵盖插件扩展。如果需要进行大量自定义，请考虑分叉插件。
* 目前，插件的管理面板部分只能使用 [patch-package](https://www.npmjs.com/package/patch-package)进行扩展，但请注意，这样做可能会在未来的 Strapi 版本中破坏你的插件。

:::

插件扩展代码位于 `./src/extensions` 文件夹（参见 [项目结构](/cms/project-structure)）。一些插件会自动在该处创建文件，准备进行修改。

🌐 Plugin extensions code is located in the `./src/extensions` folder (see [project structure](/cms/project-structure)). Some plugins automatically create files there, ready to be modified.

<details> 
<summary>扩展文件夹结构示例</summary>

```bash
/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
```
</details>

插件可以通过两种方式扩展：

🌐 Plugins can be extended in 2 ways:

- [扩展插件的内容类型](#extending-a-plugins-content-types)
- [扩展插件的接口](#extending-a-plugins-interface)（例如添加控制器、服务、策略、中间件等）

## 扩展插件的内容类型 {#extending-a-plugins-content-types}

🌐 Extending a plugin's content-types

插件的内容类型可以通过两种方式扩展：使用 `strapi-server.js|ts` 中的编程接口，以及通过重写内容类型的架构。

🌐 A plugin's Content-Types can be extended in 2 ways: using the programmatic interface within `strapi-server.js|ts` and by overriding the content-types schemas.

内容类型的最终架构取决于以下加载顺序：

🌐 The final schema of the content-types depends on the following loading order:

1. 原始插件的内容类型，
2. `./src/extensions/plugin-name/content-types/content-type-name/schema.json` 中定义的 [schema](/cms/backend-customization/models#model-schema) 中声明所覆盖的内容类型
3. [`strapi-server.js|ts` 导出的 `contentTypes`](/cms/plugins-development/server-content-types) 中的内容类型声明
4. Strapi 应用中 [`register()` 函数](/cms/configurations/functions#register) 的内容类型声明

要覆盖插件的 [content-types](/cms/backend-customization/models)：

🌐 To overwrite a plugin's [content-types](/cms/backend-customization/models):

1. _（可选）_ 如果 `./src/extensions` 文件夹尚不存在，请在应用根目录下创建该文件夹。
2. 创建一个与要扩展的插件同名的子文件夹。
3. 创建一个 `content-types` 子文件夹。
4. 在 `content-types` 子文件夹内，创建另一个与要覆盖的内容类型相同 [singularName](/cms/backend-customization/models#model-information) 的子文件夹。
5. 在这个 `content-types/name-of-content-type` 子文件夹中，在一个 `schema.json` 文件中定义该内容类型的新架构（参见 [schema](/cms/backend-customization/models#model-schema) 文档）。
6. _（可选）_ 对每种内容类型重复步骤 4 和 5 以进行覆盖。

## 扩展插件的界面 {#extending-a-plugins-interface}

🌐 Extending a plugin's interface

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

🌐 When a Strapi application is initializing, plugins, extensions and global lifecycle functions events happen in the following order:

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

插件的接口可以在步骤 2（即在 `./src/extensions` 内）或步骤 3（即在 `./src/index.js|ts` 内）进行扩展。

🌐 A plugin's interface can be extended at step 2 (i.e. within `./src/extensions`) or step 3 (i.e. inside `./src/index.js|ts`).

:::note

如果你的 Strapi 项目是基于 TypeScript 的，请确保 `index` 文件具有 TypeScript 扩展名（即 `src/index.ts`），否则它将无法被编译。

🌐 If your Strapi project is TypeScript-based, please ensure that the `index` file has a TypeScript extension (i.e., `src/index.ts`) otherwise it will not be compiled.

:::

### 在扩展文件夹内 {#within-the-extensions-folder}

🌐 Within the extensions folder

要使用 `./src/extensions` 文件夹扩展插件的服务器接口：

🌐 To extend a plugin's server interface using the `./src/extensions` folder:

1. _（可选）_ 如果 `./src/extensions` 文件夹尚不存在，请在应用根目录下创建该文件夹。
2. 创建一个与要扩展的插件同名的子文件夹。
3. 创建一个 `strapi-server.js|ts` 文件，以使用 [Server API](/cms/plugins-development/server-api) 扩展插件的后端。
4. 在此文件中，定义并导出一个函数。该函数接收 `plugin` 接口作为参数，以便可以扩展它。

<details>
<summary>后端扩展示例</summary>

```js title="./src/extensions/some-plugin-to-extend/strapi-server.js|ts"

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;
};
```
</details>

:::note Factory-based controllers

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

🌐 Some plugin controllers, such as the `auth` controller in the Users & Permissions plugin, use a factory pattern (i.e. they are exported as functions: `({ strapi }) => ({ ... })`). 

直接尝试覆盖这些控制器上的操作（例如，`plugin.controllers.auth.callback = ...`）是行不通的，因为当你的扩展代码运行时，工厂尚未被解析。 

🌐 Trying to override actions on these controllers directly (e.g., `plugin.controllers.auth.callback = ...`) will not work because the factory has not yet been resolved when your extension code runs. 

要覆盖基于工厂的控制器操作，请直接封装工厂函数本身：

🌐 To override factory-based controller actions, wrap the factory function itself:

```js title="./src/extensions/users-permissions/strapi-server.js"
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;
};
```
For more information, see the [Customizing Users & Permissions plugin routes guide](/cms/backend-customization/guides/customizing-users-permissions-plugin-routes#override-auth-route).

:::

:::note

`strapi-server.js|ts` 文件也是你可以通过替换上传插件的 `generateFileName()` 函数来覆盖图片功能的地方，从而生成自定义图片名称。

🌐 The `strapi-server.js|ts` file is also where you can override the image function, by replacing the Upload plugin's `generateFileName()` function so that it generates custom image names.

<details>
<summary>自定义文件命名逻辑示例</summary>

```js title="./src/extensions/upload/strapi-server.js|ts"

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

  return plugin;
};

```
</details>

:::

`generateFileName()` 属于上传插件的 `image-manipulation` 服务，并期望一个单一的 `name: string` 参数。

:::caution

此自定义依赖于内部上传插件服务（`image-manipulation`）。内部扩展点不是 Strapi 稳定的公共 API 的一部分，可能会在不同版本之间发生变化。

🌐 This customization relies on an internal Upload plugin service (`image-manipulation`). Internal extension points are not part of Strapi's stable public API and can change between versions.

:::

### 在注册和引导函数中 {#within-the-register-and-bootstrap-functions}

🌐 Within the register and bootstrap functions

要在 `./src/index.js|ts` 中扩展插件的接口，请使用整个项目的 `bootstrap()` 和 `register()` [函数](/cms/configurations/functions)，并通过 [getter](/cms/plugins-development/server-getters-usage) 以编程方式访问接口。

🌐 To extend a plugin's interface within `./src/index.js|ts`, use the `bootstrap()` and `register()` [functions](/cms/configurations/functions) of the whole project, and access the interface programmatically with [getters](/cms/plugins-development/server-getters-usage).

<details>
<summary>在 ./src/index.js|ts 中扩展插件内容类型的示例</summary>

```js title="./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 }) {},
};
```
</details>
