服务端 API:Getters 与用法(Getters & usage)
页面摘要: 通过顶层 getter(
strapi.plugin('my-plugin').service('name'))或全局 getter(strapi.service('plugin::my-plugin.name'))访问插件资源。两者返回相同的对象。在你自己的插件内部使用顶层 getter,在应用代码或其他插件中使用全局 getter。路由没有对应的全局 getter。配置使用专门的配置 API。
插件的服务端资源(如控制器、服务、策略、中间件和内容类型)可以通过 strapi 实例从任何服务端位置访问:其他插件、生命周期 hook、应用控制器或自定义脚本。路由和配置使用专门的 API —— 请参阅下面的 getter 参考。
在深入阅读本页概念之前,请确保你已经:
- 创建了一个 Strapi 插件,
- 阅读并理解了 Server API 的基础知识。
Getter 风格
Strapi 提供 2 种风格来访问插件资源。两者返回相同的底层对象,区别纯粹在于语法。
顶层 getter 通过插件名链式调用:
strapi.plugin('plugin-name').service('service-name')
strapi.plugin('plugin-name').controller('controller-name')
全局 getter 直接在 strapi 实例上使用完整的 UID:
strapi.service('plugin::plugin-name.service-name')
strapi.controller('plugin::plugin-name.controller-name')
选择取决于上下文和可读性:
- 在你自己的插件内部,顶层 getter 更简洁,并使插件边界更加明确。
- 从应用代码或另一个插件中,全局 getter 与
api::UID 一起阅读起来更自然。
有 2 个资源是例外:
- 路由(
strapi.plugin('plugin-name').routes)没有对应的全局 getter, - 配置使用专门的配置 API(
strapi.plugin('plugin-name').config()和strapi.config.get(...))而不是资源 getter。
完整 getter 参考
下表列出了名为 todo 的插件中名为 task 的资源的所有可用 getter:
| Top-level | Global | |
|---|---|---|
| Service | strapi.plugin('todo').service('task') | strapi.service('plugin::todo.task') |
| Controller | strapi.plugin('todo').controller('task') | strapi.controller('plugin::todo.task') |
| Content-type | strapi.plugin('todo').contentType('task') | strapi.contentType('plugin::todo.task') |
| Policy | strapi.plugin('todo').policy('is-owner') | strapi.policy('plugin::todo.is-owner') |
| Middleware | strapi.plugin('todo').middleware('audit-log') | strapi.middleware('plugin::todo.audit-log') |
| Routes | strapi.plugin('todo').routes | — |
| Configuration | strapi.plugin('todo').config('featureFlag') | strapi.config.get('plugin::todo.featureFlag') |
两种风格返回相同的底层对象。路由没有对应的全局 getter。配置使用专门的配置 API 而不是资源 getter,两种形式读取的都是相同的合并后的值。
运行 yarn strapi console 或 npm run strapi console,在实时控制台中检查 strapi 对象,并以交互方式探索可用的插件及其资源。
用法示例
从控制器调用插件服务
最常见的模式:控制器委托给其自己插件的服务:
JavaScript
'use strict';
module.exports = ({ strapi }) => ({
async find(ctx) {
// highlight-next-line
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;
},
});
TypeScript
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 调用插件服务
在 bootstrap() 中调用的服务可以访问完整的 strapi 实例,包括其他插件的服务:
JavaScript
'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,
});
}
};
TypeScript
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;
// highlight-next-line
const count = await taskService.count();
if (count === 0) {
await taskService.create({ title: 'Welcome task', done: false });
}
};
跨插件调用或从应用代码调用
从应用级控制器或服务(插件外部),或从另一个插件调用时,使用完整 UID 的全局 getter 通常更清晰:
JavaScript
'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);
// highlight-next-line
await strapi.service('plugin::todo.task').create({ // global getter: preferred in application code
title: `Review project: ${data.attributes.name}`,
done: false,
});
return { data, meta };
},
}));
TypeScript
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);
// highlight-next-line
// 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 };
},
})
);
在运行时读取插件配置
// 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') 读取合并后的配置(在插件默认值之上应用了用户覆盖)。这是在插件代码内部读取配置的首选方式。有关插件配置如何声明和合并的信息,请参阅 服务端配置。
访问内容类型 schema
当你需要 schema 对象时(例如将其传递给清理 API),请使用内容类型 getter:
JavaScript
// 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 }
);
TypeScript
// highlight-next-line
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 }
);
常见错误
-
路由处理程序与控制器键之间的命名不匹配。 如果你的路由声明了
handler: 'task.find',你的 controllers index 必须导出一个名为task的键,并且该控制器必须有一个名为find的方法。不匹配会在路由匹配时抛出运行时错误。 -
误用策略上下文参数。 策略函数的第一个参数是一个策略上下文对象,而不是原始的 Koa
ctx。它封装了请求上下文,但暴露了不同的接口。在代码中将其命名为ctx不会导致错误,但将其当作 Koa 上下文(例如调用ctx.body或ctx.status)将无法按预期工作。使用policyContext.state访问 auth 状态,并调用return false或抛出PolicyError来阻止请求。 -
在模块加载时调用服务。 在模块首次加载时,
strapi对象尚未初始化。务必在函数体内调用 getter。切勿在模块文件的顶层调用它们。 -
在全局 getter 中使用不完整的 UID。
strapi.service('todo.task')不是有效的插件 UID。请使用完整的plugin::todo.task形式。如果没有正确的命名空间,服务调用会在运行时失败或返回undefined。Scope Example UID Plugin service plugin::todo.taskAPI service api::project.project
最佳实践
-
在你自己的插件内部优先使用顶层 getter。 当两者都在同一个插件内部时,
strapi.plugin('my-plugin').service('task')比全局形式更具可读性。 -
在应用代码和跨插件调用中使用全局 getter。 当从
src/api/或从另一个插件调用时,完整的 UIDplugin::todo.task使依赖关系明确且更易于搜索。 -
在服务中访问服务,而不是在声明时。 避免在模块初始化时于闭包中捕获服务引用。始终在调用时通过 getter 解析它们,以确保 Strapi 已完全加载。