# 云盘应用技术方案文档

**项目名称:** 百度云盘类文件存储Web应用
**创建日期:** 2025-10-11
**技术分析师:** Mary (Business Analyst)
**文档版本:** v1.0

---

## 项目概述

### 核心需求
- **用户认证:** 账号密码登录系统
- **文件管理:** 完整的CRUD操作（增删改查）
- **用户隔离:** 每个用户独立的文件存储空间
- **文件操作:** 拖拽上传 + 批量操作支持
- **文件限制:** 单文件大小限制1MB

### 技术栈
- **前端:** React 19 + shadcn + Vite 5 + TailwindCSS 4
- **后端:** Python 3.12 + FastAPI + MySQL + Redis
- **测试:** 单元测试 + E2E测试

---

## 系统架构设计

### 整体架构图
```
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   前端应用       │    │   后端API       │    │   数据存储       │
│  React 19       │────│  FastAPI       │────│  MySQL + Redis  │
│  shadcn UI      │    │  Python 3.12   │    │  文件系统        │
└─────────────────┘    └─────────────────┘    └─────────────────┘
```

---

## 数据库设计

### 用户表 (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,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    is_active BOOLEAN DEFAULT TRUE
);
```

### 文件表 (files)
```sql
CREATE TABLE files (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    filename VARCHAR(255) NOT NULL,
    path VARCHAR(1000) NOT NULL,           -- 完整路径: "/文档/工作/报告.pdf"
    path_id VARCHAR(100) NOT NULL,         -- 路径标识: "doc-work-report"
    file_size BIGINT NOT NULL,
    file_type VARCHAR(100),
    mime_type VARCHAR(100),
    storage_path VARCHAR(1000) NOT NULL,   -- 服务器存储路径
    is_deleted BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id),
    INDEX idx_user_path (user_id, path),
    INDEX idx_path_id (path_id)
);
```

### 文件操作日志表 (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') NOT NULL,
    operation_details JSON,
    ip_address VARCHAR(45),
    user_agent TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (file_id) REFERENCES files(id)
);
```

---

## API接口设计

### 认证相关

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

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

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

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

Response:
{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
    "token_type": "bearer",
    "expires_in": 3600,
    "user": {
        "id": 1,
        "username": "testuser",
        "email": "test@example.com"
    }
}
```

### 文件操作相关

#### 获取文件列表
```http
GET /api/files?path=/文档
Authorization: Bearer {token}

Response:
{
    "files": [
        {
            "id": 1,
            "filename": "报告.pdf",
            "path": "/文档/工作/报告.pdf",
            "path_id": "doc-work-report",
            "file_size": 1024000,
            "file_type": "pdf",
            "mime_type": "application/pdf",
            "created_at": "2025-10-11T10:30:00Z",
            "updated_at": "2025-10-11T10:30:00Z"
        }
    ],
    "folders": [
        {
            "name": "工作",
            "path": "/文档/工作",
            "path_id": "doc-work"
        }
    ]
}
```

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

FormData:
- file: (binary data)
- path: "/文档"
```

#### 创建文件夹
```http
POST /api/folders
Authorization: Bearer {token}
Content-Type: application/json

{
    "name": "新文件夹",
    "parent_path": "/文档"
}
```

#### 删除文件/文件夹
```http
DELETE /api/files
Authorization: Bearer {token}
Content-Type: application/json

{
    "paths": ["/文档/报告.pdf", "/文档/工作"],
    "permanent": false  // 软删除或永久删除
}
```

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

{
    "operation": "move",  // move, delete, copy
    "items": [
        {"path": "/文档/文件1.pdf", "type": "file"},
        {"path": "/文档/文件夹1", "type": "folder"}
    ],
    "target_path": "/归档"
}
```

---

## 前端组件设计

### 主要组件结构
```
src/
├── components/
│   ├── auth/
│   │   ├── LoginForm.tsx
│   │   └── RegisterForm.tsx
│   ├── file/
│   │   ├── FileList.tsx
│   │   ├── FileItem.tsx
│   │   ├── FileUpload.tsx
│   │   └── FileOperations.tsx
│   ├── layout/
│   │   ├── Sidebar.tsx
│   │   ├── Header.tsx
│   │   └── FileExplorer.tsx
│   └── ui/
│       ├── Button.tsx
│       ├── Input.tsx
│       └── Modal.tsx
├── hooks/
│   ├── useAuth.ts
│   ├── useFiles.ts
│   └── useUpload.ts
└── services/
    ├── api.ts
    └── auth.ts
```

### 关键功能组件

#### 文件上传组件 (FileUpload.tsx)
```typescript
interface FileUploadProps {
  currentPath: string;
  onUploadComplete: (files: UploadedFile[]) => void;
  onError: (error: string) => void;
}

// 支持拖拽上传和点击上传
// 显示上传进度
// 支持批量文件上传
// 文件大小限制检查 (1MB)
```

#### 文件列表组件 (FileList.tsx)
```typescript
interface FileListProps {
  files: FileItem[];
  folders: FolderItem[];
  onFileSelect: (file: FileItem) => void;
  onFolderClick: (folder: FolderItem) => void;
  selectedItems: string[];
  onSelectionChange: (selected: string[]) => void;
}

// 虚拟滚动优化（处理大量文件）
// 支持列表和网格视图切换
// 批量选择功能
// 排序和筛选
```

---

## 后端实现细节

### 文件存储策略

#### 存储路径设计
```
storage/
├── uploads/
│   ├── 2025/
│   │   ├── 10/
│   │   │   ├── 11/
│   │   │   │   └── {user_id}/
│   │   │   │       └── {uuid}_{filename}
└── temp/  # 临时上传目录
```

#### 文件命名规则
- **本地文件名:** `{user_id}/{year}/{month}/{day}/{uuid}_{original_filename}`
- **数据库存储:** 相对路径 `user_id/2025/10/11/uuid_报告.pdf`

### 大文件处理方案

#### 异步处理流程
```python
import asyncio
from fastapi import BackgroundTasks

async def handle_large_upload(file_data: UploadFile, user_id: int, file_path: str):
    # 1. 保存到临时目录
    temp_path = await save_to_temp(file_data)

    # 2. 检查文件大小
    file_size = os.path.getsize(temp_path)

    if file_size > 0.5 * 1024 * 1024:  # 大于0.5MB
        # 3. 异步处理
        background_tasks.add_task(
            process_large_file_async,
            temp_path,
            user_id,
            file_path
        )
        return {"status": "processing", "message": "文件正在处理中"}
    else:
        # 4. 小文件直接处理
        return await process_file_immediately(temp_path, user_id, file_path)

async def process_large_file_async(temp_path: str, user_id: int, file_path: str):
    """异步处理大文件"""
    try:
        # 分块读取和写入
        chunk_size = 1024 * 1024  # 1MB chunks

        final_path = generate_storage_path(user_id, file_path)

        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.01)  # 避免阻塞

        # 更新数据库记录
        await update_file_record(user_id, file_path, final_path)

        # 清理临时文件
        os.remove(temp_path)

    except Exception as e:
        logger.error(f"大文件处理失败: {e}")
        # 保存失败信息到数据库，供前端查询
```

### 用户认证与安全

#### JWT Token配置
```python
from jose import JWTError, jwt
from datetime import datetime, timedelta

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt
```

#### 密码安全
```python
import bcrypt

def hash_password(password: str) -> str:
    salt = bcrypt.gensalt()
    return bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8')

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return bcrypt.checkpw(
        plain_password.encode('utf-8'),
        hashed_password.encode('utf-8')
    )
```

---

## 前端状态管理

### 文件操作状态
```typescript
interface FileState {
  currentPath: string;
  files: FileItem[];
  folders: FolderItem[];
  selectedItems: string[];
  uploading: boolean;
  uploadProgress: Record<string, number>;
  operations: FileOperation[];
}

interface FileOperation {
  id: string;
  type: 'upload' | 'delete' | 'move' | 'copy';
  status: 'pending' | 'in-progress' | 'completed' | 'failed';
  progress: number;
  items: string[];
}
```

### 错误处理策略
```typescript
// 网络错误重试机制
const retryUpload = async (file: File, maxRetries = 3) => {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await uploadFile(file);
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
};

// 断点续传支持
const resumeUpload = async (fileId: string) => {
  const uploadedBytes = await getUploadedBytes(fileId);
  const remainingChunk = file.slice(uploadedBytes);
  // 继续上传剩余部分
};
```

---

## 性能优化策略

### 前端优化
1. **虚拟滚动:** 处理大量文件列表
2. **懒加载:** 文件缩略图和预览
3. **防抖搜索:** 文件搜索功能
4. **缓存策略:** 文件列表缓存

### 后端优化
1. **Redis缓存:** 用户文件列表缓存
2. **数据库索引:** 优化查询性能
3. **异步任务:** 大文件处理不阻塞主线程
4. **连接池:** 数据库连接优化

### 文件存储优化
```python
# 文件分块存储
async def store_file_in_chunks(file_path: str, storage_path: str):
    chunk_size = 1024 * 1024  # 1MB
    chunk_number = 0

    async with aiofiles.open(file_path, 'rb') as src:
        while True:
            chunk = await src.read(chunk_size)
            if not chunk:
                break

            chunk_path = f"{storage_path}.chunk_{chunk_number}"
            async with aiofiles.open(chunk_path, 'wb') as dst:
                await dst.write(chunk)

            chunk_number += 1

    # 合并分块（可选）
    await merge_chunks_if_needed(storage_path, chunk_number)
```

---

## 安全考虑

### 文件安全
1. **文件类型检查:** 限制上传文件类型
2. **病毒扫描:** 集成病毒扫描API
3. **路径遍历防护:** 防止目录遍历攻击
4. **文件大小限制:** 严格执行1MB限制

### 访问控制
```python
# 确保用户只能访问自己的文件
async def get_user_file(user_id: int, file_path: str):
    # 验证文件所有权
    file_record = await db.query(
        "SELECT * FROM files WHERE user_id = ? AND path = ?",
        (user_id, file_path)
    )

    if not file_record:
        raise HTTPException(status_code=404, detail="文件不存在")

    return file_record
```

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

limiter = Limiter(key_func=get_remote_address)

@app.post("/api/files/upload")
@limiter.limit("10/minute")  # 每分钟最多10次上传
async def upload_file(request: Request, ...):
    pass
```

---

## 测试策略

### 后端测试
```python
# 单元测试示例
import pytest
from fastapi.testclient import TestClient

def test_file_upload(client: TestClient, auth_headers: dict):
    file_content = b"test file content"
    response = client.post(
        "/api/files/upload",
        files={"file": ("test.txt", file_content, "text/plain")},
        data={"path": "/"},
        headers=auth_headers
    )
    assert response.status_code == 200
    assert response.json()["filename"] == "test.txt"

def test_user_isolation(client: TestClient):
    # 测试用户A无法访问用户B的文件
    user_a_headers = login_user("user_a", "password")
    user_b_headers = login_user("user_b", "password")

    # 用户A上传文件
    upload_response = client.post(
        "/api/files/upload",
        files={"file": ("secret.txt", b"secret", "text/plain")},
        headers=user_a_headers
    )

    # 用户B尝试访问用户A的文件
    response = client.get(
        "/api/files?path=/",
        headers=user_b_headers
    )
    assert "secret.txt" not in response.text
```

### 前端E2E测试
```typescript
// Playwright E2E测试示例
import { test, expect } from '@playwright/test';

test('文件上传和用户隔离', async ({ page, browser }) => {
  // 用户A登录并上传文件
  const contextA = await browser.newContext();
  const pageA = await contextA.newPage();
  await pageA.goto('/login');
  await pageA.fill('[data-testid=username]', 'userA');
  await pageA.fill('[data-testid=password]', 'password123');
  await pageA.click('[data-testid=login-button]');

  // 上传文件
  await pageA.setInputFiles('[data-testid=file-input]', 'test-files/sample.pdf');
  await pageA.click('[data-testid=upload-button]');
  await expect(pageA.locator('[data-testid=upload-success]')).toBeVisible();

  // 用户B登录，确认看不到用户A的文件
  const contextB = await browser.newContext();
  const pageB = await contextB.newPage();
  await pageB.goto('/login');
  await pageB.fill('[data-testid=username]', 'userB');
  await pageB.fill('[data-testid=password]', 'password123');
  await pageB.click('[data-testid=login-button]');

  await expect(pageB.locator('text=sample.pdf')).not.toBeVisible();
});
```

---

## 部署架构

### Docker容器化
```dockerfile
# backend/Dockerfile
FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .
EXPOSE 8000

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

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

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=0 /app/dist /usr/share/nginx/html
EXPOSE 80
```

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

services:
  frontend:
    build: ./frontend
    ports:
      - "3000:80"
    depends_on:
      - backend

  backend:
    build: ./backend
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=mysql://user:password@mysql:3306/fileapp
      - REDIS_URL=redis://redis:6379
    depends_on:
      - mysql
      - redis

  mysql:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=rootpassword
      - MYSQL_DATABASE=fileapp
      - MYSQL_USER=user
      - MYSQL_PASSWORD=password
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  mysql_data:
  redis_data:
```

---

## 开发计划

### Phase 1: 基础功能 (2周)
- [ ] 用户注册/登录系统
- [ ] 基础文件上传/下载
- [ ] 简单的文件列表显示
- [ ] 用户隔离实现

### Phase 2: 核心功能 (2周)
- [ ] 文件夹创建和管理
- [ ] 拖拽上传功能
- [ ] 批量文件操作
- [ ] 文件删除和重命名

### Phase 3: 高级功能 (2周)
- [ ] 大文件异步处理
- [ ] 文件搜索功能
- [ ] 操作日志记录
- [ ] 性能优化

### Phase 4: 测试和部署 (1周)
- [ ] 单元测试完善
- [ ] E2E测试编写
- [ ] 生产环境部署
- [ ] 性能测试和调优

---

## 总结

这个技术方案为你的云盘应用提供了完整的实现路线图，涵盖了从数据库设计到前端组件，从API接口到部署策略的各个方面。关键的技术决策包括：

1. **数据库设计**: 使用path和path_id双列设计，支持灵活的文件管理
2. **大文件处理**: 异步处理方案，避免阻塞主接口
3. **用户隔离**: 严格的权限控制和数据隔离
4. **性能优化**: 前端虚拟滚动，后端缓存策略
5. **安全考虑**: 多层次的安全防护措施

按照这个方案实施，可以构建出一个功能完整、性能良好、安全可靠的云盘应用。

---

*文档创建于 2025-10-11，由业务分析师Mary协助生成*