Files
Django/jituan/services/club_rbac.py

271 lines
9.6 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 _global_role_q():
"""集团全局角色club_id 为空或 NULL。"""
return Q(ClubID='') | Q(ClubID__isnull=True)
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(Q(ClubID=club_id) | _global_role_q())
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(_global_role_q()).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 空/NULL
ALL_CLUBS已授权各俱乐部角色 + 全局角色。
使用 bytes 比对(兼容 UUIDObj避免 ORM __in 在 BINARY(16) 上匹配失败。
"""
if not role_uuids:
return []
wanted = {role_uuid_bytes(u) for u in role_uuids}
matched = []
for ruuid, rcid_raw in Role.objects.filter(RoleStatus=1).values_list('RoleUUID', 'ClubID'):
if role_uuid_bytes(ruuid) not in wanted:
continue
rcid = (rcid_raw or '').strip()
if scope == DATA_SCOPE_ALL:
club_ids = {c for c in (allowed_club_ids or []) if c}
if rcid == '' or rcid in club_ids:
matched.append(ruuid)
else:
cid = (club_id or CLUB_ID_DEFAULT).strip()
if rcid == cid or rcid == '':
matched.append(ruuid)
return matched
def resolve_permission_codes_for_club(user, club_id, allowed_club_ids=None, scope=None):
"""当前俱乐部上下文下的 gvsdsdk 权限码(与 verify_kefu_permission / 菜单共用)。"""
from jituan.constants import DATA_SCOPE_SINGLE
from gvsdsdk.models import Permission as SdkPermission, RolePermission as SdkRolePermission, UserRole as SdkUserRole
eff_scope = scope or DATA_SCOPE_SINGLE
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, eff_scope, 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 resolve_effective_permission_codes(user, club_id, scope, allowed_club_ids=None):
"""
菜单与各业务接口统一权限码:当前俱乐部 gvsdsdk 角色 + legacy 合并。
"""
from backend.utils import _legacy_backend_role_permissions
codes = list(resolve_permission_codes_for_club(
user, club_id, allowed_club_ids, scope=scope,
))
legacy_codes, _ = _legacy_backend_role_permissions(getattr(user, 'Phone', ''))
if legacy_codes:
codes = list(dict.fromkeys(codes + list(legacy_codes)))
return codes
def describe_roles_in_scope(user, club_id, scope, allowed_club_ids=None):
"""诊断:当前俱乐部上下文中生效的角色与权限码。"""
from gvsdsdk.models import Permission as SdkPermission, RolePermission as SdkRolePermission, UserRole as SdkUserRole
all_bound = list(SdkUserRole.objects.filter(
UserUUID=user.UserUUID,
).values_list('RoleUUID', flat=True))
in_scope = filter_role_uuids_for_club_scope(
all_bound, club_id, scope, allowed_club_ids,
)
rows = []
for role in Role.objects.filter(RoleUUID__in=in_scope, RoleStatus=1):
rcid = (getattr(role, 'ClubID', None) or '').strip() or '(全局)'
perm_uuids = SdkRolePermission.objects.filter(
RoleUUID=role.RoleUUID,
).values_list('PermUUID', flat=True)
perm_codes = list(
SdkPermission.objects.filter(
PermUUID__in=perm_uuids, PermStatus=1,
).values_list('PermCode', flat=True)
)
rows.append({
'role_name': role.RoleName,
'club_id': rcid,
'perm_codes': perm_codes,
})
return {
'bound_role_count': len(all_bound),
'in_scope_role_count': len(in_scope),
'roles': rows,
}
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()