Confluence曾经是团队知识库的事实标准,但近年来越来越多的团队开始寻找替代方案。授权费用逐年上涨、Atlassian强制迁移云版本、页面加载越来越慢、搜索体验差,这些问题叠加在一起,让不少技术负责人把目光投向了开源的Outline。Outline是一款基于React和Node.js构建的团队wiki,界面现代、支持Markdown语法、自带实时协作和全文搜索,并且提供Docker一键部署,社区版可以免费商用。本文将从架构差异、迁移策略和具体脚本实现三个层面,完整梳理从Confluence迁移到Outline的可行路径。

一、先搞清楚两个平台的核心差异
迁移前必须理解两者的架构差异,否则会在很多细节上踩坑。Confluence的存储格式本质上是XML化的存储文档格式(Storage Format),页面在数据库中以类似XHTML的结构保存,导出时可以选择HTML或XML。而Outline采用的是结构化的JSON文档模型,底层基于Prosemirror,页面内容以Delta或JSON树的形式存储在PostgreSQL中。这意味着两者之间没有直接的格式转换通道,必须借助中间格式过渡,通常的做法是把Confluence页面导出为HTML,再通过脚本解析HTML并转换为Outline能够理解的Markdown格式。
编辑器层面的差异同样明显。Confluence使用富文本编辑器,大量内容依赖宏(Macro),比如页面树宏、任务列表宏、Jira宏等。这些宏在Outline中大多没有对应物,任务列表可以转成Markdown的复选框语法,页面树宏则只能用文档链接手动替代,Jira集成需要依赖Outline的第三方集成能力。另外,Confluence的空间(Space)对应Outline的Collection(集合),空间的权限体系比Outline的集合权限更细粒度,迁移时需要做权限降级或重新设计。
还有一个容易被忽视的点:附件处理。Confluence的附件存储在服务器文件系统或S3中,导出HTML时会得到一个带_attachments目录的压缩包。Outline的附件上传有单文件大小限制(默认取决于存储后端配置),如果团队里有大量视频或大型设计稿,需要提前评估存储方案,比如接入S3兼容的对象存储。
二、迁移前的准备与内容映射策略
工欲善其事,必先利其器。第一步是在Confluence中做一次内容盘点,找出过期页面。根据经验,使用超过三年的Confluence实例中,至少有三成页面在过去一年没有被访问过,这些页面直接归档即可,不必迁移。盘点可以通过Confluence的REST API完成,按空间遍历页面并查询最后修改时间,把结果导出成CSV再做人工筛选。
第二步是建立空间到集合的映射关系。建议在表格中记录对照关系,方便后续脚本读取:
| Confluence空间 | 空间Key | Outline集合 | 权限策略 |
|---|---|---|---|
| 研发文档 | DEV | 研发文档 | 私有集合 |
| 产品需求 | PRD | 产品需求 | 团队可见 |
| 新员工手册 | HR | 入职指南 | 全员可见 |
第三步是确定页面层级的处理方式。Confluence支持多层嵌套的页面树,而Outline目前支持两层结构:集合下的文档和文档的子文档。如果Confluence中存在五六层的深层嵌套,需要决定是拍平层级还是把深层页面转为子文档。一般建议顶层页面映射为文档,第二层映射为子文档,第三层及以下合并到父文档中作为章节,这样既保留了信息结构,又不会破坏Outline的导航体验。
内部链接的重写是迁移中最繁琐的部分。Confluence的页面链接格式类似/pages/12345或带页面标题的相对链接,迁移到Outline后地址全部失效。解决办法是在迁移脚本中维护一个「旧页面ID到新文档ID」的映射表,在转换内容时扫描所有链接,查表替换。对于找不到映射的链接,统一替换为指向Confluence只读存档的地址,确保信息不断链。
三、迁移脚本的实现思路与代码
整体流程分为四个阶段:解析Confluence导出包、转换HTML为Markdown、上传附件、调用Outline API创建文档。Outline提供了完善的REST API,创建文档的接口支持指定集合、父文档、标题和内容,认证方式是在个人设置中生成的API Token。下面是一段Node.js实现的核心代码:
const fs = require('fs');
const path = require('path');
const TurndownService = require('turndown');
const turndown = new TurndownService({ headingStyle: 'atx' });
// Confluence页面ID到Outline文档ID的映射表
const idMapping = {};
// 第一步:解析导出的HTML,转换为Markdown
function convertPage(htmlFilePath) {
const html = fs.readFileSync(htmlFilePath, 'utf-8');
// 提取主体内容区域,Confluence导出页面有固定结构
const match = html.match(/<div id="main-content"([\s\S]*?)<\/div>/);
const body = match ? match[1] : html;
let markdown = turndown.turndown(body);
// 重写内部链接:把 pages/12345 替换为Outline文档链接
markdown = markdown.replace(/href="[^"]*pages\/(\d+)"/g, (m, pageId) => {
if (idMapping[pageId]) {
return `href="/doc/${idMapping[pageId]}"`;
}
return m; // 未找到映射则保留原链接
});
return markdown;
}
// 第二步:调用Outline API创建文档
async function createDoc(title, markdown, collectionId, parentDocId) {
const res = await fetch('https://wiki.example.yourcompany.com/api/documents.create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + process.env.OUTLINE_TOKEN
},
body: JSON.stringify({
title,
text: markdown,
collectionId,
parentDocumentId: parentDocId,
publish: true
})
});
const data = await res.json();
return data.data.id;
}
// 第三步:批量迁移,先建父文档再建子文档,保证层级正确
async function migrate(pages) {
for (const page of pages) {
const md = convertPage(page.htmlPath);
const docId = await createDoc(page.title, md, page.collectionId, page.parentDocId);
idMapping[page.confluenceId] = docId;
fs.writeFileSync('mapping.json', JSON.stringify(idMapping, null, 2));
}
}
这段代码有几个值得注意的细节。首先是链接重写放在Markdown转换之后,因为Turndown转换后链接格式统一,正则匹配更可靠。其次是映射表每次更新后立即写盘,这样即使脚本中断,重启后已迁移的页面不会重复创建。最后是publish参数,设为true可以跳过草稿状态,团队文档通常迁移完就要立即可见。
附件上传需要调用Outline的attachments.create接口,用multipart表单提交文件。Turndown默认会把图片标签转成Markdown图片语法,但地址还是本地相对路径,需要在转换前用正则把src替换成上传后返回的URL。对于Confluence特有的宏,建议在Turndown中注册自定义规则,比如把任务列表宏转换为- [ ]语法:
turndown.addRule('confluenceTask', {
filter: (node) => node.nodeName === 'DIV' && node.className.includes('task'),
replacement: (content, node) => {
const done = node.className.includes('completed');
return `- [${done ? 'x' : ' '}] ${content.trim()}\n`;
}
});
四、迁移收尾与团队落地建议
脚本跑完不等于迁移结束。第一件事是抽样检查,随机抽取百分之十的页面,重点核对表格、代码块、嵌套列表和图片这几类最容易转换出错的内容。表格是重灾区,Confluence的复杂表格(合并单元格、嵌套表格)转成Markdown后会丢失结构,这类页面建议人工重排,或者先转换为HTML块保留在Outline中,Outline的Markdown解析器支持内嵌HTML。
第二件事是权限与成员的收尾。把Confluence的只读存档地址告诉全员,设定一个过渡期(建议两到四周),期间旧库只读不写,所有新内容一律进Outline。过渡期结束后关闭Confluence的编辑入口,保留一个季度左右的只读访问作为兜底。成员账号方面,如果Outline配置了Slack或Google登录,成员首次登录即可自动加入,无需手动开通。
最后给团队一点使用上的建议:Outline的搜索体验明显优于Confluence,鼓励大家用全文搜索代替目录树浏览;文档内的反向链接和收藏功能适合构建知识网络;定期清理过期文档的习惯要延续到新平台,否则再好的工具也会被内容淹没。整个迁移过程如果准备充分,一个中型团队(几千个页面)通常一到两周可以完成主体迁移,剩下的时间留给人工校对和习惯养成,成本远低于继续支付Confluence的年度授权费用。
Confluence迁移Outline部署开源wiki修改时间:2026-09-07 16:44:51