modify: 权限认证完善
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user