# 云盘应用系统架构文档

**项目名称:** 云盘应用
**创建日期:** 2025-10-11
**架构师:** Alex
**文档版本:** v1.0

---

## 架构概述

### 系统架构原则

1. **模块化设计**: 前后端分离，组件化开发
2. **可扩展性**: 支持水平扩展和功能扩展
3. **高可用性**: 容错机制和故障恢复
4. **安全性**: 多层安全防护和数据隔离
5. **性能优先**: 响应时间和并发处理优化

### 整体架构图

```mermaid
graph TB
    subgraph "客户端层"
        A[Web浏览器]
        B[移动设备]
    end

    subgraph "负载均衡层"
        C[Nginx反向代理]
    end

    subgraph "应用层"
        D[React前端应用]
        E[FastAPI后端应用]
    end

    subgraph "缓存层"
        F[Redis缓存]
    end

    subgraph "数据层"
        G[MySQL数据库]
        H[文件系统存储]
    end

    subgraph "监控层"
        I[日志监控]
        J[性能监控]
    end

    A --> C
    B --> C
    C --> D
    C --> E
    E --> F
    E --> G
    E --> H
    E --> I
    E --> J
```

---

## 技术栈架构

### 前端技术栈

#### 核心框架
- **React 19**: 最新版本，支持并发特性
- **TypeScript**: 类型安全和开发效率
- **Vite 5**: 快速构建和热更新

#### UI框架
- **TailwindCSS 4**: 原子化CSS框架
- **shadcn/ui**: 现代化组件库
- **Radix UI**: 无障碍访问基础组件

#### 状态管理
- **Zustand**: 轻量级状态管理
- **React Query**: 服务端状态管理和缓存
- **React Hook Form**: 表单状态管理

#### 开发工具
- **ESLint**: 代码质量检查
- **Prettier**: 代码格式化
- **Vitest**: 单元测试框架

### 后端技术栈

#### 核心框架
- **Python 3.12**: 最新稳定版本
- **FastAPI**: 高性能异步Web框架
- **Pydantic V2**: 数据验证和序列化

#### 数据库
- **MySQL 8.0**: 主数据库
- **Redis 7**: 缓存和会话存储
- **SQLAlchemy**: ORM框架
- **Alembic**: 数据库迁移工具

#### 异步处理
- **Celery**: 分布式任务队列
- **RQ**: 简单任务队列
- **BackgroundTasks**: FastAPI后台任务

#### 安全
- **python-jose**: JWT处理
- **bcrypt**: 密码加密
- **python-multipart**: 文件上传处理

---

## 数据库架构设计

### 数据库选择理由

**MySQL 8.0优势:**
- ACID事务支持，确保数据一致性
- 优秀的JSON支持，适合存储操作详情
- 强大的索引和查询优化
- 成熟的生态系统和工具链

**Redis 7优势:**
- 高性能内存存储
- 丰富的数据结构支持
- 持久化和高可用
- 轻量级部署

### 数据库表结构设计

#### 用户表 (users)
```sql
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    avatar_url VARCHAR(500),
    storage_quota BIGINT DEFAULT 104857600,  -- 100MB
    storage_used BIGINT DEFAULT 0,
    is_active BOOLEAN DEFAULT TRUE,
    is_verified BOOLEAN DEFAULT FALSE,
    last_login_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    INDEX idx_username (username),
    INDEX idx_email (email),
    INDEX idx_created_at (created_at)
);
```

#### 文件表 (files) - 优化版
```sql
CREATE TABLE files (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    filename VARCHAR(255) NOT NULL,
    original_filename VARCHAR(255) NOT NULL,
    path VARCHAR(1000) NOT NULL,
    path_hash VARCHAR(64) NOT NULL,  -- SHA256哈希，用于快速查找
    parent_path VARCHAR(1000),
    file_size BIGINT NOT NULL,
    file_type VARCHAR(100),
    mime_type VARCHAR(100),
    file_hash VARCHAR(64),  -- 文件内容哈希，用于去重
    storage_path VARCHAR(1000) NOT NULL,
    processing_status ENUM('pending', 'processing', 'completed', 'failed') DEFAULT 'completed',
    processing_error TEXT,
    is_deleted BOOLEAN DEFAULT FALSE,
    deleted_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    INDEX idx_user_path (user_id, path),
    INDEX idx_user_parent (user_id, parent_path),
    INDEX idx_path_hash (path_hash),
    INDEX idx_file_hash (file_hash),
    INDEX idx_filename (filename),
    INDEX idx_file_type (file_type),
    INDEX idx_created_at (created_at),
    INDEX idx_processing_status (processing_status),
    UNIQUE KEY uk_user_path (user_id, path),
    UNIQUE KEY uk_user_filename_parent (user_id, filename, parent_path)
);
```

#### 文件操作日志表 (file_operations)
```sql
CREATE TABLE file_operations (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    file_id INT,
    operation_type ENUM('upload', 'download', 'delete', 'move', 'rename', 'create_folder') NOT NULL,
    operation_details JSON,
    source_path VARCHAR(1000),
    target_path VARCHAR(1000),
    ip_address VARCHAR(45),
    user_agent TEXT,
    status ENUM('success', 'failed', 'pending') DEFAULT 'success',
    error_message TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (file_id) REFERENCES files(id) ON DELETE SET NULL,
    INDEX idx_user_operation (user_id, operation_type),
    INDEX idx_user_created (user_id, created_at),
    INDEX idx_file_id (file_id),
    INDEX idx_created_at (created_at)
);
```

#### 文件分享表 (file_shares) - 扩展功能
```sql
CREATE TABLE file_shares (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    file_id INT NOT NULL,
    share_token VARCHAR(64) UNIQUE NOT NULL,
    share_type ENUM('public', 'password', 'private') DEFAULT 'private',
    share_password VARCHAR(255),
    expires_at TIMESTAMP NULL,
    download_limit INT DEFAULT 0,
    download_count INT DEFAULT 0,
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (file_id) REFERENCES files(id) ON DELETE CASCADE,
    INDEX idx_share_token (share_token),
    INDEX idx_user_file (user_id, file_id),
    INDEX idx_expires_at (expires_at)
);
```

### 数据库优化策略

#### 索引优化
```sql
-- 复合索引优化查询
CREATE INDEX idx_files_user_parent_name ON files(user_id, parent_path, filename);
CREATE INDEX idx_files_user_type_size ON files(user_id, file_type, file_size);

-- 分区表优化大数据量
CREATE TABLE file_operations_2025 PARTITION OF file_operations
FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');
```

#### 查询优化
```sql
-- 使用CTE优化复杂查询
WITH RECURSIVE file_tree AS (
    SELECT id, filename, path, parent_path, 0 as level
    FROM files
    WHERE user_id = ? AND parent_path = ?

    UNION ALL

    SELECT f.id, f.filename, f.path, f.parent_path, ft.level + 1
    FROM files f
    INNER JOIN file_tree ft ON f.parent_path = ft.path
    WHERE f.user_id = ?
)
SELECT * FROM file_tree ORDER BY level, filename;
```

---

## API架构设计

### RESTful API规范

#### 设计原则
- **资源导向**: URL表示资源，HTTP方法表示操作
- **状态码规范**: 标准HTTP状态码
- **版本控制**: API版本管理
- **错误处理**: 统一错误响应格式

#### API版本控制
```http
# URL版本控制
/api/v1/auth/login
/api/v2/auth/login

# Header版本控制
Accept: application/vnd.api+json;version=1
```

#### 标准响应格式
```json
{
  "success": true,
  "data": {},
  "message": "操作成功",
  "timestamp": "2025-10-11T10:30:00Z",
  "request_id": "req_123456789"
}
```

### API接口详细设计

#### 认证模块 API

##### 用户注册
```http
POST /api/v1/auth/register
Content-Type: application/json

Request:
{
  "username": "testuser",
  "email": "test@example.com",
  "password": "password123",
  "confirm_password": "password123"
}

Response 201:
{
  "success": true,
  "data": {
    "user": {
      "id": 1,
      "username": "testuser",
      "email": "test@example.com",
      "is_verified": false,
      "created_at": "2025-10-11T10:30:00Z"
    }
  },
  "message": "注册成功，请检查邮箱验证"
}

Response 400:
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "输入数据验证失败",
    "details": {
      "username": ["用户名已存在"],
      "email": ["邮箱格式不正确"]
    }
  }
}
```

##### 用户登录
```http
POST /api/v1/auth/login
Content-Type: application/json

Request:
{
  "username": "testuser",
  "password": "password123"
}

Response 200:
{
  "success": true,
  "data": {
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
    "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
    "token_type": "bearer",
    "expires_in": 3600,
    "user": {
      "id": 1,
      "username": "testuser",
      "email": "test@example.com",
      "storage_quota": 104857600,
      "storage_used": 0
    }
  }
}
```

##### Token刷新
```http
POST /api/v1/auth/refresh
Content-Type: application/json
Authorization: Bearer {refresh_token}

Response 200:
{
  "success": true,
  "data": {
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
    "expires_in": 3600
  }
}
```

#### 文件管理 API

##### 获取文件列表
```http
GET /api/v1/files?path=/文档&sort=name&order=asc&type=all&page=1&size=50
Authorization: Bearer {access_token}

Response 200:
{
  "success": true,
  "data": {
    "files": [
      {
        "id": 1,
        "filename": "报告.pdf",
        "original_filename": "项目报告.pdf",
        "path": "/文档/工作/报告.pdf",
        "parent_path": "/文档/工作",
        "file_size": 1024000,
        "file_type": "pdf",
        "mime_type": "application/pdf",
        "processing_status": "completed",
        "created_at": "2025-10-11T10:30:00Z",
        "updated_at": "2025-10-11T10:30:00Z"
      }
    ],
    "folders": [
      {
        "name": "工作",
        "path": "/文档/工作",
        "parent_path": "/文档",
        "file_count": 5,
        "total_size": 5120000,
        "created_at": "2025-10-10T09:00:00Z"
      }
    ],
    "pagination": {
      "page": 1,
      "size": 50,
      "total": 25,
      "pages": 1
    },
    "current_path": "/文档",
    "breadcrumb": [
      {"name": "根目录", "path": "/"},
      {"name": "文档", "path": "/文档"}
    ]
  }
}
```

##### 文件上传
```http
POST /api/v1/files/upload
Authorization: Bearer {access_token}
Content-Type: multipart/form-data

FormData:
- files: (multiple files)
- path: "/文档"

Response 202:
{
  "success": true,
  "data": {
    "upload_id": "upload_123456789",
    "files": [
      {
        "id": null,
        "filename": "小文件.txt",
        "file_size": 1024,
        "processing_status": "completed",
        "upload_url": null
      },
      {
        "id": 2,
        "filename": "大文件.zip",
        "file_size": 2048000,
        "processing_status": "processing",
        "upload_url": "/api/v1/files/upload/chunk/upload_123456789"
      }
    ]
  },
  "message": "文件上传开始，小文件已处理完成，大文件正在处理中"
}
```

##### 分块上传
```http
POST /api/v1/files/upload/chunk/{upload_id}
Authorization: Bearer {access_token}
Content-Type: multipart/form-data

FormData:
- chunk: (file chunk)
- chunk_number: 1
- total_chunks: 5

Response 200:
{
  "success": true,
  "data": {
    "chunk_number": 1,
    "uploaded": true,
    "progress": 20
  }
}
```

##### 文件处理状态查询
```http
GET /api/v1/files/status/{upload_id}
Authorization: Bearer {access_token}

Response 200:
{
  "success": true,
  "data": {
    "upload_id": "upload_123456789",
    "status": "processing",
    "progress": 75,
    "files": [
      {
        "filename": "大文件.zip",
        "status": "processing",
        "progress": 75,
        "error": null
      }
    ]
  }
}
```

##### 文件下载
```http
GET /api/v1/files/{file_id}/download
Authorization: Bearer {access_token}

Response 200:
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="报告.pdf"
Content-Length: 1024000

[file binary data]
```

#### 批量操作 API

##### 批量文件操作
```http
POST /api/v1/files/batch
Authorization: Bearer {access_token}
Content-Type: application/json

Request:
{
  "operation": "move",
  "items": [
    {"id": 1, "type": "file"},
    {"id": 2, "type": "folder"},
    {"path": "/手动输入路径.txt", "type": "file"}
  ],
  "target_path": "/归档"
}

Response 202:
{
  "success": true,
  "data": {
    "batch_id": "batch_123456789",
    "operation": "move",
    "total_items": 3,
    "status_url": "/api/v1/files/batch/batch_123456789"
  }
}
```

##### 批量操作状态查询
```http
GET /api/v1/files/batch/{batch_id}
Authorization: Bearer {access_token}

Response 200:
{
  "success": true,
  "data": {
    "batch_id": "batch_123456789",
    "operation": "move",
    "status": "completed",
    "total_items": 3,
    "completed_items": 3,
    "failed_items": 0,
    "results": [
      {
        "item": {"id": 1, "type": "file"},
        "status": "success",
        "new_path": "/归档/文件.txt"
      }
    ]
  }
}
```

### API安全策略

#### 认证和授权
```python
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import JWTError, jwt

security = HTTPBearer()

async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(security)
) -> User:
    try:
        payload = jwt.decode(
            credentials.credentials,
            SECRET_KEY,
            algorithms=[ALGORITHM]
        )
        user_id: int = payload.get("sub")
        if user_id is None:
            raise HTTPException(status_code=401, detail="无效token")
    except JWTError:
        raise HTTPException(status_code=401, detail="token验证失败")

    user = await get_user_by_id(user_id)
    if user is None:
        raise HTTPException(status_code=401, detail="用户不存在")

    return user
```

#### 权限控制
```python
async def check_file_permission(
    user: User = Depends(get_current_user),
    file_id: int = None,
    file_path: str = None
) -> bool:
    if file_id:
        file = await get_file_by_id(file_id)
    elif file_path:
        file = await get_file_by_path(user.id, file_path)
    else:
        return False

    if file and file.user_id == user.id:
        return True

    raise HTTPException(status_code=403, detail="无权限访问此文件")
```

#### API限流
```python
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

@app.post("/api/v1/files/upload")
@limiter.limit("10/minute")  # 每分钟10次上传
async def upload_file(
    request: Request,
    user: User = Depends(get_current_user)
):
    pass
```

---

## 安全架构设计

### 多层安全防护

#### 1. 网络层安全
- **HTTPS/TLS**: 全站HTTPS加密
- **防火墙**: 端口访问控制
- **DDoS防护**: 流量清洗和限流
- **WAF**: Web应用防火墙

#### 2. 应用层安全
- **输入验证**: 严格的数据验证
- **SQL注入防护**: 参数化查询
- **XSS防护**: 输出编码和CSP
- **CSRF防护**: Token验证

#### 3. 数据层安全
- **数据加密**: 敏感数据加密存储
- **访问控制**: 细粒度权限控制
- **审计日志**: 完整的操作记录
- **备份加密**: 备份数据加密

### 文件安全策略

#### 文件类型检查
```python
ALLOWED_FILE_TYPES = {
    'image': ['.jpg', '.jpeg', '.png', '.gif', '.webp'],
    'document': ['.pdf', '.doc', '.docx', '.txt', '.md'],
    'archive': ['.zip', '.rar', '.7z', '.tar', '.gz'],
    'video': ['.mp4', '.avi', '.mov', '.wmv'],
    'audio': ['.mp3', '.wav', '.flac', '.aac']
}

MAX_FILE_SIZE = 1024 * 1024  # 1MB

def validate_file_type(filename: str, content_type: str) -> bool:
    file_ext = Path(filename).suffix.lower()

    for category, extensions in ALLOWED_FILE_TYPES.items():
        if file_ext in extensions:
            return True

    return False

def scan_file_content(file_content: bytes) -> bool:
    # 简单的恶意文件检查
    dangerous_signatures = [
        b'<script',
        b'javascript:',
        b'vbscript:',
        b'<?php',
        b'<%'
    ]

    for signature in dangerous_signatures:
        if signature in file_content.lower():
            return False

    return True
```

#### 路径遍历防护
```python
import os
from pathlib import Path

def safe_join_path(base_path: str, *paths: str) -> str:
    """安全的路径拼接，防止路径遍历攻击"""
    try:
        # 使用Path.resolve()解析路径，防止../等攻击
        base = Path(base_path).resolve()
        target = base.joinpath(*paths).resolve()

        # 确保目标路径在基础路径内
        if not str(target).startswith(str(base)):
            raise ValueError("路径遍历攻击检测")

        return str(target)
    except (ValueError, RuntimeError) as e:
        raise HTTPException(status_code=400, detail="无效的文件路径")
```

### 数据保护策略

#### 敏感数据加密
```python
from cryptography.fernet import Fernet
import os

# 数据加密密钥
ENCRYPTION_KEY = os.environ.get('ENCRYPTION_KEY')
cipher_suite = Fernet(ENCRYPTION_KEY)

def encrypt_sensitive_data(data: str) -> str:
    """加密敏感数据"""
    return cipher_suite.encrypt(data.encode()).decode()

def decrypt_sensitive_data(encrypted_data: str) -> str:
    """解密敏感数据"""
    return cipher_suite.decrypt(encrypted_data.encode()).decode()
```

#### 数据脱敏
```python
def mask_email(email: str) -> str:
    """邮箱脱敏"""
    username, domain = email.split('@')
    if len(username) <= 2:
        return f"{username[0]}***@{domain}"
    return f"{username[:2]}***@{domain}"

def mask_file_path(file_path: str, show_levels: int = 2) -> str:
    """文件路径脱敏"""
    parts = file_path.strip('/').split('/')
    if len(parts) <= show_levels:
        return file_path

    shown = parts[-show_levels:]
    hidden_count = len(parts) - show_levels
    return f"/.../{'/'.join(shown)}"
```

---

## 性能优化架构

### 前端性能优化

#### 1. 代码分割和懒加载
```typescript
// 路由级别的代码分割
const FileExplorer = lazy(() => import('./components/FileExplorer'));
const UserSettings = lazy(() => import('./components/UserSettings'));

// 组件级别的懒加载
const VirtualizedList = lazy(() => import('./components/VirtualizedList'));
```

#### 2. 虚拟滚动实现
```typescript
import { FixedSizeList as List } from 'react-window';

interface VirtualFileListProps {
  files: FileItem[];
  height: number;
}

const VirtualFileList: React.FC<VirtualFileListProps> = ({ files, height }) => {
  const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
    <div style={style}>
      <FileItem file={files[index]} />
    </div>
  );

  return (
    <List
      height={height}
      itemCount={files.length}
      itemSize={60}
      width="100%"
    >
      {Row}
    </List>
  );
};
```

#### 3. 缓存策略
```typescript
// React Query配置
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5分钟
      cacheTime: 10 * 60 * 1000, // 10分钟
      refetchOnWindowFocus: false,
      retry: 3,
    },
  },
});

// 文件列表查询钩子
const useFileList = (path: string) => {
  return useQuery({
    queryKey: ['files', path],
    queryFn: () => fetchFileList(path),
    staleTime: 2 * 60 * 1000, // 2分钟
    enabled: !!path,
  });
};
```

### 后端性能优化

#### 1. 数据库连接池优化
```python
from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool

# 数据库连接配置
engine = create_engine(
    DATABASE_URL,
    poolclass=QueuePool,
    pool_size=20,          # 连接池大小
    max_overflow=30,       # 最大溢出连接数
    pool_pre_ping=True,    # 连接前检查
    pool_recycle=3600,     # 连接回收时间（秒）
    echo=False
)
```

#### 2. Redis缓存策略
```python
import redis
import json
from typing import Optional, Any

redis_client = redis.Redis(
    host='localhost',
    port=6379,
    db=0,
    decode_responses=True,
    max_connections=50
)

class CacheService:
    @staticmethod
    async def get_file_list(user_id: int, path: str) -> Optional[dict]:
        cache_key = f"files:{user_id}:{hash(path)}"
        cached_data = redis_client.get(cache_key)

        if cached_data:
            return json.loads(cached_data)

        return None

    @staticmethod
    async def set_file_list(user_id: int, path: str, data: dict, ttl: int = 300):
        cache_key = f"files:{user_id}:{hash(path)}"
        redis_client.setex(
            cache_key,
            ttl,
            json.dumps(data, default=str)
        )

    @staticmethod
    async def invalidate_user_cache(user_id: int):
        pattern = f"files:{user_id}:*"
        keys = redis_client.keys(pattern)
        if keys:
            redis_client.delete(*keys)
```

#### 3. 异步文件处理
```python
import asyncio
import aiofiles
from concurrent.futures import ThreadPoolExecutor

class FileProcessor:
    def __init__(self):
        self.executor = ThreadPoolExecutor(max_workers=4)

    async def process_large_file(
        self,
        temp_path: str,
        final_path: str,
        chunk_size: int = 1024 * 1024
    ):
        """异步处理大文件"""
        try:
            async with aiofiles.open(temp_path, 'rb') as src:
                async with aiofiles.open(final_path, 'wb') as dst:
                    while True:
                        chunk = await src.read(chunk_size)
                        if not chunk:
                            break

                        await dst.write(chunk)
                        # 让出控制权，避免阻塞
                        await asyncio.sleep(0.001)

            return {"status": "completed", "message": "文件处理完成"}

        except Exception as e:
            return {"status": "failed", "message": str(e)}

        finally:
            # 清理临时文件
            if os.path.exists(temp_path):
                os.remove(temp_path)
```

### 系统监控和性能分析

#### 1. 性能监控指标
```python
import time
from functools import wraps
from prometheus_client import Counter, Histogram, Gauge

# 性能指标
REQUEST_COUNT = Counter('http_requests_total', 'Total HTTP requests', ['method', 'endpoint'])
REQUEST_DURATION = Histogram('http_request_duration_seconds', 'HTTP request duration')
ACTIVE_CONNECTIONS = Gauge('active_connections', 'Active database connections')

def monitor_performance(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        start_time = time.time()
        REQUEST_COUNT.labels(method='POST', endpoint=func.__name__).inc()

        try:
            result = await func(*args, **kwargs)
            return result
        finally:
            REQUEST_DURATION.observe(time.time() - start_time)

    return wrapper
```

#### 2. 日志分析
```python
import logging
import json
from datetime import datetime

class StructuredLogger:
    def __init__(self, name: str):
        self.logger = logging.getLogger(name)
        self.logger.setLevel(logging.INFO)

        # 结构化日志格式
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        )

        handler = logging.StreamHandler()
        handler.setFormatter(formatter)
        self.logger.addHandler(handler)

    def log_file_operation(
        self,
        user_id: int,
        operation: str,
        file_path: str,
        status: str
    ):
        log_data = {
            "timestamp": datetime.utcnow().isoformat(),
            "user_id": user_id,
            "operation": operation,
            "file_path": file_path,
            "status": status,
            "service": "file-api"
        }

        self.logger.info(json.dumps(log_data))
```

---

## 部署架构设计

### 容器化部署

#### Docker配置
```dockerfile
# backend/Dockerfile
FROM python:3.12-slim

WORKDIR /app

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    gcc \
    default-libmysqlclient-dev \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖文件
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

# 创建非root用户
RUN useradd --create-home --shell /bin/bash app \
    && chown -R app:app /app
USER app

EXPOSE 8000

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
```

```dockerfile
# frontend/Dockerfile
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# 生产镜像
FROM nginx:alpine

# 复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html

# 复制nginx配置
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
```

#### Docker Compose配置
```yaml
version: '3.8'

services:
  # 前端服务
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - backend
    restart: unless-stopped

  # 后端服务
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=mysql://fileapp_user:${DB_PASSWORD}@mysql:3306/fileapp
      - REDIS_URL=redis://redis:6379/0
      - SECRET_KEY=${SECRET_KEY}
      - ENCRYPTION_KEY=${ENCRYPTION_KEY}
    volumes:
      - ./storage:/app/storage
      - ./logs:/app/logs
    depends_on:
      - mysql
      - redis
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # 数据库服务
  mysql:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=fileapp
      - MYSQL_USER=fileapp_user
      - MYSQL_PASSWORD=${DB_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/init:/docker-entrypoint-initdb.d:ro
      - ./mysql/conf:/etc/mysql/conf.d:ro
    ports:
      - "3306:3306"
    restart: unless-stopped
    command: --default-authentication-plugin=mysql_native_password

  # Redis缓存
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
      - ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
    command: redis-server /usr/local/etc/redis/redis.conf
    restart: unless-stopped

  # 监控服务
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3001:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
    volumes:
      - grafana_data:/var/lib/grafana
      - ./monitoring/grafana:/etc/grafana/provisioning:ro
    restart: unless-stopped

volumes:
  mysql_data:
  redis_data:
  prometheus_data:
  grafana_data:
```

### 生产环境部署

#### Kubernetes配置
```yaml
# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: cloud-drive

---
# k8s/backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: cloud-drive
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: backend
        image: cloud-drive/backend:latest
        ports:
        - containerPort: 8000
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: database-url
        - name: REDIS_URL
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: redis-url
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 5

---
# k8s/backend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: backend
  namespace: cloud-drive
spec:
  selector:
    app: backend
  ports:
  - protocol: TCP
    port: 8000
    targetPort: 8000
  type: ClusterIP
```

### CI/CD流水线

#### GitHub Actions配置
```yaml
# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: test_db
        options: >-
          --health-cmd="mysqladmin ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3

      redis:
        image: redis:7
        options: >-
          --health-cmd="redis-cli ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3

    steps:
    - uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.12'

    - name: Install dependencies
      run: |
        cd backend
        pip install -r requirements.txt
        pip install pytest pytest-asyncio

    - name: Run tests
      run: |
        cd backend
        pytest tests/ -v --cov=app --cov-report=xml

    - name: Upload coverage
      uses: codecov/codecov-action@v3

  build-and-deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
    - uses: actions/checkout@v3

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2

    - name: Login to Container Registry
      uses: docker/login-action@v2
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - name: Build and push backend image
      uses: docker/build-push-action@v4
      with:
        context: ./backend
        push: true
        tags: ghcr.io/${{ github.repository }}/backend:latest

    - name: Build and push frontend image
      uses: docker/build-push-action@v4
      with:
        context: ./frontend
        push: true
        tags: ghcr.io/${{ github.repository }}/frontend:latest

    - name: Deploy to Kubernetes
      run: |
        echo "${{ secrets.KUBECONFIG }}" | base64 -d > kubeconfig
        export KUBECONFIG=kubeconfig
        kubectl set image deployment/backend backend=ghcr.io/${{ github.repository }}/backend:latest -n cloud-drive
        kubectl set image deployment/frontend frontend=ghcr.io/${{ github.repository }}/frontend:latest -n cloud-drive
```

---

## 扩展性设计

### 水平扩展策略

#### 1. 无状态应用设计
```python
# 所有状态存储在外部系统（Redis、数据库）
# 应用服务器可以任意水平扩展

# Session存储在Redis
from fastapi_sessions.backends.session import InMemoryBackend

# 文件存储使用分布式文件系统或对象存储
class DistributedFileStorage:
    def __init__(self, storage_type: str = "local"):
        if storage_type == "s3":
            self.client = boto3.client('s3')
        elif storage_type == "minio":
            self.client = Minio('localhost:9000', ...)
        else:
            self.client = LocalFileSystem()
```

#### 2. 数据库分片策略
```python
# 用户数据分片
def get_shard_id(user_id: int, total_shards: int = 4) -> int:
    return user_id % total_shards

class ShardedDatabase:
    def __init__(self, shard_configs: List[dict]):
        self.shards = {}
        for i, config in enumerate(shard_configs):
            self.shards[i] = create_engine(config['url'])

    def get_user_connection(self, user_id: int):
        shard_id = get_shard_id(user_id)
        return self.shards[shard_id]
```

#### 3. 微服务拆分准备
```python
# 为未来微服务化做准备，使用消息队列解耦

import pika
import json

class MessageQueue:
    def __init__(self, rabbitmq_url: str):
        self.connection = pika.BlockingConnection(pika.URLParameters(rabbitmq_url))
        self.channel = self.connection.channel()

    def publish_file_event(self, event_type: str, file_data: dict):
        self.channel.basic_publish(
            exchange='file_events',
            routing_key=event_type,
            body=json.dumps(file_data),
            properties=pika.BasicProperties(
                delivery_mode=2,  # 持久化消息
            )
        )
```

### 监控和可观测性

#### 1. 分布式链路追踪
```python
from opentelemetry import trace
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# 配置链路追踪
trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer(__name__)

jaeger_exporter = JaegerExporter(
    agent_host_name="localhost",
    agent_port=6831,
)

span_processor = BatchSpanProcessor(jaeger_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)

# 使用链路追踪
@tracer.start_as_current_span("file_upload")
async def upload_file(file_data: bytes, user_id: int):
    # 文件上传逻辑
    pass
```

#### 2. 健康检查
```python
from fastapi import APIRouter
from sqlalchemy import text
import redis

router = APIRouter()

@router.get("/health")
async def health_check():
    """基础健康检查"""
    return {"status": "healthy", "timestamp": datetime.utcnow()}

@router.get("/ready")
async def readiness_check():
    """就绪检查 - 检查依赖服务"""
    checks = {
        "database": await check_database(),
        "redis": await check_redis(),
        "storage": await check_storage(),
    }

    all_healthy = all(checks.values())
    status_code = 200 if all_healthy else 503

    return JSONResponse(
        content={"status": "ready" if all_healthy else "not_ready", "checks": checks},
        status_code=status_code
    )

async def check_database() -> bool:
    try:
        async with engine.begin() as conn:
            await conn.execute(text("SELECT 1"))
        return True
    except Exception:
        return False

async def check_redis() -> bool:
    try:
        redis_client.ping()
        return True
    except Exception:
        return False
```

---

## 灾难恢复和备份策略

### 数据备份策略

#### 1. 数据库备份
```bash
#!/bin/bash
# scripts/backup_database.sh

BACKUP_DIR="/backups/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/fileapp_backup_$DATE.sql"

# 创建备份目录
mkdir -p $BACKUP_DIR

# 执行备份
mysqldump \
  --single-transaction \
  --routines \
  --triggers \
  --all-databases \
  -h mysql \
  -u root \
  -p$MYSQL_ROOT_PASSWORD \
  > $BACKUP_FILE

# 压缩备份文件
gzip $BACKUP_FILE

# 删除7天前的备份
find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -delete

echo "Backup completed: $BACKUP_FILE.gz"
```

#### 2. 文件备份
```bash
#!/bin/bash
# scripts/backup_files.sh

BACKUP_DIR="/backups/files"
SOURCE_DIR="/app/storage/uploads"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/files_backup_$DATE.tar.gz"

# 创建备份目录
mkdir -p $BACKUP_DIR

# 执行备份
tar -czf $BACKUP_FILE -C $SOURCE_DIR .

# 删除30天前的备份
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete

echo "Files backup completed: $BACKUP_FILE"
```

### 高可用架构

#### 1. 数据库主从复制
```yaml
# docker-compose.slave.yml
version: '3.8'

services:
  mysql-master:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=rootpassword
      - MYSQL_DATABASE=fileapp
      - MYSQL_SERVER_ID=1
      - MYSQL_LOG_BIN=mysql-bin
      - MYSQL_BINLOG_FORMAT=ROW
    volumes:
      - mysql_master_data:/var/lib/mysql
      - ./mysql/master.cnf:/etc/mysql/conf.d/master.cnf:ro

  mysql-slave:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=rootpassword
      - MYSQL_SERVER_ID=2
      - MYSQL_RELAY_LOG=relay-bin
    volumes:
      - mysql_slave_data:/var/lib/mysql
      - ./mysql/slave.cnf:/etc/mysql/conf.d/slave.cnf:ro
    depends_on:
      - mysql-master

volumes:
  mysql_master_data:
  mysql_slave_data:
```

#### 2. Redis哨兵模式
```yaml
# redis-sentinel.yml
version: '3.8'

services:
  redis-master:
    image: redis:7-alpine
    command: redis-server --port 6379 --requirepass redispassword
    volumes:
      - redis_master_data:/data

  redis-slave:
    image: redis:7-alpine
    command: redis-server --port 6379 --requirepass redispassword --slaveof redis-master 6379 --masterauth redispassword
    depends_on:
      - redis-master
    volumes:
      - redis_slave_data:/data

  redis-sentinel:
    image: redis:7-alpine
    command: redis-sentinel /etc/redis/sentinel.conf
    volumes:
      - ./redis/sentinel.conf:/etc/redis/sentinel.conf:ro
    depends_on:
      - redis-master
      - redis-slave

volumes:
  redis_master_data:
  redis_slave_data:
```

---

## 开发和测试环境

### 开发环境配置

#### Docker开发环境
```yaml
# docker-compose.dev.yml
version: '3.8'

services:
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    ports:
      - "8000:8000"
    volumes:
      - ./backend:/app
      - /app/__pycache__
    environment:
      - DEBUG=true
      - RELOAD=true
      - DATABASE_URL=mysql://root:password@mysql:3306/fileapp_dev
    depends_on:
      - mysql
      - redis
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - VITE_API_URL=http://localhost:8000
    command: npm run dev

  mysql:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=fileapp_dev
    ports:
      - "3306:3306"
    volumes:
      - mysql_dev_data:/var/lib/mysql

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_dev_data:/data

volumes:
  mysql_dev_data:
  redis_dev_data:
```

### 测试策略

#### 测试金字塔
```
    /\
   /  \     E2E Tests (少量)
  /____\
 /      \   Integration Tests (适量)
/__________\ Unit Tests (大量)
```

#### 测试配置
```python
# conftest.py
import pytest
import asyncio
from httpx import AsyncClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.main import app
from app.database import get_db, Base
from app.core.config import settings

# 测试数据库
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

@pytest.fixture(scope="session")
def event_loop():
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()

@pytest.fixture(scope="function")
def db_session():
    Base.metadata.create_all(bind=engine)
    session = TestingSessionLocal()
    try:
        yield session
    finally:
        session.close()
        Base.metadata.drop_all(bind=engine)

@pytest.fixture(scope="function")
def client(db_session):
    def override_get_db():
        try:
            yield db_session
        finally:
            pass

    app.dependency_overrides[get_db] = override_get_db

    with AsyncClient(app=app, base_url="http://test") as ac:
        yield ac

    app.dependency_overrides.clear()
```

#### 性能测试
```python
# tests/performance/test_upload_performance.py
import asyncio
import aiofiles
from pathlib import Path

@pytest.mark.asyncio
async def test_concurrent_uploads(client: AsyncClient):
    """测试并发上传性能"""
    # 创建测试文件
    test_files = []
    for i in range(10):
        file_path = Path(f"test_file_{i}.txt")
        async with aiofiles.open(file_path, 'w') as f:
            await f.write(f"Test content {i}" * 1000)
        test_files.append(file_path)

    # 并发上传
    upload_tasks = []
    for file_path in test_files:
        with open(file_path, 'rb') as f:
            task = client.post(
                "/api/v1/files/upload",
                files={"files": ("test.txt", f, "text/plain")},
                data={"path": "/"}
            )
            upload_tasks.append(task)

    # 等待所有上传完成
    results = await asyncio.gather(*upload_tasks)

    # 验证结果
    for response in results:
        assert response.status_code == 200

    # 清理测试文件
    for file_path in test_files:
        file_path.unlink()
```

---

## 总结

这个系统架构文档为云盘应用提供了全面的技术指导，包括：

### 核心优势
1. **模块化设计**: 前后端分离，组件化开发
2. **高性能**: 异步处理，缓存优化，数据库调优
3. **高可用**: 容错机制，负载均衡，监控告警
4. **安全可靠**: 多层防护，数据加密，权限控制
5. **可扩展**: 水平扩展，微服务准备，分片策略

### 技术亮点
- **大文件异步处理**: 优化用户体验
- **智能缓存策略**: 提升响应速度
- **容器化部署**: 简化运维管理
- **完整监控体系**: 实时性能监控
- **自动化测试**: 保证代码质量

### 实施建议
1. **分阶段实施**: 按照Epic顺序逐步实现
2. **持续优化**: 根据监控数据持续调优
3. **安全第一**: 严格遵循安全规范
4. **文档维护**: 及时更新技术文档

这个架构设计为云盘应用的长期发展奠定了坚实的技术基础，能够支撑业务的快速增长和功能扩展。

---

*架构文档创建完成 - 2025-10-11*
*架构师: Alex*
*项目: 云盘应用*