Files
JARVIS/development-doc/plan/forum-update/phase-f-3-permissions.md

541 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
- [ ] 所有管理操作记录日志
- [ ] 积分根据规则正确增减
- [ ] 排行榜正确排序
- [ ] 禁言功能正常工作
- [ ] 单元测试覆盖核心逻辑