Files
selfrelease 7ec4fb0747 feat(backend): AI 服务层 — 千问流式 LLM + 月报解析 + 健康度计算 + 风险检测 + Copilot
- LLM 客户端:全部 SSE 流式输出,兼容 OpenAI 接口
- AI 月报解析:SSE 流式端点 POST /reports/{id}/parse
- 健康度计算引擎:四维评分(财务/经营/AI商业化/AI成本)
- 风险自动检测引擎:6 条规则自动检测指标越界
- AI Copilot:SSE 流式对话 POST /copilot/chat
- 权限中间件:角色级 + 字段级权限控制
- 测试:21 个新测试(健康度 8 + 风险检测 8 + 权限 5),总计 64 passed
2026-07-18 22:16:40 +08:00

92 lines
2.5 KiB
Python

"""角色级 + 字段级权限中间件。
基于用户角色控制 API 访问权限和数据可见性。
"""
from fastapi import Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.dependencies import get_current_user
from app.models.user import User
# 角色层级
ROLE_HIERARCHY = {
"admin": 100,
"investor": 50,
"founder": 20,
}
def require_role(*allowed_roles: str):
"""角色级权限依赖工厂。
用法:
@router.get("/admin-only", dependencies=[Depends(require_role("admin"))])
"""
async def _check(user: User = Depends(get_current_user)) -> User:
if user.role not in allowed_roles:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"需要角色: {', '.join(allowed_roles)},当前角色: {user.role}",
)
return user
return _check
def require_min_role(min_role: str):
"""最低角色层级权限依赖工厂。
用法:
@router.get("/investor+", dependencies=[Depends(require_min_role("investor"))])
"""
min_level = ROLE_HIERARCHY.get(min_role, 0)
async def _check(user: User = Depends(get_current_user)) -> User:
user_level = ROLE_HIERARCHY.get(user.role, 0)
if user_level < min_level:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"需要最低角色: {min_role},当前角色: {user.role}",
)
return user
return _check
# 字段级权限:不同角色可见的字段
FIELD_VISIBILITY = {
"founder": {
"company": ["id", "name", "industry", "stage", "description", "website"],
"report": ["id", "company_id", "period_year", "period_month", "status", "raw_content"],
},
"investor": {
"company": ["*"], # 全部可见
"report": ["*"],
},
"admin": {
"company": ["*"],
"report": ["*"],
},
}
def filter_fields(
resource: str,
data: dict,
user: User,
) -> dict:
"""根据用户角色过滤返回字段。
Args:
resource: 资源名称(company / report 等)
data: 原始数据字典
user: 当前用户
Returns:
过滤后的数据字典
"""
allowed = FIELD_VISIBILITY.get(user.role, {}).get(resource, ["*"])
if "*" in allowed:
return data
return {k: v for k, v in data.items() if k in allowed}