fix: 俱乐部角色ORM过滤+商家订单权限002ab/003aa+legacy兜底+perm-debug

This commit is contained in:
XingQue
2026-07-06 19:25:15 +08:00
parent 7bb891187b
commit 2edca09859
6 changed files with 126 additions and 40 deletions

View File

@@ -7,6 +7,11 @@ 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:
@@ -27,7 +32,7 @@ def filter_roles_for_request(request):
if scope == DATA_SCOPE_ALL:
return qs
club_id = resolve_club_id_from_request(request)
return qs.filter(ClubID=club_id)
return qs.filter(Q(ClubID=club_id) | _global_role_q())
def filter_kefu_users_by_admin_scope(qs, request):
@@ -87,15 +92,17 @@ def role_name_exists(role_name, club_id):
def resolve_role_by_name(role_name, club_id=None):
"""按角色名解析 Role优先匹配用户俱乐部,其次集团全局角色club_id 为空)。"""
"""按角色名解析 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:
return qs.filter(ClubID=cid).first()
return qs.filter(ClubID='').first()
hit = qs.filter(ClubID=cid).first()
if hit:
return hit
return qs.filter(_global_role_q()).first()
def role_uuid_bytes(role_uuid):
@@ -133,36 +140,22 @@ def resolve_roles_for_binding(role_names, club_id=None):
def filter_role_uuids_for_club_scope(role_uuids, club_id, scope, allowed_club_ids=None):
"""
按当前俱乐部上下文过滤 UserRole 绑定的 RoleUUID。
SINGLE_CLUB本俱乐部角色Role.ClubID=当前俱乐部),不含其他俱乐部/全局
ALL_CLUBS已授权各俱乐部角色 + 全局角色club_id 为空)
SINGLE_CLUB本俱乐部角色 + 全局角色ClubID 空/NULL
ALL_CLUBS已授权各俱乐部角色 + 全局角色。
"""
if not role_uuids:
return []
wanted = {role_uuid_bytes(u) for u in role_uuids}
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}
matched = []
for role in Role.objects.filter(RoleStatus=1):
key = role_uuid_bytes(role.RoleUUID)
if key not in wanted:
continue
rcid = (getattr(role, 'ClubID', None) or '').strip()
if rcid == '' or rcid in club_ids:
matched.append(role.RoleUUID)
return matched
cid = (club_id or CLUB_ID_DEFAULT).strip()
matched = []
for role in Role.objects.filter(RoleStatus=1):
key = role_uuid_bytes(role.RoleUUID)
if key not in wanted:
continue
rcid = (getattr(role, 'ClubID', None) or '').strip()
# 本俱乐部角色 + 全局角色;不含其他俱乐部角色
if rcid == cid or rcid == '':
matched.append(role.RoleUUID)
return matched
club_ids = [c for c in (allowed_club_ids or []) if c]
q = _global_role_q()
if club_ids:
q |= Q(ClubID__in=club_ids)
qs = qs.filter(q)
else:
cid = (club_id or CLUB_ID_DEFAULT).strip()
qs = qs.filter(Q(ClubID=cid) | _global_role_q())
return list(qs.values_list('RoleUUID', flat=True))
def resolve_permission_codes_for_club(user, club_id, allowed_club_ids=None, scope=None):
@@ -191,8 +184,7 @@ def resolve_permission_codes_for_club(user, club_id, allowed_club_ids=None, scop
def resolve_effective_permission_codes(user, club_id, scope, allowed_club_ids=None):
"""
菜单与各业务接口统一权限码:当前俱乐部 gvsdsdk 角色。
gvsdsdk 完全无绑定时才兜底旧 user_role 表(旧表不区分俱乐部,避免多俱乐部串权)。
菜单与各业务接口统一权限码:当前俱乐部 gvsdsdk 角色 + 过渡期 legacy 兜底
"""
from backend.utils import _legacy_backend_role_permissions
from gvsdsdk.models import UserRole as SdkUserRole
@@ -202,14 +194,54 @@ def resolve_effective_permission_codes(user, club_id, scope, allowed_club_ids=No
)
if codes:
return codes
if SdkUserRole.objects.filter(UserUUID=user.UserUUID).exists():
return []
legacy_codes, _ = _legacy_backend_role_permissions(getattr(user, 'Phone', ''))
if legacy_codes:
if not legacy_codes:
return []
if not SdkUserRole.objects.filter(UserUUID=user.UserUUID).exists():
return list(legacy_codes)
# gvsdsdk 有绑定但当前俱乐部视图下无权限码:单俱乐部账号仍走 legacy未迁移完
allowed = {c for c in (allowed_club_ids or []) if c}
if len(allowed) <= 1:
return list(legacy_codes)
return []
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

View File

@@ -12,7 +12,7 @@ from jituan.services.admin_context import (
is_system_super_admin,
)
from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
from jituan.services.club_rbac import user_bound_role_names
from jituan.services.club_rbac import user_bound_role_names, describe_roles_in_scope
from jituan.services.kefu_menu_definitions import KEFU_MENU_ROW_DEFS
logger = logging.getLogger(__name__)
@@ -145,6 +145,7 @@ def build_menu_access_payload(request, username=None):
allowed = list(get_allowed_club_ids(request.user))
perm_codes = _permission_codes_list(permissions)
role_names = user_bound_role_names(request.user, club_id, scope, allowed)
roles_detail = describe_roles_in_scope(request.user, club_id, scope, allowed)
perm_hint = ''
if scope == DATA_SCOPE_SINGLE and not perm_codes:
@@ -174,6 +175,7 @@ def build_menu_access_payload(request, username=None):
'club_scope': scope,
'effective_club_id': club_id,
'perm_hint': perm_hint,
'roles_in_scope': roles_detail,
'can_switch_club': bool(club_ctx.get('can_switch_club')),
'is_group_admin': bool(club_ctx.get('is_group_admin')),
'menu_ready': True,

View File

@@ -17,7 +17,7 @@ KEFU_MENU_ROW_DEFS = [
{'page_id': 'order.platform', 'name': '平台订单', 'path': '/order/platform', 'parent_id': 'order', 'sort_order': 31,
'perm_codes': ['002ab']},
{'page_id': 'order.merchant', 'name': '商家订单', 'path': '/order/merchant', 'parent_id': 'order', 'sort_order': 32,
'perm_codes': ['002ac']},
'perm_codes': ['002ac', '002ab', '003aa']},
{'page_id': 'cross-platform', 'name': '跨平台管理', 'path': '', 'parent_id': '', 'sort_order': 40},
{'page_id': 'cross-platform.order', 'name': '跨平台订单', 'path': '/cross-order', 'parent_id': 'cross-platform', 'sort_order': 41,
'perm_codes': ['003aa']},