110 lines
4.9 KiB
Python
110 lines
4.9 KiB
Python
import logging
|
||
from functools import lru_cache
|
||
from django.core.exceptions import ObjectDoesNotExist
|
||
from gvsdsdk.models import UserRole, Role, Tenant, Company
|
||
from gvsdsdk.model_utils import UUIDObj
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
@lru_cache(maxsize=1024)
|
||
def _TenantName(TenantUuid: str) -> str:
|
||
"""租户 UUID → 租户名(日志展示用,带缓存避免每请求查库)"""
|
||
return Tenant.objects.filter(
|
||
TenantUUID=UUIDObj(TenantUuid).bytes
|
||
).values_list('TenantName', flat=True).first() or ''
|
||
|
||
|
||
@lru_cache(maxsize=1024)
|
||
def _CompanyName(CompanyUuid: str) -> str:
|
||
"""公司 UUID → 公司名(日志展示用,带缓存避免每请求查库)"""
|
||
return Company.objects.filter(
|
||
CompanyUUID=UUIDObj(CompanyUuid).bytes
|
||
).values_list('CompanyName', flat=True).first() or ''
|
||
|
||
|
||
def ResolveRoleContext(request, user=None):
|
||
"""解析当前角色上下文,填充 request.current_role / current_company_uuid / current_tenant_uuid
|
||
|
||
角色上下文唯一实现。两条认证链路共用本函数:
|
||
- JWT 链路:gvsds.auth.CustomJWTAuthentication 在 DRF authenticate 成功后显式调用(传 user)
|
||
- Session/中间件链路:CurrentRoleMiddleware 在认证中间件之后调用(从 request.user 取)
|
||
|
||
从 CurrentRoleUUID Cookie 读取角色 UUID,查询 UserRole + Role,
|
||
设置 request.current_role(Role 实例)/ current_company_uuid / current_tenant_uuid。
|
||
|
||
Args:
|
||
request: HTTP 请求对象(会被原地修改)
|
||
user: 已认证的 User 实例;为 None 时自动从 request.user 获取
|
||
"""
|
||
# 确保默认值(防止重复调用时残留旧值)
|
||
request.current_role = None
|
||
request.current_company_uuid = None
|
||
request.current_tenant_uuid = None
|
||
|
||
if user is None:
|
||
user = getattr(request, 'user', None)
|
||
if not user:
|
||
return
|
||
|
||
# 兼容 is_authenticated(Django 标准)与 IsAuthenticated(gvsds core.User 自有属性)
|
||
if not getattr(user, 'is_authenticated', False) and not getattr(user, 'IsAuthenticated', False):
|
||
return
|
||
|
||
role_uuid_str = request.COOKIES.get('CurrentRoleUUID', '')
|
||
if not role_uuid_str:
|
||
logger.warning('[ResolveRoleContext] 已认证用户 %s 无 CurrentRoleUUID cookie', user)
|
||
return
|
||
|
||
try:
|
||
role_uuid_bytes = UUIDObj(role_uuid_str).bytes
|
||
# UserRole.CompanyUUID / RoleUUID 是 UUIDField(非 ForeignKey),不能用 select_related
|
||
user_role = UserRole.objects.filter(
|
||
UserUUID=user.UserUUID,
|
||
RoleUUID=role_uuid_bytes
|
||
).first()
|
||
if not user_role:
|
||
logger.warning('[ResolveRoleContext] cookie=%s 但 UserRole 未找到 (user=%s)',
|
||
role_uuid_str[:36], user)
|
||
return
|
||
# 单独查询 Role 实例(Role.TenantUUID 提供租户信息)
|
||
role = Role.objects.filter(RoleUUID=role_uuid_bytes).first()
|
||
if role:
|
||
request.current_role = role
|
||
# 从 Role 实例获取租户 UUID(UUIDObj,使用方按需转 bytes)
|
||
request.current_tenant_uuid = role.TenantUUID
|
||
# UserRole.CompanyUUID 是 UUIDField,直接赋值
|
||
if user_role.CompanyUUID:
|
||
request.current_company_uuid = user_role.CompanyUUID
|
||
# 日志只输出可读信息:用户名 / 角色名 / 租户-公司名(避免一串 UUID)
|
||
if logger.isEnabledFor(logging.INFO):
|
||
TenantLabel = _TenantName(UUIDObj(request.current_tenant_uuid).str) if request.current_tenant_uuid else ''
|
||
CompanyLabel = _CompanyName(UUIDObj(request.current_company_uuid).str) if request.current_company_uuid else ''
|
||
logger.info('[ResolveRoleContext] 用户=%s, 角色=%s, company=%s',
|
||
getattr(user, 'UserName', None),
|
||
getattr(role, 'RoleName', None) if role else None,
|
||
f'{TenantLabel}-{CompanyLabel}')
|
||
except (ValueError, KeyError, ObjectDoesNotExist) as e:
|
||
logger.warning('[ResolveRoleContext] cookie=%s 解析失败: %s', role_uuid_str[:36], e)
|
||
except Exception:
|
||
logger.exception('[ResolveRoleContext] 解析当前角色失败')
|
||
|
||
|
||
class CurrentRoleMiddleware:
|
||
"""解析 CurrentRoleUUID Cookie,设置 request.current_role / current_company_uuid / current_tenant_uuid
|
||
|
||
request.current_role : gvsdsdk.models.Role 实例(或 None)
|
||
request.current_company_uuid: 通过 UserRole.CompanyUUID 获取当前角色所属公司 UUID(或 None)
|
||
request.current_tenant_uuid : 通过 Role.TenantUUID 获取当前租户 UUID(或 None)
|
||
|
||
仅对已认证用户处理角色,依赖 AuthenticationMiddleware 已设置 request.user。
|
||
实现委托给 ResolveRoleContext(与 JWT 链路共用唯一实现)。
|
||
"""
|
||
|
||
def __init__(self, get_response):
|
||
self.get_response = get_response
|
||
|
||
def __call__(self, request):
|
||
ResolveRoleContext(request)
|
||
return self.get_response(request)
|