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 import check_permission
from application.middleware.check_demo import check_demo from application.middleware.check_demo import check_demo
from src.modules.system.role import service as role_menu 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 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") @check_permission("sys:role:list")
def list(role_id): 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_permission("sys:role:save")
@check_demo @check_demo
def save(): def save():
@@ -101,5 +107,8 @@ def save():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = role_menu.save_role_menu() json_data, err = parse_request_body()
return result 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.system.menu.models import Menu
from src.modules.association.role_menu.models import RoleMenu from src.modules.association.role_menu.models import RoleMenu
from src.modules.system.menu.service import invalidate_role_users_cache 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.common.utils import regular
from src.core import response as R from src.core.exceptions import BizError
from src.common.utils.request import parse_request_body
# 获取日志记录器 # 获取日志记录器
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -77,6 +75,7 @@ logger = logging.getLogger(__name__)
# 角色菜单树查询 # 角色菜单树查询
# ============================================================ # ============================================================
def get_role_menus(role_id): def get_role_menus(role_id):
""" """
根据角色ID查询菜单权限树 根据角色ID查询菜单权限树
@@ -106,9 +105,7 @@ def get_role_menus(role_id):
try: try:
# 获取全部启用的菜单列表(按排序升序) # 获取全部启用的菜单列表(按排序升序)
menu_list = Menu.query.filter( menu_list = Menu.query.filter(and_(Menu.is_delete == 0, Menu.status == 0)).order_by(Menu.sort.asc()).all()
and_(Menu.is_delete == 0, Menu.status == 0)
).order_by(Menu.sort.asc()).all()
if not menu_list: if not menu_list:
return [] return []
@@ -121,12 +118,12 @@ def get_role_menus(role_id):
result = [] result = []
for menu in menu_list: for menu in menu_list:
data = { data = {
'id': menu.id, "id": menu.id,
'name': menu.name, "name": menu.name,
'parentId': menu.parent_id, "parentId": menu.parent_id,
} }
if menu.id in assigned_menu_ids: if menu.id in assigned_menu_ids:
data['checked'] = True data["checked"] = True
result.append(data) result.append(data)
return result 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数据 1. 表单验证
2. 表单验证 2. 删除该角色原有的所有菜单权限
3. 删除该角色原有的所有菜单权限 3. 重新创建新的菜单权限关联
4. 重新创建新的菜单权限关联
Returns: Args:
R: 操作结果 data: 请求体数据 dict
Raises:
BizError: 表单验证失败时
""" """
try: form = RoleMenuForm(data=data)
# 获取参数 if not form.validate():
json_data, error = parse_request_body() raise BizError(regular.get_err(form))
if error:
return R.failed(msg=error)
# 表单验证 role_id = form.roleId.data
form = RoleMenuForm(data=json_data) menu_ids = form.menuIds.data
if not form.validate():
err_msg = regular.get_err(form)
return R.failed(msg=err_msg)
role_id = form.roleId.data # 删除当前角色ID相关的所有菜单权限
menu_ids = form.menuIds.data 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 != "": invalidate_role_users_cache(role_id)
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="保存角色菜单权限失败,请稍后重试")
+1 -1
View File
@@ -16,8 +16,8 @@ from flask import Blueprint
from application.middleware.check_demo import check_demo from application.middleware.check_demo import check_demo
from application.middleware.check_permission import check_permission from application.middleware.check_permission import check_permission
from src.modules.system.user import service as user_role 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 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 - 权限校验中间件 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_demo import check_demo
from application.middleware.check_permission import check_permission from application.middleware.check_permission import check_permission
from src.modules.system.menu import service as menu 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 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") @check_permission("sys:menu:list")
def list(): def list():
""" """
@@ -79,15 +83,15 @@ def list():
Returns: Returns:
JSON: 菜单列表数据 JSON: 菜单列表数据
""" """
result = menu.get_menu_list() return R.ok(data=menu.get_menu_list(request.args))
return result
# ============================================================ # ============================================================
# 菜单详情查询 # 菜单详情查询
# ============================================================ # ============================================================
@menu_blue.route('/detail/<int:id>', methods=["GET"])
@menu_blue.route("/detail/<int:id>", methods=["GET"])
@check_permission("sys:menu:detail") @check_permission("sys:menu:detail")
def detail(id): 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_permission("sys:menu:add")
@check_demo @check_demo
def add(): def add():
@@ -120,15 +125,19 @@ def add():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = menu.add_menu() json_data, err = parse_request_body()
return result 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_permission("sys:menu:update")
@check_demo @check_demo
def update(): def update():
@@ -141,15 +150,19 @@ def update():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = menu.update_menu() json_data, err = parse_request_body()
return result 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_permission("sys:menu:delete")
@check_demo @check_demo
def delete(id): def delete(id):
@@ -162,5 +175,5 @@ def delete(id):
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = menu.delete_menus(id) count = menu.delete_menus(id)
return result return R.ok(msg=f"本次共删除{count}条数据")
+125 -165
View File
@@ -66,19 +66,19 @@
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from flask import request
from sqlalchemy import and_ from sqlalchemy import and_
from src.modules.system.menu.schema import MenuForm from src.modules.system.menu.schema import MenuForm
from src.modules.system.menu.models import Menu 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.role_menu.models import RoleMenu
from src.modules.association.user_role.models import UserRole from src.modules.association.user_role.models import UserRole
from src.core.database import db from src.core.database import db
from src.core import response as R
from src.common.utils import regular from src.common.utils import regular
from src.common.utils.case_converter import convert_camel_to_snake from src.common.utils.case_converter import convert_camel_to_snake
from src.core.redis_client import redis 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.common.utils.model_utils import models_to_list, model_to_dict
from src.core.security import get_username from src.core.security import get_username
@@ -101,8 +101,10 @@ _CACHE_EXPIRE = 7200
# 权限节点常量映射 # 权限节点常量映射
# ============================================================ # ============================================================
class PermissionNode: class PermissionNode:
"""权限节点常量定义""" """权限节点常量定义"""
QUERY = 1 # 查询 QUERY = 1 # 查询
ADD = 5 # 添加 ADD = 5 # 添加
UPDATE = 10 # 修改 UPDATE = 10 # 修改
@@ -121,20 +123,20 @@ class PermissionNode:
# 权限节点配置映射 # 权限节点配置映射
PERMISSION_CONFIG = { PERMISSION_CONFIG = {
PermissionNode.QUERY: {'suffix': '查询', 'action': 'list', 'perm_suffix': 'list'}, PermissionNode.QUERY: {"suffix": "查询", "action": "list", "perm_suffix": "list"},
PermissionNode.ADD: {'suffix': '添加', 'action': 'add', 'perm_suffix': 'add'}, PermissionNode.ADD: {"suffix": "添加", "action": "add", "perm_suffix": "add"},
PermissionNode.UPDATE: {'suffix': '修改', 'action': 'update', 'perm_suffix': 'update'}, PermissionNode.UPDATE: {"suffix": "修改", "action": "update", "perm_suffix": "update"},
PermissionNode.DELETE: {'suffix': '删除', 'action': 'delete', 'perm_suffix': 'delete'}, PermissionNode.DELETE: {"suffix": "删除", "action": "delete", "perm_suffix": "delete"},
PermissionNode.DETAIL: {'suffix': '详情', 'action': 'detail', 'perm_suffix': 'detail'}, PermissionNode.DETAIL: {"suffix": "详情", "action": "detail", "perm_suffix": "detail"},
PermissionNode.STATUS: {'suffix': '设置状态', 'action': 'status', 'perm_suffix': 'status'}, PermissionNode.STATUS: {"suffix": "设置状态", "action": "status", "perm_suffix": "status"},
PermissionNode.BATCH_DELETE: {'suffix': '批量删除', 'action': 'dall', 'perm_suffix': 'dall'}, PermissionNode.BATCH_DELETE: {"suffix": "批量删除", "action": "dall", "perm_suffix": "dall"},
PermissionNode.ADD_CHILD: {'suffix': '添加子级', 'action': 'addz', 'perm_suffix': 'addz'}, PermissionNode.ADD_CHILD: {"suffix": "添加子级", "action": "addz", "perm_suffix": "addz"},
PermissionNode.EXPAND: {'suffix': '全部展开', 'action': 'expand', 'perm_suffix': 'expand'}, PermissionNode.EXPAND: {"suffix": "全部展开", "action": "expand", "perm_suffix": "expand"},
PermissionNode.COLLAPSE: {'suffix': '全部折叠', 'action': 'collapse', 'perm_suffix': 'collapse'}, PermissionNode.COLLAPSE: {"suffix": "全部折叠", "action": "collapse", "perm_suffix": "collapse"},
PermissionNode.EXPORT: {'suffix': '导出', 'action': 'export', 'perm_suffix': 'export'}, PermissionNode.EXPORT: {"suffix": "导出", "action": "export", "perm_suffix": "export"},
PermissionNode.IMPORT: {'suffix': '导入', 'action': 'import', 'perm_suffix': 'import'}, PermissionNode.IMPORT: {"suffix": "导入", "action": "import", "perm_suffix": "import"},
PermissionNode.PERMISSION: {'suffix': '分配权限', 'action': 'permission', 'perm_suffix': 'permission'}, PermissionNode.PERMISSION: {"suffix": "分配权限", "action": "permission", "perm_suffix": "permission"},
PermissionNode.RESET_PWD: {'suffix': '重置密码', 'action': 'resetPwd', 'perm_suffix': 'resetPwd'}, 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: Args:
request: Flask request对象,包含查询参数 params: 查询参数
- name: 菜单名称(可选,模糊查询) - name: 菜单名称(可选,模糊查询)
Returns: Returns:
R: 包含菜单列表的JSON响应 list: 菜单字典列表
""" """
try: # 应用筛选条件
# 应用筛选条件 query = _apply_filters(params)
query = _apply_filters()
# 查询数据(按排序升序) # 查询数据(按排序升序)
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 models_to_list(menu_list)
return R.ok(data=records)
except Exception as e:
logger.error(f"查询菜单列表异常: {str(e)}")
return R.failed(msg="查询失败,请稍后重试")
def _apply_filters(): def _apply_filters(params: dict):
""" """
应用查询筛选条件 应用查询筛选条件
@@ -187,9 +183,9 @@ def _apply_filters():
query = Menu.query.filter(Menu.is_delete == 0) query = Menu.query.filter(Menu.is_delete == 0)
# 菜单名称模糊筛选 # 菜单名称模糊筛选
name = request.args.get('name') name = params.get("name")
if 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 return query
@@ -198,6 +194,7 @@ def _apply_filters():
# 菜单详情查询 # 菜单详情查询
# ============================================================ # ============================================================
def get_menu_detail(menu_id): def get_menu_detail(menu_id):
""" """
根据ID查询菜单详情(返回字典格式) 根据ID查询菜单详情(返回字典格式)
@@ -221,15 +218,13 @@ def get_menu_detail(menu_id):
data = model_to_dict(menu) data = model_to_dict(menu)
# 查询菜单权限节点(type=1 表示权限节点) # 查询菜单权限节点(type=1 表示权限节点)
permission_list = Menu.query.filter( permission_list = Menu.query.filter(and_(Menu.parent_id == menu_id, Menu.type == 1, Menu.is_delete == 0)).all()
and_(Menu.parent_id == menu_id, Menu.type == 1, Menu.is_delete == 0)
).all()
# 选中的权限节点(按sort值收集) # 选中的权限节点(按sort值收集)
checked_list = [p.sort for p in permission_list] if permission_list else [] 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 return data
@@ -242,6 +237,7 @@ def get_menu_detail(menu_id):
# 菜单信息查询(根据ID)- 核心查询方法 # 菜单信息查询(根据ID)- 核心查询方法
# ============================================================ # ============================================================
def get_info(menu_id: int): def get_info(menu_id: int):
""" """
根据ID查询菜单信息(返回模型对象) 根据ID查询菜单信息(返回模型对象)
@@ -267,26 +263,10 @@ def get_info(menu_id: int):
if menu: if menu:
print(menu.title) print(menu.title)
""" """
# 参数有效性验证 return MenuRepository.get_by_id(menu_id)
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
# ============================================================ def add_menu(data: dict) -> None:
# 菜单添加
# ============================================================
def add_menu():
""" """
添加菜单 添加菜单
@@ -299,52 +279,41 @@ def add_menu():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = MenuForm(data=json_data) form = MenuForm(data=data)
if not form.validate(): if not form.validate():
err_msg = regular.get_err(form) raise BizError(regular.get_err(form))
return R.failed(msg=err_msg)
# 权限节点 # 权限节点
checked_list = form.data.get('checked_list') checked_list = form.data.get("checked_list")
menu_type = form.data.get('type') menu_type = form.data.get("type")
title = form.data.get('title') title = form.data.get("title")
path = form.data.get('path', '') path = form.data.get("path", "")
# 移除不需要直接保存到Menu表的字段 # 移除不需要直接保存到Menu表的字段
form_data = form.data.copy() form_data = form.data.copy()
form_data.pop('checked_list', None) form_data.pop("checked_list", None)
# 创建菜单 # 创建菜单
menu = Menu(**form_data) menu = Menu(**form_data)
menu.create_user = get_username() menu.create_user = get_username()
db.session.add(menu) db.session.add(menu)
db.session.flush() 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() invalidate_all_cache()
return R.ok(msg="添加成功")
except Exception as e:
logger.error(f"添加菜单异常: {str(e)}")
return R.failed(msg="添加菜单失败,请稍后重试")
# ============================================================ # ============================================================
# 菜单更新 # 菜单更新
# ============================================================ # ============================================================
def update_menu():
def update_menu(data: dict) -> None:
""" """
更新菜单 更新菜单
@@ -358,66 +327,56 @@ def update_menu():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = MenuForm(data=json_data) form = MenuForm(data=data)
if not form.validate(): if not form.validate():
return R.failed(msg=regular.get_err(form)) raise BizError(regular.get_err(form))
# 获取菜单ID # 获取菜单ID
menu_id = form.data.get('id') menu_id = form.data.get("id")
if not menu_id or int(menu_id) <= 0: if not menu_id or int(menu_id) <= 0:
return R.failed("菜单ID无效") raise BizError("菜单ID无效")
menu_id = int(menu_id) menu_id = int(menu_id)
# 复用 get_info 方法验证菜单是否存在 # 复用 get_info 方法验证菜单是否存在
menu = get_info(menu_id) menu = get_info(menu_id)
if not menu: if not menu:
return R.failed("菜单不存在") raise BizError("菜单不存在")
# 权限节点 # 权限节点
checked_list = form.data.get('checked_list') checked_list = form.data.get("checked_list")
menu_type = form.data.get('type') menu_type = form.data.get("type")
title = form.data.get('title') title = form.data.get("title")
path = form.data.get('path', '') path = form.data.get("path", "")
# 移除不需要直接更新到Menu表的字段 # 移除不需要直接更新到Menu表的字段
form_data = convert_camel_to_snake(form.data.copy()) form_data = convert_camel_to_snake(form.data.copy())
form_data.pop('id', None) form_data.pop("id", None)
form_data.pop('checked_list', None) form_data.pop("checked_list", None)
form_data['update_time'] = datetime.now(tz=timezone.utc) form_data["update_time"] = datetime.now(tz=timezone.utc)
form_data['update_user'] = get_username() 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: if not result:
return R.failed("更新失败") 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() invalidate_all_cache()
return R.ok(msg="更新成功")
except Exception as e:
logger.error(f"更新菜单异常: {str(e)}")
return R.failed(msg="更新菜单失败,请稍后重试")
# ============================================================ # ============================================================
# 菜单删除 # 菜单删除
# ============================================================ # ============================================================
def delete_menus(menu_ids):
def delete_menus(menu_ids: str) -> int:
""" """
删除菜单(批量删除) 删除菜单(批量删除)
@@ -431,20 +390,18 @@ def delete_menus(menu_ids):
R: 操作结果 R: 操作结果
""" """
if not menu_ids: if not menu_ids:
return R.failed("记录ID不存在") raise BizError("记录ID不存在")
# 解析ID列表 # 解析ID列表
id_list = parse_id_list(menu_ids) id_list = parse_id_list(menu_ids)
if not id_list: if not id_list:
return R.failed("无效的记录ID") raise BizError("无效的记录ID")
# 检查是否有子菜单 # 检查是否有子菜单
for menu_id in id_list: for menu_id in id_list:
child_count = Menu.query.filter( child_count = Menu.query.filter(and_(Menu.parent_id == menu_id, Menu.is_delete == 0)).count()
and_(Menu.parent_id == menu_id, Menu.is_delete == 0)
).count()
if child_count > 0: if child_count > 0:
return R.failed(f"菜单ID {menu_id} 存在子菜单,请先删除子菜单") raise BizError(f"菜单ID {menu_id} 存在子菜单,请先删除子菜单")
result = Menu.batch_delete_r(menu_ids) 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): 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: try:
# 删除现有权限节点 # 删除现有权限节点
Menu.query.filter( Menu.query.filter(and_(Menu.parent_id == parent_id, Menu.type == 1, Menu.is_delete == 0)).delete()
and_(Menu.parent_id == parent_id, Menu.type == 1, Menu.is_delete == 0)
).delete()
# 模块名称(去掉"管理"后缀) # 模块名称(去掉"管理"后缀)
module_title = title.replace("管理", "") if title else "" module_title = title.replace("管理", "") if title else ""
# 请求path处理,获取模块名 # 请求path处理,获取模块名
path_arr = path.split('/') path_arr = path.split("/")
if len(path_arr) < 3: if len(path_arr) < 3:
return return
module_name = path_arr[-1] 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]: if value in [PermissionNode.DETAIL]:
node_title = f"{module_title}{config['suffix']}" node_title = f"{module_title}{config['suffix']}"
elif value in [PermissionNode.QUERY, PermissionNode.ADD, PermissionNode.UPDATE, elif value in [
PermissionNode.DELETE, PermissionNode.EXPORT, PermissionNode.IMPORT]: PermissionNode.QUERY,
PermissionNode.ADD,
PermissionNode.UPDATE,
PermissionNode.DELETE,
PermissionNode.EXPORT,
PermissionNode.IMPORT,
]:
node_title = f"{config['suffix']}{module_title}" node_title = f"{config['suffix']}{module_title}"
else: else:
node_title = config['suffix'] node_title = config["suffix"]
# 构建路径 # 构建路径
node_path = f"/{module_name}/{config['action']}" 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, status=1,
target="_self", target="_self",
sort=value, sort=value,
create_user=get_username() create_user=get_username(),
) )
permission_menu.save() 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): def get_user_permissions(user_id):
""" """
获取用户权限节点列表(带Redis缓存) 获取用户权限节点列表(带Redis缓存)
@@ -566,9 +529,7 @@ def get_user_permissions(user_id):
# 缓存未命中,查询数据库 # 缓存未命中,查询数据库
if user_id == 1: if user_id == 1:
permission_list = Menu.query.filter( permission_list = Menu.query.filter(and_(Menu.is_delete == 0, Menu.type == 1, Menu.status == 0)).all()
and_(Menu.is_delete == 0, Menu.type == 1, Menu.status == 0)
).all()
result = [item.permission for item in permission_list if item.permission] result = [item.permission for item in permission_list if item.permission]
else: else:
rows = ( rows = (
@@ -580,7 +541,7 @@ def get_user_permissions(user_id):
Menu.type == 1, Menu.type == 1,
Menu.status == 0, Menu.status == 0,
Menu.is_delete == 0, Menu.is_delete == 0,
Menu.permission != '' Menu.permission != "",
) )
.all() .all()
) )
@@ -599,6 +560,7 @@ def get_user_permissions(user_id):
# 用户菜单树获取 # 用户菜单树获取
# ============================================================ # ============================================================
def get_user_permission_menus(user_id): def get_user_permission_menus(user_id):
""" """
根据用户ID获取权限菜单树(带Redis缓存) 根据用户ID获取权限菜单树(带Redis缓存)
@@ -629,20 +591,17 @@ def get_user_permission_menus(user_id):
# 缓存未命中,查询数据库 # 缓存未命中,查询数据库
if user_id == 1: if user_id == 1:
menu_list = Menu.query.filter( menu_list = (
and_(Menu.is_delete == 0, Menu.status == 0, Menu.type == 0) Menu.query.filter(and_(Menu.is_delete == 0, Menu.status == 0, Menu.type == 0))
).order_by(Menu.sort.asc()).all() .order_by(Menu.sort.asc())
.all()
)
else: else:
menu_list = ( menu_list = (
db.session.query(Menu) db.session.query(Menu)
.join(RoleMenu, Menu.id == RoleMenu.menu_id) .join(RoleMenu, Menu.id == RoleMenu.menu_id)
.join(UserRole, UserRole.role_id == RoleMenu.role_id) .join(UserRole, UserRole.role_id == RoleMenu.role_id)
.filter( .filter(UserRole.user_id == user_id, Menu.type == 0, Menu.status == 0, Menu.is_delete == 0)
UserRole.user_id == user_id,
Menu.type == 0,
Menu.status == 0,
Menu.is_delete == 0
)
.order_by(Menu.sort.asc()) .order_by(Menu.sort.asc())
.all() .all()
) )
@@ -683,8 +642,8 @@ def _build_menu_tree(menu_data, parent_id):
def _build(pid): def _build(pid):
children = children_map.get(pid, []) children = children_map.get(pid, [])
for item in children: for item in children:
item['children'] = _build(item["id"]) item["children"] = _build(item["id"])
children.sort(key=lambda x: x.get('sort', 0)) children.sort(key=lambda x: x.get("sort", 0))
return children return children
return _build(parent_id) return _build(parent_id)
@@ -694,6 +653,7 @@ def _build_menu_tree(menu_data, parent_id):
# 权限缓存失效 # 权限缓存失效
# ============================================================ # ============================================================
def invalidate_user_cache(user_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 src.common.base_model import BaseModel
from config import DB_PREFIX from config import DB_PREFIX
from src.core.database import db 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.services.role - 角色业务逻辑层
application.middleware.check_permission - 权限校验中间件 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_demo import check_demo
from application.middleware.check_permission import check_permission from application.middleware.check_permission import check_permission
from src.modules.system.role import service as role from src.modules.system.role import service as role
from src.common.utils.request import parse_batch_ids from src.common.utils.request import parse_batch_ids, parse_request_body
role_blue = Blueprint('role', __name__, url_prefix='/role')
from src.core import response as R 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") @check_permission("sys:role:page")
def page(): def page():
""" """
@@ -82,15 +84,15 @@ def page():
Returns: Returns:
JSON: 角色分页数据,包含 records(数据列表)、total(总记录数)等 JSON: 角色分页数据,包含 records(数据列表)、total(总记录数)等
""" """
result = role.get_role_page() return R.ok(data=role.get_role_page(request.args))
return result
# ============================================================ # ============================================================
# 角色详情查询 # 角色详情查询
# ============================================================ # ============================================================
@role_blue.route('/detail/<int:id>', methods=["GET"])
@role_blue.route("/detail/<int:id>", methods=["GET"])
@check_permission("sys:role:detail") @check_permission("sys:role:detail")
def detail(id): 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_permission("sys:role:add")
@check_demo @check_demo
def add(): def add():
@@ -123,15 +126,19 @@ def add():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = role.add_role() json_data, err = parse_request_body()
return result 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_permission("sys:role:update")
@check_demo @check_demo
def update(): def update():
@@ -144,15 +151,19 @@ def update():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = role.update_role() json_data, err = parse_request_body()
return result 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_permission("sys:role:delete")
@check_demo @check_demo
def delete(id): def delete(id):
@@ -165,15 +176,16 @@ def delete(id):
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = role.delete_roles(id) count = role.delete_roles(id)
return result 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_permission("sys:role:batch_delete")
@check_demo @check_demo
def batch_delete(): 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(): def get_role_list():
""" """
获取角色数据列表(无分页) 获取角色数据列表(无分页)
+66 -111
View File
@@ -65,20 +65,18 @@
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from flask import request
from sqlalchemy import and_
from src.common.constants.common import PAGE_SIZE from src.common.constants.common import PAGE_SIZE
from src.modules.system.role.schema import RoleForm from src.modules.system.role.schema import RoleForm
from src.modules.system.role.models import Role 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.association.user_role.models import UserRole
from src.modules.system.menu.service import invalidate_user_cache 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 import regular
from src.common.utils.case_converter import convert_camel_to_snake 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.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 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": 总页数 "pages": 总页数
} }
""" """
try: # 获取分页参数
# 获取分页参数 page_no = int(params.get("pageNo", 1))
page_no = int(request.args.get('pageNo', 1)) page_size = int(params.get("pageSize", PAGE_SIZE))
page_size = int(request.args.get('pageSize', PAGE_SIZE))
# 应用筛选条件 # 应用筛选条件
query = _apply_filters() query = _apply_filters(params)
# 记录总数 # 记录总数
total = query.count() total = query.count()
# 计算总页数 # 计算总页数
pages = (total + page_size - 1) // page_size if total > 0 else 0 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() 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) page_data = _build_page_data(role_list, total, page_size, page_no, pages)
return R.ok(data=page_data) return page_data
except Exception as e:
logger.error(f"查询角色分页数据异常: {str(e)}")
return R.failed(msg="查询失败,请稍后重试")
def _apply_filters(): def _apply_filters(params: dict):
""" """
应用查询筛选条件 应用查询筛选条件
@@ -150,9 +144,9 @@ def _apply_filters():
query = Role.query.filter(Role.is_delete == 0) query = Role.query.filter(Role.is_delete == 0)
# 角色名称模糊筛选 # 角色名称模糊筛选
name = request.args.get('name') name = params.get("name")
if 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()) 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) records = models_to_list(role_list)
return { return {"records": records, "total": total, "size": page_size, "current": page_no, "pages": pages}
'records': records,
'total': total,
'size': page_size,
'current': page_no,
'pages': pages
}
# ============================================================ # ============================================================
# 角色详情查询 # 角色详情查询
# ============================================================ # ============================================================
def get_role_detail(role_id): def get_role_detail(role_id):
""" """
根据ID查询角色详情(返回字典格式) 根据ID查询角色详情(返回字典格式)
@@ -218,6 +207,7 @@ def get_role_detail(role_id):
# 角色信息查询(根据ID)- 核心查询方法 # 角色信息查询(根据ID)- 核心查询方法
# ============================================================ # ============================================================
def get_info(role_id: int): def get_info(role_id: int):
""" """
根据ID查询角色信息(返回模型对象) 根据ID查询角色信息(返回模型对象)
@@ -242,26 +232,10 @@ def get_info(role_id: int):
if role: if role:
print(role.name) print(role.name)
""" """
# 参数有效性验证 return RoleRepository.get_by_id(role_id)
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
# ============================================================ def add_role(data: dict) -> None:
# 角色添加
# ============================================================
def add_role():
""" """
添加角色 添加角色
@@ -273,35 +247,24 @@ def add_role():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = RoleForm(data=json_data) form = RoleForm(data=data)
if not form.validate(): if not form.validate():
err_msg = regular.get_err(form) raise BizError(regular.get_err(form))
return R.failed(msg=err_msg)
# 创建角色 # 创建角色
role = Role(**form.data) role = Role(**form.data)
role.create_user = get_username() role.create_user = get_username()
role.save() role.save()
return R.ok(msg="添加成功")
except Exception as e:
logger.error(f"添加角色异常: {str(e)}")
return R.failed(msg="添加失败,请稍后重试")
# ============================================================ # ============================================================
# 角色更新 # 角色更新
# ============================================================ # ============================================================
def update_role():
def update_role(data: dict) -> None:
""" """
更新角色 更新角色
@@ -314,52 +277,42 @@ def update_role():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = RoleForm(data=json_data) form = RoleForm(data=data)
if not form.validate(): if not form.validate():
return R.failed(msg=regular.get_err(form)) raise BizError(regular.get_err(form))
# 获取角色ID # 获取角色ID
role_id = form.data.get('id') role_id = form.data.get("id")
if not role_id or int(role_id) <= 0: if not role_id or int(role_id) <= 0:
return R.failed("角色ID无效") raise BizError("角色ID无效")
role_id = int(role_id) role_id = int(role_id)
# 复用 get_info 方法验证角色是否存在 # 复用 get_info 方法验证角色是否存在
role = get_info(role_id) role = get_info(role_id)
if not role: if not role:
return R.failed("角色不存在") raise BizError("角色不存在")
# 更新角色信息(移除ID,避免更新主键) # 更新角色信息(移除ID,避免更新主键)
form_data = convert_camel_to_snake(form.data.copy()) form_data = convert_camel_to_snake(form.data.copy())
form_data.pop('id', None) form_data.pop("id", None)
form_data['update_time'] = datetime.now(tz=timezone.utc) form_data["update_time"] = datetime.now(tz=timezone.utc)
form_data['update_user'] = get_username() 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: if not result:
return R.failed("更新失败") raise BizError("更新失败")
return R.ok(msg="更新成功")
except Exception as e:
logger.error(f"更新角色异常: {str(e)}")
return R.failed(msg="更新角色失败,请稍后重试")
# ============================================================ # ============================================================
# 角色删除 # 角色删除
# ============================================================ # ============================================================
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: for uid in user_ids:
invalidate_user_cache(uid) 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(): def get_role_list():
""" """
获取所有启用的角色列表(无分页) 获取所有启用的角色列表(无分页)
@@ -398,9 +355,7 @@ def get_role_list():
""" """
try: try:
# 查询所有启用的角色 # 查询所有启用的角色
role_list = Role.query.filter( role_list = Role.query.filter(Role.is_delete == 0).order_by(Role.sort.asc()).all()
Role.is_delete == 0
).order_by(Role.sort.asc()).all()
# 转换为字典列表 # 转换为字典列表
return models_to_list(role_list) return models_to_list(role_list)