Files
Django/backend/views/roles.py

959 lines
43 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.
import hmac
import threading
import traceback
import uuid
import hashlib
import xmltodict
import time
import logging
import requests
import os
import secrets
import random
import string
from calendar import monthrange
from decimal import Decimal
from datetime import date, datetime, timedelta
from django.utils import timezone
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator
from django.db import models, transaction, IntegrityError
from django.db.models import Q, Count, Sum, F, Exists, OuterRef
from gvsdsdk.fluent import db, func, FQ
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
from django.contrib.auth.hashers import make_password
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
# 工具类导入
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
from backend.utils import (
update_dashou_daily_by_action,
update_shangjia_daily
)
from jituan.services.club_context import filter_club_char_field, filter_queryset_by_club, filter_user_related_by_club, filter_user_qs_by_club, orders_for_request
from jituan.services.catalog_scope import block_non_group_catalog_scope
from jituan.services.club_penalty import (
filter_penalty_qs, resolve_penalty_club_id, filter_gsfenhong_qs,
forbid_penalty_out_of_scope, resolve_penalty_record_club_id,
)
from jituan.services.club_user_access import forbid_if_user_out_of_scope, list_response_meta
from shop.utils import update_dianpu_daily_stat
from products.utils import update_shangpin_daily_stat
from orders.notice_tasks import dingdan_guangbo
from ..utils import (
verify_kefu_permission,
PERMISSION_TO_SHENFEN,
SHENFEN_PROFILE_MAP,
has_fadan_view_permission,
has_merchant_order_permission,
check_fadan_permission,
pick_order_id,
pick_penalty_create_params,
write_xiugai_log,
XIUGAI_LEIXING_DASHOU,
XIUGAI_LEIXING_GUANSHI,
XIUGAI_LEIXING_SHANGJIA,
XIUGAI_LEIXING_ZUZHANG,
XIUGAI_LEIXING_LABEL,
)
# models 集中导入
## gvsdsdk RBAC 模型(使用 PascalCase 字段名,匹配实际数据库表结构)
from gvsdsdk.models import Role, Permission, RolePermission, UserRole
## backend
from backend.models import WithdrawalDailyStats
## users
from users.models import (
UserGuanshi, UserBoss, UserZuzhang,
UserKefu, UserDashou, Xiugaijilu, UserShangjia, UserShenheguan
)
from users.business_models import User
## orders
from orders.models import (
CommissionRate, PlayerDeliveryImage, PenaltyRecord,
OrderPlayerHistory, Order, RefundRecord,
MerchantOrderExt, Penalty, PenaltyAppealImage, PenaltyBonus
)
## shop
from shop.models import Dianpu, DianpuMorenPeizhi, YonghuPingzheng, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi
## products
from products.models import (
Shangpin, ShangpinLeixing, ShangpinZhuanqu, Huiyuan,
DuociFenhong, Czjilu, Huiyuangoumai, Gsfenhong
)
## config
from config.models import (
WithdrawConfig, ShangjiaLianjie, AccountPermission,
TixianQuotaDefault,
DailyIncomeStat, DailyPayoutStat, PopupPage, PopupConfig, PopupImage
)
## rank
from rank.models import (
KaoheguanBankuai, Bankuai, Chenghao, KaoheCishuFeiyong,
YonghuChenghao, ShenheJilu, KaoheJiluFeiyong, KaoheJujueJilu
)
# 序列化器
from ..serializers import PopupPageSerializer
# 全局常量、日志对象
logger = logging.getLogger('houtai')
SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd']
def _serialize_permission_row(perm_row):
"""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 ''
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}
class GetRolePermissionView(APIView):
"""
获取所有角色、权限以及角色关联的用户数
权限要求:用户必须拥有 000001 权限
"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('username', '').strip()
if not username:
return Response({'code': 400, 'msg': '缺少username'})
# 1. 权限校验(验证客服身份,并获取权限列表)
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions # 直接返回错误响应
from jituan.services.admin_context import can_access_admin_user_management
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]
from jituan.services.club_rbac import count_role_users_in_scope, filter_roles_for_request
kefu_user_uuids = list(
User.objects.filter(KefuProfile__isnull=False).values_list('UserUUID', flat=True)
)
roles = list(filter_roles_for_request(request))
# 批量预取 RolePermission + Permission避免循环内 N+1
_role_uuids = [r.RoleUUID for r in roles]
_rp_pairs = list(
RolePermission.objects.filter(RoleUUID__in=_role_uuids)
.values_list('RoleUUID', 'PermUUID')
) if _role_uuids else []
_perm_uuids = list({p for _, p in _rp_pairs})
_perm_rows_by_uuid = {
p['PermUUID']: p
for p in Permission.objects.filter(PermUUID__in=_perm_uuids)
.values('PermUUID', 'PermCode', 'PermName', 'PermDesc')
} 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))
roles_data = []
for role in roles:
user_count = count_role_users_in_scope(role, request, kefu_user_uuids)
perms_of_role = _perms_by_role.get(role.RoleUUID, [])
roles_data.append({
'role_code': role.RoleName,
'role_name': role.RoleName,
'description': role.RoleDesc or '',
'club_id': getattr(role, 'ClubID', '') or '',
'user_count': user_count,
'permissions': perms_of_role
})
return Response({
'code': 0,
'data': {
'roles': roles_data,
'permissions': permissions_list
}
})
class ModifyRolePermissionView(APIView):
"""
通用接口:支持删除角色、为角色添加权限、移除角色权限
操作类型通过 action 字段区分
"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('username', '').strip()
action = request.data.get('action') # delete_role, add_permission, remove_permission
if not username or not action:
return Response({'code': 400, 'msg': '缺少必要参数'})
# 权限校验
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
if '000001' not in permissions:
return Response({'code': 403, 'msg': '您无权进行此操作'})
# 获取角色(按当前俱乐部解析,避免多俱乐部同名角色冲突)
role_code = request.data.get('role_code')
if not role_code:
return Response({'code': 400, 'msg': '缺少角色编码'})
from jituan.services.club_rbac import resolve_role_by_name, resolve_role_club_id
explicit_club = (request.data.get('target_club_id') or request.data.get('club_id') or '').strip() or None
bind_club_id = resolve_role_club_id(request, explicit_club)
role = resolve_role_by_name(role_code, bind_club_id)
if not role:
return Response({'code': 404, 'msg': '角色不存在或与当前俱乐部不匹配'})
# 超级管理员角色的特殊限制
if role.RoleName == '管理员':
if action in ['delete_role']:
return Response({'code': 403, 'msg': '超级管理员角色不能删除'})
# 根据 action 处理
if action == 'delete_role':
# 删除角色:同时删除角色权限关联、用户角色关联
with transaction.atomic():
RolePermission.objects.filter(RoleUUID=role.RoleUUID).delete()
UserRole.objects.filter(RoleUUID=role.RoleUUID).delete()
role.delete()
return Response({'code': 0, 'msg': '角色删除成功'})
elif action == 'add_permission':
perm_code = request.data.get('perm_code')
if not perm_code:
return Response({'code': 400, 'msg': '缺少权限编码'})
if role.RoleName == '管理员' and perm_code == '000001':
return Response({'code': 403, 'msg': '此权限已存在,不能重复添加'})
try:
perm = Permission.objects.get(PermCode=perm_code)
except Permission.DoesNotExist:
return Response({'code': 404, 'msg': '权限不存在'})
# 检查是否已存在
if RolePermission.objects.filter(RoleUUID=role.RoleUUID, PermUUID=perm.PermUUID).exists():
return Response({'code': 400, 'msg': '该角色已拥有此权限'})
RolePermission.objects.create(
RolePermUUID=uuid.uuid4().bytes,
RoleUUID=role.RoleUUID,
PermUUID=perm.PermUUID,
CreateTime=timezone.now(),
)
return Response({'code': 0, 'msg': '权限添加成功'})
elif action == 'remove_permission':
perm_code = request.data.get('perm_code')
if not perm_code:
return Response({'code': 400, 'msg': '缺少权限编码'})
if role.RoleName == '管理员' and perm_code == '000001':
return Response({'code': 403, 'msg': '不能移除超级管理员的此权限'})
try:
perm = Permission.objects.get(PermCode=perm_code)
except Permission.DoesNotExist:
return Response({'code': 404, 'msg': '权限不存在'})
deleted, _ = RolePermission.objects.filter(RoleUUID=role.RoleUUID, PermUUID=perm.PermUUID).delete()
if deleted == 0:
return Response({'code': 400, 'msg': '该角色未拥有此权限'})
return Response({'code': 0, 'msg': '权限移除成功'})
elif action == 'update_role':
role_name = (request.data.get('role_name') or '').strip()
description = (request.data.get('description') or '').strip()
perm_codes = request.data.get('perm_codes') or []
if role.RoleName == '管理员':
role_name = role.RoleName
elif role_name and role_name != role.RoleName:
role_club_id = getattr(role, 'ClubID', '') or ''
if Role.objects.filter(
RoleName=role_name, ClubID=role_club_id,
).exclude(RoleUUID=role.RoleUUID).exists():
return Response({'code': 400, 'msg': '该俱乐部下角色名称已存在'})
with transaction.atomic():
if role.RoleName != '管理员':
if role_name:
role.RoleName = role_name
role.RoleDesc = description
role.save(update_fields=['RoleName', 'RoleDesc'])
RolePermission.objects.filter(RoleUUID=role.RoleUUID).delete()
for pc in perm_codes:
if pc == '000001' and role.RoleName != '管理员':
continue
try:
perm = Permission.objects.get(PermCode=pc, PermStatus=1)
except Permission.DoesNotExist:
continue
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)
if not RolePermission.objects.filter(
RoleUUID=role.RoleUUID, PermUUID=super_perm.PermUUID,
).exists():
RolePermission.objects.create(
RolePermUUID=uuid.uuid4().bytes,
RoleUUID=role.RoleUUID,
PermUUID=super_perm.PermUUID,
CreateTime=timezone.now(),
)
except Permission.DoesNotExist:
pass
return Response({'code': 0, 'msg': '角色保存成功'})
else:
return Response({'code': 400, 'msg': '无效的 action'})
class AddRoleView(APIView):
"""
添加角色:自动生成角色编码,前端提供角色名称、描述、选择的权限列表
"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('username', '').strip()
role_name = request.data.get('role_name', '').strip()
description = request.data.get('description', '').strip()
perm_codes = request.data.get('perm_codes', []) # 列表
if not username or not role_name:
return Response({'code': 400, 'msg': '缺少必要参数'})
# 权限校验
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
if '000001' not in permissions:
return Response({'code': 403, 'msg': '您无权进行此操作'})
# 检查角色名称是否已存在(同俱乐部内唯一)
from jituan.services.club_rbac import resolve_role_club_id, role_name_exists
target_club_id = resolve_role_club_id(
request, (request.data.get('target_club_id') or '').strip() or None,
)
if role_name_exists(role_name, target_club_id):
return Response({'code': 400, 'msg': '该俱乐部下角色名称已存在'})
# 生成唯一角色编码格式ROLE_ + 8位随机字母数字
role_code = f"ROLE_{''.join(random.choices(string.ascii_uppercase + string.digits, k=8))}"
# 创建角色
with transaction.atomic():
role = Role.objects.create(
RoleUUID=uuid.uuid4().bytes,
TenantUUID=b'\x00' * 16,
RoleName=role_name,
RoleType=2,
RoleStatus=1,
AssignScope=0,
RoleDesc=description,
ClubID=target_club_id,
CreateTime=timezone.now(),
)
# 绑定权限(过滤掉不存在的权限编码)
for pc in perm_codes:
# 不允许添加 000001 权限(此权限仅供超级管理员)
if pc == '000001':
continue
try:
perm = Permission.objects.get(PermCode=pc)
RolePermission.objects.create(
RolePermUUID=uuid.uuid4().bytes,
RoleUUID=role.RoleUUID,
PermUUID=perm.PermUUID,
CreateTime=timezone.now(),
)
except Permission.DoesNotExist:
logger.warning(f"添加角色时忽略不存在的权限编码: {pc}")
return Response({'code': 0, 'msg': '角色添加成功', 'data': {'role_code': role_code}})
class GetAdminUserListView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
try:
username = request.data.get('username', '').strip()
if not username:
return Response({'code': 400, 'msg': '缺少username'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
from jituan.services.admin_context import can_access_admin_user_management
if not can_access_admin_user_management(request.user, permissions):
return Response({'code': 403, 'msg': '您无权访问此页面'})
phone = request.data.get('phone', '').strip()
nicheng = request.data.get('nicheng', '').strip()
role_code = request.data.get('roleCode', '').strip()
page = int(request.data.get('page', 1))
page_size = int(request.data.get('pageSize', 20))
# UserType 是 @property 非数据库字段,不能用 filter
# KefuProfile__isnull=False 已确保只查到客服用户
users = User.query.filter(
KefuProfile__isnull=False
).select_related('KefuProfile')
from jituan.models import AdminAssignment, Club
from jituan.constants import ADMIN_ROLE_LABELS
from jituan.services.club_rbac import filter_kefu_users_by_admin_scope
users = filter_kefu_users_by_admin_scope(users, request)
if phone:
users = users.filter(Phone__icontains=phone)
if nicheng:
users = users.filter(KefuProfile__nicheng__icontains=nicheng)
# 关键修正:先求值为列表,再传入 phone__in
if role_code:
from jituan.services.club_rbac import resolve_role_by_name, resolve_role_club_id
filter_club_id = resolve_role_club_id(request)
target_role = resolve_role_by_name(role_code, filter_club_id)
if target_role:
user_uuids = list(
UserRole.objects.filter(RoleUUID=target_role.RoleUUID)
.values_list('UserUUID', flat=True)
)
users = users.filter(UserUUID__in=user_uuids)
else:
users = users.none()
users = users.order_by('-UserCreateTime')
paginator = Paginator(users, page_size)
page_obj = paginator.get_page(page)
# 批量预取本页所有用户的 UserRole + Role避免循环内 N+1
_page_users = list(page_obj)
_page_user_uuids = [u.UserUUID for u in _page_users]
_ur_pairs = list(
UserRole.objects.filter(UserUUID__in=_page_user_uuids)
.values_list('UserUUID', 'RoleUUID')
) if _page_user_uuids else []
_role_uuids_in_page = list({r for _, r in _ur_pairs})
_role_info_by_uuid = {
r.RoleUUID: {'role_name': r.RoleName, 'club_id': getattr(r, 'ClubID', '') or ''}
for r in Role.objects.filter(RoleUUID__in=_role_uuids_in_page)
} if _role_uuids_in_page else {}
_roles_by_user = {}
for user_uuid, role_uuid in _ur_pairs:
info = _role_info_by_uuid.get(role_uuid)
if info is not None:
_roles_by_user.setdefault(user_uuid, []).append({
'role_code': info['role_name'],
'role_name': info['role_name'],
'club_id': info['club_id'],
})
_page_yonghuids = [u.UserUID for u in _page_users]
_club_name_map = {c.club_id: c.name for c in Club.query.filter(status=1)}
_assignments_by_user = {}
if _page_yonghuids:
for a in AdminAssignment.query.filter(yonghuid__in=_page_yonghuids, status=1):
if a.club_id:
club_label = _club_name_map.get(a.club_id, a.club_id)
else:
club_label = '集团(全部子公司)'
_assignments_by_user.setdefault(a.yonghuid, []).append({
'id': a.id,
'club_id': a.club_id,
'club_label': club_label,
'role_code': a.role_code,
'role_name': ADMIN_ROLE_LABELS.get(a.role_code, a.role_code),
'is_primary': a.is_primary,
})
data_list = []
for user in _page_users:
roles_data = _roles_by_user.get(user.UserUUID, [])
data_list.append({
'phone': user.Phone or '',
'yonghuid': user.UserUID or '',
'nicheng': user.KefuProfile.nicheng,
'club_id': getattr(user, 'ClubID', '') or '',
'roles': roles_data,
'assignments': _assignments_by_user.get(user.UserUID, []),
'status': user.KefuProfile.zhuangtai,
'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '',
'update_time': user.KefuProfile.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user.KefuProfile, 'UpdateTime', None) else '',
'CreateTime': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '',
'UpdateTime': user.KefuProfile.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user.KefuProfile, 'UpdateTime', None) else '',
})
from jituan.services.admin_context import can_manage_admin_assignments, get_manageable_club_ids
return Response({
'code': 0,
'data': {
'list': data_list,
'total': paginator.count,
'page': page,
'pageSize': page_size,
'can_manage_club_assignments': can_manage_admin_assignments(request.user, permissions),
'manageable_club_ids': sorted(get_manageable_club_ids(request.user, permissions)),
}
})
except Exception as e:
logger.error(f"GetAdminUserListView 异常: {e}\n{traceback.format_exc()}")
return Response({'code': 500, 'msg': f'服务器内部错误: {str(e)}'})
class GetAdminRolesView(APIView):
"""获取所有角色(用于筛选和分配)"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('username', '').strip()
if not username:
return Response({'code': 400, 'msg': '缺少username'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
from jituan.services.admin_context import can_access_admin_user_management
if not can_access_admin_user_management(request.user, permissions):
return Response({'code': 403, 'msg': '您无权访问此页面'})
from jituan.services.admin_context import can_manage_admin_assignments, get_manageable_club_ids
from jituan.services.club_rbac import filter_roles_for_request
all_clubs = request.data.get('all_clubs') in (True, 1, '1', 'true', 'yes')
if all_clubs and can_manage_admin_assignments(request.user, permissions):
roles = Role.objects.filter(RoleStatus=1).order_by('ClubID', 'RoleName').values('RoleName', 'ClubID')
else:
manageable = get_manageable_club_ids(request.user, permissions)
if manageable:
roles = Role.objects.filter(
RoleStatus=1, ClubID__in=manageable,
).order_by('ClubID', 'RoleName').values('RoleName', 'ClubID')
else:
roles = filter_roles_for_request(request).values('RoleName', 'ClubID')
return Response({
'code': 0,
'data': {
'roles': [
{
'role_code': r['RoleName'],
'role_name': r['RoleName'],
'club_id': r.get('ClubID') or '',
}
for r in roles
]
}
})
'''class GetAdminUserListView(APIView):
"""获取后台用户列表(支持分页、筛选)"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('username', '').strip()
if not username:
return Response({'code': 400, 'msg': '缺少username'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
from jituan.services.admin_context import can_access_admin_user_management
if not can_access_admin_user_management(request.user, permissions):
return Response({'code': 403, 'msg': '您无权访问此页面'})
# 筛选条件
phone = request.data.get('phone', '').strip()
nicheng = request.data.get('nicheng', '').strip()
role_code = request.data.get('roleCode', '').strip()
page = int(request.data.get('page', 1))
page_size = int(request.data.get('pageSize', 20))
# 基础查询:存在客服扩展表
users = User.query.filter(KefuProfile__isnull=False).select_related('KefuProfile')
if phone:
users = users.filter(Phone__icontains=phone)
if nicheng:
users = users.filter(KefuProfile__nicheng__icontains=nicheng)
if role_code:
# 筛选拥有该角色的用户
user_ids_with_role = UserRole.query.filter(role__role_code=role_code).values_list('account_id', flat=True)
users = users.filter(phone__in=user_ids_with_role)
# 排序
users = users.order_by('-CreateTime')
# 分页
paginator = Paginator(users, page_size)
page_obj = paginator.get_page(page)
# 构建返回数据
data_list = []
for user in page_obj:
# 获取用户的所有角色
user_roles = UserRole.query.filter(account_id=user.Phone).select_related('role')
roles_data = [{'role_code': ur.role.role_code, 'role_name': ur.role.role_name} for ur in user_roles]
data_list.append({
'phone': user.Phone,
'nicheng': user.KefuProfile.nicheng,
'roles': roles_data,
'status': user.KefuProfile.zhuangtai,
'CreateTime': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S'),
'UpdateTime': user.UpdateTime.strftime('%Y-%m-%d %H:%M:%S'),
})
return Response({
'code': 0,
'data': {
'list': data_list,
'total': paginator.count,
'page': page,
'pageSize': page_size
}
})'''
class ModifyAdminUserView(APIView):
"""修改后台用户信息(昵称、密码、二级密码、状态、角色)"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('username', '').strip()
action = request.data.get('action')
if not username or not action:
return Response({'code': 400, 'msg': '缺少参数'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
from jituan.services.admin_context import (
can_manage_admin_assignments,
can_manage_roles_for_club,
can_access_admin_user_management,
)
club_assign_actions = {'add_club_assignment', 'remove_club_assignment'}
role_manage_actions = {'add_user_roles', 'remove_user_role'}
if action in club_assign_actions:
if not can_manage_admin_assignments(request.user, permissions):
return Response({'code': 403, 'msg': '无权分配俱乐部任职'})
elif action in role_manage_actions:
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:
return Response({'code': 403, 'msg': '您无权进行此操作'})
target_yonghuid = request.data.get('yonghuid', '').strip()
target_phone = request.data.get('phone', '').strip()
if not target_yonghuid and not target_phone:
return Response({'code': 400, 'msg': '缺少目标用户yonghuid 或 phone'})
from jituan.services.club_rbac import resolve_kefu_admin_user
target_user = resolve_kefu_admin_user(yonghuid=target_yonghuid, phone=target_phone)
if not target_user:
return Response({'code': 404, 'msg': '用户不存在或非后台账号'})
try:
target_kefu = target_user.KefuProfile
except UserKefu.DoesNotExist:
return Response({'code': 404, 'msg': '用户扩展信息缺失'})
with transaction.atomic():
if action == 'update_user_info':
# 修改昵称、状态、密码、二级密码
nicheng = request.data.get('nicheng')
status = request.data.get('status')
password = request.data.get('password')
erjimima = request.data.get('erjimima')
if nicheng is not None:
target_kefu.nicheng = nicheng
if status is not None:
target_kefu.zhuangtai = int(status)
if password:
if len(password) < 6:
return Response({'code': 400, 'msg': '密码至少6位'})
target_user.SetPassword(password)
if erjimima:
if len(erjimima) < 6:
return Response({'code': 400, 'msg': '二级密码至少6位'})
import bcrypt as _bcrypt
target_kefu.erjimima = _bcrypt.hashpw(erjimima.encode('utf-8'), _bcrypt.gensalt(rounds=12)).decode('utf-8')
target_user.save()
target_kefu.save()
return Response({'code': 0, 'msg': '修改成功'})
elif action == 'add_user_roles':
role_codes = request.data.get('role_codes', [])
if not role_codes:
return Response({'code': 400, 'msg': '请选择角色'})
from jituan.services.club_rbac import resolve_role_by_name
from jituan.services.club_context import resolve_club_id_from_request
bind_club_id = (request.data.get('target_club_id') or '').strip()
if not bind_club_id:
bind_club_id = resolve_club_id_from_request(request)
if not can_manage_roles_for_club(request.user, permissions, bind_club_id):
return Response({'code': 403, 'msg': '您无权管理该俱乐部的角色分配'})
added, missing = [], []
for rc in role_codes:
role = resolve_role_by_name(rc, bind_club_id)
if not role:
missing.append(rc)
continue
role_club = (getattr(role, 'ClubID', None) or '').strip()
if role_club and role_club != bind_club_id:
missing.append(rc)
continue
_, created = UserRole.objects.get_or_create(
UserUUID=target_user.UserUUID,
RoleUUID=role.RoleUUID,
defaults={
'UserRoleUUID': uuid.uuid4().bytes,
'CompanyUUID': uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes,
},
)
if created:
added.append(role.RoleName)
elif role.RoleName not in added:
added.append(role.RoleName)
if not added:
return Response({
'code': 400,
'msg': f'未找到可绑定的角色:{", ".join(missing or role_codes)}。请确认顶栏俱乐部与角色所属俱乐部一致',
})
if missing:
return Response({
'code': 0,
'msg': f'已绑定:{", ".join(added)};未找到:{", ".join(missing)}',
})
return Response({'code': 0, 'msg': '角色添加成功'})
elif action == 'remove_user_role':
role_code = request.data.get('role_code')
if not role_code:
return Response({'code': 400, 'msg': '缺少角色编码'})
from jituan.services.club_rbac import resolve_role_by_name
from jituan.services.club_context import resolve_club_id_from_request
bind_club_id = (request.data.get('target_club_id') or '').strip()
if not bind_club_id:
bind_club_id = resolve_club_id_from_request(request)
if not can_manage_roles_for_club(request.user, permissions, bind_club_id):
return Response({'code': 403, 'msg': '您无权管理该俱乐部的角色分配'})
role = resolve_role_by_name(role_code, bind_club_id)
if not role:
return Response({'code': 404, 'msg': '角色不存在或与所选俱乐部不匹配'})
UserRole.objects.filter(UserUUID=target_user.UserUUID, RoleUUID=role.RoleUUID).delete()
return Response({'code': 0, 'msg': '角色移除成功'})
elif action == 'add_club_assignment':
club_id = (request.data.get('club_id') or '').strip()
if not club_id:
return Response({'code': 400, 'msg': '请选择俱乐部'})
from jituan.services.admin_assignments import create_or_reactivate_assignment
from jituan.constants import DATA_SCOPE_SINGLE
role_code = (request.data.get('role_code') or 'CLUB_ADMIN').strip()
is_primary = bool(request.data.get('is_primary', False))
_, err = create_or_reactivate_assignment(
yonghuid=target_user.UserUID,
club_id=club_id,
role_code=role_code,
data_scope=DATA_SCOPE_SINGLE,
is_primary=is_primary,
granted_by=request.user.UserUID,
)
if err:
return Response({'code': 400, 'msg': err})
return Response({'code': 0, 'msg': '俱乐部任职已添加'})
elif action == 'remove_club_assignment':
assignment_id = request.data.get('assignment_id')
if not assignment_id:
return Response({'code': 400, 'msg': '缺少任职 id'})
from jituan.services.admin_assignments import deactivate_assignment
from jituan.models import AdminAssignment
row = AdminAssignment.query.filter(
id=assignment_id, yonghuid=target_user.UserUID, status=1,
).first()
if not row:
return Response({'code': 404, 'msg': '任职记录不存在'})
ok, err = deactivate_assignment(assignment_id)
if not ok:
return Response({'code': 404, 'msg': err or '停用失败'})
return Response({'code': 0, 'msg': '俱乐部任职已移除'})
else:
return Response({'code': 400, 'msg': '无效的action'})
class AddAdminUserView(APIView):
"""添加后台用户(客服账号)"""
permission_classes = [IsAuthenticated]
def post(self, request):
username = request.data.get('username', '').strip()
if not username:
return Response({'code': 400, 'msg': '缺少username'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
from jituan.services.admin_context import (
can_manage_admin_assignments,
can_manage_roles_for_club,
)
from jituan.services.club_context import resolve_club_id_from_request
target_club_id = resolve_club_id_from_request(request)
if not (
can_manage_admin_assignments(request.user, permissions)
or can_manage_roles_for_club(request.user, permissions, target_club_id)
):
return Response({'code': 403, 'msg': '您无权在该俱乐部添加用户'})
phone = request.data.get('phone', '').strip()
password = request.data.get('password', '').strip()
erjimima = request.data.get('erjimima', '').strip()
nicheng = request.data.get('nicheng', '').strip()
role_codes = request.data.get('role_codes', [])
if not all([phone, password, erjimima, nicheng]):
return Response({'code': 400, 'msg': '请填写完整信息'})
if len(password) < 6 or len(erjimima) < 6:
return Response({'code': 400, 'msg': '密码和二级密码至少6位'})
from jituan.services.club_rbac import resolve_roles_for_binding
# 校验手机号是否已被后台客服占用(同俱乐部优先)
existing_kefu = User.query.filter(
Phone=phone, KefuProfile__isnull=False
).order_by('-UserCreateTime').first()
if existing_kefu:
return Response({'code': 400, 'msg': '该账号已存在'})
if not isinstance(role_codes, list):
return Response({'code': 400, 'msg': 'role_codes 格式错误'})
# 分配角色(默认客服 + 所选角色,去重;任一角色找不到则整单拒绝)
role_names_to_bind = []
seen_names = set()
for name in (['客服'] if '客服' not in role_codes else []) + list(role_codes):
n = (name or '').strip()
if n and n not in seen_names:
seen_names.add(n)
role_names_to_bind.append(n)
roles_to_bind, missing_roles = resolve_roles_for_binding(role_names_to_bind, target_club_id)
if missing_roles:
return Response({
'code': 400,
'msg': f'角色不存在或与当前俱乐部不匹配:{", ".join(missing_roles)}',
})
if not roles_to_bind:
return Response({'code': 400, 'msg': '请至少选择一个有效角色'})
with transaction.atomic():
# 事务内二次校验,防止并发重复创建
if User.query.filter(Phone=phone, KefuProfile__isnull=False).exists():
return Response({'code': 400, 'msg': '该账号已存在'})
# 生成唯一的 yonghuid7位数字
while True:
yonghuid = str(random.randint(1000000, 9999999))
if not User.query.filter(UserUID=yonghuid).exists():
break
# 生成唯一的 openid格式admin_{时间戳}_{随机6位}
timestamp = str(int(time.time() * 1000))
random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
openid = f"admin_{timestamp}_{random_suffix}"
while User.query.filter(OpenID=openid).exists():
random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
openid = f"admin_{timestamp}_{random_suffix}"
user_main = User.query.create(
UserUID=yonghuid,
UserName=openid,
OpenID=openid,
Phone=phone,
ClubID=target_club_id,
)
user_main.SetPassword(password)
user_main.save(update_fields=['UserPassword', 'ClubID'])
# 创建客服扩展表(后台用户扩展)
import bcrypt as _bcrypt
erjimima_hashed = _bcrypt.hashpw(erjimima.encode('utf-8'), _bcrypt.gensalt(rounds=12)).decode('utf-8')
UserKefu.query.create(
user=user_main,
nicheng=nicheng,
erjimima=erjimima_hashed,
zhuangtai=1
)
for role in roles_to_bind:
UserRole.objects.get_or_create(
UserUUID=user_main.UserUUID,
RoleUUID=role.RoleUUID,
defaults={
'UserRoleUUID': uuid.uuid4().bytes,
'CompanyUUID': uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes,
},
)
from jituan.services.admin_assignments import create_or_reactivate_assignment
from jituan.constants import DATA_SCOPE_SINGLE
create_or_reactivate_assignment(
yonghuid=yonghuid,
club_id=target_club_id,
role_code='CLUB_ADMIN',
data_scope=DATA_SCOPE_SINGLE,
is_primary=True,
granted_by=request.user.UserUID,
)
return Response({'code': 0, 'msg': '添加成功'})