BookSystem/backend/auth/apps/permissions/urls.py
jayhgq 9586c7467a feat: 菜单管理添加排序功能
- Permission 模型新增 sort 字段
- 后端 API 按 sort 升序排列树形菜单
- 前端菜单管理页面增加排序列
- 初始数据添加排序号
- 修复 GET /permissions/{id} 详情接口 bug

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 08:33:51 +00:00

521 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_
from sqlalchemy.orm import selectinload
from fastcrud import FastCRUD
from typing import Dict, Any
from database import get_db
from models import Permission, Role
from schemas import (
PermissionCreate,
PermissionResponse,
PermissionTreeResponse,
RolePermissionAssign,
RolePermissionResponse,
)
from middleware import get_current_user
from models import User
from utils.like_filter import ilike_contains
from utils.log_utils import get_client_ip, get_user_agent, create_operation_log
router = APIRouter(prefix="/permissions", tags=["permissions"])
# 创建FastCRUD实例
permission_crud = FastCRUD(Permission, PermissionResponse)
@router.post("/", response_model=PermissionResponse)
async def create_permission(
permission_data: PermissionCreate,
request: Request,
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,
parentid=permission_data.parentid,
route=permission_data.route,
description=permission_data.description,
sort=permission_data.sort,
)
db.add(db_permission)
await db.flush()
await db.refresh(db_permission)
# 记录操作日志
await create_operation_log(
db, current_user.id, current_user.username, "create", "permission",
db_permission.id, db_permission.name,
{"name": permission_data.name, "code": permission_data.code},
get_client_ip(request), get_user_agent(request),
)
await db.commit()
return db_permission
@router.post("/search", response_model=list[PermissionResponse])
async def search_permissions(
query: dict = {},
request: Request = None,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""获取权限列表POST方式支持按名称、编码的模糊查询与组合查询AND"""
stmt = select(Permission)
filters = []
for col, key in ((Permission.name, "name"), (Permission.code, "code")):
raw = query.get(key)
cond = ilike_contains(col, raw)
if cond is not None:
filters.append(cond)
if filters:
stmt = stmt.where(and_(*filters))
result = await db.execute(stmt)
permissions = result.scalars().all()
# 记录查询日志
await create_operation_log(
db, current_user.id, current_user.username, "search", "permission",
None, None, {"filters": {k: v for k, v in query.items() if v}},
get_client_ip(request), get_user_agent(request),
)
await db.commit()
return [PermissionResponse.model_validate(p) for p in permissions]
@router.get("/tree", response_model=list[PermissionTreeResponse])
async def get_permission_tree(
request: Request,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""获取权限树形结构(基于 parentid"""
# 获取所有权限
result = await db.execute(select(Permission))
permissions = result.scalars().all()
# 转换为字典,便于查找
perm_dict = {perm.id: {
"id": perm.id,
"name": perm.name,
"code": perm.code,
"parentid": perm.parentid,
"route": perm.route,
"description": perm.description,
"sort": perm.sort or 0,
"createtime": perm.createtime,
"updatetime": perm.updatetime,
"children": []
} for perm in permissions}
# 构建树形结构
tree = []
for perm_id, perm_data in perm_dict.items():
parentid = perm_data["parentid"]
if parentid == 0:
# 顶级节点
tree.append(perm_data)
elif parentid in perm_dict:
# 子节点,添加到父节点的 children
perm_dict[parentid]["children"].append(perm_data)
# 递归排序:按 sort 字段升序排列
def sort_tree(nodes):
nodes.sort(key=lambda n: n.get("sort", 0))
for node in nodes:
if node.get("children"):
sort_tree(node["children"])
return nodes
tree = sort_tree(tree)
# 记录查询日志
await create_operation_log(
db, current_user.id, current_user.username, "search", "permission",
None, None, None,
get_client_ip(request), get_user_agent(request),
)
await db.commit()
return tree
@router.get("/menu", response_model=list[PermissionTreeResponse])
async def get_menu(
request: Request,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""获取当前用户的菜单树形结构(仅包含有路由的权限)"""
# 获取用户角色的权限
result = await db.execute(
select(Role)
.options(selectinload(Role.permissions))
.filter(Role.id == current_user.role_id)
)
role = result.scalar_one_or_none()
if not role:
raise HTTPException(status_code=404, detail="角色不存在")
# 获取用户有权限的权限编码集合
user_permission_codes = {perm.code for perm in role.permissions}
# 获取所有顶级模块和二级模块权限有route字段的
result = await db.execute(
select(Permission)
.filter(Permission.route.isnot(None))
)
all_menu_permissions = result.scalars().all()
# 过滤用户有权限的菜单
user_menu_permissions = []
for perm in all_menu_permissions:
# 检查用户是否有该权限或其子权限
has_access = False
for user_perm_code in user_permission_codes:
if user_perm_code.startswith(perm.code):
has_access = True
break
if has_access:
user_menu_permissions.append(perm)
# 转换为字典,便于查找
perm_dict = {perm.id: {
"id": perm.id,
"name": perm.name,
"code": perm.code,
"parentid": perm.parentid,
"route": perm.route,
"description": perm.description,
"sort": perm.sort or 0,
"createtime": perm.createtime,
"updatetime": perm.updatetime,
"children": []
} for perm in user_menu_permissions}
# 构建树形结构
tree = []
for perm_id, perm_data in perm_dict.items():
parentid = perm_data["parentid"]
if parentid == 0:
# 顶级节点
tree.append(perm_data)
elif parentid in perm_dict:
# 子节点,添加到父节点的 children
perm_dict[parentid]["children"].append(perm_data)
# 递归排序:按 sort 字段升序排列
def sort_tree(nodes):
nodes.sort(key=lambda n: n.get("sort", 0))
for node in nodes:
if node.get("children"):
sort_tree(node["children"])
return nodes
tree = sort_tree(tree)
# 记录查询日志
await create_operation_log(
db, current_user.id, current_user.username, "search", "permission",
None, None, {"type": "menu_tree"},
get_client_ip(request), get_user_agent(request),
)
await db.commit()
return tree
@router.get("/{permission_id}", response_model=PermissionResponse)
async def get_permission(
permission_id: int,
request: Request,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""获取单个权限"""
permission = await permission_crud.get(db, id=permission_id, schema_to_select=PermissionResponse, return_as_model=True)
if not permission:
raise HTTPException(status_code=404, detail="权限不存在")
# 记录查询日志
await create_operation_log(
db, current_user.id, current_user.username, "search", "permission",
permission_id, permission.name, None,
get_client_ip(request), get_user_agent(request),
)
await db.commit()
return permission
@router.put("/{permission_id}", response_model=PermissionResponse)
async def update_permission(
permission_id: int,
permission_data: PermissionCreate,
request: Request,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""更新权限"""
# 获取原权限数据
result = await db.execute(select(Permission).filter(Permission.id == permission_id))
old_permission = result.scalar_one_or_none()
if not old_permission:
raise HTTPException(status_code=404, detail="权限不存在")
# 检查权限编码是否被其他权限使用
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="权限不存在")
# 记录操作日志
changed = {}
for field in ["name", "code", "parentid", "route", "sort", "description"]:
old_val = getattr(old_permission, field, None)
new_val = update_data.get(field)
if old_val != new_val:
changed[field] = {"old": old_val, "new": new_val}
await create_operation_log(
db, current_user.id, current_user.username, "update", "permission",
permission_id, old_permission.name, changed if changed else None,
get_client_ip(request), get_user_agent(request),
)
await db.commit()
return updated_permission
@router.delete("/{permission_id}")
async def delete_permission(
permission_id: int,
request: Request,
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="权限不存在")
# 记录操作日志(删除前)
await create_operation_log(
db, current_user.id, current_user.username, "delete", "permission",
permission_id, existing_permission.name,
{"name": existing_permission.name, "code": existing_permission.code},
get_client_ip(request), get_user_agent(request),
)
# 1. 删除角色-权限关联表中的记录
from sqlalchemy import delete as sql_delete
from models import role_permission
stmt = sql_delete(role_permission).where(role_permission.c.permission_id == permission_id)
await db.execute(stmt)
# 2. 处理子权限如果有子权限将子权限的父ID置为0提升为顶级节点
from sqlalchemy import update as sql_update
stmt = (
sql_update(Permission)
.where(Permission.parentid == permission_id)
.values(parentid=0)
)
await db.execute(stmt)
# 3. 使用FastCRUD删除权限本身不提交与前述操作在同一事务中
await permission_crud.delete(db, id=permission_id, commit=False)
await db.commit()
return {"message": "权限删除成功"}
# 角色权限关联接口
@router.post("/assign", response_model=RolePermissionResponse)
async def assign_permissions_to_role(
data: RolePermissionAssign,
request: Request,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""为角色分配权限"""
# 查找角色,同时加载权限关系
result = await db.execute(select(Role).options(selectinload(Role.permissions)).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 create_operation_log(
db, current_user.id, current_user.username, "update", "role",
role.id, role.name,
{"permission_ids": data.permission_ids},
get_client_ip(request), get_user_agent(request),
)
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 permissions]
)
@router.get("/role/{role_id}", response_model=RolePermissionResponse)
async def get_role_permissions(
role_id: int,
request: Request,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""获取角色的权限列表"""
# 查找角色,同时加载权限关系
result = await db.execute(select(Role).options(selectinload(Role.permissions)).filter(Role.id == role_id))
role = result.scalar_one_or_none()
if not role:
raise HTTPException(status_code=404, detail="角色不存在")
# 记录查询日志
await create_operation_log(
db, current_user.id, current_user.username, "search", "permission",
role_id, role.name, {"type": "role_permissions"},
get_client_ip(request), get_user_agent(request),
)
await db.commit()
# 返回角色权限信息
return RolePermissionResponse(
role_id=role.id,
role_name=role.name,
permissions=[PermissionResponse.from_orm(p) for p in role.permissions]
)
@router.get("/user/me", response_model=RolePermissionResponse)
async def get_current_user_permissions(
request: Request,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""获取当前登录用户的权限列表和菜单"""
# 获取用户角色的权限
result = await db.execute(
select(Role)
.options(selectinload(Role.permissions))
.filter(Role.id == current_user.role_id)
)
role = result.scalar_one_or_none()
if not role:
raise HTTPException(status_code=404, detail="角色不存在")
# 获取用户有权限的权限编码集合
user_permission_codes = {perm.code for perm in role.permissions}
# 获取所有顶级模块和二级模块权限有route字段的
result = await db.execute(
select(Permission)
.filter(Permission.route.isnot(None))
)
all_menu_permissions = result.scalars().all()
# 过滤用户有权限的菜单
user_menu_permissions = []
for perm in all_menu_permissions:
# 检查用户是否有该权限或其子权限
has_access = False
for user_perm_code in user_permission_codes:
if user_perm_code.startswith(perm.code):
has_access = True
break
if has_access:
user_menu_permissions.append(perm)
# 转换为字典,便于查找
perm_dict = {perm.id: {
"id": perm.id,
"name": perm.name,
"code": perm.code,
"parentid": perm.parentid,
"route": perm.route,
"description": perm.description,
"sort": perm.sort or 0,
"createtime": perm.createtime,
"updatetime": perm.updatetime,
"children": []
} for perm in user_menu_permissions}
# 构建树形结构
menu_tree = []
for perm_id, perm_data in perm_dict.items():
parentid = perm_data["parentid"]
if parentid == 0:
# 顶级节点
menu_tree.append(perm_data)
elif parentid in perm_dict:
# 子节点,添加到父节点的 children
perm_dict[parentid]["children"].append(perm_data)
# 递归排序:按 sort 字段升序排列
def sort_tree(nodes):
nodes.sort(key=lambda n: n.get("sort", 0))
for node in nodes:
if node.get("children"):
sort_tree(node["children"])
return nodes
menu_tree = sort_tree(menu_tree)
# 记录查询日志
await create_operation_log(
db, current_user.id, current_user.username, "search", "permission",
None, None, {"type": "user_permissions_menu"},
get_client_ip(request), get_user_agent(request),
)
await db.commit()
# 返回用户权限信息和菜单
return RolePermissionResponse(
role_id=role.id,
role_name=role.name,
permissions=[PermissionResponse.from_orm(p) for p in role.permissions],
menu=menu_tree
)