999 lines
32 KiB
Markdown
999 lines
32 KiB
Markdown
# 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
|
|
|
|
```mermaid
|
|
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 `Admin` hoặc `SuperAdmin`.
|
|
|
|
## 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
|
|
|
|
```mermaid
|
|
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:**
|
|
1. **Database** = Logical structure (folders, hierarchy, permissions)
|
|
2. **Bucket** = Physical storage (flat UUID keys)
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```csharp
|
|
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)**
|
|
```mermaid
|
|
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**
|
|
```mermaid
|
|
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**
|
|
```sql
|
|
-- 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**
|
|
```sql
|
|
-- 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**
|
|
```sql
|
|
-- 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
|
|
|
|
1. **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
|
|
|
|
2. **Security:**
|
|
- UUID keys không predictable
|
|
- Không có path traversal vulnerabilities
|
|
- Access control trong database
|
|
|
|
3. **Scalability:**
|
|
- Database handles metadata (fast)
|
|
- Bucket handles blobs (simple)
|
|
- Horizontal scaling dễ dàng
|
|
|
|
4. **Flexibility:**
|
|
- Dễ dàng thêm features (sharing, versioning)
|
|
- Migration giữa storage providers
|
|
- Support multiple buckets/regions
|
|
|
|
5. **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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
1. **Truy Cập Có Thời Hạn**: Pre-signed URLs hết hạn sau thời gian cấu hình
|
|
2. **Chống Chỉnh Sửa**: Bất kỳ sửa đổi URL nào đều làm signature không hợp lệ
|
|
3. **Bảo Vệ Credentials**: Access keys MinIO không bao giờ lộ cho client
|
|
4. **URLs Duy Nhất**: Mỗi request tạo ra signature mới
|
|
|
|
### Implementation GetDownloadUrl
|
|
|
|
```csharp
|
|
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
|
|
|
|
```mermaid
|
|
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)
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```csharp
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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)
|
|
|
|
```yaml
|
|
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
|
|
|
|
```yaml
|
|
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
|
|
|
|
1. **Authentication**: Xác thực JWT Bearer qua IAM Service
|
|
2. **Authorization**: Kiểm tra quyền sở hữu trên files
|
|
3. **Input Validation**: Giới hạn kích thước file, xác thực content type
|
|
4. **Pre-signed URLs**: Truy cập file có thời hạn
|
|
5. **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
|
|
|
|
1. **Caching**: In-memory cache cho IAM user info
|
|
2. **Pre-signed URLs**: Client download trực tiếp từ storage
|
|
3. **Streaming Upload**: Xử lý file dựa trên stream
|
|
4. **Async Operations**: Tất cả thao tác I/O đều async
|
|
5. **Connection Pooling**: HTTP client với Polly policies
|
|
|
|
## Tài Liệu Tham Khảo
|
|
|
|
- [MinIO Documentation](https://min.io/docs/minio/)
|
|
- [Aliyun OSS Documentation](https://www.alibabacloud.com/help/oss)
|
|
- [Polly Resilience](https://github.com/App-vNext/Polly)
|
|
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
|