32 KiB
Kiến Trúc Storage Service
Tài liệu kiến trúc chi tiết cho microservice Storage Service.
Tổng Quan Kiến Trúc
graph TB
subgraph "API Layer"
C[Controllers]
CMD[Commands]
Q[Queries]
B[Behaviors]
end
subgraph "Domain Layer"
SF[StorageFile]
SQ[UserStorageQuota]
DE[Domain Events]
RI[Repository Interfaces]
end
subgraph "Infrastructure Layer"
SP[Storage Providers]
IAM[IAM Client]
R[Repositories]
CTX[DbContext]
end
subgraph "External Services"
MINIO[(MinIO)]
OSS[(Aliyun OSS)]
IAMS[IAM Service]
DB[(PostgreSQL)]
end
C --> CMD
C --> Q
CMD --> B --> SF
Q --> R
R --> CTX --> DB
SF --> DE
CMD --> SP
SP --> MINIO
SP --> OSS
C --> IAM --> IAMS
style C fill:#4a90d9,stroke:#2d5986,color:#fff
style SF fill:#50c878,stroke:#2d8659,color:#fff
style MINIO fill:#c73b3b,stroke:#922b2b,color:#fff
style OSS fill:#ff6b35,stroke:#cc5500,color:#fff
style IAMS fill:#9b59b6,stroke:#7d3c98,color:#fff
Trách Nhiệm Từng Layer
1. Domain Layer (StorageService.Domain)
Trái tim của ứng dụng chứa business logic thuần túy.
| Component | Mục đích |
|---|---|
| StorageFile | Aggregate root cho metadata file và lifecycle |
| UserStorageQuota | Aggregate root cho giới hạn và usage storage của user |
| StorageProvider | Enum: MinIO, AliyunOSS |
| FileAccessLevel | Enum: Private, Public, Shared |
| Domain Events | FileUploadedDomainEvent, FileDeletedDomainEvent, UserQuotaUpdatedDomainEvent |
2. Infrastructure Layer (StorageService.Infrastructure)
Triển khai kỹ thuật và tích hợp bên ngoài:
| Component | Mục đích |
|---|---|
| MinioStorageProvider | Thao tác storage tương thích MinIO S3 |
| AliyunOssStorageProvider | Thao tác Alibaba Cloud OSS |
| StorageProviderFactory | Chọn provider runtime dựa trên config |
| HttpIamServiceClient | Giao tiếp inter-service với IAM |
| FileRepository | EF Core repository cho StorageFile |
| QuotaRepository | EF Core repository cho UserStorageQuota |
3. API Layer (StorageService.API)
Entry point ứng dụng và triển khai CQRS:
| Component | Mục đích |
|---|---|
| FilesController | Endpoints CRUD file (proxy upload cũ) |
| SignedUrlController | Endpoints direct upload (khuyến nghị) |
| QuotaController | Endpoints quota của user |
| SignUploadCommand | Tạo pre-signed upload URLs |
| ConfirmUploadCommand | Xác nhận direct uploads và lưu metadata |
| UploadFileCommand | Xử lý proxy uploads (cũ) |
| DeleteFileCommand | Xử lý xóa file |
| Query Handlers | Xử lý các thao tác đọc |
4. Admin Backoffice Layer
Controllers và Commands cho Admin quản lý storage:
| Component | Mục đích |
|---|---|
| AdminQuotaController | Quản lý quota users (GET all, PUT update) |
| AdminFilesController | Xem/xóa files của tất cả users |
| AdminSharesController | Xem/revoke file shares vi phạm |
| AdminStatisticsController | Dashboard thống kê storage |
| UpdateUserQuotaCommand | Admin cập nhật quota limits |
| AdminDeleteFileCommand | Admin xóa file (bypass ownership) |
| AdminRevokeShareCommand | Admin revoke share vi phạm |
| Admin Query Handlers | Queries với phân trang và filter |
Authorization: Tất cả Admin endpoints yêu cầu role
AdminhoặcSuperAdmin.
Kiến Trúc Direct Upload (Khuyến Nghị)
Cho hệ thống với hàng triệu users, pattern Direct Client Upload được khuyến nghị thay vì proxy upload.
So Sánh Upload Patterns
| Khía cạnh | Proxy Upload (Cũ) | Direct Upload (Khuyến nghị) |
|---|---|---|
| Throughput | ~100-500/giây | ~10,000+/giây |
| Memory mỗi request | 100MB (kích thước file) | ~10KB (chỉ metadata) |
| Latency (file 100MB) | 30-60 giây | 10-20 giây |
| Tải backend | Cao | Tối thiểu |
Luồng Direct Upload
sequenceDiagram
participant Client
participant Storage_Service as Storage Service
participant MinIO
rect rgb(200, 230, 200)
Note over Client,Storage_Service: 1. Yêu cầu Upload URL (nhẹ)
Client->>Storage_Service: POST /api/v1/storage/sign-upload
Storage_Service->>Storage_Service: Validate JWT, Kiểm tra Quota
Storage_Service-->>Client: Pre-signed PUT URL + ObjectKey
end
rect rgb(200, 200, 230)
Note over Client,MinIO: 2. Upload trực tiếp (bỏ qua backend)
Client->>MinIO: PUT file binary vào Pre-signed URL
MinIO-->>Client: 200 OK
end
rect rgb(230, 230, 200)
Note over Client,Storage_Service: 3. Xác nhận Upload (nhẹ)
Client->>Storage_Service: POST /api/v1/storage/confirm-upload
Storage_Service->>MinIO: Xác minh file tồn tại
Storage_Service->>Storage_Service: Lưu metadata, Cập nhật quota
Storage_Service-->>Client: File metadata
end
Kiến Trúc Logical Folder (Data Sovereignty)
⚠️ QUAN TRỌNG: Theo nguyên tắc Data Sovereignty trong microservices, Storage Service phải sở hữu hoàn toàn mô hình dữ liệu của mình.
❌ Anti-pattern: Dựa vào Bucket Structure
BAD APPROACH - Folder structure phản ánh trong bucket:
storage-bucket/
├── users/john/documents/report.pdf
├── users/john/images/photo.jpg
└── users/mary/work/presentation.pptx
VẤN ĐỀ:
- Đổi tên folder "documents" → "docs" = Move hàng triệu files (O(n))
- Move file = Copy + Delete trên bucket (chậm, rủi ro)
- Path predictable → Dễ bị path traversal attack
- Không scale với hàng triệu users
- Khó migrate sang storage provider khác
✅ Correct Approach: Logical Separation
Nguyên tắc:
- Database = Logical structure (folders, hierarchy, permissions)
- Bucket = Physical storage (flat UUID keys)
graph TB
subgraph "Layer Logic - Database PostgreSQL"
F[Folders Table]
FL[Files Table]
F -->|parent_id| F
FL -->|folder_id| F
end
subgraph "Layer Vật Lý - MinIO Bucket"
B[Flat UUID Structure]
B1[private/2026/01/13/uuid1.pdf]
B2[private/2026/01/13/uuid2.jpg]
B3[public/2026/01/14/uuid3.png]
end
FL -.->|storage_key| B
style F fill:#3498db,color:#fff
style FL fill:#2ecc71,color:#fff
style B fill:#e74c3c,color:#fff
Database Schema
-- Folders: Hierarchical tree structure
CREATE TABLE folders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id VARCHAR(255) NOT NULL,
parent_id UUID REFERENCES folders(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
path VARCHAR(1000) NOT NULL, -- Materialized path: /docs/work/2024
level INT NOT NULL DEFAULT 0, -- Tree depth
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE (user_id, parent_id, name),
INDEX idx_user_path (user_id, path)
);
-- Files: Link to logical folders
CREATE TABLE storage_files (
id UUID PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
folder_id UUID REFERENCES folders(id) ON DELETE SET NULL,
file_name VARCHAR(255) NOT NULL,
storage_key VARCHAR(500) UNIQUE, -- Physical UUID key trong bucket
content_type VARCHAR(100),
file_size_bytes BIGINT,
access_level VARCHAR(20), -- Private, Public, Shared
bucket_name VARCHAR(255),
provider INT,
uploaded_at TIMESTAMP,
is_deleted BOOLEAN DEFAULT false,
INDEX idx_folder_id (folder_id),
INDEX idx_storage_key (storage_key)
);
Physical Key Generation Strategy
public class StorageKeyGenerator
{
/// <summary>
/// Generate UUID-based key (KHÔNG dựa vào folder path)
/// Pattern: {prefix}/{year}/{month}/{day}/{uuid}.{ext}
/// </summary>
public string GenerateKey(FileAccessLevel access, string fileName)
{
var now = DateTime.UtcNow;
var prefix = access == FileAccessLevel.Public ? "public" : "private";
var uuid = Guid.NewGuid().ToString("N"); // 32 chars, no hyphens
var ext = Path.GetExtension(fileName);
// Example: private/2026/01/13/d290f1ee6c544b0190e6d701748f0851.pdf
return $"{prefix}/{now:yyyy}/{now:MM}/{now:dd}/{uuid}{ext}";
}
}
Bucket Structure (Flat Physical)
goodgo/
├── private/
│ ├── 2026/
│ │ └── 01/
│ │ └── 13/
│ │ ├── d290f1ee6c544b0190e6d701748f0851.pdf
│ │ ├── a7f3b2c19d8e4f01bcd5e92f7a4d8b63.jpg
│ │ └── f9e4c1a2b7d36e5f8a0c4d9e2b1f7a8c.docx
├── public/
│ └── 2026/
│ └── 01/
│ └── 14/
│ └── c3d8f2e1a9b47c6e5d0f8a3b2e1c7d9f.png
└── shared/
└── 2026/
└── 01/
└── 15/
└── e1f2c3d4a5b6c7e8d9f0a1b2c3d4e5f6.xlsx
KHÔNG CÓ folder user/documents/... trong bucket!
Workflows
1. Tạo Folder (Chỉ Database)
sequenceDiagram
Client->>API: POST /api/v1/folders {name: "documents"}
API->>Database: INSERT INTO folders (name, path)
Database-->>API: folder_id
API-->>Client: {id, name, path: "/documents"}
Note over Client,API: Bucket KHÔNG bị động chạm
2. Upload File vào Folder
sequenceDiagram
Client->>API: POST /sign-upload {fileName, folderId}
API->>Database: Validate folder ownership
API->>StorageKeyGen: Generate UUID key
StorageKeyGen-->>API: private/2026/01/13/{uuid}.pdf
API->>MinIO: Get pre-signed PUT URL
MinIO-->>API: pre-signed URL
API->>Database: INSERT file (folder_id, storage_key)
API-->>Client: {uploadUrl, objectKey}
Client->>MinIO: PUT file binary
Client->>API: POST /confirm-upload
3. List Files trong Folder
-- Database query (fast, indexed)
SELECT * FROM storage_files
WHERE folder_id = '{folder-uuid}'
AND is_deleted = false
ORDER BY uploaded_at DESC;
-- Kết quả trả về client:
{
"folder": {
"id": "folder-123",
"path": "/documents/work"
},
"files": [
{
"id": "file-456",
"name": "report.pdf",
"logicalPath": "/documents/work/report.pdf", -- Hiển thị cho user
"storageKey": "private/2026/01/13/{uuid}.pdf" -- Physical key
}
]
}
4. Đổi Tên Folder
-- Chỉ UPDATE database (instant, O(1))
UPDATE folders
SET name = 'docs',
path = '/docs' -- Update materialized path
WHERE id = '{folder-id}';
-- Update descendant paths
UPDATE folders
SET path = REPLACE(path, '/documents', '/docs')
WHERE path LIKE '/documents/%';
-- Files GIỮ NGUYÊN storage_key - KHÔNG TOUCH bucket!
5. Move File giữa Folders
-- Chỉ UPDATE database (instant, O(1))
UPDATE storage_files
SET folder_id = '{new-folder-id}'
WHERE id = '{file-id}';
-- Physical file VẪN Ở storage_key cũ
-- Không cần Copy/Delete trong bucket
So Sánh Performance
| Thao tác | Anti-pattern (Bucket-based) | Correct (Logical) | Cải thiện |
|---|---|---|---|
| Tạo folder | N/A (virtual) | INSERT vào DB | Instant |
| Đổi tên folder | Copy + Delete hàng triệu files | UPDATE 1-N rows DB | ~1000x nhanh hơn |
| Move file | Copy + Delete 1 file | UPDATE 1 row DB | ~100x nhanh hơn |
| List files | Bucket prefix scan | Indexed DB query | ~50x nhanh hơn |
| Delete folder | Delete hàng triệu files | Cascade delete + background cleanup | Async |
Lợi Ích Data Sovereignty
-
Performance:
- Rename folder: O(1) thay vì O(n)
- Move file: O(1) thay vì O(1) bucket copy
- List files: Indexed query thay vì bucket scan
-
Security:
- UUID keys không predictable
- Không có path traversal vulnerabilities
- Access control trong database
-
Scalability:
- Database handles metadata (fast)
- Bucket handles blobs (simple)
- Horizontal scaling dễ dàng
-
Flexibility:
- Dễ dàng thêm features (sharing, versioning)
- Migration giữa storage providers
- Support multiple buckets/regions
-
Developer Experience:
- Client nhìn thấy folder tree đẹp
- Backend làm việc với simple UUIDs
- Clean separation of concerns
Components Direct Upload
| Component | Mục đích |
|---|---|
| SignUploadCommand | Validate quota, tạo object key với path prefix, tạo pre-signed PUT URL |
| SignUploadCommandHandler | Xử lý yêu cầu sign-upload |
| ConfirmUploadCommand | Xác minh file tồn tại, lưu metadata, cập nhật quota |
| ConfirmUploadCommandHandler | Xử lý confirm-upload với idempotency |
| SignedUrlController | Endpoints /sign-upload và /confirm-upload |
Kiến Trúc Download URL
Storage Service tạo các loại download URL khác nhau dựa trên access level của file.
Luồng Tạo URL
sequenceDiagram
participant Client
participant API as Storage Service
participant MinIO
Client->>API: GET /api/v1/files/{id}/download-url
API->>API: Validate ownership
alt accessLevel = Public
API-->>Client: Direct URL (không có signature)
else accessLevel = Private/Shared
API->>MinIO: GetPreSignedDownloadUrl()
MinIO-->>API: Pre-signed URL (AWS Signature V4)
API-->>Client: Pre-signed URL với thời hạn
end
Loại URL Theo Access Level
| Access Level | Storage Prefix | Loại URL | Hết hạn |
|---|---|---|---|
| Public | public/ |
Direct URL | Không bao giờ |
| Private | private/ |
Pre-signed URL | Có thể cấu hình (mặc định: 1 giờ) |
| Shared | shared/ |
Pre-signed URL | Có thể cấu hình (mặc định: 1 giờ) |
Cấu Trúc Pre-signed URL
Với files private/shared, URLs bao gồm các tham số AWS Signature Version 4:
http://minio:9000/bucket/private/2026/01/13/{uuid}.pdf
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential={accessKey}%2F{date}%2F{region}%2Fs3%2Faws4_request
&X-Amz-Date={timestamp}
&X-Amz-Expires={seconds}
&X-Amz-SignedHeaders=host
&X-Amz-Signature={signature}
| Tham số | Mục đích |
|---|---|
X-Amz-Algorithm |
Thuật toán ký (luôn là AWS4-HMAC-SHA256) |
X-Amz-Credential |
Access key + scope (date/region/service) |
X-Amz-Date |
Thời điểm tạo signature |
X-Amz-Expires |
Thời hạn URL tính bằng giây |
X-Amz-SignedHeaders |
Headers được bao gồm trong tính toán signature |
X-Amz-Signature |
Chữ ký mã hóa để xác minh tính toàn vẹn URL |
Cân Nhắc Bảo Mật
- Truy Cập Có Thời Hạn: Pre-signed URLs hết hạn sau thời gian cấu hình
- Chống Chỉnh Sửa: Bất kỳ sửa đổi URL nào đều làm signature không hợp lệ
- Bảo Vệ Credentials: Access keys MinIO không bao giờ lộ cho client
- URLs Duy Nhất: Mỗi request tạo ra signature mới
Implementation GetDownloadUrl
public async Task<string> GetDownloadUrlAsync(StorageFile file)
{
if (file.AccessLevel == FileAccessLevel.Public)
{
// Direct URL cho files public
return $"{_settings.PublicEndpoint}/{file.BucketName}/{file.ObjectKey}";
}
// Pre-signed URL cho files private/shared
return await _storageProvider.GetPreSignedDownloadUrlAsync(
file.BucketName,
file.ObjectKey,
_settings.PreSignedUrlExpirationSeconds
);
}
Kiến Trúc Multipart Upload (File Lớn)
Cho files lớn hơn 100MB, sử dụng Multipart Upload để upload theo chunks.
So Sánh Upload Methods
| Khía cạnh | Direct Upload | Multipart Upload |
|---|---|---|
| Kích thước file | < 100MB | > 100MB (lên đến 5GB+) |
| Mechanism | Single PUT request | Multiple part uploads |
| Resume support | Không | Có (từng part) |
| Progress tracking | Không | Có (theo từng part) |
| Use case | Files nhỏ/trung bình | Files lớn, video, archives |
Luồng Multipart Upload
sequenceDiagram
participant Client
participant API as Storage Service
participant DB as PostgreSQL
participant MinIO
rect rgb(200, 230, 200)
Note over Client,API: 1. Initiate Upload
Client->>API: POST /api/v1/files/multipart/initiate
API->>DB: Create MultipartUpload record
API->>MinIO: InitiateMultipartUpload
MinIO-->>API: Provider UploadId
API-->>Client: {uploadId, objectKey, totalChunks}
end
rect rgb(200, 200, 230)
Note over Client,MinIO: 2. Upload Parts (repeat for each chunk)
loop For each part 1..N
Client->>API: POST /api/v1/files/multipart/upload-part
API->>MinIO: UploadPart(partNumber, data)
MinIO-->>API: ETag
API->>DB: Save part info (partNumber, etag)
API-->>Client: {success, etag}
end
end
rect rgb(230, 200, 200)
Note over Client,API: 3. Optional: Check Progress
Client->>API: GET /api/v1/files/multipart/{uploadId}
API->>DB: Get upload + parts
API-->>Client: {progress: 75%, uploadedChunks: 3/4}
end
rect rgb(230, 230, 200)
Note over Client,API: 4. Complete Upload
Client->>API: POST /api/v1/files/multipart/complete
API->>MinIO: CompleteMultipartUpload(parts[])
MinIO-->>API: OK
API->>DB: Create StorageFile, Update quota
API-->>Client: {fileId, objectKey}
end
Database Schema (Multipart)
-- Tracking multipart upload sessions
CREATE TABLE multipart_uploads (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id VARCHAR(255) NOT NULL,
file_name VARCHAR(255) NOT NULL,
content_type VARCHAR(100),
total_size_bytes BIGINT NOT NULL,
chunk_size_bytes INT NOT NULL,
total_chunks INT NOT NULL,
status VARCHAR(50) DEFAULT 'InProgress', -- InProgress, Completed, Aborted, Failed
provider_upload_id VARCHAR(255),
bucket_name VARCHAR(255) NOT NULL,
object_key VARCHAR(500) NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
completed_at TIMESTAMP,
expires_at TIMESTAMP,
INDEX idx_user_status (user_id, status)
);
-- Tracking individual parts
CREATE TABLE multipart_upload_parts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
multipart_upload_id UUID REFERENCES multipart_uploads(id) ON DELETE CASCADE,
part_number INT NOT NULL,
etag VARCHAR(255) NOT NULL,
size_bytes BIGINT NOT NULL,
uploaded_at TIMESTAMP DEFAULT NOW(),
UNIQUE (multipart_upload_id, part_number)
);
Multipart Upload Endpoints
| Method | Endpoint | Mô tả |
|---|---|---|
POST |
/api/v1/files/multipart/initiate |
Khởi tạo upload session |
POST |
/api/v1/files/multipart/upload-part |
Upload 1 chunk |
POST |
/api/v1/files/multipart/complete |
Hoàn thành và merge parts |
DELETE |
/api/v1/files/multipart/abort |
Hủy upload, cleanup |
GET |
/api/v1/files/multipart/{uploadId} |
Kiểm tra tiến độ |
Components Multipart Upload
| Component | Mục đích |
|---|---|
| InitiateMultipartUploadCommand | Tạo upload session, generate object key |
| UploadPartCommand | Upload 1 part lên storage provider |
| CompleteMultipartUploadCommand | Merge parts, tạo StorageFile record |
| AbortMultipartUploadCommand | Cleanup parts, đánh dấu aborted |
| GetMultipartUploadProgressQuery | Lấy tiến độ upload |
| MultipartUploadController | API endpoints cho multipart |
Kiến Trúc Storage Provider
graph TD
subgraph "Storage Provider Factory"
F[StorageProviderFactory]
C[Configuration]
end
subgraph "Providers"
MP[MinioStorageProvider]
AP[AliyunOssStorageProvider]
end
subgraph "Storage Backends"
MINIO[(MinIO)]
OSS[(Aliyun OSS)]
end
C --> |STORAGE_PROVIDER=minio| F
C --> |STORAGE_PROVIDER=aliyun| F
F --> |GetProvider| MP
F --> |GetProvider| AP
MP --> MINIO
AP --> OSS
style F fill:#4a90d9,stroke:#2d5986,color:#fff
style MP fill:#c73b3b,stroke:#922b2b,color:#fff
style AP fill:#ff6b35,stroke:#cc5500,color:#fff
Interface Storage Provider
public interface IStorageProvider
{
Task<UploadResult> UploadAsync(Stream stream, string objectKey, ...);
Task<Stream> DownloadAsync(string bucketName, string objectKey);
Task DeleteAsync(string bucketName, string objectKey);
Task<bool> ExistsAsync(string bucketName, string objectKey);
Task<string> GetPreSignedDownloadUrlAsync(string bucketName, string objectKey, int expirationSeconds);
Task<string> GetPreSignedUploadUrlAsync(string bucketName, string objectKey, int expirationSeconds);
}
Giao Tiếp Inter-Service
sequenceDiagram
participant Client
participant Storage as Storage Service
participant Cache as In-Memory Cache
participant IAM as IAM Service
Client->>Storage: Upload File (JWT)
Storage->>Cache: Kiểm tra cache user
alt Cache Hit
Cache-->>Storage: Thông tin user
else Cache Miss
Storage->>IAM: GET /api/v1/users/me
Note over Storage,IAM: Headers: Authorization, X-Service-Name
IAM-->>Storage: Thông tin user
Storage->>Cache: Lưu (5 phút TTL)
end
Storage->>Storage: Validate quota
Storage->>Storage: Upload lên provider
Storage-->>Client: Kết quả upload
Tính Năng IAM Client
| Tính năng | Mô tả |
|---|---|
| Caching | In-memory cache cho user info (5 phút TTL) |
| Health Check | Kiểm tra IAM availability với caching (1 phút TTL) |
| Polly Resilience | Retry (3x exponential) + Circuit Breaker |
| Permission Check | HasPermissionAsync, HasRoleAsync |
Các Phương Thức IAM Client
// Thao tác User
Task<IamUserInfo?> ValidateUserAsync(string accessToken);
Task<IamUserInfo?> GetUserByIdAsync(string userId, string accessToken);
Task<bool> UserExistsAsync(string userId, string accessToken);
Task<IReadOnlyList<string>> GetUserRolesAsync(string userId, string accessToken);
Task<IReadOnlyList<string>> GetUserPermissionsAsync(string userId, string accessToken);
Task<bool> HasPermissionAsync(string userId, string permission, string accessToken);
Task<bool> HasRoleAsync(string userId, string role, string accessToken);
// Health Check
Task<IamHealthStatus> CheckHealthAsync();
Task<bool> IsAvailableAsync();
// Quản lý Cache
void InvalidateUserCache(string userId);
void ClearCache();
Database Schema
erDiagram
storage_files {
uuid id PK
varchar file_name
varchar bucket_name
varchar object_key
varchar content_type
bigint file_size_bytes
varchar user_id
varchar tenant_id
int provider
int access_level
timestamp uploaded_at
timestamp expires_at
varchar checksum
boolean is_deleted
timestamp deleted_at
}
user_storage_quotas {
uuid id PK
varchar user_id UK
bigint max_storage_bytes
bigint used_storage_bytes
int max_file_count
int current_file_count
varchar quota_tier
timestamp last_updated_at
timestamp created_at
}
API Endpoints
Files (Proxy Upload Cũ)
| Method | Endpoint | Mô tả |
|---|---|---|
POST |
/api/v1/files/upload |
Upload file qua backend (tối đa 100MB) |
GET |
/api/v1/files |
Danh sách file với phân trang |
GET |
/api/v1/files/{id} |
Lấy metadata file |
GET |
/api/v1/files/{id}/download-url |
Lấy pre-signed download URL |
DELETE |
/api/v1/files/{id} |
Xóa file (soft delete) |
Direct Upload (Khuyến Nghị)
| Method | Endpoint | Mô tả |
|---|---|---|
POST |
/api/v1/storage/sign-upload |
Lấy pre-signed PUT URL để upload trực tiếp |
POST |
/api/v1/storage/confirm-upload |
Xác nhận upload và lưu metadata |
Quota
| Method | Endpoint | Mô tả |
|---|---|---|
GET |
/api/v1/quota |
Lấy quota storage của user |
Admin API (Yêu cầu Role: Admin)
Quản lý Quota
| Method | Endpoint | Mô tả |
|---|---|---|
GET |
/api/v1/admin/quotas |
Lấy quota tất cả users |
GET |
/api/v1/admin/quotas/{userId} |
Lấy quota user cụ thể |
PUT |
/api/v1/admin/quotas/{userId} |
Cập nhật quota limits |
Quản lý Files
| Method | Endpoint | Mô tả |
|---|---|---|
GET |
/api/v1/admin/files |
Xem tất cả files |
DELETE |
/api/v1/admin/files/{id} |
Xóa file vi phạm |
Quản lý Shares
| Method | Endpoint | Mô tả |
|---|---|---|
GET |
/api/v1/admin/shares |
Xem tất cả shares |
DELETE |
/api/v1/admin/shares/{id} |
Revoke share |
Thống kê
| Method | Endpoint | Mô tả |
|---|---|---|
GET |
/api/v1/admin/statistics |
Dashboard thống kê |
GET |
/api/v1/admin/statistics/users-near-limit |
Users gần hết quota |
Kiến Trúc File Versioning
Storage Service duy trì lịch sử phiên bản cho files, cho phép users theo dõi thay đổi và khôi phục phiên bản trước.
Luồng Versioning
sequenceDiagram
participant Client
participant API as Storage Service
participant DB as PostgreSQL
participant MinIO
rect rgb(44, 62, 80)
Note over Client,API: 1. Upload Phiên Bản Mới
Client->>API: PUT /files/{id} (file mới)
API->>DB: Tạo FileVersion (version N+1)
API->>MinIO: Lưu object mới với version key
API->>DB: Cập nhật StorageFile.ObjectKey
API-->>Client: {versionNumber, fileId}
end
rect rgb(39, 174, 96)
Note over Client,API: 2. Danh Sách Versions
Client->>API: GET /files/{id}/versions
API->>DB: SELECT * FROM file_versions WHERE file_id = ?
API-->>Client: [{version: 1, ...}, {version: 2, ...}]
end
rect rgb(230, 126, 34)
Note over Client,API: 3. Khôi Phục Version
Client->>API: POST /files/{id}/versions/{N}/restore
API->>DB: Lấy FileVersion N
API->>DB: Cập nhật StorageFile từ version
API-->>Client: {restored: true, versionNumber: N}
end
Entity FileVersion
| Thuộc tính | Type | Mô tả |
|---|---|---|
Id |
Guid |
Version ID |
FileId |
Guid |
Tham chiếu file cha |
VersionNumber |
int |
Số phiên bản tuần tự |
ObjectKey |
string |
Storage key cho version này |
SizeBytes |
long |
Kích thước file của version |
ContentType |
string |
MIME type |
Checksum |
string? |
Hash tùy chọn |
CreatedAt |
DateTime |
Thời gian tạo version |
CreatedBy |
string |
User tạo version |
IsCurrent |
bool |
Có phải version hiện tại không |
Kiến Trúc File Sharing
Storage Service cung cấp chia sẻ file an toàn qua token-based access với bảo vệ mật khẩu, hết hạn, và giới hạn download tùy chọn.
Luồng Share
sequenceDiagram
participant Owner
participant API as Storage Service
participant DB as PostgreSQL
participant Guest
rect rgb(44, 62, 80)
Note over Owner,API: 1. Tạo Share
Owner->>API: POST /storage/files/{id}/shares
API->>DB: Tạo FileShare (token, options)
API-->>Owner: {shareToken, shareUrl}
end
rect rgb(39, 174, 96)
Note over Guest,API: 2. Truy Cập Share (Công khai)
Guest->>API: GET /storage/shares/public/{token}
API->>DB: Validate share (hết hạn? thu hồi? giới hạn?)
API->>DB: Kiểm tra mật khẩu tùy chọn
API->>DB: Tăng số lần download
API-->>Guest: {downloadUrl, fileMetadata}
end
rect rgb(192, 57, 43)
Note over Owner,API: 3. Thu Hồi Share
Owner->>API: DELETE /storage/shares/{id}
API->>DB: Đặt status = Revoked
API-->>Owner: 204 No Content
end
Entity FileShare
| Thuộc tính | Type | Mô tả |
|---|---|---|
Id |
Guid |
Share ID |
FileId |
Guid |
File được chia sẻ |
SharedBy |
string |
User tạo share |
SharedWith |
string? |
User cụ thể (null = link công khai) |
Permission |
SharePermission |
View, Download, Edit, Admin |
ShareToken |
string |
Token URL-safe duy nhất |
PasswordHash |
string? |
Mật khẩu tùy chọn (PBKDF2) |
ExpiresAt |
DateTime? |
Hết hạn tùy chọn |
MaxDownloads |
int? |
Giới hạn download tùy chọn |
DownloadCount |
int |
Số lần download hiện tại |
Status |
FileShareStatus |
Active, Expired, Revoked, LimitReached |
Quyền Share
| Permission | Xem | Download | Sửa | Quản lý |
|---|---|---|---|---|
| View | ✅ | ❌ | ❌ | ❌ |
| Download | ✅ | ✅ | ❌ | ❌ |
| Edit | ✅ | ✅ | ✅ | ❌ |
| Admin | ✅ | ✅ | ✅ | ✅ |
Health Checks
graph TD
HC[Health Check Endpoint]
HC --> |/health/live| L[Liveness]
HC --> |/health/ready| R[Readiness]
R --> DB[(PostgreSQL)]
R --> MINIO[(MinIO)]
R --> IAM[IAM Service]
style HC fill:#2C3E50,color:#ECF0F1,stroke:#34495E,stroke-width:3px
style L fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px
style R fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
style DB fill:#34495E,color:#ECF0F1,stroke:#2C3E50,stroke-width:2px
style MINIO fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
style IAM fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:2px
Kiến Trúc Deployment
Docker Compose (Local)
services:
storage-api:
build: .
ports: ["5002:8080"]
depends_on:
- postgres
- redis
- minio
environment:
- Storage__Provider=minio
- Storage__MinIO__Endpoint=minio:9000
minio:
image: minio/minio:latest
ports: ["9000:9000", "9001:9001"]
Tích Hợp Traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.storage-service.rule=PathPrefix(`/api/v1/files`) || PathPrefix(`/api/v1/quota`)"
- "traefik.http.services.storage-service.loadbalancer.server.port=8080"
Cân Nhắc Bảo Mật
- Authentication: Xác thực JWT Bearer qua IAM Service
- Authorization: Kiểm tra quyền sở hữu trên files
- Input Validation: Giới hạn kích thước file, xác thực content type
- Pre-signed URLs: Truy cập file có thời hạn
- Soft Delete: Files được đánh dấu xóa, không xóa ngay lập tức
Tối Ưu Hiệu Suất
- Caching: In-memory cache cho IAM user info
- Pre-signed URLs: Client download trực tiếp từ storage
- Streaming Upload: Xử lý file dựa trên stream
- Async Operations: Tất cả thao tác I/O đều async
- Connection Pooling: HTTP client với Polly policies