feat(agents): Phase 8.4-10.5 built-in plugins, bundled skills, coordinator
This commit is contained in:
174
development-doc/plan/forum-update/README.md
Normal file
174
development-doc/plan/forum-update/README.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# Jarvis Forum 升级计划索引
|
||||
|
||||
本目录用于存放 Jarvis 论坛系统的分阶段升级规划文档。
|
||||
|
||||
## 文档说明
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `README.md` | 总览、阶段关系、实施顺序 |
|
||||
| `phase-f-0-current-state.md` | 当前现状、问题、目标架构、VCPToolBox 借鉴 |
|
||||
| `phase-f-1-data-model.md` | 数据模型升级 |
|
||||
| `phase-f-2-forum-api.md` | API 增强与安全 |
|
||||
| `phase-f-3-permissions.md` | 权限系统 |
|
||||
| `phase-f-4-ai-integration.md` | AI 集成 |
|
||||
| `checklist.md` | 执行清单 |
|
||||
|
||||
## 推荐阅读顺序
|
||||
|
||||
1. 先读 `phase-f-0-current-state.md`
|
||||
2. 再按顺序阅读 phase f-1 ~ f-4
|
||||
3. 实施时严格按阶段推进
|
||||
4. 参考 `checklist.md` 进行任务追踪
|
||||
|
||||
---
|
||||
|
||||
## 总体升级原则
|
||||
|
||||
1. **安全第一** - 输入验证、路径安全、并发控制
|
||||
2. **可扩展性** - 支持多板块、标签、分类
|
||||
3. **AI 增强** - AI 自动回复、摘要生成、智能分类
|
||||
4. **测试优先** - 所有升级都要配套测试
|
||||
5. **向后兼容** - 保持现有 API 兼容性
|
||||
|
||||
---
|
||||
|
||||
## 阶段总览图
|
||||
|
||||
```
|
||||
F.0 ──────────────────────────────────────────────────────────────┐
|
||||
│ 现状与目标 │
|
||||
│ - 当前架构分析 │
|
||||
│ - 短板识别 │
|
||||
│ - VCPToolBox VCP论坛 借鉴 │
|
||||
└────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
F.1 ──────────────────────────────────────────────────────────────┐
|
||||
│ 数据模型升级 │
|
||||
│ - 板块/分类系统 │
|
||||
│ - 标签系统 │
|
||||
│ - 帖子元数据扩展 │
|
||||
│ │
|
||||
│ 核心文件: models/forum.py │
|
||||
│ 工作量: 2 天 │
|
||||
└────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
F.2 ──────────────────────────────────────────────────────────────┐
|
||||
│ API 增强与安全 │
|
||||
│ - 文件锁机制 │
|
||||
│ - 输入验证强化 │
|
||||
│ - 并发控制 │
|
||||
│ - API 端点扩展 │
|
||||
│ │
|
||||
│ 核心文件: routers/forum.py, services/forum_service.py │
|
||||
│ 依赖: F.1 │
|
||||
│ 工作量: 3 天 │
|
||||
└────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
F.3 ──────────────────────────────────────────────────────────────┐
|
||||
│ 权限系统 │
|
||||
│ - 用户角色管理 │
|
||||
│ - 板块权限控制 │
|
||||
│ - 操作日志 │
|
||||
│ - 积分/奖励系统 │
|
||||
│ │
|
||||
│ 核心文件: models/user.py, services/forum_service.py │
|
||||
│ 依赖: F.2 │
|
||||
│ 工作量: 3 天 │
|
||||
└────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
F.4 ──────────────────────────────────────────────────────────────┐
|
||||
│ AI 集成 │
|
||||
│ - AI 自动回复 │
|
||||
│ - 智能摘要生成 │
|
||||
│ - 智能分类打标 │
|
||||
│ - Agent 自主发帖 │
|
||||
│ │
|
||||
│ 核心文件: services/forum_ai_service.py │
|
||||
│ 依赖: F.3 │
|
||||
│ 工作量: 5 天 │
|
||||
└────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## VCPToolBox VCP论坛 核心借鉴
|
||||
|
||||
| 借鉴点 | 实现位置 | 难度 |
|
||||
|--------|---------|------|
|
||||
| 文件锁机制(并发控制) | F.2 | 🟡 中 |
|
||||
| 严格输入验证 | F.2 | 🟢 低 |
|
||||
| 安全路径检查 | F.2 | 🟢 低 |
|
||||
| 板块/分类系统 | F.1 | 🟢 低 |
|
||||
| 标签系统 | F.1 | 🟢 低 |
|
||||
| 权限管理 | F.3 | 🟡 中 |
|
||||
| 积分系统 | F.3 | 🟡 中 |
|
||||
| AI 自动回复 | F.4 | 🟡 中 |
|
||||
| Agent 自主发帖 | F.4 | 🟡 中 |
|
||||
|
||||
---
|
||||
|
||||
## 实施顺序
|
||||
|
||||
```
|
||||
F.0 → F.1 → F.2 → F.3 → F.4
|
||||
│ │ │ │ │
|
||||
│ │ │ │ └── AI集成
|
||||
│ │ │ └── 权限/积分
|
||||
│ │ └── API增强/安全
|
||||
│ └── 数据模型
|
||||
└── 现状与目标
|
||||
```
|
||||
|
||||
**注意:** F.1 是基础,后续阶段都依赖 F.1;F.4 需要 Agent 能力成熟。
|
||||
|
||||
---
|
||||
|
||||
## 文件变更追踪
|
||||
|
||||
| Phase | 新增文件 | 修改文件 |
|
||||
|-------|---------|---------|
|
||||
| F.1 | - | `models/forum.py`, `schemas/forum.py` |
|
||||
| F.2 | `services/forum_service.py` | `routers/forum.py` |
|
||||
| F.3 | `models/user.py` (扩展角色), `services/permission_service.py` | `routers/forum.py`, `services/forum_service.py` |
|
||||
| F.4 | `services/forum_ai_service.py`, `services/summary_service.py` | `routers/forum.py`, `agents/` |
|
||||
|
||||
---
|
||||
|
||||
## 与 Agent Phase 1-5 的关系
|
||||
|
||||
| Agent Phase | Forum 协作内容 |
|
||||
|-------------|---------------|
|
||||
| Phase 1 | Task Schema 追踪 Forum 任务 |
|
||||
| Phase 2 | Forum 任务可分解给执行 Agent |
|
||||
| Phase 3 | Agent 可以自主在论坛发帖 |
|
||||
| Phase 4 | Forum 操作可视化 |
|
||||
| Phase 5 | 多 Agent 论坛协作 |
|
||||
| **Phase F** | **Forum 升级路径,与 Phase 1-5 协同** |
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
| 注意事项 | 说明 |
|
||||
|---------|------|
|
||||
| F.1 是基础 | F.2-F.4 都依赖 F.1 的数据模型 |
|
||||
| 安全第一 | 严格输入验证,防止注入攻击 |
|
||||
| API 兼容性 | 保持现有 API 兼容 |
|
||||
| AI 集成 | F.4 需要 Agent 能力成熟后才能实现 |
|
||||
|
||||
---
|
||||
|
||||
## 总工作量
|
||||
|
||||
| Phase | 工作量 |
|
||||
|-------|--------|
|
||||
| F.1 | 2 天 |
|
||||
| F.2 | 3 天 |
|
||||
| F.3 | 3 天 |
|
||||
| F.4 | 5 天 |
|
||||
| **总计** | **13 天** |
|
||||
305
development-doc/plan/forum-update/checklist.md
Normal file
305
development-doc/plan/forum-update/checklist.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# Forum 升级执行清单
|
||||
|
||||
本清单用于追踪 Forum 升级计划的执行进度。
|
||||
|
||||
---
|
||||
|
||||
## 总进度
|
||||
|
||||
| Phase | 名称 | 状态 | 工作量 |
|
||||
|-------|------|------|--------|
|
||||
| F.0 | 现状与目标 | ✅ 完成 | - |
|
||||
| F.1 | 数据模型升级 | ⬜ 待开始 | 2 天 |
|
||||
| F.2 | API 增强与安全 | ⬜ 待开始 | 3 天 |
|
||||
| F.3 | 权限系统 | ⬜ 待开始 | 3 天 |
|
||||
| F.4 | AI 集成 | ⬜ 待开始 | 5 天 |
|
||||
| **总计** | | | **13 天** |
|
||||
|
||||
---
|
||||
|
||||
## Phase F.1:数据模型升级
|
||||
|
||||
### 目标
|
||||
升级 Forum 数据模型,支持多板块、标签系统、帖子元数据扩展。
|
||||
|
||||
### 任务清单
|
||||
|
||||
#### 数据库迁移
|
||||
- [ ] 创建数据库迁移脚本
|
||||
- 新增 `forum_boards` 表
|
||||
- 新增 `forum_tags` 表
|
||||
- 新增 `forum_post_tags` 表
|
||||
- 新增 `forum_likes` 表
|
||||
- 新增 `forum_stats` 表
|
||||
- 扩展 `forum_posts` 表
|
||||
- 扩展 `forum_replies` 表
|
||||
|
||||
#### 模型实现
|
||||
- [ ] 扩展 `models/forum.py`
|
||||
- [ ] 创建 `ForumBoard` 模型
|
||||
- [ ] 创建 `ForumTag` 模型
|
||||
- [ ] 创建 `ForumPostTag` 模型
|
||||
- [ ] 创建 `ForumLike` 模型
|
||||
- [ ] 创建 `ForumStats` 模型
|
||||
- [ ] 扩展 `ForumPost` 字段
|
||||
- [ ] 扩展 `ForumReply` 字段
|
||||
|
||||
#### Schema 实现
|
||||
- [ ] 扩展 `schemas/forum.py`
|
||||
- [ ] 创建 `ForumBoardCreate/Out` Schema
|
||||
- [ ] 扩展 `ForumPostCreate/Out` Schema
|
||||
- [ ] 扩展 `ForumReplyCreate/Out` Schema
|
||||
- [ ] 创建 `ForumTagOut` Schema
|
||||
|
||||
#### 测试
|
||||
- [ ] 编写单元测试
|
||||
- [ ] 测试 ForumBoard CRUD
|
||||
- [ ] 测试 ForumTag CRUD
|
||||
- [ ] 测试 ForumLike 功能
|
||||
- [ ] 测试 ForumStats 更新
|
||||
|
||||
### 产出文件
|
||||
- `models/forum.py`
|
||||
- `schemas/forum.py`
|
||||
|
||||
### 验收
|
||||
- [ ] 迁移脚本可正常运行
|
||||
- [ ] 所有新模型可正常创建
|
||||
- [ ] Schema 验证正常工作
|
||||
- [ ] 单元测试通过
|
||||
|
||||
---
|
||||
|
||||
## Phase F.2:API 增强与安全
|
||||
|
||||
### 目标
|
||||
增强 Forum API 功能,实现文件锁、输入验证、限流、缓存。
|
||||
|
||||
### 任务清单
|
||||
|
||||
#### 基础服务
|
||||
- [ ] 创建 `services/forum_service.py`
|
||||
- [ ] 实现 `ForumLockManager` 类
|
||||
- [ ] 实现 `ForumService` 类
|
||||
- [ ] 实现板块 CRUD 方法
|
||||
- [ ] 实现帖子 CRUD 方法
|
||||
- [ ] 实现回复 CRUD 方法
|
||||
- [ ] 实现点赞方法
|
||||
|
||||
#### 安全机制
|
||||
- [ ] 实现输入验证
|
||||
- [ ] 实现 `sanitize_input` 函数
|
||||
- [ ] 实现 `validate_post_data` 函数
|
||||
- [ ] 实现配置常量
|
||||
- [ ] 实现限流器
|
||||
- [ ] 实现 `RateLimiter` 类
|
||||
- [ ] 集成发帖限流
|
||||
- [ ] 集成回复限流
|
||||
- [ ] 实现缓存
|
||||
- [ ] 实现 `ForumCache` 类
|
||||
- [ ] 实现帖子缓存
|
||||
- [ ] 实现标签缓存
|
||||
|
||||
#### API 端点
|
||||
- [ ] 扩展 `routers/forum.py`
|
||||
- [ ] GET `/boards` - 列出板块
|
||||
- [ ] POST `/boards` - 创建板块
|
||||
- [ ] GET `/posts` - 分页获取帖子
|
||||
- [ ] POST `/posts` - 创建帖子
|
||||
- [ ] PATCH `/posts/{id}/pin` - 置顶
|
||||
- [ ] PATCH `/posts/{id}/lock` - 锁定
|
||||
- [ ] GET `/tags` - 列出/搜索标签
|
||||
- [ ] POST `/posts/{id}/tags` - 添加标签
|
||||
- [ ] POST `/like` - 切换点赞
|
||||
|
||||
#### 测试
|
||||
- [ ] 编写单元测试
|
||||
- [ ] 测试 ForumLockManager
|
||||
- [ ] 测试输入验证
|
||||
- [ ] 测试限流器
|
||||
- [ ] 测试 API 端点
|
||||
|
||||
### 产出文件
|
||||
- `services/forum_service.py`
|
||||
|
||||
### 验收
|
||||
- [ ] 并发锁正常工作
|
||||
- [ ] 输入验证可过滤危险字符
|
||||
- [ ] 限流正常工作
|
||||
- [ ] 缓存提升读取速度
|
||||
- [ ] 所有 API 端点正常
|
||||
- [ ] 单元测试通过
|
||||
|
||||
---
|
||||
|
||||
## Phase F.3:权限系统
|
||||
|
||||
### 目标
|
||||
实现用户角色管理、板块权限控制、操作日志、积分系统。
|
||||
|
||||
### 任务清单
|
||||
|
||||
#### 用户扩展
|
||||
- [ ] 扩展 `models/user.py`
|
||||
- [ ] 添加 `role` 字段
|
||||
- [ ] 添加 `forum_score` 字段
|
||||
- [ ] 添加论坛统计字段
|
||||
- [ ] 添加禁言相关字段
|
||||
- [ ] 添加 `moderated_boards` 字段
|
||||
|
||||
#### 权限服务
|
||||
- [ ] 创建 `services/permission_service.py`
|
||||
- [ ] 实现 `UserRole` 枚举
|
||||
- [ ] 实现 `Permission` 枚举
|
||||
- [ ] 实现 `ROLE_PERMISSIONS` 映射
|
||||
- [ ] 实现 `PermissionService` 类
|
||||
- [ ] 实现 `has_permission` 方法
|
||||
- [ ] 实现 `can_edit_post` 方法
|
||||
- [ ] 实现 `can_delete_post` 方法
|
||||
- [ ] 实现 `ban_user` 方法
|
||||
- [ ] 实现 `unban_user` 方法
|
||||
|
||||
#### 日志系统
|
||||
- [ ] 扩展 `models/forum.py`
|
||||
- [ ] 创建 `ForumLog` 模型
|
||||
- [ ] 集成日志记录
|
||||
- [ ] 记录帖子操作
|
||||
- [ ] 记录回复操作
|
||||
- [ ] 记录用户操作
|
||||
|
||||
#### 积分服务
|
||||
- [ ] 创建 `services/score_service.py`
|
||||
- [ ] 实现 `SCORE_RULES` 配置
|
||||
- [ ] 实现 `ScoreService` 类
|
||||
- [ ] 实现 `add_score` 方法
|
||||
- [ ] 实现 `get_leaderboard` 方法
|
||||
- [ ] 实现积分自动增减
|
||||
|
||||
#### 管理 API
|
||||
- [ ] 扩展 `routers/forum.py`
|
||||
- [ ] POST `/admin/ban/{user_id}` - 禁言
|
||||
- [ ] POST `/admin/unban/{user_id}` - 解除禁言
|
||||
- [ ] GET `/admin/logs` - 查看日志
|
||||
- [ ] GET `/leaderboard` - 积分排行榜
|
||||
|
||||
#### 测试
|
||||
- [ ] 编写单元测试
|
||||
- [ ] 测试权限检查
|
||||
- [ ] 测试禁言功能
|
||||
- [ ] 测试积分计算
|
||||
- [ ] 测试排行榜
|
||||
|
||||
### 产出文件
|
||||
- `models/user.py`
|
||||
- `models/forum.py` (ForumLog)
|
||||
- `services/permission_service.py`
|
||||
- `services/score_service.py`
|
||||
|
||||
### 验收
|
||||
- [ ] 角色权限正确控制
|
||||
- [ ] 禁言功能正常
|
||||
- [ ] 操作日志正确记录
|
||||
- [ ] 积分正确增减
|
||||
- [ ] 排行榜正确排序
|
||||
- [ ] 单元测试通过
|
||||
|
||||
---
|
||||
|
||||
## Phase F.4:AI 集成
|
||||
|
||||
### 目标
|
||||
实现 AI 自动回复、摘要生成、智能打标、Agent 自主发帖。
|
||||
|
||||
### 任务清单
|
||||
|
||||
#### AI 服务
|
||||
- [ ] 创建 `services/forum_ai_service.py`
|
||||
- [ ] 实现 `ForumAIConfig` 配置
|
||||
- [ ] 实现 `ForumAIService` 类
|
||||
- [ ] 实现 `generate_auto_reply` 方法
|
||||
- [ ] 实现 `_should_auto_reply` 判断
|
||||
- [ ] 实现 `generate_summary` 方法
|
||||
- [ ] 实现 `suggest_tags` 方法
|
||||
- [ ] 实现 `classify_category` 方法
|
||||
|
||||
#### 摘要服务
|
||||
- [ ] 创建 `services/summary_service.py`
|
||||
- [ ] 实现 `SummaryService` 类
|
||||
- [ ] 实现 `get_post_summary` 方法
|
||||
- [ ] 实现 `get_thread_summary` 方法
|
||||
- [ ] 实现缓存失效
|
||||
|
||||
#### Agent 工具
|
||||
- [ ] 创建 `agents/tools/forum_tools.py`
|
||||
- [ ] 实现 `create_forum_post` 工具
|
||||
- [ ] 实现 `reply_to_post` 工具
|
||||
- [ ] 实现 `search_forum_posts` 工具
|
||||
- [ ] 实现 `get_forum_trending` 工具
|
||||
- [ ] 创建 `agents/prompts/forum_agent.py`
|
||||
- [ ] 编写 Forum Agent 提示词
|
||||
- [ ] 配置工具列表
|
||||
|
||||
#### 定时任务
|
||||
- [ ] 创建 `tasks/forum_auto_reply.py`
|
||||
- [ ] 实现 `auto_reply_task` 函数
|
||||
- [ ] 实现 `setup_forum_scheduler` 函数
|
||||
|
||||
#### API 端点
|
||||
- [ ] 扩展 `routers/forum.py`
|
||||
- [ ] POST `/posts/{id}/generate-summary` - 生成摘要
|
||||
- [ ] POST `/posts/suggest-tags` - 推荐标签
|
||||
- [ ] POST `/posts/classify` - 分类帖子
|
||||
- [ ] GET `/posts/{id}/ai-status` - AI 状态
|
||||
|
||||
#### 测试
|
||||
- [ ] 编写单元测试
|
||||
- [ ] 测试自动回复生成
|
||||
- [ ] 测试摘要生成
|
||||
- [ ] 测试智能打标
|
||||
- [ ] 测试 API 端点
|
||||
|
||||
### 产出文件
|
||||
- `services/forum_ai_service.py`
|
||||
- `services/summary_service.py`
|
||||
- `agents/tools/forum_tools.py`
|
||||
- `agents/prompts/forum_agent.py`
|
||||
- `tasks/forum_auto_reply.py`
|
||||
|
||||
### 验收
|
||||
- [ ] AI 服务正常调用
|
||||
- [ ] 自动回复正常生成
|
||||
- [ ] 摘要生成正常
|
||||
- [ ] 智能打标推荐
|
||||
- [ ] 智能分类推荐
|
||||
- [ ] Agent 工具可用
|
||||
- [ ] 定时任务正常执行
|
||||
- [ ] 单元测试通过
|
||||
|
||||
---
|
||||
|
||||
## 完成标准
|
||||
|
||||
- [ ] 所有 Phase F.1-F.4 任务完成
|
||||
- [ ] 所有单元测试通过
|
||||
- [ ] API 文档更新完成
|
||||
- [ ] 用户文档更新完成
|
||||
- [ ] 部署验证通过
|
||||
|
||||
---
|
||||
|
||||
## 风险与注意事项
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| F.4 AI 调用成本高 | 费用增加 | 设置每日调用上限 |
|
||||
| 并发锁死锁 | 服务不可用 | 超时机制 + 定期清理 |
|
||||
| 迁移数据丢失 | 用户流失 | 备份 + 分阶段迁移 |
|
||||
| Agent 刷屏 | 用户体验下降 | 严格限流 + 人工审核 |
|
||||
|
||||
---
|
||||
|
||||
## 更新日志
|
||||
|
||||
| 日期 | Phase | 变更内容 |
|
||||
|------|-------|----------|
|
||||
| 2026-04-04 | F.0 | 创建文档 |
|
||||
205
development-doc/plan/forum-update/phase-f-0-current-state.md
Normal file
205
development-doc/plan/forum-update/phase-f-0-current-state.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Phase F.0:Forum 现状与目标
|
||||
|
||||
日期:2026-04-04
|
||||
状态:已完成
|
||||
借鉴来源:VCPToolBox VCP论坛 模块
|
||||
|
||||
---
|
||||
|
||||
## 1. 本阶段目的
|
||||
|
||||
本文件用于统一背景认知,明确:
|
||||
|
||||
- Jarvis 当前 Forum 架构处于什么水平
|
||||
- 主要短板是什么
|
||||
- 为什么要升级
|
||||
- 升级后的目标形态是什么
|
||||
- VCPToolBox VCP论坛 给我们什么启发
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前 Jarvis Forum 架构
|
||||
|
||||
### 2.1 核心流程
|
||||
|
||||
```
|
||||
用户发帖/回复 → ForumRouter → ForumPost/ForumReply 模型 → SQLite
|
||||
```
|
||||
|
||||
### 2.2 核心文件
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `backend/app/routers/forum.py` | 论坛 API 路由 |
|
||||
| `backend/app/models/forum.py` | ForumPost/ForumReply 数据模型 |
|
||||
| `backend/app/schemas/forum.py` | Pydantic 请求/响应模型 |
|
||||
|
||||
### 2.3 当前数据模型
|
||||
|
||||
```python
|
||||
class ForumPost(BaseModel):
|
||||
user_id: str
|
||||
title: str
|
||||
content: str
|
||||
category: str # instruction, discussion, question
|
||||
is_executed: bool
|
||||
execution_result: Optional[str]
|
||||
reply_count: int
|
||||
|
||||
class ForumReply(BaseModel):
|
||||
post_id: str
|
||||
user_id: Optional[str]
|
||||
agent_id: Optional[str]
|
||||
content: str
|
||||
is_ai_reply: bool
|
||||
```
|
||||
|
||||
### 2.4 当前 API 端点
|
||||
|
||||
| 方法 | 路径 | 功能 |
|
||||
|------|------|------|
|
||||
| GET | `/api/forum/posts` | 列出帖子 |
|
||||
| POST | `/api/forum/posts` | 创建帖子 |
|
||||
| GET | `/api/forum/posts/{post_id}` | 获取帖子 |
|
||||
| DELETE | `/api/forum/posts/{post_id}` | 删除帖子 |
|
||||
| GET | `/api/forum/posts/{post_id}/replies` | 列出回复 |
|
||||
| POST | `/api/forum/posts/{post_id}/replies` | 创建回复 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 当前能力矩阵
|
||||
|
||||
| 能力 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| 基本 CRUD | ✅ | 帖子和回复的增删查 |
|
||||
| 分类标签 | ✅ | instruction/discussion/question |
|
||||
| AI 回复标记 | ✅ | `is_ai_reply` 字段 |
|
||||
| 回复计数 | ✅ | `reply_count` 字段 |
|
||||
| 执行状态 | ✅ | `is_executed`/`execution_result` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 当前短板
|
||||
|
||||
| 短板 | 严重程度 | 影响 |
|
||||
|------|----------|------|
|
||||
| 无板块系统 | 🔴 高 | 所有帖子混在一起 |
|
||||
| 无标签系统 | 🟡 中 | 无法细粒度分类 |
|
||||
| 无输入验证 | 🔴 高 | 安全风险 |
|
||||
| 无并发控制 | 🟡 中 | 多用户操作可能冲突 |
|
||||
| 无权限管理 | 🟡 中 | 无法控制谁可以做什么 |
|
||||
| 无积分系统 | 🟡 中 | 无法激励参与 |
|
||||
| 无 AI 自动回复 | 🟡 中 | 需要人工回复 |
|
||||
| 无摘要生成 | 🟢 低 | 长帖子难以快速浏览 |
|
||||
| 无 Agent 自主发帖 | 🟢 低 | Agent 无法主动参与 |
|
||||
|
||||
---
|
||||
|
||||
## 5. VCPToolBox VCP论坛 核心借鉴
|
||||
|
||||
### 5.1 VCP论坛架构
|
||||
|
||||
VCPToolBox 的论坛是 Agent 社区交流平台,支持:
|
||||
- 多 Agent 和用户共同参与
|
||||
- 超栈追踪和统一 FileAPI
|
||||
- 文件上传和多媒体支持
|
||||
- 积分系统和任务版集成
|
||||
|
||||
### 5.2 核心安全机制
|
||||
|
||||
```javascript
|
||||
// forumApi.js 中的安全配置
|
||||
const FORUM_CONFIG = {
|
||||
MAX_CONTENT_LENGTH: 50000, // 单条内容最大长度 50KB
|
||||
MAX_FILE_SIZE: 1024 * 1024 * 2, // 单个帖子文件最大 2MB
|
||||
MAX_MAID_LENGTH: 50, // 用户名最大长度
|
||||
MAX_TITLE_LENGTH: 100, // 标题最大长度
|
||||
MAX_FLOORS_PER_POST: 500, // 单帖最大楼层数
|
||||
UID_PATTERN: /^[a-zA-Z0-9_-]+$/, // UID 允许的字符
|
||||
LOCK_TIMEOUT: 10000, // 文件锁超时 10秒
|
||||
MAX_CONCURRENT_WRITES: 5, // 最大并发写入数
|
||||
};
|
||||
```
|
||||
|
||||
### 5.3 核心安全函数
|
||||
|
||||
```javascript
|
||||
// 文件锁管理器 - 并发控制
|
||||
class FileLockManager {
|
||||
async acquireLock(filePath, timeout)
|
||||
releaseLock(filePath)
|
||||
}
|
||||
|
||||
// 安全路径检查 - 防止路径遍历
|
||||
function isPathSafe(targetPath, rootPath)
|
||||
|
||||
// 输入清理 - 防止注入
|
||||
function sanitizeInput(input, maxLength)
|
||||
|
||||
// 安全的文件名解析
|
||||
function parsePostFilename(filename)
|
||||
```
|
||||
|
||||
### 5.4 关键设计理念
|
||||
|
||||
1. **安全第一** - 严格的输入验证、路径安全、并发控制
|
||||
2. **可扩展性** - 支持多板块、多楼层、文件附件
|
||||
3. **Agent 集成** - Agent 可以自主发帖、回帖
|
||||
4. **积分激励** - Agent 通过发帖获得积分
|
||||
|
||||
---
|
||||
|
||||
## 6. 目标架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ User/Agent │
|
||||
└─────────────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────┴───────────────────────────────────┐
|
||||
│ Forum Router Layer │
|
||||
│ - 输入验证 (sanitize) │
|
||||
│ - 权限检查 (permission) │
|
||||
│ - 限流控制 (rate_limit) │
|
||||
└─────────────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────┴───────────────────────────────────┐
|
||||
│ Forum Service Layer │
|
||||
│ - FileLock (并发控制) │
|
||||
│ - PermissionService (权限) │
|
||||
│ - AIService (AI 回复/摘要) │
|
||||
└─────────────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────┴───────────────────────────────────┐
|
||||
│ Data Layer │
|
||||
│ - ForumPost (板块/标签) │
|
||||
│ - ForumReply (层级) │
|
||||
│ - UserRole (权限) │
|
||||
│ - ForumStats (积分) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 借鉴点映射
|
||||
|
||||
| VCPToolBox 借鉴点 | Jarvis 实现位置 | 优先级 |
|
||||
|-------------------|---------------|--------|
|
||||
| 文件锁机制 | `services/forum_service.py` | 🟢 高 |
|
||||
| 输入验证强化 | `routers/forum.py` | 🟢 高 |
|
||||
| 安全路径检查 | `services/forum_service.py` | 🟢 高 |
|
||||
| 板块/分类系统 | `models/forum.py` | 🟢 高 |
|
||||
| 标签系统 | `models/forum.py` | 🟡 中 |
|
||||
| 权限管理 | `services/permission_service.py` | 🟡 中 |
|
||||
| 积分系统 | `models/user.py` | 🟡 中 |
|
||||
| AI 自动回复 | `services/forum_ai_service.py` | 🟡 中 |
|
||||
| 摘要生成 | `services/summary_service.py` | 🟢 低 |
|
||||
| Agent 自主发帖 | `agents/` | 🟡 中 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 本阶段产出要求
|
||||
|
||||
- [x] 团队对 Jarvis 当前 Forum 问题和目标方向达成一致
|
||||
- [x] VCPToolBox VCP论坛 借鉴点已映射到具体 Phase
|
||||
- [x] 后续 phase 文档能够在这个认知基础上展开
|
||||
361
development-doc/plan/forum-update/phase-f-1-data-model.md
Normal file
361
development-doc/plan/forum-update/phase-f-1-data-model.md
Normal file
@@ -0,0 +1,361 @@
|
||||
# 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
|
||||
- [ ] 迁移脚本可回滚
|
||||
- [ ] 单元测试覆盖新增功能
|
||||
488
development-doc/plan/forum-update/phase-f-2-forum-api.md
Normal file
488
development-doc/plan/forum-update/phase-f-2-forum-api.md
Normal file
@@ -0,0 +1,488 @@
|
||||
# Phase F.2:API 增强与安全
|
||||
|
||||
日期:2026-04-04
|
||||
状态:待开始
|
||||
依赖:F.1(待完成)
|
||||
前置:routers/forum.py
|
||||
|
||||
---
|
||||
|
||||
## 1. 本阶段目的
|
||||
|
||||
增强 Jarvis Forum API 的功能和安全:
|
||||
|
||||
- 引入文件锁机制(并发控制)
|
||||
- 强化输入验证
|
||||
- 扩展 API 端点
|
||||
- 实现缓存机制
|
||||
- 添加限流保护
|
||||
|
||||
---
|
||||
|
||||
## 2. Forum Service 架构
|
||||
|
||||
### 2.1 目录结构
|
||||
|
||||
```
|
||||
backend/app/services/
|
||||
└── forum_service.py # 新增
|
||||
```
|
||||
|
||||
### 2.2 核心服务类
|
||||
|
||||
```python
|
||||
class ForumLockManager:
|
||||
"""文件锁管理器 - 防止并发写入冲突"""
|
||||
|
||||
def __init__(self, max_concurrent: int = 5, timeout: int = 10):
|
||||
self.locks: Dict[str, LockInfo] = {}
|
||||
self.max_concurrent = max_concurrent
|
||||
self.timeout = timeout
|
||||
|
||||
async def acquire_lock(self, resource_id: str) -> bool:
|
||||
"""获取锁"""
|
||||
|
||||
def release_lock(self, resource_id: str) -> None:
|
||||
"""释放锁"""
|
||||
|
||||
def cleanup_stale_locks(self) -> None:
|
||||
"""清理过期锁"""
|
||||
|
||||
|
||||
class ForumService:
|
||||
"""论坛服务主类"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
lock_manager: ForumLockManager,
|
||||
):
|
||||
self.db = db
|
||||
self.lock = lock_manager
|
||||
|
||||
# === 板块操作 ===
|
||||
async def create_board(self, data: ForumBoardCreate) -> ForumBoard:
|
||||
"""创建板块"""
|
||||
|
||||
async def list_boards(self) -> List[ForumBoard]:
|
||||
"""列出板块"""
|
||||
|
||||
# === 帖子操作 ===
|
||||
async def create_post(
|
||||
self,
|
||||
user_id: str,
|
||||
data: ForumPostCreate,
|
||||
) -> ForumPost:
|
||||
"""创建帖子(带锁)"""
|
||||
async with self.lock.acquire_lock(f"post:{user_id}"):
|
||||
# 创建帖子逻辑
|
||||
|
||||
async def get_posts(
|
||||
self,
|
||||
board_id: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
tags: Optional[List[str]] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> PaginatedResult[ForumPostOut]:
|
||||
"""分页获取帖子"""
|
||||
|
||||
async def get_post(self, post_id: str, user_id: Optional[str] = None) -> ForumPostOut:
|
||||
"""获取帖子详情(增加浏览量)"""
|
||||
|
||||
async def update_post(
|
||||
self,
|
||||
post_id: str,
|
||||
user_id: str,
|
||||
data: ForumPostUpdate,
|
||||
) -> ForumPost:
|
||||
"""更新帖子"""
|
||||
|
||||
async def delete_post(self, post_id: str, user_id: str) -> None:
|
||||
"""删除帖子(软删除)"""
|
||||
|
||||
async def pin_post(self, post_id: str, is_pinned: bool) -> None:
|
||||
"""置顶/取消置顶"""
|
||||
|
||||
async def lock_post(self, post_id: str, is_locked: bool) -> None:
|
||||
"""锁定/解锁帖子"""
|
||||
|
||||
# === 回复操作 ===
|
||||
async def create_reply(
|
||||
self,
|
||||
post_id: str,
|
||||
user_id: Optional[str],
|
||||
data: ForumReplyCreate,
|
||||
) -> ForumReply:
|
||||
"""创建回复(带锁)"""
|
||||
|
||||
async def get_replies(
|
||||
self,
|
||||
post_id: str,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> PaginatedResult[ForumReplyOut]:
|
||||
"""获取回复列表"""
|
||||
|
||||
async def update_reply(
|
||||
self,
|
||||
reply_id: str,
|
||||
user_id: str,
|
||||
content: str,
|
||||
) -> ForumReply:
|
||||
"""更新回复"""
|
||||
|
||||
async def delete_reply(self, reply_id: str, user_id: str) -> None:
|
||||
"""删除回复"""
|
||||
|
||||
# === 点赞操作 ===
|
||||
async def toggle_like(
|
||||
self,
|
||||
user_id: str,
|
||||
post_id: Optional[str] = None,
|
||||
reply_id: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""切换点赞状态"""
|
||||
|
||||
# === 标签操作 ===
|
||||
async def create_tag(self, name: str, color: str = "#666666") -> ForumTag:
|
||||
"""创建标签"""
|
||||
|
||||
async def search_tags(self, query: str) -> List[ForumTag]:
|
||||
"""搜索标签"""
|
||||
|
||||
async def add_tags_to_post(
|
||||
self,
|
||||
post_id: str,
|
||||
user_id: str,
|
||||
tags: List[str],
|
||||
) -> None:
|
||||
"""为帖子添加标签"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 安全机制
|
||||
|
||||
### 3.1 输入验证
|
||||
|
||||
```python
|
||||
# Forum 安全配置
|
||||
FORUM_CONFIG = {
|
||||
"MAX_CONTENT_LENGTH": 50000, # 内容最大 50KB
|
||||
"MAX_TITLE_LENGTH": 200, # 标题最大 200 字符
|
||||
"MAX_TAGS_PER_POST": 10, # 每帖最多标签
|
||||
"MAX_REPLIES_PER_POST": 500, # 每帖最多回复
|
||||
"MAX_POSTS_PER_USER_PER_DAY": 50, # 每人每天最多发帖
|
||||
"MAX_REPLIES_PER_USER_PER_DAY": 200, # 每人每天最多回复
|
||||
}
|
||||
|
||||
|
||||
def sanitize_input(text: str, max_length: int) -> str:
|
||||
"""清理用户输入"""
|
||||
if not text or not isinstance(text, str):
|
||||
return ""
|
||||
# 移除控制字符
|
||||
text = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '', text)
|
||||
# 限制长度
|
||||
return text[:max_length]
|
||||
|
||||
|
||||
def validate_post_data(data: ForumPostCreate) -> ForumPostCreate:
|
||||
"""验证帖子数据"""
|
||||
# 标题验证
|
||||
data.title = sanitize_input(data.title, FORUM_CONFIG["MAX_TITLE_LENGTH"])
|
||||
if len(data.title.strip()) < 3:
|
||||
raise ValueError("标题至少 3 个字符")
|
||||
|
||||
# 内容验证
|
||||
data.content = sanitize_input(data.content, FORUM_CONFIG["MAX_CONTENT_LENGTH"])
|
||||
if len(data.content.strip()) < 10:
|
||||
raise ValueError("内容至少 10 个字符")
|
||||
|
||||
# 标签验证
|
||||
if len(data.tags) > FORUM_CONFIG["MAX_TAGS_PER_POST"]:
|
||||
raise ValueError(f"最多 {FORUM_CONFIG['MAX_TAGS_PER_POST']} 个标签")
|
||||
data.tags = [sanitize_input(t, 50) for t in data.tags]
|
||||
|
||||
return data
|
||||
```
|
||||
|
||||
### 3.2 并发控制
|
||||
|
||||
```python
|
||||
class RateLimiter:
|
||||
"""简单的限流器"""
|
||||
|
||||
def __init__(self):
|
||||
self.user_requests: Dict[str, List[datetime]] = defaultdict(list)
|
||||
|
||||
def check_rate_limit(
|
||||
self,
|
||||
user_id: str,
|
||||
action: str,
|
||||
max_requests: int,
|
||||
window_seconds: int = 86400,
|
||||
) -> bool:
|
||||
"""检查是否超过限流"""
|
||||
now = datetime.utcnow()
|
||||
cutoff = now - timedelta(seconds=window_seconds)
|
||||
|
||||
# 清理过期记录
|
||||
self.user_requests[user_id] = [
|
||||
t for t in self.user_requests[user_id] if t > cutoff
|
||||
]
|
||||
|
||||
if len(self.user_requests[user_id]) >= max_requests:
|
||||
return False
|
||||
|
||||
self.user_requests[user_id].append(now)
|
||||
return True
|
||||
```
|
||||
|
||||
### 3.3 缓存策略
|
||||
|
||||
```python
|
||||
from functools import lru_cache
|
||||
from typing import Optional
|
||||
import json
|
||||
|
||||
|
||||
class ForumCache:
|
||||
"""论坛缓存"""
|
||||
|
||||
def __init__(self):
|
||||
self.cache: Dict[str, CacheEntry] = {}
|
||||
self.max_size = 1000
|
||||
self.default_ttl = 300 # 5 分钟
|
||||
|
||||
async def get(self, key: str) -> Optional[Any]:
|
||||
"""获取缓存"""
|
||||
if key in self.cache:
|
||||
entry = self.cache[key]
|
||||
if entry.is_expired():
|
||||
del self.cache[key]
|
||||
else:
|
||||
return entry.value
|
||||
return None
|
||||
|
||||
async def set(self, key: str, value: Any, ttl: int = None) -> None:
|
||||
"""设置缓存"""
|
||||
if len(self.cache) >= self.max_size:
|
||||
# LRU 清理
|
||||
oldest = min(self.cache.items(), key=lambda x: x[1].created_at)
|
||||
del self.cache[oldest[0]]
|
||||
|
||||
self.cache[key] = CacheEntry(
|
||||
value=value,
|
||||
ttl=ttl or self.default_ttl,
|
||||
)
|
||||
|
||||
async def invalidate(self, pattern: str) -> None:
|
||||
"""清除匹配模式的缓存"""
|
||||
for key in list(self.cache.keys()):
|
||||
if pattern.format(key):
|
||||
del self.cache[key]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. API 端点扩展
|
||||
|
||||
### 4.1 板块 API
|
||||
|
||||
```python
|
||||
@router.get("/boards", response_model=list[ForumBoardOut])
|
||||
async def list_boards(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""列出所有板块"""
|
||||
service = ForumService(db, lock_manager)
|
||||
return await service.list_boards()
|
||||
|
||||
|
||||
@router.post("/boards", response_model=ForumBoardOut, status_code=201)
|
||||
async def create_board(
|
||||
data: ForumBoardCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""创建板块(仅管理员)"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="需要管理员权限")
|
||||
service = ForumService(db, lock_manager)
|
||||
return await service.create_board(data)
|
||||
```
|
||||
|
||||
### 4.2 帖子 API 扩展
|
||||
|
||||
```python
|
||||
@router.get("/posts", response_model=PaginatedResponse[ForumPostOut])
|
||||
async def list_posts(
|
||||
board_id: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
tags: Optional[str] = None, # comma-separated
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""分页获取帖子"""
|
||||
service = ForumService(db, lock_manager)
|
||||
tag_list = tags.split(",") if tags else None
|
||||
return await service.get_posts(
|
||||
board_id=board_id,
|
||||
category=category,
|
||||
tags=tag_list,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/posts", response_model=ForumPostOut, status_code=201)
|
||||
async def create_post(
|
||||
data: ForumPostCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""创建帖子"""
|
||||
# 限流检查
|
||||
if not rate_limiter.check_rate_limit(
|
||||
current_user.id, "post", FORUM_CONFIG["MAX_POSTS_PER_USER_PER_DAY"]
|
||||
):
|
||||
raise HTTPException(status_code=429, detail="今日发帖次数已达上限")
|
||||
|
||||
# 验证数据
|
||||
data = validate_post_data(data)
|
||||
|
||||
service = ForumService(db, lock_manager)
|
||||
return await service.create_post(current_user.id, data)
|
||||
|
||||
|
||||
@router.patch("/posts/{post_id}/pin")
|
||||
async def pin_post(
|
||||
post_id: str,
|
||||
is_pinned: bool,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""置顶/取消置顶(仅版主+)"""
|
||||
service = ForumService(db, lock_manager)
|
||||
await service.pin_post(post_id, is_pinned)
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.patch("/posts/{post_id}/lock")
|
||||
async def lock_post(
|
||||
post_id: str,
|
||||
is_locked: bool,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""锁定/解锁帖子"""
|
||||
service = ForumService(db, lock_manager)
|
||||
await service.lock_post(post_id, is_locked)
|
||||
return {"success": True}
|
||||
```
|
||||
|
||||
### 4.3 标签 API
|
||||
|
||||
```python
|
||||
@router.get("/tags", response_model=list[ForumTagOut])
|
||||
async def list_tags(
|
||||
query: Optional[str] = None,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""搜索/列出标签"""
|
||||
service = ForumService(db, lock_manager)
|
||||
if query:
|
||||
return await service.search_tags(query)
|
||||
return await service.list_tags()
|
||||
|
||||
|
||||
@router.post("/posts/{post_id}/tags")
|
||||
async def add_tags(
|
||||
post_id: str,
|
||||
tags: List[str],
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""为帖子添加标签"""
|
||||
service = ForumService(db, lock_manager)
|
||||
await service.add_tags_to_post(post_id, current_user.id, tags)
|
||||
return {"success": True}
|
||||
```
|
||||
|
||||
### 4.4 点赞 API
|
||||
|
||||
```python
|
||||
@router.post("/like")
|
||||
async def toggle_like(
|
||||
post_id: Optional[str] = None,
|
||||
reply_id: Optional[str] = None,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""切换点赞状态"""
|
||||
if not post_id and not reply_id:
|
||||
raise HTTPException(status_code=400, detail="需要指定 post_id 或 reply_id")
|
||||
|
||||
service = ForumService(db, lock_manager)
|
||||
liked = await service.toggle_like(
|
||||
user_id=current_user.id,
|
||||
post_id=post_id,
|
||||
reply_id=reply_id,
|
||||
)
|
||||
return {"liked": liked}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 实现步骤
|
||||
|
||||
| 步骤 | 任务 | 优先级 |
|
||||
|------|------|--------|
|
||||
| 1 | 创建 ForumLockManager | 🟢 高 |
|
||||
| 2 | 创建 ForumService | 🟢 高 |
|
||||
| 3 | 实现输入验证函数 | 🟢 高 |
|
||||
| 4 | 实现限流器 | 🟡 中 |
|
||||
| 5 | 实现缓存 | 🟡 中 |
|
||||
| 6 | 扩展 Router 端点 | 🟢 高 |
|
||||
| 7 | 单元测试 | 🟡 中 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 核心文件变更
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `services/forum_service.py` | 新增 |
|
||||
| `services/__init__.py` | 添加 export |
|
||||
| `routers/forum.py` | 扩展端点,集成服务 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 工作量估算
|
||||
|
||||
| 任务 | 工作量 |
|
||||
|------|--------|
|
||||
| ForumLockManager | 0.5 天 |
|
||||
| ForumService 核心 | 1 天 |
|
||||
| 输入验证/限流/缓存 | 0.5 天 |
|
||||
| API 端点扩展 | 0.5 天 |
|
||||
| 单元测试 | 0.5 天 |
|
||||
| **总计** | **3 天** |
|
||||
|
||||
---
|
||||
|
||||
## 8. 验收标准
|
||||
|
||||
- [ ] ForumLockManager 可正确管理并发锁
|
||||
- [ ] 输入验证可过滤危险字符
|
||||
- [ ] 限流器可防止滥用
|
||||
- [ ] 缓存可提升热门帖子读取速度
|
||||
- [ ] 所有新增 API 端点正常工作
|
||||
- [ ] 现有 API 保持向后兼容
|
||||
- [ ] 单元测试覆盖核心逻辑
|
||||
540
development-doc/plan/forum-update/phase-f-3-permissions.md
Normal file
540
development-doc/plan/forum-update/phase-f-3-permissions.md
Normal file
@@ -0,0 +1,540 @@
|
||||
# Phase F.3:权限系统
|
||||
|
||||
日期:2026-04-04
|
||||
状态:待开始
|
||||
依赖:F.2(待完成)
|
||||
前置:models/user.py
|
||||
|
||||
---
|
||||
|
||||
## 1. 本阶段目的
|
||||
|
||||
实现 Jarvis Forum 的权限系统:
|
||||
|
||||
- 用户角色管理(user/moderator/admin)
|
||||
- 板块权限控制
|
||||
- 操作日志记录
|
||||
- 积分/奖励系统
|
||||
|
||||
---
|
||||
|
||||
## 2. 用户角色系统
|
||||
|
||||
### 2.1 角色定义
|
||||
|
||||
```python
|
||||
class UserRole(str, Enum):
|
||||
"""用户角色枚举"""
|
||||
GUEST = "guest" # 访客 - 只能浏览
|
||||
USER = "user" # 普通用户 - 可发帖/回复
|
||||
MODERATOR = "moderator" # 版主 - 可管理板块
|
||||
ADMIN = "admin" # 管理员 - 全权限
|
||||
|
||||
|
||||
class Permission(str, Enum):
|
||||
"""权限枚举"""
|
||||
# 帖子权限
|
||||
POST_CREATE = "post:create"
|
||||
POST_EDIT_OWN = "post:edit:own"
|
||||
POST_EDIT_ANY = "post:edit:any"
|
||||
POST_DELETE_OWN = "post:delete:own"
|
||||
POST_DELETE_ANY = "post:delete:any"
|
||||
POST_PIN = "post:pin"
|
||||
POST_LOCK = "post:lock"
|
||||
POST_VIEW = "post:view"
|
||||
|
||||
# 回复权限
|
||||
REPLY_CREATE = "reply:create"
|
||||
REPLY_EDIT_OWN = "reply:edit:own"
|
||||
REPLY_DELETE_OWN = "reply:delete:own"
|
||||
REPLY_DELETE_ANY = "reply:delete:any"
|
||||
REPLY_PIN = "reply:pin"
|
||||
REPLY_BEST = "reply:best"
|
||||
|
||||
# 板块权限
|
||||
BOARD_CREATE = "board:create"
|
||||
BOARD_EDIT = "board:edit"
|
||||
BOARD_DELETE = "board:delete"
|
||||
|
||||
# 标签权限
|
||||
TAG_CREATE = "tag:create"
|
||||
TAG_MANAGE = "tag:manage"
|
||||
|
||||
# 用户权限
|
||||
USER_BAN = "user:ban"
|
||||
USER_ROLE = "user:role"
|
||||
|
||||
# 积分权限
|
||||
SCORE_VIEW = "score:view"
|
||||
SCORE_MANAGE = "score:manage"
|
||||
```
|
||||
|
||||
### 2.2 角色权限映射
|
||||
|
||||
```python
|
||||
ROLE_PERMISSIONS: Dict[UserRole, Set[Permission]] = {
|
||||
UserRole.GUEST: {
|
||||
Permission.POST_VIEW,
|
||||
},
|
||||
UserRole.USER: {
|
||||
Permission.POST_VIEW,
|
||||
Permission.POST_CREATE,
|
||||
Permission.POST_EDIT_OWN,
|
||||
Permission.POST_DELETE_OWN,
|
||||
Permission.REPLY_CREATE,
|
||||
Permission.REPLY_EDIT_OWN,
|
||||
Permission.REPLY_DELETE_OWN,
|
||||
Permission.SCORE_VIEW,
|
||||
},
|
||||
UserRole.MODERATOR: {
|
||||
# 用户权限
|
||||
Permission.POST_VIEW,
|
||||
Permission.POST_CREATE,
|
||||
Permission.POST_EDIT_OWN,
|
||||
Permission.POST_DELETE_OWN,
|
||||
Permission.REPLY_CREATE,
|
||||
Permission.REPLY_EDIT_OWN,
|
||||
Permission.REPLY_DELETE_OWN,
|
||||
# 版主权限
|
||||
Permission.POST_PIN,
|
||||
Permission.POST_LOCK,
|
||||
Permission.POST_DELETE_ANY,
|
||||
Permission.REPLY_PIN,
|
||||
Permission.REPLY_BEST,
|
||||
Permission.TAG_MANAGE,
|
||||
Permission.SCORE_VIEW,
|
||||
},
|
||||
UserRole.ADMIN: set(Permission), # 所有权限
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. User 模型扩展
|
||||
|
||||
### 3.1 新增字段
|
||||
|
||||
```python
|
||||
# models/user.py 扩展
|
||||
class User(BaseModel):
|
||||
__tablename__ = "users"
|
||||
|
||||
# === 现有字段 ===
|
||||
id: str
|
||||
username: str
|
||||
email: str
|
||||
hashed_password: str
|
||||
avatar: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
# === Forum 相关扩展 ===
|
||||
role: str = "user" # guest/user/moderator/admin
|
||||
forum_score: int = 0 # 论坛积分
|
||||
|
||||
# 论坛统计
|
||||
post_count: int = 0
|
||||
reply_count: int = 0
|
||||
like_received: int = 0
|
||||
best_reply_count: int = 0
|
||||
|
||||
# 状态
|
||||
is_forum_banned: bool = False # 论坛禁言
|
||||
forum_ban_until: Optional[datetime] = None # 禁言截止时间
|
||||
|
||||
# 板块版主关系
|
||||
moderated_boards: List[str] = [] # 管理的板块 ID 列表
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Permission Service
|
||||
|
||||
### 4.1 服务实现
|
||||
|
||||
```python
|
||||
class PermissionService:
|
||||
"""权限服务"""
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
def has_permission(self, user: User, permission: Permission) -> bool:
|
||||
"""检查用户是否拥有某权限"""
|
||||
if user.is_forum_banned:
|
||||
return False
|
||||
if user.forum_ban_until and user.forum_ban_until > datetime.utcnow():
|
||||
return False
|
||||
|
||||
role = UserRole(user.role)
|
||||
return permission in ROLE_PERMISSIONS.get(role, set())
|
||||
|
||||
def has_board_permission(
|
||||
self,
|
||||
user: User,
|
||||
permission: Permission,
|
||||
board_id: str,
|
||||
) -> bool:
|
||||
"""检查用户在特定板块的权限"""
|
||||
if not self.has_permission(user, permission):
|
||||
return False
|
||||
|
||||
# 版主只能管理自己板块
|
||||
if role == UserRole.MODERATOR:
|
||||
return board_id in user.moderated_boards
|
||||
|
||||
return True
|
||||
|
||||
async def can_edit_post(self, user: User, post: ForumPost) -> bool:
|
||||
"""检查用户是否可以编辑帖子"""
|
||||
if user.role == UserRole.ADMIN:
|
||||
return True
|
||||
if user.role == UserRole.MODERATOR:
|
||||
return post.board_id in user.moderated_boards
|
||||
return post.user_id == user.id
|
||||
|
||||
async def can_delete_post(self, user: User, post: ForumPost) -> bool:
|
||||
"""检查用户是否可以删除帖子"""
|
||||
if user.role == UserRole.ADMIN:
|
||||
return True
|
||||
if user.role == UserRole.MODERATOR:
|
||||
return post.board_id in user.moderated_boards
|
||||
return post.user_id == user.id
|
||||
|
||||
async def ban_user(
|
||||
self,
|
||||
admin: User,
|
||||
target_user_id: str,
|
||||
reason: str,
|
||||
duration: Optional[timedelta] = None,
|
||||
) -> None:
|
||||
"""禁言用户"""
|
||||
if admin.role != UserRole.ADMIN:
|
||||
raise PermissionError("需要管理员权限")
|
||||
|
||||
result = await self.db.execute(
|
||||
select(User).where(User.id == target_user_id)
|
||||
)
|
||||
target_user = result.scalar_one_or_none()
|
||||
|
||||
if not target_user:
|
||||
raise ValueError("用户不存在")
|
||||
|
||||
target_user.is_forum_banned = True
|
||||
target_user.forum_ban_until = (
|
||||
datetime.utcnow() + duration if duration else None
|
||||
)
|
||||
|
||||
# 记录操作日志
|
||||
await self._log_action(
|
||||
admin=admin,
|
||||
action="ban_user",
|
||||
target_id=target_user_id,
|
||||
details={"reason": reason, "duration": str(duration)},
|
||||
)
|
||||
```
|
||||
|
||||
### 4.2 依赖注入
|
||||
|
||||
```python
|
||||
# 在 ForumService 中注入
|
||||
class ForumService:
|
||||
def __init__(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
lock_manager: ForumLockManager,
|
||||
permission_service: PermissionService,
|
||||
):
|
||||
self.db = db
|
||||
self.lock = lock_manager
|
||||
self.perms = permission_service
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 操作日志
|
||||
|
||||
### 5.1 日志模型
|
||||
|
||||
```python
|
||||
class ForumLog(BaseModel):
|
||||
__tablename__ = "forum_logs"
|
||||
|
||||
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
||||
user_id: str # 操作者
|
||||
action: str # 操作类型
|
||||
target_type: str # post/reply/board/user/tag
|
||||
target_id: str # 目标 ID
|
||||
details: Optional[str] = None # JSON 详情
|
||||
ip_address: Optional[str] = None
|
||||
user_agent: Optional[str] = None
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
```
|
||||
|
||||
### 5.2 日志记录
|
||||
|
||||
```python
|
||||
async def _log_action(
|
||||
self,
|
||||
admin: User,
|
||||
action: str,
|
||||
target_id: str,
|
||||
details: Optional[dict] = None,
|
||||
request: Optional[Request] = None,
|
||||
) -> None:
|
||||
"""记录操作日志"""
|
||||
log = ForumLog(
|
||||
user_id=admin.id,
|
||||
action=action,
|
||||
target_type=action.split(":")[0],
|
||||
target_id=target_id,
|
||||
details=json.dumps(details) if details else None,
|
||||
ip_address=request.client.host if request else None,
|
||||
user_agent=request.headers.get("user-agent") if request else None,
|
||||
)
|
||||
self.db.add(log)
|
||||
await self.db.commit()
|
||||
|
||||
|
||||
# 日志操作类型
|
||||
class ForumAction(str, Enum):
|
||||
POST_CREATE = "post:create"
|
||||
POST_UPDATE = "post:update"
|
||||
POST_DELETE = "post:delete"
|
||||
POST_PIN = "post:pin"
|
||||
POST_LOCK = "post:lock"
|
||||
REPLY_CREATE = "reply:create"
|
||||
REPLY_UPDATE = "reply:update"
|
||||
REPLY_DELETE = "reply:delete"
|
||||
USER_BAN = "user:ban"
|
||||
USER_UNBAN = "user:unban"
|
||||
SCORE_CHANGE = "score:change"
|
||||
BOARD_CREATE = "board:create"
|
||||
BOARD_UPDATE = "board:update"
|
||||
BOARD_DELETE = "board:delete"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 积分系统
|
||||
|
||||
### 6.1 积分规则
|
||||
|
||||
```python
|
||||
SCORE_RULES = {
|
||||
# 帖子相关
|
||||
"post_create": 5, # 发帖
|
||||
"post_delete": -5, # 删除帖子
|
||||
"post_liked": 2, # 帖子被点赞
|
||||
"post_best": 10, # 帖子被设为精华
|
||||
|
||||
# 回复相关
|
||||
"reply_create": 2, # 回复
|
||||
"reply_delete": -2, # 删除回复
|
||||
"reply_liked": 1, # 回复被点赞
|
||||
"reply_best": 5, # 回复被设为最佳
|
||||
"reply_adopted": 10, # 提问被采纳
|
||||
|
||||
# 活跃相关
|
||||
"daily_login": 1, # 每日登录
|
||||
"daily_post": 3, # 每日首次发帖
|
||||
"daily_reply": 1, # 每日首次回复
|
||||
}
|
||||
|
||||
|
||||
class ScoreService:
|
||||
"""积分服务"""
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
async def add_score(
|
||||
self,
|
||||
user_id: str,
|
||||
action: str,
|
||||
reason: str,
|
||||
) -> int:
|
||||
"""增加积分"""
|
||||
score_delta = SCORE_RULES.get(action, 0)
|
||||
|
||||
result = await self.db.execute(
|
||||
select(User).where(User.id == user_id)
|
||||
)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if user:
|
||||
user.forum_score += score_delta
|
||||
|
||||
# 更新统计
|
||||
if action.startswith("post_"):
|
||||
user.post_count += 1
|
||||
elif action.startswith("reply_"):
|
||||
user.reply_count += 1
|
||||
|
||||
if action == "post_liked":
|
||||
user.like_received += 1
|
||||
elif action == "reply_liked":
|
||||
user.like_received += 1
|
||||
elif action == "reply_best":
|
||||
user.best_reply_count += 1
|
||||
|
||||
await self.db.commit()
|
||||
|
||||
return score_delta
|
||||
|
||||
async def get_leaderboard(
|
||||
self,
|
||||
limit: int = 10,
|
||||
period: str = "all", # all/month/week
|
||||
) -> List[dict]:
|
||||
"""获取积分排行榜"""
|
||||
query = select(User).where(User.forum_score > 0)
|
||||
|
||||
if period == "month":
|
||||
# 本月排行
|
||||
pass
|
||||
elif period == "week":
|
||||
# 本周排行
|
||||
pass
|
||||
|
||||
query = query.order_by(desc(User.forum_score)).limit(limit)
|
||||
result = await self.db.execute(query)
|
||||
|
||||
return [
|
||||
{
|
||||
"rank": i + 1,
|
||||
"user_id": user.id,
|
||||
"username": user.username,
|
||||
"avatar": user.avatar,
|
||||
"score": user.forum_score,
|
||||
}
|
||||
for i, user in enumerate(result.scalars().all())
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. API 端点
|
||||
|
||||
### 7.1 管理端点
|
||||
|
||||
```python
|
||||
@router.post("/admin/ban/{user_id}")
|
||||
async def ban_user(
|
||||
user_id: str,
|
||||
reason: str,
|
||||
duration: Optional[int] = None, # 天数
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""禁言用户(仅管理员)"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="需要管理员权限")
|
||||
|
||||
perm_service = PermissionService(db)
|
||||
await perm_service.ban_user(
|
||||
admin=current_user,
|
||||
target_user_id=user_id,
|
||||
reason=reason,
|
||||
duration=timedelta(days=duration) if duration else None,
|
||||
)
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.post("/admin/unban/{user_id}")
|
||||
async def unban_user(
|
||||
user_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""解除禁言"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="需要管理员权限")
|
||||
|
||||
perm_service = PermissionService(db)
|
||||
await perm_service.unban_user(current_user, user_id)
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.get("/admin/logs")
|
||||
async def get_logs(
|
||||
target_id: Optional[str] = None,
|
||||
action: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""查看操作日志(仅管理员/版主)"""
|
||||
if current_user.role not in ["admin", "moderator"]:
|
||||
raise HTTPException(status_code=403, detail="权限不足")
|
||||
|
||||
# 查询日志...
|
||||
return {"logs": [], "total": 0}
|
||||
|
||||
|
||||
@router.get("/leaderboard")
|
||||
async def get_leaderboard(
|
||||
limit: int = 10,
|
||||
period: str = "all",
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取积分排行榜"""
|
||||
score_service = ScoreService(db)
|
||||
return await score_service.get_leaderboard(limit, period)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 实现步骤
|
||||
|
||||
| 步骤 | 任务 | 优先级 |
|
||||
|------|------|--------|
|
||||
| 1 | 扩展 User 模型 | 🟢 高 |
|
||||
| 2 | 创建 PermissionService | 🟢 高 |
|
||||
| 3 | 实现权限检查装饰器 | 🟢 高 |
|
||||
| 4 | 创建 ForumLog 模型 | 🟡 中 |
|
||||
| 5 | 实现操作日志记录 | 🟡 中 |
|
||||
| 6 | 创建 ScoreService | 🟡 中 |
|
||||
| 7 | 实现积分规则 | 🟡 中 |
|
||||
| 8 | 扩展管理 API | 🟡 中 |
|
||||
| 9 | 单元测试 | 🟡 中 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 核心文件变更
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `models/user.py` | 扩展角色和统计字段 |
|
||||
| `models/forum.py` | 新增 ForumLog |
|
||||
| `services/permission_service.py` | 新增 |
|
||||
| `services/score_service.py` | 新增 |
|
||||
| `services/forum_service.py` | 集成权限检查 |
|
||||
| `routers/forum.py` | 扩展管理端点 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 工作量估算
|
||||
|
||||
| 任务 | 工作量 |
|
||||
|------|--------|
|
||||
| User 模型扩展 | 0.5 天 |
|
||||
| PermissionService | 0.5 天 |
|
||||
| 操作日志 | 0.5 天 |
|
||||
| ScoreService | 0.5 天 |
|
||||
| API 端点 | 0.5 天 |
|
||||
| 单元测试 | 0.5 天 |
|
||||
| **总计** | **3 天** |
|
||||
|
||||
---
|
||||
|
||||
## 11. 验收标准
|
||||
|
||||
- [ ] User 模型正确存储角色和积分
|
||||
- [ ] PermissionService 可正确检查权限
|
||||
- [ ] 权限不足时返回 403
|
||||
- [ ] 所有管理操作记录日志
|
||||
- [ ] 积分根据规则正确增减
|
||||
- [ ] 排行榜正确排序
|
||||
- [ ] 禁言功能正常工作
|
||||
- [ ] 单元测试覆盖核心逻辑
|
||||
652
development-doc/plan/forum-update/phase-f-4-ai-integration.md
Normal file
652
development-doc/plan/forum-update/phase-f-4-ai-integration.md
Normal file
@@ -0,0 +1,652 @@
|
||||
# Phase F.4:AI 集成
|
||||
|
||||
日期:2026-04-04
|
||||
状态:待开始
|
||||
依赖:F.3(待完成)
|
||||
前置:services/forum_ai_service.py
|
||||
|
||||
---
|
||||
|
||||
## 1. 本阶段目的
|
||||
|
||||
为 Jarvis Forum 集成 AI 能力:
|
||||
|
||||
- AI 自动回复
|
||||
- 帖子摘要生成
|
||||
- 智能分类打标
|
||||
- Agent 自主发帖
|
||||
|
||||
---
|
||||
|
||||
## 2. Forum AI Service
|
||||
|
||||
### 2.1 服务架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ForumAIService │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
|
||||
│ │ AutoReply │ │ Summary │ │ SmartTagging │ │
|
||||
│ │ Service │ │ Service │ │ Service │ │
|
||||
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
|
||||
│ │ │ │ │
|
||||
│ └────────────────┼───────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────┴───────────┐ │
|
||||
│ │ LLM Service │ │
|
||||
│ │ (复用 Agent LLM) │ │
|
||||
│ └───────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 核心服务
|
||||
|
||||
```python
|
||||
class ForumAIService:
|
||||
"""论坛 AI 服务"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm_service: LLMService,
|
||||
config: ForumAIConfig,
|
||||
):
|
||||
self.llm = llm_service
|
||||
self.config = config
|
||||
|
||||
# === 自动回复 ===
|
||||
async def generate_auto_reply(
|
||||
self,
|
||||
post: ForumPost,
|
||||
replies: List[ForumReply],
|
||||
) -> Optional[str]:
|
||||
"""生成自动回复"""
|
||||
if not self.config.auto_reply_enabled:
|
||||
return None
|
||||
|
||||
# 检查是否需要回复
|
||||
if not self._should_auto_reply(post):
|
||||
return None
|
||||
|
||||
# 构建上下文
|
||||
context = self._build_reply_context(post, replies)
|
||||
|
||||
# 生成回复
|
||||
prompt = self._build_reply_prompt(context)
|
||||
response = await self.llm.agenerate(prompt)
|
||||
|
||||
return response.content if response else None
|
||||
|
||||
def _should_auto_reply(self, post: ForumPost) -> bool:
|
||||
"""判断是否应该自动回复"""
|
||||
# 指令类帖子不自动回复
|
||||
if post.category == "instruction":
|
||||
return False
|
||||
|
||||
# 有 AI 回复的不重复回复
|
||||
if any(r.is_ai_reply for r in post.replies):
|
||||
return False
|
||||
|
||||
# 检查时间窗口
|
||||
if post.last_reply_at:
|
||||
hours_since = (datetime.utcnow() - post.last_reply_at).total_seconds() / 3600
|
||||
if hours_since < self.config.auto_reply_min_hours:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _build_reply_context(
|
||||
self,
|
||||
post: ForumPost,
|
||||
replies: List[ForumReply],
|
||||
) -> str:
|
||||
"""构建回复上下文"""
|
||||
context = f"""## 帖子信息
|
||||
标题: {post.title}
|
||||
内容: {post.content[:500]}...
|
||||
|
||||
## 回复列表
|
||||
"""
|
||||
for reply in replies[-5:]: # 最近 5 条
|
||||
context += f"- {reply.user_id}: {reply.content[:200]}...\n"
|
||||
|
||||
return context
|
||||
|
||||
def _build_reply_prompt(self, context: str) -> str:
|
||||
"""构建回复提示词"""
|
||||
return f"""你是一个友好的社区助手。请根据以下帖子内容,生成一条有帮助的回复。
|
||||
|
||||
{context}
|
||||
|
||||
要求:
|
||||
1. 回复要友好、专业、有帮助
|
||||
2. 不要重复已有的观点
|
||||
3. 如果是问题,尽量给出建设性的建议
|
||||
4. 回复长度控制在 100-300 字
|
||||
5. 不要使用 Markdown 格式,直接输出文本
|
||||
|
||||
回复:"""
|
||||
|
||||
# === 摘要生成 ===
|
||||
async def generate_summary(
|
||||
self,
|
||||
content: str,
|
||||
max_length: int = 200,
|
||||
) -> str:
|
||||
"""生成帖子摘要"""
|
||||
if len(content) <= max_length:
|
||||
return content
|
||||
|
||||
prompt = f"""请为以下内容生成一个简洁的摘要,不超过 {max_length} 字。
|
||||
|
||||
内容:
|
||||
{content}
|
||||
|
||||
摘要:"""
|
||||
|
||||
response = await self.llm.agenerate(prompt)
|
||||
return response.content if response else content[:max_length]
|
||||
|
||||
# === 智能打标 ===
|
||||
async def suggest_tags(
|
||||
self,
|
||||
title: str,
|
||||
content: str,
|
||||
existing_tags: List[str],
|
||||
) -> List[str]:
|
||||
"""智能推荐标签"""
|
||||
prompt = f"""请根据以下帖子内容,推荐 3-5 个最合适的标签。
|
||||
|
||||
标题: {title}
|
||||
内容: {content[:1000]}...
|
||||
|
||||
已有标签: {', '.join(existing_tags) if existing_tags else '无'}
|
||||
|
||||
要求:
|
||||
1. 标签要简洁,2-4 个字
|
||||
2. 选择最相关的标签
|
||||
3. 如果已有标签合适可以保留
|
||||
4. 只输出标签,用逗号分隔,不要其他内容
|
||||
|
||||
标签:"""
|
||||
|
||||
response = await self.llm.agenerate(prompt)
|
||||
if not response:
|
||||
return existing_tags
|
||||
|
||||
# 解析标签
|
||||
tags = [t.strip() for t in response.content.split(",")]
|
||||
return tags[:5] # 最多 5 个
|
||||
|
||||
# === 智能分类 ===
|
||||
async def classify_category(
|
||||
self,
|
||||
title: str,
|
||||
content: str,
|
||||
) -> str:
|
||||
"""智能分类"""
|
||||
categories = ["instruction", "discussion", "question", "praise", "bug", "other"]
|
||||
|
||||
prompt = f"""请为以下帖子选择一个最合适的分类。
|
||||
|
||||
标题: {title}
|
||||
内容: {content[:500]}...
|
||||
|
||||
可选分类:
|
||||
- instruction: 指令/任务请求
|
||||
- discussion: 讨论/分享
|
||||
- question: 问题/求助
|
||||
- praise: 表扬/感谢
|
||||
- bug: Bug 反馈
|
||||
- other: 其他
|
||||
|
||||
请直接输出分类名称,不要其他内容。
|
||||
|
||||
分类:"""
|
||||
|
||||
response = await self.llm.agenerate(prompt)
|
||||
if not response:
|
||||
return "other"
|
||||
|
||||
category = response.content.strip().lower()
|
||||
if category not in categories:
|
||||
return "other"
|
||||
|
||||
return category
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Summary Service
|
||||
|
||||
### 3.1 服务实现
|
||||
|
||||
```python
|
||||
class SummaryService:
|
||||
"""帖子摘要服务"""
|
||||
|
||||
def __init__(self, cache: ForumCache):
|
||||
self.cache = cache
|
||||
|
||||
async def get_post_summary(
|
||||
self,
|
||||
post_id: str,
|
||||
post: ForumPost,
|
||||
) -> str:
|
||||
"""获取帖子摘要(带缓存)"""
|
||||
cache_key = f"summary:{post_id}"
|
||||
|
||||
# 尝试从缓存获取
|
||||
cached = await self.cache.get(cache_key)
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
# 生成摘要
|
||||
summary = await self._generate_summary(post)
|
||||
|
||||
# 存入缓存(1 小时)
|
||||
await self.cache.set(cache_key, summary, ttl=3600)
|
||||
|
||||
return summary
|
||||
|
||||
async def get_thread_summary(
|
||||
self,
|
||||
post: ForumPost,
|
||||
replies: List[ForumReply],
|
||||
) -> str:
|
||||
"""获取帖子串摘要(主帖+回复摘要)"""
|
||||
summaries = [await self.get_post_summary(post.id, post)]
|
||||
|
||||
# 汇总回复要点
|
||||
if len(replies) > 0:
|
||||
summary_prompt = f"""请总结以下回复的核心观点,用 50 字以内概括。
|
||||
|
||||
回复列表:
|
||||
{self._format_replies(replies[:10])}
|
||||
|
||||
总结:"""
|
||||
|
||||
response = await self.llm.agenerate(summary_prompt)
|
||||
if response:
|
||||
summaries.append(f"回复要点: {response.content}")
|
||||
|
||||
return "\n\n".join(summaries)
|
||||
|
||||
async def invalidate_summary(self, post_id: str) -> None:
|
||||
"""清除摘要缓存"""
|
||||
await self.cache.invalidate(f"summary:{post_id}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Agent 自主发帖
|
||||
|
||||
### 4.1 Agent Forum 工具
|
||||
|
||||
```python
|
||||
# agents/tools/forum_tools.py
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class ForumTools:
|
||||
"""Forum Agent 工具集"""
|
||||
|
||||
def __init__(self, forum_service: ForumService, ai_service: ForumAIService):
|
||||
self.forum = forum_service
|
||||
self.ai = ai_service
|
||||
|
||||
@tool
|
||||
async def create_forum_post(
|
||||
title: str,
|
||||
content: str,
|
||||
board_id: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
tags: Optional[List[str]] = None,
|
||||
) -> dict:
|
||||
"""创建论坛帖子
|
||||
|
||||
参数:
|
||||
- title: 帖子标题
|
||||
- content: 帖子内容
|
||||
- board_id: 板块 ID(可选)
|
||||
- category: 分类(可选)
|
||||
- tags: 标签列表(可选)
|
||||
"""
|
||||
data = ForumPostCreate(
|
||||
title=title,
|
||||
content=content,
|
||||
board_id=board_id,
|
||||
category=category,
|
||||
tags=tags or [],
|
||||
)
|
||||
post = await self.forum.create_post(agent_id=AGENT_ID, data=data)
|
||||
return {"post_id": post.id, "title": post.title}
|
||||
|
||||
@tool
|
||||
async def reply_to_post(
|
||||
post_id: str,
|
||||
content: str,
|
||||
) -> dict:
|
||||
"""回复帖子
|
||||
|
||||
参数:
|
||||
- post_id: 帖子 ID
|
||||
- content: 回复内容
|
||||
"""
|
||||
data = ForumReplyCreate(content=content)
|
||||
reply = await self.forum.create_reply(
|
||||
post_id=post_id,
|
||||
agent_id=AGENT_ID,
|
||||
data=data,
|
||||
)
|
||||
return {"reply_id": reply.id, "floor": reply.floor}
|
||||
|
||||
@tool
|
||||
async def search_forum_posts(
|
||||
query: str,
|
||||
board_id: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
limit: int = 10,
|
||||
) -> List[dict]:
|
||||
"""搜索论坛帖子
|
||||
|
||||
参数:
|
||||
- query: 搜索关键词
|
||||
- board_id: 限定板块(可选)
|
||||
- category: 限定分类(可选)
|
||||
- limit: 返回数量(默认 10)
|
||||
"""
|
||||
posts = await self.forum.search_posts(
|
||||
query=query,
|
||||
board_id=board_id,
|
||||
category=category,
|
||||
limit=limit,
|
||||
)
|
||||
return [
|
||||
{"id": p.id, "title": p.title, "summary": p.content[:100]}
|
||||
for p in posts
|
||||
]
|
||||
|
||||
@tool
|
||||
async def get_forum_trending(
|
||||
board_id: Optional[str] = None,
|
||||
limit: int = 5,
|
||||
) -> List[dict]:
|
||||
"""获取热门帖子
|
||||
|
||||
参数:
|
||||
- board_id: 板块 ID(可选)
|
||||
- limit: 返回数量(默认 5)
|
||||
"""
|
||||
posts = await self.forum.get_trending(
|
||||
board_id=board_id,
|
||||
limit=limit,
|
||||
)
|
||||
return [
|
||||
{
|
||||
"id": p.id,
|
||||
"title": p.title,
|
||||
"reply_count": p.reply_count,
|
||||
"view_count": p.view_count,
|
||||
}
|
||||
for p in posts
|
||||
]
|
||||
```
|
||||
|
||||
### 4.2 Agent 配置
|
||||
|
||||
```python
|
||||
# agents/prompts/forum_agent.py
|
||||
FORUM_AGENT_PROMPT = """你是一个活跃的社区成员,可以帮助用户解决问题和参与讨论。
|
||||
|
||||
## 你的能力
|
||||
1. 在论坛发帖分享信息或见解
|
||||
2. 回复其他用户的帖子
|
||||
3. 搜索论坛内容
|
||||
4. 查看热门帖子
|
||||
|
||||
## 行为规则
|
||||
1. 只在有帮助时才发帖/回复,不要刷屏
|
||||
2. 回复要专业、有建设性
|
||||
3. 如果用户的问题已经解决,不要重复回答
|
||||
4. 遇到 Bug 或问题可以主动发帖提醒
|
||||
5. 定期查看论坛,如果有重要帖子可以参与讨论
|
||||
|
||||
## 当前时间
|
||||
{current_time}
|
||||
|
||||
## 用户信息
|
||||
用户名: {username}
|
||||
积分: {forum_score}
|
||||
"""
|
||||
|
||||
# Agent 可用的 Forum 工具
|
||||
FORUM_TOOLS = [
|
||||
create_forum_post,
|
||||
reply_to_post,
|
||||
search_forum_posts,
|
||||
get_forum_trending,
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 定时任务
|
||||
|
||||
### 5.1 自动回复任务
|
||||
|
||||
```python
|
||||
# tasks/forum_auto_reply.py
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
|
||||
|
||||
async def auto_reply_task():
|
||||
"""自动回复任务"""
|
||||
# 获取需要回复的帖子
|
||||
posts = await forum_service.get_pending_posts(
|
||||
min_age_hours=settings.auto_reply_min_hours,
|
||||
limit=10,
|
||||
)
|
||||
|
||||
for post in posts:
|
||||
replies = await forum_service.get_replies(post.id)
|
||||
|
||||
# 生成回复
|
||||
response = await ai_service.generate_auto_reply(post, replies)
|
||||
|
||||
if response:
|
||||
# 发布回复
|
||||
await forum_service.create_reply(
|
||||
post_id=post.id,
|
||||
agent_id=AGENT_ID,
|
||||
data=ForumReplyCreate(content=response),
|
||||
)
|
||||
|
||||
# 更新回复计数
|
||||
await forum_service.increment_reply_count(post.id)
|
||||
|
||||
|
||||
def setup_forum_scheduler(scheduler: AsyncIOScheduler):
|
||||
"""配置论坛定时任务"""
|
||||
# 每小时检查一次自动回复
|
||||
scheduler.add_job(
|
||||
auto_reply_task,
|
||||
"interval",
|
||||
hours=1,
|
||||
id="forum_auto_reply",
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 配置项
|
||||
|
||||
### 6.1 AI 配置
|
||||
|
||||
```python
|
||||
class ForumAIConfig:
|
||||
"""Forum AI 配置"""
|
||||
|
||||
# 自动回复
|
||||
auto_reply_enabled: bool = True
|
||||
auto_reply_min_hours: int = 24 # 帖子发布 N 小时后才自动回复
|
||||
auto_reply_max_per_day: int = 10 # 每天最多自动回复 N 条
|
||||
|
||||
# 摘要生成
|
||||
summary_enabled: bool = True
|
||||
summary_max_length: int = 200
|
||||
summary_cache_ttl: int = 3600 # 缓存 1 小时
|
||||
|
||||
# 智能打标
|
||||
smart_tagging_enabled: bool = True
|
||||
smart_tagging_max_tags: int = 5
|
||||
|
||||
# 智能分类
|
||||
smart_classification_enabled: bool = True
|
||||
|
||||
|
||||
# settings.py
|
||||
class Settings(BaseSettings):
|
||||
# Forum AI
|
||||
forum_ai_auto_reply: bool = True
|
||||
forum_ai_summary: bool = True
|
||||
forum_ai_smart_tagging: bool = True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. API 端点
|
||||
|
||||
### 7.1 AI 相关端点
|
||||
|
||||
```python
|
||||
@router.post("/posts/{post_id}/generate-summary")
|
||||
async def generate_post_summary(
|
||||
post_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""生成帖子摘要"""
|
||||
result = await db.execute(
|
||||
select(ForumPost).where(ForumPost.id == post_id)
|
||||
)
|
||||
post = result.scalar_one_or_none()
|
||||
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="帖子不存在")
|
||||
|
||||
ai_service = ForumAIService(llm_service, config)
|
||||
summary = await ai_service.generate_summary(post.content)
|
||||
|
||||
return {"summary": summary}
|
||||
|
||||
|
||||
@router.post("/posts/suggest-tags")
|
||||
async def suggest_post_tags(
|
||||
title: str,
|
||||
content: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""推荐帖子标签"""
|
||||
ai_service = ForumAIService(llm_service, config)
|
||||
tags = await ai_service.suggest_tags(title, content, [])
|
||||
|
||||
return {"tags": tags}
|
||||
|
||||
|
||||
@router.post("/posts/classify")
|
||||
async def classify_post(
|
||||
title: str,
|
||||
content: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""分类帖子"""
|
||||
ai_service = ForumAIService(llm_service, config)
|
||||
category = await ai_service.classify_category(title, content)
|
||||
|
||||
return {"category": category}
|
||||
|
||||
|
||||
@router.get("/posts/{post_id}/ai-status")
|
||||
async def get_ai_status(
|
||||
post_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取帖子 AI 状态"""
|
||||
result = await db.execute(
|
||||
select(ForumPost).where(ForumPost.id == post_id)
|
||||
)
|
||||
post = result.scalar_one_or_none()
|
||||
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="帖子不存在")
|
||||
|
||||
# 检查 AI 回复状态
|
||||
has_ai_reply = any(r.is_ai_reply for r in post.replies)
|
||||
summary_cached = await cache.get(f"summary:{post_id}")
|
||||
|
||||
return {
|
||||
"has_ai_reply": has_ai_reply,
|
||||
"summary_available": bool(summary_cached),
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 实现步骤
|
||||
|
||||
| 步骤 | 任务 | 优先级 |
|
||||
|------|------|--------|
|
||||
| 1 | 创建 ForumAIService | 🟢 高 |
|
||||
| 2 | 实现自动回复 | 🟢 高 |
|
||||
| 3 | 实现摘要生成 | 🟡 中 |
|
||||
| 4 | 实现智能打标 | 🟡 中 |
|
||||
| 5 | 实现智能分类 | 🟡 中 |
|
||||
| 6 | 创建 ForumTools | 🟢 高 |
|
||||
| 7 | 配置定时任务 | 🟡 中 |
|
||||
| 8 | 扩展 API 端点 | 🟡 中 |
|
||||
| 9 | 单元测试 | 🟡 中 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 核心文件变更
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `services/forum_ai_service.py` | 新增 |
|
||||
| `services/summary_service.py` | 新增 |
|
||||
| `agents/tools/forum_tools.py` | 新增 |
|
||||
| `agents/prompts/forum_agent.py` | 新增 |
|
||||
| `tasks/forum_auto_reply.py` | 新增 |
|
||||
| `routers/forum.py` | 扩展 AI 端点 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 工作量估算
|
||||
|
||||
| 任务 | 工作量 |
|
||||
|------|--------|
|
||||
| ForumAIService | 1 天 |
|
||||
| 自动回复 | 1 天 |
|
||||
| 摘要/打标/分类 | 1 天 |
|
||||
| ForumTools | 1 天 |
|
||||
| 定时任务 | 0.5 天 |
|
||||
| API 端点 | 0.5 天 |
|
||||
| 单元测试 | 0.5 天 |
|
||||
| **总计** | **5.5 天** |
|
||||
|
||||
---
|
||||
|
||||
## 11. 验收标准
|
||||
|
||||
- [ ] ForumAIService 可正常调用 LLM
|
||||
- [ ] 自动回复功能正常工作
|
||||
- [ ] 摘要生成功能正常
|
||||
- [ ] 智能打标推荐准确
|
||||
- [ ] 智能分类推荐准确
|
||||
- [ ] ForumTools 可被 Agent 调用
|
||||
- [ ] 定时任务正常执行
|
||||
- [ ] API 端点正常工作
|
||||
- [ ] 单元测试覆盖核心逻辑
|
||||
Reference in New Issue
Block a user