Files
pos-system/microservices/services/storage-service-net/docs/vi/ARCHITECTURE.md
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

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 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

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)
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

  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/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

  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

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

  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