首页
/ 在 Backstage 中集成 Confluence 搜索:Collator 与结果项组件的完整接入指南

在 Backstage 中集成 Confluence 搜索:Collator 与结果项组件的完整接入指南

2026-09-09 15:33:25作者:宣利权Counsellor

[!NOTE] 本文内容基于仓库 contrib/search/confluence 目录下的原始实现整理。该目录文档已标注为弃用(deprecated),官方推荐改用社区维护的 @backstage-community/plugin-search-backend-module-confluence-collator 插件。本文仅用于帮助读者理解 Backstage 搜索插件接入第三方文档源(Collator)的完整技术原理与历史实现路径,若用于生产环境请优先评估社区插件方案。

导读

Backstage 搜索插件通过「Collator(文档收集器)」从各类数据源抓取文档并构建全文索引。本篇文章聚焦仓库 contrib/search/confluence 提供的 Confluence 搜索集成方案,完整讲解如何把 Atlassian Confluence 空间与页面作为搜索源接入 Backstage:包括后端 ConfluenceCollator 的 REST API 调用逻辑、前端 ConfluenceResultListItem 结果项组件,以及前后端注册代码的落地位置。读完本文,你将掌握 Backstage 搜索插件中「数据收集器 + 结果展示组件」双端协作的标准接入范式,并能据此扩展出任意第三方文档源的搜索集成。

集成概述:把 Confluence 变成 Backstage 的搜索源

Backstage 的搜索插件采用「索引构建器(IndexBuilder)+ 调度器(Scheduler)+ 搜索引擎」的架构:Collator 负责从数据源收集文档并归一化为可索引结构,Decorator 负责对文档做加工,搜索引擎负责存储与检索。本目录提供的两个文件正是这条链路中的两端:

文件 角色 部署位置
ConfluenceCollatorConfluenceCollator.md 中的参考实现) 后端文档收集器,调用 Confluence REST API 拉取空间与页面 packages/backend/src/plugins/search/
ConfluenceResultListItemConfluenceResultListItem.md 中的参考实现) 前端搜索结果项组件,渲染标题、摘要并跳转原页面 packages/app/src/components/search/

接入流程分为三步:先把两个文件复制到 Backstage 应用的对应目录,再在前端 SearchPage.tsx 中为 confluence 类型注册渲染组件,最后在后端 search.ts 中把 ConfluenceCollator 注册进 indexBuilder

弃用说明与社区替代方案

目录 README.md 开头的 [!NOTE] 明确指出:本文档已弃用,将在未来移除,官方建议使用社区插件 @backstage-community/plugin-search-backend-module-confluence-collator。这意味着本文讲解的 ConfluenceCollator.tsConfluenceResultListItem.tsx 属于早期的参考实现,其主要价值在于揭示搜索插件扩展机制的原理。生产环境部署时,应优先查看社区插件仓库的安装与配置文档。

后端接入:复制 Collator 文件到搜索插件目录

按照 README.md 的指引,首先把本目录下的两个文件复制到 Backstage 应用的 packages/backend/src/plugins/search/ 路径下。该路径是旧版(非新后端系统)Backstage 应用后端插件默认的代码组织位置,后端搜索插件的路由与 Collator 注册通常都集中在这里:

packages/backend/src/plugins/search/
├── ConfluenceCollator.ts        # 新增:Confluence 文档收集器
└── ConfluenceResultListItem.tsx # 注意:该组件实际应置于前端(见下文)

需要说明的是:ConfluenceResultListItem 是 React 组件,实际使用位置是前端 packages/app/src/components/search/(README 中的代码示例也印证了这一点)。原 README 要求"将本目录两个文件都放到后端 search 路径下"的表述更像一份宽松的放置说明,实操时建议把 Collator 放后端、结果组件放前端,保持前后端职责清晰。

前端接入:在 SearchPage 中渲染 Confluence 结果

在 Backstage 应用前端的 SearchPage.tsx 中,搜索页通过 useSearch 拿到查询结果后,会根据文档的 type 字段选择对应的结果项组件渲染。接入 Confluence 需要在两处改动:

1. 引入结果项组件

import { ConfluenceResultListItem } from './ConfluenceResultListItem';

2. 在类型分发中注册 confluence 分支

搜索结果组件通常维护一个从 document.type 到渲染组件的映射,在 SearchPage.tsx 中补上 confluence 分支即可:

case 'confluence':
  return (
    <ConfluenceResultListItem
      key={document.location}
      result={document}
    />
  );

这里的关键关联点是 document.type:Collator 通过 public readonly type: string = 'confluence' 声明了自己的文档类型(详见后文),前端正是靠这个 type 值把搜索结果路由到 ConfluenceResultListItemresult 属性接收的是 IndexableDocument,即搜索插件定义的统一文档结构。

IndexableDocument 的字段契约

ConfluenceResultListItem 的 Props 类型定义如下:

type Props = {
  result: IndexableDocument;
};

它依赖 resulttexttitlelocation 三个字段:

  • title:文档标题,显示为结果项的主标题;
  • text:文档正文,用于生成摘要(该组件会先截取前 500 字符做 HTML 标签剥离,再取前 80 字符展示);
  • location:文档的跳转链接,指向 Confluence 页面 web UI。

这恰好对应后端 Collator 返回的文档结构 { title, text, location },前后端通过 IndexableDocument 这一契约完成对接。IndexableDocument 接口定义在 plugins/search-common/src/types.ts,是搜索插件所有 Collator 输出与搜索结果的通用数据形状。

结果项组件解析:剥离 HTML 与展示摘要

ConfluenceResultListItem.md 提供了 ConfluenceResultListItem.tsx 的完整参考实现,它展示了三个值得关注的处理点:

1. 手工剥离 HTML 标签

Confluence REST API 返回的 body.storage.value 是富文本 HTML。组件在渲染摘要前,用一个简单的字符级状态机把 <...> 标签剥离掉,只保留纯文本:

const chars = [];
let isTag = false;
for (const c of result.text.substring(0, 500)) {
  if (c === '<') {
    isTag = true;
    continue;
  }
  if (c === '>') {
    isTag = false;
    chars.push(' ');
    continue;
  }
  if (!isTag) {
    chars.push(c);
  }
}
const excerpt =
  chars.join('').substring(0, 80) + (result.text.length > 80 ? '...' : '');

处理策略总结:先截取原文前 500 字符(控制计算量),剔除标签后取前 80 字符,若原文超过 80 字符则追加 ...。这是早期参考实现的做法,实际生产中可以替换为更健壮的 DOM 解析方案。

2. 结果项布局

<Link to={result.location}>
  <ListItem alignItems="center">
    <ListItemIcon>
      <img
        width="20"
        height="20"
        src="https://cdn.worldvectorlogo.com/logos/confluence-1.svg"
      />
    </ListItemIcon>
    <ListItemText
      primaryTypographyProps={{ variant: 'h6' }}
      primary={result.title}
      secondary={excerpt}
    />
  </ListItem>
  <Divider />
</Link>
  • Link 来自 @backstage/core-componentsto 指向 result.location,点击即可跳转到原始 Confluence 页面;
  • ListItem / ListItemIcon / ListItemText / Divider 来自 Material-UI(当前仓库该历史实现使用的版本为 @material-ui/core),用于组织列表布局;
  • 图标使用 Confluence 的 SVG Logo,作为结果项的视觉标识。

3. 组件导出方式

该组件以命名导出export const ConfluenceResultListItem = ...)方式暴露,这符合 Backstage 仓库自身的编码约定——ADRs 中明确提倡避免默认导出(参见 adr003-avoid-default-exports.md),因此在 SearchPage.tsx 中需要用 import { ConfluenceResultListItem } from './ConfluenceResultListItem' 的形式引入。

后端核心:ConfluenceCollator 的 REST API 调用链

ConfluenceCollator.md 给出了 ConfluenceCollator.ts 的完整参考实现,它实现了 @backstage/plugin-search-commonDocumentCollator 接口。整个收集过程分为三个阶段,对应三次对 Confluence REST API 的调用:

阶段一:获取全部全局空间(getSpaces)

async function getSpaces(): Promise<string[]> {
  const data = await getConfluenceData(
    `${ConfluenceUrlBase}/space?&limit=1000&type=global&status=current`,
  );
  // 从 data['results'] 中逐个取出 result['key'] 作为空间 key
}

调用 Confluence REST API 的 /wiki/rest/api/space 端点,带 limit=1000type=globalstatus=current 参数,拉取所有处于 current 状态的全局空间,仅保留每个空间的 key 字段。

阶段二:按空间分页拉取页面(getDocumentsFromSpaces)

let requestUrl = `${ConfluenceUrlBase}/content?limit=1000&status=current&spaceKey=${space}`;
while (next) {
  const data = await getConfluenceData(requestUrl);
  // 收集 data['results'][]._links.self(页面详情 URL)
  if (data['_links']['next']) {
    requestUrl = data['_links']['base'] + data['_links']['next'];
  } else {
    next = false;
  }
}

对每个空间调用 /wiki/rest/api/content 端点,按 spaceKey 过滤、limit=1000 分页,从 results 中收集每个页面的 _links.self(即页面详情资源的 URL)。分页通过 _links.next_links.base 拼接下一页 URL,直到没有下一页为止。

阶段三:逐页拉取正文并归一化(getDocumentInfo)

const data = await getConfluenceData(documentUrl + '?expand=body.storage');
if (data['status'] && data['status'] == 'current') {
  const documentMetaData = {
    title: data['title'],
    text: data['body']['storage']['value'],
    location: data['_links']['base'] + data['_links']['webui'],
  };
  documentInfo.push(documentMetaData);
}

对每个页面 URL 追加 ?expand=body.storage 参数,一次性取回标题、HTML 正文与页面 Web 地址,组装成 { title, text, location } 结构——这正是前端 ConfluenceResultListItem 消费的数据形态。status === 'current' 的判断确保已归档或已删除的页面不会被收录。

统一出口:execute() 与 type 声明

export class ConfluenceCollator implements DocumentCollator {
  public readonly type: string = 'confluence';

  async execute() {
    const spacesList = await getSpaces();
    const documentsList = await getDocumentsFromSpaces(spacesList);
    const documentMetaDataList = await getDocumentInfo(documentsList);
    return documentMetaDataList;
  }
}
  • type = 'confluence' 是文档类型标识,后端 IndexBuilder 会以它为索引名/类型名(见下文源码),前端也用它做结果项路由;
  • execute() 串联三个阶段,返回文档元数据数组。

鉴权方式:Basic Auth 与 CONFLUENCE_TOKEN 环境变量

所有 API 请求都通过统一封装的 getConfluenceData 发起:

const res = await fetch(requestUrl, {
  method: 'get',
  headers: {
    Authorization: `Basic ${process.env.CONFLUENCE_TOKEN}`,
  },
});
  • 使用 HTTP Basic 鉴权,process.env.CONFLUENCE_TOKEN 存放 base64 编码后的 用户名:API Token 凭据,需要提前在 Backstage 后端进程的环境变量中配置;
  • 网络请求使用 cross-fetch(历史实现;当前仓库的核心依赖已迁移到 Node 原生 fetch,可参见 ADR adr014-use-fetch.md);
  • 失败兜底:请求失败或响应非 ok 时返回空对象 {},调用方对 results 做空值判断后安全退出,保证单个空间或页面失败不会拖垮整个收集任务。

后端注册:把 Collator 挂进 indexBuilder

packages/backend/src/plugins/search.ts 中完成两步注册:

1. 引入 ConfluenceCollator

import { ConfluenceCollator } from './search/ConfluenceCollator';

2. 注册到索引构建器

indexBuilder.addCollator({
  defaultRefreshIntervalSeconds: 600,
  collator: new ConfluenceCollator(),
});

defaultRefreshIntervalSeconds: 600 表示每 600 秒(10 分钟)重新执行一次收集任务,让新发布的 Confluence 页面能被周期性收录进索引。注册完成后,indexBuilder.build() 会把所有已注册 Collator 编译成调度任务交给 Scheduler 周期性执行。

源码印证:addCollator 的调度与类型注册机制

通过阅读当前仓库源码,可以确认 indexBuilder.addCollator 的底层行为。IndexBuilder 类定义在 plugins/search-backend-node/src/IndexBuilder.ts,其 addCollator 实现如下:

addCollator(options: RegisterCollatorParameters): void {
  const { factory, schedule } = options;

  this.logger.info(
    `Added ${factory.constructor.name} collator factory for type ${factory.type}`,
  );
  this.collators[factory.type] = {
    factory,
    schedule,
  };
  this.documentTypes[factory.type] = {
    visibilityPermission: factory.visibilityPermission,
  };
}
  • factory.type 为键存储 collator,并同步登记文档类型信息 documentTypes
  • RegisterCollatorParameters 接口(见 plugins/search-backend-node/src/types.ts)要求提供 schedule(调度器任务运行器)与 factory(返回文档收集器的工厂)。README 示例中 defaultRefreshIntervalSeconds 这种简写形式属于旧版 IndexBuilder.addCollator 的便捷参数,最终都会被编译为对应的调度任务;
  • 注册完成后,build() 会将 Collator 包装进 Scheduler(见 plugins/search-backend-node/src/Scheduler.ts),由调度器在后台按间隔周期触发 execute(),把结果送入搜索引擎索引。

DocumentCollator 接口本身定义在 plugins/search-common/src/types.ts,对每个 Collator 的要求正是 type(文档类型/索引名)与 getCollator()(旧接口为 execute())两要素,ConfluenceCollator 的实现完全符合这一契约。

常见问题与排查要点

  1. 搜索不到 Confluence 内容:检查后端进程是否正确设置了 CONFLUENCE_TOKEN 环境变量;确认 indexBuilder.addCollator 的注册代码已生效,且 build() 被调用;首次索引建立后需等待一个刷新周期或触发一次收集。
  2. 部分页面缺失:确认页面状态为 current(API 会过滤非 current 页面);检查空间是否属于 global 类型且为 current 状态;分页是否被 _links.next 完整遍历。
  3. 前端点开结果无跳转:确认 ConfluenceResultListItem 收到的 result.location 是完整的 Confluence Web 地址(由 _links.base + _links.webui 拼接),且 SearchPage.tsxcase 'confluence' 分支已正确注册。
  4. 类型不匹配:前端 case 分支的字符串必须与 Collator 的 type 属性('confluence')完全一致,否则搜索结果无法路由到对应组件。
  5. 生产环境选型:本实现已弃用,若要在生产环境接入 Confluence 搜索,请改用官方推荐的社区插件 @backstage-community/plugin-search-backend-module-confluence-collator,其功能与配置方式以该插件仓库文档为准。

小结

contrib/search/confluence 目录完整演示了 Backstage 搜索插件接入第三方文档源的端到端路径:后端用 ConfluenceCollator 通过 REST API 拉取空间、分页抓取页面、归一化为 { title, text, location } 文档并周期性送入索引;前端用 ConfluenceResultListItem 按类型路由渲染搜索结果并跳转原文。虽然这份参考实现已被社区插件取代,但其揭示的「Collator 类型契约 + IndexBuilder 注册 + 前端类型分发渲染」的扩展模式,适用于任何文档源的搜索集成,是理解 Backstage 搜索插件架构的一份高质量样例。

相关仓库资源

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395