# 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 { /// /// Generate UUID-based key (KHÔNG dựa vào folder path) /// Pattern: {prefix}/{year}/{month}/{day}/{uuid}.{ext} /// 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 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 UploadAsync(Stream stream, string objectKey, ...); Task DownloadAsync(string bucketName, string objectKey); Task DeleteAsync(string bucketName, string objectKey); Task ExistsAsync(string bucketName, string objectKey); Task GetPreSignedDownloadUrlAsync(string bucketName, string objectKey, int expirationSeconds); Task 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 ValidateUserAsync(string accessToken); Task GetUserByIdAsync(string userId, string accessToken); Task UserExistsAsync(string userId, string accessToken); Task> GetUserRolesAsync(string userId, string accessToken); Task> GetUserPermissionsAsync(string userId, string accessToken); Task HasPermissionAsync(string userId, string permission, string accessToken); Task HasRoleAsync(string userId, string role, string accessToken); // Health Check Task CheckHealthAsync(); Task 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)