为了支持多人协作,系统实现了完整的团队协作功能:
10.1 团队管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 graph TD A[用户] -->|创建| B[团队] A -->|管理| C[团队成员] A -->|分配| D[成员角色] B -->|拥有| E[团队文档] C -->|访问| E subgraph "角色权限" D1[owner-所有者] D2[admin-管理员] D3[member-成员] end D1 -->|完全控制| B D2 -->|管理权限| C D3 -->|使用权限| E
10.2 文档归属与权限 每个云文档都归属于一个团队或个人,系统中的文档权限设计如下:
文档类型 :
个人文档:归属于个人,可自行决定是否共享
团队文档:归属于团队,团队成员默认具有读写权限
权限级别 :
read
:只读权限
write
:编辑权限
admin
:管理权限(可进行权限分配)
权限分配 :
文档创建者自动获得管理权限
团队文档对团队成员默认具有write
权限
团队管理员可调整成员对特定文档的权限
10.3 团队文档工作流 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 sequenceDiagram participant User1 as 团队成员1 participant User2 as 团队成员2 participant API as REST API participant Team as 团队服务 participant Doc as 文档服务 participant WS as WebSocket服务 User1->>API: 创建团队文档 API->>Team: 验证团队成员身份 Team-->>API: 验证通过 API->>Doc: 创建团队文档 Doc->>Doc: 设置默认权限 Doc-->>API: 返回文档ID API-->>User1: 创建成功 User2->>API: 查询团队文档列表 API->>Team: 验证团队成员身份 Team-->>API: 验证通过 API->>Doc: 获取团队文档列表 Doc-->>API: 返回文档列表 API-->>User2: 文档列表 User2->>WS: 建立WebSocket连接 WS->>Doc: 验证文档访问权限 Doc->>Team: 检查团队权限 Team-->>Doc: 返回权限信息 Doc-->>WS: 验证通过 WS-->>User2: 连接成功 User1->>WS: 发送文档更新 WS->>WS: 广播到所有连接的成员 WS-->>User2: 接收实时更新
10.4 团队功能API 团队功能提供以下API接口:
团队管理:
POST /teams
- 创建团队
PUT /teams/{teamId}
- 更新团队信息
DELETE /teams/{teamId}
- 删除团队
GET /teams/{teamId}
- 获取团队信息
GET /teams
- 获取用户所在团队列表
团队成员管理:
POST /teams/{teamId}/members
- 添加团队成员
PUT /teams/{teamId}/members/{userId}/role
- 更新成员角色
DELETE /teams/{teamId}/members/{userId}
- 移除团队成员
GET /teams/{teamId}/members
- 获取团队成员列表
团队文档:
POST /documents/team/{teamId}
- 创建团队文档
GET /documents/team/{teamId}
- 获取团队文档列表# Yjs+Monaco 协同编辑平台架构实现文档
1. 系统架构概述 本文档详细描述了支持Yjs+Monaco前端协同编辑的后端系统架构实现。该系统采用MySQL存储元数据和MongoDB存储文档内容的混合数据存储策略,主要负责实时数据同步、持久化存储、版本管理和用户权限控制。另外,系统支持团队协作功能,每个文档都可以归属于一个团队,团队成员可以共同编辑文档。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 graph TD Client1["前端客户端1 (Yjs+Monaco)"] <-->|WebSocket| WS[WebSocket服务] Client2["前端客户端2 (Yjs+Monaco)"] <-->|WebSocket| WS Client3["前端客户端3 (Yjs+Monaco)"] <-->|WebSocket| WS Client1 <-->|HTTP| RC[REST控制器] Client2 <-->|HTTP| RC Client3 <-->|HTTP| RC WS <--> YDoc[文档同步服务] RC <--> DocService[文档服务] RC <--> TeamService[团队服务] YDoc <--> DocService DocService <--> YDoc DocService <--> TeamService DocService <--> MetaDB[(MySQL元数据)] YDoc <--> ContentDB[(MongoDB内容)] TeamService <--> MetaDB Auth[认证授权服务] <--> Client1 Auth <--> Client2 Auth <--> Client3 Auth <--> WS Auth <--> RC Cache[Redis缓存] <--> DocService Cache <--> YDoc Cache <--> TeamService Task[定时任务] --> YDoc subgraph "Java SpringBoot后端" WS RC YDoc DocService TeamService Auth Task end
2. 数据模型设计 2.1 MySQL元数据模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 classDiagram class User { +Long id +String username +String password +String email +String avatar +Integer status +LocalDateTime createTime +LocalDateTime updateTime +Boolean isDeleted } class Team { +Long id +String name +String description +Long ownerId +LocalDateTime createTime +LocalDateTime updateTime +Boolean isDeleted } class TeamMember { +Long id +Long teamId +Long userId +String role +LocalDateTime createTime +LocalDateTime updateTime +Boolean isDeleted } class DocumentMeta { +Long id +String docId +String title +String description +String type +String language +Long ownerId +Long teamId +String sharedType +Integer status +LocalDateTime lastEditTime +Long lastEditUserId +LocalDateTime createTime +LocalDateTime updateTime +Boolean isDeleted } class DocumentPermission { +Long id +String docId +Long userId +Long teamId +String permissionType +LocalDateTime createTime +LocalDateTime updateTime +Boolean isDeleted } class DocumentVersion { +Long id +String docId +Integer version +String title +Long userId +LocalDateTime createTime +Boolean isDeleted } class DocumentHistory { +Long id +String docId +Long userId +String operationType +String operationDesc +LocalDateTime createTime } User "1" --> "*" TeamMember : 创建/加入 Team "1" --> "*" TeamMember : 包含 Team "1" -- "1" User : 所有者 User "1" --> "*" DocumentMeta : 创建 Team "1" --> "*" DocumentMeta : 拥有 DocumentMeta "1" --> "*" DocumentPermission : 授权 DocumentMeta "1" --> "*" DocumentVersion : 版本历史 DocumentMeta "1" --> "*" DocumentHistory : 操作历史 User "1" --> "*" DocumentVersion : 创建版本 User "1" --> "*" DocumentHistory : 操作记录 TeamMember --o Team : 成员关系 TeamMember --o User : 成员关系 DocumentPermission --o User : 个人权限 DocumentPermission --o Team : 团队权限
2.2 MongoDB文档内容模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 classDiagram class YjsDocument { +String id +String docId +Binary stateVector +List~UpdateEntry~ updates +LocalDateTime createdAt +LocalDateTime lastModifiedAt +Map~String,Object~ metadata +addUpdate(byte[] update, Long userId, String username) } class UpdateEntry { +LocalDateTime timestamp +Long userId +String username +Binary update } class DocumentVersionContent { +String id +String docId +Integer version +Binary stateVector +Long userId +String username +LocalDateTime createdAt +String remark } YjsDocument "1" --> "*" UpdateEntry YjsDocument "1" -- "*" DocumentVersionContent
3. 系统组件详细实现 3.1 数据访问层 实现了基于MyBatis Plus的MySQL数据访问层和基于Spring Data MongoDB的MongoDB数据访问层:
3.1.1 MySQL Mapper MyBatis Plus Mapper接口用于操作MySQL中的元数据,包括:
DocumentMetaMapper - 文档元数据管理
DocumentPermissionMapper - 文档权限管理
DocumentVersionMapper - 文档版本管理
DocumentHistoryMapper - 文档历史记录管理
TeamMapper - 团队管理
TeamMemberMapper - 团队成员管理
UserMapper - 用户管理
3.1.2 MongoDB Repository MongoDB Repository接口用于操作MongoDB中的文档内容数据,包括:
YjsDocumentRepository - Yjs文档内容管理
DocumentVersionContentRepository - 文档版本内容管理
3.2 服务层 服务层实现了业务逻辑处理,主要包括以下服务:
3.2.1 YjsDocumentService Yjs文档服务,负责文档内容的存储、加载和版本管理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public interface YjsDocumentService { void storeUpdate (String docId, byte [] update, Long userId, String username) ; byte [] getDocumentState(String docId); Integer createVersion (String docId, String title, String remark, Long userId, String username) ; List<DocumentVersionVO> getVersions (String docId) ; byte [] rollbackToVersion(String docId, Integer version); }
3.2.2 DocumentService 文档元数据服务,负责文档元数据和权限管理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public interface DocumentService { String createDocument (DocumentMeta documentMeta, Long userId) ; String createTeamDocument (DocumentMeta documentMeta, Long teamId, Long userId) ; boolean updateDocument (DocumentMeta documentMeta, Long userId) ; boolean deleteDocument (String docId, Long userId) ; DocumentMetaVO getDocumentMeta (String docId, Long userId) ; IPage<DocumentMetaVO> getUserDocuments (Page<DocumentMetaVO> page, Long userId, String keyword) ; boolean checkPermission (String docId, Long userId, String permissionType) ; }
3.2.3 TeamService 团队服务,负责团队管理和成员权限控制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public interface TeamService { Long createTeam (Team team, Long userId) ; boolean updateTeam (Team team, Long userId) ; boolean deleteTeam (Long teamId, Long userId) ; boolean addTeamMember (Long teamId, Long userId, String role, Long adminId) ; boolean updateTeamMemberRole (Long teamId, Long userId, String role, Long adminId) ; boolean removeTeamMember (Long teamId, Long userId, Long adminId) ; boolean isTeamMember (Long teamId, Long userId) ; boolean isTeamAdmin (Long teamId, Long userId) ; }
3.3 WebSocket通信 WebSocket是协同编辑的核心,用于实时传输Yjs更新数据:
3.3.1 WebSocket配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Autowired private YjsWebSocketHandler yjsWebSocketHandler; @Override public void registerWebSocketHandlers (WebSocketHandlerRegistry registry) { registry.addHandler(yjsWebSocketHandler, "/ws/yjs/{docId}" ) .addInterceptors(new HttpSessionHandshakeInterceptor ()) .setAllowedOrigins("*" ); } }
3.3.2 WebSocket处理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Component @Slf4j public class YjsWebSocketHandler extends TextWebSocketHandler { @Autowired private YjsDocumentService yjsDocumentService; @Autowired private DocumentService documentService; private final Map<String, Set<WebSocketSession>> roomSessions = new ConcurrentHashMap <>(); private final Map<String, Long> sessionUserMap = new ConcurrentHashMap <>(); @Override public void afterConnectionEstablished (WebSocketSession session) throws Exception { } @Override protected void handleBinaryMessage (WebSocketSession session, BinaryMessage message) throws Exception { } }
3.4 REST API控制器 REST API提供了文档管理的HTTP接口:
3.4.1 文档控制器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @RestController @RequestMapping("/documents") @Slf4j public class DocumentController { @Autowired private DocumentService documentService; @Autowired private YjsDocumentService yjsDocumentService; @PostMapping public ApiResult<String> createDocument (@RequestBody DocumentMeta documentMeta) { } @PutMapping("/{docId}") public ApiResult<Boolean> updateDocument (@PathVariable String docId, @RequestBody DocumentMeta documentMeta) { } }
3.5 缓存配置 使用Redis缓存提高系统性能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration @EnableCaching public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate (RedisConnectionFactory factory) { } @Bean public CacheManager cacheManager (RedisConnectionFactory factory) { } }
3.6 定时任务 实现文档优化定时任务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Component @Slf4j public class DocumentMaintenanceTask { @Autowired private YjsDocumentService yjsDocumentService; @Autowired private DocumentMetaMapper documentMetaMapper; @Scheduled(cron = "0 0 2 * * ?") public void optimizeDocuments () { } }
4. 关键流程实现 4.1 文档加载流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 sequenceDiagram participant Client as 前端客户端 participant WS as WebSocket处理器 participant Doc as 文档服务 participant YDoc as Yjs文档服务 participant Cache as Redis缓存 participant MongoDB as MongoDB Note over Client,MongoDB: WebSocket建立连接 Client->>WS: 建立WebSocket连接 (docId, token) WS->>Doc: 检查文档访问权限 Doc-->>WS: 权限验证结果 alt 有权限访问 WS->>YDoc: 获取文档状态 (docId) YDoc->>Cache: 查询缓存 alt 缓存命中 Cache-->>YDoc: 返回文档状态 else 缓存未命中 YDoc->>MongoDB: 查询文档 MongoDB-->>YDoc: 返回文档数据 YDoc->>Cache: 缓存文档状态 end YDoc-->>WS: 返回文档状态 WS-->>Client: 发送初始文档状态 Note over Client: 应用文档状态到本地Yjs文档 else 无权限访问 WS-->>Client: 关闭连接(无权限) end
4.2 文档更新流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 sequenceDiagram participant Client1 as 客户端1 participant Client2 as 客户端2 participant WS as WebSocket处理器 participant Doc as 文档服务 participant YDoc as Yjs文档服务 participant Cache as Redis缓存 participant MongoDB as MongoDB participant MySQL as MySQL Note over Client1,MySQL: 编辑文档并同步 Client1->>WS: 发送文档更新 WS->>Doc: 检查编辑权限 Doc-->>WS: 权限验证结果 alt 有编辑权限 WS->>YDoc: 存储更新 YDoc->>MongoDB: 保存更新数据 YDoc->>Cache: 更新缓存 WS->>Doc: 更新最后编辑信息 Doc->>MySQL: 更新元数据 WS->>Client2: 广播更新给其他客户端 Note over Client2: 应用更新到本地Yjs文档 else 无编辑权限 WS-->>Client1: 忽略更新(无权限) end
4.3 版本管理流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 sequenceDiagram participant Client as 客户端 participant API as REST API participant Doc as 文档服务 participant YDoc as Yjs文档服务 participant MySQL as MySQL participant MongoDB as MongoDB Note over Client,MongoDB: 创建文档版本 Client->>API: 请求创建版本 API->>Doc: 检查编辑权限 Doc-->>API: 权限验证结果 alt 有编辑权限 API->>YDoc: 创建版本 YDoc->>MongoDB: 获取当前文档状态 MongoDB-->>YDoc: 返回文档数据 YDoc->>MongoDB: 保存版本内容 YDoc->>MySQL: 保存版本元数据 MySQL-->>YDoc: 返回版本号 YDoc-->>API: 返回版本号 API-->>Client: 返回版本创建结果 else 无编辑权限 API-->>Client: 返回错误(无权限) end
5. 数据存储优化策略 5.1 MongoDB数据模型优化 MongoDB中的Yjs文档存储采用以下优化策略:
增量更新存储 :
将每次更新作为单独条目存储在updates
数组中
包含时间戳、用户ID和用户名等元数据
状态向量存储 :
更新合并策略 :
当更新数量超过阈值时,进行更新合并
仅保留最近的N个更新,减少存储空间占用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void optimizeUpdates (YjsDocument document) { List<YjsDocument.UpdateEntry> updates = document.getUpdates(); if (updates.size() <= KEEP_RECENT_UPDATES) { return ; } log.info("Optimizing document: {}, updates count: {}" , document.getDocId(), updates.size()); document.setUpdates( updates.subList(updates.size() - KEEP_RECENT_UPDATES, updates.size()) ); log.info("After optimization, updates count: {}" , document.getUpdates().size()); }
5.2 缓存策略 使用Redis缓存优化文档加载性能:
文档状态缓存 :
缓存文档最新状态,避免频繁查询MongoDB
设置合理的过期时间(如10分钟)
文档元数据缓存 :
缓存文档元数据,提高权限检查性能
设置较长的过期时间(如30分钟)
1 2 3 4 5 6 7 8 9 10 11 @Cacheable(value = "documentStates", key = "#docId") public byte [] getDocumentState(String docId) { return yjsDocumentRepository.findByDocId(docId) .map(doc -> doc.getStateVector().getData()) .orElse(null ); } @CacheEvict(value = "documentStates", key = "#docId") public void storeUpdate (String docId, byte [] update, Long userId, String username) { }
5.3 定时优化任务 实现定时任务对文档进行优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Scheduled(cron = "0 0 2 * * ?") public void optimizeDocuments () { log.info("Starting document optimization task" ); List<DocumentMeta> documents = documentMetaMapper.selectList( new LambdaQueryWrapper <DocumentMeta>() .eq(DocumentMeta::getStatus, 0 ) ); for (DocumentMeta document : documents) { try { yjsDocumentService.optimizeDocument(document.getDocId()); } catch (Exception e) { log.error("Failed to optimize document: {}" , document.getDocId(), e); } } }
6. 部署架构 系统可以采用以下部署架构实现高可用性和可扩展性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 graph TD Client[客户端浏览器] <-->|HTTPS/WSS| LB[负载均衡器] subgraph "应用服务器集群" App1[应用实例1] App2[应用实例2] App3[应用实例3] end LB <--> App1 LB <--> App2 LB <--> App3 App1 <-->|分布式缓存| Redis[Redis集群] App2 <-->|分布式缓存| Redis App3 <-->|分布式缓存| Redis App1 <--> MySQL[(MySQL主从集群)] App2 <--> MySQL App3 <--> MySQL App1 <--> MongoDB[(MongoDB复制集)] App2 <--> MongoDB App3 <--> MongoDB
6.1 水平扩展支持
无状态应用设计 :
WebSocket连接信息保存在Redis中
用户会话通过Redis共享
所有应用实例可以处理任何请求
Redis分布式锁 :
MongoDB分片 :
7. 安全机制 系统实现了多层次的安全机制:
身份验证 :
基于token的认证机制
WebSocket连接需要有效token
权限控制 :
文档级权限控制(读、写、管理)
用户和团队权限分离
权限缓存优化
数据加密 :
8. 总结与展望 本系统实现了一个完整的基于Yjs+Monaco的协同编辑平台,采用MySQL+MongoDB混合存储架构,具有以下特点:
实时协作 :基于WebSocket的低延迟实时数据同步
高效存储 :混合存储架构,元数据与内容分离
版本管理 :完整的文档版本控制功能
权限系统 :细粒度的文档访问权限控制
性能优化 :缓存、增量更新和定时优化机制
未来优化方向
WebSocket集群扩展 :
实现基于Redis的WebSocket集群,支持更大规模用户同时在线
文档快照机制 :
优化大文档的历史版本存储,实现快照与增量更新结合的存储策略
实时分析 :
离线编辑支持 :
实现基于IndexedDB的本地存储机制,支持离线编辑后的自动同步