From 3dbce04eb1931a561f5ac9e06fba4f78b2c95a0b Mon Sep 17 00:00:00 2001 From: DjangoAdmin Dev Date: Fri, 7 Aug 2026 15:40:33 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20P4c=20=E6=89=B9=E6=AC=A14=20?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=B1=82=E6=94=B9=E9=80=A0=EF=BC=88role/menu?= =?UTF-8?q?/user=5Frole/role=5Fmenu=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/modules/association/role_menu/router.py | 19 +- src/modules/association/role_menu/service.py | 85 +++--- src/modules/association/user_role/router.py | 2 +- src/modules/system/menu/repository.py | 15 + src/modules/system/menu/router.py | 43 ++- src/modules/system/menu/service.py | 290 ++++++++----------- src/modules/system/role/models.py | 1 - src/modules/system/role/repository.py | 15 + src/modules/system/role/router.py | 49 ++-- src/modules/system/role/service.py | 177 +++++------ 10 files changed, 329 insertions(+), 367 deletions(-) create mode 100644 src/modules/system/menu/repository.py create mode 100644 src/modules/system/role/repository.py diff --git a/src/modules/association/role_menu/router.py b/src/modules/association/role_menu/router.py index d7991e9..4459bdf 100644 --- a/src/modules/association/role_menu/router.py +++ b/src/modules/association/role_menu/router.py @@ -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/', methods=["GET"]) + +@role_menu_blue.route("/list/", 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="保存成功") diff --git a/src/modules/association/role_menu/service.py b/src/modules/association/role_menu/service.py index f45b5dc..627aa28 100644 --- a/src/modules/association/role_menu/service.py +++ b/src/modules/association/role_menu/service.py @@ -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) diff --git a/src/modules/association/user_role/router.py b/src/modules/association/user_role/router.py index 9d23755..67a3d90 100644 --- a/src/modules/association/user_role/router.py +++ b/src/modules/association/user_role/router.py @@ -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') # ============================================================ diff --git a/src/modules/system/menu/repository.py b/src/modules/system/menu/repository.py new file mode 100644 index 0000000..257f256 --- /dev/null +++ b/src/modules/system/menu/repository.py @@ -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 diff --git a/src/modules/system/menu/router.py b/src/modules/system/menu/router.py index 7eb3538..cc47b94 100644 --- a/src/modules/system/menu/router.py +++ b/src/modules/system/menu/router.py @@ -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/', methods=["GET"]) + +@menu_blue.route("/detail/", 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/', methods=["DELETE"]) + +@menu_blue.route("/delete/", 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}条数据") diff --git a/src/modules/system/menu/service.py b/src/modules/system/menu/service.py index 2057dd4..e6bc6d5 100644 --- a/src/modules/system/menu/service.py +++ b/src/modules/system/menu/service.py @@ -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): """ 清除指定用户的权限和菜单缓存 diff --git a/src/modules/system/role/models.py b/src/modules/system/role/models.py index 6c00c2a..a9a2ee6 100644 --- a/src/modules/system/role/models.py +++ b/src/modules/system/role/models.py @@ -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 # ============================================================ diff --git a/src/modules/system/role/repository.py b/src/modules/system/role/repository.py new file mode 100644 index 0000000..5d102d3 --- /dev/null +++ b/src/modules/system/role/repository.py @@ -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 diff --git a/src/modules/system/role/router.py b/src/modules/system/role/router.py index 32acdb5..8c820fb 100644 --- a/src/modules/system/role/router.py +++ b/src/modules/system/role/router.py @@ -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/', methods=["GET"]) + +@role_blue.route("/detail/", 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/', methods=["DELETE"]) + +@role_blue.route("/delete/", 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(): """ 获取角色数据列表(无分页) diff --git a/src/modules/system/role/service.py b/src/modules/system/role/service.py index 6f607a4..c041ac4 100644 --- a/src/modules/system/role/service.py +++ b/src/modules/system/role/service.py @@ -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)