服务器 API:获取器与使用
🌐 Server API: Getters & usage
Page summary:
通过顶层 getter (
strapi.plugin('my-plugin').service('name')) 或全局 getter (strapi.service('plugin::my-plugin.name')) 访问插件资源。两者返回相同的对象。在自己的插件中使用顶层 getter,在应用代码或其他插件中使用全局 getter。路由没有全局 getter 的对应项。配置使用专用的配置 API。
插件服务器资源,例如控制器、服务、策略、中间件和内容类型,可以通过 strapi 实例从任何服务器端位置访问:其他插件、生命周期钩子、应用控制器或自定义脚本。路由和配置使用专用的 API — 请参见下面的 getter 参考。
🌐 Plugin server resources, such as controllers, services, policies, middlewares, and content-types, are accessible from any server-side location through the strapi instance: other plugins, lifecycle hooks, application controllers, or custom scripts. Routes and configuration use dedicated APIs — see the getter reference below.
在深入了解本页的概念之前,请确保你已经:
🌐 Before diving deeper into the concepts on this page, please ensure you have:
- 创建了一个 Strapi 插件,
- 已阅读并理解了服务器 API的基础知识
Getter 风格
🌐 Getter styles
Strapi 提供两种访问插件资源的方式。两者返回相同的底层对象,唯一的区别只是语法上的。
🌐 Strapi exposes 2 styles for accessing plugin resources. Both return the same underlying object, the difference is purely syntactic.
顶层 getter 通过插件名称链式调用:
strapi.plugin('plugin-name').service('service-name')
strapi.plugin('plugin-name').controller('controller-name')
全局获取器 直接在 strapi 实例上使用完整 UID:
strapi.service('plugin::plugin-name.service-name')
strapi.controller('plugin::plugin-name.controller-name')
选择取决于具体情况和可读性:
🌐 The choice is a matter of context and readability:
- 在你自己的插件中,顶层 getter 更简洁,并使插件边界更加明确。
- 从应用代码或另一个插件中,全局 getter 与
api::UID 一起使用时读起来更自然。
2 个资源是例外:
- routes(
strapi.plugin('plugin-name').routes)没有全局 getter 等价物, - 并且配置使用专用的配置 API(
strapi.plugin('plugin-name').config()和strapi.config.get(...)),而不是资源获取器。
完整的 getter 引用
🌐 Full getter reference
下表列出了名为 todo、资源名为 task 的插件的所有可用获取器:
🌐 The following table lists all available getters for a plugin named todo with a resource named task:
| | 服务 | 控制器 | 内容类型 | 策略 | 中间件 | 路由 | 配置 || --- | --- | --- | --- | --- | --- | --- | --- || 顶层 | strapi.plugin('todo').service('task') | strapi.plugin('todo').controller('task') | strapi.plugin('todo').contentType('task') | strapi.plugin('todo').policy('is-owner') | strapi.plugin('todo').middleware('audit-log') | strapi.plugin('todo').routes | strapi.plugin('todo').config('featureFlag') || 全局 | strapi.service('plugin::todo.task') | strapi.controller('plugin::todo.task') | strapi.contentType('plugin::todo.task') | strapi.policy('plugin::todo.is-owner') | strapi.middleware('plugin::todo.audit-log') | — | strapi.config.get('plugin::todo.featureFlag') |
这两种风格返回相同的底层对象。路由没有全局 getter 的对应物。配置使用专用的配置 API 而不是资源 getter,这两种形式读取的都是相同的合并值。
🌐 Both styles return the same underlying object. Routes have no global getter equivalent. Configuration uses dedicated config APIs rather than resource getters, both forms read the same merged value.
运行 yarn strapi console 或 npm run strapi console 来在实时控制台中检查 strapi 对象,并以交互方式探索可用的插件及其资源。
🌐 Run yarn strapi console or npm run strapi console to inspect the strapi object in a live console and explore available plugins and their resources interactively.
使用示例
🌐 Usage examples
从控制器调用插件服务
🌐 Calling a plugin service from a controller
最常见的模式:控制器委托给它自己插件的服务:
🌐 The most common pattern: a controller delegates to its own plugin's service:
- JavaScript
- TypeScript
'use strict';
module.exports = ({ strapi }) => ({
async find(ctx) {
const tasks = await strapi.plugin('todo').service('task').findAll(); // top-level getter: preferred inside your own plugin
ctx.body = tasks;
},
async create(ctx) {
const task = await strapi
.plugin('todo')
.service('task')
.create(ctx.request.body);
ctx.status = 201;
ctx.body = task;
},
});
import type { Context } from 'koa';
import type { Core } from '@strapi/strapi';
type TaskService = {
findAll(): Promise<unknown[]>;
create(data: unknown): Promise<unknown>;
};
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async find(ctx: Context) {
// Narrow cast: plugin services require app-level type augmentation for full typing.
const tasks = await (strapi.plugin('todo').service('task') as TaskService).findAll();
ctx.body = tasks;
},
async create(ctx: Context) {
const task = await (strapi.plugin('todo').service('task') as TaskService).create(
(ctx.request as any).body
);
(ctx as any).status = 201;
ctx.body = task;
},
});
从 bootstrap 调用插件服务
🌐 Calling a plugin service from bootstrap
在 bootstrap() 中调用的服务可以访问完整的 strapi 实例,包括其他插件的服务:
🌐 Services called in bootstrap() have access to the full strapi instance, including other plugins' services:
- JavaScript
- TypeScript
'use strict';
module.exports = async ({ strapi }) => {
// Call own plugin service to seed initial data
const count = await strapi.plugin('todo').service('task').count();
if (count === 0) {
await strapi.plugin('todo').service('task').create({
title: 'Welcome task',
done: false,
});
}
};
import type { Core } from '@strapi/strapi';
type TaskService = {
count(): Promise<number>;
create(data: unknown): Promise<unknown>;
};
export default async ({ strapi }: { strapi: Core.Strapi }) => {
// Narrow cast: plugin services are resolved dynamically unless your project augments Strapi service typings.
const taskService = strapi.plugin('todo').service('task') as TaskService;
const count = await taskService.count();
if (count === 0) {
await taskService.create({ title: 'Welcome task', done: false });
}
};
在插件之间或从应用代码中调用
🌐 Calling across plugins or from application code
从应用级别的控制器或服务(在插件外部),或者从另一个插件调用时,使用完整 UID 的全局获取器通常更清晰:
🌐 From application-level controllers or services (outside the plugin), or when calling from another plugin, global getters using the full UID are often clearer:
- JavaScript
- TypeScript
'use strict';
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::project.project', ({ strapi }) => ({
async create(ctx) {
const { data, meta } = await super.create(ctx);
await strapi.service('plugin::todo.task').create({ // global getter: preferred in application code
title: `Review project: ${data.attributes.name}`,
done: false,
});
return { data, meta };
},
}));
import { factories } from '@strapi/strapi';
type TaskService = {
create(data: unknown): Promise<unknown>;
};
export default factories.createCoreController(
'api::project.project',
({ strapi }) => ({
async create(ctx: any) {
const { data, meta } = await super.create(ctx);
// Narrow cast: this generic documentation cannot infer your app-specific service signatures.
await (strapi.service('plugin::todo.task') as TaskService).create({
title: `Review project: ${data.attributes.name}`,
done: false,
});
return { data, meta };
},
})
);
在运行时读取插件配置
🌐 Reading plugin configuration at runtime
// Read a single key
const maxItems = strapi.plugin('todo').config('maxItems');
// Read the full config object
const todoConfig = strapi.config.get('plugin::todo');
// Read a nested key
const endpoint = strapi.config.get('plugin::todo.endpoint');
strapi.plugin('my-plugin').config('key') 读取合并后的配置(用户覆盖应用在插件默认值之上)。这是在插件代码中读取配置的推荐方式。有关插件配置如何声明和合并,请参阅 服务器配置。
访问内容类型架构
🌐 Accessing a content-type schema
当你需要 schema 对象时使用 content-type 获取器,例如将其传递给清理 API:
🌐 Use the content-type getter when you need the schema object, for example to pass it to the sanitization API:
- JavaScript
- TypeScript
// Access the content-type schema
const schema = strapi.contentType('plugin::todo.task');
const sanitizedOutput = await strapi.contentAPI.sanitize.output(
data,
schema,
{ auth: ctx.state.auth }
);
const schema = strapi.contentType('plugin::todo.task'); // access the content-type schema
const sanitizedOutput = await strapi.contentAPI.sanitize.output(
data,
schema,
{ auth: ctx.state.auth }
);
常见错误
🌐 Common errors
-
路由处理程序与控制器键命名不匹配。 如果你的路由声明了
handler: 'task.find',你的控制器索引必须导出一个名为task的键,并且该控制器必须有一个名为find的方法。当路由匹配时,如果不匹配会抛出运行时错误。 -
滥用策略上下文参数。 策略函数的第一个参数是一个策略上下文对象,而不是原生的 Koa
ctx。它封装了请求上下文,但提供了不同的接口。在你的代码中将它命名为ctx不会导致错误,但将其当作 Koa 上下文处理(例如,调用ctx.body或ctx.status)将无法按预期工作。使用policyContext.state访问认证状态,并调用return false或抛出PolicyError来阻止请求。 -
在模块加载时调用服务。 当模块首次加载时,
strapi对象尚未初始化。始终在函数体内调用 getter。不要在模块文件的顶层调用它们。 -
在全局 getter 中使用不完整的 UID。
strapi.service('todo.task')不是有效的插件 UID。请使用完整的plugin::todo.task形式。如果没有正确的命名空间,服务调用将在运行时失败或返回undefined。范围 示例 UID 插件服务 plugin::todo.taskAPI 服务 api::project.project
最佳实践
🌐 Best practices
- 在你自己的插件中优先使用顶层 getter。 当两者都在同一个插件中时,
strapi.plugin('my-plugin').service('task')比全局形式更易读。 - 在应用代码和跨插件调用中使用全局 getter。 当从
src/api/调用或从另一个插件调用时,使用完整的 UIDplugin::todo.task可以明确依赖,并且更容易搜索。 - 在使用服务时访问服务,而不是在声明时。 避免在模块初始化时在闭包中捕获服务引用。始终在调用时使用 getter 解析它们,以确保 Strapi 完全加载。