feat: 新增 2 号超级管理员权限 000002

This commit is contained in:
XingQue committed 2026-08-10 07:50:39 +08:00
1 parent 962689e579
commit 99d05d97c6
8 files changed
+469 -58

No files matched your search

+98 -3
View File
@@ -77,6 +77,37 @@ class _AllPermissions(list):
return True
# 2 号超级管理(权限码 000002):接近本店超管,但敏感码必须显式持有
CLUB_SECOND_SUPER_CODE = '000002'
CLUB_SUPER_CODE = '000001'
# 2 号超管默认不拥有的敏感码(须角色显式绑定才放行;角色配置时也不展示给纯 2 号)
SECOND_SUPER_EXPLICIT_ONLY_CODES = frozenset({
'000001', # 旧本店超管:不可碰、不可赋
'005bb', # 提现审核通过/驳回
'bankuai', # 板块配置
'djcfg001', # 资金冻结规则
'djled001', # 资金冻结明细
'djrisk001', # 罚款投诉指标
})
# 角色权限配置里对纯 2 号额外隐藏(含集团相关分类在视图层再滤)
SECOND_SUPER_ROLE_CONFIG_HIDDEN_CODES = frozenset(SECOND_SUPER_EXPLICIT_ONLY_CODES)
class _SecondSuperPermissions(list):
"""
仅持有 000002(无 000001)时使用:多数权限码 in 检查视为 True,
但 SECOND_SUPER_EXPLICIT_ONLY_CODES 必须出现在真实列表里才通过。
这样旧闸门里大量 `xxx in permissions` 对 2 号自动放行,又不动 000001 行为。
"""
def __contains__(self, item):
if item in SECOND_SUPER_EXPLICIT_ONLY_CODES:
return list.__contains__(self, item)
return True
def has_perm_code(permissions, *codes):
"""权限码 in 检查;系统超管 _AllPermissions 视为全部通过。"""
if isinstance(permissions, _AllPermissions):
@@ -86,12 +117,68 @@ def has_perm_code(permissions, *codes):
def has_club_super_perm(permissions):
"""本店超级管理 000001(含系统超管 _AllPermissions)。"""
"""本店超级管理 000001(含系统超管 _AllPermissions)。不含 000002。"""
if isinstance(permissions, _AllPermissions):
return True
# 勿用 `in permissions`:_SecondSuperPermissions 对非敏感码恒为 True
if isinstance(permissions, _SecondSuperPermissions):
return list.__contains__(permissions, '000001')
return '000001' in (permissions or [])
def has_second_super_perm(permissions):
"""是否持有 2 号超管 000002(系统超管视为 True)。"""
if isinstance(permissions, _AllPermissions):
return True
if isinstance(permissions, _SecondSuperPermissions):
return True
return CLUB_SECOND_SUPER_CODE in (permissions or [])
def is_second_super_only(permissions):
"""纯 2 号:有 000002、无 000001、非系统超管展开。"""
if isinstance(permissions, _AllPermissions):
return False
if isinstance(permissions, _SecondSuperPermissions):
return True
perms = permissions or []
return CLUB_SECOND_SUPER_CODE in perms and '000001' not in perms
def has_role_manage_perm(permissions):
"""可维护本店角色/后台用户:000001 或 000002 或系统超管。"""
if isinstance(permissions, _AllPermissions):
return True
if isinstance(permissions, _SecondSuperPermissions):
return True
perms = permissions or []
return '000001' in perms or CLUB_SECOND_SUPER_CODE in perms
def wrap_second_super_permissions(permissions):
"""若仅有 000002,则包装为 _SecondSuperPermissions;其余原样返回。"""
if isinstance(permissions, (_AllPermissions, _SecondSuperPermissions)):
return permissions
perms = list(permissions or [])
if CLUB_SECOND_SUPER_CODE in perms and '000001' not in perms:
return _SecondSuperPermissions(perms)
return permissions
def second_super_display_perm_name(perm_code, perm_name, viewer_permissions):
"""
展示名:查看者有旧超管时,000002 显示「2号超级管理员」;
否则显示「超级管理员」(不标 2 号)。
"""
code = (perm_code or '').strip()
name = (perm_name or '').strip() or code
if code != CLUB_SECOND_SUPER_CODE:
return name
if has_club_super_perm(viewer_permissions):
return '2号超级管理员'
return '超级管理员'
# 本店超管可直接使用的核心业务码(不必再绑集团权限)
CLUB_SUPER_IMPLIED_PERMS = frozenset({
'002ab', # 平台订单
@@ -100,6 +187,13 @@ CLUB_SUPER_IMPLIED_PERMS = frozenset({
'005bb', # 提现审核
})
# 2 号超管隐含核心业务码(不含提现审核)
CLUB_SECOND_SUPER_IMPLIED_PERMS = frozenset({
'002ab',
'002ac',
'003aa',
})
def has_perm_code_or_club_super(permissions, *codes):
"""指定业务码,或本店超管 000001(对核心业务码生效)。"""
@@ -109,7 +203,6 @@ def has_perm_code_or_club_super(permissions, *codes):
return True
return False
def has_merchant_order_permission(permissions):
"""商家/链接订单处理权限:002ac 商家订单、002ab 平台订单、003aa 跨平台;本店超管 000001 亦可。"""
return has_perm_code_or_club_super(permissions, '002ac', '002ab', '003aa')
@@ -274,6 +367,9 @@ def verify_kefu_permission(request, username_from_frontend=None):
if '000001' not in permissions:
permissions.insert(0, '000001')
permissions = _AllPermissions(permissions)
else:
# 纯 000002:多数业务闸门放行,敏感码仍须显式持有(不影响 000001)
permissions = wrap_second_super_permissions(permissions)
if not permissions:
logger.warning(f"用户 {current_user.Phone} 没有任何权限,请检查角色分配")
@@ -283,7 +379,6 @@ def verify_kefu_permission(request, username_from_frontend=None):
def update_dashou_daily_by_action(yonghuid, amount, action, club_id=None):
"""
根据行为类型更新打手每日统计(接单、成交、退款)
+146 -23
View File
@@ -63,6 +63,11 @@ from ..utils import (
XIUGAI_LEIXING_SHANGJIA,
XIUGAI_LEIXING_ZUZHANG,
XIUGAI_LEIXING_LABEL,
has_role_manage_perm,
has_club_super_perm,
is_second_super_only,
second_super_display_perm_name,
SECOND_SUPER_ROLE_CONFIG_HIDDEN_CODES,
)
# models 集中导入
@@ -114,23 +119,79 @@ from ..serializers import PopupPageSerializer
logger = logging.getLogger('houtai')
SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd']
def _serialize_permission_row(perm_row):
def _serialize_permission_row(perm_row, viewer_permissions=None):
"""gvsdsdk Permission → 后台 Vue 约定的小写字段"""
if isinstance(perm_row, dict):
code = perm_row.get('PermCode') or perm_row.get('perm_code') or ''
name = perm_row.get('PermName') or perm_row.get('perm_name') or code
desc = perm_row.get('PermDesc') or perm_row.get('perm_desc') or ''
category = perm_row.get('PermCategory') or perm_row.get('perm_category') or ''
else:
code = perm_row.PermCode or ''
name = perm_row.PermName or code
desc = perm_row.PermDesc or ''
return {'perm_code': code, 'perm_name': name, 'perm_desc': desc}
category = getattr(perm_row, 'PermCategory', '') or ''
if viewer_permissions is not None:
name = second_super_display_perm_name(code, name, viewer_permissions)
return {
'perm_code': code,
'perm_name': name,
'perm_desc': desc,
'perm_category': category,
}
def _perm_hidden_for_second_super(perm_row):
"""纯 2 号在角色配置可选列表中不可见的权限。"""
if isinstance(perm_row, dict):
code = perm_row.get('PermCode') or perm_row.get('perm_code') or ''
category = perm_row.get('PermCategory') or perm_row.get('perm_category') or ''
else:
code = getattr(perm_row, 'PermCode', '') or ''
category = getattr(perm_row, 'PermCategory', '') or ''
if code in SECOND_SUPER_ROLE_CONFIG_HIDDEN_CODES:
return True
if '集团' in (category or ''):
return True
return False
def _role_has_perm_code(role, perm_code):
try:
return RolePermission.objects.filter(
RoleUUID=role.RoleUUID,
PermUUID__in=Permission.objects.filter(
PermCode=perm_code, PermStatus=1,
).values_list('PermUUID', flat=True),
).exists()
except Exception:
return False
def _second_super_forbid_touch_club_super(permissions, perm_code, action_label='操作'):
"""纯 2 号不可碰旧超管码 000001。"""
if not is_second_super_only(permissions):
return None
if (perm_code or '').strip() == '000001':
return Response({'code': 403, 'msg': f'无权{action_label}旧超级管理员权限(000001)'})
if (perm_code or '').strip() in SECOND_SUPER_ROLE_CONFIG_HIDDEN_CODES:
return Response({'code': 403, 'msg': f'无权{action_label}该权限'})
return None
def _second_super_forbid_bind_club_super_role(permissions, role):
"""纯 2 号不可把带 000001 的角色绑给别人。"""
if not is_second_super_only(permissions):
return None
if _role_has_perm_code(role, '000001') or (role.RoleName or '') in ('管理员', '超级管理员'):
return Response({'code': 403, 'msg': '无权分配旧超级管理员角色'})
return None
class GetRolePermissionView(APIView):
"""
获取所有角色、权限以及角色关联的用户数
权限要求:用户必须拥有 000001 权限
权限要求:000001 或 000002(或集团/系统超管)
"""
permission_classes = [IsAuthenticated]
@@ -148,9 +209,17 @@ class GetRolePermissionView(APIView):
if not can_access_admin_user_management(request.user, permissions):
return Response({'code': 403, 'msg': '您无权访问此页面'})
# 3. 获取所有权限
all_perms = Permission.objects.filter(PermStatus=1).values('PermCode', 'PermName', 'PermDesc')
permissions_list = [_serialize_permission_row(p) for p in all_perms]
# 3. 获取所有权限(纯 2 号不展示旧超管/提现/冻结/板块/集团类)
all_perms = list(
Permission.objects.filter(PermStatus=1).values(
'PermCode', 'PermName', 'PermDesc', 'PermCategory',
)
)
if is_second_super_only(permissions):
all_perms = [p for p in all_perms if not _perm_hidden_for_second_super(p)]
permissions_list = [
_serialize_permission_row(p, viewer_permissions=permissions) for p in all_perms
]
from jituan.services.club_rbac import count_role_users_in_scope, filter_roles_for_request
@@ -168,13 +237,16 @@ class GetRolePermissionView(APIView):
_perm_rows_by_uuid = {
p['PermUUID']: p
for p in Permission.objects.filter(PermUUID__in=_perm_uuids)
.values('PermUUID', 'PermCode', 'PermName', 'PermDesc')
.values('PermUUID', 'PermCode', 'PermName', 'PermDesc', 'PermCategory')
} if _perm_uuids else {}
_perms_by_role = {}
for role_uuid, perm_uuid in _rp_pairs:
perm_row = _perm_rows_by_uuid.get(perm_uuid)
if perm_row is not None:
_perms_by_role.setdefault(role_uuid, []).append(_serialize_permission_row(perm_row))
# 角色已绑权限仍展示(避免保存时误删);仅可选列表做隐藏
_perms_by_role.setdefault(role_uuid, []).append(
_serialize_permission_row(perm_row, viewer_permissions=permissions)
)
roles_data = []
for role in roles:
@@ -197,7 +269,6 @@ class GetRolePermissionView(APIView):
}
})
class ModifyRolePermissionView(APIView):
"""
通用接口:支持删除角色、为角色添加权限、移除角色权限
@@ -215,7 +286,7 @@ class ModifyRolePermissionView(APIView):
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
if '000001' not in permissions:
if not has_role_manage_perm(permissions):
return Response({'code': 403, 'msg': '您无权进行此操作'})
# 获取角色(按当前俱乐部解析,避免多俱乐部同名角色冲突)
@@ -243,6 +314,8 @@ class ModifyRolePermissionView(APIView):
# 根据 action 处理
if action == 'delete_role':
if is_second_super_only(permissions) and _role_has_perm_code(role, '000001'):
return Response({'code': 403, 'msg': '无权删除带旧超级管理员权限的角色'})
# 删除角色:同时删除角色权限关联、用户角色关联
with transaction.atomic():
RolePermission.objects.filter(RoleUUID=role.RoleUUID).delete()
@@ -254,6 +327,9 @@ class ModifyRolePermissionView(APIView):
perm_code = request.data.get('perm_code')
if not perm_code:
return Response({'code': 400, 'msg': '缺少权限编码'})
deny = _second_super_forbid_touch_club_super(permissions, perm_code, '添加')
if deny:
return deny
if role.RoleName == '管理员' and perm_code == '000001':
return Response({'code': 403, 'msg': '此权限已存在,不能重复添加'})
try:
@@ -275,6 +351,9 @@ class ModifyRolePermissionView(APIView):
perm_code = request.data.get('perm_code')
if not perm_code:
return Response({'code': 400, 'msg': '缺少权限编码'})
deny = _second_super_forbid_touch_club_super(permissions, perm_code, '移除')
if deny:
return deny
if role.RoleName == '管理员' and perm_code == '000001':
return Response({'code': 403, 'msg': '不能移除超级管理员的此权限'})
try:
@@ -298,6 +377,22 @@ class ModifyRolePermissionView(APIView):
RoleName=role_name, ClubID=role_club_id,
).exclude(RoleUUID=role.RoleUUID).exists():
return Response({'code': 400, 'msg': '该俱乐部下角色名称已存在'})
# 纯 2 号:保存时保留角色上已有的敏感码,避免因前端未展示而误删
preserve_codes = set()
if is_second_super_only(permissions):
existing_codes = set(
Permission.objects.filter(
PermUUID__in=RolePermission.objects.filter(
RoleUUID=role.RoleUUID,
).values_list('PermUUID', flat=True),
PermStatus=1,
).values_list('PermCode', flat=True)
)
preserve_codes = existing_codes & SECOND_SUPER_ROLE_CONFIG_HIDDEN_CODES
perm_codes = [
pc for pc in (perm_codes or [])
if pc not in SECOND_SUPER_ROLE_CONFIG_HIDDEN_CODES
]
with transaction.atomic():
if role.RoleName != '管理员':
if role_name:
@@ -308,6 +403,8 @@ class ModifyRolePermissionView(APIView):
for pc in perm_codes:
if pc == '000001' and role.RoleName != '管理员':
continue
if is_second_super_only(permissions) and pc == '000001':
continue
try:
perm = Permission.objects.get(PermCode=pc, PermStatus=1)
except Permission.DoesNotExist:
@@ -318,6 +415,20 @@ class ModifyRolePermissionView(APIView):
PermUUID=perm.PermUUID,
CreateTime=timezone.now(),
)
for pc in preserve_codes:
try:
perm = Permission.objects.get(PermCode=pc, PermStatus=1)
except Permission.DoesNotExist:
continue
if not RolePermission.objects.filter(
RoleUUID=role.RoleUUID, PermUUID=perm.PermUUID,
).exists():
RolePermission.objects.create(
RolePermUUID=uuid.uuid4().bytes,
RoleUUID=role.RoleUUID,
PermUUID=perm.PermUUID,
CreateTime=timezone.now(),
)
if role.RoleName == '管理员':
try:
super_perm = Permission.objects.get(PermCode='000001', PermStatus=1)
@@ -333,7 +444,6 @@ class ModifyRolePermissionView(APIView):
except Permission.DoesNotExist:
pass
return Response({'code': 0, 'msg': '角色保存成功'})
else:
return Response({'code': 400, 'msg': '无效的 action'})
@@ -357,7 +467,7 @@ class AddRoleView(APIView):
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
if '000001' not in permissions:
if not has_role_manage_perm(permissions):
return Response({'code': 403, 'msg': '您无权进行此操作'})
# 检查角色名称是否已存在(同俱乐部内唯一);单俱乐部视图强制写入当前顶栏俱乐部
@@ -395,8 +505,13 @@ class AddRoleView(APIView):
# 不允许添加 000001 权限(此权限仅供超级管理员)
if pc == '000001':
continue
# 纯 2 号不可写入敏感/集团类权限
if is_second_super_only(permissions) and pc in SECOND_SUPER_ROLE_CONFIG_HIDDEN_CODES:
continue
try:
perm = Permission.objects.get(PermCode=pc)
if is_second_super_only(permissions) and _perm_hidden_for_second_super(perm):
continue
RolePermission.objects.create(
RolePermUUID=uuid.uuid4().bytes,
RoleUUID=role.RoleUUID,
@@ -409,7 +524,6 @@ class AddRoleView(APIView):
return Response({'code': 0, 'msg': '角色添加成功', 'data': {'role_code': role_code}})
class GetAdminUserListView(APIView):
permission_classes = [IsAuthenticated]
@@ -557,10 +671,10 @@ class GetAdminUserListView(APIView):
request.user, permissions, current_club_id=curr_club,
))
if scope != DATA_SCOPE_ALL:
# 顶栏在具体俱乐部时,可管理集合收敛到当前店(含超级管理 000001)
# 顶栏在具体俱乐部时,可管理集合收敛到当前店(含超级管理 000001/000002)
manageable = [c for c in manageable if c == curr_club]
# 兜底:本店超级管理一定能管本店用户(不依赖集团任职)
if not manageable and curr_club and permissions and '000001' in permissions:
if not manageable and curr_club and permissions and has_role_manage_perm(permissions):
manageable = [curr_club]
return Response({
'code': 0,
@@ -571,14 +685,13 @@ class GetAdminUserListView(APIView):
'pageSize': page_size,
'can_manage_club_assignments': can_manage_admin_assignments(request.user, permissions),
'can_manage_club_users': bool(manageable) or (
bool(permissions) and '000001' in permissions and scope != DATA_SCOPE_ALL
bool(permissions) and has_role_manage_perm(permissions) and scope != DATA_SCOPE_ALL
),
'manageable_club_ids': manageable,
'current_club_id': curr_club,
'scope': scope,
}
})
except Exception as e:
logger.error(f"GetAdminUserListView 异常: {e}\n{traceback.format_exc()}")
return Response({'code': 500, 'msg': f'服务器内部错误: {str(e)}'})
@@ -734,10 +847,10 @@ class ModifyAdminUserView(APIView):
if not can_manage_admin_assignments(request.user, permissions):
return Response({'code': 403, 'msg': '无权分配俱乐部任职(需集团权限)'})
elif action in role_manage_actions:
# 本店超级管理 000001 可给本俱乐部用户增删功能角色
# 本店超级管理 000001/000002 可给本俱乐部用户增删功能角色
if not can_access_admin_user_management(request.user, permissions):
return Response({'code': 403, 'msg': '您无权管理角色分配'})
elif not can_manage_admin_assignments(request.user, permissions) and '000001' not in permissions:
elif not can_manage_admin_assignments(request.user, permissions) and not has_role_manage_perm(permissions):
return Response({'code': 403, 'msg': '您无权进行此操作'})
target_yonghuid = request.data.get('yonghuid', '').strip()
@@ -800,7 +913,7 @@ class ModifyAdminUserView(APIView):
return Response({'code': 400, 'msg': '请先切换到具体俱乐部再分配角色'})
if not (
can_manage_roles_for_club(request.user, permissions, bind_club_id)
or ('000001' in permissions)
or has_role_manage_perm(permissions)
):
return Response({'code': 403, 'msg': '您无权管理该俱乐部的角色分配'})
added, missing = [], []
@@ -813,6 +926,9 @@ class ModifyAdminUserView(APIView):
if role_club and role_club != bind_club_id:
missing.append(rc)
continue
deny_bind = _second_super_forbid_bind_club_super_role(permissions, role)
if deny_bind:
return deny_bind
_, created = UserRole.objects.get_or_create(
UserUUID=target_user.UserUUID,
RoleUUID=role.RoleUUID,
@@ -849,12 +965,15 @@ class ModifyAdminUserView(APIView):
return Response({'code': 400, 'msg': '请先切换到具体俱乐部再操作角色'})
if not (
can_manage_roles_for_club(request.user, permissions, bind_club_id)
or ('000001' in permissions)
or has_role_manage_perm(permissions)
):
return Response({'code': 403, 'msg': '您无权管理该俱乐部的角色分配'})
role = resolve_role_by_name(role_code, bind_club_id)
if not role:
return Response({'code': 404, 'msg': '角色不存在或与所选俱乐部不匹配'})
deny_bind = _second_super_forbid_bind_club_super_role(permissions, role)
if deny_bind:
return deny_bind
UserRole.objects.filter(UserUUID=target_user.UserUUID, RoleUUID=role.RoleUUID).delete()
return Response({'code': 0, 'msg': '角色移除成功'})
@@ -924,11 +1043,11 @@ class AddAdminUserView(APIView):
target_club_id = resolve_club_id_from_request(request)
if resolve_club_scope(request) == DATA_SCOPE_ALL or not target_club_id:
return Response({'code': 400, 'msg': '请先切换到具体俱乐部再添加用户'})
# 俱乐部超级管理 000001 即可添加本店后台用户,无需集团权限
# 俱乐部超级管理 000001/000002 即可添加本店后台用户,无需集团权限
if not (
can_manage_admin_assignments(request.user, permissions)
or can_manage_roles_for_club(request.user, permissions, target_club_id)
or ('000001' in permissions)
or has_role_manage_perm(permissions)
):
return Response({'code': 403, 'msg': '您无权在该俱乐部添加用户'})
@@ -974,6 +1093,10 @@ class AddAdminUserView(APIView):
})
if not roles_to_bind:
return Response({'code': 400, 'msg': '请至少选择一个有效角色'})
for role in roles_to_bind:
deny_bind = _second_super_forbid_bind_club_super_role(permissions, role)
if deny_bind:
return deny_bind
with transaction.atomic():
# 事务内二次校验,防止并发重复创建
@@ -80,6 +80,13 @@ EXTRA_PERMISSIONS = [
'修改平台订单/本店商品的抢单要求类型(会员/押金门槛)。须显式绑定;本店超管 000001 不会自动拥有。',
'订单管理',
),
(
'000002',
'超级管理员',
'2号超级管理员:接近本店超管;不可碰000001/提现审核/资金冻结/成交自动结算/板块/集团配置。',
'系统',
),
# ---------- 店铺管理(999 系列:列表/详情/复制/提现等) ----------
('999a', '店铺状态管理', '修改店铺封禁/正常状态', '店铺管理'),
('999b', '店铺余额与创建', '修改可提现余额、添加新店铺', '店铺管理'),
@@ -0,0 +1,137 @@
"""写入 2 号超级管理员权限码 000002(可重复执行)。
权限码:
000002 超级管理员(展示名:有旧超管 000001 的查看者看到「2号超级管理员」)
说明:
- 不修改 000001 行为
- 默认不自动挂到任何角色;挂角色请显式传 --club-id / --role-name
用法:
python manage.py seed_second_super_perm
python manage.py seed_second_super_perm --club-id eh --role-name 运营管理
python manage.py seed_second_super_perm --dry-run
"""
import uuid
from django.core.management.base import BaseCommand
from django.db import transaction
from django.utils import timezone
from gvsdsdk.models import Permission, Role, RolePermission
from jituan.models import Club
PERM_CODE = '000002'
PERM_NAME = '超级管理员'
PERM_DESC = (
'2号超级管理员:可维护本店角色与后台用户,业务能力接近本店超管;'
'不可碰旧超管000001、不可提现审核、不可资金冻结/成交自动结算/板块配置/集团配置。'
)
PERM_CATEGORY = '系统'
class Command(BaseCommand):
help = '写入 2 号超级管理员权限码 000002'
def add_arguments(self, parser):
parser.add_argument('--club-id', default='', help='仅处理该俱乐部(配合 --role-name)')
parser.add_argument('--all-clubs', action='store_true')
parser.add_argument('--role-name', default='', help='角色名;空则只确保权限码')
parser.add_argument('--dry-run', action='store_true')
def handle(self, *args, **options):
dry_run = options['dry_run']
club_id = (options.get('club_id') or '').strip()
all_clubs = options.get('all_clubs')
role_name = (options.get('role_name') or '').strip()
sample = Permission.objects.filter(PermStatus=1).first()
tenant_uuid = sample.TenantUUID if sample else uuid.UUID(
'00000000-0000-0000-0000-000000000001'
).bytes
perm = Permission.objects.filter(PermCode=PERM_CODE).first()
if perm:
self.stdout.write(f' 权限码已存在: {PERM_CODE}')
if not dry_run:
changed = False
if perm.PermName != PERM_NAME:
perm.PermName = PERM_NAME
changed = True
if (perm.PermDesc or '') != PERM_DESC:
perm.PermDesc = PERM_DESC
changed = True
if (perm.PermCategory or '') != PERM_CATEGORY:
perm.PermCategory = PERM_CATEGORY
changed = True
if perm.PermStatus != 1:
perm.PermStatus = 1
changed = True
if changed:
perm.UpdateTime = timezone.now()
perm.save(update_fields=[
'PermName', 'PermDesc', 'PermCategory', 'PermStatus', 'UpdateTime',
])
self.stdout.write(self.style.SUCCESS(' 已更新权限元数据'))
else:
if dry_run:
self.stdout.write(f'would create perm {PERM_CODE}')
else:
perm = Permission.objects.create(
PermUUID=uuid.uuid4().bytes,
TenantUUID=tenant_uuid,
PermCode=PERM_CODE,
PermName=PERM_NAME,
PermDesc=PERM_DESC,
PermCategory=PERM_CATEGORY,
PermStatus=1,
CreateTime=timezone.now(),
UpdateTime=timezone.now(),
)
self.stdout.write(self.style.SUCCESS(f' 已创建权限码: {PERM_CODE}'))
if not role_name:
self.stdout.write('未指定 --role-name,跳过挂角色')
self.stdout.write(self.style.SUCCESS(
'完成。请在「角色与权限」里给需要的角色勾选 000002(超级管理员)。'
))
return
if not perm:
self.stdout.write(self.style.ERROR('权限码不存在,无法挂角色'))
return
clubs = []
if all_clubs:
clubs = list(Club.query.filter(status=1).values_list('club_id', flat=True))
elif club_id:
clubs = [club_id]
else:
self.stdout.write(self.style.WARNING('请指定 --club-id 或 --all-clubs'))
return
with transaction.atomic():
for cid in clubs:
roles = Role.objects.filter(RoleName=role_name, ClubID=cid, RoleStatus=1)
if not roles.exists():
roles = Role.objects.filter(RoleName=role_name, RoleStatus=1)
roles = [r for r in roles if (getattr(r, 'ClubID', None) or '') == cid]
for role in roles:
exists = RolePermission.objects.filter(
RoleUUID=role.RoleUUID, PermUUID=perm.PermUUID,
).exists()
if exists:
self.stdout.write(f' 已挂: {cid} {role_name} <- {PERM_CODE}')
continue
if dry_run:
self.stdout.write(f'would link {cid} {role_name} <- {PERM_CODE}')
else:
RolePermission.objects.create(
RoleUUID=role.RoleUUID,
PermUUID=perm.PermUUID,
CreateTime=timezone.now(),
)
self.stdout.write(self.style.SUCCESS(
f' 已挂: {cid} {role_name} <- {PERM_CODE}'
))
+11 -7
View File
@@ -215,25 +215,28 @@ def get_switchable_club_ids(user):
def can_manage_roles_for_club(user, permissions, club_id):
"""
是否可为指定俱乐部分配/移除功能角色、添加本店后台用户。
集团高层:全部俱乐部;否则须在该俱乐部拥有 000001 超级管理(不必有集团权限)。
集团高层:全部俱乐部;否则须在该俱乐部拥有 000001/000002(不必有集团权限)。
"""
if can_manage_admin_assignments(user, permissions):
return True
cid = (club_id or '').strip()
if not cid:
return False
from backend.utils import has_role_manage_perm
from jituan.constants import DATA_SCOPE_SINGLE
from jituan.services.club_rbac import resolve_effective_permission_codes
# 与菜单/verify_kefu 同一套:含 legacy,避免「页面能进但添加按钮出不来」
codes = resolve_effective_permission_codes(user, cid, DATA_SCOPE_SINGLE)
return '000001' in (codes or [])
return has_role_manage_perm(codes)
def get_manageable_club_ids(user, permissions=None, current_club_id=None):
"""
当前操作者可管理角色/后台用户的俱乐部集合。
current_club_id:若请求上下文已带 000001,务必把当前顶栏俱乐部算进去。
current_club_id:若请求上下文已带 000001/000002,务必把当前顶栏俱乐部算进去。
"""
from backend.utils import has_role_manage_perm
if can_manage_admin_assignments(user, permissions):
return {c.club_id for c in Club.query.filter(status=1)}
ids = {
@@ -241,7 +244,7 @@ def get_manageable_club_ids(user, permissions=None, current_club_id=None):
if can_manage_roles_for_club(user, permissions, c.club_id)
}
cid = (current_club_id or '').strip()
if cid and permissions and '000001' in permissions:
if cid and permissions and has_role_manage_perm(permissions):
ids.add(cid)
return ids
@@ -249,17 +252,18 @@ def get_manageable_club_ids(user, permissions=None, current_club_id=None):
def can_access_admin_user_management(user, permissions=None):
"""
是否可访问「后台用户」页(列表、按俱乐部分配功能角色)。
系统超管 / 000001 / 集团高管 / 任一俱乐部超管均可。
系统超管 / 000001 / 000002 / 集团高管 / 任一俱乐部超管均可。
"""
from backend.utils import has_role_manage_perm
if is_system_super_admin(user):
return True
if permissions and '000001' in permissions:
if permissions and has_role_manage_perm(permissions):
return True
if can_manage_admin_assignments(user, permissions):
return True
return bool(get_manageable_club_ids(user, permissions))
def _pack(scope, club_id, is_group_admin, assignments, clubs, can_switch_club,
role_code, role_name):
allowed_club_ids = sorted({
+38 -9
View File
@@ -2,7 +2,15 @@
import logging
from types import SimpleNamespace
from backend.utils import _AllPermissions, verify_kefu_permission, CLUB_SUPER_IMPLIED_PERMS
from backend.utils import (
_AllPermissions,
verify_kefu_permission,
CLUB_SUPER_IMPLIED_PERMS,
CLUB_SECOND_SUPER_IMPLIED_PERMS,
has_club_super_perm,
has_role_manage_perm,
is_second_super_only,
)
from jituan.models import KefuMenuPage
from jituan.services.admin_context import (
build_admin_club_context,
@@ -17,16 +25,26 @@ from jituan.services.kefu_menu_definitions import KEFU_MENU_ROW_DEFS
logger = logging.getLogger(__name__)
# 纯 2 号超管不可见的菜单(即便隐含订单权限也不展示成交配置页)
_SECOND_SUPER_HIDDEN_PAGE_IDS = frozenset({
'order.deal-config',
'finance.bankuai',
'finance.fund-freeze-config',
'finance.fund-freeze-ledger',
'finance.fund-risk-stats',
'withdraw.audit',
'withdraw.detail',
'withdraw.data',
})
def _is_super_permissions(permissions):
return isinstance(permissions, _AllPermissions)
def _has_super_perm(permissions):
"""俱乐部超级管理权限 000001(角色/后台用户等),不含集团级配置。"""
if _is_super_permissions(permissions):
return True
return '000001' in permissions
"""俱乐部超级管理权限 000001(角色/后台用户等),不含集团级配置、不含纯 000002。"""
return has_club_super_perm(permissions)
def _page_visible(page, permissions, user):
@@ -35,13 +53,17 @@ def _page_visible(page, permissions, user):
if page.require_group_manage:
return can_manage_admin_assignments(user)
# 后台用户:本店超级管理 000001 / 集团 / 系统超管均可。
# 纯 2 号:敏感页一律隐藏
if is_second_super_only(permissions) and page_id in _SECOND_SUPER_HIDDEN_PAGE_IDS:
return False
# 后台用户:本店超级管理 000001/000002 / 集团 / 系统超管均可。
# 注意:历史上 DB 模型缺 require_admin_user_manage 字段,导致该页永远不可见,必须按 page_id 兜底。
if page_id == 'admin.user' or getattr(page, 'require_admin_user_manage', False):
return can_access_admin_user_management(user, permissions)
if page.require_super:
return _has_super_perm(permissions) or is_system_super_admin(user)
return has_role_manage_perm(permissions) or is_system_super_admin(user)
# 仅系统级超管可见全部菜单;俱乐部 000001 不展开全部业务菜单
if is_system_super_admin(user):
@@ -55,9 +77,13 @@ def _page_visible(page, permissions, user):
# 本店超管:解锁订单/提现审核等核心页(与加余额闸门一致,不要求集团权限)
if _has_super_perm(permissions) and any(code in CLUB_SUPER_IMPLIED_PERMS for code in codes):
return True
# 2 号超管:解锁订单等核心页,但不含提现审核
if is_second_super_only(permissions) and any(
code in CLUB_SECOND_SUPER_IMPLIED_PERMS for code in codes
):
return True
return False
def _permission_codes_list(permissions):
if isinstance(permissions, _AllPermissions):
# 系统超管前端也要拿到全量码(含新 seed 的 002ad),否则按钮被 v-if 藏掉
@@ -191,6 +217,8 @@ def build_menu_access_payload(request, username=None):
path_by_id = {p.page_id: p.path for p in pages if p.path}
visible_paths = [path_by_id[pid] for pid in visible_ids if pid in path_by_id]
is_super = _has_super_perm(permissions) or is_system_super_admin(request.user)
# 仅「纯 2 号」打标;同时拥有旧超管时走 is_super,展示层再标「2号」权限名
is_second_super = (not is_super) and is_second_super_only(permissions)
return {
'visible_page_ids': sorted(set(visible_ids)),
'visible_paths': visible_paths,
@@ -205,6 +233,7 @@ def build_menu_access_payload(request, username=None):
'can_switch_club': bool(club_ctx.get('can_switch_club')),
'is_group_admin': bool(club_ctx.get('is_group_admin')),
'is_super': bool(is_super),
'is_second_super': bool(is_second_super),
'menu_ready': True,
'menu_source': menu_source,
}, None
}, None
+4 -4
View File
@@ -4,7 +4,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from backend.utils import verify_kefu_permission
from backend.utils import verify_kefu_permission, has_club_super_perm, has_perm_code
from jituan.services import fund_freeze_admin as admin_svc
PERM_CFG = 'djcfg001'
@@ -19,11 +19,11 @@ def _auth(request, perm_code: str):
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return None, None, permissions
codes = set(permissions or [])
user = getattr(request, 'user', None)
if getattr(user, 'IsSuperuser', False) or '000001' in codes:
# 仅旧本店超管 000001 / 系统超管;纯 000002 不放行
if getattr(user, 'IsSuperuser', False) or has_club_super_perm(permissions):
return kefu, permissions, None
if perm_code not in codes:
if not has_perm_code(permissions, perm_code):
return None, None, Response({'code': 403, 'msg': f'无权限(需要 {perm_code})'})
return kefu, permissions, None
+28 -12
View File
@@ -4,7 +4,13 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from backend.utils import verify_kefu_permission, has_merchant_order_permission
from backend.utils import (
verify_kefu_permission,
has_merchant_order_permission,
has_club_super_perm,
has_perm_code,
is_second_super_only,
)
from jituan.services.club_context import resolve_club_id_from_request
from jituan.services import order_deal as deal_cfg
from jituan.services import deal_stats as stats_svc
@@ -13,29 +19,39 @@ from jituan.services import deal_stats as stats_svc
def _auth_order(request):
username = (request.data.get('username') or request.data.get('phone') or '').strip()
if not username:
return None, Response({'code': 400, 'msg': '缺少username'})
return None, None, Response({'code': 400, 'msg': '缺少username'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return None, permissions
codes = set(permissions or [])
return None, None, permissions
user = getattr(request, 'user', None)
if getattr(user, 'IsSuperuser', False) or '000001' in codes:
return kefu, None
if has_merchant_order_permission(permissions) or '002ab' in codes:
return kefu, None
return None, Response({'code': 403, 'msg': '无订单管理权限'})
if getattr(user, 'IsSuperuser', False) or has_club_super_perm(permissions):
return kefu, permissions, None
if has_merchant_order_permission(permissions) or has_perm_code(permissions, '002ab'):
return kefu, permissions, None
return None, None, Response({'code': 403, 'msg': '无订单管理权限'})
def _auth_deal_config(request):
"""成交与自动结算配置页:纯 000002 不可;其余同订单权限。"""
kefu, permissions, err = _auth_order(request)
if err:
return None, err
if is_second_super_only(permissions):
return None, Response({'code': 403, 'msg': '无权管理成交与自动结算配置'})
return kefu, None
class OrderDealConfigView(APIView):
"""
POST /jituan/houtai/order-deal-config
action: get | save | whitelist_list | whitelist_save | whitelist_delete
纯 000002 不可;旧超管 000001 / 系统超管 / 持订单权限者可。
"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
kefu, err = _auth_order(request)
kefu, err = _auth_deal_config(request)
if err:
return err
club_id = (resolve_club_id_from_request(request) or '').strip()
@@ -92,7 +108,7 @@ class MerchantDealStatView(APIView):
parser_classes = [JSONParser]
def post(self, request):
kefu, err = _auth_order(request)
kefu, permissions, err = _auth_order(request)
if err:
return err
club_id = (resolve_club_id_from_request(request) or '').strip()
@@ -123,7 +139,7 @@ class DashouDealStatView(APIView):
parser_classes = [JSONParser]
def post(self, request):
kefu, err = _auth_order(request)
kefu, permissions, err = _auth_order(request)
if err:
return err
club_id = (resolve_club_id_from_request(request) or '').strip()