feat(auth): 添加权限管理功能
实现权限管理模块,包括: 1. 权限模型、路由和中间件 2. 角色权限多对多关联 3. 权限验证中间件 4. 初始化权限数据 5. 权限管理API接口
This commit is contained in:
parent
b9150c3e29
commit
a3206bb0f6
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/auth/apps/permissions/__pycache__/urls.cpython-313.pyc
Normal file
BIN
backend/auth/apps/permissions/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
173
backend/auth/apps/permissions/urls.py
Normal file
173
backend/auth/apps/permissions/urls.py
Normal file
@ -0,0 +1,173 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from fastcrud import FastCRUD
|
||||
|
||||
from database import get_db
|
||||
from models import Permission, Role
|
||||
from schemas import (
|
||||
PermissionCreate,
|
||||
PermissionResponse,
|
||||
RolePermissionAssign,
|
||||
RolePermissionResponse,
|
||||
)
|
||||
from middleware import get_current_user
|
||||
from models import User
|
||||
|
||||
router = APIRouter(prefix="/permissions", tags=["permissions"])
|
||||
|
||||
# 创建FastCRUD实例
|
||||
permission_crud = FastCRUD(Permission, PermissionResponse)
|
||||
|
||||
|
||||
@router.post("/", response_model=PermissionResponse)
|
||||
async def create_permission(
|
||||
permission_data: PermissionCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""创建权限"""
|
||||
# 检查权限编码是否已存在
|
||||
result = await db.execute(select(Permission).filter(Permission.code == permission_data.code))
|
||||
existing_permission = result.scalar_one_or_none()
|
||||
if existing_permission:
|
||||
raise HTTPException(status_code=400, detail="权限编码已存在")
|
||||
|
||||
# 创建权限实例
|
||||
db_permission = Permission(
|
||||
name=permission_data.name,
|
||||
code=permission_data.code,
|
||||
description=permission_data.description,
|
||||
)
|
||||
|
||||
db.add(db_permission)
|
||||
await db.commit()
|
||||
await db.refresh(db_permission)
|
||||
|
||||
return db_permission
|
||||
|
||||
|
||||
@router.get("/", response_model=list[PermissionResponse])
|
||||
async def get_permissions(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""获取所有权限"""
|
||||
result = await db.execute(select(Permission))
|
||||
permissions = result.scalars().all()
|
||||
return permissions
|
||||
|
||||
|
||||
@router.get("/{permission_id}", response_model=PermissionResponse)
|
||||
async def get_permission(
|
||||
permission_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""获取单个权限"""
|
||||
permission = await permission_crud.get(db, id=permission_id)
|
||||
if not permission:
|
||||
raise HTTPException(status_code=404, detail="权限不存在")
|
||||
return permission
|
||||
|
||||
|
||||
@router.put("/{permission_id}", response_model=PermissionResponse)
|
||||
async def update_permission(
|
||||
permission_id: int,
|
||||
permission_data: PermissionCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""更新权限"""
|
||||
# 检查权限编码是否被其他权限使用
|
||||
result = await db.execute(
|
||||
select(Permission).filter(Permission.code == permission_data.code, Permission.id != permission_id)
|
||||
)
|
||||
existing_permission = result.scalar_one_or_none()
|
||||
if existing_permission:
|
||||
raise HTTPException(status_code=400, detail="权限编码已存在")
|
||||
|
||||
# 准备更新数据
|
||||
update_data = permission_data.model_dump()
|
||||
|
||||
# 使用FastCRUD更新权限
|
||||
updated_permission = await permission_crud.update(
|
||||
db, update_data, id=permission_id, schema_to_select=PermissionResponse, return_as_model=True
|
||||
)
|
||||
if not updated_permission:
|
||||
raise HTTPException(status_code=404, detail="权限不存在")
|
||||
return updated_permission
|
||||
|
||||
|
||||
@router.delete("/{permission_id}")
|
||||
async def delete_permission(
|
||||
permission_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""删除权限"""
|
||||
# 检查权限是否存在
|
||||
result = await db.execute(select(Permission).filter(Permission.id == permission_id))
|
||||
existing_permission = result.scalar_one_or_none()
|
||||
if not existing_permission:
|
||||
raise HTTPException(status_code=404, detail="权限不存在")
|
||||
|
||||
# 使用FastCRUD删除权限
|
||||
deleted = await permission_crud.delete(db, id=permission_id)
|
||||
return {"message": "权限删除成功"}
|
||||
|
||||
|
||||
# 角色权限关联接口
|
||||
@router.post("/assign", response_model=RolePermissionResponse)
|
||||
async def assign_permissions_to_role(
|
||||
data: RolePermissionAssign,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""为角色分配权限"""
|
||||
# 查找角色
|
||||
result = await db.execute(select(Role).filter(Role.id == data.role_id))
|
||||
role = result.scalar_one_or_none()
|
||||
if not role:
|
||||
raise HTTPException(status_code=404, detail="角色不存在")
|
||||
|
||||
# 查找要分配的权限
|
||||
permissions = []
|
||||
for permission_id in data.permission_ids:
|
||||
result = await db.execute(select(Permission).filter(Permission.id == permission_id))
|
||||
permission = result.scalar_one_or_none()
|
||||
if permission:
|
||||
permissions.append(permission)
|
||||
|
||||
# 更新角色的权限
|
||||
role.permissions = permissions
|
||||
await db.commit()
|
||||
await db.refresh(role)
|
||||
|
||||
# 返回角色权限信息
|
||||
return RolePermissionResponse(
|
||||
role_id=role.id,
|
||||
role_name=role.name,
|
||||
permissions=[PermissionResponse.from_orm(p) for p in role.permissions]
|
||||
)
|
||||
|
||||
|
||||
@router.get("/role/{role_id}", response_model=RolePermissionResponse)
|
||||
async def get_role_permissions(
|
||||
role_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""获取角色的权限列表"""
|
||||
# 查找角色
|
||||
result = await db.execute(select(Role).filter(Role.id == role_id))
|
||||
role = result.scalar_one_or_none()
|
||||
if not role:
|
||||
raise HTTPException(status_code=404, detail="角色不存在")
|
||||
|
||||
# 返回角色权限信息
|
||||
return RolePermissionResponse(
|
||||
role_id=role.id,
|
||||
role_name=role.name,
|
||||
permissions=[PermissionResponse.from_orm(p) for p in role.permissions]
|
||||
)
|
||||
@ -1,10 +1,12 @@
|
||||
from fastapi import APIRouter
|
||||
from .users.urls import router as users_router
|
||||
from .roles.urls import router as roles_router
|
||||
from .permissions.urls import router as permissions_router
|
||||
|
||||
# 创建主路由
|
||||
api_router = APIRouter()
|
||||
|
||||
# 包含子路由
|
||||
api_router.include_router(users_router)
|
||||
api_router.include_router(roles_router)
|
||||
api_router.include_router(roles_router)
|
||||
api_router.include_router(permissions_router)
|
||||
BIN
backend/auth/booksystem.db
Normal file
BIN
backend/auth/booksystem.db
Normal file
Binary file not shown.
@ -23,4 +23,4 @@ async def get_db():
|
||||
try:
|
||||
yield session
|
||||
finally:
|
||||
await session.close()
|
||||
await session.close()
|
||||
|
||||
@ -1,9 +1,116 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from models import Role, User
|
||||
from models import Role, User, Permission
|
||||
from utils.password import get_password_hash
|
||||
|
||||
|
||||
async def init_permissions(db: AsyncSession):
|
||||
"""初始化权限数据"""
|
||||
# 定义需要初始化的权限
|
||||
required_permissions = [
|
||||
{"name": "用户管理", "code": "user:manage", "description": "用户管理权限"},
|
||||
{"name": "用户查询", "code": "user:read", "description": "用户查询权限"},
|
||||
{"name": "用户创建", "code": "user:create", "description": "用户创建权限"},
|
||||
{"name": "用户更新", "code": "user:update", "description": "用户更新权限"},
|
||||
{"name": "用户删除", "code": "user:delete", "description": "用户删除权限"},
|
||||
{"name": "角色管理", "code": "role:manage", "description": "角色管理权限"},
|
||||
{"name": "角色查询", "code": "role:read", "description": "角色查询权限"},
|
||||
{"name": "角色创建", "code": "role:create", "description": "角色创建权限"},
|
||||
{"name": "角色更新", "code": "role:update", "description": "角色更新权限"},
|
||||
{"name": "角色删除", "code": "role:delete", "description": "角色删除权限"},
|
||||
{"name": "权限管理", "code": "permission:manage", "description": "权限管理权限"},
|
||||
{"name": "权限查询", "code": "permission:read", "description": "权限查询权限"},
|
||||
{"name": "权限创建", "code": "permission:create", "description": "权限创建权限"},
|
||||
{"name": "权限更新", "code": "permission:update", "description": "权限更新权限"},
|
||||
{"name": "权限删除", "code": "permission:delete", "description": "权限删除权限"},
|
||||
]
|
||||
|
||||
# 检查现有权限
|
||||
result = await db.execute(select(Permission))
|
||||
existing_permissions = result.scalars().all()
|
||||
existing_permission_codes = {perm.code for perm in existing_permissions}
|
||||
|
||||
# 检查需要创建或更新的权限
|
||||
permissions_to_add = []
|
||||
permissions_to_update = []
|
||||
|
||||
for perm_data in required_permissions:
|
||||
perm_code = perm_data["code"]
|
||||
if perm_code not in existing_permission_codes:
|
||||
# 权限不存在,需要创建
|
||||
permissions_to_add.append(Permission(**perm_data))
|
||||
else:
|
||||
# 权限存在,检查是否需要更新
|
||||
existing_perm = next(
|
||||
perm for perm in existing_permissions if perm.code == perm_code
|
||||
)
|
||||
if existing_perm.name != perm_data["name"] or existing_perm.description != perm_data["description"]:
|
||||
existing_perm.name = perm_data["name"]
|
||||
existing_perm.description = perm_data["description"]
|
||||
permissions_to_update.append(existing_perm)
|
||||
|
||||
# 执行创建和更新操作
|
||||
if permissions_to_add:
|
||||
for perm in permissions_to_add:
|
||||
db.add(perm)
|
||||
await db.commit()
|
||||
print(f"创建了 {len(permissions_to_add)} 个权限")
|
||||
|
||||
if permissions_to_update:
|
||||
await db.commit()
|
||||
print(f"更新了 {len(permissions_to_update)} 个权限")
|
||||
|
||||
if not permissions_to_add and not permissions_to_update:
|
||||
print("权限数据已存在且无需更新,跳过初始化")
|
||||
|
||||
print("权限数据初始化/更新完成")
|
||||
|
||||
|
||||
async def init_role_permissions(db: AsyncSession):
|
||||
"""初始化角色权限分配"""
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
# 获取管理员角色(使用预加载避免懒加载问题)
|
||||
result = await db.execute(
|
||||
select(Role)
|
||||
.filter(Role.name == "管理员")
|
||||
.options(selectinload(Role.permissions))
|
||||
)
|
||||
admin_role = result.scalar_one_or_none()
|
||||
if not admin_role:
|
||||
print("管理员角色不存在,请先初始化角色数据")
|
||||
return
|
||||
|
||||
# 获取所有权限
|
||||
result = await db.execute(select(Permission))
|
||||
all_permissions = result.scalars().all()
|
||||
|
||||
if not all_permissions:
|
||||
print("没有找到任何权限,请先初始化权限数据")
|
||||
return
|
||||
|
||||
# 获取管理员角色当前的权限(使用预加载的数据)
|
||||
admin_permission_codes = set()
|
||||
if admin_role.permissions:
|
||||
admin_permission_codes = {perm.code for perm in admin_role.permissions}
|
||||
|
||||
# 检查是否需要添加权限
|
||||
permissions_to_add = []
|
||||
for perm in all_permissions:
|
||||
if perm.code not in admin_permission_codes:
|
||||
permissions_to_add.append(perm)
|
||||
|
||||
if permissions_to_add:
|
||||
# 直接设置权限(替换而非追加,确保权限完整)
|
||||
admin_role.permissions = all_permissions
|
||||
await db.commit()
|
||||
print(f"为管理员角色分配了 {len(permissions_to_add)} 个权限")
|
||||
else:
|
||||
print("管理员角色权限已完整,无需更新")
|
||||
|
||||
print("角色权限初始化/更新完成")
|
||||
|
||||
|
||||
async def init_roles(db: AsyncSession):
|
||||
"""初始化角色数据"""
|
||||
# 定义需要初始化的角色
|
||||
@ -187,6 +294,8 @@ async def init_all_data(db: AsyncSession):
|
||||
"""初始化所有数据"""
|
||||
print("开始初始化数据库数据...")
|
||||
await init_roles(db)
|
||||
await init_permissions(db)
|
||||
await init_role_permissions(db)
|
||||
await init_admin_user(db)
|
||||
await init_test_user(db)
|
||||
print("数据库数据初始化完成")
|
||||
|
||||
@ -3,6 +3,7 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
import redis.asyncio as redis
|
||||
from typing import Callable
|
||||
|
||||
from database import get_db
|
||||
from utils.jwt import decode_token
|
||||
@ -49,3 +50,108 @@ async def get_current_user(
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
||||
|
||||
return user
|
||||
|
||||
|
||||
def has_permission(required_permission_code: str) -> Callable:
|
||||
"""
|
||||
权限验证依赖函数工厂
|
||||
|
||||
使用示例:
|
||||
@router.get("/protected", dependencies=[Depends(has_permission("user:read"))])
|
||||
async def protected_route():
|
||||
...
|
||||
"""
|
||||
async def permission_checker(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
# 获取用户角色的所有权限
|
||||
from models import Permission, Role
|
||||
|
||||
result = await db.execute(
|
||||
select(Permission)
|
||||
.join(Role.permissions)
|
||||
.filter(Role.id == current_user.role_id)
|
||||
)
|
||||
permissions = result.scalars().all()
|
||||
|
||||
# 检查是否有 required_permission_code 权限
|
||||
has_perm = any(perm.code == required_permission_code for perm in permissions)
|
||||
if not has_perm:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"没有访问权限,需要权限: {required_permission_code}",
|
||||
)
|
||||
return current_user
|
||||
|
||||
return permission_checker
|
||||
|
||||
|
||||
def has_any_permission(required_permission_codes: list[str]) -> Callable:
|
||||
"""
|
||||
多权限验证依赖函数工厂(满足任一权限即可)
|
||||
|
||||
使用示例:
|
||||
@router.get("/protected", dependencies=[Depends(has_any_permission(["user:read", "admin:read"]))])
|
||||
async def protected_route():
|
||||
...
|
||||
"""
|
||||
async def permission_checker(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
from models import Permission, Role
|
||||
|
||||
result = await db.execute(
|
||||
select(Permission)
|
||||
.join(Role.permissions)
|
||||
.filter(Role.id == current_user.role_id)
|
||||
)
|
||||
permissions = result.scalars().all()
|
||||
user_permission_codes = {perm.code for perm in permissions}
|
||||
|
||||
# 检查是否有任一需要的权限
|
||||
has_perm = any(code in user_permission_codes for code in required_permission_codes)
|
||||
if not has_perm:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"没有访问权限,需要以下任一权限: {', '.join(required_permission_codes)}",
|
||||
)
|
||||
return current_user
|
||||
|
||||
return permission_checker
|
||||
|
||||
|
||||
def has_all_permissions(required_permission_codes: list[str]) -> Callable:
|
||||
"""
|
||||
多权限验证依赖函数工厂(需要满足所有权限)
|
||||
|
||||
使用示例:
|
||||
@router.get("/protected", dependencies=[Depends(has_all_permissions(["user:read", "user:write"]))])
|
||||
async def protected_route():
|
||||
...
|
||||
"""
|
||||
async def permission_checker(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
from models import Permission, Role
|
||||
|
||||
result = await db.execute(
|
||||
select(Permission)
|
||||
.join(Role.permissions)
|
||||
.filter(Role.id == current_user.role_id)
|
||||
)
|
||||
permissions = result.scalars().all()
|
||||
user_permission_codes = {perm.code for perm in permissions}
|
||||
|
||||
# 检查是否满足所有需要的权限
|
||||
missing_permissions = [code for code in required_permission_codes if code not in user_permission_codes]
|
||||
if missing_permissions:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"缺少必要权限: {', '.join(missing_permissions)}",
|
||||
)
|
||||
return current_user
|
||||
|
||||
return permission_checker
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, func
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, func, Table
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from datetime import datetime
|
||||
@ -20,6 +20,12 @@ class Role(Base):
|
||||
|
||||
# 关联到User
|
||||
users = relationship("User", back_populates="role_rel")
|
||||
# 关联到Permission(多对多)
|
||||
permissions = relationship(
|
||||
"Permission",
|
||||
secondary="role_permission",
|
||||
back_populates="roles"
|
||||
)
|
||||
|
||||
|
||||
class User(Base):
|
||||
@ -43,3 +49,33 @@ class User(Base):
|
||||
|
||||
# 关联到Role
|
||||
role_rel = relationship("Role", back_populates="users")
|
||||
|
||||
|
||||
class Permission(Base):
|
||||
"""权限数据库模型"""
|
||||
|
||||
__tablename__ = "permission"
|
||||
id = Column(Integer, primary_key=True, index=True, comment="权限ID")
|
||||
name = Column(String(50), nullable=False, comment="权限名称")
|
||||
code = Column(String(50), nullable=False, unique=True, comment="权限编码")
|
||||
description = Column(String(255), nullable=True, comment="权限描述")
|
||||
createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
|
||||
updatetime = Column(
|
||||
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
|
||||
)
|
||||
|
||||
# 关联到Role(多对多)
|
||||
roles = relationship(
|
||||
"Role",
|
||||
secondary="role_permission",
|
||||
back_populates="permissions"
|
||||
)
|
||||
|
||||
|
||||
# 角色权限关联表(多对多)
|
||||
role_permission = Table(
|
||||
"role_permission",
|
||||
Base.metadata,
|
||||
Column("role_id", Integer, ForeignKey("role.id"), primary_key=True, comment="角色ID"),
|
||||
Column("permission_id", Integer, ForeignKey("permission.id"), primary_key=True, comment="权限ID")
|
||||
)
|
||||
|
||||
@ -106,3 +106,44 @@ class RoleResponse(RoleBase):
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# 权限相关模型
|
||||
class PermissionBase(BaseModel):
|
||||
"""权限基础模型"""
|
||||
|
||||
name: str = Field(..., max_length=50, description="权限名称")
|
||||
code: str = Field(..., max_length=50, description="权限编码")
|
||||
description: Optional[str] = Field(None, max_length=255, description="权限描述")
|
||||
|
||||
|
||||
class PermissionCreate(PermissionBase):
|
||||
"""权限创建模型"""
|
||||
pass
|
||||
|
||||
|
||||
class PermissionResponse(PermissionBase):
|
||||
"""权限响应模型"""
|
||||
|
||||
id: int = Field(..., description="权限ID")
|
||||
createtime: datetime = Field(..., description="创建时间")
|
||||
updatetime: datetime = Field(..., description="更新时间")
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# 角色权限关联相关模型
|
||||
class RolePermissionAssign(BaseModel):
|
||||
"""角色权限分配模型"""
|
||||
|
||||
role_id: int = Field(..., description="角色ID")
|
||||
permission_ids: list[int] = Field(..., description="权限ID列表")
|
||||
|
||||
|
||||
class RolePermissionResponse(BaseModel):
|
||||
"""角色权限响应模型"""
|
||||
|
||||
role_id: int = Field(..., description="角色ID")
|
||||
role_name: str = Field(..., description="角色名称")
|
||||
permissions: list[PermissionResponse] = Field(..., description="权限列表")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user