baseline: Clover 独立仓库首次基线提交

将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。
当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包),
M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-16 11:30:22 +08:00
commit 6a2632da70
253 changed files with 27467 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
"""
app/middleware/workspace_guard.py — 多租户隔离中间件
所有业务接口强制注入 workspace_id基石C
读操作信任 JWT写操作+切换 workspace 查 workspace_members 校验。
"""
import logging
from typing import Annotated
import jwt
from fastapi import Depends, Header
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.response import raise_forbidden, raise_unauthorized
from app.core.security import decode_access_token
from app.models.workspace import WorkspaceMember
logger = logging.getLogger(__name__)
class CurrentUser:
"""JWT 解码后的请求上下文,注入到每个路由函数。"""
def __init__(
self,
user_id: int,
workspace_id: int,
role: str,
):
self.user_id = user_id
self.workspace_id = workspace_id
self.role = role
def _extract_token(authorization: str | None) -> str:
"""从 Authorization: Bearer <token> 中提取 token。"""
if not authorization or not authorization.startswith("Bearer "):
raise_unauthorized("缺少 Authorization header")
return authorization.split(" ", 1)[1]
async def get_current_user(
authorization: Annotated[str | None, Header()] = None,
) -> CurrentUser:
"""
FastAPI 依赖:解码 JWT返回 CurrentUser。
所有业务路由 Depends(get_current_user)。
"""
token = _extract_token(authorization)
try:
payload = decode_access_token(token)
except jwt.ExpiredSignatureError:
raise_unauthorized("Token 已过期")
except jwt.PyJWTError:
raise_unauthorized("Token 无效")
return CurrentUser(
user_id=int(payload["user_id"]),
workspace_id=int(payload["current_workspace_id"]),
role=payload["role"],
)
def require_write_permission(
current_user: Annotated[CurrentUser, Depends(get_current_user)],
db: Annotated[Session, Depends(get_db)],
) -> CurrentUser:
"""
写操作依赖:查 workspace_members 校验当前用户确实属于此 workspace。
读操作用 get_current_user 即可JWT 加速)。
"""
member = (
db.query(WorkspaceMember)
.filter(
WorkspaceMember.workspace_id == current_user.workspace_id,
WorkspaceMember.user_id == current_user.user_id,
)
.first()
)
if not member:
logger.warning(
"workspace permission denied: user=%s workspace=%s",
current_user.user_id,
current_user.workspace_id,
)
raise_forbidden("无权限访问此 workspace")
return current_user
def require_admin(
current_user: Annotated[CurrentUser, Depends(require_write_permission)],
) -> CurrentUser:
"""仅管理员可访问的路由依赖。"""
if current_user.role != "admin":
raise_forbidden("需要管理员权限")
return current_user
def require_supervisor_or_above(
current_user: Annotated[CurrentUser, Depends(require_write_permission)],
) -> CurrentUser:
"""组长及以上supervisor/admin"""
if current_user.role not in ("supervisor", "admin"):
raise_forbidden("需要组长或管理员权限")
return current_user