Yjs+Monaco Java后端架构设计文档

1. 系统架构概述

本文档详细描述了支持Yjs+Monaco前端协同编辑的Java后端系统架构设计。该系统主要负责实时数据同步、持久化存储、版本管理和用户权限控制。

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
Client1["前端客户端1 (Yjs+Monaco)"] <-->|WebSocket| WS[WebSocket服务]
Client2["前端客户端2 (Yjs+Monaco)"] <-->|WebSocket| WS
Client3["前端客户端3 (Yjs+Monaco)"] <-->|WebSocket| WS

WS <--> YDoc[文档同步服务]
YDoc <--> PersistenceService[持久化服务]
YDoc <--> VersionService[版本管理服务]

PersistenceService <--> DB[(数据库存储)]
VersionService <--> DB

Auth[认证授权服务] <--> Client1
Auth <--> Client2
Auth <--> Client3
Auth <--> WS

subgraph "Java SpringBoot后端"
WS
YDoc
PersistenceService
VersionService
Auth
end

2. 核心组件说明

2.1 WebSocket服务

负责管理前端客户端与后端的实时通信,实现Yjs文档更新的接收和分发。

2.2 文档同步服务

处理Yjs文档更新的核心逻辑,包括数据合并、冲突处理等。

2.3 持久化服务

负责将Yjs文档状态安全地存储到数据库中,并支持高效的数据加载。

2.4 版本管理服务

维护文档的历史版本,支持查看历史和回滚操作。

2.5 认证授权服务

控制用户对文档的访问权限,保证数据安全。

3. 数据模型设计

3.1 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
classDiagram
class YjsDocument {
+ObjectId _id
+String documentId
+List~Binary~ updates
+Binary stateVector
+List~DocumentVersion~ versionHistory
+Map~String,Object~ metadata
+Date createdAt
+Date updatedAt
+String createdBy
+addUpdate(byte[] update)
+mergeUpdates()
+getLatestState() Binary
}

class DocumentVersion {
+ObjectId _id
+Date timestamp
+String author
+Binary update
+String message
+Map~String,Object~ metadata
}

class DocumentAccess {
+ObjectId _id
+String documentId
+String userId
+String accessLevel
+Date grantedAt
+String grantedBy
}

YjsDocument "1" --> "*" DocumentVersion
YjsDocument "1" --> "*" DocumentAccess

MongoDB的文档模型非常适合存储Yjs的数据结构,因为它们都是基于JSON的格式,并且MongoDB的Binary类型可以高效地存储Yjs的二进制更新数据。

4. 关键流程设计

4.1 文档加载流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sequenceDiagram
participant Client as 前端客户端
participant WS as WebSocket服务
participant YDoc as 文档同步服务
participant PS as 持久化服务
participant DB as 数据库

Client->>WS: 建立WebSocket连接 (roomId)
WS->>YDoc: 请求文档状态 (roomId)
YDoc->>PS: 获取文档状态
PS->>DB: 查询文档数据
DB-->>PS: 返回文档数据
PS-->>YDoc: 返回文档状态
YDoc-->>WS: 返回文档状态
WS-->>Client: 发送初始文档状态
Note over Client: 应用文档状态到本地Yjs文档

4.2 文档更新流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sequenceDiagram
participant Client1 as 客户端1
participant Client2 as 客户端2
participant WS as WebSocket服务
participant YDoc as 文档同步服务
participant PS as 持久化服务
participant DB as 数据库

Client1->>WS: 发送文档更新
WS->>YDoc: 处理文档更新
YDoc->>PS: 存储更新
PS->>DB: 保存更新数据
YDoc-->>WS: 广播更新给其他客户端
WS-->>Client2: 接收文档更新
Note over Client2: 应用更新到本地Yjs文档

4.3 版本回滚流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sequenceDiagram
participant Client as 客户端
participant API as REST API
participant VS as 版本管理服务
participant PS as 持久化服务
participant DB as 数据库
participant WS as WebSocket服务

Client->>API: 请求回滚到特定版本
API->>VS: 处理回滚请求
VS->>PS: 获取目标版本状态
PS->>DB: 查询版本数据
DB-->>PS: 返回版本数据
PS-->>VS: 返回目标版本状态
VS->>PS: 更新当前文档状态
PS->>DB: 保存新状态
VS-->>API: 回滚完成
API-->>Client: 返回成功
VS->>WS: 通知所有客户端
WS-->>Client: 推送新状态
Note over Client: 重新加载文档

5. 系统组件详细设计

5.1 WebSocket处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
classDiagram
class WebSocketConfig {
+registerWebSocketHandlers()
}

class YjsWebSocketHandler {
-Map~String,Set~WebSocketSession~~ roomSessions
-YjsDocumentService yjsDocumentService
+afterConnectionEstablished(WebSocketSession session)
+handleBinaryMessage(WebSocketSession session, BinaryMessage message)
+afterConnectionClosed(WebSocketSession session, CloseStatus status)
-extractRoomId(WebSocketSession session) String
-broadcastToRoom(String roomId, WebSocketSession excludeSession, BinaryMessage message)
}

WebSocketConfig --> YjsWebSocketHandler
YjsWebSocketHandler --> YjsDocumentService

5.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
33
classDiagram
class YjsDocumentService {
-YjsDocumentRepository documentRepository
-DocumentVersionRepository versionRepository
-MongoTemplate mongoTemplate
-CacheManager cacheManager
+storeUpdate(String documentId, byte[] update, String author)
+getDocumentState(String documentId) byte[]
+getDocumentHistory(String documentId) List~DocumentVersion~
+rollbackToVersion(String documentId, String versionId)
+findDocumentsByMetadata(Map criteria) List~YjsDocument~
-mergeUpdates(YjsDocument document)
-createSnapshot(YjsDocument document)
}

class YjsDocumentRepository {
+findByDocumentId(String documentId) Optional~YjsDocument~
+findByDocumentIdAndUpdatedAtAfter(String documentId, Date date) Optional~YjsDocument~
+save(YjsDocument document)
+findAll() List~YjsDocument~
+findByMetadataContaining(String key, Object value) List~YjsDocument~
}

class DocumentVersionRepository {
+findByDocumentId(String documentId) List~DocumentVersion~
+findByDocumentIdOrderByTimestampDesc(String documentId) List~DocumentVersion~
+findByDocumentIdAndTimestampBetween(String documentId, Date start, Date end) List~DocumentVersion~
+save(DocumentVersion version)
}

YjsDocumentService --> YjsDocumentRepository
YjsDocumentService --> DocumentVersionRepository
YjsDocumentService --> MongoTemplate

5.3 REST API控制器

1
2
3
4
5
6
7
8
9
10
classDiagram
class DocumentController {
-YjsDocumentService documentService
+getDocumentHistory(String documentId) ResponseEntity
+rollbackToVersion(String documentId, String versionId) ResponseEntity
+getDocumentMetadata(String documentId) ResponseEntity
+updateDocumentMetadata(String documentId, Map metadata) ResponseEntity
}

DocumentController --> YjsDocumentService

6. MongoDB优化策略

6.1 性能优化架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
graph LR
A[原始方案] --> B[Redis缓存优化]
B --> C[MongoDB增量更新优化]
C --> D[MongoDB聚合合并优化]
D --> E[GridFS大文档存储]

subgraph "缓存层"
B
end

subgraph "数据处理层"
C
D
end

subgraph "MongoDB存储层"
E
F[TTL索引自动过期]
G[分片集群]
H[索引优化]
end

6.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
sequenceDiagram
participant PS as 持久化服务
participant Scheduler as 定时任务
participant Doc as YjsDocument
participant Mongo as MongoDB

Note over PS,Mongo: 正常更新流程
PS->>Doc: 添加增量更新
Doc->>Doc: 检查更新数量是否超过阈值
alt 超过合并阈值
Doc->>Doc: 执行更新合并
end
PS->>Mongo: 使用$push添加更新

Note over Scheduler,Mongo: 定时合并流程
Scheduler->>PS: 触发定期合并任务
PS->>Mongo: 聚合查询获取需要合并的文档
Mongo-->>PS: 返回文档列表
loop 每个文档
PS->>Doc: 检查更新数量
alt 超过合并阈值
Doc->>Doc: 执行更新合并
PS->>Mongo: 使用$set更新stateVector和$pull移除已合并更新
end
end

6.3 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
sequenceDiagram
participant PS as 持久化服务
participant Mongo as MongoDB
participant GridFS as GridFS

Note over PS,GridFS: 存储大文档
PS->>PS: 检查更新大小
alt 大于16MB阈值
PS->>GridFS: 使用GridFS存储大型文档
GridFS->>Mongo: 自动拆分为块存储
PS->>Mongo: 保存引用到YjsDocument
else 小于16MB阈值
PS->>Mongo: 直接存储到YjsDocument集合
end

Note over PS,GridFS: 加载大文档
PS->>Mongo: 查询文档元数据
Mongo-->>PS: 返回文档信息
alt 是GridFS文档
PS->>GridFS: 获取GridFS文件
GridFS-->>PS: 返回合并后的文件
else 是普通文档
PS->>Mongo: 直接获取数据
Mongo-->>PS: 返回文档数据
end

6.4 MongoDB特有优化技术

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
graph TD
A[MongoDB优化技术] --> B[Change Streams]
A --> C[TTL索引]
A --> D[读写分离]
A --> E[分片集群]

subgraph "监控与通知"
B[Change Streams] --> B1[实时监控文档变更]
B --> B2[触发WebSocket通知]
end

subgraph "自动清理"
C[TTL索引] --> C1[自动过期历史版本]
C --> C2[降低存储成本]
end

subgraph "高可用性"
D[读写分离] --> D1[提高并发读取性能]
E[分片集群] --> E1[水平扩展]
E --> E2[基于documentId分片]
end

7. 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
43
44
45
46
47
48
49
graph TD
Client[客户端浏览器] <-->|HTTPS/WSS| LB[负载均衡器]

subgraph "应用服务器集群"
App1[应用实例1]
App2[应用实例2]
App3[应用实例3]
end

LB <--> App1
LB <--> App2
LB <--> App3

App1 <-->|分布式缓存| RC[Redis集群]
App2 <-->|分布式缓存| RC
App3 <-->|分布式缓存| RC

App1 <--> Router
App2 <--> Router
App3 <--> Router

subgraph "MongoDB集群"
Router[MongoDB Router - mongos]

subgraph "配置服务器"
Config1[配置节点1]
Config2[配置节点2]
Config3[配置节点3]
end

Router <--> Config1
Router <--> Config2
Router <--> Config3

subgraph "分片1 - ReplicaSet"
Shard1Primary[主节点]
Shard1Secondary1[从节点1]
Shard1Secondary2[从节点2]
end

subgraph "分片2 - ReplicaSet"
Shard2Primary[主节点]
Shard2Secondary1[从节点1]
Shard2Secondary2[从节点2]
end

Router <--> Shard1Primary
Router <--> Shard2Primary
end

7.1 MongoDB分片策略

针对Yjs协同编辑系统的MongoDB分片策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
graph TD
A[分片策略] --> B[按documentId范围分片]
A --> C[按更新时间分片]

B --> B1["优点: 相关文档集中在同一分片"]
B --> B2["优点: 查询效率高"]
B --> B3["缺点: 热点文档可能导致负载不均"]

C --> C1["优点: 负载更均衡"]
C --> C2["优点: 适合基于时间的查询"]
C --> C3["缺点: 跨分片查询可能增加"]

A --> D[推荐配置]
D --> D1["分片键: { documentId: 1, updatedAt: -1 }"]
D --> D2["区域标记: 热门文档分配更多资源"]
D --> D3["每分片至少3节点复制集"]

8. 实现配置

8.1 MongoDB与Spring Boot配置

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
spring:
application:
name: yjs-collaborative-editor

# MongoDB配置
data:
mongodb:
uri: mongodb://localhost:27017/yjsdb
auto-index-creation: true
field-naming-strategy: org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy
# 对大型文档的支持配置
gridfs:
database: yjsdb
bucket: yjsDocuments

# WebSocket配置
websocket:
max-text-message-buffer-size: 8192
max-binary-message-buffer-size: 1048576 # 1MB
# 对于超大文档支持更大的消息大小
max-binary-message-buffer-size-large: 10485760 # 10MB

# 缓存配置
cache:
type: redis
redis:
time-to-live: 3600000 # 1小时
cache-null-values: false

# Redis配置 (用于缓存和WebSocket会话共享)
redis:
host: localhost
port: 6379
timeout: 10000
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0

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
@Configuration
public class MongoConfig extends AbstractMongoClientConfiguration {

@Override
protected String getDatabaseName() {
return "yjsdb";
}

@Bean
public MongoCustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
// 添加自定义Binary到byte[]的转换器
converters.add(new BinaryToByteArrayConverter());
converters.add(new ByteArrayToBinaryConverter());
return new MongoCustomConversions(converters);
}

@EventListener(ApplicationReadyEvent.class)
public void initIndicesAfterStartup() {
MongoTemplate mongoTemplate = mongoTemplate();

// 为documentId创建唯一索引
IndexOperations indexOps = mongoTemplate.indexOps(YjsDocument.class);
IndexDefinition documentIdIndex = new Index().on("documentId", Sort.Direction.ASC).unique();
indexOps.ensureIndex(documentIdIndex);

// 为更新时间创建索引以支持高效查询
IndexDefinition updatedAtIndex = new Index().on("updatedAt", Sort.Direction.DESC);
indexOps.ensureIndex(updatedAtIndex);

// 为元数据字段创建索引以支持查询
IndexDefinition metadataIndex = new Index().on("metadata.title", Sort.Direction.ASC);
indexOps.ensureIndex(metadataIndex);
}
}

8.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
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
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<!-- MongoDB支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- MongoDB GridFS支持 - 用于大文档存储 -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
</dependency>

<!-- 缓存支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- 安全支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

<!-- WebSocket会话共享支持 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>

<!-- 工具库 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>

<!-- 用于处理二进制数据 -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-cbor</artifactId>
</dependency>
</dependencies>

9. 总结

本架构设计提供了一个完整的Java后端方案,用于支持基于Yjs+Monaco的协同编辑前端。该方案具有以下特点:

  1. 使用WebSocket实现实时数据同步
  2. 支持文档数据的高效持久化和加载
  3. 提供完善的版本管理和历史回溯功能
  4. 通过多种优化策略提高系统性能

通过此架构,可以构建一个稳定、高效、可扩展的协同编辑系统,满足各种复杂业务场景的需求。