feat(auth): 添加权限管理功能

实现权限管理模块,包括:
1. 权限模型、路由和中间件
2. 角色权限多对多关联
3. 权限验证中间件
4. 初始化权限数据
5. 权限管理API接口
This commit is contained in:
jayhgq 2026-04-29 17:59:41 +08:00
parent b9150c3e29
commit a3206bb0f6
16 changed files with 471 additions and 4 deletions

View 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]
)

View File

@ -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

Binary file not shown.

View File

@ -23,4 +23,4 @@ async def get_db():
try:
yield session
finally:
await session.close()
await session.close()

View File

@ -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("数据库数据初始化完成")

View File

@ -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

View File

@ -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")
)

View File

@ -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="权限列表")