refactor: P4c 批次4 服务层改造(role/menu/user_role/role_menu)

- role/menu 走 codemod(get_role_page/get_menu_detail 等 + get_info 委托 Repository)
- menu get_menu_list params 化返回 list;权限函数(get_user_permissions/
  get_user_permission_menus/invalidate_*)保持完整未动
- user_role 已符合新范式(参数化+返回 list)仅验证
- role_menu save_role_menu(data) 改 BizError + router 取参包 R

【验收】flake8 零告警;应用启动;权限函数跨模块运行正常;12 passed

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
DjangoAdmin Dev
2026-08-07 15:40:33 +08:00
co-authored by Claude
parent d97e456744
commit 3dbce04eb1
10 changed files with 329 additions and 367 deletions
+14 -5
View File
@@ -57,15 +57,20 @@ from flask import Blueprint
from application.middleware import check_permission
from application.middleware.check_demo import check_demo
from src.modules.system.role import service as role_menu
role_menu_blue = Blueprint('role_menu', __name__, url_prefix='/role/menu')
from src.common.utils.request import parse_request_body
from src.core import response as R
role_menu_blue = Blueprint("role_menu", __name__, url_prefix="/role/menu")
# ============================================================
# 角色菜单列表查询
# ============================================================
@role_menu_blue.route('/list/<int:role_id>', methods=["GET"])
@role_menu_blue.route("/list/<int:role_id>", methods=["GET"])
@check_permission("sys:role:list")
def list(role_id):
"""
@@ -88,7 +93,8 @@ def list(role_id):
# 角色菜单权限保存
# ============================================================
@role_menu_blue.route('/save', methods=["POST"])
@role_menu_blue.route("/save", methods=["POST"])
@check_permission("sys:role:save")
@check_demo
def save():
@@ -101,5 +107,8 @@ def save():
Returns:
JSON: 操作结果
"""
result = role_menu.save_role_menu()
return result
json_data, err = parse_request_body()
if err:
return R.failed(msg=err)
role_menu.save_role_menu(json_data)
return R.ok(msg="保存成功")
+34 -51
View File
@@ -64,10 +64,8 @@ from src.modules.association.role_menu.schema import RoleMenuForm
from src.modules.system.menu.models import Menu
from src.modules.association.role_menu.models import RoleMenu
from src.modules.system.menu.service import invalidate_role_users_cache
from src.core.database import db
from src.common.utils import regular
from src.core import response as R
from src.common.utils.request import parse_request_body
from src.core.exceptions import BizError
# 获取日志记录器
logger = logging.getLogger(__name__)
@@ -77,6 +75,7 @@ logger = logging.getLogger(__name__)
# 角色菜单树查询
# ============================================================
def get_role_menus(role_id):
"""
根据角色ID查询菜单权限树
@@ -106,9 +105,7 @@ def get_role_menus(role_id):
try:
# 获取全部启用的菜单列表(按排序升序)
menu_list = Menu.query.filter(
and_(Menu.is_delete == 0, Menu.status == 0)
).order_by(Menu.sort.asc()).all()
menu_list = Menu.query.filter(and_(Menu.is_delete == 0, Menu.status == 0)).order_by(Menu.sort.asc()).all()
if not menu_list:
return []
@@ -121,12 +118,12 @@ def get_role_menus(role_id):
result = []
for menu in menu_list:
data = {
'id': menu.id,
'name': menu.name,
'parentId': menu.parent_id,
"id": menu.id,
"name": menu.name,
"parentId": menu.parent_id,
}
if menu.id in assigned_menu_ids:
data['checked'] = True
data["checked"] = True
result.append(data)
return result
@@ -140,54 +137,40 @@ def get_role_menus(role_id):
# 角色菜单权限保存
# ============================================================
def save_role_menu():
def save_role_menu(data: dict) -> None:
"""
保存角色菜单权限分配
流程:
1. 解析请求体JSON数据
2. 表单验证
3. 删除该角色原有的所有菜单权限
4. 重新创建新的菜单权限关联
1. 表单验证
2. 删除该角色原有的所有菜单权限
3. 重新创建新的菜单权限关联
Returns:
R: 操作结果
Args:
data: 请求体数据 dict
Raises:
BizError: 表单验证失败时
"""
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
form = RoleMenuForm(data=data)
if not form.validate():
raise BizError(regular.get_err(form))
# 表单验证
form = RoleMenuForm(data=json_data)
if not form.validate():
err_msg = regular.get_err(form)
return R.failed(msg=err_msg)
role_id = form.roleId.data
menu_ids = form.menuIds.data
role_id = form.roleId.data
menu_ids = form.menuIds.data
# 删除当前角色ID相关的所有菜单权限
RoleMenu.query.filter(RoleMenu.role_id == role_id).delete()
# 删除当前角色ID相关的所有菜单权限
RoleMenu.query.filter(RoleMenu.role_id == role_id).delete()
# 处理菜单数据
if menu_ids and menu_ids != "":
for menu_id in menu_ids:
if not menu_id or menu_id <= 0:
continue
# 创建角色菜单关联数据
role_menu = RoleMenu(role_id=role_id, menu_id=menu_id)
role_menu.save()
# 处理菜单数据
if menu_ids and menu_ids != "":
for menu_id in menu_ids:
if not menu_id or menu_id <= 0:
continue
# 创建角色菜单关联数据
role_menu = RoleMenu(
role_id=role_id,
menu_id=menu_id
)
role_menu.save()
# 清除该角色下所有用户的权限缓存
invalidate_role_users_cache(role_id)
return R.ok(msg="保存成功")
except Exception as e:
logger.error(f"保存角色菜单权限异常: {str(e)}")
return R.failed(msg="保存角色菜单权限失败,请稍后重试")
# 清除该角色下所有用户的权限缓存
invalidate_role_users_cache(role_id)
+1 -1
View File
@@ -16,8 +16,8 @@ from flask import Blueprint
from application.middleware.check_demo import check_demo
from application.middleware.check_permission import check_permission
from src.modules.system.user import service as user_role
user_role_blue = Blueprint('user_role', __name__, url_prefix='/user/role')
from src.core import response as R
user_role_blue = Blueprint('user_role', __name__, url_prefix='/user/role')
# ============================================================
+15
View File
@@ -0,0 +1,15 @@
"""
Menu 仓储模块
=============================================================
数据访问层,继承 BaseRepository 获得通用 CRUD 能力。
"""
from src.common.base_repository import BaseRepository
from src.modules.system.menu.models import Menu
class MenuRepository(BaseRepository):
"""Menu 仓储"""
model = Menu
+28 -15
View File
@@ -53,21 +53,25 @@
application.middleware.check_permission - 权限校验中间件
"""
from flask import Blueprint
from flask import Blueprint, request
from application.middleware.check_demo import check_demo
from application.middleware.check_permission import check_permission
from src.modules.system.menu import service as menu
menu_blue = Blueprint('menu', __name__, url_prefix='/menu')
from src.common.utils.request import parse_request_body
from src.core import response as R
menu_blue = Blueprint("menu", __name__, url_prefix="/menu")
# ============================================================
# 菜单列表查询
# ============================================================
@menu_blue.route('/list', methods=["GET"])
@menu_blue.route("/list", methods=["GET"])
@check_permission("sys:menu:list")
def list():
"""
@@ -79,15 +83,15 @@ def list():
Returns:
JSON: 菜单列表数据
"""
result = menu.get_menu_list()
return result
return R.ok(data=menu.get_menu_list(request.args))
# ============================================================
# 菜单详情查询
# ============================================================
@menu_blue.route('/detail/<int:id>', methods=["GET"])
@menu_blue.route("/detail/<int:id>", methods=["GET"])
@check_permission("sys:menu:detail")
def detail(id):
"""
@@ -107,7 +111,8 @@ def detail(id):
# 菜单添加
# ============================================================
@menu_blue.route('/add', methods=["POST"])
@menu_blue.route("/add", methods=["POST"])
@check_permission("sys:menu:add")
@check_demo
def add():
@@ -120,15 +125,19 @@ def add():
Returns:
JSON: 操作结果
"""
result = menu.add_menu()
return result
json_data, err = parse_request_body()
if err:
return R.failed(msg=err)
menu.add_menu(json_data)
return R.ok(msg="添加成功")
# ============================================================
# 菜单更新
# ============================================================
@menu_blue.route('/update', methods=["PUT"])
@menu_blue.route("/update", methods=["PUT"])
@check_permission("sys:menu:update")
@check_demo
def update():
@@ -141,15 +150,19 @@ def update():
Returns:
JSON: 操作结果
"""
result = menu.update_menu()
return result
json_data, err = parse_request_body()
if err:
return R.failed(msg=err)
menu.update_menu(json_data)
return R.ok(msg="更新成功")
# ============================================================
# 菜单删除
# ============================================================
@menu_blue.route('/delete/<string:id>', methods=["DELETE"])
@menu_blue.route("/delete/<string:id>", methods=["DELETE"])
@check_permission("sys:menu:delete")
@check_demo
def delete(id):
@@ -162,5 +175,5 @@ def delete(id):
Returns:
JSON: 操作结果
"""
result = menu.delete_menus(id)
return result
count = menu.delete_menus(id)
return R.ok(msg=f"本次共删除{count}条数据")
+125 -165
View File
@@ -66,19 +66,19 @@
import logging
from datetime import datetime, timezone
from flask import request
from sqlalchemy import and_
from src.modules.system.menu.schema import MenuForm
from src.modules.system.menu.models import Menu
from src.modules.system.menu.repository import MenuRepository
from src.core.exceptions import BizError
from src.modules.association.role_menu.models import RoleMenu
from src.modules.association.user_role.models import UserRole
from src.core.database import db
from src.core import response as R
from src.common.utils import regular
from src.common.utils.case_converter import convert_camel_to_snake
from src.core.redis_client import redis
from src.common.utils.request import parse_request_body, parse_id_list, like_contains
from src.common.utils.request import parse_id_list, like_contains
from src.common.utils.model_utils import models_to_list, model_to_dict
from src.core.security import get_username
@@ -101,8 +101,10 @@ _CACHE_EXPIRE = 7200
# 权限节点常量映射
# ============================================================
class PermissionNode:
"""权限节点常量定义"""
QUERY = 1 # 查询
ADD = 5 # 添加
UPDATE = 10 # 修改
@@ -121,20 +123,20 @@ class PermissionNode:
# 权限节点配置映射
PERMISSION_CONFIG = {
PermissionNode.QUERY: {'suffix': '查询', 'action': 'list', 'perm_suffix': 'list'},
PermissionNode.ADD: {'suffix': '添加', 'action': 'add', 'perm_suffix': 'add'},
PermissionNode.UPDATE: {'suffix': '修改', 'action': 'update', 'perm_suffix': 'update'},
PermissionNode.DELETE: {'suffix': '删除', 'action': 'delete', 'perm_suffix': 'delete'},
PermissionNode.DETAIL: {'suffix': '详情', 'action': 'detail', 'perm_suffix': 'detail'},
PermissionNode.STATUS: {'suffix': '设置状态', 'action': 'status', 'perm_suffix': 'status'},
PermissionNode.BATCH_DELETE: {'suffix': '批量删除', 'action': 'dall', 'perm_suffix': 'dall'},
PermissionNode.ADD_CHILD: {'suffix': '添加子级', 'action': 'addz', 'perm_suffix': 'addz'},
PermissionNode.EXPAND: {'suffix': '全部展开', 'action': 'expand', 'perm_suffix': 'expand'},
PermissionNode.COLLAPSE: {'suffix': '全部折叠', 'action': 'collapse', 'perm_suffix': 'collapse'},
PermissionNode.EXPORT: {'suffix': '导出', 'action': 'export', 'perm_suffix': 'export'},
PermissionNode.IMPORT: {'suffix': '导入', 'action': 'import', 'perm_suffix': 'import'},
PermissionNode.PERMISSION: {'suffix': '分配权限', 'action': 'permission', 'perm_suffix': 'permission'},
PermissionNode.RESET_PWD: {'suffix': '重置密码', 'action': 'resetPwd', 'perm_suffix': 'resetPwd'},
PermissionNode.QUERY: {"suffix": "查询", "action": "list", "perm_suffix": "list"},
PermissionNode.ADD: {"suffix": "添加", "action": "add", "perm_suffix": "add"},
PermissionNode.UPDATE: {"suffix": "修改", "action": "update", "perm_suffix": "update"},
PermissionNode.DELETE: {"suffix": "删除", "action": "delete", "perm_suffix": "delete"},
PermissionNode.DETAIL: {"suffix": "详情", "action": "detail", "perm_suffix": "detail"},
PermissionNode.STATUS: {"suffix": "设置状态", "action": "status", "perm_suffix": "status"},
PermissionNode.BATCH_DELETE: {"suffix": "批量删除", "action": "dall", "perm_suffix": "dall"},
PermissionNode.ADD_CHILD: {"suffix": "添加子级", "action": "addz", "perm_suffix": "addz"},
PermissionNode.EXPAND: {"suffix": "全部展开", "action": "expand", "perm_suffix": "expand"},
PermissionNode.COLLAPSE: {"suffix": "全部折叠", "action": "collapse", "perm_suffix": "collapse"},
PermissionNode.EXPORT: {"suffix": "导出", "action": "export", "perm_suffix": "export"},
PermissionNode.IMPORT: {"suffix": "导入", "action": "import", "perm_suffix": "import"},
PermissionNode.PERMISSION: {"suffix": "分配权限", "action": "permission", "perm_suffix": "permission"},
PermissionNode.RESET_PWD: {"suffix": "重置密码", "action": "resetPwd", "perm_suffix": "resetPwd"},
}
@@ -142,37 +144,31 @@ PERMISSION_CONFIG = {
# 菜单列表查询
# ============================================================
def get_menu_list():
def get_menu_list(params: dict) -> list:
"""
查询菜单列表
用于菜单管理页面的列表展示,支持按名称模糊搜索。
Args:
request: Flask request对象,包含查询参数
params: 查询参数
- name: 菜单名称(可选,模糊查询)
Returns:
R: 包含菜单列表的JSON响应
list: 菜单字典列表
"""
try:
# 应用筛选条件
query = _apply_filters()
# 应用筛选条件
query = _apply_filters(params)
# 查询数据(按排序升序)
menu_list = query.order_by(Menu.sort.asc()).all()
# 查询数据(按排序升序)
menu_list = query.order_by(Menu.sort.asc()).all()
# 转换为字典列表
records = models_to_list(menu_list)
return R.ok(data=records)
except Exception as e:
logger.error(f"查询菜单列表异常: {str(e)}")
return R.failed(msg="查询失败,请稍后重试")
# 转换为字典列表
return models_to_list(menu_list)
def _apply_filters():
def _apply_filters(params: dict):
"""
应用查询筛选条件
@@ -187,9 +183,9 @@ def _apply_filters():
query = Menu.query.filter(Menu.is_delete == 0)
# 菜单名称模糊筛选
name = request.args.get('name')
name = params.get("name")
if name:
query = query.filter(Menu.name.like(like_contains(name), escape='\\'))
query = query.filter(Menu.name.like(like_contains(name), escape="\\"))
return query
@@ -198,6 +194,7 @@ def _apply_filters():
# 菜单详情查询
# ============================================================
def get_menu_detail(menu_id):
"""
根据ID查询菜单详情(返回字典格式)
@@ -221,15 +218,13 @@ def get_menu_detail(menu_id):
data = model_to_dict(menu)
# 查询菜单权限节点(type=1 表示权限节点)
permission_list = Menu.query.filter(
and_(Menu.parent_id == menu_id, Menu.type == 1, Menu.is_delete == 0)
).all()
permission_list = Menu.query.filter(and_(Menu.parent_id == menu_id, Menu.type == 1, Menu.is_delete == 0)).all()
# 选中的权限节点(按sort值收集)
checked_list = [p.sort for p in permission_list] if permission_list else []
# 权限节点
data['checked_list'] = checked_list
data["checked_list"] = checked_list
return data
@@ -242,6 +237,7 @@ def get_menu_detail(menu_id):
# 菜单信息查询(根据ID)- 核心查询方法
# ============================================================
def get_info(menu_id: int):
"""
根据ID查询菜单信息(返回模型对象)
@@ -267,26 +263,10 @@ def get_info(menu_id: int):
if menu:
print(menu.title)
"""
# 参数有效性验证
if not menu_id or menu_id <= 0:
return None
try:
# 查询未删除的菜单
menu = Menu.query.filter(
and_(Menu.id == menu_id, Menu.is_delete == 0)
).first()
return menu
except Exception as e:
logger.error(f"查询菜单信息异常,ID: {menu_id}, 错误: {str(e)}")
return None
return MenuRepository.get_by_id(menu_id)
# ============================================================
# 菜单添加
# ============================================================
def add_menu():
def add_menu(data: dict) -> None:
"""
添加菜单
@@ -299,52 +279,41 @@ def add_menu():
Returns:
R: 操作结果
"""
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证
form = MenuForm(data=json_data)
if not form.validate():
err_msg = regular.get_err(form)
return R.failed(msg=err_msg)
# 表单验证
form = MenuForm(data=data)
if not form.validate():
raise BizError(regular.get_err(form))
# 权限节点
checked_list = form.data.get('checked_list')
menu_type = form.data.get('type')
title = form.data.get('title')
path = form.data.get('path', '')
# 权限节点
checked_list = form.data.get("checked_list")
menu_type = form.data.get("type")
title = form.data.get("title")
path = form.data.get("path", "")
# 移除不需要直接保存到Menu表的字段
form_data = form.data.copy()
form_data.pop('checked_list', None)
# 移除不需要直接保存到Menu表的字段
form_data = form.data.copy()
form_data.pop("checked_list", None)
# 创建菜单
menu = Menu(**form_data)
menu.create_user = get_username()
db.session.add(menu)
db.session.flush()
# 创建菜单
menu = Menu(**form_data)
menu.create_user = get_username()
db.session.add(menu)
db.session.flush()
# 保存权限节点数据
_create_permission_nodes(menu_type, checked_list, title, path, menu.id)
# 保存权限节点数据
_create_permission_nodes(menu_type, checked_list, title, path, menu.id)
# 菜单变更,递增全局版本号使所有用户缓存失效
invalidate_all_cache()
return R.ok(msg="添加成功")
except Exception as e:
logger.error(f"添加菜单异常: {str(e)}")
return R.failed(msg="添加菜单失败,请稍后重试")
# 菜单变更,递增全局版本号使所有用户缓存失效
invalidate_all_cache()
# ============================================================
# 菜单更新
# ============================================================
def update_menu():
def update_menu(data: dict) -> None:
"""
更新菜单
@@ -358,66 +327,56 @@ def update_menu():
Returns:
R: 操作结果
"""
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证
form = MenuForm(data=json_data)
if not form.validate():
return R.failed(msg=regular.get_err(form))
# 表单验证
form = MenuForm(data=data)
if not form.validate():
raise BizError(regular.get_err(form))
# 获取菜单ID
menu_id = form.data.get('id')
if not menu_id or int(menu_id) <= 0:
return R.failed("菜单ID无效")
menu_id = int(menu_id)
# 获取菜单ID
menu_id = form.data.get("id")
if not menu_id or int(menu_id) <= 0:
raise BizError("菜单ID无效")
menu_id = int(menu_id)
# 复用 get_info 方法验证菜单是否存在
menu = get_info(menu_id)
if not menu:
return R.failed("菜单不存在")
# 复用 get_info 方法验证菜单是否存在
menu = get_info(menu_id)
if not menu:
raise BizError("菜单不存在")
# 权限节点
checked_list = form.data.get('checked_list')
menu_type = form.data.get('type')
title = form.data.get('title')
path = form.data.get('path', '')
# 权限节点
checked_list = form.data.get("checked_list")
menu_type = form.data.get("type")
title = form.data.get("title")
path = form.data.get("path", "")
# 移除不需要直接更新到Menu表的字段
form_data = convert_camel_to_snake(form.data.copy())
form_data.pop('id', None)
form_data.pop('checked_list', None)
# 移除不需要直接更新到Menu表的字段
form_data = convert_camel_to_snake(form.data.copy())
form_data.pop("id", None)
form_data.pop("checked_list", None)
form_data['update_time'] = datetime.now(tz=timezone.utc)
form_data['update_user'] = get_username()
form_data["update_time"] = datetime.now(tz=timezone.utc)
form_data["update_user"] = get_username()
# 更新菜单信息
result = Menu.query.filter_by(id=menu_id).update(form_data)
# 更新菜单信息
result = Menu.query.filter_by(id=menu_id).update(form_data)
if not result:
return R.failed("更新失败")
if not result:
raise BizError("更新失败")
# 保存权限节点数据
_create_permission_nodes(menu_type, checked_list, title, path, menu_id)
# 保存权限节点数据
_create_permission_nodes(menu_type, checked_list, title, path, menu_id)
# 菜单变更,递增全局版本号使所有用户缓存失效
invalidate_all_cache()
return R.ok(msg="更新成功")
except Exception as e:
logger.error(f"更新菜单异常: {str(e)}")
return R.failed(msg="更新菜单失败,请稍后重试")
# 菜单变更,递增全局版本号使所有用户缓存失效
invalidate_all_cache()
# ============================================================
# 菜单删除
# ============================================================
def delete_menus(menu_ids):
def delete_menus(menu_ids: str) -> int:
"""
删除菜单(批量删除)
@@ -431,20 +390,18 @@ def delete_menus(menu_ids):
R: 操作结果
"""
if not menu_ids:
return R.failed("记录ID不存在")
raise BizError("记录ID不存在")
# 解析ID列表
id_list = parse_id_list(menu_ids)
if not id_list:
return R.failed("无效的记录ID")
raise BizError("无效的记录ID")
# 检查是否有子菜单
for menu_id in id_list:
child_count = Menu.query.filter(
and_(Menu.parent_id == menu_id, Menu.is_delete == 0)
).count()
child_count = Menu.query.filter(and_(Menu.parent_id == menu_id, Menu.is_delete == 0)).count()
if child_count > 0:
return R.failed(f"菜单ID {menu_id} 存在子菜单,请先删除子菜单")
raise BizError(f"菜单ID {menu_id} 存在子菜单,请先删除子菜单")
result = Menu.batch_delete_r(menu_ids)
@@ -458,6 +415,7 @@ def delete_menus(menu_ids):
# 权限节点管理
# ============================================================
def _create_permission_nodes(menu_type, checked_list, title, path, parent_id):
"""
创建权限节点
@@ -477,15 +435,13 @@ def _create_permission_nodes(menu_type, checked_list, title, path, parent_id):
try:
# 删除现有权限节点
Menu.query.filter(
and_(Menu.parent_id == parent_id, Menu.type == 1, Menu.is_delete == 0)
).delete()
Menu.query.filter(and_(Menu.parent_id == parent_id, Menu.type == 1, Menu.is_delete == 0)).delete()
# 模块名称(去掉"管理"后缀)
module_title = title.replace("管理", "") if title else ""
# 请求path处理,获取模块名
path_arr = path.split('/')
path_arr = path.split("/")
if len(path_arr) < 3:
return
module_name = path_arr[-1]
@@ -501,11 +457,17 @@ def _create_permission_nodes(menu_type, checked_list, title, path, parent_id):
# 构建节点标题
if value in [PermissionNode.DETAIL]:
node_title = f"{module_title}{config['suffix']}"
elif value in [PermissionNode.QUERY, PermissionNode.ADD, PermissionNode.UPDATE,
PermissionNode.DELETE, PermissionNode.EXPORT, PermissionNode.IMPORT]:
elif value in [
PermissionNode.QUERY,
PermissionNode.ADD,
PermissionNode.UPDATE,
PermissionNode.DELETE,
PermissionNode.EXPORT,
PermissionNode.IMPORT,
]:
node_title = f"{config['suffix']}{module_title}"
else:
node_title = config['suffix']
node_title = config["suffix"]
# 构建路径
node_path = f"/{module_name}/{config['action']}"
@@ -523,7 +485,7 @@ def _create_permission_nodes(menu_type, checked_list, title, path, parent_id):
status=1,
target="_self",
sort=value,
create_user=get_username()
create_user=get_username(),
)
permission_menu.save()
@@ -536,6 +498,7 @@ def _create_permission_nodes(menu_type, checked_list, title, path, parent_id):
# 用户权限节点获取
# ============================================================
def get_user_permissions(user_id):
"""
获取用户权限节点列表(带Redis缓存)
@@ -566,9 +529,7 @@ def get_user_permissions(user_id):
# 缓存未命中,查询数据库
if user_id == 1:
permission_list = Menu.query.filter(
and_(Menu.is_delete == 0, Menu.type == 1, Menu.status == 0)
).all()
permission_list = Menu.query.filter(and_(Menu.is_delete == 0, Menu.type == 1, Menu.status == 0)).all()
result = [item.permission for item in permission_list if item.permission]
else:
rows = (
@@ -580,7 +541,7 @@ def get_user_permissions(user_id):
Menu.type == 1,
Menu.status == 0,
Menu.is_delete == 0,
Menu.permission != ''
Menu.permission != "",
)
.all()
)
@@ -599,6 +560,7 @@ def get_user_permissions(user_id):
# 用户菜单树获取
# ============================================================
def get_user_permission_menus(user_id):
"""
根据用户ID获取权限菜单树(带Redis缓存)
@@ -629,20 +591,17 @@ def get_user_permission_menus(user_id):
# 缓存未命中,查询数据库
if user_id == 1:
menu_list = Menu.query.filter(
and_(Menu.is_delete == 0, Menu.status == 0, Menu.type == 0)
).order_by(Menu.sort.asc()).all()
menu_list = (
Menu.query.filter(and_(Menu.is_delete == 0, Menu.status == 0, Menu.type == 0))
.order_by(Menu.sort.asc())
.all()
)
else:
menu_list = (
db.session.query(Menu)
.join(RoleMenu, Menu.id == RoleMenu.menu_id)
.join(UserRole, UserRole.role_id == RoleMenu.role_id)
.filter(
UserRole.user_id == user_id,
Menu.type == 0,
Menu.status == 0,
Menu.is_delete == 0
)
.filter(UserRole.user_id == user_id, Menu.type == 0, Menu.status == 0, Menu.is_delete == 0)
.order_by(Menu.sort.asc())
.all()
)
@@ -683,8 +642,8 @@ def _build_menu_tree(menu_data, parent_id):
def _build(pid):
children = children_map.get(pid, [])
for item in children:
item['children'] = _build(item["id"])
children.sort(key=lambda x: x.get('sort', 0))
item["children"] = _build(item["id"])
children.sort(key=lambda x: x.get("sort", 0))
return children
return _build(parent_id)
@@ -694,6 +653,7 @@ def _build_menu_tree(menu_data, parent_id):
# 权限缓存失效
# ============================================================
def invalidate_user_cache(user_id):
"""
清除指定用户的权限和菜单缓存
-1
View File
@@ -50,7 +50,6 @@ from src.common.base_db import BaseDB
from src.common.base_model import BaseModel
from config import DB_PREFIX
from src.core.database import db
from src.common.utils.case_converter import convert_camel_to_snake
# ============================================================
+15
View File
@@ -0,0 +1,15 @@
"""
Role 仓储模块
=============================================================
数据访问层,继承 BaseRepository 获得通用 CRUD 能力。
"""
from src.common.base_repository import BaseRepository
from src.modules.system.role.models import Role
class RoleRepository(BaseRepository):
"""Role 仓储"""
model = Role
+31 -18
View File
@@ -55,22 +55,24 @@
依赖模块: application.services.role - 角色业务逻辑层
application.middleware.check_permission - 权限校验中间件
"""
from flask import Blueprint
from flask import Blueprint, request
from application.middleware.check_demo import check_demo
from application.middleware.check_permission import check_permission
from src.modules.system.role import service as role
from src.common.utils.request import parse_batch_ids
role_blue = Blueprint('role', __name__, url_prefix='/role')
from src.common.utils.request import parse_batch_ids, parse_request_body
from src.core import response as R
role_blue = Blueprint("role", __name__, url_prefix="/role")
# ============================================================
# 角色分页查询
# ============================================================
@role_blue.route('/page', methods=["GET"])
@role_blue.route("/page", methods=["GET"])
@check_permission("sys:role:page")
def page():
"""
@@ -82,15 +84,15 @@ def page():
Returns:
JSON: 角色分页数据,包含 records(数据列表)、total(总记录数)等
"""
result = role.get_role_page()
return result
return R.ok(data=role.get_role_page(request.args))
# ============================================================
# 角色详情查询
# ============================================================
@role_blue.route('/detail/<int:id>', methods=["GET"])
@role_blue.route("/detail/<int:id>", methods=["GET"])
@check_permission("sys:role:detail")
def detail(id):
"""
@@ -110,7 +112,8 @@ def detail(id):
# 角色添加
# ============================================================
@role_blue.route('/add', methods=["POST"])
@role_blue.route("/add", methods=["POST"])
@check_permission("sys:role:add")
@check_demo
def add():
@@ -123,15 +126,19 @@ def add():
Returns:
JSON: 操作结果
"""
result = role.add_role()
return result
json_data, err = parse_request_body()
if err:
return R.failed(msg=err)
role.add_role(json_data)
return R.ok(msg="添加成功")
# ============================================================
# 角色更新
# ============================================================
@role_blue.route('/update', methods=["PUT"])
@role_blue.route("/update", methods=["PUT"])
@check_permission("sys:role:update")
@check_demo
def update():
@@ -144,15 +151,19 @@ def update():
Returns:
JSON: 操作结果
"""
result = role.update_role()
return result
json_data, err = parse_request_body()
if err:
return R.failed(msg=err)
role.update_role(json_data)
return R.ok(msg="更新成功")
# ============================================================
# 角色删除
# ============================================================
@role_blue.route('/delete/<string:id>', methods=["DELETE"])
@role_blue.route("/delete/<string:id>", methods=["DELETE"])
@check_permission("sys:role:delete")
@check_demo
def delete(id):
@@ -165,15 +176,16 @@ def delete(id):
Returns:
JSON: 操作结果
"""
result = role.delete_roles(id)
return result
count = role.delete_roles(id)
return R.ok(msg=f"本次共删除{count}条数据")
# ============================================================
# 角色批量删除
# ============================================================
@role_blue.route('/batchDelete', methods=["DELETE"])
@role_blue.route("/batchDelete", methods=["DELETE"])
@check_permission("sys:role:batch_delete")
@check_demo
def batch_delete():
@@ -199,7 +211,8 @@ def batch_delete():
# 角色列表获取(无分页)
# ============================================================
@role_blue.route('/list', methods=["GET"])
@role_blue.route("/list", methods=["GET"])
def get_role_list():
"""
获取角色数据列表(无分页)
+66 -111
View File
@@ -65,20 +65,18 @@
import logging
from datetime import datetime, timezone
from flask import request
from sqlalchemy import and_
from src.common.constants.common import PAGE_SIZE
from src.modules.system.role.schema import RoleForm
from src.modules.system.role.models import Role
from src.modules.system.role.repository import RoleRepository
from src.core.exceptions import BizError
from src.modules.association.user_role.models import UserRole
from src.modules.system.menu.service import invalidate_user_cache
from src.core.database import db
from src.core import response as R
from src.common.utils import regular
from src.common.utils.case_converter import convert_camel_to_snake
from src.common.utils.model_utils import models_to_list, model_to_dict
from src.common.utils.request import parse_request_body, parse_id_list, like_contains
from src.common.utils.request import parse_id_list, like_contains
from src.core.security import get_username
# 获取日志记录器
@@ -89,7 +87,8 @@ logger = logging.getLogger(__name__)
# 角色分页查询
# ============================================================
def get_role_page():
def get_role_page(params: dict) -> dict:
"""
查询角色分页数据
@@ -109,31 +108,26 @@ def get_role_page():
"pages": 总页数
}
"""
try:
# 获取分页参数
page_no = int(request.args.get('pageNo', 1))
page_size = int(request.args.get('pageSize', PAGE_SIZE))
# 获取分页参数
page_no = int(params.get("pageNo", 1))
page_size = int(params.get("pageSize", PAGE_SIZE))
# 应用筛选条件
query = _apply_filters()
# 应用筛选条件
query = _apply_filters(params)
# 记录总数
total = query.count()
# 计算总页数
pages = (total + page_size - 1) // page_size if total > 0 else 0
# 记录总数
total = query.count()
# 计算总页数
pages = (total + page_size - 1) // page_size if total > 0 else 0
# 分页查询
role_list = query.limit(page_size).offset((page_no - 1) * page_size).all()
# 构建返回数据
page_data = _build_page_data(role_list, total, page_size, page_no, pages)
return R.ok(data=page_data)
except Exception as e:
logger.error(f"查询角色分页数据异常: {str(e)}")
return R.failed(msg="查询失败,请稍后重试")
# 分页查询
role_list = query.limit(page_size).offset((page_no - 1) * page_size).all()
# 构建返回数据
page_data = _build_page_data(role_list, total, page_size, page_no, pages)
return page_data
def _apply_filters():
def _apply_filters(params: dict):
"""
应用查询筛选条件
@@ -150,9 +144,9 @@ def _apply_filters():
query = Role.query.filter(Role.is_delete == 0)
# 角色名称模糊筛选
name = request.args.get('name')
name = params.get("name")
if name:
query = query.filter(Role.name.like(like_contains(name), escape='\\'))
query = query.filter(Role.name.like(like_contains(name), escape="\\"))
# 按排序值升序排列
return query.order_by(Role.sort.asc())
@@ -175,19 +169,14 @@ def _build_page_data(role_list, total, page_size, page_no, pages):
# 将模型对象列表转换为字典列表
records = models_to_list(role_list)
return {
'records': records,
'total': total,
'size': page_size,
'current': page_no,
'pages': pages
}
return {"records": records, "total": total, "size": page_size, "current": page_no, "pages": pages}
# ============================================================
# 角色详情查询
# ============================================================
def get_role_detail(role_id):
"""
根据ID查询角色详情(返回字典格式)
@@ -218,6 +207,7 @@ def get_role_detail(role_id):
# 角色信息查询(根据ID)- 核心查询方法
# ============================================================
def get_info(role_id: int):
"""
根据ID查询角色信息(返回模型对象)
@@ -242,26 +232,10 @@ def get_info(role_id: int):
if role:
print(role.name)
"""
# 参数有效性验证
if not role_id or role_id <= 0:
return None
try:
# 查询未删除的角色
role = Role.query.filter(
and_(Role.id == role_id, Role.is_delete == 0)
).first()
return role
except Exception as e:
logger.error(f"查询角色信息异常,ID: {role_id}, 错误: {str(e)}")
return None
return RoleRepository.get_by_id(role_id)
# ============================================================
# 角色添加
# ============================================================
def add_role():
def add_role(data: dict) -> None:
"""
添加角色
@@ -273,35 +247,24 @@ def add_role():
Returns:
R: 操作结果
"""
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证
form = RoleForm(data=json_data)
if not form.validate():
err_msg = regular.get_err(form)
return R.failed(msg=err_msg)
# 表单验证
form = RoleForm(data=data)
if not form.validate():
raise BizError(regular.get_err(form))
# 创建角色
role = Role(**form.data)
role.create_user = get_username()
role.save()
return R.ok(msg="添加成功")
except Exception as e:
logger.error(f"添加角色异常: {str(e)}")
return R.failed(msg="添加失败,请稍后重试")
# 创建角色
role = Role(**form.data)
role.create_user = get_username()
role.save()
# ============================================================
# 角色更新
# ============================================================
def update_role():
def update_role(data: dict) -> None:
"""
更新角色
@@ -314,52 +277,42 @@ def update_role():
Returns:
R: 操作结果
"""
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证
form = RoleForm(data=json_data)
if not form.validate():
return R.failed(msg=regular.get_err(form))
# 表单验证
form = RoleForm(data=data)
if not form.validate():
raise BizError(regular.get_err(form))
# 获取角色ID
role_id = form.data.get('id')
if not role_id or int(role_id) <= 0:
return R.failed("角色ID无效")
role_id = int(role_id)
# 获取角色ID
role_id = form.data.get("id")
if not role_id or int(role_id) <= 0:
raise BizError("角色ID无效")
role_id = int(role_id)
# 复用 get_info 方法验证角色是否存在
role = get_info(role_id)
if not role:
return R.failed("角色不存在")
# 复用 get_info 方法验证角色是否存在
role = get_info(role_id)
if not role:
raise BizError("角色不存在")
# 更新角色信息(移除ID,避免更新主键)
form_data = convert_camel_to_snake(form.data.copy())
form_data.pop('id', None)
# 更新角色信息(移除ID,避免更新主键)
form_data = convert_camel_to_snake(form.data.copy())
form_data.pop("id", None)
form_data['update_time'] = datetime.now(tz=timezone.utc)
form_data['update_user'] = get_username()
form_data["update_time"] = datetime.now(tz=timezone.utc)
form_data["update_user"] = get_username()
result = Role.query.filter_by(id=role_id).update(form_data)
result = Role.query.filter_by(id=role_id).update(form_data)
if not result:
return R.failed("更新失败")
return R.ok(msg="更新成功")
except Exception as e:
logger.error(f"更新角色异常: {str(e)}")
return R.failed(msg="更新角色失败,请稍后重试")
if not result:
raise BizError("更新失败")
# ============================================================
# 角色删除
# ============================================================
def delete_roles(role_ids):
def delete_roles(role_ids: str) -> int:
"""
删除角色(批量删除)
@@ -379,13 +332,17 @@ def delete_roles(role_ids):
for uid in user_ids:
invalidate_user_cache(uid)
return Role.batch_delete_r(role_ids)
role_ids_list = parse_id_list(role_ids)
if not role_ids_list:
raise BizError("无效的记录ID")
return Role.batch_delete(role_ids_list)
# ============================================================
# 角色列表获取(无分页)
# ============================================================
def get_role_list():
"""
获取所有启用的角色列表(无分页)
@@ -398,9 +355,7 @@ def get_role_list():
"""
try:
# 查询所有启用的角色
role_list = Role.query.filter(
Role.is_delete == 0
).order_by(Role.sort.asc()).all()
role_list = Role.query.filter(Role.is_delete == 0).order_by(Role.sort.asc()).all()
# 转换为字典列表
return models_to_list(role_list)