# Phase F.1：数据模型升级

日期：2026-04-04
状态：待开始
依赖：F.0（已完成）
前置：models/forum.py

---

## 1. 本阶段目的

升级 Jarvis Forum 的数据模型，支持：
- 多板块系统（指令/讨论/任务等）
- 标签系统（细粒度分类）
- 帖子元数据扩展（阅读量、点赞、收藏等）
- 层级回复结构

---

## 2. 目标模型

### 2.1 ForumBoard（板块）

```python
class ForumBoard(BaseModel):
    __tablename__ = "forum_boards"
    
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    name: str  # 板块名称，如 "指令广场"
    slug: str  # URL 友好名称，如 "instruction"
    description: Optional[str]
    icon: Optional[str]  # emoji 或图标名
    sort_order: int = 0
    is_active: bool = True
    min_role: str = "user"  # 查看权限：guest/user/moderator/admin
    
    parent_id: Optional[str]  # 父板块，支持层级
    color: Optional[str]  # 板块颜色
```

### 2.2 ForumPost（帖子）扩展

```python
class ForumPost(BaseModel):
    __tablename__ = "forum_posts"
    
    # === 现有字段 ===
    user_id: str
    title: str
    content: str
    reply_count: int = 0
    
    # === 新增字段 ===
    board_id: Optional[str] = None  # 所属板块
    parent_id: Optional[str] = None  # 父帖子（支持回复嵌套）
    
    # 分类与标签
    category: Optional[str]  # instruction/discussion/question/praise/bug
    tags: Optional[str] = ""  # JSON 数组 ["python", "api"]
    
    # 状态与权限
    is_pinned: bool = False  # 置顶
    is_locked: bool = False  # 锁定（禁止回复）
    is_deleted: bool = False  # 软删除
    is_executed: bool = False  # 指令已执行
    execution_result: Optional[str]
    
    # 统计与互动
    view_count: int = 0
    like_count: int = 0
    favorite_count: int = 0
    share_count: int = 0
    
    # 时间戳
    last_reply_at: Optional[datetime] = None  # 最后回复时间
    last_activity_at: Optional[datetime] = None
    
    # === 关系 ===
    board = relationship("ForumBoard", back_populates="posts")
    replies = relationship("ForumReply", back_populates="post", cascade="all, delete-orphan")
    post_tags = relationship("ForumPostTag", back_populates="post", cascade="all, delete-orphan")
    likes = relationship("ForumLike", back_populates="post", cascade="all, delete-orphan")
```

### 2.3 ForumReply（回复）扩展

```python
class ForumReply(BaseModel):
    __tablename__ = "forum_replies"
    
    # === 现有字段 ===
    post_id: str
    user_id: Optional[str]
    agent_id: Optional[str]
    content: str
    is_ai_reply: bool = False
    
    # === 新增字段 ===
    parent_id: Optional[str] = None  # 父回复（支持嵌套）
    floor: int = 1  # 楼层号
    is_pinned: bool = False  # 置顶
    is_best: bool = False  # 最佳回复
    is_deleted: bool = False  # 软删除
    like_count: int = 0
    edited_at: Optional[datetime] = None  # 编辑时间
    
    # === 关系 ===
    post = relationship("ForumPost", back_populates="replies")
    parent = relationship("ForumReply", remote_side=[id], backref="children")
    likes = relationship("ForumLike", back_populates="reply", cascade="all, delete-orphan")
```

### 2.4 ForumTag（标签）

```python
class ForumTag(BaseModel):
    __tablename__ = "forum_tags"
    
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    name: str  # 标签名，如 "python"
    slug: str  # URL 友好名
    color: str = "#666666"  # 标签颜色
    post_count: int = 0  # 帖子数量（缓存）
    is_official: bool = False  # 官方标签
    board_id: Optional[str] = None  # 限定板块
    
    posts = relationship("ForumPostTag", back_populates="tag")


class ForumPostTag(BaseModel):
    """帖子-标签关联"""
    __tablename__ = "forum_post_tags"
    
    post_id: str
    tag_id: str
    
    post = relationship("ForumPost", back_populates="post_tags")
    tag = relationship("ForumTag", back_populates="posts")
```

### 2.5 ForumLike（点赞）

```python
class ForumLike(BaseModel):
    __tablename__ = "forum_likes"
    
    user_id: str
    post_id: Optional[str] = None
    reply_id: Optional[str] = None
    created_at: datetime = Field(default_factory=datetime.utcnow)
    
    post = relationship("ForumPost", back_populates="likes")
    reply = relationship("ForumReply", back_populates="likes")
```

### 2.6 ForumStats（用户统计）

```python
class ForumStats(BaseModel):
    """用户论坛统计"""
    __tablename__ = "forum_stats"
    
    user_id: str = Field(primary_key=True)
    post_count: int = 0
    reply_count: int = 0
    like_received: int = 0  # 收到的赞
    best_reply_count: int = 0  # 最佳回复数
    score: int = 0  # 积分
    
    last_post_at: Optional[datetime] = None
    last_reply_at: Optional[datetime] = None
```

---

## 3. 数据库迁移

### 3.1 新增表

```sql
-- 板块表
CREATE TABLE forum_boards (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    slug TEXT UNIQUE NOT NULL,
    description TEXT,
    icon TEXT,
    sort_order INTEGER DEFAULT 0,
    is_active BOOLEAN DEFAULT TRUE,
    min_role TEXT DEFAULT 'user',
    parent_id TEXT,
    color TEXT
);

-- 帖子-标签关联表
CREATE TABLE forum_post_tags (
    post_id TEXT NOT NULL,
    tag_id TEXT NOT NULL,
    PRIMARY KEY (post_id, tag_id)
);

-- 标签表
CREATE TABLE forum_tags (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL UNIQUE,
    slug TEXT UNIQUE NOT NULL,
    color TEXT DEFAULT '#666666',
    post_count INTEGER DEFAULT 0,
    is_official BOOLEAN DEFAULT FALSE,
    board_id TEXT
);

-- 点赞表
CREATE TABLE forum_likes (
    user_id TEXT NOT NULL,
    post_id TEXT,
    reply_id TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id, post_id, reply_id)
);

-- 用户论坛统计表
CREATE TABLE forum_stats (
    user_id TEXT PRIMARY KEY,
    post_count INTEGER DEFAULT 0,
    reply_count INTEGER DEFAULT 0,
    like_received INTEGER DEFAULT 0,
    best_reply_count INTEGER DEFAULT 0,
    score INTEGER DEFAULT 0,
    last_post_at TIMESTAMP,
    last_reply_at TIMESTAMP
);

-- 扩展 forum_posts 表
ALTER TABLE forum_posts ADD COLUMN board_id TEXT;
ALTER TABLE forum_posts ADD COLUMN parent_id TEXT;
ALTER TABLE forum_posts ADD COLUMN tags TEXT DEFAULT '';
ALTER TABLE forum_posts ADD COLUMN is_pinned BOOLEAN DEFAULT FALSE;
ALTER TABLE forum_posts ADD COLUMN is_locked BOOLEAN DEFAULT FALSE;
ALTER TABLE forum_posts ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE;
ALTER TABLE forum_posts ADD COLUMN view_count INTEGER DEFAULT 0;
ALTER TABLE forum_posts ADD COLUMN like_count INTEGER DEFAULT 0;
ALTER TABLE forum_posts ADD COLUMN favorite_count INTEGER DEFAULT 0;
ALTER TABLE forum_posts ADD COLUMN share_count INTEGER DEFAULT 0;
ALTER TABLE forum_posts ADD COLUMN last_reply_at TIMESTAMP;
ALTER TABLE forum_posts ADD COLUMN last_activity_at TIMESTAMP;

-- 扩展 forum_replies 表
ALTER TABLE forum_replies ADD COLUMN parent_id TEXT;
ALTER TABLE forum_replies ADD COLUMN floor INTEGER DEFAULT 1;
ALTER TABLE forum_replies ADD COLUMN is_pinned BOOLEAN DEFAULT FALSE;
ALTER TABLE forum_replies ADD COLUMN is_best BOOLEAN DEFAULT FALSE;
ALTER TABLE forum_replies ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE;
ALTER TABLE forum_replies ADD COLUMN like_count INTEGER DEFAULT 0;
ALTER TABLE forum_replies ADD COLUMN edited_at TIMESTAMP;
```

---

## 4. Schema 扩展

### 4.1 ForumBoardSchema

```python
class ForumBoardCreate(BaseModel):
    name: str = Field(..., max_length=100)
    slug: str = Field(..., max_length=50, pattern=r"^[a-z0-9-]+$")
    description: Optional[str] = None
    icon: Optional[str] = None
    sort_order: int = 0
    min_role: str = "user"
    parent_id: Optional[str] = None
    color: Optional[str] = None


class ForumBoardOut(BaseModel):
    id: str
    name: str
    slug: str
    description: Optional[str]
    icon: Optional[str]
    sort_order: int
    post_count: int = 0  # 动态计算


class ForumPostCreate(BaseModel):
    title: str = Field(..., max_length=200)
    content: str = Field(..., max_length=50000)
    board_id: Optional[str] = None
    category: Optional[str] = None
    tags: List[str] = []


class ForumPostOut(BaseModel):
    id: str
    user_id: str
    title: str
    content: str
    board_id: Optional[str]
    board_name: Optional[str] = None
    category: Optional[str]
    tags: List[str] = []
    reply_count: int
    view_count: int
    like_count: int
    is_pinned: bool
    is_locked: bool
    created_at: datetime
    last_reply_at: Optional[datetime]
    user_liked: bool = False  # 当前用户是否点赞
```

---

## 5. 实现步骤

| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 创建数据库迁移脚本 | 🟢 高 |
| 2 | 扩展 models/forum.py | 🟢 高 |
| 3 | 创建 ForumBoard 模型 | 🟢 高 |
| 4 | 创建 ForumTag/ForumPostTag 模型 | 🟡 中 |
| 5 | 创建 ForumLike 模型 | 🟡 中 |
| 6 | 创建 ForumStats 模型 | 🟡 中 |
| 7 | 扩展 schemas/forum.py | 🟢 高 |
| 8 | 编写单元测试 | 🟡 中 |

---

## 6. 核心文件变更

| 文件 | 变更 |
|------|------|
| `models/forum.py` | 新增 ForumBoard, ForumTag, ForumPostTag, ForumLike, ForumStats；扩展 ForumPost, ForumReply |
| `schemas/forum.py` | 新增 ForumBoardCreate/Out；扩展 ForumPostCreate/Out |
| `database.py` | 注册新模型 |

---

## 7. 工作量估算

| 任务 | 工作量 |
|------|--------|
| 数据库迁移脚本 | 0.5 天 |
| 模型扩展 | 0.5 天 |
| Schema 扩展 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **2 天** |

---

## 8. 验收标准

- [ ] ForumBoard 模型可创建/查询板块
- [ ] ForumPost 可关联板块和标签
- [ ] ForumTag 可创建/查询标签
- [ ] ForumLike 支持点赞功能
- [ ] ForumStats 正确统计用户数据
- [ ] 所有新模型有对应的 Pydantic Schema
- [ ] 迁移脚本可回滚
- [ ] 单元测试覆盖新增功能
