feat(agents): Phase 8.4-10.5 built-in plugins, bundled skills, coordinator
This commit is contained in:
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
|
||||
- [ ] 所有管理操作记录日志
|
||||
- [ ] 积分根据规则正确增减
|
||||
- [ ] 排行榜正确排序
|
||||
- [ ] 禁言功能正常工作
|
||||
- [ ] 单元测试覆盖核心逻辑
|
||||
Reference in New Issue
Block a user