news 2026/9/12 14:34:28

Backstage 后端插件开发实战:从 `yarn new` 创建到服务注入与安全加固

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Backstage 后端插件开发实战:从 `yarn new` 创建到服务注入与安全加固

Backstage 后端插件开发实战:从yarn new创建到服务注入与安全加固

【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage

本文以 Backstage 新后端系统(New Backend System)为主线,完整讲解后端插件(backend plugin)的创建、独立开发调试、接入应用、安全默认策略(secure by default)、依赖注入(deps / init)、数据库访问与用户身份获取的完整流程。读者在读完本文后,将能够从零创建一个可独立运行、可挂载到 Backstage 主后端、并具备认证控制与持久化能力的生产级后端插件。

本文主体基于 docs/plugins/backend-plugin.md,并结合仓库内yarn new的实际脚手架模板(packages/cli-module-new/templates/backend-plugin/)与核心服务定义源码(如 packages/backend-plugin-api/src/services/definitions/HttpRouterService.ts)进行源码级印证与扩充。

注意:该页面属于 legacy plugins 文档的一部分,虽然页面本身已按新后端系统模式书写,但 Backstage 官方当前推荐的后端插件权威指南位于 docs/backend-system/building-plugins-and-modules/01-index.md,本文内容与其相互印证、可对照阅读。

创建后端插件

在 Backstage 仓库根目录执行yarn new,在交互式提示中选择backend-plugin,即可生成一个最小可用的后端插件包:

yarn new

创建过程会要求为插件提供一个名称。这个名称将成为 NPM 包名的一部分,因此建议使用简短、只含小写字母和连字符的标识符。例如,如果你要开发一个与名为 Carmen 的系统做集成的插件,可以命名为carmen;结合传给new命令的其他标志以及根目录package.json中针对new命令的配置,最终 NPM 包名形如@internal/plugin-carmen-backend

创建插件会花费一些时间,因为脚手架会自动运行依赖安装与构建命令,使包直接处于可开发状态。生成后的代码位于仓库plugins目录下的新文件夹中,本例为plugins/carmen-backend

从仓库模板 packages/cli-module-new/templates/backend-plugin/package.json.hbs 可以看出,一个全新的后端插件包具备以下特征:

  • "backstage": { "role": "backend-plugin", "pluginId": "..." }:声明该包是后端插件,并标注插件 ID;
  • 提供start/build/lint/test/clean/prepack/postpack等脚本,统一基于backstage-cli package *命令;
  • 默认依赖@backstage/backend-defaults@backstage/backend-plugin-api@backstage/catalog-client@backstage/errorsexpressexpress-promise-routerzod等;测试相关依赖包括@backstage/backend-test-utilssupertest

独立开发模式(Standalone)

为方便开发,后端插件可以被独立启动,无需依赖整个 Backstage 应用。进入插件目录并启动:

cd plugins/carmen-backend yarn start

启动一段时间后会输出Listening on :7007。在另一个终端窗口中执行:

curl localhost:7007/api/carmen/todos

预期响应为:

{ "items": [] }

当前模板的默认路由就是/api/<pluginId>/todos;如果希望为健康检查提供/health端点,需要自己在 router 中实现。

独立开发环境的实现在模板的 packages/cli-module-new/templates/backend-plugin/dev/index.ts.hbs 中。可以看到,它用createBackend()组装了一个最小后端:

import { createBackend } from '@backstage/backend-defaults'; import { mockServices } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; const backend = createBackend(); // 使用 mock 的 auth 与 httpAuth 服务,这样无需真实认证即可调用插件 API; // 如需真实认证,可改为: // backend.add(import('@backstage/plugin-auth-backend')); // backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); backend.add(mockServices.auth.factory()); backend.add(mockServices.httpAuth.factory()); // 使用带固定实体集合的 catalog mock,而非真实 catalog backend.add( catalogServiceMock.factory({ entities: [ { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', metadata: { name: 'sample', title: 'Sample Component' }, spec: { type: 'service' }, }, ], }), ); backend.add(import('../src')); backend.start();

模板自带的 dev 注释还提供了几组可直接试用的 curl 命令,可用于验证创建、列出 TODO 以及显式携带 mock 认证头的场景:

# 创建一条 TODO(独立创建或关联 sample 组件) curl http://localhost:7007/api/<pluginId>/todos -H 'Content-Type: application/json' -d '{"title": "My Todo"}' curl http://localhost:7007/api/<pluginId>/todos -H 'Content-Type: application/json' -d '{"title": "My Todo", "entityRef": "component:default/sample"}' # 列出 TODO curl http://localhost:7007/api/<pluginId>/todos # 显式使用未认证 / 服务认证的 token curl http://localhost:7007/api/<pluginId>/todos -H 'Authorization: Bearer mock-none-token' curl http://localhost:7007/api/<pluginId>/todos -H 'Authorization: Bearer mock-service-token'

将插件接入主后端

一个新创建的后端插件在应用层面“什么都不做”:它只有一组基础依赖,并在src/service/router.ts中暴露一个 Express router。你需要把路由接入具体业务功能,同时在 Backstage 应用/后端中显式挂载它。

首先在 Backstage 根目录将插件包加入后端依赖(包名以插件实际package.json中的为准):

yarn --cwd packages/backend add @internal/plugin-carmen-backend@^0.1.0

然后修改packages/backend/src/index

const backend = createBackend(); // ... backend.add(import('@internal/plugin-carmen-backend')); // ... backend.start();

从仓库根目录用yarn start-backend启动后端后,即可访问插件接口:

# 注意这里的 /api 前缀 curl localhost:7007/api/carmen/health

预期返回{"status":"ok"},说明插件已成功挂载到主后端。

需要指出的是:模板中插件的入口文件 packages/cli-module-new/templates/backend-plugin/src/index.ts.hbs 只是把plugin.ts中的插件对象以默认导出再导出一次:

export { {{pluginVar}} as default } from './plugin';

这正是backend.add(import('...'))所要求的默认导出形态,也是整个新后端系统“零配置装配”的基础。

Secure by Default:默认安全与认证策略

自 Backstage 1.25 起,插件体系开始转向 secure by default 模型:插件收到的网络请求默认不允许未认证用户访问。以文档中允许未认证访问的/health请求为例,其路由定义在plugins/carmen-backend/src/service/router.ts

export async function createRouter( options: RouterOptions, ): Promise<express.Router> { // ... router.get('/health', (_, response) => { logger.info('PONG!'); response.json({ status: 'ok' }); }); // ... return router; }

可以看到路由本身没有定义任何认证机制,只有路由名与响应数据——认证由插件定义(plugins/carmen-backend/src/plugin.ts)统一处理:

httpRouter.use( await createRouter({ logger, }), ); httpRouter.addAuthPolicy({ path: '/health', allow: 'unauthenticated', });

addAuthPolicy声明了对插件/health端点的放行策略,允许该路径的请求以未认证身份通过;而其他未显式声明的路径则继续遵循 secure by default 的默认拒绝行为。

该 API 的底层契约定义在 packages/backend-plugin-api/src/services/definitions/HttpRouterService.ts:addAuthPolicy(policy: HttpRouterServiceAuthPolicy): void,其中HttpRouterServiceAuthPolicy携带pathallow(如'unauthenticated')字段;而 HttpAuthService 文档中同样指出,对未认证请求的放行应配合HttpRouterService.addAuthPolicy使用。从源码结构看,实际的请求凭据校验由后端默认实现中的createCredentialsBarrier等机制承载(参见 packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts 及同目录的 createCredentialsBarrier.ts),它会拦截未携带有效凭据的请求,只有命中显式放行策略的路径才能以未认证身份通过。

使用依赖:deps 声明与 init 注入

新后端系统中,依赖在注册阶段以静态方式声明,在初始化阶段被“注入”。以插件定义 packages/cli-module-new/templates/backend-plugin/src/plugin.ts.hbs 为参照:

export const carmenPlugin = createBackendPlugin({ pluginId: 'carmen', register(env) { env.registerInit({ deps: { httpRouter: coreServices.httpRouter, logger: coreServices.logger, }, async init({ httpRouter, logger }) { // ... }, }); }, });

deps中的每一项都会在init的参数对象中可用。要添加自定义依赖,只需在deps中新增具名条目:

deps: { myDependency: coreServices.rootConfig, },

然后在init中通过解构访问:

async init({ myDependency }) { // .. }

之后就可以自由地调用它,或将其传入 router 等业务代码中。新后端系统的一个关键特性是:plugin 作用域的服务(如loggerhttpRouter)是“专属于当前插件”的实例——logger 可能会用插件 ID 标记日志,httpRouter 可能会为 API 路由自动加上插件 ID 前缀(具体行为取决于实现),这让多插件并行运行时代码更加隔离与整洁。

Backstage 内置了丰富的coreServices,完整列表见 docs/backend-system/core-services/01-index.md。

使用数据库

Backstage 后端内置了统一的 SQL 数据库访问能力。大多数有持久化需求的插件都应优先使用该设施,以便 Backstage 运维人员能够统一管理数据库需求。

使用方式是在插件定义中依赖coreServices.database,它会提供一个 Knex 连接对象:

deps: { // ... database: coreServices.database, }, async init({ database, }) { // 将 client 传入插件实际实现代码,例如: const model = new CarmenDatabaseModel(database); httpRouter.use( await createRouter({ model, logger, }), ); }

所有插件数据库需求都配置在app-config.yamlbackend.database配置键下。框架还会在后台根据 Backstage 运维者设定的规则,在逻辑数据库不存在时自动创建它。

需要注意两点限制:

  • 框架不负责数据库 schema 迁移:主仓库中的内置插件选择使用 Knex 库来管理 schema 迁移,你也可以采用任何你认为合适的方式;
  • 迁移表命名冲突:模块若与目标插件共享同一逻辑数据库实例,应谨慎选择表名。01-index.md中给出的建议命名模式是<package name>__<table name>,例如 scheduler 核心服务创建的backstage_backend_tasks__<table>表。若使用 Knex 默认迁移设施,还应为其内部记账用的迁移状态表指定带前缀的名字,避免与插件主迁移表冲突:
await knex.migrate.latest({ directory: migrationsDir, tableName: 'backstage_backend_tasks__knex_migrations', });

Knex 迁移编写与 SQL 查询的具体示例,参见 Knex 官方文档。

使用用户身份

Backstage 后端提供coreServices.httpAuthcoreServices.userInfo两个核心服务来访问用户身份。先在插件定义中声明:

deps: { httpAuth: coreServices.httpAuth, userInfo: coreServices.userInfo, }, async init({ httpAuth, userInfo, }) { httpRouter.use( await createRouter({ httpAuth, userInfo, logger, }), ); }

随后在 router 中从请求里提取身份信息:

export interface RouterOptions { logger: LoggerService; userInfo: UserInfoService; httpAuth: HttpAuthService; } export async function createRouter( options: RouterOptions, ): Promise<express.Router> { const { userInfo, httpAuth } = options; router.post('/me', async (req, res) => { const credentials = await httpAuth.credentials(req, { // 这会拒绝来自非用户的请求。仅当插件确实需要访问用户身份时才使用; // 大多数情况下只需调用 `httpAuth.credentials(req)` 即可。 allow: ['user'], }); const user = await userInfo.getUserInfo(credentials); res.json({ // 用户的 catalog 实体引用。 userEntityRef: user.userEntityRef, // 该用户或其所属团队所拥有的实体引用列表。 ownershipEntityRefs: user.ownershipEntityRefs, }); }); // ... }

这一模式同样出现在模板 router(packages/cli-module-new/templates/backend-plugin/src/router.ts)中:创建 TODO 的接口通过httpAuth.credentials(req, { allow: ['user'] })校验用户身份,并把凭据传给业务服务。模板中的TodoListService(packages/cli-module-new/templates/backend-plugin/src/services/TodoListService.ts)进一步展示了两个进阶实践:

  • 跨插件调用 catalog:通过catalogServiceRef依赖 catalog 服务,用getEntityByRef根据实体引用拉取实体(跨插件通信使用服务到服务认证,AuthService可生成仅对目标插件有效的 token;若想以插件后端自身身份发起请求,可调用auth.getOwnServiceCredentials(),但要注意这绕过了用户权限检查);
  • 记录创建者:从options.credentials.principal.userEntityRef中读取当前用户实体引用,写入 TODO 记录。

源码视角:模板中的完整数据流

综合模板各文件可以看出一个后端插件的完整分层结构:

  1. 入口层:src/index.ts.hbs 默认导出插件对象;
  2. 装配层:src/plugin.ts.hbs 声明deps、在init中把服务注入 router;
  3. 路由层:src/router.ts 使用express-promise-router定义 REST 路由,用zod做请求体校验,并用httpAuth.credentials把关身份;
  4. 业务层:src/services/TodoListService.ts 通过createServiceRef/createServiceFactory定义可注入的服务;
  5. 测试层:src/router.test.ts 直接针对createRouter做单元测试——用mockServices.httpAuth()模拟认证、用jest.Mocked模拟 todoList 服务,并断言:携带 mock 用户凭据的 POST 返回 201;携带mockCredentials.none.header()(未认证)的 POST 返回 401。这个测试用例恰好从另一个角度印证了 secure by default 行为。

测试依赖统一来自@backstage/backend-test-utils(如mockServicesmockCredentialsmockErrorHandler),相关类型可参阅 packages/backend-test-utils 目录。

下一步

  • 后端插件的权威指南与模块(module)开发、扩展点(extension point)机制,见 docs/backend-system/building-plugins-and-modules/01-index.md;
  • 核心服务的完整清单与用法,见 docs/backend-system/core-services/01-index.md;
  • 为新插件定义配置 schema 时,参考 docs/conf/defining.md 中关于插件自定义配置的说明;
  • 生成脚手架本身的实现位于 packages/cli-module-new(yarn new即由该 CLI 模块驱动,模板目录为 packages/cli-module-new/templates)。

【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 14:27:30

降AI率解读:为什么纯手写论文AIGC检测也会超标2026深度解析

降AI率解读&#xff1a;为什么纯手写论文AIGC检测也会超标2026深度解析 手写论文降AI率超标原因解读背后的机制&#xff0c;很多人说不清楚。这篇梳理清楚降AI率核心逻辑&#xff0c;以及针对性的解决方案。 主推嘎嘎降AI&#xff08;www.aigcleaner.com&#xff09;&#xf…

作者头像 李华
网站建设 2026/9/12 14:27:15

Costas环载波同步仿真:BPSK/QPSK/MSK/GMSK的Simulink实现

简介&#xff1a;这是一套面向通信与信号处理方向学习者的 MATLAB/Simulink 仿真资源&#xff0c;重点围绕 MSK、GMSK、QPSK、BPSK 四种调制方式下的 Costas 环载波同步问题&#xff0c;提供可直接运行的仿真模型&#xff0c;适合本科、硕士阶段的课程作业、科研入门以及教师备…

作者头像 李华
网站建设 2026/9/12 14:25:00

blind_watermark 盲水印视觉定制:3 个参数调出你的专属水印输出

blind_watermark 盲水印视觉定制&#xff1a;3 个参数调出你的专属水印输出 【免费下载链接】blind_watermark Blind&Invisible Watermark &#xff0c;图片盲水印&#xff0c;提取水印无须原图&#xff01; 项目地址: https://gitcode.com/GitHub_Trending/bl/blind_wat…

作者头像 李华
网站建设 2026/9/12 14:23:42

论文讨论部分怎么搭?按论证层次拆解

讨论不是结果之后的自由发挥&#xff0c;而是一句一句垫出来的。每一句判断底下垫的是哪一类凭据&#xff0c;这类凭据允许你说到哪一步&#xff0c;直接决定了讨论能不能立得住。 讨论要写到多深、跟前人怎么对比、跟结果怎么分工、按什么顺序写&#xff0c;各有专篇&#xff…

作者头像 李华