# +====================================================================== # | 模块: 权限节点解析层 # | 说明: 跨模块共享的权限解析,供访问控制中间件与个人中心使用, # | 带 Redis 缓存与版本号即时失效 # +====================================================================== """权限节点解析(跨模块共享,供访问控制中间件与个人中心使用)。 权限节点存储于 menu 表(type=1),非独立业务模块: - get_permissions_list:按用户解析权限节点列表(带 Redis 缓存) - invalidate_permission_cache:角色/菜单/用户角色变更时使权限缓存失效 """ # ---------------------------------------------------------------------- # 导入依赖 # 涵盖:JSON 序列化、SQLAlchemy 条件表达式、数据库会话、日志器、 # Redis 客户端、菜单/角色/用户模型 # ---------------------------------------------------------------------- import json from sqlalchemy import and_, or_ from core.database import db from core.logger import logger from core.redis import redis from modules.menu.models import Menu from modules.role.models import Role from modules.role_menu.models import RoleMenu from modules.user.models import User from modules.user_role.models import UserRole # ============================================================ # 缓存配置 # ============================================================ # 权限缓存 TTL(秒):角色/菜单变更走版本号即时失效,TTL 仅兜底 _PERM_CACHE_TTL = 300 # 权限缓存版本号 key:自增即表示"所有用户的权限缓存失效" _PERM_VERSION_KEY = "perm:version" # ====================================================================== # 读取权限缓存版本号 # ====================================================================== def _get_perm_version() -> int: """读取权限缓存版本号(Redis 异常时回退 1,仅按 TTL 失效)。 :return: 当前权限缓存版本号 """ try: version = redis.get(_PERM_VERSION_KEY) return int(version) if version else 1 except Exception: return 1 # ====================================================================== # 失效权限缓存 # ====================================================================== def invalidate_permission_cache() -> None: """角色/菜单/用户角色变更后调用:自增版本号使所有权限缓存立即失效。""" try: redis.incr(_PERM_VERSION_KEY) except Exception: logger.warning("权限缓存失效失败(将按 TTL 自动过期)") # ====================================================================== # 获取用户权限节点列表 # ====================================================================== def get_permissions_list(user_id) -> list: """获取用户权限节点列表(带 Redis 缓存)。超级管理员(id=1)拥有全部节点。 :param user_id: 用户 ID :return: 权限节点标识列表 """ user_id = int(user_id) version = _get_perm_version() cache_key = f"perm:list:{version}:{user_id}" # 读缓存 try: cached = redis.get(cache_key) if cached is not None: return json.loads(cached) except Exception: pass # 缓存读取异常降级为实时查询 # 实时查询 try: if user_id == 1: # 超级管理员:全部权限节点 nodes = db.query(Menu).filter(and_(Menu.is_delete == 0, Menu.type == 1)).all() else: # 其他用户:通过角色关联查询权限节点(过滤软删的用户/角色/菜单关联, # 且仅取状态正常的用户,停用的用户不再授权) nodes = ( db.query(Menu) .join(RoleMenu, RoleMenu.menu_id == Menu.id) .join(UserRole, UserRole.role_id == RoleMenu.role_id) .join(Role, Role.id == UserRole.role_id) .join(User, User.id == UserRole.user_id) .filter( UserRole.user_id == user_id, UserRole.is_delete == 0, RoleMenu.is_delete == 0, Role.is_delete == 0, User.is_delete == 0, User.status == 1, or_(Menu.type == 1, and_(Menu.type == 0, Menu.permission != "")), Menu.status == 0, Menu.is_delete == 0, ) .distinct() .all() ) # 提取权限标识,过滤空值 result = [item.permission for item in nodes if item.permission] finally: db.close() # 写缓存(Redis 异常时静默,仅损失缓存收益) try: redis.set(cache_key, json.dumps(result), ex=_PERM_CACHE_TTL) except Exception: pass return result