系统架构总览
架构图
模块职责
platform-api
所有模块依赖的共享 Dubbo 接口定义:
BlockChainService- 区块链操作DistributedStorageService- 存储操作- 公共 DTO 和响应类型
platform-backend
多模块后端服务(Dubbo Consumer):
| 子模块 | 职责 |
|---|---|
| backend-web | REST 控制器、JWT 过滤器、限流、CORS |
| backend-service | 业务逻辑、Saga 编排、Outbox 发布 |
| backend-dao | MyBatis Plus 映射、实体、VO |
| backend-api | 内部 API 接口定义 |
| backend-common | 工具类、常量、注解 |
platform-fisco
区块链集成服务(Dubbo Provider):
- 智能合约交互(Storage.sol, Sharing.sol)
- 多链适配器(本地 FISCO、BSN FISCO、Besu)
- 证书管理
platform-storage
分布式存储服务(Dubbo Provider):
- 多节点 S3 客户端管理
- 故障域管理
- 一致性哈希和再平衡
- 文件加密/解密
核心业务流程
文件上传存证
文件下载流程
下载策略对比:
| 策略 | 适用场景 | 特点 |
|---|---|---|
| 内存模式 | 小文件 (< 50MB) | 全部分片加载到内存后解密,速度快 |
| 流式模式 | 大文件 (≥ 50MB) | 使用 StreamSaver.js,边下载边写入,内存占用低 |
| 后端代理 | 特殊场景 | 后端代理下载,适用于无法直连 S3 的环境 |
密钥链解密:每个分片使用独立密钥加密,下载时按 chunkIndex 顺序匹配密钥进行解密。
文件分享流程
普通分享(链接分享)
好友分享
分享类型对比:
| 类型 | 访问控制 | 有效期 | 特点 |
|---|---|---|---|
| 公开分享 | 无限制 | 可设置 | 任何人可通过链接访问 |
| 私密分享 | 访问密码 | 可设置 | 需要密码才能访问 |
| 好友分享 | 好友关系 | 永久 | 仅指定好友可见,支持已读状态 |
Saga 补偿流程
| 步骤 | 正向操作 | 补偿操作 |
|---|---|---|
| PENDING | 初始化 | - |
| S3_UPLOADING | 存储分片 | 清理已存储分片 |
| S3_UPLOADED | 分片存储完成 | 删除 S3 文件 |
| CHAIN_STORING | 区块链存证 | 标记链上记录删除 |
| COMPLETED | 提交 | - |
补偿策略:指数退避重试(初始 1s,最多 5 次),失败后进入人工处理队列。
Saga 状态机
FileSagaOrchestrator 管理完整的状态机:
事务性 Outbox 模式
RecordPlatform 使用 Outbox 模式实现到 RabbitMQ 的可靠事件发布。
工作原理
组件
| 组件 | 职责 |
|---|---|
OutboxService | 在业务事务中追加事件 |
OutboxPublisher | 后台轮询和发布(2 秒间隔) |
outbox_event 表 | 带租户隔离的持久化事件存储 |
保证
- 至少一次投递:事件在消息队列不可用时仍能存活
- 事务一致性:事件在同一数据库事务中与业务数据一起创建
- 租户感知轮询:每个租户的事件独立处理
配置
outbox:
publisher:
batch-size: 100
poll-interval-ms: 2000
max-retries: 5
cleanup:
sent-retention-days: 7
failed-retention-days: 30
cron: 0 0 3 * * ?CQRS 架构
文件模块采用命令查询职责分离:
Virtual Thread 异步方法
查询服务使用 Java 21 Virtual Thread 提供异步方法:
getUserFilesListAsync()getFileAddressAsync()getFileDecryptInfoAsync()
文件版本链
文件版本链允许同一逻辑文件拥有多个历史版本,并通过链式结构追踪版本演进。详细设计请参阅 文件版本链 专题文档。
核心字段
| 字段 | 说明 |
|---|---|
version_group_id | 同一逻辑文件所有版本共享的分组 ID |
parent_version_id | 父版本 ID(首个版本为 null) |
is_latest | 标记当前链中最新版本(每个 group 仅一条为 true) |
版本 API
GET /api/v1/files/{id}/versions— 查询版本链列表POST /api/v1/files/{id}/versions— 将现有文件指定为新版本的父版本(上传时传入targetFileId)
多租户
隔离策略
| 层级 | 隔离方式 |
|---|---|
| 数据库 | tenant_id 字段,MyBatis 自动注入 |
| Redis | Key 前缀 tenant:{tenantId}: |
| S3 存储 | 路径 /{tenantId}/{userId}/ |
| Dubbo | Context 透传 TenantContext |
租户上下文控制
@TenantScope 注解用于声明式租户隔离:
// 跨租户查询(定时任务)
@TenantScope(ignoreIsolation = true)
@Scheduled(cron = "0 0 3 * * ?")
public void cleanupDeletedFiles() { ... }
// 切换到指定租户
@TenantScope(tenantId = 1)
public void migrateDataForTenant() { ... }实时通知(SSE)
服务器推送事件(Server-Sent Events)为连接的客户端提供实时更新。
多连接架构
系统支持同一用户的多个同时连接:
连接配置
| 参数 | 默认值 | 说明 |
|---|---|---|
| 每用户最大连接数 | 5 | 超出时关闭最旧连接 |
| 心跳间隔 | 30 秒 | 保活信号 |
| 连接超时 | 30 分钟 | 无活动后自动关闭 |
| 重连延迟(基础) | 2 秒 | 客户端指数退避重连基础间隔 |
| 重连延迟(上限) | 30 秒 | 客户端指数退避重连上限 |
| 最大重连次数 | 5 | 达到上限后转为手动重连 |
事件类型
| 事件 | 载荷 | 说明 |
|---|---|---|
connected | { connectionId } | 连接确认 |
notification | { title, content } | 通用通知 |
message-received | { conversationId, preview } | 会话新消息 |
file-record-success | { fileName, fileHash, status } | 文件存证成功通知 |
file-record-failed | { fileName, status, reason } | 文件存证失败通知 |
announcement-published | { id, title } | 系统公告 |
ticket-updated | { ticketId, status } | 工单状态变更 |
badge-update | { unreadMessages, tickets } | UI 徽章数量更新 |
friend-request | { requesterName, ... } | 新好友请求 |
friend-accepted | { friendName, ... } | 好友请求被接受 |
friend-share | { sharerName, fileCount, ... } | 好友文件分享 |
audit-alert | { type, message, details, severity } | 审计异常告警(管理员/监控员) |
integrity-alert | { alertType, fileHash, details } | 存储完整性异常告警(管理员) |
SSE 认证握手
SSE 连接采用短期一次性令牌:
- 登录态下调用
POST /api/v1/auth/tokens/sse获取短期令牌(需常规 JWT) - 使用
GET /api/v1/sse/connect?token={sseToken}&connectionId={optional}建立连接
GET /api/v1/sse/connect为公开端点,但依赖短期令牌完成认证;不是匿名开放连接。
监控容量口径
系统监控新增容量聚合端点:
GET /api/v1/system/storage-capacity
返回结构包含:
- 集群维度:
totalCapacityBytes、usedCapacityBytes、availableCapacityBytes - 数据质量:
degraded、source - 细粒度明细:
nodes[]、domains[]
GET /api/v1/system/stats 的 totalStorage 已优先使用该容量聚合结果;当 Dubbo 调用失败时,回退到 totalFiles * 1MB 的估算逻辑,并输出固定日志标记 MONITOR_STORAGE_CAPACITY_FALLBACK。
下载策略(前端)
前端按文件大小和浏览器能力动态选择下载策略:
- 小文件:内存下载(in-memory)
- 大文件:优先流式下载(streaming)
- 超大文件或浏览器能力不足:后端代理或阻断并提示
默认阈值(platform-frontend/src/lib/utils/fileSize.ts):
| 阈值常量 | 默认值 |
|---|---|
LARGE_FILE_WARNING_THRESHOLD | 500MB |
STREAMING_RECOMMENDED_THRESHOLD | 1GB |
MAX_SAFE_INMEMORY_SIZE | 2GB |
MAX_DOWNLOADABLE_SIZE | 100GB |
配额治理
平台支持按用户和租户维度的存储配额管控,提供两种执行模式:
- SHADOW(默认):配额违规仅记录和告警,不阻止上传。在正式执行前用于观察影响。
- ENFORCE:超出配额的上传会被拒绝,返回
50013 QUOTA_EXCEEDED。
灰度策略:
| 属性 | 说明 |
|---|---|
quota.enforcement-mode | 全局模式:SHADOW 或 ENFORCE |
quota.rollout.strategy | TENANT_WHITELIST — 仅白名单内租户使用 ENFORCE |
quota.rollout.enforce-tenant-whitelist | 逗号分隔的 ENFORCE 模式租户 ID 列表 |
quota.rollout.force-shadow | 强制所有租户使用 SHADOW 模式 |
对账:定时任务(quota.reconcile.cron,默认每 30 分钟)重新计算使用量快照,修正缓存与实际使用量之间的偏差。
API 端点:
GET /api/v1/files/quota— 查询当前用户配额状态POST /api/v1/admin/quota/rollout/audits— 写入灰度审计记录(管理员)GET /api/v1/admin/quota/rollout/audits— 查询灰度审计记录(管理员)
批量下载
前端支持多选文件批量下载:
- 选择:用户从文件列表中选择多个文件
- 并行下载:文件按可配置的并发数同时下载
- 自动重试:下载失败的文件自动重试
- 指标上报:完成后,前端通过
POST /api/v1/files/download-batches/report上报批量下载质量指标
上报载荷包含总文件数、成功/失败数、重试次数、总耗时、错误类型分布等,用于后端质量可观测。
关键词搜索模式
文件查询(GET /api/v1/files)支持 keywordMode 参数控制 keyword 的匹配方式:
| 模式 | 文件名匹配 | 文件哈希匹配 | 适用场景 |
|---|---|---|---|
FUZZY(默认) | LIKE %keyword% | LIKE %keyword% | 通用搜索 |
PREFIX | LIKE keyword% | 精确匹配 | 索引加速查询 |
EXACT_HASH | 不搜索 | 仅精确匹配 | 哈希直查 |
AUTO | 取决于关键词 | 取决于关键词 | 智能检测 |
AUTO 模式会检查关键词:如果匹配十六进制哈希模式(/^[0-9a-fA-F]{32,128}$/),则解析为 EXACT_HASH;否则解析为 PREFIX。
前端 Leader 选举
对于多标签页场景,前端使用 BroadcastChannel 进行 Leader 选举:
- Leader 标签页:维护单一 SSE 连接
- Follower 标签页:通过 BroadcastChannel 接收事件
- 故障转移:Leader 标签页关闭时自动选举新 Leader
这可以防止同一浏览器建立多个 SSE 连接,减少服务器负载。