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 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[文档服务] YDoc <--> DocService DocService <--> YDoc DocService <--> MetaDB[(MySQL元数据)] YDoc <--> ContentDB[(MongoDB内容)] Auth[认证授权服务] <--> Client1 Auth <--> Client2 Auth <--> Client3 Auth <--> WS Auth <--> RC Cache[Redis缓存] <--> DocService Cache <--> YDoc Task[定时任务] --> YDoc subgraph "Java SpringBoot后端" WS RC YDoc DocService 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 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 User "1" --> "*" DocumentMeta Team "1" --> "*" DocumentMeta DocumentMeta "1" --> "*" DocumentPermission DocumentMeta "1" --> "*" DocumentVersion DocumentMeta "1" --> "*" DocumentHistory User "1" --> "*" DocumentVersion User "1" --> "*" DocumentHistory
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 - 文档版本管理
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 public interface DocumentService { String createDocument (DocumentMeta documentMeta, 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.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的本地存储机制,支持离线编辑后的自动同步