Files
Django/jituan/services/club_rbac.py
2026-07-06 18:23:29 +08:00

210 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""角色/后台客服按俱乐部隔离(权限码全局共用,角色实体按 club 分开)。"""
from django.db.models import Q
from gvsdsdk.models import Role, UserRole
from jituan.constants import CLUB_ID_DEFAULT, DATA_SCOPE_ALL
from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
def resolve_role_club_id(request, explicit=None):
"""创建/筛选角色时使用的俱乐部;集团视图可显式指定 target_club_id。"""
if explicit:
return (explicit or '').strip()
scope = resolve_club_scope(request)
if scope == DATA_SCOPE_ALL:
return ''
return resolve_club_id_from_request(request)
def filter_roles_for_request(request):
"""
子公司视图:仅本俱乐部角色。
集团汇总视图:全部角色(含各俱乐部 + 全局 club_id 为空)。
"""
qs = Role.objects.filter(RoleStatus=1)
scope = resolve_club_scope(request)
if scope == DATA_SCOPE_ALL:
return qs
club_id = resolve_club_id_from_request(request)
return qs.filter(ClubID=club_id)
def filter_kefu_users_by_admin_scope(qs, request):
"""
后台用户列表:按任职俱乐部过滤,而非仅 User.ClubID。
同一账号有多俱乐部任职时,在任一已分配俱乐部视图下均可见。
"""
from jituan.models import AdminAssignment
scope = resolve_club_scope(request)
if scope == DATA_SCOPE_ALL:
return qs
club_id = resolve_club_id_from_request(request)
assigned_uids = list(
AdminAssignment.query.filter(status=1, club_id=club_id).values_list('yonghuid', flat=True)
)
if club_id == CLUB_ID_DEFAULT:
return qs.filter(
Q(UserUID__in=assigned_uids)
| Q(ClubID=club_id)
| Q(ClubID__isnull=True)
| Q(ClubID='')
)
return qs.filter(Q(UserUID__in=assigned_uids) | Q(ClubID=club_id))
def filter_kefu_users_qs(qs, request):
"""后台客服账号列表按 User.ClubID 过滤(角色统计等场景)。"""
scope = resolve_club_scope(request)
if scope == DATA_SCOPE_ALL:
return qs
club_id = resolve_club_id_from_request(request)
if club_id == CLUB_ID_DEFAULT:
return qs.filter(Q(ClubID=club_id) | Q(ClubID__isnull=True) | Q(ClubID=''))
return qs.filter(ClubID=club_id)
def count_role_users_in_scope(role, request, kefu_user_uuids):
"""统计角色下客服数(仅计当前数据范围内的客服)。"""
from users.business_models import User
uuids = list(
UserRole.objects.filter(RoleUUID=role.RoleUUID)
.values_list('UserUUID', flat=True)
)
if not uuids:
return 0
qs = User.objects.filter(UserUUID__in=uuids, KefuProfile__isnull=False)
qs = filter_kefu_users_qs(qs, request)
if kefu_user_uuids is not None:
qs = qs.filter(UserUUID__in=kefu_user_uuids)
return qs.count()
def role_name_exists(role_name, club_id):
return Role.objects.filter(RoleName=role_name, ClubID=club_id or '').exists()
def resolve_role_by_name(role_name, club_id=None):
"""按角色名解析 Role优先匹配用户俱乐部其次集团全局角色club_id 为空)。"""
name = (role_name or '').strip()
if not name:
return None
qs = Role.objects.filter(RoleName=name, RoleStatus=1)
cid = (club_id or '').strip()
if cid:
hit = qs.filter(ClubID=cid).first()
if hit:
return hit
return qs.filter(ClubID='').first() or qs.first()
def role_uuid_bytes(role_uuid):
"""RoleUUID 统一转 bytes兼容 UUIDObj / bytes / str"""
from gvsdsdk.model_utils import UUIDObj
if isinstance(role_uuid, bytes):
return role_uuid
return UUIDObj(role_uuid).bytes
def resolve_roles_for_binding(role_names, club_id=None):
"""
按角色名解析并去重;任一角色在当前俱乐部下找不到则整体失败。
返回 (roles, missing_names)。
"""
resolved = []
missing = []
seen_bytes = set()
for raw in role_names or []:
name = (raw or '').strip()
if not name:
continue
role = resolve_role_by_name(name, club_id)
if not role:
missing.append(name)
continue
key = role_uuid_bytes(role.RoleUUID)
if key in seen_bytes:
continue
seen_bytes.add(key)
resolved.append(role)
return resolved, missing
def filter_role_uuids_for_club_scope(role_uuids, club_id, scope, allowed_club_ids=None):
"""
按当前俱乐部上下文过滤 UserRole 绑定的 RoleUUID。
SINGLE_CLUB本俱乐部角色 + 全局角色ClubID 为空)。
ALL_CLUBS全局角色 + 各已授权俱乐部的角色(并集)。
"""
if not role_uuids:
return []
qs = Role.objects.filter(RoleUUID__in=role_uuids, RoleStatus=1)
if scope == DATA_SCOPE_ALL:
club_ids = [c for c in (allowed_club_ids or []) if c]
if club_ids:
return list(
qs.filter(Q(ClubID='') | Q(ClubID__in=club_ids)).values_list('RoleUUID', flat=True)
)
return list(qs.filter(ClubID='').values_list('RoleUUID', flat=True))
cid = (club_id or CLUB_ID_DEFAULT).strip()
return list(qs.filter(Q(ClubID=cid) | Q(ClubID='')).values_list('RoleUUID', flat=True))
def resolve_permission_codes_for_club(user, club_id, allowed_club_ids=None):
"""指定俱乐部 SINGLE 视图下用户的 gvsdsdk 权限码。"""
from jituan.constants import DATA_SCOPE_SINGLE
from gvsdsdk.models import Permission as SdkPermission, RolePermission as SdkRolePermission, UserRole as SdkUserRole
role_uuids = list(SdkUserRole.objects.filter(
UserUUID=user.UserUUID,
).values_list('RoleUUID', flat=True))
role_uuids = filter_role_uuids_for_club_scope(
role_uuids, club_id, DATA_SCOPE_SINGLE, allowed_club_ids,
)
if not role_uuids:
return []
perm_uuids = SdkRolePermission.objects.filter(
RoleUUID__in=role_uuids,
).values_list('PermUUID', flat=True)
return list(
SdkPermission.objects.filter(
PermUUID__in=perm_uuids, PermStatus=1,
).values_list('PermCode', flat=True).distinct()
)
def user_bound_role_names(user, club_id=None, scope=None, allowed_club_ids=None):
from backend.utils import _legacy_backend_role_permissions
uuids = list(
UserRole.objects.filter(UserUUID=user.UserUUID).values_list('RoleUUID', flat=True)
)
if club_id is not None and scope is not None:
uuids = filter_role_uuids_for_club_scope(uuids, club_id, scope, allowed_club_ids)
names = []
if uuids:
names = list(
Role.objects.filter(RoleUUID__in=uuids, RoleStatus=1).values_list('RoleName', flat=True)
)
if club_id is None:
_, legacy_names = _legacy_backend_role_permissions(getattr(user, 'Phone', ''))
return sorted(set(names + legacy_names))
return sorted(set(names))
def resolve_kefu_admin_user(yonghuid=None, phone=None):
"""定位后台客服 User优先 yonghuid同手机号多账号时必须用 ID"""
from users.business_models import User
uid = (yonghuid or '').strip()
if uid:
user = User.objects.filter(UserUID=uid, KefuProfile__isnull=False).first()
if user:
return user
ph = (phone or '').strip()
if not ph:
return None
return User.objects.filter(Phone=ph, KefuProfile__isnull=False).order_by('-UserCreateTime').first()