1. 项目背景与需求分析
在日常文档协作中,我们团队长期使用语雀作为核心知识管理平台。随着文档数量突破500+,遇到了三个典型痛点:
- 备份焦虑:重要技术文档缺乏可靠的本地备份
- 迁移困难:需要将部分文档转移至其他平台时,手动复制效率低下
- 版本混乱:语雀自动保存的版本记录无法满足我们的审计需求
提示:语雀API的rate limit为每小时1000次请求,批量导出时需要注意控制频率
经过技术调研,市面上现有工具存在以下局限:
- 官方导出功能仅支持单文档操作
- 第三方工具无法保持原始目录结构
- 图片资源常出现丢失或路径错误
2. 技术架构设计
2.1 核心模块分解
mermaid复制graph TD
A[主控模块] --> B[API调用]
A --> C[文件处理]
B --> D[文档树获取]
B --> E[文档内容获取]
C --> F[图片下载]
C --> G[路径替换]
C --> H[结构保存]
2.2 关键技术选型
| 技术点 | 选型方案 | 替代方案 | 选择理由 |
|---|---|---|---|
| HTTP客户端 | Axios | Node-fetch | 拦截器、自动转换JSON等企业级特性 |
| 异步控制 | Async/Await | Promise链 | 代码可读性更优 |
| 图片处理 | 原生Buffer | Sharp库 | 减少依赖,满足基础需求 |
| 路径处理 | path.join | 字符串拼接 | 跨平台兼容性 |
3. 核心实现细节
3.1 文档树获取优化
实际开发中发现语雀API的TOC接口存在两个关键问题:
- 深度超过5层时数据截断
- 大知识库响应时间超过10秒
解决方案:
javascript复制const getFullToc = async (namespace, retry = 3) => {
let mergedData = []
let page = 1
do {
try {
const res = await axios.get(`https://yuque.com/api/v2/repos/${namespace}/toc`, {
params: { page },
headers: { 'X-Auth-Token': token }
})
if (res.data.data.length === 0) break
mergedData = [...mergedData, ...res.data.data]
page++
// 防止高频请求
await new Promise(r => setTimeout(r, 500))
} catch (err) {
if (retry-- > 0) {
await new Promise(r => setTimeout(r, 2000))
continue
}
throw err
}
} while (true)
return mergedData
}
3.2 图片处理增强
原始方案存在三个典型问题:
- 同名图片覆盖
- 特殊字符文件名报错
- CDN图片过期
改进后的下载逻辑:
javascript复制const sanitizeFilename = (str) =>
str.replace(/[^a-z0-9]/gi, '_').toLowerCase()
const downloadImage = async (url, dir) => {
const timestamp = Date.now()
const ext = url.split('.').pop().split('?')[0]
const filename = `img_${timestamp}_${crypto.createHash('md5')
.update(url)
.digest('hex')
.substring(0, 6)}.${ext}`
const writer = fs.createWriteStream(path.join(dir, filename))
const response = await axios({
url,
method: 'GET',
responseType: 'stream',
timeout: 15000
})
response.data.pipe(writer)
return new Promise((resolve, reject) => {
writer.on('finish', () => resolve(filename))
writer.on('error', reject)
})
}
4. 企业级功能扩展
4.1 断点续传实现
通过记录处理状态实现异常恢复:
javascript复制const state = {
processedDocs: new Set(),
failedDocs: [],
lastSuccessTime: null
}
const saveState = () => {
fs.writeFileSync('./state.json', JSON.stringify({
processedDocs: [...state.processedDocs],
lastSuccessTime: state.lastSuccessTime
}))
}
process.on('SIGINT', () => {
saveState()
process.exit()
})
4.2 目录结构重建
根据parent_uuid还原原始层级:
javascript复制const buildDirectoryTree = (items) => {
const tree = {}
const itemMap = new Map()
// 建立映射关系
items.forEach(item => {
itemMap.set(item.uuid, { ...item, children: [] })
})
// 构建树形结构
itemMap.forEach(item => {
if (item.parent_uuid && itemMap.has(item.parent_uuid)) {
itemMap.get(item.parent_uuid).children.push(item)
} else {
tree[item.uuid] = item
}
})
return tree
}
5. 性能优化方案
5.1 并发控制
使用p-limit库实现可控并发:
javascript复制import pLimit from 'p-limit'
const limit = pLimit(5) // 最大并发数
const downloadTasks = docList.map(doc =>
limit(() => processDocument(doc))
)
await Promise.all(downloadTasks)
5.2 缓存策略
javascript复制const cachedRequest = async (url) => {
const cacheKey = `cache_${crypto.createHash('md5').update(url).digest('hex')}`
const cacheFile = path.join(cacheDir, cacheKey)
if (fs.existsSync(cacheFile)) {
return JSON.parse(fs.readFileSync(cacheFile))
}
const res = await axios.get(url)
fs.writeFileSync(cacheFile, JSON.stringify(res.data))
return res.data
}
6. 错误处理机制
6.1 分类处理策略
| 错误类型 | 处理方案 | 重试策略 |
|---|---|---|
| 网络超时 | 指数退避重试 | 3次,间隔2^n秒 |
| 403权限错误 | 终止并报警 | 不重试 |
| 404文档不存在 | 记录到skip列表 | 不重试 |
| 500服务器错误 | 等待5分钟后重试 | 2次 |
6.2 日志系统集成
javascript复制const { createLogger, transports, format } = require('winston')
const logger = createLogger({
level: 'debug',
format: format.combine(
format.timestamp(),
format.json()
),
transports: [
new transports.File({ filename: 'error.log', level: 'error' }),
new transports.File({ filename: 'combined.log' })
]
})
// 使用示例
logger.info('开始处理文档', { docId: 123 })
logger.error('下载失败', { error: err.message })
7. 安全防护措施
7.1 敏感信息处理
javascript复制const hideToken = (str) => {
if (!str || str.length < 8) return str
return str.substring(0, 2) + '*'.repeat(6) + str.slice(-2)
}
process.on('uncaughtException', (err) => {
logger.error('未捕获异常', {
message: hideToken(err.message),
stack: hideToken(err.stack)
})
})
7.2 请求签名验证
javascript复制const signRequest = (config) => {
const timestamp = Date.now()
const nonce = crypto.randomBytes(8).toString('hex')
const signStr = `${config.method}&${config.url}&${timestamp}&${nonce}`
const signature = crypto
.createHmac('sha256', secretKey)
.update(signStr)
.digest('hex')
config.headers = {
...config.headers,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Signature': signature
}
return config
}
axios.interceptors.request.use(signRequest)
8. 部署与监控
8.1 PM2配置方案
javascript复制module.exports = {
apps: [{
name: 'yuque-exporter',
script: './index.js',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'production'
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss'
}]
}
8.2 健康检查端点
javascript复制import express from 'express'
const app = express()
app.get('/health', (req, res) => {
res.json({
status: 'UP',
lastSync: state.lastSuccessTime,
memoryUsage: process.memoryUsage()
})
})
app.listen(3001)
9. 企业级扩展方案
9.1 分布式架构设计
mermaid复制graph TB
A[负载均衡] --> B[Worker 1]
A --> C[Worker 2]
A --> D[Worker 3]
B --> E[Redis任务队列]
C --> E
D --> E
E --> F[共享存储]
9.2 自动伸缩策略
| 指标 | 伸缩动作 | 冷却时间 |
|---|---|---|
| CPU > 70% 持续5分钟 | 增加2个Worker | 10分钟 |
| 队列积压 > 100 | 增加1个Worker | 5分钟 |
| CPU < 30% 持续30分钟 | 减少1个Worker | 15分钟 |
10. 质量保障体系
10.1 测试用例设计
javascript复制describe('图片下载测试', () => {
const testDir = './test_temp'
beforeAll(() => {
fs.mkdirSync(testDir)
})
it('应正确处理PNG图片', async () => {
const url = 'https://example.com/test.png'
const filename = await downloadImage(url, testDir)
expect(fs.existsSync(path.join(testDir, filename))).toBeTruthy()
})
afterAll(() => {
fs.rmdirSync(testDir, { recursive: true })
})
})
10.2 性能基准测试
bash复制# 测试100个文档的导出性能
npx artillery run --config ./perf.yml ./scenarios/100docs.json
perf.yml配置示例:
yaml复制config:
target: "http://localhost:3000"
phases:
- duration: 60
arrivalRate: 5
processor: "./custom-metrics.js"
11. 持续集成方案
11.1 GitHub Actions配置
yaml复制name: CI Pipeline
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm ci
- run: npm test
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm run build
- uses: actions/upload-artifact@v2
with:
name: dist
path: dist/
12. 项目演进路线
12.1 版本规划
| 版本 | 主要特性 | 预计周期 |
|---|---|---|
| v1.0 | 基础导出功能 | 2周 |
| v1.5 | 增量同步+断点续传 | 3周 |
| v2.0 | 分布式架构支持 | 6周 |
| v2.5 | 管理控制台 | 4周 |
12.2 技术债管理
markdown复制- [ ] 重构图片处理模块为Worker线程
- [ ] 实现WebSocket进度通知
- [ ] 增加PDF导出格式支持
- [ ] 完善API文档生成