System Overview
Architecture Diagram
Module Responsibilities
platform-api
Shared Dubbo interface definitions that all modules depend on:
BlockChainService- Blockchain operationsDistributedStorageService- Storage operations- Common DTOs and response types
platform-backend
Multi-module backend service (Dubbo Consumer):
| Submodule | Responsibility |
|---|---|
| backend-web | REST controllers, JWT filters, rate limiting, CORS |
| backend-service | Business logic, Saga orchestration, Outbox publishing |
| backend-dao | MyBatis Plus mappers, entities, VOs |
| backend-api | Internal API interfaces |
| backend-common | Utilities, constants, annotations |
platform-fisco
Blockchain integration service (Dubbo Provider):
- Smart contract interaction (Storage.sol, Sharing.sol)
- Multi-chain adapters (Local FISCO, BSN FISCO, Besu)
- Certificate management
platform-storage
Distributed storage service (Dubbo Provider):
- Multi-node S3 client management
- Fault domain management
- Consistent hashing and rebalancing
- File encryption/decryption
Core Business Flow
File Upload & Attestation
File Download Flow
Download Strategy Comparison:
| Strategy | Use Case | Characteristics |
|---|---|---|
| Memory Mode | Small files (< 50MB) | Load all chunks into memory then decrypt, fast |
| Streaming Mode | Large files (≥ 50MB) | Uses StreamSaver.js, download while writing, low memory |
| Backend Proxy | Special scenarios | Backend proxies download, for environments without direct S3 access |
Key Chain Decryption: Each chunk is encrypted with an independent key. During download, keys are matched by chunkIndex order for decryption.
File Sharing Flow
Link Sharing
Friend Sharing
Share Type Comparison:
| Type | Access Control | Expiry | Characteristics |
|---|---|---|---|
| Public Share | None | Configurable | Anyone can access via link |
| Private Share | Password | Configurable | Password required for access |
| Friend Share | Friendship | Permanent | Only specified friend can view, supports read status |
Saga Compensation Flow
| Step | Forward Action | Compensation |
|---|---|---|
| PENDING | Initialize | - |
| S3_UPLOADING | Store chunks | Clean stored chunks |
| S3_UPLOADED | Chunks stored | Delete S3 files |
| CHAIN_STORING | Blockchain attestation | Mark chain record deleted |
| COMPLETED | Commit | - |
Compensation Strategy: Exponential backoff (initial 1s, max 5 retries), then manual queue.
Saga State Machine
The FileSagaOrchestrator manages the complete state machine:
Transactional Outbox Pattern
RecordPlatform uses the Outbox pattern for reliable event publishing to RabbitMQ.
How It Works
Components
| Component | Responsibility |
|---|---|
OutboxService | Appends events within business transaction |
OutboxPublisher | Background polling and publishing (2s interval) |
outbox_event table | Persistent event store with tenant isolation |
Guarantees
- At-least-once delivery: Events survive broker unavailability
- Transactional consistency: Event created in same DB transaction as business data
- Tenant-aware polling: Each tenant's events processed independently
Configuration
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 Architecture
File module uses Command Query Responsibility Segregation:
Virtual Thread Async Methods
Query service provides async methods using Java 21 Virtual Threads:
getUserFilesListAsync()getFileAddressAsync()getFileDecryptInfoAsync()
File Version Chain
The file version chain allows a single logical file to have multiple historical versions, linked together in a chain structure. See the dedicated File Version Chain page for detailed design.
Core Fields
| Field | Description |
|---|---|
version_group_id | Shared group ID across all versions of the same logical file |
parent_version_id | Parent version ID (null for first version) |
is_latest | Marks the latest version in the chain (only one true per group) |
Version API
GET /api/v1/files/{id}/versions— List the version chain for a filePOST /api/v1/files/{id}/versions— Mark an existing file as parent for a new upload (viatargetFileId)
Multi-tenancy
Isolation Strategy
| Layer | Isolation Method |
|---|---|
| Database | tenant_id field, MyBatis auto-inject |
| Redis | Key prefix tenant:{tenantId}: |
| S3 Storage | Path /{tenantId}/{userId}/ |
| Dubbo | Context propagation TenantContext |
Tenant Context Control
@TenantScope annotation for declarative tenant isolation:
// Cross-tenant query (scheduled tasks)
@TenantScope(ignoreIsolation = true)
@Scheduled(cron = "0 0 3 * * ?")
public void cleanupDeletedFiles() { ... }
// Switch to specific tenant
@TenantScope(tenantId = 1)
public void migrateDataForTenant() { ... }Real-time Notifications (SSE)
Server-Sent Events provide real-time updates to connected clients.
Multi-Connection Architecture
The system supports multiple simultaneous connections per user:
Connection Configuration
| Parameter | Default | Description |
|---|---|---|
| Max connections per user | 5 | Oldest connection closed when exceeded |
| Heartbeat interval | 30s | Keep-alive signal |
| Connection timeout | 30m | Auto-close after inactivity |
| Reconnect delay (base) | 2s | Base delay for exponential client reconnect |
| Reconnect delay (max) | 30s | Upper bound for reconnect backoff |
| Max reconnect attempts | 5 | Falls back to manual reconnect after limit |
Event Types
| Event | Payload | Description |
|---|---|---|
connected | { connectionId } | Initial connection confirmation |
notification | { title, content } | General notification |
message-received | { conversationId, preview } | New message in conversation |
file-record-success | { fileName, fileHash, status } | File attestation success notification |
file-record-failed | { fileName, status, reason } | File attestation failure notification |
announcement-published | { id, title } | System announcement |
ticket-updated | { ticketId, status } | Ticket status change |
badge-update | { unreadMessages, tickets } | UI badge count update |
friend-request | { requesterName, ... } | New friend request |
friend-accepted | { friendName, ... } | Friend request accepted |
friend-share | { sharerName, fileCount, ... } | Friend file sharing |
audit-alert | { type, message, details, severity } | Audit anomaly alert (admin/monitor) |
integrity-alert | { alertType, fileHash, details } | Storage integrity anomaly alert (admin) |
SSE Authentication Handshake
SSE connections use a short-lived one-time token:
- Call
POST /api/v1/auth/tokens/ssewith regular JWT auth - Connect via
GET /api/v1/sse/connect?token={sseToken}&connectionId={optional}
GET /api/v1/sse/connectis publicly exposed in Spring Security but still requires valid short-lived token authentication.
Monitoring Capacity Semantics
System monitoring now includes:
GET /api/v1/system/storage-capacity
Response shape includes:
- Cluster totals:
totalCapacityBytes,usedCapacityBytes,availableCapacityBytes - Data quality flags:
degraded,source - Detailed aggregates:
nodes[],domains[]
GET /api/v1/system/stats now uses this capacity result for totalStorage. Only when Dubbo capacity RPC fails does it fall back to totalFiles * 1MB, logging marker MONITOR_STORAGE_CAPACITY_FALLBACK.
Download Strategy (Frontend)
Frontend selects download strategy by file size and browser capability:
- Small files: in-memory download
- Large files: prefer streaming download
- Very large files or unsupported browsers: backend proxy / guarded fallback
Default thresholds (platform-frontend/src/lib/utils/fileSize.ts):
| Threshold Constant | Default |
|---|---|
LARGE_FILE_WARNING_THRESHOLD | 500MB |
STREAMING_RECOMMENDED_THRESHOLD | 1GB |
MAX_SAFE_INMEMORY_SIZE | 2GB |
MAX_DOWNLOADABLE_SIZE | 100GB |
Quota Governance
The platform enforces per-user and per-tenant storage quotas with two enforcement modes:
- SHADOW (default): Quota violations are logged and alerted but uploads are not blocked. Use during rollout to observe impact.
- ENFORCE: Uploads exceeding quota are rejected with
50013 QUOTA_EXCEEDED.
Rollout Strategy:
| Property | Description |
|---|---|
quota.enforcement-mode | Global mode: SHADOW or ENFORCE |
quota.rollout.strategy | TENANT_WHITELIST — only whitelisted tenants use ENFORCE |
quota.rollout.enforce-tenant-whitelist | Comma-separated tenant IDs for ENFORCE mode |
quota.rollout.force-shadow | Override to force SHADOW for all tenants |
Reconciliation: A scheduled job (quota.reconcile.cron, default every 30 minutes) recalculates usage snapshots to correct any drift between cached and actual usage.
API Endpoints:
GET /api/v1/files/quota— Current user quota statusPOST /api/v1/admin/quota/rollout/audits— Write rollout audit record (admin)GET /api/v1/admin/quota/rollout/audits— Query rollout audit record (admin)
Batch Download
The frontend supports selecting multiple files for batch download:
- Select: User selects files from the file list
- Parallel Download: Files are downloaded concurrently (up to configurable concurrency limit)
- Auto-Retry: Failed downloads are automatically retried
- Metrics Report: After completion, the frontend reports batch quality metrics via
POST /api/v1/files/download-batches/report
The report payload includes total files, successful/failed counts, retry count, total duration, and error type breakdown — used for backend quality observability.
Keyword Search Modes
File queries (GET /api/v1/files) support a keywordMode parameter that controls how the keyword is matched:
| Mode | File Name Matching | File Hash Matching | Use Case |
|---|---|---|---|
FUZZY (default) | LIKE %keyword% | LIKE %keyword% | General search |
PREFIX | LIKE keyword% | Exact match | Faster indexed lookup |
EXACT_HASH | Not searched | Exact match only | Direct hash lookup |
AUTO | Depends on keyword | Depends on keyword | Smart detection |
AUTO mode inspects the keyword: if it matches the hex hash pattern (/^[0-9a-fA-F]{32,128}$/), it resolves to EXACT_HASH; otherwise it resolves to PREFIX.
Frontend Leader Election
For multi-tab scenarios, frontend uses BroadcastChannel for leader election:
- Leader tab: Maintains single SSE connection
- Follower tabs: Receive events via BroadcastChannel
- Failover: Auto-elect new leader when leader tab closes
This prevents multiple SSE connections from same browser, reducing server load.