2026-09-21 15:56:52 +08:00
|
|
|
|
"""通用 FastAPI 依赖"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from fastapi import Depends, HTTPException, status
|
2026-09-21 17:07:46 +08:00
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
2026-09-21 15:56:52 +08:00
|
|
|
|
|
2026-09-21 17:07:46 +08:00
|
|
|
|
from app.core.database import get_db
|
2026-09-21 15:56:52 +08:00
|
|
|
|
from app.core.roles import ADMIN_ROLES
|
|
|
|
|
|
from app.services.auth_service import get_current_user
|
2026-09-21 17:07:46 +08:00
|
|
|
|
from app.services.data_scope_service import DataScope, resolve_data_scope
|
2026-09-21 15:56:52 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def require_roles(*roles: str):
|
|
|
|
|
|
"""生成「限定角色」依赖,避免同一个内联判断被复制到每个端点。
|
|
|
|
|
|
|
|
|
|
|
|
用法::
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/x")
|
|
|
|
|
|
async def x(current_user: dict = Depends(require_admin)):
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
失败一律 403 且不透露允许的角色集合(避免给探测者提供线索)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
allowed = frozenset(roles)
|
|
|
|
|
|
|
|
|
|
|
|
async def _guard(current_user: dict = Depends(get_current_user)) -> dict:
|
|
|
|
|
|
if (current_user or {}).get("role") not in allowed:
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
|
|
|
|
detail="当前角色无权访问该接口",
|
|
|
|
|
|
)
|
|
|
|
|
|
return current_user
|
|
|
|
|
|
|
|
|
|
|
|
return _guard
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 审计日志等高权限接口复用同一实例
|
|
|
|
|
|
require_admin = require_roles(*ADMIN_ROLES)
|
2026-09-21 17:07:46 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def get_data_scope(
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: dict = Depends(get_current_user),
|
|
|
|
|
|
) -> DataScope:
|
|
|
|
|
|
"""业务分组数据范围 —— 列表 / 统计类接口的统一入口。
|
|
|
|
|
|
|
|
|
|
|
|
⚠️ **硬依赖 get_current_user**:匿名请求直接 401,**不放行成空范围**。
|
|
|
|
|
|
两个理由:
|
|
|
|
|
|
1. 「匿名 = 空范围」会让大屏变成一个永远空白的页面,比 401 更难排查;
|
|
|
|
|
|
2. 匿名不过滤本身就是绕过口子 —— 谁能不登录看全厂数据,分组就形同虚设。
|
|
|
|
|
|
|
|
|
|
|
|
刻意**不加 TTL 缓存**:把人踢出组必须立即生效,任何缓存都会造成
|
|
|
|
|
|
「已经踢了还在看」的窗口。这里是单表 + 索引扫描,成本可忽略;
|
|
|
|
|
|
FastAPI 的依赖缓存在单请求内已经生效(同一请求多处 Depends 只解析一次)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
return await resolve_data_scope(db, current_user)
|