# 云账户账单详情 Source: https://strapi.nodejs.cn/cloud/account/account-billing # 云账户计费与发票 {#cloud-account-billing--invoices} 🌐 Cloud account billing & invoices 账单详情和发票在个人资料页面管理,可以在此更新付款方式并查看发票历史记录。 通过 *个人资料* 页面,可通过点击界面右上角的头像,然后点击 **个人资料** 访问,你可以进入 [ *账单*](#account-billing) 和 [ *发票*](#account-invoices) 标签页。 ## 账户计费 {#account-billing} 🌐 Account billing *账单*标签显示并允许你修改账户设置的账单详细信息和付款方式。 *账单*标签的*支付方式*部分允许你管理可用于 Strapi Cloud 项目的信用卡。*账单详情*部分需要填写,至少要填写必填字段,因为这些信息将作为与你的账户相关的所有 Strapi Cloud 项目的默认账单详情。 ### 添加新的信用卡 {#adding-a-new-credit-card} 🌐 Adding a new credit card 1. 在 *账单*选项卡的*付款方式*部分,点击**添加银行卡**按钮。 2. 填写以下字段: | 字段名称 | 描述 | | --- | --- | | 卡号 | 输入要添加为支付方式的信用卡号码。 | | 到期日 | 输入信用卡的到期日期。 | | CVC | 输入显示在信用卡背面的三位数代码。 | 3. 点击 **保存** 按钮。 :::tip 第一个被添加为账户付款方式的信用卡默认将是主要信用卡。然而,可以通过点击 图标,然后选择**设为主要**来将另一张信用卡设为主要信用卡。 ::: ### 删除信用卡 {#deleting-a-credit-card} 🌐 Deleting a credit card 要从账户的支付方式列表中移除信用卡: 🌐 To remove a credit card from the list of payment methods for the account: 1. 点击你希望删除的信用卡的 图标。 2. 点击 **移除卡片**。卡片会立即被删除。 :::note 你无法删除主要信用卡,因为至少必须有一张信用卡可用作支付方式,而主要信用卡默认就是那一张。如果你希望删除的信用卡当前是主要信用卡,则必须先将另一张信用卡设为主要信用卡,然后再删除它。 🌐 You cannot delete the primary card as at least one credit card must be available as payment method, and the primary card is by default that one. If the credit card you wish to delete is currently the primary card, you must first define another credit card as primary, then delete it. ::: ## 账户发票 {#account-invoices} 🌐 Account invoices *发票* 选项卡显示你所有 Strapi Cloud 项目的完整发票列表。 发票可以具有以下任何状态: 🌐 Invoices can have any of the following statuses: - 已付款:付款已完成,发票已可用,无需其他操作。 - 待付款:发票尚未完成或验证 - 付款到期:付款未成功,需要修复 - 未支付:付款失败,且不会自动重试 - 作废:发票已被取消。 :::tip 点击 ![下载图标](/img/assets/icons/download.svg) 图标下载发票。 🌐 Click the ![download icon](/img/assets/icons/download.svg) icon to download an invoice. ::: :::strapi Invoices are also available per project. 在任何项目的 *设置 > 账单与发票* 选项卡中,你将只找到该项目的发票。你可以随时查看 [专用文档](/cloud/projects/settings#billing--invoices)。 🌐 In the *Settings > Billing & Invoices* tab of any project, you will find the invoices for that project only. Feel free to check the [dedicated documentation](/cloud/projects/settings#billing--invoices). ::: # 云配置文件设置 Source: https://strapi.nodejs.cn/cloud/account/account-settings # 云配置文件设置 {#cloud-profile-settings} 🌐 Cloud profile settings 个人资料页面的设置包括账户详情、已连接的账户以及账户删除选项。 *个人资料* 页面使你能够管理你的账户详细信息和偏好设置。你可以通过点击界面右上角的个人头像,然后选择 **个人资料** 来访问该页面。 🌐 The *Profile* page enables you to manage your account details and preferences. It is accessible by clicking on your profile picture, on the top right hand corner of the interface, and **Profile**. *个人资料*界面中有3个可用选项卡: [*常规*](#general)、 *账单* 和 发票(后两个在本指南的[账户账单详情](/cloud/account/account-billing)部分有说明)。 ## 一般 {#general} 🌐 General “ *常规*”选项卡允许你编辑账户资料的以下详细信息: - 详情:查看与你的账户关联的名称。 - 已连接的账户:用于管理与你的 Strapi Cloud 账户关联的 Google、GitHub、GitLab 和电子邮件账户(请参阅 [管理已连接的账户](#managing-connected-accounts))。 - 删除账户:永久删除你的 Strapi Cloud 账户(参见 [删除 Strapi Cloud 账户](#deleting-strapi-cloud-account))。 ### 管理已连接的账户 {#managing-connected-accounts} 🌐 Managing connected accounts 你可以将 Google、GitLab、GitHub 和电子邮件账户连接到你的 Strapi Cloud 账户。_已连接账户_ 部分列出了当前已连接到你的 Strapi Cloud 账户的账户。在此处,如果尚未连接,你也可以连接新的 Google、GitLab、GitHub 和电子邮件账户。 🌐 You can connect a Google, GitLab, GitHub and email account to your Strapi Cloud account. The _Connected accounts_ section lists accounts that are currently connected to your Strapi Cloud account. From there you can also connect a new Google, GitLab, GitHub and email account if one is not already connected. 要将新的 Google、GitLab、GitHub 或电子邮件账户连接到你的 Strapi Cloud 账户,请点击 **连接账户** 按钮,并按照相应网站上的下一步操作进行操作。 🌐 To connect a new Google, GitLab, GitHub or email account to your Strapi Cloud account, click on the **Connect account** button and follow the next steps on the corresponding website. 你还可以点击已连接账户的三点按钮,然后点击“在……上管理”按钮,直接在相应网站上管理你的 GitHub、GitLab 或 Google 账户。 🌐 You can also click on the three dots button of a connected account and click on the "Manage on" button to manage your GitHub, GitLab or Google account directly on the corresponding website. ### 删除 Strapi Cloud 账户 {#deleting-strapi-cloud-account} 🌐 Deleting Strapi Cloud account 你可以删除你的 Strapi Cloud 账户,但这是永久且不可逆的。所有相关的项目及其数据也将被删除,项目的订阅将自动取消。 🌐 You can delete your Strapi Cloud account, but it will be permanent and irreversible. All associated projects and their data will be deleted as well and the subscriptions for the projects will automatically be canceled. 1. 在 *常规*选项卡的*删除账户*部分,点击**删除账户**按钮。 2. 在对话框中,在文本框里输入 `DELETE`。 3. 通过点击 **删除** 按钮确认删除你的账户。 # 云数据库配置 Source: https://strapi.nodejs.cn/cloud/advanced/database # 云数据库配置 {#cloud-database-configuration} 🌐 Cloud database configuration 默认的 PostgreSQL 可以通过调整配置和环境变量来替换为任何支持的 SQL 数据库。 Strapi Cloud 默认提供预配置的 PostgreSQL 数据库。但是,如果需要,你也可以将其配置为使用外部 SQL 数据库。 🌐 Strapi Cloud provides a pre-configured PostgreSQL database by default. However, you can also configure it to utilize an external SQL database, if needed. :::prerequisites - 一个在 `v4.8.2+` 上运行的本地 Strapi 项目。 - 外部数据库的凭证。 - 如果使用现有数据库,模式必须与 Strapi 项目的模式匹配。 ::: :::caution 虽然可以在 Strapi Cloud 中使用外部数据库,但在使用时应考虑以下事项: 🌐 While it's possible to use an external database with Strapi Cloud, you should do it while keeping in mind the following considerations: - Strapi Cloud 已经提供了一个为 Strapi 优化的托管数据库。 - 使用外部数据库可能导致意外行为和/或性能问题(例如,网络延迟可能影响性能)。出于性能原因,建议将外部数据库托管在与你的 Strapi Cloud 项目所在地区接近的位置。你可以在项目设置中找到你的 Strapi Cloud 项目托管位置(见 [项目设置 > 常规 > 选择的地区](/cloud/projects/settings#general))。 - Strapi 无法为与 Strapi Cloud 一起使用的外部数据库提供安全性或支持。 ::: :::warning 任何添加到你的项目中以 `DATABASE_` 开头的环境变量都将导致 Strapi Cloud 假定你将使用外部数据库,并且所有 Strapi Cloud 特定的数据库变量都不会被注入! 🌐 Any environment variable added to your project that starts with `DATABASE_` will cause Strapi Cloud to assume that you will be using an external database and all Strapi Cloud specific database variables will not be injected! ::: ## 配置 {#configuration} 🌐 Configuration 项目 `/config/database.js` 或 `/config/database.ts` 文件必须与 [数据库配置中的环境变量](https://strapi.nodejs.cn/cms/configurations/database#environment-variables-in-database-configurations) 部分中的配置匹配。 🌐 The project `/config/database.js` or `/config/database.ts` file must match the configuration found in the [environment variables in database configurations](https://strapi.nodejs.cn/cms/configurations/database#environment-variables-in-database-configurations) section. 在推送更改之前,将环境变量添加到 Strapi Cloud 项目中: 🌐 Before pushing changes, add environment variables to the Strapi Cloud project: 1. 登录 Strapi Cloud,然后在“项目”页面上点击相应的项目。 2. 点击 **设置** 标签,然后在左侧菜单中选择 **变量**。 3. 添加以下环境变量: | 变量 | 值 | 详情 | | -------------------------------- | ---------------- | ---------- | | `DATABASE_CLIENT` | your_db | 应该是 `mysql`、`postgres` 或 `sqlite` 之一。 | | `DATABASE_HOST` | your_db_host | 数据库主机的 URL 或 IP 地址 | | `DATABASE_PORT` | your_db_port | 用于访问数据库的端口 | | `DATABASE_NAME` | your_db_name | 数据库名称 | | `DATABASE_USERNAME` | your_db_username | 用于访问数据库的用户名 | | `DATABASE_PASSWORD` | your_db_password | 与该用户名关联的密码 | | `DATABASE_SSL_REJECT_UNAUTHORIZED` | false | 是否应拒绝未经授权的连接 | | `DATABASE_SCHEMA` | public | - | 4. 点击 **保存**。 :::caution 为了确保顺利部署,建议不要更改环境变量的名称。 🌐 To ensure a smooth deployment, it is recommended to not change the names of the environment variables. ::: ## 部署 {#deployment} 🌐 Deployment 要部署项目并使用外部数据库,请推送之前的更改。这将触发 Strapi Cloud 项目的重新构建和新的部署。 🌐 To deploy the project and utilize the external database, push the changes from earlier. This will trigger a rebuild and new deployment of the Strapi Cloud project. 一旦应用完成构建,项目将使用外部数据库。 🌐 Once the application finishes building, the project will use the external database. ## 恢复到默认数据库 {#reverting-to-the-default-database} 🌐 Reverting to the default database 要恢复到默认数据库,请从 Strapi Cloud 项目仪表板中删除之前添加的与外部数据库相关的环境变量,然后保存。要使更改生效,你必须重新部署 Strapi Cloud 项目。 🌐 To revert back to the default database, remove the previously added environment variables related to the external database from the Strapi Cloud project dashboard, and save. For the changes to take effect, you must redeploy the Strapi Cloud project. # 云邮件提供商 Source: https://strapi.nodejs.cn/cloud/advanced/email # Strapi Cloud 的电子邮件提供商配置 {#email-providers-configuration-for-strapi-cloud} 🌐 Email Providers configuration for Strapi Cloud 第三方电子邮件服务通过插件和环境变量集成,以替代默认发件人。 Strapi Cloud 开箱即用自带一个基础的电子邮件提供商。不过,如果需要,它也可以配置为使用其他电子邮件提供商。 🌐 Strapi Cloud comes with a basic email provider out of the box. However, it can also be configured to utilize another email provider, if needed. :::caution 请注意,Strapi 无法为第三方电子邮件提供商提供支持。 🌐 Please be advised that Strapi is unable to provide support for third-party email providers. ::: :::prerequisites - 一个在 `v4.8.2+` 上运行的本地 Strapi 项目。 - 另一个电子邮件提供商的凭据(请参见 [Strapi Market](https://market.strapi.io/providers))。 ::: ## 配置 {#configuration} 🌐 Configuration 配置另一个电子邮件提供商以在 Strapi Cloud 中使用需要三个步骤: 🌐 Configuring another email provider for use with Strapi Cloud requires 3 steps: 1. 在本地 Strapi 项目中安装提供程序插件。 2. 在本地 Strapi 项目中配置提供者。 3. 将环境变量添加到 Strapi Cloud 项目中。 ### 安装提供程序插件 {#install-the-provider-plugin} 🌐 Install the Provider Plugin 使用 `npm` 或 `yarn`,按照对应供应商在 [Marketplace](https://market.strapi.io/providers) 中的条目说明,将提供程序插件作为包依赖安装到本地 Strapi 项目中。 ### 配置提供者 {#configure-the-provider} 🌐 Configure the Provider 在你的 Strapi 项目中,创建一个 `/config/env/production/plugins.js` 或 `/config/env/production/plugins.ts` 文件,并包含以下内容: 🌐 In your Strapi project, create a `/config/env/production/plugins.js` or `/config/env/production/plugins.ts` file with the following content: ```js title=/config/env/production/plugins.js module.exports = ({ env }) => ({ // … some unrelated plugins configuration options // highlight-start email: { config: { // … provider-specific upload configuration options go here } // highlight-end // … some other unrelated plugins configuration options } }); ``` ```ts title=/config/env/production/plugins.ts // … some unrelated plugins configuration options // highlight-start email: { config: { // … provider-specific upload configuration options go here } // highlight-end // … some other unrelated plugins configuration options } }); ``` :::caution 文件结构必须与上述路径完全匹配,否则配置将不会应用到 Strapi 云。 🌐 The file structure must match the above path exactly, or the configuration will not be applied to Strapi Cloud. ::: 每个提供商将有不同的配置设置可用。请查看 [Marketplace](https://market.strapi.io/providers)中该提供商的相应条目。 **示例:** ```js title=/config/env/production/plugins.js module.exports = ({ env }) => ({ // ... email: { config: { provider: 'sendgrid', providerOptions: { apiKey: env('SENDGRID_API_KEY'), }, settings: { defaultFrom: 'myemail@protonmail.com', defaultReplyTo: 'myemail@protonmail.com', }, }, }, // ... }); ``` ```js title=/config/env/production/plugins.js module.exports = ({ env }) => ({ // ... email: { config: { provider: 'amazon-ses', providerOptions: { key: env('AWS_SES_KEY'), secret: env('AWS_SES_SECRET'), amazon: 'https://email.us-east-1.amazonaws.com', }, settings: { defaultFrom: 'myemail@protonmail.com', defaultReplyTo: 'myemail@protonmail.com', }, }, }, // ... }); ``` ```js title=/config/env/production/plugins.js module.exports = ({ env }) => ({ // ... email: { config: { provider: 'mailgun', providerOptions: { key: env('MAILGUN_API_KEY'), // Required domain: env('MAILGUN_DOMAIN'), // Required url: env('MAILGUN_URL', 'https://api.mailgun.net'), //Optional. If domain region is Europe use 'https://api.eu.mailgun.net' }, settings: { defaultFrom: 'myemail@protonmail.com', defaultReplyTo: 'myemail@protonmail.com', }, }, }, // ... }); ``` ```ts title=/config/env/production/plugins.ts // ... email: { config: { provider: 'sendgrid', providerOptions: { apiKey: env('SENDGRID_API_KEY'), }, settings: { defaultFrom: 'myemail@protonmail.com', defaultReplyTo: 'myemail@protonmail.com', }, }, }, // ... }); ``` ```ts title=/config/env/production/plugins.ts // ... email: { config: { provider: 'amazon-ses', providerOptions: { key: env('AWS_SES_KEY'), secret: env('AWS_SES_SECRET'), amazon: 'https://email.us-east-1.amazonaws.com', }, settings: { defaultFrom: 'myemail@protonmail.com', defaultReplyTo: 'myemail@protonmail.com', }, }, }, // ... }); ``` ```ts title=/config/env/production/plugins.ts // ... email: { config: { provider: 'mailgun', providerOptions: { key: env('MAILGUN_API_KEY'), // Required domain: env('MAILGUN_DOMAIN'), // Required url: env('MAILGUN_URL', 'https://api.mailgun.net'), //Optional. If domain region is Europe use 'https://api.eu.mailgun.net' }, settings: { defaultFrom: 'myemail@protonmail.com', defaultReplyTo: 'myemail@protonmail.com', }, }, }, // ... }); ``` :::tip 在将上述更改推送到 GitHub 之前,向 Strapi Cloud 项目添加环境变量,以防在更改完成之前触发项目的重建和新部署。 🌐 Before pushing the above changes to GitHub, add environment variables to the Strapi Cloud project to prevent triggering a rebuild and new deployment of the project before the changes are complete. ::: ### Strapi 云配置 {#strapi-cloud-configuration} 🌐 Strapi Cloud Configuration 1. 登录 Strapi Cloud,然后在“项目”页面上点击相应的项目。 2. 点击 **设置** 标签,然后在左侧菜单中选择 **变量**。 3. 添加电子邮件提供商特定的所需环境变量。 4. 点击 **保存**。 **示例:** | 变量 | 值 | |--------------------|-----------------------| | `SENDGRID_API_KEY` | your_sendgrid_api_key | | 变量 | 值 | |------------------|---------------------| | `AWS_SES_KEY` | your_aws_ses_key | | `AWS_SES_SECRET` | your_aws_ses_secret | | 变量 | 值 | |-------------------|----------------------| | `MAILGUN_API_KEY` | your_mailgun_api_key | | `MAILGUN_DOMAIN` | your_mailgun_domain | | `MAILGUN_URL` | your_mailgun_url | ## 部署 {#deployment} 🌐 Deployment 要部署项目并使用其他方的电子邮件提供商,请推送之前的更改。这将触发 Strapi Cloud 项目的重建和新部署。 🌐 To deploy the project and utilize another party email provider, push the changes from earlier. This will trigger a rebuild and new deployment of the Strapi Cloud project. 一旦应用构建完成,项目将使用新的电子邮件提供商。 🌐 Once the application finishes building, the project will use the new email provider. :::strapi Custom Provider 如果你想创建自定义电子邮件提供商,请参阅 CMS 文档中的 [电子邮件提供商](/cms/features/email#providers) 文档。 🌐 If you want to create a custom email provider, please refer to the [Email providers](/cms/features/email#providers) documentation in the CMS Documentation. ::: # Strapi Cloud 的中间件配置 Source: https://strapi.nodejs.cn/cloud/advanced/middlewares # Strapi Cloud 的中间件配置 {#middleware-configuration-for-strapi-cloud} 🌐 Middleware Configuration for Strapi Cloud 在 Strapi Cloud 上,中间件自定义必须放在 `config/env/production/middlewares` 中。对全局配置文件的更改在部署时会被覆盖。 :::prerequisites - 一个本地的 Strapi 项目。 - 一个 Strapi Cloud 项目(请参阅 [入门指南](/cloud/getting-started/deployment))。 ::: 在 Strapi Cloud 上,`NODE_ENV` 始终设置为 `production`。平台在部署时会应用其自身的生产级中间件配置。对全局 `config/middlewares` 文件的任何更改都会被覆盖,并且不会生效。有关可用的中间件选项,请参见 [中间件配置](/cms/configurations/middlewares)。 🌐 On Strapi Cloud, `NODE_ENV` is always set to `production`. The platform applies its own production-level middleware configuration on deploy. Any changes to the global `config/middlewares` file are overwritten and will not take effect. For available middleware options, see [Middlewares configuration](/cms/configurations/middlewares). 要在 Strapi Cloud 上应用自定义中间件配置,请将更改放置在: 🌐 To apply custom middleware configuration on Strapi Cloud, place your changes in: ``` config/env/production/middlewares.js ``` ``` config/env/production/middlewares.ts ``` :::caution `config/env/production/middlewares` 文件**完全替换**全局中间件数组。你的文件必须包含完整的列表: 🌐 The `config/env/production/middlewares` file **fully replaces** the global middleware array. Your file must include the complete list: - `strapi::errors` - `strapi::security` - `strapi::cors` - `strapi::poweredBy` - `strapi::logger` - `strapi::query` - `strapi::body` - `strapi::session` - `strapi::favicon` - `strapi::public` CSP 和 CORS 的自定义可以在同一个文件中结合使用。 🌐 Both CSP and CORS customizations can be combined in the same file. ::: :::note - 你可以按原样保留现有的 `config/middlewares` 文件,因为它不会引起冲突。在 Strapi Cloud 上,特定于生产的文件优先。 - Strapi Cloud 上的上传大小限制是在基础设施层面强制执行的,不能通过 `strapi::body` 配置覆盖。 - 有关每个计划的值以及基于内存的图片上传推荐,请参阅 [Strapi Cloud 上传大小限制](/cloud/advanced/upload-size-limits)。有关外部存储选项,请参阅 [上传提供程序配置](/cloud/advanced/upload)。 ::: ## 自定义内容安全策略 (CSP) {#custom-content-security-policy-csp} 🌐 Custom Content Security Policy (CSP) 如果你使用外部上传提供商,请在 CSP 指令中允许其域名。否则,Strapi 管理面板将阻止来自这些来源的图片和媒体。 🌐 If you use an external upload provider, allow its domain in the CSP directives. Without this, the Strapi Admin panel will block images and media from those sources. 创建或更新 `config/env/production/middlewares`: 🌐 Create or update `config/env/production/middlewares`: ```js title="config/env/production/middlewares.js" module.exports = [ 'strapi::errors', { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'connect-src': ["'self'", 'https:'], 'img-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'your-custom-domain.com', // replace with your provider domain ], 'media-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'your-custom-domain.com', // replace with your provider domain ], upgradeInsecureRequests: null, }, }, }, }, 'strapi::cors', 'strapi::poweredBy', 'strapi::logger', 'strapi::query', 'strapi::body', 'strapi::session', 'strapi::favicon', 'strapi::public', ]; ``` ```ts title="config/env/production/middlewares.ts" 'strapi::errors', { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'connect-src': ["'self'", 'https:'], 'img-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'your-custom-domain.com', // replace with your provider domain ], 'media-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'your-custom-domain.com', // replace with your provider domain ], upgradeInsecureRequests: null, }, }, }, }, 'strapi::cors', 'strapi::poweredBy', 'strapi::logger', 'strapi::query', 'strapi::body', 'strapi::session', 'strapi::favicon', 'strapi::public', ]; ``` :::tip 有关上传提供商及其所需域的完整列表,请参见 [Strapi Market](https://market.strapi.io/providers)。 ::: ## 自定义 CORS 头 {#custom-cors-headers} 🌐 Custom CORS headers 如果你的前端发送自定义请求头(例如用于授权流程),你需要在 CORS 配置中显式允许它们。在全局 `config/middlewares` 文件中设置是无法在 Strapi Cloud 上生效的。应将其放在 `config/env/production/middlewares` 中。 🌐 If your frontend sends custom request headers (e.g. for authorization flows), you need to explicitly allow them in the CORS configuration. Placing this in the global `config/middlewares` file will not work on Strapi Cloud. Place it in `config/env/production/middlewares` instead. ```js title="config/env/production/middlewares.js" module.exports = ({ env }) => [ 'strapi::errors', 'strapi::security', { name: 'strapi::cors', config: { enabled: true, origin: [env('CLIENT_URL')], headers: [ 'Content-Type', 'Authorization', 'Origin', 'Accept', 'X-Requested-With', 'your-custom-header', // add any custom headers your frontend sends ], }, }, 'strapi::poweredBy', 'strapi::logger', 'strapi::query', 'strapi::body', 'strapi::session', 'strapi::favicon', 'strapi::public', ]; ``` ```ts title="config/env/production/middlewares.ts" 'strapi::errors', 'strapi::security', { name: 'strapi::cors', config: { enabled: true, origin: [env('CLIENT_URL')], headers: [ 'Content-Type', 'Authorization', 'Origin', 'Accept', 'X-Requested-With', 'your-custom-header', // add any custom headers your frontend sends ], }, }, 'strapi::poweredBy', 'strapi::logger', 'strapi::query', 'strapi::body', 'strapi::session', 'strapi::favicon', 'strapi::public', ]; ``` # 为 Strapi Cloud 上传提供商配置 Source: https://strapi.nodejs.cn/cloud/advanced/upload # 为 Strapi Cloud 上传提供商配置 {#upload-provider-configuration-for-strapi-cloud} 🌐 Upload Provider Configuration for Strapi Cloud 像 S3 或 Cloudinary 这样的外部存储需要插件设置、安全中间件和云变量。 Strapi Cloud 开箱即用带有本地上传提供者。不过,如果需要,它也可以配置为使用第三方上传提供者。 🌐 Strapi Cloud comes with a local upload provider out of the box. However, it can also be configured to use a third-party upload provider, if needed. :::note 有关适用于上传的文件大小和基于内存的限制,请参见 [Strapi Cloud 上传大小限制](/cloud/advanced/upload-size-limits)。 🌐 For the file size and memory-based limits that apply to uploads, see [Upload size limits for Strapi Cloud](/cloud/advanced/upload-size-limits). ::: ## 配置第三方上传提供商 {#configuring-a-third-party-upload-provider} 🌐 Configuring a third-party upload provider :::caution 请注意,Strapi 无法为第三方上传提供商提供支持。 🌐 Please be advised that Strapi is unable to provide support for third-party upload providers. ::: :::prerequisites - 一个在 `v4.8.2+` 上运行的本地 Strapi 项目。 - 第三方上传提供者的凭证(参见 [Strapi Market](https://market.strapi.io/providers))。 ::: 为在 Strapi Cloud 中使用第三方上传提供商进行配置,需要以下四个配置步骤,然后进行部署: 🌐 Configuring a third-party upload provider for use with Strapi Cloud requires the following 4 configuration steps, followed by a deployment: 1. 在本地 Strapi 项目中安装提供程序插件。 2. 在本地 Strapi 项目中配置提供者。 3. 在本地 Strapi 项目中配置安全中间件。 4. 将环境变量添加到 Strapi Cloud 项目中。 ### 安装提供程序插件 {#install-the-provider-plugin} 🌐 Install the provider plugin 使用 `npm` 或 `yarn`,按照对应供应商在 [Marketplace](https://market.strapi.io/providers) 中的条目说明,将提供程序插件作为包依赖安装到本地 Strapi 项目中。 ### 配置提供程序 {#configure-the-provider} 🌐 Configure the provider 要在你的 Strapi 项目中配置第三方上传提供商,请通过以下方式创建或编辑生产环境 `/config/env/production/plugins.js|ts` 的插件配置文件,添加上传配置选项如下: 🌐 To configure a third-party upload provider in your Strapi project, create or edit the plugins configuration file for your production environment `/config/env/production/plugins.js|ts` by adding upload configuration options as follows: ```js title=/config/env/production/plugins.js module.exports = ({ env }) => ({ // … some unrelated plugins configuration options // highlight-start upload: { config: { // … provider-specific upload configuration options go here } // highlight-end // … some other unrelated plugins configuration options } }); ``` ```ts title=/config/env/production/plugins.ts // … some unrelated plugins configuration options // highlight-start upload: { config: { // … provider-specific upload configuration options go here } // highlight-end // … some other unrelated plugins configuration options } }); ``` :::caution 文件结构必须与上述路径完全匹配,否则配置将不会应用到 Strapi 云。 🌐 The file structure must match the above path exactly, or the configuration will not be applied to Strapi Cloud. ::: 每个提供商将有不同的配置设置可用。请查看 [Marketplace](https://market.strapi.io/providers)中该提供商的相应条目。 **示例:** ```js title=/config/env/production/plugins.js module.exports = ({ env }) => ({ // ... upload: { config: { provider: 'cloudinary', providerOptions: { cloud_name: env('CLOUDINARY_NAME'), api_key: env('CLOUDINARY_KEY'), api_secret: env('CLOUDINARY_SECRET'), }, actionOptions: { upload: {}, uploadStream: {}, delete: {}, }, }, }, // ... }); ``` :::tip 有关完整的 S3 提供程序配置详情(凭证格式、扩展选项、兼容 S3 的服务),请参阅 CMS 文档中的 [Amazon S3 提供程序](/cms/configurations/media-library-providers/amazon-s3) 页面。 🌐 For full S3 provider configuration details (credential formats, extended options, S3-compatible services), see the [Amazon S3 provider](/cms/configurations/media-library-providers/amazon-s3) page in the CMS documentation. ::: ```js title=/config/env/production/plugins.js module.exports = ({ env }) => ({ // ... upload: { config: { provider: 'aws-s3', providerOptions: { baseUrl: env('CDN_URL'), rootPath: env('CDN_ROOT_PATH'), s3Options: { credentials: { accessKeyId: env('AWS_ACCESS_KEY_ID'), secretAccessKey: env('AWS_ACCESS_SECRET'), }, region: env('AWS_REGION'), params: { ACL: env('AWS_ACL', 'public-read'), signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 15 * 60), Bucket: env('AWS_BUCKET'), }, }, }, actionOptions: { upload: {}, uploadStream: {}, delete: {}, }, }, }, // ... }); ``` ```ts title=/config/env/production/plugins.ts // ... upload: { config: { provider: 'cloudinary', providerOptions: { cloud_name: env('CLOUDINARY_NAME'), api_key: env('CLOUDINARY_KEY'), api_secret: env('CLOUDINARY_SECRET'), }, actionOptions: { upload: {}, uploadStream: {}, delete: {}, }, }, }, // ... }); ``` ```ts title=/config/env/production/plugins.ts // ... upload: { config: { provider: 'aws-s3', providerOptions: { baseUrl: env('CDN_URL'), rootPath: env('CDN_ROOT_PATH'), s3Options: { credentials: { accessKeyId: env('AWS_ACCESS_KEY_ID'), secretAccessKey: env('AWS_ACCESS_SECRET'), }, region: env('AWS_REGION'), params: { ACL: env('AWS_ACL', 'public-read'), signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 15 * 60), Bucket: env('AWS_BUCKET'), }, }, }, actionOptions: { upload: {}, uploadStream: {}, delete: {}, }, }, }, // ... }); ``` ### 配置安全中间件 {#configure-the-security-middleware} 🌐 Configure the security middleware 由于 Strapi 安全中间件的默认设置,你需要修改 `contentSecurityPolicy` 设置才能在媒体库中正确查看缩略图预览。 🌐 Due to the default settings in the Strapi security middleware you will need to modify the `contentSecurityPolicy` settings to properly see thumbnail previews in the Media Library. :::caution 在 Strapi Cloud 上,`NODE_ENV` 始终设置为 `production`。对全局 `config/middlewares.ts` 文件的更改会在每次部署时被覆盖,因此不会生效。请将你的安全中间件自定义放在 `config/env/production/middlewares.ts` 中。详情请参见 [Strapi Cloud 的中间件配置](/cloud/advanced/middlewares)。 🌐 On Strapi Cloud, `NODE_ENV` is always set to `production`. Changes to the global `config/middlewares.ts` file are overwritten on each deploy and will not take effect. Place your Security Middleware customizations in `config/env/production/middlewares.ts` instead. See [Middleware Configuration for Strapi Cloud](/cloud/advanced/middlewares) for details. ::: 在你的 Strapi 项目中执行此操作: 🌐 To do this in your Strapi project: 1. 在你的 Strapi 项目中,导航到 `/config/env/production/middlewares.js` 或 `/config/env/production/middlewares.ts`。 2. 用上传提供者提供的对象替换默认的 `strapi::security` 字符串。 **示例:** ```js title=/config/env/production/middlewares.js module.exports = [ // ... { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'connect-src': ["'self'", 'https:'], 'img-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'res.cloudinary.com' ], 'media-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'res.cloudinary.com', ], upgradeInsecureRequests: null, }, }, }, }, // ... ]; ``` ```js title=/config/env/production/middlewares.js module.exports = [ // ... { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'connect-src': ["'self'", 'https:'], 'img-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'yourBucketName.s3.yourRegion.amazonaws.com', ], 'media-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'yourBucketName.s3.yourRegion.amazonaws.com', ], upgradeInsecureRequests: null, }, }, }, }, // ... ]; ``` ```ts title=/config/env/production/middlewares.ts // ... { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'connect-src': ["'self'", 'https:'], 'img-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'res.cloudinary.com' ], 'media-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'res.cloudinary.com', ], upgradeInsecureRequests: null, }, }, }, }, // ... ]; ``` ```ts title=/config/env/production/middlewares.ts // ... { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'connect-src': ["'self'", 'https:'], 'img-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'yourBucketName.s3.yourRegion.amazonaws.com', ], 'media-src': [ "'self'", 'data:', 'blob:', 'market-assets.strapi.io', 'yourBucketName.s3.yourRegion.amazonaws.com', ], upgradeInsecureRequests: null, }, }, }, }, // ... ]; ``` :::tip 在将上述更改推送到 GitHub 之前,向 Strapi Cloud 项目添加环境变量,以防在更改完成之前触发项目的重建和新部署。 🌐 Before pushing the above changes to GitHub, add environment variables to the Strapi Cloud project to prevent triggering a rebuild and new deployment of the project before the changes are complete. ::: ### Strapi 云配置 {#strapi-cloud-configuration} 🌐 Strapi Cloud configuration 1. 登录 Strapi Cloud,然后在“项目”页面上点击相应的项目。 2. 点击 **设置** 标签,然后在左侧菜单中选择 **变量**。 3. 添加特定于上传提供者的所需环境变量。 4. 点击 **保存**。 **示例:** | 变量 | 值 | |---------------------|-------------------------| | `CLOUDINARY_NAME` | your_cloudinary_name | | `CLOUDINARY_KEY` | your_cloudinary_api_key | | `CLOUDINARY_SECRET` | your_cloudinary_secret | | 变量 | 值 | |---------------------|------------------------| | `AWS_ACCESS_KEY_ID` | 你的_aws_access_key_id | | `AWS_ACCESS_SECRET` | 你的_aws_access_secret | | `AWS_REGION` | 你的_aws_region | | `AWS_BUCKET` | 你的_aws_bucket | | `CDN_URL` | 你的_cdn_url | | `CDN_ROOT_PATH` | 你的_cdn_root_path | ### 部署 {#deployment} 🌐 Deployment 要部署项目并使用第三方上传提供商,请推送之前的更改。这将触发 Strapi Cloud 项目的重新构建和新部署。 🌐 To deploy the project and use the third-party upload provider, push the changes from earlier. This will trigger a rebuild and new deployment of the Strapi Cloud project. 一旦应用构建完成,项目将使用新的上传提供者。 🌐 Once the application finishes building, the project will use the new upload provider. :::strapi Custom Provider 如果你想创建自定义上传提供程序,请参阅 CMS 文档中的 [Providers](/cms/features/media-library#providers) 文档。 🌐 If you want to create a custom upload provider, please refer to the [Providers](/cms/features/media-library#providers) documentation in the CMS Documentation. ::: # Strapi 云的上传大小限制 Source: https://strapi.nodejs.cn/cloud/advanced/upload-size-limits # Strapi 云的上传大小限制 {#upload-size-limits-for-strapi-cloud} 🌐 Upload size limits for Strapi Cloud 所有方案中,非图片文件的大小上限为 200 MB。图片文件的推荐最大值基于内存,并根据格式、方案以及媒体库设置而有所不同。要上传更大的图片,请禁用响应式友好上传和尺寸优化。 Strapi Cloud 对上传应用两种不同的限制。第一种是硬性最大文件大小限制,在基础设施层面对非图片文件强制执行。第二种是基于内存的对图片文件的推荐限制,取决于你的 CMS 设置。 🌐 Strapi Cloud applies 2 distinct limits to uploads. The first is a hard maximum file size, enforced at the infrastructure level for non-image files. The second is a memory-based recommendation for image files that depends on your CMS settings. ## 非图片文件的最大上传文件大小 {#maximum-upload-file-size-for-non-image-files} 🌐 Maximum upload file size for non-image files 在所有 Strapi Cloud 计划中,非图片上传的大小上限为 200 MB。该上限在基础设施层面执行,无法通过 `strapi::body` 中间件配置覆盖。 🌐 Non-image uploads are capped at 200 MB on all Strapi Cloud plans. The cap is enforced at the infrastructure level and cannot be overridden via the `strapi::body` middleware configuration. ## 图片文件的建议最大上传大小 {#recommended-maximum-upload-size-for-image-files} 🌐 Recommended maximum upload size for image files 图片上传受额外的、基于内存的推荐限制,该限制独立于非图片上限,并且因图片格式而异。 🌐 Image uploads are subject to an additional, memory-driven recommendation that is independent of the non-image cap and varies by image format. :::tip Uploading a large image? 要上传大于建议最大值的图片,请在[媒体库设置](/cms/features/media-library#configuring-settings)中同时禁用响应式友好上传和大小优化。这样,CMS 将按原样存储源文件,而不进行处理,从而提高建议的最大值(请参见下表中的 _处理关闭_ 值)。 🌐 To upload an image larger than the recommended maximum, disable both Responsive friendly upload and Size optimization in the [Media Library settings](/cms/features/media-library#configuring-settings). The CMS then stores the source file as-is without in-process processing, which raises the recommended maximum (see the _Processing off_ values in the tables below). ::: 当在[媒体库设置](/cms/features/media-library#configuring-settings)中同时启用响应式友好上传和大小优化时,CMS 会调整源图片的大小,并在保存之前在实例的内存中生成一组缩略图(小、中、大)。此处理在进程内进行,无论配置的上传提供者如何。 🌐 When Responsive friendly upload and Size optimization are both enabled in the [Media Library settings](/cms/features/media-library#configuring-settings), the CMS resizes the source image and generates a set of thumbnails (small, medium, large) in the instance's memory before persisting them. This processing happens in-process, regardless of the configured upload provider. 切换到第三方提供商(Amazon S3、Cloudinary 等)并不是一个解决方法。调整大小和生成缩略图的步骤仍然在 Strapi Cloud 实例内部运行,并且仍然需要与源图片尺寸成比例的内存。 🌐 Switching to a third-party provider (Amazon S3, Cloudinary, etc.) is not a workaround. The resize and thumbnail generation step still runs inside the Strapi Cloud instance and still requires memory proportional to the source image dimensions. 实际内存使用情况取决于图片的尺寸、格式以及你的媒体库设置。以下表格中的数值仅为推荐值,而非硬性限制。超过推荐大小的上传很可能导致实例内存不足并重启。 🌐 Actual memory usage depends on the image dimensions, format, and your Media Library settings. The values in the following tables are a recommendation, not a hard limit. Uploads above the recommended size are likely to cause the instance to run out of memory and restart. 该建议取决于[媒体库设置](/cms/features/media-library#configuring-settings)中是否启用了响应式友好上传和尺寸优化: 🌐 The recommendation depends on whether Responsive friendly upload and Size optimization are enabled in the [Media Library settings](/cms/features/media-library#configuring-settings): - _处理于_:两个设置均已启用,使用默认的 `small`、`medium` 和 `large` 尺寸。Strapi 在内存中生成缩略图,因此安全上传大小较小。 - _处理关闭_:两个设置均已禁用。源图片将按原样存储,不进行处理,因此安全上传大小更高。 每种格式和计划的推荐最大图片尺寸,以兆像素(MP)表示: 🌐 Recommended maximum image size, expressed in megapixels (MP), per format and plan: | 格式 | 初级 | 专业与商业 | |--------|------------------|-------------| | JPEG | 26 MP | 135 MP | | PNG | 10 MP | 90 MP | | WebP | 4 MP | 12 MP | | TIFF | 24 MP | 125 MP | | AVIF | 92 MP | 92 MP | | 格式 | 初级 | 专业与商业 | |--------|------------------|-------------| | JPEG | 224 兆像素 | 265 兆像素 | | PNG | 24 兆像素 | 115 兆像素 | | WebP | 15 兆像素 | 40 兆像素 | | TIFF | 24 兆像素 | 125 兆像素 | | AVIF | 96 兆像素 | 96 兆像素 | :::note Converting pixels to megapixels 图片的百万像素数是其宽度乘以高度的像素数,然后除以1,000,000。与给定百万像素数匹配的像素尺寸取决于纵横比。对于正方形图片,1百万像素大约是1000×1000像素,4百万像素大约是2000×2000像素,而100百万像素大约是10000×10000像素。 🌐 The number of megapixels of an image is its width multiplied by its height in pixels, divided by 1,000,000. The pixel dimensions that match a given megapixel count depend on the aspect ratio. For a square image, 1 MP is roughly 1000×1000 px, 4 MP is roughly 2000×2000 px, and 100 MP is roughly 10000×10000 px. ::: :::strapi Configuring a provider 要配置诸如 Amazon S3 或 Cloudinary 之类的外部存储,请参阅 [Strapi Cloud 的上传提供程序配置](/cloud/advanced/upload)。 🌐 To configure external storage such as Amazon S3 or Cloudinary, see [Upload Provider Configuration for Strapi Cloud](/cloud/advanced/upload). ::: # 命令行接口 (CLI) {#command-line-interface-cli} Source: https://strapi.nodejs.cn/cloud/cli/cloud-cli # 命令行接口 (CLI) {#command-line-interface-cli} 🌐 Command Line Interface (CLI) CLI 命令处理登录、项目关联、部署、列出和注销,无需远程仓库。 Strapi Cloud 配备了一个命令行接口(CLI),允许你登录和注销,将本地项目链接到现有的 Strapi Cloud 项目,并在无需将其托管在远程 git 仓库上的情况下进行部署。CLI 可与 `yarn` 和 `npm` 包管理器一起使用。 🌐 Strapi Cloud comes with a Command Line Interface (CLI) which allows you to log in and out, link a local project to an existing Strapi Cloud project, and deploy it without having to host it on a remote git repository. The CLI works with both the `yarn` and `npm` package managers. :::note 建议仅在本地安装 Strapi,这需要在所有以下 `strapi` 命令前加上用于项目设置的包管理器(例如 `npm run strapi help` 或 `yarn strapi help`)或专用的 node 包执行器(例如 `npx strapi help`)。 🌐 It is recommended to install Strapi locally only, which requires prefixing all of the following `strapi` commands with the package manager used for the project setup (e.g `npm run strapi help` or `yarn strapi help`) or a dedicated node package executor (e.g. `npx strapi help`). ::: ## Strapi 登录 {#strapi-login} 🌐 strapi login **别名:** `strapi cloud:login` 登录 Strapi 云。 🌐 Log in Strapi Cloud. ```bash strapi login ``` 此命令会自动打开一个浏览器窗口,首先要求你确认浏览器窗口和终端中显示的代码是否相同。然后,你将能够通过 Google、GitHub 或 GitLab 登录 Strapi Cloud。一旦浏览器窗口确认登录成功,就可以安全关闭。 🌐 This command automatically opens a browser window to first ask you to confirm that the codes displayed in both the browser window and the terminal are the same. Then you will be able to log into Strapi Cloud via Google, GitHub or GitLab. Once the browser window confirms successful login, it can be safely closed. 如果浏览器窗口没有自动打开,终端将显示一个可点击的链接以及可手动输入的代码。 🌐 If the browser window doesn't automatically open, the terminal will display a clickable link as well as the code to enter manually. ## strapi 链接 {#strapi-link} 🌐 strapi link **别名:** `strapi cloud:link` 将当前文件夹中的项目链接到 Strapi Cloud 中的现有项目。 🌐 Links project in the current folder to an existing project in Strapi Cloud. ```bash strapi link ``` 此命令将你当前目录中的本地项目与你 Strapi Cloud 账户上的现有项目连接。系统将提示你从 Strapi Cloud 上托管的可用项目列表中选择你希望链接的项目。 🌐 This command connects your local project in the current directory with an existing project on your Strapi Cloud account. You will be prompted to select the project you wish to link from a list of available projects hosted on Strapi Cloud. ## strapi 部署 {#strapi-deploy} 🌐 strapi deploy **别名:** `strapi cloud:deploy` 将关联的本地项目 (< 100MB) 部署到 Strapi 云。 🌐 Deploy a linked local project (< 100MB) to Strapi Cloud. ```bash strapi deploy ``` 此命令必须在 [`login`](#strapi-login) 和 [`link`](#strapi-link) 命令之后使用。它将本地 Strapi 项目部署到与当前文件夹关联的现有 Strapi Cloud 项目中。终端会在项目成功部署到 Strapi Cloud 上时通知你。 🌐 This command must be used after the [`login`](#strapi-login) and [`link`](#strapi-link) commands. It deploys a local Strapi project to an existing Strapi Cloud project that is linked to the current folder. The terminal will inform you when the project is successfully deployed on Strapi Cloud. 一旦项目首次通过 CLI 在 Strapi Cloud 上链接并部署后,可以重复使用 `deploy` 命令来触发同一项目的新部署。 🌐 Once the project is first linked and deployed on Strapi Cloud with the CLI, the `deploy` command can be reused to trigger a new deployment of the same project. :::note 一旦你部署了你的项目,如果你访问 Strapi Cloud 仪表板,你可能会看到一些限制,以及由于创建了一个不在远程仓库中的 Strapi Cloud 项目并通过 CLI 部署而产生的影响。 🌐 Once you deployed your project, if you visit the Strapi Cloud dashboard, you may see some limitations as well as impacts due to creating a Strapi Cloud project that is not in a remote repository and which was deployed with the CLI. - 仪表板中通常用于显示关于 Git 提供者信息的某些区域将会是空白的。 - 某些按钮,例如 **触发部署** 按钮,将会是灰色且无法点击,因为除非你已经[将 Git 仓库连接到你的 Strapi 云项目](/cloud/getting-started/deployment-cli#automatically-deploying-subsequent-changes)。 ::: ## strapi 项目 {#strapi-projects-newbadge-} 🌐 strapi projects **别名:** `strapi cloud:projects` 列出与你的账户关联的所有 Strapi Cloud 项目。 🌐 Lists all Strapi Cloud projects associated with your account. ```bash strapi projects ``` 此命令检索并显示托管在你的 Strapi Cloud 账户上的所有项目的列表。 🌐 This command retrieves and displays a list of all projects hosted on your Strapi Cloud account. ## strapi 登出 {#strapi-logout} 🌐 strapi logout **别名:** `strapi cloud:logout` 退出 Strapi Cloud。 🌐 Log out of Strapi Cloud. ```bash strapi logout ``` 此命令会将你从 Strapi Cloud 登出。一旦运行 `logout` 命令,浏览器页面将会打开,终端将显示你已成功登出的确认信息。你将无法再使用 `deploy` 命令。 🌐 This command logs you out of Strapi Cloud. Once the `logout` command is run, a browser page will open and the terminal will display a confirmation message that you were successfully logged out. You will not be able to use the `deploy` command anymore. # Strapi 云基础 {#strapi-cloud-fundamentals} Source: https://strapi.nodejs.cn/cloud/cloud-fundamentals # Strapi 云基础 {#strapi-cloud-fundamentals} 🌐 Strapi Cloud fundamentals Strapi Cloud 是一个用于部署 Strapi CMS 项目的 PaaS 托管平台,提供三种具有不同功能和支持级别的定价方案,两种用户角色(所有者和维护者),以及与自托管服务器行为完全相同的 REST/GraphQL API。 🌐 Strapi Cloud is a PaaS hosting platform for deploying Strapi CMS projects, offering three pricing plans with different features and support levels, two user roles (owners and maintainers), and REST/GraphQL APIs that behave identically to self-hosted servers. 在进一步阅读此 Strapi Cloud 文档之前,我们建议你先了解以下主要概念。它们将帮助你理解 Strapi Cloud 的工作原理,并确保顺利使用 Strapi Cloud。 🌐 Before going any further into this Strapi Cloud documentation, we recommend you to acknowledge the main concepts below. They will help you to understand how Strapi Cloud works, and ensure a smooth Strapi Cloud experience. - **托管平台**
Strapi Cloud 是一个托管平台,可用于部署已经使用 Strapi CMS(内容管理系统)创建的现有 Strapi 项目。Strapi Cloud *不是* Strapi CMS 的 SaaS([Software-as-a-Service](https://en.wikipedia.org/wiki/Software_as_a_service))版本,而应被视为 PaaS([Platform-as-a-Service](https://en.wikipedia.org/wiki/Platform_as_a_service))。如需了解有关 Strapi CMS 的更多信息,请随时参考 [CMS 文档](https://strapi.nodejs.cn/cms/intro)。 - **Strapi 云定价计划**
作为 Strapi 云的用户,你可以在三种计划之间进行选择:入门版、专业版和企业版。根据所选计划,你可以访问不同的功能、支持和定制选项(详情请参见[定价页面](https://strapi.io/pricing-cloud))。在本 Strapi 云文档中, 和 徽章可以显示在章节标题下,以表示该功能仅从相应计划开始可用。如果未显示徽章,则该功能在入门版计划中可用。云文档中仅使用专业版和企业版徽章,因为入门版是基础计划; 组件是为了与其他 Strapi 文档保持一致而存在,但不会显示在云页面上。 - **Strapi Cloud 用户类型**
在 Strapi Cloud 项目中可以有两种类型的用户:所有者和维护者。所有者是创建项目的人,因此可以访问该项目的所有功能和选项。维护者是被项目所有者邀请来为已创建的项目做出贡献的用户。正如[协作](/cloud/projects/collaboration)页面所述,维护者无法查看和访问 Strapi Cloud 仪表板中的所有功能和选项。 - **支持**
Strapi 支持团队提供的支持级别取决于你所订阅的 Strapi Cloud 计划。Starter 和 Pro 计划包含基础支持,而 Business 计划包含标准支持。有关支持级别的所有详细信息,请参阅[专门的支持文章](https://support.strapi.io/support/solutions/articles/67000680833-what-is-supported-by-the-strapi-team#Not-Supported)。 - **Strapi Cloud 与自托管环境中的 API 访问**
REST 和 GraphQL API 在 Strapi Cloud 和自托管服务器上的表现是相同的。唯一的区别是 URL: - 基础 API 域:在 Strapi Cloud 上,你的 API 使用环境的域(例如 `https://.strapiapp.com/api/...`),或者如果你设置了自定义域,则使用你的自定义域(参见 [域文档](/cloud/projects/settings#domains))。自托管项目将使用你所暴露的任何域。 - 媒体库网址:来自 Strapi Cloud 的 REST 和 GraphQL 响应中的媒体字段始终使用项目媒体域(例如 `.media.strapiapp.com`),即使你通过自定义域访问 API。自托管项目会返回配置的上传提供商的 URL,因此域名可以与你自己的网站或 CDN 匹配。当你将项目从自托管迁移到 Strapi Cloud 时,请确保你的前端读取 API 返回的绝对 URL 或接受 Strapi Cloud 的媒体域。 # 云缓存与性能 {#cloud-caching--performance} Source: https://strapi.nodejs.cn/cloud/getting-started/caching # 云缓存与性能 {#cloud-caching--performance} 🌐 Cloud caching & performance 通过 Cache-Control 头进行边缘缓存可以减少大型静态内容的延迟和服务器负载。 对于具有大量可缓存内容(如图片、视频和其他静态资源)的 Strapi Cloud 应用,通过 [`Cache-control` header](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Cache-Control) 启用 CDN(内容分发网络)缓存可以帮助提升应用性能。 🌐 For Strapi Cloud applications with large amounts of cacheable content, such as images, videos, and other static assets, enabling CDN (Content Delivery Network) caching via the [`Cache-control` header](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Cache-Control) can help improve application performance. CDN 缓存可以通过几种方式帮助提高应用性能: 🌐 CDN caching can help improve application performance in a few ways: * **降低延迟**:将频繁访问的内容缓存到更靠近终端用户的边缘服务器上,可以减少加载内容所需的时间。 * **卸载源服务器**:通过在边缘服务器上缓存内容,可以卸载源服务器,减少其负载,并使其能够专注于提供更多动态内容。 * **应对流量骤增**:通过将负载分配到多个边缘服务器来帮助应对流量骤增。这可以防止源服务器在高峰流量期间不堪重负,并确保用户体验的一致性。 ## Strapi Cloud 中的 Cache-Control 头 {#cache-control-header-in-strapi-cloud} 🌐 Cache-Control Header in Strapi Cloud 部署在 Strapi Cloud 上的静态网站默认包含一个 `Cache-Control` 头,该头在 CDN 边缘服务器上缓存 24 小时,在网页浏览器中缓存 10 秒。这是为了确保向用户提供网站的最新版本。 🌐 Static sites deployed on Strapi Cloud include, by default, a `Cache-Control` header set to cache for 24 hours on CDN edge servers and 10 seconds in web browsers. This is done to ensure that the latest version of the site is always served to users. 由 Strapi Cloud 提供的动态应用的响应默认不缓存。要启用缓存,你必须在应用的 `HTTP` 响应函数中设置 `Cache-Control` 头。 🌐 Responses from dynamic apps served by Strapi Cloud are not cached by default. To enable caching, you must set the `Cache-Control` header in the app’s `HTTP` response functions. ```js function myHandler(req, res) { // Set the Cache-Control header to cache responses for 1 day res.setHeader('Cache-Control', 'max-age=86400'); // Add your logic to generate the response here } ``` ```ts function myHandler(req: Request, res: Response) { // Set the Cache-Control header to cache responses for 1 day res.setHeader('Cache-Control', 'max-age=86400'); // Add your logic to generate the response here } ``` # Strapi 云端 - 仪表板部署 Source: https://strapi.nodejs.cn/cloud/getting-started/deployment # 使用云仪表板进行项目部署 {#project-deployment-with-the-cloud-dashboard} 🌐 Project deployment with the Cloud dashboard 通过仪表板在 Strapi Cloud 上部署你的 Strapi 项目,方法是选择一个计划,连接 Git 仓库,配置项目设置,并设置计费详情。 🌐 Deploy your Strapi project on Strapi Cloud using the dashboard by choosing a plan, connecting a Git repository, configuring your project settings, and setting up billing details. 这是一个使用云仪表板首次在 Strapi Cloud 部署你的项目的逐步指南。 🌐 This is a step-by-step guide for deploying your project on Strapi Cloud for the first time, using the Cloud dashboard. :::prerequisites 在你可以使用 Cloud 仪表板将 Strapi 应用部署到 Strapi Cloud 之前,你需要具备以下先决条件: 🌐 Before you can deploy your Strapi application on Strapi Cloud using the Cloud dashboard, you need to have the following prerequisites: * Strapi 版本 `4.8.2` 或更高 * 项目数据库必须与 PostgreSQL 兼容。Strapi 不支持也不推荐使用任何外部数据库,尽管可以配置一个(参见 [高级数据库配置](/cloud/advanced/database))。 * 项目源代码托管在 [GitHub](https://github.com) 或 [GitLab](https://about.gitlab.com/)。所连接的仓库可以包含多个 Strapi 应用。每个 Strapi 应用必须位于单独的目录中。 * 专门针对 GitLab:至少需要对项目拥有“[Maintainer](https://docs.gitlab.com/ee/user/permissions.html)”权限,才能在 Strapi Cloud 上导入。 ::: ## 登录 Strapi 云 {#logging-in-to-strapi-cloud} 🌐 Logging in to Strapi Cloud 1. 导航到 [Strapi Cloud](https://cloud.strapi.io) 登录页面。 2. 你可以选择使用 **GitHub**、**Google**、**GitLab** 或通过 **一次性密码** 登录。请选择你偏好的选项并登录。首次登录将会创建你的 Strapi Cloud 账户。登录后,你将被重定向到 Strapi Cloud 的 *项目* 页面,在那里你可以创建你的第一个 Strapi Cloud 项目。 ## 创建一个项目 {#deploying-a-project} 🌐 Creating a project 1. 在 *Projects* 页面,点击 **Create project** 按钮。 2. 你将被重定向到项目创建界面。此界面包含三个步骤:选择计划、连接远程 Git 仓库,以及设置项目。 3. 为你的 Strapi Cloud 项目选择一个计划和计费周期(详情请参见 [定价](https://strapi.io/pricing-cloud))。 4. 将 git 仓库连接到你的新 Strapi Cloud 项目。你可能首先需要选择一个 git 提供商。如果你已经使用一个 git 提供商部署了项目,之后可以通过点击 **切换 git 提供商** 按钮并选择 GitHub 或 GitLab 来使用另一个提供商部署另一个项目。 :::strapi Choose your path for your new Strapi Cloud project! 根据你的操作方式选择下面的一个选项卡: - 通过部署预先构建的 Strapi 模板 *(推荐给新用户和初学者 — 仅在 GitHub 上可用)*, - 或者通过部署你现有的 Strapi 项目。 ::: 4.a. 在连接你的 GitHub 账户后,点击 **使用模板** 按钮。 4.b. 在 *使用模板创建仓库* 模态框中,选择将要创建仓库的 GitHub 账户 4.c. 点击 **Create repository** 按钮。弹出窗口将确认仓库的创建。 4.d. 如果你已经授予 Strapi Cloud 访问你 GitHub 账户中所有仓库的权限,请直接进入下一步。如果没有,你将被重定向到 GitHub 弹出窗口,在那里你需要允许 Strapi Cloud 访问新创建的仓库(更多信息请参见 [GitHub documentation](https://docs.github.com/en/apps/overview))。 4.e. 回到项目创建界面,*账户* 和 *仓库* 字段现在与新创建的模板匹配。 :::tip 连接拥有你想要部署的存储库的 GitHub 或 GitLab 账户。这可以与你登录 Strapi Cloud 账户时使用的账户不同。 ::: 4.a. 如果你已经向 Strapi Cloud 授予了访问你 GitHub 或 GitLab 账户下所有仓库的权限,请直接进入下一步。如果没有,你将被重定向到一个弹出窗口,在那里你需要允许 Strapi Cloud 访问你在 GitHub/GitLab 上的部分或所有仓库的权限(更多信息请参见 [GitHub](https://docs.github.com/en/apps/overview) 和 [GitLab](https://docs.gitlab.com/ee/integration/oauth_provider.html#view-all-authorized-applications) 文档)。 4.c. 回到项目创建界面,选择要部署的*账户*和*代码库*。 5. 设置你的 Strapi Cloud 项目。 5.a. 填写以下信息: | 设置名称 | 说明 | |--------------|--------------------------------------------------------------------------------------------------| | 显示名称 | 名称会根据你选择的仓库自动填充,但你可以根据需要编辑它。 | | Git 分支 | 从下拉菜单中选择你想要部署的分支。 | | 推送时部署 | 勾选此框将在向所选分支推送更改时自动触发部署。禁用时,你需要手动部署最新的更改。 | | 地区 | 选择托管你的 Strapi 应用的服务器地理位置。可选地区包括美国(东部)、欧洲(西部)或亚洲(东南部)。 | :::note Git 分支和“在推送时部署”设置可以随后通过项目设置进行修改。然而,托管区域只能在创建项目时选择(参见 [项目设置](/cloud/projects/settings))。 ::: 5.b.(可选)点击 **显示高级设置** 来填写以下选项: | Setting name | Instructions | |--------------|---------------------------------------------------------------------------------------------------------| | Base directory | Write the name of the directory where your Strapi app is located in the repository. This is useful if you have multiple Strapi apps in the same repository or if you have a monorepo. | | Environment variables | Click on **Add variable** to add environment variables used to configure your Strapi app (see [Environment variables](/cms/configurations/environment/) for more information). You can also add environment variables to your Strapi application by adding a `.env` file to the root of your Strapi app directory. The environment variables defined in the `.env` file will be used by Strapi Cloud. | | Node version | Choose a Node version from the drop-down. The default Node version will automatically be chosen to best match the version of your Strapi project. If you manually choose a version that doesn't match with your Strapi project, the build will fail but the explanation will be displayed in the build logs. | :::strapi Using Environment Variables 你可以使用环境变量将你的项目连接到外部数据库,而不是 Strapi Cloud 使用的默认数据库(更多详情请参见 [数据库配置](/cms/configurations/database#environment-variables-in-database-configurations))。如果你想回退并再次使用 Strapi 的默认数据库,请删除你的 `DATABASE_` 环境变量(不涉及自动迁移)。 你也可以在这里设置自定义电子邮件提供商。Sendgrid 被设置为在 Strapi Cloud 上托管的 Strapi 应用的默认提供商(更多详情请参见 [providers configuration](/cms/features/email#providers))。 ::: ## 设置账单详情 {#setting-up-billing-details} 🌐 Setting up billing details 1. 点击 **继续到付款** 按钮。你将被重定向到付款页面,在那里你可以输入你的付款信息并查看你的发票。 2. 在*付款方式*部分,添加一张信用卡。此卡将用于所有与项目相关的事务,包括附加项目和超额费用。 3. 在*账单信息*部分,填写你的付款信息和账单地址。 4. 查看*发票*部分。购买月度订阅时,订阅价格将按当前计费周期剩余天数按比例计算。也可以展开*折扣码*部分以输入代码。 :::note 根据你的账单地址,可能会在你的发票上加收税费: - 在欧盟、英国、加拿大和印度,提供有效的增值税号可以免除增值税。如果未提供有效的增值税号,增值税将被添加到你的发票中。 - 在美国,适用的销售税是根据你的州和地址计算的。 ::: 5. 点击 **订阅** 按钮以完成新 Strapi Cloud 项目的创建。 ## 部署你的项目 {#deploying-your-project} 🌐 Deploying your project 确认项目创建后,你将被重定向到你的*项目仪表板*,在这里你将能够跟踪其创建和首次部署。 🌐 After confirming the project creation, you will be redirected to your *Project dashboard* where you will be able to follow its creation and first deployment. 在你的项目部署时,你 already 可以开始配置一些 [项目设置](/cloud/projects/settings)。 🌐 While your project is deploying, you can already start configuring some of your [project settings](/cloud/projects/settings). :::note 如果在项目创建过程中发生错误,进度指示器将停止并显示错误消息。你将在失败步骤旁看到一个**重试**按钮,允许你重新启动创建过程。 🌐 If an error occurs during the project creation, the progress indicator will stop and display an error message. You will see a **Retry** button next to the failed step, allowing you to restart the creation process. ::: 一旦你的项目成功部署,创建跟踪器将被你的部署列表取代,你将能够访问你的云托管项目。不要忘记在分享你的 Strapi 项目之前创建第一个管理员用户。 🌐 Once your project is successfully deployed, the creation tracker will be replaced by your deployments list and you will be able to visit your Cloud hosted project. Don't forget to create the first Admin user before sharing your Strapi project. ## 接下来做什么? {#icon-namefast-forward--what-to-do-next} 现在你已经通过云控制面板部署了项目,我们鼓励你探索以下想法,以获得更完整的 Strapi 云体验: 🌐 Now that you have deployed your project via the Cloud dashboard, we encourage you to explore the following ideas to have an even more complete Strapi Cloud experience: - 邀请其他用户[协作你的项目](/cloud/projects/collaboration)。 - 查看 [部署管理文档](/cloud/projects/deploys) 以了解如何为你的项目触发新的部署。 # Strapi 云端 - CLI 部署 Source: https://strapi.nodejs.cn/cloud/getting-started/deployment-cli # 使用命令行接口 (CLI) 进行项目部署 {#project-deployment-with-the-command-line-interface-cli} 🌐 Project deployment with the Command Line Interface (CLI) 使用 `strapi login`、`strapi link` 和 `strapi deploy` CLI 命令将 Strapi 项目部署到 Strapi Cloud,并可选择在 git 仓库提交时自动部署。 🌐 Deploy a Strapi project to Strapi Cloud using the `strapi login`, `strapi link`, and `strapi deploy` CLI commands, with optional automatic deployment on git repository commits. 这是一个使用命令行接口在 Strapi Cloud 上部署你的项目的逐步指南。 🌐 This is a step-by-step guide for deploying your project on Strapi Cloud using the Command Line Interface. :::prerequisites 在使用命令行接口将你的 Strapi 应用部署到 Strapi Cloud 之前,你需要具备以下先决条件: 🌐 Before you can deploy your Strapi application on Strapi Cloud using the Command Line Interface, you need to have the following prerequisites: - 拥有一个 Google、GitHub 或 GitLab 账号。 - 已经创建了 Strapi Cloud 项目(请参见 [使用 Cloud 仪表板部署项目](/cloud/getting-started/deployment))。 - 拥有一个已创建的 Strapi 项目(参见 CMS 文档中的 [从 CLI 安装](/cms/installation/cli)),存储在本地。项目必须小于 100MB。 - 确保你的硬盘中有可用存储空间,用于存放操作系统的临时文件夹。 ::: ## 登录 Strapi 云 {#logging-in-to-strapi-cloud} 🌐 Logging in to Strapi Cloud 1. 打开你的终端。 2. 导航到存储在本地电脑上的 Strapi 项目的文件夹。 3. 输入以下命令以登录 Strapi Cloud: ```bash yarn strapi login ``` ```bash npx run strapi login ``` 4. 在自动打开的浏览器窗口中,确认显示的代码与终端消息中写的代码相同。 5. 仍然在浏览器窗口中,选择通过 Google、GitHub 或 GitLab 登录。窗口应在不久后确认登录成功。 ## 将本地项目链接到 Strapi Cloud {#linking-your-local-project-to-strapi-cloud} 🌐 Linking your local project to Strapi Cloud 1. 在你的终端中,仍然在你的 Strapi 项目文件夹中,输入以下命令将本地项目链接到你现有的 Strapi Cloud 项目: ```bash yarn strapi link ``` ```bash npx run strapi link ``` 2. 从终端显示的列表中选择你想要关联的 Strapi Cloud 项目。 ## 部署你的项目 {#deploying-your-project} 🌐 Deploying your project 1. 在你的终端中,仍然在你的 Strapi 项目文件夹中,输入以下命令来部署项目: ```bash yarn strapi deploy ``` ```bash npx run strapi deploy ``` 2. 在终端中跟随进度条,直到确认项目已成功通过 Strapi Cloud 部署。 ### 自动部署后续更改 {#automatically-deploying-subsequent-changes} 🌐 Automatically deploying subsequent changes 默认情况下,在使用 Cloud CLI 部署项目时,每次进行更改后,都需要手动再次通过运行相应的 `deploy` 命令来部署所有后续更改。 🌐 By default, when deploying a project with the Cloud CLI, you need to manually deploy again all subsequent changes by running the corresponding `deploy` command everytime you make a change. 另一种选择是通过 git 仓库启用自动部署。操作步骤如下: 🌐 Another option is to enable automatic deployment through a git repository. To do so: 1. 将你的代码托管在 git 仓库上,例如 [GitHub](https://www.github.com) 或 [GitLab](https://www.gitlab.com)。 2. 将你的 Strapi Cloud 项目连接到仓库(请参阅 [项目设置 > 常规](/cloud/projects/settings#general) 中的 _已连接仓库_ 设置)。 3. 仍然在 _项目设置 > 常规_ 选项卡中,勾选“在每次推送到此分支的提交时部署项目”设置框。从现在起,每当提交推送到已连接的 Git 仓库时,都会触发一次新的 Strapi Cloud 部署。 :::note 自动部署与所有其他部署方法兼容,因此一旦连接了 git 仓库,你可以从 [Cloud 仪表板](/cloud/projects/deploys)、[CLI](/cloud/cli/cloud-cli#strapi-deploy) 或通过向已连接的仓库推送新提交来触发对 Strapi Cloud 的新部署。 🌐 Automatic deployment is compatible with all other deployment methods, so once a git repository is connected, you can trigger a new deployment to Strapi Cloud [from the Cloud dashboard](/cloud/projects/deploys), [from the CLI](/cloud/cli/cloud-cli#strapi-deploy), or by pushing new commits to your connected repository. ::: ## 接下来做什么? {#icon-namefast-forward--what-to-do-next} 既然你已经通过命令行接口部署了项目,我们鼓励你探索以下想法,以获得更完整的 Strapi Cloud 体验: 🌐 Now that you have deployed your project via the Command Line Interface, we encourage you to explore the following ideas to have an even more complete Strapi Cloud experience: - 访问 Cloud 仪表板以查看你 Strapi 项目的[有用指标和信息](/cloud/projects/overview)。 - 请查看完整的 [命令行接口文档](/cloud/cli/cloud-cli) 以了解其他可用命令。 # 云项目部署 Source: https://strapi.nodejs.cn/cloud/getting-started/deployment-options # 使用 Strapi Cloud 部署项目 {#project-deployment-with-strapi-cloud} 🌐 Project deployment with Strapi Cloud 使用 Cloud 仪表板或 CLI 在 Strapi 云上部署你的 Strapi 应用,我们为这两种方法提供了逐步指南。 🌐 Deploy your Strapi application on Strapi Cloud using either the Cloud dashboard or the CLI, with step-by-step guides provided for both methods. 你有两种选择可以使用 Strapi Cloud 部署你的项目: 🌐 You have 2 options to deploy your project with Strapi Cloud: - 要么通过用户界面(UI),这意味着你将直接在 Strapi Cloud 仪表板上执行所有操作, - 或者使用云评论命令行接口(CLI),这意味着你将只与终端进行交互。 下面的指南将引导你完成每种部署选项的所有步骤。 🌐 The guides below will guide you through all the steps for each of the deployment options. - [通过云仪表板](/cloud/getting-started/deployment): 通过用户界面创建和部署项目的分步指南。 - [通过命令行接口](/cloud/getting-started/deployment-cli): 使用云命令行接口链接和部署本地项目的逐步指南。 # 云计费与使用 {#cloud-billing--usage} Source: https://strapi.nodejs.cn/cloud/getting-started/usage-billing # 云计费与使用 {#cloud-billing--usage} 🌐 Cloud billing & usage Strapi Cloud 提供三种方案(入门版、专业版和企业版),按照使用量计费,费用根据 API 请求次数、资源存储和带宽而异,超出部分每月收费;未支付发票或违反方案条款的项目可能会被暂停。 🌐 Strapi Cloud offers three plans (Starter, Pro, and Business) with usage-based pricing that varies by API requests, asset storage, and bandwidth, plus overages charged monthly; projects may be suspended for unpaid invoices or plan violations. 此页面包含与你的 Strapi Cloud 账户和项目的使用及计费相关的一般信息。 🌐 This page contains general information related to the usage and billing of your Strapi Cloud account and projects. Strapi Cloud 提供 3 种计划:Starter、Pro 和 Business(请参见 [定价页面](https://strapi.io/pricing-cloud))。下表总结了 Strapi Cloud 按使用量计费的计划,涵盖一般功能和使用情况: 🌐 Strapi Cloud offers 3 plans: Starter, Pro, and Business (see [Pricing page](https://strapi.io/pricing-cloud)). The table below summarizes Strapi Cloud usage-based pricing plans, for general features and usage: | 功能 | 初级版 | 专业版 | 商务版 | | --- | --- | --- | --- | | **数据库条目** | 无限* | 无限* | 无限* | | **资源存储** | 50GB | 250GB | 1,000GB | | **资源带宽(每月)** | 50GB | 500GB | 1,000GB | | **API 请求(每月)** | 100,000 | 1,000,000 | 10,000,000 | | | | | | | **备份** | 不适用 | 每周 | 每日 | | **自定义域名** | 包含 | 包含 | 包含 | | **环境** | 不适用 | 包含 0 个(最多可增加 99 个) | 包含 1 个(最多可增加 99 个) | | **电子邮件(每月)** | 无限* | 无限* | 无限* | :::strapi Additional information on usage and features - 一般特性与使用方式: - 数据库条目是你的数据库中的条目数量。 - 资源存储是你的资源所使用的存储量。 - 资源带宽是你的资源所使用的带宽量。 - API 请求是对你的 API 发出的请求数量。这包括对 GraphQL 和 REST API 的请求,但不包括计入 CDN 带宽和存储的文件和媒体资源请求。所有 API 请求都计入你的每月使用量,无论响应类型如何。 - 云特定功能: - 备份指的是 Strapi Cloud 项目的自动备份(有关此功能的更多信息,请参见 [备份文档](/cloud/projects/settings#backups))。 - 自定义域名是指为你的 Strapi 云定义自定义域名的能力(参见 [自定义域名](/cloud/projects/settings#connecting-a-custom-domain))。 - 环境是指计划中包含的环境数量,除默认的生产环境之外(有关该功能的更多信息,请参见[环境](/cloud/projects/settings#environments)文档)。 ::: ## 环境管理 {#environments-management} 🌐 Environments management 环境是你的 Strapi Cloud 项目的隔离实例。所有项目都有一个默认的生产环境,但对于 Pro 或 Business 计划的项目,可以从项目设置的 *环境* 标签中配置其他额外环境(请参见 [Environments](/cloud/projects/settings#environments))。Strapi Cloud 项目可以配置的额外环境数量没有限制。 🌐 Environments are isolated instances of your Strapi Cloud project. All projects have a default production environment, but other additional environments can be configured for projects on a Pro or Business plan, from the *Environments* tab of the project settings (see [Environments](/cloud/projects/settings#environments)). There is no limit to the number of additional environments that can be configured for a Strapi Cloud project. 额外环境的使用限制与项目的生产环境相同(例如,Pro 计划中的额外环境在资源存储上将限制为 250GB,超出部分将按照与生产环境相同的方式收费)。但请注意,资源带宽和 API 调用是基于项目的,而不是基于环境的,因此即使有额外环境,这些使用限制也不会改变。 🌐 The usage limits of additional environments are the same as for the project's production environment (e.g. an additional environment on the Pro plan will be limited at 250GB for asset storage, and overages will be charged the same way as for the production environment). Note however that the asset bandwidth and API calls are project-based, not environment-based, so these usage limits do not change even with additional environments. ## 账单 {#billing} 🌐 Billing 计费是基于你 Strapi Cloud 项目的使用情况。项目计划和附加组件会根据你的计费周期按月或按年计费,而超出部分按月计费。你可以在项目设置的*计费与发票*标签中查看你的计费信息。 🌐 Billing is based on the usage of your Strapi Cloud projects. Project plans and addons are either billed monthly or yearly, depending on your billing cycle, while overages are billed monthly. You can view your billing information in the *Billing & Invoices* tab of your project settings. ### 税收 {#taxes} 🌐 Taxes 对于美国、英国、加拿大、印度和欧盟的账单地址,你的发票可能会加收当地税费。税额根据你的账单地址和增值税/税务识别号状态计算,并在结账时以及发票上显示。 🌐 For billing addresses in the US, UK, Canada, India, and EU, local taxes may be added to your invoices. Tax amounts are calculated based on your billing address and VAT/Tax ID status, and are displayed during checkout and on invoices. 你可以在你的 [账户账单](/cloud/account/account-billing) 设置中添加或更新你的增值税/税号。 🌐 You can add or update your VAT/Tax ID from your [Account Billing](/cloud/account/account-billing) settings. ### 超额 {#overages} 🌐 Overages 如果你超出了 API 请求、资源带宽或资源存储的计划限制,你将被收取相应的超额费用。 🌐 If you exceed the limits of your plan for API Requests, Asset Bandwidth, or Asset Storage, you will be charged for the corresponding overages. 例如,如果你在专业版计划的资源带宽中超过 500GB 的限制,超出部分将在当前计费周期结束时或项目删除时收费。超额部分不按比例计算,将全额收费。 🌐 For example, if you exceed the 500GB limit in asset bandwidth of the Pro plan, you will be charged for the excess bandwidth at the end of the current billing period or on project deletion. Overages are not prorated and are charged in full. 超出部分按月收费,费率如下: 🌐 Overages are charged monthly, according to the following rates: | 功能 | 价格 | | --- | --- | | **API 请求** | $1.50 / 25,000 次请求 | | **资源带宽** | $30.00 / 100GB | | **资源存储** | $0.60 / GB 每月 | ### 项目暂停 {#project-suspension} 🌐 Project suspension 项目可能由于各种原因最终处于**暂停**状态,包括未支付的发票或违反 Strapi Cloud 的 [terms of service](https://strapi.io/cloud-legal)。 如果你的项目被暂停,你将无法再访问 Strapi 管理面板,也无法触发新的部署。你的项目仪表板上将会显示一个横幅,说明暂停的原因。你也会收到电子邮件通知。 🌐 If your project is suspended, you will no longer be able to access the Strapi admin panel, nor trigger new deployments. A banner will appear in your project's dashboard, indicating the cause of the suspension. You will also be notified by email. #### 由于账单问题项目暂停 {#project-suspension-due-to-billing-issues} 🌐 Project suspension due to billing issues 如果你有未支付的发票,你的项目订阅将会自动被取消,项目将会被暂停。 🌐 If you have unpaid invoices, the subscription of your project will automatically be canceled and the project suspended. 要重新激活你的项目订阅: 🌐 To reactivate your project subscription: 1. 点击项目横幅中的 **立即付款** 按钮,或在 *设置 > 账单与发票* 中点击 2. 在外部支付页面支付你的逾期发票 3. 等待最多1分钟以重新激活你的项目 :::warning 如果你在30天内不解决该问题,你的暂停项目将被删除,所有数据将永久丢失。 🌐 If you do not resolve the issue within 30 days, your suspended project will be deleted and all its data will be permanently lost. ::: #### 因其他原因暂停项目 {#project-suspension-for-other-reasons} 🌐 Project suspension for other reasons 如果你的项目因除未支付发票导致订阅取消以外的原因而被暂停,你可能无法自行重新激活项目。你应该会收到一封包含如何解决问题说明的电子邮件。如果你没有收到邮件通知,请联系 [Strapi Support platform](https://support.strapi.io/support/home)。 ### 取消订阅 {#subscription-cancellation} 🌐 Subscription cancellation 如果你想取消你的 Strapi Cloud 订阅,你有两种选择: 🌐 If you want to cancel your Strapi Cloud subscription, you have 2 options: - 要么删除你的项目(参见 [删除 Strapi Cloud 项目](/cloud/projects/settings#deleting-a-strapi-cloud-project) 文档), - 或完全删除你的账户(参见 [删除 Strapi Cloud 账户](/cloud/account/account-settings#deleting-strapi-cloud-account) 文档)。 # 欢迎使用 Strapi Cloud 文档! {#welcome-to-the-strapi-cloud-documentation} Source: https://strapi.nodejs.cn/cloud/intro # 欢迎使用 Strapi Cloud 文档! {#welcome-to-the-strapi-cloud-documentation} 🌐 Welcome to the Strapi Cloud Documentation! Strapi Cloud 是一个完全托管的托管平台,用于部署 Strapi 应用,它抽象了基础设施的复杂性,同时提供内容管理、协作和合规功能。 🌐 Strapi Cloud is a fully managed hosting platform for deploying Strapi applications, abstracting infrastructure complexity while enabling content management, collaboration, and compliance features. Strapi Cloud 文档包含与你的 Strapi Cloud 账户和应用的设置、部署、更新和自定义相关的所有信息。 🌐 The Strapi Cloud documentation contains all information related to the setup, deployment, update and customization of your Strapi Cloud account and applications. :::strapi What is Strapi Cloud? [Strapi Cloud](https://strapi.io/cloud) 是一个托管平台,允许你部署你的 Strapi 应用。它是一个完全托管的内容平台 **🤝 为什么选择 Strapi Cloud?**
Strapi Cloud 使你能够提高内容发布速度,而无需在自定义需求和要求上做出妥协。
开发团队可以依靠 Strapi Cloud 来抽象基础设施管理的复杂性,同时保持你的开发工作流程并扩展 Strapi 的核心功能。
内容管理者可以使用 Strapi Cloud 自主管理所有类型的内容,并受益于完整的内容协作、安全和合规功能。 构建在开源无头 CMS Strapi 之上。 ::: :::prerequisites Strapi 团队推荐的典型工作流程是: 🌐 The typical workflow, which is recommended by the Strapi team, is: 1. 在本地创建你的 Strapi 应用(版本 4.8.2 或更高)。 2. 可以选择通过插件或自定义代码扩展应用。 3. 通过你的 git 提供商(GitHub 或 GitLab)对应用的代码库进行版本控制。 4. 使用 Strapi Cloud 部署应用。 ::: Strapi Cloud 文档按主题组织,顺序应与你使用产品的流程相对应。以下卡片,你可以点击,将重定向到主要主题和步骤。 🌐 The Strapi Cloud documentation is organised in topics in a order that should correspond to your journey with the product. The following cards, on which you can click, will redirect you to the main topics and steps. - [项目创建](/cloud/getting-started/deployment): 逐步指南:创建和部署 Strapi Cloud 项目。 - [账单和使用信息](/cloud/getting-started/usage-billing): Strapi Cloud 计划、计费、超额使用和项目暂停。 - [项目概览](/cloud/projects/overview): 关于如何访问 Strapi Cloud 项目以查看其详细信息和使用情况,以及管理这些项目的信息。 - [项目设置](/cloud/projects/settings): 有关 Strapi Cloud 项目的所有可用设置及其配置方法的详细信息。 - [合作](/cloud/projects/collaboration): 协作功能文档,用于邀请其他用户访问和管理项目。 - [部署管理](/cloud/projects/deploys): 有关 Strapi Cloud 项目部署的所有详细信息,包括触发或取消部署。 - [账户账单与详情](/cloud/account/account-billing): 关于 Strapi Cloud 订阅的信息以及如何管理、编辑和取消它们。 :::strapi Welcome to the Strapi community! Strapi Cloud 构建在 Strapi 之上,Strapi 是一个开源的、以社区为导向的项目。Strapi 团队秉持着分享他们的愿景,并与 Strapi 社区共同打造 Strapi 的未来。这就是为什么 [roadmap](https://feedback.strapi.io) 是开放的:因为所有的见解都非常重要,并将帮助引导项目朝着正确的方向发展。任何社区成员都非常欢迎在这里分享想法和意见。 你还可以加入 [GitHub](https://github.com/strapi/strapi)、 [GitHub Discussions](https://github.com/strapi/strapi/discussions)和 [Discord](https://discord.strapi.io) ,并从Strapi社区整体多年的经验、知识和贡献中受益。 ::: :::tip Need help? 符合条件方案的 Strapi Cloud 客户可以通过 [Strapi Support platform](https://support.strapi.io/support/home)联系 Strapi 团队。社区用户可以在 [GitHub Discussions](https://github.com/strapi/strapi/discussions) 或 [Discord](https://discord.strapi.io)提问。支持级别取决于你的方案(请参阅 [Cloud 基础](/cloud/cloud-fundamentals) 页面)。 ::: # 云项目协作 Source: https://strapi.nodejs.cn/cloud/projects/collaboration # 云项目协作 {#cloud-project-collaboration} 🌐 Cloud project collaboration 项目所有者通过“分享”按钮邀请维护者,管理待处理的邀请,并撤销访问权限。 项目由用户通过他们的 Strapi Cloud 账户创建。Strapi Cloud 用户可以将他们的项目分享给任何其他人,因此这些新用户可以访问项目仪表板并在该项目上进行协作,而项目拥有者无需分享他们的凭据。 🌐 Projects are created by a user via their Strapi Cloud account. Strapi Cloud users can share their projects to anyone else, so these new users can have access to the project dashboard and collaborate on that project, without the project owner to ever have to share their credentials. 被邀请协作项目的用户,称为维护者,不具有与项目所有者相同的权限。与项目所有者相反,维护者: 🌐 Users invited to collaborate on a project, called maintainers, do not have the same permissions as the project owner. Contrary to the project owner, maintainers: - 不能自己将项目分享给他人 - 无法从项目设置中删除项目 - 无法访问项目设置的*计费*部分 ## 共享项目 {#sharing-a-project} 🌐 Sharing a project 邀请新的维护者参与项目合作: 🌐 To invite a new maintainer to collaborate on a project: 1. 在*项目*页面上,点击你选择的项目即可跳转到其仪表板。 2. 点击位于仪表板页眉的**分享**按钮。 3. 在 *共享 [项目名称]* 对话框中,在文本框中输入要邀请的人的电子邮件地址。应出现一个下拉菜单,显示“邀请 [电子邮件地址]”。 4. 点击下拉菜单:电子邮件地址应显示在文本框正下方的紫色框中。 5. (可选) 重复步骤3和4以邀请更多人。电子邮件地址只能逐一输入,但邀请可以同时发送给多个电子邮件地址。 6. 点击 **发送** 按钮。 新维护者将收到一封电子邮件,其中包含一个链接,点击该链接即可加入项目。一旦项目被共享,代表维护者的头像将显示在项目仪表板的标题栏中,位于 **共享** 按钮旁边,以查看有多少维护者在该项目上协作以及他们是谁。 🌐 New maintainers will be sent an email containing a link to click on to join the project. Once a project is shared, avatars representing the maintainers will be displayed in the project dashboard's header, next to the **Share** button, to see how many maintainers collaborate on that project and who they are. :::tip 头像使用 GitHub、Google 或 GitLab 的个人资料图片,但对于待审核用户,只有在维护者账户激活之前显示首字母。你可以将鼠标悬停在头像上以显示维护者的全名。 🌐 Avatars use GitHub, Google or GitLab profile pictures, but for pending users only initials will be displayed until the activation of the maintainer account. You can hover over an avatar to display the full name of the maintainer. ::: ## 管理维护者 {#managing-maintainers} 🌐 Managing maintainers 在通过点击项目仪表板的**共享**按钮可访问的*共享 [项目名称]* 对话框中,项目所有者可以查看已被邀请参与项目协作的维护者完整列表。在那里,可以查看每个维护者的当前状态并对其进行管理。 🌐 From the *Share [project name]* dialog accessible by clicking on the **Share** button of a project dashboard, projects owners can view the full list of maintainers who have been invited to collaborate on the project. From there, it is possible to see the current status of each maintainer and to manage them. 显示全名的维护者是指那些在收到邀请邮件后激活了账户的用户。然而,如果列表中显示的是电子邮件地址的维护者,这意味着他们尚未激活账户,因此无法访问项目仪表板。在这种情况下,应在电子邮件地址旁边标明状态以解释问题: 🌐 Maintainers whose full name is displayed are users who did activate their account following the invitation email. If however there are maintainers in the list whose email address is displayed, it means they haven't activated their accounts and can't access the project dashboard yet. In that case, a status should be indicated right next to the email address to explain the issue: - 待处理:邀请邮件已发送,但维护者尚未采取行动。 - 已过期:电子邮件已在 72 小时前发送,邀请已过期。 对于已过期的状态,可以通过点击 **管理** 按钮,然后选择 **重新发送邀请** 来发送另一封邀请邮件。 🌐 For Expired statuses, it is possible to send another invitation email by clicking on the **Manage** button, then **Resend invite**. ### 撤销维护者权限 {#revoking-maintainers} 🌐 Revoking maintainers 撤销维护者对项目仪表板的访问权限: 🌐 To revoke a maintainer's access to the project dashboard: 1. 点击项目仪表板标题中的 **分享** 按钮。 2. 在“*有权限的人*”列表中,找到要撤销访问权限的维护者,然后点击**管理**按钮。 3. 点击 **撤销** 按钮。 4. 在确认对话框中,再次点击 **撤销** 按钮。 被撤销的维护者将完全无法访问项目仪表板。 🌐 The revoked maintainer will completely stop having access to the project dashboard. :::note 被撤销项目访问权限的维护者不会收到任何电子邮件或通知。 🌐 Maintainers whose access to the project has been revoked do not receive any email or notification. ::: # 云部署管理 Source: https://strapi.nodejs.cn/cloud/projects/deploys # 云部署管理 {#cloud-deployments-management} 🌐 Cloud deployments management 部署触发器可以是手动的,也可以在 git 推送时自动触发,并且能够从仪表板或命令行接口取消正在进行的构建。 创建一个新的 Strapi Cloud 项目会自动触发该项目的部署。之后,部署可以是: 🌐 The creation of a new Strapi Cloud project automatically trigger the deployment of that project. After that, deployments can be: - 在需要时手动触发,[从云仪表板](#triggering-a-new-deployment)或[从命令行接口](/cloud/cli/cloud-cli#strapi-deploy), - 或者每次向分支推送新提交时自动触发,如果 Strapi Cloud 项目已连接到 git 仓库并且启用了“推送时部署”选项(参见 [项目设置](/cloud/projects/settings#modifying-git-repository--branch))。 如果需要,正在进行的部署也可以[手动取消](#cancelling-a-deployment)。 🌐 Ongoing deployments can also be [manually canceled](#cancelling-a-deployment) if needed. ## 触发新的部署 {#triggering-a-new-deployment} 🌐 Triggering a new deployment 要手动触发项目的新部署,请点击项目仪表板标题右上角始终显示的**触发部署**按钮。此操作将在*部署*标签中添加一个新卡片,你可以在其中监控状态并实时查看部署日志(参见[部署历史和日志](/cloud/projects/deploys-history))。 🌐 To manually trigger a new deployment for your project, click on the **Trigger deployment** button always displayed in the right corner of a project dashboard's header. This action will add a new card in the *Deployments* tab, where you can monitor the status and view the deployment logs live (see [Deploy history and logs](/cloud/projects/deploys-history)). ## 取消部署 {#cancelling-a-deployment} 🌐 Cancelling a deployment 如果出于任何原因你想取消正在进行且未完成的部署: 🌐 If for any reason you want to cancel an ongoing and unfinished deployment: 1. 转到最新触发的部署的*部署详细信息*页面(参见[访问日志详情](/cloud/projects/deploys-history#accessing-deployment-details--logs))。 2. 点击右上角的 **取消部署** 按钮。部署的状态将自动更改为 *已取消*。 :::tip 你也可以从列出部署历史的 *Deployments* 标签中取消部署。处于 *Building* 状态的正在进行的部署卡片将显示一个 ![取消按钮](/img/assets/icons/clear.svg) 按钮,用于取消部署。 🌐 You can also cancel a deployment from the *Deployments* tab which lists the deployments history. The card of ongoing deployment with the *Building* status will display a ![Cancel button](/img/assets/icons/clear.svg) button for cancelling the deployment. ::: # 云部署历史和日志 Source: https://strapi.nodejs.cn/cloud/projects/deploys-history # 云部署历史和日志 {#deploy-history-and-logs} 🌐 Cloud deployment history and logs 部署选项卡列出每个构建的状态,并允许深入检查构建和部署日志。 对于每个 Strapi Cloud 项目,你可以访问所有已发生部署的历史记录及其详细信息,包括构建和部署日志。此信息可在 *部署* 选项卡中查看。 🌐 For each Strapi Cloud project, you can access the history of all deployments that occurred and their details including build and deployment logs. This information is available in the *Deployments* tab. ## 查看部署历史记录 {#viewing-deploy-history} 🌐 Viewing the deployment history 在*部署*选项卡中显示了按时间顺序排列的卡片列表,其中包含你项目的所有历史部署的详细信息。 🌐 In the *Deployments* tab is displayed a chronological list of cards with the details of all historical deployments for your project. 每张卡片显示以下信息: 🌐 Each card displays the following information: - 提交 SHA 💡 提交 SHA(或哈希值)是你提交的唯一 ID,它指向在特定时间进行的特定更改。,带有指向你的 git 提供者的直接链接,以及提交信息 - 部署状态: - *部署* - *完成* - *已取消* - *构建失败* - *部署失败* - 上次部署时间(部署触发时间及持续时间) - 分支 ## 访问部署详情和日志 {#accessing-deployment-details--logs} 🌐 Accessing deployment details & logs 在 *Deployments* 标签页中,你可以将鼠标悬停在部署卡片上,使 ![查看日志按钮](/img/assets/icons/Eye.svg) **显示详情** 按钮出现。点击此按钮将重定向到包含部署详细日志的 *Deployment details* 页面。 🌐 From the *Deployments* tab, you can hover a deployment card to make the ![See logs button](/img/assets/icons/Eye.svg) **Show details** button appear. Clicking on this button will redirect you to the *Deployment details* page which contains the deployment's detailed logs. 在页面的*构建日志*和*部署日志*部分,你可以点击箭头按钮 ![下箭头](/img/assets/icons/ONHOLDCarretDown.svg) ![上箭头](/img/assets/icons/ONHOLDCarretUp.svg) 来显示或隐藏部署的构建和部署日志。 🌐 In the *Build logs* and *Deployment logs* sections of the page you can click on the arrow buttons ![Down arrow](/img/assets/icons/ONHOLDCarretDown.svg) ![Up arrow](/img/assets/icons/ONHOLDCarretUp.svg) to show or hide the build and deployment logs of the deployment. :::tip 点击 ![复制按钮](/img/assets/icons/duplicate.svg) **复制到剪贴板** 按钮以复制日志内容。 🌐 Click the ![Copy button](/img/assets/icons/duplicate.svg) **Copy to clipboard** button to copy the log contents. ::: 在*部署详情*页面的右侧,还显示以下信息: 🌐 In the right side of the *Deployment details* page is also displayed the following information: - *提交*: 提交 SHA 💡 提交 SHA(或哈希)是你的提交的唯一ID,指向在特定时间进行的特定更改,并提供指向你的 git 提供者的直接链接,以及用于此部署的提交信息 - *状态*,可以是 *构建中*、*部署中*、*完成*、*已取消*、*构建失败* 或 *部署失败* - *来源*: 此次部署的分支和提交信息 - *持续时间*:部署所花费的时间以及发生的时间 # 云项目日志 Source: https://strapi.nodejs.cn/cloud/projects/logs # 云项目日志 {#cloud-project-logs} 🌐 Cloud project logs 日志标签在一个可搜索、可筛选的表格中显示你项目的实时日志。点击任意条目以查看其完整消息和元数据。 在项目仪表板中,*日志* 标签会将项目的实时日志以结构化条目的形式流式传输,你可以搜索、过滤和检查这些条目。 🌐 From the project dashboard, the *Logs* tab streams the live logs of the project as structured entries you can search, filter, and inspect. :::note *日志* 页面只有在项目成功部署后才能访问,在重大环境操作期间(如项目创建、数据传输或环境清理)无法访问。 🌐 The *Logs* page is only accessible once the project has a successful deployment and is inaccessible during major environment operations, such as project creation, data transfer, or environment clearing. ::: ## 查看日志 {#viewing-logs} 🌐 Viewing logs 查看器跟随实时日志流并自动滚动以保持最新条目在视图中。每行显示三列: 🌐 The viewer follows the live logs stream and auto-scrolls to keep the latest entries in view. Each row shows three columns: | 列 | 描述 | | --- | --- | | 时间戳 | 日志发出的时间。 | | 类型 | 日志级别,*错误*、*警告*、*信息* 或 *HTTP*,显示为彩色徽章。对于 *HTTP* 条目,响应状态码显示在徽章旁边。 | | 消息 | 日志消息。表格中长消息会被截断;点击条目可在抽屉中查看完整文本。 | 你可以通过点击在行悬停时显示的复制按钮 ,或在抽屉内的按钮来复制单条日志记录。要复制当前日志查看器中显示的所有条目,请使用工具栏中的复制按钮 。 :::caution 实时日志流当前仅限于最近15分钟,并且最多为100,000行。历史日志可见性正在开发中。 🌐 The live log stream is currently limited to the last 15 minutes and capped at 100,000 rows. Historical log visibility is under development. ::: ## 检查日志条目 {#inspecting-a-log-entry} 🌐 Inspecting a log entry 点击任意日志行以打开详细信息抽屉并显示完整消息,并在可用时显示日志的*元数据*。*元数据*部分显示以下信息: 🌐 Click any log row to open a detail drawer and display the full message and, when available, the *Metadata* of the log. The *Metadata* section shows the following information: | 字段 | 描述 | | --- | --- | | `method` | HTTP 方法(例如 `GET`、`POST`)。 | | `path` | 请求的路径。 | | `route` | 匹配的应用路由。 | | `status_code` | HTTP 响应状态码。 | | `error_type` | 错误分类,当条目为错误时。 | | `duration_ms` | 处理请求所用的时间,单位为毫秒。 | | `response_size` | 响应的大小。 | | `request_type` | 请求类型(例如 API、管理员)。 | ## 查看历史日志 {#viewing-historical-logs} 🌐 Viewing historical logs 使用工具栏中的时间范围选择器在实时模式和有限的历史范围之间切换。查看器默认显示最近15分钟。 🌐 Use the timeframe selector in the toolbar to switch between live mode and a bounded historical range. The viewer defaults to the last 15 minutes. 可用时间范围取决于你的 Strapi Cloud 计划: 🌐 Available time ranges depend on your Strapi Cloud plan: | 时间范围 | 计划可用性 | | --- | --- | | 15分钟, 1小时, 4小时, 24小时, 2天, 7天 | 所有计划 | | 14天 | | | 30天 | | 对于所有非实时的时间范围,选择器旁会出现一个刷新按钮,用于手动重新加载范围。在实时模式下,日志会自动刷新。 🌐 For all non-live timeframes, a Refresh button appears next to the picker to manually reload the range. In live mode, logs refresh happens automatically. ## 搜索和筛选日志 {#searching-and-filtering-logs} 🌐 Searching and filtering logs 你可以使用搜索和筛选工具来优化查看器中显示的日志。筛选器和搜索可以结合使用,例如,你可以仅显示提到特定路由且状态为 *5xx* 的 *HTTP* 条目。 🌐 You can use the search and filter tools to refine the logs displayed in the viewer. Filters and search combine, so you can, for example, show only *HTTP* entries with a *5xx* status that mention a specific route. :::note 在引入结构化查看器之前部署的项目,其日志以纯文本显示,不支持搜索、过滤或每条日志的元数据。要访问新的日志查看器,请触发项目的手动重新部署。 🌐 Projects deployed before the structured viewer was introduced display their logs as plain text, without search, filtering, or per-entry metadata. To access the new log viewer, trigger a manual redeployment of the project. ::: ### 搜索栏 {#search-bar} 🌐 Search bar 在搜索字段中输入,以仅保留消息中包含你文本的条目。 🌐 Type in the search field to keep only the entries whose message contains your text. ### 类型筛选 {#type-filter} 🌐 Type filter 按以下日志级别筛选: 🌐 Filter by the following log levels: - 错误 - 警告 - 信息 - HTTP 错误日志以红色标出,以便在浏览时高亮。 🌐 Error logs are highlighted in red so they stand out as you scan. ### HTTP状态码过滤器 {#http-status-code-filter} 🌐 HTTP status code filter 按以下状态码筛选: 🌐 Filter by the following status codes: - 2xx - 3xx - 4xx - 5xx # 云通知 Source: https://strapi.nodejs.cn/cloud/projects/notifications # 云通知 {#cloud-notifications} 🌐 Cloud notifications 铃铛图标打开最近部署事件的动态,在30天后自动清除。 可以通过点击云仪表板顶部导航中的铃铛图标 来打开通知中心。 它显示你所有现有项目的最新通知列表。点击列表中的通知卡片将重定向到对应部署的*日志详情*页面(更多信息请参见[部署历史与日志](/cloud/projects/deploys-history#accessing-deployment-details--logs))。 🌐 It displays a list of the latest notifications for all your existing projects. Clicking on a notification card from the list will redirect you to the *Log details* page of the corresponding deployment (more information in [Deploy history & logs](/cloud/projects/deploys-history#accessing-deployment-details--logs)). 以下通知可以在通知中心列出: 🌐 The following notifications can be listed in the Notifications center: - *部署完成*:当部署成功完成时。 - *构建失败*:当部署在构建阶段失败时。 - *部署失败*:当部署阶段的部署失败时。 - *已触发部署*:当连接的仓库有新的推送时,会触发部署。然而,当部署是手动触发时,该通知不会发送。 :::note 所有超过30天的通知将自动从通知中心移除。 🌐 All notifications older than 30 days are automatically removed from the Notification center. ::: # 云项目可观测性 Source: https://strapi.nodejs.cn/cloud/projects/observability # 云项目可观测性 {#cloud-project-observability} 🌐 Cloud project observability 可观测性标签直接从你的项目仪表板展示使用指标、端点流量和基础设施健康数据。使用它来监控 API 和带宽消耗,识别高流量端点,并跟踪 CPU 和内存使用情况随时间的变化。 🌐 The Observability tab surfaces usage metrics, endpoint traffic, and infrastructure health data directly from your project dashboard. Use it to monitor API and bandwidth consumption, identify high-traffic endpoints, and track CPU and Memory usage over time. *可观察性* 标签可以从你的项目仪表板访问。它包含 4 个部分:[摘要](#summary)、[项目消耗](#project-consumption)、[流量洞察](#traffic-insights) 和 [CPU 和内存使用情况](#cpu-and-memory-usage)。 🌐 The *Observability* tab is accessible from your project dashboard. It contains 4 sections: [Summary](#summary), [Project consumption](#project-consumption), [Traffic insights](#traffic-insights), and [CPU and Memory usage](#cpu-and-memory-usage). ## 摘要 {#summary} 🌐 Summary *摘要* 部分显示你每月的资源使用概览以及任何超额费用。 🌐 The *Summary* section displays a monthly overview of your resource consumption and any overage charges. 使用本节右上角的月份选择器查看前几个月。 🌐 Use the month picker in the top-right corner of the section to view previous months. 该部分包含4张指标卡: 🌐 The section contains 4 metric cards: | 卡片 | 描述 | |------|-------------| | API 请求 | 本月的 API 请求总数,相对于你的计划限额显示带进度条。如果超过限额,则显示超额费用。 | | 资源带宽 | 本月消耗的总带宽,相对于你的计划限额显示带进度条。如果超过限额,则显示超额费用。 | | 资源存储 | 每个环境使用的存储。每个环境显示进度条,显示其使用量相对于限额的情况。如果在任何时间段内任何环境超出限额,则显示超额费用。 | | 超额费用 | 本月的总超额金额,不含适用税费。 | API 请求的数据是实时可用的,而资源带宽和资源存储是每小时更新一次的。要更新这些指标,请刷新仪表板。 🌐 API requests data is available in real time, whereas Asset bandwidth and Asset storage are updated hourly. To update these metrics, refresh the dashboard. :::tip 有关使用计费和超量管理的更多信息,请参见 [使用与计费](/cloud/getting-started/usage-billing)。 🌐 For more information on usage billing and overage management, see [Usage & Billing](/cloud/getting-started/usage-billing). ::: ## 项目消耗 {#project-consumption} 🌐 Project consumption *项目消耗*部分显示按时间划分的 API 请求和资源带宽使用情况,并按环境分列。 🌐 The *Project consumption* section shows API requests and asset bandwidth usage over time, broken down by environment. 使用右上角的时间范围选择器来调整显示的时间段: 🌐 Use the time range selector in the top-right corner to adjust the period displayed: | 时间范围 | 计划可用性 | |------------|-------------------| | 24小时 | 所有计划 | | 7天 | 所有计划 | | 14天 | | | 30天 | | | 60天 | | 本节包含2张图表: 🌐 The section contains 2 charts: - API 请求:所有环境的总 API 调用次数,以堆叠柱状图显示。 - 资源带宽:在所有环境中传输的资源总数据,使用相同的堆叠条形图格式。 每个柱状图代表一个时间段(按所选范围,可为每小时或每天),并按环境堆叠。在图表上悬停可查看工具提示,显示特定时间点各环境的消耗明细。要从图表中过滤环境,请点击图例中的环境名称。 🌐 Each bar represents a time bucket (hourly or daily, depending on the selected range) and is stacked by environment. Hover over the chart to see a tooltip showing the consumption breakdown per environment at a specific point in time. To filter out environments from the graph, click on their name in the legend. ## 流量洞察 {#traffic-insights} 🌐 Traffic insights *流量洞察* 部分显示所选环境和时间范围内哪些 API 端点和资源产生的流量最多。 🌐 The *Traffic insights* section shows which API endpoints and assets generate the most traffic for the selected environment and time range. 使用*环境*下拉菜单和时间范围选择器来筛选数据。可用的时间范围与[项目消耗](#project-consumption)中的相同。 🌐 Use the *environment* dropdown and the time range selector to filter the data. The available time ranges are the same as in [Project consumption](#project-consumption). ### 按 API 流量排序的顶层端点 {#top-endpoints-by-api-traffic} 🌐 Top endpoints by API traffic *按 API 流量排序的顶部端点* 表列出了在所选期间收到请求最多的 5 个 API 端点,并显示以下指标: 🌐 The *Top endpoints by API traffic* table lists the 5 API endpoints that received the most requests in the selected period and displays the following metrics: | 列 | 描述 | |--------|-------------| | 端点 | 匹配的 API 路径。 | | 请求数 | 选定期间的请求总数。 | | P50 (毫秒) | 中位响应时间。50%的请求完成速度比此值快。 | | P95 (毫秒) | 第95百分位响应时间。95%的请求完成速度比此值快。 | | P99 (毫秒) | 第99百分位响应时间。99%的请求完成速度比此值快。 | | 最大值 (毫秒) | 在选定期间记录的最高单次响应时间。 | | 错误率 (%) | 返回4xx响应的请求百分比。 | ### 按带宽使用量排序的顶层资源 {#top-assets-by-bandwidth-usage} 🌐 Top assets by bandwidth usage *按带宽使用量排名的顶层资源* 表列出了在所选期间消耗带宽最多的5个资源,并显示以下指标: 🌐 The *Top assets by bandwidth usage* table lists the 5 assets that consumed the most bandwidth in the selected period and displays the following metrics: | 列 | 描述 | |--------|-------------| | 资源路径 | 资源的文件路径。 | | 总带宽 | 在所选期间此资源传输的总数据量。 | ## CPU 和内存使用情况 {#cpu-and-memory-usage} 🌐 CPU and Memory usage *CPU 和内存随时间变化* 部分显示你的应用使用的 CPU 和内存占可用资源的百分比。 🌐 The *CPU and Memory over time* section shows your application's CPU and Memory consumption as a percentage of the available resources. 该部分显示一个包含 2 个系列的区域图: 🌐 The section displays a single area chart with 2 series: - CPU:显示为蓝色虚线 - 内存:显示为绿色实线 使用视图右上角的 *环境* 下拉菜单选择要显示指标的环境,并使用时间范围选择器调整时间周期。将鼠标悬停在图表上可以看到显示特定时间点 CPU 和内存值的工具提示。数据根据所选范围按小时或按日显示。 🌐 Use the *environment* dropdown at the top right of the view to select which environment's metrics to display and the time range selector to adjust the period. Hover over the chart to see a tooltip showing the CPU and Memory values at a specific point in time. Data is displayed hourly or daily, depending on the selected range. 点击部分工具栏中的刷新按钮 以手动加载最新数据。该部分也会自动刷新: - 每30秒一次,适用于5米和15米范围 - 在1小时、24小时和7天范围内每5分钟一次 # 云项目概览 Source: https://strapi.nodejs.cn/cloud/projects/overview # 云项目概览 {#cloud-projects-overview} 🌐 Cloud projects overview 项目页面列出了所有应用及其状态和快捷操作;选择某一个会打开一个显示指标和控制的仪表板。 *项目* 页面显示你所有 Strapi Cloud 项目的列表。在这里,你可以管理你的项目并访问相应的应用。 🌐 The *Projects* page displays a list of all your Strapi Cloud projects. From here you can manage your projects and access the corresponding applications. 每个项目卡显示以下信息: 🌐 Each project card displays the following information: * 项目名称 * 生产环境上一次成功部署的日期 * 项目的当前状态: * *未连接*,如果项目仓库未连接到 Strapi Cloud * *已暂停*,如果项目已被暂停(请参阅[项目暂停](/cloud/getting-started/usage-billing#project-suspension)以重新激活项目) * *版本不兼容*,如果项目使用的 Strapi 版本与 Strapi Cloud 不兼容 每个项目卡还显示一个 菜单图标,以访问以下选项: * **访问应用**:跳转到应用 * **转到部署**:以跳转到 [*部署*](/cloud/projects/deploys) 页面 * **前往设置**:以被重定向到 [*设置*](/cloud/projects/settings) 页面 :::tip 点击导航栏中的 * 产品更新* 按钮,查看最新发布的功能和修复内容。 ::: ## 访问项目仪表板 {#accessing-a-projects-dashboard} 🌐 Accessing a project's dashboard 在 *项目* 页面上,点击任何项目卡即可访问其仪表板。它显示项目和环境的详细信息,并可以访问部署历史、日志、可观测性以及所有可用设置。 🌐 From the *Projects* page, click on any project card to access its dashboard. It displays the project and environment details and gives access to the deployment history, logs, observability and all available settings. 在所选项目的仪表板页眉中,你可以: 🌐 From the dashboard header of a chosen project, you can: - 切换或添加一个环境 , - 使用 **共享** 按钮邀请用户协作项目(参见 [协作](/cloud/projects/collaboration))并查看已被邀请的用户图标 , - 使用 **设置**按钮访问项目及其现有环境的设置 , - 在部署之间导航(参见 [部署历史](/cloud/projects/deploys-history))、日志(参见 [日志](/cloud/projects/logs))和可观测性(参见 [可观测性](/cloud/projects/observability)) , - 触发新的部署(参见 [部署管理](/cloud/projects/deploys))并访问你的应用 。 你的项目仪表板还显示: 🌐 Your project dashboard also displays: - 你环境中所有部署的列表(参见 [部署历史](/cloud/projects/deploys-history)) , - 界面右侧框中的项目和环境详细信息 ,包括: - API、带宽和存储消耗的总结(完整细分请参见 [项目可观测性](/cloud/projects/observability)), - 分支的名称以及一个 **管理** 按钮,用于重定向到分支设置(参见 [修改 git 仓库和分支](/cloud/projects/settings#modifying-git-repository--branch)), - 基目录的名称, - Strapi 版本号, - Strapi 应用的 URL。 # 云项目设置 Source: https://strapi.nodejs.cn/cloud/projects/settings # 云项目设置 {#cloud-project-settings} 🌐 Cloud project settings 设置区域涵盖项目级别的控制(通用、账单与发票、计划)以及每个环境的配置。 在所选项目的仪表板中,位于页眉的 **设置**按钮使你能够管理你的 Strapi Cloud 项目及其环境的配置和设置。 界面左侧的设置菜单分为两类:整个项目的设置以及针对项目中配置的任何环境的特定设置。 🌐 The settings' menu on the left side of the interface is separated into 2 categories: the settings for the entire project and the settings specific to any configured environment for the project. ## 项目级设置 {#project-level-settings} 🌐 Project-level settings 项目设置有4个可用的标签页: 🌐 There are 4 tabs available for the project settings: - [*通用*](#general), - [*环境*](#environments), - [*账单与发票*](#billing--invoices), - 和 [*计划*](#plans)。 ### 一般 {#general} 🌐 General 项目级设置的 *常规*选项卡使你能够检查和更新项目的以下选项: - *基本信息*,请参见: - 你的 Strapi Cloud 项目的名称——用于在云控制面板、Strapi CLI 和部署 URL 上识别项目——并更改它(参见 [重命名项目](#renaming-project))。 - 你为 Strapi Cloud 项目选择的托管区域,即项目及其数据和资源存储的服务器的地理位置。托管区域在创建项目时设置(参见 [项目创建](/cloud/getting-started/deployment)),之后无法修改。 - 项目的元数据,包括生产应用的内部名称和订阅 ID,这对于调试和支持目的可能很有用。 - *Strapi CMS 许可证密钥*:用于在你的云项目上直接启用和使用某些 CMS 功能(请参阅[定价页面](https://strapi.io/pricing-self-hosted)购买许可证)。 - *已连接的 Git 仓库*:用于更改项目使用的仓库和分支(请参见 [修改 git 仓库与分支](#modifying-git-repository--branch))。还可以启用/禁用“推送时部署”选项。 - *危险区域*,与: - *转移所有权*:项目所有者将云项目的所有权转移给已存在的维护者(参见[转移项目所有权](#transferring-project-ownership))。 - *删除项目*:永久删除你的 Strapi Cloud 项目(参见 [删除 Strapi Cloud 项目](#deleting-a-strapi-cloud-project))。 #### 重命名项目 {#renaming-project} 🌐 Renaming project 项目名称在项目创建时设置(参见 [项目创建](/cloud/getting-started/deployment)),之后可以通过项目设置进行修改。 🌐 The project name is set at project creation (see [Project creation](/cloud/getting-started/deployment)) and can be modified afterwards via the project settings. 1. 在 *常规*选项卡的*基本信息*部分,点击 编辑按钮。 2. 在对话框中,在*项目名称*文本框中写上你选择的新项目名称。 3. 点击 **重命名** 按钮以确认项目名称的修改。 #### 添加 CMS 许可证密钥 {#adding-cms-license-key} 🌐 Adding a CMS license key 可以将 CMS 许可证密钥添加并连接到 Strapi Cloud 项目,以解锁整个项目环境中的额外 Strapi CMS 功能。通过许可证密钥可访问的 CMS 功能取决于所购买的许可证类型。有关更多信息和/或购买许可证,请参阅 [Strapi Pricing page](https://strapi.io/pricing-self-hosted) 。 :::note 如果你没有看到 *Strapi CMS 许可密钥* 部分,这可能意味着你的订阅是旧版订阅,并且不支持自定义 CMS 许可。这意味着你已经拥有一个会自动包含在你的项目中的许可。 🌐 If you don't see the *Strapi CMS license key* section, it probably means that your subscription is a legacy one and does not support custom CMS licenses. It means that you already have one that is automatically included on your project. ::: 1. 在 *Strapi CMS 许可密钥* 部分,点击 **添加许可** 按钮。 2. 在对话框中,将你的许可密钥粘贴到字段中。 3. 点击 **保存并部署** 按钮以使更改生效。 要从你的 Strapi Cloud 项目中移除 Strapi CMS 许可证,你可以点击 **取消关联许可证** 按钮。这也将移除对之前添加的许可证中包含的 CMS 功能的访问和使用权限。 🌐 To remove the Strapi CMS license from your Strapi Cloud project, you can click on the **Unlink license** button. This will also remove access and usage to the CMS features included in the previously added license. :::note 许可证密钥已应用于项目中的所有环境。 🌐 The license key is applied to all the environments in the project. ::: #### 修改 git 仓库和分支 {#modifying-git-repository--branch} 🌐 Modifying git repository & branch Strapi Cloud 项目的 GitHub 或 GitLab 仓库、分支和基础目录默认在创建项目时选择(参见 [创建项目](/cloud/getting-started/deployment))。在项目创建后,可以通过项目设置更新项目仓库或切换到其他 Git 提供商。 🌐 The GitHub or GitLab repository, branch and base directory for a Strapi Cloud project are by default chosen at the creation of the project (see [Creating a project](/cloud/getting-started/deployment)). After the project's creation, via the project settings, it is possible to update the project repository or switch to another git provider. :::caution 更新 git 仓库可能导致项目及其数据的丢失,例如如果选择了错误的仓库或旧仓库与新仓库之间的数据结构不匹配。 🌐 Updating the git repository could result in the loss of the project and its data, for instance if the wrong repository is selected or if the data schema between the old and new repository doesn't match. ::: 1. 在 *常规*选项卡的*已连接的 Git 仓库*部分,点击**更新仓库**按钮。你将被重定向到另一个界面。 2. (可选)如果你希望不仅更新仓库,还想切换到另一个 git 提供商,请点击界面右上角的 **切换 Git 提供商** 按钮。在返回 *更新仓库* 界面之前,你将被重定向到所选 git 提供商的授权设置。 3. 在*更新仓库*部分,填写两个可用的设置: | 设置名称 | 使用说明 | | --------------- | ------------------------------------------------------------------------ | | 账户 | 从下拉列表中选择一个账户。 | | 仓库 | 从下拉列表中选择一个仓库。 | 4. 在 *选择 Git 分支* 部分,为你的任何环境填写可用设置。请注意,每个环境的分支可以通过其自身的设置进行编辑,参见 [常规(环境)](#environments)。 | 设置名称 | 说明 | | --------------- | ------------------------------------------------------------------------ | | 分支 | 从下拉列表中选择一个分支。 | | 基本目录 | 在文本框中填写基本目录的路径。 | | 自动部署 | 勾选该框以在向所选分支推送新提交时自动触发新部署。取消勾选以禁用此选项。 | 5. 点击 **保存并部署** 按钮以使更改生效。 #### 转移项目所有权 {#transferring-project-ownership} 🌐 Transferring project ownership Strapi Cloud 项目的所有权可以转让给其他用户,只要他们是该项目的维护者。这可以由当前项目所有者主动发起,也可以由项目维护者提出请求。一旦所有权转让完成,该转让将是永久的,直到新所有者决定再次将所有权转让给另一位维护者。 🌐 The ownership of the Strapi Cloud project can be transferred to another user, as long as they're a maintainer of the project. It can either be at the initiative of the current project owner, or can be requested by a project maintainer. Once the ownership is transferred, it is permanent until the new owner decides to transfer the ownership again to another maintainer. :::prerequisites 要转让项目的所有权,必须满足以下要求: 🌐 For the ownership of a project to be transferred, the following requirements must be met: - 该项目当前不得有过期的卡和/或未支付的账单。 - 维护者必须已经填写了他们的账单信息。 - 该项目不得有任何现有的所有权转让在进行中。 请注意,当在订阅续订的同一天(即每月的1日)进行所有权转让时,转让可能会失败。如果当天转让失败,但所有先决条件已满足,你应等待几小时后再尝试。 🌐 Note that ownership transfers might fail when done the same day of subscription renewal (i.e. 1st of every month). If the transfer fails that day, but all prerequisites are met, you should wait a few hours and try again. ::: 1. 在 *常规*选项卡的*危险区域*部分,点击**转移所有权**按钮。 2. 在对话中: - 如果你是项目拥有者:通过点击其名字旁的 **...** > **转让所有权** 来选择应被转让所有权的维护者。 - 如果你是维护者:在列表中找到自己,然后点击与你的名字相关的 **...** > **转让所有权**。 3. 在新对话框中点击 **转移所有权** 按钮以确认转移/请求。 一封电子邮件将发送给两位用户。需要转让所有权或继承的人员必须点击电子邮件中的 **确认转让** 按钮。完成后,前所有者将收到一封确认电子邮件,确认转让已成功完成。 🌐 An email will be sent to both users. The person who needs to transfer the ownership or inherit it will have to click on the **Confirm transfer** button in the email. Once done, the previous owner will receive a confirmation email that the transfer has successfully been done. :::tip 只要所有权转移或请求尚未确认,就可以在选择维护者的同一对话框中选择取消。 🌐 As long as the ownership transfer or request hasn't been confirmed, there is the option to cancel in the same dialog that the maintainer was chosen. ::: :::note 一旦所有权转移完成,项目将与 Strapi Cloud 断开连接。作为新所有者,请确保前往项目设置的 *常规*选项卡以重新连接项目。 ::: #### 删除 Strapi Cloud 项目 {#deleting-a-strapi-cloud-project} 🌐 Deleting a Strapi Cloud project 你可以删除任何 Strapi Cloud 项目,但这是永久且不可逆的。相关的域名、部署和数据将被删除,项目的订阅将自动取消。 🌐 You can delete any Strapi Cloud project, but it will be permanent and irreversible. Associated domains, deployments and data will be deleted and the subscription for the project will automatically be canceled. 1. 在 *常规*选项卡的*危险区域*部分,点击**删除项目**按钮。 2. 在对话框中,选择删除项目的原因。 3. 通过点击 **删除项目** 按钮来确认删除你的项目。 ### 环境 {#environments} 🌐 Environments “环境”选项卡允许查看 Strapi Cloud 项目中配置的所有环境,以及创建新的环境。生产环境是默认环境,无法删除。可以根据项目的订阅计划创建其他环境,以便在 Strapi Cloud 项目的独立实例上更安全地工作(例如,一个暂存环境,用于在上线到生产环境前进行测试)。 :::note 你购买的附加环境的计费周期将与你的计划计费周期相匹配。 🌐 The billing cycle of additional environments you purchase will match the billing cycle of your plan. ::: 要创建一个新环境: 🌐 To create a new environment: 1. 点击 **添加新环境** 按钮。 2. 在设置步骤中,填写可用的设置: | 设置名称 | 说明 | | ---------------- | ------------------------------------------------------------------------ | | 环境名称 | (必填)为你的项目新环境写一个名称。 | | Git 分支 | (必填)为你的新环境选择正确的分支。 | | 基础目录 | 写入你的新环境的基础目录名称。 | | 推送时部署 | 勾选此框以在向所选分支推送更改时自动触发部署。若未启用,则需要手动部署最新更改。 | | 导入变量 | 勾选此框从现有环境导入变量名称。值不会被导入,所有变量将保持为空。 | 3. 点击 **确认** 以继续到结账步骤。 4. 查看环境价格、适用税费和按比例调整。 5. 点击 **添加环境** 按钮以创建项目的新环境。然后,你将被重定向到你的 *项目仪表板*,在那里你可以跟踪新环境的创建和首次部署。 :::note 如果在环境创建过程中发生错误,进度指示器将停止并显示错误消息。你将在失败步骤旁看到一个**重试**按钮,允许你重新启动创建过程。 🌐 If an error occurs during the environment creation, the progress indicator will stop and display an error message. You will see a **Retry** button next to the failed step, allowing you to restart the creation process. ::: ### 账单与发票 {#billing--invoices} 🌐 Billing & Invoices “ *账单与发票*”标签显示你的订阅详情以及你 Strapi Cloud 项目的所有发票清单。 :::note 只有项目所有者可以访问*账单与发票*选项卡。维护者无法访问此选项卡。 🌐 Only project owners can access the *Billing & Invoices* tab. Maintainers do not have access to this tab. ::: 通过此标签,你可以: 🌐 Through this tab, you can: - 点击 **更改** 按钮将被重定向到 *计划* 标签,在那里你可以更改你的订阅计划或计费周期(参见 [计划](#plans)), - 点击 **编辑** 按钮以设置新的支付方式(参见[相关文档](/cloud/account/account-billing))。 :::note 你可以通过直接从此页面选择支付方式,将专用卡附加到你的项目,从而使用不同的卡管理你的订阅。 🌐 You can attach a dedicated card to your project by choosing the payment method directly from this page, allowing you to manage your subscriptions with different cards. ::: 该选项卡还列出了你 Strapi Cloud 项目的所有发票及其状态。 🌐 The tab also lists all invoices for your Strapi Cloud project and their status. 发票可以具有以下任何状态: 🌐 Invoices can have any of the following statuses: - 已付款:付款已完成,发票已可用,无需其他操作。 - 待付款:发票尚未完成或验证 - 付款到期:付款未成功,需要修复 - 未支付:付款失败,且不会自动重试 - 作废:发票已被取消。 :::tip 点击 ![下载图标](/img/assets/icons/download.svg) 图标下载发票。 🌐 Click the ![download icon](/img/assets/icons/download.svg) icon to download an invoice. ::: :::strapi Invoices are also available in your profile settings. 在 *个人资料 > 发票* 选项卡中,你将找到所有项目的完整发票列表。欢迎查看[专用文档](/cloud/account/account-billing#account-invoices)。 🌐 In the *Profile > Invoices* tab, you will find the complete list of invoices for all your projects. Feel free to check the [dedicated documentation](/cloud/account/account-billing#account-invoices). ::: ### 计划 {#plans} 🌐 Plans “Plans” 标签显示可用的 Strapi Cloud 计划概览,并允许你更改当前计划或账单周期。 :::info 如果你当前的计划被标记为*旧版*,你将能够切换到新的计划(请参阅[降级部分](#downgrading-to-another-plan))。一旦你切换,你将无法再访问之前的计划。 🌐 If your current plan is labeled as *legacy*, you will be able to sidegrade to a new plan (see [downgrade section](#downgrading-to-another-plan)). Once you sidegrade, you will no longer have access to your previous plan. ::: #### 升级到其他方案 {#upgrading-to-another-plan} 🌐 Upgrading to another plan 计划升级是即时的,并且可以通过每个项目的项目设置进行管理。 🌐 Plan upgrades are immediate and can be managed, for each project, via the project settings. 要将你当前的计划升级到更高级别,请: 🌐 To upgrade your current plan to a higher one: 1. 在项目设置的 *计划*选项卡中,选择按月或按年计费的频率,然后点击你想升级的计划的**升级**按钮。 2. 在打开的窗口中,查看升级的付款详情和条款。 a.(可选)点击 **编辑** 按钮以选择其他付款方式。 b.(可选)点击 **我有折扣码**,在字段中输入你的折扣码,然后点击 **应用** 按钮。 3. 点击 **升级到 [plan name]** 按钮以确认升级。项目将自动重新部署。 #### 降级到另一个计划 {#downgrading-to-another-plan} 🌐 Downgrading to another plan 每个项目的计划降级可以通过项目设置进行管理。然而,降级不会立即生效:当前计划将继续有效,直到当前计费周期结束。 🌐 Plan downgrades can be managed, for each project, via the project settings. Downgrades are, however, not immediately effective: the current plan will remain active until the end of the current billing period. :::caution 在降级之前,请确保检查你的 Strapi Cloud 项目的使用情况:如果你当前的使用量超出了较低套餐的限制,你将面临超额收费的风险。你还可能会失去某些功能的访问权限:例如,降级到 Starter 计划将导致你项目的所有备份丢失。有关更多信息,请参阅 [计费与使用信息](/cloud/getting-started/usage-billing)。 🌐 Make sure to check the usage of your Strapi Cloud project before downgrading: if your current usage exceeds the limits of the lower plan, you are taking the risk of getting charged for overages. You may also lose access to some features: for example, downgrading to the Starter plan would result in the loss of all your project's backups. Please refer to [Information on billing & usage](/cloud/getting-started/usage-billing) for more information. 还请注意,如果你有额外的付费环境,则无法降级。你需要先删除计划基本价格中未包含的所有额外环境(请参见[清理和删除环境](#resetting--deleting-environment)),然后才能安排降级。当从 Business 降级到 Pro 时,额外包含的环境将在降级生效时自动删除。 🌐 Note also that you cannot downgrade if you have additional paid environments. You will first need to delete all additional environments that were not included in the base price of your plan (see [Clearing and deleting environments](#resetting--deleting-environment)) before you can schedule a downgrade. When downgrading from Business to Pro, the additional included environment will automatically be deleted when the downgrade takes effect. ::: 要将你当前的计划降级到较低的计划: 🌐 To downgrade your current plan to a lower one: 1. 在项目设置的 *计划*选项卡中,选择按月或按年计费频率,然后点击你想降级到的计划的**降级**按钮。 2. 在打开的窗口中,查看降级条款。 3. 点击 **降级** 按钮以确认降级。项目将自动重新部署。 :::tip 降级将在当前计费周期结束时生效。在更改待处理期间,你可以取消已安排的降级并继续使用当前计划。 🌐 Downgrades are effective at the end of the current billing period. Whilst the change is pending, you can cancel the scheduled downgrade and stay on your current plan. ::: #### 更改账单周期 {#changing-billing-cycle} 🌐 Changing billing cycle 你可以随时在每月和每年计费之间切换项目的计费周期。虽然项目计划和附加组件可以根据你的计费周期按月或按年计费,但超额部分始终按月计费。 🌐 You can switch your project's billing cycle between monthly and yearly billing at any time. While project plans and addons can either be billed monthly or yearly depending on your billing cycle, overages are always billed monthly. 要更改你的计费周期: 🌐 To change your billing cycle: 1. 在项目设置的 *计划*选项卡中,使用计划部分顶部的切换按钮在按月和按年计费之间切换。 2. 点击你当前计划的 **切换到[每月/每年]计费** 按钮。 3. 在打开的窗口中,查看计费周期更改的条款。 4. 点击**确认切换**以确认更改。 :::note 当从年度计费切换到每月计费时,你的计划将在下一个续订日期之前保持年度周期。在更改待处理期间,你可以取消计划中的更改并继续使用当前的计费周期。然而,当从每月计费切换到年度计费时,更改会立即生效。 🌐 When switching from yearly to monthly billing, your plan will remain on its yearly cycle until your next renewal date. Whilst the change is pending, you can cancel the scheduled change and stay on your current billing cycle. When switching from monthly to yearly, however, the change is immediate. ::: ## 环境级设置 {#environment-level-settings} 🌐 Environment-level settings 在项目的环境设置中,你首先需要使用下拉菜单选择要配置其设置的环境。根据所选的环境,通常有3到4个可用的选项卡: 🌐 In the project's environments' settings, you first need to select the environment whose settings you would like to configure, using the dropdown. Depending on the chosen environment, there are 3 to 4 tabs available: - [*配置*](#configuration), - [*备份*](#backups),仅适用于生产环境, - [*字段*](#domains), - 和 [*变量*](#variables)。 ### 配置 {#configuration} 🌐 Configuration 环境级设置的 *配置*选项卡使你能够检查和更新项目的以下选项: - *基本信息*,请参见: - 你的 Strapi Cloud 项目的环境名称。环境名称在创建时设置,之后无法修改。 - 环境的 Node 版本:要更改项目的 Node 版本(参见 [修改 Node 版本](#modifying-node-version))。 - 应用用于环境的内部名称,这对于调试和支持目的可能非常有用。 - *已连接的分支*: 用于更改环境所使用的 GitHub 仓库分支(请参阅 [编辑 Git 分支](#editing-git-branch))。还可以启用/禁用“推送时部署”选项。 - *环境数据*:从同一项目内的其他环境传输数据(参见 [在环境之间传输数据](#transferring-data-between-environments))或在保留当前环境设置的同时删除其所有数据和资源(参见 [清空环境](#clearing-an-environment))。 - *危险区域*:永久删除额外环境(请参见 [删除环境](#deleting-an-environment))。 #### 修改 Node 版本 {#modifying-node-version} 🌐 Modifying Node version 环境的 Node 版本基于创建项目时选择的版本(参见[创建项目](/cloud/getting-started/deployment)),通过高级设置。之后可以为任何环境切换到其他 Node 版本。 🌐 The environment's Node version is based on the one chosen at the creation of the project (see [Creating a project](/cloud/getting-started/deployment)), through the advanced settings. It is possible to switch to another Node version afterwards, for any environment. 1. 在 *配置*选项卡的*基本信息*部分,点击*节点版本*的编辑 按钮。 2. 在对话框中使用 *Node 版本* 下拉菜单,点击你选择的版本。 3. 点击 **保存**,或者如果你希望更改立即生效,就点击 **保存并部署**。 :::tip 在部署之前,确保你在 Strapi 项目中配置的 Node 版本与项目仪表板中显示的 Node 版本匹配。 🌐 Ensure the Node version configured in your Strapi project matches the Node version shown in your project’s dashboard before deploying. ::: :::note Package manager version 项目设置中未设置包管理器版本。在构建期间,Strapi Cloud 会根据项目的锁文件检测使用哪个包管理器,默认使用 npm。 🌐 The package manager version is not set in the project settings. During the build, Strapi Cloud detects which package manager to use from your project's lockfile, defaulting to npm. - 对于 `yarn` 和 `pnpm`,在构建环境中已启用 [Corepack](https://github.com/nodejs/corepack#readme),因此你在 `package.json` `packageManager` 字段中固定的版本会被自动遵循。如果未固定版本,则使用 Corepack 随附的默认版本。 - `npm` 不受 Corepack 管理:它使用随所选 Node.js 版本打包的版本。 ::: #### 编辑 Git 分支 {#editing-git-branch} 🌐 Editing Git branch 1. 在 *编辑分支* 对话框中,编辑可用的设置。请注意,可以通过项目设置同时编辑所有环境的分支,详见 [常规](#general)。 | 设置名称 | 说明 | | --------------- | ------------------------------------------------------------------------ | | 选择的分支 | (必填)从下拉列表中选择一个分支。 | | 基础目录 | 在文本框中填写基础目录的路径。 | | 在每次向此分支推送提交时部署项目 | 选中此框可在向所选分支推送新提交时自动触发新部署。取消选中以禁用该选项。 | 2. 点击 **保存并部署** 按钮以使更改生效。 #### 在环境之间传输数据 {#transferring-data-between-environments} 🌐 Transferring data between environments 数据传输功能允许你将整个 CMS 内容(数据库和资源)从一个环境传输到同一 Strapi Cloud 项目中的另一个环境。这对于在次要环境中使用最新的生产数据测试更改,或在将内容投入生产之前在次要环境中准备和排练内容非常有用。 🌐 The data transfer feature allows you to transfer the entire CMS content (database and assets) from one environment to another within the same Strapi Cloud project. This is useful for testing changes in a secondary environment with up-to-date production data, or for preparing and staging content in a secondary environment before taking it to production. 在环境之间传输数据目前存在以下限制: 🌐 Transferring data between environments currently comes with the following limitations: - 你只能转移到辅助环境(不能转移到生产环境)。 - 只有项目所有者才能发起和管理正在进行的转移。 - 暂停的项目无法发起转账。 :::caution Data transfers are destructive 将数据传输到某个环境将永久覆盖目标环境中的所有现有数据和资源。源环境的数据不受影响,并且在传输期间可以访问其 CMS。环境设置(例如变量和域)不会受到传输的影响。 🌐 Transferring data to an environment will permanently overwrite all existing data and assets in the target environment. The source environment's data remains unaffected, and its CMS can be accessed during the transfer. Environment settings (such as variables and domains) are not affected by the transfer. ::: 将数据传输到次要环境: 🌐 To transfer data to a secondary environment: 1. 创建并部署源和目标[环境](#environments)。 2. 在 *配置*选项卡的*环境数据*部分,点击**导入数据**按钮。 3. 在打开的模态窗口中,从下拉列表中选择源环境。只有完全创建并部署的环境可用作源。 4. 点击 **导入数据** 以继续,然后按照步骤确认传输。 5. 一旦启动,你将被重定向到环境的仪表板,在那里你可以监控传输的进度。传输完成后,仪表板将刷新,显示正在进行的和历史的部署。 :::note 在传输进行时,目标环境的 CMS 将无法访问。你可以取消正在进行的传输,但这将使目标环境为空。如果在传输过程中发生错误,你将有选项重新尝试或取消。 🌐 The CMS of the target environment will be inaccessible whilst the transfer is ongoing. You can cancel an ongoing transfer, but this will leave the target environment empty. If an error occurs during the transfer, you will have the option to retry or cancel. ::: #### 清理和删除环境 {#resetting--deleting-environment} 🌐 Clearing and deleting environments 你可以清除任何环境中的数据库内容和资源,包括生产环境,或永久删除额外的环境。默认的生产环境无法被删除。 🌐 You can clear database content and assets from any environment, including the production environment, or permanently delete additional environments. The default production environment cannot be deleted. :::note 在商务计划中,你无法删除包含的次要环境。但是,你可以清除其内容。 🌐 On the Business plan, you cannot delete the included secondary environment. You can, however, clear its content. ::: 清除和删除是永久性的,仅对项目所有者可用。项目暂停期间,你无法启动环境清除。 🌐 Clearing and deleting are permanent and only available to the project owner. You cannot initiate an environment clearing while the project is suspended. ##### 清理环境 {#clearing-an-environment} 🌐 Clearing an environment :::warning Environment clearing is destructive 清除环境将永久删除其所有现有数据和资源。环境设置(例如变量和域)不会受到清除的影响。 🌐 Clearing an environment will permanently delete all its existing data and assets. Environment settings (such as variables and domains) are not affected by the clearing. ::: 清除环境: 🌐 To clear an environment: 1. 在项目设置中,选择环境,然后打开 *配置*选项卡。 2. 在*环境数据*部分,点击**清除环境**。 3. 在确认对话框中,输入环境名称,然后确认以开始清理。系统会将你重定向到环境仪表板,你可以在此跟踪进度。 清理进行中时: 🌐 While clearing is in progress: - 环境 CMS 不可用。 - 你无法触发该环境的部署或查看其日志。 - 需要部署的计划升级、计划降级和设置更改暂时无法使用。 如果清除失败,请在环境仪表板上点击 **重试** 以再次运行该操作。 🌐 If clearing fails, click **Retry** on the environment dashboard to run the operation again. ##### 删除环境 {#deleting-an-environment} 🌐 Deleting an environment 1. 在 *配置*选项卡的*危险区域*部分,点击**删除环境**按钮。 2. 在文本框中输入你的*环境名称*。 3. 点击 **删除环境** 按钮以确认删除。 ### 备份 {#backups} 🌐 Backups “ *备份*”标签会通知你 Strapi Cloud 项目的最新备份状态和日期。所有现有 Strapi Cloud 项目关联的数据库确实会自动备份(专业版计划为每周一次,企业版计划为每日一次)。备份会保留 28 天。此外,你还可以创建单次手动备份。 :::note Notes - 在入门计划的 Strapi Cloud 项目中,备份功能不可用。你需要升级到专业版或企业版计划,才能启用自动备份并使用手动备份选项。 - 备份仅包括你默认生产环境的数据库。上传到你的项目的资源和任何辅助环境的数据库不包括在内。 - 项目首次成功部署后不久,手动备份选项就会可用。 ::: :::tip 对于在2023年10月备份功能发布之前创建的项目,首次备份将在下次项目部署时自动触发。 🌐 For projects created before the release of the Backup feature in October 2023, the first backup will automatically be triggered with the next deployment of the project. ::: #### 创建手动备份 {#creating-a-manual-backup} 🌐 Creating a manual backup 要创建手动备份,在 *备份*部分,点击**创建备份**按钮。 手动备份应立即开始,在备份完成之前,恢复或创建其他备份将被禁用。 🌐 The manual backup should start immediately, and restoration or creation of other backups will be disabled until the backup is complete. :::caution 在创建新的手动备份时,任何现有的手动备份都将被删除。你一次只能拥有一个手动备份。 🌐 When creating a new manual backup, any existing manual backup will be deleted. You can only have one manual backup at a time. ::: #### 恢复备份 {#restoring-a-backup} 🌐 Restoring a backup 如果你需要恢复项目的备份: 🌐 If you need to restore a backup of your project: 1. 在 *备份*部分,点击**恢复备份**按钮。 2. 在对话框中,从*选择备份*下拉菜单中选择你项目的可用备份(自动备份或手动备份)之一。 3. 点击对话框中的 **恢复** 按钮。恢复完成后,你的项目将回到所选备份时的状态。你将能够在 *备份* 标签中看到恢复的时间戳和所恢复的备份。 4. 将显示上次完成恢复的时间戳,以帮助你追踪项目上次恢复的时间。 #### 正在下载备份 {#downloading-a-backup} 🌐 Downloading a backup 如果你需要下载项目的备份: 🌐 If you need to download a backup of your project: 1. 在 *备份*部分,点击**下载备份**按钮。 2. 在对话框中,从*选择备份*下拉菜单中选择你项目的可用备份(自动备份或手动备份)之一。 3. 点击对话框中的 **下载** 按钮,以 `.sql` 格式下载所选备份的归档文件。 :::note 备份文件将仅包含你默认生产环境的数据库。它不会包含资源或任何其他环境的数据库。 🌐 The backup file will include only the database of your default Production environment. It will not include assets or any other environment databases. ::: ### 域名 {#domains} 🌐 Domains “Domains”选项卡使你能够管理域并连接新的域名。 你 Strapi Cloud 项目的所有现有域都列在 *域*标签中。对于每个域,你可以: - 查看其当前状态: - 活跃:该域名当前已确认并处于活跃状态 - 待处理:域名转移正在进行中,正在等待 DNS 变更生效 - 失败:由于发生错误,域更改请求未完成 - 点击 编辑按钮以访问域名设置 - 点击 删除按钮以删除域 #### 连接自定义域名 {#connecting-a-custom-domain} 🌐 Connecting a custom domain 默认域名由两个随机生成的单词加上一个哈希组成。它们可以被你选择的任何自定义域名替换。 🌐 Default domain names are made of 2 randomly generated words followed by a hash. They can be replaced by any custom domain of your choice. 1. 点击 **连接新域名** 按钮。 2. 在打开的窗口中,填写以下字段: | 设置名称 | 说明 | | --- | --- | | 域名 | 输入新的域名(例如 *custom-domain-name.com*) | | 主机名 | 输入主机名(即终端用户在浏览器中输入的地址,或通过 API 调用的地址)。 | | 目标 | 输入目标(即用户输入主机名时重定向到的实际地址)。 | | 设置为默认域名 | 勾选此框以将新域名设置为默认域名。 | 3. 点击 **保存并部署** 以使更改生效。 :::tip 要完成自定义域名的设置,请在你的域名注册商或托管平台的设置中,将目标值(例如 `proud-unicorn-123456af.strapiapp.com`)作为 CNAME 别名添加到你域名的 DNS 记录中。 🌐 To finish setting up your custom domain, in the settings of your domain registrar or hosting platform, please add the Target value (e.g., `proud-unicorn-123456af.strapiapp.com`) as a CNAME alias to the DNS records of your domain. ::: :::info Custom domains and assets 使用自定义域名时,这些域名不适用于已上传资源的 URL。已上传的资源保持 Strapi Cloud 基于项目的 URL。 🌐 When using custom domains, these domains do not apply to the URLs of uploaded assets. Uploaded assets keep the Strapi Cloud project-based URL. 这意味着,如果你的自定义域托管在 `https://my-custom-domain.com`,并且你的 Strapi Cloud 项目名称是 `my-strapi-cloud-instance`,API 调用仍将返回类似 `https://my-strapi-cloud-instance.media.strapiapp.com/example.png` 的 URL。 🌐 This means that, if your custom domain is hosted at `https://my-custom-domain.com` and your Strapi Cloud project name is `my-strapi-cloud-instance`, API calls will still return URLs such as `https://my-strapi-cloud-instance.media.strapiapp.com/example.png`. 通过 REST 或 GraphQL 进行的媒体库查询总是返回 Strapi Cloud 上的项目媒体域。如果你从自托管项目迁移,媒体 URL 将不再与你自己的域或 CDN 匹配。请计划使用 API 返回的绝对 URL,或调整前端以允许 Strapi Cloud 媒体域(更多详情请参见 [Cloud Fundamentals](/cloud/cloud-fundamentals))。 🌐 Media library queries over REST or GraphQL always return the project media domain on Strapi Cloud. If you move from a self-hosted project, media URLs will no longer match your own domain or CDN. Plan to use the absolute URLs returned by the API, or adjust your frontend to allow the Strapi Cloud media domain (see [Cloud Fundamentals](/cloud/cloud-fundamentals) for more details). ::: ### 变量 {#variables} 🌐 Variables 环境变量(更多信息请参见 [CMS 文档](/cms/configurations/environment))用于配置你的 Strapi 应用的环境,例如数据库连接。 🌐 Environment variables (more information in the [CMS Documentation](/cms/configurations/environment)) are used to configure the environment of your Strapi application, such as the database connection. :::note 自定义变量在运行时可用于 Strapi 服务器。以 `STRAPI_ADMIN_` 为前缀的变量不会暴露给 Strapi Cloud 上的管理前端。 🌐 Custom variables are available to the Strapi server at runtime. Variables prefixed with `STRAPI_ADMIN_` are not exposed to the admin front end on Strapi Cloud. ::: 在 *变量*标签中列出了你 Strapi Cloud 项目的默认和自定义环境变量。每个变量由一个*名称*和一个*值*组成。 #### 管理环境变量 {#managing-environment-variables} 🌐 Managing environment variables 将鼠标悬停在环境变量上,无论是默认的还是自定义的,都会显示以下可用选项: 🌐 Hovering on an environment variable, either default or custom, displays the following available options: - **显示值**以使用变量的实际值替换`*`字符。 - **复制到剪贴板**以复制变量的值。 - **操作**以访问 编辑和 删除按钮。 - 编辑默认变量时,*名称*无法修改,*值*只能通过 生成值按钮自动生成。别忘了 **保存**,如果你希望更改立即生效,则选择 **保存并部署**。 - 在编辑自定义变量时,*名称*和*值*都可以通过输入新内容或使用 生成值按钮来修改。别忘了**保存**,如果希望更改立即生效,请选择**保存并部署**。 - 删除变量时,系统会要求你通过选择 **保存** 或 **保存并部署** 来确认,如果你希望更改立即生效请选择 **保存并部署**。 :::tip 使用搜索栏可以更快地在列表中找到环境变量! 🌐 Use the search bar to find more quickly an environment variable in the list! ::: #### 创建自定义环境变量 {#creating-custom-environment-variables} 🌐 Creating custom environment variables 可以为 Strapi Cloud 项目创建自定义环境变量。创建或编辑环境变量后,请确保重新部署你的项目。 🌐 Custom environment variables can be created for the Strapi Cloud project. Make sure to redeploy your project after creating or editing an environment variable. 1. 在*自定义环境变量*部分,点击**添加变量**按钮。 2. 在同名字段中填写新环境变量的*名称*和*值*。或者,你可以点击 图标自动生成名称和值。 3. (可选)点击 **添加另一个** 来直接创建一个或多个其他自定义环境变量。 4. 点击 **保存** 按钮以确认创建自定义环境变量。要立即应用更改,请点击 **保存并部署**。 # 管理面板定制 Source: https://strapi.nodejs.cn/cms/admin-panel-customization # 管理面板定制 {#admin-panel-customization} 🌐 Admin panel customization 管理员面板可以通过编辑 `src/admin/app` 并使用 `extensions` 文件夹来替换徽标、网站图标、本地语言、翻译、主题、打包器或编辑器,从而定制以匹配你的品牌。 Strapi 的 **前端部分** 有关以下区分的说明:
  • Strapi 管理面板(Strapi 的前端),
  • Strapi 服务器(Strapi 的后端),
  • 以及面向终端用户的 Strapi 驱动应用的前端,
请参考 [开发入门](/cms/customization)。 称为管理面板。管理面板提供图形用户界面,帮助你构建和管理通过内容 API 可访问的内容。要了解管理面板的概览,请参考 [入门 > 管理面板](/cms/features/admin-panel) 页面。 从开发者的角度来看,Strapi 的管理面板是一个基于 React 的单页应用,它封装了 Strapi 应用的所有功能和已安装的插件。 🌐 From a developer point of view, Strapi's admin panel is a React-based single-page application that encapsulates all the features and installed plugins of a Strapi application. 管理员面板的自定义通过调整 `src/admin/app` 文件或 `src/admin` 文件夹中包含的其他文件的代码来完成(参见 [项目结构](/cms/project-structure))。通过这样做,你可以: 🌐 Admin panel customization is done by tweaking the code of the `src/admin/app` file or other files included in the `src/admin` folder (see [project structure](/cms/project-structure)). By doing so, you can: - 自定义管理面板的某些部分,以更好地体现你的品牌标识(徽标、网站图标)或语言。 - 替换管理面板的其他部分,例如富文本编辑器和打包器。 - 扩展主题或管理​​面板以添加新功能或自定义现有用户界面。 :::strapi Plugins and Admin Panel API 除了本节中详细说明的支持自定义功能外,你还可以更进一步,创建可以使用 [管理员面板 API](/cms/plugins-development/admin-panel-api) 的插件。 🌐 In addition to supported customizations detailed in this section, you can go further and create plugins that tap into the [Admin Panel API](/cms/plugins-development/admin-panel-api). ::: ## 一般考虑 {#general-considerations} 🌐 General considerations :::prerequisites 在更新代码以自定义管理面板之前: 🌐 Before updating code to customize the admin panel: - 将默认的 `app.example.tsx|js` 文件重命名为 `app.ts|js`。 - 在 `/src/admin/` 中创建一个新的 `extensions` 文件夹。 - 如果你想在开发时实时看到你的更改生效,请确保管理员面板服务器正在运行(通常如果你没有更改管理员面板的默认[主机、端口和路径](/cms/configurations/admin-panel#admin-panel-server),可以使用 `yarn develop` 或 `npm run develop` 命令来启动)。 ::: 大多数基本的管理面板自定义将在 `/src/admin/app` 文件中完成,该文件包含一个 `config` 对象。 🌐 Most basic admin panel customizations will be done in the `/src/admin/app` file, which includes a `config` object. 任何 `config` 对象使用的文件(例如,自定义徽标)都应放置在 `/src/admin/extensions/` 文件夹中,并在 `/src/admin/app.js` 中导入。 🌐 Any file used by the `config` object (e.g., a custom logo) should be placed in a `/src/admin/extensions/` folder and imported inside `/src/admin/app.js`. :::tip Tip: Hot reloading while developing 在 Strapi 5 中,服务器默认以 `watch-admin` 模式运行,因此每当你更改其代码时,管理员面板会自动重新加载。这简化了管理员面板和前端插件的开发。要禁用此功能,请运行 `yarn develop --no-watch-admin`(参见 [CLI 参考](/cms/cli#strapi-develop))。 🌐 In Strapi 5, the server runs in `watch-admin` mode by default, so the admin panel auto-reloads whenever you change its code. This simplifies admin panel and front-end plugins development. To disable this, run `yarn develop --no-watch-admin` (see [CLI reference](/cms/cli#strapi-develop)). ::: 在部署之前,需要通过从项目的根目录运行以下命令来构建管理面板: 🌐 Before deployment, the admin panel needs to be built, by running the following command from the project's root directory: ```sh yarn build ``` ```sh npm run build ``` 这将替换位于 `./build` 的文件夹内容。访问 [http://localhost:1337/admin](http://localhost:1337/admin) 以确保自定义设置已被考虑。 :::note Note: Admin panel extensions vs. plugins extensions 默认情况下,Strapi 项目在 `/src` 中已经包含另一个 `extensions` 文件夹,但它仅用于插件扩展(参见 [插件扩展](/cms/plugins-development/plugins-extension))。 🌐 By default, Strapi projects already contain another `extensions` folder in `/src` but it is for plugins extensions only (see [Plugins extension](/cms/plugins-development/plugins-extension)). ::: ## 可用的自定义选项 {#available-customizations} 🌐 Available customizations `/src/admin/app` 的 `config` 对象接受以下参数: 🌐 The `config` object of `/src/admin/app` accepts the following parameters: | 参数 | 类型 | 描述 | | --- | --- | --- | | `auth` | 对象 | 接受一个 `logo` 键来替换登录屏幕上的默认 Strapi 徽标 | | `head` | 对象 | 接受一个 `favicon` 键来替换默认的 Strapi 网站图标 | | `locales` | 字符串数组 | 定义可用的语言环境 | | `translations` | 对象 | 扩展翻译 | | `menu` | 对象 | 接受一个 `logo` 键以更改主导航中的徽标 | | `theme.light` 和 `theme.dark` | 对象 | 覆盖明亮和黑夜间模式的主题属性 | | `tutorials` | 布尔值 | 切换视频教程的显示 | | `notifications` | 对象 | 接受 `releases` 键(布尔值)以切换关于新版本通知的显示 |
点击以下任意卡片以获取有关特定主题的更多详细信息: - [标志](/cms/admin-panel-customization/logos): 更新管理员面板中显示的徽标,以匹配你自己的品牌。 - [网站图标](/cms/admin-panel-customization/favicon): 更新网站图标以匹配你自己的品牌。 - [本地化与翻译](/cms/admin-panel-customization/locales-translations): 定义区域设置并扩展管理员面板中可用的翻译。 - [富文本编辑器](/cms/admin-panel-customization/wysiwyg-editor): 了解更多关于替换内置富文本编辑器的可能策略。 - [打包器](/cms/admin-panel-customization/bundlers): 在 Vite 和 webpack 打包工具之间进行选择并进行配置。 - [主题扩展](/cms/admin-panel-customization/theme-extension): 学习扩展管理面板内置主题的基础知识。 - [管理面板扩展](/cms/admin-panel-customization/extension): 学习扩展管理面板的基础知识。 ## 基本示例 {#basic-example} 🌐 Basic example 以下是管理面板基本自定义的示例: 🌐 The following is an example of a basic customization of the admin panel: ```jsx title="/src/admin/app.js" config: { // Replace the Strapi logo in auth (login) views auth: { logo: AuthLogo, }, // Replace the favicon head: { favicon: favicon, }, // Add a new locale, other than 'en' locales: ["fr", "de"], // Replace the Strapi logo in the main navigation menu: { logo: MenuLogo, }, // Override or extend the theme theme: { // overwrite light theme properties light: { colors: { primary100: "#f6ecfc", primary200: "#e0c1f4", primary500: "#ac73e6", primary600: "#9736e8", primary700: "#8312d1", danger700: "#b72b1a", }, }, // overwrite dark theme properties dark: { // ... }, }, // Extend the translations translations: { fr: { "Auth.form.email.label": "test", Users: "Utilisateurs", City: "CITY (FRENCH)", // Customize the label of the Content Manager table. Id: "ID french", }, }, // Disable video tutorials tutorials: false, // Disable notifications about new Strapi releases notifications: { releases: false }, }, bootstrap() {}, }; ``` 如果 TypeScript 报告导入的图片文件缺少模块声明,请在 `/src/admin/tsconfig.json` 中包含 Vite 的客户端类型: 🌐 If TypeScript reports missing module declarations for imported image files, include Vite's client types in `/src/admin/tsconfig.json`: ```json title="/src/admin/tsconfig.json" { "compilerOptions": { "types": ["vite/client"] } } ``` ```ts title="/src/admin/app.ts" config: { // Replace the Strapi logo in auth (login) views auth: { logo: AuthLogo, }, // Replace the favicon head: { favicon: favicon, }, // Add a new locale, other than 'en' locales: ["fr", "de"], // Replace the Strapi logo in the main navigation menu: { logo: MenuLogo, }, // Override or extend the theme theme: { dark:{ colors: { alternative100: '#f6ecfc', alternative200: '#e0c1f4', alternative500: '#ac73e6', alternative600: '#9736e8', alternative700: '#8312d1', buttonNeutral0: '#ffffff', buttonPrimary500: '#7b79ff', // you can see other colors in the link below }, }, light:{ // you can see the light color here just like dark colors https://github.com/strapi/design-system/blob/main/packages/design-system/src/themes/lightTheme/light-colors.ts }, }, }, // Extend the translations // you can see the traslations keys here https://github.com/strapi/strapi/blob/develop/packages/core/admin/admin/src/translations translations: { fr: { "Auth.form.email.label": "test", Users: "Utilisateurs", City: "CITY (FRENCH)", // Customize the label of the Content Manager table. Id: "ID french", }, }, // Disable video tutorials tutorials: false, // Disable notifications about new Strapi releases notifications: { releases: false }, }, bootstrap() {}, }; ``` :::strapi Detailed examples in the codebase * 你可以查看完整的翻译键,例如要更改欢迎消息,请访问 [GitHub](https://github.com/strapi/strapi/blob/develop/packages/core/admin/admin/src/translations)。 * 明暗颜色也可以在 [GitHub](https://github.com/strapi/design-system/tree/main/packages/design-system/src/themes) 上找到。 ::: # 管理面板打包器 Source: https://strapi.nodejs.cn/cms/admin-panel-customization/bundlers # 管理面板打包器 {#admin-panel-bundlers} 🌐 Admin panel bundlers 支持的 JavaScript 打包工具会影响构建和开发流程。 Strapi 的 [管理面板](/cms/admin-panel-customization) 是一个基于 React 的单页应用,封装了 Strapi 应用的所有功能和已安装的插件。你的 Strapi 5 应用可以使用两种不同的打包工具,[Vite](#vite)(默认工具)和 [webpack](#webpack)。这两种打包工具都可以根据你的需求进行配置。 🌐 Strapi's [admin panel](/cms/admin-panel-customization) is a React-based single-page application that encapsulates all the features and installed plugins of a Strapi application. 2 different bundlers can be used with your Strapi 5 application, [Vite](#vite) (the default one) and [webpack](#webpack). Both bundlers can be configured to suit your needs. :::info 为了简化,以下文档提到 `strapi develop` 命令,但实际上你可能会根据所选择的软件包管理器运行 `yarn develop` 或 `npm run develop` 来使用它的别名。 🌐 For simplification, the following documentation mentions the `strapi develop` command, but in practice you will probably use its alias by running either `yarn develop` or `npm run develop` depending on your package manager of choice. ::: ## 快 {#vite} 🌐 Vite 在 Strapi 5 中, [Vite](https://vitejs.dev/) 是 Strapi 用于构建管理面板的默认打包器。因此,当你运行 `strapi develop` 命令时,将默认使用 Vite。 为了扩展 Vite 的使用,在 `/src/admin/vite.config` 内定义一个扩展其配置的函数: 🌐 To extend the usage of Vite, define a function that extends its configuration inside `/src/admin/vite.config`: ```js title="/src/admin/vite.config.js" const { mergeConfig } = require("vite"); module.exports = (config) => { // Important: always return the modified config return mergeConfig(config, { resolve: { alias: { "@": "/src", }, }, }); }; ``` ```ts title="/src/admin/vite.config.ts" // Important: always return the modified config return mergeConfig(config, { resolve: { alias: { "@": "/src", }, }, }); }; ``` :::tip Strapi 还支持 Vite 配置文件的 `.mts` 文件扩展名(`vite.config.mts`),适用于在其 `package.json` 中使用显式 ESM 模块解析的项目。这很有用,因为 Vite 的 CJS Node API 是 [deprecated since Vite 6](https://v6.vite.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated) ,并将在未来的版本中被移除。 ::: ## Webpack 在 Strapi 5 中,默认的打包工具是 Vite。要使用 [webpack](https://webpack.js.org/) 作为打包工具,你需要将其作为选项传递给 `strapi develop` 命令: ```bash strapi develop --bundler=webpack ``` :::prerequisites 如果你打算自定义 webpack,请从项目根目录中的示例文件开始。重命名: 🌐 If you plan to customize webpack, start from the example file in your project root. Rename: - `webpack.config.example.js` → `webpack.config.js`(JavaScript) - 或 `webpack.config.example.ts` → `webpack.config.ts`(TypeScript) 当你运行 `strapi develop --bundler=webpack` 时,Strapi 会自动选择 `webpack.config.js` 或 `webpack.config.ts`。 🌐 Strapi will pick up `webpack.config.js` or `webpack.config.ts` automatically when you run `strapi develop --bundler=webpack`. ::: 要扩展 webpack v5,请在 `/src/admin/webpack.config.js` 或 `/src/admin/webpack.config.ts` 中定义一个返回修改后配置的函数: 🌐 To extend webpack v5, define a function that returns a modified config in `/src/admin/webpack.config.js` or `/src/admin/webpack.config.ts`: ```js title="/src/admin/webpack.config.js" module.exports = (config, webpack) => { // Note: we provide webpack above so you should not `require` it // Perform customizations to webpack config config.plugins.push(new webpack.IgnorePlugin(/\/__tests__\//)); // Important: return the modified config return config; }; ``` ```ts title="/src/admin/webpack.config.ts" // Note: we provide webpack above so you should not `require` it // Perform customizations to webpack config config.plugins.push(new webpack.IgnorePlugin(/\/__tests__\//)); // Important: return the modified config return config; }; ``` # 管理面板扩展 Source: https://strapi.nodejs.cn/cms/admin-panel-customization/extension # 管理面板扩展 {#admin-panel-extension} 🌐 Admin panel extension 基于 React 的 Strapi 管理面板可以通过 `/src/admin/app` 在本地进行扩展以满足项目特定的需求,或者通过插件进行扩展,以便在多个 Strapi 实例之间重复使用和分发。 🌐 Strapi's React-based admin panel can be extended locally via `/src/admin/app` for project-specific needs or through plugins for reusable, distributable extensions across multiple Strapi instances. Strapi的[管理面板](/cms/admin-panel-customization)是一个基于React的单页应用,封装了Strapi应用的所有功能和已安装的插件。如果Strapi提供的[自定义选项](/cms/admin-panel-customization#available-customizations)不足以满足你的使用需求,你将需要扩展Strapi的管理面板。 🌐 Strapi's [admin panel](/cms/admin-panel-customization) is a React-based single-page application that encapsulates all the features and installed plugins of a Strapi application. If the [customization options](/cms/admin-panel-customization#available-customizations) provided by Strapi are not enough for your use case, you will need to extend Strapi's admin panel. 扩展 Strapi 的管理面板意味着利用其 React 基础根据项目的特定需求调整和增强界面和功能,这可能意味着创建新组件或添加新类型的字段。 🌐 Extending Strapi's admin panel means leveraging its React foundation to adapt and enhance the interface and features according to the specific needs of your project, which might imply creating new components or adding new types of fields. 在 2 个用例中,你可能需要扩展管理面板: 🌐 There are 2 use cases where you might want to extend the admin panel: | 方法 | 范围 | 入口点 | 文档 | |---|---|---|---| | 本地扩展 | 一个 Strapi 项目 | `/src/admin/app.(js\|ts)` 和 `/src/admin/extensions/` | [管理面板自定义](/cms/admin-panel-customization) | | 插件扩展 | 安装了你的插件的任何项目 | `[plugin-name]/admin/src/index.(js\|ts)` | [管理面板 API 概览](/cms/plugins-development/admin-panel-api) | - 作为 Strapi 插件开发者,你希望开发一个 Strapi 插件,使其在**每次安装到任何 Strapi 应用时**都能扩展管理面板。👉 这可以通过利用 [插件的管理面板 API](/cms/plugins-development/admin-panel-api) 来实现,该 API 允许你添加导航链接和设置部分,将 React 组件注入到预定义区域,使用 Redux 管理状态,扩展内容管理器的编辑和列表视图,等等。 - 作为一名 Strapi 开发者,你希望为一个只需要扩展 Strapi 应用特定实例的 Strapi 用户开发一个独特的解决方案。👉 这可以通过直接更新 `/src/admin/app` 文件来完成,该文件可以导入位于 `/src/admin/extensions` 中的任何文件。 :::tip Tip: Hot reloading while developing 在 Strapi 5 中,服务器默认以 `watch-admin` 模式运行,因此每当你更改其代码时,管理员面板会自动重新加载。这简化了管理员面板和前端插件的开发。要禁用此功能,请运行 `yarn develop --no-watch-admin`(参见 [CLI 参考](/cms/cli#strapi-develop))。 🌐 In Strapi 5, the server runs in `watch-admin` mode by default, so the admin panel auto-reloads whenever you change its code. This simplifies admin panel and front-end plugins development. To disable this, run `yarn develop --no-watch-admin` (see [CLI reference](/cms/cli#strapi-develop)). ::: :::note 本节关于 `/src/admin` 下的管理面板打包包。要更改核心插件的服务器行为(例如在浏览 `/admin/plugins/upload` 时使用的上传 API),请按照 [插件扩展](/cms/plugins-development/plugins-extension) 中的说明使用 `./src/extensions//strapi-server.js|ts`。 ::: ## 何时考虑使用插件 {#when-to-consider-a-plugin-instead} 🌐 When to consider a plugin instead 从在 `/src/admin/app` 中进行直接自定义开始,是满足项目特定需求的正确默认方式。当出现以下一种或多种信号时,考虑转向基于插件的方法: 🌐 Starting with a direct customization in `/src/admin/app` is the right default for project-specific needs. Consider moving to a plugin-based approach when one or more of these signals appear: - 你正在将相同的管理员自定义重复应用到多个 Strapi 项目中。 - 你想要对扩展进行版本控制和分发——无论是内部还是通过 [Strapi Marketplace](https://market.strapi.io/)。 - 你需要更强大的自动化测试,独立于单个项目代码库。 - 多个团队需要对同一个扩展进行共享所有权和版本管理。 有关插件开发的完整介绍,请参见 [开发 Strapi 插件](/cms/plugins-development/developing-plugins)。 🌐 For a full introduction to plugin development, see [Developing Strapi plugins](/cms/plugins-development/developing-plugins). :::strapi Additional resources * 如果你正在寻找替换默认富文本编辑器的方法,请参阅[相应页面](/cms/admin-panel-customization/wysiwyg-editor)。 * 要了解插件如何与 Strapi 管理面板集成,请从 [管理面板 API 概述](/cms/plugins-development/admin-panel-api) 开始。 ::: # 网站图标 Source: https://strapi.nodejs.cn/cms/admin-panel-customization/favicon # 网站图标 {#favicon} 🌐 Favicon 通过替换项目根目录下的 `favicon.png` 文件或配置 `strapi::favicon` 中间件来替换 Strapi 管理面板的 favicon,然后重新构建应用。 🌐 Replace the Strapi admin panel favicon by replacing the `favicon.png` file at the project root or configuring the `strapi::favicon` middleware, then rebuild the app. Strapi 的 [管理面板](/cms/admin-panel-customization) 会在多个地方显示其品牌标识,包括 [徽标](/cms/admin-panel-customization/logos) 和网站图标。替换这些图片可以让你将界面和应用与你的身份相匹配。 🌐 Strapi's [admin panel](/cms/admin-panel-customization) displays its branding on various places, including the [logo](/cms/admin-panel-customization/logos) and the favicon. Replacing these images allows you to match the interface and application to your identity. 更换网站图标有两种方法: 🌐 There are 2 approaches to replacing the favicon: * 替换 Strapi 项目根目录下的 `favicon.png` 文件 * 用以下代码编辑 [`strapi::favicon` 中间件配置](/cms/configurations/middlewares#favicon): ```js title="/config/middlewares.js" // … { name: 'strapi::favicon', config: { path: 'my-custom-favicon.png', }, }, // … ``` 完成后,通过在终端运行 `yarn build && yarn develop` 来重建、启动并重新访问你的 Strapi 应用。 🌐 Once done, rebuild, launch and revisit your Strapi app by running `yarn build && yarn develop` in the terminal. :::caution 确保清除缓存的收藏夹图标。它可能缓存于你的网页浏览器中,也可能缓存于像 Cloudflare 的 CDN 这样的域名管理工具中。 🌐 Make sure that the cached favicon is cleared. It can be cached in your web browser and also with your domain management tool like Cloudflare's CDN. ::: # 首页自定义 Source: https://strapi.nodejs.cn/cms/admin-panel-customization/homepage # 首页自定义 管理面板主页显示默认内容和个人资料小部件,并支持通过 `app.widgets.register` API 添加自定义内容。 The 主页是 Strapi 管理面板的登录页面。默认情况下,它提供包含 6 个默认小部件的内容概览: - 最近编辑的条目:显示最近修改的内容条目,包括它们的内容类型、状态以及更新时间。 - _最近发布的条目_:显示最近发布的内容条目,使你能够快速访问和管理已发布的内容。 - _个人资料_:显示你的个人资料简要摘要,包括你的名称、电子邮件地址和角色。 - _条目_:显示草稿和已发布条目的总数。 - _项目统计_: 显示有关你的条目、内容类型、语言环境、资源等的统计信息。 - _部署_:显示一个 **立即部署** 按钮,该按钮链接到 [Strapi Cloud](https://cloud.strapi.io/login) 用于部署你的项目。此小部件仅在本地开发中显示,应用在生产环境运行时会隐藏。 这些默认小部件目前无法删除,但你可以通过创建自己的小部件来自定义主页。 🌐 These default widgets cannot currently be removed, but you can customize the Homepage by creating your own widgets. :::note 如果你最近创建了一个 Strapi 项目,首页还可能在小部件上方显示引导游览,如果你尚未跳过它(详情请参阅 [管理面板](/cms/features/admin-panel) 文档)。 🌐 If you recently created a Strapi project, the Homepage may also display a guided tour above widgets if you haven't skipped it yet (see [Admin Panel](/cms/features/admin-panel) documentation for details). ::: ## 添加自定义小部件 {#adding-custom-widgets} 🌐 Adding custom widgets 要添加自定义小部件,你可以: 🌐 To add a custom widget, you can: - 从[市场](/cms/plugins/installing-plugins-via-marketplace)安装插件 - 或者,创建并注册你自己的小部件 本页面将介绍如何创建和注册小部件。 🌐 The present page will describe how to create and register your widgets. ### 注册自定义小部件 {#registering-custom-widgets} 🌐 Registering custom widgets 要注册一个小部件,使用 `app.widgets.register()`: 🌐 To register a widget, use `app.widgets.register()`: - 如果你正在构建插件(推荐方式),在 `index` 文件的插件 [`register` 生命周期方法](/cms/plugins-development/server-lifecycle#register) 中, - 或者在[应用的全局 `register()` 生命周期方法](/cms/configurations/functions#register)中,如果你只是将小部件添加到一个 Strapi 应用而不使用插件。 :::info 本页的示例将涵盖通过插件注册小部件。如果你在应用的全局 `register()` 生命周期方法中注册小部件,大部分代码应该是可重用的,只是你不应该传递 `pluginId` 属性。 🌐 The examples on the present page will cover registering a widget through a plugin. Most of the code should be reusable if you register the widget in the application's global `register()` lifecycle method, except you should not pass the `pluginId` property. ::: ```jsx title="src/plugins/my-plugin/admin/src/index.js" register(app) { // Register the plugin itself app.registerPlugin({ id: pluginId, name: 'My Plugin', }); // Register a widget for the Homepage app.widgets.register({ icon: MyWidgetIcon, title: { id: `${pluginId}.widget.title`, defaultMessage: 'My Widget', }, component: async () => { const component = await import('./components/MyWidget'); return component.default; }, /** * Use this instead if you used a named export for your component */ // component: async () => { // const { Component } = await import('./components/MyWidget'); // return Component; // }, id: 'my-custom-widget', pluginId: pluginId, }); }, bootstrap() {}, // ... }; ``` ```tsx title="src/plugins/my-plugin/admin/src/index.ts" register(app: StrapiApp) { // Register the plugin itself app.registerPlugin({ id: pluginId, name: 'My Plugin', }); // Register a widget for the Homepage app.widgets.register({ icon: MyWidgetIcon, title: { id: `${pluginId}.widget.title`, defaultMessage: 'My Widget', }, component: async () => { const component = await import('./components/MyWidget'); return component.default; }, /** * Use this instead if you used a named export for your component */ // component: async () => { // const { Component } = await import('./components/MyWidget'); // return Component; // }, id: 'my-custom-widget', pluginId: pluginId, }); }, bootstrap() {}, // ... }; ``` :::note The API requires Strapi 5.13+ `app.widgets.register` API 仅适用于 Strapi 5.13 及以上版本。尝试在较旧版本的 Strapi 中调用该 API 会导致管理面板崩溃。 希望注册小部件的插件开发者应当选择以下一种方式: 🌐 The `app.widgets.register` API only works with Strapi 5.13 and above. Trying to call the API with older versions of Strapi will crash the admin panel. Plugin developers who want to register widgets should either: - 在他们的插件 `package.json` 中将 `^5.13.0` 设置为其 `@strapi/strapi` 的 peerDependency。这个 peerDependency 支撑了市场的兼容性检查。 - 或者,在调用 API 之前检查它是否存在: ```js if ('widgets' in app) { // proceed with the registration } ``` 如果插件的全部目的是注册小部件,建议使用 peerDependency 方法。如果插件想添加小部件,但其大部分功能在其他地方,第二种方法更合理。 🌐 The peerDependency approach is recommended if the whole purpose of the plugin is to register widgets. The second approach makes more sense if a plugin wants to add a widget but most of its functionality is elsewhere. ::: #### 组件 API 参考 {#widget-api-reference} 🌐 Widget API reference `app.widgets.register()` 方法可以接受单个小部件配置对象或一个配置对象数组。每个小部件配置对象可以接受以下属性: 🌐 The `app.widgets.register()` method can take either a single widget configuration object or an array of configuration objects. Each widget configuration object can accept the following properties: | 属性 | 类型 | 描述 | 是否必填 | |-------------|------------------------|-------------------------------------------------------|----------| | `icon` | `React.ComponentType` | 显示在小部件标题旁的图标组件 | 是 | | `title` | `MessageDescriptor` | 支持翻译的小部件标题 | 是 | | `component` | `() => Promise` | 返回小部件组件的异步函数 | 是 | | `id` | `string` | 小部件的唯一标识符 | 是 | | `link` | `Object` | 可选的要添加到小部件的链接(请参见链接对象属性) | 否 | | `pluginId` | `string` | 注册小部件的插件ID | 否 | | `permissions` | `Permission[]` | 查看小部件所需的权限 | 否 | **链接对象属性:** 如果你想为你的小部件添加一个链接(例如,导航到详细视图),你可以提供一个具有以下属性的 `link` 对象: 🌐 If you want to add a link to your widget (e.g., to navigate to a detailed view), you can provide a `link` object with the following properties: | 属性 | 类型 | 描述 | 必填 | |----------|---------------------|------------------------------------------------|----------| | `label` | `MessageDescriptor` | 链接显示的文本 | 是 | | `href` | `string` | 链接应导航到的 URL | 是 | ### 创建一个小部件组件 {#creating-a-widget-component} 🌐 Creating a widget component 小部件组件的设计应以紧凑且信息丰富的方式显示内容。 🌐 Widget components should be designed to display content in a compact and informative way. 以下是如何实现一个基本的小部件组件: 🌐 Here's how to implement a basic widget component: ```jsx title="src/plugins/my-plugin/admin/src/components/MyWidget/index.js" const MyWidget = () => { const [loading, setLoading] = useState(true); const [data, setData] = useState(null); const [error, setError] = useState(null); useEffect(() => { // Fetch your data here const fetchData = async () => { try { // Replace with your actual API call const response = await fetch('/my-plugin/data'); const result = await response.json(); setData(result); setLoading(false); } catch (err) { setError(err); setLoading(false); } }; fetchData(); }, []); if (loading) { return ; } if (error) { return ; } if (!data || data.length === 0) { return ; } return (
{/* Your widget content here */}
    {data.map((item) => (
  • {item.name}
  • ))}
); }; ``` ```tsx title="src/plugins/my-plugin/admin/src/components/MyWidget/index.tsx" interface DataItem { id: number; name: string; } const MyWidget: React.FC = () => { const [loading, setLoading] = useState(true); const [data, setData] = useState(null); const [error, setError] = useState(null); useEffect(() => { // Fetch your data here const fetchData = async () => { try { // Replace with your actual API call const response = await fetch('/my-plugin/data'); const result = await response.json(); setData(result); setLoading(false); } catch (err) { setError(err instanceof Error ? err : new Error(String(err))); setLoading(false); } }; fetchData(); }, []); if (loading) { return ; } if (error) { return ; } if (!data || data.length === 0) { return ; } return (
{/* Your widget content here */}
    {data.map((item) => (
  • {item.name}
  • ))}
); }; ``` :::tip 为了简单起见,下面的示例在 useEffect 钩子内部直接使用数据获取。虽然这在演示中可行,但它可能不反映生产中的最佳实践。 🌐 For simplicity, the example below uses data fetching directly inside a useEffect hook. While this works for demonstration purposes, it may not reflect best practices in production. 对于更稳健的解决方案,请考虑在[React 文档](https://react.nodejs.cn/learn/build-a-react-app-from-scratch#data-fetching)中推荐的替代方法。如果你希望集成数据获取库,我们建议使用[TanStackQuery](https://tanstack.com/query/v3/)。 🌐 For more robust solutions, consider alternative approaches recommended in the [React documentation](https://react.nodejs.cn/learn/build-a-react-app-from-scratch#data-fetching). If you're looking to integrate a data fetching library, we recommend using [TanStackQuery](https://tanstack.com/query/v3/). ::: **数据管理**: ![Rendering and Data management](/img/assets/homepage-customization/rendering-data-management.png) 上方的绿色框表示用户的 React 组件(来自 [API](#widget-api-reference) 中的 `widget.component`)被渲染的区域。你可以在这个框内渲染你想要的任何内容。然而,框外的所有内容都是由 Strapi 渲染的。这确保了管理员面板内整体设计的一致性。API 提供的 `icon`、`title` 和(可选的)`link` 属性用于显示小部件。 🌐 The green box above represents the area where the user’s React component (from `widget.component` in the [API](#widget-api-reference)) is rendered. You can render whatever you like inside of this box. Everything outside that box is, however, rendered by Strapi. This ensures overall design consistency within the admin panel. The `icon`, `title`, and `link` (optional) properties provided in the API are used to display the widget. #### 小部件辅助组件参考 {#widget-helper-components-reference} 🌐 Widget helper components reference Strapi 提供了几个辅助组件,以便在各个小部件之间保持一致的用户体验: 🌐 Strapi provides several helper components to maintain a consistent user experience across widgets: | 组件 | 描述 | 用法 | |------------------|-----------------------------------------------------|--------------------------------------| | `Widget.Loading` | 显示加载旋转器和消息 | 当数据正在加载时 | | `Widget.Error` | 显示错误状态 | 当发生错误时 | | `Widget.NoData` | 当没有可用数据时显示 | 当小部件没有数据可显示时 | | `Widget.NoPermissions` | 当用户缺少所需权限时显示 | 当用户无法访问该小部件时 | 这些组件有助于在不同的部件中保持一致的外观和感觉。 你可以在没有子组件的情况下渲染这些组件以获得默认文案:`` 或者你可以传递子组件来覆盖默认文案并指定你自己的文字:`Your custom error message`。 🌐 These components help maintain a consistent look and feel across different widgets. You could render these components without children to get the default wording: `` or you could pass children to override the default copy and specify your own wording: `Your custom error message`. ## 示例:添加内容指标小部件 {#example-adding-a-content-metrics-widget} 🌐 Example: Adding a content metrics widget 以下是如何创建内容指标小部件的完整示例,该小部件显示 Strapi 应用中每种内容类型的条目数量。 🌐 The following is a complete example of how to create a content metrics widget that displays the number of entries for each content type in your Strapi application. 最终结果在你的管理员面板的 首页中将如下所示: 该小工具显示 Strapi 在安装时提供 `--example` 标志时自动生成的示例内容类型的计数(详情请参见 [CLI 安装选项](/cms/installation/cli#cli-installation-options))。 🌐 The widget shows counts for example content-types automatically generated by Strapi when you provide the `--example` flag on installation (see [CLI installation options](/cms/installation/cli#cli-installation-options) for details). 此小部件可以通过以下方式添加到 Strapi: 🌐 This widget can be added to Strapi by: 1. 创建一个“内容指标”插件(详情请参见[插件创建](/cms/plugins-development/create-a-plugin)文档) 2. 重复使用下面提供的代码示例。 :::tip 如果你更喜欢动手操作,可以重复使用以下 [CodeSandbox link](https://codesandbox.io/p/sandbox/github/pwizla/strapi-custom-widget-content-metrics)。 ::: 以下文件注册插件和小部件: 🌐 The following file registers the plugin and the widget: ```jsx title="src/plugins/content-metrics/admin/src/index.js" {28-42} register(app) { app.addMenuLink({ to: `plugins/${PLUGIN_ID}`, icon: PluginIcon, intlLabel: { id: `${PLUGIN_ID}.plugin.name`, defaultMessage: PLUGIN_ID, }, Component: () => import('./pages/App'), }); app.registerPlugin({ id: PLUGIN_ID, initializer: Initializer, isReady: false, name: PLUGIN_ID, }); // Registers the widget app.widgets.register({ icon: Stethoscope, title: { id: `${PLUGIN_ID}.widget.metrics.title`, defaultMessage: 'Content Metrics', }, component: async () => { const component = await import('./components/MetricsWidget'); return component.default; }, id: 'content-metrics', pluginId: PLUGIN_ID, }); }, async registerTrads({ locales }) { return Promise.all( locales.map(async (locale) => { try { const { default: data } = await import(`./translations/${locale}.json`); return { data, locale }; } catch { return { data: {}, locale }; } }) ); }, bootstrap() {}, }; ``` 以下文件定义了小部件的组件及其逻辑。它正在使用我们将为插件创建的特定控制器和路由: 🌐 The following file defines the widget's component and its logic. It's tapping into a specific controller and route that we'll create for the plugin: ```jsx title="src/plugins/content-metrics/admin/src/components/MetricsWidget/index.js" const MetricsWidget = () => { const [loading, setLoading] = useState(true); const [metrics, setMetrics] = useState(null); const [error, setError] = useState(null); useEffect(() => { const fetchMetrics = async () => { try { const response = await fetch('/api/content-metrics/count'); const data = await response.json(); console.log("data:", data); const formattedData = {}; if (data && typeof data === 'object') { Object.keys(data).forEach(key => { const value = data[key]; formattedData[key] = typeof value === 'number' ? value : String(value); }); } setMetrics(formattedData); setLoading(false); } catch (err) { console.error(err); setError(err.message || 'An error occurred'); setLoading(false); } }; fetchMetrics(); }, []); if (loading) { return ( ); } if (error) { return ( ); } if (!metrics || Object.keys(metrics).length === 0) { return No content types found; } return ( {Object.entries(metrics).map(([contentType, count], index) => ( {String(contentType)} {String(count)} ))} ); }; ``` 以下文件定义了一个自定义控制器,用于统计所有内容类型: 🌐 The following file defines a custom controller that counts all content-types: ```js title="src/plugins/content-metrics/server/src/controllers/metrics.js" 'use strict'; module.exports = ({ strapi }) => ({ async getContentCounts(ctx) { try { // Get all content types const contentTypes = Object.keys(strapi.contentTypes) .filter(uid => uid.startsWith('api::')) .reduce((acc, uid) => { const contentType = strapi.contentTypes[uid]; acc[contentType.info.displayName || uid] = 0; return acc; }, {}); // Count entities for each content type for (const [name, _] of Object.entries(contentTypes)) { const uid = Object.keys(strapi.contentTypes) .find(key => strapi.contentTypes[key].info.displayName === name || key === name ); if (uid) { // Using the count() method from the Document Service API const count = await strapi.documents(uid).count(); contentTypes[name] = count; } } ctx.body = contentTypes; } catch (err) { ctx.throw(500, err); } } }); ``` 以下文件确保指标控制器可以通过自定义的 `/count` 路由访问: 🌐 The following file ensures that the metrics controller is reachable at a custom `/count` route: ```js title="src/plugins/content-metrics/server/src/routes/index.js" 'content-api': { type: 'content-api', routes: [ { method: 'GET', path: '/count', handler: 'metrics.getContentCounts', config: { policies: [], }, }, ], }, }; ``` 以下文件注册插件和小部件: 🌐 The following file registers the plugin and the widget: ```tsx title="src/plugins/content-metrics/admin/src/index.ts" {28-42} register(app) { app.addMenuLink({ to: `plugins/${PLUGIN_ID}`, icon: PluginIcon, intlLabel: { id: `${PLUGIN_ID}.plugin.name`, defaultMessage: PLUGIN_ID, }, Component: () => import('./pages/App'), }); app.registerPlugin({ id: PLUGIN_ID, initializer: Initializer, isReady: false, name: PLUGIN_ID, }); // Registers the widget app.widgets.register({ icon: Stethoscope, title: { id: `${PLUGIN_ID}.widget.metrics.title`, defaultMessage: 'Content Metrics', }, component: async () => { const component = await import('./components/MetricsWidget'); return component.default; }, id: 'content-metrics', pluginId: PLUGIN_ID, }); }, async registerTrads({ locales }) { return Promise.all( locales.map(async (locale) => { try { const { default: data } = await import(`./translations/${locale}.json`); return { data, locale }; } catch { return { data: {}, locale }; } }) ); }, bootstrap() {}, }; ``` 以下文件定义了小部件的组件及其逻辑。它正在使用我们将为插件创建的特定控制器和路由: 🌐 The following file defines the widget's component and its logic. It's tapping into a specific controller and route that we'll create for the plugin: ```tsx title="src/plugins/content-metrics/admin/src/components/MetricsWidget/index.ts" const MetricsWidget = () => { const [loading, setLoading] = useState(true); const [metrics, setMetrics] = useState(null); const [error, setError] = useState(null); useEffect(() => { const fetchMetrics = async () => { try { const response = await fetch('/api/content-metrics/count'); const data = await response.json(); console.log("data:", data); const formattedData = {}; if (data && typeof data === 'object') { Object.keys(data).forEach(key => { const value = data[key]; formattedData[key] = typeof value === 'number' ? value : String(value); }); } setMetrics(formattedData); setLoading(false); } catch (err) { console.error(err); setError(err.message || 'An error occurred'); setLoading(false); } }; fetchMetrics(); }, []); if (loading) { return ( ); } if (error) { return ( ); } if (!metrics || Object.keys(metrics).length === 0) { return No content types found; } return ( {Object.entries(metrics).map(([contentType, count], index) => ( {String(contentType)} {String(count)} ))} ); }; ``` 以下文件定义了一个自定义控制器,用于统计所有内容类型: 🌐 The following file defines a custom controller that counts all content-types: ```js title="src/plugins/content-metrics/server/src/controllers/metrics.js" 'use strict'; module.exports = ({ strapi }) => ({ async getContentCounts(ctx) { try { // Get all content types const contentTypes = Object.keys(strapi.contentTypes) .filter(uid => uid.startsWith('api::')) .reduce((acc, uid) => { const contentType = strapi.contentTypes[uid]; acc[contentType.info.displayName || uid] = 0; return acc; }, {}); // Count entities for each content type using Document Service for (const [name, _] of Object.entries(contentTypes)) { const uid = Object.keys(strapi.contentTypes) .find(key => strapi.contentTypes[key].info.displayName === name || key === name ); if (uid) { // Using the count() method from Document Service instead of strapi.db.query const count = await strapi.documents(uid).count(); contentTypes[name] = count; } } ctx.body = contentTypes; } catch (err) { ctx.throw(500, err); } } }); ``` 以下文件确保指标控制器可以通过自定义的 `/count` 路由访问: 🌐 The following file ensures that the metrics controller is reachable at a custom `/count` route: ```js title="src/plugins/content-metrics/server/src/routes/index.js" 'content-api': { type: 'content-api', routes: [ { method: 'GET', path: '/count', handler: 'metrics.getContentCounts', config: { policies: [], }, }, ], }, }; ``` # 管理面板自定义 - URL、主机和路径配置 Source: https://strapi.nodejs.cn/cms/admin-panel-customization/host-port-path # 管理面板自定义:主机、端口和路径配置 {#admin-panel-customization-host-port-and-path-configuration} 🌐 Admin panel customization: Host, port, and path configuration 在 `config/admin.[ts|js]` 文件中配置 Strapi 管理面板的主机、端口和 URL 路径,以更改其默认位置 `/admin` 的访问位置。 🌐 Configure the Strapi admin panel's host, port, and URL path in the `config/admin.[ts|js]` file to change where it is accessible from its default location at `/admin`. 默认情况下,Strapi 的 [管理面板](/cms/admin-panel-customization) 通过 [http://localhost:1337/admin](http://localhost:1337/admin) 暴露。出于安全原因,可以更新主机、端口和路径。 ## 仅更新管理员面板的路径 {#update-the-admin-panels-path-only} 🌐 Update the admin panel's path only 除非你选择将 Strapi 的后端服务器和管理面板服务器部署在不同的服务器上(参见 [部署](/cms/configurations/admin-panel#deploy-on-different-servers)),否则默认情况下: 🌐 Unless you chose to deploy Strapi's back-end server and admin panel server on different servers (see [deployment](/cms/configurations/admin-panel#deploy-on-different-servers)), by default: - Strapi 的后端服务器和管理面板服务器都运行在相同的主机和端口上,即 `http://localhost:1337/`。 - 管理员面板可以通过 `/admin` 路径访问,而后端服务器可以通过 `/api` 路径访问。 要使管理面板可以通过另一路径访问,例如在 `http://localhost:1337/dashboard`,请在[管理面板配置文件](/cms/configurations/admin-panel)中定义或更新 `url` 属性,如下所示: 🌐 To make the admin panel accessible at another path, for instance at `http://localhost:1337/dashboard`, define or update the `url` property in the [admin panel configuration file](/cms/configurations/admin-panel) as follows: ```js title="/config/admin.js" module.exports = ({ env }) => ({ // … other configuration properties url: "/dashboard", }); ``` 由于默认情况下,后端服务器和管理面板服务器在同一主机和端口上运行,如果你在[服务器配置](/cms/configurations/server)文件中未更改`host`和`port`属性值,只更新`config/admin.[ts|js]`文件应该就可以工作,配置应如下所示: 🌐 Since by default the back-end server and the admin panel server run on the same host and port, only updating the `config/admin.[ts|js]` file should work if you left the `host` and `port` property values untouched in the [server configuration](/cms/configurations/server) file, which should be as follows: ```js title="/config/server.js" module.exports = ({ env }) => ({ host: env("HOST", "0.0.0.0"), port: env.int("PORT", 1337), }); ``` ```js title="/config/server.ts" host: env("HOST", "0.0.0.0"), port: env.int("PORT", 1337), }); ``` ## 更新管理员面板的主机和端口 {#update-the-admin-panels-host-and-port} 🌐 Update the admin panel's host and port 如果 Strapi 的管理面板和后端服务器没有托管在同一台服务器上(参见 [deployment](/cms/configurations/admin-panel#deploy-on-different-servers)),你需要更新管理面板的主机和端口。 🌐 If the admin panel and the back-end server of Strapi are not hosted on the same server (see [deployment](/cms/configurations/admin-panel#deploy-on-different-servers)), you will need to update the host and port of the admin panel. 这是在管理员面板配置文件中完成的,例如,要在 `my-host.com:3000` 上托管管理员面板,应按如下方式更新属性: 🌐 This is done in the admin panel configuration file, for example to host the admin panel on `my-host.com:3000` properties should be updated follows: ```js title="./config/admin.js" module.exports = ({ env }) => ({ host: "my-host.com", port: 3000, // Additionally you can define another path instead of the default /admin one 👇 // url: '/dashboard' }); ``` ```js title="./config/admin.ts" host: "my-host.com", port: 3000, // Additionally you can define another path instead of the default /admin one 👇 // url: '/dashboard' }); ```
:::strapi Other admin panel configurations `/config/admin.[ts|js]` 文件可以用于配置许多其他方面。详情请参阅 [管理面板配置](/cms/configurations/admin-panel) 文档。 🌐 The `/config/admin.[ts|js]` file can be used to configure many other aspects. Please refer to the [admin panel configuration](/cms/configurations/admin-panel) documentation for details. ::: :::tip Behind a reverse proxy 当从与 API 不同的公共来源(域名、主机或端口)访问管理端时,相应设置 `host`/`port`,并确保你的代理转发头信息(例如,`X-Forwarded-*`)。如果管理端可通过不同来源访问,优先显式配置该来源。 🌐 When serving the admin from a different public origin (domain, host, or port) than the API, set `host`/`port` accordingly and ensure your proxy forwards headers (e.g., `X-Forwarded-*`). If the admin is reachable at a different origin, prefer configuring that origin explicitly. ::: # 本地化与翻译 Source: https://strapi.nodejs.cn/cms/admin-panel-customization/locales-translations # 本地化与翻译 {#locales--translations} 🌐 Locales & translations 通过更新 `config.locales` 数组来配置管理面板语言,并使用 `config.translations` 或自定义翻译文件覆盖默认或插件字符串。 Strapi [管理面板](/cms/admin-panel-customization) 默认提供英文字符串,并支持添加其他语言环境,以便你的编辑团队可以使用他们偏好的语言进行工作。语言环境决定界面中显示的语言,而翻译则提供在某个语言环境中每个键显示的文本。 🌐 The Strapi [admin panel](/cms/admin-panel-customization) ships with English strings and supports adding other locales so your editorial team can work in their preferred language. Locales determine which languages appear in the interface, while translations provide the text displayed for each key in a locale. 本指南面向从应用代码库自定义管理体验的项目维护者。所有示例都修改从 `/src/admin/app` 文件导出的配置,Strapi 在构建管理面板时会加载该配置。你将学习如何声明额外的语言环境,以及当语言环境缺少字符串时如何扩展 Strapi 或插件的翻译。 🌐 This guide targets project maintainers customizing the admin experience from the application codebase. All examples modify the configuration exported from `/src/admin/app` file, which Strapi loads when the admin panel builds. You'll learn how to declare additional locales and how to extend Strapi or plugin translations when a locale is missing strings. ## 定义区域设置 {#defining-locales} 🌐 Defining locales 要在管理面板中更新可用语言环境列表,请在 `src/admin/app` 文件中设置 `config.locales` 数组: 🌐 To update the list of available locales in the admin panel, set the `config.locales` array in `src/admin/app` file: ```js title="/src/admin/app.js" config: { locales: ["ru", "zh"], }, bootstrap() {}, }; ``` ```ts title="/src/admin/app.ts" config: { locales: ["ru", "zh"], }, bootstrap() {}, }; ``` :::note Notes - `en` 语言环境无法从构建中移除,因为它既是回退语言环境(即如果在某个语言环境中未找到翻译,将使用 `en`),也是默认语言环境(即当用户第一次打开管理面板时使用)。 - 可用区域设置的完整列表可在 [Strapi](https://github.com/strapi/strapi/blob/v4.0.0/packages/plugins/i18n/server/constants/iso-locales.json)上访问。 ::: ## 扩展翻译 {#extending-translations} 🌐 Extending translations 翻译键/值对在 `@strapi/admin/admin/src/translations/[language-name].json` 文件中声明。 🌐 Translation key/value pairs are declared in `@strapi/admin/admin/src/translations/[language-name].json` files. 这些键可以通过 `src/admin/app` 文件中的 `config.translations` 键进行扩展: 🌐 These keys can be extended through the `config.translations` key in `src/admin/app` file: ```js title="/src/admin/app.js" config: { locales: ["fr"], translations: { fr: { "Auth.form.email.label": "test", Users: "Utilisateurs", City: "CITY (FRENCH)", // Customize the label of the Content Manager table. Id: "ID french", }, }, }, bootstrap() {}, }; ``` ```ts title="/src/admin/app.ts" config: { locales: ["fr"], translations: { fr: { "Auth.form.email.label": "test", Users: "Utilisateurs", City: "CITY (FRENCH)", // Customize the label of the Content Manager table. Id: "ID french", }, }, }, bootstrap() {}, }; ``` 插件的键/值对在插件的文件 `/admin/src/translations/[language-name].json` 中独立声明。这些键/值对可以通过在 `config.translations` 键中添加前缀(插件的名称,即 `[plugin name].[key]: 'value'`)来类似地扩展,如以下示例所示: 🌐 A plugin's key/value pairs are declared independently in the plugin's files at `/admin/src/translations/[language-name].json`. These key/value pairs can similarly be extended in the `config.translations` key by prefixing the key with the plugin's name (i.e. `[plugin name].[key]: 'value'`) as in the following example: ```js title="/src/admin/app.js" config: { locales: ["fr"], translations: { fr: { "Auth.form.email.label": "test", // Translate a plugin's key/value pair by adding the plugin's name as a prefix // In this case, we translate the "plugin.name" key of plugin "content-type-builder" "content-type-builder.plugin.name": "Constructeur de Type-Contenu", }, }, }, bootstrap() {}, }; ``` ```ts title="/src/admin/app.ts" config: { locales: ["fr"], translations: { fr: { "Auth.form.email.label": "test", // Translate a plugin's key/value pair by adding the plugin's name as a prefix // In this case, we translate the "plugin.name" key of plugin "content-type-builder" "content-type-builder.plugin.name": "Constructeur de Type-Contenu", }, }, }, bootstrap() {}, }; ``` 如果你需要发送额外的翻译 JSON 文件——例如为了组织大型覆盖或支持 Strapi 未打包的本地化——请将它们放在 `/src/admin/extensions/translations` 文件夹中,并确保本地化代码已列在 `config.locales` 中。 🌐 If you need to ship additional translation JSON files—for example to organize large overrides or to support a locale not bundled with Strapi—place them in the `/src/admin/extensions/translations` folder and ensure the locale code is listed in `config.locales`. :::tip Rebuild the admin 当管理员重建时,翻译更改会生效。如果更新未显示,请重新运行开发服务器或重建管理员以刷新打包的翻译。 🌐 Translation changes apply when the admin rebuilds. If updates don’t show, re-run your dev server or rebuild the admin to refresh bundled translations. ::: # 标志 Source: https://strapi.nodejs.cn/cms/admin-panel-customization/logos # 标志 {#logos} 🌐 Logos 通过扩展管理应用来更新登录和导航徽标。优先使用 SVG 以获得清晰的显示效果;如可能,请提供明/暗两种版本以增强对比度。 Strapi 的 [管理面板](/cms/admin-panel-customization) 在登录屏幕和主导航中显示其品牌标识。更换这些图片可以让界面与你的身份保持一致。本页展示了如何通过管理面板配置覆盖这两个徽标文件。如果你更喜欢直接在 UI 中上传,请参见 [自定义徽标](/cms/features/admin-panel#customizing-the-logo)。 🌐 Strapi's [admin panel](/cms/admin-panel-customization) displays its branding on both the login screen and in the main navigation. Replacing these images allows you to match the interface to your identity. The present page shows how to override the two logo files via the admin panel configuration. If you prefer uploading them directly in the UI, see [Customizing the logo](/cms/features/admin-panel#customizing-the-logo). Strapi 管理面板在 2 个不同位置显示徽标,由管理面板配置中的 2 个不同键表示: 🌐 The Strapi admin panel displays a logo in 2 different locations, represented by 2 different keys in the admin panel configuration: | 在用户界面中的位置 | 要更新的配置键 | | --- | --- | | 在登录页面 | `config.auth.logo` | | 在主导航中 | `config.menu.logo` | :::note 通过管理面板上传的徽标将取代通过配置文件设置的任何徽标。 🌐 Logos uploaded via the admin panel supersede any logo set through the configuration files. ::: ### 管理面板中的标志位置 {#logos-location-in-the-admin-panel} 🌐 Logos location in the admin panel `config.auth.logo` 处理的徽标只显示在登录屏幕上: 🌐 The logo handled by `config.auth.logo` logo is only shown on the login screen: ![Location of the auth logo](/img/assets/development/config-auth-logo.png) `config.menu.logo` 处理的徽标位于管理员面板左上角的主导航中: 🌐 The logo handled by `config.menu.logo` logo is located in the main navigation at the top left corner of the admin panel: ![Location of Menu logo](/img/assets/development/config-menu-logo.png) ### 更新标志 {#updating-logos} 🌐 Updating logos 要更新徽标,请将图片文件放入 `/src/admin/extensions` 文件夹,在 `src/admin/app` 中导入这些文件,并按以下示例更新相应的键: 🌐 To update the logos, put image files in the `/src/admin/extensions` folder, import these files in `src/admin/app` and update the corresponding keys as in the following example: ```jsx title="/src/admin/app.js" config: { // … other configuration properties auth: { // Replace the Strapi logo in auth (login) views logo: AuthLogo, }, menu: { // Replace the Strapi logo in the main navigation logo: MenuLogo, }, // … other configuration properties bootstrap() {}, }; ``` ```jsx title="/src/admin/app.ts" config: { // … other configuration properties auth: { // Replace the Strapi logo in auth (login) views logo: AuthLogo, }, menu: { // Replace the Strapi logo in the main navigation logo: MenuLogo, }, // … other configuration properties bootstrap() {}, }; ``` :::note 通过配置文件设置的图片文件没有大小限制。 🌐 There is no size limit for image files set through the configuration files. ::: # 主题扩展 Source: https://strapi.nodejs.cn/cms/admin-panel-customization/theme-extension # 主题扩展 {#theme-extension} 🌐 Theme extension 通过在 `/src/admin/app.js` 中自定义 `config.theme.light` 和 `config.theme.dark` 键来扩展 Strapi 管理面板的浅色和夜间模式主题,以覆盖颜色和其他设计系统属性。 🌐 Extend the Strapi admin panel theme for light and dark modes by customizing `config.theme.light` and `config.theme.dark` keys in `/src/admin/app.js` to override colors and other design system properties. Strapi 的 [管理面板](/cms/admin-panel-customization) 可以显示为浅色或夜间模式(参见 [个人资料设置](/cms/getting-started/setting-up-admin-panel#setting-up-your-administrator-profile)),两者都可以通过自定义主题设置进行扩展。 🌐 Strapi's [admin panel](/cms/admin-panel-customization) can be displayed either in light or dark mode (see [profile setup](/cms/getting-started/setting-up-admin-panel#setting-up-your-administrator-profile)), and both can be extended through custom theme settings. 要扩展主题,请使用: 🌐 To extend the theme, use either: - Light 模式的 `config.theme.light` 键 - `config.theme.dark` 键用于夜间模式 :::strapi Strapi Design System 默认的 [Strapi theme](https://github.com/strapi/design-system/tree/main/packages/design-system/src/themes) 定义了各种与主题相关的键(阴影、颜色…),可以通过`./admin/src/app.js`中的`config.theme.light`和`config.theme.dark`键进行更新。 [Strapi Design System](https://design-system.strapi.io/) 是完全可定制的,并且有专门的 [StoryBook](https://design-system-git-main-strapijs.vercel.app) 文档。 ::: 以下示例展示了如何通过自定义 [管理面板配置](/cms/configurations/admin-panel) 中的浅色和深色主题键来覆盖主颜色: 🌐 The following example shows how to override the primary color by customizing the light and dark theme keys in the [admin panel configuration](/cms/configurations/admin-panel): ```js title="/src/admin/app.js" config: { theme: { light: { colors: { primary600: "#4A6EFF", }, }, dark: { colors: { primary600: "#9DB2FF", }, }, }, }, bootstrap() {}, } ``` ```ts title="/src/admin/app.ts" config: { theme: { light: { colors: { primary600: '#4A6EFF', }, }, dark: { colors: { primary600: '#9DB2FF', }, }, }, }, bootstrap() {}, } ``` # 自定义富文本编辑器 Source: https://strapi.nodejs.cn/cms/admin-panel-customization/wysiwyg-editor # 更改默认富文本编辑器 {#change-the-default-rich-text-editor} 🌐 Change the default rich text editor Strapi 的管理面板为 `richtext` 字段内置了 WYSIWYG Markdown 编辑器。你可以通过从市场安装第三方编辑器插件或创建自定义字段以实现更深入的集成来替换它。 🌐 Strapi's admin panel includes a built-in WYSIWYG markdown editor for `richtext` fields. You can replace it by installing third-party editor plugins from the Marketplace or creating a custom field for deeper integration. :::note 本页涵盖用于 `richtext` 字段的 **所见即所得 Markdown 编辑器** 的自定义内容。对于 **Blocks** 字段(基于 JSON 的富文本编辑器),请参阅 [内容管理器 API:addRichTextBlocks](/cms/plugins-development/content-manager-apis#addrichtextblocks)。 🌐 This page covers customization of the **WYSIWYG markdown editor** used for `richtext` fields. For the **Blocks** field (the JSON-based rich text editor), see [Content Manager APIs: addRichTextBlocks](/cms/plugins-development/content-manager-apis#addrichtextblocks). ::: Strapi 的 [管理面板](/cms/admin-panel-customization) 附带了一个内置的富文本编辑器。要更改默认编辑器,你有几种选择: 🌐 Strapi's [admin panel](/cms/admin-panel-customization) comes with a built-in rich text editor. To change the default editor, several options are at your disposal: - 你可以通过访问 [Strapi](https://market.strapi.io/)安装第三方插件,例如用于CKEditor的插件。 - 你可以创建自己的插件来创建和注册一个完全自定义的所见即所得字段(参见 [自定义字段文档](/cms/features/custom-fields))。 :::tip Next steps 评估编辑器时,建议先从 Marketplace 下载插件进行快速试用,如果需要更深入的集成(例如架构、验证或自定义工具栏行为),则可以考虑使用自定义字段。 🌐 When evaluating editors, start with a plugin from the Marketplace for a quick trial, then consider a custom field if you need deeper integration (schema, validation, or custom toolbar behavior). ::: # Docs MCP 服务器 Source: https://strapi.nodejs.cn/cms/ai/docs-mcp-server # Docs MCP 服务器 {#docs-mcp-server} 🌐 Docs MCP server Docs MCP 服务器向 AI 编码工具公开 Strapi 文档。将其连接到你的 IDE,即可在开发环境中直接获得针对 Strapi 的代码建议和答案。 🌐 A Docs MCP server exposes the Strapi documentation to AI coding tools. Connect it to your IDE to get Strapi-aware code suggestions and answers directly in your development environment. Docs [MCP](https://modelcontextprotocol.io)(模型上下文协议)服务器由 [Kapa](https://kapa.ai) 提供支持,这也是文档网站上 **Ask AI** 按钮背后的服务。它使用完整的 Strapi 文档,包括指南、API 参考和代码示例。Docs MCP 服务器是 Strapi 提供的 [开发者 AI 工具](/cms/ai/for-developers) 的一部分。 🌐 The Docs [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server is powered by [Kapa](https://kapa.ai), the same service behind the **Ask AI** button on the documentation website. It draws from the full Strapi documentation, including guides, API references, and code examples. The Docs MCP server is part of the [AI tools for developers](/cms/ai/for-developers) that Strapi offers. :::strapi MCP servers for Strapi Strapi 提供 2 种不同的 MCP 服务器: 🌐 Strapi offers 2 different MCP servers: - 本页介绍的 Docs MCP 服务器, - 以及用于内容管理的 Strapi MCP 服务器,详见其[专用功能页面](/cms/features/strapi-mcp-server)。 ::: ## 兼容工具 {#compatible-tools} 🌐 Compatible tools MCP 服务器兼容任何支持 MCP 协议的工具,包括: 🌐 The MCP server works with any tool that supports the MCP protocol, including: - [光标](https://cursor.com) - [VS Code](https://code.visualstudio.com) 与 GitHub Copilot - [克劳德代码](https://docs.anthropic.com/en/docs/claude-code) - [风帆冲浪](https://codeium.com/windsurf) - 任何其他兼容 MCP 的 IDE 或工具 ## 连接详情 {#connection-details} 🌐 Connection details 打开 Ask AI 窗口时,你应该会在右上角看到一个 **使用 MCP** 下拉菜单。点击它并选择你想要连接的工具: 🌐 When opening the Ask AI window, you should see a **Use MCP** dropdown in the top right corner. Click on it and choose which tool you'd like to connect: 如果需要手动配置 MCP 服务器: 🌐 If manual MCP server configuration is required: 1. 从下拉菜单中点击 **复制 MCP URL**。服务器 URL 应该是:`https://strapi-docs.mcp.kapa.ai` 2. 将服务器添加到你的 IDE 的 MCP 配置文件中: 添加到你的 `.cursor/mcp.json` 文件中: ```json title=".cursor/mcp.json" { "mcpServers": { "strapi-docs": { "url": "https://strapi-docs.mcp.kapa.ai" } } } ``` 添加到你的 `.vscode/mcp.json` 文件中: ```json title=".vscode/mcp.json" { "servers": { "strapi-docs": { "type": "http", "url": "https://strapi-docs.mcp.kapa.ai" } } } ``` 添加到你的 `~/.codeium/windsurf/mcp_config.json` 文件中: ```json title="~/.codeium/windsurf/mcp_config.json" { "mcpServers": { "strapi-docs": { "serverUrl": "https://strapi-docs.mcp.kapa.ai" } } } ``` 一旦连接,你的 AI 编码助手可以直接查询 Strapi 文档来回答问题、提供实现建议并验证 API 的使用。 🌐 Once connected, your AI coding assistant can query the Strapi documentation directly to answer questions, suggest implementations, and verify API usage. :::tip 对于与文档相关的问题,请在提示开头使用 `Use the strapi-docs MCP server to answer:`。这将确保工具查询 docs.strapi.io,而不是基于其可能已过时的训练数据返回答案。 🌐 For docs-related questions, start your prompts with `Use the strapi-docs MCP server to answer:`. This will ensure the tool queries docs.strapi.io instead of returning answers based on its training data, which can be outdated. ::: # 面向内容管理的人工智能 Source: https://strapi.nodejs.cn/cms/ai/for-content-managers # 面向内容管理的人工智能 {#ai-for-content-managers} 🌐 AI for content managers Strapi AI 帮助内容管理员设计内容结构、翻译内容并从管理面板生成资源元数据。Strapi 还包括一个内置的 MCP 服务器,使 AI 客户端可以通过自然语言管理内容。 🌐 Strapi AI helps content managers design content structures, translate content, and generate asset metadata from the admin panel. Strapi also includes a built-in MCP server that lets AI clients manage content through natural language. 本页面介绍了 Strapi 中面向内容管理者的 AI 功能:内置于管理面板的 Strapi AI 功能,以及允许 AI 客户端管理你内容的 MCP 服务器。 🌐 This page covers the AI-powered capabilities available to content managers in Strapi: the Strapi AI features built into the admin panel, and the MCP server that lets AI clients manage your content. ## Strapi 人工智能 {#strapi-ai} 🌐 Strapi AI 一些 Strapi CMS 功能可以通过 Strapi AI 得到增强,帮助内容管理者和管理员设计内容结构、自动翻译内容以及生成资源元数据,所有操作都可以在管理面板完成。 🌐 Some Strapi CMS features can be enhanced with Strapi AI, helping content managers and administrators design content structures, translate content automatically, and generate asset metadata, all from the admin panel. ### 激活和配置 {#activation} 🌐 Activation and configuration 自 Strapi 5.30 起,Strapi AI 对 Growth 计划用户可用,并且适用于 Strapi Cloud 和自托管部署。开始使用方法如下: 🌐 Strapi AI is available for Growth plan users since Strapi 5.30 and works with both Strapi Cloud and self-hosted deployments. To get started: 1. 升级到 Strapi v5.30+。AI 功能在早期版本中不可用。 2. 通过 CLI 或 Strapi Cloud 激活 Growth 许可证密钥,或开始 30 天免费试用。试用包括 10 个用于探索 AI 功能的免费积分。 3. 可以从内容类型构建器、媒体库或内容管理器访问 AI 功能;它们默认启用。 所有 Strapi AI 功能可以通过管理员面板配置在全局范围内启用或禁用: 🌐 All Strapi AI features can be enabled or disabled globally through the admin panel configuration: ```js title="/config/admin.js|ts" module.exports = { // ... ai: { enabled: true, // set to false to disable all Strapi AI features }, }; ``` 有关所有配置选项,请参见 [管理员面板配置 > Strapi AI](/cms/configurations/admin-panel#strapi-ai)。 ### 可用功能 {#features} 🌐 Available features | 功能 | 描述 | |---------|-------------| | [内容类型构建器](/cms/features/content-type-builder#strapi-ai) | AI 聊天助手,帮助设计内容类型结构,解释现有的架构,并规划数据模型。使用你现有的内容类型作为上下文。 | | [国际化](/cms/features/internationalization#ai-powered-internationalization) | 当你保存条目时,会自动将内容从默认语言翻译到所有其他配置的语言。 | | [媒体库](/cms/features/media-library#ai-powered-metadata-generation) | 为上传的图片生成替代文本、标题和描述。 | ### 积分与数据处理 {#credits} 🌐 Credits and data handling Strapi AI 功能会消耗 AI 积分。 🌐 Strapi AI features consume AI credits. Strapi AI 在 计划中每月包含 1,000 个积分,并在免费试用期间提供 10 个免费积分。Strapi AI 在企业计划中不可用。 轻量级操作使用较少的积分,而更复杂的操作使用更多。 🌐 Lightweight actions use fewer credits, while more complex ones use more. 你可以在管理面板的 [Settings Overview](http://localhost:1337/admin/settings/application-infos) 中查看你的信用使用情况。当你的使用量达到每月配额的80%、90%和100%时,会发送通知。超额使用将产生额外费用。 积分在同一项目实例的所有用户之间共享。 🌐 Credits are shared across all users within the same project instance. 当你的积分用完时,你仍然可以继续使用 Strapi AI,超出部分将按月收费。有关 Strapi AI 的更多信息,请参阅[专门的支持文章](https://support.strapi.io/articles/1821143913-understanding-strapi-ai)。 🌐 When your credits run out, you can keep using Strapi AI, with overages billed monthly. For more information about Strapi AI, please refer to the [dedicated support article](https://support.strapi.io/articles/1821143913-understanding-strapi-ai). 所有 AI 请求都是通过 Strapi 管理的基础设施处理的。内容仅在每次请求期间临时使用,不会存储在你的实例之外。Strapi AI 遵循与 Strapi Cloud 相同的符合 GDPR 的框架。 🌐 All AI requests are processed through Strapi-managed infrastructure. Content is only used temporarily during each request and is not stored outside your instance. Strapi AI follows the same GDPR-aligned framework as Strapi Cloud. 有关详细信息,请参见 [使用信息 > Strapi AI 数据处理](/cms/usage-information#strapi-ai-data-handling)。 ## Strapi MCP 服务器 {#strapi-mcp-server} 🌐 Strapi MCP server Strapi 包含一个内置的 [模型上下文协议 (MCP)](https://modelcontextprotocol.io) 服务器,它允许像 Claude、Cursor 或任何 MCP 兼容工具这样的 AI 客户端通过自然语言管理你的内容。一旦启用并连接,AI 客户端可以直接通过 Strapi 的内容管理器创建、读取、更新、删除、发布和取消发布条目,所有操作都受管理员令牌权限的控制。 🌐 Strapi includes a built-in [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that lets AI clients like Claude, Cursor, or any MCP-compatible tool manage your content through natural language. Once enabled and connected, an AI client can create, read, update, delete, publish, and unpublish entries directly through Strapi's Content Manager, all gated by Admin token permissions. - [MCP 服务器](/cms/features/strapi-mcp-server): 了解如何启用、配置、连接和使用 Strapi 内置的 MCP 服务器。 # 面向开发者的人工智能 Source: https://strapi.nodejs.cn/cms/ai/for-developers # 面向开发者的人工智能 {#ai-for-developers} 🌐 AI for developers Strapi 文档网站包括免费的 AI 驱动工具,包括 AI 工具栏、由 Kapa 驱动的聊天机器人、`llms.txt` 文件和 MCP 服务器,以帮助开发者更有效地学习和集成 Strapi。 🌐 The Strapi documentation site includes free AI-powered tools including an AI toolbar, chatbot powered by Kapa, `llms.txt` files, and MCP servers to help developers learn and integrate Strapi more effectively. Strapi 文档网站包括由 AI 支持的工具,帮助开发者更有效地学习、探索和集成 Strapi。这些工具免费使用,面向所有人开放。 🌐 The Strapi documentation site includes AI-powered tools to help developers learn, explore, and integrate Strapi more effectively. These tools are free to use and available to everyone. :::tip AGENTS.MD files In addition to docs and product features described on the present page, the [strapi/strapi](https://github.com/strapi/strapi/blob/develop/AGENTS.md) and [strapi/documentation](https://github.com/strapi/documentation/blob/main/AGENTS.md) repositories also have their own `AGENTS.md` files. Use them to guide your AI-based tools when developing Strapi features or updating documentation. ::: ## 人工智能工具栏 {#ai-toolbar} 🌐 AI toolbar 每个文档页面在标题下方的页面顶部附近都包含一个 AI 工具栏。该工具栏提供对当前页面的所有 AI 相关操作的快速访问。 🌐 Every documentation page includes an AI toolbar near the top of the page, right after the title. The toolbar provides quick access to all AI-related actions for the current page. 点击下拉箭头会显示更多选项: 🌐 Clicking the dropdown arrow reveals additional options: 工具栏包括以下操作: 🌐 The toolbar includes the following actions: | 操作 | 描述 | |--------|-------------| | **复制 Markdown** | 将当前页面的干净 Markdown 版本复制到剪贴板。在 Elegant 和 AI 模式下可用;在 Markdown 模式下,请使用工具栏旁的 **以 .md 格式查看此页面** 按钮。 | | **以 Markdown 查看** | 在新标签页中打开当前页面的干净 Markdown 版本。
在 Elegant 和 AI 模式下可用;在 Markdown 模式下,它被工具栏旁显示的 " 以 .md 格式查看此页面" 按钮替代。 | | **使用 ChatGPT 打开** | 打开一个新的 ChatGPT 对话,并预填充当前页面的 URL | | **使用 Claude 打开** | 打开一个新的 Claude 对话,并将提示复制到剪贴板 | | **查看 LLMs.txt** | 打开 AI 模型的轻量级页面索引 | | **查看 LLMs-code.txt** | 打开 AI 模型的代码示例文件 | | **查看 LLMs-full.txt** | 打开 AI 模型的完整文档文件 | ### 复制 Markdown {#copy-markdown} 🌐 Copy Markdown 工具栏中的主要操作。点击 **复制 Markdown** 会获取当前页面的干净 Markdown 版本(与页面的 `.md` URL 内容相同,布局组件已转换为纯 Markdown),并将其复制到剪贴板。然后,你可以将其粘贴到任何 AI 助手(ChatGPT、Claude、Gemini 等)中,用于: 🌐 The primary action in the toolbar. Clicking **Copy Markdown** fetches the clean Markdown version of the current page (the same content as the page's `.md` URL, with layout components resolved into plain Markdown) and copies it to your clipboard. You can then paste it into any AI assistant (ChatGPT, Claude, Gemini, etc.) for: - 在有完整上下文的情况下询问有关特定页面的问题 - 总结或简化文档内容 - 根据已记录的 API 生成代码 - 将文档翻译成另一种语言 在 Markdown 模式下,工具栏的 **复制 Markdown** 和 **以 Markdown 查看** 操作被一个单独的 **以 .md 查看此页面** 按钮取代,该按钮显示在工具栏旁,点击可打开相同的干净 Markdown。你也可以通过在任何页面 URL 后添加 `.md` 直接访问它。 🌐 In Markdown mode, the toolbar's **Copy Markdown** and **View as Markdown** actions are replaced by a single **View this page as .md** button shown next to the toolbar, which opens the same clean Markdown. You can also reach it directly by adding `.md` to any page URL. ### 用大型语言模型打开 {#open-with-llm} 🌐 Open with LLM **使用 ChatGPT 打开** 和 **使用 Claude 打开** 按钮会在相应的 AI 助手中打开一个新的对话,预先填充包含当前页面 URL 的提示。该提示会自动根据你的浏览器语言进行本地化。 🌐 The **Open with ChatGPT** and **Open with Claude** buttons open a new conversation in the respective AI assistant, prefilled with a prompt that includes the current page URL. The prompt is automatically localized to your browser's language. 对于 Claude,由于 URL 编码的工作方式不同,提示也会被复制到你的剪贴板。 🌐 For Claude, the prompt is also copied to your clipboard since URL encoding works differently. ## 人工智能聊天机器人 {#chatbot} 🌐 AI chatbot 由 [Kapa](https://kapa.ai) 驱动的 AI 聊天机器人直接集成在文档网站中。它从完整的文档、社区论坛、博客文章以及其他 Strapi 资源中获取信息,以提供上下文相关的答案。 🌐 An AI chatbot powered by [Kapa](https://kapa.ai) is integrated directly into the documentation site. It draws from the full documentation, community forums, blog posts, and other Strapi resources to provide contextual answers. ### 侧边栏入口 {#sidebar-entry-point} 🌐 Sidebar entry point 点击左侧边栏(搜索栏旁边)的 **询问 AI**按钮,开启关于 Strapi 相关内容的对话。 你可以提出如下问题: 🌐 You can ask questions like: - 我如何创建一个 Strapi 项目? - 在 REST API 中,人口(population)是如何工作的? - 我如何自定义管理面板? 对于复杂的问题,请启用**深度思考模式**以获得更全面(但更慢)的回答。 🌐 For complex questions, enable **deep thinking mode** for more thorough (but slower) answers. ### 代码块入口点 {#code-block-entry-point} 🌐 Code block entry point 将鼠标悬停在文档页面的任何代码块上,以显示代码块右上角的**Ask AI**按钮。点击它会打开一个预填充代码片段的对话框,因此你可以请求解释或修改。 🌐 Hover over any code block on a documentation page to reveal an **Ask AI** button in the top-right corner of the block. Clicking it opens a conversation prefilled with the code snippet, so you can ask for an explanation or adaptation. 这对于理解配置示例、API 响应或生命周期钩子模式尤其有用。 🌐 This is particularly useful for understanding configuration examples, API responses, or lifecycle hook patterns. ### 人工智能模式入口 {#ai-mode-entry-point} 🌐 AI mode entry point 每个文档页面都可以使用页面顶部的模式选择器(在 **Elegant 模式** 和 **Markdown 模式** 旁边)切换到 **AI 模式**。AI 模式将页面分为两列:左侧为文档内容,右侧为 AI 助手面板。 🌐 Every documentation page can be switched to **AI mode** using the mode selector at the top of the page (next to **Elegant mode** and **Markdown mode**). AI mode splits the page into two columns: the documentation content on the left, and an AI assistant panel on the right. 该面板显示当前页面的 AI 生成摘要和一个问题框,这样你可以在不离开页面或打开单独窗口的情况下,一边阅读页面一边提问。问题将由同一个 Kapa 驱动的聊天机器人回答,范围限定在你正在阅读的页面上。 🌐 The panel shows an AI-generated summary of the current page and a question box, so you can read the page and ask questions about it side by side, without leaving the page or opening a separate window. Questions are answered with the same Kapa-powered chatbot, scoped to the page you are reading. 要退出 AI 模式,请通过模式选择器切换回 **优雅模式** 或 **Markdown 模式**,或点击 AI 面板右上角的 。 ## 大型语言模型文本文件 {#llms-txt} 🌐 LLMs text files 有3个文本文件可用于将 Strapi 文档内容直接提供给大型语言模型(LLM)。这些文件遵循 [llms.txt](https://llmstxt.org/) 约定,并且旨在供 AI 工具以编程方式使用。 | 文件 | URL | 内容 | 最适合 | |------|-----|---------|----------| | `llms.txt` | [/llms.txt](https://strapi.nodejs.cn/llms.txt) | 所有页面的简明、链接丰富的概览 | 高层次上下文、导航、RAG 流水线 | | `llms-full.txt` | [/llms-full.txt](https://strapi.nodejs.cn/llms-full.txt) | 将整个文档放在一个文件中 | 在令牌限制允许的情况下获取完整站点上下文 | | `llms-code.txt` | [/llms-code.txt](https://strapi.nodejs.cn/llms-code.txt) | 所有代码示例,按页面分组 | 以代码为中心的工作、迁移、API 发现 | ### 何时使用每个文件 {#when-to-use-each-file} 🌐 When to use each file - **`llms.txt`**:使用此方法可以向 AI 模型概述 Strapi 文档的内容,而不会消耗过多的令牌。非常适合用于 RAG(检索增强生成)系统,或作为深入研究之前的初步浏览。 - **`llms-full.txt`**:当你需要 AI 访问完整文档内容时使用此选项。这个文件很大;请确保你的模型的上下文窗口能够处理它。 - **`llms-code.txt`**:当你在编写代码时,并且想要将 Strapi 的所有文档代码示例提供给 AI 使用时,请使用此选项。每个代码片段都包含来源页面的 URL 和锚点以便追踪。 ## MCP 服务器 {#mcp} 🌐 MCP servers [模型上下文协议 (MCP)](https://modelcontextprotocol.io) 是一个开放标准,允许 AI 工具与外部服务交互。Strapi 提供 2 个 MCP 服务器: 🌐 The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard that lets AI tools interact with external services. 2 MCP servers are available for Strapi: - [Strapi MCP 服务器](/cms/features/strapi-mcp-server): 将 AI 客户端连接到你的 Strapi 实例,通过自然语言管理内容。 - [Docs MCP 服务器](/cms/ai/docs-mcp-server): 将 Strapi 文档连接到你的 IDE,以获取最新、可靠的信息。 ### 使用 Docs MCP 服务器获得更好结果的提示 {#tips} 🌐 Tips for better results with the Docs MCP server 以下提示将帮助你优化提示以获得最佳结果: 🌐 The following tips will help you fine-tune your prompts to get the best results: - 在你的 IDE 中使用 [Docs MCP 服务器](/cms/ai/docs-mcp-server) 以获得最快的开发者体验。对于与文档相关的问题,在提示前加上 `Use the strapi-docs MCP server to answer:`,这样工具会查询 docs.strapi.io,而不是使用可能过时的训练数据。 - 包含页面网址,以便助手在正确的上下文中提供答案。 - 提到你的 Strapi 版本(例如,Strapi 5)以避免过时的建议。 - 在分享 `llms-code.txt` 的代码片段时,将代码示例与其来源页面配对。 - 在请求代码生成时,优先使用有文档的 API 而不是私有内部接口。 ## Inki {#inki} [Inki](https://github.com/strapi/documentation/tree/main/claude-plugins/inki) 是一个 Claude Code 插件,可以让参与 Strapi 文档的贡献变得更容易。它整合了文档团队在研究新内容的归属位置、从正确的模板起草、根据风格指南审查以及验证代码示例时所使用的技能、提示、模板和编辑规则,然后创建拉取请求。你可以从本仓库的市场安装它,并在 Claude Code 中运行整个工作流程,或单独运行其中的任何一步。 如果你不使用 Claude Code,你仍然可以从 Inki 中受益:它的提示和创作指南与 LLM 无关,因此你可以将任何 AI 代理(Cursor、GitHub Copilot、Cline、Windsurf 等)指向插件的文件夹,并重用其提示、模板、创作指南和编辑规则。其中大多数位于 `references/` 下,技能和代理定义也是可阅读的 Markdown。 🌐 If you don't use Claude Code, you can still benefit from Inki: its prompts and authoring guides are LLM-agnostic, so you can point any AI agent (Cursor, GitHub Copilot, Cline, Windsurf, and others) at the plugin's folder and reuse its prompts, templates, authoring guides, and editorial rules. Most of these live under `references/`, and the skill and agent definitions are readable Markdown too. # Strapi 客户端 Source: https://strapi.nodejs.cn/cms/api/client # Strapi 客户端 {#strapi-client} 🌐 Strapi Client Strapi 客户端是一个 JavaScript 库,它简化了与 Strapi 后端的交互,用于通过 `collection()`、`single()` 和 `files()` 方法获取、创建、更新和删除内容。 🌐 The Strapi Client is a JavaScript library that simplifies interactions with your Strapi back end for fetching, creating, updating, and deleting content through `collection()`, `single()`, and `files()` methods. Strapi 客户端库简化了与 Strapi 后端的交互,提供了一种获取、创建、更新和删除内容的方式。本指南将引导你完成 Strapi 客户端的设置、身份验证配置,以及有效使用其主要功能。 🌐 The Strapi Client library simplifies interactions with your Strapi back end, providing a way to fetch, create, update, and delete content. This guide walks you through setting up the Strapi Client, configuring authentication, and using its key features effectively. ## 入门 {#getting-started} 🌐 Getting Started :::prerequisites - Strapi 项目已创建并正在运行。如果你还没有设置,请按照[快速入门指南](/cms/quick-start)创建一个。 - 你知道你的 Strapi 实例的内容 API 的 URL(例如,`http://localhost:1337/api`)。 ::: ### 安装 {#installation} 🌐 Installation 要在你的项目中使用 Strapi 客户端,请使用你首选的包管理器将其作为依赖安装: 🌐 To use the Strapi Client in your project, install it as a dependency using your preferred package manager: ```bash yarn add @strapi/client ``` ```bash npm install @strapi/client ``` ```bash pnpm add @strapi/client ``` ### 基本配置 {#basic-configuration} 🌐 Basic configuration 要开始与 Strapi 后端交互,请初始化 Strapi 客户端并设置基本 API URL: 🌐 To start interacting with your Strapi back end, initialize the Strapi Client and set the base API URL: 使用 Javascript,导入 `strapi` 函数并创建一个客户端实例: 🌐 With Javascript, import the `strapi` function and create a client instance: ```js const client = strapi({ baseURL: 'http://localhost:1337/api' }); ``` 使用 Typescript,导入 `strapi` 函数,并使用你的 Strapi API 基础 URL 创建一个客户端实例: 🌐 With Typescript, import the `strapi` function and create a client instance with your Strapi API base URL: ```typescript const client = strapi({ baseURL: 'http://localhost:1337/api' }); ``` 如果你在浏览器环境中使用 Strapi 客户端,可以使用 ` ``` `baseURL` 必须包含协议(`http` 或 `https`)。无效的 URL 将抛出错误 `StrapiInitializationError`。 🌐 The `baseURL` must include the protocol (`http` or `https`). An invalid URL will throw an error `StrapiInitializationError`. ### 身份验证 {#authentication} 🌐 Authentication Strapi 客户端支持不同的身份验证策略来访问 Strapi 后端中受保护的资源。 🌐 The Strapi Client supports different authentication strategies to access protected resources in your Strapi back end. 如果你的 Strapi 实例使用 [API 令牌](/cms/features/api-tokens),请按如下方式配置 Strapi 客户端: 🌐 If your Strapi instance uses [API tokens](/cms/features/api-tokens), configure the Strapi Client as follows: ```js const client = strapi({ baseURL: 'http://localhost:1337/api', auth: 'your-api-token-here', }); ``` 这允许你的请求自动包含必要的身份验证凭据。如果令牌无效或缺失,客户端在初始化时将抛出错误 `StrapiValidationError`。 🌐 This allows your requests to include the necessary authentication credentials automatically. If the token is invalid or missing, the client will throw an error during initialization `StrapiValidationError`. ## API参考 {#api-reference} 🌐 API Reference Strapi 客户端提供以下关键属性和方法用于与你的 Strapi 后端交互: 🌐 The Strapi Client provides the following key properties and methods for interacting with your Strapi back end: | 参数 | 描述 | | ---| --- | | `baseURL` | 你的 Strapi 后端的基础 API URL。 | | `fetch()` | 一个用于发起通用 API 请求的工具方法,类似于原生 fetch API。 | | `collection()` | 管理集合类型资源(例如博客文章、产品)。 | | `single()` | 管理单一类型资源(例如主页设置、全局配置)。 | | `files()` | 直接向 Strapi 媒体库上传、检索和管理文件的功能。 | ### 通用抓取 {#general-purpose-fetch} 🌐 General purpose fetch Strapi 客户端提供了对底层 JavaScript `fetch` 函数的访问,以便直接进行 API 请求。请求总是相对于客户端初始化时提供的基础 URL: 🌐 The Strapi Client provides access to the underlying JavaScript `fetch` function to make direct API requests. The request is always relative to the base URL provided during client initialization: ```js const result = await client.fetch('articles', { method: 'GET' }); ``` ### 与集合类型一起工作 {#working-with-collection-types} 🌐 Working with collection types Strapi 中的集合类型是具有多个条目的实体(例如,拥有多篇文章的博客)。Strapi 客户端提供了一个 `collection()` 方法来与这些资源进行交互,具有以下可用方法: 🌐 Collection types in Strapi are entities with multiple entries (e.g., a blog with many posts). The Strapi Client provides a `collection()` method to interact with these resources, with the following methods available: | 参数 | 描述 | | ---| --- | | `find(queryParams?)` | 获取多个文档,可选择性地进行过滤、排序或分页。 | | `findOne(documentID, queryParams?)` | 根据唯一 ID 检索单个文档。 | | `create(data, queryParams?)` | 在集合中创建新文档。 | | `update(documentID, data, queryParams?)` | 更新现有文档。 | | `delete(documentID, queryParams?)` | 删除现有文档。 | **使用示例:** ```js const articles = client.collection('articles'); // Fetch all english articles sorted by title const allArticles = await articles.find({ locale: 'en', sort: 'title', }); // Fetch a single article const singleArticle = await articles.findOne('article-document-id'); // Create a new article const newArticle = await articles.create({ title: 'New Article', content: '...' }); // Update an existing article const updatedArticle = await articles.update('article-document-id', { title: 'Updated Title' }); // Delete an article await articles.delete('article-id'); ``` ### 处理单一类型 {#working-with-single-types} 🌐 Working with single types Strapi 中的单一类型表示只存在一次的独特内容条目(例如首页设置或全站配置)。Strapi 客户端提供了 `single()` 方法来与这些资源进行交互,可用的方法如下: | 参数 | 描述 | | ----------| -------------------------------------------------------------------------------------------- | | `find(queryParams?)` | 获取文档。 | | `update(documentID, data, queryParams?)` | 更新文档。 | | `delete(queryParams?)` | 删除文档。 | 🌐 Single types in Strapi represent unique content entries that exist only once (e.g., the homepage settings or site-wide configurations). The Strapi Client provides a `single()` method to interact with these resources, with the following methods available: | Parameter | Description | | ----------| -------------------------------------------------------------------------------------------- | | `find(queryParams?)` | Fetch the document. | | `update(documentID, data, queryParams?)` | Update the document. | | `delete(queryParams?)` | Remove the document. | **使用示例:** ```js const homepage = client.single('homepage'); // Fetch the default homepage content const defaultHomepage = await homepage.find(); // Fetch the Spanish version of the homepage const spanishHomepage = await homepage.find({ locale: 'es' }); // Update the homepage draft content const updatedHomepage = await homepage.update( { title: 'Updated Homepage Title' }, { status: 'draft' } ); // Delete the homepage content await homepage.delete(); ``` ### 处理文件 {#working-with-files} 🌐 Working with files Strapi 客户端通过 `files` 属性提供对 [媒体库](/cms/features/media-library) 的访问。这使你能够在不直接与 REST API 交互的情况下检索和管理文件元数据。 🌐 The Strapi Client provides access to the [Media Library](/cms/features/media-library) via the `files` property. This allows you to retrieve and manage file metadata without directly interacting with the REST API. 以下方法可用于处理文件。点击表格中的方法名称即可跳转到相应部分,查看更多详细信息和示例: 🌐 The following methods are available for working with files. Click on the method name in the table to jump to the corresponding section with more details and examples: | 方法 | 描述 | |--------|-------------| | [`find(params?)`](#find) | 根据可选查询参数检索文件元数据列表 | | [`findOne(fileId)`](#findone) | 根据文件 ID 检索单个文件的元数据 | | [`update(fileId, fileInfo)`](#update) | 更新现有文件的元数据 | | [`upload(file, options)`](#upload) | 上传文件(Blob 或 Buffer),可选提供用于元数据的 `options` 对象 | | [`delete(fileId)`](#delete) | 根据文件 ID 删除文件 | #### `find` `strapi.client.files.find()` 方法根据可选查询参数检索文件元数据列表。 🌐 The `strapi.client.files.find()` method retrieves a list of file metadata based on optional query parameters. 该方法的使用方式如下: 🌐 The method can be used as follows: ```js // Initialize the client const client = strapi({ baseURL: 'http://localhost:1337/api', auth: 'your-api-token', }); // Find all file metadata const allFiles = await client.files.find(); console.log(allFiles); // Find file metadata with filtering and sorting const imageFiles = await client.files.find({ filters: { mime: { $contains: 'image' }, // Only get image files name: { $contains: 'avatar' }, // Only get files with 'avatar' in the name }, sort: ['name:asc'], // Sort by name in ascending order }); ``` #### `findOne` {#findone} `strapi.client.files.findOne()` 方法通过其 ID 检索单个文件的元数据。 🌐 The `strapi.client.files.findOne()` method retrieves the metadata for a single file by its id. 该方法的使用方式如下: 🌐 The method can be used as follows: ```js // Initialize the client const client = strapi({ baseURL: 'http://localhost:1337/api', auth: 'your-api-token', }); // Find file metadata by ID const file = await client.files.findOne(1); console.log(file.name); console.log(file.url); console.log(file.mime); // The file MIME type ``` #### `update` `strapi.client.files.update()` 方法更新现有文件的元数据,接受两个参数,`fileId`,以及包含选项的对象,例如媒体的名称、替代文本和标题。 🌐 The `strapi.client.files.update()` method updates metadata for an existing file, accepting 2 parameters, the `fileId`, and an object containing options such as the name, alternative text, and caption for the media. 这些方法的使用方式如下: 🌐 The methods can be used as follows: ```js // Initialize the client const client = strapi({ baseURL: 'http://localhost:1337/api', auth: 'your-api-token', }); // Update file metadata const updatedFile = await client.files.update(1, { name: 'New file name', alternativeText: 'Descriptive alt text for accessibility', caption: 'A caption for the file', }); ``` #### `upload` {#上传} 🌐 `upload` Strapi 客户端通过 `FilesManager` 提供媒体文件上传功能,可通过 `strapi.client.files.upload()` 方法访问。该方法允许你将媒体文件(如图片、视频或文档)上传到你的 Strapi 后端。 🌐 The Strapi Client provides media file upload functionality through the `FilesManager`, accessible through the `strapi.client.files.upload()` method. The method allows you to upload media files (such as images, videos, or documents) to your Strapi backend. 该方法支持将文件作为 `Blob`(在浏览器或 Node.js 中)或作为 `Buffer`(仅在 Node.js 中)上传。该方法还支持向上传的文件附加元数据,例如 `alternativeText` 和 `caption`。 🌐 The method supports uploading files as `Blob` (in browsers or Node.js) or as `Buffer` (in Node.js only). The method also supports attaching metadata to the uploaded file, such as `alternativeText` and `caption`. ##### 方法签名 {#method-signature} 🌐 Method Signature ```js async upload(file: Blob, options?: BlobUploadOptions): Promise async upload(file: Buffer, options: BufferUploadOptions): Promise ``` - 对于 `Blob` 上传,`options` 是可选的,并且可以包含 `fileInfo` 作为元数据。 - 对于 `Buffer` 上传,`options` 必须包含 `filename` 和 `mimetype`,并且可以包含 `fileInfo`。 响应是一个文件对象数组,每个对象包含 `id`、`name`、`url`、`size` 和 `mime` 等详细信息 [source](https://github.com/strapi/client/blob/60a0117e361346073bed1959d354c7facfb963b3/src/files/types.ts)。 🌐 The response is an array of file objects, each containing details such as `id`, `name`, `url`, `size`, and `mime` [source](https://github.com/strapi/client/blob/60a0117e361346073bed1959d354c7facfb963b3/src/files/types.ts). 你可以通过浏览器上传文件,如下所示: 🌐 You can upload a file use through the browser as follows: ```js const client = strapi({ baseURL: 'http://localhost:1337/api' }); const fileInput = document.querySelector('input[type="file"]'); const file = fileInput.files[0]; try { const result = await client.files.upload(file, { fileInfo: { alternativeText: 'A user uploaded image', caption: 'Uploaded via browser', }, }); console.log('Upload successful:', result); } catch (error) { console.error('Upload failed:', error); } ``` 使用 Node.js,你可以上传 blob 或缓冲区,如下例所示: 🌐 With Node.js, you can either upload a blob or a buffer, as in the following examples: ```js const client = strapi({ baseURL: 'http://localhost:1337/api' }); const filePath = './image.png'; const mimeType = 'image/png'; const fileContentBuffer = await readFile(filePath); const fileBlob = new Blob([fileContentBuffer], { type: mimeType }); try { const result = await client.files.upload(fileBlob, { fileInfo: { name: 'Image uploaded as Blob', alternativeText: 'Uploaded from Node.js Blob', caption: 'Example upload', }, }); console.log('Blob upload successful:', result); } catch (error) { console.error('Blob upload failed:', error); } ``` ```js const client = strapi({ baseURL: 'http://localhost:1337/api' }); const filePath = './image.png'; const fileContentBuffer = await readFile(filePath); try { const result = await client.files.upload(fileContentBuffer, { filename: 'image.png', mimetype: 'image/png', fileInfo: { name: 'Image uploaded as Buffer', alternativeText: 'Uploaded from Node.js Buffer', caption: 'Example upload', }, }); console.log('Buffer upload successful:', result); } catch (error) { console.error('Buffer upload failed:', error); } ``` ##### 响应结构 {#response-structure} 🌐 Response Structure `strapi.client.files.upload()` 方法返回一个文件对象数组,每个对象都有如下字段: 🌐 The `strapi.client.files.upload()` method returns an array of file objects, each with fields such as: ```json { "id": 1, "name": "image.png", "alternativeText": "Uploaded from Node.js Buffer", "caption": "Example upload", "mime": "image/png", "url": "/uploads/image.png", "size": 12345, "createdAt": "2025-07-23T12:34:56.789Z", "updatedAt": "2025-07-23T12:34:56.789Z" } ``` :::note Additional response fields 上传响应包括上述显示内容之外的其他字段。有关所有可用字段,请参阅 [client source code](https://github.com/strapi/client/blob/main/src/files/types.ts) 中的完整 FileResponse 接口。 ::: #### `delete` `strapi.client.files.delete()` 方法通过其 ID 删除文件。 🌐 The `strapi.client.files.delete()` method deletes a file by its ID. 该方法的使用方式如下: 🌐 The method can be used as follows: ```js // Initialize the client const client = strapi({ baseURL: 'http://localhost:1337/api', auth: 'your-api-token', }); // Delete a file by ID const deletedFile = await client.files.delete(1); console.log('File deleted successfully'); console.log('Deleted file ID:', deletedFile.id); console.log('Deleted file name:', deletedFile.name); ```
## 处理常见错误 {#handling-common-errors} 🌐 Handling Common Errors 通过 Strapi 客户端发送查询时可能会出现以下错误: 🌐 The following errors might occur when sending queries through the Strapi Client: | 错误 | 描述 | |-------|-------------| | 权限错误 | 如果经过身份验证的用户没有上传或管理文件的权限,将抛出 `FileForbiddenError`。 | | HTTP 错误 | 如果服务器无法访问、身份验证失败或存在网络问题,将抛出 `HTTPError`。 | | 缺少参数 | 当上传 `Buffer` 时,必须在选项对象中提供 `filename` 和 `mimetype`。如果缺少任何一个,将抛出错误。 | :::strapi Additional information 有关 Strapi 客户端的更多详细信息可以在 [package](https://github.com/strapi/client/blob/main/README.md)中找到。 ::: # 内容 API Source: https://strapi.nodejs.cn/cms/api/content-api # Strapi API 访问你的内容 {#strapi-apis-to-access-your-content} 🌐 Strapi APIs to access your content Strapi 的内容 API 通过 REST 和 GraphQL API 为前端应用提供对内容的访问,同时为后端和插件开发提供更底层的文档服务和查询引擎 API。 🌐 Strapi's Content API provides access to your content through REST and GraphQL APIs for front-end applications, plus lower-level Document Service and Query Engine APIs for backend and plugin development. 一旦你创建并配置了一个 Strapi 项目,使用 [内容类型构建器](/cms/features/content-type-builder) 创建了内容结构,并通过 [内容管理器](/cms/features/content-manager) 开始添加数据,你可能希望访问你的内容。 🌐 Once you've created and configured a Strapi project, created a content structure with the [Content-Type Builder](/cms/features/content-type-builder) and started adding data through the [Content Manager](/cms/features/content-manager), you likely would like to access your content. 从前端应用,你的内容可以通过 Strapi 的 Content API 访问,该 API 公开: 🌐 From a front-end application, your content can be accessed through Strapi's Content API, which is exposed: - 默认通过 [REST API](/cms/api/rest) - 如果你安装了 Strapi 内置的 [GraphQL 插件](/cms/plugins/graphql),也可以通过 [GraphQL API](/cms/api/graphql) 访问。 你也可以使用 [Strapi Client](/cms/api/client) 库与 REST API 进行交互。 🌐 You can also use the [Strapi Client](/cms/api/client) library to interact with the REST API. REST 和 GraphQL API 代表了面向外部应用暴露的内容 API 的顶层。Strapi 还提供了两个较低层次的 API: 🌐 REST and GraphQL APIs represent the top-level layers of the Content API exposed to external applications. Strapi also provides 2 lower-level APIs: - [文档服务 API](/cms/api/document-service),可通过 `strapi.documents` 访问,是在 [后端服务器](/cms/customization) 内或通过 [插件](/cms/plugins-development/developing-plugins) 与你的应用数据库交互的推荐 API。文档服务是处理 **文档** 以及 Strapi 的复杂内容结构(如组件和动态区域)的层。 - [查询引擎 API](/cms/api/query-engine) 可通过 `db.query`(即 `strapi.db.query`)访问,它在更底层与数据库层交互,并用于在后台执行数据库查询。它提供对数据库层的不受限制的内部访问,但无法识别 Strapi 5 可以处理的任何高级功能,例如草稿与发布、国际化、内容历史等。
⚠️ 在大多数(如果不是所有)使用场景中,你应该使用文档服务 API。
本文档部分包括有关以下 Strapi API 的参考信息以及一些与第三方技术的集成指南: 🌐 This documentation section includes reference information about the following Strapi APIs and some integration guides with 3rd party technologies: - [REST API](/cms/api/rest): 通过 REST 从前端应用查询内容 API。 - [GraphQL API](/cms/api/graphql): 通过 GraphQL 从前端应用查询内容 API。 - [Strapi 客户端](/cms/api/client): 通过 Strapi 客户端库与 REST API 进行交互。 - [文档服务 API](/cms/api/document-service): 通过后端服务器或插件查询你的数据。 - [OpenAPI 规范](/cms/api/openapi): 为你的 Strapi 应用生成 OpenAPI 规范。 :::strapi Integrations 如果你想了解如何将 Strapi 与其他平台(如 [Next.js](https://strapi.io/integrations/nextjs-cms)、 [Astro](https://strapi.io/integrations/astro)、 [Angular](https://strapi.io/integrations/angular-cms)等)集成,请参考 Strapi 的 [integrations pages](https://strapi.io/integrations)。 ::: # 文件 Source: https://strapi.nodejs.cn/cms/api/document
# 文件 {#documents} 🌐 Documents 文档是一个仅限 API 的概念,表示单个内容类型条目的所有内容变体(本地化、草稿/已发布版本)。使用文档服务 API 在后端操作文档。 🌐 A document is an API-only concept representing all content variations (locales, draft/published versions) for a single content-type entry. Use the Document Service API to manipulate documents on the back-end. 在 Strapi 5 中,**文档**是一个仅限 API 的概念。文档代表了给定内容类型条目所有不同的内容变体。 🌐 A **document** in Strapi 5 is an API-only concept. A document represents all the different variations of content for a given entry of a content-type. 单一类型包含一个唯一文档,而集合类型可以包含多个文档。 🌐 A single type contains a unique document, and a collection type can contain several documents. 当你使用管理员面板时,从未提到文档的概念,并且对终端用户并不必要。用户在[内容管理器](/cms/features/content-manager)中创建和编辑**条目**。例如,作为用户,你要么列出给定语言环境下的条目,要么编辑给定语言环境中特定条目的草稿版本。 🌐 When you use the admin panel, the concept of a document is never mentioned and not necessary for the end user. Users create and edit **entries** in the [Content Manager](/cms/features/content-manager). For instance, as a user, you either list the entries for a given locale, or edit the draft version of a specific entry in a given locale. 但是,在 API 级别,条目字段的值实际上可以具有: 🌐 However, at the API level, the value of the fields of an entry can actually have: - 英语和法语语言环境的内容不同, - 甚至在每个语言环境中为草稿和已发布版本设置不同的内容。 包含所有语言环境的所有草稿和已发布版本内容的存储桶是一个文档。 🌐 The bucket that includes the content of all the draft and published versions for all the locales is a document. 使用 [文档服务 API](/cms/api/document-service) 操作文档将帮助你创建、检索、更新和删除文档或其中包含的特定数据子集。 🌐 Manipulating documents with the [Document Service API](/cms/api/document-service) will help you create, retrieve, update, and delete documents or a specific subset of the data they contain. 下列图表显示了内容的所有可能变体,这取决于内容类型启用了哪些功能,例如[国际化 (i18n)](/cms/features/internationalization)和[草稿与发布](/cms/features/draft-and-publish): 🌐 The following diagrams represent all the possible variations of content depending on which features, such as [Internationalization (i18n)](/cms/features/internationalization) and [Draft & Publish](/cms/features/draft-and-publish), are enabled for a content-type: - 如果内容类型启用了国际化 (i18n) 功能,则一个文档可以拥有多个**文档语言环境**。 - 如果在内容类型上启用了“草稿与发布”功能,文档可以同时拥有**已发布**版本和**草稿**版本。 :::strapi APIs to query documents data 要与文档或它们所代表的数据进行交互: 🌐 To interact with documents or the data they represent: - 从后端服务器(例如,从控制器、服务以及插件的后端部分)使用 [文档服务 API](/cms/api/document-service)。 - 从应用的前端部分,使用 [REST API](/cms/api/rest) 或 [GraphQL API](/cms/api/graphql) 查询你的数据。 有关 API 的更多信息,请参阅 [内容 API 介绍](/cms/api/content-api)。 🌐 For additional information about the APIs, please refer to the [Content API introduction](/cms/api/content-api). ::: :::info Default version in returned results 后端和前端 API 之间的一个重要区别是关于未传递参数时返回的默认版本: 🌐 An important difference between the back-end and front-end APIs is about the default version returned when no parameter is passed: - 文档服务 API 默认返回草稿版本, - 而 REST 和 GraphQL API 默认返回已发布的版本。 :::
# 文档服务 API Source: https://strapi.nodejs.cn/cms/api/document-service # 文档服务 API {#document-service-api} 🌐 Document Service API 文档服务 API 是与内容交互的推荐后端 API,提供 `findOne`、`findMany`、`create`、`update` 和 `delete` 方法,这些方法可与稳定的 `documentId` 标识符一起使用,并支持草稿与发布操作。 🌐 The Document Service API is the recommended backend API for interacting with content, providing `findOne`, `findMany`, `create`, `update`, and `delete` methods that work with stable `documentId` identifiers and support Draft & Publish operations. 文档服务 API 建立在 **查询引擎 API** 之上 有两种不同的后端 API 可让你与内容进行交互:
  • [查询引擎 API](/cms/api/query-engine) 是较底层的一层,提供对数据库的无限制访问,但不了解诸如组件和动态区域等复杂的 Strapi 内容结构。
  • 文档服务 API 构建在查询引擎之上,是在自定义后端服务器或开发插件时与内容交互的推荐方式。
更多详情可参见 [内容 API](/cms/api/content-api) 和 [后端自定义](/cms/backend-customization) 介绍。 并用于对 **文档** 执行 CRUD([创建](#create)、[检索](#findone)、[更新](#update) 和 [删除](#delete))操作 。 文档服务 API 还支持[计数](#count)文档,并且如果内容类型启用了[草稿与发布](/cms/features/draft-and-publish),可以执行 Strapi 特定操作,例如[发布](#publish)、[取消发布](#unpublish)以及[丢弃草稿](#discarddraft)。 🌐 The Document Service API also supports [counting](#count) documents and, if [Draft & Publish](/cms/features/draft-and-publish) is enabled on the content-type, performing Strapi-specific operations such as [publishing](#publish), [unpublishing](#unpublish), and [discarding drafts](#discarddraft). 在 Strapi 5 中,文档在 API 级别上通过它们的 `documentId` 唯一标识。 🌐 In Strapi 5, documents are uniquely identified by their `documentId` at the API level. **`documentId` 解释:从 Strapi v4 替换 `id`** 在以前的 Strapi 版本中,`id` 的概念(既用于内容 API,也用作数据库行标识符)并不总是稳定:单个条目可能有多个版本或本地化,其数字标识符 `id` 在复制或导入/导出操作等情况下可能会发生变化。 🌐 In previous Strapi versions, the concept of `id` (used both in the Content API and as the database row identifier) was not always stable: a single entry could have multiple versions or localizations, and its numeric identifier `id` could change in cases such as duplication or import/export operations. 为了解决这一限制,Strapi 5 引入了 `documentId`,一个由 24 个字符组成的字母数字字符串,作为内容条目的唯一且持久的标识符,独立于其物理记录。 🌐 To address this limitation, Strapi 5 introduced `documentId`, a 24-character alphanumeric string, as a unique and persistent identifier for a content entry, independent of its physical records. 这个新的标识符在 Strapi 5 内部用于管理关系、发布、本地化和版本历史,因为内容条目的所有可能变体现在都被归类在单一的[文档](/cms/api/document)概念下。 🌐 This new identifier is used internally in Strapi 5 to manage relationships, publishing, localization, and version history, as all possible variations of a content entry are now grouped under a single [document](/cms/api/document) concept. 因此,从 Strapi 5 开始,许多 API 和服务依赖 `documentId` 而不是 `id` 来确保操作的一致性。一些 API 可能仍会返回 `documentId` 和 `id` 以便过渡,但强烈建议在内容查询中使用 `documentId`,因为 `documentId` 可能是未来 Strapi 版本中唯一使用的标识符。 🌐 As a result, starting with Strapi 5, many APIs and services rely on `documentId` instead of `id` to ensure consistency across operations. Some APIs may still return both `documentId` and `id` to ease the transition, but using `documentId` for content queries is strongly recommended, as `documentId` might be the only identifier used in future Strapi versions. 有关从 `id` 过渡到 `documentId` 的更多详细信息,请参阅 [重大更改页面](/cms/migration/v4-to-v5/breaking-changes/use-document-id) 和 [从实体服务到文档服务 API 的迁移指南](/cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service)。 🌐 For more details on the transition from `id` to `documentId`, refer to the [breaking change page](/cms/migration/v4-to-v5/breaking-changes/use-document-id) and the [migration guide from Entity Service to Document Service API](/cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service). :::strapi Entity Service API is deprecated in Strapi 5 文档服务 API 取代了 Strapi v4 ([see Strapi v4 documentation](https://docs-v4.strapi.io/dev-docs/api/entity-service)) 中使用的实体服务 API。 有关如何从实体服务 API 迁移到文档服务 API 的更多信息,请参阅 [迁移参考](/cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service)。 🌐 Additional information on how to migrate from the Entity Service API to the Document Service API can be found in the [migration reference](/cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service). ::: :::note 关系也可以通过文档服务 API 进行连接、断开和设置,就像使用 REST API 一样(示例见 [REST API 关系文档](/cms/api/rest/relations))。 🌐 Relations can also be connected, disconnected, and set through the Document Service API, just like with the REST API (see the [REST API relations documentation](/cms/api/rest/relations) for examples). ::: :::caution Document Service returns unsanitized data 文档服务是一个数据访问层:它与数据库交互,并且不知道用户权限或字段可见性。结果可能包含私有字段、密码和受限制的关系。 🌐 The Document Service is a data-access layer: it interacts with the database and is not aware of user permissions or field visibility. Results may include private fields, passwords, and restricted relations. 内置的 REST 和 GraphQL API 会在将响应发送给客户端之前自动进行清理。但是,如果你构建自定义控制器或插件路由并直接调用文档服务方法,你必须在返回输出之前自己进行清理。在你的控制器中使用 `strapi.contentAPI.sanitize.output()`(详情和代码示例请参阅 [构建自定义控制器时的清理和验证](/cms/backend-customization/controllers#sanitize-validate-custom-controllers))。 🌐 The built-in REST and GraphQL APIs automatically sanitize responses before sending them to the client. But if you build custom controllers or plugin routes that call Document Service methods directly, you must sanitize the output yourself before returning it. Use `strapi.contentAPI.sanitize.output()` in your controller (see [Sanitization and validation when building custom controllers](/cms/backend-customization/controllers#sanitize-validate-custom-controllers) for details and code examples). ::: ## 配置 {#configuration} 🌐 Configuration `documents.strictParams` 选项启用对传递给 Document Service 方法(如 `findMany` 和 `findOne`)的参数的严格验证。可以在 [API 配置](/cms/configurations/api) 文件(`./config/api.js` 或 `./config/api.ts`)中进行配置。有关 `documents.strictParams` 的详细信息,请参阅 [API 配置](/cms/configurations/api) 表。 🌐 The `documents.strictParams` option enables strict validation of parameters passed to Document Service methods such as `findMany` and `findOne`. Configure it in the [API configuration](/cms/configurations/api) file (`./config/api.js` or `./config/api.ts`). See the [API configuration](/cms/configurations/api) table for details on `documents.strictParams`. ## 文档对象 {#document-objects} 🌐 Document objects 文档方法返回一个文档对象或文档对象列表,这些对象表示在稳定的 `documentId` 下分组的内容条目的一个版本。返回的对象通常包括: 🌐 Document methods return a document object or a list of document objects, which represent a version of a content entry grouped under a stable `documentId`. Returned objects typically include: - `documentId`:跨语言环境和草稿/已发布版本的条目持久标识符。 - `id`:特定语言环境/版本记录的数据库标识符。 - 模型字段:内容类型架构中定义的所有字段。关系、组件和动态区域不会被填充,除非你选择使用 `populate`(参见 [填充字段](/cms/api/document-service/populate))或使用 `fields` 限制字段(参见 [选择字段](/cms/api/document-service/fields))。 - 元数据:`publishedAt`、`createdAt`、`updatedAt`,以及在可用时的 `createdBy`/`updatedBy`。 可选地,如果为内容类型启用了 [Draft & Publish](/cms/features/draft-and-publish) 和 [Internationalization](/cms/features/internationalization),文档对象也可以包含 `status` 和 `locale` 属性。 🌐 Optionally, document objects can also include a `status` and `locale` property if [Draft & Publish](/cms/features/draft-and-publish) and [Internationalization](/cms/features/internationalization) are enabled for the content-type. ## 方法概述 {#method-overview} 🌐 Method overview 以下各节分别介绍特定方法的参数和示例: 🌐 Each section below documents the parameters and examples for a specific method: | 方法 | 目的 | | --- | --- | | [`findOne()`](#findone) | 通过 `documentId` 获取文档,可选择限定到特定语言或状态。 | | [`findFirst()`](#findfirst) | 返回第一个匹配过滤条件的文档。 | | [`findMany()`](#findmany) | 列出符合过滤条件的文档,并支持排序和分页。 | | [`create()`](#create) | 创建文档,可选择针对特定语言。 | | [`update()`](#update) | 通过 `documentId` 更新文档。 | | [`delete()`](#delete) | 删除文档或特定语言版本。 | | [`deleteMany()`](#deletemany) | 删除符合过滤条件和关系参数的多个文档。 | | [`publish()`](#publish) | 发布文档的草稿版本。 | | [`unpublish()`](#unpublish) | 将已发布的文档移回草稿状态。 | | [`discardDraft()`](#discarddraft) | 删除草稿数据,仅保留已发布版本。 | | [`count()`](#count) | 统计有多少文档符合参数条件。 | :::note Draft & Publish method availability [`publish()`](#publish)、[`unpublish()`](#unpublish) 和 [`discardDraft()`](#discarddraft) 方法仅在内容类型启用了“草稿与发布”功能时可用。在未启用“草稿与发布”的内容类型上调用这些方法将会抛出错误。要启用“草稿与发布”,请参阅 [草稿与发布文档](/cms/features/draft-and-publish)。 🌐 The [`publish()`](#publish), [`unpublish()`](#unpublish), and [`discardDraft()`](#discarddraft) methods are only available when the Draft & Publish feature is enabled on the content-type. Calling these methods on a content-type that does not have Draft & Publish enabled will throw an error. To enable Draft & Publish, see the [Draft & Publish documentation](/cms/features/draft-and-publish). ::: ### `findOne()` 语法:`findOne(parameters: Params) => Document` 🌐 Syntax: `findOne(parameters: Params) => Document` #### GET strapi.documents().findOne() — findOne() 查找与传入的 documentId 和参数匹配的文档。如果仅传入 documentId 而没有其他参数,findOne() 将返回默认语言环境下文档的草稿版本。如果找到匹配的文档,则返回该文档,否则返回 null。 **Parameters:** - `documentId` (ID, required): Document id - `locale` (String or undefined): Locale of the document to find. Defaults to the default locale. See locale docs (/cms/api/document-service/locale#find-one). - `status` (): If Draft & Publish (/cms/features/draft-and-publish) is enabled: publication status. Can be `published` or `draft`. Default: `draft`. See status docs (/cms/api/document-service/status#find-one). - `publicationFilter` (String): If Draft & Publish (/cms/features/draft-and-publish) is enabled: select documents by how their draft and published versions relate, before applying `status`. See publicationFilter docs (/cms/api/document-service/publication-filter). - `fields` (Object): Select fields (/cms/api/document-service/fields#findone) to return. Defaults to all fields (except those not populated by default). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. **Request:** ``` await strapi.documents('api::restaurant.restaurant').findOne({ documentId: 'a1b2c3d4e5f6g7h8i9j0klmn' }) ``` **Response 200 OK:** ```json { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "name": "Biscotte Restaurant", "publishedAt": null, "locale": "en" } ``` ### `findFirst()` 语法:`findFirst(parameters: Params) => Document` 🌐 Syntax: `findFirst(parameters: Params) => Document` #### GET strapi.documents().findFirst() — findFirst() 查找与参数匹配的第一个文档。默认情况下,findFirst() 返回传入唯一标识符(集合类型 ID 或单类型 ID)的第一个文档的草稿版本,使用默认语言环境。 **Parameters:** - `locale` (String or undefined): Locale of the documents to find. Defaults to the default locale. See locale docs (/cms/api/document-service/locale#find-first). - `status` (): If Draft & Publish (/cms/features/draft-and-publish) is enabled: publication status. Can be `published` or `draft`. Default: `draft`. See status docs (/cms/api/document-service/status#find-first). - `publicationFilter` (String): If Draft & Publish (/cms/features/draft-and-publish) is enabled: select documents by how their draft and published versions relate, before applying `status`. See publicationFilter docs (/cms/api/document-service/publication-filter). - `filters` (Object): Filters (/cms/api/document-service/filters) to use. Default: `null`. - `fields` (Object): Select fields (/cms/api/document-service/fields#findfirst) to return. Defaults to all fields (except those not populated by default). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. **Generic example:** ``` await strapi.documents('api::restaurant.restaurant').findFirst() ``` **With filters:** ``` await strapi.documents('api::restaurant.restaurant').findFirst( { filters: { name: { $startsWith: "Pizzeria" } } } ) ``` **Response 200 Generic:** ```json { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "name": "Restaurant Biscotte", "publishedAt": null, "locale": "en" } ``` **Response 200 With filters:** ```json { "documentId": "j9k8l7m6n5o4p3q2r1s0tuvw", "name": "Pizzeria Arrivederci", "publishedAt": null, "locale": "en" } ``` 如果未传入 `locale` 或 `status` 参数,结果将返回默认语言环境的草稿版本。 🌐 If no `locale` or `status` parameters are passed, results return the draft version for the default locale. ### `findMany()` 语法:`findMany(parameters: Params) => Document[]` 🌐 Syntax: `findMany(parameters: Params) => Document[]` #### GET strapi.documents().findMany() — findMany() 查找与参数匹配的文档。当未传递参数时,findMany() 会返回每个文档在默认语言环境下的草稿版本。 **Parameters:** - `locale` (String or undefined): Locale of the documents to find. Defaults to the default locale. See locale docs (/cms/api/document-service/locale#find-many). - `status` (): If Draft & Publish (/cms/features/draft-and-publish) is enabled: publication status. Can be `published` or `draft`. Default: `draft`. See status docs (/cms/api/document-service/status#find-many). - `publicationFilter` (String): If Draft & Publish (/cms/features/draft-and-publish) is enabled: select documents by how their draft and published versions relate, before applying `status`. See publicationFilter docs (/cms/api/document-service/publication-filter). - `filters` (Object): Filters (/cms/api/document-service/filters) to use. Default: `null`. - `fields` (Object): Select fields (/cms/api/document-service/fields#findmany) to return. Defaults to all fields (except those not populated by default). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. - `pagination` (Object): Paginate (/cms/api/document-service/sort-pagination#pagination) results. - `sort` (Object): Sort (/cms/api/document-service/sort-pagination#sort) results. **Generic example:** ``` await strapi.documents('api::restaurant.restaurant').findMany() ``` **With filters:** ``` await strapi.documents('api::restaurant.restaurant').findMany( { filters: { name: { $startsWith: 'Pizzeria' } } } ) ``` **Response 200 Generic:** ```json [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "name": "Biscotte Restaurant", "publishedAt": null, "locale": "en" }, { "documentId": "j9k8l7m6n5o4p3q2r1s0tuvw", "name": "Pizzeria Arrivederci", "publishedAt": null, "locale": "en" } ] ``` **Response 200 With filters:** ```json [ { "documentId": "j9k8l7m6n5o4p3q2r1s0tuvw", "name": "Pizzeria Arrivederci", "locale": "en", "publishedAt": null } ] ``` 可用的过滤器详细信息请参见文档服务 API 参考中的 [filters](/cms/api/document-service/filters) 页面。 🌐 Available filters are detailed in the [filters](/cms/api/document-service/filters) page of the Document Service API reference. 如果未传入 `locale` 或 `status` 参数,结果将返回默认语言环境的草稿版本。 🌐 If no `locale` or `status` parameters are passed, results return the draft version for the default locale. ### `create()` 语法:`create(parameters: Params) => Document` 🌐 Syntax: `create(parameters: Params) => Document` #### GET strapi.documents().create() — create() 创建一个新文档。如果没有传递 locale 参数,create() 会为默认语言环境创建文档的草稿版本。 **Parameters:** - `locale` (String or undefined): Locale of the document to create. Defaults to the default locale. See locale docs (/cms/api/document-service/locale#create). - `fields` (Object): Select fields (/cms/api/document-service/fields#create) to return. Defaults to all fields (except those not populated by default). - `status` (): If Draft & Publish (/cms/features/draft-and-publish) is enabled: can be set to `published` to automatically publish the draft version of a document while creating it. See status docs (/cms/api/document-service/status#create). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. **Request:** ``` await strapi.documents('api::restaurant.restaurant').create({ data: { name: 'Restaurant B' } }) ``` **Response 200 OK:** ```json { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "name": "Restaurant B", "publishedAt": null, "locale": "en" } ``` :::tip 如果在内容类型上启用了[草稿与发布](/cms/features/draft-and-publish)功能,你可以在创建文档的同时自动发布它(参见[`status` 文档`](/cms/api/document-service/status#create))。 🌐 If the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled on the content-type, you can automatically publish a document while creating it (see [`status` documentation](/cms/api/document-service/status#create)). ::: ### `update()` 语法:`update(parameters: Params) => Promise` 🌐 Syntax: `update(parameters: Params) => Promise` #### GET strapi.documents().update() — update() 通过 documentId 更新文档。如果未传递 locale 参数,update() 将更新默认语言环境的文档。 **Parameters:** - `documentId` (ID, required): Document id - `locale` (String or null): Locale of the document to update. Defaults to the default locale. See locale docs (/cms/api/document-service/locale#update). - `filters` (Object): Filters (/cms/api/document-service/filters) to use. Default: `null`. - `fields` (Object): Select fields (/cms/api/document-service/fields#update) to return. Defaults to all fields (except those not populated by default). - `status` (): If Draft & Publish (/cms/features/draft-and-publish) is enabled: can be set to `published` to automatically publish the draft version of a document while updating it. See status docs (/cms/api/document-service/status#update). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. **Request:** ``` await strapi.documents('api::restaurant.restaurant').update({ documentId: 'a1b2c3d4e5f6g7h8i9j0klmn', data: { name: "New restaurant name" } }) ``` **Response 200 OK:** ```json { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "name": "New restaurant name", "locale": "en", "publishedAt": null } ``` :::tip 已发布的版本是只读的,因此你不能从技术上更新文档的已发布版本。 要更新文档并立即发布新版本,你可以: 🌐 Published versions are read-only, so you can not technically update the published version of a document. To update a document and publish the new version right away, you can: - 使用 `update()` 更新其草稿版本,然后使用 `publish()` [发布它](#publish), - 或者直接将 `status: 'published'` 与传递给 `update()` 的其他参数一起添加(参见 [`status` 文档](/cms/api/document-service/status#update))。 ::: :::caution 不建议使用文档服务 API 更新可重复组件(更多详情请参见相关的[重大更改条目](/cms/migration/v4-to-v5/breaking-changes/do-not-update-repeatable-components-with-document-service-api.md))。 🌐 It's not recommended to update repeatable components with the Document Service API (see the related [breaking change entry](/cms/migration/v4-to-v5/breaking-changes/do-not-update-repeatable-components-with-document-service-api.md) for more details). ::: ### `delete()` 语法:`delete(parameters: Params): Promise<{ documentId: ID, entries: Number }>` 🌐 Syntax: `delete(parameters: Params): Promise<{ documentId: ID, entries: Number }>` #### GET strapi.documents().delete() — delete() 删除文档或特定语言版本。如果未传递语言参数,delete() 仅删除文档的默认语言版本。这会删除草稿版和已发布版。 **Parameters:** - `documentId` (ID, required): Document id - `locale` (String, ): Locale version of the document to delete. Default: `null` (deletes only the default locale). See locale docs (/cms/api/document-service/locale#delete). - `filters` (Object): Filters (/cms/api/document-service/filters) to use. Default: `null`. - `fields` (Object): Select fields (/cms/api/document-service/fields#delete) to return. Defaults to all fields (except those not populated by default). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. **Request:** ``` await strapi.documents('api::restaurant.restaurant').delete({ documentId: 'a1b2c3d4e5f6g7h8i9j0klmn', }) ``` **Response 200 OK:** ```json { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "entries": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "name": "Biscotte Restaurant", "publishedAt": "2024-03-14T18:30:48.870Z", "locale": "en" } ] } ``` ### `deleteMany()` 语法:`deleteMany(parameters: Params): Promise<{ documentId: ID, entries: Number }>` 🌐 Syntax: `deleteMany(parameters: Params): Promise<{ documentId: ID, entries: Number }>` #### GET strapi.documents().deleteMany() — deleteMany() 删除符合过滤器和关联参数的多个文档。 **Parameters:** - `locale` (String, ): Locale version of documents to delete. Default: only the default locale. See locale docs (/cms/api/document-service/locale#delete). - `filters` (Object): Filters (/cms/api/document-service/filters) to use. Default: `null`. - `fields` (Object): Select fields (/cms/api/document-service/fields#delete) to return. Defaults to all fields (except those not populated by default). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. **Request:** ``` await strapi.documents('api::restaurant.restaurant').deleteMany({ filters: { city: { name: { $eq: 'New York' } } } }); ``` **Response 200 OK:** ```json { "documentId": "multiple_documents", "entries": 3 } ``` ### `publish()` 语法:`publish(parameters: Params): Promise<{ documentId: ID, entries: Number }>` 🌐 Syntax: `publish(parameters: Params): Promise<{ documentId: ID, entries: Number }>` #### GET strapi.documents().publish() — publish() 发布文档的草稿版本。仅当内容类型启用了“草稿与发布”时,此方法才可用。如果未传入 locale 参数,publish() 只会发布文档的默认语言版本。 **Parameters:** - `documentId` (ID, required): Document id - `locale` (String, ): Locale of the documents to publish. Default: only the default locale. See locale docs (/cms/api/document-service/locale#publish). - `filters` (Object): Filters (/cms/api/document-service/filters) to use. Default: `null`. - `fields` (Object): Select fields (/cms/api/document-service/fields#publish) to return. Defaults to all fields (except those not populated by default). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. **Request:** ``` await strapi.documents('api::restaurant.restaurant').publish({ documentId: 'a1b2c3d4e5f6g7h8i9j0klmn', }); ``` **Response 200 OK:** ```json { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "entries": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "name": "Biscotte Restaurant", "publishedAt": "2024-03-14T18:30:48.870Z", "locale": "en" } ] } ``` ### `unpublish()` 语法:`unpublish(parameters: Params): Promise<{ documentId: ID, entries: Number }>` 🌐 Syntax: `unpublish(parameters: Params): Promise<{ documentId: ID, entries: Number }>` #### GET strapi.documents().unpublish() — unpublish() 将已发布的文档移回草稿状态。仅当内容类型启用了草稿和发布时,才可以使用此方法。如果未传递 locale 参数,unpublish() 仅会取消发布文档的默认语言版本。 **Parameters:** - `documentId` (ID, required): Document id - `locale` (String, ): Locale of the documents to unpublish. Default: only the default locale. See locale docs (/cms/api/document-service/locale#unpublish). - `filters` (Object): Filters (/cms/api/document-service/filters) to use. Default: `null`. - `fields` (Object): Select fields (/cms/api/document-service/fields#unpublish) to return. Defaults to all fields (except those not populated by default). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. **Request:** ``` await strapi.documents('api::restaurant.restaurant').unpublish({ documentId: 'a1b2c3d4e5f6g7h8i9j0klmn' }); ``` **Response 200 OK:** ```json { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "entries": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "name": "Biscotte Restaurant", "publishedAt": null, "locale": "en" } ] } ``` ### `discardDraft()` 语法:`discardDraft(parameters: Params): Promise<{ documentId: ID, entries: Number }>` 🌐 Syntax: `discardDraft(parameters: Params): Promise<{ documentId: ID, entries: Number }>` #### GET strapi.documents().discardDraft() — discardDraft() 删除草稿数据,仅保留已发布的版本。只有在内容类型启用了“草稿与发布”功能时,此方法才可用。如果未传递 locale 参数,discardDraft() 将删除草稿数据,并仅在默认语言环境下用已发布的版本覆盖它。 **Parameters:** - `documentId` (ID, required): Document id - `locale` (String, ): Locale of the documents to discard. Default: only the default locale. See locale docs (/cms/api/document-service/locale#discard-draft). - `filters` (Object): Filters (/cms/api/document-service/filters) to use. Default: `null`. - `fields` (Object): Select fields (/cms/api/document-service/fields#discarddraft) to return. Defaults to all fields (except those not populated by default). - `populate` (Object): Populate (/cms/api/document-service/populate) results with additional fields. Default: `null`. **Request:** ``` strapi.documents('api::restaurant.restaurant').discardDraft({ documentId: 'a1b2c3d4e5f6g7h8i9j0klmn', }); ``` **Response 200 OK:** ```json { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "entries": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klmn", "name": "Biscotte Restaurant", "publishedAt": null, "locale": "en" } ] } ``` ### `count()` 语法:`count(parameters: Params) => number` 🌐 Syntax: `count(parameters: Params) => number` #### GET strapi.documents().count() — count() 计算有多少文档符合参数。如果未传递参数,count() 方法将返回默认语言环境的文档总数。 **Parameters:** - `locale` (String or null): Locale of the documents to count. Defaults to the default locale. See locale docs (/cms/api/document-service/locale#count). - `status` (): If Draft & Publish (/cms/features/draft-and-publish) is enabled: publication status. `published` to count only published documents, `draft` to count draft documents (returns all documents). Default: `draft`. See status docs (/cms/api/document-service/status#count). - `publicationFilter` (String): If Draft & Publish (/cms/features/draft-and-publish) is enabled: select documents by how their draft and published versions relate, before applying `status`. See publicationFilter docs (/cms/api/document-service/publication-filter). - `filters` (Object): Filters (/cms/api/document-service/filters) to use. Default: `null`. **Generic example:** ``` await strapi.documents('api::restaurant.restaurant').count() ``` **Count published:** ``` strapi.documents('api::restaurant.restaurant').count({ status: 'published' }) ``` **With filters:** ``` /** * 计算草稿文档的数量(如果省略状态则为默认值) * 英语(默认语言环境) * 名字以 'Pizzeria' 开头 */strapi.documents('api::restaurant.restaurant').count({ filters: { name: { $startsWith: "Pizzeria" }}}) ``` :::note 由于已发布的文档必然也有草稿副本,因此已发布的文档仍算作具有草稿版本。 🌐 Since published documents necessarily also have a draft counterpart, a published document is still counted as having a draft version. 这意味着使用 `status: 'draft'` 参数计数仍然会返回符合其他参数的文档总数,即使某些文档已经发布,并且在内容管理器中不再显示为“草稿”或“已修改”。要仅计数从未发布过的草稿,请传递 [`publicationFilter`](/cms/api/document-service/publication-filter) 值,如 `'never-published'` 或 `'never-published-document'`。 🌐 This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. To count only never-published drafts, pass a [`publicationFilter`](/cms/api/document-service/publication-filter) value such as `'never-published'` or `'never-published-document'`. ::: # 使用文档服务 API 的字段 Source: https://strapi.nodejs.cn/cms/api/document-service/fields # 文档服务 API:选择字段 {#document-service-api-selecting-fields} 🌐 Document Service API: Selecting fields 在文档服务 API 查询中使用 `fields` 参数以选择要随结果返回的特定字段,从而减少在 `findOne()`、`findMany()`、`create()`、`update()`、`delete()`、`publish()` 及其他文档操作中的数据传输量。 🌐 Use the `fields` parameter in Document Service API queries to select specific fields to return with your results, reducing data payload across `findOne()`, `findMany()`, `create()`, `update()`, `delete()`, `publish()`, and other document operations. 默认情况下,[文档服务 API](/cms/api/document-service) 会返回文档的所有字段,但不会填充任何字段。本页介绍如何使用 `fields` 参数仅返回查询结果中的特定字段。 🌐 By default the [Document Service API](/cms/api/document-service) returns all the fields of a document but does not populate any fields. This page describes how to use the `fields` parameter to return only specific fields with the query results. :::tip 你也可以使用 `populate` 参数来填充关系、媒体字段、组件或动态区域(参见 [`populate` 参数](/cms/api/document-service/populate) 文档)。 🌐 You can also use the `populate` parameter to populate relations, media fields, components, or dynamic zones (see the [`populate` parameter](/cms/api/document-service/populate) documentation). ::: :::note 虽然建议在 Strapi 5 中使用条目的 `documentId` 进行定位,但条目仍可能有一个 `id` 字段,并且你将在返回的响应中看到它。这应该会减轻你从 Strapi 4 过渡的难度。有关更多详细信息,请参考[重大变更条目](/cms/migration/v4-to-v5/breaking-changes/use-document-id)。 🌐 Though it's recommended to target entries by their `documentId` in Strapi 5, entries might still have an `id` field, and you will see it in the returned response. This should ease your transition from Strapi 4. Please refer to the [breaking change entry](/cms/migration/v4-to-v5/breaking-changes/use-document-id) for more details. ::: ## 使用 `findOne()` 查询选择字段 {#findone} 🌐 Select fields with `findOne()` queries #### GET strapi.documents( — Select fields with findOne() Select specific fields to return when finding a document by documentId. **JavaScript:** ``` const document = await strapi.documents("api::restaurant.restaurant").findOne({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', fields: ["name", "description"], }); ``` **Response 200 OK:** ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", description: "Welcome to Biscotte restaurant! …" } ``` ## 使用 `findFirst()` 查询选择字段 {#findfirst} 🌐 Select fields with `findFirst()` queries #### GET strapi.documents( — Select fields with findFirst() Select specific fields to return when finding the first matching document. **JavaScript:** ``` const document = await strapi.documents("api::restaurant.restaurant").findFirst({ fields: ["name", "description"], }); ``` **Response 200 OK:** ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", description: "Welcome to Biscotte restaurant! …" } ``` ## 使用 `findMany()` 查询选择字段 {#findmany} 🌐 Select fields with `findMany()` queries #### GET strapi.documents( — Select fields with findMany() Select specific fields to return when finding multiple documents. **JavaScript:** ``` const documents = await strapi.documents("api::restaurant.restaurant").findMany({ fields: ["name", "description"], }); ``` **Response 200 OK:** ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", description: "Welcome to Biscotte restaurant! …" } // ... ] ``` ## 使用 `create()` 查询选择字段 {#create} 🌐 Select fields with `create()` queries #### GET strapi.documents( — Select fields with create() Select specific fields to return when creating a new document. **JavaScript:** ``` const document = await strapi.documents("api::restaurant.restaurant").create({ data: { name: "Restaurant B", description: "Description for the restaurant", }, fields: ["name", "description"], }); ``` **Response 200 OK:** ```json { id: 4, documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', name: 'Restaurant B', description: 'Description for the restaurant' } ``` ## 使用 `update()` 查询选择字段 {#update} 🌐 Select fields with `update()` queries #### GET strapi.documents( — Select fields with update() Select specific fields to return when updating a document. **JavaScript:** ``` const document = await strapi.documents("api::restaurant.restaurant").update({ documentId: "fmtr6d7ktzpgrijqaqgr6vxs", data: { name: "Restaurant C", }, fields: ["name"], }); ``` **Response 200 OK:** ```json { documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', name: 'Restaurant C' } ``` ## 使用 `delete()` 查询选择字段 {#delete} 🌐 Select fields with `delete()` queries #### GET strapi.documents( — Select fields with delete() Select specific fields to return when deleting a document. **JavaScript:** ``` const document = await strapi.documents("api::restaurant.restaurant").delete({ documentId: "fmtr6d7ktzpgrijqaqgr6vxs", fields: ["name"], }); ``` **Response 200 OK:** ```json documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', // All of the deleted document's versions are returned entries: [ { id: 4, documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', name: 'Restaurant C', // … } ] } ``` ## 使用 `publish()` 查询选择字段 {#publish} 🌐 Select fields with `publish()` queries #### GET strapi.documents( — Select fields with publish() Select specific fields to return when publishing a document. **JavaScript:** ``` const document = await strapi.documents("api::restaurant.restaurant").publish({ documentId: "fmtr6d7ktzpgrijqaqgr6vxs", fields: ["name"], }); ``` **Response 200 OK:** ```json { documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', // All of the published locale entries are returned entries: [ { documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', name: 'Restaurant B' } ] } ``` ## 选择包含 `unpublish()` 查询的字段 {#unpublish} 🌐 Select fields with `unpublish()` queries #### GET strapi.documents( — Select fields with unpublish() Select specific fields to return when unpublishing a document. **JavaScript:** ``` const document = await strapi.documents("api::restaurant.restaurant").unpublish({ documentId: "cjld2cjxh0000qzrmn831i7rn", fields: ["name"], }); ``` **Response 200 OK:** ```json { documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', // All of the published locale entries are returned entries: [ { documentId: 'fmtr6d7ktzpgrijqaqgr6vxs', name: 'Restaurant B' } ] } ``` ## 选择包含 `discardDraft()` 查询的字段 {#discarddraft} 🌐 Select fields with `discardDraft()` queries #### GET strapi.documents( — Select fields with discardDraft() Select specific fields to return when discarding a draft document. **JavaScript:** ``` const document = await strapi.documents("api::restaurant.restaurant").discardDraft({ documentId: "fmtr6d7ktzpgrijqaqgr6vxs", fields: ["name"], }); ``` **Response 200 OK:** ```json { documentId: "fmtr6d7ktzpgrijqaqgr6vxs", // All of the discarded draft entries are returned entries: [ { "name": "Restaurant B" } ] } ``` # 在文档服务 API 中使用过滤器 Source: https://strapi.nodejs.cn/cms/api/document-service/filters # 文档服务 API:筛选器 {#document-service-api-filters} 🌐 Document Service API: Filters 文档服务 API 提供属性操作符(`$eq`、`$lt`、`$contains` 等)和逻辑操作符(`$and`、`$or`、`$not`),用于筛选查询结果,并支持区分大小写和不区分大小写的匹配。 🌐 The Document Service API provides attribute operators (`$eq`, `$lt`, `$contains`, etc.) and logical operators (`$and`, `$or`, `$not`) to filter query results with support for case-sensitive and case-insensitive matching. [文档服务 API](/cms/api/document-service) 提供筛选结果的功能。 🌐 The [Document Service API](/cms/api/document-service) offers the ability to filter results. 可以使用以下运算符: 🌐 The following operators are available: | 运算符 | 描述 | | --- | --- | | [`$eq`](#eq) | 相等 | | [`$eqi`](#eqi) | 相等(不区分大小写) | | [`$ne`](#ne) | 不相等 | | [`$nei`](#nei) | 不等于(不区分大小写) | | [`$lt`](#lt) | 少于 | | [`$lte`](#lte) | 小于或等于 | | [`$gt`](#gt) | 大于 | | [`$gte`](#gte) | 大于或等于 | | [`$in`](#in) | 包含在数组中 | | [`$notIn`](#notin) | 不包含在数组中 | | [`$contains`](#contains) | 包含 | | [`$notContains`](#notcontains) | 不包含 | | [`$containsi`](#containsi) | 包含(不区分大小写) | | [`$notContainsi`](#notcontainsi) | 不包含(不区分大小写) | | [`$null`](#null) | 为空 | | [`$notNull`](#notnull) | 不为空 | | [`$between`](#between) | 介于 | | [`$startsWith`](#startswith) | 以...开始 | | [`$startsWithi`](#startswithi) | 以…开头(不区分大小写) | | [`$endsWith`](#endswith) | 以...结尾 | | [`$endsWithi`](#endswithi) | 以…结尾(不区分大小写) | | [`$or`](#or) | 将过滤器组合成“或”表达式 | | [`$and`](#and) | 将过滤器组合成“与”表达式 | | [`$not`](#not) | 在“not”表达式中连接过滤器 | :::strapi Deep filtering with the various APIs 有关如何使用各种 API 进行深度过滤的示例,请参阅 [this blog article](https://strapi.io/blog/deep-filtering-alpha-26)。 ::: ## 属性运算符 {#attribute-operators} 🌐 Attribute operators
#### GET strapi.documents().findMany() — $not 否定嵌套条件。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $not: { $contains: 'Hello World', }, }, }, }); ``` #### GET strapi.documents().findMany() — $eq 属性等于输入值。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $eq: 'Hello World', }, }, }); ``` **Shorthand:** ``` // $eq can be omitted: const entries = await strapi.documents('api::article.article').findMany({ filters: { title: 'Hello World', }, }); ``` #### GET strapi.documents().findMany() — $eqi 属性等于输入值(不区分大小写)。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $eqi: 'HELLO World', }, }, }); ``` #### GET strapi.documents().findMany() — $ne 属性不等于输入值。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $ne: 'ABCD', }, }, }); ``` #### GET strapi.documents().findMany() — $nei 属性不等于输入值(不区分大小写)。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $nei: 'abcd', }, }, }); ``` #### GET strapi.documents().findMany() — $in 属性包含在输入列表中。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $in: ['Hello', 'Hola', 'Bonjour'], }, }, }); ``` **Shorthand:** ``` // $in can be omitted when passing an array of values: const entries = await strapi.documents('api::article.article').findMany({ filters: { title: ['Hello', 'Hola', 'Bonjour'], }, }); ``` #### GET strapi.documents().findMany() — $notIn 输入列表中不包含属性。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $notIn: ['Hello', 'Hola', 'Bonjour'], }, }, }); ``` #### GET strapi.documents().findMany() — $lt 属性小于输入值。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { rating: { $lt: 10, }, }, }); ``` #### GET strapi.documents().findMany() — $lte 属性小于或等于输入值。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { rating: { $lte: 10, }, }, }); ``` #### GET strapi.documents().findMany() — $gt 属性大于输入值。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { rating: { $gt: 5, }, }, }); ``` #### GET strapi.documents().findMany() — $gte 属性大于或等于输入值。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { rating: { $gte: 5, }, }, }); ``` #### GET strapi.documents().findMany() — $between **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { rating: { $between: [1, 20], }, }, }); ``` #### GET strapi.documents().findMany() — $contains 属性包含输入值(区分大小写)。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $contains: 'Hello', }, }, }); ``` #### GET strapi.documents().findMany() — $notContains 属性不包含输入值(区分大小写)。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $notContains: 'Hello', }, }, }); ``` #### GET strapi.documents().findMany() — $containsi **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $containsi: 'hello', }, }, }); ``` #### GET strapi.documents().findMany() — $notContainsi **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $notContainsi: 'hello', }, }, }); ``` #### GET strapi.documents().findMany() — $startsWith Attribute starts with input value (case-sensitive). **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $startsWith: 'ABCD', }, }, }); ``` #### GET strapi.documents().findMany() — $startsWithi Attribute starts with input value (case-insensitive). **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $startsWithi: 'ABCD', // will return the same as filtering with 'abcd' }, }, }); ``` #### GET strapi.documents().findMany() — $endsWith Attribute ends with input value (case-sensitive). **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $endsWith: 'ABCD', }, }, }); ``` #### GET strapi.documents().findMany() — $endsWithi Attribute ends with input value (case-insensitive). **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $endsWith: 'ABCD', // will return the same as filtering with 'abcd' }, }, }); ``` #### GET strapi.documents().findMany() — $null **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $null: true, }, }, }); ``` #### GET strapi.documents().findMany() — $notNull **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { title: { $notNull: true, }, }, }); ``` ## 逻辑运算符 {#logical-operators} 🌐 Logical operators #### GET strapi.documents().findMany() — $and **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { $and: [ { title: 'Hello World', }, { createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, ], }, }); ``` **Implicit $and:** ``` // $and will be used implicitly when passing an object with nested conditions: const entries = await strapi.documents('api::article.article').findMany({ filters: { title: 'Hello World', createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, }); ``` #### GET strapi.documents().findMany() — $or **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { $or: [ { title: 'Hello World', }, { createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, ], }, }); ``` #### GET strapi.documents().findMany() — $not 否定嵌套条件。 **JavaScript:** ``` const entries = await strapi.documents('api::article.article').findMany({ filters: { $not: { title: 'Hello World', }, }, }); ``` :::note `$not` 可以用作: - 一个逻辑运算符(例如在 `filters: { $not: { // conditions... }}` 中) - [属性操作符](#not)(例如在 `filters: { attribute-name: $not: { ... } }` 中)。 ::: :::tip `$and`、`$or` 和 `$not` 操作符可以嵌套在另一个 `$and`、`$or` 或 `$not` 操作符中。 ::: # 在文档服务 API 中使用区域参数 Source: https://strapi.nodejs.cn/cms/api/document-service/locale # 文档服务 API:使用 `locale` 参数 {#document-service-api-using-the-locale-parameter} 🌐 Document Service API: Using the `locale` parameter 文档服务 API 中的 `locale` 参数允许你使用 `findOne()`、`findMany()`、`update()` 和 `delete()` 等方法查询、创建、更新、删除、发布和取消发布特定语言版本的文档。 🌐 The `locale` parameter in the Document Service API lets you query, create, update, delete, publish, and unpublish documents for specific language versions using methods like `findOne()`, `findMany()`, `update()`, and `delete()`. 默认情况下,[Document Service API](/cms/api/document-service) 返回文档的默认语言版本(默认为'en',即英语版本,除非应用已设置了其他默认语言,详见[国际化(i18n)功能](/cms/features/internationalization))。本页面描述了如何使用 `locale` 参数仅获取或操作特定语言的数据。 🌐 By default the [Document Service API](/cms/api/document-service) returns the default locale version of documents (which is 'en', i.e. the English version, unless another default locale has been set for the application, see [Internationalization (i18n) feature](/cms/features/internationalization)). This page describes how to use the `locale` parameter to get or manipulate data only for specific locales. ## 获取带有 `findOne()` 的本地化版本 {#find-one} 🌐 Get a locale version with `findOne()` #### GET strapi.documents().findOne() — Get a locale version with findOne() Pass a locale to findOne() to get the version of the document for that locale. **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findOne({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: 'fr', }); ``` **Response 200 OK:** ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: null, // draft version (default) locale: "fr", // as asked from the parameters // … } ``` 如果没有传递 `status` 参数,则默认返回 `draft` 版本。 🌐 If no `status` parameter is passed, the `draft` version is returned by default. ## 获取带有 `findFirst()` 的本地化版本 {#find-first} 🌐 Get a locale version with `findFirst()` #### GET strapi.documents().findFirst() — Get a locale version with findFirst() Pass a locale to findFirst() to return documents matching that locale. **JavaScript:** ``` const document = await strapi.documents('api::article.article').findFirst({ locale: 'fr', }); ``` **Response 200 OK:** ```json { "documentId": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article" // … } ``` 如果没有传递 `status` 参数,则默认返回 `draft` 版本。 🌐 If no `status` parameter is passed, the `draft` version is returned by default. ## 使用 `findMany()` 获取本地化版本 {#find-many} 🌐 Get locale versions with `findMany()` 如果没有传递 `status` 参数,则默认返回 `draft` 版本。 🌐 If no `status` parameter is passed, the `draft` versions are returned by default. #### GET strapi.documents().findMany() — Get locale versions with findMany() Pass a locale to findMany() to return all documents that have this locale available. **JavaScript:** ``` // Defaults to status: draft await strapi.documents('api::restaurant.restaurant').findMany({ locale: 'fr' }); ``` **Response 200 OK:** ```json [ { documentId: 'a1b2c3d4e5f6g7h8i9j0klm', name: 'Restaurant Biscotte', publishedAt: null, locale: 'fr', // … }, // … ] ```
解释: 给定以下 4 个具有不同语言环境的文档: 🌐 Given the following 4 documents that have various locales: - 文件A: - en - `fr` - it - 文件 B: - en - it - 文件 C: - `fr` - 文件 D: - `fr` - it `findMany({ locale: 'fr' })` 只会返回那些有 `'fr'` 语言版本的文档的草稿版本,即文档 A、C 和 D。
## `create()` 为一个区域创建文档 {#create} 🌐 `create()` a document for a locale #### GET strapi.documents().create() — Create a document for a locale Pass a locale to create() to create the document for that specific locale. **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').create({ locale: 'es' // if not passed, the draft is created for the default locale data: { name: 'Restaurante B' } }) ``` **Response 200 OK:** ```json { documentId: "pw2s0nh5ub1zmnk0d80vgqrh", name: "Restaurante B", publishedAt: null, locale: "es" // … } ``` ## `update()` 本地化版本 {#update} 🌐 `update()` a locale version #### GET strapi.documents().update() — Update a locale version Pass a locale to update() to update only that specific locale version of a document. **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').update({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: 'es', data: { name: 'Nuevo nombre del restaurante' }, }); ``` **Response 200 OK:** ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Nuevo nombre del restaurante", locale: "es", publishedAt: null, // … } ``` ## `delete()` 语言区域版本 {#delete} 🌐 `delete()` locale versions 使用 Document Service API 的 [`delete()` 方法](/cms/api/document-service#delete) 的 `locale` 参数仅删除某些语言版本。除非传入特定的 `status` 参数,否则这将删除草稿和已发布版本。 🌐 Use the `locale` parameter with the [`delete()` method](/cms/api/document-service#delete) of the Document Service API to delete only some locales. Unless a specific `status` parameter is passed, this deletes both the draft and published versions. ### 删除一个语言版本 {#delete-a-locale-version} 🌐 Delete a locale version #### GET strapi.documents().delete() — 删除一个语言版本 Pass a locale to delete() to delete only that specific locale version of a document. **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').delete({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', // documentId, locale: 'es', }); ``` ### 删除所有语言版本 {#delete-all-locale-versions} 🌐 Delete all locale versions #### GET strapi.documents().delete() — 删除所有语言版本 Use the * wildcard with the locale parameter to delete all locale versions of a document. **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').delete({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', // documentId, locale: '*', }); // for all existing locales ``` **Response 200 OK:** ```json { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", // All of the deleted locale versions are returned "versions": [ { "title": "Test Article" } ] } ``` ## `publish()` 语言版本 {#publish} 🌐 `publish()` locale versions 要使用文档服务 API 的 [`publish()` 方法](/cms/api/document-service#publish) 仅发布文档的特定语言版本,请将 `locale` 作为参数传递: 🌐 To publish only specific locale versions of a document with the [`publish()` method](/cms/api/document-service#publish) of the Document Service API, pass `locale` as a parameter: ### 发布本地化版本 {#publish-a-locale-version} 🌐 Publish a locale version #### GET strapi.documents().publish() — 发布本地化版本 Pass a locale to publish() to publish only that specific locale version of a document. **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').publish({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: 'fr', }); ``` **Response 200 OK:** ```json { versions: [ { documentId: 'a1b2c3d4e5f6g7h8i9j0klm', name: 'Restaurant Biscotte', publishedAt: '2024-03-14T18:38:05.674Z', locale: 'fr', // … }, ] } ``` ### 发布所有语言版本 {#publish-all-locale-versions} 🌐 Publish all locale versions #### GET strapi.documents().publish() — 发布所有语言版本 Use the * wildcard with the locale parameter to publish all locale versions of a document. **JavaScript:** ``` await strapi .documents('api::restaurant.restaurant') .publish({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: '*' }); ``` **Response 200 OK:** ```json { "versions": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "publishedAt": "2024-03-14T18:45:21.857Z", "locale": "en" // … }, { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "publishedAt": "2024-03-14T18:45:21.857Z", "locale": "es" // … }, { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "publishedAt": "2024-03-14T18:45:21.857Z", "locale": "fr" // … } ] } ``` ## `unpublish()` 语言版本 {#unpublish} 🌐 `unpublish()` locale versions 要使用文档服务 API 的 [`unpublish()` 方法](/cms/api/document-service#unpublish) 仅发布文档的特定语言版本,请将 `locale` 作为参数传递: 🌐 To publish only specific locale versions of a document with the [`unpublish()` method](/cms/api/document-service#unpublish) of the Document Service API, pass `locale` as a parameter: ### 取消发布本地版本 {#unpublish-a-locale-version} 🌐 Unpublish a locale version #### GET strapi.documents().unpublish() — 取消发布本地版本 Pass a locale to unpublish() to unpublish only that specific locale version of a document. **JavaScript:** ``` await strapi .documents('api::restaurant.restaurant') .unpublish({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: 'fr' }); ``` **Response 200 OK:** ```json { versions: 1 } ``` ### 取消发布所有语言版本 {#unpublish-all-locale-versions} 🌐 Unpublish all locale versions #### GET strapi.documents().unpublish() — 取消发布所有语言版本 Use the * wildcard with the locale parameter to unpublish all locale versions of a document. **JavaScript:** ``` await strapi .documents('api::restaurant.restaurant') .unpublish({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: '*' }); ``` **Response 200 OK:** ```json { versions: 3 } ``` #### GET strapi.documents().unpublish() — Unpublish with fields selection Unpublish a document while selecting specific fields to return. **JavaScript:** ``` const document = await strapi.documents('api::article.article').unpublish({ documentId: 'cjld2cjxh0000qzrmn831i7rn', fields: ['title'], }); ``` **Response 200 OK:** ```json { "documentId": "cjld2cjxh0000qzrmn831i7rn", // All of the unpublished locale versions are returned "versions": [ { "title": "Test Article" } ] } ``` ## `discardDraft()` 用于本地化版本 {#discard-draft} 🌐 `discardDraft()` for locale versions 要仅丢弃某些语言版本文档的草稿数据,使用文档服务 API 的 [`discardDraft()` 方法](/cms/api/document-service#discarddraft),传递 `locale` 作为参数: 🌐 To discard draft data only for some locales versions of a document with the [`discardDraft()` method](/cms/api/document-service#discarddraft) of the Document Service API, pass `locale` as a parameter: ### 丢弃本地化版本的草稿 {#discard-draft-for-a-locale-version} 🌐 Discard draft for a locale version #### GET strapi.documents().discardDraft() — 丢弃本地化版本的草稿 Pass a locale to discardDraft() to discard draft data for that specific locale version. **JavaScript:** ``` await strapi .documents('api::restaurant.restaurant') .discardDraft({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: 'fr' }); ``` **Response 200 OK:** ```json { versions: [ { documentId: 'a1b2c3d4e5f6g7h8i9j0klm', name: 'Restaurant Biscotte', publishedAt: null, locale: 'fr', // … }, ] } ``` ### 丢弃所有语言版本的草稿 {#discard-drafts-for-all-locale-versions} 🌐 Discard drafts for all locale versions #### GET strapi.documents().discardDraft() — 丢弃所有语言版本的草稿 Use the * wildcard with the locale parameter to discard drafts for all locale versions of a document. **JavaScript:** ``` await strapi .documents('api::restaurant.restaurant') .discardDraft({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: '*' }); ``` **Response 200 OK:** ```json { versions: [ { documentId: 'a1b2c3d4e5f6g7h8i9j0klm', name: 'Biscotte Restaurant', publishedAt: null, locale: 'en', // … }, { documentId: 'a1b2c3d4e5f6g7h8i9j0klm', name: 'Restaurant Biscotte', publishedAt: null, locale: 'fr', // … }, { documentId: 'a1b2c3d4e5f6g7h8i9j0klm', name: 'Biscotte Restaurante', publishedAt: null, locale: 'es', // … }, ] } ``` ## `count()` 个本地化文档 {#count} 🌐 `count()` documents for a locale 要统计特定语言环境的文档,请将 `locale` 与其他参数一起传递给文档服务 API 的 [`count()` 方法](/cms/api/document-service#count)。 🌐 To count documents for a specific locale, pass the `locale` along with other parameters to the [`count()` method](/cms/api/document-service#count) of the Document Service API. 如果没有传递 `status` 参数,则统计草稿文档(即该语言环境下可用文档的总数,因为即使已发布的文档也会被计为有草稿版本): 🌐 If no `status` parameter is passed, draft documents are counted (which is the total of available documents for the locale since even published documents are counted as having a draft version): ```js // Count number of published documents in French strapi.documents('api::restaurant.restaurant').count({ locale: 'fr' }); ``` # 扩展文档服务行为 Source: https://strapi.nodejs.cn/cms/api/document-service/middlewares # 文档服务 API:中间件 {#document-service-api-middlewares} 🌐 Document Service API: Middlewares 文档服务中间件允许你在文档服务方法运行之前和之后执行操作,通过 `strapi.documents.use()` 注册中间件函数,并可以访问内容类型上下文和方法参数。 🌐 Document Service middlewares allow you to perform actions before and after Document Service methods run by registering middleware functions via `strapi.documents.use()` with access to content type context and method parameters. [文档服务 API](/cms/api/document-service) 提供了通过中间件扩展其行为的能力。 🌐 The [Document Service API](/cms/api/document-service) offers the ability to extend its behavior thanks to middlewares. 文档服务中间件允许你在方法运行之前和/或之后执行操作。 🌐 Document Service middlewares allow you to perform actions before and/or after a method runs.
Simplified Strapi backend diagram with controllers highlighted
该图表示请求在 Strapi 后端传输的简化版本,并突出了文档服务。后端自定义介绍页面包括一个完整的、 交互式图表
## 注册中间件 {#registering-a-middleware} 🌐 Registering a middleware 语法:`strapi.documents.use(middleware)` 🌐 Syntax: `strapi.documents.use(middleware)` ### 参数 {#parameters} 🌐 Parameters 中间件是一种接收上下文和下一个函数的函数。 🌐 A middleware is a function that receives a context and a next function. 语法:`(context, next) => ReturnType` 🌐 Syntax: `(context, next) => ReturnType` | 参数 | 描述 | 类型 | |-----------|---------------------------------------|------------| | `context` | 中间件上下文 | `Context` | | `next` | 调用堆栈中的下一个中间件 | `function` | #### `context` | 参数 | 描述 | 类型 | |---------------|--------------------------------------------------------------------------------------|---------------| | `action` | 正在运行的方法([查看可用方法](/cms/api/document-service)) | `string` | | `params` | 方法参数([查看可用方法](/cms/api/document-service)) | `Object` | | `uid` | 内容类型唯一标识 | `string` | | `contentType` | 内容类型 | `ContentType` |
示例: 以下示例显示了根据调用的方法,`context` 可能包含的内容: 🌐 The following examples show what `context` might include depending on the method called: ```js { uid: "api::restaurant.restaurant", contentType: { kind: "collectionType", collectionName: "restaurants", info: { singularName: "restaurant", pluralName: "restaurants", displayName: "restaurant" }, options: { draftAndPublish: true }, pluginOptions: {}, attributes: { name: { /*...*/ }, description: { /*...*/ }, createdAt: { /*...*/ }, updatedAt: { /*...*/ }, publishedAt: { /*...*/ }, createdBy: { /*...*/ }, updatedBy: { /*...*/ }, locale: { /*...*/ }, }, apiName: "restaurant", globalId: "Restaurants", uid: "api::restaurant.restaurant", modelType: "contentType", modelName: "restaurant", actions: { /*...*/ }, lifecycles: { /*...*/ }, }, action: "findOne", params: { documentId: 'hp7hjvrbt8rcgkmabntu0aoq', locale: undefined, status: "publish" populate: { /*...*/ }, } } ``` ```js { uid: "api::restaurant.restaurant", contentType: { kind: "collectionType", collectionName: "restaurants", info: { singularName: "restaurant", pluralName: "restaurants", displayName: "restaurant" }, options: { draftAndPublish: true }, pluginOptions: {}, attributes: { name: { /*...*/ }, description: { /*...*/ }, createdAt: { /*...*/ }, updatedAt: { /*...*/ }, publishedAt: { /*...*/ }, createdBy: { /*...*/ }, updatedBy: { /*...*/ }, locale: { /*...*/ }, }, apiName: "restaurant", globalId: "Restaurants", uid: "api::restaurant.restaurant", modelType: "contentType", modelName: "restaurant", actions: { /*...*/ }, lifecycles: { /*...*/ }, }, action: "findMany", params: { filters: { /*...*/ }, status: "draft", locale: null, fields: ['name', 'description'], } } ``` ```js { uid: "api::restaurant.restaurant", contentType: { kind: "collectionType", collectionName: "restaurants", info: { singularName: "restaurant", pluralName: "restaurants", displayName: "restaurant" }, options: { draftAndPublish: true }, pluginOptions: {}, attributes: { name: { /*...*/ }, description: { /*...*/ }, createdAt: { /*...*/ }, updatedAt: { /*...*/ }, publishedAt: { /*...*/ }, createdBy: { /*...*/ }, updatedBy: { /*...*/ }, locale: { /*...*/ }, }, apiName: "restaurant", globalId: "Restaurants", uid: "api::restaurant.restaurant", modelType: "contentType", modelName: "restaurant", actions: { /*...*/ }, lifecycles: { /*...*/ }, }, action: "create", params: { data: { /*...*/ }, status: "draft", populate: { /*...*/ }, } } ``` ```js { uid: "api::restaurant.restaurant", contentType: { kind: "collectionType", collectionName: "restaurants", info: { singularName: "restaurant", pluralName: "restaurants", displayName: "restaurant" }, options: { draftAndPublish: true }, pluginOptions: {}, attributes: { name: { /*...*/ }, description: { /*...*/ }, createdAt: { /*...*/ }, updatedAt: { /*...*/ }, publishedAt: { /*...*/ }, createdBy: { /*...*/ }, updatedBy: { /*...*/ }, locale: { /*...*/ }, }, apiName: "restaurant", globalId: "Restaurants", uid: "api::restaurant.restaurant", modelType: "contentType", modelName: "restaurant", actions: { /*...*/ }, lifecycles: { /*...*/ }, }, action: "update", params: { data: { /*...*/ }, documentId: 'hp7hjvrbt8rcgkmabntu0aoq', locale: undefined, status: "draft" populate: { /*...*/ }, } } ``` ```js { uid: "api::restaurant.restaurant", contentType: { kind: "collectionType", collectionName: "restaurants", info: { singularName: "restaurant", pluralName: "restaurants", displayName: "restaurant" }, options: { draftAndPublish: true }, pluginOptions: {}, attributes: { name: { /*...*/ }, description: { /*...*/ }, createdAt: { /*...*/ }, updatedAt: { /*...*/ }, publishedAt: { /*...*/ }, createdBy: { /*...*/ }, updatedBy: { /*...*/ }, locale: { /*...*/ }, }, apiName: "restaurant", globalId: "Restaurants", uid: "api::restaurant.restaurant", modelType: "contentType", modelName: "restaurant", actions: { /*...*/ }, lifecycles: { /*...*/ }, }, action: "delete", params: { data: { /*...*/ }, documentId: 'hp7hjvrbt8rcgkmabntu0aoq', locale: "*", populate: { /*...*/ }, } } ```
#### `next` `next` 是一个没有参数的函数,它调用堆栈中的下一个中间件并返回其响应。 **示例** ```js strapi.documents.use((context, next) => { return next(); }); ``` ### 在哪里注册 {#where-to-register} 🌐 Where to register 一般来说,你应该在 Strapi 注册阶段注册你的中间件。 🌐 Generaly speaking you should register your middlewares during the Strapi registration phase. #### 用户 {#users} 🌐 Users 中间件必须在通用的 `register()` 生命周期方法中注册: 🌐 The middleware must be registered in the general `register()` lifecycle method: ```js title="/src/index.js|ts" module.exports = { register({ strapi }) { strapi.documents.use((context, next) => { // your logic return next(); }); }, // bootstrap({ strapi }) {}, // destroy({ strapi }) {}, }; ``` #### 插件开发者 {#plugin-developers} 🌐 Plugin developers 中间件必须在插件的 `register()` 生命周期方法中注册: 🌐 The middleware must be registered in the plugin's `register()` lifecycle method: ```js title="/(plugin-root-folder)/strapi-server.js|ts" module.exports = { register({ strapi }) { strapi.documents.use((context, next) => { // your logic return next(); }); }, // bootstrap({ strapi }) {}, // destroy({ strapi }) {}, }; ``` ## 实现中间件 {#implementing-a-middleware} 🌐 Implementing a middleware 在实现中间件时,总是要返回来自 `next()` 的响应。 如果不这样做,将会导致 Strapi 应用出错。 🌐 When implementing a middleware, always return the response from `next()`. Failing to do this will break the Strapi application. ### 例子 {#examples} 🌐 Examples ```js const applyTo = ['api::article.article']; strapi.documents.use((context, next) => { // Only run for certain content types if (!applyTo.includes(context.uid)) { return next(); } // Only run for certain actions if (['create', 'update'].includes(context.action)) { context.params.data.fullName = `${context.params.data.firstName} ${context.params.data.lastName}`; } const result = await next(); // do something with the result before returning it return result }); ```
:::strapi Lifecycle hooks 文档服务 API 会根据调用的方法触发各种数据库生命周期钩子。完整参考请参见 [文档服务 API:生命周期钩子](/cms/migration/v4-to-v5/breaking-changes/lifecycle-hooks-document-service#table)。 🌐 The Document Service API triggers various database lifecycle hooks based on which method is called. For a complete reference, see [Document Service API: Lifecycle hooks](/cms/migration/v4-to-v5/breaking-changes/lifecycle-hooks-document-service#table). ::: # 使用 Document Service API 的 Populate Source: https://strapi.nodejs.cn/cms/api/document-service/populate # 文档服务 API:填充字段 {#document-service-api-populating-fields} 🌐 Document Service API: Populating fields 使用 Document Service API 的 `populate` 参数可以显式加载关系、媒体字段、组件和动态区域,支持一层或多层深度,并在 `create()`、`update()`、`publish()` 和 `delete()` 操作中使用。 🌐 Use the `populate` parameter with the Document Service API to explicitly load relations, media fields, components, and dynamic zones at one or multiple levels deep, and within `create()`, `update()`, `publish()`, and `delete()` operations. 默认情况下,[文档服务 API](/cms/api/document-service) 不会填充任何关系、媒体字段、组件或动态区域。本页介绍如何使用 `populate` 参数来填充特定字段。 🌐 By default the [Document Service API](/cms/api/document-service) does not populate any relations, media fields, components, or dynamic zones. This page describes how to use the `populate` parameter to populate specific fields. :::tip 你也可以使用 `select` 参数仅返回查询结果中的特定字段(参见 [`select` 参数](/cms/api/document-service/fields) 文档)。 🌐 You can also use the `select` parameter to return only specific fields with the query results (see the [`select` parameter](/cms/api/document-service/fields) documentation). ::: :::caution 如果安装了“用户与权限”插件,则必须为正在填充的内容类型启用 `find` 权限。如果某个角色无法访问某个内容类型,则该内容类型将不会被填充。 🌐 If the Users & Permissions plugin is installed, the `find` permission must be enabled for the content-types that are being populated. If a role doesn't have access to a content-type it will not be populated. ::: ## 关系与媒体字段 {#relations-and-media-fields} 🌐 Relations and media fields 查询可以接受一个 `populate` 参数来显式定义要填充的字段,以下是语法选项示例。这包括所有关系类型:一对多、多对一、多对多以及多态关系(morphToOne、morphToMany)。 🌐 Queries can accept a `populate` parameter to explicitly define which fields to populate, with the following syntax option examples. This includes all relation types: one-to-many, many-to-one, many-to-many, and polymorphic relations (morphToOne, morphToMany). ### 为所有关系填充 1 级 {#populate-1-level-for-all-relations} 🌐 Populate 1 level for all relations #### GET strapi.documents( — 为所有关系填充 1 级 Populate one-level deep for all relations using the wildcard. **JavaScript:** ``` const documents = await strapi.documents("api::article.article").findMany({ populate: "*", }); ``` **Response 200 OK:** ```json { [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", // ... "headerImage": { "data": { "id": 1, "attributes": { "name": "17520.jpg", "alternativeText": "17520.jpg", "formats": { // ... } // ... } } }, "author": { // ... }, "categories": { // ... } } // ... ] } ``` ### 为特定关系填充 1 级 {#populate-1-level-for-specific-relations} 🌐 Populate 1 level for specific relations #### GET strapi.documents( — 为特定关系填充 1 级 Populate specific relations one-level deep using an array. **JavaScript:** ``` const documents = await strapi.documents("api::article.article").findMany({ populate: ["headerImage"], }); ``` **Response 200 OK:** ```json [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", // ... "headerImage": { "id": 2, "name": "17520.jpg" // ... } } // ... ] ``` ### 为特定关系填充多层数据 {#populate-several-levels-deep-for-specific-relations} 🌐 Populate several levels deep for specific relations #### GET strapi.documents( — 为特定关系填充多层数据 Populate specific relations several levels deep using nested populate. **JavaScript:** ``` const documents = await strapi.documents("api::article.article").findMany({ populate: { categories: { populate: ["articles"], }, }, }); ``` **Response 200 OK:** ```json [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", // ... "categories": { "id": 1, "name": "Test Category", "slug": "test-category", "description": "Test 1" // ... "articles": [ { "id": 1, "title": "Test Article", "slug": "test-article", "body": "Test 1", // ... } // ... ] } } // ... ] ``` ### 排序已填充的关系 {#sort-populated-relations} 🌐 Sort populated relations 在 `populate` 对象中使用 `sort` 参数按属性对相关条目进行排序。对于多对多和其他关联表关系,显式的 `sort` 优先于默认的连接顺序。 🌐 Use the `sort` parameter inside a `populate` object to order related entries by an attribute. For many-to-many and other join-table relations, an explicit `sort` takes precedence over the default connect order. #### GET strapi.documents( — 排序已填充的关系 Order related entries by an attribute using the sort parameter inside a populate object. **JavaScript:** ``` const documents = await strapi.documents("api::article.article").findMany({ populate: { categories: { sort: 'name:asc', }, }, }); ``` **Response 200 OK:** ```json [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", // ... "categories": [ { "id": 1, "name": "Architecture" // ... }, { "id": 3, "name": "Technology" // ... } ] } // ... ] ``` :::note 从 `populate` 对象中省略 `sort` 以保留默认的连接顺序(条目关联的顺序)。 🌐 Omit `sort` from a `populate` object to preserve the default connect order (the order in which entries were associated). ::: ## 组件与动态区域 {#components--dynamic-zones} 🌐 Components & Dynamic Zones 组件的填充方式与关系相同: 🌐 Components are populated the same way as relations: #### GET strapi.documents( — 填充组件 Populate components using the same syntax as relations. **JavaScript:** ``` const documents = await strapi.documents("api::article.article").findMany({ populate: ["testComp"], }); ``` **Response 200 OK:** ```json [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", // ... "testComp": { "id": 1, "name": "Test Component" // ... } } // ... ] ``` 动态区域本质上是高度动态的内容结构。标准填充查询(例如 `populate: '*'` 或 `populate: ['testDZ']`)只会检索动态区域内组件的默认非关联标量字段(例如字符串、数字)。它们**不会**自动获取嵌套关系、媒体字段或嵌套组件。 🌐 Dynamic zones are highly dynamic content structures by essence. Standard populate queries (like `populate: '*'` or `populate: ['testDZ']`) will only retrieve the default, non-relational scalar fields (e.g., strings, numbers) of components within a dynamic zone. They will **not** automatically fetch nested relations, media fields, or nested components. 要填充特定组件的嵌套关系、媒体字段或动态区域内的组件,必须使用 `on` 属性(片段填充语法)定义每个组件的填充查询。 🌐 To populate component-specific nested relations, media fields, or components within a dynamic zone, you must define per-component populate queries using the `on` property (fragment population syntax). #### GET strapi.documents( — 填充动态区域 Populate dynamic zones using per-component queries with the on property. **JavaScript:** ``` const documents = await strapi.documents("api::article.article").findMany({ populate: { testDZ: { on: { "test.test-compo": { fields: ["testString"], populate: ["testNestedCompo"], }, }, }, }, }); ``` **Response 200 OK:** ```json [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", // ... "testDZ": [ { "id": 3, "__component": "test.test-compo", "testString": "test1", "testNestedCompo": { "id": 3, "testNestedString": "testNested1" } } ] } // ... ] ``` ## 正在用 `create()` 填充 {#populating-with-create} 🌐 Populating with `create()` #### GET strapi.documents( — Populate with create Populate relations in the response when creating a document. **JavaScript:** ``` strapi.documents("api::article.article").create({ data: { title: "Test Article", slug: "test-article", body: "Test 1", headerImage: 2, }, populate: ["headerImage"], }); ``` **Response 200 OK:** ```json { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", "headerImage": { "id": 2, "name": "17520.jpg" // ... } } ``` ## 正在用 `update()` 填充 {#populating-with-update} 🌐 Populating with `update()` #### GET strapi.documents( — Populate with update Populate relations in the response when updating a document. **JavaScript:** ``` strapi.documents("api::article.article").update({ documentId: "cjld2cjxh0000qzrmn831i7rn", data: { title: "Test Article Update", }, populate: ["headerImage"], }); ``` **Response 200 OK:** ```json { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article Update", "slug": "test-article", "body": "Test 1", "headerImage": { "id": 2, "name": "17520.jpg" // ... } } ``` ## 正在用 `publish()` 填充 {#populating-with-publish} 🌐 Populating with `publish()` 相同的行为适用于 `unpublish()` 和 `discardDraft()`。 🌐 Same behavior applies with `unpublish()` and `discardDraft()`. #### GET strapi.documents( — Populate with publish Populate relations in the response when publishing a document. **JavaScript:** ``` strapi.documents("api::article.article").publish({ documentId: "cjld2cjxh0000qzrmn831i7rn", populate: ["headerImage"], }); ``` **Response 200 OK:** ```json { "id": "cjld2cjxh0000qzrmn831i7rn", "versions": [ { "id": "cjld2cjxh0001qzrm1q1i7rn", "locale": "en", // ... "headerImage": { "id": 2, "name": "17520.jpg" // ... } } ] } ``` ## 正在用 `delete()` 填充 {#populating-with-delete} 🌐 Populating with `delete()` 在删除文档时进行填充: 🌐 To populate while deleting documents: #### GET strapi.documents( — Populate with delete Populate relations in the response when deleting a document. **JavaScript:** ``` strapi.documents("api::article.article").delete({ documentId: "cjld2cjxh0000qzrmn831i7rn", populate: ["headerImage"], }); ``` **Response 200 OK:** ```json { "documentId": "cjld2cjxh0000qzrmn831i7rn", "entries": [ { "id": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1", "headerImage": { "id": 2, "name": "17520.jpg" // ... } // ... } ] } ``` # 在文档服务 API 中使用 publicationFilter Source: https://strapi.nodejs.cn/cms/api/document-service/publication-filter # 文档服务 API:`publicationFilter` {#document-service-api-publicationfilter} 🌐 Document Service API: `publicationFilter` 使用可选的 `publicationFilter` 参数根据文档草稿与已发布版本之间的关系查询文档,例如从未发布的草稿,或自上次发布以来已被修改的条目。它适用于 `findOne()`、`findFirst()`、`findMany()` 和 `count()`,并可与其他查询参数结合使用。`status` 仍然决定你获取的是草稿版本还是已发布版本。 🌐 Use the optional `publicationFilter` parameter to query documents by the relationship between their draft and published versions, for example drafts that were never published, or entries modified since they were last published. It works with `findOne()`, `findFirst()`, `findMany()`, and `count()`, and combines with other query parameters. `status` still decides whether you get the draft or the published version. `publicationFilter` 是一个参数,与 [status 参数](/cms/api/document-service/status) 结合使用时,可以帮助你通过 [文档服务 API](/cms/api/document-service) 处理复杂查询,以精确找到你需要的内容。 🌐 The `publicationFilter` is a parameter that, combined with [the `status` parameter](/cms/api/document-service/status), can help you cover complex queries to find exactly what you need with the [Document Service API](/cms/api/document-service). 当 `status` 回答“我想要草稿还是已发布的版本?”时,`publicationFilter` 参数回答了一个不同的问题:“我想要哪些文档,基于它们的草稿和已发布版本的关系?” 例如,这对于查找从未发布的草稿,或草稿与在线版本相比有未保存更改的条目非常有用。 🌐 While `status` answers "do I want the draft or the published version?", the `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". This is useful for example to find drafts that were never published, or entries whose draft has unsaved changes compared to what is live. :::prerequisites 内容类型必须启用[草稿与发布](/cms/features/draft-and-publish)功能。如果草稿与发布被禁用,`publicationFilter`将不起作用。 🌐 The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. ::: ## 可用值 {#values} 🌐 Available values `publicationFilter` 接受以下值之一: | 值 | 选择 | | --- | --- | | `never-published` | 从未在特定语言环境中发布的文档 | | `never-published-document` | 从未在任何语言环境中发布的文档 | | `modified` | 自上次发布以来草稿被编辑过的文档 | | `unmodified` | 自上次发布以来草稿未更改的文档 | | `has-published-version` | 同时拥有草稿和已发布版本的文档 | | `published-without-draft` | 已发布但没有草稿对应的文档
([仅用于诊断](#diagnostics)) | | `published-with-draft` | 已发布且同时拥有草稿的文档
([仅用于诊断](#diagnostics)) | | `has-published-version-document` | 至少在一个语言环境中发布的文档
(在启用 [i18n](/cms/features/internationalization) 时有用) | 有关如何使用 `publicationFilter` 值的详细示例,包括与 `status` 参数一起使用,请参见 [可能的用例](#use-cases) 表。 🌐 For detailed examples of how to use the `publicationFilter` values, including with the `status` parameter, see the [possible use cases](#use-cases) table. :::note * 未知值会引发验证错误。 * 以 `-document` 结尾的值会考虑文档的所有语言环境,这在启用 [国际化 (i18n)](/cms/features/internationalization) 时很重要:例如,`never-published-document` 一旦文档的某个语言环境被发布就会将其排除。所有其他值则一次只考虑一个语言环境。在未启用 i18n 的情况下,这两种变体的行为相同。 ::: :::caution Caution: Different default behaviors for different APIs 当省略 `status` 时,文档服务 API 会返回文档的草稿版本,而 REST 和 GraphQL 则会返回已发布的版本,因此 REST API 查询需要明确指定 `status`(参见 [REST API: `publicationFilter`](/cms/api/rest/publication-filter))。 🌐 The Document Service API returns draft versions of documents when `status` is omitted, while REST and GraphQL return the published ones instead, so REST API queries need an explicit `status` (see [REST API: `publicationFilter`](/cms/api/rest/publication-filter)). ::: ## 可能的使用场景 {#use-cases} 🌐 Possible use cases 下表列出了许多可能的使用场景,说明如何将 `status` 和 `publicationFilter` 参数结合使用,以在文档服务 API 中精确找到你所需的内容。点击一个使用场景即可跳转到完整示例: 🌐 The following table lists many possible use cases, illustrating how the `status` and `publicationFilter` parameters can be combined to find exactly what you need with the Document Service API. Click a use case to jump to a complete example: | 我想… | 使用 `status` 作为… | 使用 `publicationFilter` 作为… | | --- | --- | --- | | [查找从未发布的草稿](#never-published) | `draft` | `never-published` | | [查找在任何地区从未发布的草稿](#never-published-document) | `draft` | `never-published-document` | | [查找已修改的文档](#modified) | `draft` 或 `published` | `modified` | | [查找未修改的文档](#unmodified) | `draft` 或 `published` | `unmodified` | | [查找有已发布版本的文档](#has-published-version) | `draft` 或 `published` | `has-published-version` | | [查找至少在一个地区发布的文档](#has-published-version-document) | `draft` 或 `published` | `has-published-version-document` | | [与 `findOne()` 和 `findFirst()` 一起使用](#find-one-find-first) | `draft` 或 `published` | 任何值 | | [只计算匹配的文档](#count) | `draft` 或 `published` | 任何值 | :::note 将一个值与上表中的相反 `status` 配对是有效的,但不会返回错误,而是返回空结果:例如,`never-published` 与 `status: 'published'` 配对会返回空结果,因为这些文档尚无已发布版本。 🌐 Pairing a value with the opposite `status` from the table above is valid but returns nothing rather than an error: for example, `never-published` with `status: 'published'` returns an empty result, because these documents have no published version yet. ::: ## 例子 {#examples} 🌐 Examples 以下部分列出了上方[表格](#use-cases)中总结的最常见用例。 🌐 The following section lists the most common use cases summed up in the [table](#use-cases) above. ### 查找从未发布的草稿 {#never-published} 🌐 Find never published drafts 最常见的用例之一是查找从未发布的草稿。为此,请传递 `status: 'draft'` 和 `publicationFilter: 'never-published'`。 🌐 One of the most common use cases is to find the drafts that have never been published. To do so, pass `status: 'draft'` and `publicationFilter: 'never-published'`. 此参数组合仅适用于特定语言环境;要在所有语言环境中查找这些文档,请改为[使用 `never-published-document`](#never-published-document)。 🌐 This parameter combination works only on a given locale; to find these documents across all locales, [use `never-published-document`](#never-published-document) instead. #### GET strapi.documents().findMany() — findMany() with publicationFilter: 返回从未在其所在地区发布的草稿。 **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'never-published', }); ``` **Response 200 OK:** ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "New Restaurant", publishedAt: null, locale: "en", // default locale // … } // … ] ``` ### 查找从未在任何地区发布的草稿 {#never-published-document} 🌐 Find drafts never published in any locale `publicationFilter: never-published-document` 返回从未在任何地区发布的文档。它查看整个文档的所有地区版本,而不是一次查看一个地区版本。要仅查找给定地区的这些文档,请改用 [使用 `never-published`](#never-published)。 一旦文档的其中一个本地化版本被发布,该文档就算作已发布:文档随后会被排除在外,即使其他本地化版本仅以草稿形式存在。下面的示例返回那些从未在任何地方发布过的文档的草稿版本: 🌐 A document counts as published as soon as one of its locales is published: the document is then left out, even the locales that only exist as a draft. The example below returns the draft versions of documents that were never published anywhere: #### GET strapi.documents().findMany() — findMany() with publicationFilter: 返回从未在任何地区发布的文件草稿。 **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'never-published-document', }); ``` **Response 200 OK:** ```json [ { documentId: "d41r46wac4xix5vpba7561at", name: "New Restaurant", publishedAt: null, locale: "en", // default locale // … } // … ] ``` ### 查找已修改的文档 {#modified} 🌐 Find modified documents `publicationFilter: modified` 选择草稿有修改但未发布更改的文档。然后 `status` 决定你得到这些文档的哪个版本。 例如,使用 `status: 'draft'` 时,查询返回草稿版本: 🌐 For instance, with `status: 'draft'`, the query returns the draft versions: #### GET strapi.documents().findMany() — findMany() with publicationFilter: 返回含有未发布更改的文档草稿版本。 **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'modified', }); ``` **Response 200 OK:** ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant (updated)", publishedAt: null, locale: "en", // default locale // … } // … ] ```
使用 `status: 'published'` 时,相同的查询会返回这些文档当前的实时版本: #### GET strapi.documents().findMany() — findMany() with publicationFilter: 返回具有未发布更改的文档的当前版本。 **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'modified', }); ``` **Response 200 OK:** ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: "2024-03-14T15:40:45.330Z", locale: "en", // default locale // … } // … ] ``` ### 查找未修改的文档 {#unmodified} 🌐 Find unmodified documents `publicationFilter: unmodified` 选择自上次发布以来草稿未更改的文档。然后 `status` 决定你将获取这些文档的哪个版本。 例如,使用 `status: 'draft'` 时,查询返回草稿版本: 🌐 For instance, with `status: 'draft'`, the query returns the draft versions: #### GET strapi.documents().findMany() — findMany() with publicationFilter: 返回自上次发布以来未更改的文件草稿版本。 **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'unmodified', }); ``` **Response 200 OK:** ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: null, locale: "en", // default locale // … } // … ] ```
使用 `status: 'published'` 时,相同的查询会返回这些文档当前的实时版本: #### GET strapi.documents().findMany() — findMany() with publicationFilter: 返回自上次发布以来未更改的文档当前版本。 **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'unmodified', }); ``` **Response 200 OK:** ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: "2024-03-14T15:40:45.330Z", locale: "en", // default locale // … } // … ] ``` ### 查找有已发布版本的文档 {#has-published-version} 🌐 Find documents with a published version `publicationFilter: has-published-version` 选择那些在相同语言环境下既有草稿版本又有已发布版本的文档。`status` 然后决定你获得这些文档的哪个版本。 例如,使用 `status: 'draft'` 时,查询返回草稿版本: 🌐 For instance, with `status: 'draft'`, the query returns the draft versions: #### GET strapi.documents().findMany() — findMany() with publicationFilter: 返回那些在相同语言环境下也有已发布版本的文档草稿版本。 **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'has-published-version', }); ``` **Response 200 OK:** ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: null, locale: "en", // default locale // … } // … ] ```
使用 `status: 'published'` 时,相同的查询会返回这些文档当前的实时版本: #### GET strapi.documents().findMany() — findMany() with publicationFilter: 返回当前在线版本的文档,这些文档在同一语言区域也有已发布的版本。 **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'has-published-version', }); ``` **Response 200 OK:** ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: "2024-03-14T15:40:45.330Z", locale: "en", // default locale // … } // … ] ``` ### 查找在至少一个地区有已发布版本的文档 {#has-published-version-document} 🌐 Find documents with a published version in at least one locale `publicationFilter: has-published-version-document` 会考虑所有地区,因此只要其中一个地区的文档已发布,它就会匹配该文档。使用 `status: 'draft'` 时,它会返回这些文档每个地区的草稿版本,包括那些从未发布过的地区: #### GET strapi.documents().findMany() — findMany() with publicationFilter: 返回至少在一个地区发布的文档的草稿版本。 **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findMany({ status: 'draft', publicationFilter: 'has-published-version-document', }); ``` **Response 200 OK:** ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: null, locale: "en", // published in at least one locale // … } // … ] ``` ### 与 `findOne()` 和 `findFirst()` 一起使用 {#find-one-find-first} 🌐 Use with `findOne()` and `findFirst()` 如果所请求的文档(以及适用时的区域设置)与筛选条件不匹配,即使 `documentId` 存在,`findOne()` 和 `findFirst()` 也会返回 `null`: 🌐 If the requested document (and locale, when applicable) does not match the filter, `findOne()` and `findFirst()` return `null` even when the `documentId` exists: #### GET strapi.documents().findOne() — findOne() with publicationFilter: Return the document only if it matches the filter, null otherwise. **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findOne({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', status: 'draft', publicationFilter: 'never-published', }); ``` **Response 200 OK:** ```json null // the documentId exists, but the document does not match never-published ``` ### 只计算匹配的文档 {#count} 🌐 Count only matching documents 没有 `publicationFilter`,`count({ status: 'draft' })` 会计算每一个草稿版本,包括那些文档已经有已发布版本的草稿。添加 `publicationFilter` 以仅计算符合给定值的文档(参见 [`status` 文档](/cms/api/document-service/status#count)): 🌐 Without `publicationFilter`, `count({ status: 'draft' })` counts every draft version, including drafts whose document already has a published version. Add `publicationFilter` to count only the documents that match a given value (see the [`status` documentation](/cms/api/document-service/status#count)): #### GET strapi.documents().count() — count() with publicationFilter: Count only the documents that match a given value. **JavaScript:** ``` const neverPublishedCount = await strapi .documents('api::restaurant.restaurant') .count({ status: 'draft', publicationFilter: 'never-published', }); ``` **Response 200 OK:** ```json 12 // the number of never-published drafts ``` ## 诊断值 {#diagnostics} 🌐 Diagnostic values `published-without-draft` 和 `published-with-draft` 值仅用于数据完整性检查,而不是日常查询。在健康的数据库中,每个已发布的文档也都有一个草稿版本,因此这些值仅用于检测由遗留数据或手动数据库编辑导致处于不一致状态的文档。它们仅与 `status: 'published'` 一起使用。 🌐 The `published-without-draft` and `published-with-draft` values are meant for data-integrity checks only, not for everyday queries. In a healthy database, every published document also has a draft version, so these values are only useful to detect documents left in an inconsistent state by legacy data or manual database edits. They only work with `status: 'published'`.
显示诊断值示例 `publicationFilter: published-without-draft` 选择没有草稿对应的已发布文档。在正常操作中,这应返回空值: #### GET strapi.documents().findMany() — findMany() with publicationFilter: 返回在同一语言环境下没有匹配草稿版本的已发布文档。 **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'published-without-draft', }); ``` **Response 200 OK:** ```json [ { documentId: "j0klm1n2o3p4q5r6s7t8u9v", name: "Legacy Restaurant", publishedAt: "2024-01-10T09:15:00.000Z", locale: "en", // default locale // … } // … ] ```
`publicationFilter: published-with-draft` 选择那些也有草稿的已发布文档,这在一个健康的数据库中是每个已发布的文档: #### GET strapi.documents().findMany() — findMany() with publicationFilter: 返回那些也有相同本地版本草稿的已发布文档。 **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findMany({ status: 'published', publicationFilter: 'published-with-draft', }); ``` **Response 200 OK:** ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: "2024-03-14T15:40:45.330Z", locale: "en", // default locale // … } // … ] ```
## 与其他参数的组合 {#combine} 🌐 Combination with other parameters `publicationFilter` 与其他查询参数作为逻辑 `AND` 进行组合,包括 [`filters`](/cms/api/document-service/filters) 和 [`populate`](/cms/api/document-service/populate)。在填充 draft 与 publish 关系时,嵌套查询会继承相同的过滤逻辑。 ## 内容管理员映射 {#content-manager} 🌐 Content Manager mapping 在内容管理器中,**草稿(从未发布)** 列表过滤器映射到 `status: 'draft'` 和 `publicationFilter: 'never-published-document'`(以文档为范围,而不是每区域语言的 `never-published`)。 🌐 In the Content Manager, the **Draft (never published)** list filter maps to `status: 'draft'` and `publicationFilter: 'never-published-document'` (document-scoped, not the per-locale `never-published`). # 在文档服务 API 中使用排序和分页 Source: https://strapi.nodejs.cn/cms/api/document-service/sort-pagination # 文档服务 API:结果的排序和分页 {#document-service-api-sorting-and-paginating-results} 🌐 Document Service API: Sorting and paginating results 使用文档服务 API 的 `sort` 和分页参数按单个或多个字段排序查询结果,并使用 `limit` 和 `start` 控制结果的限制。 🌐 Use the Document Service API's `sort` and pagination parameters to order query results by single or multiple fields and control result limits with `limit` and `start`. [文档服务 API](/cms/api/document-service) 提供对查询结果进行排序和分页的功能。 🌐 The [Document Service API](/cms/api/document-service) offers the ability to sort and paginate query results. ## 排序 {#sort} 🌐 Sort 要对文档服务 API 返回的结果进行排序,请在查询中包含 `sort` 参数。 🌐 To sort results returned by the Document Service API, include the `sort` parameter with queries. ### 按单个字段排序 {#sort-on-a-single-field} 🌐 Sort on a single field #### GET strapi.documents().findMany() — 按单个字段排序 Sort results based on a single field using a string value. **JavaScript:** ``` const documents = await strapi.documents("api::article.article").findMany({ sort: "title:asc", }); ``` **Response 200 OK:** ```json [ { "documentId": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1" }, { "documentId": "cjld2cjxh0001qzrm5q1j5q7m", "title": "Test Article 2", "slug": "test-article-2", "body": "Test 2" } ] ``` ### 按多个字段排序 {#sort-on-multiple-fields} 🌐 Sort on multiple fields #### GET strapi.documents().findMany() — 按多个字段排序 Sort results on multiple fields by passing an array of sort objects. **JavaScript:** ``` const documents = await strapi.documents("api::article.article").findMany({ sort: [{ title: "asc" }, { slug: "desc" }], }); ``` **Response 200 OK:** ```json [ { "documentId": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1" }, { "documentId": "cjld2cjxh0001qzrm5q1j5q7m", "title": "Test Article 2", "slug": "test-article-2", "body": "Test 2" } ] ``` ## 分页 {#pagination} 🌐 Pagination #### GET strapi.documents().findMany() — 分页 Paginate results using the limit and start parameters. **JavaScript:** ``` const documents = await strapi.documents("api::article.article").findMany({ limit: 10, start: 0, }); ``` **Response 200 OK:** ```json [ { "documentId": "cjld2cjxh0000qzrmn831i7rn", "title": "Test Article", "slug": "test-article", "body": "Test 1" }, { "documentId": "cjld2cjxh0001qzrm5q1j5q7m", "title": "Test Article 2", "slug": "test-article-2", "body": "Test 2" } ] ``` # 在文档服务 API 中使用草稿与发布 Source: https://strapi.nodejs.cn/cms/api/document-service/status # 文档服务 API:草稿与发布的使用 {#document-service-api-usage-with-draft--publish} 🌐 Document Service API: Usage with Draft & Publish 使用 Document Service API 的 `status` 参数可以检索文档的已发布或草稿版本,按状态统计文档数量,并在创建或更新文档时直接发布文档。 🌐 Use the `status` parameter with the Document Service API to retrieve published or draft versions of documents, count documents by status, and directly publish documents during creation or updates. 默认情况下,当启用 [Draft & Publish](/cms/features/draft-and-publish) 功能时,[Document Service API](/cms/api/document-service) 会返回文档的草稿版本。此页面描述了如何使用 `status` 参数来: 🌐 By default the [Document Service API](/cms/api/document-service) returns the draft version of a document when the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled. This page describes how to use the `status` parameter to: - 返回文档的已发布版本, - 根据文档的状态计数文档, - 并在创建或更新文档时直接发布文档。 :::note 将 `{ status: 'draft' }` 传递给文档服务 API 查询返回的结果与未传递任何 `status` 参数时相同。 🌐 Passing `{ status: 'draft' }` to a Document Service API query returns the same results as not passing any `status` parameter. ::: 要根据文档的草稿版本和已发布版本的关系(从未发布、已修改等)选择文档,请参见 [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter)。 🌐 To select documents by how their draft and published versions relate (never-published, modified, and others), see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter). ## 获取已发布的版本与 `findOne()` {#find-one} 🌐 Get the published version with `findOne()` #### GET strapi.documents().findOne() — findOne() with status: Return the published version of a specific document. **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').findOne({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', status: 'published' }); ``` **Response 200 OK:** ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: "2024-03-14T15:40:45.330Z", locale: "en", // default locale // … } ``` ## 获取带有 `findFirst()` {#find-first} 的已发布版本 🌐 Get the published version with `findFirst()` #### GET strapi.documents().findFirst() — findFirst() with status: Return the published version of the first matching document. **JavaScript:** ``` const document = await strapi.documents("api::restaurant.restaurant").findFirst({ status: 'published', }); ``` **Response 200 OK:** ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: "2024-03-14T15:40:45.330Z", locale: "en", // default locale // … } ``` ## 获取已发布的版本与 `findMany()` {#find-many} 🌐 Get the published version with `findMany()` #### GET strapi.documents().findMany() — findMany() with status: Return the published versions of all matching documents. **JavaScript:** ``` const documents = await strapi.documents("api::restaurant.restaurant").findMany({ status: 'published' }); ``` **Response 200 OK:** ```json [ { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant", publishedAt: "2024-03-14T15:40:45.330Z", locale: "en", // default locale // … } // … ] ``` ## `count()` 仅草稿或已发布版本 {#count} 🌐 `count()` only draft or published versions 在使用文档服务 API [计数文档](/cms/api/document-service#count) 时,如果只考虑文档的草稿或已发布版本,请传递相应的 `status` 参数: 🌐 To take into account only draft or published versions of documents while [counting documents](/cms/api/document-service#count) with the Document Service API, pass the corresponding `status` parameter: ```js // Count draft documents (also actually includes published documents) const draftsCount = await strapi.documents("api::restaurant.restaurant").count({ status: 'draft' }); ``` ```js // Count only published documents const publishedCount = await strapi.documents("api::restaurant.restaurant").count({ status: 'published' }); ``` :::note 由于已发布的文档必然也有草稿副本,因此已发布的文档仍算作具有草稿版本。 🌐 Since published documents necessarily also have a draft counterpart, a published document is still counted as having a draft version. 这意味着,即使某些文档已经发布,并且在内容管理器中不再显示为“草稿”或“已修改”,使用 `status: 'draft'` 参数进行计数仍会返回匹配其他参数的文档总数。要仅计数从未发布的草稿,请[传递 `publicationFilter` 值](/cms/api/document-service/publication-filter),例如 `'never-published'` 或 `'never-published-document'`。 🌐 This means that counting with the `status: 'draft'` parameter still returns the total number of documents matching other parameters, even if some documents have already been published and are not displayed as "draft" or "modified" in the Content Manager anymore. To count only never-published drafts, [pass a `publicationFilter` value](/cms/api/document-service/publication-filter) such as `'never-published'` or `'never-published-document'`. ::: ## 创建草稿并发布它 {#create} 🌐 Create a draft and publish it #### GET strapi.documents().create() — create() with status: Create a new document and immediately publish it. **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').create({ data: { name: "New Restaurant", }, status: 'published', }) ``` **Response 200 OK:** ```json { documentId: "d41r46wac4xix5vpba7561at", name: "New Restaurant", publishedAt: "2024-03-14T17:29:03.399Z", locale: "en" // default locale // … } ``` ## 更新草稿并发布它 {#update} 🌐 Update a draft and publish it #### GET strapi.documents().update() — update() with status: Update an existing document and immediately publish it. **JavaScript:** ``` await strapi.documents('api::restaurant.restaurant').update({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', data: { name: "Biscotte Restaurant (closed)", }, status: 'published', }) ``` **Response 200 OK:** ```json { documentId: "a1b2c3d4e5f6g7h8i9j0klm", name: "Biscotte Restaurant (closed)", publishedAt: "2024-03-14T17:29:03.399Z", locale: "en" // default locale // … } ``` # 实体服务 API Source: https://strapi.nodejs.cn/cms/api/entity-service # 实体服务 API {#entity-service-api} 🌐 Entity Service API 实体服务 API 是一个后端层,用于处理复杂的内容结构,如组件和动态区域,提供通过 `strapi.entityService` 的 CRUD 操作、过滤、关系填充和分页功能。 🌐 The Entity Service API is a backend layer that handles complex content structures like components and dynamic zones, providing CRUD operations, filtering, populating relations, and pagination via `strapi.entityService`. :::caution 在 Strapi v5 中,实体服务 API 已被弃用。请考虑改用 [文档服务 API](/cms/api/document-service)。 🌐 The Entity Service API is deprecated in Strapi v5. Please consider using the [Document Service API](/cms/api/document-service) instead. ::: :::prerequisites 在深入了解实体服务 API 文档之前,建议你阅读以下介绍: 🌐 Before diving deeper into the Entity Service API documentation, it is recommended that you read the following introductions: - 关于[后端自定义介绍](/cms/backend-customization), - 以及 [内容 API 介绍](/cms/api/content-api)。 ::: Strapi 后端提供了一个实体服务 API,构建在 [Query Engine API](/cms/api/query-engine/) 之上。实体服务是处理 Strapi 复杂内容结构(如 [组件](/cms/backend-customization/models#components-json) 和 [动态区域](/cms/backend-customization/models#dynamic-zones))的层,并在底层使用 Query Engine API 来执行数据库查询。 🌐 The Strapi backend provides an Entity Service API, built on top of the [Query Engine API](/cms/api/query-engine/). The Entity Service is the layer that handles Strapi's complex content structures like [components](/cms/backend-customization/models#components-json) and [dynamic zones](/cms/backend-customization/models#dynamic-zones), and uses the Query Engine API under the hood to execute database queries. :::strapi Entity Service API vs. Query Engine API Strapi v4 提供了多个层来与后端交互并构建查询: 🌐 Strapi v4 offers several layers to interact with the backend and build your queries: * 文档服务 API 是与你的应用数据库交互的推荐 API。文档服务是处理 Strapi 文档模型以及组件和动态区域等复杂内容结构的层,底层层不知晓这些内容结构。 * 查询引擎 API 在更低的层次与数据库层交互,并在后台用于执行数据库查询。它提供对数据库层的不受限制的内部访问,但只有在文档服务 API 无法满足你的使用场景时才应使用。 * 如果你需要直接访问 `knex` 函数,请使用 `strapi.db.connection`。 ::: :::info Disambiguation: Services vs. Entity Service 虽然[服务](/cms/backend-customization/services)可以使用实体服务 API,但服务和实体服务 API 并没有直接关系。你可以在[后端自定义](/cms/backend-customization)文档中找到有关 Strapi 后端核心元素的更多信息。 🌐 While [services](/cms/backend-customization/services) can use the Entity Service API, services and the Entity Service API are not directly related. You can find more information about the core elements of the Strapi back end in the [back-end customization](/cms/backend-customization) documentation. ::: ## 基本用法 {#basic-usage} 🌐 Basic usage 实体服务可通过 `strapi.entityService` 使用: 🌐 The Entity Service is available through `strapi.entityService`: ```js const entry = await strapi.entityService.findOne('api::article.article', 1, { populate: { someRelation: true }, }); ``` ## 可用操作 {#available-operations} 🌐 Available operations 实体服务 API 允许对实体执行以下操作: 🌐 The Entity Service API allows the following operations on entities: - [增删改查操作](/cms/api/entity-service/crud): 使用实体服务 API 创建、读取、更新和删除实体。 - [过滤器](/cms/api/entity-service/filter): 通过使用你的实体服务 API 查询过滤实体,精确获取所需内容。 - [填充](/cms/api/entity-service/populate): 通过填充关系,使用你的实体服务 API 查询获取更多数据。 - [排序与分页](/cms/api/entity-service/order-pagination): 对你的实体服务 API 查询的结果进行排序和分页。 - [组件/动态区域](/cms/api/entity-service/components-dynamic-zones): 使用你的实体服务 API 查询创建和更新组件及动态区域。 # 组件与动态区域 Source: https://strapi.nodejs.cn/cms/api/entity-service/components-dynamic-zones # 使用实体服务 API 创建组件和动态区域 {#creating-components-and-dynamic-zones-with-the-entity-service-api} 🌐 Creating components and dynamic zones with the Entity Service API 使用实体服务 API 在创建或更新条目时创建和更新组件及动态区域。组件是单个对象,而动态区域是具有 `__component` 类型标识符的组件列表。 🌐 Use the Entity Service API to create and update components and dynamic zones while creating or updating entries. Components are single objects while dynamic zones are lists of components with a `__component` type identifier. :::caution 在 Strapi v5 中,实体服务 API 已被弃用。请考虑改用 [文档服务 API](/cms/api/document-service)。 🌐 The Entity Service API is deprecated in Strapi v5. Please consider using the [Document Service API](/cms/api/document-service) instead. ::: [实体服务](/cms/api/entity-service) 是处理 [组件](/cms/backend-customization/models#components-json) 和 [动态区域](/cms/backend-customization/models#dynamic-zones) 逻辑的层。通过实体服务 API,可以在创建或更新条目时 [创建](#creation) 和 [更新](#update) 组件和动态区域。 🌐 The [Entity Service](/cms/api/entity-service) is the layer that handles [components](/cms/backend-customization/models#components-json) and [dynamic zones](/cms/backend-customization/models#dynamic-zones) logic. With the Entity Service API, components and dynamic zones can be [created](#creation) and [updated](#update) while creating or updating entries. ## 创造 {#creation} 🌐 Creation 在使用实体服务 API 创建条目时,可以创建一个 [组件](/cms/backend-customization/models#components-json): 🌐 A [component](/cms/backend-customization/models#components-json) can be created while creating an entry with the Entity Service API: ```js strapi.entityService.create('api::article.article', { data: { myComponent: { foo: 'bar', }, }, }); ``` 在使用实体服务 API 创建条目时,可以创建一个[动态区域](/cms/backend-customization/models#dynamic-zones)(即组件列表): 🌐 A [dynamic zone](/cms/backend-customization/models#dynamic-zones) (i.e. a list of components) can be created while creating an entry with the Entity Service API: ```js strapi.entityService.create('api::article.article', { data: { myDynamicZone: [ { __component: 'compo.type', foo: 'bar', }, { __component: 'compo.type2', foo: 'bar', }, ], }, }); ``` ## 更新 {#update} 🌐 Update 在使用实体服务 API 更新条目时,可以更新一个 [组件](/cms/backend-customization/models#components-json)。如果指定了组件 `id`,则组件会被更新,否则旧的组件会被删除并创建一个新的组件: 🌐 A [component](/cms/backend-customization/models#components-json) can be updated while updating an entry with the Entity Service API. If a component `id` is specified, the component is updated, otherwise the old one is deleted and a new one is created: ```js strapi.entityService.update('api::article.article', 1, { data: { myComponent: { id: 1, // will update component with id: 1 (if not specified, would have deleted it and created a new one) foo: 'bar', }, }, }); ``` 使用实体服务 API 更新条目时,动态区域(即组件列表)可以被更新。如果指定了组件 `id`,则该组件会被更新,否则旧的组件会被删除并创建一个新的组件: 🌐 A [dynamic zone](/cms/backend-customization/models#dynamic-zones) (i.e. a list of components) can be updated while updating an entry with the Entity Service API. If a component `id` is specified, the component is updated, otherwise the old one is deleted and a new one is created: ```js strapi.entityService.update('api::article.article', 1, { data: { myDynamicZone: [ { // will update id: 2, __component: 'compo.type', foo: 'bar', }, { // will add a new & delete old ones __component: 'compo.type2', foo: 'bar2', }, ], }, }); ``` # 增删改查操作 Source: https://strapi.nodejs.cn/cms/api/entity-service/crud # 使用实体服务 API 的 CRUD 操作 {#crud-operations-with-the-entity-service-api} 🌐 CRUD operations with the Entity Service API 实体服务 API 通过 `findOne()`、`findMany()`、`create()`、`update()` 和 `delete()` 方法对内容执行 CRUD 操作,支持过滤、分页、关联和本地化。 🌐 The Entity Service API performs CRUD operations on content through `findOne()`, `findMany()`, `create()`, `update()`, and `delete()` methods, supporting filtering, pagination, relations, and localization. :::caution 在 Strapi v5 中,实体服务 API 已被弃用。请考虑改用 [文档服务 API](/cms/api/document-service)。 🌐 The Entity Service API is deprecated in Strapi v5. Please consider using the [Document Service API](/cms/api/document-service) instead. ::: [实体服务 API](/cms/api/entity-service) 构建在 [查询引擎 API](/cms/api/query-engine) 之上,并使用它对实体执行 CRUD 操作。 🌐 The [Entity Service API](/cms/api/entity-service) is built on top of the the [Query Engine API](/cms/api/query-engine) and uses it to perform CRUD operations on entities. 在此 API 的函数调用中使用的 `uid` 参数是一个按以下格式构建的 `string`:`[category]::[content-type]`,其中 `category` 可以是:`admin`、`plugin` 或 `api`。 🌐 The `uid` parameter used in function calls for this API is a `string` built with the following format: `[category]::[content-type]` where `category` is one of: `admin`, `plugin` or `api`. 示例: 🌐 Examples: - 获取 Strapi 管理面板用户的正确 `uid` 是 `admin::user`。 - 上传插件的一个可能的 `uid` 可以是 `plugin::upload.file`。 - 由于用户定义的自定义内容类型的 `uid` 遵循 `api::[content-type]` 语法,如果存在内容类型 `article`,则通过 `api::article.article` 进行引用。 :::tip 在终端中运行 [`strapi content-types:list`](/cms/cli#strapi-content-typeslist) 命令,以显示特定 Strapi 实例的所有可能内容类型的 `uid`。 🌐 Run the [`strapi content-types:list`](/cms/cli#strapi-content-typeslist) command in a terminal to display all possible content-types' `uid`s for a specific Strapi instance. ::: ## findOne() 查找与参数匹配的第一个条目。 🌐 Finds the first entry matching the parameters. 语法:`findOne(uid: string, id: ID, parameters: Params)` ⇒ `Entry` 🌐 Syntax: `findOne(uid: string, id: ID, parameters: Params)` ⇒ `Entry` ### 参数 {#parameters} 🌐 Parameters | 参数 | 描述 | 类型 | | --- | --- | --- | | `fields` | 要返回的属性 | `String[]` | | `populate` | 要 [填充](/cms/api/entity-service/populate) 的关系、组件和动态区域 | [`PopulateParameter`](/cms/api/entity-service/populate) | | `locale` | 当启用国际化插件时的语言代码(例如 `fr-FR`)。针对本地化变体而非默认语言。 | `string` | ### 例子 {#example} 🌐 Example ```js const entry = await strapi.entityService.findOne('api::article.article', 1, { fields: ['title', 'description'], populate: { category: true }, }); ``` ## findMany() 查找与参数匹配的条目。 🌐 Finds entries matching the parameters. 语法:`findMany(uid: string, parameters: Params)` ⇒ `Entry[]` 🌐 Syntax: `findMany(uid: string, parameters: Params)` ⇒ `Entry[]` ### 参数 {#parameters-1} 🌐 Parameters | 参数 | 描述 | 类型 | | --- | --- | --- | | `fields` | 要返回的属性 | `String[]` | | `filters` | 使用的[筛选器](/cms/api/entity-service/filter) | [`FiltersParameters`](/cms/api/entity-service/filter) | | `start` | 要跳过的条目数量(参见 [分页](/cms/api/entity-service/order-pagination#pagination)) | `Number` | | `limit` | 要返回的条目数量(参见 [分页](/cms/api/entity-service/order-pagination#pagination)) | `Number` | | `sort` | [订单](/cms/api/entity-service/order-pagination) 定义 | [`OrderByParameter`](/cms/api/entity-service/order-pagination) | | `populate` | 关系、组件和动态区域以 [填充](/cms/api/entity-service/populate) | [`PopulateParameter`](/cms/api/entity-service/populate) | | `publicationState` | 发布状态,可以是:
  • `live` 仅返回已发布条目
  • `preview` 返回草稿条目和已发布条目(默认)
| `PublicationStateParameter` | | `locale` | 当启用国际化插件时的语言环境代码。将结果限制为该语言环境(默认语言环境请省略)。 | `string` | ### 例子 {#example-1} 🌐 Example ```js const entries = await strapi.entityService.findMany('api::article.article', { fields: ['title', 'description'], filters: { title: 'Hello World' }, sort: { createdAt: 'DESC' }, populate: { category: true }, }); ```
:::tip 要仅检索草稿条目,请结合使用 `preview` 发布状态和 `publishedAt` 字段: 🌐 To retrieve only draft entries, combine the `preview` publication state and the `publishedAt` fields: ```js const entries = await strapi.entityService.findMany('api::article.article', { publicationState: 'preview', filters: { publishedAt: { $null: true, }, }, }); ::: ## create() Creates one entry and returns it Syntax: `create(uid: string, parameters: Params)` ⇒ `Entry` ### Parameters | Parameter | Description | Type | | ---------- | ----------- | ---------- | | `fields` | Attributes to return | `String[]` | | `populate` | Relations, components and dynamic zones to [populate](/cms/api/entity-service/populate) | [`PopulateParameter`](/cms/api/entity-service/populate) | | `locale` | Locale code when the Internationalization plugin is enabled. Creates the entry for that locale. | `string` | | `data` | Input data | `Object` | :::tip 在 `data` 对象中,可以使用 `connect`、`disconnect` 和 `set` 参数按照 REST API 描述的语法管理关系(参见 [管理关系](/cms/api/rest/relations))。 🌐 In the `data` object, relations can be managed with the `connect`, `disconnect`, and `set` parameters using the syntax described for the REST API (see [managing relations](/cms/api/rest/relations)). ::: ### Example ```js const entry = await strapi.entityService.create('api::article.article', { data: { title: 'My Article', }, }); ``` ## update() Updates one entry and returns it. :::note `update()` only performs a partial update, so existing fields that are not included won't be replaced. ::: Syntax: `update(uid: string, id: ID, parameters: Params)` ⇒ `Entry` :::tip 在 `data` 对象中,可以使用 `connect`、`disconnect` 和 `set` 参数按照 REST API 描述的语法管理关系(参见 [管理关系](/cms/api/rest/relations))。 🌐 In the `data` object, relations can be managed with the `connect`, `disconnect`, and `set` parameters using the syntax described for the REST API (see [managing relations](/cms/api/rest/relations)). ::: ### Parameters | Parameter | Description | Type | | ---------- | ------------- | ---------- | | `fields` | Attributes to return | `String[]` | | `populate` | Relations, components and dynamic zones to [populate](/cms/api/entity-service/populate) | [`PopulateParameter`](/cms/api/entity-service/populate) | | `locale` | Locale code when the Internationalization plugin is enabled. Updates the matching localized variant. | `string` | | `data` | Input data | `object` | ### Example ```js const entry = await strapi.entityService.update('api::article.article', 1, { data: { title: 'xxx', }, }); ``` ## delete() Deletes one entry and returns it. Syntax: `delete(uid: string, id: ID, parameters: Params)` ⇒ `Entry` ### Parameters | Parameter | Description | Type | | ---------- | --------- | -------- | | `fields` | Attributes to return | `String[]` | | `populate` | Relations, components and dynamic zones to [populate](/cms/api/entity-service/populate) | [`PopulateParameter`](/cms/api/entity-service/populate) | | `locale` | Locale code when the Internationalization plugin is enabled. Deletes the localized variant that matches this locale. | `string` | ### Example ```js const entry = await strapi.entityService.delete('api::article.article', 1); ``` # 使用实体服务 API 进行筛选 Source: https://strapi.nodejs.cn/cms/api/entity-service/filter # 使用实体服务 API 进行筛选 {#filtering-with-the-entity-service-api} 🌐 Filtering with the Entity Service API 使用 `findMany()` 中的 `filters` 参数,通过逻辑运算符(`$and`、`$or`、`$not`)和属性运算符(`$eq`、`$contains`、`$gt`、`$between` 等)过滤实体服务 API 查询结果。 🌐 Filter Entity Service API query results using logical operators (`$and`, `$or`, `$not`) and attribute operators (`$eq`, `$contains`, `$gt`, `$between`, etc.) with the `filters` parameter in `findMany()`. :::caution 在 Strapi v5 中,实体服务 API 已被弃用。请考虑改用 [文档服务 API](/cms/api/document-service)。 🌐 The Entity Service API is deprecated in Strapi v5. Please consider using the [Document Service API](/cms/api/document-service) instead. ::: [实体服务 API](/cms/api/entity-service) 提供了使用其 [findMany()](/cms/api/entity-service/crud#findmany) 方法筛选结果的功能。 🌐 The [Entity Service API](/cms/api/entity-service) offers the ability to filter results found with its [findMany()](/cms/api/entity-service/crud#findmany) method. 结果通过 `filters` 参数进行过滤,该参数接受 [逻辑运算符](#logical-operators) 和 [属性运算符](#attribute-operators)。每个运算符都应以 `$` 为前缀。 🌐 Results are filtered with the `filters` parameter that accepts [logical operators](#logical-operators) and [attribute operators](#attribute-operators). Every operator should be prefixed with `$`. :::strapi Deep filtering with the various APIs 有关如何使用各种 API 进行深度过滤的示例,请参阅 [this blog article](https://strapi.io/blog/deep-filtering-alpha-26)。 ::: ## 逻辑运算符 {#logical-operators} 🌐 Logical operators ### `$and` 所有嵌套条件必须是 `true`。 🌐 All nested conditions must be `true`. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { $and: [ { title: 'Hello World', }, { createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, ], }, }); ``` `$and` 在传递带有嵌套条件的对象时将被隐式使用: ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: 'Hello World', createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, }); ``` ### `$or` 一个或多个嵌套条件必须是 `true`。 🌐 One or many nested conditions must be `true`. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { $or: [ { title: 'Hello World', }, { createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, ], }, }); ``` ### `$not` 否定嵌套条件。 🌐 Negates the nested conditions. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { $not: { title: 'Hello World', }, }, }); ``` :::note `$not` 可以用作: - 一个逻辑运算符(例如在 `filters: { $not: { // conditions… }}` 中) - [属性操作符](#not-1)(例如在 `filters: { attribute-name: $not: { … } }` 中)。 ::: :::tip `$and`、`$or` 和 `$not` 操作符可以嵌套在另一个 `$and`、`$or` 或 `$not` 操作符中。 ::: ## 属性运算符 {#attribute-operators} 🌐 Attribute Operators :::caution 根据数据库的实现,使用这些运算符可能会给出不同的结果,因为比较是由数据库而不是 Strapi 处理的。 🌐 Using these operators may give different results depending on the database's implementation, as the comparison is handled by the database and not by Strapi. ::: ### `$not` 否定嵌套条件。 🌐 Negates the nested condition(s). **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $not: { $contains: 'Hello World', }, }, }, }); ``` ### `$eq` 属性等于输入值。 🌐 Attribute equals input value. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $eq: 'Hello World', }, }, }); ``` `$eq`可以省略: ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: 'Hello World', }, }); ``` ### `$eqi` 属性等于输入值(不区分大小写)。 🌐 Attribute equals input value (case-insensitive). **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $eqi: 'HELLO World', }, }, }); ``` ### `$ne` 属性不等于输入值。 🌐 Attribute does not equal input value. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $ne: 'ABCD', }, }, }); ``` ### `$nei` 属性不等于输入值(不区分大小写)。 🌐 Attribute does not equal input value (case-insensitive). **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $nei: 'abcd', }, }, }); ``` ### `$in` 属性包含在输入列表中。 🌐 Attribute is contained in the input list. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $in: ['Hello', 'Hola', 'Bonjour'], }, }, }); ``` 在传递一个值数组时可以省略 `$in`: ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: ['Hello', 'Hola', 'Bonjour'], }, }); ``` ### `$notIn` 输入列表中不包含属性。 🌐 Attribute is not contained in the input list. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $notIn: ['Hello', 'Hola', 'Bonjour'], }, }, }); ``` ### `$lt` 属性小于输入值。 🌐 Attribute is less than the input value. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { rating: { $lt: 10, }, }, }); ``` ### `$lte` 属性小于或等于输入值。 🌐 Attribute is less than or equal to the input value. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { rating: { $lte: 10, }, }, }); ``` ### `$gt` 属性大于输入值。 🌐 Attribute is greater than the input value. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { rating: { $gt: 5, }, }, }); ``` ### `$gte` 属性大于或等于输入值。 🌐 Attribute is greater than or equal to the input value. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { rating: { $gte: 5, }, }, }); ``` ### `$between` 属性介于两个输入值之间,包括边界(例如,`$between[1, 3]` 也会返回 `1` 和 `3`)。 🌐 Attribute is between the 2 input values, boundaries included (e.g., `$between[1, 3]` will also return `1` and `3`). **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { rating: { $between: [1, 20], }, }, }); ``` ### `$contains` 属性包含输入值(区分大小写)。 🌐 Attribute contains the input value (case-sensitive). **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $contains: 'Hello', }, }, }); ``` ### `$notContains` 属性不包含输入值(区分大小写)。 🌐 Attribute does not contain the input value (case-sensitive). **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $notContains: 'Hello', }, }, }); ``` ### `$containsi` 属性包含输入值。`$containsi`不区分大小写,而[$contains](#contains)区分大小写。 🌐 Attribute contains the input value. `$containsi` is not case-sensitive, while [$contains](#contains) is. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $containsi: 'hello', }, }, }); ``` ### `$notContainsi` 属性不包含输入值。`$notContainsi` 不区分大小写,而 [$notContains](#notcontains) 区分大小写。 🌐 Attribute does not contain the input value. `$notContainsi` is not case-sensitive, while [$notContains](#notcontains) is. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $notContainsi: 'hello', }, }, }); ``` ### `$startsWith` 属性以输入值开始。 🌐 Attribute starts with input value. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $startsWith: 'ABCD', }, }, }); ``` ### `$endsWith` 属性以输入值结尾。 🌐 Attribute ends with input value. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $endsWith: 'ABCD', }, }, }); ``` ### `$null` 属性是 `null`。 🌐 Attribute is `null`. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $null: true, }, }, }); ``` ### `$notNull` 属性不是 `null`。 🌐 Attribute is not `null`. **示例** ```js const entries = await strapi.entityService.findMany('api::article.article', { filters: { title: { $notNull: true, }, }, }); ``` # 使用实体服务 API 进行排序和分页 Source: https://strapi.nodejs.cn/cms/api/entity-service/order-pagination # 使用实体服务 API 进行排序和分页 {#ordering-and-paginating-with-the-entity-service-api} 🌐 Ordering and Paginating with the Entity Service API 使用 `sort`、`start`/`limit` 或 `page`/`pageSize` 参数对实体服务 API 查询结果进行排序和分页,以控制结果顺序并检索特定的数据子集。 🌐 Order and paginate Entity Service API query results using `sort`, `start`/`limit`, or `page`/`pageSize` parameters to control result ordering and retrieve specific data subsets. :::caution 在 Strapi v5 中,实体服务 API 已被弃用。请考虑改用 [文档服务 API](/cms/api/document-service)。 🌐 The Entity Service API is deprecated in Strapi v5. Please consider using the [Document Service API](/cms/api/document-service) instead. ::: [实体服务 API](/cms/api/entity-service) 提供通过其 [findMany()](/cms/api/entity-service/crud#findmany) 方法找到的结果进行 [排序](#ordering) 和 [分页](#pagination) 的能力。 🌐 The [Entity Service API](/cms/api/entity-service) offers the ability to [order](#ordering) and [paginate](#pagination) results found with its [findMany()](/cms/api/entity-service/crud#findmany) method. ## 排序 {#ordering} 🌐 Ordering 要对实体服务 API 返回的结果进行排序,请使用 `sort` 参数。结果可以基于[单个](#single)或[多个](#multiple)属性进行排序,也可以使用[关系排序](#relational-ordering)。 🌐 To order results returned by the Entity Service API, use the `sort` parameter. Results can be ordered based on a [single](#single) or on [multiple](#multiple) attribute(s) and can also use [relational ordering](#relational-ordering). ### 单身 {#single} 🌐 Single 要按单个字段对结果进行排序,请将其传递给 `sort` 参数,方法如下: 🌐 To order results by a single field, pass it to the `sort` parameter either: - 作为 `string` 用默认的升序排序,或者 - 作为一个 `object` 来定义字段名和顺序(即 `'asc'` 表示升序,`'desc'` 表示降序) ```js strapi.entityService.findMany('api::article.article', { sort: 'id', }); // single with direction strapi.entityService.findMany('api::article.article', { sort: { id: 'desc' }, }); ``` ### 多个 {#multiple} 🌐 Multiple 要按多个字段排序结果,请将字段作为数组传递给 `sort` 参数,方式如下: 🌐 To order results by multiple fields, pass the fields as an array to the `sort` parameter either: - 作为字符串数组,使用默认升序对多个字段进行排序,或者 - 作为对象数组来定义字段名称和顺序(即升序为 `'asc'`,降序为 `'desc'`) ```js strapi.entityService.findMany('api::article.article', { sort: ['publishDate', 'name'], }); // multiple with direction strapi.entityService.findMany('api::article.article', { sort: [{ title: 'asc' }, { publishedAt: 'desc' }], }); ``` ### 关系顺序 {#relational-ordering} 🌐 Relational ordering 字段还可以根据关系中的字段进行排序: 🌐 Fields can also be sorted based on fields from relations: ```js strapi.entityService.findMany('api::article.article', { sort: { author: { name: 'asc', }, }, }); ``` ## 分页 {#pagination} 🌐 Pagination 要对实体服务 API 返回的结果进行分页,你可以使用 `start` 和 `limit` 参数: 🌐 To paginate results returned by the Entity Service API, you can use the `start` and `limit` parameters: ```js strapi.entityService.findMany('api::article.article', { start: 10, limit: 15, }); ``` 你也可以改用 `page` 和 `pageSize` 参数: 🌐 You may instead use the `page` and `pageSize` parameters: ```js strapi.entityService.findMany('api::article.article', { page: 1, pageSize: 15, }); ``` # 使用实体服务 API 填充 Source: https://strapi.nodejs.cn/cms/api/entity-service/populate # 使用实体服务 API 填充 {#populating-with-the-entity-service-api} 🌐 Populating with the Entity Service API 实体服务 API 的 `populate` 参数用于获取关系、组件和动态区域。使用 `populate: '*'` 获取所有根级关系,使用数组获取特定字段,使用对象进行带过滤器和嵌套填充的高级查询,或使用片段处理多态内容结构。 🌐 The Entity Service API's `populate` parameter retrieves relations, components, and dynamic zones. Use `populate: '*'` for all root-level relations, arrays for specific fields, objects for advanced queries with filters and nested populating, or fragments for polymorphic content structures. :::caution 在 Strapi v5 中,实体服务 API 已被弃用。请考虑改用 [文档服务 API](/cms/api/document-service)。 🌐 The Entity Service API is deprecated in Strapi v5. Please consider using the [Document Service API](/cms/api/document-service) instead. ::: [实体服务 API](/cms/api/entity-service) 默认不会填充关系、组件或动态区域,这意味着不使用 `populate` 参数的实体服务 API 查询不会返回关于关系、组件或动态区域的信息。 🌐 The [Entity Service API](/cms/api/entity-service) does not populate relations, components or dynamic zones by default, which means an Entity Service API query that does not use the `populate` parameter will not return information about relations, components, or dynamic zones. ## 基本填充 {#basic-populating} 🌐 Basic populating 要填充所有根级关系,请使用 `populate: '*'`: 🌐 To populate all the root level relations, use `populate: '*'`: ```js const entries = await strapi.entityService.findMany('api::article.article', { populate: '*', }); ``` 通过传递属性名称数组来填充各种组件或关系字段: 🌐 Populate various component or relation fields by passing an array of attribute names: ```js const entries = await strapi.entityService.findMany('api::article.article', { populate: ['componentA', 'relationA'], }); ``` ## 高级填充 {#advanced-populating} 🌐 Advanced populating 可以传递一个对象以进行更高级的填充: 🌐 An object can be passed for more advanced populating: ```js const entries = await strapi.entityService.findMany('api::article.article', { populate: { relationA: true, repeatableComponent: { fields: ['fieldA'], filters: {}, sort: 'fieldA:asc', populate: { relationB: true, }, }, }, }); ``` 可以通过使用[`filters`参数](/cms/api/entity-service/filter)并选择或填充嵌套关系或组件来实现复杂填充: 🌐 Complex populating can be achieved by using the [`filters` parameter](/cms/api/entity-service/filter) and select or populate nested relations or components: ```js const entries = await strapi.entityService.findMany('api::article.article', { populate: { relationA: { filters: { name: { $contains: 'Strapi', }, }, }, repeatableComponent: { fields: ['someAttributeName'], sort: ['someAttributeName'], populate: { componentRelationA: true, }, }, }, }); ``` ## 填充碎片 {#populate-fragments} 🌐 Populate fragments 在处理多态内容结构(动态区域、多态关系等)时,可以使用填充片段来更好地控制填充策略。 🌐 When dealing with polymorphic content structures (dynamic zones, polymorphic relations, etc...), it is possible to use populate fragments to have a better granularity on the populate strategy. ```js const entries = await strapi.entityService.findMany('api::article.article', { populate: { dynamicZone: { on: { 'components.foo': { fields: ['title'], filters: { title: { $contains: 'strapi' } }, }, 'components.bar': { fields: ['name'], }, }, }, morphAuthor: { on: { 'plugin::users-permissions.user': { fields: ['username'], }, 'api::author.author': { fields: ['name'], }, }, }, }, }); ``` # GraphQL API Source: https://strapi.nodejs.cn/cms/api/graphql # GraphQL API GraphQL API 允许对内容类型进行查询和修改,并支持过滤、排序和分页。它使用 `documentId` 作为唯一标识符,并提供单数和复数查询,支持关系、媒体字段、组件、动态区域和本地化。 🌐 The GraphQL API allows querying and mutating content-types with filtering, sorting, and pagination. It uses `documentId` as the unique identifier and provides singular and plural queries with support for relations, media fields, components, dynamic zones, and localization. GraphQL API 允许通过 Strapi 的 [GraphQL 插件](/cms/plugins/graphql) 对 [内容类型](/cms/backend-customization/models#content-types) 执行查询和变更。结果可以被 [过滤](#filters)、[排序](#sorting) 和 [分页](#pagination)。 🌐 The GraphQL API allows performing queries and mutations to interact with the [content-types](/cms/backend-customization/models#content-types) through Strapi's [GraphQL plugin](/cms/plugins/graphql). Results can be [filtered](#filters), [sorted](#sorting) and [paginated](#pagination). :::prerequisites 要使用 GraphQL API,请安装 [GraphQL](/cms/plugins/graphql) 插件: 🌐 To use the GraphQL API, install the [GraphQL](/cms/plugins/graphql) plugin: ```sh yarn add @strapi/plugin-graphql ``` ```sh npm install @strapi/plugin-graphql ``` ::: 安装完成后,GraphQL playground 可通过 `/graphql` URL 访问,并可用于交互式构建查询和变更操作,以及阅读针对你的内容类型定制的文档: 🌐 Once installed, the GraphQL playground is accessible at the `/graphql` URL and can be used to interactively build your queries and mutations and read documentation tailored to your content-types:
GraphQL 插件只公开一个处理所有查询和变更的端点。默认端点是 `/graphql`,并在 [插件配置文件](/cms/plugins/graphql#code-based-configuration) 中定义: 🌐 The GraphQL plugin exposes only one endpoint that handles all queries and mutations. The default endpoint is `/graphql` and is defined in the [plugins configuration file](/cms/plugins/graphql#code-based-configuration): ```js title="/config/plugins.js|ts" export default { shadowCRUD: true, endpoint: '/graphql', // <— single GraphQL endpoint subscriptions: false, maxLimit: -1, apolloServer: {}, v4CompatibilityMode: process.env.STRAPI_GRAPHQL_V4_COMPATIBILITY_MODE ?? false, }; ``` :::note No GraphQL API to upload media files GraphQL API 不支持媒体上传。请使用 [REST API `POST /upload` 端点](/cms/api/rest/upload) 进行所有文件上传,并使用返回的信息在内容类型中链接它。你仍然可以使用 `updateUploadFile` 和 `deleteUploadFile` 变更通过媒体文件 `id` 更新或删除已上传的文件(参见 [媒体文件的变更](#mutations-on-media-files))。 🌐 The GraphQL API does not support media upload. Use the [REST API `POST /upload` endpoint](/cms/api/rest/upload) for all file uploads and use the returned info to link to it in content types. You can still update or delete uploaded files with the `updateUploadFile` and `deleteUploadFile` mutations using media files `id` (see [mutations on media files](#mutations-on-media-files)). ::: :::caution `documentId` only GraphQL API 仅使用 `documentId` 字段公开文档。之前的数字 `id` 在这里不再可用,尽管它仍然通过 REST API 返回以保持向后兼容性(详见 [breaking change](/cms/migration/v4-to-v5/breaking-changes/use-document-id))。 🌐 The GraphQL API exposes documents using only the `documentId` field. The previous numeric `id` is no longer available here, although it is still returned by the REST API for backward compatibility (see [breaking change](/cms/migration/v4-to-v5/breaking-changes/use-document-id) for details). ::: ## 查询 {#queries} 🌐 Queries GraphQL 中的查询用于获取数据而不修改数据。 🌐 Queries in GraphQL are used to fetch data without modifying it. 当将内容类型添加到你的项目时,2 个自动生成的 GraphQL 查询将添加到你的架构中,以内容类型的单数和复数 API ID 命名,如下例所示: 🌐 When a content-type is added to your project, 2 automatically generated GraphQL queries are added to your schema, named after the content-type's singular and plural API IDs, as in the following example: | 内容类型显示名称 | 单数 API ID | 复数 API ID | |----------------|-----------------|---------------| | 餐厅 | `restaurant` | `restaurants` |
单数 API ID 与复数 API ID: 在内容类型生成器中创建内容类型时,会定义单数 API ID 和复数 API ID 值,并且可以在管理面板中编辑内容类型时找到(参见 [用户指南](/cms/features/content-type-builder#creating-content-types))。你可以在创建内容类型时定义自定义 API ID,但之后无法修改这些 ID。 🌐 Singular API ID and Plural API ID values are defined when creating a content-type in the Content-Type Builder, and can be found while editing a content-type in the admin panel (see [User Guide](/cms/features/content-type-builder#creating-content-types)). You can define custom API IDs while creating the content-type, but these can not modified afterwards.
### 获取单个文档 {#fetch-a-single-document} 🌐 Fetch a single document 可以通过它们的 `documentId` 获取文档 。 ```graphql title="Example query: Find a restaurant with its documentId" { restaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl") { name description } } ``` ### 获取多个文档 {#fetch-multiple-documents} 🌐 Fetch multiple documents 要获取多个文档 你可以使用简单的、扁平的查询或 [Relay-style](https://www.apollographql.com/docs/technotes/TN0029-relay-style-connections/) 查询: 扁平查询只返回每个文档中请求的字段。Relay 风格查询以 `_connection` 结尾,并返回一个 `nodes` 数组以及一个 `pageInfo` 对象。当你需要分页元数据时,请使用 Relay 风格查询。 🌐 Flat queries return only the requested fields for each document. Relay-style queries end with `_connection` and return a `nodes` array together with a `pageInfo` object. Use Relay-style queries when you need pagination metadata. 要获取多个文档,你可以使用如下所示的扁平查询: 🌐 To fetch multiple documents you can use flat queries like the following: ```graphql title="Example query: Find all restaurants" restaurants { documentId title } ``` 关系也可以通过文档服务 API 连接、断开和设置,就像 REST API 一样(有关示例,请参阅 XX1)。 🌐 Relay-style queries can be used to fetch multiple documents and return meta information: ```graphql title="Example query: Find all restaurants" { restaurants_connection { nodes { documentId name } pageInfo { pageSize page pageCount total } } } ``` #### 获取关系 {#fetch-relations} 🌐 Fetch relations 你可以在你的扁平查询或你的 [Relay-style](https://www.apollographql.com/docs/technotes/TN0029-relay-style-connections/) 查询中请求包含关联数据: 以下示例获取所有“Restaurant”内容类型的文档,并且对于每个文档,还返回与“Category”内容类型的多对多关系的一些字段: 🌐 The following example fetches all documents from the "Restaurant" content-type, and for each of them, also returns some fields for the many-to-many relation with the "Category" content-type: ```graphql title="Example query: Find all restaurants and their associated categories" { restaurants { documentId name description # categories is a many-to-many relation categories { documentId name } } } ``` 以下示例使用 Relay 风格的查询从 “Restaurant” 内容类型中获取所有文档,并且对于每个餐厅,还返回与 “Category” 内容类型的多对多关系的一些字段: 🌐 The following example fetches all documents from the "Restaurant" content-type using a Relay-style query, and for each restaurant, also returns some fields for the many-to-many relation with the "Category" content-type: ```graphql title="Example query: Find all restaurants and their associated categories" { restaurants_connection { nodes { documentId name description # categories is a many-to-many relation categories_connection { nodes { documentId name } } } pageInfo { page pageCount pageSize total } } } ``` :::info 目前,`pageInfo` 仅适用于一级文档。Strapi 的未来版本可能会为关系实现 `pageInfo`。 🌐 For now, `pageInfo` only works for documents at the first level. Future implementations of Strapi might implement `pageInfo` for relations.
pageInfo 的可能用例: 这个可行: ```graphql { restaurants_connection { nodes { documentId name description # many-to-many relation categories_connection { nodes { documentId name } } } pageInfo { page pageCount pageSize total } } } ``` 这不起作用: ```graphql {13-19} { restaurants_connection { nodes { documentId name description # many-to-many relation categories_connection { nodes { documentId name } # not supported pageInfo { page pageCount pageSize total } } } pageInfo { page pageCount pageSize total } } }} ```
::: ### 获取媒体字段 {#fetch-media-fields} 🌐 Fetch media fields 媒体字段内容的获取方式与其他属性一样。 🌐 Media fields content is fetched just like other attributes. 以下示例获取“Restaurants”内容类型中附加到每个文档的每个 `cover` 媒体字段的 `url` 属性值: 🌐 The following example fetches the `url` attribute value for each `cover` media field attached to each document from the "Restaurants" content-type: ```graphql { restaurants { images { documentId url } } } ``` 对于多个媒体字段,你可以使用扁平查询或 [Relay-style](https://www.apollographql.com/docs/technotes/TN0029-relay-style-connections/) 查询: 以下示例从“餐厅”内容类型中找到的 `images` 多媒体字段获取一些属性: 🌐 The following example fetches some attributes from the `images` multiple media field found in the "Restaurant" content-type: ```graphql { restaurants { images_connection { nodes { documentId url } } } } ``` 以下示例使用 Relay 风格的查询从“Restaurant”内容类型中的 `images` 多媒体字段获取一些属性: 🌐 The following example fetches some attributes from the `images` multiple media field found in the "Restaurant" content-type using a Relay-style query: ```graphql { restaurants { images_connection { nodes { documentId url } } } } ``` :::info 目前,`pageInfo` 仅适用于文档。未来版本的 Strapi 可能也会在媒体字段 `_connection` 上实现 `pageInfo`。 🌐 For now, `pageInfo` only works for documents. Future implementations of Strapi might implement `pageInfo` for the media fields `_connection` too. ::: ### 获取组件 {#fetch-components} 🌐 Fetch components 组件内容的获取方式与其他属性一样。 🌐 Components content is fetched just like other attributes. 以下示例获取每个文档中添加的每个 `closingPeriod` 组件的 `label`、`start_date` 和 `end_date` 属性值,这些文档来自“餐馆”内容类型: 🌐 The following example fetches the `label`, `start_date`, and `end_date` attributes values for each `closingPeriod` component added to each document from the "Restaurants" content-type: ```graphql { restaurants { closingPeriod { label start_date end_date } } } ``` ### 获取动态区域数据 {#fetch-dynamic-zone-data} 🌐 Fetch dynamic zone data 动态区域是在 GraphQL 中的联合类型,因此你需要使用 [fragments](https://www.apollographql.com/docs/react/data/fragments/) (即使用 `...on`)来查询字段,并将组件名称(使用 `ComponentCategoryComponentname` 语法)传递给 [`__typename`](https://www.apollographql.com/docs/apollo-server/schema/schema/#the-__typename-field): 以下示例获取可以添加到“dz”动态区域的“Default”组件类别中“Closingperiod”组件的 `label` 属性的数据: 🌐 The following example fetches data for the `label` attribute of a "Closingperiod" component from the "Default" components category that can be added to the "dz" dynamic zone: ```graphql { restaurants { dz { __typename ...on ComponentDefaultClosingperiod { # define which attributes to return for the component label } } } } ``` ### 获取草稿或已发布的版本 {#status} 🌐 Fetch draft or published versions 如果内容类型启用了 [Draft & Publish](/cms/features/draft-and-publish) 功能,你可以在查询中添加 `status` 参数以获取文档的草稿或已发布版本 : ```graphql title="Example: Fetch draft versions of documents" query Query($status: PublicationStatus) { restaurants(status: DRAFT) { documentId name publishedAt # should return null } } ``` ```graphql title="Example: Fetch published versions of documents" query Query($status: PublicationStatus) { restaurants(status: PUBLISHED) { documentId name publishedAt } } ``` ### 使用 `publicationFilter` 过滤 {#publication-filter} 🌐 Filter with `publicationFilter` 如果启用了 [Draft & Publish](/cms/features/draft-and-publish) 功能,你可以在内置集合和单类型查询中添加 `publicationFilter` 参数。它根据[草稿版本和已发布版本之间的关系](/cms/api/document-service/publication-filter)筛选文档:例如,从未发布的草稿,或者自上次发布以来被修改的条目。GraphQL 通过 `PublicationFilter` 枚举暴露与 REST API 和文档服务 API 相同的值。 🌐 If the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled, you can add a `publicationFilter` argument to built-in collection and single-type queries. It filters documents by the [relationship between their draft and published versions](/cms/api/document-service/publication-filter): for example, drafts that were never published, or entries modified since they were last published. GraphQL exposes the same values as the REST API and the Document Service API through the `PublicationFilter` enum. `publicationFilter` 首先选择文档组;然后 `status` 参数决定每个结果返回其草稿行还是已发布行。 :::caution 当省略 `status` 时,GraphQL 会在应用 `publicationFilter` 之前默认使用 `PUBLISHED`(与 REST 相同)。草稿类值如 `NEVER_PUBLISHED` 不会返回结果,除非你传入 `status: DRAFT`。 🌐 When `status` is omitted, GraphQL defaults to `PUBLISHED` before applying `publicationFilter` (same as REST). Draft-only values such as `NEVER_PUBLISHED` return no results unless you pass `status: DRAFT`. ::: ```graphql title="Example: Fetch never-published draft documents" query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) { restaurants(status: DRAFT, publicationFilter: NEVER_PUBLISHED) { documentId name publishedAt } } ``` ```graphql title="Example: Modified documents with default PUBLISHED status" query Query { restaurants(publicationFilter: MODIFIED) { documentId name publishedAt } } ``` 可用的枚举值: 🌐 Available enum values: | GraphQL 枚举 | 文档服务 / REST 值 | | --- | --- | | `NEVER_PUBLISHED` | `never-published` | | `HAS_PUBLISHED_VERSION` | `has-published-version` | | `MODIFIED` | `modified` | | `UNMODIFIED` | `unmodified` | | `NEVER_PUBLISHED_DOCUMENT` | `never-published-document` | | `HAS_PUBLISHED_VERSION_DOCUMENT` | `has-published-version-document` | | `PUBLISHED_WITHOUT_DRAFT` | `published-without-draft`([仅诊断](/cms/api/document-service/publication-filter#diagnostics)) | | `PUBLISHED_WITH_DRAFT` | `published-with-draft`([仅诊断](/cms/api/document-service/publication-filter#diagnostics)) | 要了解更多信息,请参阅文档服务 API 页面上的[用例和接受的值](/cms/api/document-service/publication-filter#values)。 🌐 To learn more, see the [use cases and accepted values](/cms/api/document-service/publication-filter#values) on the Document Service API page. ## 突变 {#mutations} 🌐 Mutations GraphQL 中的突变用于修改数据(例如创建、更新和删除数据)。 🌐 Mutations in GraphQL are used to modify data (e.g. create, update, and delete data). 当将内容类型添加到你的项目时,将向你的架构添加 3 个自动生成的 GraphQL 修改,用于创建、更新和删除文档 。 例如,对于“餐厅”内容类型,会生成以下变更: 🌐 For instance, for a "Restaurant" content-type, the following mutations are generated: | 用例 | 单一 API ID | |---------------------------------------------|---------------------| | 创建一个新的“餐厅”文档 | `createRestaurant` | | 更新一个现有的“餐厅”餐厅 | `updateRestaurant` | | 删除一个现有的“餐厅”餐厅 | `deleteRestaurant` | ### 创建新文档 {#create-a-new-document} 🌐 Create a new document 在创建新文档时,`data` 参数将具有与你的内容类型特定相关的输入类型。 🌐 When creating new documents, the `data` argument will have an associated input type that is specific to your content-type. 例如,如果你的 Strapi 项目包含“餐厅”内容类型,你将拥有以下内容: 🌐 For instance, if your Strapi project contains the "Restaurant" content-type, you will have the following: | 突变 | 参数 | 输入类型 | |--------------------|------------------|--------------------| | `createRestaurant` | `data` | `RestaurantInput!` | 以下示例为“餐厅”内容类型创建一个新文档,并返回其 `name` 和 `documentId`: 🌐 The following example creates a new document for the "Restaurant" content-type and returns its `name` and `documentId`: ```graphql mutation CreateRestaurant($data: RestaurantInput!) { createRestaurant(data: { name: "Pizzeria Arrivederci" }) { name documentId } } ``` 创建新文档时,会自动生成一个 `documentId`。 🌐 When creating a new document, a `documentId` is automatically generated. 突变的实现也支持关系属性。例如,你可以创建一个新的“类别”,并通过编写如下查询,将许多“餐馆”(使用它们的 `documentId`)附加到它上面: 🌐 The implementation of the mutations also supports relational attributes. For example, you can create a new "Category" and attach many "Restaurants" (using their `documentId`) to it by writing your query like follows: ```graphql mutation CreateCategory { createCategory(data: { Name: "Italian Food" restaurants: ["a1b2c3d4e5d6f7g8h9i0jkl", "bf97tfdumkcc8ptahkng4puo"] }) { documentId Name restaurants { documentId name } } } ``` :::tip 如果你的内容类型启用了国际化 (i18n) 功能,你可以为特定的区域创建文档(参见 [创建新的本地化文档](/cms/api/graphql#locale-create))。 🌐 If the Internationalization (i18n) feature is enabled for your content-type, you can create a document for a specific locale (see [create a new localized document](/cms/api/graphql#locale-create)). ::: ### 更新现有文档 {#update-an-existing-document} 🌐 Update an existing document 在更新现有文档 时,传递包含新内容的 `documentId` 和 `data` 对象。`data` 参数将具有与你的内容类型特定的关联输入类型。 例如,如果你的 Strapi 项目包含“餐厅”内容类型,你将拥有以下内容: 🌐 For instance, if your Strapi project contains the "Restaurant" content-type, you will have the following: | 突变 | 参数 | 输入类型 | |--------------------|------------------|--------------------| | `updateRestaurant` | `data` | `RestaurantInput!` | 例如,以下示例会更新一个现有的“餐厅”内容类型的文档,并给它一个新名称: 🌐 For instance, the following example updates an existing document from the "Restaurants" content-type and give it a new name: ```graphql mutation UpdateRestaurant($documentId: ID!, $data: RestaurantInput!) { updateRestaurant( documentId: "bf97tfdumkcc8ptahkng4puo", data: { name: "Pizzeria Amore" } ) { documentId name } } ``` :::tip 如果为你的内容类型启用了国际化 (i18n) 功能,你可以为特定的区域创建文档(参见 [i18n 文档](/cms/api/graphql#locale-update))。 🌐 If the Internationalization (i18n) feature is enabled for your content-type, you can create a document for a specific locale (see [i18n documentation](/cms/api/graphql#locale-update)). ::: #### 更新关系 {#update-relations} 🌐 Update relations 你可以通过传递一个 `documentId` 或一个 `documentId` 数组(取决于关系类型)来更新关系属性。 🌐 You can update relational attributes by passing a `documentId` or an array of `documentId` (depending on the relation type). 例如,以下示例会更新“Restaurant”内容类型的文档,并通过 `categories` 关联字段向“Category”内容类型的文档添加关联: 🌐 For instance, the following example updates a document from the "Restaurant" content-type and adds a relation to a document from the "Category" content-type through the `categories` relation field: ```graphql mutation UpdateRestaurant($documentId: ID!, $data: RestaurantInput!) { updateRestaurant( documentId: "slwsiopkelrpxpvpc27953je", data: { categories: ["kbbvj00fjiqoaj85vmylwi17"] } ) { documentId name categories { documentId Name } } } ``` ### 删除文档 {#delete-a-document} 🌐 Delete a document 要删除文档 ,传入其 `documentId` : ```graphql mutation DeleteRestaurant { deleteRestaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl") { documentId } } ``` :::tip 如果你的内容类型启用了国际化 (i18n) 功能,你可以删除文档的特定本地化版本(参见 [i18n 文档](/cms/api/graphql#locale-delete))。 🌐 If the Internationalization (i18n) feature is enabled for your content-type, you can delete a specific localized version of a document (see [i18n documentation](/cms/api/graphql#locale-delete)). ::: ### 媒体文件的修改 {#mutations-on-media-files} 🌐 Mutations on media files :::caution 目前,媒体字段上的变更使用 Strapi v4 `id`,而不是 Strapi 5 `documentId`,作为媒体文件的唯一标识符。 🌐 Currently, mutations on media fields use Strapi v4 `id`, not Strapi 5 `documentId`, as unique identifiers for media files. ::: 媒体字段的变更使用文件 `id`。然而,Strapi 5 中的 GraphQL API 查询不再返回 `id`。可以找到媒体文件 `id`: 🌐 Media fields mutations use files `id`. However, GraphQL API queries in Strapi 5 do not return `id` anymore. Media files `id` can be found: - 也可以在管理员面板的[媒体库](/cms/features/media-library)中, - 或者通过发送 REST API `GET` 请求来[填充媒体文件](/cms/api/rest/populate-select#population),因为 REST API 请求目前会返回媒体文件的 `id` 和 `documentId`。 #### 更新已上传的媒体文件 {#update-an-uploaded-media-file} 🌐 Update an uploaded media file 在更新已上传的媒体文件时,传入媒体的 `id`(而不是它的 `documentId`)以及包含新内容的 `info` 对象。`info` 参数将具有与媒体文件特定相关的输入类型。 🌐 When updating an uploaded media file, pass the media's `id` (not its `documentId`) and the `info` object containing new content. The `info` argument will has an associated input type that is specific to media files. 例如,如果你的 Strapi 项目包含“餐厅”内容类型,你将拥有以下内容: 🌐 For instance, if your Strapi project contains the "Restaurant" content-type, you will have the following: | 突变 | 参数 | 输入类型 | |--------------------|------------------|--------------------| | `updateUploadFile` | `info` | `FileInfoInput!` | 例如,下面的示例更新了 `id` 为 3 的媒体文件的 `alternativeText` 属性: 🌐 For instance, the following example updates the `alternativeText` attribute for a media file whose `id` is 3: ```graphql mutation Mutation($updateUploadFileId: ID!, $info: FileInfoInput) { updateUploadFile( id: 3, info: { alternativeText: "New alt text" } ) { documentId url alternativeText } } ``` :::tip 如果上传变更返回禁止访问错误,请确保为上传插件设置了适当的权限(参见[用户指南](/cms/features/users-permissions#editing-a-role))。 🌐 If upload mutations return a forbidden access error, ensure proper permissions are set for the Upload plugin (see [User Guide](/cms/features/users-permissions#editing-a-role)). ::: #### 删除已上传的媒体文件 {#delete-an-uploaded-media-file} 🌐 Delete an uploaded media file 在删除已上传的媒体文件时,传递媒体的 `id`(而不是它的 `documentId`)。 🌐 When deleting an uploaded media file, pass the media's `id` (not its `documentId`). ```graphql title="Example: Delete the media file with id 4" mutation DeleteUploadFile($deleteUploadFileId: ID!) { deleteUploadFile(id: 4) { documentId # return its documentId } } ``` :::tip 如果上传变更返回禁止访问错误,请确保为上传插件设置了适当的权限(参见[用户指南](/cms/features/users-permissions#editing-a-role))。 🌐 If upload mutations return a forbidden access error, ensure proper permissions are set for the Upload plugin (see [User Guide](/cms/features/users-permissions#editing-a-role)). ::: ## 过滤器 {#filters} 🌐 Filters 查询可以接受带有以下语法的 `filters` 参数: 🌐 Queries can accept a `filters` parameter with the following syntax: `filters: { field: { operator: value } }` 多个筛选器可以组合在一起,逻辑运算符(`and`、`or`、`not`)也可以使用,并且接受对象数组。当多个字段条件被组合时,它们会默认使用 `and` 连接。 🌐 Multiple filters can be combined together, and logical operators (`and`, `or`, `not`) can also be used and accept arrays of objects. When multiple field conditions are combined, they are implicitly joined with `and`. :::tip `and`、`or` 和 `not` 运算符可以互相嵌套。 ::: 可以使用以下运算符: 🌐 The following operators are available: | 操作符 | 描述 | | --- | --- | | `eq` | 等于 | | `eqi` | 等于,忽略大小写 | | `ne` | 不等于 | | `nei` | 不等于,忽略大小写 | | `lt` | 小于 | | `lte` | 小于或等于 | | `gt` | 大于 | | `gte` | 大于或等于 | | `in` | 包含于数组中 | | `notIn` | 不包含于数组中 | | `contains` | 包含,区分大小写 | | `notContains` | 不包含,区分大小写 | | `containsi` | 包含,忽略大小写 | | `notContainsi` | 不包含,忽略大小写 | | `null` | 为空 | | `notNull` | 不为空 | | `between` | 介于之间 | | `startsWith` | 以...开头 | | `endsWith` | 以...结尾 | | `and` | 逻辑 `and` | | `or` | 逻辑 `or` | | `not` | 逻辑 `not` | ```graphql title="Simple examples for comparison operators (eq, ne, lt, lte, gt, gte, between)" # eq - returns restaurants with the exact name "Biscotte" { restaurants(filters: { name: { eq: "Biscotte" } }) { name } } # eqi - returns restaurants whose name equals "Biscotte", # comparison is case-insensitive { restaurants(filters: { name: { eqi: "Biscotte" } }) { name } } # ne - returns restaurants whose name is not "Biscotte" { restaurants(filters: { name: { ne: "Biscotte" } }) { name } } # nei - returns restaurants whose name is not "Biscotte", # comparison is case-insensitive { restaurants(filters: { name: { nei: "Biscotte" } }) { name } } # lt - returns restaurants with averagePrice less than 20 { restaurants(filters: { averagePrice: { lt: 20 } }) { name } } # lte - returns restaurants with averagePrice less than or equal to 20 { restaurants(filters: { averagePrice: { lte: 20 } }) { name } } # gt - returns restaurants with averagePrice greater than 20 { restaurants(filters: { averagePrice: { gt: 20 } }) { name } } # gte - returns restaurants with averagePrice greater than or equal to 20 { restaurants(filters: { averagePrice: { gte: 20 } }) { name } } # between - returns restaurants with averagePrice between 10 and 30 { restaurants(filters: { averagePrice: { between: [10, 30] } }) { name } } ``` ```graphql title="Simple examples for membership operators (in, notIn)" # in - returns restaurants with category either "pizza" or "burger" { restaurants(filters: { category: { in: ["pizza", "burger"] } }) { name } } # notIn - returns restaurants whose category is neither "pizza" nor "burger" { restaurants(filters: { category: { notIn: ["pizza", "burger"] } }) { name } } ``` ```graphql title="Simple examples for string matching operators (contains, notContains, containsi, notContains, startsWith, endsWith)" # contains - returns restaurants whose name contains "Pizzeria" { restaurants(filters: { name: { contains: "Pizzeria" } }) { name } } # notContains - returns restaurants whose name does NOT contain "Pizzeria" { restaurants(filters: { name: { notContains: "Pizzeria" } }) { name } } # containsi - returns restaurants whose name contains "pizzeria" (case‑insensitive) { restaurants(filters: { name: { containsi: "pizzeria" } }) { name } } # notContainsi - returns restaurants whose name does NOT contain "pizzeria" (case‑insensitive) { restaurants(filters: { name: { notContainsi: "pizzeria" } }) { name } } # startsWith - returns restaurants whose name starts with "Pizza" { restaurants(filters: { name: { startsWith: "Pizza" } }) { name } } # endsWith - returns restaurants whose name ends with "Inc" { restaurants(filters: { name: { endsWith: "Inc" } }) { name } } ``` ```graphql title="Simple examples for null checks operators (null, notNull)" # null - returns restaurants where description is null { restaurants(filters: { description: { null: true } }) { name } } # notNull - returns restaurants where description is not null { restaurants(filters: { description: { notNull: true } }) { name } } ``` ```graphql title="Simple examples for logical operators (and, or, not)" # and - both category must be "pizza" AND averagePrice must be < 20 { restaurants(filters: { and: [ { category: { eq: "pizza" } }, { averagePrice: { lt: 20 } } ] }) { name } } # or - category is "pizza" OR category is "burger" { restaurants(filters: { or: [ { category: { eq: "pizza" } }, { category: { eq: "burger" } } ] }) { name } } # not - category must NOT be "pizza" { restaurants(filters: { not: { category: { eq: "pizza" } } }) { name } } ``` ```graphql title="Example with nested logical operators: use and, or, and not to find pizzerias under 20 euros" { restaurants( filters: { and: [ { not: { averagePrice: { gte: 20 } } } { or: [ { name: { eq: "Pizzeria" } } { name: { startsWith: "Pizzeria" } } ] } ] } ) { documentId name averagePrice } } ``` :::strapi Deep filtering with the various APIs 有关如何使用各种 API 进行深度过滤的示例,请参阅 [this blog article](https://strapi.io/blog/deep-filtering-alpha-26)。 ::: ## 排序 {#sorting} 🌐 Sorting 查询可以接受带有以下语法的 `sort` 参数: 🌐 Queries can accept a `sort` parameter with the following syntax: - 根据单个值排序:`sort: "value"` - 根据多个值排序:`sort: ["value1", "value2"]` 排序顺序可以用 `:asc`(升序,默认,可省略)或 `:desc`(降序)来定义。 🌐 The sorting order can be defined with `:asc` (ascending order, default, can be omitted) or `:desc` (for descending order). ```graphql title="Example: Fetch and sort on name by ascending order" { restaurants(sort: "name") { documentId name } } ``` ```graphql title="Example: Fetch and sort on average price by descending order" { restaurants(sort: "averagePrice:desc") { documentId name averagePrice } } ``` ```graphql title="Example: Fetch and sort on title by ascending order, then on average price by descending order" { restaurants(sort: ["name:asc", "averagePrice:desc"]) { documentId name averagePrice } } ``` ## 分页 {#pagination} 🌐 Pagination [Relay-style](https://www.apollographql.com/docs/technotes/TN0029-relay-style-connections/) 查询可以接受一个`pagination`参数。结果可以通过页码或偏移量进行分页。 :::note 分页方法不能混用。始终要么使用 `page` 与 `pageSize`,要么使用 `start` 与 `limit`。 🌐 Pagination methods can not be mixed. Always use either `page` with `pageSize` or `start` with `limit`. ::: ### 按页分页 {#pagination-by-page} 🌐 Pagination by page | 参数 | 描述 | 默认值 | | --- | --- | --- | | `pagination.page` | 页码 | 1 | | `pagination.pageSize` | 每页条数 | 10 | ```graphql title="Example query: Pagination by page" { restaurants_connection(pagination: { page: 1, pageSize: 10 }) { nodes { documentId name } pageInfo { page pageSize pageCount total } } } ``` ### 按偏移量分页 {#pagination-by-offset} 🌐 Pagination by offset | 参数 | 描述 | 默认值 | 最大值 | | --- | --- | --- | --- | | `pagination.start` | 起始值 | 0 | - | | `pagination.limit` | 返回的实体数量 | 10 | -1 | ```graphql title="Example query: Pagination by offset" { restaurants_connection(pagination: { start: 10, limit: 19 }) { nodes { documentId name } pageInfo { page pageSize pageCount total } } } ``` :::tip `pagination.limit` 的默认值和最大值可以在 `./config/plugins.js` 文件中通过 `graphql.config.defaultLimit` 和 `graphql.config.maxLimit` 键进行配置。 🌐 The default and maximum values for `pagination.limit` can be [configured in the `./config/plugins.js`](/cms/plugins/graphql#code-based-configuration) file with the `graphql.config.defaultLimit` and `graphql.config.maxLimit` keys. ::: :::note Many-to-many relation ordering with pagination 在使用分页查询多对多关系时,管理员面板中设置的自定义排序会被保留。如果在查询中对关系进行排序(例如,`categories(sort: "name")`),分页将遵循你指定的排序顺序,而不是内容管理器中配置的自定义排序。 🌐 When querying many-to-many relations with pagination, the custom order set in the admin panel is preserved. If you sort relations in a query (e.g., `categories(sort: "name")`), the pagination respects your specified sort order rather than the custom order configured in the content manager. ::: ## `locale` {#locale} [国际化 (i18n)](/cms/features/internationalization) 功能为 GraphQL API 添加了新功能: 🌐 The [Internationalization (i18n)](/cms/features/internationalization) feature adds new features to the GraphQL API: - 在 GraphQL 模式中添加了 `locale` 字段。 - GraphQL 可以用于: - 使用 `locale` 参数查询特定区域的文档 - 用于针对特定语言环境的文档进行[创建](#locale-create)、[更新](#locale-update)和[删除](#locale-delete)的变更 ### 获取特定区域的所有文档 {#locale-fetch-all} 🌐 Fetch all documents in a specific locale 要获取特定区域的所有文档 ,请将 `locale` 参数传递给查询: ```graphql query { restaurants(locale: "fr") { documentId name locale } } ``` ```json { "data": { "restaurants": [ { "documentId": "a1b2c3d4e5d6f7g8h9i0jkl", "name": "Restaurant Biscotte", "locale": "fr" }, { "documentId": "m9n8o7p6q5r4s3t2u1v0wxyz", "name": "Pizzeria Arrivederci", "locale": "fr" }, ] } } ``` ### 获取特定语言环境的文档 {#locale-fetch} 🌐 Fetch a document in a specific locale 要获取特定区域的文档 ,请将 `documentId` 和 `locale` 参数传递给查询: **示例查询:** ```graphql query Restaurant($documentId: ID!, $locale: I18NLocaleCode) { restaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl", locale: "fr") { documentId name description locale } } ``` **示例响应:** ```json { "data": { "restaurant": { "documentId": "lviw819d5htwvga8s3kovdij", "name": "Restaurant Biscotte", "description": "Bienvenue au restaurant Biscotte!", "locale": "fr" } } } ``` ### 创建一个新的本地化文档 {#locale-create} 🌐 Create a new localized document `locale` 字段可以传递以创建针对特定语言环境的本地化文档 (有关使用 GraphQL 进行变更的更多信息,请参阅 [GraphQL API 文档](/cms/api/graphql#create-a-new-document))。 ```graphql title="Example: Create a new restaurant for the French locale" mutation CreateRestaurant($data: RestaurantInput!, $locale: I18NLocaleCode) { createRestaurant( data: { name: "Brasserie Bonjour", description: "Description in French goes here" }, locale: "fr" ) { documentId name description locale } ``` ### 为特定地区更新文档 {#locale-update} 🌐 Update a document for a specific locale 可以在变更中传入 `locale` 参数以更新给定语言环境的文档 (有关使用 GraphQL 的变更的更多信息,请参阅 [GraphQL API 文档](/cms/api/graphql#update-an-existing-document))。 ```graphql title="Example: Update the description field of restaurant for the French locale" mutation UpdateRestaurant($documentId: ID!, $data: RestaurantInput!, $locale: I18NLocaleCode) { updateRestaurant( documentId: "a1b2c3d4e5d6f7g8h9i0jkl" data: { description: "New description in French" }, locale: "fr" ) { documentId name description locale } ``` ### 删除文档的语言区域 {#locale-delete} 🌐 Delete a locale for a document 在变更中传递 `locale` 参数以删除文档的特定本地化 : ```graphql mutation DeleteRestaurant($documentId: ID!, $locale: I18NLocaleCode) { deleteRestaurant(documentId: "xzmzdo4k0z73t9i68a7yx2kk", locale: "fr") { documentId } } ``` ## 高级用例 {#advanced-use-cases} 🌐 Advanced use cases 点击以下卡片,查看利用 GraphQL API 和 Strapi 功能的更高级用例的简短指南: 🌐 Click on the following cards for short guides on more advanced use cases leveraging the GraphQL API and Strapi features: - [高级查询](/cms/api/graphql/advanced-queries): 查看 GraphQL API 的多级查询和自定义解析器链示例。 - [高级政策](/cms/api/graphql/advanced-policies): 查看高级策略示例,例如 GraphQL API 的条件可见性和组成员资格。 :::info Aggregations not yet available GraphQL 聚合(count、avg、sum、min、max、groupBy)尚未在 `@strapi/plugin-graphql` 中实现。当该功能可用时,本节将会更新。 🌐 GraphQL aggregations (count, avg, sum, min, max, groupBy) are not yet implemented in `@strapi/plugin-graphql`. This section will be updated when the feature becomes available. 与此同时,你可以通过 REST API 获取文档总数(例如,`GET /api/restaurants?pagination[pageSize]=1` 返回 `meta.pagination.total`),或者编写一个使用 [文档服务 API](/cms/api/document-service) 来计算聚合的 [自定义 GraphQL 解析器](/cms/api/graphql/advanced-queries#resolver-chains)。 🌐 In the meantime, you can get a total document count through the REST API (e.g., `GET /api/restaurants?pagination[pageSize]=1` returns `meta.pagination.total`), or write a [custom GraphQL resolver](/cms/api/graphql/advanced-queries#resolver-chains) that uses the [Document Service API](/cms/api/document-service) to compute aggregations. ::: # GraphQL 的高级策略 Source: https://strapi.nodejs.cn/cms/api/graphql/advanced-policies # GraphQL API 的高级策略 {#advanced-policies-for-the-graphql-api} 🌐 Advanced policies for the GraphQL API 策略可以附加到 GraphQL 解析器上以实现复杂的授权规则,例如限制未认证用户的结果或根据组成员身份限制访问。 🌐 Policies can be attached to GraphQL resolvers to implement complex authorization rules, such as limiting results for unauthenticated users or restricting access based on group membership. 发送到 [GraphQL API](/cms/api/graphql) 的请求会通过 Strapi 的 [middlewares](/cms/backend-customization/middlewares.md) 和 [policies](/cms/backend-customization/policies.md) 系统。策略可以附加到解析器上,以实现复杂的授权规则,如本短指南所示。 🌐 Requests sent to the [GraphQL API](/cms/api/graphql) pass through Strapi's [middlewares](/cms/backend-customization/middlewares.md) and [policies](/cms/backend-customization/policies.md) system. Policies can be attached to resolvers to implement complex authorization rules, as shown in the present short guide. 有关 GraphQL 策略的更多信息,请参阅 [GraphQL 插件配置](/cms/plugins/graphql#extending-the-schema) 文档。 🌐 For additional information on GraphQL policies, please refer to the [GraphQL plugin configuration](/cms/plugins/graphql#extending-the-schema) documentation. ## 条件可见性 {#conditional-visibility} 🌐 Conditional visibility 要限制未经身份验证的用户返回的条目数量,你可以编写一个修改解析器参数的策略: 🌐 To limit the number of returned entries for unauthenticated users you can write a policy that modifies resolver arguments: ```ts title="/src/policies/limit-public-results.ts" const { state, args } = policyContext; if (!state.user) { args.limit = 4; // only return 4 results for public } return true; }; ``` 在 `/config/policies.ts` 中注册策略并将其应用到解析器: 🌐 Register the policy in `/config/policies.ts` and apply it to a resolver: ```ts title="/config/policies.ts" 'api::restaurant.restaurant': { find: [ 'global::limit-public-results' ], }, }; ``` ## 群体成员资格 {#group-membership} 🌐 Group membership 策略可以访问 `policyContext.state.user` 来检查组成员身份,如以下示例所示: 🌐 Policies can access `policyContext.state.user` to check group membership, as in the following example: ```ts title="/src/policies/is-group-member.ts" const userGroups = await strapi.query('plugin::users-permissions.group').findMany({ where: { users: { id: state.user.id } }, }); return userGroups.some(g => g.name === config.group); }; ``` 使用以下配置的策略: 🌐 Use the policy with the following configuration: ```ts title="/config/policies.ts" 'api::restaurant.restaurant': { find: [{ name: 'global::is-group-member', config: { group: 'editors' } }], }, }; ``` 在这个设置下,解析器只有在已认证用户属于 `editors` 组时才返回结果。 🌐 With this setup the resolver only returns results if the authenticated user belongs to the `editors` group. # GraphQL 的高级查询 Source: https://strapi.nodejs.cn/cms/api/graphql/advanced-queries # GraphQL API 的高级查询 {#advanced-queries-for-the-graphql-api} 🌐 Advanced queries for the GraphQL API Strapi 的 GraphQL API 中的高级查询使用嵌套选择集来获取多级关联,并使用自定义解析器链在解析器之间重用逻辑并应用特定于上下文的行为。 🌐 Advanced queries in Strapi's GraphQL API use nested selection sets to fetch multi-level relations and custom resolver chains to reuse logic across resolvers and apply context-specific behavior. Strapi 的 [GraphQL API](/cms/api/graphql) 可以自动解析许多查询,但复杂的数据访问可能需要更深入的关系获取或链接解析器。本简短指南解释了如何处理这些高级场景。 🌐 Strapi's [GraphQL API](/cms/api/graphql) resolves many queries automatically, but complex data access can require deeper relation fetching or chaining resolvers. The present short guide explains how to handle such advanced scenarios. 如需更多信息,请参阅 [GraphQL 自定义](/cms/plugins/graphql#extending-the-schema) 文档。 🌐 For additional information, please refer to the [GraphQL customization](/cms/plugins/graphql#extending-the-schema) documentation. ## 多层查询 {#multi-level-queries} 🌐 Multi-level queries 使用嵌套选择集来获取多层级关系,如下例所示: 🌐 Use nested selection sets to fetch relations several levels deep, as in the following example: ```graphql { restaurants { documentId name categories { documentId name parent { documentId name } } } } ``` GraphQL 插件会自动解析嵌套关系。如果你需要在特定级别应用自定义逻辑,请为该字段创建自定义解析器。 🌐 The GraphQL plugin automatically resolves nested relations. If you need to apply custom logic at a specific level, create a custom resolver for that field. ## 解析器链 {#resolver-chains} 🌐 Resolver chains 自定义解析器可以调用其他解析器以重用现有逻辑。一种常见的模式是在父解析器中解析权限或上下文数据,并将其传递给子解析器,如以下示例所示: 🌐 Custom resolvers can call other resolvers to reuse existing logic. A common pattern is to resolve permissions or context data in a parent resolver and pass it down to child resolvers, as in the following example: ```js title="/src/api/restaurant/resolvers/restaurant.ts" Query: { restaurants: async (parent, args, ctx) => { const documents = await strapi.documents('api::restaurant.restaurant').findMany(args); return documents.map(doc => ctx.request.graphql.resolve('Restaurant', doc)); }, }, }; ``` 在这个示例中,父解析器使用 [Document Service API](/cms/api/document-service) 获取餐馆,然后委托给插件提供的生成的 `Restaurant` 解析器,因此默认行为(例如字段选择)仍然适用。 🌐 In this example the parent resolver fetches restaurants using the [Document Service API](/cms/api/document-service), then delegates to the generated `Restaurant` resolver provided by the plugin so default behavior such as field selection still applies. :::info Aggregations not yet available `@strapi/plugin-graphql` 中尚未实现 GraphQL 聚合。详情和解决方法请参见 [GraphQL 高级用例](/cms/api/graphql#advanced-use-cases)。 🌐 GraphQL aggregations are not yet implemented in `@strapi/plugin-graphql`. See the [GraphQL advanced use cases](/cms/api/graphql#advanced-use-cases) for details and workarounds. ::: # 在 GraphQL API 中使用 locale 参数 Source: https://strapi.nodejs.cn/cms/api/graphql/locale # 在 GraphQL API {#graphql} 中使用 `locale` 🌐 Use `locale` with the GraphQL API 使用 GraphQL API 的 `locale` 参数来查询、创建、更新和删除特定语言环境的文档。 🌐 Use the `locale` argument with the GraphQL API to query, create, update, and delete documents for a specific locale. i18n 功能为 [GraphQL API](/cms/api/graphql) 添加了新功能: 🌐 The i18n feature adds new features to the [GraphQL API](/cms/api/graphql): - 在 GraphQL 模式中添加了 `locale` 字段。 - GraphQL 可以用于: - 使用 `locale` 参数查询特定区域的文档 - 用于针对特定语言环境的文档进行[创建](#graphql-create)、[更新](#graphql-update)和[删除](#graphql-delete)的变更 ### 获取特定区域的所有文档 {#graphql-fetch-all} 🌐 Fetch all documents in a specific locale 要获取特定区域的所有文档 ,请将 `locale` 参数传递给查询: ```graphql query { restaurants(locale: "fr") { documentId name locale } } ``` ```json { "data": { "restaurants": [ { "documentId": "a1b2c3d4e5d6f7g8h9i0jkl", "name": "Restaurant Biscotte", "locale": "fr" }, { "documentId": "m9n8o7p6q5r4s3t2u1v0wxyz", "name": "Pizzeria Arrivederci", "locale": "fr" }, ] } } ``` ### 在特定区域获取文档 {#graphql-fetch} 🌐 Fetch a document in a specific locale 要获取特定区域的文档 ,请将 `documentId` 和 `locale` 参数传递给查询: **示例查询:** ```graphql query Restaurant($documentId: ID!, $locale: I18NLocaleCode) { restaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl", locale: "fr") { documentId name description locale } } ``` **示例响应:** ```json { "data": { "restaurant": { "documentId": "lviw819d5htwvga8s3kovdij", "name": "Restaurant Biscotte", "description": "Bienvenue au restaurant Biscotte!", "locale": "fr" } } } ``` ### 创建一个新的本地化文档 {#graphql-create} 🌐 Create a new localized document `locale` 字段可以传递以创建针对特定语言环境的本地化文档 (有关使用 GraphQL 进行变更的更多信息,请参阅 [GraphQL API 文档](/cms/api/graphql#create-a-new-document))。 ```graphql title="Example: Create a new restaurant for the French locale" mutation CreateRestaurant($data: RestaurantInput!, $locale: I18NLocaleCode) { createRestaurant( data: { name: "Brasserie Bonjour", description: "Description in French goes here" }, locale: "fr" ) { documentId name description locale } ``` ### 为特定地区更新文档 {#graphql-update} 🌐 Update a document for a specific locale 可以在变更中传入 `locale` 参数以更新给定语言环境的文档 (有关使用 GraphQL 的变更的更多信息,请参阅 [GraphQL API 文档](/cms/api/graphql#update-an-existing-document))。 ```graphql title="Example: Update the description field of restaurant for the French locale" mutation UpdateRestaurant($documentId: ID!, $data: RestaurantInput!, $locale: I18NLocaleCode) { updateRestaurant( documentId: "a1b2c3d4e5d6f7g8h9i0jkl" data: { description: "New description in French" }, locale: "fr" ) { documentId name description locale } ``` ### 删除文档的某个语言环境 {#graphql-delete} 🌐 Delete a locale for a document 在变更中传递 `locale` 参数以删除文档的特定本地化 : ```graphql mutation DeleteRestaurant($documentId: ID!, $locale: I18NLocaleCode) { deleteRestaurant(documentId: "xzmzdo4k0z73t9i68a7yx2kk", locale: "fr") { documentId } } ``` # OpenAPI 规范 Source: https://strapi.nodejs.cn/cms/api/openapi # OpenAPI 规范生成 {#openapi-specification-generation} 🌐 OpenAPI specification generation Strapi 提供了一个 CLI 工具,用于自动生成 OpenAPI 3.1.0 规范,记录所有 API 端点、参数和响应。生成的规范可以与 Swagger UI 集成,用于交互式 API 文档。 🌐 Strapi provides a CLI tool to automatically generate OpenAPI 3.1.0 specifications documenting all API endpoints, parameters, and responses. The generated specification can be integrated with Swagger UI for interactive API documentation. Strapi 提供了一个命令行工具来为你的应用生成 [OpenAPI](https://www.openapis.org/) 规范。 CLI 工具会自动创建全面的 API 文档,描述你 Strapi 应用的内容 API 中所有可用的端点、参数和响应格式。在可能的使用场景中,生成的规范可以集成到像 [Swagger UI ](https://swagger.io/tools/swagger-ui/) 这样的文档工具中。 :::callout 🚧 Experimental feature OpenAPI 生成特性目前处于实验阶段。其行为和输出在未来版本中可能会发生变化,且不遵循语义化版本控制。如需更多信息和背景,请参阅 [Strapi Contributor Docs ](https://contributor.strapi.io/openapi)。 ::: ## 生成 OpenAPI 规范 {#generating-an-openapi-specification} 🌐 Generating an OpenAPI specification OpenAPI 生成工具包含在 Strapi 核心中,无需额外安装。你可以在任何 Strapi 项目中直接从命令行使用它来生成全面的 API 文档。 🌐 The OpenAPI generation tool is included with Strapi core and doesn't require additional installation. You can use it directly from the command line in any Strapi project to generate comprehensive API documentation. :::note Known limitation for nested component `required` metadata 管理面板可以将组件中的内部字段标记为必填,但生成的 OpenAPI 文件仍可能跳过这些标量的 `required` 条目。父对象(例如动态区域内的组件)可能列出 `required`,而嵌套属性则没有。在 [GitHub issue #2236](https://github.com/strapi/documentation/issues/2236) 中有更多背景信息。因此,仅信任原始模式的客户端生成器可能会生成看起来比 Strapi 实际执行更宽松的类型。 🌐 The Admin panel can mark inner fields on a component as required, yet the generated OpenAPI file may still skip a `required` entry for those scalars. The parent object (for instance a component inside a dynamic zone) might list `required` while the nested properties do not. There is more background in [GitHub issue #2236](https://github.com/strapi/documentation/issues/2236). Client generators that trust the raw schema alone can therefore emit types that look looser than what Strapi actually enforces. 此区域仍处于实验阶段,与页面顶部的警告横幅相同,因此请继续在应用代码中或使用控制器指南中的 [REST 验证助手](/cms/backend-customization/controllers#sanitization-and-validation-in-controllers) 验证嵌套负载,而不要假设每条管理员规则都已反映在导出的 JSON 模式中。 🌐 This area is still experimental, same as the warning banner at the top of the page, so keep validating nested payloads in application code or with the [REST validation helpers](/cms/backend-customization/controllers#sanitization-and-validation-in-controllers) from the controllers guide instead of assuming every Admin rule is mirrored in the exported JSON schema yet. ::: ### 命令行接口使用 {#cli-usage} 🌐 CLI usage 在不带任何参数的情况下执行该命令将在你的 Strapi 项目文件夹根目录下生成一个 `specification.json` 文件: 🌐 Executing the command without any arguments will generate a `specification.json` file at the root of your Strapi folder project: ```shell yarn strapi openapi generate ``` ```shell npm run strapi openapi generate ``` 你还可以传递一个可选的 `--output` 参数来指定路径和文件名,如下面的示例所示: 🌐 You can also pass an optional `--output` argument to specify the path and filename, as in the following example: ```bash yarn strapi openapi generate --output ./docs/api-spec.json ``` ```bash npm run strapi openapi generate -- --output ./docs/api-spec.json ``` ### 规范结构和内容 {#specification-structure-and-content} 🌐 Specification structure and content 生成的 OpenAPI 规范遵循 [OpenAPI 3.1.0 standard](https://spec.openapis.org/oas/v3.1.0.html) ,并可能在以下简化示例中显示如下: ```json { "openapi": "3.1.0", "x-powered-by": "strapi", "x-strapi-version": "5.21.0", "info": { "title": "My Strapi API", "description": "API documentation for My Strapi API", "version": "1.0.0" }, "paths": { "/api/articles": { "get": { "operationId": "article/get/articles", "parameters": [ { "name": "fields", "in": "query", "schema": { "type": "array", "items": { "type": "string" } } } ], "responses": { "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Article" } } } } } } } } } } }, "components": { "schemas": { "Article": { "type": "object", "properties": { "id": { "type": "string" }, "title": { "type": "string" }, "content": { "type": "string" } } } } } } ```
生成的 OpenAPI 规范包含 Strapi 应用中所有可用的 API 端点,以及有关这些端点的信息,例如: 🌐 The generated OpenAPI specification includes all available API endpoints in your Strapi application, and information about these endpoints, such as the following: - 适用于所有内容类型的 CRUD 操作 - 应用中定义的自定义 API 路由 - 用于用户管理的身份验证端点 - 用于媒体处理的文件上传端点 - 已安装插件的插件端点 ## 配置 {#configuring} 🌐 Configuring 默认情况下,Strapi 不会为生成的 OpenAPI 规范公开 HTTP 端点。要选择启用,请在 `/config/server` 文件中添加一个 `openapi` 键。 🌐 By default, Strapi does not expose HTTP endpoints for the generated OpenAPI specification. To opt in, add an `openapi` key to the `/config/server` file. ### HTTP 端点访问 {#http-endpoint-access} 🌐 HTTP endpoint access `openapi` 配置接受 2 个子键,`content-api` 和 `admin`,每个都有一个 `access` 属性: 🌐 The `openapi` configuration accepts 2 sub-keys, `content-api` and `admin`, each with an `access` property: | 子键 | 端点 | `access` 值 | 默认值 | 行为 | |---------|----------|----------------|---------|----------| | `content-api` | `GET /api/openapi.json` | `disabled` | 是 | 端点未注册。 | | `content-api` | `GET /api/openapi.json` | `public` | 否 | 端点可在未认证的情况下访问。 | | `admin` | `GET /admin/openapi.json` | `disabled` | 是 | 端点未注册。 | | `admin` | `GET /admin/openapi.json` | `authenticated` | 否 | 端点需要认证的管理员用户。 | 以下示例展示了两个端点: 🌐 The following example exposes both endpoints: ```js title="/config/server.js" module.exports = { openapi: { 'content-api': { access: 'public', }, admin: { access: 'authenticated', }, }, }; ``` ```ts title="/config/server.ts" openapi: { 'content-api': { access: 'public', }, admin: { access: 'authenticated', }, }, }; ``` :::caution 将 `content-api.access` 设置为 `authenticated` 或将 `admin.access` 设置为 `public` 会在启动时抛出错误。 🌐 Setting `content-api.access` to `authenticated` or `admin.access` to `public` throws an error at startup. ::: :::note OpenAPI 端点的基于角色的访问控制尚不支持。管理端点使用 `admin::isAuthenticatedAdmin` 策略,并且不按管理员角色或权限进行过滤:任何已认证的管理员用户都可以读取规范。 🌐 Role-based access control for OpenAPI endpoints is not supported yet. The admin endpoint uses the `admin::isAuthenticatedAdmin` policy and does not filter by admin role or permission: any authenticated admin user can read the specification. ::: :::tip 公共内容 API 规范向任何可以访问该端点的人描述了你的整个内容 API 界面,包括不可公开读取的内容类型。如果你不想在未认证的情况下公开规范,请将 `content-api.access` 保留为 `'disabled'`,并使用 CLI 生成静态文件。 🌐 A public Content API specification describes your entire Content API surface, including content types that are not publicly readable, to anyone who can reach the endpoint. If you do not want to expose the specification without authentication, leave `content-api.access` at `'disabled'` and use the CLI to generate a static file instead. ::: ### 端点选项 {#endpoint-options} 🌐 Endpoint options 除了 `access`,每个端点(`content-api` 和 `admin`)都接受以下选项: 🌐 Besides `access`, each endpoint (`content-api` and `admin`) accepts the following options: | 选项 | 类型 | 默认值 | 描述 | |------|------|---------|------| | `route.path` | 字符串 | `'/openapi.json'` | 规范提供的子路径。在 `content-api` 下解析为 `/api`,在 `admin` 下解析为 `/admin`。 | | `cache.enabled` | 布尔值 | `true` | 启用基于文件的生成规范缓存。 | | `cache.maxAgeMs` | 数字 | `60000` | 缓存文件的最大有效期(毫秒),超过该时间规范将被重新生成。 | | `cache.filePath` | 字符串 | `.strapi/openapi/.json` | 缓存文件路径。相对路径从应用根目录解析。 | 使用默认的 `route.path`,完整的 URL 对于内容 API 端点是 `http://localhost:1337/api/openapi.json`,对于管理端点是 `http://localhost:1337/admin/openapi.json`。 🌐 With the default `route.path`, the full URLs are `http://localhost:1337/api/openapi.json` for the Content API endpoint and `http://localhost:1337/admin/openapi.json` for the Admin endpoint. :::caution 两个端点必须解析到不同的完整路径。如果 `content-api` 和 `admin` 端点解析到相同的 URL,Strapi 在启动时会抛出错误。 🌐 Both endpoints must resolve to different full paths. If the `content-api` and `admin` endpoints resolve to the same URL, Strapi throws an error at startup. ::: ## 与 Swagger UI 集成 {#integrating-with-swagger-ui} 🌐 Integrating with Swagger UI :::tip 如果你[公开了一个 HTTP 端点](#http-endpoint-access),你可以直接将 Swagger UI 指向实时 URL(例如,`http://localhost:1337/api/openapi.json`),而不是生成静态文件。跳过下面的第 1 步,在第 3 步中将端点 URL 用作 `url` 的值。 🌐 If you [exposed an HTTP endpoint](#http-endpoint-access), you can point Swagger UI directly at the live URL (e.g., `http://localhost:1337/api/openapi.json`) instead of generating a static file. Skip step 1 below and use the endpoint URL as the `url` value in step 3. ::: 通过以下步骤,你可以快速生成一个与 [Swagger UI](https://swagger.io/) 兼容的页面: 🌐 With the following steps you can quickly generate a [Swagger UI](https://swagger.io/)-compatible page: 1. 生成规范: ```bash yarn strapi openapi generate --output ./public/swagger-spec.json ``` ```bash npm run strapi openapi generate -- --output ./public/swagger-spec.json ``` 2. 使用以下代码更新 [ `/config/middlewares.js` 配置文件](/cms/configurations/middlewares): ```js title="/config/middlewares.js" module.exports = [ 'strapi::logger', 'strapi::errors', { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'script-src': ["'self'", "'unsafe-inline'", 'https://unpkg.com'], 'style-src': ["'self'", "'unsafe-inline'", 'https://unpkg.com'], 'connect-src': ["'self'", 'https:'], 'img-src': ["'self'", 'data:', 'blob:', 'https:'], 'media-src': ["'self'", 'data:', 'blob:'], upgradeInsecureRequests: null, }, }, }, }, 'strapi::cors', 'strapi::poweredBy', 'strapi::query', 'strapi::body', 'strapi::session', 'strapi::favicon', 'strapi::public', ]; ``` ```js title="/config/middlewares.ts" export default [ 'strapi::logger', 'strapi::errors', { name: 'strapi::security', config: { contentSecurityPolicy: { useDefaults: true, directives: { 'script-src': ["'self'", "'unsafe-inline'", 'https://unpkg.com'], 'style-src': ["'self'", "'unsafe-inline'", 'https://unpkg.com'], 'connect-src': ["'self'", 'https:'], 'img-src': ["'self'", 'data:', 'blob:', 'https:'], 'media-src': ["'self'", 'data:', 'blob:'], upgradeInsecureRequests: null, }, }, }, }, 'strapi::cors', 'strapi::poweredBy', 'strapi::query', 'strapi::body', 'strapi::session', 'strapi::favicon', 'strapi::public', ]; ``` 这将确保来自 [unpkg.com](https://unpkg.com/) 的 Swagger UI 显示不会被由[安全中间件](/cms/configurations/middlewares#security)处理的 Strapi CSP 策略阻止。 3. 在你的 Strapi 项目中创建一个 `public/openapi.html` 文件来显示 Swagger UI,代码如下: ```html API Documentation
``` 4. 使用 `yarn develop` 或 `npm run develop` 重新启动 Strapi 服务器,然后访问 `/openapi.html` 页面。应该会显示 Swagger UI: ![使用 Strapi OpenAPI 规范的 Swagger UI 示例](/img/assets/apis/swagger-open-api.png) # 查询引擎 API Source: https://strapi.nodejs.cn/cms/api/query-engine # 查询引擎 API {#query-engine-api} 🌐 Query Engine API 查询引擎 API 通过 `strapi.db.query` 提供对 Strapi 数据库层的低级、不受限制的后端访问,支持带有筛选、填充、排序和分页的单条和批量操作。 🌐 The Query Engine API provides low-level, unrestricted backend access to Strapi's database layer through `strapi.db.query`, supporting single and bulk operations with filtering, populating, ordering, and pagination. Strapi 后端提供了一个查询引擎 API 来与更底层的数据库层进行交互。 🌐 The Strapi backend provides a Query Engine API to interact with the database layer at a lower level. :::caution 在大多数情况下,你不应该使用查询引擎 API,而应使用 [文档服务 API](/cms/api/document-service)。 🌐 In most cases you should not use the Query Engine API and rather use the [Document Service API](/cms/api/document-service). 仅当你确切知道自己在做什么时才使用查询引擎 API,例如,如果你想使用直接与数据库的唯一行交互的底层 API。 🌐 Only use the Query Engine API if you exactly know what you are doing, for instance if you want to use a lower-level API that directly interacts with unique rows of the database. 请记住,查询引擎 API 并不了解 Strapi 5 的最新功能,如草稿与发布、国际化、内容历史记录等,可能还有更多功能。这也意味着查询引擎 API 将无法使用 `documentId`,而将使用 `id`,这可能会在数据库层面导致未预料的后果,或者与 Strapi 5 功能存在部分或不完全兼容的情况。 🌐 Please keep in mind that the Query Engine API is not aware of the most advanced Strapi 5 features like Draft & Publish, Internationalization, Content History, and possibly more. This also means that the Query Engine API will not be able to use `documentId` and will use `id`, which means it could lead to unattended consequences at the database level or partial or incomplete compatibility with Strapi 5 features. ::: :::prerequisites 在深入了解查询引擎 API 文档之前,建议你阅读以下介绍: 🌐 Before diving deeper into the Query Engine API documentation, it is recommended that you read the following introductions: - 关于[后端自定义介绍](/cms/backend-customization), - 以及 [内容 API 介绍](/cms/api/content-api)。 ::: ## 基本用法 {#basic-usage} 🌐 Basic usage 查询引擎可通过 `strapi.db.query` 使用: 🌐 The Query Engine is available through `strapi.db.query`: ```js strapi.db.query('api::blog.article').findMany({ // uid syntax: 'api::api-name.content-type-name' where: { title: { $startsWith: '2021', $endsWith: 'v4', }, }, populate: { category: true, }, }); ``` ## 可用操作 {#available-operations} 🌐 Available operations 查询引擎允许对数据库条目执行以下操作: 🌐 The Query Engine allows the following operations on database entries: - [单次操作](/cms/api/query-engine/single-operations): 使用查询引擎 API 创建、读取、更新和删除单个数据库条目。 - [批量操作](/cms/api/query-engine/bulk-operations): 使用查询引擎 API 创建、读取、更新和删除多个数据库条目。 - [过滤器](/cms/api/query-engine/filtering): 通过使用查询引擎 API 过滤数据库条目,精准获取你所需的内容。 - [填充](/cms/api/query-engine/populating): 通过填充关系,使用你的查询引擎 API 查询获取更多数据。 - [排序与分页](/cms/api/query-engine/order-pagination): 对你的查询引擎 API 查询结果进行排序和分页。 # 批量操作 Source: https://strapi.nodejs.cn/cms/api/query-engine/bulk-operations # 使用查询引擎 API 批量操作 {#bulk-operations-with-the-query-engine-api} 🌐 Bulk Operations with the Query Engine API 使用查询引擎 API 的批量操作功能,你可以通过 `createMany()`、`updateMany()`、`deleteMany()` 和 `count()` 方法一次性创建、更新、删除和统计多个条目。 🌐 Bulk Operations with the Query Engine API enable you to create, update, delete, and count multiple entries at once using `createMany()`, `updateMany()`, `deleteMany()`, and `count()` methods. :::caution 在大多数情况下,你不应该使用查询引擎 API,而应使用 [文档服务 API](/cms/api/document-service)。 🌐 In most cases you should not use the Query Engine API and rather use the [Document Service API](/cms/api/document-service). 仅当你确切知道自己在做什么时才使用查询引擎 API,例如,如果你想使用直接与数据库的唯一行交互的底层 API。 🌐 Only use the Query Engine API if you exactly know what you are doing, for instance if you want to use a lower-level API that directly interacts with unique rows of the database. 请记住,查询引擎 API 并不了解 Strapi 5 的最新功能,如草稿与发布、国际化、内容历史记录等,可能还有更多功能。这也意味着查询引擎 API 将无法使用 `documentId`,而将使用 `id`,这可能会在数据库层面导致未预料的后果,或者与 Strapi 5 功能存在部分或不完全兼容的情况。 🌐 Please keep in mind that the Query Engine API is not aware of the most advanced Strapi 5 features like Draft & Publish, Internationalization, Content History, and possibly more. This also means that the Query Engine API will not be able to use `documentId` and will use `id`, which means it could lead to unattended consequences at the database level or partial or incomplete compatibility with Strapi 5 features. ::: :::prerequisites 在深入了解查询引擎 API 文档之前,建议你阅读以下介绍: 🌐 Before diving deeper into the Query Engine API documentation, it is recommended that you read the following introductions: - 关于[后端自定义介绍](/cms/backend-customization), - 以及 [内容 API 介绍](/cms/api/content-api)。 ::: :::caution 为了避免性能问题,不允许对关系进行批量操作。 🌐 To avoid performance issues, bulk operations are not allowed on relations. ::: ## createMany() 创建多个条目。 🌐 Creates multiple entries. 语法:`createMany(parameters) => { count: number, ids: id[] }` 🌐 Syntax: `createMany(parameters) => { count: number, ids: id[] }` ### 参数 {#parameters} 🌐 Parameters | 参数 | 类型 | 描述 | | --- | --- | --- | | `data` | 对象数组 | 输入数据数组 | :::caution * MySQL 将仅返回包含最后插入的 id 的一个 id 数组,而不是整个列表。 * 在 Strapi v4.9.0 之前,`createMany()` 只会返回 `count`。 ::: ### 例子 {#example} 🌐 Example ```js await strapi.db.query("api::blog.article").createMany({ data: [ { title: "ABCD", }, { title: "EFGH", }, ], }); // { count: 2 , ids: [1,2]} ``` ## updateMany() 更新与参数匹配的多个条目。 🌐 Updates multiple entries matching the parameters. 语法:`updateMany(parameters) => { count: number }` 🌐 Syntax: `updateMany(parameters) => { count: number }` ### 参数 {#parameters-1} 🌐 Parameters | 参数 | 类型 | 描述 | | --- | --- | --- | | `where` | [`WhereParameter`](/cms/api/query-engine/filtering/) | 要使用的[过滤器](/cms/api/query-engine/filtering/) | | `data` | 对象 | 输入数据 | ### 例子 {#example-1} 🌐 Example ```js await strapi.db.query("api::shop.article").updateMany({ where: { price: 20, }, data: { price: 18, }, }); // { count: 42 } ``` ## deleteMany() 删除与参数匹配的多个条目。 🌐 Deletes multiple entries matching the parameters. 语法:`deleteMany(parameters) => { count: number }` 🌐 Syntax: `deleteMany(parameters) => { count: number }` ### 参数 {#parameters-2} 🌐 Parameters | 参数 | 类型 | 描述 | | --- | --- | --- | | `where` | [`WhereParameter`](/cms/api/query-engine/filtering/) | 使用的[过滤器](/cms/api/query-engine/filtering/) | ### 例子 {#example-2} 🌐 Example ```js await strapi.db.query("api::blog.article").deleteMany({ where: { title: { $startsWith: "v3", }, }, }); // { count: 42 } ``` ## 聚合 {#aggregations} 🌐 Aggregations ### count() 计算与参数匹配的条目数。 🌐 Counts entries matching the parameters. 语法:`count(parameters) => number` 🌐 Syntax: `count(parameters) => number` #### 参数 {#parameters-3} 🌐 Parameters | 参数 | 类型 | 描述 | | --- | --- | --- | | `where` | [`WhereParameter`](/cms/api/query-engine/filtering/) | 使用的[过滤器](/cms/api/query-engine/filtering/) | ```js const count = await strapi.db.query("api::blog.article").count({ where: { title: { $startsWith: "v3", }, }, }); // 12 ``` # 使用查询引擎 API 进行过滤 Source: https://strapi.nodejs.cn/cms/api/query-engine/filtering # 使用查询引擎 API 进行过滤 {#filtering-with-the-query-engine-api} 🌐 Filtering with the Query Engine API 查询引擎 API 使用 `where` 参数结合逻辑运算符(`$and`、`$or`、`$not`)以及以 `$` 为前缀的属性运算符(比较、字符串匹配、范围)来过滤查询结果。 🌐 The Query Engine API filters query results using the `where` parameter with logical operators (`$and`, `$or`, `$not`) and attribute operators (comparison, string matching, range) prefixed with `$`. :::caution 在大多数情况下,你不应该使用查询引擎 API,而应使用 [文档服务 API](/cms/api/document-service)。 🌐 In most cases you should not use the Query Engine API and rather use the [Document Service API](/cms/api/document-service). 仅当你确切知道自己在做什么时才使用查询引擎 API,例如,如果你想使用直接与数据库的唯一行交互的底层 API。 🌐 Only use the Query Engine API if you exactly know what you are doing, for instance if you want to use a lower-level API that directly interacts with unique rows of the database. 请记住,查询引擎 API 并不了解 Strapi 5 的最新功能,如草稿与发布、国际化、内容历史记录等,可能还有更多功能。这也意味着查询引擎 API 将无法使用 `documentId`,而将使用 `id`,这可能会在数据库层面导致未预料的后果,或者与 Strapi 5 功能存在部分或不完全兼容的情况。 🌐 Please keep in mind that the Query Engine API is not aware of the most advanced Strapi 5 features like Draft & Publish, Internationalization, Content History, and possibly more. This also means that the Query Engine API will not be able to use `documentId` and will use `id`, which means it could lead to unattended consequences at the database level or partial or incomplete compatibility with Strapi 5 features. ::: :::prerequisites 在深入了解查询引擎 API 文档之前,建议你阅读以下介绍: 🌐 Before diving deeper into the Query Engine API documentation, it is recommended that you read the following introductions: - 关于[后端自定义介绍](/cms/backend-customization), - 以及 [内容 API 介绍](/cms/api/content-api)。 ::: [查询引擎 API](/cms/api/query-engine/) 提供了使用其 [findMany()](/cms/api/query-engine/single-operations#findmany) 方法过滤结果的功能。 🌐 The [Query Engine API](/cms/api/query-engine/) offers the ability to filter results found with its [findMany()](/cms/api/query-engine/single-operations#findmany) method. 结果通过 `where` 参数进行过滤,该参数接受 [逻辑运算符](#logical-operators) 和 [属性运算符](#attribute-operators)。每个运算符都应以 `$` 为前缀。 🌐 Results are filtered with the `where` parameter that accepts [logical operators](#logical-operators) and [attribute operators](#attribute-operators). Every operator should be prefixed with `$`. :::strapi Deep filtering with the various APIs 有关如何使用各种 API 进行深度过滤的示例,请参阅 [this blog article](https://strapi.io/blog/deep-filtering-alpha-26)。 ::: ## 逻辑运算符 {#logical-operators} 🌐 Logical operators ### `$and` 所有嵌套条件必须是 `true`。 🌐 All nested conditions must be `true`. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { $and: [ { title: 'Hello World', }, { createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, ], }, }); ``` 在传递具有嵌套条件的对象时,会隐式使用 `$and`: ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: 'Hello World', createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, }); ``` ### `$or` 一个或多个嵌套条件必须是 `true`。 🌐 One or many nested conditions must be `true`. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { $or: [ { title: 'Hello World', }, { createdAt: { $gt: '2021-11-17T14:28:25.843Z' }, }, ], }, }); ``` ### `$not` 否定嵌套条件。 🌐 Negates the nested conditions. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { $not: { title: 'Hello World', }, }, }); ``` :::note `$not` 可以使用: - 作为逻辑运算符(例如在 `where: { $not: { // conditions… }}` 中) - 或 [作为属性运算符](#not-1)(例如在 `where: { attribute-name: $not: { … } }` 中)。 ::: :::tip `$and`、`$or` 和 `$not` 操作符可以嵌套在另一个 `$and`、`$or` 或 `$not` 操作符中。 ::: ## 属性运算符 {#attribute-operators} 🌐 Attribute Operators :::caution 根据数据库的实现,使用这些运算符可能会给出不同的结果,因为比较是由数据库而不是 Strapi 处理的。 🌐 Using these operators may give different results depending on the database's implementation, as the comparison is handled by the database and not by Strapi. ::: ### `$not` 否定嵌套条件。 🌐 Negates nested condition(s). **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $not: { $contains: 'Hello World', }, }, }, }); ``` ### `$eq` 属性等于输入值。 🌐 Attribute equals input value. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $eq: 'Hello World', }, }, }); ``` `$eq`可以省略: ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: 'Hello World', }, }); ``` ### `$eqi` 属性等于输入值(不区分大小写)。 🌐 Attribute equals input value (case-insensitive). **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $eqi: 'HELLO World', }, }, }); ``` ### `$ne` 属性不等于输入值。 🌐 Attribute does not equal input value. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $ne: 'ABCD', }, }, }); ``` ### `$nei` 属性不等于输入值(不区分大小写)。 🌐 Attribute does not equal input value (case-insensitive). **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $nei: 'abcd', }, }, }); ``` ### `$in` 属性包含在输入列表中。 🌐 Attribute is contained in the input list. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $in: ['Hello', 'Hola', 'Bonjour'], }, }, }); ``` 在传递一个值数组时可以省略 `$in`: ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: ['Hello', 'Hola', 'Bonjour'], }, }); ``` ### `$notIn` 输入列表中不包含属性。 🌐 Attribute is not contained in the input list. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $notIn: ['Hello', 'Hola', 'Bonjour'], }, }, }); ``` ### `$lt` 属性小于输入值。 🌐 Attribute is less than the input value. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { rating: { $lt: 10, }, }, }); ``` ### `$lte` 属性小于或等于输入值。 🌐 Attribute is less than or equal to the input value. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { rating: { $lte: 10, }, }, }); ``` ### `$gt` 属性大于输入值。 🌐 Attribute is greater than the input value. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { rating: { $gt: 5, }, }, }); ``` ### `$gte` 属性大于或等于输入值。 🌐 Attribute is greater than or equal to the input value. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { rating: { $gte: 5, }, }, }); ``` ### `$between` 属性介于两个输入值之间,包括边界(例如,`$between[1, 3]` 也会返回 `1` 和 `3`)。 🌐 Attribute is between the 2 input values, boundaries included (e.g., `$between[1, 3]` will also return `1` and `3`). **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { rating: { $between: [1, 20], }, }, }); ``` ### `$contains` 属性包含输入值(区分大小写)。 🌐 Attribute contains the input value (case-sensitive). **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $contains: 'Hello', }, }, }); ``` ### `$notContains` 属性不包含输入值(区分大小写)。 🌐 Attribute does not contain the input value (case-sensitive). **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $notContains: 'Hello', }, }, }); ``` ### `$containsi` 属性包含输入值。`$containsi`不区分大小写,而[$contains](#contains)区分大小写。 🌐 Attribute contains the input value. `$containsi` is not case-sensitive, while [$contains](#contains) is. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $containsi: 'hello', }, }, }); ``` ### `$notContainsi` 属性不包含输入值。`$notContainsi` 不区分大小写,而 [$notContains](#notcontains) 区分大小写。 🌐 Attribute does not contain the input value. `$notContainsi` is not case-sensitive, while [$notContains](#notcontains) is. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $notContainsi: 'hello', }, }, }); ``` ### `$startsWith` 属性以输入值开始。 🌐 Attribute starts with input value. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $startsWith: 'ABCD', }, }, }); ``` ### `$endsWith` 属性以输入值结尾。 🌐 Attribute ends with input value. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $endsWith: 'ABCD', }, }, }); ``` ### `$null` 属性是 `null`。 🌐 Attribute is `null`. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $null: true, }, }, }); ``` ### `$notNull` 属性不是 `null`。 🌐 Attribute is not `null`. **示例** ```js const entries = await strapi.db.query('api::article.article').findMany({ where: { title: { $notNull: true, }, }, }); ``` # 使用查询引擎 API 进行排序和分页 Source: https://strapi.nodejs.cn/cms/api/query-engine/order-pagination # 使用查询引擎 API 进行排序和分页 {#ordering-and-paginating-with-the-query-engine-api} 🌐 Ordering and Paginating with the Query Engine API 查询引擎 API 支持通过 `orderBy` 参数对单个或多个属性进行结果排序,包括关联排序,并通过 `offset` 和 `limit` 参数进行分页。 🌐 The Query Engine API supports ordering results with the `orderBy` parameter on single or multiple attributes, including relational ordering, and pagination with `offset` and `limit` parameters. :::caution 在大多数情况下,你不应该使用查询引擎 API,而应使用 [文档服务 API](/cms/api/document-service)。 🌐 In most cases you should not use the Query Engine API and rather use the [Document Service API](/cms/api/document-service). 仅当你确切知道自己在做什么时才使用查询引擎 API,例如,如果你想使用直接与数据库的唯一行交互的底层 API。 🌐 Only use the Query Engine API if you exactly know what you are doing, for instance if you want to use a lower-level API that directly interacts with unique rows of the database. 请记住,查询引擎 API 并不了解 Strapi 5 的最新功能,如草稿与发布、国际化、内容历史记录等,可能还有更多功能。这也意味着查询引擎 API 将无法使用 `documentId`,而将使用 `id`,这可能会在数据库层面导致未预料的后果,或者与 Strapi 5 功能存在部分或不完全兼容的情况。 🌐 Please keep in mind that the Query Engine API is not aware of the most advanced Strapi 5 features like Draft & Publish, Internationalization, Content History, and possibly more. This also means that the Query Engine API will not be able to use `documentId` and will use `id`, which means it could lead to unattended consequences at the database level or partial or incomplete compatibility with Strapi 5 features. ::: :::prerequisites 在深入了解查询引擎 API 文档之前,建议你阅读以下介绍: 🌐 Before diving deeper into the Query Engine API documentation, it is recommended that you read the following introductions: - 关于[后端自定义介绍](/cms/backend-customization), - 以及 [内容 API 介绍](/cms/api/content-api)。 ::: [查询引擎 API](/cms/api/query-engine) 提供了[排序](#ordering)和[分页](#pagination)结果的功能。 🌐 The [Query Engine API](/cms/api/query-engine) offers the ability to [order](#ordering) and [paginate](#pagination) results. ## 排序 {#ordering} 🌐 Ordering 要对查询引擎返回的结果进行排序,请使用 `orderBy` 参数。结果可以基于[单个](#single)或[多个](#multiple)属性进行排序,也可以使用[关系排序](#relational-ordering)。 🌐 To order results returned by the Query Engine, use the `orderBy` parameter. Results can be ordered based on a [single](#single) or on [multiple](#multiple) attributes and can also use [relational ordering](#relational-ordering). ### 单身 {#single} 🌐 Single ```js strapi.db.query('api::article.article').findMany({ orderBy: 'id', }); // single with direction strapi.db.query('api::article.article').findMany({ orderBy: { id: 'asc' }, }); ``` ### 多个 {#multiple} 🌐 Multiple ```js strapi.db.query('api::article.article').findMany({ orderBy: ['id', 'name'], }); // multiple with direction strapi.db.query('api::article.article').findMany({ orderBy: [{ title: 'asc' }, { publishedAt: 'desc' }], }); ``` ### 关系顺序 {#relational-ordering} 🌐 Relational ordering ```js strapi.db.query('api::article.article').findMany({ orderBy: { author: { name: 'asc', }, }, }); ``` ## 分页 {#pagination} 🌐 Pagination 要对查询引擎 API 返回的结果进行分页,请使用 `offset` 和 `limit` 参数: 🌐 To paginate results returned by the Query Engine API, use the `offset` and `limit` parameters: ```js strapi.db.query('api::article.article').findMany({ offset: 15, limit: 10, }); ``` # 使用查询引擎 API 填充 Source: https://strapi.nodejs.cn/cms/api/query-engine/populating # 使用查询引擎 API 填充 {#populating-with-the-query-engine-api} 🌐 Populating with the Query Engine API 查询引擎 API 的 `populate` 参数在查询中加载相关数据,支持基本填充、选择性属性、过滤嵌套关系以及通过填充片段处理多态结构。 🌐 The Query Engine API's `populate` parameter loads related data in queries, supporting basic population, selective attributes, filtering nested relations, and polymorphic structures via populate fragments. :::caution 在大多数情况下,你不应该使用查询引擎 API,而应使用 [文档服务 API](/cms/api/document-service)。 🌐 In most cases you should not use the Query Engine API and rather use the [Document Service API](/cms/api/document-service). 仅当你确切知道自己在做什么时才使用查询引擎 API,例如,如果你想使用直接与数据库的唯一行交互的底层 API。 🌐 Only use the Query Engine API if you exactly know what you are doing, for instance if you want to use a lower-level API that directly interacts with unique rows of the database. 请记住,查询引擎 API 并不了解 Strapi 5 的最新功能,如草稿与发布、国际化、内容历史记录等,可能还有更多功能。这也意味着查询引擎 API 将无法使用 `documentId`,而将使用 `id`,这可能会在数据库层面导致未预料的后果,或者与 Strapi 5 功能存在部分或不完全兼容的情况。 🌐 Please keep in mind that the Query Engine API is not aware of the most advanced Strapi 5 features like Draft & Publish, Internationalization, Content History, and possibly more. This also means that the Query Engine API will not be able to use `documentId` and will use `id`, which means it could lead to unattended consequences at the database level or partial or incomplete compatibility with Strapi 5 features. ::: :::prerequisites 在深入了解查询引擎 API 文档之前,建议你阅读以下介绍: 🌐 Before diving deeper into the Query Engine API documentation, it is recommended that you read the following introductions: - 关于[后端自定义介绍](/cms/backend-customization), - 以及 [内容 API 介绍](/cms/api/content-api)。 ::: 关系和组件有一个统一的 API 来填充它们。 🌐 Relations and components have a unified API for populating them. 要填充所有根级关系,请使用 `populate: true`: 🌐 To populate all the root level relations, use `populate: true`: ```js strapi.db.query('api::article.article').findMany({ populate: true, }); ``` 通过传递属性名称数组来选择要填充的数据: 🌐 Select which data to populate by passing an array of attribute names: ```js strapi.db.query('api::article.article').findMany({ populate: ['componentA', 'relationA'], }); ``` 可以传递一个对象以进行更高级的用法: 🌐 An object can be passed for more advanced usage: ```js strapi.db.query('api::article.article').findMany({ populate: { componentB: true, dynamiczoneA: true, relation: someLogic || true, }, }); ``` 复杂的填充也可以通过应用 `where` 过滤器并选择或填充嵌套关系来实现: 🌐 Complex populating can also be achieved by applying `where` filters and select or populate nested relations: ```js strapi.db.query('api::article.article').findMany({ populate: { relationA: { where: { name: { $contains: 'Strapi', }, }, }, repeatableComponent: { select: ['someAttributeName'], orderBy: ['someAttributeName'], populate: { componentRelationA: true, }, }, dynamiczoneA: true, }, }); ``` 在处理多态内容结构(动态区域、多态关系等)时,可以使用填充片段来更好地控制填充策略。 🌐 When dealing with polymorphic content structures (dynamic zones, polymorphic relations, etc...), it is possible to use populate fragments to have a better granularity on the populate strategy. ```js strapi.db.query('api::article.article').findMany('api::article.article', { populate: { dynamicZone: { on: { 'components.foo': { select: ['title'], where: { title: { $contains: 'strapi' } }, }, 'components.bar': { select: ['name'], }, }, }, morphAuthor: { on: { 'plugin::users-permissions.user': { select: ['username'], }, 'api::author.author': { select: ['name'], }, }, }, }, }); ``` # 单次操作 Source: https://strapi.nodejs.cn/cms/api/query-engine/single-operations # 使用查询引擎 API 的单次操作 {#single-operations-with-the-query-engine-api} 🌐 Single Operations with the Query Engine API 查询引擎 API 提供了方法来查找、创建、更新和删除单个条目,并支持过滤、选择、分页和关联填充选项。 🌐 The Query Engine API provides methods to find, create, update, and delete individual entries with filtering, selection, pagination, and relation population options. :::caution 在大多数情况下,你不应该使用查询引擎 API,而应使用 [文档服务 API](/cms/api/document-service)。 🌐 In most cases you should not use the Query Engine API and rather use the [Document Service API](/cms/api/document-service). 仅当你确切知道自己在做什么时才使用查询引擎 API,例如,如果你想使用直接与数据库的唯一行交互的底层 API。 🌐 Only use the Query Engine API if you exactly know what you are doing, for instance if you want to use a lower-level API that directly interacts with unique rows of the database. 请记住,查询引擎 API 并不了解 Strapi 5 的最新功能,如草稿与发布、国际化、内容历史记录等,可能还有更多功能。这也意味着查询引擎 API 将无法使用 `documentId`,而将使用 `id`,这可能会在数据库层面导致未预料的后果,或者与 Strapi 5 功能存在部分或不完全兼容的情况。 🌐 Please keep in mind that the Query Engine API is not aware of the most advanced Strapi 5 features like Draft & Publish, Internationalization, Content History, and possibly more. This also means that the Query Engine API will not be able to use `documentId` and will use `id`, which means it could lead to unattended consequences at the database level or partial or incomplete compatibility with Strapi 5 features. ::: :::prerequisites 在深入了解查询引擎 API 文档之前,建议你阅读以下介绍: 🌐 Before diving deeper into the Query Engine API documentation, it is recommended that you read the following introductions: - 关于[后端自定义介绍](/cms/backend-customization), - 以及 [内容 API 介绍](/cms/api/content-api)。 ::: ## findOne() :::note 只有在[文档服务的`findOne()`](/cms/api/document-service#findone)方法无法满足你的使用场景时,才使用查询引擎的`findOne()`方法。 ::: 查找与参数匹配的第一个条目。 🌐 Finds the first entry matching the parameters. 语法:`findOne(parameters) ⇒ Entry` 🌐 Syntax: `findOne(parameters) ⇒ Entry` ### 参数 {#parameters} 🌐 Parameters | 参数 | 类型 | 描述 | | --- | --- | --- | | `select` | 字符串,或字符串数组 | 要返回的[属性](/cms/backend-customization/models#model-attributes) | | `where` | [`WhereParameter`](/cms/api/query-engine/filtering/) | 要使用的[过滤器](/cms/api/query-engine/filtering/) | | `offset` | 整数 | 要跳过的条目数量 | | `orderBy` | [`OrderByParameter`](/cms/api/query-engine/order-pagination/) | [排序](/cms/api/query-engine/order-pagination/) 定义 | | `populate` | [`PopulateParameter`](/cms/api/query-engine/populating/) | 要[填充](/cms/api/query-engine/populating/)的关系 | ### 例子 {#example} 🌐 Example ```js const entry = await strapi.db.query('api::blog.article').findOne({ select: ['title', 'description'], where: { title: 'Hello World' }, populate: { category: true }, }); ``` ## findMany() :::note 只有在[文档服务 `findMany()`](/cms/api/document-service#findmany) 方法无法满足你的使用场景时,才使用查询引擎的 `findMany()` 方法。 ::: 查找与参数匹配的条目。 🌐 Finds entries matching the parameters. 语法:`findMany(parameters) ⇒ Entry[]` 🌐 Syntax: `findMany(parameters) ⇒ Entry[]` ### 参数 {#parameters-1} 🌐 Parameters | 参数 | 类型 | 描述 | | --- | --- | --- | | `select` | 字符串或字符串数组 | 要返回的[属性](/cms/backend-customization/models#model-attributes) | | `where` | [`WhereParameter`](/cms/api/query-engine/filtering/) | 要使用的[过滤器](/cms/api/query-engine/filtering/) | | `limit` | 整数 | 要返回的条目数量 | | `offset` | 整数 | 要跳过的条目数量 | | `orderBy` | [`OrderByParameter`](/cms/api/query-engine/order-pagination/) | [排序](/cms/api/query-engine/order-pagination/)定义 | | `populate` | [`PopulateParameter`](/cms/api/query-engine/populating/) | 要[填充](/cms/api/query-engine/populating/)的关联关系 | ### 例子 {#example-1} 🌐 Example ```js const entries = await strapi.db.query('api::blog.article').findMany({ select: ['title', 'description'], where: { title: 'Hello World' }, orderBy: { publishedAt: 'DESC' }, populate: { category: true }, }); ``` ## findWithCount() 查找与参数匹配的条目并对其进行计数。 🌐 Finds and counts entries matching the parameters. 语法:`findWithCount(parameters) => [Entry[], number]` 🌐 Syntax: `findWithCount(parameters) => [Entry[], number]` ### 参数 {#parameters-2} 🌐 Parameters | 参数 | 类型 | 描述 | | --- | --- | --- | | `select` | 字符串或字符串数组 | 要返回的[属性](/cms/backend-customization/models#model-attributes) | | `where` | [`WhereParameter`](/cms/api/query-engine/filtering/) | 要使用的[过滤器](/cms/api/query-engine/filtering/) | | `limit` | 整数 | 要返回的条目数量 | | `offset` | 整数 | 要跳过的条目数量 | | `orderBy` | [`OrderByParameter`](/cms/api/query-engine/order-pagination/) | [排序](/cms/api/query-engine/order-pagination/)定义 | | `populate` | [`PopulateParameter`](/cms/api/query-engine/populating/) | 要[填充](/cms/api/query-engine/populating/)的关联关系 | ### 例子 {#example-2} 🌐 Example ```js const [entries, count] = await strapi.db.query('api::blog.article').findWithCount({ select: ['title', 'description'], where: { title: 'Hello World' }, orderBy: { title: 'DESC' }, populate: { category: true }, }); ``` ## create() :::note 只有在[文档服务 `create()` 方法](/cms/api/document-service#create)无法覆盖你的使用场景时,才使用查询引擎的 `create()` 方法。 ::: 创建一个条目并返回它。 🌐 Creates one entry and returns it. 语法:`create(parameters) => Entry` 🌐 Syntax: `create(parameters) => Entry` ### 参数 {#parameters-3} 🌐 Parameters | 参数 | 类型 | 描述 | | --- | --- | --- | | `select` | 字符串,或字符串数组 | 要返回的[属性](/cms/backend-customization/models#model-attributes) | | `populate` | [`PopulateParameter`](/cms/api/query-engine/populating/) | 要[填充](/cms/api/query-engine/populating/)的关联 | | `data` | 对象 | 输入数据 | ### 例子 {#example-3} 🌐 Example ```js const entry = await strapi.db.query('api::blog.article').create({ data: { title: 'My Article', }, }); ``` ## update() :::note 只有在[文档服务 `update()`](/cms/api/document-service#update) 方法无法满足你的使用场景时,才使用查询引擎的 `update()` 方法。 ::: 更新一项并返回它。 🌐 Updates one entry and returns it. 语法:`update(parameters) => Entry` 🌐 Syntax: `update(parameters) => Entry` ### 参数 {#parameters-4} 🌐 Parameters | 参数 | 类型 | 描述 | | --- | --- | --- | | `select` | 字符串,或字符串数组 | 要返回的 [属性](/cms/backend-customization/models#model-attributes) | | `populate` | [`PopulateParameter`](/cms/api/query-engine/populating/) | 要 [填充](/cms/api/query-engine/populating/) 的关系 | | `where` | [`WhereParameter`](/cms/api/query-engine/filtering/) | 要使用的 [过滤器](/cms/api/query-engine/filtering/) | | `data` | 对象 | 输入数据 | ### 例子 {#example-4} 🌐 Example ```js const entry = await strapi.db.query('api::blog.article').update({ where: { id: 1 }, data: { title: 'xxx', }, }); ``` ## delete() :::note 只有在[文档服务 `delete()`](/cms/api/document-service#delete) 方法无法满足你的使用场景时,才使用查询引擎的 `delete()` 方法。 ::: 删除一项并将其返回。 🌐 Deletes one entry and returns it. 语法:`delete(parameters) => Entry` 🌐 Syntax: `delete(parameters) => Entry` ### 参数 {#parameters-5} 🌐 Parameters | 参数 | 类型 | 描述 | | --- | --- | --- | | `select` | 字符串,或字符串数组 | 要返回的[属性](/cms/backend-customization/models#model-attributes) | | `populate` | [`PopulateParameter`](/cms/api/query-engine/populating/) | 要[填充](/cms/api/query-engine/populating/)的关系 | | `where` | [`WhereParameter`](/cms/api/query-engine/filtering/) | 要使用的[过滤器](/cms/api/query-engine/filtering/) | ### 例子 {#example-5} 🌐 Example ```js const entry = await strapi.db.query('api::blog.article').delete({ where: { id: 1 }, }); ``` # REST API参考 Source: https://strapi.nodejs.cn/cms/api/rest # REST API参考 {#rest-api-reference} 🌐 REST API reference Strapi 的 REST API 会自动为内容类型生成端点,使用 GET、POST、PUT 和 DELETE 方法来获取、创建、更新和删除文档,并支持过滤、排序、字段选择和关联填充。 🌐 Strapi's REST API automatically generates endpoints for content-types to fetch, create, update, and delete documents using GET, POST, PUT, and DELETE methods, with support for filtering, sorting, field selection, and relation population. REST API 允许通过 API 端点访问 [内容类型](/cms/backend-customization/models)。Strapi 在创建内容类型时会自动创建 [API 端点](#endpoints)。在查询 API 端点时可以使用 [API 参数](/cms/api/rest/parameters) 来优化结果。 🌐 The REST API allows accessing the [content-types](/cms/backend-customization/models) through API endpoints. Strapi automatically creates [API endpoints](#endpoints) when a content-type is created. [API parameters](/cms/api/rest/parameters) can be used when querying API endpoints to refine the results. 本节文档是针对内容类型的 REST API 参考。我们还提供了针对特定用例的[指南](/cms/api/rest/guides/intro)。 🌐 This section of the documentation is for the REST API reference for content-types. We also have [guides](/cms/api/rest/guides/intro) available for specific use cases. :::prerequisites 所有内容类型默认都是私有的,需要将其设为公开,或者查询需要使用适当权限进行认证。有关更多详细信息,请参阅[快速入门指南](/cms/quick-start#step-10-set-roles--permissions)、[用户与权限功能](/cms/features/users-permissions#roles)用户指南,以及[API 令牌配置文档](/cms/features/api-tokens)。 🌐 All content types are private by default and need to be either made public or queries need to be authenticated with the proper permissions. See the [Quick Start Guide](/cms/quick-start#step-10-set-roles--permissions), the user guide for the [Users & Permissions feature](/cms/features/users-permissions#roles), and [API tokens configuration documentation](/cms/features/api-tokens) for more details. ::: :::note 默认情况下,REST API 响应仅包含顶层字段,不会填充任何关系、媒体字段、组件或动态区域。使用 [`populate` 参数](/cms/api/rest/populate-select) 来填充特定字段。确保为要填充的关系的字段授予查找权限。 🌐 By default, the REST API responses only include top-level fields and does not populate any relations, media fields, components, or dynamic zones. Use the [`populate` parameter](/cms/api/rest/populate-select) to populate specific fields. Ensure that the find permission is given to the field(s) for the relation(s) you populate. ::: :::tip Performance best practices 对于生产环境的应用,要有意地进行数据获取:使用显式填充,限制填充深度,并在路由中间件中集中填充逻辑。请参阅 Strapi 博客上的 [Building High-Performance Strapi Applications](https://strapi.io/blog/building-high-performance-strapi-applications-common-pitfalls-and-best-practices) 获取完整指南。 ::: :::strapi Strapi Client [Strapi 客户端](/cms/api/client) 库简化了与你的 Strapi 后端的交互,提供了一种获取、创建、更新和删除内容的方式。 🌐 The [Strapi Client](/cms/api/client) library simplifies interactions with your Strapi back end, providing a way to fetch, create, update, and delete content. ::: ## 端点 {#endpoints} 🌐 Endpoints 对于每个 Content-Type,会自动生成以下端点: 🌐 For each Content-Type, the following endpoints are automatically generated:
复数 API ID 与单数 API ID: 在下表中: 🌐 In the following tables: - `:singularApiId` 指内容类型中“API ID(单数)”字段的值, - 而 `:pluralApiId` 指的是内容类型的“API ID(复数)”字段的值。 这些值是在内容类型构建器中创建内容类型时定义的,并且可以在管理面板编辑内容类型时找到(参见 [用户指南](/cms/features/content-type-builder#creating-content-types))。例如,对于“文章”内容类型,默认情况下: 🌐 These values are defined when creating a content-type in the Content-Type Builder, and can be found while editing a content-type in the admin panel (see [User Guide](/cms/features/content-type-builder#creating-content-types)). For instance, by default, for an "Article" content-type: - `:singularApiId` 将是 `article` - `:pluralApiId` 将是 `articles`
| 方法 | URL | 描述 | | --- | --- | --- | | `GET` | `/api/:pluralApiId` | [获取文档列表](#get-all) | | `POST` | `/api/:pluralApiId` | [创建文档](#create) | | `GET` | `/api/:pluralApiId/:documentId` | [获取文档](#get) | | `PUT` | `/api/:pluralApiId/:documentId` | [更新文档](#update) | | `DELETE` | `/api/:pluralApiId/:documentId` | [删除文档](#delete) | | 方法 | URL | 描述 | | --- | --- | --- | | `GET` | `/api/:singularApiId` | [获取文档](#get) | | `PUT` | `/api/:singularApiId` | [更新/创建文档](#update) | | `DELETE` | `/api/:singularApiId` | [删除文档](#delete) | :::strapi Upload API 上传包(为[媒体库功能](/cms/features/media-library)提供支持)有一个特定的 API,可通过其[`/api/upload`端点](/cms/api/rest/upload)访问。 🌐 The Upload package (which powers the [Media Library feature](/cms/features/media-library)) has a specific API accessible through its [`/api/upload` endpoints](/cms/api/rest/upload). ::: :::note [组件](/cms/backend-customization/models#components-json) 没有 API 端点。 ::: ## 请求 {#requests} 🌐 Requests :::strapi Strapi 5 vs. Strapi v4 Strapi 5 的内容 API 与 Strapi v4 有 2 个主要区别: 🌐 Strapi 5's Content API includes 2 major differences with Strapi v4: - 响应格式已被扁平化,这意味着属性不再嵌套在 `data.attributes` 对象中,而是可以直接在 `data` 对象的第一层访问(例如,内容类型的“title”属性可以使用 `data.title` 访问)。 - Strapi 5 现在使用 **文档** ,并且文档通过它们的 `documentId` 访问(详情请参见 [重大更改条目](/cms/migration/v4-to-v5/breaking-changes/use-document-id)) ::: 请求以对象形式返回响应,通常包含以下键: 🌐 Requests return a response as an object which usually includes the following keys: - `data`:响应数据本身,可以是: - 单个文档,作为具有以下键的对象: - `id`(整数) - `documentId`(字符串),这是在查询特定文档时使用的唯一标识符, - 属性(每个属性的类型取决于该属性,详细信息请参阅 [models attributes](/cms/backend-customization/models#model-attributes) 文档) - `meta`(对象) - 文档列表,作为对象数组 - 自定义响应 - `meta`(对象):有关分页、发布状态、可用语言环境等的信息。 - `error`(对象,_可选_):关于请求抛出的任何[错误](/cms/error-handling)的信息 :::note 某些插件(包括用户和权限以及上传)可能不遵循此响应格式。 🌐 Some plugins (including Users & Permissions and Upload) may not follow this response format. ::: ### 获取文档 {#get-all} 🌐 Get documents :::tip Tip: Strapi 5 vs. Strapi 4 在 Strapi 5 中,响应格式已被扁平化,属性可以直接从 `data` 对象访问,而不再嵌套在 `data.attributes` 中。 🌐 In Strapi 5 the response format has been flattened, and attributes are directly accessible from the `data` object instead of being nested in `data.attributes`. 在迁移到 Strapi 5 时,你可以传递一个可选的头(参见[相关破坏性更改](/cms/migration/v4-to-v5/breaking-changes/new-response-format))。 🌐 You can pass an optional header while you're migrating to Strapi 5 (see the [related breaking change](/cms/migration/v4-to-v5/breaking-changes/new-response-format)). ::: #### GET /api/:pluralApiId — 列出文件 返回分页的文档列表。支持过滤、排序、字段选择和关联填充。 **Parameters:** - `sort` (string | string[]): Sort by field. Use `field:asc` or `field:desc` - `filters` (object): Filter with operators: `$eq`, `$contains`, `$gt`, `$lt`. See filtering (/cms/api/rest/filters). - `populate` (string | object): Relations and components to include. Use `*` for all. See populate (/cms/api/rest/populate-select). - `fields` (string[]): Select specific fields to return. See field selection (/cms/api/rest/populate-select#field-selection). - `pagination[page]` (integer): Page number. Default: `1` - `pagination[pageSize]` (integer): Items per page. Default `25`, max `100` - `locale` (string): Locale of the documents to fetch. See locale (/cms/api/rest/locale). - `status` (string): `published` or `draft`. See status (/cms/api/rest/status). - `publicationFilter` (string): Query documents by the relationship between their draft and published versions. See publicationFilter (/cms/api/rest/publication-filter). ```bash curl 'http://localhost:1337/api/restaurants' \ -H 'Authorization: Bearer ' ``` ```js const response = await fetch( 'http://localhost:1337/api/restaurants', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ```json { "data": [ { "id": 2, "documentId": "hgv1vny5cebq2l3czil1rpb3", "Name": "BMK Paris Bamako", "Description": null, "createdAt": "2024-03-06T13:42:05.098Z", "updatedAt": "2024-03-06T13:42:05.098Z", "publishedAt": "2024-03-06T13:42:05.103Z", "locale": "en" }, { "id": 4, "documentId": "znrlzntu9ei5onjvwfaalu2v", "Name": "Biscotte Restaurant", "createdAt": "2024-03-06T13:43:30.172Z", "updatedAt": "2024-03-06T13:43:30.172Z", "publishedAt": "2024-03-06T13:43:30.175Z", "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 2 } } } ``` ### 获取文档 {#get} 🌐 Get a document :::strapi Strapi 5 vs. Strapi v4 在 Strapi 5 中,特定文档可以通过其 `documentId` 来访问。 🌐 In Strapi 5, a specific document is reached by its `documentId`. ::: #### GET /api/:pluralApiId/:documentId — 获取文件 通过其 documentId 返回单个文档。支持字段选择和关系填充。 **Parameters:** - `pluralApiId` (string, required): Plural API ID of the content-type (e.g. `restaurants`) - `documentId` (string, required): Unique document identifier ```bash curl 'http://localhost:1337/api/restaurants/znrlzntu9ei5onjvwfaalu2v' \ -H 'Authorization: Bearer ' ``` ```js const response = await fetch( 'http://localhost:1337/api/restaurants/znrlzntu9ei5onjvwfaalu2v', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ```json { "data": { "id": 6, "documentId": "znrlzntu9ei5onjvwfaalu2v", "Name": "Biscotte Restaurant", "Description": [ { "type": "paragraph", "children": [{ "type": "text", "text": "Welcome to Biscotte restaurant! Restaurant Biscotte offers a cuisine based on fresh, quality products." }] } ], "createdAt": "2024-02-27T10:19:04.953Z", "updatedAt": "2024-03-05T15:52:05.591Z", "publishedAt": "2024-03-05T15:52:05.600Z", "locale": "en" }, "meta": {} } ``` ```json { "data": null, "error": { "status": 404, "name": "NotFoundError", "message": "Document not found" } } ``` ### 创建文档 {#create} 🌐 Create a document 如果安装了[国际化 (i18n) 插件](/cms/features/internationalization),则可以使用 POST 请求向 REST API [创建本地化文档](/cms/api/rest/locale#rest-delete)。 🌐 If the [Internationalization (i18n) plugin](/cms/features/internationalization) is installed, it's possible to use POST requests to the REST API to [create localized documents](/cms/api/rest/locale#rest-delete). :::note 在创建文档时,你可以定义其关系及其顺序(有关更多详细信息,请参见[通过 REST API 管理关系](/cms/api/rest/relations.md))。 🌐 While creating a document, you can define its relations and their order (see [Managing relations through the REST API](/cms/api/rest/relations.md) for more details). ::: :::info Dynamic zones 当你提交(POST)一个文档时,动态区域数组中的每个对象都必须包含该变体的 UID(例如 `shared.slider`)的 `__component`。将 `__component` 放在表示动态区域中一行的对象上。该对象内部的嵌套字段应当与使用 `populate` 获取同一文档时返回的 JSON 相匹配,因此除非模式将该层级视为另一个区别结构,否则不要在内部对象上放置 `__component`。 🌐 When you POST a document, each object inside a dynamic zone array must include `__component` with that variant's UID (for example `shared.slider`). Put `__component` on the object that represents one row in the dynamic zone. Nested fields inside that object should mirror the JSON you get back when the same document is fetched with `populate` so you do not place `__component` on inner objects unless the schema treats that level as another discriminated structure. ::: #### POST /api/:pluralApiId — 创建一个文档 创建一个新文档并返回它。在请求正文中将字段值放入数据对象中。 **Parameters:** - `data` (object, required): Object containing the field values for the new document ```bash curl -X POST \ 'http://localhost:1337/api/restaurants' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "data": { "Name": "Restaurant D", "Description": [ { "type": "paragraph", "children": [{ "type": "text", "text": "A very short description goes here." }] } ] } }' ``` ```js const response = await fetch( 'http://localhost:1337/api/restaurants', { method: 'POST', headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json', }, body: JSON.stringify({ data: { Name: 'Restaurant D', Description: [ { type: 'paragraph', children: [{ type: 'text', text: 'A very short description goes here.' }], }, ], }, }), } ); const data = await response.json(); ``` ```json { "data": { "documentId": "bw64dnu97i56nq85106yt4du", "Name": "Restaurant D", "Description": [ { "type": "paragraph", "children": [{ "type": "text", "text": "A very short description goes here." }] } ], "createdAt": "2024-03-05T16:44:47.689Z", "updatedAt": "2024-03-05T16:44:47.689Z", "publishedAt": "2024-03-05T16:44:47.687Z", "locale": "en" }, "meta": {} } ``` ### 更新文档 {#update} 🌐 Update a document :::note NOTES * 即使安装了[国际化 (i18n) 插件](/cms/features/internationalization),目前仍然无法[更新文档的语言环境](/cms/api/rest/locale#rest-update)。 * 在更新文档时,你可以定义其关系及其顺序(有关更多详细信息,请参见[通过 REST API 管理关系](/cms/api/rest/relations))。 ::: :::info Dynamic zones 你发送到[动态区域](/cms/backend-customization/models#dynamic-zones)的每个条目必须包含 `__component`,其值为目标组件的 UID(例如 `shared.media`)。Strapi 使用该字段在你创建或更新区域中的项目时选择组件的模式;如果没有它,写入操作可能会验证失败或返回成功但不更改数据。请使用内容类型生成器中显示的每个组件的 UID。 🌐 Each entry you send for a [dynamic zone](/cms/backend-customization/models#dynamic-zones) must include `__component` with the target component's UID (for example `shared.media`). Strapi uses that field to pick the component schema when you create or update items in the zone; without it, writes can fail validation or return success without changing data. Use the UID shown in the Content-Type Builder for each component in the zone. ::: #### PUT /api/:pluralApiId/:documentId — 更新文档 通过 documentId 部分更新文档并返回其值。发送 null 值以清除字段。 **Parameters:** - `data` (object, required): Object containing the field values to update ```bash curl -X PUT \ 'http://localhost:1337/api/restaurants/hgv1vny5cebq2l3czil1rpb3' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "data": { "Name": "BMK Paris Bamako", "Description": [ { "type": "paragraph", "children": [{ "type": "text", "text": "A very short description goes here." }] } ] } }' ``` ```js const response = await fetch( 'http://localhost:1337/api/restaurants/hgv1vny5cebq2l3czil1rpb3', { method: 'PUT', headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json', }, body: JSON.stringify({ data: { Name: 'BMK Paris Bamako', Description: [ { type: 'paragraph', children: [{ type: 'text', text: 'A very short description goes here.' }], }, ], }, }), } ); const data = await response.json(); ``` ```json { "data": { "id": 9, "documentId": "hgv1vny5cebq2l3czil1rpb3", "Name": "BMK Paris Bamako", "Description": [ { "type": "paragraph", "children": [{ "type": "text", "text": "A very short description goes here." }] } ], "createdAt": "2024-03-06T13:42:05.098Z", "updatedAt": "2024-03-06T14:16:56.883Z", "publishedAt": "2024-03-06T14:16:56.895Z", "locale": "en" }, "meta": {} } ``` ### 删除文档 {#delete} 🌐 Delete a document #### DELETE /api/:pluralApiId/:documentId — 删除文档 通过文档ID永久删除文档。此操作不可撤销。 **Parameters:** - `pluralApiId` (string, required): Plural API ID (e.g. `restaurants`) - `documentId` (string, required): Document ID of the entry to delete ```bash curl -X DELETE \ 'http://localhost:1337/api/restaurants/bw64dnu97i56nq85106yt4du' \ -H 'Authorization: Bearer ' ``` ```js const response = await fetch( 'http://localhost:1337/api/restaurants/bw64dnu97i56nq85106yt4du', { method: 'DELETE', headers: { Authorization: 'Bearer ', }, } ); ``` # 过滤器 Source: https://strapi.nodejs.cn/cms/api/rest/filters # REST API:过滤器 {#rest-api-filters} 🌐 REST API: Filters REST API 筛选功能允许使用 `$eq`、`$contains` 和 `$between` 等操作符对查询结果进行筛选,并支持使用 `$and`、`$or` 和 `$not` 进行复杂筛选,以及跨相关内容进行深度筛选。 🌐 The REST API filters feature allows filtering query results using operators like `$eq`, `$contains`, and `$between`, with support for complex filtering using `$and`, `$or`, and `$not`, as well as deep filtering across related content. [REST API](/cms/api/rest) 提供了使用其 ["获取条目"](/cms/api/rest#get-all) 方法筛选结果的能力。
使用可选的 Strapi 功能可以提供更多筛选条件: - 如果在某个内容类型上启用了[国际化 (i18n) 插件](/cms/features/internationalization),就可以按语言环境进行过滤。 - 如果启用了 [Draft & Publish](/cms/features/draft-and-publish),可以根据 `published`(默认)或 `draft` 状态进行筛选。 :::tip Strapi 利用 [`qs` 库](https://github.com/ljharb/qs) 解析嵌套对象的能力来创建更复杂的查询。 🌐 Strapi takes advantage of the ability of [the `qs` library](https://github.com/ljharb/qs) to parse nested objects to create more complex queries. 直接使用 `qs` 来生成复杂查询,而不是手动创建它们。本说明文档中的示例展示了如何使用 `qs`。 🌐 Use `qs` directly to generate complex queries instead of creating them manually. Examples in this documentation showcase how you can use `qs`. 如果你更喜欢使用我们的在线工具,而不是在你的机器上使用 `qs` 生成查询,你也可以使用 [交互式查询构建器](/cms/api/rest/interactive-query-builder)。 🌐 You can also use the [interactive query builder](/cms/api/rest/interactive-query-builder) if you prefer playing with our online tool instead of generating queries with `qs` on your machine. ::: 查询可以接受带有以下语法的 `filters` 参数: 🌐 Queries can accept a `filters` parameter with the following syntax: `GET /api/:pluralApiId?filters[field][operator]=value` 可以使用以下运算符: 🌐 The following operators are available: | 操作符 | 描述 | | --- | --- | | `$eq` | 等于 | | `$eqi` | 等于(不区分大小写) | | `$ne` | 不等于 | | `$nei` | 不等于(不区分大小写) | | `$lt` | 小于 | | `$lte` | 小于或等于 | | `$gt` | 大于 | | `$gte` | 大于或等于 | | `$in` | 包含在数组中 | | `$notIn` | 不包含在数组中 | | `$contains` | 包含 | | `$notContains` | 不包含 | | `$containsi` | 包含(不区分大小写) | | `$notContainsi` | 不包含(不区分大小写) | | `$null` | 为空 | | `$notNull` | 不为空 | | `$between` | 在范围内 | | `$startsWith` | 以...开头 | | `$startsWithi` | 以...开头(不区分大小写) | | `$endsWith` | 以...结尾 | | `$endsWithi` | 以...结尾(不区分大小写) | | `$or` | 将筛选器连接为“或”表达式 | | `$and` | 将筛选器连接为“与”表达式 | | `$not` | 将筛选器连接为“非”表达式 | 当 `filters` 对象中传入多个字段时,它们会与 `$and` 隐式组合(例如,`GET /api/restaurants?filters[stars][$gte]=3&filters[open][$eq]=true` 只返回开放且评分至少为 3 星的餐厅)。 🌐 When several fields are passed in the `filters` object, they are implicitly combined with `$and` (e.g. `GET /api/restaurants?filters[stars][$gte]=3&filters[open][$eq]=true` only returns restaurants that are open and have at least 3 stars). :::tip `$and`、`$or` 和 `$not` 运算符可以互相嵌套。 ::: :::caution 默认情况下,滤镜只能用于由内容类型构建器和 CLI 生成的 `find` 端点。 🌐 By default, the filters can only be used from `find` endpoints generated by the Content-type Builder and the CLI. ::: ## 示例:查找名字为 'John' 的用户 {#example-find-users-having-john-as-a-first-name} 🌐 Example: Find users having 'John' as a first name #### GET /api/users?filters[username][$eq]=John — 查找名字为 使用 $eq 过滤操作符查找完全匹配。 ```bash curl 'http://localhost:1337/api/users?filters[username][$eq]=John' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ filters: { username: { $eq: 'John', }, }, }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/users?${query}`); ``` ```json { "data": [ { "id": 1, "documentId": "znrlzntu9ei5onjvwfaalu2v", "username": "John", "email": "john@test.com", "provider": "local", "confirmed": true, "blocked": false, "createdAt": "2021-12-03T20:08:17.740Z", "updatedAt": "2021-12-03T20:08:17.740Z" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` ## 示例:查找 ID 为 3、6、8 的多家餐厅 {#example-find-multiple-restaurants-with-ids-3-68} 🌐 Example: Find multiple restaurants with ids 3, 6,8 #### GET /api/restaurants?filters[id][$in][0]=3&filters[id][$in][1]=6&filters[id][$in][2]=8 — 查找ID为3、6、8的多家餐厅 使用 $in 过滤运算符和一个值数组来查找多个精确值。 ```bash curl 'http://localhost:1337/api/restaurants?filters[id][$in][0]=3&filters[id][$in][1]=6&filters[id][$in][2]=8' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ filters: { id: { $in: [3, 6, 8], }, }, }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "id": 3, "documentId": "ethwxjxtvuxl89jq720e38uk", "name": "test3" }, { "id": 6, "documentId": "ethwxjxtvuxl89jq720e38uk", "name": "test6" }, { "id": 8, "documentId": "cf07g1dbusqr8mzmlbqvlegx", "name": "test8" } ], "meta": {} } ``` ## 复杂筛选 {#complex-filtering} 🌐 Complex filtering #### GET /api/books?filters[$and][0][$or][0][date][$eq]=2020-01-01&filters[$and][0][$or][1][date][$eq]=2020-01-02&filters[$and][1][author][name][$eq]=Kai%20doe — 查找有两个可能日期和特定作者的书籍 结合 $and 和 $or 操作符进行复杂过滤。 ```bash curl 'http://localhost:1337/api/books?filters[$and][0][$or][0][date][$eq]=2020-01-01&filters[$and][0][$or][1][date][$eq]=2020-01-02&filters[$and][1][author][name][$eq]=Kai%20doe' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ filters: { $and: [ { $or: [ { date: { $eq: '2020-01-01', }, }, { date: { $eq: '2020-01-02', }, }, ], }, { author: { name: { $eq: 'Kai doe', }, }, }, ], }, }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/books?${query}`); ``` ```json { "data": [ { "id": 1, "documentId": "rxngxzclq0zdaqtvz67hj38d", "name": "test1", "date": "2020-01-01" }, { "id": 2, "documentId": "kjkhff4e269a50b4vi16stst", "name": "test2", "date": "2020-01-02" } ], "meta": {} } ``` :::note 上述响应仅包含书籍自身的属性。通过 `$and` 过滤器遍历的 `author` 关系不会被返回,除非通过 [`populate` 参数](/cms/api/rest/populate-select#population) 请求,例如通过在请求中添加 `&populate=author`。 🌐 The response above only contains a book's own attributes. The `author` relation traversed by the `$and` filter is not returned unless requested through the [`populate` parameter](/cms/api/rest/populate-select#population), for example by adding `&populate=author` to the request. ::: ## 深度过滤 {#deep-filtering} 🌐 Deep filtering :::note - 关系、媒体字段、组件和动态区域默认情况下未填充。使用 `populate` 参数来填充这些内容结构(参见 [`populate` 文档`](/cms/api/rest/populate-select#population)) - 你可以过滤填充的内容,也可以过滤嵌套关系,但不能对多态内容结构(例如媒体字段和动态区域)使用过滤器。 ::: :::caution 使用深层过滤器查询你的 API 可能会导致性能问题。如果其中一个深层过滤查询过慢,我们建议构建一个包含优化查询版本的自定义路由。 🌐 Querying your API with deep filters may cause performance issues. If one of your deep filtering queries is too slow, we recommend building a custom route with an optimized version of the query. ::: :::strapi Deep filtering with the various APIs 有关如何使用各种 API 进行深度过滤的示例,请参阅 [this blog article](https://strapi.io/blog/deep-filtering-alpha-26)。 ::: #### GET /api/restaurants?filters[chef][restaurants][stars][$eq]=5 — 寻找由属于五星级餐厅的厨师拥有的餐厅 使用深度过滤来过滤关系的字段。 ```bash curl 'http://localhost:1337/api/restaurants?filters[chef][restaurants][stars][$eq]=5' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ filters: { chef: { restaurants: { stars: { $eq: 5, }, }, }, }, }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "id": 1, "documentId": "cvsz61qg33rtyv1qljb1nrtg", "name": "GORDON RAMSAY STEAK", "stars": 5 }, { "id": 2, "documentId": "uh17h7ibw0g8thit6ivi71d8", "name": "GORDON RAMSAY BURGER", "stars": 5 } ], "meta": {} } ``` :::note 上面的响应反映了默认的 REST 输出,它不包括过滤器所遍历的关系。添加一个 [`populate` 参数](/cms/api/rest/populate-select#population),例如 `&populate[chef][populate][restaurants]=true`,以同时返回过滤器中引用的 `chef.restaurants` 关系。 🌐 The response above mirrors the default REST output, which excludes the relations traversed by the filter. Add a [`populate` parameter](/cms/api/rest/populate-select#population) such as `&populate[chef][populate][restaurants]=true` to also return the `chef.restaurants` relation referenced in the filter. ::: # REST API 指南 Source: https://strapi.nodejs.cn/cms/api/rest/guides/intro # REST API 指南 {#rest-api-guides} 🌐 REST API Guides 探索关于特定 REST API 主题的详细指南和逐步说明,包括访问创作者字段的 populate 参数和自定义控制器。 🌐 Explore detailed guides and step-by-step instructions on specific REST API topics, including populate parameters and custom controllers for accessing creator fields. [REST API 参考](/cms/api/rest) 文档旨在为所有可用的端点和参数提供快速参考。 🌐 The [REST API reference](/cms/api/rest) documentation is meant to provide a quick reference for all the endpoints and parameters available. ## 指南 {#guides} 🌐 Guides 以下指南由 Strapi 文档团队官方维护,涵盖专门主题并为某些用例提供详细说明(用 🧠 表示的指南)或分步说明(用 🛠️ 表示的指南): 🌐 The following guides, officially maintained by the Strapi Documentation team, cover dedicated topics and provide detailed explanations (guides indicated with 🧠) or step-by-step instructions (guides indicated with 🛠️) for some use cases: - [理解填充](/cms/api/rest/guides/understanding-populate): 了解填充(populating)是什么意思,以及如何在你的 REST API 查询中使用 populate 参数向响应中添加额外字段。 - [如何填写创建者字段](/cms/api/rest/guides/populate-creator-fields): 阅读逐步说明,了解如何构建一个自定义控制器,该控制器利用 populate 参数将 ## 附加资源 {#additional-resources} 🌐 Additional resources :::strapi Want to help other users? 本节中列出的一些附加资源是为 Strapi v4 创建的,可能无法完全适用于 Strapi 5。如果你想将以下文章中的某一篇更新为适用于 Strapi 5,请随时 [propose an article](https://strapi.io/write-for-the-community) 加入社区写作计划。 ::: 其他教程和指南可以在以下博客文章中找到: 🌐 Additional tutorials and guides can be found in the following blog posts: - [什么是 REST API?初学者指南 + 使用 Strapi 的示例](https://strapi.io/blog/what-is-a-rest-api-beginners-guide-examples-using-strapi): 了解 REST API 的基本原则以及如何使用 Strapi REST API。 - [使用 REST API 进行请求身份验证](https://strapi.io/blog/guide-on-authenticating-requests-with-the-rest-api): 了解如何使用 JSON Web 令牌和 API 令牌验证你的 REST API 查询。 - [使用 Fetch 调用 Strapi 的内容 API](https://strapi.io/blog/mastering-api-requests-using-fetch-with-strapi-content-api): 探索如何使用 Fetch API 的 fetch() 方法与 Strapi 的内容 API 进行交互。 - [在内容分发网络(CDN)后请求 Strapi 的 REST API](https://strapi.io/blog/request-strapi-s-rest-api-behind-a-content-delivery-network-cdn): 了解如何通过使用 CDN 和 Strapi 的 REST API,在请求大量媒体资源时克服网络延迟问题。 # 如何填写创建者字段 Source: https://strapi.nodejs.cn/cms/api/rest/guides/populate-creator-fields # 如何填写创建者字段,例如 `createdBy` 和 `updatedBy` {#icon-namewrench--how-to-populate-creator-fields-such-as-createdby-and-updatedby} 在内容类型模式中启用 `populateCreatorFields` 选项,并创建路由中间件以在 REST API 响应中包含 `createdBy` 和 `updatedBy` 字段。 🌐 Enable the `populateCreatorFields` option in a content-type schema and create a route middleware to include `createdBy` and `updatedBy` fields in REST API responses. 创建者字段 `createdBy` 和 `updatedBy` 默认从 [REST API](/cms/api/rest) 响应中移除。通过在内容类型级别激活 `populateCreatorFields` 参数,这两个字段可以在 REST API 中返回。 🌐 The creator fields `createdBy` and `updatedBy` are removed from the [REST API](/cms/api/rest) response by default. These 2 fields can be returned in the REST API by activating the `populateCreatorFields` parameter at the content-type level. :::note `populateCreatorFields` 属性在 GraphQL API 中不可用。 🌐 The `populateCreatorFields` property is not available to the GraphQL API. 只有以下字段会被填充:`id`、`firstname`、`lastname`、`username`、`preferedLanguage`、`createdAt` 和 `updatedAt`。 🌐 Only the following fields will be populated: `id`, `firstname`, `lastname`, `username`, `preferedLanguage`, `createdAt`, and `updatedAt`. ::: 将 `createdBy` 和 `updatedBy` 添加到 API 响应中: 🌐 To add `createdBy` and `updatedBy` to the API response: 1. 打开内容类型为 `schema.json` 的文件。 2. 将 `"populateCreatorFields": true` 添加到 `options` 对象中: ```json "options": { "draftAndPublish": true, "populateCreatorFields": true }, ``` 3. 保存 `schema.json`。 4. 可以使用 [generate CLI](/cms/cli.md) 创建一个新的路由中间件,或者通过在 `./src/api/[content-type-name]/middlewares/[your-middleware-name].js` 中手动创建一个新文件来实现 5. 添加以下代码,你可以修改此示例以满足你的需要: ```js title="./src/api/test/middlewares/defaultTestPopulate.js" "use strict"; module.exports = (config, { strapi }) => { return async (ctx, next) => { if (!ctx.query.populate) { ctx.query.populate = ["createdBy", "updatedBy"]; } await next(); }; }; ``` 6. 修改你的默认路由工厂,以在你希望此群体应用的特定路由上启用此中间件,并将内容类型/中间件名称替换为你的内容类型/中间件名称: ```js title="./src/api/test/routes/test.js" "use strict"; const { createCoreRouter } = require("@strapi/strapi").factories; module.exports = createCoreRouter("api::test.test", { config: { find: { middlewares: ["api::test.default-test-populate"], }, findOne: { middlewares: ["api::test.default-test-populate"], }, }, }); ``` 没有 `populate` 参数的 REST API 请求默认会包含 `createdBy` 或 `updatedBy` 字段。 🌐 REST API requests with no `populate` parameter will include the `createdBy` or `updatedBy` fields by default. # 理解填充 Source: https://strapi.nodejs.cn/cms/api/rest/guides/understanding-populate # 理解 REST API 的 `populate` 参数 {#icon-namebrain--understanding-the-populate-parameter-for-the-rest-api} 在 REST API 查询中,`populate` 参数在响应中包含默认属性之外的额外字段、关系、组件和动态区域。使用 `populate=*` 获取所有一级深度关系,或者使用嵌套数组和片段语法显式指定字段以获取更深层或有选择的填充。 🌐 The `populate` parameter in REST API queries includes additional fields, relations, components, and dynamic zones in responses beyond default attributes. Use `populate=*` for all 1-level-deep relations, or explicitly specify fields with nested arrays and fragment syntax for deeper or selective population. :::note Note: Example responses might differ from your experience 此页面的内容可能尚未与 Strapi 5 完全同步: 🌐 The content of this page might not be fully up-to-date with Strapi 5 yet: - 所有概念信息和解释都是正确且最新的。 - 但是,在示例中,响应内容可能略有不同。 示例将在 Strapi 5.0.0(稳定版本)发布之后以及 [FoodAdvisor](https://github.com/strapi/foodadvisor) 示例应用升级到 Strapi 5 后完全更新。 但是,响应示例略有不同不应妨碍你掌握本页所教授的基本概念。 🌐 However, having slightly different response examples should not prevent you from grasping the essential concepts taught in this page. ::: 在使用 Strapi 的 [REST API](/cms/api/rest) 查询内容类型时,默认情况下,响应只包含顶层字段,不包含任何关联、媒体字段、组件或动态区域。 🌐 When querying content-types with Strapi's [REST API](/cms/api/rest), by default, responses only include top-level fields and do not include any relations, media fields, components, or dynamic zones. 在 Strapi REST API 的上下文中,Populating 意味着通过返回比默认返回的字段更多的字段来在响应中包含额外的内容。你可以使用[`populate`参数](/cms/api/rest/populate-select#population)来实现这一点。 🌐 Populating in the context of the Strapi REST API means including additional content with your response by returning more fields than the ones returned by default. You use the [`populate` parameter](/cms/api/rest/populate-select#population) to achieve this. :::info 在本指南中,示例是使用从 [FoodAdvisor](https://github.com/strapi/foodadvisor) 示例应用附带的服务器查询的真实数据构建的。要自己测试示例,请设置 FoodAdvisor,在 `/api/` 文件夹中启动服务器,并在发送查询之前确保为被查询的内容类型授予了适当的 `find` 权限。 ::: 本指南将详细解释以下用例: 🌐 The present guide will cover detailed explanations for the following use cases: - 填充 [所有字段和关系,深度1级](#populate-all-relations-and-fields-1-level-deep), - 填充 [一些字段和关系,1 级深度](#populate-1-level-deep-for-specific-relations), - 填充[一些字段和关系,几层深](#populate-several-levels-deep-for-specific-relations), - 填充 [组件](#populate-components), - 填充[动态区域](#populate-dynamic-zones)。 :::info 填充多层通常被称为“深度填充”。 🌐 Populating several levels deep is often called "deep populate". ::: :::strapi Advanced use case: Populating creator fields 除了在查询中使用 `populate` 参数的各种方法外,你还可以构建自定义控制器作为一种解决方法来填充创建者字段(例如,`createdBy` 和 `updatedBy`)。这在专门的 [如何填充创建者字段](/cms/api/rest/guides/populate-creator-fields) 指南中有说明。 🌐 In addition to the various ways of using the `populate` parameter in your queries, you can also build a custom controller as a workaround to populate creator fields (e.g., `createdBy` and `updatedBy`). This is explained in the dedicated [How to populate creator fields](/cms/api/rest/guides/populate-creator-fields) guide. ::: ## 填充所有关系和字段,深度为1级 {#populate-all-relations-and-fields-1-level-deep} 🌐 Populate all relations and fields, 1 level deep 你可以通过单个查询返回所有关系、媒体字段、组件和动态区域。对于关系,这仅适用于一层深度,以防止性能问题和长时间的响应。 🌐 You can return all relations, media fields, components and dynamic zones with a single query. For relations, this will only work 1 level deep, to prevent performance issues and long response times. 要填充所有一级内容,请在查询中添加 `populate=*` 参数。 🌐 To populate everything 1 level deep, add the `populate=*` parameter to your query. 下图比较了 [FoodAdvisor](https://github.com/strapi/foodadvisor) 示例应用在填充所有一级数据和未填充时返回的数据: ![Diagram with populate use cases with FoodAdvisor data ](/img/assets/rest-api/populate-foodadvisor-diagram1.png) 让我们比较并解释使用和不使用此查询参数时会发生什么: 🌐 Let's compare and explain what happens with and without this query parameter: ### 示例:没有 `populate` {#example-without-populate} 🌐 Example: Without `populate` 如果没有 populate 参数,对 `/api/articles` 的 `GET` 请求只会返回默认属性,不会返回任何媒体字段、关联、组件或动态区域。 🌐 Without the populate parameter, a `GET` request to `/api/articles` only returns the default attributes and does not return any media fields, relations, components or dynamic zones. 以下示例是来自 `articles` 内容类型的所有 4 个条目的完整响应。 🌐 The following example is the full response for all 4 entries from the `articles` content-types. 请注意,响应仅包含 `title`、`slug`、`createdAt`、`updatedAt`、`publishedAt` 和 `locale` 字段,以及由 CKEditor 插件处理的文章字段内容(`ckeditor_content`,为简洁起见已截断): 🌐 Notice how the response only includes the `title`, `slug`, `createdAt`, `updatedAt`, `publishedAt`, and `locale` fields, and the field content of the article as handled by the CKEditor plugin (`ckeditor_content`, truncated for brevity): #### GET /api/articles — 未填充 仅返回默认属性,不包含任何媒体字段、关系、组件或动态区域。 ```bash curl 'http://localhost:1337/api/articles' \ -H 'Authorization: Bearer ' ``` ```js const response = await fetch( 'http://localhost:1337/api/articles', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ```json { "data": [ { "id": 1, "documentId": "t3q2i3v1z2j7o8p6d0o4xxg", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "// truncated content" }, { "id": 2, "documentId": "k2r5l0i9g3u2j3b4p7f0sed", "title": "What are chinese hamburgers and why aren't you eating them?", "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them", "createdAt": "2021-11-11T13:33:19.948Z", "updatedAt": "2023-06-01T14:32:50.984Z", "publishedAt": "2022-09-22T12:36:48.312Z", "locale": "en", "ckeditor_content": "// truncated content" }, { "id": 3, "documentId": "k6m6l9q0n6v9z2m3i0z5jah", "title": "7 Places worth visiting for the food alone", "slug": "7-places-worth-visiting-for-the-food-alone", "createdAt": "2021-11-12T13:33:19.948Z", "updatedAt": "2023-06-02T11:30:00.075Z", "publishedAt": "2023-06-02T11:30:00.075Z", "locale": "en", "ckeditor_content": "// truncated content" }, { "id": 4, "documentId": "d5m4b6z6g5d9e3v1k9n5gbn", "title": "If you don't finish your plate in these countries, you might offend someone", "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone", "createdAt": "2021-11-15T13:33:19.948Z", "updatedAt": "2023-06-02T10:59:35.148Z", "publishedAt": "2022-09-22T12:35:53.899Z", "locale": "en", "ckeditor_content": "// truncated content" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ### 示例:使用 `populate=*` {#example-with-populate} 🌐 Example: With `populate=*` 使用 `populate=*` 参数,对 `/api/articles` 的 `GET` 请求也会返回所有媒体字段、一级关系、组件和动态区域。 🌐 With the `populate=*` parameter, a `GET` request to `/api/articles` also returns all media fields, first-level relations, components and dynamic zones. 以下示例是来自 `articles` 内容类型的所有 4 个条目中的第一个条目的完整响应(id 为 2、3 和 4 的文章数据为了简洁而被截断)。 🌐 The following example is the full response for the first of all 4 entries from the `articles` content-types (the data from articles with ids 2, 3, and 4 is truncated for brevity). 向下滚动查看响应大小比没有使用 populate 时大得多。响应现在包括额外的字段(见高亮行),例如: 🌐 Scroll down to see that the response size is much bigger than without populate. The response now includes additional fields (see highlighted lines) such as: * `image` 媒体字段(存储关于文章封面 的所有信息,包括其所有不同格式), * `blocks` 动态区域和 `seo` 组件的一级字段, * `category` 关系及其字段, * 甚至还有一些关于以其他语言翻译的文章的信息,如 `localizations` 对象所示。 :::tip 要填充深层嵌套的组件,请参阅[填充组件](#populate-components)部分。 🌐 To populate deeply nested components, see the [populate components](#populate-components) section. :::
#### GET /api/articles — 使用 populate=* 返回所有媒体字段、一阶关系、组件和动态区域。 ```bash curl 'http://localhost:1337/api/articles?populate=*' \ -H 'Authorization: Bearer ' ``` ```js const response = await fetch( 'http://localhost:1337/api/articles?populate=*', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ```json { "data": [ { "id": 1, "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "// truncated content", "image": { "data": { "id": 12, "documentId": "o5d4b0l4p8l4o4k5n1l3rxa", "name": "Basque dish", "alternativeText": "Basque dish", "caption": "Basque dish", "width": 758, "height": 506, "formats": { "thumbnail": { "name": "thumbnail_https://4d40-2a01-cb00-c8b-1800-7cbb-7da-ea9d-2011.ngrok.io/uploads/basque_cuisine_17fa4567e0.jpeg", "hash": "thumbnail_basque_cuisine_17fa4567e0_f033424240", "ext": ".jpeg", "mime": "image/jpeg", "width": 234, "height": 156, "size": 11.31, "path": null, "url": "/uploads/thumbnail_basque_cuisine_17fa4567e0_f033424240.jpeg" }, "medium": { "name": "medium_https://4d40-2a01-cb00-c8b-1800-7cbb-7da-ea9d-2011.ngrok.io/uploads/basque_cuisine_17fa4567e0.jpeg", "hash": "medium_basque_cuisine_17fa4567e0_f033424240", "ext": ".jpeg", "mime": "image/jpeg", "width": 750, "height": 501, "size": 82.09, "path": null, "url": "/uploads/medium_basque_cuisine_17fa4567e0_f033424240.jpeg" }, "small": { "name": "small_https://4d40-2a01-cb00-c8b-1800-7cbb-7da-ea9d-2011.ngrok.io/uploads/basque_cuisine_17fa4567e0.jpeg", "hash": "small_basque_cuisine_17fa4567e0_f033424240", "ext": ".jpeg", "mime": "image/jpeg", "width": 500, "height": 334, "size": 41.03, "path": null, "url": "/uploads/small_basque_cuisine_17fa4567e0_f033424240.jpeg" } }, "hash": "basque_cuisine_17fa4567e0_f033424240", "ext": ".jpeg", "mime": "image/jpeg", "size": 58.209999999999994, "url": "/uploads/basque_cuisine_17fa4567e0_f033424240.jpeg", "previewUrl": null, "provider": "local", "provider_metadata": null, "createdAt": "2021-11-23T14:05:33.460Z", "updatedAt": "2021-11-23T14:05:46.084Z" } } }, "blocks": [ { "id": 2, "__component": "blocks.related-articles" }, { "id": 2, "documentId": "w8r5k8o8v0t9l9e0d7y6vco", "__component": "blocks.cta-command-line", "theme": "primary", "title": "Want to give a try to a Strapi starter?", "text": "❤️", "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git" } ], "seo": { "id": 1, "documentId": "h7c8d0u3i3q5v1j3j3r4cxf", "metaTitle": "Articles - FoodAdvisor", "metaDescription": "Discover our articles about food, restaurants, bars and more! - FoodAdvisor", "keywords": "food", "metaRobots": null, "structuredData": null, "metaViewport": null, "canonicalURL": null }, "category": { "data": { "id": 4, "documentId": "t1t3d9k6n1k5a6r8l7f8rox", "name": "European", "slug": "european", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } }, "localizations": { "data": [ { "id": 10, "documentId": "h7c8d0u3i3q5v1j3j3r4cxf", "title": "Voici pourquoi il faut essayer la cuisine basque, selon un chef basque", "slug": "voici-pourquoi-il-faut-essayer-la-cuisine-basque-selon-un-chef-basque", "createdAt": "2021-11-18T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.606Z", "publishedAt": "2022-09-22T13:00:00.069Z", "locale": "fr-FR", "ckeditor_content": "// truncated content" } ] } } }, { "id": 2, "// truncated content": true }, { "id": 3, "// truncated content": true }, { "id": 4, "// truncated content": true } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ## 填充特定的关系和字段 {#populate-specific-relations-and-fields} 🌐 Populate specific relations and fields 你也可以通过明确定义要填充的内容来填充特定的关系和字段。这要求你知道要填充的字段和关系的名称。 🌐 You can also populate specific relations and fields, by explicitly defining what to populate. This requires that you know the name of fields and relations to populate. 通过这种方式填充的关系和字段可以有1级或多级深度。下图比较了当你填充[1级深度](#populate-1-level-deep-for-specific-relations)与[2级深度](#populate-several-levels-deep-for-specific-relations)时, [FoodAdvisor](https://github.com/strapi/foodadvisor) 示例应用返回的数据: ![Diagram with populate use cases with FoodAdvisor data ](/img/assets/rest-api/populate-foodadvisor-diagram2.png) **不同的填充策略但结果相似** 根据你的内容结构,你可能会发现使用不同的查询以不同的方式呈现类似的数据。例如,FoodAdvisor 示例应用包含文章、类别和餐厅内容类型,它们以不同的方式相互关联。这意味着,如果你想在单个 GET 请求中获取关于这三种内容类型的数据,你有两个选择: - 查询文章并填充类别,同时填充类别与餐厅之间的嵌套关系([两级深度填充](#populate-several-levels-deep-for-specific-relations)) - 查询类别并填充文章和餐馆,因为类别与另外两种内容类型有一级关系([1级深](#populate-1-level-deep-for-specific-relations)) 下图说明了这两种不同的策略: 🌐 The 2 different strategies are illustrated in the following diagram: ![Diagram with populate use cases with FoodAdvisor data ](/img/assets/rest-api/populate-foodadvisor-diagram3.png)
作为对象填充与作为数组填充:使用交互式查询构建器 高级查询参数的语法手动构建可能相当复杂。我们建议你使用我们的[交互式查询构建器](/cms/api/rest/interactive-query-builder)工具来生成 URL。 🌐 The syntax for advanced query parameters can be quite complex to build manually. We recommend you use our [interactive query builder](/cms/api/rest/interactive-query-builder) tool to generate the URL. 使用这个工具,你将使用熟悉的(JavaScript)格式编写清晰且易读的请求,这应该有助于你理解不同查询和不同填充方式之间的差异。例如,深度填充 2 级意味着将 populate 用作对象,而深度填充 1 级的多个关系意味着将 populate 用作数组: 🌐 Using this tool, you will write clean and readable requests in a familiar (JavaScript) format, which should help you understand the differences between different queries and different ways of populating. For instance, populating 2 levels deep implies using populate as an object, while populating several relations 1 level deep implies using populate as an array: 填充为对象
(填充 1 个关系多个级别): ```json { populate: { category: { populate: ['restaurants'], }, }, } ``` 填充为数组
(填充许多 1 层深的关系) ```json { populate: [ 'articles', 'restaurants' ], } ```
### 为特定关系填充 1 级深度 {#populate-1-level-deep-for-specific-relations} 🌐 Populate 1 level deep for specific relations 你可以使用 populate 参数作为数组来填充 1 层深度的特定关系。 🌐 You can populate specific relations 1 level deep by using the populate parameter as an array. 由于 REST API 使用 [LHS bracket notation](https://christiangiacomi.com/posts/rest-design-principles/#lhs-brackets) (即带方括号的 `[]`),要填充一级参数的语法如下所示: | 填充多少关系 | 语法示例 | |-------------------------------|--------------------| | 仅 1 个关系 | `populate[0]=a-relation-name` | | 多个关系 | `populate[0]=relation-name&populate[1]=another-relation-name&populate[2]=yet-another-relation-name` | 让我们比较并解释在向 [FoodAdvisor](https://github.com/strapi/foodadvisor) 示例应用发送查询时,填充关系一级与不填充关系一级会发生什么: #### 示例:没有 `populate` {#example-without-populate-1} 🌐 Example: Without `populate` 如果没有 populate 参数,向 `/api/articles` 的 `GET` 请求只会返回默认属性。 🌐 Without the populate parameter, a `GET` request to `/api/articles` only returns the default attributes. 以下示例是 `articles` 内容类型所有 4 个条目的完整响应。 🌐 The following example is the full response for all 4 entries from the `articles` content-type. 请注意,响应不包含任何媒体字段、关系、组件或动态区域: 🌐 Notice that the response does not include any media fields, relations, components or dynamic zones:
#### GET /api/articles — 未填充 仅返回所有文章的默认属性。 ```bash curl 'http://localhost:1337/api/articles' \ -H 'Authorization: Bearer ' ``` ```js const response = await fetch( 'http://localhost:1337/api/articles', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ```json { "data": [ { "id": 1, "documentId": "x2m0d7d9o4m2z3u2r2l9yes", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…" }, { "id": 2, "documentId": "k6m6l9q0n6v9z2m3i0z5jah", "title": "What are chinese hamburgers and why aren't you eating them?", "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them", "createdAt": "2021-11-11T13:33:19.948Z", "updatedAt": "2023-06-01T14:32:50.984Z", "publishedAt": "2022-09-22T12:36:48.312Z", "locale": "en", "ckeditor_content": "…" }, { "id": 3, "documentId": "o5d4b0l4p8l4o4k5n1l3rxa", "title": "7 Places worth visiting for the food alone", "slug": "7-places-worth-visiting-for-the-food-alone", "createdAt": "2021-11-12T13:33:19.948Z", "updatedAt": "2023-06-02T11:30:00.075Z", "publishedAt": "2023-06-02T11:30:00.075Z", "locale": "en", "ckeditor_content": "…" }, { "id": 4, "documentId": "t3q2i3v1z2j7o8p6d0o4xxg", "title": "If you don't finish your plate in these countries, you might offend someone", "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone", "createdAt": "2021-11-15T13:33:19.948Z", "updatedAt": "2023-06-02T10:59:35.148Z", "publishedAt": "2022-09-22T12:35:53.899Z", "locale": "en", "ckeditor_content": "…" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` #### 示例:使用 `populate[0]=category` {#example-with-populate0category} 🌐 Example: With `populate[0]=category` 在请求中添加 `populate[0]=category` 后,我们明确要求包含一些关于 `category` 的信息,`category` 是一个关系字段,用于链接 `articles` 和 `categories` 内容类型。 🌐 With `populate[0]=category` added to the request, we explicitly ask to include some information about `category`, which is a relation field that links the `articles` and the `categories` content-types. 以下示例是 `articles` 内容类型所有 4 个条目的完整响应。 🌐 The following example is the full response for all 4 entries from the `articles` content-type. 请注意,响应现在包含每篇文章的 `category` 字段的额外数据(见高亮行): 🌐 Notice that the response now includes additional data with the `category` field for each article (see highlighted lines): #### GET /api/articles — 使用 populate[0]=category 返回填充了相关类别数据的文章。 ```bash curl 'http://localhost:1337/api/articles?populate[0]=category' \ -H 'Authorization: Bearer ' ``` ```js const response = await fetch( 'http://localhost:1337/api/articles?populate[0]=category', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ```json { "data": [ { "id": 1, "documentId": "w8r5k8o8v0t9l9e0d7y6vco", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 4, "documentId": "u6x8u7o7j5q1l5y3t8j9yxi", "name": "European", "slug": "european", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } }, { "id": 2, "documentId": "k6m6l9q0n6v9z2m3i0z5jah", "title": "What are chinese hamburgers and why aren't you eating them?", "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them", "createdAt": "2021-11-11T13:33:19.948Z", "updatedAt": "2023-06-01T14:32:50.984Z", "publishedAt": "2022-09-22T12:36:48.312Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 13, "documentId": "x2m0d7d9o4m2z3u2r2l9yes", "name": "Chinese", "slug": "chinese", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } }, { "id": 3, "title": "7 Places worth visiting for the food alone", "slug": "7-places-worth-visiting-for-the-food-alone", "createdAt": "2021-11-12T13:33:19.948Z", "updatedAt": "2023-06-02T11:30:00.075Z", "publishedAt": "2023-06-02T11:30:00.075Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 3, "documentId": "h7c8d0u3i3q5v1j3j3r4cxf", "name": "International", "slug": "international", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } }, { "id": 4, "documentId": "t1t3d9k6n1k5a6r8l7f8rox", "title": "If you don't finish your plate in these countries, you might offend someone", "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone", "createdAt": "2021-11-15T13:33:19.948Z", "updatedAt": "2023-06-02T10:59:35.148Z", "publishedAt": "2022-09-22T12:35:53.899Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 3, "documentId": "u6x8u7o7j5q1l5y3t8j9yxi", "name": "International", "slug": "international", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ### 为特定关系填充多层数据 {#populate-several-levels-deep-for-specific-relations} 🌐 Populate several levels deep for specific relations 你还可以填充特定的多层关系。例如,当你填充一个关系,而该关系本身又填充另一个关系时,你就是在填充两层深的关系。在本指南中介绍的示例就是填充两层深的关系。 🌐 You can also populate specific relations several levels deep. For instance, when you populate a relation which itself populates another relation, you are populating 2 levels deep. Populating 2 levels deep is the example covered in this guide. :::caution 可以填充的层级数量没有限制。然而,填充的层级越深,请求执行所需的时间就越长。 🌐 There is no limit on the number of levels that can be populated. However, the deeper the populates, the more the request will take time to be performed. ::: 由于 REST API 使用 [LHS bracket notation](https://christiangiacomi.com/posts/rest-design-principles/#lhs-brackets)(即使用方括号 `[]`),例如,如果你想填充嵌套在另一个关系中的关系,参数语法如下所示: `populate[first-level-relation-to-populate][populate][0]=second-level-relation-to-populate` :::tip 高级查询参数的语法手动构建起来可能相当复杂。我们建议你使用我们的[交互式查询构建器](/cms/api/rest/interactive-query-builder)工具来生成 URL。例如,以下示例中使用的 `/api/articles?populate[category][populate][0]=restaurants` URL 是通过使用我们的工具将以下对象转换生成的: 🌐 The syntax for advanced query parameters can be quite complex to build manually. We recommend you use our [interactive query builder](/cms/api/rest/interactive-query-builder) tool to generate the URL. For instance, the `/api/articles?populate[category][populate][0]=restaurants` URL used in the following examples has been generated by converting the following object using our tool: ```json { populate: { category: { populate: ['restaurants'], }, }, } ``` ::: 该 [FoodAdvisor](https://github.com/strapi/foodadvisor) 示例应用包含内容类型之间的各种级别的关系。例如: - 一个 `article` 内容类型包括与 `category` 内容类型的关系, - 但是 `category` 也可以分配给任何 `restaurant` 内容类型。 通过对 `/api/articles` 发出一次 `GET` 请求并使用适当的 populate 参数,你可以同时返回关于文章、餐厅和类别的信息。 🌐 With a single `GET` request to `/api/articles` and the appropriate populate parameters, you can return information about articles, restaurants, and categories simultaneously. 让我们比较并解释在向 FoodAdvisor 发送查询时,`populate[0]=category`(1 级深度)和 `populate[category][populate][0]=restaurants`(2 级深度)返回的响应: 🌐 Let's compare and explain the responses returned with `populate[0]=category` (1 level deep) and `populate[category][populate][0]=restaurants` (2 levels deep) when sending queries to FoodAdvisor: #### 示例:具有 1 级深度的人口 {#example-with-1-level-deep-population} 🌐 Example: With 1-level deep population 当我们只填充一层深度时,查询与文章相关的类别,我们可以得到以下示例响应(高亮行显示 `category` 关系字段): 🌐 When we only populate 1 level deep, asking for the categories associated to articles, we can get the following example response (highlighted lines show the `category` relations field): #### GET /api/articles — 具有1级深度的人口 填充类别关系,深入1级。 ```bash curl 'http://localhost:1337/api/articles?populate[0]=category' \ -H 'Authorization: Bearer ' ``` ```js const response = await fetch( 'http://localhost:1337/api/articles?populate[0]=category', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ```json { "data": [ { "id": 1, "documentId": "9ih6hy1bnma3q3066kdwt3", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 4, "name": "European", "slug": "european", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } }, { "id": 2, "documentId": "sen6qfgxcac13pwchf8xbu", "title": "What are chinese hamburgers and why aren't you eating them?", "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them", "createdAt": "2021-11-11T13:33:19.948Z", "updatedAt": "2023-06-01T14:32:50.984Z", "publishedAt": "2022-09-22T12:36:48.312Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 13, "documentId": "r3rhzcxd7gjx07vkq3pia5", "name": "Chinese", "slug": "chinese", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } }, { "id": 3, "documentId": "s9uu7rkukhfcsmj2e60b67", "title": "7 Places worth visiting for the food alone", "slug": "7-places-worth-visiting-for-the-food-alone", "createdAt": "2021-11-12T13:33:19.948Z", "updatedAt": "2023-06-02T11:30:00.075Z", "publishedAt": "2023-06-02T11:30:00.075Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 3, "documentId": "4sevz15w6bdol6y4t8kblk", "name": "International", "slug": "international", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } }, { "id": 4, "documentId": "iy5ifm3xj8q0t8vlq6l23h", "title": "If you don't finish your plate in these countries, you might offend someone", "slug": "if-you-don-t-finish-your-plate-in-these-countries-you-might-offend-someone", "createdAt": "2021-11-15T13:33:19.948Z", "updatedAt": "2023-06-02T10:59:35.148Z", "publishedAt": "2022-09-22T12:35:53.899Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 3, "documentId": "0eor603u8qej933maphdv3", "name": "International", "slug": "international", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z" } } } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` #### 示例:具有两级深度的人口 {#example-with-2-level-deep-population} 🌐 Example: With 2-level deep population 当我们填充 2 层深度时,询问与文章相关的类别,以及与这些类别相关的餐馆,我们可以得到以下示例响应。 🌐 When we populate 2 levels deep, asking for the categories associated to articles, but also for restaurants associated to these categories, we can get the following example response. 请注意,我们现在在 `category` 关系内的响应中包含了 `restaurants` 关系字段(见高亮行): 🌐 Notice that we now have the `restaurants` relation field included with the response inside the `category` relation (see highlighted lines): #### GET /api/articles — 具有两级深度的人口 填充类别关系和嵌套的餐厅关系,深度为2级。 ```bash curl 'http://localhost:1337/api/articles?populate[category][populate][0]=restaurants' \ -H 'Authorization: Bearer ' ``` ```js const response = await fetch( 'http://localhost:1337/api/articles?populate[category][populate][0]=restaurants', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ```json { "data": [ { "id": 1, "documentId": "iy5ifm3xj8q0t8vlq6l23h", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…", "category": { "data": { "id": 4, "name": "European", "slug": "european", "createdAt": "2021-11-09T13:33:20.123Z", "updatedAt": "2021-11-09T13:33:20.123Z", "restaurants": { "data": [ { "id": 1, "documentId": "ozlqrdxpnjb7wtvf6lp74v", "name": "Mint Lounge", "slug": "mint-lounge", "price": "p3", "createdAt": "2021-11-09T14:07:47.125Z", "updatedAt": "2021-11-23T16:41:30.504Z", "publishedAt": "2021-11-23T16:41:30.501Z", "locale": "en" }, { "id": 9, "// truncated content": true }, { "id": 10, "// truncated content": true }, { "id": 12, "// truncated content": true }, { "id": 21, "// truncated content": true }, { "id": 26, "// truncated content": true } ] } } } }, { "id": 2, "// truncated content": true }, { "id": 3, "// truncated content": true }, { "id": 4, "// truncated content": true } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ### 填充组件 {#populate-components} 🌐 Populate components 默认情况下,组件和动态区域不包含在响应中,你需要显式填充每个动态区域、组件及其嵌套组件。 🌐 Components and dynamic zones are not included in responses by default and you need to explicitly populate each dynamic zones, components, and their nested components. 由于 REST API 使用 [LHS bracket notation](https://christiangiacomi.com/posts/rest-design-principles/#lhs-brackets)(即带方括号的 `[]`),你需要在 `populate` 数组中传递所有元素。嵌套字段也可以传递,参数语法可能如下所示: `populate[0]=a-first-field&populate[1]=a-second-field&populate[2]=a-third-field&populate[3]=a-third-field.a-nested-field&populate[4]=a-third-field.a-nested-component.a-nested-field-within-the-component` :::tip 高级查询参数的语法手动构建起来可能相当复杂。我们建议你使用我们的[交互式查询构建器](/cms/api/rest/interactive-query-builder)工具来生成 URL。例如,以下示例中使用的 `/api/articles?populate[0]=seo&populate[1]=seo.metaSocial&populate[2]=seo.metaSocial.image` URL 是通过使用我们的工具将以下对象转换生成的: 🌐 The syntax for advanced query parameters can be quite complex to build manually. We recommend you use our [interactive query builder](/cms/api/rest/interactive-query-builder) tool to generate the URL. For instance, the `/api/articles?populate[0]=seo&populate[1]=seo.metaSocial&populate[2]=seo.metaSocial.image` URL used in the following examples has been generated by converting the following object using our tool: ```json { populate: [ 'seoData', 'seoData.sharedImage', 'seoData.sharedImage.media', ], }, ``` ::: 该 [FoodAdvisor](https://github.com/strapi/foodadvisor) 示例应用包括各种组件,甚至包括嵌套在其他组件中的组件。例如: - 一个 `article` 内容类型包括一个 `seo` 组件 , - `seo` 组件包含一个嵌套的、可重复的 `metaSocial` 组件 , - 而 `metaSocial` 组件本身有几个字段,包括一个 `image` 媒体字段 。 ![FoodAdvisor's SEO component structure in the Content-Type Builder](/img/assets/rest-api/ctb-article-components-structure.png) 默认情况下,这些字段或组件都不会包含在对 `/api/articles` 的 `GET` 请求响应中。但通过适当的 populate 参数,你可以在一次请求中返回它们所有。 🌐 By default, none of these fields or components are included in the response of a `GET` request to `/api/articles`. But with the appropriate populate parameters, you can return all of them in a single request. 让我们比较并解释通过 `populate[0]=seo`(一级组件)和 `populate[0]=seo&populate[1]=seo.metaSocial`(嵌套在一级组件中的二级组件)返回的响应: 🌐 Let's compare and explain the responses returned with `populate[0]=seo` (1st level component) and `populate[0]=seo&populate[1]=seo.metaSocial` (2nd level component nested within the 1st level component): #### 示例:仅第一级组件 {#example-only-1st-level-component} 🌐 Example: Only 1st level component 当我们只填充 `seo` 组件时,我们只深入 1 级,并且可以得到以下示例响应。高亮的行显示了 `seo` 组件。 🌐 When we only populate the `seo` component, we go only 1 level deep, and we can get the following example response. Highlighted lines show the `seo` component. 注意没有提到嵌套在 `seo` 组件中的 `metaSocial` 组件: 🌐 Notice there's no mention of the `metaSocial` component nested within the `seo` component: #### GET /api/articles — 仅一级组件 仅填充 SEO 组件,深度为1级。 ```bash curl 'http://localhost:1337/api/articles?populate[0]=seo' \ -H 'Authorization: Bearer ' ``` ```js const response = await fetch( 'http://localhost:1337/api/articles?populate[0]=seo', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ```json { "data": [ { "id": 1, "documentId": "md60m5cy3dula5g87x1uar", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…", "seo": { "id": 1, "documentId": "kqcwhq6hes25kt9ebj8x7j", "metaTitle": "Articles - FoodAdvisor", "metaDescription": "Discover our articles about food, restaurants, bars and more! - FoodAdvisor", "keywords": "food", "metaRobots": null, "structuredData": null, "metaViewport": null, "canonicalURL": null } }, { "id": 2, "// truncated content": true }, { "id": 3, "// truncated content": true }, { "id": 4, "// truncated content": true } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` #### 示例:一级组件和二级组件 {#example-1st-level-and-2nd-level-component} 🌐 Example: 1st level and 2nd level component 当我们填充两层深度时,同时请求 `seo` 组件和嵌套在 `seo` 内的 `metaSocial` 组件,我们可以得到以下示例响应。 🌐 When we populate 2 levels deep, asking both for the `seo` component and the `metaSocial` component nested inside `seo`, we can get the following example response. 请注意,我们现在在响应中包含了 `metaSocial` 组件相关的数据(见高亮行): 🌐 Notice that we now have the `metaSocial` component-related data included with the response (see highlighted lines): #### GET /api/articles — 一级组件和二级组件 填充 seo 组件和嵌套的 metaSocial 组件。 ```bash curl 'http://localhost:1337/api/articles?populate[0]=seo&populate[1]=seo.metaSocial' \ -H 'Authorization: Bearer ' ``` ```js const response = await fetch( 'http://localhost:1337/api/articles?populate[0]=seo&populate[1]=seo.metaSocial', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ```json { "data": [ { "id": 1, "documentId": "c2imt19iywk27hl2ftph7s", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "…", "seo": { "id": 1, "documentId": "e8cnux5ejxyqrejd5addfv", "metaTitle": "Articles - FoodAdvisor", "metaDescription": "Discover our articles about food, restaurants, bars and more! - FoodAdvisor", "keywords": "food", "metaRobots": null, "structuredData": null, "metaViewport": null, "canonicalURL": null, "metaSocial": [ { "id": 1, "documentId": "ks7xsp9fewoi0qljcz9qa0", "socialNetwork": "Facebook", "title": "Browse our best articles about food and restaurants ", "description": "Discover our articles about food, restaurants, bars and more!" } ] } }, { "id": 2, "// truncated content": true }, { "id": 3, "// truncated content": true }, { "id": 4, "// truncated content": true } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ### 填充动态区域 {#populate-dynamic-zones} 🌐 Populate dynamic zones 动态区域本质上是高度动态的内容结构。在查询动态区域时,标准填充参数(例如 `populate[0]=dynamic-zone-name` 或 `populate=*`)只会获取动态区域内组件的默认标量字段(例如字符串、数字、布尔值)。默认情况下,它们**不会**填充这些组件内部的嵌套关系、媒体字段或嵌套组件。 🌐 Dynamic zones are highly dynamic content structures by essence. When querying dynamic zones, standard populate parameters (such as `populate[0]=dynamic-zone-name` or `populate=*`) will only fetch the default scalar fields (e.g., strings, numbers, booleans) of the components within the dynamic zone. By default, they will **not** populate nested relations, media fields, or nested components inside those components. 要检索特定组件的嵌套关系、媒体字段或动态区域内的组件,必须使用 `on` 属性(片段填充语法)为每个组件定义填充查询。这是因为动态区域中的不同组件可能具有完全不同的结构,并且需要它们自己的独特嵌套查询。 🌐 To retrieve component-specific nested relations, media fields, or components within a dynamic zone, you must define per-component populate queries using the `on` property (fragment population syntax). This is because different components in a dynamic zone can have completely different structures, and require their own unique nested queries. 例如,在 [FoodAdvisor](https://github.com/strapi/foodadvisor) 示例应用中: - `article` 内容类型上存在一个 `blocks` 动态区域 。 - 动态区域包括3个不同的组件:`relatedArticles` 、`faq` 和 `CtaCommandLine` 。所有组件都有不同的内容结构,包含各种字段。 - `relatedArticles` 组件与文章内容类型具有 `articles` 关系 。 ![FoodAdvisor's 'blocks' dynamic zone structure in the Content-Type Builder](/img/assets/rest-api/ctb-blocks-dynamic-zone-structure-2.png) 默认情况下,对 `/api/articles` 发起的 `GET` 请求的响应中不会包含任何深度嵌套的字段或关系。通过使用适当的 populate 参数并应用使用 `on` 属性的详细填充策略,你可以返回精确所需的数据。 🌐 By default, none of the deeply nested fields or relations are included in the response of a `GET` request to `/api/articles`. With the appropriate populate parameters and by applying a detailed population strategy using the fragment `on` property, you can return precisely the data you need. :::tip 高级查询参数的语法手动构建起来可能相当复杂。我们建议你使用我们的[交互式查询构建器](/cms/api/rest/interactive-query-builder)工具来生成 URL。例如,以下示例中使用的 `/api/articles?populate[blocks][on][blocks.related-articles][populate][articles][populate][0]=image&populate[blocks][on][blocks.cta-command-line][populate]=*` URL 是通过使用我们的工具将以下对象转换生成的: 🌐 The syntax for advanced query parameters can be quite complex to build manually. We recommend you use our [interactive query builder](/cms/api/rest/interactive-query-builder) tool to generate the URL. For instance, the `/api/articles?populate[blocks][on][blocks.related-articles][populate][articles][populate][0]=image&populate[blocks][on][blocks.cta-command-line][populate]=*` URL used in the following example has been generated by converting the following object using our tool: ```json { populate: { blocks: { // asking to populate the blocks dynamic zone on: { // using a detailed population strategy to explicitly define what you want 'blocks.related-articles': { populate: { 'articles': { populate: ['image'] } } }, 'blocks.cta-command-line': { populate: '*' } }, }, }, } ``` ::: 让我们通过共享人口策略和详细人口策略的一些示例来比较和解释返回的响应: 🌐 Let's compare and explain the responses returned with some examples of a shared population strategy and a detailed population strategy: #### 例子 {#example} 🌐 Example 当我们填充 `blocks` 动态区域时,我们会明确规定要填充哪些数据。 🌐 When we populate the `blocks` dynamic zone, we explicitly define which data to populate. 在以下示例响应中,高亮的行显示: 🌐 In the following example response, highlighted lines show that: - 我们深入填充了 `relatedArticles` 组件的 `articles` 关联,甚至使用片段(`on`)填充相关文章的 `image` 媒体字段。 - 但是因为我们只要求为 `CtaCommandLine` 组件填充所有内容,并且没有为 `faq` 组件定义任何内容,所以没有返回来自 `faq` 组件的数据。 #### GET /api/articles — 动态区域的详细人口策略 使用详细的逐组件填充策略来填充区块的动态区域。 ```bash curl 'http://localhost:1337/api/articles?populate[blocks][on][blocks.related-articles][populate][articles][populate][0]=image&populate[blocks][on][blocks.cta-command-line][populate]=*' \ -H 'Authorization: Bearer ' ``` ```js const response = await fetch( 'http://localhost:1337/api/articles?populate[blocks][on][blocks.related-articles][populate][articles][populate][0]=image&populate[blocks][on][blocks.cta-command-line][populate]=*', { headers: { Authorization: 'Bearer ', }, } ); const data = await response.json(); ``` ```json { "data": [ { "id": 1, "documentId": "it9bbhcgc6mcfsqas7h1dp", "title": "Here's why you have to try basque cuisine, according to a basque chef", "slug": "here-s-why-you-have-to-try-basque-cuisine-according-to-a-basque-chef", "createdAt": "2021-11-09T13:33:19.948Z", "updatedAt": "2023-06-02T10:57:19.584Z", "publishedAt": "2022-09-22T09:30:00.208Z", "locale": "en", "ckeditor_content": "// truncated content", "blocks": [ { "id": 2, "documentId": "e8cnux5ejxyqrejd5addfv", "__component": "blocks.related-articles", "articles": { "data": [ { "id": 2, "documentId": "wkgojrcg5bkz8teqx1foz7", "title": "What are chinese hamburgers and why aren't you eating them?", "slug": "what-are-chinese-hamburgers-and-why-aren-t-you-eating-them", "createdAt": "2021-11-11T13:33:19.948Z", "updatedAt": "2023-06-01T14:32:50.984Z", "publishedAt": "2022-09-22T12:36:48.312Z", "locale": "en", "ckeditor_content": "// truncated content", "image": { "data": { "// …": true } } } }, { "id": 3, "// …": true }, { "id": 4, "// …": true } ] } }, { "id": 2, "__component": "blocks.cta-command-line", "theme": "primary", "title": "Want to give a try to a Strapi starter?", "text": "❤️", "commandLine": "git clone https://github.com/strapi/nextjs-corporate-starter.git" } ] }, { "id": 2, "// …": true }, { "id": 3, "documentId": "z5jnfvyuj07fogzh1kcbd3", "title": "7 Places worth visiting for the food alone", "slug": "7-places-worth-visiting-for-the-food-alone", "createdAt": "2021-11-12T13:33:19.948Z", "updatedAt": "2023-06-02T11:30:00.075Z", "publishedAt": "2023-06-02T11:30:00.075Z", "locale": "en", "ckeditor_content": "// truncated content", "blocks": [ { "id": 1, "documentId": "ks7xsp9fewoi0qljcz9qa0", "__component": "blocks.related-articles", "articles": { "// …": true } }, { "id": 1, "documentId": "c2imt19iywk27hl2ftph7s", "__component": "blocks.cta-command-line", "theme": "secondary", "title": "Want to give it a try with a brand new project?", "text": "Up & running in seconds 🚀", "commandLine": "npx create-strapi-app my-project --quickstart" } ] }, { "id": 4, "// …": true } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` :::tip Avoid over-populating in production 使用 `populate=*` 或深度填充插件可能会产生不可预测且代价高昂的数据库查询。在生产环境中,始终显式填充并将深度限制为 2-3 级。考虑使用路由级中间件来集中填充逻辑。详见 Strapi 博客上的 [Building High-Performance Strapi Applications](https://strapi.io/blog/building-high-performance-strapi-applications-common-pitfalls-and-best-practices) 。 ::: # 交互式查询构建器 Source: https://strapi.nodejs.cn/cms/api/rest/interactive-query-builder # 使用 Strapi 的交互式工具构建你的查询 URL {#build-your-query-url-with-strapis-interactive-tool} 🌐 Build your query URL with Strapi's interactive tool 一个交互式查询生成工具,可以从你的端点和参数自动生成 REST API 查询 URL,由 `qs` 库提供支持以处理复杂的嵌套查询。 🌐 An interactive query builder tool that automatically generates REST API query URLs from your endpoint and parameters, powered by the `qs` library to handle complex nested queries. 可以使用并组合各种参数通过[REST API](/cms/api/rest)查询你的内容,这可能会导致长且复杂的查询URL。 🌐 A wide range of parameters can be used and combined to query your content with the [REST API](/cms/api/rest), which can result in long and complex query URLs. Strapi 的代码库使用 [`qs` 库](https://github.com/ljharb/qs) 来解析和序列化嵌套的 JavaScript 对象。建议直接使用 `qs` 来生成复杂的查询 URL,而不是手动创建它们。 🌐 Strapi's codebase uses [the `qs` library](https://github.com/ljharb/qs) to parse and stringify nested JavaScript objects. It's recommended to use `qs` directly to generate complex query URLs instead of creating them manually. 你可以使用以下交互式查询构建器工具自动生成查询 URL: 🌐 You can use the following interactive query builder tool to generate query URLs automatically: 1. 将 _Endpoint_ 和 _Endpoint Query Parameters_ 字段中的值替换为适合你需求的内容。 2. 点击 **复制到剪贴板** 按钮以复制自动生成的 _查询字符串 URL_,该 URL 会随着你输入而更新。 :::info Parameters usage 请参阅[REST API 参数表](/cms/api/rest/parameters)并阅读相应的参数文档页面,以更好地理解参数的使用。 🌐 Please refer to the [REST API parameters table](/cms/api/rest/parameters) and read the corresponding parameters documentation pages to better understand parameters usage. :::


:::note 默认的端点路径以 `/api/` 为前缀,除非你使用 [ `rest.prefix` API 配置选项](/cms/configurations/api) 配置了不同的 API 前缀,否则应保持不变。
例如,要使用默认 API 前缀查询 `books` 集合类型,请在 _Endpoint_ 字段中输入 `/api/books`。 ::: :::caution Disclaimer 本页面提供的 `qs` 库和交互式查询构建器: 🌐 The `qs` library and the interactive query builder provided on this page: - 可能无法检测到所有语法错误, - 不知道 Strapi 项目中可用的参数和值, - 并且不提供自动补齐功能。 目前,这些工具仅用于将 JavaScript 对象转换为内联查询字符串 URL。使用生成的查询 URL 并不保证你的 API 能返回正确的结果。 🌐 Currently, these tools are only provided to transform the JavaScript object in an inline query string URL. Using the generated query URL does not guarantee that proper results will get returned with your API. ::: # 本地化 Source: https://strapi.nodejs.cn/cms/api/rest/locale # REST API:`locale` {#rest-api-locale} 🌐 REST API: `locale` `locale` REST API 参数用于检索和管理特定语言的文档,默认使用应用的默认语言环境。使用它可以获取、创建、更新和删除集合类型和单一类型文档的特定语言版本。 🌐 The `locale` REST API parameter retrieves and manages documents in specific languages, defaulting to the application's default locale. Use it to fetch, create, update, and delete locale-specific versions of documents in both collection and single types. [国际化 (i18n) 功能](/cms/features/internationalization) 为 [REST API](/cms/api/rest) 添加了新功能。 🌐 The [Internationalization (i18n) feature](/cms/features/internationalization) adds new abilities to the [REST API](/cms/api/rest). :::prerequisites 要使用某个区域设置的 API 内容,请确保该区域设置已在 Strapi 管理面板中 [添加](/cms/features/internationalization#settings)。 🌐 To work with API content for a locale, please ensure the locale has been already [added to Strapi in the admin panel](/cms/features/internationalization#settings). ::: `locale` [API 参数](/cms/api/rest/parameters) 可用于仅处理特定语言环境的文档。`locale` 以语言环境代码作为值(见 [full list of available locales](https://github.com/strapi/strapi/blob/main/packages/plugins/i18n/server/src/constants/iso-locales.json))。 :::tip 如果未定义 `locale` 参数,它将被设置为默认语言环境。创建新的 Strapi 项目时,`en` 是默认语言环境,但可以在管理面板中将另一种语言环境 [设置为默认语言环境](/cms/features/internationalization#settings)。 🌐 If the `locale` parameter is not defined, it will be set to the default locale. `en` is the default locale when a new Strapi project is created, but another locale can be [set as the default locale](/cms/features/internationalization#settings) in the admin panel. 例如,默认情况下,对 `/api/restaurants` 的 GET 请求将返回与对 `/api/restaurants?locale=en` 的请求相同的响应。 🌐 For instance, by default, a GET request to `/api/restaurants` will return the same response as a request to `/api/restaurants?locale=en`. ::: 下表列出了 i18n 添加到 REST API 的新可能用例并给出了语法示例(你可以单击请求跳转到包含更多详细信息的相应部分): 🌐 The following table lists the new possible use cases added by i18n to the REST API and gives syntax examples (you can click on requests to jump to the corresponding section with more details): | 用例 | 语法示例
及更多信息链接 | |---------|-------| | 获取特定语言环境的所有文档 | [`GET /api/restaurants?locale=fr`](#rest-get-all) | | 获取文档的特定语言环境版本 | [`GET /api/restaurants/abcdefghijklmno456?locale=fr`](#get-one-collection-type) | | 为默认语言环境创建新文档 | [`POST /api/restaurants`](#rest-create-default-locale)
+ 在请求正文中传递属性 | | 为特定语言环境创建新文档 | [`POST /api/restaurants?locale=fr`](#rest-create-specific-locale)
+ 在请求正文中传递属性 | | 为现有文档创建新的或更新现有的语言环境版本 | [`PUT /api/restaurants/abcdefghijklmno456?locale=fr`](#rest-put-collection-type)
+ 在请求正文中传递属性 | | 删除文档的特定语言环境版本 | [`DELETE /api/restaurants/abcdefghijklmno456?locale=fr`](#rest-delete-collection-type) | | 用例 | 语法示例
及更多信息链接 | |----------------------------------------------|--------------------------------------------------| | 获取文档的特定语言版本 | [`GET /api/homepage?locale=fr`](#get-one-single-type) | | 为现有文档创建新的或更新现有的语言版本 | [`PUT /api/homepage?locale=fr`](#rest-put-single-type)
+ 在请求主体中传递属性 | | 删除文档的特定语言版本 | [`DELETE /api/homepage?locale=fr`](#rest-delete-single-type) | ### `GET` 获取特定语言环境中的所有文档 {#rest-get-all} 🌐 `GET` Get all documents in a specific locale #### GET /api/restaurants?locale=fr — 获取特定语言环境下的所有文档 返回给定语言环境的所有文档。 ```bash curl 'http://localhost:1337/api/restaurants?locale=fr' \ -H 'Authorization: Bearer ' ``` ```json { "data": [ { "id": 5, "documentId": "h90lgohlzfpjf3bvan72mzll", "Title": "Meilleures pizzas", "Body": [ { "type": "paragraph", "children": [ { "type": "text", "text": "On déguste les meilleures pizzas de la ville à la Pizzeria Arrivederci." } ] } ], "createdAt": "2024-03-06T22:08:59.643Z", "updatedAt": "2024-03-06T22:10:21.127Z", "publishedAt": "2024-03-06T22:10:21.130Z", "locale": "fr" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` ### `GET` 获取特定语言环境的文档 {#rest-get} 🌐 `GET` Get a document in a specific locale 要在指定的区域获取特定文档,请在查询中添加 `locale` 参数: 🌐 To get a specific document in a given locale, add the `locale` parameter to the query: | 使用场景 | 语法格式及更多信息链接 | | --- | --- | | 在集合类型中 | [`GET /api/content-type-plural-name/document-id?locale=locale-code`](#get-one-collection-type) | | 在单一类型中 | [`GET /api/content-type-singular-name?locale=locale-code`](#get-one-single-type) | #### 集合类型 {#get-one-collection-type} 🌐 Collection types 要在给定语言环境中获取集合类型中的特定文档,请在 `documentId` 之后将 `locale` 参数添加到查询中: 🌐 To get a specific document in a collection type in a given locale, add the `locale` parameter to the query, after the `documentId`: #### GET /api/restaurants/:documentId?locale=fr — 获取特定区域(集合类型)的文档 返回特定区域设置的集合类型中的特定文档。 ```bash curl 'http://localhost:1337/api/restaurants/lr5wju2og49bf820kj9kz8c3?locale=fr' \ -H 'Authorization: Bearer ' ``` ```json { "data": [ { "id": 22, "documentId": "lr5wju2og49bf820kj9kz8c3", "Name": "Biscotte Restaurant", "Description": [ { "type": "paragraph", "children": [ { "type": "text", "text": "Bienvenue au restaurant Biscotte! Le Restaurant Biscotte propose une cuisine à base de produits frais et de qualité, souvent locaux, biologiques lorsque cela est possible, et toujours produits par des producteurs passionnés." } ] } ], "locale": "fr" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 3 } } } ``` #### 单类型 {#get-one-single-type} 🌐 Single types 要在指定区域获取特定单一类型的文档,请在单一类型名称后将 `locale` 参数添加到查询中: 🌐 To get a specific single type document in a given locale, add the `locale` parameter to the query, after the single type name: #### GET /api/homepage?locale=fr — 获取特定区域的文档(单一类型) 返回给定区域设置的特定单类型文档。 ```bash curl 'http://localhost:1337/api/homepage?locale=fr' \ -H 'Authorization: Bearer ' ``` ```json { "data": { "id": 10, "documentId": "ukbpbnu8kbutpn98rsanyi50", "Title": "Page d'accueil", "Body": null, "createdAt": "2024-03-07T13:28:26.349Z", "updatedAt": "2024-03-07T13:28:26.349Z", "publishedAt": "2024-03-07T13:28:26.353Z", "locale": "fr" }, "meta": {} } ``` ### `POST` 为集合类型创建新的本地化文档 {#rest-create} 🌐 `POST` Create a new localized document for a collection type 要从头创建本地化文档,请向内容 API 发送 POST 请求。根据你是要为默认语言创建文档还是为其他语言创建文档,你可能需要在查询中传递 `locale` 参数。 🌐 To create a localized document from scratch, send a POST request to the Content API. Depending on whether you want to create it for the default locale or for another locale, you might need to pass the `locale` parameter in the query. | 使用案例 | 语法格式及更多信息链接 | | --- | --- | | 为默认语言创建 | [`POST /api/content-type-plural-name`](#rest-create-default-locale) | | 为特定语言创建 | [`POST /api/content-type-plural-name?locale=fr`](#rest-create-specific-locale) | #### 对于默认区域设置 {#rest-create-default-locale} 🌐 For the default locale 如果请求主体中未传递任何语言环境,则使用应用的默认语言环境创建文档: 🌐 If no locale has been passed in the request body, the document is created using the default locale for the application: #### POST /api/restaurants — 为默认语言环境创建文档 使用默认语言环境创建新文档。 ```bash curl -X POST \ 'http://localhost:1337/api/restaurants' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "data": { "Name": "Oplato" } }' ``` ```json { "data": { "id": 13, "documentId": "jae8klabhuucbkgfe2xxc5dj", "Name": "Oplato", "Description": null, "createdAt": "2024-03-06T22:19:54.646Z", "updatedAt": "2024-03-06T22:19:54.646Z", "publishedAt": "2024-03-06T22:19:54.649Z", "locale": "en" }, "meta": {} } ``` #### 针对特定区域 {#rest-create-specific-locale} 🌐 For a specific locale 要为与默认语言不同的语言环境创建本地化条目,请在 POST 请求的查询 URL 中添加 `locale` 参数: 🌐 To create a localized entry for a locale different from the default one, add the `locale` parameter to the query URL of the POST request: #### POST /api/restaurants?locale=fr — 为特定地区创建文档 为指定的语言环境创建一个新文档。 ```bash curl -X POST \ 'http://localhost:1337/api/restaurants?locale=fr' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "data": { "Name": "She'\''s Cake" } }' ``` ```json { "data": { "id": 15, "documentId": "ldcmn698iams5nuaehj69j5o", "Name": "She's Cake", "Description": null, "createdAt": "2024-03-06T22:21:18.373Z", "updatedAt": "2024-03-06T22:21:18.373Z", "publishedAt": "2024-03-06T22:21:18.378Z", "locale": "fr" }, "meta": {} } ``` ### `PUT` 为现有文档创建新的本地化版本,或更新现有的本地化版本 {#rest-update} 🌐 `PUT` Create a new, or update an existing, locale version for an existing document 通过向现有文档发送 `PUT` 请求,你可以: 🌐 With `PUT` requests sent to an existing document, you can: - 创建文档的另一个语言环境版本, - 或更新文档的现有语言环境版本。 将 `PUT` 请求发送到相应的 URL,在查询 URL 中添加 `locale=your-locale-code` 参数,并在请求的主体中通过 `data` 对象传递属性: 🌐 Send the `PUT` request to the appropriate URL, adding the `locale=your-locale-code` parameter to the query URL and passing attributes in a `data` object in the request's body: | 用例 | 语法格式及更多信息链接 | | --- | --- | | 在集合类型中 | [`PUT /api/content-type-plural-name/document-id?locale=locale-code`](#rest-put-collection-type) | | 在单一类型中 | [`PUT /api/content-type-singular-name?locale=locale-code`](#rest-put-single-type) | :::caution 为现有本地化条目创建本地化时,请求正文只能接受本地化字段。 🌐 When creating a localization for existing localized entries, the body of the request can only accept localized fields. ::: :::tip Content-Type 应启用 [`createLocalization` 权限](/cms/features/rbac#collection-and-single-types),否则请求将返回 `403: Forbidden` 状态。 🌐 The Content-Type should have the [`createLocalization` permission](/cms/features/rbac#collection-and-single-types) enabled, otherwise the request will return a `403: Forbidden` status. ::: :::note 无法更改现有本地化条目的区域设置。更新本地化条目时,如果在请求正文中设置 `locale` 属性,该属性将被忽略。 🌐 It is not possible to change the locale of an existing localized entry. When updating a localized entry, if you set a `locale` attribute in the request body it will be ignored. ::: #### 在集合类型 {#rest-put-collection-type} 中 🌐 In a collection type 要为集合类型中的现有文档创建新区域,本地化,请在 `documentId` 之后将 `locale` 参数添加到查询中,并将数据传递到请求的主体中: 🌐 To create a new locale for an existing document in a collection type, add the `locale` parameter to the query, after the `documentId`, and pass data to the request's body: #### PUT /api/restaurants/:documentId?locale=fr — 创建或更新本地版本(集合类型) 为现有餐厅创建法语区域设置,如果已存在则更新它。 ```bash curl -X PUT \ 'http://localhost:1337/api/restaurants/lr5wju2og49bf820kj9kz8c3?locale=fr' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "data": { "Name": "She'\''s Cake in French" } }' ``` ```json { "data": { "id": 19, "documentId": "lr5wju2og49bf820kj9kz8c3", "Name": "She's Cake in French", "Description": null, "createdAt": "2024-03-07T12:13:09.551Z", "updatedAt": "2024-03-07T12:13:09.551Z", "publishedAt": "2024-03-07T12:13:09.554Z", "locale": "fr" }, "meta": {} } ``` #### 在单一类型中 {#rest-put-single-type} 🌐 In a single type 要为现有的单类型文档创建新的本地化版本,请在单类型名称后将 `locale` 参数添加到查询中,并将数据传递到请求的主体中: 🌐 To create a new locale for an existing single type document, add the `locale` parameter to the query, after the single type name, and pass data to the request's body: #### PUT /api/homepage?locale=fr — 创建或更新本地化版本(单类型) 为现有的主页单一类型创建一个法语区域设置,如果它已存在则更新它。 ```bash curl -X PUT \ 'http://localhost:1337/api/homepage?locale=fr' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "data": { "Title": "Page d'\''accueil" } }' ``` ```json { "data": { "id": 10, "documentId": "ukbpbnu8kbutpn98rsanyi50", "Title": "Page d'accueil", "Body": null, "createdAt": "2024-03-07T13:28:26.349Z", "updatedAt": "2024-03-07T13:28:26.349Z", "publishedAt": "2024-03-07T13:28:26.353Z", "locale": "fr" }, "meta": {} } ```
### `DELETE` 删除文档的某个语言版本 {#rest-delete} 🌐 `DELETE` Delete a locale version of a document 要删除文档的本地化版本,请发送带有适当 `locale` 参数的 `DELETE` 请求。 🌐 To delete a locale version of a document, send a `DELETE` request with the appropriate `locale` parameter. `DELETE` 请求在成功时仅发送 204 HTTP 状态码,并且不会在响应体中返回任何数据。 #### 在集合类型 {#rest-delete-collection-type} 中 🌐 In a collection type 要仅删除集合类型中文档的特定语言版本,请在 `documentId` 之后将 `locale` 参数添加到查询中: 🌐 To delete only a specific locale version of a document in a collection type, add the `locale` parameter to the query after the `documentId`: #### DELETE /api/restaurants/:documentId?locale=fr — 删除一个本地版本(集合类型) 删除集合类型中文档的特定本地化版本。 ```bash curl -X DELETE \ 'http://localhost:1337/api/restaurants/abcdefghijklmno456?locale=fr' \ -H 'Authorization: Bearer ' ``` ```json ``` #### 在单一类型中 {#rest-delete-single-type} 🌐 In a single type 要仅删除单类型文档的特定语言版本,请在单类型名称后将 `locale` 参数添加到查询中: 🌐 To delete only a specific locale version of a single type document, add the `locale` parameter to the query after the single type name: #### DELETE /api/homepage?locale=fr — 删除一个本地版本(单一类型) 删除单一类型文档的特定语言版本。 ```bash curl -X DELETE \ 'http://localhost:1337/api/homepage?locale=fr' \ -H 'Authorization: Bearer ' ``` ```json ``` # 参数 Source: https://strapi.nodejs.cn/cms/api/rest/parameters # REST API 参数 {#rest-api-parameters} 🌐 REST API parameters REST API 参数在 Strapi 查询中用于过滤、排序、分页以及选择字段和关联。使用 `filters`、`locale`、`populate`、`sort` 和 `pagination` 来优化你的内容请求。 🌐 REST API parameters filter, sort, paginate, and select fields and relations in Strapi queries. Use `filters`, `locale`, `populate`, `sort`, and `pagination` to refine your content requests. API 参数可以与 [REST API](/cms/api/rest) 一起使用,以筛选、排序和分页结果,并选择要填充的字段和关联。此外,还可以使用与可选 Strapi 功能相关的特定参数,例如内容类型的发布状态和语言环境。 🌐 API parameters can be used with the [REST API](/cms/api/rest) to filter, sort, and paginate results and to select fields and relations to populate. Additionally, specific parameters related to optional Strapi features can be used, like the publication state and locale of a content-type. 以下 API 参数可用: 🌐 The following API parameters are available: | 操作符 | 类型 | 描述 | | --- | --- | --- | | `filters` | 对象 | [筛选响应](/cms/api/rest/filters) | | `locale` | 字符串 | [选择语言环境](/cms/api/rest/locale) | | `status` | 字符串 | [选择草稿和发布状态](/cms/api/rest/status) | | `publicationFilter` | 字符串 | [根据草稿和发布版本的关系选择文档](/cms/api/rest/publication-filter) | | `populate` | 字符串或对象 | [填充关联、组件或动态区域](/cms/api/rest/populate-select#population) | | `fields` | 数组 | [仅选择要显示的特定字段](/cms/api/rest/populate-select#field-selection) | | `sort` | 字符串或数组 | [对响应进行排序](/cms/api/rest/sort-pagination.md#sorting) | | `pagination` | 对象 | [分页获取条目](/cms/api/rest/sort-pagination.md#pagination) | :::note 参数中的长括号编码列表(例如 `populate` 或 `fields`)受 [`arrayLimit` 在 `strapi::query`](/cms/configurations/middlewares#query) 的限制。参见 [Population](/cms/api/rest/populate-select#population)。 🌐 Long bracket-encoded lists in a parameter (for example `populate` or `fields`) are limited by [`arrayLimit` on `strapi::query`](/cms/configurations/middlewares#query). See [Population](/cms/api/rest/populate-select#population). ::: 查询参数使用 [LHS bracket syntax](https://christiangiacomi.com/posts/rest-design-principles/#lhs-brackets) (即它们使用方括号 `[]` 编码)。 :::tip 可以使用和组合广泛的 REST API 参数来查询你的内容,这可能会导致长且复杂的查询 URL。
👉 你可以使用 Strapi 的[交互式查询构建器](/cms/api/rest/interactive-query-builder)工具更方便地构建查询 URL。🤗 ::: # 填充并选择 Source: https://strapi.nodejs.cn/cms/api/rest/populate-select # REST API:人口和字段选择 {#rest-api-population--field-selection} 🌐 REST API: Population & Field Selection 使用 `populate` 参数在 REST API 响应中包含关系、媒体字段、组件和动态区域。使用 `fields` 参数仅返回特定字段。 🌐 Use the `populate` parameter to include relations, media fields, components, and dynamic zones in REST API responses. Use the `fields` parameter to return only specific fields. [REST API](/cms/api/rest) 默认不会填充任何关联、媒体字段、组件或动态区域。使用 [`populate` 参数](#population) 来填充特定字段。使用 [`fields` 参数](#field-selection) 仅返回查询结果中的特定字段。 🌐 The [REST API](/cms/api/rest) by default does not populate any relations, media fields, components, or dynamic zones. Use the [`populate` parameter](#population) to populate specific fields. Use the [`fields` parameter](#field-selection) to return only specific fields with the query results. :::tip Strapi 利用 [`qs` 库](https://github.com/ljharb/qs) 解析嵌套对象的能力来创建更复杂的查询。 🌐 Strapi takes advantage of the ability of [the `qs` library](https://github.com/ljharb/qs) to parse nested objects to create more complex queries. 直接使用 `qs` 来生成复杂查询,而不是手动创建它们。本说明文档中的示例展示了如何使用 `qs`。 🌐 Use `qs` directly to generate complex queries instead of creating them manually. Examples in this documentation showcase how you can use `qs`. 如果你更喜欢使用我们的在线工具,而不是在你的机器上使用 `qs` 生成查询,你也可以使用 [交互式查询构建器](/cms/api/rest/interactive-query-builder)。 🌐 You can also use the [interactive query builder](/cms/api/rest/interactive-query-builder) if you prefer playing with our online tool instead of generating queries with `qs` on your machine. ::: ## 字段选择 {#field-selection} 🌐 Field selection 查询可以接受一个 `fields` 参数来只选择某些字段。默认情况下,REST API 仅返回以下 [类型的字段](/cms/backend-customization/models#model-attributes): 🌐 Queries can accept a `fields` parameter to select only some fields. By default, the REST API only returns the following [types of fields](/cms/backend-customization/models#model-attributes): - 字符串类型:`string`、`text`、`richtext`、`enumeration`、`email`、`password` 和 `uid`, - 日期类型:`date`、`time`、`datetime` 和 `timestamp`, - 数字类型:`integer`、`biginteger`、`float` 和 `decimal`, - 泛型类型:`boolean`、`array` 和 `JSON`。 | 用例 | 示例参数语法 | |-----------------------|---------------------------------------| | 选择单个字段 | `fields=name` | | 选择多个字段 | `fields[0]=name&fields[1]=description` | :::note 字段选择在关系型、媒体、组件或动态区域字段上不起作用。要填充这些字段,请使用[`populate`参数](#population)。 🌐 Field selection does not work on relational, media, component, or dynamic zone fields. To populate these fields, use the [`populate` parameter](#population). ::: #### GET /api/restaurants?fields[0]=name&fields[1]=description — 只返回名称和描述字段 使用 fields 参数仅选择响应中的特定字段。 ```bash curl 'http://localhost:1337/api/restaurants?fields[0]=name&fields[1]=description' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify( { fields: ['name', 'description'], }, { encodeValuesOnly: true, // prettify URL } ); await request(`/api/users?${query}`); ``` ```json { "data": [ { "id": 4, "Name": "Pizzeria Arrivederci", "Description": [ { "type": "paragraph", "children": [ { "type": "text", "text": "Specialized in pizza, we invite you to rediscover our classics, such as 4 Formaggi or Calzone, and our original creations such as Do Luigi or Nduja." } ] } ], "documentId": "lr5wju2og49bf820kj9kz8c3" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` ## 人口 {#population} 🌐 Population 默认情况下,REST API 不会填充任何类型的字段,因此它不会填充关联、媒体字段、组件或动态区域,除非你传递一个 `populate` 参数来填充各种字段类型。已填充的关联总是返回完整对象;REST API 目前无法仅返回 ID 数组。 🌐 The REST API by default does not populate any type of fields, so it will not populate relations, media fields, components, or dynamic zones unless you pass a `populate` parameter to populate various field types. Populated relations always return full objects; the REST API currently cannot return just an array of IDs. :::prerequisites 必须为正在填充的内容类型启用 `find` 权限。如果某个角色无法访问某个内容类型,则该内容类型将不会被填充(有关如何为内容类型启用 `find` 权限的更多信息,请参见 [用户与权限](/cms/features/users-permissions#editing-a-role))。 🌐 The `find` permission must be enabled for the content-types that are being populated. If a role does not have access to a content-type, the content-type will not be populated (see [Users & Permissions](/cms/features/users-permissions#editing-a-role) for additional information on how to enable `find` permissions for content-types). ::: 你可以单独使用 `populate` 参数,或[与多个操作符结合使用](#combining-population-with-other-operators)以更好地控制人口。 🌐 You can use the `populate` parameter alone or [in combination with multiple operators](#combining-population-with-other-operators) for more control over the population. :::caution `populate=deep` 插件在 Strapi 中 [不推荐使用](https://support.strapi.io/articles/8544110758-why-populate-deep-plugins-are-not-recommended-in-strapi)。 ::: :::note 查询字符串中的大型 `populate` 列表(许多 `populate[0]`、`populate[1]` 等条目)受到查询解析器 `arrayLimit` 的限制(默认值:`100`)。要允许更长的列表,请在 [`strapi::query` 中间件](/cms/configurations/middlewares#query) 上提高 `arrayLimit`。更高的值会增加每个请求的解析开销。 🌐 Large `populate` lists in the query string (many `populate[0]`, `populate[1]`, … entries) are bounded by the query parser `arrayLimit` (default: `100`). To allow a longer list, raise `arrayLimit` on the [`strapi::query` middleware](/cms/configurations/middlewares#query). Higher values increase parsing cost per request. ::: 下表列出了 populate 的用例及示例语法。每一行都链接到“理解 populate”指南以获取详细信息: 🌐 The following table lists populate use cases with example syntax. Each row links to the Understanding populate guide for details: | 用例 | 参数示例语法 | 详细说明阅读 | |-----------| ---|-----------------------| | 填充所有内容,深度 1 级,包括媒体字段、关联、组件和动态区域 | `populate=*` | [填充所有关联和字段,深度 1 级](/cms/api/rest/guides/understanding-populate#populate-all-relations-and-fields-1-level-deep) | | 填充一个关系,
深度为1 | `populate=a-relation-name`| [为特定关系填充1级深度](/cms/api/rest/guides/understanding-populate#populate-1-level-deep-for-specific-relations) | | 填充多个关系,
1 级深 | `populate[0]=relation-name&populate[1]=another-relation-name&populate[2]=yet-another-relation-name`| [为特定关系填充 1 级深](/cms/api/rest/guides/understanding-populate#populate-1-level-deep-for-specific-relations) | | 填充一些关系,几层深 | `populate[root-relation-name][populate][0]=nested-relation-name`| [为特定关系填充几层深](/cms/api/rest/guides/understanding-populate#populate-several-levels-deep-for-specific-relations) | | 填充组件 | `populate[0]=component-name`| [填充组件](/cms/api/rest/guides/understanding-populate#populate-components) | | 填充一个组件及其嵌套组件之一 | `populate[0]=component-name&populate[1]=component-name.nested-component-name`| [填充组件](/cms/api/rest/guides/understanding-populate#populate-components) | | 填充动态区域(仅其第一级标量字段) | `populate[0]=dynamic-zone-name`| [填充动态区域](/cms/api/rest/guides/understanding-populate#populate-dynamic-zones) | | 填充动态区域,包括组件特定字段、嵌套组件和关系 | `populate[dynamic-zone-name][on][component-category.component-name][populate][relation-name][populate][0]=field-name`| [填充动态区域](/cms/api/rest/guides/understanding-populate#populate-dynamic-zones) | :::tip 要构建具有多级填充的复杂查询,请使用 [交互式查询构建器](/cms/api/rest/interactive-query-builder) 工具。有关更详细的说明和示例,请参阅 [REST API 指南](/cms/api/rest/guides/intro)。 🌐 To build complex queries with multiple-level population, use the [interactive query builder](/cms/api/rest/interactive-query-builder) tool. For more detailed explanations and examples, see the [REST API guides](/cms/api/rest/guides/intro). ::: ### 将人口与其他操作符结合 {#combining-population-with-other-operators} 🌐 Combining population with other operators 你可以在填充查询中将 `populate` 操作符与其他操作符结合使用,例如 [字段选择](/cms/api/rest/populate-select#field-selection)、[过滤器](/cms/api/rest/filters) 和 [排序](/cms/api/rest/sort-pagination)。 🌐 You can combine the `populate` operator with other operators such as [field selection](/cms/api/rest/populate-select#field-selection), [filters](/cms/api/rest/filters), and [sort](/cms/api/rest/sort-pagination) in the population queries. :::note 顶层分页参数(例如,`pagination[page]` 和 `pagination[pageSize]`)与 `populate` 一起用于分页主查询结果。然而,你不能将分页参数直接应用于已填充的关联,以限制每个结果中返回的相关条目数量(REST API 不支持关联上的嵌套分页)。 🌐 Top-level pagination parameters (e.g., `pagination[page]` and `pagination[pageSize]`) work alongside `populate` to paginate the main query results. However, you cannot apply pagination parameters directly to populated relations to limit the number of related entries returned within each result (nested pagination on relations is not supported in the REST API). ::: #### 用字段选择填充 {#populate-with-field-selection} 🌐 Populate with field selection `fields` 和 `populate` 可以结合。 #### GET /api/articles?fields[0]=title&fields[1]=slug&populate[headerImage][fields][0]=name&populate[headerImage][fields][1]=url — 用字段选择填充 组合字段并填写参数,以选择主条目及其关联条目上的特定字段。 ```bash curl 'http://localhost:1337/api/articles?fields[0]=title&fields[1]=slug&populate[headerImage][fields][0]=name&populate[headerImage][fields][1]=url' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify( { fields: ['title', 'slug'], populate: { headerImage: { fields: ['name', 'url'], }, }, }, { encodeValuesOnly: true, // prettify URL } ); await request(`/api/articles?${query}`); ``` ```json { "data": [ { "id": 1, "documentId": "h90lgohlzfpjf3bvan72mzll", "title": "Test Article", "slug": "test-article", "headerImage": { "id": 1, "documentId": "cf07g1dbusqr8mzmlbqvlegx", "name": "17520.jpg", "url": "/uploads/17520_73c601c014.jpg" } } ], "meta": {} } ``` #### 填充并筛选 {#populate-with-filtering} 🌐 Populate with filtering `filters` 和 `populate` 可以结合。 #### GET /api/articles?populate[categories][sort][0]=name%3Aasc&populate[categories][filters][name][$eq]=Cars — 填充并筛选 将 populate 与 sort 和 filter 参数结合使用,以优化返回的相关条目。 ```bash curl 'http://localhost:1337/api/articles?populate[categories][sort][0]=name%3Aasc&populate[categories][filters][name][$eq]=Cars' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify( { populate: { categories: { sort: ['name:asc'], filters: { name: { $eq: 'Cars', }, }, }, }, }, { encodeValuesOnly: true, // prettify URL } ); await request(`/api/articles?${query}`); ``` ```json { "data": [ { "id": 1, "documentId": "a1b2c3d4e5d6f7g8h9i0jkl", "title": "Test Article", "categories": { "data": [ { "id": 2, "documentId": "jKd8djla9ndalk98hflj3", "name": "Cars" } ] } } ], "meta": {} } ``` :::note 对于多对多和其他关联表关系,在 `populate` 对象中显式的 `sort` 会覆盖默认的连接顺序。省略 `sort` 可以保留连接顺序(条目关联的顺序)。 🌐 For many-to-many and other join-table relations, an explicit `sort` within a `populate` object overrides the default connect order. Omit `sort` to preserve the connect order (the order in which entries were associated). ::: :::tip Performance tip 在生产环境中,请始终使用显式填充,而不是像 `populate=*` 这样的通配符。将填充深度限制在 2-3 级,并考虑将填充逻辑集中在路由中间件中。参见 Strapi 博客上的 [Building High-Performance Strapi Applications](https://strapi.io/blog/building-high-performance-strapi-applications-common-pitfalls-and-best-practices) 。 ::: # REST API:publicationFilter Source: https://strapi.nodejs.cn/cms/api/rest/publication-filter # REST API:`publicationFilter` {#rest-api-publicationfilter} 🌐 REST API: `publicationFilter` 添加可选的 `publicationFilter` 查询参数,以根据文档草稿版本与已发布版本之间的关系查询文档,例如从未发布的文档,或自上次发布以来已修改的文档。它可以与其他查询参数结合使用,而 `status` 仍然决定你获取的是草稿版本还是已发布版本。 🌐 Add the optional `publicationFilter` query parameter to query documents by the relationship between their draft and published versions, for example documents that were never published, or documents modified since they were last published. It combines with other query parameters, and `status` still decides whether you get the draft or the published version. `publicationFilter` 是一个查询参数,与 [ `status` 参数](/cms/api/rest/status) 结合使用时,可以帮助你通过 [REST API](/cms/api/rest) 完成复杂查询,精确找到所需内容。 🌐 The `publicationFilter` is a query parameter that, combined with [the `status` parameter](/cms/api/rest/status), can help you cover complex queries to find exactly what you need with the [REST API](/cms/api/rest). 当 `status` 回答“我想要草稿版还是发布版?”时,`publicationFilter` 参数回答的是一个不同的问题:“根据文档的草稿版和发布版之间的关系,我想要哪些文档?”这对于例如查找从未发布的文档,或草稿与已发布内容相比有未保存更改的文档非常有用。 🌐 While `status` answers "do I want the draft or the published version?", the `publicationFilter` parameter answers a different question: "which documents do I want, based on how their draft and published versions relate?". This is useful for example to find documents that were never published, or documents whose draft has unsaved changes compared to what is live. `publicationFilter` 的工作原理背后的基础模型是在后端服务器上由 [Document Service API](/cms/api/document-service/publication-filter) 处理的。本页遵循完全相同的结构和解释,但示例针对 REST API 进行了定制,因此你无需在两个不同的页面之间切换。 🌐 The underlying model behind how `publicationFilter` works is handled on the back-end server by the [Document Service API](/cms/api/document-service/publication-filter). The present page follows the exact same structure and explanations, but with examples tailored for the REST API, so you don't have to jump between 2 different pages. :::prerequisites 内容类型必须启用[草稿与发布](/cms/features/draft-and-publish)功能。如果草稿与发布被禁用,`publicationFilter`将不起作用。 🌐 The [Draft & Publish](/cms/features/draft-and-publish) feature must be enabled on the content-type. If Draft & Publish is disabled, `publicationFilter` has no effect. ::: ## 可用值 {#values} 🌐 Available values `publicationFilter` 接受以下值之一: | 值 | 选择 | | --- | --- | | `never-published` | 从未在特定语言环境中发布的文档 | | `never-published-document` | 从未在任何语言环境中发布的文档 | | `modified` | 自上次发布以来草稿被编辑过的文档 | | `unmodified` | 自上次发布以来草稿未更改的文档 | | `has-published-version` | 同时拥有草稿和已发布版本的文档 | | `published-without-draft` | 已发布但没有草稿对应的文档
([仅用于诊断](#diagnostics)) | | `published-with-draft` | 已发布且同时拥有草稿的文档
([仅用于诊断](#diagnostics)) | | `has-published-version-document` | 至少在一个语言环境中发布的文档
(在启用 [i18n](/cms/features/internationalization) 时有用) | 有关如何使用 `publicationFilter` 值的详细示例,包括与 `status` 参数一起使用,请参见 [可能的用例](#use-cases) 表。 🌐 For detailed examples of how to use the `publicationFilter` values, including with the `status` parameter, see the [possible use cases](#use-cases) table. :::note * 未知值返回 HTTP `400` 错误。 * 以 `-document` 结尾的值会考虑文档的所有语言环境,这在启用 [国际化 (i18n)](/cms/features/internationalization) 时很重要:例如,`never-published-document` 一旦文档的某个语言环境被发布就会将其排除。所有其他值则一次只考虑一个语言环境。在未启用 i18n 的情况下,这两种变体的行为相同。 ::: :::caution Caution: Different default behaviors for different APIs 当省略 `status` 时,REST API 会返回文档的已发布版本,因此对仅草稿值(如 `never-published`)的查询需要明确指定 `status=draft`。而文档服务 API 则返回草稿版本(参见 [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter))。 🌐 The REST API returns published versions of documents when `status` is omitted, so queries for draft-only values such as `never-published` need an explicit `status=draft`. The Document Service API returns draft versions instead (see [Document Service API: `publicationFilter`](/cms/api/document-service/publication-filter)). ::: ## 可能的使用场景 {#use-cases} 🌐 Possible use cases 下表列出了许多可能的使用场景,说明了如何将 `status` 和 `publicationFilter` 参数结合使用,以在 REST API 中准确找到所需内容。点击一个使用场景即可跳转到完整示例: 🌐 The following table lists many possible use cases, illustrating how the `status` and `publicationFilter` parameters can be combined to find exactly what you need with the REST API. Click a use case to jump to a complete example: | 我想要… | 使用 `status` 作为… | 使用 `publicationFilter` 作为… | | --- | --- | --- | | [查找从未发布的稿件](#never-published) | `draft` | `never-published` | | [查找在任何地区从未发布的稿件](#never-published-document) | `draft` | `never-published-document` | | [查找已修改的文档](#modified) | `draft` 或 `published` | `modified` | | [查找未修改的文档](#unmodified) | `draft` 或 `published` | `unmodified` | | [查找有已发布版本的文档](#has-published-version) | `draft` 或 `published` | `has-published-version` | | [查找至少在一个地区有已发布版本的文档](#has-published-version-document) | `draft` 或 `published` | `has-published-version-document` | :::note 将一个值与上表中的相反 `status` 配对是有效的,但不会返回错误,而是返回空结果:例如,`never-published` 与 `status=published` 配对会返回空结果,因为这些文档尚无已发布版本。 🌐 Pairing a value with the opposite `status` from the table above is valid but returns nothing rather than an error: for example, `never-published` with `status=published` returns an empty result, because these documents have no published version yet. ::: ## 例子 {#examples} 🌐 Examples 以下部分列出了上方[表格](#use-cases)中总结的最常见用例。 🌐 The following section lists the most common use cases summed up in the [table](#use-cases) above. ### 查找从未发布的草稿 {#never-published} 🌐 Find never published drafts 最常见的用例之一是查找从未发布的草稿。为此,请传递 `status=draft` 和 `publicationFilter=never-published`。 🌐 One of the most common use cases is to find the drafts that have never been published. To do so, pass `status=draft` and `publicationFilter=never-published`. 此参数组合仅适用于特定语言环境;要在所有语言环境中查找这些文档,请改为[使用 `never-published-document`](#never-published-document)。 🌐 This parameter combination works only on a given locale; to find these documents across all locales, [use `never-published-document`](#never-published-document) instead. #### GET /api/restaurants?status=draft&publicationFilter=never-published — 获取从未在其所在地区发布过的餐厅草稿 返回从未在其所在地区发布的草稿。 ```bash curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=never-published' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ status: 'draft', publicationFilter: 'never-published', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "name": "New Restaurant", "publishedAt": null, "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` ### 查找从未在任何地区发布的草稿 {#never-published-document} 🌐 Find drafts never published in any locale `publicationFilter=never-published-document` 返回从未在任何语言环境下发布过的文档。它查看整个文档的所有语言环境,而不是一次查看一个语言环境。要仅查找特定语言环境的这些文档,请使用 [ `never-published`](#never-published)。 一旦文档的其中一个本地化版本被发布,该文档就算作已发布:文档随后会被排除在外,即使其他本地化版本仅以草稿形式存在。下面的示例返回那些从未在任何地方发布过的文档的草稿版本: 🌐 A document counts as published as soon as one of its locales is published: the document is then left out, even the locales that only exist as a draft. The example below returns the draft versions of documents that were never published anywhere: #### GET /api/restaurants?status=draft&publicationFilter=never-published-document — 获取从未在任何地方发布过的餐厅草稿 返回从未在任何地区发布的文件草稿。 ```bash curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=never-published-document' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ status: 'draft', publicationFilter: 'never-published-document', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "documentId": "d41r46wac4xix5vpba7561at", "name": "New Restaurant", "publishedAt": null, "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` ### 查找已修改的文档 {#modified} 🌐 Find modified documents `publicationFilter=modified` 选择草稿有修改但未发布更改的文档。然后 `status` 决定你得到这些文档的哪个版本。 例如,使用 `status=draft` 时,查询返回草稿版本: 🌐 For instance, with `status=draft`, the query returns the draft versions: #### GET /api/restaurants?status=draft&publicationFilter=modified — 获取修改过的餐厅的草稿版本 返回含有未发布更改的文档草稿版本。 ```bash curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=modified' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ status: 'draft', publicationFilter: 'modified', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "name": "Biscotte Restaurant (updated)", "publishedAt": null, "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ```
使用 `status=published`(REST 默认值)时,相同的查询将返回这些文档的当前最新版本: #### GET /api/restaurants?publicationFilter=modified — 获取当前已修改餐厅的实时版本 返回具有未发布更改的文档的当前版本。 ```bash curl 'http://localhost:1337/api/restaurants?publicationFilter=modified' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ publicationFilter: 'modified', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "name": "Biscotte Restaurant", "publishedAt": "2024-03-14T15:40:45.330Z", "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` ### 查找未修改的文档 {#unmodified} 🌐 Find unmodified documents `publicationFilter=unmodified` 选择自上次发布后草稿未更改的文档。然后 `status` 决定你将获取这些文档的哪个版本。 例如,使用 `status=draft` 时,查询返回草稿版本: 🌐 For instance, with `status=draft`, the query returns the draft versions: #### GET /api/restaurants?status=draft&publicationFilter=unmodified — 获取未修改餐厅的草稿版本 返回自上次发布以来未更改的文件草稿版本。 ```bash curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=unmodified' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ status: 'draft', publicationFilter: 'unmodified', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "name": "Biscotte Restaurant", "publishedAt": null, "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ```
使用 `status=published`(REST 默认值)时,相同的查询将返回这些文档的当前最新版本: #### GET /api/restaurants?publicationFilter=unmodified — 获取当前未修改餐厅的实时版本 返回自上次发布以来未更改的文档当前版本。 ```bash curl 'http://localhost:1337/api/restaurants?publicationFilter=unmodified' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ publicationFilter: 'unmodified', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "name": "Biscotte Restaurant", "publishedAt": "2024-03-14T15:40:45.330Z", "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` ### 查找有已发布版本的文档 {#has-published-version} 🌐 Find documents with a published version `publicationFilter=has-published-version` 选择那些在相同语言环境下既有草稿版本又有已发布版本的文档。`status` 然后决定你获得这些文档的哪个版本。 例如,使用 `status=draft` 时,查询返回草稿版本: 🌐 For instance, with `status=draft`, the query returns the draft versions: #### GET /api/restaurants?status=draft&publicationFilter=has-published-version — 获取也有已发布版本的餐厅的草稿版本 返回那些在相同语言环境下也有已发布版本的文档草稿版本。 ```bash curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=has-published-version' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ status: 'draft', publicationFilter: 'has-published-version', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "name": "Biscotte Restaurant", "publishedAt": null, "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ```
使用 `status=published`(REST 默认值)时,相同的查询将返回这些文档的当前最新版本: #### GET /api/restaurants?publicationFilter=has-published-version — 获取同时拥有草稿的餐厅的当前在线版本 返回当前在线版本的文档,这些文档在同一语言区域也有已发布的版本。 ```bash curl 'http://localhost:1337/api/restaurants?publicationFilter=has-published-version' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ publicationFilter: 'has-published-version', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "name": "Biscotte Restaurant", "publishedAt": "2024-03-14T15:40:45.330Z", "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` ### 查找在至少一个地区有已发布版本的文档 {#has-published-version-document} 🌐 Find documents with a published version in at least one locale `publicationFilter=has-published-version-document` 会考虑所有地区,因此只要文档的某个地区已发布,它就会匹配该文档。使用 `status=draft` 时,它会返回这些文档每个地区的草稿版本,包括那些从未发布过的地区: #### GET /api/restaurants?status=draft&publicationFilter=has-published-version-document — 获取至少在一个地区发布的餐厅草稿 返回至少在一个地区发布的文档的草稿版本。 ```bash curl 'http://localhost:1337/api/restaurants?status=draft&publicationFilter=has-published-version-document' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ status: 'draft', publicationFilter: 'has-published-version-document', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "name": "Biscotte Restaurant", "publishedAt": null, "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` ## 诊断值 {#diagnostics} 🌐 Diagnostic values `published-without-draft` 和 `published-with-draft` 值仅用于数据完整性检查,而不是日常查询。在一个健康的数据库中,每个已发布的文档也都有一个草稿版本,因此这些值仅用于检测由遗留数据或人工数据库编辑导致的文档不一致状态。它们描述已发布的行,因此 REST 会随默认的 `status=published` 一起返回它们。 🌐 The `published-without-draft` and `published-with-draft` values are meant for data-integrity checks only, not for everyday queries. In a healthy database, every published document also has a draft version, so these values are only useful to detect documents left in an inconsistent state by legacy data or manual database edits. They describe published rows, so REST returns them with the default `status=published`.
显示诊断值示例 `publicationFilter=published-without-draft` 选择没有草稿对应的已发布文档。在正常操作中,这应返回空值: #### GET /api/restaurants?publicationFilter=published-without-draft — 获取同一地点没有草稿的已发布餐厅 返回在同一语言环境下没有匹配草稿版本的已发布文档。 ```bash curl 'http://localhost:1337/api/restaurants?publicationFilter=published-without-draft' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ publicationFilter: 'published-without-draft', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "documentId": "j0klm1n2o3p4q5r6s7t8u9v", "name": "Legacy Restaurant", "publishedAt": "2024-01-10T09:15:00.000Z", "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ``` `publicationFilter=published-with-draft` 选择那些已发布且也有草稿的文档,在健康的数据库中,这几乎包括每个已发布的文档: #### GET /api/restaurants?publicationFilter=published-with-draft — 获取同一地区既已发布又有草稿的餐厅 返回那些也有相同本地版本草稿的已发布文档。 ```bash curl 'http://localhost:1337/api/restaurants?publicationFilter=published-with-draft' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ publicationFilter: 'published-with-draft', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "documentId": "a1b2c3d4e5f6g7h8i9j0klm", "name": "Biscotte Restaurant", "publishedAt": "2024-03-14T15:40:45.330Z", "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 } } } ```
## 与其他参数结合 {#combine} 🌐 Combine with other parameters `publicationFilter` 可以与 [`filters`](/cms/api/rest/filters)、[`locale`](/cms/api/rest/locale)、[`populate`](/cms/api/rest/populate-select) 以及其他 [REST 参数](/cms/api/rest/parameters) 结合使用。所有条件同时适用。 # 关系 Source: https://strapi.nodejs.cn/cms/api/rest/relations # 管理与 API 请求的关系 {#managing-relations-with-api-requests} 🌐 Managing relations with API requests 在 REST 和 GraphQL API 请求中使用 `connect`、`disconnect` 和 `set` 参数来管理内容类型之间的关系。使用位置参数如 `before`、`after`、`start` 或 `end` 对关系进行重新排序。 🌐 Use `connect`, `disconnect`, and `set` parameters in REST and GraphQL API requests to manage relations between content-types. Reorder relations using positional arguments like `before`, `after`, `start`, or `end`. 定义内容类型(在数据库层中指定为实体)之间的关系是将实体相互连接起来。 🌐 Defining relations between content-types (that are designated as entities in the database layers) is connecting entities with each other. 内容类型之间的关系可以通过[管理面板](/cms/features/content-manager#relational-fields)或通过[REST API](/cms/api/rest)或[文档服务 API](/cms/api/document-service)请求进行管理。 🌐 Relations between content-types can be managed through the [admin panel](/cms/features/content-manager#relational-fields) or through [REST API](/cms/api/rest) or [Document Service API](/cms/api/document-service) requests. 关系可以通过内容 API 连接、断开或设置,只需在请求的主体中传递参数。这些有效负载适用于单条条目关系和多重关系(一对多、多对一、多对多及多向)。当关系字段允许多个链接时,API 需要关系 ID 的数组,并在响应中返回数组。 🌐 Relations can be connected, disconnected or set through the Content API by passing parameters in the body of the request. These payloads work for both single-entry relations and multi relations (one-to-many, many-to-one, many-to-many, and many-way). When a relational field allows multiple links, the API expects arrays of relation IDs and returns arrays in responses. | 参数名称 | 描述 | 更新类型 | |-------------------------|-------------|----------------| | [`connect`](#connect) | 连接新实体。

可以与 `disconnect` 结合使用。

可以与 [位置参数](#relations-reordering) 一起使用,以定义关系的顺序。 | 部分 | | [`disconnect`](#disconnect) | 断开实体连接。

可以与 `connect` 结合使用。 | 部分 | | [`set`](#set) | 将实体设置为特定集合。使用 `set` 会覆盖与其他实体的所有现有连接。

不能与 `connect` 或 `disconnect` 一起使用。 | 完整 | :::info Relations across REST, GraphQL, and Document Service 本页描述的 `connect`、`disconnect` 和 `set` 有效负载适用于 [REST](/cms/api/rest) 和 [GraphQL](/cms/api/graphql#fetch-relations) 请求,并且在从服务器或插件代码调用 [文档服务](/cms/api/document-service) 方法时也支持相同的对象结构。文档服务介绍部分重复了这一点,而本页稍下方的国际化示例显示了 `strapi.documents(...).update()` 和 `connect`。 🌐 The `connect`, `disconnect`, and `set` payloads described on this page apply to [REST](/cms/api/rest) and [GraphQL](/cms/api/graphql#fetch-relations) requests, and the same object shapes are supported when you call [Document Service](/cms/api/document-service) methods from server or plugin code. The Document Service introduction repeats this, and the Internationalization example further down on this page shows `strapi.documents(...).update()` with `connect`. 如果 TypeScript 报告 `TS2353` 并声称 `connect` 不是你的 `data` 对象上的有效属性,同时你照搬了这些示例,请将其视为类型定义的缺口,而不是 Strapi 在运行时拒绝调用。在 Strapi 类型包完全对齐之前,可以通过类型断言缩小 `data` 的负载,或者通过一个类型更宽松的小型辅助构建它,以便保留文档中描述的形状。有关讨论,请参见 [GitHub issue #2904](https://github.com/strapi/documentation/issues/2904) 。 🌐 If TypeScript reports `TS2353` and claims `connect` is not a valid property on your `data` object while you mirror these examples, treat that as a typings gap rather than Strapi rejecting the call at runtime. Until the Strapi type packages align fully, narrow the `data` payload with a type assertion or build it through a small helper typed more loosely so you can keep the documented shape. See [GitHub issue #2904](https://github.com/strapi/documentation/issues/2904) for discussion. ::: :::note 当在内容类型上启用[国际化 (i18n)](/cms/features/internationalization)时,你还可以传递一个语言环境以为特定语言环境设置关系,如在此文档服务 API 示例中所示: 🌐 When [Internationalization (i18n)](/cms/features/internationalization) is enabled on the content-type, you can also pass a locale to set relations for a specific locale, as in this Document Service API example: ```js await strapi.documents('api::restaurant.restaurant').update({ documentId: 'a1b2c3d4e5f6g7h8i9j0klm', locale: 'fr', data: { category: { connect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv'] } } }) ``` :::tip TypeScript Workaround 在 TypeScript 中使用 Document Service API 的 connect、disconnect 或 set 时,你可能会遇到 TS2353 错误,提示这些属性在 LongHandEntity 类型上不存在。 🌐 When using connect, disconnect, or set with the Document Service API in TypeScript, you may encounter a TS2353 error stating that these properties do not exist on type LongHandEntity. 虽然运行时引擎完全支持这种语法,但当前的类型定义不支持。你可以通过将数据负载转换为 any 或更广泛的对象类型来安全地跳过此验证: 🌐 While the runtime engine fully supports this syntax, the current typings do not. You can safely bypass this validation by casting your data payload to any or a broader object type: ```js await strapi.documents('api::cart.cart').update({ documentId: cart.documentId, data: { status: 'checked_out', order: { connect: [order.documentId] } as any, // Temporary workaround }, }); ``` 如果未传递任何语言环境,则将假定使用默认语言环境。 🌐 If no locale is passed, the default locale will be assumed. ::: ## `connect` 在请求体中使用 `connect` 会执行部分更新,连接指定的关系。 🌐 Using `connect` in the body of a request performs a partial update, connecting the specified relations. `connect` 接受简写或长写语法: | 语法类型 | 语法示例 | | --- | ---------------- | | 简写 | `connect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv']` | | 全写 | ```connect: [{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }]``` | 你也可以使用长格式语法来[重新排序关系](#relations-reordering)。 🌐 You can also use the longhand syntax to [reorder relations](#relations-reordering). `connect` 可以与 [`disconnect`](#disconnect) 结合使用。 :::caution `connect` 官方不支持媒体属性。高级用户在技术上可以通过定位上传的文件 ID 来连接媒体条目,但这种方法不被 Strapi 推荐或支持,并且很容易出错(例如,当草稿与发布使用不匹配的 ID 时)。请谨慎操作。 ::: 发送以下请求会更新一个由其 `documnentId` `a1b2c3d4e5f6g7h8i9j0klm` 标识的 `restaurant`。该请求使用 `categories` 属性将餐厅与由其 `documentId` 标识的 2 个类别关联起来: 🌐 Sending the following request updates a `restaurant`, identified by its `documnentId` `a1b2c3d4e5f6g7h8i9j0klm`. The request uses the `categories` attribute to connect the restaurant with 2 categories identified by their `documentId`: `PUT` `http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm` ```js { data: { categories: { connect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv'] } } } ``` ```js const fetch = require('node-fetch'); const response = await fetch( 'http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm', { method: 'put', body: { data: { categories: { connect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv'] } } } } ); ``` 发送以下请求会更新一个由其 `documnentId` `a1b2c3d4e5f6g7h8i9j0klm` 标识的 `restaurant`。该请求使用 `categories` 属性将餐厅与由其 `documentId` 标识的 2 个类别关联起来: 🌐 Sending the following request updates a `restaurant`, identified by its `documnentId` `a1b2c3d4e5f6g7h8i9j0klm`. The request uses the `categories` attribute to connect the restaurant with 2 categories identified by their `documentId`: `PUT` `http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm` ```js { data: { categories: { connect: [ { documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' } ] } } } ``` ```js const fetch = require('node-fetch'); const response = await fetch( 'http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm', { method: 'put', body: { data: { categories: { connect: [ { documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' } ] } } } } ); ``` ### 关系重新排序 {#relations-reordering} 🌐 Relations reordering 可以将位置参数传递给 `connect` 的长格式语法,以定义关系的顺序。 🌐 Positional arguments can be passed to the longhand syntax of `connect` to define the order of relations. 长格式语法接受一个对象数组,每个对象包含要连接条目的 `documentId`,以及用于定义连接关系位置的可选 `position` 对象。 🌐 The longhand syntax accepts an array of objects, each object containing the `documentId` of the entry to be connected and an optional `position` object to define where to connect the relation. :::note Different syntaxes for different relations 本文件中描述的语法对于一对多、多对多和多向关系非常有用。
对于一对一、多对一和单向关系,这些语法也被支持,但只会使用最后一个关系,因此建议使用较短的格式(例如:`{ data: { category: 'a1b2c3d4e5f6g7h8i9j0klm' } }`,参见 [REST API 文档](/cms/api/rest#requests))。 ::: 要为一个关系定义 `position`,请传递以下四个不同的位置属性之一: 🌐 To define the `position` for a relation, pass one of the following 4 different positional attributes: | 参数名称和语法 | 描述 | 类型 | | --- | --- | --- | | `before: documentId` | 将关系放置在给定的 `documentId` 之前。 | `documentId` (字符串) | | `after: documentId` | 将关系放置在给定的 `documentId` 之后。 | `documentId` (字符串) | | `start: true` | 将关系放置在现有关系列表的开头。 | 布尔值 | | `end: true` | 将关系放置在现有关系列表的末尾。 | 布尔值 | `position` 参数是可选的,默认值为 `position: { end: true }`。 🌐 The `position` argument is optional and defaults to `position: { end: true }`. :::note Sequential order 由于 `connect` 是一个数组,操作的顺序很重要,因为它们将被按顺序处理(见下面的组合示例)。 🌐 Since `connect` is an array, the order of operations is important as they will be treated sequentially (see combined example below). ::: :::caution 同一关系不应连接多次,否则 API 将返回验证错误。 🌐 The same relation should not be connected more than once, otherwise it would return a Validation error by the API. ::: 考虑数据库中的以下记录: 🌐 Consider the following record in the database: ```js categories: [ { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' } { documentId: 'z0y2x4w6v8u1t3s5r7q9onm' } ] ``` 发送以下请求会更新一个 `restaurant`,通过其 `documentId` `a1b2c3d4e5f6g7h8i9j0klm` 进行标识,将一个实体的关系连接到具有 `ma12bc34de56fg78hi90jkl` 的 `documentId` 上用于 `categories` 属性,并将其定位在具有 `documentId` `z0y2x4w6v8u1t3s5r7q9onm` 的实体之前: 🌐 Sending the following request updates a `restaurant`, identified by its `documentId` `a1b2c3d4e5f6g7h8i9j0klm`, connecting a relation of entity with a `documentId` of `ma12bc34de56fg78hi90jkl` for the `categories` attribute and positioning it before the entity with `documentId` `z0y2x4w6v8u1t3s5r7q9onm`: `PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm` ```js { data: { categories: { connect: [ { documentId: 'ma12bc34de56fg78hi90jkl', position: { before: 'z0y2x4w6v8u1t3s5r7q9onm' } }, ] } } } ``` 考虑数据库中的以下记录: 🌐 Consider the following record in the database: ```js categories: [ { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' } { documentId: 'z0y2x4w6v8u1t3s5r7q9onm' } ] ``` 在 PUT 请求的请求正文中发送以下示例会更新多个关系: 🌐 Sending the following example in the request body of a PUT request updates multiple relations: `PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm` ```js { data: { categories: { connect: [ { id: '6u86wkc6x3parjd4emikhmx', position: { after: 'j9k8l7m6n5o4p3q2r1s0tuv'} }, { id: '3r1wkvyjwv0b9b36s7hzpxl', position: { before: 'z0y2x4w6v8u1t3s5r7q9onm' } }, { id: 'rkyqa499i84197l29sbmwzl', position: { end: true } }, { id: 'srkvrr77k96o44d9v6ef1vu' }, { id: 'nyk7047azdgbtjqhl7btuxw', position: { start: true } }, ] } } } ``` 省略 `position` 参数(如 `documentId: 'srkvrr77k96o44d9v6ef1vu9'`)默认为 `position: { end: true }`。所有其他关系都是相对于另一个已存在的 `id`(使用 `after` 或 `before`)或相对于关系列表(使用 `start` 或 `end`)定位的。操作按 `connect` 数组中定义的顺序依次处理,因此生成的数据库记录将如下所示: 🌐 Omitting the `position` argument (as in `documentId: 'srkvrr77k96o44d9v6ef1vu9'`) defaults to `position: { end: true }`. All other relations are positioned relative to another existing `id` (using `after` or `before`) or relative to the list of relations (using `start` or `end`). Operations are treated sequentially in the order defined in the `connect` array, so the resulting database record will be the following: ```js categories: [ { id: 'nyk7047azdgbtjqhl7btuxw' }, { id: 'j9k8l7m6n5o4p3q2r1s0tuv' }, { id: '6u86wkc6x3parjd4emikhmx6' }, { id: '3r1wkvyjwv0b9b36s7hzpxl7' }, { id: 'a1b2c3d4e5f6g7h8i9j0klm' }, { id: 'rkyqa499i84197l29sbmwzl' }, { id: 'srkvrr77k96o44d9v6ef1vu9' } ] ``` ### 边缘情况:草稿与发布或国际化禁用 {#edge-cases-draft--publish-or-i18n-disabled} 🌐 Edge cases: Draft & Publish or i18n disabled 当 Strapi 5 的某些内置功能在内容类型中被禁用时,例如 [草稿与发布](/cms/features/draft-and-publish) 和 [国际化 (i18n)](/cms/features/internationalization),`connect` 参数可能会有不同的使用方式: 🌐 When some built-in features of Strapi 5 are disabled for a content-type, such as [Draft & Publish](/cms/features/draft-and-publish) and [Internationalization (i18)](/cms/features/internationalization), the `connect` parameter might be used differently: **从 i18n _关闭_ 的 `Category` 到 i18n _开启_ 的 `Article` 的关系:** 在这种情况下,你可以选择要连接到哪个语言环境: 🌐 In this situation you can select which locale you are connecting to: ```js data: { categories: { connect: [ { documentId: 'z0y2x4w6v8u1t3s5r7q9onm', locale: 'en' }, // Connect to the same document id but with a different locale 👇 { documentId: 'z0y2x4w6v8u1t3s5r7q9onm', locale: 'fr' }, ] } } ``` **从 Draft & Publish _关闭_ 的 `Category` 到 Draft & Publish _开启_ 的 `Article` 的关系:** ```js data: { categories: { connect: [ { documentId: 'z0y2x4w6v8u1t3s5r7q9onm', status: 'draft' }, // Connect to the same document id but with different publication states 👇 { documentId: 'z0y2x4w6v8u1t3s5r7q9onm', status: 'published' }, ] } } ``` ## `disconnect` 在请求的主体中使用 `disconnect` 会执行部分更新,断开指定的关联。 🌐 Using `disconnect` in the body of a request performs a partial update, disconnecting the specified relations. `disconnect` 接受简写或长写语法: | 语法类型 | 语法示例 | | ---|----------------| | 简写 | `disconnect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv']` | | 全写 | ```disconnect: [{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }]``` | `disconnect` 可以与 [`connect`](#connect) 结合使用。
发送以下请求会更新一个 `restaurant`,该 `restaurant` 由其 `documentId` `a1b2c3d4e5f6g7h8i9j0klm` 标识,同时断开与两个由其 `documentId` 标识的条目的关系: 🌐 Sending the following request updates a `restaurant`, identified by its `documentId` `a1b2c3d4e5f6g7h8i9j0klm`, disconnecting the relations with 2 entries identified by their `documentId`: `PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm` ```js { data: { categories: { disconnect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv'], } } } ``` 发送以下请求会更新一个 `restaurant`,该 `restaurant` 由其 `documentId` `a1b2c3d4e5f6g7h8i9j0klm` 标识,同时断开与两个由其 `documentId` 标识的条目的关系: 🌐 Sending the following request updates a `restaurant`, identified by its `documentId` `a1b2c3d4e5f6g7h8i9j0klm`, disconnecting the relations with 2 entries identified by their `documentId`: `PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm` ```js { data: { categories: { disconnect: [ { documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' } ], } } } ``` ## `set` 使用 `set` 会执行完整更新,用指定的顺序用指定的关系替换所有现有关系。 🌐 Using `set` performs a full update, replacing all existing relations with the ones specified, in the order specified. `set` 接受简写或长写语法: | 语法类型 | 语法示例 | | --- | --- | | 简写 | `set: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv']` | | 全写 | ```set: [{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }]``` | 由于 `set` 会替换所有现有关系,因此不应与其他参数一起使用。要执行部分更新,请使用 [`connect`](#connect) 和 [`disconnect`](#disconnect)。 🌐 As `set` replaces all existing relations, it should not be used in combination with other parameters. To perform a partial update, use [`connect`](#connect) and [`disconnect`](#disconnect). :::note Omitting set 省略任何参数等同于使用 `set`。
例如,以下三种语法都是等效的: - `data: { categories: set: [{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }] }}` - `data: { categories: set: ['z0y2x4w6v8u1t3s5r7q9onm2', 'j9k8l7m6n5o4p3q2r1s0tuv'] }}` - `data: { categories: ['z0y2x4w6v8u1t3s5r7q9onm2', 'j9k8l7m6n5o4p3q2r1s0tuv'] }` ::: 发送以下请求会更新一个 `restaurant`,该 `restaurant` 由其 `documentId` `a1b2c3d4e5f6g7h8i9j0klm` 标识,替换所有之前存在的关系,并使用 `categories` 属性连接由其 `documentId` 标识的两个类别: 🌐 Sending the following request updates a `restaurant`, identified by its `documentId` `a1b2c3d4e5f6g7h8i9j0klm`, replacing all previously existing relations and using the `categories` attribute to connect 2 categories identified by their `documentId`: `PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm` ```js { data: { categories: { set: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv4'], } } } ``` 发送以下请求会更新一个 `restaurant`,该 `restaurant` 由其 `documentId` `a1b2c3d4e5f6g7h8i9j0klm` 标识,替换所有之前存在的关系,并使用 `categories` 属性连接由其 `documentId` 标识的两个类别: 🌐 Sending the following request updates a `restaurant`, identified by its `documentId` `a1b2c3d4e5f6g7h8i9j0klm`, replacing all previously existing relations and using the `categories` attribute to connect 2 categories identified by their `documentId`: `PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm` ```js { data: { categories: { set: [ { documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' } ], } } } ``` # 排序与分页 Source: https://strapi.nodejs.cn/cms/api/rest/sort-pagination # REST API:排序与分页 {#rest-api-sort--pagination} 🌐 REST API: Sort & Pagination 使用 `:asc` 或 `:desc` 语法对 REST API 结果按一个或多个字段进行排序,并使用基于页码或基于偏移量的参数进行分页。 🌐 Sort REST API results on one or multiple fields with `:asc` or `:desc` syntax, and paginate using either page-based or offset-based parameters. 通过对 [REST API](/cms/api/rest) 的查询返回的条目可以进行排序和分页。 🌐 Entries that are returned by queries to the [REST API](/cms/api/rest) can be sorted and paginated. :::tip Strapi 利用 [`qs` 库](https://github.com/ljharb/qs) 解析嵌套对象的能力来创建更复杂的查询。 🌐 Strapi takes advantage of the ability of [the `qs` library](https://github.com/ljharb/qs) to parse nested objects to create more complex queries. 直接使用 `qs` 来生成复杂查询,而不是手动创建它们。本说明文档中的示例展示了如何使用 `qs`。 🌐 Use `qs` directly to generate complex queries instead of creating them manually. Examples in this documentation showcase how you can use `qs`. 如果你更喜欢使用我们的在线工具,而不是在你的机器上使用 `qs` 生成查询,你也可以使用 [交互式查询构建器](/cms/api/rest/interactive-query-builder)。 🌐 You can also use the [interactive query builder](/cms/api/rest/interactive-query-builder) if you prefer playing with our online tool instead of generating queries with `qs` on your machine. ::: ## 排序 {#sorting} 🌐 Sorting 查询可以接受一个 `sort` 参数,该参数允许使用以下语法对一个或多个字段进行排序: 🌐 Queries can accept a `sort` parameter that allows sorting on one or multiple fields with the following syntaxes: - `GET /api/:pluralApiId?sort=value` 按 1 个字段排序 - `GET /api/:pluralApiId?sort[0]=value1&sort[1]=value2` 按多个字段排序(例如按 2 个字段) 排序顺序可以定义为: 🌐 The sorting order can be defined with: - `:asc` 表示升序(默认顺序,可省略) - 或使用 `:desc` 表示降序。 ### 示例:使用两个字段排序 {#example-sort-using-2-fields} 🌐 Example: Sort using 2 fields 你可以通过在 `sort` 数组中传入字段来按多个字段排序。 🌐 You can sort by multiple fields by passing fields in a `sort` array. #### GET /api/restaurants?sort[0]=Description&sort[1]=Name — 使用两个字段排序 按描述和名称字段排序结果。 ```bash curl 'http://localhost:1337/api/restaurants?sort[0]=Description&sort[1]=Name' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ sort: ['Description', 'Name'], }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "id": 9, "documentId": "hgv1vny5cebq2l3czil1rpb3", "Name": "BMK Paris Bamako", "Description": [ { "type": "paragraph", "children": [ { "type": "text", "text": "A very short description goes here." } ] } ] // … }, { "id": 8, "documentId": "flzc8qrarj19ee0luix8knxn", "Name": "Restaurant D", "Description": [ { "type": "paragraph", "children": [ { "type": "text", "text": "A very short description goes here." } ] } ] // … } // … ], "meta": { // … } } ``` ### 示例:使用两个字段排序并设置顺序 {#example-sort-using-2-fields-and-set-the-order} 🌐 Example: Sort using 2 fields and set the order 使用 `sort` 参数并在已排序的字段上定义 `:asc` 或 `:desc`,你可以获得按特定顺序排序的结果。 🌐 Using the `sort` parameter and defining `:asc` or `:desc` on sorted fields, you can get results sorted in a particular order. #### GET /api/restaurants?sort[0]=Description:asc&sort[1]=Name:desc — 使用两个字段进行排序并设置顺序 按描述升序排列结果,按名称降序排列。 ```bash curl 'http://localhost:1337/api/restaurants?sort[0]=Description:asc&sort[1]=Name:desc' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ sort: ['Description:asc', 'Name:desc'], }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/restaurants?${query}`); ``` ```json { "data": [ { "id": 8, "documentId": "flzc8qrarj19ee0luix8knxn", "Name": "Restaurant D", "Description": [ { "type": "paragraph", "children": [ { "type": "text", "text": "A very short description goes here." } ] } ] // … }, { "id": 9, "documentId": "hgv1vny5cebq2l3czil1rpb3", "Name": "BMK Paris Bamako", "Description": [ { "type": "paragraph", "children": [ { "type": "text", "text": "A very short description goes here." } ] } ] // … } // … ], "meta": { // … } } ``` ## 分页 {#pagination} 🌐 Pagination 查询可以接受 `pagination` 参数。结果可以分页: 🌐 Queries can accept `pagination` parameters. Results can be paginated: - 可以通过 [page](#pagination-by-page)(即,指定页码和每页条目数) - 或者通过 [offset](#pagination-by-offset)(即指定要跳过多少条条目以及要返回多少条) :::note 分页方法不能混合使用。始终要么使用 `page` 和 `pageSize`,**或者**使用 `start` 和 `limit`。 🌐 Pagination methods can not be mixed. Always use either `page` with `pageSize` **or** `start` with `limit`. ::: ### 按页分页 {#pagination-by-page} 🌐 Pagination by page 要按页对结果进行分页,请使用以下参数: 🌐 To paginate results by page, use the following parameters: | 参数 | 类型 | 描述 | 默认值 | | --- | --- | --- | --- | | `pagination[page]` | 整数 | 页码 | 1 | | `pagination[pageSize]` | 整数 | 每页数量 | 25 | | `pagination[withCount]` | 布尔值 | 在响应中添加条目总数和页数 | True | #### GET /api/articles?pagination[page]=1&pagination[pageSize]=10 — 按页分页 仅在第1页显示10条记录。 ```bash curl 'http://localhost:1337/api/articles?pagination[page]=1&pagination[pageSize]=10' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ pagination: { page: 1, pageSize: 10, }, }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/articles?${query}`); ``` ```json { "data": [ // ... ], "meta": { "pagination": { "page": 1, "pageSize": 10, "pageCount": 5, "total": 48 } } } ``` ### 按偏移量分页 {#pagination-by-offset} 🌐 Pagination by offset 要按偏移量对结果进行分页,请使用以下参数: 🌐 To paginate results by offset, use the following parameters: | 参数 | 类型 | 描述 | 默认值 | | --- | --- | --- | --- | | `pagination[start]` | 整数 | 起始值(即要返回的第一个条目) | 0 | | `pagination[limit]` | 整数 | 要返回的条目数量 | 25 | | `pagination[withCount]` | 布尔值 | 切换是否在响应中显示条目总数 | `true` | :::tip `pagination[limit]` 的默认值和最大值可以在 `/cms/configurations/api` 文件中的 `./config/api.js` 配置里通过 `api.rest.defaultLimit` 和 `api.rest.maxLimit` 键进行设置。 🌐 The default and maximum values for `pagination[limit]` can be [configured in the `./config/api.js`](/cms/configurations/api) file with the `api.rest.defaultLimit` and `api.rest.maxLimit` keys. ::: #### GET /api/articles?pagination[start]=0&pagination[limit]=10 — 按偏移量分页 使用偏移量只返回前10个条目。 ```bash curl 'http://localhost:1337/api/articles?pagination[start]=0&pagination[limit]=10' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ pagination: { start: 0, limit: 10, }, }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/articles?${query}`); ``` ```json { "data": [ // ... ], "meta": { "pagination": { "start": 0, "limit": 10, "total": 42 } } } ``` # 状态 Source: https://strapi.nodejs.cn/cms/api/rest/status # REST API:`status` {#rest-api-status} 🌐 REST API: `status` REST API 的 `status` 参数通过文档的发布状态进行过滤,通过传递 `status=draft` 来返回已发布的版本(默认)或草稿。 🌐 The REST API's `status` parameter filters documents by their publication state, returning either published versions (default) or drafts by passing `status=draft`. [REST API](/cms/api/rest) 提供根据状态(草稿或已发布)过滤结果的功能。 🌐 The [REST API](/cms/api/rest) offers the ability to filter results based on their status, draft or published. :::prerequisites 应该启用 [Draft & Publish](/cms/features/draft-and-publish) 功能。 🌐 The [Draft & Publish](/cms/features/draft-and-publish) feature should be enabled. ::: 查询可以接受一个 `status` 参数以根据其状态获取文档: 🌐 Queries can accept a `status` parameter to fetch documents based on their status: - `published`:仅返回文档的已发布版本(默认) - `draft`:仅返回文档的草稿版本 :::tip 在响应数据中,`publishedAt` 字段对于草稿来说是 `null`。 🌐 In the response data, the `publishedAt` field is `null` for drafts. ::: :::note 由于默认返回已发布版本,因此不传递状态参数等同于传递 `status=published`。 🌐 Since published versions are returned by default, passing no status parameter is equivalent to passing `status=published`. ::: 要按文档草稿与已发布版本的关系(从未发布、已修改及其他)选择文档,请参见 [REST API:`publicationFilter`](/cms/api/rest/publication-filter)。 🌐 To select documents by how their draft and published versions relate (never-published, modified, and others), see [REST API: `publicationFilter`](/cms/api/rest/publication-filter).

#### GET /api/articles?status=draft — 获取餐厅草稿版本 通过传递 status=draft 查询参数返回文档的草稿版本。 ```bash curl 'http://localhost:1337/api/articles?status=draft' \ -H 'Authorization: Bearer ' ``` ```js const qs = require('qs'); const query = qs.stringify({ status: 'draft', }, { encodeValuesOnly: true, // prettify URL }); await request(`/api/articles?${query}`); ``` ```json { "data": [ { "id": 5, "documentId": "znrlzntu9ei5onjvwfaalu2v", "Name": "Biscotte Restaurant", "Description": [ { "type": "paragraph", "children": [ { "type": "text", "text": "This is the draft version." } ] } ], "createdAt": "2024-03-06T13:43:30.172Z", "updatedAt": "2024-03-06T21:38:46.353Z", "publishedAt": null, "locale": "en" } ], "meta": { "pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 4 } } } ``` # 上传文件 Source: https://strapi.nodejs.cn/cms/api/rest/upload # REST API:上传文件 {#rest-api-upload-files} 🌐 REST API: Upload files `/api/upload` REST API 端点使你能够将文件上传到媒体库,获取分页文件列表,更新文件元数据,以及从你的 Strapi 应用中删除文件。 🌐 The `/api/upload` REST API endpoints enable you to upload files to the Media Library, retrieve paginated file lists, update file metadata, and delete files from your Strapi application. [媒体库功能](/cms/features/media-library) 在 Strapi 的后端服务器中由 `upload` 包提供支持。要向 Strapi 上传文件,你可以直接从管理面板使用媒体库,也可以使用 [REST API](/cms/api/rest),可用的端点如下: 🌐 The [Media Library feature](/cms/features/media-library) is powered in the back-end server of Strapi by the `upload` package. To upload files to Strapi, you can either use the Media Library directly from the admin panel, or use the [REST API](/cms/api/rest), with the following available endpoints : | 方法 | 路径 | 描述 | | :----- | :------------------------- | :--------------------------- | | GET | `/api/upload/files` | 获取文件列表 | | GET | `/api/upload/files/page` | 获取分页文件列表 | | GET | `/api/upload/files/:id` | 获取特定文件 | | POST | `/api/upload` | 上传文件 | | POST | `/api/upload?id=x` | 更新文件信息 | | DELETE | `/api/upload/files/:id` | 删除文件 | :::note Notes - [文件夹](/cms/features/media-library#organizing-assets-with-folders) 是仅限管理员面板的功能,不属于内容 API(REST 或 GraphQL)。通过 REST 上传的文件位于自动创建的“API 上传”文件夹中。 - GraphQL API 不支持上传媒体文件。要上传文件,请使用 REST API 或直接从管理面板的 [媒体库](/cms/features/media-library) 添加文件。一些用于更新或删除已上传媒体文件的 GraphQL 变更仍然可能(详情请参见 [GraphQL API 文档](/cms/api/graphql#mutations-on-media-files))。 ::: ## 获取文件列表 {#get-a-list-of-files} 🌐 Get a list of files 2 个端点从媒体库返回文件:`/api/upload/files` 将每个文件作为平铺数组返回,而 `/api/upload/files/page` 使用标准分页响应返回文件。对于任何非简单的媒体库,请使用 `/api/upload/files/page`。 ### 获取所有文件 {#get-all-files} 🌐 Get all files `GET /api/upload/files` 返回媒体库中所有文件的扁平数组: ```json [ { "id": 1, "documentId": "a1b2c3...", "name": "photo.jpg", "url": "/uploads/photo.jpg", "mime": "image/jpeg", "size": 12.34 // ...other file fields } // ... ] ``` 此端点会忽略分页参数,并始终返回所有文件。对于大型媒体库,响应可能会非常大,因此建议使用 `/api/upload/files/page`。 🌐 This endpoint ignores pagination parameters and always returns every file. For large media libraries the response can be very large, so prefer `/api/upload/files/page` instead. ### 获取分页文件列表 {#get-a-paginated-list-of-files} 🌐 Get a paginated list of files `GET /api/upload/files/page` 使用标准的 [分页响应](/cms/api/rest/sort-pagination) 返回文件,封装在 `data` 数组和 `meta.pagination` 对象中。 它接受与其他 REST 集合端点相同的查询参数: 🌐 It accepts the same query parameters as other REST collection endpoints: | 参数 | 描述 | | --- | --- | | `pagination[page]` | 页码(以1为起始)。默认 `1`。 | | `pagination[pageSize]` | 每页文件数量。默认使用 `api.rest.defaultLimit` [配置](/cms/configurations/api) (`25`),如果设置了 `api.rest.maxLimit` 则上限为其值。 | | `pagination[start]` | 基于偏移的分页:要跳过的文件数量。 | | `pagination[limit]` | 基于偏移的分页:返回的最大文件数量。 | | `pagination[withCount]` | 是否运行计数查询并在响应中包含 `total` 和 `pageCount`。默认值为 `true`。 | | `filters` | 对结果进行 [过滤](/cms/api/rest/filters)。 | | `sort` | 对结果进行 [排序](/cms/api/rest/sort-pagination#sorting)。 | | `fields` | [选择](/cms/api/rest/populate-select#field-selection) 要返回的字段。 | | `populate` | [填充](/cms/api/rest/populate-select#population) 关联关系。 | :::note 分页参数使用嵌套格式(`pagination[page]=2&pagination[pageSize]=10`),而不是扁平格式(`page=2&pageSize=10`)。按页分页(`page`/`pageSize`)和按偏移分页(`start`/`limit`)是互斥的:将它们结合使用会返回 `400` 错误。有关两种分页方法的详细信息,请参见 [排序与分页](/cms/api/rest/sort-pagination#pagination)。 🌐 Pagination parameters use the nested format (`pagination[page]=2&pagination[pageSize]=10`), not the flat format (`page=2&pageSize=10`). Pagination by page (`page`/`pageSize`) and pagination by offset (`start`/`limit`) are mutually exclusive: combining them returns a `400` error. For details on both pagination methods, see [Sort & Pagination](/cms/api/rest/sort-pagination#pagination). ::: **示例请求:获取10个文件的第二页:** `GET /api/upload/files/page?pagination[page]=2&pagination[pageSize]=10` **示例响应:** ```json { "data": [ { "id": 1, "documentId": "a1b2c3...", "name": "photo.jpg", "url": "/uploads/photo.jpg", "mime": "image/jpeg", "size": 12.34 // ...other file fields } ], "meta": { "pagination": { "page": 2, "pageSize": 10, "pageCount": 4, "total": 100 } } } ``` 在使用偏移分页时,`meta.pagination` 对象返回 `start` 和 `limit`,而不是 `page` 和 `pageSize`: 🌐 When using pagination by offset, the `meta.pagination` object returns `start` and `limit` instead of `page` and `pageSize`: **示例请求:获取5个图片文件,跳过前20个,不要总数:** `GET /api/upload/files/page?pagination[start]=20&pagination[limit]=5&pagination[withCount]=false&filters[mime][$startsWith]=image/` **示例响应:** ```json { "data": [ // ... ], "meta": { "pagination": { "start": 20, "limit": 5 } } } ``` 当 `pagination[withCount]` 是 `false` 时,计数查询将被跳过,并且 `total` 和 `pageCount` 将从响应中省略。 🌐 When `pagination[withCount]` is `false`, the count query is skipped and `total` and `pageCount` are omitted from the response. ## 上传文件 {#upload-files} 🌐 Upload files 将一个或多个文件上传到你的应用。 🌐 Upload one or more files to your application. `files` 是唯一接受的参数,用于描述要上传的文件。其值可以是 Buffer 或 Stream。 :::info Signed URLs with private S3 buckets 在使用 AWS S3 并将 `ACL` 参数设置为 `"private"` 时,上传端点返回的文件 URL 会自动生成签名。签名 URL 包含 `X-Amz-Signature` 查询参数和响应中的 `isUrlSigned: true` 标志,使其即使在私有桶 ACL 下也可访问。签名 URL 的过期时间基于你的 `signedUrlExpires` 配置(默认:15 分钟)。 🌐 When using AWS S3 with the `ACL` parameter set to `"private"`, file URLs returned by the upload endpoints are automatically signed. Signed URLs include `X-Amz-Signature` query parameters and an `isUrlSigned: true` flag in the response, making them accessible despite the private bucket ACL. Signed URLs expire based on your `signedUrlExpires` configuration (default: 15 minutes). ::: :::tip 上传图片时,包含一个 `fileInfo` 对象以设置文件名、替代文本和标题。 🌐 When uploading an image, include a `fileInfo` object to set the file name, alt text, and caption. ::: ```html
``` ```js const file = await blobFrom('./1.png', 'image/png'); const form = new FormData(); form.append('files', file, "1.png"); form.append( 'fileInfo', JSON.stringify({ name: 'Homepage hero', alternativeText: 'Person smiling while holding laptop', caption: 'Hero image used on the homepage', }) ); const response = await fetch('http://localhost:1337/api/upload', { method: 'post', body: form, }); ``` :::caution 你必须在请求正文中发送 FormData。 🌐 You have to send FormData in your request body. ::: ## 上传入口文件 {#upload-entry-files} 🌐 Upload entry files 上传一个或多个将链接到特定条目的文件。 🌐 Upload one or more files that will be linked to a specific entry. 接受以下参数: 🌐 The following parameters are accepted: | 参数 | 描述 | | --- | --- | |`files` | 要上传的文件。值可以是 Buffer 或 Stream。 | |`path` (可选) | 文件将上传到的文件夹(仅在 strapi-provider-upload-aws-s3 上支持)。 | | `refId` | 将与文件关联的条目 ID。 | | `ref` | 文件将关联的模型的唯一 ID(uid)(详见下文)。 | | `source` (可选) | 模型所在插件的名称。 | | `field` | 文件将精确关联到的条目字段。 | 例如,给定 `Restaurant` 模型属性: 🌐 For example, given the `Restaurant` model attributes: ```json title="/src/api/restaurant/content-types/restaurant/schema.json" { // ... "attributes": { "name": { "type": "string" }, "cover": { "type": "media", "multiple": false, } } // ... } ``` 以下是相应前端的示例代码: 🌐 The following is an example of a corresponding front-end code: ```html
``` :::caution 你必须在请求正文中发送 FormData。 🌐 You have to send FormData in your request body. ::: ## 更新文件信息 {#update-fileinfo} 🌐 Update fileInfo 更新应用中的文件。 🌐 Update a file in your application. `fileInfo` 是唯一被接受的参数,用于描述要更新的文件信息: ```js const fileId = 50; const newFileData = { alternativeText: 'My new alternative text for this image!', }; const form = new FormData(); form.append('fileInfo', JSON.stringify(newFileData)); const response = await fetch(`http://localhost:1337/api/upload?id=${fileId}`, { method: 'post', body: form, }); ``` ## 模型定义 {#models-definition} 🌐 Models definition 向[模型](/cms/backend-customization/models)(或另一个插件的模型)添加文件属性就像添加一个新的关联。 🌐 Adding a file attribute to a [model](/cms/backend-customization/models) (or the model of another plugin) is like adding a new association. 以下示例允许你上传并附加一个文件到 `avatar` 属性: 🌐 The following example lets you upload and attach one file to the `avatar` attribute: ```json title="/src/api/restaurant/content-types/restaurant/schema.json" { // ... { "attributes": { "pseudo": { "type": "string", "required": true }, "email": { "type": "email", "required": true, "unique": true }, "avatar": { "type": "media", "multiple": false, } } } // ... } ``` 以下示例允许你上传并附加多张图片到 `restaurant` 内容类型: 🌐 The following example lets you upload and attach multiple pictures to the `restaurant` content-type: ```json title="/src/api/restaurant/content-types/restaurant/schema.json" { // ... { "attributes": { "name": { "type": "string", "required": true }, "covers": { "type": "media", "multiple": true, } } } // ... } ``` # 后端定制 Source: https://strapi.nodejs.cn/cms/backend-customization
# 后端自定义 Strapi 的后端是一个基于 Koa 的服务器,请求会通过全局中间件、路由、控制器、服务和模型,然后 Document 服务才返回响应。 :::strapi Disambiguation: Strapi back end 作为一个无头 CMS,Strapi 软件整体上可以被视为你网站或应用的“后端”。 但 Strapi 软件本身包含两个不同的部分: 🌐 As a headless CMS, the Strapi software as a whole can be considered as the "back end" of your website or application. But the Strapi software itself includes 2 different parts: - Strapi 的 **后端** 部分是 Strapi 运行的一个 HTTP 服务器。像任何 HTTP 服务器一样,Strapi 后端接收请求并发送响应。你的内容存储在数据库中,Strapi 后端与数据库交互以创建、检索、更新和删除内容。 - Strapi 的 **前端** 部分称为管理面板。管理面板提供图形用户界面,帮助你组织和管理内容。 在整个开发者文档中,“后端”专指 Strapi 的后端部分。 🌐 Throughout this developer documentation, 'back end' refers _exclusively_ to the back-end part of Strapi. [入门 > 管理员面板页面](/cms/features/admin-panel) 提供了管理员面板的概览,[管理员面板自定义部分](/cms/admin-panel-customization) 详细说明了管理员面板可用的各种自定义选项。 🌐 The [Getting Started > Admin panel page](/cms/features/admin-panel) gives an admin panel overview and the [admin panel customization section](/cms/admin-panel-customization) details the various customization options available for the admin panel. ::: Strapi 后端运行一个基于 [Koa](https://koa.nodejs.cn/) 的 HTTP 服务器, [Koa](https://koa.nodejs.cn/) 是一个后端 JavaScript 框架。 像任何 HTTP 服务器一样,Strapi 后端接收请求并发送响应。你可以通过 [REST](/cms/api/rest) 或 [GraphQL](/cms/api/graphql) API 向 Strapi 后端发送请求,以创建、检索、更新或删除数据。 🌐 Like any HTTP server, the Strapi back end receives requests and send responses. You can send requests to the Strapi back end to create, retrieve, update, or delete data through the [REST](/cms/api/rest) or [GraphQL](/cms/api/graphql) APIs. 请求可以通过 Strapi 后端传输,如下所示: 🌐 A request can travel through the Strapi back end as follows: 1. Strapi 服务器接收一个 [请求](/cms/backend-customization/requests-responses)。 2. 请求会触发按顺序运行的[全局中间件](/cms/backend-customization/middlewares)。 3. 该请求访问了一个[路由](/cms/backend-customization/routes)。
默认情况下,Strapi 会为你创建的所有内容类型生成路由文件(参见[REST API 文档](/cms/api/rest)),并且可以添加和配置更多路由。 4. [路由策略](/cms/backend-customization/policies) 充当只读的验证步骤,可以阻止访问某条路由。[路由中间件](/cms/backend-customization/routes#middlewares) 可以控制请求流程,并在继续之前修改请求本身。 5. [控制器](/cms/backend-customization/controllers) 在路由被访问后执行代码。[服务](/cms/backend-customization/services) 是可选的附加代码,可用于构建可被控制器重复使用的自定义逻辑。 6. 控制器和服务执行的代码与[模型](/cms/backend-customization/models)交互,这些模型是存储在数据库中的内容结构的表示形式。
通过模型表示的数据的交互由[文档服务](/cms/api/document-service)和[查询引擎](/cms/api/query-engine)处理。 7. 你可以实现 [文档服务中间件](/cms/api/document-service/middlewares) 来在数据发送到查询引擎之前进行控制。查询引擎也可以使用生命周期钩子,不过我们建议你使用文档服务中间件,除非你确实需要直接与数据库交互。 7. 服务器返回一个 [响应](/cms/backend-customization/requests-responses)。响应可以在发送之前通过路由中间件和全局中间件返回。 全局和路由中间件都包含一个异步回调函数 `await next()`。根据中间件返回的内容,请求将在后端走一条较短或较长的路径: 🌐 Both global and route middlewares include an asynchronous callback function, `await next()`. Depending on what is returned by the middleware, the request will either go through a shorter or longer path through the back end: * 如果中间件不返回任何内容,则请求将继续穿过后端的各个核心元素(即控制器、服务以及与数据库交互的其他层)。 * 如果中间件在调用 `await next()` 之前返回,将会立即发送响应,跳过其余的核心元素。然后,它将沿着原路返回同一条链。 :::info 请注意,本节页面中描述的所有自定义仅适用于 REST API。[GraphQL 自定义](/cms/plugins/graphql#customization) 在 GraphQL 插件文档中描述。 🌐 Please note that all customizations described in the pages of this section are only for the REST API. [GraphQL customizations](/cms/plugins/graphql#customization) are described in the GraphQL plugin documentation. ::: ## 交互式图表 {#interactive-diagram} 🌐 Interactive diagram 下图展示了请求如何通过 Strapi 后端传输。你可以点击任意形状跳转到文档中的相关页面。 🌐 The following diagram represents how requests travel through the Strapi back end. You can click on any shape to jump to the relevant page in the documentation.
# 控制器 Source: https://strapi.nodejs.cn/cms/backend-customization/controllers # 控制器 {#controllers} 🌐 Controllers 控制器将处理业务逻辑的操作打包在 Strapi 的 MVC 模式中的每个路由内。本文档演示了如何生成控制器、使用 `createCoreController` 扩展核心控制器,以及将繁重的逻辑委托给服务。 控制器是包含一组方法(称为动作)的 JavaScript 文件,客户端可以根据请求的[路由](/cms/backend-customization/routes)访问这些方法。每当客户端请求该路由时,动作会执行业务逻辑代码并返回[响应](/cms/backend-customization/requests-responses)。控制器代表模型-视图-控制器(MVC)模式中的 C。 🌐 Controllers are JavaScript files that contain a set of methods, called actions, reached by the client according to the requested [route](/cms/backend-customization/routes). Whenever a client requests the route, the action performs the business logic code and sends back the [response](/cms/backend-customization/requests-responses). Controllers represent the C in the model-view-controller (MVC) pattern. 在大多数情况下,控制器将包含项目大部分的业务逻辑。但随着控制器的逻辑变得越来越复杂,使用[服务](/cms/backend-customization/services)将代码组织成可重用的部分是一种好习惯。 🌐 In most cases, the controllers will contain the bulk of a project's business logic. But as a controller's logic becomes more and more complicated, it's a good practice to use [services](/cms/backend-customization/services) to organize the code into re-usable parts.
Simplified Strapi backend diagram with controllers highlighted
该图表示请求在 Strapi 后端传递的简化版本,控制器被高亮。后端自定义介绍页面包含一个完整的, 交互式图表
:::caution Sanitize inputs and outputs 在重写核心操作时,始终验证和清理查询和响应,以避免泄露私有字段或绕过访问规则。在从自定义操作返回数据之前,使用 `validateQuery`(可选)、`sanitizeQuery`(推荐)和 `sanitizeOutput`。请参见下面的示例,了解安全的 `find` 重写。 🌐 When overriding core actions, always validate and sanitize queries and responses to avoid leaking private fields or bypassing access rules. Use `validateQuery` (optional), `sanitizeQuery` (recommended), and `sanitizeOutput` before returning data from custom actions. See the example below for a safe `find` override. ::: ## 实现 {#implementation} 🌐 Implementation 控制器可以[生成或手动添加](#adding-a-new-controller)。Strapi 提供了一个 `createCoreController` 工厂函数,可以自动生成核心控制器,并允许构建自定义控制器或[扩展或替换生成的控制器](#extending-core-controllers)。 🌐 Controllers can be [generated or added manually](#adding-a-new-controller). Strapi provides a `createCoreController` factory function that automatically generates core controllers and allows building custom ones or [extend or replace the generated controllers](#extending-core-controllers). ### 添加新控制器 {#adding-a-new-controller} 🌐 Adding a new controller 可以实现一个新的控制器: 🌐 A new controller can be implemented: - 使用 [交互式 CLI 命令 `strapi generate`](/cms/cli) - 或通过创建 JavaScript 文件手动: - 在 `./src/api/[api-name]/controllers/` 中用于 API 控制器(此位置很重要,因为 Strapi 会从这里自动加载控制器) - 或者在像 `./src/plugins/[plugin-name]/server/controllers/` 这样的文件夹中用于插件控制器,尽管它们可以创建在其他地方,只要插件接口在 `strapi-server.js` 文件中正确导出(参见 [插件的服务器 API 文档](/cms/plugins-development/server-api)) ```js title="./src/api/restaurant/controllers/restaurant.js" const { createCoreController } = require('@strapi/strapi').factories; module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) => ({ // Method 1: Creating an entirely custom action async exampleAction(ctx) { try { ctx.body = 'ok'; } catch (err) { ctx.body = err; } }, // Method 2: Wrapping a core action (leaves core logic in place) async find(ctx) { // some custom logic here ctx.query = { ...ctx.query, local: 'en' } // Calling the default core action const { data, meta } = await super.find(ctx); // some more custom logic meta.date = Date.now() return { data, meta }; }, // Method 3: Replacing a core action with proper sanitization async find(ctx) { // validateQuery (optional) // to throw an error on query params that are invalid or the user does not have access to await this.validateQuery(ctx); // sanitizeQuery to remove any query params that are invalid or the user does not have access to // It is strongly recommended to use sanitizeQuery even if validateQuery is used const sanitizedQueryParams = await this.sanitizeQuery(ctx); const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams); const sanitizedResults = await this.sanitizeOutput(results, ctx); return this.transformResponse(sanitizedResults, { pagination }); } })); ``` ```ts title="./src/api/restaurant/controllers/restaurant.ts" // Method 1: Creating an entirely custom action async exampleAction(ctx) { try { ctx.body = 'ok'; } catch (err) { ctx.body = err; } }, // Method 2: Wrapping a core action (leaves core logic in place) async find(ctx) { // some custom logic here ctx.query = { ...ctx.query, local: 'en' } // Calling the default core action const { data, meta } = await super.find(ctx); // some more custom logic meta.date = Date.now() return { data, meta }; }, // Method 3: Replacing a core action with proper sanitization async find(ctx) { // validateQuery (optional) // to throw an error on query params that are invalid or the user does not have access to await this.validateQuery(ctx); // sanitizeQuery to remove any query params that are invalid or the user does not have access to // It is strongly recommended to use sanitizeQuery even if validateQuery is used const sanitizedQueryParams = await this.sanitizeQuery(ctx); const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams); // sanitizeOutput to ensure the user does not receive any data they do not have access to const sanitizedResults = await this.sanitizeOutput(results, ctx); return this.transformResponse(sanitizedResults, { pagination }); } })); ``` 每个控制器动作可以是 `async` 或 `sync` 函数。每个动作都接收一个上下文对象(`ctx`)作为参数。`ctx` 包含 [请求上下文](/cms/backend-customization/requests-responses#ctxrequest) 和 [响应上下文](/cms/backend-customization/requests-responses#ctxresponse)。 🌐 Each controller action can be an `async` or `sync` function. Every action receives a context object (`ctx`) as a parameter. `ctx` contains the [request context](/cms/backend-customization/requests-responses#ctxrequest) and the [response context](/cms/backend-customization/requests-responses#ctxresponse).
示例:GET /hello 路由调用一个基本控制器 定义了一个特定的 `GET /hello` [路由](/cms/backend-customization/routes),路由文件的名称(即 `index`)用于调用控制器处理程序(即 `index`)。每次向服务器发送 `GET /hello` 请求时,Strapi 会在 `hello.js` 控制器中调用 `index` 操作,并返回 `Hello World!`: 🌐 A specific `GET /hello` [route](/cms/backend-customization/routes) is defined, the name of the router file (i.e. `index`) is used to call the controller handler (i.e. `index`). Every time a `GET /hello` request is sent to the server, Strapi calls the `index` action in the `hello.js` controller, which returns `Hello World!`: ```js "title="./src/api/hello/routes/hello.js" module.exports = { routes: [ { method: 'GET', path: '/hello', handler: 'api::hello.hello.index', } ] } ``` ```js title="./src/api/hello/controllers/hello.js" module.exports = { async index(ctx, next) { // called by GET /hello ctx.body = 'Hello World!'; // we could also send a JSON }, }; ``` ```js title="./src/api/hello/routes/hello.ts" routes: [ { method: 'GET', path: '/hello', handler: 'api::hello.hello.index', } ] } ``` ```js title="./src/api/hello/controllers/hello.ts" async index(ctx, next) { // called by GET /hello ctx.body = 'Hello World!'; // we could also send a JSON }, }; ```
:::note 当创建一个新的 [内容类型](/cms/backend-customization/models#content-types) 时,Strapi 会生成一个带有占位代码的通用控制器,准备进行自定义。 🌐 When a new [content-type](/cms/backend-customization/models#content-types) is created, Strapi builds a generic controller with placeholder code, ready to be customized. ::: :::tip 要了解自定义控制器可能的高级用法,请阅读后端自定义示例手册的 [服务和控制器](/cms/backend-customization/examples/services-and-controllers) 页面。 🌐 To see a possible advanced usage for custom controllers, read the [services and controllers](/cms/backend-customization/examples/services-and-controllers) page of the backend customization examples cookbook. ::: ### 控制器与路由:路由如何到达控制器动作 {#controllers--routes-how-routes-reach-controller-actions} 🌐 Controllers & Routes: How routes reach controller actions - 核心映射是自动的:当你生成一个内容类型时,Strapi 会创建匹配的控制器和一个已经指向标准操作(`find`、`findOne`、`create`、`update` 和 `delete`)的路由文件。在生成的控制器中覆盖这些操作中的任何一个不需要修改路由——路由保持相同的处理程序字符串并执行你更新后的逻辑。 - 添加路由应仅针对新的操作或路径进行。如果引入一个全新的方法,例如 `exampleAction`,则创建或更新一个路由条目,其 `handler` 指向该操作,以便 HTTP 请求可以访问它。使用完整限定的处理程序语法 `::..`(例如 API 控制器的 `api::restaurant.restaurant.exampleAction` 或插件控制器的 `plugin::menus.menu.exampleAction`)。 - 关于控制器和路由文件名:默认的控制器名称来自 `./src/api/[api-name]/controllers/` 中的文件名。使用 `createCoreRouter` 创建的核心路由采用相同的名称,因此生成的处理程序字符串会自动匹配。自定义路由可以遵循任何文件命名方案,只要 `handler` 字符串引用一个导出的控制器操作。 :::note About core mapping Strapi 为内容类型生成的 REST 路由将每个 HTTP 方法指向一个处理器字符串(例如 `api::restaurant.restaurant.find`)。该字符串已经标识了控制器文件和导出的动作名称(`find`、`findOne`、`create`、`update` 或 `delete`)。当你更改这些导出中的实现但保留名称时,路由仍然会调用你的代码。只有在你添加默认 CRUD 集之外的新动作名称或路径时,才编辑路由文件。 🌐 The REST routes Strapi generates for a content-type point each HTTP method at a handler string (for example `api::restaurant.restaurant.find`). That string already identifies the controller file and the exported action name (`find`, `findOne`, `create`, `update`, or `delete`). When you change the implementation inside those exports but keep the names, the router still calls your code. Edit the route file only when you add a new action name or path outside that default CRUD set. ::: 以下示例添加了一个新的控制器操作,并通过自定义路由将其公开,而不会重复现有的 CRUD 路由定义: 🌐 The example below adds a new controller action and exposes it through a custom route without duplicating the existing CRUD route definitions: ```js title="./src/api/restaurant/controllers/restaurant.js" const { createCoreController } = require('@strapi/strapi').factories; module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) => ({ async exampleAction(ctx) { const specials = await strapi.service('api::restaurant.restaurant').find({ filters: { isSpecial: true } }); return this.transformResponse(specials.results); }, })); ``` ```js title="./src/api/restaurant/routes/01-custom-restaurant.js" module.exports = { routes: [ { method: 'GET', path: '/restaurants/specials', handler: 'api::restaurant.restaurant.exampleAction', }, ], }; ``` ### 控制器中的清理和验证 {#sanitization-and-validation-in-controllers} 🌐 Sanitization and Validation in controllers :::warning 强烈建议你使用新的 `sanitizeQuery` 和 `validateQuery` 函数对传入的请求查询进行消毒(v4.8.0+)和/或验证(v4.13.0+),以防止私有数据泄露。 🌐 It's strongly recommended you sanitize (v4.8.0+) and/or validate (v4.13.0+) your incoming request query utilizing the new `sanitizeQuery` and `validateQuery` functions to prevent the leaking of private data. ::: 清理意味着对象被“清理”并返回。 🌐 Sanitization means that the object is “cleaned” and returned. 验证意味着断言数据已经干净,如果发现不应该存在的内容,则会引发错误。 🌐 Validation means an assertion is made that the data is already clean and throws an error if something is found that shouldn't be there. 在 Strapi 5 中,查询参数和输入数据(即创建和更新的请求体数据)都会被验证。任何包含以下无效输入的创建和更新数据请求都会抛出 `400 Bad Request` 错误: 🌐 In Strapi 5, both query parameters and input data (i.e., create and update body data) are validated. Any create and update data requests with the following invalid input will throw a `400 Bad Request` error: - 用户无权创建的关系 - 模式中不存在的无法识别的值 - 不可写字段和内部时间戳,如 `createdAt` 和 `createdBy` 字段 - 设置或更新 `id` 字段(连接关系除外) :::note Internal `entity-validator` service Strapi 提供了一个低级别的 `entity-validator` 服务(可通过 `strapi.service('entity-validator')` 访问),它具有 `validateEntityCreation` 和 `validateEntityUpdate` 方法,文档服务在内部使用这些方法。这些方法不是公共 API 的一部分,其语义可能在不同版本之间发生变化。从自定义的控制器、服务或中间件中,应使用文档化的 `validateInput` 工厂助手或 [`strapi.contentAPI.validate.*` 助手](#sanitize-validate-custom-controllers)。它们应用相同的内容类型模式规则,并额外考虑权限、不可写字段以及请求身份验证策略。 🌐 Strapi exposes a low-level `entity-validator` service (accessible through `strapi.service('entity-validator')`) with `validateEntityCreation` and `validateEntityUpdate` methods that the document service uses internally. These are not part of the public API and their semantics can change between releases. From a custom controller, service, or middleware, use the documented `validateInput` factory helper or the [`strapi.contentAPI.validate.*` helpers](#sanitize-validate-custom-controllers) instead. They apply the same content-type schema rules and additionally account for permissions, non-writable fields, and the request authentication strategy. ::: #### 使用控制器工厂时的消毒 {#sanitization-when-utilizing-controller-factories} 🌐 Sanitization when utilizing controller factories 在 Strapi 工厂中,公开了以下可用于清理和验证的函数: 🌐 Within the Strapi factories the following functions are exposed that can be used for sanitization and validation: | 函数名称 | 参数 | 描述 | |------------------|----------------------------|--------------------------------------------------------------------------------------| | `sanitizeQuery` | `ctx` | 清理请求查询 | | `sanitizeOutput` | `entity`/`entities`, `ctx` | 清理输出数据,其中实体/实体集合应为对象或数据数组 | | `sanitizeInput` | `data`, `ctx` | 清理输入数据 | | `validateQuery` | `ctx` | 验证请求查询(在参数无效时抛出错误) | | `validateInput` | `data`, `ctx` | (实验性)验证输入数据(在数据无效时抛出错误) | 这些函数自动从模型继承清理设置,并根据内容类型架构和任何内容 API 身份验证策略(例如用户和权限插件或 API 令牌)相应地清理数据。 🌐 These functions automatically inherit the sanitization settings from the model and sanitize the data accordingly based on the content-type schema and any of the content API authentication strategies, such as the Users & Permissions plugin or API tokens. :::warning 因为这些方法使用的是与当前控制器关联的模型,如果你查询的数据来自另一个模型(例如,在“restaurant”控制器方法中查找“menus”),你必须改为使用 `strapi.contentAPI` 方法,例如在 [Sanitizing Custom Controllers](#sanitize-validate-custom-controllers) 中描述的 `strapi.contentAPI.sanitize.query`,否则你的查询结果将会被错误的模型进行清理。 🌐 Because these methods use the model associated with the current controller, if you query data that is from another model (i.e., doing a find for "menus" within a "restaurant" controller method), you must instead use the `strapi.contentAPI` methods, such as `strapi.contentAPI.sanitize.query` described in [Sanitizing Custom Controllers](#sanitize-validate-custom-controllers), or else the result of your query will be sanitized against the wrong model. ::: ```js title="./src/api/restaurant/controllers/restaurant.js" const { createCoreController } = require('@strapi/strapi').factories; module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) => ({ async find(ctx) { await this.validateQuery(ctx); const sanitizedQueryParams = await this.sanitizeQuery(ctx); const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams); const sanitizedResults = await this.sanitizeOutput(results, ctx); return this.transformResponse(sanitizedResults, { pagination }); } })); ``` ```js title="./src/api/restaurant/controllers/restaurant.ts" async find(ctx) { const sanitizedQueryParams = await this.sanitizeQuery(ctx); const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams); const sanitizedResults = await this.sanitizeOutput(results, ctx); return this.transformResponse(sanitizedResults, { pagination }); } })); ``` #### 构建自定义控制器时的清理和验证 {#sanitize-validate-custom-controllers} 🌐 Sanitization and validation when building custom controllers 在自定义控制器中,Strapi 通过 `strapi.contentAPI` 提供以下用于清理和验证的函数。要向内容 API 路由(例如在 `register` 中)添加自定义查询或请求体参数,请参见 [自定义内容 API 参数](/cms/backend-customization/routes#custom-content-api-parameters)。 🌐 Within custom controllers, Strapi exposes the following functions via `strapi.contentAPI` for sanitization and validation. To add custom query or body parameters to Content API routes (e.g. in `register`), see [Custom Content API parameters](/cms/backend-customization/routes#custom-content-api-parameters). | 函数名 | 参数 | 描述 | |------------------------------|--------------------|---------------------------------------------------------| | `strapi.contentAPI.sanitize.input` | `data`,`schema`,`auth` | 清理请求输入,包括不可写字段,移除受限制的关系,以及其他由插件添加的嵌套“访问者” | | `strapi.contentAPI.sanitize.output` | `data`、`schema`、`auth` | 清理响应输出,包括受限制的关系、私有字段、密码以及插件添加的其他嵌套“访问者” | | `strapi.contentAPI.sanitize.query` | `ctx.query`、`schema`、`auth` | 清理请求查询,包括过滤器、排序、字段和填充 | | `strapi.contentAPI.validate.query` | `ctx.query`、`schema`、`auth` | 验证请求查询,包括过滤器、排序、字段(当前未填充) | | `strapi.contentAPI.validate.input` | `data`、`schema`、`auth` |(实验性)验证请求输入,包括不可写字段、移除受限关系,以及插件添加的其他嵌套“访问器”| :::note 根据自定义控制器的复杂性,你可能需要 Strapi 目前无法考虑的额外清理,尤其是在组合多个来源的数据时。 🌐 Depending on the complexity of your custom controllers, you may need additional sanitization that Strapi cannot currently account for, especially when combining the data from multiple sources. ::: ```js title="./src/api/restaurant/controllers/restaurant.js" module.exports = { async findCustom(ctx) { const contentType = strapi.contentType('api::test.test'); await strapi.contentAPI.validate.query(ctx.query, contentType, { auth: ctx.state.auth }); const sanitizedQueryParams = await strapi.contentAPI.sanitize.query(ctx.query, contentType, { auth: ctx.state.auth }); const documents = await strapi.documents(contentType.uid).findMany(sanitizedQueryParams); return await strapi.contentAPI.sanitize.output(documents, contentType, { auth: ctx.state.auth }); } } ``` ```js title="./src/api/restaurant/controllers/restaurant.ts" async findCustom(ctx) { const contentType = strapi.contentType('api::test.test'); await strapi.contentAPI.validate.query(ctx.query, contentType, { auth: ctx.state.auth }); const sanitizedQueryParams = await strapi.contentAPI.sanitize.query(ctx.query, contentType, { auth: ctx.state.auth }); const documents = await strapi.documents(contentType.uid).findMany(sanitizedQueryParams); return await strapi.contentAPI.sanitize.output(documents, contentType, { auth: ctx.state.auth }); } } ``` ### 扩展核心控制器 {#extending-core-controllers} 🌐 Extending core controllers 每种内容类型都会创建默认的控制器和操作。这些默认控制器用于响应 API 请求(例如,当访问 `GET /api/articles/3` 时,会调用 “Article” 内容类型默认控制器的 `findOne` 操作)。默认控制器可以自定义以实现你自己的逻辑。以下代码示例应能帮助你入门。 🌐 Default controllers and actions are created for each content-type. These default controllers are used to return responses to API requests (e.g. when `GET /api/articles/3` is accessed, the `findOne` action of the default controller for the "Article" content-type is called). Default controllers can be customized to implement your own logic. The following code examples should help you get started. :::tip - 核心控制器的一个操作可以完全通过[创建自定义操作](#adding-a-new-controller)来替换,并将该操作命名为与原操作相同的名称(例如 `find`、`findOne`、`create`、`update` 或 `delete`)。 - 在扩展核心控制器时,你无需重新实现任何清理功能,因为这些功能已经由你扩展的核心控制器处理。尽可能强烈建议扩展核心控制器,而不是创建自定义控制器。 :::
集合类型示例 :::tip [后端自定义示例手册](/cms/backend-customization/examples)展示了如何覆盖默认的控制器操作,例如针对[`create`操作](/cms/backend-customization/examples/services-and-controllers#custom-controller)。 🌐 The [backend customization examples cookbook](/cms/backend-customization/examples) shows how you can overwrite a default controller action, for instance for the [`create` action](/cms/backend-customization/examples/services-and-controllers#custom-controller). ::: ```js async find(ctx) { // some logic here const { data, meta } = await super.find(ctx); // some more logic return { data, meta }; } ``` ```js async findOne(ctx) { // some logic here const response = await super.findOne(ctx); // some more logic return response; } ``` ```js async create(ctx) { // some logic here const response = await super.create(ctx); // some more logic return response; } ``` ```js async update(ctx) { // some logic here const response = await super.update(ctx); // some more logic return response; } ``` ```js async delete(ctx) { // some logic here const response = await super.delete(ctx); // some more logic return response; } ```
单类型示例 ```js async find(ctx) { // some logic here const response = await super.find(ctx); // some more logic return response; } ``` ```js async update(ctx) { // some logic here const response = await super.update(ctx); // some more logic return response; } ``` ```js async delete(ctx) { // some logic here const response = await super.delete(ctx); // some more logic return response; } ```
## 使用 {#usage} 🌐 Usage 控制器被声明并附加到一个路由上。当路由被调用时,控制器会自动被调用,因此通常不需要显式调用控制器。但是,[服务](/cms/backend-customization/services) 可以调用控制器,在这种情况下应使用以下语法: 🌐 Controllers are declared and attached to a route. Controllers are automatically called when the route is called, so controllers usually do not need to be called explicitly. However, [services](/cms/backend-customization/services) can call controllers, and in this case the following syntax should be used: ```js // access an API controller strapi.controller('api::api-name.controller-name'); // access a plugin controller strapi.controller('plugin::plugin-name.controller-name'); ``` :::tip 要列出所有可用的控制器,请运行 `yarn strapi controllers:list`。 🌐 To list all the available controllers, run `yarn strapi controllers:list`. ::: # 后端定制示例手册 Source: https://strapi.nodejs.cn/cms/backend-customization/examples # 后端自定义:使用 FoodAdvisor 的示例秘诀 {#backend-customization-an-examples-cookbook-using-foodadvisor} 🌐 Backend customization: An examples cookbook using FoodAdvisor 一本使用 FoodAdvisor 演示应用的真实后端定制示例的烹饪书,演示如何在 Strapi 中实现自定义路由、控制器、服务、策略和中间件。 🌐 A cookbook of real-world backend customization examples using the FoodAdvisor demo application, demonstrating how to implement custom routes, controllers, services, policies, and middlewares in Strapi. :::callout 🏗 About these examples 这些示例是围绕 [FoodAdvisor](https://github.com/strapi/foodadvisor)构建的,而 [FoodAdvisor](https://github.com/strapi/foodadvisor)不再是Strapi的特色演示应用(它已被 [LaunchPad](https://github.com/strapi/LaunchPad)取代)。这里重要的是理解所展示的后端机制,而不是FoodAdvisor本身。这些页面将被重新访问以使用LaunchPad。 ::: 本文档的当前部分面向希望更深入了解 Strapi 后端定制可能性的开发者。 🌐 The present section of the documentation is intended for developers who would like to get a deeper understanding of the Strapi back end customization possibilities. 本节是示例的集合,演示了 Strapi 后端服务器的核心组件如何在实际项目中使用。与后端交互的前端代码也可能是某些示例的一部分,但默认情况下会以折叠块显示,因为前端代码示例并不是本手册的主要关注点。 🌐 The section is a collection of examples that demonstrate how the core components of the back-end server of Strapi can be used in a real-world project. Front-end code that interacts with the back end may also be part of some examples, but displayed in collapsed blocks by default since front-end code examples are not the main focus of this cookbook. 示例旨在扩展 [FoodAdvisor](https://github.com/strapi/foodadvisor)的功能,这是官方的 Strapi 演示应用。FoodAdvisor 构建了一个现成的餐馆目录,由 Strapi 后端驱动(包含在 `/api` 文件夹中),并呈现由 [Next.js](https://next.nodejs.cn/) 驱动的前端网站(包含在 `/client` 文件夹中)。 :::prerequisites - 👀 你已经阅读了 [快速入门指南](/cms/quick-start) 和/或理解了 Strapi 是一个 **无头 CMS** 无头 CMS 是一种将呈现层(即前端,内容展示的地方)与后端(内容管理的地方)分离的内容管理系统。

Strapi 是一个无头 CMS,它提供:
  • 一个用于你内容的后端服务器 API,
  • 以及一个图形用户界面,称为管理面板,用于管理内容。
呈现层应由其他框架处理,而不是由 Strapi 处理。 它帮助你通过 [内容类型构建器](/cms/features/content-type-builder) 创建内容结构,并通过 [内容管理器](/cms/features/content-manager) 添加一些内容,然后通过 API 公开内容。 - 👀 你已经阅读了[后端自定义介绍](/cms/backend-customization),以便对 Strapi 中的路由、策略、中间件、控制器和服务有一个大致的了解。 - 👷 如果你想自己测试和玩这些代码示例,请确保你已经克隆了 [FoodAdvisor](https://github.com/strapi/foodadvisor) 仓库,设置了项目,并启动了前端和后端服务器。Strapi 管理面板应该可以从 [`localhost:1337/admin`](http://localhost:1337/admin) 访问,而基于 Next.js 的 FoodAdvisor 前端网站应该在 [`localhost:3000`](http://localhost:3000) 运行。 ::: 你可以从头到尾阅读本节,或者你可能想直接跳转到特定页面以了解如何使用 Strapi 后端的给定核心元素来解决实际用例示例: 🌐 This section can be read from start to finish, or you might want to jump directly to a specific page to understand how a given core element from the Strapi back end can be used to solve a real-world use case example: | 我想了解… | 专用页面 | |------------|---------------| | 如何验证我的查询 | [使用 JWT 的身份验证流程](/cms/backend-customization/examples/authentication) | | 如何以及何时使用
自定义控制器和服务 | [自定义控制器和服务示例](/cms/backend-customization/examples/services-and-controllers) | | 如何使用自定义策略
并发送自定义错误 | [自定义策略示例](/cms/backend-customization/examples/policies) | | 如何配置和使用自定义路由 | [自定义路由示例](/cms/backend-customization/examples/routes) | | 如何以及何时使用
自定义全局中间件 | [自定义中间件示例](/cms/backend-customization/examples/middlewares) | # 使用 JWT 的身份验证流程 Source: https://strapi.nodejs.cn/cms/backend-customization/examples/authentication # 示例手册:使用 JWT 的认证流程 {#examples-cookbook-authentication-flow-with-jwt} 🌐 Examples cookbook: Authentication flow with JWT 使用 JWT 验证 REST API 请求,通过将凭证发送到 `/auth/local` 端点并将令牌存储在 `localStorage`,可选的会话管理支持刷新令牌。 🌐 Authenticate REST API requests using JWT by sending credentials to the `/auth/local` endpoint and storing the token in `localStorage`, with optional session management for refresh token support. :::prerequisites 此页面是后端自定义示例手册的一部分。请确保你已阅读其[介绍](/cms/backend-customization/examples)。 🌐 This page is part of the back end customization examples cookbook. Please ensure you've read its [introduction](/cms/backend-customization/examples). ::: **💭 上下文:** 开箱即用, [FoodAdvisor](https://github.com/strapi/foodadvisor) 的前端网站没有提供任何登录功能。登录是通过访问 Strapi 的管理面板完成的,地址为 [`localhost:1337/admin`](http://localhost:1337/admin`)。 让我们在前端添加一个基本的登录页面,该前端是由 [Next.js](https://next.nodejs.cn/)驱动的网站,包含在 FoodAdvisor 的 `/client` 文件夹中。登录页面可以通过 [`localhost:3000/auth/login`](http://localhost:3000/auth/login) 访问,并包含一个典型的邮箱/密码登录表单。这将允许以编程方式验证发送到 Strapi 的 API 请求。
Example login page
FoodAdvisor 前端网站上登录表单的一个可能示例
**🎯 目标**: 创建前端组件以: 🌐 Create a front-end component to: 1. 显示登录表单, 2. 向 Strapi 后端服务器的 `/auth/local` 路由发送请求以进行身份验证, 3. 获取一个 [JSON Web Token](https://en.wikipedia.org/wiki/JSON_Web_Token) (JWT), 4. 并将 JWT 存储到浏览器的 [`localStorage`](https://web.nodejs.cn/en-US/docs/Web/API/Window/localStorage) 属性中,以便以后检索和认证我们的请求。 **相关概念** 有关 JWT 认证的更多信息可以在 [用户与权限插件](/cms/features/users-permissions) 文档中找到。 🌐 Additional information about JWT authentication can be found in the [Users & Permissions plugin](/cms/features/users-permissions) documentation. **🧑‍💻 代码示例:** 为了实现这一点,在 [FoodAdvisor](https://github.com/strapi/foodadvisor) 项目的`/client`文件夹中,你可以创建一个包含以下示例代码的`pages/auth/login.js`文件。高亮的行显示了发送到 Strapi 的用户与权限插件提供的`/auth/local`路由的请求: 此文件使用了 formik 包 - 使用 `yarn add formik` 安装它并重启开发服务器。 🌐 This file uses the formik package - install it using `yarn add formik` and restart the dev server. ```jsx title="/client/pages/auth/login.js" {21-27} const Login = () => { const { handleSubmit, handleChange } = useFormik({ initialValues: { identifier: '', password: '', }, onSubmit: async (values) => { /** * API URLs in Strapi are by default prefixed with /api, * but because the API prefix can be configured * with the rest.prefix property in the config/api.js file, * we use the getStrapiURL() method to build the proper full auth URL. **/ const res = await fetch(getStrapiURL('/auth/local'), { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(values), }); /** * Gets the JWT from the server response. * The actual response is { jwt, user }, but we only need the JWT here. */ const { jwt } = await res.json(); /** * Stores the JWT in the localStorage of the browser. * A better implementation would be to do this with an authentication context provider * or something more sophisticated, but it's not the purpose of this tutorial. */ localStorage.setItem('token', jwt); }, }); /** * The following code renders a basic login form * accessible from the localhost:3000/auth/login page. */ return (

Login

Login
); }; ``` ## 增强认证与会话管理 {#enhanced-authentication-with-session-management} 🌐 Enhanced authentication with session management 上面的示例使用了传统的 JWT 方法。为了增强安全性,你可以在“用户与权限”配置中启用会话管理模式,该模式提供生命周期更短的访问令牌和刷新令牌功能。 🌐 The above example uses the traditional JWT approach. For enhanced security, you can enable session management mode in your Users & Permissions configuration, which provides shorter-lived access tokens and refresh token functionality. ### 配置 {#configuration} 🌐 Configuration 首先,在你的 `/config/plugins.js` 中启用会话管理: 🌐 First, enable session management in your `/config/plugins.js`: ```js title="/config/plugins.js" module.exports = ({ env }) => ({ 'users-permissions': { config: { jwtManagement: 'refresh', sessions: { accessTokenLifespan: 600, // 10 minutes (default) maxRefreshTokenLifespan: 2592000, // 30 days (default) idleRefreshTokenLifespan: 1209600, // 14 days (default) maxSessionLifespan: 86400, // 1 day (default) idleSessionLifespan: 7200, // 2 hours (default) }, }, }, }); ``` ### 增强的登录组件 {#enhanced-login-component} 🌐 Enhanced login component 这是一个更新的登录组件,可以同时处理 JWT 和刷新令牌: 🌐 Here's an updated login component that handles both JWT and refresh tokens: ```jsx title="/client/pages/auth/enhanced-login.js" const EnhancedLogin = () => { const [isLoading, setIsLoading] = useState(false); const { handleSubmit, handleChange } = useFormik({ initialValues: { identifier: '', password: '', }, onSubmit: async (values) => { setIsLoading(true); try { const res = await fetch(getStrapiURL('/auth/local'), { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(values), }); const data = await res.json(); if (res.ok) { // Store both tokens (session management mode) if (data.refreshToken) { localStorage.setItem('accessToken', data.jwt); localStorage.setItem('refreshToken', data.refreshToken); } else { // Legacy mode - single JWT localStorage.setItem('token', data.jwt); } // Redirect to protected area window.location.href = '/dashboard'; } else { console.error('Login failed:', data.error); } } catch (error) { console.error('Login error:', error); } finally { setIsLoading(false); } }, }); return (

Enhanced Login

{isLoading ? 'Logging in...' : 'Login'}
); }; ```
:::strapi What's next? 了解更多关于自定义[服务和控制器](/cms/backend-customization/examples/services-and-controllers)如何帮助你调整基于 Strapi 的应用的信息。 🌐 Learn more about how custom [services and controllers](/cms/backend-customization/examples/services-and-controllers) can help you tweak a Strapi-based application. ::: # 自定义中间件 Source: https://strapi.nodejs.cn/cms/backend-customization/examples/middlewares # 示例手册:自定义全局中间件 {#examples-cookbook-custom-global-middlewares} 🌐 Examples cookbook: Custom global middlewares 自定义全局中间件在控制器执行之前拦截传入请求,使你能够添加诸如分析跟踪等逻辑。此示例创建了一个中间件,将餐厅页面访问记录到 Google 表格中。 🌐 Custom global middlewares intercept incoming requests before controller execution, enabling you to add logic like analytics tracking. This example creates a middleware that logs restaurant page visits to a Google Sheet. :::callout 🏗 About these examples 这些示例是围绕 [FoodAdvisor](https://github.com/strapi/foodadvisor)构建的,而 [FoodAdvisor](https://github.com/strapi/foodadvisor)不再是Strapi的特色演示应用(它已被 [LaunchPad](https://github.com/strapi/LaunchPad)取代)。这里重要的是理解所展示的后端机制,而不是FoodAdvisor本身。这些页面将被重新访问以使用LaunchPad。 ::: :::prerequisites 此页面是后端自定义示例手册的一部分。请确保你已阅读其[介绍](/cms/backend-customization/examples)。 🌐 This page is part of the back end customization examples cookbook. Please ensure you've read its [introduction](/cms/backend-customization/examples). ::: 开箱即用, [FoodAdvisor](https://github.com/strapi/foodadvisor) 不提供任何可以使用传入请求并在执行控制器代码之前执行一些额外逻辑的自定义中间件。 Strapi 中有两种类型的中间件:**路由中间件** 控制对某个路由的访问,而 **全局中间件** 的作用范围更广(参见参考文档中的 [中间件自定义](/cms/backend-customization/middlewares))。 🌐 There are 2 types of middlewares in Strapi: **route middlewares** control access to a route while **global middlewares** have a wider scope (see reference documentation for [middlewares customization](/cms/backend-customization/middlewares)). 可以使用自定义路由中间件来代替策略控制终端的访问(参见 [policies cookbook](/cms/backend-customization/examples/policies)),并且可以在将上下文传递给 Strapi 服务器的核心其他元素之前修改上下文。此页面不会介绍自定义路由中间件,而是说明 **自定义全局中间件** 的更复杂用法。 🌐 Custom route middlewares could be used instead of policies to control access to an endpoint (see [policies cookbook](/cms/backend-customization/examples/policies)) and could modify the context before passing it down to further core elements of the Strapi server. This page will _not_ cover custom route middlewares but rather illustrate a more elaborated usage for **custom global middlewares**. ## 使用自定义中间件在 Google 表格中填充分析仪表板 {#populating-an-analytics-dashboard-in-google-sheets-with-a-custom-middleware} 🌐 Populating an analytics dashboard in Google Sheets with a custom middleware **💭 上下文:** 本质上,中间件是在请求到达服务器和控制器函数执行之间执行的。因此,例如,中间件是执行某些分析的好地方。 🌐 In essence, a middleware gets executed between a request arriving at the server and the controller function getting executed. So, for instance, a middleware is a good place to perform some analytics. 让我们创建一个用 Google 电子表格制作的基础分析仪表板示例,以了解 [FoodAdvisor](https://github.com/strapi/foodadvisor) 的哪些餐厅页面访问量更高。
Visiting a restaurant page updates the Google Sheets spreadsheet
对餐厅页面的每个 GET 请求都会执行自定义中间件的代码,实时更新 Google 表格电子表格。
**🎯 目标**: - 创建一些与 Google Sheets 交互的实用函数。 - 创建一个自定义 Strapi 中间件,每次我们收到对 FoodAdvisor 项目的餐厅页面的传入请求时,该中间件都会创建和/或更新现有的 Google Sheet 文档。 - 将自定义中间件附加到我们希望执行它的路由。 **相关概念** 更多信息可以在 [中间件自定义](/cms/backend-customization/middlewares) 文档中找到。 🌐 Additional information can be found in the [middlewares customization](/cms/backend-customization/middlewares) documentation. **🧑‍💻 代码示例:** 1. 在 [FoodAdvisor](https://github.com/strapi/foodadvisor) 项目的`/api`文件夹中,创建一个包含以下示例代码的`/restaurant/middlewares/utils.js`文件:
可用于读取、写入和更新 Google 电子表格的示例实用函数: 以下代码允许在给定从 JSON 文件读取的 API 密钥和从 URL 检索的电子表格 ID 的情况下读取、写入和更新 Google 电子表格: ![Google 电子表格链接](/img/assets/backend-customization/tutorial-spreadsheet-url.png) 更多信息可以在官方 [Google Sheets API documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values?hl=es-419)中找到。 ```jsx title="src/api/restaurant/middlewares/utils.js" const { google } = require('googleapis'); const createGoogleSheetClient = async ({ keyFile, sheetId, tabName, range, }) => { async function getGoogleSheetClient() { const auth = new google.auth.GoogleAuth({ keyFile, scopes: ['https://www.googleapis.com/auth/spreadsheets'], }); const authClient = await auth.getClient(); return google.sheets({ version: 'v4', auth: authClient, }); } const googleSheetClient = await getGoogleSheetClient(); const writeGoogleSheet = async (data) => { googleSheetClient.spreadsheets.values.append({ spreadsheetId: sheetId, range: `${tabName}!${range}`, valueInputOption: 'USER_ENTERED', insertDataOption: 'INSERT_ROWS', resource: { majorDimension: 'ROWS', values: data, }, }); }; const updateoogleSheet = async (cell, data) => { googleSheetClient.spreadsheets.values.update({ spreadsheetId: sheetId, range: `${tabName}!${cell}`, valueInputOption: 'USER_ENTERED', resource: { majorDimension: 'ROWS', values: data, }, }); }; const readGoogleSheet = async () => { const res = await googleSheetClient.spreadsheets.values.get({ spreadsheetId: sheetId, range: `${tabName}!${range}`, }); return res.data.values; }; return { writeGoogleSheet, updateoogleSheet, readGoogleSheet, }; }; module.exports = { createGoogleSheetClient, }; ```
2. 在 FoodAdvisor 项目的 `/api` 文件夹中,创建一个自定义的 `analytics` 中间件,并使用以下代码: ```jsx title="src/api/restaurant/middlewares/analytics.js" 'use strict'; const { createGoogleSheetClient } = require('./utils'); const serviceAccountKeyFile = './gs-keys.json'; // Replace the sheetId value with the corresponding id found in your own URL const sheetId = '1P7Oeh84c18NlHp1Zy-5kXD8zgpoA1WmvYL62T4GWpfk'; const tabName = 'Restaurants'; const range = 'A2:C'; const VIEWS_CELL = 'C'; const transformGSheetToObject = (response) => response.reduce( (acc, restaurant) => ({ ...acc, [restaurant[0]]: { id: restaurant[0], name: restaurant[1], views: restaurant[2], cellNum: Object.keys(acc).length + 2 // + 2 because we need to consider the header and that the initial length is 0, so our first real row would be 2, }, }), {} ); module.exports = (config, { strapi }) => { return async (context, next) => { // Generating google sheet client const { readGoogleSheet, updateoogleSheet, writeGoogleSheet } = await createGoogleSheetClient({ keyFile: serviceAccountKeyFile, range, sheetId, tabName, }); // Get the restaurant documentId from the params in the URL const restaurantId = context.params.id; const restaurant = await strapi.documents('api::restaurant.restaurant').findOne({ documentId: restaurantId, }); // Read the spreadsheet to get the current data const restaurantAnalytics = await readGoogleSheet(); /** * The returned data comes in the shape [1, "Mint Lounge", 23], * and we need to transform it into an object: {id: 1, name: "Mint Lounge", views: 23, cellNum: 2} */ const requestedRestaurant = transformGSheetToObject(restaurantAnalytics)[restaurantId]; if (requestedRestaurant) { await updateoogleSheet( `${VIEWS_CELL}${requestedRestaurant.cellNum}:${VIEWS_CELL}${requestedRestaurant.cellNum}`, [[Number(requestedRestaurant.views) + 1]] ); } else { /** If we don't have the restaurant in the spreadsheet already, * we create it with 1 view. */ const newRestaurant = [[restaurant.id, restaurant.name, 1]]; await writeGoogleSheet(newRestaurant); } // Call next to continue with the flow and get to the controller await next(); }; }; ``` 3. 配置“Restaurants”内容类型的路由,以便在查询餐厅页面时执行自定义的 `analytics` 中间件。为此,请使用以下代码: ```jsx title="src/api/restaurant/routes/restaurant.js" 'use strict'; const { createCoreRouter } = require('@strapi/strapi').factories; module.exports = createCoreRouter('api::restaurant.restaurant', { config: { findOne: { auth: false, policies: [], middlewares: ['api::restaurant.analytics'], }, }, }); ``` # 自定义策略 Source: https://strapi.nodejs.cn/cms/backend-customization/examples/policies # 示例手册:自定义策略 {#examples-cookbook-custom-policies} 🌐 Examples cookbook: Custom policies 自定义策略通过允许或阻止请求来控制对内容类型端点的访问,并且可以使用 `PolicyError` 抛出自定义错误,以实现更好的错误处理和前端集成。 🌐 Custom policies control access to content-type endpoints by allowing or blocking requests, and can throw custom errors using `PolicyError` for better error handling and front-end integration. :::callout 🏗 About these examples 这些示例是围绕 [FoodAdvisor](https://github.com/strapi/foodadvisor)构建的,而 [FoodAdvisor](https://github.com/strapi/foodadvisor)不再是Strapi的特色演示应用(它已被 [LaunchPad](https://github.com/strapi/LaunchPad)取代)。这里重要的是理解所展示的后端机制,而不是FoodAdvisor本身。这些页面将被重新访问以使用LaunchPad。 ::: :::prerequisites 此页面是后端自定义示例手册的一部分。请确保你已阅读其[介绍](/cms/backend-customization/examples)。 🌐 This page is part of the back end customization examples cookbook. Please ensure you've read its [introduction](/cms/backend-customization/examples). ::: 开箱即用, [FoodAdvisor](https://github.com/strapi/foodadvisor) 不使用任何可以控制内容类型端点访问的自定义策略或路由中间件。 在 Strapi 中,可以使用策略或路由中间件来控制对内容类型端点的访问: 🌐 In Strapi, controlling access to a content-type endpoint can be done either with a policy or route middleware: - 策略是只读的,允许请求传递或返回错误, - 而路由中间件可以执行额外的逻辑。 在我们的示例中,我们使用一个策略。 🌐 In our example, let's use a policy. ## 创建自定义策略 {#creating-a-custom-policy} 🌐 Creating a custom policy **💭 上下文:** 假设我们想要自定义 [FoodAdvisor](https://github.com/strapi/foodadvisor) 的后台,以防止餐厅老板使用前端网站上[之前创建的表单](/cms/backend-customization/examples/services-and-controllers#rest-api-queries-from-the-front-end)为他们的业务创建虚假评论。 **🎯 目标**: 1. 为仅适用于“评论”集合类型的策略创建一个新文件夹。 2. 创建新的策略文件。 3. 当到达 `/reviews` 端点时,使用文档服务 API 的 `findMany()` 方法获取餐厅所有者的信息。 4. 如果经过身份验证的用户是餐厅的所有者,则返回错误,或者在其他情况下让请求通过。 **相关概念** 更多信息可以在[政策](/cms/backend-customization/policies)、[路线](/cms/backend-customization/routes)和[文档服务 API](/cms/api/document-service)文档中找到。 🌐 Additional information can be found in the [Policies](/cms/backend-customization/policies), [Routes](/cms/backend-customization/routes), and [Document Service API](/cms/api/document-service) documentation. **🧑‍💻 代码示例:** 在 [FoodAdvisor](https://github.com/strapi/foodadvisor) 项目的`/api`文件夹中,创建一个新的`src/api/review/policies/is-owner-review.js`文件,并输入以下代码: ```jsx title="src/api/review/policies/is-owner-review.js" module.exports = async (policyContext, config, { strapi }) => { const { body } = policyContext.request; const { user } = policyContext.state; // Return an error if there is no authenticated user with the request if (!user) { return false; } /** * Queries the Restaurants collection type * using the Document Service API * to retrieve information about the restaurant's owner. */ const [restaurant] = await strapi.documents('api::restaurant.restaurant').findMany({ filters: { slug: body.restaurant, }, populate: ['owner'], }); if (!restaurant) { return false; } /** * If the user submitting the request is the restaurant's owner, * we don't allow the review creation. */ if (user.id === restaurant.owner.id) { return false; } return true; }; ``` :::caution 策略或路由中间件应在路由配置中声明,以实际控制访问。有关路由的更多信息,请参阅[参考文档](/cms/backend-customization/routes)或在[路由示例手册](/cms/backend-customization/examples/routes)中查看示例。 🌐 Policies or route middlewares should be declared in the configuration of a route to actually control access. Read more about routes in the [reference documentation](/cms/backend-customization/routes) or see an example in the [routes cookbook](/cms/backend-customization/examples/routes). ::: ## 通过策略发送自定义错误 {#sending-custom-errors-through-policies} 🌐 Sending custom errors through policies **💭 上下文:** 开箱即用时, [FoodAdvisor](https://github.com/strapi/foodadvisor) 在策略拒绝访问某条路由时会发送默认错误。假设我们想自定义当[之前创建的自定义策略](#creating-a-custom-policy)不允许创建评论时发送的错误。 **🎯 目标:** 配置自定义策略以引发自定义错误而不是默认错误。 🌐 Configure the custom policy to throw a custom error instead of the default error. **相关概念** 更多信息可以在 [错误处理](/cms/error-handling) 文档中找到。 🌐 Additional information can be found in the [Error handling](/cms/error-handling) documentation. **🧑‍💻 代码示例:** 在 [FoodAdvisor](https://github.com/strapi/foodadvisor) 项目的 `/api` 文件夹中,按如下方式更新[之前创建的 `is-owner-review` 自定义策略](#creating-a-custom-policy)(高亮显示的行是唯一修改的行): ```jsx title="src/api/review/policies/is-owner-review.js" showLineNumbers const { errors } = require('@strapi/utils'); const { PolicyError } = errors; module.exports = async (policyContext, config, { strapi }) => { const { body } = policyContext.request; const { user } = policyContext.state; // Return an error if there is no authenticated user with the request if (!user) { return false; } /** * Queries the Restaurants collection type * using the Document Service API * to retrieve information about the restaurant's owner. */ const filteredRestaurants = await strapi.documents('api::restaurant.restaurant').findMany({ filters: { slug: body.restaurant, }, populate: ['owner'], }); const restaurant = filteredRestaurants[0]; if (!restaurant) { return false; } /** * If the user submitting the request is the restaurant's owner, * we don't allow the review creation. */ if (user.id === restaurant.owner.id) { // highlight-start /** * Throws a custom policy error * instead of just returning false * (which would result into a generic Policy Error). */ throw new PolicyError('The owner of the restaurant cannot submit reviews', { errCode: 'RESTAURANT_OWNER_REVIEW', // can be useful for identifying different errors on the front end }); // highlight-end } return true; }; ```
使用默认策略错误与自定义策略错误发送的响应: 当策略拒绝访问路由并引发默认错误时,尝试通过 REST API 查询内容类型时将发送以下响应: 🌐 When a policy refuses access to a route and a default error is thrown, the following response will be sent when trying to query the content-type through the REST API: ```jsx { "data": null, "error": { "status": 403, "name": "PolicyError", "message": "Policy Failed", "details": {} } } ``` 当策略拒绝访问路由并且自定义策略抛出上面代码示例中定义的自定义错误时,尝试通过 REST API 查询内容类型时将发送以下响应: 🌐 When a policy refuses access to a route and the custom policy throws the custom error defined in the code example above, the following response will be sent when trying to query the content-type through the REST API: ```jsx { "data": null, "error": { "status": 403, "name": "PolicyError", "message": "The owner of the restaurant cannot submit reviews", "details": { "policy": "is-owner-review", "errCode": "RESTAURANT_OWNER_REVIEW" } } } ```

### 在前端使用自定义错误 {#using-custom-errors-on-the-front-end} 🌐 Using custom errors on the front end **💭 上下文:** 开箱即用时,随 [FoodAdvisor](https://github.com/strapi/foodadvisor) 提供的由 Next.js 驱动的前端网站在访问内容时不会在前端网站上显示错误或成功消息。例如,当使用[之前创建的表单](/cms/backend-customization/examples/services-and-controllers#rest-api-queries-from-the-front-end)添加新评论不可行时,网站不会通知用户。 假设我们想要自定义 FoodAdvisor 的前端,以捕获由[先前创建的自定义策略](#creating-a-custom-policy)抛出的自定义错误,并使用 [React Hot Toast notification](https://github.com/timolins/react-hot-toast)将其显示给用户。作为额外功能,当评论成功创建时,还会显示另一个 toast 通知。
Restaurant owner can't submit reviews
当餐厅的老板尝试提交新的评论时,会通过 REST API 返回自定义错误,并且在前端网站上显示一个 toast 通知。
**🎯 目标**: - 在前端网站上捕获错误并将其显示在通知中。 - 如果政策允许创建新评论,请发送另一条通知。 **🧑‍💻 代码示例:** 在 [FoodAdvisor](https://github.com/strapi/foodadvisor) 项目的`/client`文件夹中,你可以按如下方式更新[之前创建的`new-review`组件](/cms/backend-customization/examples/services-and-controllers#rest-api-queries-from-the-front-end)(修改的行已高亮):
用于显示自定义错误或成功创建评论的 toast 通知的示例前端代码: ```jsx title="/client/components/pages/restaurant/RestaurantContent/Reviews/new-review.js" showLineNumbers // highlight-start /** * A notification will be displayed on the front-end using React Hot Toast * (See https://github.com/timolins/react-hot-toast). * React Hot Toast should be added to your project's dependencies; * Use yarn or npm to install it and it will be added to your package.json file. */ class UnauthorizedError extends Error { constructor(message) { super(message); } } // highlight-end const NewReview = () => { const router = useRouter(); const { handleSubmit, handleChange, values } = useFormik({ initialValues: { note: '', content: '', }, onSubmit: async (values) => { // highlight-start /** * The previously added code is wrapped in a try/catch block. */ try { // highlight-end const res = await fetch(getStrapiURL('/reviews'), { method: 'POST', body: JSON.stringify({ restaurant: router.query.slug, ...values, }), headers: { Authorization: `Bearer ${localStorage.getItem('token')}`, 'Content-Type': 'application/json', }, }); // highlight-start const { data, error } = await res.json(); /** * If the Strapi backend server returns an error, * we use the custom error message to throw a custom error. * If the request is a success, we display a success message. * In both cases, a toast notification is displayed on the front-end. */ if (error) { throw new UnauthorizedError(error.message); } toast.success('Review created!'); return data; } catch (err) { toast.error(err.message); console.error(err); } }, // highlight-end }); return (

Write your review

Send
); }; ```

:::strapi What's next? 了解更多关于如何配置 [自定义路由](/cms/backend-customization/examples/routes) 以使用你的自定义策略,以及如何使用这些自定义路由来调整基于 Strapi 的应用的内容。 🌐 Learn more about how to configure [custom routes](/cms/backend-customization/examples/routes) to use your custom policies, and how these custom routes can be used to tweak a Strapi-based application. ::: # 自定义路线 Source: https://strapi.nodejs.cn/cms/backend-customization/examples/routes # 示例手册:自定义路由 {#examples-cookbook-custom-routes} 🌐 Examples cookbook: Custom routes 自定义路由让你可以明确配置内容类型的路由,以控制身份验证并应用策略,例如绕过默认的 Strapi 身份验证或根据自定义条件限制访问。 🌐 Custom routes let you explicitly configure routes for content-types to control authentication and apply policies, such as bypassing default Strapi authentication or restricting access based on custom conditions. :::callout 🏗 About these examples 这些示例是围绕 [FoodAdvisor](https://github.com/strapi/foodadvisor)构建的,而 [FoodAdvisor](https://github.com/strapi/foodadvisor)不再是Strapi的特色演示应用(它已被 [LaunchPad](https://github.com/strapi/LaunchPad)取代)。这里重要的是理解所展示的后端机制,而不是FoodAdvisor本身。这些页面将被重新访问以使用LaunchPad。 ::: :::prerequisites 此页面是后端自定义示例手册的一部分。请确保你已阅读其[介绍](/cms/backend-customization/examples)。 🌐 This page is part of the back end customization examples cookbook. Please ensure you've read its [introduction](/cms/backend-customization/examples). ::: **💭 上下文:** 开箱即用时, [FoodAdvisor](https://github.com/strapi/foodadvisor) 不会控制对其内容类型端点的访问。 假设我们[之前创建了一个策略](/cms/backend-customization/examples/policies)来限制对“评论”内容类型的访问某些条件,例如防止餐厅老板为他们的餐厅创建评论。我们现在必须在用于创建评论的路由上启用该策略。 🌐 Let's say we [previously created a policy](/cms/backend-customization/examples/policies) to restrict access to the "Reviews" content-type to some conditions, for instance to prevent a restaurant's owner to create a review for their restaurants. We must now enable the policy on the route we use to create reviews. **🎯 目标**: - 明确定义“Reviews”内容类型的路由配置。 - 将创建评论时使用的路由配置为: - 绕过默认的 Strapi 身份验证系统 - 并根据[先前定义的自定义策略](/cms/backend-customization/examples/policies)限制访问。 **相关概念** 更多信息可以在[政策](/cms/backend-customization/policies)和[路线](/cms/backend-customization/routes)文档中找到。 🌐 Additional information can be found in the [Policies](/cms/backend-customization/policies) and [Routes](/cms/backend-customization/routes) documentation. **🧑‍💻 代码示例:** 在 [FoodAdvisor](https://github.com/strapi/foodadvisor) 项目的 `/api` 文件夹中,将 `api/src/api/review/routes/review.js` 文件的内容替换为以下代码: ```jsx title="src/api/review/routes/review.js" 'use strict'; const { createCoreRouter } = require('@strapi/strapi').factories; module.exports = createCoreRouter('api::review.review', { config: { create: { auth: false, // set the route to bypass the normal Strapi authentication system policies: ['is-owner-review'], // set the route to use a custom policy middlewares: [], }, }, }); ```
:::strapi What's next? 了解更多关于如何配置[自定义中间件](/cms/backend-customization/examples/middlewares)以执行扩展你的 Strapi 应用的附加操作的信息。 🌐 Learn more about how to configure [custom middlewares](/cms/backend-customization/examples/middlewares) to perform additional actions that extend your Strapi-based application. ::: # 自定义服务和控制器 Source: https://strapi.nodejs.cn/cms/backend-customization/examples/services-and-controllers # 示例手册:自定义服务和控制器 {#examples-cookbook-custom-services-and-controllers} 🌐 Examples cookbook: Custom services and controllers 服务封装了可重用的业务逻辑,控制器调用这些服务来处理评论和电子邮件通知。本指南演示了如何使用文档服务 API 创建自定义服务,以及调用这些服务的自定义控制器。 🌐 Services encapsulate reusable business logic that controllers invoke to handle reviews and email notifications. This guide demonstrates creating custom services using the Document Service API and custom controllers that call them. :::callout 🏗 About these examples 这些示例是围绕 [FoodAdvisor](https://github.com/strapi/foodadvisor)构建的,而 [FoodAdvisor](https://github.com/strapi/foodadvisor)不再是Strapi的特色演示应用(它已被 [LaunchPad](https://github.com/strapi/LaunchPad)取代)。这里重要的是理解所展示的后端机制,而不是FoodAdvisor本身。这些页面将被重新访问以使用LaunchPad。 ::: :::prerequisites 此页面是后端自定义示例手册的一部分。请确保你已阅读其[介绍](/cms/backend-customization/examples)。 🌐 This page is part of the back end customization examples cookbook. Please ensure you've read its [introduction](/cms/backend-customization/examples). ::: 在 [FoodAdvisor](https://github.com/strapi/foodadvisor)的前端网站上,你可以浏览可访问的餐厅列表,链接为[`localhost:3000/restaurants`](http://localhost:3000/restaurants)。点击列表中的任何餐厅,将使用`/client`文件夹中的代码显示有关该餐厅的更多信息。餐厅页面上显示的内容是在Strapi的内容管理器中创建的,并通过查询Strapi的REST API获取,该API使用`/api`文件夹中的代码。 本页将教授以下高级主题: 🌐 This page will teach about the following advanced topics: | 主题 | 部分 | |------|---------| | 创建一个与 Strapi 后端交互的组件 | [前端的 REST API 查询](#rest-api-queries-from-the-front-end) | | 了解服务和控制器如何协同工作 | [控制器 vs. 服务](#controllers-vs-services) | | 创建自定义服务 |
  • 一个仅使用文档服务 API 的 [自定义服务](#custom-service-creating-a-review)
  • 另一个更 [高级的自定义服务](#custom-service-sending-an-email-to-the-restaurant-owner),使用文档服务 API 和一个 Strapi 插件
| | 在控制器中使用服务 | [自定义控制器](#custom-controller) |
### 来自前端的 REST API 查询 {#rest-api-queries-from-the-front-end} 🌐 REST API queries from the front end **💭 上下文:** [FoodAdvisor](https://github.com/strapi/foodadvisor) 前端网站上的餐厅页面包含一个只读的评论部分。添加评论需要登录到 Strapi 的管理面板,并通过 [内容管理器](/cms/features/content-manager) 向“评论”集合类型添加内容。 让我们在餐厅页面上添加一个小型前端组件。这个组件将允许用户直接从前端网站撰写评论。 🌐 Let's add a small front-end component to restaurant pages. This component will allow a user to write a review directly from the front-end website.
Writing a review on the front end
一个可能的示例表单,允许用户在FoodAdvisor前端网站的餐厅页面提交新的评论
**🎯 目标**: * 添加一个表格来撰写评论。 * 在任何餐厅页面上显示该表格。 * 提交表单时,向 Strapi 的 REST API 发送 POST 请求。 * 使用[先前存储的 JWT](/cms/backend-customization/examples/authentication)来验证请求。 **相关概念** 有关内容类型端点的更多信息,请参阅 [REST API](/cms/api/rest#endpoints) 文档。 🌐 Additional information on endpoints for content types can be found in the [REST API](/cms/api/rest#endpoints) documentation. **🧑‍💻 代码示例:** 在 [FoodAdvisor](https://github.com/strapi/foodadvisor) 项目的`/client`文件夹中,你可以使用以下代码示例来: - 创建一个新的 `pages/restaurant/RestaurantContent/Reviews/new-review.js` 文件, - 并更新现有的 `components/pages/restaurant/RestaurantContent/Reviews/reviews.js`。
用于在餐厅页面添加评论组件并显示它的示例前端代码: 1. 在 `/client` 文件夹中创建一个新文件,以添加一个用于撰写评论的新组件,代码如下: ```jsx title='/client/components/pages/restaurant/RestaurantContent/Reviews/new-review.js' import { Button, Input, Textarea } from '@nextui-org/react'; import { useFormik } from 'formik'; import { useRouter } from 'next/router'; import React from 'react'; import { getStrapiURL } from '../../../../../utils'; const NewReview = () => { const router = useRouter(); const { handleSubmit, handleChange, values } = useFormik({ initialValues: { note: '', content: '', }, onSubmit: async (values) => { /** * Queries Strapi REST API to reach the reviews endpoint * using the JWT previously stored in localStorage to authenticate */ const res = await fetch(getStrapiURL('/reviews'), { method: 'POST', body: JSON.stringify({ restaurant: router.query.slug, ...values, }), headers: { Authorization: `Bearer ${localStorage.getItem('token')}`, 'Content-Type': 'application/json', }, }); }, }); /** * Renders the form */ return (

Write your review

Send
); }; export default NewReview; ``` 2. 通过将高亮的行(7、8 和 13)添加到用于渲染餐厅信息的代码中,在任何餐厅页面上显示新的表单组件: ```jsx title='/client/components/pages/restaurant/RestaurantContent/Reviews/reviews.js' showLineNumbers import React from 'react'; import delve from 'dlv'; import { formatDistance } from 'date-fns'; import { getStrapiMedia } from '../../../../../utils'; // highlight-start import { Textarea } from '@nextui-org/react'; import NewReview from './new-review'; // highlight-end const Reviews = ({ reviews }) => { return (
// highlight-next-line {reviews && reviews.map((review, index) => ( // … ```

### 控制器与服务 {#controllers-vs-services} 🌐 Controllers vs. Services 控制器可以包含在客户端请求某个路由时执行的任何业务逻辑。然而,随着你的代码不断增大并变得更加结构化,最佳实践是将逻辑拆分到只做好一件事的特定服务中,然后从控制器中调用这些服务。 🌐 Controllers could contain any business logic to be executed when the client requests a route. However, as your code grows bigger and becomes more structured, it is a best practice to split the logic into specific services that do only one thing well, then call the services from controllers. 为了说明服务的使用,在本文档中,自定义控制器不处理任何职责,并将所有业务逻辑委托给服务。 🌐 To illustrate the use of services, in this documentation the custom controller does not handle any responsibilities and delegates all the business logic to services. 假设我们希望自定义 [FoodAdvisor](https://github.com/strapi/foodadvisor) 的后端以实现以下情景:当在前端网站提交[之前添加的评价表单](#rest-api-queries-from-the-front-end)时,Strapi将在后端创建一个评价,并通过电子邮件通知餐厅老板。将其转化为Strapi后端自定义意味着需要执行三个操作: 1. 创建一个自定义服务来[创建评论](#custom-service-creating-a-review)。 2. 创建一个自定义服务以[发送电子邮件](#custom-service-sending-an-email-to-the-restaurant-owner)。 3. [自定义 Strapi 为 Review 内容类型提供的默认控制器](#custom-controller) 以使用这两个新服务。
### 定制服务:创建评论 {#custom-service-creating-a-review} 🌐 Custom service: Creating a review **💭 上下文:** 默认情况下,Strapi 中的服务文件包含使用 `createCoreService` 工厂函数的基本样板代码。 🌐 By default, service files in Strapi includes basic boilerplate code that use the `createCoreService` factory function. 让我们通过替换其创建评论的代码来更新现有的 `review.js` 服务文件,以用于 [FoodAdvisor](https://github.com/strapi/foodadvisor) 的“Reviews”集合类型。 **🎯 目标**: - 声明一个 `create` 方法。 - 从请求中获取上下文。 - 使用文档服务 API 的 `findMany()` 方法来查找餐厅。 - 使用文档服务 API 中的 `create()` 方法向餐厅追加数据,并填写餐厅所有者信息。 - 返回新的评论数据。 **相关概念** 更多信息可以在[请求上下文](/cms/backend-customization/requests-responses)、[服务](/cms/backend-customization/services)和[文档服务 API](/cms/api/document-service)文档中找到。 🌐 Additional information can be found in the [request context](/cms/backend-customization/requests-responses), [services](/cms/backend-customization/services) and [Document Service API](/cms/api/document-service) documentation. **🧑‍💻 代码示例:** 要创建这样的服务,在 [FoodAdvisor](https://github.com/strapi/foodadvisor) 项目的 `/api` 文件夹中,将 `src/api/review/services/review.js` 文件的内容替换为以下代码: ```jsx title="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; /** * Queries the Restaurants collection type * using the Document Service API * to retrieve information about the restaurant. */ const restaurants = await strapi.documents('api::restaurant.restaurant').findMany({ filters: { slug: body.restaurant, }, }); /** * Creates a new entry for the Reviews collection type * and populates data with information about the restaurant's owner * using the Document Service API. */ const newReview = await strapi.documents('api::review.review').create({ data: { note: body.note, content: body.content, restaurant: restaurants[0].documentId, author: user.id, }, populate: ['restaurant.owner'], }); return newReview; }, })); ``` :::tip Tips - 在控制器的代码中,可以通过 `strapi.service('api::review.review').create(ctx)` 调用此服务的 `create` 方法,其中 `ctx` 是请求的 [context](/cms/backend-customization/requests-responses)。 - 所提供的示例代码未涵盖错误处理。你应该考虑处理错误,例如当餐厅不存在时。更多信息可以查阅[错误处理](/cms/error-handling)文档。 :::
### 客户服务:向餐厅老板发送电子邮件 {#custom-service-sending-an-email-to-the-restaurant-owner} 🌐 Custom Service: Sending an email to the restaurant owner **💭 上下文:** 开箱即用, [FoodAdvisor](https://github.com/strapi/foodadvisor) 不提供任何自动邮件服务功能。 让我们创建一个 `email.js` 服务文件来发送电子邮件。我们可以在 [自定义控制器](#custom-controller) 中使用它,以便在前端网站上创建新评论时通知餐厅老板。 🌐 Let's create an `email.js` service file to send an email. We could use it in a [custom controller](#custom-controller) to notify the restaurant owner whenever a new review is created on the front-end website. :::callout 🤗 Optional service 此服务是一个使用 [Email](/cms/features/email) 插件的高级代码示例,需要了解插件和提供者在 Strapi 中的工作原理。如果你不需要通过电子邮件服务通知餐厅老板,可以跳过此部分,直接查看自定义 [控制器](#custom-controller) 示例。 🌐 This service is an advanced code example using the [Email](/cms/features/email) plugin and requires understanding how plugins and providers work with Strapi. If you don't need an email service to notify the restaurant's owner, you can skip this part and jump next to the custom [controller](#custom-controller) example. ::: :::prerequisites - 你已经为电子邮件插件设置了一个[提供商](/cms/features/email),例如 [Sendmail](https://www.npmjs.com/package/@strapi/provider-email-sendmail) 提供商。 - 在 Strapi 的管理面板中,你已经[创建了一个 `Email` 单类型](/cms/features/content-type-builder#creating-content-types),其中包含一个 `from` 文本字段,用于定义发件人电子邮件地址。 :::
Email Single Type in Admin Panel
在管理面板中已创建一个电子邮件单一类型。它包含一个“发件人”字段,用于定义电子邮件插件的发件人地址。
**🎯 目标**: - 为“Email”单一类型创建一个新的服务文件, - 为此服务声明一个 `send()` 方法, - 使用文档服务 API 获取存储在电子邮件单类型中的发件人地址, - 使用在调用服务的 `send()` 方法时传入的电子邮件详细信息(收件人地址、主题和邮件正文),通过电子邮件插件和之前配置的提供者发送电子邮件。 **相关概念** 更多信息可以在[服务](/cms/backend-customization/services)、[文档服务 API](/cms/api/document-service)和[电子邮件功能](/cms/features/email)文档中找到。 🌐 Additional information can be found in the [Services](/cms/backend-customization/services), [Document Service API](/cms/api/document-service) and [Email feature](/cms/features/email) documentation. **🧑‍💻 代码示例:** 要创建这样的服务,在 [FoodAdvisor](https://github.com/strapi/foodadvisor) 项目的 `/api` 文件夹中,创建一个新的 `src/api/email/services/email.js` 文件,并输入以下代码: ```jsx title="src/api/email/services/email.js" const { createCoreService } = require('@strapi/strapi').factories; module.exports = createCoreService('api::email.email', ({ strapi }) => ({ async send({ to, subject, html }) { /** * Retrieves email configuration data * stored in the Email single type * using the Document Service API. * For a single type, use findFirst() to get its document. */ const emailConfig = await strapi.documents('api::email.email').findFirst(); /** * Sends an email using: * - parameters to pass when invoking the service * - the 'from' address previously retrieved with the email configuration */ await strapi.plugins['email'].services.email.send({ to, subject, html, from: emailConfig.from, }); }, })); ``` :::tip 在控制器的代码中,可以使用 `strapi.service('api::email.email).send(parameters)` 调用此电子邮件服务的 `send` 方法,其中 `parameters` 是包含电子邮件相关信息(收件人地址、主题和邮件正文)的对象。 🌐 In a controller's code, the `send` method from this email service can be called with `strapi.service('api::email.email).send(parameters)` where `parameters` is an object with the email's related information (recipient's address, subject, and email body). :::
### 自定义控制器 {#custom-controller} 🌐 Custom controller **💭 上下文:** 默认情况下,Strapi 中的控制器文件包含使用 `createCoreController` 工厂函数的基本模板代码。这提供了基本的方法来创建、检索、更新和删除内容,当访问请求的端点时。控制器的默认代码可以进行自定义,以执行任何业务逻辑。 🌐 By default, controllers files in Strapi includes basic boilerplate code that use the `createCoreController` factory function. This exposes basic methods to create, retrieve, update, and delete content when reaching the requested endpoint. The default code for the controllers can be customized to perform any business logic. 让我们为 [FoodAdvisor](https://github.com/strapi/foodadvisor) 的“评论”集合类型自定义默认控制器,使用以下情景:在对`/reviews`端点的`POST`请求时,控制器调用之前创建的服务,同时[创建评论](#custom-service-creating-a-review)并[发送电子邮件](#custom-service-sending-an-email-to-the-restaurant-owner)给餐厅的老板。 **🎯 目标**: - 扩展现有的“Reviews”集合类型控制器。 - 声明一个自定义 `create()` 方法。 - 调用之前创建的服务。 - 清理要返回的内容。 **相关概念** 更多信息可以在[控制器](/cms/backend-customization/controllers)文档中找到。 🌐 Additional information can be found in the [controllers](/cms/backend-customization/controllers) documentation. **🧑‍💻 代码示例:** 在 [FoodAdvisor](https://github.com/strapi/foodadvisor) 项目的 `/api` 文件夹中,根据之前是仅创建了[一个自定义服务](#custom-service-creating-a-review)还是同时为评论创建和[邮件通知](#custom-service-sending-an-email-to-the-restaurant-owner)创建了两个自定义服务,将 `src/api/review/controllers/review.js` 文件的内容替换为以下代码示例之一: ```jsx title="src/api/review/controllers/review.js" const { createCoreController } = require('@strapi/strapi').factories; module.exports = createCoreController('api::review.review', ({ strapi }) => ({ /** * As the controller action is named * exactly like the original `create` action provided by the core controller, * it overwrites it. */ 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; }, })); ``` ```jsx title="src/api/review/controllers/review.js" const { createCoreController } = require('@strapi/strapi').factories; module.exports = createCoreController('api::review.review', ({ strapi }) => ({ /** * As the controller action is named * exactly like the original `create` action provided by the core controller, * it overwrites it. */ async create(ctx) { // Creates the new review using a service const newReview = await strapi.service('api::review.review').create(ctx); // Sends an email to the restaurant's owner, using another service 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 What's next? 了解更多关于[自定义策略](/cms/backend-customization/examples/policies)如何帮助你调整基于 Strapi 的应用,并根据特定条件限制对某些资源的访问。 🌐 Learn more about how [custom policies](/cms/backend-customization/examples/policies) can help you tweak a Strapi-based application and restrict access to some resources based on specific conditions. ::: # 自定义用户与权限插件路由 Source: https://strapi.nodejs.cn/cms/backend-customization/guides/customizing-users-permissions-plugin-routes # 自定义用户与权限插件路由 {#customizing-users--permissions-plugin-routes} 🌐 Customizing Users & Permissions plugin routes [用户与权限](/cms/features/users-permissions) 功能公开了 `/users` 和 `/auth` 路由,可以使用插件扩展系统进行扩展或覆盖。本指南展示了如何为用户集合添加自定义策略、覆盖控制器以及添加新路由。 用户与权限功能附带用于身份验证(`/auth`)和用户管理(`/users`)的内置路由。因为这些路由属于插件而不是用户创建的内容类型,所以不能使用 `createCoreRouter` 自定义它们。相反,可以通过在 `/src/extensions/users-permissions/` 文件夹中使用 `strapi-server` 文件,通过 [插件扩展系统](/cms/plugins-development/plugins-extension) 扩展它们。 🌐 The Users & Permissions feature ships with built-in routes for authentication (`/auth`) and user management (`/users`). Because these routes belong to a plugin rather than a user-created content-type, they cannot be customized with `createCoreRouter`. Instead, extend them through the [plugin extension system](/cms/plugins-development/plugins-extension) using a `strapi-server` file in the `/src/extensions/users-permissions/` folder. :::prerequisites - 一个 Strapi 5 项目。 - 熟悉[路线](/cms/backend-customization/routes)和[政策](/cms/backend-customization/policies)。 ::: ## 它是如何运作的 {#how-it-works} 🌐 How it works [用户与权限](/cms/features/users-permissions) 使用的路由数组和控制器对象与标准内容类型不同。在自定义它们之前,理解其结构是至关重要的。 ### 路线结构 {#route-structure} 🌐 Route structure 与你创建的内容类型(例如,`api::restaurant.restaurant`)不同,Users & Permissions 插件在 `plugin.routes['content-api'].routes` 数组中注册其路由。该数组包含所有 `/users`、`/auth`、`/roles` 和 `/permissions` 路由定义。 🌐 Unlike content-types you create (e.g., `api::restaurant.restaurant`), the Users & Permissions plugin registers its routes inside the `plugin.routes['content-api'].routes` array. This array contains all `/users`, `/auth`, `/roles`, and `/permissions` route definitions. 每条路线都是具有以下结构的对象: 🌐 Each route is an object with the following shape: ```js { method: 'GET', // HTTP method path: '/users', // URL path (relative to /api) handler: 'user.find', // controller.action config: { prefix: '', // path prefix (empty means /api) }, } ``` 路由配置还可以包含可选的 `policies` 和 `middlewares` 数组(参见 [添加自定义策略](#add-custom-policy))。 🌐 Route configurations can also include optional `policies` and `middlewares` arrays (see [Add a custom policy](#add-custom-policy)). ### `strapi-server` 扩展文件 {#extend-routes} 🌐 The `strapi-server` extension file 对“用户与权限”插件的所有自定义都放在一个文件中: 🌐 All customizations to the Users & Permissions plugin go in a single file: ```js title="/src/extensions/users-permissions/strapi-server.js" module.exports = (plugin) => { // Your customizations here return plugin; }; ``` ```ts title="/src/extensions/users-permissions/strapi-server.ts" // Your customizations here return plugin; }; ``` 该函数接收完整的插件对象并必须返回该插件。在返回之前,你可以修改 `plugin.routes`、`plugin.controllers`、`plugin.policies` 和 `plugin.services`。 🌐 The function receives the full plugin object and must return the plugin. You can modify `plugin.routes`, `plugin.controllers`, `plugin.policies`, and `plugin.services` before returning. ### 可用操作 {#available-actions} 🌐 Available actions `user` 控制器是一个普通对象,提供以下操作: 🌐 The `user` controller is a plain object that exposes the following actions: | 操作 | 方法 | 路径 | 描述 | | --- | --- | --- | --- | | `user.count` | `GET` | `/users/count` | 统计用户 | | `user.find` | `GET` | `/users` | 查找所有用户 | | `user.me` | `GET` | `/users/me` | 获取认证用户 | | `user.findOne` | `GET` | `/users/:id` | 查找单个用户 | | `user.create` | `POST` | `/users` | 创建用户 | | `user.update` | `PUT` | `/users/:id` | 更新用户 | | `user.destroy` | `DELETE` | `/users/:id` | 删除用户 | `auth` 控制器是一个工厂函数 `({ strapi }) => ({...})`,它暴露了以下操作: 🌐 The `auth` controller is a factory function `({ strapi }) => ({...})` that exposes the following actions: | 操作 | 方法 | 路径 | 限速 | | --- | --- | --- | --- | | `auth.callback` | `POST` | `/auth/local` | 是 | | `auth.callback` | `GET` | `/auth/:provider/callback` | 否 | | `auth.register` | `POST` | `/auth/local/register` | 是 | | `auth.connect` | `GET` | `/connect/(.*)` | 是 | | `auth.forgotPassword` | `POST` | `/auth/forgot-password` | 是 | | `auth.resetPassword` | `POST` | `/auth/reset-password` | 是 | | `auth.changePassword` | `POST` | `/auth/change-password` | 是 | | `auth.emailConfirmation` | `GET` | `/auth/email-confirmation` | 否 | | `auth.sendEmailConfirmation` | `POST` | `/auth/send-email-confirmation` | 否 | | `auth.refresh` | `POST` | `/auth/refresh` | 否 | | `auth.logout` | `POST` | `/auth/logout` | 否 | :::note 因为 `user` 和 `auth` 控制器的类型不同(普通对象 vs. 工厂函数),它们需要不同的重写模式(参见 [重写 `user` 控制器操作](#override-controller) 和 [重写 `auth` 控制器操作](#override-auth-route))。 🌐 Because the `user` and `auth` controllers have different types (plain object vs. factory function), they require different override patterns (see [Override a `user` controller action](#override-controller) and [Override an `auth` controller action](#override-auth-route)). ::: ## 自定义路由 {#customize-routes} 🌐 Customize routes 你可以通过修改扩展文件中的 `plugin.routes['content-api'].routes` 数组来添加策略、注册新端点或移除现有端点。 🌐 You can add policies, register new endpoints, or remove existing ones by modifying the `plugin.routes['content-api'].routes` array in the extension file. ### 添加自定义策略 {#add-custom-policy} 🌐 Add a custom policy 一个常见的要求是限制谁可以更新或删除用户账户:例如,确保用户只能更新自己的资料。 🌐 A common requirement is restricting who can update or delete user accounts: for example, ensuring users can only update their own profile. #### 1. 创建策略文件 {#1-create-the-policy-file} 🌐 1. Create the policy file 创建一个全局策略,用于检查身份验证的用户是否与目标用户匹配。策略函数接收 Koa 上下文(可以访问 `state.user` 和 `params`)、一个可选的配置对象,以及 `{ strapi }`: 🌐 Create a global policy that checks whether the authenticated user matches the target user. The policy function receives the Koa context (with access to `state.user` and `params`), an optional configuration object, and `{ strapi }`: ```js title="/src/policies/is-own-user.js" "use strict"; module.exports = (policyContext, config, { strapi }) => { const currentUser = policyContext.state.user; if (!currentUser) { return false; } const targetUserId = Number(policyContext.params.id); if (currentUser.id !== targetUserId) { return false; } return true; }; ``` ```ts title="/src/policies/is-own-user.ts" const currentUser = policyContext.state.user; if (!currentUser) { return false; } const targetUserId = Number(policyContext.params.id); if (currentUser.id !== targetUserId) { return false; } return true; }; ``` :::tip 上述 `is-own-user` 策略专门适用于 Users & Permissions 插件的路由。对于标准内容类型的类似模式(限制访问条目作者),请参见 [is-owner 中间件示例](/cms/backend-customization/middlewares#restricting-content-access-with-an-is-owner-policy) 和 [is-owner-review 策略示例](/cms/backend-customization/examples/policies#creating-a-custom-policy)。 🌐 The `is-own-user` policy above applies specifically to Users & Permissions plugin routes. For a similar pattern on standard content-types (restricting access to the entry author), see the [is-owner middleware example](/cms/backend-customization/middlewares#restricting-content-access-with-an-is-owner-policy) and the [is-owner-review policy example](/cms/backend-customization/examples/policies#creating-a-custom-policy). ::: #### 2. 将策略附加到用户路由 {#2-attach-the-policy-to-the-user-routes} 🌐 2. Attach the policy to the user routes 在插件扩展文件中,找到 `update` 和 `delete` 路由并添加策略: 🌐 In the plugin extension file, find the `update` and `delete` routes and add the policy: ```js title="/src/extensions/users-permissions/strapi-server.js" module.exports = (plugin) => { // Find the routes that need the policy const routes = plugin.routes['content-api'].routes; // Add the 'is-own-user' policy to the update route const updateRoute = routes.find( (route) => route.handler === 'user.update' ); if (updateRoute) { updateRoute.config = updateRoute.config || {}; updateRoute.config.policies = updateRoute.config.policies || []; updateRoute.config.policies.push('global::is-own-user'); } // Add the same policy to the delete route const deleteRoute = routes.find( (route) => route.handler === 'user.destroy' ); if (deleteRoute) { deleteRoute.config = deleteRoute.config || {}; deleteRoute.config.policies = deleteRoute.config.policies || []; deleteRoute.config.policies.push('global::is-own-user'); } return plugin; }; ``` ```ts title="/src/extensions/users-permissions/strapi-server.ts" // Find the routes that need the policy const routes = plugin.routes['content-api'].routes; // Add the 'is-own-user' policy to the update route const updateRoute = routes.find( (route) => route.handler === 'user.update' ); if (updateRoute) { updateRoute.config = updateRoute.config || {}; updateRoute.config.policies = updateRoute.config.policies || []; updateRoute.config.policies.push('global::is-own-user'); } // Add the same policy to the delete route const deleteRoute = routes.find( (route) => route.handler === 'user.destroy' ); if (deleteRoute) { deleteRoute.config = deleteRoute.config || {}; deleteRoute.config.policies = deleteRoute.config.policies || []; deleteRoute.config.policies.push('global::is-own-user'); } return plugin; }; ``` 在此配置下,如果经过身份验证的用户与 URL 中的 `:id` 不匹配,`PUT /api/users/:id` 和 `DELETE /api/users/:id` 会返回 `403 Forbidden` 错误。 🌐 With this configuration, `PUT /api/users/:id` and `DELETE /api/users/:id` return a `403 Forbidden` error if the authenticated user does not match the `:id` in the URL. :::tip 为了获得更有信息量的错误消息,应抛出 `PolicyError` 而不是返回 `false`: 🌐 For a more informative error message, throw a `PolicyError` instead of returning `false`: ```js const { errors } = require('@strapi/utils'); const { PolicyError } = errors; // Inside the policy: throw new PolicyError('You can only modify your own account'); ```
有关策略模式和错误处理的更多详细信息,请参阅 [策略文档](/cms/backend-customization/policies)。 ::: ### 添加新路线 {#add-new-route} 🌐 Add a new route 你可以向“用户与权限”插件添加自定义路由。例如,可以按如下方式添加一个停用用户账户的端点: 🌐 You can add custom routes to the Users & Permissions plugin. For example, add an endpoint that deactivates a user account as follows: ```js title="/src/extensions/users-permissions/strapi-server.js" module.exports = (plugin) => { // Add a new controller action plugin.controllers.user.deactivate = async (ctx) => { const { id } = ctx.params; const user = await strapi .plugin('users-permissions') .service('user') .edit(id, { blocked: true }); ctx.body = { message: `User ${user.username} has been deactivated` }; }; // Register the route plugin.routes['content-api'].routes.push({ method: 'POST', path: '/users/:id/deactivate', handler: 'user.deactivate', config: { prefix: '', policies: ['global::is-own-user'], }, }); return plugin; }; ``` ```ts title="/src/extensions/users-permissions/strapi-server.ts" // Add a new controller action plugin.controllers.user.deactivate = async (ctx) => { const { id } = ctx.params; const user = await strapi .plugin('users-permissions') .service('user') .edit(id, { blocked: true }); ctx.body = { message: `User ${user.username} has been deactivated` }; }; // Register the route plugin.routes['content-api'].routes.push({ method: 'POST', path: '/users/:id/deactivate', handler: 'user.deactivate', config: { prefix: '', policies: ['global::is-own-user'], }, }); return plugin; }; ``` 重启 Strapi 后,`POST /api/users/:id/deactivate` 将可用。在管理面板的 *用户与权限插件 > 角色* 中,为需要访问此端点的角色授予相应权限。 ### 删除一条路由 {#remove-route} 🌐 Remove a route 你可以通过从路由数组中过滤掉某条路由来禁用它。例如,按如下方式禁用用户计数端点: 🌐 You can disable a route by filtering it out of the routes array. For example, disable the user count endpoint as follows: ```js title="/src/extensions/users-permissions/strapi-server.js" module.exports = (plugin) => { plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter( (route) => route.handler !== 'user.count' ); return plugin; }; ``` ```ts title="/src/extensions/users-permissions/strapi-server.ts" plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter( (route) => route.handler !== 'user.count' ); return plugin; }; ``` ## 覆盖控制器 {#override-controllers} 🌐 Override controllers 除了路由级别的自定义之外,你还可以覆盖控制器的操作本身,以改变插件处理请求的方式。`user` 和 `auth` 控制器使用不同的模式,因此每个都需要特定的方法。 🌐 Beyond route-level customizations, you can override the controller actions themselves to change how the plugin handles requests. The `user` and `auth` controllers use different patterns, so each requires a specific approach. ### 重写 `user` 控制器操作 {#override-controller} 🌐 Override a `user` controller action `user` 控制器是一个普通对象,因此你可以直接在扩展文件中读取和替换其方法。例如,要为 `me` 端点添加自定义逻辑: 🌐 The `user` controller is a plain object, so you can directly read and replace its methods in the extension file. For instance, to add custom logic to the `me` endpoint: ```js title="/src/extensions/users-permissions/strapi-server.js" module.exports = (plugin) => { const originalMe = plugin.controllers.user.me; plugin.controllers.user.me = async (ctx) => { // Call the original controller await originalMe(ctx); // Add extra data to the response if (ctx.body) { ctx.body.timestamp = new Date().toISOString(); } }; return plugin; }; ``` ```ts title="/src/extensions/users-permissions/strapi-server.ts" const originalMe = plugin.controllers.user.me; plugin.controllers.user.me = async (ctx) => { // Call the original controller await originalMe(ctx); // Add extra data to the response if (ctx.body) { ctx.body.timestamp = new Date().toISOString(); } }; return plugin; }; ``` :::caution 在封装控制器时,始终先调用原始函数以保留默认行为。跳过原始函数意味着你将完全接管请求处理,包括数据清理和错误处理。 🌐 When wrapping a controller, always call the original function first to preserve the default behavior. Skipping the original function means you take over the full request handling, including sanitization and error handling. ::: ### 覆盖 `auth` 控制器操作 {#override-auth-route} 🌐 Override an `auth` controller action `auth` 控制器使用工厂模式:它导出一个函数 `({ strapi }) => ({...})` 而不是一个普通对象。当你的扩展代码运行时,Strapi 尚未解析这个工厂。因此,`plugin.controllers.auth` 是一个函数,而不是具有方法的对象。 🌐 The `auth` controller uses a factory pattern: it exports a function `({ strapi }) => ({...})` instead of a plain object. When your extension code runs, Strapi has not yet resolved this factory. As a result, `plugin.controllers.auth` is a function, not an object with methods. 要覆盖授权操作,请将工厂本身封装起来: 🌐 To override an auth action, wrap the factory 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 }); // Override the register method const originalRegister = originalAuth.register; originalAuth.register = async (ctx) => { // Call the original register logic await originalRegister(ctx); // Custom post-registration logic if (ctx.body && ctx.body.user) { strapi.log.info(`New user registered: ${ctx.body.user.email}`); } }; return originalAuth; }; return plugin; }; ``` ```ts title="/src/extensions/users-permissions/strapi-server.ts" const originalAuthFactory = plugin.controllers.auth; plugin.controllers.auth = ({ strapi }) => { // Resolve the original factory to get the controller methods const originalAuth = originalAuthFactory({ strapi }); // Override the register method const originalRegister = originalAuth.register; originalAuth.register = async (ctx) => { // Call the original register logic await originalRegister(ctx); // Custom post-registration logic if (ctx.body && ctx.body.user) { strapi.log.info(`New user registered: ${ctx.body.user.email}`); } }; return originalAuth; }; return plugin; }; ``` :::caution 不要直接访问 `plugin.controllers.auth.register`。因为 `auth` 在扩展时是一个工厂函数,它的方法在 Strapi 调用工厂之前是无法访问的。总是像上面示例那样封装工厂。 🌐 Do not access `plugin.controllers.auth.register` directly. Because `auth` is a factory function at extension time, its methods are not accessible until Strapi calls the factory. Always wrap the factory as shown above. ::: ## 完整示例 {#combine-customizations} 🌐 Full example 以下示例在单个文件中结合了多种自定义:它向 `update` 和 `delete` 添加了策略,封装了 `me` 控制器,并添加了一个新的 `profile` 路由。 🌐 The following example combines several customizations in a single file: it adds a policy to `update` and `delete`, wraps the `me` controller, and adds a new `profile` route. ```js title="/src/extensions/users-permissions/strapi-server.js" module.exports = (plugin) => { const routes = plugin.routes['content-api'].routes; // 1. Add 'is-own-user' policy to update and delete for (const route of routes) { if (route.handler === 'user.update' || route.handler === 'user.destroy') { route.config = route.config || {}; route.config.policies = route.config.policies || []; route.config.policies.push('global::is-own-user'); } } // 2. Wrap the 'me' controller to include the user's role const originalMe = plugin.controllers.user.me; plugin.controllers.user.me = async (ctx) => { await originalMe(ctx); if (ctx.state.user && ctx.body) { const user = await strapi .plugin('users-permissions') .service('user') .fetch(ctx.state.user.id, { populate: ['role'] }); ctx.body.role = user.role; } }; // 3. Add a custom route plugin.controllers.user.profile = async (ctx) => { const user = await strapi .plugin('users-permissions') .service('user') .fetch(ctx.state.user.id, { populate: ['role'] }); ctx.body = { username: user.username, email: user.email, role: user.role?.name, createdAt: user.createdAt, }; }; routes.push({ method: 'GET', path: '/users/profile', handler: 'user.profile', config: { prefix: '' }, }); return plugin; }; ``` ```ts title="/src/extensions/users-permissions/strapi-server.ts" const routes = plugin.routes['content-api'].routes; // 1. Add 'is-own-user' policy to update and delete for (const route of routes) { if (route.handler === 'user.update' || route.handler === 'user.destroy') { route.config = route.config || {}; route.config.policies = route.config.policies || []; route.config.policies.push('global::is-own-user'); } } // 2. Wrap the 'me' controller to include the user's role const originalMe = plugin.controllers.user.me; plugin.controllers.user.me = async (ctx) => { await originalMe(ctx); if (ctx.state.user && ctx.body) { const user = await strapi .plugin('users-permissions') .service('user') .fetch(ctx.state.user.id, { populate: ['role'] }); ctx.body.role = user.role; } }; // 3. Add a custom route plugin.controllers.user.profile = async (ctx) => { const user = await strapi .plugin('users-permissions') .service('user') .fetch(ctx.state.user.id, { populate: ['role'] }); ctx.body = { username: user.username, email: user.email, role: user.role?.name, createdAt: user.createdAt, }; }; routes.push({ method: 'GET', path: '/users/profile', handler: 'user.profile', config: { prefix: '' }, }); return plugin; }; ``` ## 验证 {#validation} 🌐 Validation 在进行更改后,重启 Strapi 并验证你的自定义设置: 🌐 After making changes, restart Strapi and verify your customizations: 1. 运行 `yarn strapi routes:list` 以确认你的新路由或修改后的路由是否出现。 2. 在没有身份验证的情况下测试受保护的路由以验证策略返回 `403 Forbidden`。 3. 使用经过身份验证的用户进行测试以确认预期行为。 4. 检查 Strapi 服务器启动期间的错误日志。 ## 故障排除 {#troubleshooting} 🌐 Troubleshooting | 症状 | 可能原因 | | --- | --- | | 路由未找到 (404) | 新路由未推送到 `plugin.routes['content-api'].routes`,或其 `prefix` 属性缺失。 | | 策略未应用 | 策略名称不正确。全局策略需要 `global::` 前缀(例如,`global::is-own-user`)。 | | 控制器返回 500 | 控制器操作名称与路由定义中的 `handler` 值不匹配。 | | 修改未生效 | 修改扩展文件后未重启 Strapi。扩展在启动时加载。 | | 权限被拒绝 (403) | 新操作未对该角色启用。在 *用户和权限插件 > 角色* 中启用它。 | | 无法读取 `auth` 控制器的属性 | `auth` 控制器是一个工厂函数,而不是普通对象。应封装工厂函数而不是直接访问方法(参见 [覆盖 `auth` 控制器操作](#override-auth-route))。 | # 中间件 Source: https://strapi.nodejs.cn/cms/backend-customization/middlewares # 中间件定制 {#middlewares-customization} 🌐 Middlewares customization 中间件在应用或 API 级别上改变请求或响应的流程。本文档区分了全局中间件与路由中间件,并通过生成模式展示了自定义实现方法。 :::strapi Different types of middlewares 在 Strapi 中,3 个中间件概念共存: 🌐 In Strapi, 3 middleware concepts coexist: - **全局中间件** 是为整个 Strapi 服务器应用[配置和启用](/cms/configurations/middlewares)的。这些中间件可以应用于应用级别或 API 级别。
本指南描述了如何实现它们。
插件也可以添加全局中间件(参见[服务器 API 文档](/cms/plugins-development/server-api))。 - **路由中间件** 的作用范围更有限,并且作为路由级别的中间件进行配置和使用。它们在 [路由文档](/cms/backend-customization/routes#middlewares) 中有描述。 - **文档服务中间件** 适用于文档服务 API,并且有其自己的 [实现](/cms/api/document-service/middlewares) 和相关的 [生命周期钩子](/cms/migration/v4-to-v5/breaking-changes/lifecycle-hooks-document-service#table)。 :::
Simplified Strapi backend diagram with global middlewares highlighted
该图表示请求在 Strapi 后端中传输的简化版本,重点展示了全局中间件。后端自定义介绍页面包含一个完整的、 交互式图表
## 实现 {#implementation} 🌐 Implementation 可以实现新的应用级或 API 级中间件: 🌐 A new application-level or API-level middleware can be implemented: - 使用 [交互式 CLI 命令 `strapi generate`](/cms/cli#strapi-generate) - 或者通过在相应的文件夹中创建一个 JavaScript 文件手动完成(参见 [项目结构](/cms/project-structure)): - `./src/middlewares/` 用于应用级中间件 - `./src/api/[api-name]/middlewares/` 用于 API 级中间件 - `./src/plugins/[plugin-name]/middlewares/` 用于 [插件中间件](/cms/plugins-development/server-policies-middlewares) 使用 REST API 的中间件具有如下功能: 🌐 Middlewares working with the REST API are functions like the following: ```js title="./src/middlewares/my-middleware.js or ./src/api/[api-name]/middlewares/my-middleware.js" module.exports = (config, { strapi })=> { return (context, next) => {}; }; ``` ```js title="./src/middlewares/my-middleware.js or ./src/api/[api-name]/middlewares/my-middleware.ts" return (context, next) => {}; }; ``` 全局作用域的自定义中间件应该添加到 [中间件配置文件](/cms/configurations/middlewares#loading-order),否则 Strapi 将无法加载它们。 🌐 Globally scoped custom middlewares should be added to the [middlewares configuration file](/cms/configurations/middlewares#loading-order) or Strapi won't load them. API 级别和插件中间件可以添加到与其相关的特定路由中,如下所示: 🌐 API level and plugin middlewares can be added into the specific router that they are relevant to like the following: ```js title="./src/api/[api-name]/routes/[collection-name].js or ./src/plugins/[plugin-name]/server/routes/index.js" module.exports = { routes: [ { method: "GET", path: "/[collection-name]", handler: "[controller].find", config: { middlewares: ["[middleware-name]"], // See the usage section below for middleware naming conventions }, }, ], }; ```
自定义计时器中间件示例 ```js title="/config/middlewares.js" module.exports = () => { return async (ctx, next) => { const start = Date.now(); await next(); const delta = Math.ceil(Date.now() - start); ctx.set('X-Response-Time', delta + 'ms'); }; }; ``` ```ts title="/config/middlewares.ts" return async (ctx, next) => { const start = Date.now(); await next(); const delta = Math.ceil(Date.now() - start); ctx.set('X-Response-Time', delta + 'ms'); }; }; ```
GraphQL 插件还允许[实现自定义中间件](/cms/plugins/graphql#middlewares),语法有所不同。 🌐 The GraphQL plugin also allows [implementing custom middlewares](/cms/plugins/graphql#middlewares), with a different syntax. :::tip Discover loaded middlewares 运行 `yarn strapi middlewares:list` 列出所有已注册的中间件,并在将它们连接到路由时仔细检查名称。 🌐 Run `yarn strapi middlewares:list` to list all registered middlewares and double‑check naming when wiring them in routers. ::: ## 使用 {#usage} 🌐 Usage 中间件根据其范围有不同的调用方式: 🌐 Middlewares are called different ways depending on their scope: - 在应用级中间件中使用 `global::middleware-name` - 使用 `api::api-name.middleware-name` 进行 API 级中间件 - 使用 `plugin::plugin-name.middleware-name` 作为插件中间件 :::tip 要列出所有已注册的中间件,运行 `yarn strapi middlewares:list`。 🌐 To list all the registered middlewares, run `yarn strapi middlewares:list`. ::: ### 使用“是所有者策略”限制内容访问 {#restricting-content-access-with-an-is-owner-policy} 🌐 Restricting content access with an "is-owner policy" 通常要求条目的作者是唯一被允许编辑或删除该条目的用户。在 Strapi 的早期版本中,这被称为“is-owner 策略”。在 Strapi v4 中,实现此行为的推荐方式是使用中间件。 🌐 It is often required that the author of an entry is the only user allowed to edit or delete the entry. In previous versions of Strapi, this was known as an "is-owner policy". With Strapi v4, the recommended way to achieve this behavior is to use a middleware. 正确的实现很大程度上取决于你的项目的需求和自定义代码,但最基本的实现可以通过以下过程来实现: 🌐 Proper implementation largely depends on your project's needs and custom code, but the most basic implementation could be achieved with the following procedure: 1. 在你的项目文件夹中,通过在终端运行 `yarn strapi generate`(或 `npm run strapi generate`)命令,使用 Strapi CLI 生成器创建一个中间件。 2. 使用键盘箭头从列表中选择 `middleware`,然后按回车键。 3. 给中间件起一个名字,例如 `isOwner`。 4. 从列表中选择 `Add middleware to an existing API`。 5. 选择你希望中间件应用哪个 API。 6. 将 `/src/api/[your-api-name]/middlewares/isOwner.js` 文件中的代码替换为以下内容,并在第 22 行将 `api::restaurant.restaurant` 替换为你在第 5 步选择的 API 对应的标识符(例如,如果你的 API 名称是 `blog-post`,则替换为 `api::blog-post.blog-post`): ```js showLineNumbers title="src/api/blog-post/middlewares/isOwner.js" "use strict"; /** * `isOwner` middleware */ module.exports = (config, { strapi }) => { // Add your own logic here. return async (ctx, next) => { const user = ctx.state.user; const entryId = ctx.params.id ? ctx.params.id : undefined; let entry = {}; /** * Gets all information about a given entry, * populating every relations to ensure * the response includes author-related information */ if (entryId) { entry = await strapi.documents('api::restaurant.restaurant').findOne( entryId, { populate: "*" } ); } /** * Compares user id and entry author id * to decide whether the request can be fulfilled * by going forward in the Strapi backend server */ if (user.id !== entry.author.id) { return ctx.unauthorized("This action is unauthorized."); } else { return next(); } }; }; ``` 7. 确保中间件配置应用于某些路由。在 `src/api/[your-api–name]/routes/[your-content-type-name].js` 文件中找到的 `config` 对象中,定义你希望中间件应用的动作键(`find`、`findOne`、`create`、`update`、`delete` 等),并为这些路由声明 `isOwner` 中间件。

例如,如果你希望允许 GET 请求(对应 `find` 和 `findOne` 动作)和 POST 请求(即 `create` 动作)对任何用户在 `restaurant` API 中的 `restaurant` 内容类型生效,但希望将 PUT(即 `update` 动作)和 DELETE 请求限制为仅对创建该条目的用户生效,你可以在 `src/api/restaurant/routes/restaurant.js` 文件中使用以下代码: ```js title="src/api/restaurant/routes/restaurant.js" /** * restaurant router */ const { createCoreRouter } = require("@strapi/strapi").factories; module.exports = createCoreRouter("api::restaurant.restaurant", { config: { update: { middlewares: ["api::restaurant.is-owner"], }, delete: { middlewares: ["api::restaurant.is-owner"], }, }, }); ``` :::info 你可以在 [路由文档](/cms/backend-customization/routes) 中找到有关路由中间件的更多信息。 🌐 You can find more information about route middlewares in the [routes documentation](/cms/backend-customization/routes). ::: :::tip Middlewares for performance 路由级中间件是集中处理数据填充逻辑和防止意外过度获取的好地方。请参见 Strapi 博客上的 [Building High-Performance Strapi Applications](https://strapi.io/blog/building-high-performance-strapi-applications-common-pitfalls-and-best-practices) 获取模式和示例。 ::: # 模型 Source: https://strapi.nodejs.cn/cms/backend-customization/models # 模型 {#models} 🌐 Models 模型通过内容类型和可重用组件定义 Strapi 的内容结构。本指南将介绍如何在内容类型构建器或 CLI 中创建这些模型,以及如何使用可选的生命周期钩子管理模式文件。 由于 Strapi 是一个无头内容管理系统(CMS),为内容创建内容结构是使用该软件时最重要的方面之一。模型定义了内容结构的表示。 🌐 As Strapi is a headless Content Management System (CMS), creating a content structure for the content is one of the most important aspects of using the software. Models define a representation of the content structure. Strapi 有 2 种不同类型的模型: 🌐 There are 2 different types of models in Strapi: - 内容类型,可以是集合类型或单一类型,具体取决于它们管理的条目数量, - 以及可在多种内容类型中重复使用的内容结构组件。 如果你刚刚开始,可以直接在管理面板中使用 [Content-type Builder](/cms/features/content-type-builder) 生成一些模型。这种用户界面承担了许多验证任务,并展示了创建内容结构的所有可用选项。然后可以使用此文档在代码层面上查看生成的模型映射。 🌐 If you are just starting out, it is convenient to generate some models with the [Content-type Builder](/cms/features/content-type-builder) directly in the admin panel. The user interface takes over a lot of validation tasks and showcases all the options available to create the content's content structure. The generated model mappings can then be reviewed at the code level using this documentation. ## 模型创建 {#model-creation} 🌐 Model creation 内容类型和组件模型的创建和存储方式不同。 🌐 Content-types and components models are created and stored differently. ### 内容类型 {#content-types} 🌐 Content-types 可以在 Strapi 中创建内容类型: 🌐 Content-types in Strapi can be created: - 在管理面板的[内容类型构建器](/cms/features/content-type-builder)中, - 或者使用 [Strapi 的交互式 CLI `strapi generate`](/cms/cli#strapi-generate) 命令。 内容类型使用以下文件: 🌐 The content-types use the following files: - `schema.json` 用于模型的 [schema](#model-schema) 定义。(自动生成,在使用任一方法创建内容类型时) - `lifecycles.js` 用于 [生命周期钩子](#lifecycle-hooks)。此文件必须手动创建。 这些模型文件存储在 `./src/api/[api-name]/content-types/[content-type-name]/` 中,任何在这些文件夹中找到的 JavaScript 或 JSON 文件都将被加载为内容类型的模型(参见 [项目结构](/cms/project-structure))。 🌐 These models files are stored in `./src/api/[api-name]/content-types/[content-type-name]/`, and any JavaScript or JSON file found in these folders will be loaded as a content-type's model (see [project structure](/cms/project-structure)). :::note 在启用了 [TypeScript](/cms/typescript.md) 的项目中,可以使用 `ts:generate-types` 命令生成模式类型定义。 🌐 In [TypeScript](/cms/typescript.md)-enabled projects, schema typings can be generated using the `ts:generate-types` command. ::: ### 组件 {#components-creation} 🌐 Components 组件模型无法使用 CLI 工具创建。请使用 [内容类型构建器](/cms/features/content-type-builder) 或手动创建它们。 🌐 Component models can't be created with CLI tools. Use the [Content-type Builder](/cms/features/content-type-builder) or create them manually. 组件模型存储在 `./src/components` 文件夹中。每个组件必须位于一个子文件夹内,该子文件夹的名称应与组件所属的类别相同(参见 [项目结构](/cms/project-structure))。 🌐 Components models are stored in the `./src/components` folder. Every component has to be inside a subfolder, named after the category the component belongs to (see [project structure](/cms/project-structure)). ## 模型架构 {#model-schema} 🌐 Model schema 一个模型的 `schema.json` 文件包含: 🌐 The `schema.json` file of a model consists of: - [设置](#model-settings),例如模型表示的内容类型或应存储数据的表名, - [信息](#model-information),主要用于在管理面板中显示模型并通过 REST 和 GraphQL API 访问它, - [属性](#model-attributes),描述模型的内容结构, - 以及 [options](#model-options) 用于定义模型上的特定行为。 ### 模型设置 {#model-settings} 🌐 Model settings 模型的常规设置可以使用以下参数进行配置: 🌐 General settings for the model can be configured with the following parameters: | 参数 | 类型 | 描述 | | --- | --- | --- | | `collectionName` | 字符串 | 数据应存储的数据库表名称 | | `kind`

_可选,
仅用于内容类型_ | 字符串 | 定义内容类型是否为:
  • 集合类型(`collectionType`)
  • 或单一类型(`singleType`)
| ```json // ./src/api/[api-name]/content-types/restaurant/schema.json { "kind": "collectionType", "collectionName": "Restaurants_v1", } ``` ### 模型信息 {#model-information} 🌐 Model information 模型模式中的 `info` 键描述了用于在管理面板中显示模型以及通过内容 API 访问模型的信息。它包括以下参数: 🌐 The `info` key in the model's schema describes information used to display the model in the admin panel and access it through the Content API. It includes the following parameters: | 参数 | 类型 | 描述 | | --- | --- | --- | | `displayName` | 字符串 | 在管理面板中使用的默认名称 | | `singularName` | 字符串 | 内容类型名称的单数形式。
用于生成 API 路由和数据库/表集合。

应使用 kebab-case 格式。 | | `pluralName` | 字符串 | 内容类型名称的复数形式。
用于生成 API 路由和数据库/表集合。

应使用 kebab-case 格式。 | | `description` | 字符串 | 模型的描述 | ```json title="./src/api/[api-name]/content-types/restaurant/schema.json" "info": { "displayName": "Restaurant", "singularName": "restaurant", "pluralName": "restaurants", "description": "" }, ``` ### 模型属性 {#model-attributes} 🌐 Model attributes 模型的内容结构由一系列属性组成。每个属性都有一个 `type` 参数,用于描述其性质,并将该属性定义为简单的数据片段或 Strapi 使用的更复杂结构。 🌐 The content structure of a model consists of a list of attributes. Each attribute has a `type` parameter, which describes its nature and defines the attribute as a simple piece of data or a more complex structure used by Strapi. 有多种类型的属性可用: 🌐 Many types of attributes are available: - 标量类型(例如字符串、日期、数字、布尔值等), - Strapi 特有的类型,例如: - `media` 用于通过 [媒体库](/cms/features/content-type-builder#media) 上传的文件 - `relation` 用于描述内容类型之间的 [关系](#relations) - `customField` 用于描述 [自定义字段](#custom-fields) 及其特定键 - `component` 用于定义一个 [组件](#components-json)(即可在多种内容类型中使用的内容结构) - `dynamiczone` 用于定义一个 [动态区域](#dynamic-zones)(即基于组件列表的灵活空间) - 以及 `locale` 和 `localizations` 类型,仅由 [国际化 (i18n) 插件](/cms/features/internationalization) 使用 属性的 `type` 参数应为以下值之一: 🌐 The `type` parameter of an attribute should be one of the following values: | 类型类别 | 可用类型 | |------|-------| | 字符串类型 |
  • `string`
  • `text`
  • `richtext`
  • `enumeration`
  • `email`
  • `password`
  • [`uid`](#uid-type)
| | 日期类型 |
  • `date`
  • `time`
  • `datetime`
  • `timestamp`
| | 数字类型 |
  • `integer`
  • `biginteger`
  • `float`
  • `decimal`
| | 其他通用类型 |
  • `boolean`
  • `json`
| | Strapi 独有的特殊类型 |
  • `media`
  • [`relation`](#relations)
  • [`customField`](#custom-fields)
  • [`component`](#components-json)
  • [`dynamiczone`](#dynamic-zones)
| | 国际化 (i18n) 相关类型

_仅在内容类型启用了 [i18n](/cms/features/internationalization) 时可用_|
  • `locale`
  • `localizations`
| #### 验证 {#validations} 🌐 Validations 可以使用以下参数将基本验证应用于属性: 🌐 Basic validations can be applied to attributes using the following parameters: | 参数 | 类型 | 描述 | 默认值 | | --- | --- | --- | --- | | `required` | 布尔值 | 如果 `true`,为此属性添加必填验证器 | `false` | | `max` | 整数 | 检查值是否大于或等于给定的最大值 | - | | `min` | 整数 | 检查值是否小于或等于给定的最小值 | - | | `minLength` | 整数 | 字段输入值的最小字符数 | - | | `maxLength` | 整数 | 字段输入值的最大字符数 | - | | `private` | 布尔值 | 如果 `true`,该属性将从服务器响应中移除。

💡 这对于隐藏敏感数据很有用。 | `false` | | `configurable` | 布尔值 | 如果 `false`,该属性无法通过内容类型构建器插件进行配置。 | `true` | ```json title="./src/api/[api-name]/content-types/restaurant/schema.json" { // ... "attributes": { "title": { "type": "string", "minLength": 3, "maxLength": 99, "unique": true }, "description": { "default": "My description", "type": "text", "required": true }, "slug": { "type": "uid", "targetField": "title" } // ... } } ``` #### 数据库验证和设置 {#database-validations-and-settings} 🌐 Database validations and settings :::caution 🚧 This API is considered experimental. 这些设置应该保留给高级使用,因为它们可能会导致某些功能失效。目前没有计划让这些设置稳定。 🌐 These settings should be reserved to an advanced usage, as they might break some features. There are no plans to make these settings stable. ::: 数据库验证和设置是直接传递给 `tableBuilder` Knex.js 函数的自定义选项,用于架构迁移期间。数据库验证允许对设置自定义列设置进行高级控制。以下选项在每个属性的 `column: {}` 对象中设置: 🌐 Database validations and settings are custom options passed directly onto the `tableBuilder` Knex.js function during schema migrations. Database validations allow for an advanced degree of control for setting custom column settings. The following options are set in a `column: {}` object per attribute: | 参数 | 类型 | 描述 | 默认值 | | --- | --- | --- | --- | | `name` | 字符串 | 更改数据库中列的名称 | - | | `defaultTo` | 字符串 | 设置数据库的 `defaultTo`,通常与 `notNullable` 一起使用 | - | | `notNullable` | 布尔值 | 设置数据库的 `notNullable`,确保列不能为空 | `false` | | `unsigned` | 布尔值 | 仅适用于数字列,移除允许负数的功能,但将最大长度加倍 | `false` | | `unique` | 布尔值 | 强制对已发布条目执行数据库级唯一性检查。当启用“草稿与发布”功能时,草稿保存会跳过检查,因此仅在发布时重复才会失败 | `false` | | `type` | 字符串 | 更改数据库类型,如果 `type` 有参数,应在 `args` 中传入 | - | | `args` | 数组 | 传递给 Knex.js 函数的参数,可更改如 `type` 的内容 | `[]` | :::caution Draft & Publish and `unique` 当启用 [Draft & Publish](/cms/features/draft-and-publish) 时,Strapi 会在条目被保存为草稿时故意跳过 `unique` 验证。因此,重复内容在发布之前不会被检测到,此时数据库约束会触发错误,即使 UI 之前对于草稿显示了“已保存文档”。 🌐 When [Draft & Publish](/cms/features/draft-and-publish) is enabled, Strapi intentionally skips `unique` validations while an entry is saved as a draft. Duplicates therefore remain undetected until publication, at which point the database constraint triggers an error even though the UI previously displayed “Saved document” for the drafts. 为避免意外发布失败: 🌐 To avoid unexpected publication failures: - 禁用必须保持全局唯一的内容类型的“草稿和发布”功能, - 或添加自定义验证(例如生命周期钩子或中间件),在保存前检查草稿是否重复, - 或者依赖自动生成的唯一标识符,例如 `uid` 字段和文档编辑规范。 ::: ```json title="./src/api/[api-name]/content-types/restaurant/schema.json" { // ... "attributes": { "title": { "type": "string", "minLength": 3, "maxLength": 99, "unique": true, "column": { "unique": true // enforce database unique also } }, "description": { "default": "My description", "type": "text", "required": true, "column": { "defaultTo": "My description", // set database level default "notNullable": true // enforce required at database level, even for drafts } }, "rating": { "type": "decimal", "default": 0, "column": { "defaultTo": 0, "type": "decimal", // using the native decimal type but allowing for custom precision "args": [ 6,1 // using custom precision and scale ] } } // ... } } ``` #### `uid` 类型 {#uid-type} 🌐 `uid` type `uid` 类型用于在管理面板中自动预填字段值,使用唯一标识符(UID)(例如文章的 slug),基于两个可选参数: 🌐 The `uid` type is used to automatically prefill the field value in the admin panel with a unique identifier (UID) (e.g. slugs for articles) based on 2 optional parameters: - `targetField`(字符串):如果使用,定义为目标的字段的值将用于自动生成 UID。 - `options`(字符串):如果使用,UID 将基于传递给[底层 `uid` 生成器](https://github.com/sindresorhus/slugify)的一组选项生成。生成的 `uid` 必须符合以下正则表达式模式:`/^[A-Za-z0-9-_.~]*$`。 #### 关系 {#relations} 🌐 Relations 关系将内容类型连接在一起。Strapi 支持单条条目关系(单向和一对一)以及多条关系,其中至少一方可以指向多个条目(一对多、多对一、多对多和多向)。多条关系在数据库层中以数组形式保存,并在内容 API 响应中以数组形式返回。 🌐 Relations link content-types together. Strapi supports both single-entry relations (one-way and one-to-one) and multi relations where at least one side can point to several entries (one-to-many, many-to-one, many-to-many, and many-way). Multi relations are persisted as arrays in the database layer and are returned as arrays in the Content API responses. 关系在模型的[属性](#model-attributes)中用`type: 'relation'`明确定义,并接受以下附加参数: 🌐 Relations are explicitly defined in the [attributes](#model-attributes) of a model with `type: 'relation'` and accept the following additional parameters: | 参数 | 描述 | | --- | --- | | `relation` | 这些值之间的关系类型:
  • `oneToOne`
  • `oneToMany`
  • `manyToOne`
  • `manyToMany`
| | `target` | 接受一个字符串值作为目标内容类型的名称 | | `mappedBy` 和 `inversedBy`

_可选_ | 在双向关系中,拥有方声明 `inversedBy` 键,而被引用方声明 `mappedBy` 键 | 当一个条目只能链接到另一个条目时,一对一关系非常有用。 🌐 One-to-One relationships are useful when one entry can be linked to only one other entry. 它们可以是单向或双向的。在单向关系中,只有其中一个模型可以通过其关联项进行查询。 🌐 They can be unidirectional or bidirectional. In unidirectional relationships, only one of the models can be queried with its linked item.
单向使用案例示例: - 博客文章属于一个类别。 - 查询一篇文章可以检索它的类别, - 但查询类别不会检索拥有的文章。 ```json title="./src/api/[api-name]/content-types/article/schema.json" // … attributes: { category: { type: 'relation', relation: 'oneToOne', target: 'category', }, }, // … ```
双向使用案例示例: - 博客文章属于一个类别。 - 查询一篇文章可以检索它的类别, - 查询类别也会检索其拥有的文章。 ```json title="./src/api/[api-name]/content-types/article/schema.json" // … attributes: { category: { type: 'relation', relation: 'oneToOne', target: 'category', inversedBy: 'article', }, }, // … ``` ```json title="./src/api/[api-name]/content-types/category/schema.json" // … attributes: { article: { type: 'relation', relation: 'oneToOne', target: 'article', mappedBy: 'category', }, }, // … ```
一对多关系在以下情况下很有用: 🌐 One-to-Many relationships are useful when: - 内容类型 A 的条目链接到另一个内容类型 B 的许多条目, - 而内容类型 B 的条目仅链接到内容类型 A 的一个条目。 一对多关系始终是双向的,通常用相应的多对一关系定义: 🌐 One-to-many relationships are always bidirectional, and are usually defined with the corresponding Many-to-One relationship:
例子: 一个人可以拥有许多植物,但一株植物只能被一个人拥有。 ```json title="./src/api/[api-name]/content-types/plant/schema.json" // … attributes: { owner: { type: 'relation', relation: 'manyToOne', target: 'api::person.person', inversedBy: 'plants', }, }, // … ``` ```json title="./src/api/person/models/schema.json" // … attributes: { plants: { type: 'relation', relation: 'oneToMany', target: 'api::plant.plant', mappedBy: 'owner', }, }, // … ```
多对一关系对于将多个条目链接到一个条目非常有用。 🌐 Many-to-One relationships are useful to link many entries to one entry. 它们可以是单向或双向的。在单向关系中,只有其中一个模型可以通过其关联项进行查询。 🌐 They can be unidirectional or bidirectional. In unidirectional relationships, only one of the models can be queried with its linked item.
单向使用案例示例: 一本书可以由许多作者撰写。 ```json title="./src/api/[api-name]/content-types/book/schema.json" // … attributes: { author: { type: 'relation', relation: 'manyToOne', target: 'author', }, }, // … ```
双向使用案例示例: 一篇文章只属于一个类别,但一个类别可以包含许多文章。 ```json title="./src/api/[api-name]/content-types/article/schema.json" // … attributes: { author: { type: 'relation', relation: 'manyToOne', target: 'category', inversedBy: 'article', }, }, // … ``` ```json title="./src/api/[api-name]/content-types/category/schema.json" // … attributes: { books: { type: 'relation', relation: 'oneToMany', target: 'article', mappedBy: 'category', }, }, // … ```
多对多关系在以下情况下很有用: 🌐 Many-to-Many relationships are useful when: - 内容类型 A 的条目链接到内容类型 B 的许多条目, - 内容类型 B 中的条目也链接到内容类型 A 中的许多条目。 多对多关系可以是单向的或双向的。在单向关系中,只有其中一个模型可以通过其关联项进行查询。 🌐 Many-to-many relationships can be unidirectional or bidirectional. In unidirectional relationships, only one of the models can be queried with its linked item.
单向使用案例示例: ```json // … attributes: { categories: { type: 'relation', relation: 'manyToMany', target: 'category', }, }, // … ```
双向使用案例示例: 一篇文章可以有多个标签,一个标签可以分配给多篇文章。 🌐 An article can have many tags and a tag can be assigned to many articles. ```json title="/src/api/[api-name]/content-types/article/schema.json" // … attributes: { tags: { type: 'relation', relation: 'manyToMany', target: 'tag', inversedBy: 'articles', }, }, // … ``` ```json title="./src/api/[api-name]/content-types/tag/schema.json" // … attributes: { articles: { type: 'relation', relation: 'manyToMany', target: 'article', mappedBy: 'tag', }, }, // … ```
#### 自定义字段 {#custom-fields} 🌐 Custom fields [自定义字段](/cms/features/custom-fields) 通过向内容类型添加新类型的字段来扩展 Strapi 的功能。自定义字段在模型的 [属性](#model-attributes) 中使用 `type: customField` 明确定义。 自定义字段的属性还显示以下特性: 🌐 Custom fields' attributes also show the following specificities: - 一个 `customField` 属性,其值作为唯一标识符,用于指示应使用哪个已注册的自定义字段。其值如下: - 如果插件创建了自定义字段,则使用 `plugin::plugin-name.field-name` 格式 - 或者用于当前 Strapi 应用特定自定义字段的 `global::field-name` 格式 - 以及额外的参数,这取决于在注册自定义字段时定义的内容(参见[自定义字段文档](/cms/features/custom-fields))。 ```json title="./src/api/[apiName]/[content-type-name]/content-types/schema.json" { // … "attributes": { "attributeName": { // attributeName would be replaced by the actual attribute name "type": "customField", "customField": "plugin::color-picker.color", "options": { "format": "hex" } } } // … } ``` #### 组件 {#components-json} 🌐 Components 组件字段在内容类型和组件结构之间创建关系。组件在模型的 [attributes](#model-attributes) 中通过 `type: 'component'` 明确定义,并接受以下附加参数: 🌐 Component fields create a relation between a content-type and a component structure. Components are explicitly defined in the [attributes](#model-attributes) of a model with `type: 'component'` and accept the following additional parameters: | 参数 | 类型 | 描述 | | --- | --- | --- | | `repeatable` | 布尔值 | 根据组件是否可重复,可能是 `true` 或 `false` | | `component` | 字符串 | 定义相应的组件,遵循此格式:
`.` | ```json title="./src/api/[apiName]/restaurant/content-types/schema.json" { "attributes": { "openinghours": { "type": "component", "repeatable": true, "component": "restaurant.openinghours" } } } ``` #### 动态区域 {#dynamic-zones} 🌐 Dynamic zones 动态区域创建了一个灵活的空间,用于根据混合的[组件](#components-json)列表来编排内容。 🌐 Dynamic zones create a flexible space in which to compose content, based on a mixed list of [components](#components-json). 动态区域在模型的 [attributes](#model-attributes) 中用 `type: 'dynamiczone'` 明确定义。它们还接受一个 `components` 数组,其中每个组件的名称应遵循以下格式:`.`。 🌐 Dynamic zones are explicitly defined in the [attributes](#model-attributes) of a model with `type: 'dynamiczone'`. They also accept a `components` array, where each component should be named following this format: `.`. ```json title="./src/api/[api-name]/content-types/article/schema.json" { "attributes": { "body": { "type": "dynamiczone", "components": ["article.slider", "article.content"] } } } ``` ### 模型选项 {#model-options} 🌐 Model options `options` 键用于定义特定行为,并接受以下参数: 🌐 The `options` key is used to define specific behaviors and accepts the following parameter: | 参数 | 类型 | 描述 | |---------------------|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `privateAttributes` | 字符串数组 | 允许将一组属性视为私有,即使它们实际上并未在模型中定义为属性。它可以用于从 API 响应中移除它们的时间戳。

模型中定义的 `privateAttributes` 会与全局 Strapi 配置中定义的 `privateAttributes` 合并。 | | `draftAndPublish` | 布尔值 | 启用草稿和发布功能。

默认值:`true`(如果内容类型是通过交互式 CLI 创建,则为 `false`)。 | | `populateCreatorFields` | 布尔值 | 在 REST API 返回的响应中填充 `createdBy` 和 `updatedBy` 字段(更多详情参见[指南](/cms/api/rest/guides/populate-creator-fields))。

默认值:`false`。 | ```json title="./src/api/[api-name]/content-types/restaurant/schema.json" { "options": { "privateAttributes": ["id", "createdAt"], "draftAndPublish": true } } ``` ### 插件选项 {#plugin-options} 🌐 Plugin options `pluginOptions` 是一个可选对象,允许插件为模型或特定属性存储配置。 | 键 | 值 | 描述 | |---------------------------|-------------------------------|--------------------------------------------------------| | `i18n` | `localized: true` | 启用本地化。 | | `content-manager` | `visible: false` | 在管理员面板中从内容管理器隐藏。 | | `content-type-builder` | `visible: false` | 在管理员面板中从内容类型构建器隐藏。 | ```json title="./src/api/[api-name]/content-types/[content-type-name]/schema.json" { "attributes": { "name": { "pluginOptions": { "i18n": { "localized": true } }, "type": "string", "required": true }, "slug": { "pluginOptions": { "i18n": { "localized": true } }, "type": "uid", "targetField": "name", "required": true } // …additional attributes } } ``` ## 生命周期钩子 {#lifecycle-hooks} 🌐 Lifecycle hooks 生命周期钩子是当 Strapi 查询被调用时触发的函数。当通过管理面板管理内容或使用 `queries` 开发自定义代码时,它们会自动触发。 🌐 Lifecycle hooks are functions that get triggered when Strapi queries are called. They are triggered automatically when managing content through the administration panel or when developing custom code using `queries`· 生命周期钩子可以通过声明或编程方式自定义。 🌐 Lifecycle hooks can be customized declaratively or programmatically. :::caution 当直接使用 [knex](https://knex.nodejs.cn/) 库而不是Strapi函数时,生命周期钩子不会被触发。 ::: :::strapi Document Service API: lifecycles and middlewares 文档服务 API 会根据调用的方法触发各种数据库生命周期钩子。完整参考请参见 [文档服务 API:生命周期钩子](/cms/migration/v4-to-v5/breaking-changes/lifecycle-hooks-document-service#table)。批量操作生命周期(`createMany`、`updateMany`、`deleteMany`)绝不会被文档服务 API 方法触发。也可以实现 [文档服务中间件](/cms/api/document-service/middlewares)。 🌐 The Document Service API triggers various database lifecycle hooks based on which method is called. For a complete reference, see [Document Service API: Lifecycle hooks](/cms/migration/v4-to-v5/breaking-changes/lifecycle-hooks-document-service#table). Bulk actions lifecycles (`createMany`, `updateMany`, `deleteMany`) will never be triggered by a Document Service API method. [Document Service middlewares](/cms/api/document-service/middlewares) can be implemented too. ::: ### 可用的生命周期事件 {#available-lifecycle-events} 🌐 Available lifecycle events 以下生命周期事件可用: 🌐 The following lifecycle events are available: - `beforeCreate` - `beforeCreateMany` - `afterCreate` - `afterCreateMany` - `beforeUpdate` - `beforeUpdateMany` - `afterUpdate` - `afterUpdateMany` - `beforeDelete` - `beforeDeleteMany` - `afterDelete` - `afterDeleteMany` - `beforeCount` - `afterCount` - `beforeFindOne` - `afterFindOne` - `beforeFindMany` - `afterFindMany` ### 钩子 `event` 对象 {#hook-event-object} 🌐 Hook `event` object 生命周期钩子是接收一个 `event` 参数的函数,该参数是一个包含以下键的对象: 🌐 Lifecycle hooks are functions that take an `event` parameter, an object with the following keys: | 键 | 类型 | 描述 | | --- | --- | --- | | `action` | 字符串 | 已触发的生命周期事件(参见 [列表](#available-lifecycle-events)) | | `model` | 字符串数组(uid) | 一个包含要监听事件的内容类型 uid 的数组。
如果未提供此参数,则会监听所有内容类型的事件。 | | `params` | 对象 | 接受以下参数:
  • `data`
  • `select`
  • `where`
  • `orderBy`
  • `limit`
  • `offset`
  • `populate`
| | `result` | 对象 | _可选,仅在 `afterXXX` 事件中可用_

包含操作的结果。 | | `state` | 对象 | 查询状态,可用于在查询的 `beforeXXX` 和 `afterXXX` 事件之间共享状态。 | ### 声明式和程序化使用 {#declarative-and-programmatic-usage} 🌐 Declarative and programmatic usage 要配置内容类型生命周期钩子,请在 `./src/api/[api-name]/content-types/[content-type-name]/` 文件夹中创建一个 `lifecycles.js` 文件。 🌐 To configure a content-type lifecycle hook, create a `lifecycles.js` file in the `./src/api/[api-name]/content-types/[content-type-name]/` folder. 每个事件监听器按顺序调用。它们可以是同步的也可以是异步的。 🌐 Each event listener is called sequentially. They can be synchronous or asynchronous. ```js title="./src/api/[api-name]/content-types/[content-type-name]/lifecycles.js" module.exports = { beforeCreate(event) { const { data, where, select, populate } = event.params; // let's do a 20% discount everytime event.params.data.price = event.params.data.price * 0.8; }, afterCreate(event) { const { result, params } = event; // do something to the result; }, }; ``` ```js title="./src/api/[api-name]/content-types/[content-type-name]/lifecycles.ts" beforeCreate(event) { const { data, where, select, populate } = event.params; // let's do a 20% discount everytime event.params.data.price = event.params.data.price * 0.8; }, afterCreate(event) { const { result, params } = event; // do something to the result; }, }; ``` 使用数据库层 API,还可以注册订阅者并以编程方式监听事件: 🌐 Using the database layer API, it's also possible to register a subscriber and listen to events programmatically: ```js title="./src/index.js" module.exports = { async bootstrap({ strapi }) { // registering a subscriber strapi.db.lifecycles.subscribe({ models: [], // optional; beforeCreate(event) { const { data, where, select, populate } = event.params; event.state = 'doStuffAfterWards'; }, afterCreate(event) { if (event.state === 'doStuffAfterWards') { } const { result, params } = event; // do something to the result }, }); // generic subscribe for generic handling strapi.db.lifecycles.subscribe((event) => { if (event.action === 'beforeCreate') { // do something } }); } } ``` # 政策 Source: https://strapi.nodejs.cn/cms/backend-customization/policies # 政策 {#policies} 🌐 Policies 策略在控制器之前执行,以对路由执行授权或其他检查。本文档中的说明涵盖了生成全局或特定范围的策略以及将它们连接到路由配置中。 策略是在每个请求到达[控制器](/cms/backend-customization/controllers)之前执行特定逻辑的函数。它们主要用于保护业务逻辑。 🌐 Policies are functions that execute specific logic on each request before it reaches the [controller](/cms/backend-customization/controllers). They are mostly used for securing business logic. Strapi 项目的每个[路由](/cms/backend-customization/routes)都可以关联到一组策略。例如,一个名为 `is-admin` 的策略可以检查请求是否由管理员用户发送,并限制对关键路由的访问。 🌐 Each [route](/cms/backend-customization/routes) of a Strapi project can be associated to an array of policies. For example, a policy named `is-admin` could check that the request is sent by an admin user, and restrict access to critical routes. 策略可以是全局的或有范围的。[全局策略](#global-policies)可以关联到项目中的任何路由。有范围的策略只适用于特定的[API](#api-policies)或[插件](#plugin-policies),并应存放在相应的`./src/api//policies/`或`./src/plugins//policies/`文件夹下。
Simplified Strapi backend diagram with routes and policies highlighted
该图表示请求在 Strapi 后端传输的简化版本,并突出了策略和路由。后端自定义介绍页面包括一个完整的, 交互式图表
## 实现 {#implementation} 🌐 Implementation 可以实现一项新政策: 🌐 A new policy can be implemented: - 使用 [交互式 CLI 命令 `strapi generate`](/cms/cli#strapi-generate) - 或者通过在相应的文件夹中创建一个 JavaScript 文件手动完成(参见 [项目结构](/cms/project-structure)): - `./src/policies/` 用于全球政策 - `./src/api/[api-name]/policies/` 用于 API 策略 - `./src/plugins/[plugin-name]/policies/` 用于插件策略
全球政策实现示例: 🌐 Global policy implementation example: ```js title="./src/policies/is-authenticated.js" module.exports = (policyContext, config, { strapi }) => { if (policyContext.state.user) { // if a session is open // go to next policy or reach the controller's action return true; } return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass }; ``` ```ts title="./src/policies/is-authenticated.ts" if (policyContext.state.user) { // if a session is open // go to next policy or reach the controller's action return true; } return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass }; ``` `policyContext` 是 [controller](/cms/backend-customization/controllers) 上下文的一个封装。它增加了一些逻辑,这些逻辑对于在 REST 和 GraphQL 中实现策略可能很有用。
可以使用 `config` 对象配置策略: 🌐 Policies can be configured using a `config` object: ```js title="./src/api/[api-name]/policies/my-policy.js" module.exports = (policyContext, config, { strapi }) => { if (policyContext.state.user.role.code === config.role) { // if user's role is the same as the one described in configuration return true; } return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass }; ``` ```ts title="./src/api/[api-name]/policies/my-policy.ts" if (policyContext.state.user.role.code === config.role) { // if user's role is the same as the one described in configuration return true; } return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass }; ``` ## 使用 {#usage} 🌐 Usage 要将策略应用到路由,请将它们添加到路由的配置对象中(参见 [路由文档](/cms/backend-customization/routes#policies))。 🌐 To apply policies to a route, add them to its configuration object (see [routes documentation](/cms/backend-customization/routes#policies)). 策略根据其范围有不同的调用方式: 🌐 Policies are called different ways depending on their scope: - 使用 `global::policy-name` 用于 [全局策略](#global-policies) - 使用 `api::api-name.policy-name` 用于 [API 政策](#api-policies) - 使用 `plugin::plugin-name.policy-name` 用于 [插件政策](#plugin-policies) :::tip 要列出所有可用的策略,请运行 `yarn strapi policies:list`。 🌐 To list all the available policies, run `yarn strapi policies:list`. ::: ### 全球政策 {#global-policies} 🌐 Global policies 全局策略可以与项目中的任何路由关联。 🌐 Global policies can be associated to any route in a project. ```js title="./src/api/restaurant/routes/custom-restaurant.js" module.exports = { routes: [ { method: 'GET', path: '/restaurants', handler: 'Restaurant.find', config: { /** Before executing the find action in the Restaurant.js controller, we call the global 'is-authenticated' policy, found at ./src/policies/is-authenticated.js. */ policies: ['global::is-authenticated'] } } ] } ``` ```ts title="./src/api/restaurant/routes/custom-restaurant.ts" routes: [ { method: 'GET', path: '/restaurants', handler: 'Restaurant.find', config: { /** Before executing the find action in the Restaurant.js controller, we call the global 'is-authenticated' policy, found at ./src/policies/is-authenticated.js. */ policies: ['global::is-authenticated'] } } ] } ``` ### 插件政策 {#plugin-policies} 🌐 Plugin policies 插件可以向应用添加并公开策略。例如,[用户与权限功能](/cms/features/users-permissions) 配备了策略,以确保用户已通过身份验证或拥有执行某个操作的权限: 🌐 Plugins can add and expose policies to an application. For example, the [Users & Permissions feature](/cms/features/users-permissions) comes with policies to ensure that the user is authenticated or has the rights to perform an action: ```js title="./src/api/restaurant/routes/custom-restaurant.js" module.exports = { routes: [ { method: 'GET', path: '/restaurants', handler: 'Restaurant.find', config: { /** The `isAuthenticated` policy prodived with the `users-permissions` plugin is executed before the `find` action in the `Restaurant.js` controller. */ policies: ['plugin::users-permissions.isAuthenticated'] } } ] } ``` ```ts title="./src/api/restaurant/routes/custom-restaurant.ts" routes: [ { method: 'GET', path: '/restaurants', handler: 'Restaurant.find', config: { /** The `isAuthenticated` policy prodived with the `users-permissions` plugin is executed before the `find` action in the `Restaurant.js` controller. */ policies: ['plugin::users-permissions.isAuthenticated'] } } ] } ``` ### API 政策 {#api-policies} 🌐 API policies API 策略与声明它们的 API 中定义的路由相关联。 🌐 API policies are associated to the routes defined in the API where they have been declared. ```js title="./src/api/restaurant/policies/is-admin.js." module.exports = async (policyContext, config, { strapi }) => { if (policyContext.state.user.role.name === 'Administrator') { // Go to next policy or will reach the controller's action. return true; } return false; }; ``` ```js title="./src/api/restaurant/routes/custom-restaurant.js" module.exports = { routes: [ { method: 'GET', path: '/restaurants', handler: 'Restaurant.find', config: { /** The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.js` is executed before the `find` action in the `Restaurant.js` controller. */ policies: ['is-admin'] } } ] } ``` ```ts title="./src/api/restaurant/policies/is-admin.ts" if (policyContext.state.user.role.name === 'Administrator') { // Go to next policy or will reach the controller's action. return true; } return false; }; ``` ```ts title="./src/api/restaurant/routes/custom-restaurant.ts" routes: [ { method: 'GET', path: '/restaurants', handler: 'Restaurant.find', config: { /** The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.js` is executed before the `find` action in the `Restaurant.ts` controller. */ policies: ['is-admin'] } } ] } ``` 要在另一个 API 中使用策略,请使用以下语法引用它:`api::[apiName].[policyName]`: 🌐 To use a policy in another API, reference it with the following syntax: `api::[apiName].[policyName]`: ```js title="./src/api/category/routes/custom-category.js" module.exports = { routes: [ { method: 'GET', path: '/categories', handler: 'Category.find', config: { /** The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.js` is executed before the `find` action in the `Restaurant.js` controller. */ policies: ['api::restaurant.is-admin'] } } ] } ``` ```ts title="./src/api/category/routes/custom-category.ts" routes: [ { method: 'GET', path: '/categories', handler: 'Category.find', config: { /** The `is-admin` policy found at `./src/api/restaurant/policies/is-admin.ts` is executed before the `find` action in the `Restaurant.js` controller. */ policies: ['api::restaurant.is-admin'] } } ] } ``` # 请求与响应 Source: https://strapi.nodejs.cn/cms/backend-customization/requests-responses # 请求与响应 {#requests-and-responses} 🌐 Requests and Responses Koa 的上下文(`ctx`)在每个 Strapi 端点中携带请求信息、状态和响应数据。本文档详细说明了 `ctx.request`、`ctx.state` 和 `ctx.response`,以及用于在任何地方访问上下文的辅助工具。 Strapi 后端服务器基于 [Koa](https://koa.nodejs.cn/)。当你通过 [REST API](/cms/api/rest) 发送请求时,一个上下文对象(`ctx`)会传递给 Strapi 后端的每个元素(例如,[policies](/cms/backend-customization/policies)、[controllers](/cms/backend-customization/controllers)、[services](/cms/backend-customization/services))。 `ctx` 包含 3 个主要对象: - [`ctx.request`](#ctxrequest) 了解关于发起 API 请求的客户端发送的请求的信息, - [`ctx.state`](#ctxstate) 了解有关请求在 Strapi 后端状态的信息, - 以及 [`ctx.response`](#ctxresponse) 获取有关服务器将返回的响应的信息。 :::tip 请求的上下文也可以通过代码中的任何位置使用 [`strapi.requestContext` 函数](#accessing-the-request-context-anywhere) 访问。 🌐 The request's context can also be accessed from anywhere in the code with the [`strapi.requestContext` function](#accessing-the-request-context-anywhere). ::: :::info 除了以下文档中描述的概念和参数外,你可能还会在 [Koa request documentation](http://koa.nodejs.cn/#request)、 [Koa Router documentation](https://github.com/koajs/router/blob/master/API.md) 和 [Koa response documentation](http://koa.nodejs.cn/#response)中找到额外的信息。 :::
Simplified Strapi backend diagram with requests and responses highlighted
该图表示请求在 Strapi 后端传输的简化版本,并突出了请求和响应。后端自定义介绍页面包括一个完整的、 交互式图表
## `ctx.request` `ctx.request` 对象包含以下参数: 🌐 The `ctx.request` object contains the following parameters: | 参数 | 描述 | 类型 | | --- | --- | --- | | `ctx.request.body` | 正文的解析版本。 | `Object` | | `ctx.request.files` | 随请求发送的文件。 | `Array` | | `ctx.request.headers` | 随请求发送的头信息。 | `Object` | | `ctx.request.host` | URL 的主机部分,包括端口。 | `String` | | `ctx.request.hostname`| URL 的主机部分,不包括端口。 | `String` | | `ctx.request.href` | 请求资源的完整 URL,包括协议、域名、端口(如果指定)、路径和查询参数。 | `String` | | `ctx.request.ip` | 发送请求的人的IP。| `String` | | `ctx.request.ips` | 当 `X-Forwarded-For` 存在并且 `app.proxy` 启用时,将返回一个 IP 数组,按从上游到下游的顺序排列。

例如,如果值为 "client, proxy1, proxy2",你将收到 `["client", "proxy1", "proxy2"]` 数组。 | `Array` | | `ctx.request.method` | 请求方法(例如,`GET`、`POST`)。 | `String` | | `ctx.request.origin` | 第一个 `/` 之前的 URL 部分。 | `String` | | `ctx.request.params` | 在URL中发送的参数。

例如,如果内部URL是 `/restaurants/:id`,在实际请求中替换 `:id` 的任何内容都可以通过 `ctx.request.params.id` 访问。 | `Object` | | `ctx.request.path` | 请求资源的路径,不包括查询参数。 | `String` | | `ctx.request.protocol`| 使用的协议(例如,`https` 或 `http`)。 | `String` | | `ctx.request.query` | Strapi特定的[查询参数](#ctxrequestquery)。 | `Object` | | `ctx.request.subdomains`| 包含在URL中的子域名。

例如,如果域名是 `tobi.ferrets.example.com`,则值为以下数组:`["ferrets", "tobi"]`。 | `Array` | | `ctx.request.url` | 请求资源的路径和查询参数,不包括协议、域名和端口。 | `String` |
协议、来源、URL、href、路径、主机和主机名之间的区别: 对于发送到 `https://example.com:1337/api/restaurants?id=123` URL 的 API 请求,`ctx.request` 对象的不同参数返回如下内容: 🌐 Given an API request sent to the `https://example.com:1337/api/restaurants?id=123` URL, here is what different parameters of the `ctx.request` object return: | 参数 | 返回值 | | --- | --- | | `ctx.request.href` | `https://example.com:1337/api/restaurants?id=123` | | `ctx.request.protocol` | `https` | | `ctx.request.host` | `localhost:1337` | | `ctx.request.hostname` | `localhost` | | `ctx.request.origin` | `https://example.com:1337` | | `ctx.request.url` | `/api/restaurants?id=123` | | `ctx.request.path` | `/api/restaurants` |
### `ctx.request.query` `ctx.request` 提供了一个 `query` 对象,用于访问 Strapi 查询参数。下表列出了可用参数、简短描述以及相关 REST API 文档部分的链接(更多信息请参见 [REST API 参数](/cms/api/rest/parameters)): | 参数 | 描述 | 类型 | | ---| --- | --- | | `ctx.request.query`
`ctx.query` | 整个查询对象。 | `Object` | | `ctx.request.query.sort` | 用于 [排序响应](/cms/api/rest/sort-pagination.md#sorting) 的参数 | `String` 或 `Array` | | `ctx.request.query.filters` | 用于[筛选响应](/cms/api/rest/filters)的参数 | `Object` | | `ctx.request.query.populate` | 用于[填充关联、组件或动态区域](/cms/api/rest/populate-select#population)的参数 | `String` 或 `Object` | | `ctx.request.query.fields` | [仅选择要随响应返回的特定字段](/cms/api/rest/populate-select#field-selection) 的参数 | `Array` | | `ctx.request.query.pagination` | 用于[翻页浏览条目](/cms/api/rest/sort-pagination.md#pagination)的参数 | `Object` | | `ctx.request.query.publicationState` | 参数用于[选择草稿和发布状态](/cms/api/rest/status) | `String` | | `ctx.request.query.locale` | 参数,用于[选择一个或多个语言环境](/cms/api/rest/locale) | `String` 或 `Array` | ## `ctx.state` `ctx.state` 对象提供对 Strapi 后端请求状态的访问,包括关于 [用户](#ctxstateuser)、[认证](#ctxstateauth)、[路由](#ctxstateroute) 的具体值: 🌐 The `ctx.state` object gives access to the state of the request within the Strapi back end, including specific values about the [user](#ctxstateuser), [authentication](#ctxstateauth), [route](#ctxstateroute): | 参数 | 描述 | 类型 | | ---|---------------------------------------------------------------------------- | --- | | `ctx.state.isAuthenticated`| 返回当前用户是否以任何方式经过身份验证。 | `Boolean` | ### `ctx.state.user` `ctx.state.user` 对象提供对执行请求的用户信息的访问,并包括以下参数: 🌐 The `ctx.state.user` object gives access to information about the user performing the request and includes the following parameters: | 参数 | 描述 | 类型 | | --- | --- | --- | | `ctx.state.user` | 用户的信息。只填写一个关系。 | `Object` | | `ctx.state.user.role` | 用户的角色 | `Object` | ### `ctx.state.auth` `ctx.state.auth` 对象提供与身份验证相关的信息访问,包括以下参数: 🌐 The `ctx.state.auth` object gives access to information related to the authentication and includes the following parameters: | 参数 | 描述 | 类型 | | ---| --- | --- | | `ctx.state.auth.strategy` | 当前使用的认证策略的信息([用户与权限插件](/cms/features/users-permissions) 或 [API 令牌](/cms/features/api-tokens)) | `Object` | | `ctx.state.auth.strategy.name`| 当前使用的策略名称 | `String` | | `ctx.state.auth.credentials` | 用户的凭证 | `String` | ### `ctx.state.route` `ctx.state.route` 对象提供对与当前路由相关的信息的访问,包括以下参数: 🌐 The `ctx.state.route` object gives access to information related to the current route and includes the following parameters: | 参数 | 描述 | 类型 | | ---| --- | --- | | `ctx.state.route.method`| 用于访问当前路由的方法。 | `String` | | `ctx.state.route.path`| 当前路由的路径。 | `String` | | `ctx.state.route.config`| 关于当前路由的配置信息。 | `Object` | | `ctx.state.route.handler`| 当前路由的处理程序(控制器)。 | `Object` | | `ctx.state.route.info`| 关于当前路由的附加信息,例如 apiName 和 API 请求类型。 | `Object` | | `ctx.state.route.info.apiName`| 所使用 API 的名称。 | `String` | | `ctx.state.route.info.type`| 所使用 API 的类型。 | `String` | ## `ctx.response` `ctx.response` 对象提供对服务器将返回的响应相关信息的访问,并包括以下参数: 🌐 The `ctx.response` object gives access to information related to the response that the server will return and includes the following parameters: | 参数 | 描述 | 类型 | | ---| --- | --- | | `ctx.response.body`| 响应的内容。 | `Any` | | `ctx.response.status` | 响应的状态码。 | `Integer` | | `ctx.response.message`| 响应的状态消息。

默认情况下,`response.message` 与 `response.status` 相关联。 | `String` | | `ctx.response.header`
`ctx.response.headers`| 随响应发送的头信息。 | `Object` | | `ctx.response.length`| 当存在时,将 `[`Content-Length`](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Content-Length)` 头的值作为数字,或在可能时从 `ctx.body` 推断;否则,返回 `undefined`。 | `Integer` | | `ctx.response.redirect`
`ctx.response.redirect(url, [alt])` | 执行对 URL 的 `302` 重定向。字符串 “back” 被特殊处理以提供 Referrer 支持;当 Referrer 不存在时,使用 alt 或 “/”。

示例: `ctx.response.redirect('back', '/index.html');` | `Function` | | `ctx.response.attachment`

`ctx.response.attachment([filename], [options])` | 将 [`Content-Disposition`](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Content-Disposition) 头设置为 "attachment",以提示客户端进行下载。可选地指定下载文件名和一些 [options](https://github.com/jshttp/content-disposition#options)。 | `Function` | | `ctx.response.type`| [`Content-Type`](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Content-Type) 头,不包含诸如 "charset" 之类的参数。 | `String` | | `ctx.response.lastModified`| 将 `[`Last-Modified`](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Last-Modified)` 头作为日期,如果它存在的话。 | `DateTime` | | `ctx.response.etag`| 设置响应的 [`ETag`](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/ETag),包括封装的 s.
没有对应的 `response.etag` 获取器。 | `String` | ## 在任何地方访问请求上下文 {#accessing-the-request-context-anywhere} 🌐 Accessing the request context anywhere Strapi 公开了一种从代码中的任何位置访问当前请求上下文的方法(例如生命周期函数)。 🌐 Strapi exposes a way to access the current request context from anywhere in the code (e.g. lifecycle functions). 你可以按如下方式访问该请求: 🌐 You can access the request as follows: ```js const ctx = strapi.requestContext.get(); ``` 你应该只在 HTTP 请求上下文中调用的函数内部使用它。 🌐 You should only use this inside of functions that will be called in the context of an HTTP request. ```js // correct const service = { myFunction() { const ctx = strapi.requestContext.get(); console.log(ctx.state.user); }, }; // incorrect const ctx = strapi.requestContext.get(); const service = { myFunction() { console.log(ctx.state.user); }, }; ``` **示例:** ```js title="./api/test/content-types/article/lifecycles.js" module.exports = { beforeUpdate() { const ctx = strapi.requestContext.get(); console.log('User info in service: ', ctx.state.user); }, }; ``` :::note Strapi 使用 Node.js 的一个名为 [AsyncLocalStorage](https://nodejs.cn/docs/latest-v16.x/api/async_context.html#class-asynclocalstorage) 的功能来使上下文在任何地方都可用。 ::: :::tip 有关在插件中从管理面板发起经过身份验证的 HTTP 请求,请参见 [Admin Panel API: 获取客户端](/cms/plugins-development/admin-fetch-client)。 🌐 For making authenticated HTTP requests from the admin panel in plugins, see [Admin Panel API: Fetch client](/cms/plugins-development/admin-fetch-client). ::: # 路线 Source: https://strapi.nodejs.cn/cms/backend-customization/routes # 路线 {#routes} 🌐 Routes 路由将传入的 URL 映射到控制器,并为每种内容类型预生成页面。本文档展示了如何添加或自定义核心和自定义路由,并附加策略或中间件以实现额外控制。 发送到 Strapi 上任何 URL 的请求都由路由处理。默认情况下,Strapi 会为所有内容类型生成路由(请参阅 [REST API 文档](/cms/api/rest))。路由可以被 [添加](#implementation) 和配置: 🌐 Requests sent to Strapi on any URL are handled by routes. By default, Strapi generates routes for all the content-types (see [REST API documentation](/cms/api/rest)). Routes can be [added](#implementation) and configured: - 使用 [策略](#policies),这是一种阻止访问某个路线的方法, - 以及使用 [中间件](#middlewares),中间件是一种控制和改变请求流程及请求本身的方式。 一旦路由存在,访问它会执行由控制器处理的一些代码(参见 [控制器文档](/cms/backend-customization/controllers))。要查看所有现有路由及其层级顺序,你可以运行 `yarn strapi routes:list`(参见 [CLI 参考](/cms/cli))。 🌐 Once a route exists, reaching it executes some code handled by a controller (see [controllers documentation](/cms/backend-customization/controllers)). To view all existing routes and their hierarchal order, you can run `yarn strapi routes:list` (see [CLI reference](/cms/cli)). :::tip 如果你只是自定义 Strapi 为某个内容类型生成的默认控制器操作(`find`、`findOne`、`create`、`update` 或 `delete`),你可以保持路由不变。这些核心路由已经指向相同的处理程序名称,并会运行你新的控制器逻辑。只有在你需要一个全新的 HTTP 路径/方法或想要暴露自定义控制器操作时,才需要添加或编辑路由。 🌐 If you only customize the default controller actions (`find`, `findOne`, `create`, `update`, or `delete`) that Strapi generates for a content-type, you can leave the router as-is. Those core routes already target the same handler names and will run your new controller logic. Add or edit a route only when you need a brand-new HTTP path/method or want to expose a custom controller action. :::
Simplified Strapi backend diagram with routes highlighted
该图表示请求在 Strapi 后端传输的简化版本,并标出了路由。后端自定义介绍页面包括完整的、 交互式图表
## 实现 {#implementation} 🌐 Implementation 实现一个新路由包括在 `./src/api/[apiName]/routes` 文件夹中的路由文件中定义它(参见 [项目结构](/cms/project-structure))。 🌐 Implementing a new route consists in defining it in a router file within the `./src/api/[apiName]/routes` folder (see [project structure](/cms/project-structure)). 根据用例,有 2 种不同的路由文件结构: 🌐 There are 2 different router file structures, depending on the use case: - 配置[核心路由](#configuring-core-routers) - 或创建[自定义路由](#creating-custom-routers)。 ### 配置核心路由 {#configuring-core-routers} 🌐 Configuring core routers 核心路由(即 `find`、`findOne`、`create`、`update` 和 `delete`)对应于当创建新的 [内容类型](/cms/backend-customization/models#model-creation) 时 Strapi 自动创建的 [默认路由](/cms/api/rest#endpoints)。 🌐 Core routers (i.e. `find`, `findOne`, `create`, `update`, and `delete`) correspond to [default routes](/cms/api/rest#endpoints) automatically created by Strapi when a new [content-type](/cms/backend-customization/models#model-creation) is created. Strapi 提供了一个 `createCoreRouter` 工厂函数,它会自动生成核心路由并允许: 🌐 Strapi provides a `createCoreRouter` factory function that automatically generates the core routers and allows: - 将配置选项传递给每个路由 - 并禁用一些核心路由以[创建自定义路由](#creating-custom-routers)。 核心路由文件是一个 JavaScript 文件,导出对 `createCoreRouter` 调用的结果,该调用具有以下参数: 🌐 A core router file is a JavaScript file exporting the result of a call to `createCoreRouter` with the following parameters: | 参数 | 描述 | 类型 | | --- | --- | --- | | `prefix` | 允许传入自定义前缀,添加到该模型的所有路由(例如 `/test`) | `String` | | `only` | 仅会加载的核心路由

数组中未包含的内容将被忽略。 | `Array` | --> | `except` | 不应加载的核心路由

在功能上与 `only` 参数相反。 | `Array` | | `config` | 配置路由的 [策略](#policies)、[中间件](#middlewares) 和 [公共可用性](#public-routes) | `Object` |
```js title="./src/api/[apiName]/routes/[routerName].js (e.g './src/api/restaurant/routes/restaurant.js')" const { createCoreRouter } = require('@strapi/strapi').factories; module.exports = createCoreRouter('api::restaurant.restaurant', { prefix: '', only: ['find', 'findOne'], except: [], config: { find: { auth: false, policies: [], middlewares: [], }, findOne: {}, create: {}, update: {}, delete: {}, }, }); ``` ```ts title="./src/api/[apiName]/routes/[routerName].ts (e.g './src/api/restaurant/routes/restaurant.ts')" prefix: '', only: ['find', 'findOne'], except: [], config: { find: { auth: false, policies: [], middlewares: [], }, findOne: {}, create: {}, update: {}, delete: {}, }, }); ```
通用实现示例: 🌐 Generic implementation example: ```js title="./src/api/restaurant/routes/restaurant.js" const { createCoreRouter } = require('@strapi/strapi').factories; module.exports = createCoreRouter('api::restaurant.restaurant', { only: ['find'], config: { find: { auth: false, policies: [], middlewares: [], } } }); ``` ```ts title="./src/api/restaurant/routes/restaurant.ts" only: ['find'], config: { find: { auth: false, policies: [], middlewares: [], } } }); ``` 这只允许在核心 `find` [控制器](/cms/backend-customization/controllers) 上对 `/restaurants` 路径发起 `GET` 请求,无需身份验证。当你在自定义路由中引用自定义控制器操作时,建议使用完整限定的 `api::..` 形式以提高清晰度(例如,`api::restaurant.restaurant.review`)。 ### 创建自定义路由 {#creating-custom-routers} 🌐 Creating custom routers 创建自定义路由包括创建一个导出对象数组的文件,每个对象都是具有以下参数的路由: 🌐 Creating custom routers consists in creating a file that exports an array of objects, each object being a route with the following parameters: | 参数 | 描述 | 类型 | | --- | --- | --- | | `method` | 路由关联的方法(例如 `GET`、`POST`、`PUT`、`DELETE` 或 `PATCH`) | `String` | | `path` | 要访问的路径,以斜杠开头(例如 `/articles`) | `String` | | `handler` | 当路由被访问时执行的函数。
使用完整限定语法 `api::api-name.controllerName.actionName`(或 `plugin::plugin-name.controllerName.actionName`)。遗留项目中也可使用简短形式 `.`。 | `String` | | `config`

_可选_ | 用于处理路由的[策略](#policies)、[中间件](#middlewares)和[公开访问](#public-routes)的配置

| `Object` |
可以使用参数和正则表达式创建动态路由。这些参数将会在 `ctx.params` 对象中暴露。更多详情,请参阅 [PathToRegex](https://github.com/pillarjs/path-to-regexp) 文档。 :::caution 路由文件按字母顺序加载。要在核心路由之前加载自定义路由,请确保适当地命名自定义路由(例如 `01-custom-routes.js` 和 `02-core-routes.js`)。 🌐 Routes files are loaded in alphabetical order. To load custom routes before core routes, make sure to name custom routes appropriately (e.g. `01-custom-routes.js` and `02-core-routes.js`). ::: :::info Controller handler naming reference `handler` 字符串充当指向应为该路由运行的控制器操作的指针。Strapi 支持以下格式: 🌐 The `handler` string acts as a pointer to the controller action that should run for the route. Strapi supports the following formats: - API 控制器:`api::..`(例如 `api::restaurant.restaurant.exampleAction`)。`` 来自 `./src/api//controllers/` 内的控制器文件名。 - 插件控制器:当控制器位于插件中时为 `plugin::..`。 为了向后兼容,Strapi 也接受 API 控制器的简短 `.` 字符串,但使用完全限定的形式可以使路由更明确,并避免不同 API 和插件之间的命名冲突。 🌐 For backwards compatibility, Strapi also accepts a short `.` string for API controllers, but using the fully-qualified form makes the route more explicit and avoids naming collisions across APIs and plugins. :::
使用 URL 参数和正则表达式进行路由的自定义路由示例 在以下示例中,自定义路由文件名以 `01-` 为前缀,以确保在核心路由之前访问该路由。 🌐 In the following example, the custom routes file name is prefixed with `01-` to make sure the route is reached before the core routes. ```js title="./src/api/restaurant/routes/01-custom-restaurant.js" /** @type {import('@strapi/strapi').Core.RouterConfig} */ const config = { type: 'content-api', routes: [ { // Path defined with an URL parameter method: 'POST', path: '/restaurants/:id/review', handler: 'api::restaurant.restaurant.review', }, { // Path defined with a regular expression method: 'GET', path: '/restaurants/:category([a-z]+)', // Only match when the URL parameter is composed of lowercase letters handler: 'api::restaurant.restaurant.findByCategory', } ] } module.exports = config ``` ```js title="./src/api/restaurant/routes/01-custom-restaurant.ts" const config: Core.RouterConfig = { type: 'content-api', routes: [ { // Path defined with a URL parameter method: 'GET', path: '/restaurants/:category/:id', handler: 'api::restaurant.restaurant.findOneByCategory', }, { // Path defined with a regular expression method: 'GET', path: '/restaurants/:region(\\d{2}|\\d{3})/:id', // Only match when the first parameter contains 2 or 3 digits. handler: 'api::restaurant.restaurant.findOneByRegion', } ] } ```
## 配置 {#configuration} 🌐 Configuration 核心路由和自定义路由都有相同的配置选项。路由配置在一个 `config` 对象中定义,该对象可以用来处理策略和中间件,或将路由设为公开。 🌐 Both [core routers](#configuring-core-routers) and [custom routers](#creating-custom-routers) have the same configuration options. The routes configuration is defined in a `config` object that can be used to handle [policies](#policies) and [middlewares](#middlewares) or to [make the route public](#public-routes). ### 政策 {#policies} 🌐 Policies 可以将[策略](/cms/backend-customization/policies)添加到路由配置中: - 通过指向在 `./src/policies` 中注册的策略,无论是否传递自定义配置 - 或者通过直接声明策略实现,将其作为一个函数,该函数以 `policyContext` 扩展 [Koa](https://koa.nodejs.cn/#context) (`ctx`) 和 `strapi` 实例作为参数(参见 [策略文档](/cms/backend-customization/routes)) ```js title="./src/api/restaurant/routes/restaurant.js" const { createCoreRouter } = require('@strapi/strapi').factories; module.exports = createCoreRouter('api::restaurant.restaurant', { config: { find: { policies: [ // point to a registered policy 'policy-name', // point to a registered policy with some custom configuration { name: 'policy-name', config: {} }, // pass a policy implementation directly (policyContext, config, { strapi }) => { return true; }, ] } } }); ``` ```js title="./src/api/restaurant/routes/restaurant.ts" config: { find: { policies: [ // point to a registered policy 'policy-name', // point to a registered policy with some custom configuration { name: 'policy-name', config: {} }, // pass a policy implementation directly (policyContext, config, { strapi }) => { return true; }, ] } } }); ``` ```js title="./src/api/restaurant/routes/custom-restaurant.js" module.exports = { routes: [ { method: 'GET', path: '/articles/customRoute', handler: 'api::api-name.controllerName.functionName', // or 'plugin::plugin-name.controllerName.functionName' for a plugin-specific controller config: { policies: [ // point to a registered policy 'policy-name', // point to a registered policy with some custom configuration { name: 'policy-name', config: {} }, // pass a policy implementation directly (policyContext, config, { strapi }) => { return true; }, ] }, }, ], }; ``` ```js title="./src/api/restaurant/routes/custom-restaurant.ts" routes: [ { method: 'GET', path: '/articles/customRoute', handler: 'api::api-name.controllerName.functionName', // or 'plugin::plugin-name.controllerName.functionName' for a plugin-specific controller config: { policies: [ // point to a registered policy 'policy-name', // point to a registered policy with some custom configuration { name: 'policy-name', config: {} }, // pass a policy implementation directly (policyContext, config, { strapi }) => { return true; }, ] }, }, ], }; ``` ### 中间件 {#middlewares} 🌐 Middlewares 可以将[中间件](/cms/backend-customization/middlewares)添加到路由配置中: - 通过指向在 `./src/middlewares` 中注册的中间件,无论是否传递自定义配置 - 或者直接通过声明中间件实现,作为一个函数,该函数以 [Koa](https://koa.nodejs.cn/#context) (`ctx`) 和 `strapi` 实例作为参数: ```js title="./src/api/restaurant/routes/restaurant.js" const { createCoreRouter } = require('@strapi/strapi').factories; module.exports = createCoreRouter('api::restaurant.restaurant', { config: { find: { middlewares: [ // point to a registered middleware 'middleware-name', // point to a registered middleware with some custom configuration { name: 'middleware-name', config: {} }, // pass a middleware implementation directly (ctx, next) => { return next(); }, ] } } }); ``` ```js title="./src/api/restaurant/routes/restaurant.ts" config: { find: { middlewares: [ // point to a registered middleware 'middleware-name', // point to a registered middleware with some custom configuration { name: 'middleware-name', config: {} }, // pass a middleware implementation directly (ctx, next) => { return next(); }, ] } } }); ``` ```js title="./src/api/restaurant/routes/custom-restaurant.js" module.exports = { routes: [ { method: 'GET', path: '/articles/customRoute', handler: 'api::api-name.controllerName.functionName', // or 'plugin::plugin-name.controllerName.functionName' for a plugin-specific controller config: { middlewares: [ // point to a registered middleware 'middleware-name', // point to a registered middleware with some custom configuration { name: 'middleware-name', config: {} }, // pass a middleware implementation directly (ctx, next) => { return next(); }, ], }, }, ], }; ``` ```js title="./src/api/restaurant/routes/custom-restaurant.ts" routes: [ { method: 'GET', path: '/articles/customRoute', handler: 'api::api-name.controllerName.functionName', // or 'plugin::plugin-name.controllerName.functionName' for a plugin-specific controller config: { middlewares: [ // point to a registered middleware 'middleware-name', // point to a registered middleware with some custom configuration { name: 'middleware-name', config: {} }, // pass a middleware implementation directly (ctx, next) => { return next(); }, ], }, }, ], }; ```
将中间件应用于内容类型的所有核心路由 `createCoreRouter` 每个操作附加 `middlewares`。要在每个默认内容 API 操作(`find`、`findOne`、`create`、`update`、`delete`)上运行相同的中间件,可以在 `config` 的每个键下列出它,或者只构建一次该对象: ```js title="./src/api/restaurant/routes/restaurant.js" const { createCoreRouter } = require('@strapi/strapi').factories; const audit = ['global::audit-log']; const actions = ['find', 'findOne', 'create', 'update', 'delete']; module.exports = createCoreRouter('api::restaurant.restaurant', { config: Object.fromEntries(actions.map((action) => [action, { middlewares: audit }])), }); ``` ```ts title="./src/api/restaurant/routes/restaurant.ts" const audit = ['global::audit-log'] as const; const actions = ['find', 'findOne', 'create', 'update', 'delete'] as const; config: Object.fromEntries(actions.map((action) => [action, { middlewares: [...audit] }])), }); ``` 如果你使用 `only` 或 `except`,请构建 `actions` 数组以匹配你实际注册的路由,这样你就不会将中间件附加到已禁用的处理程序上。 🌐 If you use `only` or `except`, build the `actions` array to match the routes you actually register so you do not attach middlewares to disabled handlers.
:::tip Centralizing population logic 路由级中间件是推荐用于在整个 API 中执行一致的填充规则的地方。这可以防止意外的过度获取,并确保响应大小可预测。请参阅 Strapi 博客上的 [Building High-Performance Strapi Applications](https://strapi.io/blog/building-high-performance-strapi-applications-common-pitfalls-and-best-practices) 。 ::: ### 公共路线 {#public-routes} 🌐 Public routes 默认情况下,路由受 Strapi 的身份验证系统保护,该系统基于[API 令牌](/cms/features/api-tokens)或使用[用户与权限插件](/cms/features/users-permissions)。 🌐 By default, routes are protected by Strapi's authentication system, which is based on [API tokens](/cms/features/api-tokens) or on the use of the [Users & Permissions plugin](/cms/features/users-permissions). 在某些情况下,将某条路由公开并在正常的 Strapi 身份验证系统之外控制访问可能是有用的。这可以通过将路由的 `auth` 配置参数设置为 `false` 来实现: 🌐 In some scenarios, it can be useful to have a route publicly available and control the access outside of the normal Strapi authentication system. This can be achieved by setting the `auth` configuration parameter of a route to `false`: ```js title="./src/api/restaurant/routes/restaurant.js" const { createCoreRouter } = require('@strapi/strapi').factories; module.exports = createCoreRouter('api::restaurant.restaurant', { config: { find: { auth: false } } }); ``` ```js title="./src/api/restaurant/routes/restaurant.ts" config: { find: { auth: false } } }); ``` ```js title="./src/api/restaurant/routes/custom-restaurant.js" module.exports = { routes: [ { method: 'GET', path: '/articles/customRoute', handler: 'api::api-name.controllerName.functionName', // or 'plugin::plugin-name.controllerName.functionName' for a plugin-specific controller config: { auth: false, }, }, ], }; ``` ```js title="./src/api/restaurant/routes/custom-restaurant.ts" routes: [ { method: 'GET', path: '/articles/customRoute', handler: 'api::api-name.controllerName.functionName', // or 'plugin::plugin-name.controllerName.functionName' for a plugin-specific controller config: { auth: false, }, }, ], }; ``` ## 自定义内容 API 参数 {#custom-content-api-parameters} 🌐 Custom Content API parameters 你可以通过在 [register](/cms/configurations/functions#register) 生命周期中注册,将允许在内容 API 路由上的 `query` 和 body 参数扩展。注册的参数随后会像核心参数一样被验证和清理。客户端可以发送额外的查询键(例如 `?search=...`)或根级 body 键(例如 `clientMutationId`),而无需自定义路由或控制器。 🌐 You can extend the `query` and body parameters allowed on Content API routes by registering them in the [register](/cms/configurations/functions#register) lifecycle. Registered parameters are then validated and sanitized like core parameters. Clients can send extra query keys (e.g. `?search=...`) or root-level body keys (e.g. `clientMutationId`) without requiring custom routes or controllers. | 什么 | 哪里 | |------|--------| | 启用严格参数(拒绝未知的查询/主体键) | [API 配置](/cms/configurations/api):在 `./config/api.js`(或 `./config/api.ts`)中设置 `rest.strictParams: true`。 | | 添加允许的参数(应用) | 在 `./src/index.js` 或 `./src/index.ts` 的 [注册](/cms/configurations/functions#register) 中调用 `addQueryParams` / `addInputParams`。 | | 添加允许的参数(插件) | 在插件的 [注册](/cms/plugins-development/server-lifecycle#register) 生命周期中调用 `addQueryParams` / `addInputParams`。 | 当启用 `rest.strictParams` 时,仅接受核心参数和每个路由请求模式上的参数;你注册的参数会合并到该模式中。模式请使用来自 `@strapi/utils`(或 `zod/v4`)的 `z` 实例。 🌐 When `rest.strictParams` is enabled, only core parameters and parameters on each route's request schema are accepted; the parameters you register are merged into that schema. Use the `z` instance from `@strapi/utils` (or `zod/v4`) for schemas. ### `addQueryParams` `strapi.contentAPI.addQueryParams(options)` 注册额外的 `query` 参数。模式必须是标量或标量数组(字符串、数字、布尔值、枚举)。对于嵌套结构,请改用 `addInputParams`。每个条目可以有一个可选的 `matchRoute: (route) => boolean` 回调,仅将参数添加到回调返回 true 的路由。你不能将核心查询参数名称(例如 `filters`、`sort`、`fields`)注册为额外参数;它们是保留的。 ### `addInputParams` `strapi.contentAPI.addInputParams(options)` 注册额外的输入参数:请求体中的根级键(例如与 `data` 并列),可以使用任何 Zod 类型。可选的 `matchRoute` 回调的作用与 `addQueryParams` 相同。你不能将保留名称如 `id` 或 `documentId` 注册为输入参数。 ### `matchRoute` `matchRoute` 回调接收一个具有以下属性的 `route` 对象: 🌐 The `matchRoute` callback receives a `route` object with the following properties: - `route.method`:HTTP 方法(`'GET'`、`'POST'` 等) - `route.path`:路线路径 - `route.handler`:控制器操作字符串 - `route.info`:关于路线的元数据 例如,要仅针对 GET 路由,请使用 `matchRoute: (route) => route.method === 'GET'`。要仅针对路径包含 `articles` 的路由,请使用 `matchRoute: (route) => route.path.includes('articles')`。 🌐 For example, to target only GET routes, use `matchRoute: (route) => route.method === 'GET'`. To target only routes whose path includes `articles`, use `matchRoute: (route) => route.path.includes('articles')`. ```js title="./src/index.js" module.exports = { register({ strapi }) { strapi.contentAPI.addQueryParams({ search: { schema: (z) => z.string().max(200).optional(), matchRoute: (route) => route.path.includes('articles'), }, }); strapi.contentAPI.addInputParams({ clientMutationId: { schema: (z) => z.string().max(100).optional(), }, }); }, }; ``` ```ts title="./src/index.ts" register({ strapi }) { strapi.contentAPI.addQueryParams({ search: { schema: (z) => z.string().max(200).optional(), matchRoute: (route) => route.path.includes('articles'), }, }); strapi.contentAPI.addInputParams({ clientMutationId: { schema: (z) => z.string().max(100).optional(), }, }); }, }; ``` # 服务 Source: https://strapi.nodejs.cn/cms/backend-customization/services # 服务 {#services} 🌐 Services 服务存储可重用的函数,以保持控制器简洁并遵循 DRY 原则。本文件说明了如何使用 `createCoreService` 生成或扩展服务,以及如何为 API 或插件组织它们。 服务是一组可重用的功能。它们对于遵循“不要重复自己”(DRY)编程理念以及简化[控制器](/cms/backend-customization/controllers.md)的逻辑特别有用。 🌐 Services are a set of reusable functions. They are particularly useful to respect the "don’t repeat yourself" (DRY) programming concept and to simplify [controllers](/cms/backend-customization/controllers.md) logic.
Simplified Strapi backend diagram with services highlighted
该图表示请求在 Strapi 后端传递的简化版本,并高亮了服务。后端自定义介绍页面包括完整的、 交互式图表
## 实现 {#implementation} 🌐 Implementation 服务可以[手动生成或添加](#adding-a-new-service)。Strapi 提供了一个 `createCoreService` 工厂函数,能够自动生成核心服务,并允许构建自定义服务或[扩展或替换生成的服务](#extending-core-services)。 🌐 Services can be [generated or added manually](#adding-a-new-service). Strapi provides a `createCoreService` factory function that automatically generates core services and allows building custom ones or [extend or replace the generated services](#extending-core-services). ### 添加新服务 {#adding-a-new-service} 🌐 Adding a new service 可以实现一个新的服务: 🌐 A new service can be implemented: - 使用 [交互式 CLI 命令 `strapi generate`](/cms/cli#strapi-generate) - 或者通过在相应的文件夹中创建一个 JavaScript 文件手动完成(参见 [项目结构](/cms/project-structure.md)): - `./src/api/[api-name]/services/` 用于 API 服务 - 或用于[插件服务](/cms/plugins-development/server-controllers-services)的 `./src/plugins/[plugin-name]/services/`。 要手动创建一个服务,请导出一个工厂函数,该函数返回服务实现(即包含方法的对象)。该工厂函数接收 `strapi` 实例: 🌐 To manually create a service, export a factory function that returns the service implementation (i.e. an object with methods). This factory function receives the `strapi` instance: ```js title="./src/api/restaurant/services/restaurant.js" const { createCoreService } = require('@strapi/strapi').factories; module.exports = createCoreService('api::restaurant.restaurant', ({ strapi }) => ({ // Method 1: Creating an entirely new custom service async exampleService(...args) { let response = { okay: true } if (response.okay === false) { return { response, error: true } } return response }, // Method 2: Wrapping a core service (leaves core logic in place) async find(...args) { // Calling the default core controller const { results, pagination } = await super.find(...args); // some custom logic results.forEach(result => { result.counter = 1; }); return { results, pagination }; }, // Method 3: Replacing a core service async findOne(documentId, params = {}) { return strapi.documents('api::restaurant.restaurant').findOne({ documentId, // Use super to keep core fetch parameter formatting ...super.getFetchParams(params), }); } })); ``` ```ts title="./src/api/restaurant/services/restaurant.ts" // Method 1: Creating an entirely custom service async exampleService(...args) { let response = { okay: true } if (response.okay === false) { return { response, error: true } } return response }, // Method 2: Wrapping a core service (leaves core logic in place) async find(...args) { // Calling the default core controller const { results, pagination } = await super.find(...args); // some custom logic results.forEach(result => { result.counter = 1; }); return { results, pagination }; }, // Method 3: Replacing a core service async findOne(documentId, params = {}) { return strapi.documents('api::restaurant.restaurant').findOne({ documentId, // Use super to keep core fetch parameter formatting ...super.getFetchParams(params) }) as any; } })); ``` :::strapi Document Service API 要开始创建你自己的服务,请参阅 Strapi 的内置函数,详见 [文档服务 API](/cms/api/document-service) 文档。 🌐 To get started creating your own services, see Strapi's built-in functions in the [Document Service API](/cms/api/document-service) documentation. :::
自定义电子邮件服务示例(使用 Nodemailer) 服务的目标是存储可重复使用的函数。一个 `sendNewsletter` 服务可能在我们的代码库中从不同函数发送电子邮件时非常有用,这些函数有特定的用途: 🌐 The goal of a service is to store reusable functions. A `sendNewsletter` service could be useful to send emails from different functions in our codebase that have a specific purpose: ```js title="./src/api/restaurant/services/restaurant.js" const { createCoreService } = require('@strapi/strapi').factories; const nodemailer = require('nodemailer'); // Requires nodemailer to be installed (npm install nodemailer) // Create reusable transporter object using SMTP transport. const transporter = nodemailer.createTransport({ service: 'Gmail', auth: { user: 'user@gmail.com', pass: 'password', }, }); module.exports = createCoreService('api::restaurant.restaurant', ({ strapi }) => ({ sendNewsletter(from, to, subject, text) { // Setup e-mail data. const options = { from, to, subject, text, }; // Return a promise of the function that sends the email. return transporter.sendMail(options); }, })); ``` ```ts title="./src/api/restaurant/services/restaurant.ts" const nodemailer = require('nodemailer'); // Requires nodemailer to be installed (npm install nodemailer) // Create reusable transporter object using SMTP transport. const transporter = nodemailer.createTransport({ service: 'Gmail', auth: { user: 'user@gmail.com', pass: 'password', }, }); sendNewsletter(from, to, subject, text) { // Setup e-mail data. const options = { from, to, subject, text, }; // Return a promise of the function that sends the email. return transporter.sendMail(options); }, })); ``` 该服务现在可以通过 `strapi.service('api::restaurant.restaurant').sendNewsletter(...args)` 全局变量使用。它可以在代码库的其他部分使用,例如在以下控制器中: 🌐 The service is now available through the `strapi.service('api::restaurant.restaurant').sendNewsletter(...args)` global variable. It can be used in another part of the codebase, like in the following controller: ```js title="./src/api/restaurant/controllers/restaurant.js" module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) => ({ // GET /hello async signup(ctx) { const { userData } = ctx.body; // Store the new user in database. const user = await strapi.service('plugin::users-permissions.user').add(userData); // Send an email to validate his subscriptions. strapi.service('api::restaurant.restaurant').sendNewsletter('welcome@mysite.com', user.email, 'Welcome', '...'); // Send response to the server. ctx.send({ ok: true, }); }, })); ``` ```js title="./src/api/restaurant/controllers/restaurant.ts" // GET /hello async signup(ctx) { const { userData } = ctx.body; // Store the new user in database. const user = await strapi.service('plugin::users-permissions.user').add(userData); // Send an email to validate his subscriptions. strapi.service('api::restaurant.restaurant').sendNewsletter('welcome@mysite.com', user.email, 'Welcome', '...'); // Send response to the server. ctx.send({ ok: true, }); }, })); ```
:::note 当创建新的 [内容类型](/cms/backend-customization/models.md#content-types) 时,Strapi 会生成一个带有占位符代码的通用服务,准备进行自定义。 🌐 When a new [content-type](/cms/backend-customization/models.md#content-types) is created, Strapi builds a generic service with placeholder code, ready to be customized. ::: ### 扩展核心服务 {#extending-core-services} 🌐 Extending core services 核心服务是为每种内容类型创建的,可以被[控制器](/cms/backend-customization/controllers.md)使用,以在 Strapi 项目中执行可重用的逻辑。核心服务可以自定义以实现你自己的逻辑。以下代码示例应能帮助你入门。 🌐 Core services are created for each content-type and could be used by [controllers](/cms/backend-customization/controllers.md) to execute reusable logic through a Strapi project. Core services can be customized to implement your own logic. The following code examples should help you get started. :::tip 核心服务可以完全通过[创建自定义服务](#adding-a-new-service)来替代,并将其命名为与核心服务相同(例如 `find`、`findOne`、`create`、`update` 或 `delete`)。 🌐 A core service can be replaced entirely by [creating a custom service](#adding-a-new-service) and naming it the same as the core service (e.g. `find`, `findOne`, `create`, `update`, or `delete`). :::
集合类型示例 ```js async find(params) { // some logic here const { results, pagination } = await super.find(params); // some more logic return { results, pagination }; } ``` ```js async findOne(documentId, params) { // some logic here const result = await super.findOne(documentId, params); // some more logic return result; } ``` ```js async create(params) { // some logic here const result = await super.create(params); // some more logic return result; } ``` ```js async update(documentId, params) { // some logic here const result = await super.update(documentId, params); // some more logic return result; } ``` ```js async delete(documentId, params) { // some logic here const result = await super.delete(documentId, params); // some more logic return result; } ```
单类型示例 ```js async find(params) { // some logic here const document = await super.find(params); // some more logic return document; } ``` ```js async createOrUpdate({ data, ...params }) { // some logic here const document = await super.createOrUpdate({ data, ...params }); // some more logic return document; } ``` ```js async delete(params) { // some logic here const document = await super.delete(params); // some more logic return document; } ```
## 使用 {#usage} 🌐 Usage 一旦服务被创建,就可以从[控制器](/cms/backend-customization/controllers.md)或其他服务访问它: 🌐 Once a service is created, it's accessible from [controllers](/cms/backend-customization/controllers.md) or from other services: ```js // access an API service strapi.service('api::apiName.serviceName').FunctionName(); // access a plugin service strapi.service('plugin::pluginName.serviceName').FunctionName(); ``` 在上面的语法示例中,`serviceName` 是 API 服务的服务文件名称,或者用于将服务文件导出到 `services/index.js` 的插件服务使用的名称。 🌐 In the syntax examples above, `serviceName` is the name of the service file for API services or the name used to export the service file to `services/index.js` for plugin services. :::tip 要列出所有可用的服务,请运行 `yarn strapi services:list`。 🌐 To list all the available services, run `yarn strapi services:list`. ::: ### 核心服务方法 {#core-service-methods} 🌐 Core service methods 使用 `createCoreService` 生成的服务继承了封装 [文档服务 API](/cms/api/document-service) 的方法。可用的方法取决于内容类型: 🌐 Services generated with `createCoreService` inherit methods that wrap the [Document Service API](/cms/api/document-service). The available methods depend on the content-type: #### 集合类型 {#collection-types} 🌐 Collection types | 方法 | 描述 | | --- | --- | | `find(params)` | [`findMany`](/cms/api/document-service#findmany) 的封装;返回文档的分页列表。 | | `findOne(documentId, params)` | [`findOne`](/cms/api/document-service#findone) 的封装器;通过其 `documentId` 返回单个文档。 | | `create(params)` | [`create`](/cms/api/document-service#create) 的封装器;创建一个新文档。 | | `update(documentId, params)` | [`update`](/cms/api/document-service#update) 的封装器;更新现有文档。 | | `delete(documentId, params)` | [`delete`](/cms/api/document-service#delete) 的封装器;删除一个文档。 | | `count(params)` | [`count`](/cms/api/document-service#count) 的封装器;返回匹配文档的数量。 | | `publish(documentId, params)` | [`publish`](/cms/api/document-service#publish) 的封装器;发布草稿文档。 | | `unpublish(documentId, params)` | [`unpublish`](/cms/api/document-service#unpublish) 的封装;取消发布文档。 | | `discardDraft(documentId, params)` | [`discardDraft`](/cms/api/document-service#discarddraft) 的封装器;删除草稿副本。 | #### 单一类型 {#single-types} 🌐 Single types | 方法 | 描述 | | --- | --- | | `find(params)` | 返回单个文档(内部使用 [`findFirst`](/cms/api/document-service#findfirst))。 | | `createOrUpdate({ data, ...params })` | 如果文档不存在则创建,或更新文档(使用 [`update`](/cms/api/document-service#update))。 | | `delete(params)` | 删除文档(使用 [`delete`](/cms/api/document-service#delete))。 | | `count(params)` | 计算匹配过滤条件的文档数量(使用 [`count`](/cms/api/document-service#count))。 | | `publish(params)` | 发布草稿文档(使用 [`publish`](/cms/api/document-service#publish))。 | | `unpublish(params)` | 取消发布文档(使用 [`unpublish`](/cms/api/document-service#unpublish))。 | | `discardDraft(params)` | 删除草稿副本(使用 [`discardDraft`](/cms/api/document-service#discarddraft))。 | #### 参数和默认行为 {#parameters-and-default-behavior} 🌐 Parameters and default behavior 核心服务方法接受与其底层的 [Document Service API](/cms/api/document-service) 调用相同的参数,例如 `fields`、`filters`、`sort`、`pagination`、`populate`、`locale` 和 `status`。当未提供 `status` 时,Strapi 会自动设置 `status: 'published'`,因此只返回已发布的内容。要查询草稿文档,请明确传递 `status: 'draft'` 或 Document Service 支持的其他值。 🌐 Core service methods accept the same parameters as their underlying [Document Service API](/cms/api/document-service) calls, such as `fields`, `filters`, `sort`, `pagination`, `populate`, `locale`, and `status`. When no `status` is provided, Strapi automatically sets `status: 'published'` so only published content is returned. To query draft documents, explicitly pass `status: 'draft'` or another value supported by the Document Service. `createCoreService` 工厂还提供了一个 `getFetchParams(params)` 辅助工具,它可以将控制器的查询对象转换为这些方法期望的参数格式。在重写核心方法以将清理后的参数传递给 `strapi.documents()` 时,可以重复使用此辅助工具。 🌐 The `createCoreService` factory also exposes a `getFetchParams(params)` helper that converts a controller's query object into the parameter format expected by these methods. This helper can be reused when overriding core methods to forward sanitized parameters to `strapi.documents()`. # 网络钩子 Source: https://strapi.nodejs.cn/cms/backend-customization/webhooks # 网络钩子 {#webhooks} 🌐 Webhooks Webhooks 让 Strapi 在内容发生变化时通知外部系统,同时为了隐私省略 Users 类型。在 `config/server` 中的配置设置了默认的头信息和触发第三方处理的端点。 Webhook 是一种由应用使用的结构,用于通知其他应用某个事件已经发生。更准确地说,webhook 是用户定义的 HTTP 回调。使用 webhook 是告知第三方提供商开始某些处理(持续集成、构建、部署……)的好方法。 🌐 Webhook is a construct used by an application to notify other applications that an event occurred. More precisely, webhook is a user-defined HTTP callback. Using a webhook is a good way to tell third-party providers to start some processing (CI, build, deployment ...). Webhook 的工作方式是通过 HTTP 请求(通常是 POST 请求)向接收应用传递信息。 🌐 The way a webhook works is by delivering information to a receiving application through HTTP requests (typically POST requests). ## 用户内容类型的网页钩子 {#user-content-type-webhooks} 🌐 User content-type webhooks 为了防止无意中将任何用户的信息发送到其他应用,Webhooks 将不会对用户内容类型起作用。如果你需要向其他应用通知用户集合的更改,可以通过使用 `./src/index.js` 示例创建 [生命周期钩子](/cms/backend-customization/models#lifecycle-hooks) 来实现。 🌐 To prevent from unintentionally sending any user's information to other applications, Webhooks will not work for the User content-type. If you need to notify other applications about changes in the Users collection, you can do so by creating [Lifecycle hooks](/cms/backend-customization/models#lifecycle-hooks) using the `./src/index.js` example. ## 可用配置 {#available-configurations} 🌐 Available configurations 你可以在文件 `./config/server` 中设置 webhook 配置。 🌐 You can set webhook configurations inside the file `./config/server`. - `webhooks` - `defaultHeaders`:你可以为你的 webhook 请求设置默认头部。此选项会被 webhook 本身设置的头部覆盖。 **示例配置** ```js title="./config/server.js" module.exports = { webhooks: { defaultHeaders: { "Custom-Header": "my-custom-header", }, }, }; ``` ```js title="./config/server.ts" webhooks: { defaultHeaders: { "Custom-Header": "my-custom-header", }, }, }; ``` ## Webhooks 安全 {#webhooks-security} 🌐 Webhooks security 大多数时候,Webhook 会向公共 URL 发出请求,因此有人可能会找到该 URL 并向其发送错误信息。 🌐 Most of the time, webhooks make requests to public URLs, therefore it is possible that someone may find that URL and send it wrong information. 为了防止这种情况发生,你可以发送带有身份验证令牌的头信息。使用管理面板时,你必须对每个 webhook 都这样做。 🌐 To prevent this from happening you can send a header with an authentication token. Using the Admin panel you would have to do it for every webhook. 另一种方法是定义 `defaultHeaders` 以添加到每个 webhook 请求中。 🌐 Another way is to define `defaultHeaders` to add to every webhook request. 你可以通过更新 `./config/server` 文件来配置这些全局头信息: 🌐 You can configure these global headers by updating the file at `./config/server`: ```js title="./config/server.js" module.exports = { webhooks: { defaultHeaders: { Authorization: "Bearer my-very-secured-token", }, }, }; ``` ```js title="./config/server.ts" webhooks: { defaultHeaders: { Authorization: "Bearer my-very-secured-token", }, }, }; ``` ```js title="./config/server.js" module.exports = { webhooks: { defaultHeaders: { Authorization: `Bearer ${process.env.WEBHOOK_TOKEN}`, }, }, }; ``` ```js title="./config/server.ts" webhooks: { defaultHeaders: { Authorization: `Bearer ${process.env.WEBHOOK_TOKEN}`, }, }, }; ``` 如果你自己开发 Webhook 处理程序,你现在可以通过读取标头来验证令牌。 🌐 If you are developing the webhook handler yourself you can now verify the token by reading the headers. ### 验证签名 {#verifying-signatures} 🌐 Verifying signatures 除了认证头之外,建议对 webhook 负载进行签名并在服务器端验证签名,以防止篡改和重放攻击。为此,你可以使用以下指南: 🌐 In addition to auth headers, it's recommended to sign webhook payloads and verify signatures server‑side to prevent tampering and replay attacks. To do so, you can use the following guidelines: - 生成共享密钥并将其存储在环境变量中。 - 让发送方对原始请求正文加上时间戳计算 HMAC(例如,SHA-256)。 - 在头信息中发送签名(和时间戳)(例如,`X‑Webhook‑Signature`,`X‑Webhook‑Timestamp`) - 收到请求后,重新计算 HMAC 并使用恒定时间检查进行比较。 - 如果签名无效或时间戳过旧,无法避免重放攻击,则拒绝请求。
示例:验证 HMAC 签名(Node.js) 这是一个最小的 Node.js 中间件示例(伪代码),显示 [HMAC](https://nodejs.cn/api/crypto.html#class-hmac) 验证: ```js title="/src/middlewares/verify-webhook.js" const crypto = require("crypto"); module.exports = (config, { strapi }) => { const secret = process.env.WEBHOOK_SECRET; return async (ctx, next) => { const signature = ctx.get("X-Webhook-Signature"); const timestamp = ctx.get("X-Webhook-Timestamp"); if (!signature || !timestamp) return ctx.unauthorized("Missing signature"); // Compute HMAC over raw body + timestamp const raw = ctx.request.rawBody || (ctx.request.body and JSON.stringify(ctx.request.body)) || ""; const hmac = crypto.createHmac("sha256", secret); hmac.update(timestamp + "." + raw); const expected = "sha256=" + hmac.digest("hex"); // Constant-time compare + basic replay protection const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); const skew = Math.abs(Date.now() - Number(timestamp)); if (!ok or skew > 5 * 60 * 1000) { return ctx.unauthorized("Invalid or expired signature"); } await next(); }; }; ``` ```ts title="/src/middlewares/verify-webhook.ts" const secret = process.env.WEBHOOK_SECRET as string; return async (ctx: any, next: any) => { const signature = ctx.get("X-Webhook-Signature") as string; const timestamp = ctx.get("X-Webhook-Timestamp") as string; if (!signature || !timestamp) return ctx.unauthorized("Missing signature"); // Compute HMAC over raw body + timestamp const raw: string = ctx.request.rawBody || (ctx.request.body && JSON.stringify(ctx.request.body)) || ""; const hmac = crypto.createHmac("sha256", secret); hmac.update(`${timestamp}.${raw}`); const expected = `sha256=${hmac.digest("hex")}`; // Constant-time compare + basic replay protection const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); const skew = Math.abs(Date.now() - Number(timestamp)); if (!ok || skew > 5 * 60 * 1000) { return ctx.unauthorized("Invalid or expired signature"); } await next(); }; }; ``` 以下是一些额外的外部示例: 🌐 Here are a few additional external examples: - [GitHub — Validating webhook deliveries](https://docs.github.com/webhooks/using-webhooks/validating-webhook-deliveries) - [Stripe — Verify webhook signatures](https://stripe.com/docs/webhooks/signatures)
## 可用活动 {#available-events} 🌐 Available events 默认情况下,Strapi webhook 可以由以下事件触发: 🌐 By default Strapi webhooks can be triggered by the following events: | 名称 | 描述 | | --- | --- | | [`entry.create`](#entrycreate) | 当创建内容类型条目时触发。 | | [`entry.update`](#entryupdate) | 当内容类型条目被更新时触发。 | | [`entry.delete`](#entrydelete) | 当内容类型条目被删除时触发。 | |['entry.publish'](#entrypublish) |当发布内容类型条目时触发。\* | | [`entry.unpublish`](#entryunpublish) | 当内容类型条目被取消发布时触发。* | | [`media.create`](#mediacreate) | 当媒体被创建时触发。 | | [`media.update`](#mediaupdate) | 当媒体被更新时触发。 | | [`media.delete`](#mediadelete) | 当媒体被删除时触发。 | | [`review-workflows.updateEntryStage`](#review-workflowsupdateentrystage) | 当内容在审核阶段之间移动时触发(参见 [审核工作流程](/cms/features/review-workflows#configuration))。
此事件仅适用于 Strapi 的 版本。 | | [`releases.publish`](#releases-publish) | 当发布 Release 时触发(参见 [Releases](/cms/features/releases))。
此事件仅在 Strapi CMS 的 或 计划中可用。 | *仅当此内容类型上的 `draftAndPublish` 已启用时。 ## 有效载荷 {#payloads} 🌐 Payloads :::info 私有字段不会在有效负载中发送。 🌐 Private fields are not sent in the payload. ::: ### 标题 {#headers} 🌐 Headers 当有效负载传递到你的 webhook 的 URL 时,它将包含特定标头: 🌐 When a payload is delivered to your webhook's URL, it will contain specific headers: | 标题 | 描述 | | --- | --- | | `X-Strapi-Event` | 被触发的事件类型的名称。 | ### `entry.create` 创建新条目时会触发此事件。 🌐 This event is triggered when a new entry is created. **示例有效负载** ```json { "event": "entry.create", "createdAt": "2020-01-10T08:47:36.649Z", "model": "address", "entry": { "id": 1, "geolocation": {}, "city": "Paris", "postal_code": null, "category": null, "full_name": "Paris", "createdAt": "2020-01-10T08:47:36.264Z", "updatedAt": "2020-01-10T08:47:36.264Z", "cover": null, "images": [] } } ``` ### `entry.update` 当条目更新时会触发此事件。 🌐 This event is triggered when an entry is updated. **示例有效负载** ```json { "event": "entry.update", "createdAt": "2020-01-10T08:58:26.563Z", "model": "address", "entry": { "id": 1, "geolocation": {}, "city": "Paris", "postal_code": null, "category": null, "full_name": "Paris", "createdAt": "2020-01-10T08:47:36.264Z", "updatedAt": "2020-01-10T08:58:26.210Z", "cover": null, "images": [] } } ``` ### `entry.delete` 当删除条目时会触发此事件。 🌐 This event is triggered when an entry is deleted. **示例有效负载** ```json { "event": "entry.delete", "createdAt": "2020-01-10T08:59:35.796Z", "model": "address", "entry": { "id": 1, "geolocation": {}, "city": "Paris", "postal_code": null, "category": null, "full_name": "Paris", "createdAt": "2020-01-10T08:47:36.264Z", "updatedAt": "2020-01-10T08:58:26.210Z", "cover": null, "images": [] } } ``` ### `entry.publish` 发布条目时会触发此事件。 🌐 This event is triggered when an entry is published. **示例有效负载** ```json { "event": "entry.publish", "createdAt": "2020-01-10T08:59:35.796Z", "model": "address", "entry": { "id": 1, "geolocation": {}, "city": "Paris", "postal_code": null, "category": null, "full_name": "Paris", "createdAt": "2020-01-10T08:47:36.264Z", "updatedAt": "2020-01-10T08:58:26.210Z", "publishedAt": "2020-08-29T14:20:12.134Z", "cover": null, "images": [] } } ``` ### `entry.unpublish` 当条目未发布时会触发此事件。 🌐 This event is triggered when an entry is unpublished. **示例有效负载** ```json { "event": "entry.unpublish", "createdAt": "2020-01-10T08:59:35.796Z", "model": "address", "entry": { "id": 1, "geolocation": {}, "city": "Paris", "postal_code": null, "category": null, "full_name": "Paris", "createdAt": "2020-01-10T08:47:36.264Z", "updatedAt": "2020-01-10T08:58:26.210Z", "publishedAt": null, "cover": null, "images": [] } } ``` ### `media.create` 当你在条目创建时或通过媒体界面上传文件时,会触发此事件。 🌐 This event is triggered when you upload a file on entry creation or through the media interface. **示例有效负载** ```json { "event": "media.create", "createdAt": "2020-01-10T10:58:41.115Z", "media": { "id": 1, "name": "image.png", "hash": "353fc98a19e44da9acf61d71b11895f9", "sha256": "huGUaFJhmcZRHLcxeQNKblh53vtSUXYaB16WSOe0Bdc", "ext": ".png", "mime": "image/png", "size": 228.19, "url": "/uploads/353fc98a19e44da9acf61d71b11895f9.png", "provider": "local", "provider_metadata": null, "createdAt": "2020-01-10T10:58:41.095Z", "updatedAt": "2020-01-10T10:58:41.095Z", "related": [] } } ``` ### `media.update` 当你通过媒体接口更换媒体或更新媒体元数据时,会触发该事件。 🌐 This event is triggered when you replace a media or update the metadata of a media through the media interface. **示例有效负载** ```json { "event": "media.update", "createdAt": "2020-01-10T10:58:41.115Z", "media": { "id": 1, "name": "image.png", "hash": "353fc98a19e44da9acf61d71b11895f9", "sha256": "huGUaFJhmcZRHLcxeQNKblh53vtSUXYaB16WSOe0Bdc", "ext": ".png", "mime": "image/png", "size": 228.19, "url": "/uploads/353fc98a19e44da9acf61d71b11895f9.png", "provider": "local", "provider_metadata": null, "createdAt": "2020-01-10T10:58:41.095Z", "updatedAt": "2020-01-10T10:58:41.095Z", "related": [] } } ``` ### `media.delete` 仅当你通过媒体接口删除媒体时才会触发该事件。 🌐 This event is triggered only when you delete a media through the media interface. **示例有效负载** ```json { "event": "media.delete", "createdAt": "2020-01-10T11:02:46.232Z", "media": { "id": 11, "name": "photo.png", "hash": "43761478513a4c47a5fd4a03178cfccb", "sha256": "HrpDOKLFoSocilA6B0_icA9XXTSPR9heekt2SsHTZZE", "ext": ".png", "mime": "image/png", "size": 4947.76, "url": "/uploads/43761478513a4c47a5fd4a03178cfccb.png", "provider": "local", "provider_metadata": null, "createdAt": "2020-01-07T19:34:32.168Z", "updatedAt": "2020-01-07T19:34:32.168Z", "related": [] } } ``` ### `review-workflows.updateEntryStage` 此事件仅适用于 Strapi 的 计划。
当内容被移动到新的审核阶段时,此事件将被触发(参见 [审核工作流](/cms/features/review-workflows#configuration))。 **示例有效负载** ```json { "event": "review-workflows.updateEntryStage", "createdAt": "2023-06-26T15:46:35.664Z", "model": "model", "uid": "uid", "entity": { "id": 2 }, "workflow": { "id": 1, "stages": { "from": { "id": 1, "name": "Stage 1" }, "to": { "id": 2, "name": "Stage 2" } } } } ``` ### `releases.publish` {#releases-publish} 当发布[release](/cms/features/releases)时,将触发该事件。 🌐 The event is triggered when a [release](/cms/features/releases) is published. **示例有效负载** ```json { "event": "releases.publish", "createdAt": "2024-02-21T16:45:36.877Z", "isPublished": true, "release": { "id": 2, "name": "Fall Winter highlights", "releasedAt": "2024-02-21T16:45:36.873Z", "scheduledAt": null, "timezone": null, "createdAt": "2024-02-21T15:16:22.555Z", "updatedAt": "2024-02-21T16:45:36.875Z", "actions": { "count": 1 } } } ``` ## Webhook 处理的最佳实践 {#best-practices-for-webhook-handling} 🌐 Best practices for webhook handling - 通过检查标头和有效负载签名来验证传入请求。 - 对失败的 webhook 请求实现重试以处理瞬态错误。 - 记录 webhook 事件以进行调试和监控。 - 使用安全的 HTTPS 端点接收 webhook。 - 设置速率限制以避免被多个 webhook 请求淹没。 :::tip 如果你想了解更多关于如何在 Next.js 中使用 webhooks 的信息,请查看[专门的博客文章](https://strapi.io/blog/how-to-create-an-ssg-static-site-generation-application-with-strapi-webhooks-and-nextjs)。 🌐 If you want to learn more about how to use webhooks with Next.js, please have a look at the [dedicated blog article](https://strapi.io/blog/how-to-create-an-ssg-static-site-generation-application-with-strapi-webhooks-and-nextjs). ::: # 账单门户 Source: https://strapi.nodejs.cn/cms/billing-portal # 计费门户 Strapi 账单门户是你可以查看所有 Strapi 订阅以及管理付款方式、账单详细信息和发票的地方。只有 Growth 订阅可以在门户中管理;Cloud 和 Enterprise 订阅仅可查看。 [Strapi 账单门户](https://billing.strapi.io) 是你查看和管理 Strapi 订阅账单的地方。对于所有订阅,你可以更新付款方式、编辑账单详情以及下载发票。 🌐 The [Strapi billing portal](https://billing.strapi.io) is where you view and manage billing for your Strapi subscriptions. For all subscriptions, you can update payment methods, edit billing details, and download invoices. 虽然你可以在门户中查看所有订阅,但只有增长订阅可以直接在那里管理。云订阅在 [Strapi Cloud 仪表板](/cloud/projects/settings#plans) 中管理。如需更改企业合同条款,请 [联系销售](mailto:sales@strapi.io)。 🌐 While you can view all subscriptions in the portal, only Growth subscriptions can be managed directly there. Cloud subscriptions are managed in the [Strapi Cloud dashboard](/cloud/projects/settings#plans). To change Enterprise contract terms, [contact sales](mailto:sales@strapi.io). ## 登录 {#sign-in} 🌐 Sign in 要登录 [账单门户](https://billing.strapi.io) : 🌐 To sign in to the [billing portal](https://billing.strapi.io): 1. 请输入你在购买或用于 CLI 认证时使用的电子邮件地址。 2. 输入发送到你收件箱的6位数验证码。 3. 如果你的电子邮件关联了多个账单账户,请选择你想要管理的账户。 ## 订阅 {#subscriptions} 🌐 Subscriptions *订阅* 标签显示你所有的 Strapi 订阅。 🌐 The *Subscriptions* tab displays all your Strapi subscriptions. 订阅被分为以下几个部分: 🌐 Subscriptions are grouped into the following sections: - **试用中**:尚未转换为付费的试用订阅。 - **活跃**:活跃的订阅。 - **计划取消**:订阅将在当前计费周期结束时被取消。 - **已取消**:已完全取消的订阅。 每张订阅卡显示计划名称、产品系列、状态、价格、计费周期(月度或年度)、续订或试用结束日期,以及订阅ID。在订阅ID旁边,云订阅显示云项目名称,Growth订阅显示与许可证关联的CMS项目ID。 🌐 Each subscription card shows the plan name, product family, status, price, billing period (monthly or yearly), renewal or trial end date, and subscription ID. Next to the subscription ID, Cloud subscriptions show the Cloud project name and Growth subscriptions show the CMS project ID linked to the license. ### 激活试用期内的 Growth 订阅 {#activating-an-in-trial-growth-subscription} 🌐 Activating an in-trial Growth subscription 在激活之前,请确保在[支付方式](#payment-methods)标签中添加支付方式,并完成你的[账单详情](#billing-details)资料。 🌐 Before you activate, make sure to add a payment method in the [Payment methods](#payment-methods) tab and complete your [Billing details](#billing-details) profile. 要激活订阅: 🌐 To activate a subscription: 1. 在*试用中*部分点击**激活订阅**。 2. 在 *管理订阅* 模态中,设置所需的席位数量,并选择计划是否应包含 SSO 附加组件。 3. 点击**继续**以查看费用摘要。 4. 点击 **立即激活** 以确认。 :::note 增长订阅必须至少包含 3 个席位。 🌐 Growth subscriptions must include a minimum of 3 seats. ::: ### 更改 Growth 订阅的座位和附加项 {#changing-seats-and-add-ons-on-a-growth-subscription} 🌐 Changing seats and add-ons on a Growth subscription 要更新活跃的 Growth 订阅的座位数量或附加功能: 🌐 To update the seat count or add-ons of an active Growth subscription: 1. 点击 **管理订阅**。 2. 在*管理订阅*弹窗中,根据需要调整座位数量和包含的附加组件。 3. 点击**继续**以查看费用摘要。 4. 点击 **确认** 以应用更改。 :::note 当你增加座位或启用 SSO 附加组件时,你会立即按比例收费。但是,你的 Strapi CMS 实例会在启动时以及每隔 12 小时检查一次许可证更新,因此新座位可能需要最多 12 小时才能出现在管理员面板中。若要更快地应用更改,请重启你的 Strapi 实例。 🌐 When you add seats or enable the SSO add-on, you are charged a prorated amount immediately. However, your Strapi CMS instance checks for license updates on startup and every 12 hours, so new seats may take up to 12 hours to appear in the admin panel. To apply the change sooner, restart your Strapi instance. 当你删除座位或禁用 SSO 附加组件时,更改将在下次续订时生效。你当前的座位数量和附加组件将在此之前保持有效。 🌐 When you remove seats or disable the SSO add-on, the change takes effect at the next renewal. Your current seat count and add-ons stay active until then. 如果你在同一次更改中增加席位并禁用 SSO,席位增加将立即收费。SSO 的移除仍将延期到下次续订时生效。 🌐 If you add seats and disable SSO in the same change, the seat increase is charged immediately. SSO removal is still deferred to the next renewal. ::: ### 取消增长订阅 {#canceling-a-growth-subscription} 🌐 Canceling a Growth subscription 要取消正在进行的增长订阅: 🌐 To cancel an active Growth subscription: 1. 点击 **取消订阅**。 2. 在 *取消订阅* 对话框中,点击 **确认** 以安排取消。 取消将在账单周期结束时生效。在更改待处理期间,你可以撤销已安排的取消并重新激活你的订阅。 🌐 Cancellations are effective at the end of the billing period. While the change is pending, you can undo the scheduled cancellation and reactivate your subscription. 即使订阅取消已生效,你也可以重新激活已取消的订阅。要重新激活,请点击订阅卡上的 **重新激活** 并按照步骤操作。 🌐 You can also reactivate a canceled subscription even after the cancellation has taken effect. To reactivate, click **Reactivate** on the subscription card and follow the steps. ## 支付方式 {#payment-methods} 🌐 Payment methods *支付方式* 标签让你管理用于订阅的付款卡。 🌐 The *Payment methods* tab lets you manage the payment cards used for your subscriptions. ### 添加新卡 {#adding-a-new-card} 🌐 Adding a new card 要添加新卡: 🌐 To add a new card: 1. 点击 **添加卡片**。 2. 在“添加支付方式”对话框中,输入卡号、有效期和 CVV/CVC。 3. 可选择,勾选 **设为默认付款方式**。 4. 点击 **保存**。 :::note 默认卡将用于所有订阅相关的交易,包括附加服务和超额费用。 🌐 The default card will be used for all subscription-related transactions, including add-ons and overages. ::: ### 更新或删除卡片 {#updating-or-removing-a-card} 🌐 Updating or removing a card 要更新现有的支付卡: 🌐 To update an existing payment card: 1. 点击你想编辑的付款卡的 图标。 2. 执行以下操作之一: - 点击 **设为默认** 将此卡设置为默认卡。 - 点击 **编辑卡片**,更新卡号、有效期和 CVV/CVC,然后点击 **保存**。 要删除现有的支付卡: 🌐 To remove an existing payment card: 1. 点击你想删除的付款卡的 图标。 2. 点击 **移除卡片**。 3. 在*移除支付方式*对话框中点击**确认**。 :::caution 你无法移除默认卡。在附加了次要卡的订阅(例如云订阅)的情况下,移除该卡将自动取消附加到该卡的所有订阅。 🌐 You cannot remove your default card. In cases where a secondary card is attached to a subscription (e.g. for Cloud subscriptions), removing the card will automatically cancel all the subscriptions attached to that card. ::: ## 账单详情 {#billing-details} 🌐 Billing details *账单详情*选项卡允许你查看和编辑账户账单信息。必须填写此部分的必填字段以激活试用订阅。 🌐 The *Billing details* tab lets you view and edit account billing information. Required fields in this section must be completed to activate a trial subscription. :::note 根据你的账单地址,可能会在你的发票上加收税费: 🌐 Taxes may be added to your invoice based on your billing address: - 在欧盟、英国、加拿大和印度,提供有效的增值税号可以免除增值税。如果未提供有效的增值税号,增值税将被添加到你的发票中。 - 在美国,适用的销售税是根据你的州和地址计算的。 ::: ## 发票 {#invoices} 🌐 Invoices *发票* 标签显示你所有 Strapi 订阅的发票及其状态。 🌐 The *Invoices* tab displays all invoices for your Strapi subscriptions and their status. 发票可以具有以下任何状态: 🌐 Invoices can have any of the following statuses: - **已付款**:款项已收到,发票可用,无需其他操作。 - **待处理**:发票尚未完成或验证,或付款未成功,需要修复。 - **未付款**:支付失败,且不会自动重试。 - **作废**:发票已被取消。 点击 ![下载图标](/img/assets/icons/download.svg) 图标下载发票。 🌐 Click the ![download icon](/img/assets/icons/download.svg) icon to download an invoice. # 命令行接口 Source: https://strapi.nodejs.cn/cms/cli # 命令行接口 (CLI) {#command-line-interface-cli} 🌐 Command Line Interface (CLI) Strapi 带有功能齐全的命令行接口(CLI),使你可以在几秒钟内搭建和管理项目。CLI 可与 `yarn` 和 `npm` 包管理器一起使用。 🌐 Strapi comes with a full featured Command Line Interface (CLI) which lets you scaffold and manage your project in seconds. The CLI works with both the `yarn` and `npm` package managers. 本页上的 Strapi CLI 命令按类别分组: 🌐 Strapi CLI commands on the present page are grouped by category: | 类别 | 命令 | |---|---| | [开发](#development) | `develop`, `start`, `build`, `console` | | [云](#cloud) | `login`, `link`, `deploy`, `logout` | | [数据管理](#data-management) | `export`, `import`, `transfer` | | [项目信息](#project-information) | `report`, `telemetry:disable`, `telemetry:enable`, `version`, `help` | | [配置](#configuration) | `configuration:dump`, `configuration:restore` | | [管理](#administration) | `admin:create-user`, `admin:reset-user-password`, `admin:list-users`, `admin:active-user`, `admin:block-user`, `admin:delete-user` | | [代码生成](#code-generation) | `generate`, `openapi generate`, `templates:generate`, `ts:generate-types` | | [列表](#listing) | `routes:list`, `policies:list`, `middlewares:list`, `content-types:list`, `hooks:list`, `controllers:list`, `services:list` | :::caution 像 `strapi admin:create-user` 这样的交互命令在使用 `npm` 时不会显示提示。请考虑使用 `yarn` 包管理器。 🌐 Interactive commands such as `strapi admin:create-user` don't display prompts with `npm`. Please consider using the `yarn` package manager. ::: ## 一般考虑 {#general-considerations} 🌐 General considerations 建议仅在本地安装 Strapi,这需要在所有以下 `strapi` 命令前加上用于项目设置的包管理器(例如 `npm run strapi help` 或 `yarn strapi help`)或专用的 node 包执行器(例如 `npx strapi help`)。 🌐 It is recommended to install Strapi locally only, which requires prefixing all of the following `strapi` commands with the package manager used for the project setup (e.g `npm run strapi help` or `yarn strapi help`) or a dedicated node package executor (e.g. `npx strapi help`). :::note The format for passing options differs between npm and yarn * 要使用 `npm` 传递选项,请使用以下语法:
`npm run strapi -- --