126 lines
4.8 KiB
Python
126 lines
4.8 KiB
Python
# +======================================================================
|
|
# | 模块: 权限节点解析层
|
|
# | 说明: 跨模块共享的权限解析,供访问控制中间件与个人中心使用,
|
|
# | 带 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
|