多人协同开发确实是比较难的知识点,在技术实现上有一定挑战,但随着各种技术库的发展,目前已经有了比较成熟的解决方案。今介绍 Yjs 基于CRDT算法,用于构建自动同步的协作应用程序

yjs官网:https://docs.yjs.dev/

服务端 server.js

    const { WebSocketServer } = require("ws");

    // 创建 yjs ws 服务
    const yjsws = new WebSocketServer({ port: 1234 });

    yjsws.on("connection", (conn, req) => {
      console.log(req.url); // 标识每一个连接用户,用于广播不同的文件协同
      conn.onmessage = (event) => {
        yjsws.clients.forEach((conn) => {
          conn.send(event.data);
        });
      };

      conn.on("close", (conn) => {
        console.log("yjs 用户关闭连接");
      });
    });

yjs官网给的server.js

const Websocket = require("ws");
const { setupWSConnection } = require("y-websocket/bin/utils");

const port = 1234;

// 创建一个 ws 服务
const wss = new Websocket.Server({ port });

// 当服务连接时,调用 setupWSConnection 方法,将 y-websocket 的配置传入
wss.on("connection", (ws) => {
  setupWSConnection(ws, wss);
});

console.log(`Server started on ws://localhost:${port}`);

web客户端使用

import { ref, onMounted, onBeforeUnmount, watch } from "vue";
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";
import { useVueFlow } from '@vue-flow/core'

export default () => {
    const { addNodes, findNode, updateNode } = useVueFlow();
    const ydoc = new Y.Doc();
    // 初始化传输
    const provider = new WebsocketProvider(
        "ws://localhost:1234",
        "collaborative-editor",
        ydoc
    );

    // 共享数据类型
    // 在 Yjs 文档中创建共享的 nodes 和 edges 数组
    const ymap = ydoc.getMap('flow-data')

    const randomColor = () => {
        return (
            "#" +
            Math.floor(Math.random() * 0xffffff)
                .toString(16)
                .padEnd(6, "0")
        );
    };

    provider.on('status', event => {
        console.log('provider status', event.status) // logs "connected" or "disconnected"
    })

    provider.awareness.setLocalStateField("user", {
        name: "heyi-" + Math.random(),
        color: randomColor(),
    });

    onMounted(() => {
        const localUser = provider.awareness.getLocalState()?.user;
        const remoteUsers = provider.awareness.getStates();

        watch(() => provider.awareness.getStates(), (val) => {
            console.log('watch provider.awareness.getStates()', val)
        }, { deep: true, immediate: true })
        
        provider.awareness.on('change', (changes: any) => {
            // Whenever somebody updates their awareness information,
            // we log all awareness information from all users.
            console.log('Awareness changed:', changes);
            console.log('Array.from(provider.awareness.getStates().values())', Array.from(provider.awareness.getStates().values()));
        })
        ymap.observe(({ transaction, changes }) => {
            // if (!transaction.origin) return; // 没有 origin 表示的是本地发起
            changes.keys.forEach((change, key) => {
                console.log('observe change-->', change)
                console.log('observe key-->', key)
                console.log('ymap.get(key)-->', ymap.get(key))
                // YjsHandle({
                //     change,
                //     key,
                //     value: ymap.get(key)
                // });
            });
        });
        ydoc.on("update", (data) => {
            // console.log("Yjs document updated:", data);
        });
    })

    onBeforeUnmount(() => {
        provider.destroy();
        ydoc.destroy();
    });

    function YjsHandle({ change, key, value }: { change: any, key: any, value: any }) {
        switch (key) {
            case "addNode":
                addNodes([value]);
                break;
            case "position":
                const node = findNode(value.id);
                if (node) {
                    node.position = value.position;
                    node.data.updateTime = new Date().getTime();
                    updateNode(node.id, node);
                }
                break;
            default:
                break;
        }
    }

    return {
        ymap
    }
}

使用

onNodesChange((changes) => {
    changes.forEach((change) => {
        
        if (change.type === 'add') {
            ymap.set('addNode', change.item)
        }
        
        if (change.type === 'position') {
            ymap.set('position', {
                id: change.id,
                position: change.position
            })
        }
    })
})
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐