在 Backstage 中查看我拥有的实体(Viewing What You Own):归属关系与过滤机制完全指南
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
Backstage 的软件目录(Software Catalog)是团队管理组件、API、网站、系统等软件实体的统一入口,而"我拥有什么"则是每位开发者登录后最常关心的视图。本文基于官方入门文档 docs/getting-started/view-what-you-own.md,讲解如何通过目录页面查看当前用户直接拥有或通过所在分组间接拥有的实体,并结合仓库源码深入剖析ownedBy归属关系在前端过滤与后端查询中的实现原理。读完本文,你将掌握在 Backstage 门户中定位"我的资产"的标准操作流程,并能从代码层面理解归属过滤的完整调用链。
前置概念:什么是"拥有实体"
在 Backstage 的实体模型(Entity Model)中,一个实体(如Component、API、System)可以通过关系(Relations)指向它的所有者。所有者通常是用户(User)或分组(Group),这一关系在 catalog-model 中被定义为RELATION_OWNED_BY(即ownedBy反向关系,对应的正向关系为ownerOf)。
因此,你可以通过两种途径"拥有"实体:
- 直接拥有:实体的
spec.owner指向你本人(当前登录用户); - 间接拥有:实体的
spec.owner指向你所在的分组(Group),而你是该分组的成员。
这两种归属在目录页面中被分别称为Direct Relations(直接关系)与Aggregated Relations(聚合关系),下文会详细说明。
操作步骤:三步查看你所拥有的实体
根据官方入门文档,查看你拥有实体只需三步:
- 进入主页:在左侧边栏(Sidebar)中选择
Home,进入软件目录的默认主页; - 选择类型:在
Kind下拉列表中选择User; - 选择用户:在
All Users列表中选择你的用户名。
页面随即展示你拥有(直接或通过所在分组间接拥有)的实体列表。你可以在两个视图之间切换:
Direct Relations:直接归你所有的实体;Aggregated Relations:通过你所在分组拥有的实体。
官方文档为此提供了截图(见下),展示的是以guest用户登录时目录页面呈现的归属实体结果:
说明:上述
All Users下拉与Kind筛选属于目录页左侧过滤面板的一部分。它们与"我拥有的实体"(owned)筛选共同构成了目录页的完整过滤体验,具体实现见下文源码分析。
源码级剖析:归属关系如何被计算与过滤
要理解上述交互背后的原理,需要从前端 Hook、筛选器、后端查询三个层面来看。
1. 身份与归属引用:ownershipEntityRefs
归属判断的第一步是拿到当前登录用户的"所有权引用列表"。在 plugins/catalog-react/src/hooks/useEntityOwnership.ts 中,useEntityOwnership通过identityApi.getBackstageIdentity()获取ownershipEntityRefs(一个实体引用字符串数组,包含用户自身的引用以及其所属分组的引用),然后将其与实体上的ownedBy关系逐一比对:
const { ownershipEntityRefs } = await identityApi.getBackstageIdentity(); ... const isOwnedEntity = useMemo(() => { const myOwnerRefs = new Set(refs ?? []); return (entity: Entity) => { const entityOwnerRefs = getEntityRelations(entity, RELATION_OWNED_BY).map( stringifyEntityRef, ); for (const ref of entityOwnerRefs) { if (myOwnerRefs.has(ref)) { return true; } } return false; }; }, [refs]);从源码可以看到,isOwnedEntity采用集合交集的方式判断:只要实体的任意一个ownedBy引用落在当前用户的ownershipEntityRefs集合中,即判定为"我拥有"。由于ownershipEntityRefs天然包含"用户本身 + 用户所属分组",因此该逻辑同时覆盖了直接拥有与聚合拥有两种情形。值得注意的是,该 Hook 仅在挂载时加载一次(useAsync依赖为空数组[]),加载期间isOwnedEntity恒返回false,这是它文档注释中特别声明的行为。
2. 前端筛选器:EntityUserFilter
目录页左侧的过滤面板由 UserListPicker 组件实现。它渲染两组过滤项:
- 个人过滤(Personal Filters):
owned(我拥有的)与starred(我收藏的); - 组织过滤(Organization):
all(全部实体),分组名取自配置organization.name(默认回退为Backstage)。
每个过滤项右侧还会显示对应的实体计数,例如"我拥有的"右侧的数字即当前登录用户拥有的实体数量。当某个过滤项计数为 0 时,该菜单项会被禁用(disabled={filterCounts[item.id] === 0});当用户选择"owned"且计数为 0 时,组件会自动回退到all过滤(见 UserListPicker.tsx)。
选择owned后,组件通过updateFilters({ user: EntityUserFilter.owned(ownershipEntityRefs) })应用筛选。EntityUserFilter定义于 plugins/catalog-react/src/filters.ts,它把当前用户的归属引用集合包装成目录查询可识别的过滤条件。在计数实现 useOwnedEntitiesCount.ts 中可以看到,最终它会调用catalogApi.queryEntities并携带'relations.ownedBy': ownedClaims这样的过滤键——这正是把"归属"语义映射为目录 API 查询参数的落点:
const { totalItems } = await catalogApi.queryEntities({ ...req.filter, filter: { ...filter, 'relations.ownedBy': ownedClaims, }, limit: 0, }); return totalItems;若当前用户与所选的owners过滤没有公共引用(getOwnedCountClaims返回undefined),代码会直接返回计数 0 而跳过网络请求,这是一种针对空结果的短路优化(见 useOwnedEntitiesCount.ts)。
3. 页面默认行为:DefaultCatalogPage与initiallySelectedFilter
官方入门文档描述的操作是在目录主页完成的,该页面对应插件包中的DefaultCatalogPage(plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx)。它的initiallySelectedFilter属性默认值为'owned',意味着目录页在首次打开时默认就选中"我拥有的"过滤视图,这与文档中"打开 Home 即可查看归属实体"的体验一致:
export function DefaultCatalogPage(props: DefaultCatalogPageProps) { const { ... initiallySelectedFilter = 'owned', initialKind = 'component', ... } = props;开发者也可以通过向DefaultCatalogPage传入initiallySelectedFilter(可选值包括owned、starred、all)或filters属性来定制页面的初始过滤状态与过滤面板。
Direct Relations 与 Aggregated Relations:两种视图的含义
当你按文档步骤打开"我拥有的实体"页面时,可以切换两种视图:
| 视图 | 含义 | 判定依据 |
|---|---|---|
Direct Relations | 实体直接归你所有 | 实体的ownedBy关系直接指向你的用户实体 |
Aggregated Relations | 通过你所在分组拥有的实体 | 实体的ownedBy关系指向你的某个所属分组 |
这两种视图本质上是ownershipEntityRefs集合中"用户自身引用"与"所属分组引用"的两种呈现方式。无论切换哪种视图,底层使用的都是同一套ownedBy关系查询(见上文EntityUserFilter与queryEntities的'relations.ownedBy'过滤参数),区别仅在于结果集展示维度与聚合粒度的不同。
如何让实体归属到你:spec.owner配置
要让某类实体出现在"我拥有的实体"视图中,需要在实体的catalog-info.yaml描述文件中正确声明所有者。以最常见的Component为例:
apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: my-service description: An example service spec: type: service owner: group:team-a- 若
spec.owner写为user:alice,则只有用户alice能通过"我拥有的"视图看到它(Direct Relations); - 若
spec.owner写为group:team-a(推荐做法),则team-a的所有成员都能通过聚合视图看到它(Aggregated Relations)。
spec.owner使用实体引用(Entity Reference)语法,通常写作<kind>:<namespace>/<name>的形式,其中namespace缺省时为default。关于实体引用格式的细节,可参考仓库中的架构决策文档 docs/architecture-decisions/adr009-entity-references.md。
常见问题与排查思路
- 为什么我拥有的视图是空的?先确认当前登录用户身份是否正确;再检查目标实体
catalog-info.yaml中spec.owner是否指向你本人或你所属的分组,且用户/分组实体确实存在于目录中。 - 为什么"我拥有的"计数为 0 且被禁用?当
useOwnedEntitiesCount计算得到 0 时,UserListPicker会禁用该菜单项并自动回退到all,这是预期行为(见 UserListPicker.tsx)。 - 如何让目录页默认打开"我拥有的"视图?默认即为
'owned';如需修改,可向DefaultCatalogPage传入initiallySelectedFilter="starred"或"all"。
延伸阅读
- 入门指南:在目录中查看实体的一般流程见 docs/getting-started/viewing-catalog.md,筛选目录见 docs/getting-started/filter-catalog.md;
- 实体模型与关系:软件目录的核心实体与关系定义见 docs/features/software-catalog;
- 源码参考:归属判断 Hook 见 plugins/catalog-react/src/hooks/useEntityOwnership.ts,计数逻辑见 plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.ts,过滤面板见 plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx。
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考