modify: 权限认证完善

This commit is contained in:
2026-04-30 14:38:43 +08:00
parent 70a1ebdf4f
commit 072a59a169
25 changed files with 3317 additions and 361 deletions
+237 -13
View File
@@ -1,15 +1,20 @@
from typing import Optional
from typing import Optional, List, Sequence
from uuid import UUID
from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import select
from ..model import Permission, RolePermission, Role, User
from ..model import Permission, PermissionUpdate, RolePermission, Role, User
from ..core.cache import acached, get_cache
from .user import UserService
from .role import RoleService
# 用户权限缓存的 TTL(秒),权限变更后最多 2 分钟自动刷新
_USER_PERMS_CACHE_TTL = 120
class PermissionService:
"""权限服务类,提供权限的创建、查询、分配和解除绑定等功能"""
@@ -69,6 +74,189 @@ class PermissionService:
)
return result.scalar_one_or_none()
@classmethod
async def list_permissions(
cls,
session: AsyncSession,
skip: int = 0,
limit: int = 100,
resource: Optional[str] = None,
) -> Sequence[Permission]:
"""
获取权限列表,支持分页和按资源筛选
:param session: 数据库会话对象
:param skip: 跳过的记录数,用于分页
:param limit: 返回的最大记录数,用于分页
:param resource: 资源名称筛选条件,可选
:return: 权限对象列表
"""
query = select(Permission)
if resource:
query = query.where(Permission.resource == resource)
query = query.offset(skip).limit(limit)
result = await session.execute(query)
return result.scalars().all()
@classmethod
async def update_permission(
cls,
session: AsyncSession,
permission_id: int,
permission_update: PermissionUpdate,
) -> Permission:
"""
更新权限信息
:param session: 数据库会话对象
:param permission_id: 权限ID
:param permission_update: 权限更新数据
:return: 更新后的权限对象
:raises HTTPException: 当权限不存在时抛出404错误
"""
permission = await cls.get_permission_by_id(session, permission_id)
if not permission:
raise HTTPException(status_code=404, detail="Permission not found")
# 只更新提供的字段
update_data = permission_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(permission, field, value)
await session.commit()
await session.refresh(permission)
return permission
@classmethod
async def delete_permission(
cls,
session: AsyncSession,
permission_id: int,
) -> bool:
"""
删除权限
:param session: 数据库会话对象
:param permission_id: 权限ID
:return: 删除成功返回True
:raises HTTPException: 当权限不存在或正在被使用时抛出对应错误
"""
# 检查权限是否被角色使用
result = await session.execute(
select(RolePermission).where(
RolePermission.permission_id == permission_id
)
)
if result.scalars().all():
raise HTTPException(
status_code=400, detail="Permission is assigned to roles, cannot delete"
)
permission = await cls.get_permission_by_id(session, permission_id)
if not permission:
raise HTTPException(status_code=404, detail="Permission not found")
await session.delete(permission)
await session.commit()
return True
@classmethod
async def get_permissions_by_role(
cls,
session: AsyncSession,
role_id: int,
) -> Sequence[Permission]:
"""
获取指定角色的所有权限
使用 JOIN 查询直接从数据库获取,避免在异步上下文中触发懒加载。
:param session: 数据库会话对象
:param role_id: 角色ID
:return: 权限对象列表
:raises HTTPException: 当角色不存在时抛出404错误
"""
from ..model.relationship_table import RolePermission as RolePermTB
stmt = (
select(Permission)
.join(RolePermTB, RolePermTB.permission_id == Permission.id) # type: ignore
.where(RolePermTB.role_id == role_id)
)
result = await session.execute(stmt)
permissions = result.scalars().all()
if not permissions:
# 确认角色是否存在
role = await RoleService.get_role_by_id(session, role_id)
if not role:
raise HTTPException(status_code=404, detail="Role not found")
return permissions
# ---- TTL 缓存辅助方法 ----
@classmethod
def _get_user_perms_cache_key(cls, user_id: UUID) -> str:
"""生成用户权限缓存的键"""
return f"user_perms:{user_id}"
@classmethod
def _invalidate_user_perms_cache(cls, user_id: UUID):
"""
清除指定用户的权限缓存
:param user_id: 用户UUID
"""
cache = get_cache()
cache_key = cls._get_user_perms_cache_key(user_id)
cache.delete(cache_key)
# ---- 核心业务方法 ----
@classmethod
@acached(ttl=_USER_PERMS_CACHE_TTL, key_func=lambda cls, session, user_id: f"user_perms:{user_id}")
async def get_permissions_by_user(
cls,
session: AsyncSession,
user_id: UUID,
) -> List[Permission]:
"""
获取指定用户的所有权限(通过用户的所有角色汇总)
使用 @acached 装饰器自动缓存查询结果,缓存键为 user_perms:{user_id}。
TTL 120 秒作为兜底机制。当用户权限发生变更时,主动调用
_invalidate_user_perms_cache() 清除缓存。
:param session: 数据库会话对象
:param user_id: 用户UUID
:return: 去重后的权限对象列表
:raises HTTPException: 当用户不存在时抛出404错误
"""
# 通过 role_permission 和 user_role 关联表进行 JOIN 查询
from ..model.relationship_table import UserRole as UserRoleTB, RolePermission as RolePermTB
stmt = (
select(Permission)
.join(RolePermTB, RolePermTB.permission_id == Permission.id) # type: ignore
.join(Role, Role.id == RolePermTB.role_id) # type: ignore
.join(UserRoleTB, UserRoleTB.role_id == Role.id) # type: ignore
.where(UserRoleTB.user_id == user_id)
.distinct()
)
result = await session.execute(stmt)
permissions = result.scalars().all()
if not permissions:
# 确认用户是否存在
user = await UserService.get_user_by_id(user_id, session)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return list(permissions)
@classmethod
async def assign_permission_to_role(
cls, session: AsyncSession, role_id: int, permission_id: int
@@ -80,7 +268,26 @@ class PermissionService:
:param role_id: 角色ID
:param permission_id: 权限ID
:return: 创建的角色权限关联对象
:raises HTTPException: 当角色或权限不存在,或权限已分配给角色时抛出错误
"""
role = await RoleService.get_role_by_id(session, role_id)
if not role:
raise HTTPException(status_code=404, detail="Role not found")
permission = await cls.get_permission_by_id(session, permission_id)
if not permission:
raise HTTPException(status_code=404, detail="Permission not found")
# 检查是否已经分配
existing = await session.execute(
select(RolePermission).where(
RolePermission.role_id == role_id,
RolePermission.permission_id == permission_id,
)
)
if existing.scalar_one_or_none():
raise HTTPException(status_code=400, detail="Permission already assigned to role")
role_permission = RolePermission(role_id=role_id, permission_id=permission_id)
session.add(role_permission)
await session.commit()
@@ -133,6 +340,8 @@ class PermissionService:
user.roles.append(role)
await session.commit()
await session.refresh(user)
# 清除该用户的权限缓存,下次查询时重新加载
cls._invalidate_user_perms_cache(user_id)
return user
@classmethod
@@ -142,6 +351,8 @@ class PermissionService:
"""
从角色中解除权限绑定
直接通过 RolePermission 关联表删除记录,避免在异步上下文中触发懒加载。
:param session: 数据库会话对象
:param role_id: 角色ID
:param permission_id: 权限ID
@@ -153,8 +364,15 @@ class PermissionService:
if role is None:
raise HTTPException(status_code=404, detail="Role not found")
# 从角色的权限列表中移除指定权限
role.permissions = [p for p in role.permissions if p.id != permission_id]
# 通过关联表删除记录
from ..model.relationship_table import RolePermission as RolePermTB
from sqlmodel import delete
stmt = delete(RolePermTB).where(
RolePermTB.role_id == role_id, # type: ignore
RolePermTB.permission_id == permission_id, # type: ignore
)
await session.execute(stmt)
await session.commit()
await session.refresh(role)
return role
@@ -166,7 +384,7 @@ class PermissionService:
"""
从用户中解除权限绑定
通过找到用户的专属角色,从该角色中移除指定权限
通过找到用户的专属角色,从该角色中移除指定权限。
:param session: 数据库会话对象
:param user_id: 用户UUID
@@ -183,11 +401,17 @@ class PermissionService:
if role is None:
raise HTTPException(status_code=404, detail="Role not found")
# 从角色权限列表中移除指定权限
role.permissions = [
permission
for permission in role.permissions
if permission.id != permission_id
]
await session.commit()
await session.refresh(role)
if role.id is not None:
# 直接通过关联表删除记录
from ..model.relationship_table import RolePermission as RolePermTB
from sqlmodel import delete
stmt = delete(RolePermTB).where(
RolePermTB.role_id == role.id, # type: ignore
RolePermTB.permission_id == permission_id, # type: ignore
)
await session.execute(stmt)
await session.commit()
await session.refresh(role)
# 清除该用户的权限缓存
cls._invalidate_user_perms_cache(user_id)