控制器
¥Controllers
控制器是 JavaScript 文件,其中包含一组称为操作的方法,客户端根据请求的 route。每当客户端请求路由时,该操作都会执行业务逻辑代码并发回 response。控制器代表模型-视图-控制器 (MVC) 模式中的 C。
¥Controllers are JavaScript files that contain a set of methods, called actions, reached by the client according to the requested route. Whenever a client requests the route, the action performs the business logic code and sends back the response. Controllers represent the C in the model-view-controller (MVC) pattern.
在大多数情况下,控制器将包含项目的大部分业务逻辑。但随着控制器的逻辑变得越来越复杂,使用 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 to organize the code into re-usable parts.

执行
¥Implementation
控制器可以是 手动生成或添加。Strapi 提供了 createCoreController
工厂功能,可自动生成核心控制器并允许构建自定义控制器或 扩展或替换生成的控制器。
¥Controllers can be generated or added manually. Strapi provides a createCoreController
factory function that automatically generates core controllers and allows building custom ones or extend or replace the generated controllers.
添加新控制器
¥Adding a new controller
可以实现一个新的控制器:
¥A new controller can be implemented:
-
¥with the interactive CLI command
strapi generate
-
或通过创建 JavaScript 文件手动:
¥or manually by creating a JavaScript file:
-
在
./src/api/[api-name]/controllers/
中用于 API 控制器(此位置很重要,因为 Strapi 从那里自动加载控制器)¥in
./src/api/[api-name]/controllers/
for API controllers (this location matters as controllers are auto-loaded by Strapi from there) -
或者在插件控制器的
./src/plugins/[plugin-name]/server/controllers/
这样的文件夹中,尽管只要插件接口在strapi-server.js
文件中正确导出(请参阅 插件服务器 API 文档),它们就可以在其他地方创建¥or in a folder like
./src/plugins/[plugin-name]/server/controllers/
for plugin controllers, though they can be created elsewhere as long as the plugin interface is properly exported in thestrapi-server.js
file (see Server API for Plugins documentation)
-
- JavaScript
- TypeScript
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 });
}
}));
import { factories } from '@strapi/strapi';
export default factories.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);
// 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
包含 请求上下文 和 响应上下文。
¥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 and the response context.
Example: GET /hello route calling a basic controller
定义了具体的 GET /hello
route,路由文件的名称(即 index
)用于调用控制器处理程序(即 index
)。每次向服务器发送 GET /hello
请求时,Strapi 都会调用 hello.js
控制器中的 index
操作,该操作返回 Hello World!
:
¥A specific GET /hello
route 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!
:
- JavaScript
- TypeScript
module.exports = {
routes: [
{
method: 'GET',
path: '/hello',
handler: 'hello.index',
}
]
}
module.exports = {
async index(ctx, next) { // called by GET /hello
ctx.body = 'Hello World!'; // we could also send a JSON
},
};
export default {
routes: [
{
method: 'GET',
path: '/hello',
handler: 'hello.index',
}
]
}
export default {
async index(ctx, next) { // called by GET /hello
ctx.body = 'Hello World!'; // we could also send a JSON
},
};
当创建新的 content-type 时,Strapi 会使用占位符代码构建通用控制器,以供自定义。
¥When a new content-type is created, Strapi builds a generic controller with placeholder code, ready to be customized.
要了解自定义控制器可能的高级用法,请阅读后端自定义示例说明书的 服务和控制器 页。
¥To see a possible advanced usage for custom controllers, read the services and controllers page of the backend customization examples cookbook.
控制器中的清理和验证
¥Sanitization and Validation in controllers
强烈建议你使用新的 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:
-
用户无权创建的关系
¥relations the user do not have permission to create
-
模式中不存在的无法识别的值
¥unrecognized values that are not present on a schema
-
不可写字段和内部时间戳(如
createdAt
和createdBy
字段)标识¥non-writable fields and internal timestamps like
createdAt
andcreatedBy
fields -
设置或更新
id
字段(连接关系除外)¥setting or updating an
id
field (except for connecting relations)
使用控制器工厂时的消毒
¥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 | (EXPERIMENTAL)验证输入数据(无效数据引发错误) |
这些函数自动从模型继承清理设置,并根据内容类型架构和任何内容 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.
由于这些方法使用与当前控制器关联的模型,如果你查询来自另一个模型的数据(即,在 "restaurant" 控制器方法中查找 "menus"),则必须改用 @strapi/utils
工具,例如 清理自定义控制器 中描述的 sanitize.contentAPI.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/utils
tools, such as sanitize.contentAPI.query
described in Sanitizing Custom Controllers, or else the result of your query will be sanitized against the wrong model.
- JavaScript
- TypeScript
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 });
}
}));
import { factories } from '@strapi/strapi';
export default factories.createCoreController('api::restaurant.restaurant', ({ strapi }) => ({
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 });
}
}));
构建自定义控制器时的清理和验证
¥Sanitization and validation when building custom controllers
在自定义控制器中,通过 @strapi/utils
包公开了 5 个主要功能,可用于清理和验证:
¥Within custom controllers, there are 5 primary functions exposed via the @strapi/utils
package that can be used for sanitization and validation:
函数名称 | 参数 | 描述 |
---|---|---|
sanitize.contentAPI.input | data 、schema 、auth | 清理请求输入,包括不可写字段、删除受限关系以及插件添加的其他嵌套 "visitors" |
sanitize.contentAPI.output | data 、schema 、auth | 清理响应输出,包括受限关系、私有字段、密码和插件添加的其他嵌套 "visitors" |
sanitize.contentAPI.query | ctx.query 、schema 、auth | 清理请求查询,包括过滤器、排序、字段和填充 |
validate.contentAPI.query | ctx.query 、schema 、auth | 验证请求查询,包括过滤器、排序、字段(当前未填充) |
validate.contentAPI.input | data 、schema 、auth | (EXPERIMENTAL)验证请求输入,包括不可写字段、删除受限关系以及插件添加的其他嵌套 "visitors" |
根据自定义控制器的复杂性,你可能需要 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.
- JavaScript
- TypeScript
const { sanitize, validate } = require('@strapi/utils');
module.exports = {
async findCustom(ctx) {
const contentType = strapi.contentType('api::test.test');
await validate.contentAPI.query(ctx.query, contentType, { auth: ctx.state.auth });
const sanitizedQueryParams = await sanitize.contentAPI.query(ctx.query, contentType, { auth: ctx.state.auth });
const documents = await strapi.documents(contentType.uid).findMany(sanitizedQueryParams);
return await sanitize.contentAPI.output(documents, contentType, { auth: ctx.state.auth });
}
}
import { sanitize, validate } from '@strapi/utils';
export default {
async findCustom(ctx) {
const contentType = strapi.contentType('api::test.test');
await validate.contentAPI.query(ctx.query, contentType, { auth: ctx.state.auth });
const sanitizedQueryParams = await sanitize.contentAPI.query(ctx.query, contentType, { auth: ctx.state.auth });
const documents = await strapi.documents(contentType.uid).findMany(sanitizedQueryParams);
return await sanitize.contentAPI.output(documents, contentType, { auth: ctx.state.auth });
}
}
扩展核心控制器
¥Extending core controllers
为每个内容类型创建默认控制器和操作。这些默认控制器用于返回对 API 请求的响应(例如,当访问 GET /api/articles/3
时,将调用 "文章" 内容类型的默认控制器的 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.