feat(agents): Phase 8.4-10.5 built-in plugins, bundled skills, coordinator

This commit is contained in:
2026-04-04 23:24:34 +08:00
parent 88955ed550
commit d18167826e
105 changed files with 14780 additions and 15685 deletions

View File

@@ -0,0 +1,488 @@
# Phase F.2API 增强与安全
日期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 保持向后兼容
- [ ] 单元测试覆盖核心逻辑