离线优先应用与同步

知识库
知识库文档
/tech-stacks/couchdb/tutorial/离线优先应用与同步.md

文档

Apache CouchDB 从零到实战:离线优先应用

1. 背景与概念

1.1 为什么选择 CouchDB?

传统 Web 应用假设始终在线。但移动端(快递员扫码、野外采集)需要离线可用 → 联网同步。CouchDB 的多主复制天然支持这种模式:

浏览器 PouchDB  ────sync───▶  CouchDB Server  ────sync───▶  另一 CouchDB
(离线草稿)                      (线上汇总)                    (灾备/分析)

1.2 核心特性

特性 说明
MVCC 多版本并发控制,读不阻塞写
_changes 变更流,实时推送所有修改
_rev 文档版本号,乐观锁冲突检测
Replication 多主双向同步,支持过滤
附件 文档可直接关联二进制文件

2. 分步实战:离线笔记应用

场景

构建一个笔记应用:用户离线时可添加/编辑笔记,联网后自动同步到服务器。

步骤一:搭建后端 CouchDB

docker run -d -p 5984:5984 \
  -e COUCHDB_USER=admin \
  -e COUCHDB_PASSWORD=secret \
  -v couch:/opt/couchdb/data \
  couchdb:latest

# 创建数据库
curl -X PUT http://admin:secret@localhost:5984/notes

步骤二:前端 PouchDB 初始化

<!DOCTYPE html>
<html>
<head>
    <title>离线笔记</title>
    <script src="https://cdn.jsdelivr.net/npm/pouchdb@8.0.0/dist/pouchdb.min.js"></script>
</head>
<body>
    <textarea id="input" placeholder="写点什么..."></textarea>
    <button onclick="saveNote()">保存</button>
    <ul id="notes"></ul>

    <script>
        // 初始化本地数据库(IndexedDB 存储)
        const localDB = new PouchDB('notes_local');
        const remoteDB = new PouchDB('http://admin:secret@localhost:5984/notes');

        // 双向同步
        const sync = localDB.sync(remoteDB, {
            live: true,        // 持续监听变更
            retry: true        // 断线重连
        });

        sync.on('change', (info) => {
            console.log('同步变更:', info.direction);
            loadNotes();       // 刷新 UI
        });

        sync.on('error', (err) => {
            console.log('同步错误(可能离线):', err.message);
        });

        // 保存笔记
        function saveNote() {
            const text = document.getElementById('input').value;
            if (!text.trim()) return;

            localDB.put({
                _id: new Date().toISOString(),
                content: text,
                createdAt: new Date().toISOString()
            }).then(() => {
                document.getElementById('input').value = '';
                loadNotes();
                console.log('已保存到本地,联网后自动同步');
            });
        }

        // 加载所有笔记
        function loadNotes() {
            localDB.allDocs({ include_docs: true, descending: true })
                .then(result => {
                    const list = document.getElementById('notes');
                    list.innerHTML = '';
                    result.rows.forEach(row => {
                        const li = document.createElement('li');
                        li.textContent = row.doc.content;
                        list.appendChild(li);
                    });
                });
        }

        loadNotes();
    </script>
</body>
</html>

步骤三:冲突处理

当离线编辑了同一条笔记时会产生冲突。CouchDB 保留所有冲突版本:

// 检测并处理冲突
async function resolveConflicts() {
    const result = await localDB.allDocs({ include_docs: true, conflicts: true });
    
    for (const row of result.rows) {
        if (row.doc._conflicts) {
            console.log('冲突文档:', row.doc._id);
            // 策略:保留最新版本,删除旧版本
            const allRevs = await localDB.get(row.doc._id, { open_revs: 'all' });
            // 合并逻辑...
        }
    }
}

3. 思考题

  1. PouchDB 和 CouchDB 之间同步时,如果两边都修改了同一文档,_rev 机制如何检测冲突?
  2. 设计一个"只同步自己部门的文档"的过滤复制策略。
  3. CouchDB 的 _changes feed 与 MQ 消息队列相比,适用场景有何不同?

信息

路径
/tech-stacks/couchdb/tutorial/离线优先应用与同步.md
更新时间
2026/5/31