name: confluence-docs description: Atlassian Confluence 企业文档集成。通过 API 创建和更新页面,管理空间和权限,处理内容迁移,并在 Markdown 和 Confluence 之间同步。 allowed-tools: Read, Write, Edit, Bash, Glob, Grep backlog-id: SK-013 metadata: author: babysitter-sdk version: “1.0.0”
Confluence 集成技能
Atlassian Confluence 企业文档集成。
能力
- 通过 API 创建和更新页面
- 空间管理和权限控制
- 宏和模板管理
- 内容迁移(Markdown 到 Confluence)
- 附件处理
- 标签和元数据管理
- 支持 Confluence Cloud 和 Server
- Confluence 到 Markdown 导出
用法
当您需要时调用此技能:
- 将文档同步到 Confluence
- 在不同格式之间迁移内容
- 以编程方式管理 Confluence 空间
- 从 CI/CD 自动化页面更新
- 将 Confluence 导出为 Markdown
输入
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| action | string | 是 | create, update, migrate, export |
| baseUrl | string | 是 | Confluence 实例 URL |
| spaceKey | string | 是 | 目标空间键 |
| sourcePath | string | 否 | 源 Markdown 文件路径 |
| pageId | string | 否 | 用于更新的特定页面 ID |
| parentPageId | string | 否 | 用于层次结构的父页面 ID |
输入示例
{
"action": "migrate",
"baseUrl": "https://company.atlassian.net/wiki",
"spaceKey": "DOCS",
"sourcePath": "./docs",
"parentPageId": "123456"
}
配置
confluence.config.json
{
"baseUrl": "https://company.atlassian.net/wiki",
"auth": {
"type": "token",
"email": "${CONFLUENCE_EMAIL}",
"token": "${CONFLUENCE_TOKEN}"
},
"space": {
"key": "DOCS",
"name": "Documentation"
},
"migration": {
"preserveStructure": true,
"convertTables": true,
"uploadImages": true,
"macroMapping": {
"note": "info",
"warning": "warning",
"code": "code"
}
},
"sync": {
"dryRun": false,
"updateExisting": true,
"createMissing": true,
"archiveRemoved": false
}
}
API 集成
Confluence REST API 客户端
const ConfluenceClient = require('confluence-api');
class ConfluenceManager {
constructor(config) {
this.client = new ConfluenceClient({
username: config.email,
password: config.token,
baseUrl: config.baseUrl
});
}
// 创建新页面
async createPage(spaceKey, title, content, parentId = null) {
const page = {
type: 'page',
title,
space: { key: spaceKey },
body: {
storage: {
value: content,
representation: 'storage'
}
}
};
if (parentId) {
page.ancestors = [{ id: parentId }];
}
return await this.client.postContent(page);
}
// 更新现有页面
async updatePage(pageId, title, content, version) {
const page = {
id: pageId,
type: 'page',
title,
version: { number: version + 1 },
body: {
storage: {
value: content,
representation: 'storage'
}
}
};
return await this.client.putContent(page);
}
// 按标题获取页面
async getPageByTitle(spaceKey, title) {
const result = await this.client.getContentBySpaceKey(spaceKey, {
title,
expand: 'version,body.storage'
});
return result.results[0] || null;
}
// 上传附件
async uploadAttachment(pageId, filePath, comment = '') {
const form = new FormData();
form.append('file', fs.createReadStream(filePath));
form.append('comment', comment);
return await this.client.createAttachment(pageId, form);
}
// 添加标签
async addLabels(pageId, labels) {
const labelPayload = labels.map(name => ({
prefix: 'global',
name
}));
return await this.client.postLabels(pageId, labelPayload);
}
}
Markdown 到 Confluence 转换
转换器
const marked = require('marked');
class MarkdownToConfluence {
constructor(options = {}) {
this.options = options;
this.attachments = [];
}
convert(markdown, metadata = {}) {
// 解析 front matter
const { content, frontMatter } = this.parseFrontMatter(markdown);
// 将 markdown 转换为 HTML
let html = marked.parse(content);
// 转换为 Confluence 存储格式
html = this.convertToStorageFormat(html);
// 处理宏
html = this.convertMacros(html);
// 处理代码块
html = this.convertCodeBlocks(html);
// 处理图片
html = this.convertImages(html);
// 处理表格
html = this.convertTables(html);
return {
title: frontMatter.title || metadata.title,
content: html,
labels: frontMatter.tags || [],
attachments: this.attachments
};
}
convertMacros(html) {
// 将 admonitions 转换为 Confluence 宏
const macroMap = {
'note': 'info',
'warning': 'warning',
'tip': 'tip',
'danger': 'warning'
};
for (const [mdType, confType] of Object.entries(macroMap)) {
const regex = new RegExp(`<div class="${mdType}">([\\s\\S]*?)</div>`, 'g');
html = html.replace(regex, (match, content) => {
return `<ac:structured-macro ac:name="${confType}">
<ac:rich-text-body>${content}</ac:rich-text-body>
</ac:structured-macro>`;
});
}
return html;
}
convertCodeBlocks(html) {
// 将代码块转换为 Confluence 代码宏
return html.replace(
/<pre><code class="language-(\w+)">([\s\S]*?)<\/code><\/pre>/g,
(match, language, code) => {
const decodedCode = this.decodeHtml(code);
return `<ac:structured-macro ac:name="code">
<ac:parameter ac:name="language">${language}</ac:parameter>
<ac:plain-text-body><![CDATA[${decodedCode}]]></ac:plain-text-body>
</ac:structured-macro>`;
}
);
}
convertImages(html) {
// 将图片转换为 Confluence 附件
return html.replace(
/<img src="([^"]+)" alt="([^"]*)"[^>]*>/g,
(match, src, alt) => {
if (src.startsWith('http')) {
// 外部图片
return `<ac:image><ri:url ri:value="${src}" /></ac:image>`;
} else {
// 本地附件
const filename = path.basename(src);
this.attachments.push({ src, filename });
return `<ac:image><ri:attachment ri:filename="${filename}" /></ac:image>`;
}
}
);
}
convertTables(html) {
// Confluence 使用标准 HTML 表格但需要特定属性
return html.replace(/<table>/g, '<table class="wrapped">');
}
}
Confluence 到 Markdown 导出
导出器
class ConfluenceToMarkdown {
constructor(client) {
this.client = client;
}
async exportSpace(spaceKey, outputDir) {
const pages = await this.getAllPages(spaceKey);
const structure = this.buildHierarchy(pages);
for (const page of pages) {
const markdown = await this.exportPage(page);
const filePath = this.getFilePath(page, structure, outputDir);
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, markdown);
}
return { exported: pages.length };
}
async exportPage(page) {
const content = page.body.storage.value;
// 将 Confluence 存储格式转换为 Markdown
let markdown = this.convertToMarkdown(content);
// 添加 front matter
const frontMatter = {
title: page.title,
confluence_id: page.id,
last_modified: page.version.when
};
return `---
${yaml.stringify(frontMatter)}---
${markdown}`;
}
convertToMarkdown(storage) {
let md = storage;
// 转换代码宏
md = md.replace(
/<ac:structured-macro ac:name="code"[^>]*>[\s\S]*?<ac:parameter ac:name="language">(\w+)<\/ac:parameter>[\s\S]*?<ac:plain-text-body><!\[CDATA\[([\s\S]*?)\]\]><\/ac:plain-text-body>[\s\S]*?<\/ac:structured-macro>/g,
(match, lang, code) => `\`\`\`${lang}
${code}
\`\`\``
);
// 转换信息宏
md = md.replace(
/<ac:structured-macro ac:name="(info|warning|tip)"[^>]*>[\s\S]*?<ac:rich-text-body>([\s\S]*?)<\/ac:rich-text-body>[\s\S]*?<\/ac:structured-macro>/g,
(match, type, content) => `> **${type.toUpperCase()}:** ${this.stripHtml(content)}`
);
// 转换标题、列表等
md = this.convertHtmlToMarkdown(md);
return md;
}
}
同步工作流
双向同步
async function syncDocumentation(config) {
const confluence = new ConfluenceManager(config);
const converter = new MarkdownToConfluence(config.migration);
// 获取本地文件
const localFiles = await glob('docs/**/*.md');
// 获取 Confluence 页面
const pages = await confluence.getSpaceContent(config.space.key);
const results = {
created: [],
updated: [],
skipped: [],
errors: []
};
for (const file of localFiles) {
try {
const markdown = await fs.readFile(file, 'utf8');
const converted = converter.convert(markdown, { file });
// 检查页面是否存在
const existing = await confluence.getPageByTitle(
config.space.key,
converted.title
);
if (existing) {
if (config.sync.updateExisting) {
await confluence.updatePage(
existing.id,
converted.title,
converted.content,
existing.version.number
);
results.updated.push(file);
} else {
results.skipped.push(file);
}
} else if (config.sync.createMissing) {
await confluence.createPage(
config.space.key,
converted.title,
converted.content,
config.parentPageId
);
results.created.push(file);
}
// 上传附件
for (const attachment of converted.attachments) {
await confluence.uploadAttachment(
existing?.id || results.created[results.created.length - 1].id,
attachment.src
);
}
} catch (error) {
results.errors.push({ file, error: error.message });
}
}
return results;
}
空间管理
创建空间
async function createDocumentationSpace(config) {
const client = new ConfluenceManager(config);
const space = await client.client.postSpace({
key: config.space.key,
name: config.space.name,
description: {
plain: { value: config.space.description, representation: 'plain' }
},
permissions: [
{
subjects: { group: { name: 'confluence-users' } },
operation: { key: 'read', target: 'space' }
}
]
});
// 创建首页
await client.createPage(
config.space.key,
'Home',
'<h1>Welcome to Documentation</h1>',
null
);
return space;
}
工作流
- 配置 - 设置 Confluence 凭据和空间
- 转换 - 将 Markdown 转换为 Confluence 格式
- 同步 - 通过 API 上传/更新页面
- 附件 - 上传图片和文件
- 标签 - 应用标签以进行组织
- 验证 - 检查页面渲染
依赖项
{
"devDependencies": {
"confluence-api": "^1.4.0",
"marked": "^12.0.0",
"gray-matter": "^4.0.0",
"form-data": "^4.0.0"
}
}
CLI 命令
# 将 Markdown 同步到 Confluence
node scripts/confluence-sync.js --config confluence.config.json
# 将 Confluence 导出为 Markdown
node scripts/confluence-export.js --space DOCS --output ./exported
# 创建新空间
node scripts/confluence-space.js create --key NEWDOCS --name "New Documentation"
应用的最佳实践
- 使用页面模板以确保一致性
- 使用父页面进行组织
- 应用标签以提高可发现性
- 在 Git 中保留单一事实来源
- 在合并到主分支时同步
- 正确处理附件
参考
- Confluence REST API: https://developer.atlassian.com/cloud/confluence/rest/
- 存储格式: https://confluence.atlassian.com/doc/confluence-storage-format-790796544.html
- confluence-api npm: https://www.npmjs.com/package/confluence-api
目标流程
- knowledge-base-setup.js
- docs-pr-workflow.js
- content-strategy.js