"refactor: route ugm/site-user/role-permission/user-detail through gvsdsdk managers
- ugm auth flows use AuthManager - site_user management uses AdminUserManager - role permission save uses RoleManager.SaveRolePermissions - user detail aggregation uses UserService.GetUserDetail - MC custom business moved into gvsds.apps.mc (manager/models) - remove dead imports (User2FA/UserAvatar/Password/bcrypt/Role/transaction) "
This commit is contained in:
1 parent
7d578f82a4
commit
3346a3b9ce
16 files changed
+719
-400
No files matched your search
@@ -4,7 +4,7 @@
|
||||
Tenant/Company)。业务规则移植自旧 Flask 实现:
|
||||
|
||||
- ``StoreValueRule``:6 种储值方案规则表(活期/定期×5 档)
|
||||
- ``GetGVSDSUserToMCPlayerUUID``:委托 ``gvsdsdk.minecraft.EasyAuthManager``
|
||||
- ``GetGVSDSUserToMCPlayerUUID``:委托 ``gvsds.apps.mc.manager.EasyAuthManager``
|
||||
反查 easyauth 表的 UserRoleUUID 绑定标记,把 GVSDS UserUUID 映射为
|
||||
MC 玩家 UUID 字符串(与 MC 绑定流程保持一致)
|
||||
- ``GetUserByUUID`` / ``GetUserByName``:通过业务子类的 User 模型按
|
||||
@@ -45,14 +45,14 @@ StoreValueRule: dict = {
|
||||
def GetGVSDSUserToMCPlayerUUID(gvsds_user_uuid) -> Optional[str]:
|
||||
"""GVSDS UserUUID → MC 玩家 UUID 字符串(varchar36)
|
||||
|
||||
委托 ``gvsdsdk.minecraft.EasyAuthManager.GetPlayerUUIDByGVSDSUser`` 反查
|
||||
委托 ``gvsds.apps.mc.manager.EasyAuthManager.GetPlayerUUIDByGVSDSUser`` 反查
|
||||
easyauth 表的 UserRoleUUID 绑定标记,与 MC 绑定流程(``UserBindView``)
|
||||
保持一致。旧 Flask 时代使用的 ``CmeWalletGVSDSMapping`` 表已被新绑定
|
||||
机制取代,不再写入数据,因此不再作为查询源。
|
||||
|
||||
接受 bytes/str/UUIDObj 形式的 GVSDS UserUUID。
|
||||
"""
|
||||
from gvsdsdk.minecraft import EasyAuthManager
|
||||
from gvsds.apps.mc.manager import EasyAuthManager
|
||||
return EasyAuthManager.GetPlayerUUIDByGVSDSUser(gvsds_user_uuid)
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ def GetPlayerNameByGVSDSUser(gvsds_user_uuid) -> Optional[str]:
|
||||
player_uuid = GetGVSDSUserToMCPlayerUUID(gvsds_user_uuid)
|
||||
if not player_uuid:
|
||||
return None
|
||||
from gvsdsdk.minecraft import EasyAuthManager
|
||||
from gvsds.apps.mc.manager import EasyAuthManager
|
||||
ea = EasyAuthManager.GetByPlayerUUID(player_uuid)
|
||||
return ea.Username if ea else None
|
||||
|
||||
@@ -90,7 +90,7 @@ def GetGVSDSUserByPlayerName(player_name: str):
|
||||
"""
|
||||
if not player_name:
|
||||
return None
|
||||
from gvsdsdk.minecraft.models import EasyAuth
|
||||
from gvsds.apps.mc.models import EasyAuth
|
||||
from gvsdsdk.models import UserRole
|
||||
from gvsdsdk.model_utils import UUIDObj
|
||||
ea = EasyAuth.objects.filter(Username=player_name).first()
|
||||
|
||||
@@ -1403,7 +1403,7 @@ class CmeStoreValueViewSet(CompanyPrivateAccessMixin, viewsets.ModelViewSet):
|
||||
# 玩家名从 easyauth 表取(已绑定后的 MC 玩家名)
|
||||
player_name = ''
|
||||
try:
|
||||
from gvsdsdk.minecraft import EasyAuthManager
|
||||
from gvsds.apps.mc.manager import EasyAuthManager
|
||||
if player_uuid_str:
|
||||
ea = EasyAuthManager.GetByPlayerUUID(player_uuid_str)
|
||||
if ea:
|
||||
|
||||
@@ -7,11 +7,10 @@
|
||||
import logging
|
||||
import uuid as _uuid
|
||||
|
||||
from django.db import transaction
|
||||
from rest_framework.response import Response
|
||||
|
||||
from . import ELTAuthenticatedAPIView
|
||||
from gvsds.apps.gerp.models import Role, Permission, RolePermission, Company
|
||||
from gvsds.apps.gerp.models import Permission, RolePermission, Company
|
||||
from gvsds.apps.gerp.views.form_api import _is_admin, _is_super_admin, _get_current_context
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -180,54 +179,19 @@ class RoleSavePermissionsView(ELTAuthenticatedAPIView):
|
||||
return Response({'code': 1, 'msg': 'CompanyUUID 格式无效'}, status=400)
|
||||
|
||||
# 校验角色和公司存在
|
||||
if not Role.objects.filter(RoleUUID=role_uuid).exists():
|
||||
from gvsdsdk.auth.managers import RoleManager
|
||||
if RoleManager.GetRoleByUUID(role_uuid) is None:
|
||||
return Response({'code': 1, 'msg': '角色不存在'}, status=404)
|
||||
if not Company.objects.filter(CompanyUUID=company_uuid).exists():
|
||||
return Response({'code': 1, 'msg': '公司不存在'}, status=404)
|
||||
|
||||
# 规范化 PermUUIDs(去重 + 转 bytes + 过滤无效)
|
||||
target_perm_uuids = set()
|
||||
for p_str in perm_uuid_list:
|
||||
p_bytes = _to_uuid_bytes(p_str)
|
||||
if p_bytes:
|
||||
target_perm_uuids.add(p_bytes)
|
||||
|
||||
# 校验权限是否真实存在(避免脏数据)
|
||||
valid_perm_uuids = set(
|
||||
Permission.objects.filter(PermUUID__in=target_perm_uuids).values_list('PermUUID', flat=True)
|
||||
) if target_perm_uuids else set()
|
||||
|
||||
# 规范化 PermUUIDs(去重 + 转 bytes + 过滤无效)并校验存在性、增量更新
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# 现有权限集合
|
||||
existing_qs = RolePermission.objects.filter(
|
||||
RoleUUID=role_uuid,
|
||||
CompanyUUID=company_uuid,
|
||||
)
|
||||
existing_perm_uuids = set(existing_qs.values_list('PermUUID', flat=True))
|
||||
|
||||
# 需要删除的(现有但不在目标列表中)
|
||||
to_delete = existing_perm_uuids - valid_perm_uuids
|
||||
if to_delete:
|
||||
existing_qs.filter(PermUUID__in=to_delete).delete()
|
||||
|
||||
# 需要新增的(目标中但现有不存在)
|
||||
to_add = valid_perm_uuids - existing_perm_uuids
|
||||
new_objs = [
|
||||
RolePermission(
|
||||
RolePermUUID=_uuid.uuid4().bytes,
|
||||
RoleUUID=role_uuid,
|
||||
PermUUID=p_uuid,
|
||||
CompanyUUID=company_uuid,
|
||||
)
|
||||
for p_uuid in to_add
|
||||
]
|
||||
if new_objs:
|
||||
RolePermission.objects.bulk_create(new_objs)
|
||||
|
||||
added_count = len(to_add)
|
||||
removed_count = len(to_delete)
|
||||
kept_count = len(existing_perm_uuids & valid_perm_uuids)
|
||||
added_count, removed_count, kept_count, total = RoleManager.SaveRolePermissions(
|
||||
RoleUUID=role_uuid,
|
||||
CompanyUUID=company_uuid,
|
||||
PermUUIDs=perm_uuid_list,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception('[RoleSavePermissions] 保存角色权限失败')
|
||||
return Response({'code': 1, 'msg': f'保存失败: {e}'}, status=500)
|
||||
@@ -239,6 +203,6 @@ class RoleSavePermissionsView(ELTAuthenticatedAPIView):
|
||||
'Added': added_count,
|
||||
'Removed': removed_count,
|
||||
'Kept': kept_count,
|
||||
'Total': len(valid_perm_uuids),
|
||||
'Total': total,
|
||||
}
|
||||
})
|
||||
@@ -2,11 +2,9 @@
|
||||
提供管理员对用户的管理功能:列表、密码重置、启用/禁用、邮箱验证、背包管理。"""
|
||||
|
||||
import uuid
|
||||
import bcrypt
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from gvsds.apps.core.models import User, User2FA
|
||||
from gvsds.apps.gerp.models import Password, Backpack, ItemReference
|
||||
from gvsds.apps.gerp.models import Backpack, ItemReference
|
||||
from gvsdsdk.model_utils import UUIDObj
|
||||
from gvsdsdk.auth.permissions import IsAdminUser
|
||||
from . import ELTAuthenticatedAPIView
|
||||
@@ -22,25 +20,13 @@ class SiteUserManagementView(ELTAuthenticatedAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request):
|
||||
from gvsdsdk.auth.managers import AdminUserManager
|
||||
keyword = request.query_params.get('keyword', '')
|
||||
qs = User.objects.all()
|
||||
if keyword:
|
||||
qs = qs.filter(UserName__icontains=keyword)
|
||||
offset = int(request.query_params.get('offset', 0))
|
||||
page_size = int(request.query_params.get('PageSize', 20))
|
||||
users = qs[offset:offset + page_size]
|
||||
total = qs.count()
|
||||
data = []
|
||||
for u in users:
|
||||
data.append({
|
||||
'UserUUID': UUIDObj(u.UserUUID).str,
|
||||
'UserName': u.UserName,
|
||||
'UserEmail': u.UserEmail,
|
||||
'UserEmailVerify': u.UserEmailVerify,
|
||||
'UserGender': u.UserGender,
|
||||
'UserAccountLicense': u.UserAccountLicense,
|
||||
'UserPositionStatus': u.UserPositionStatus,
|
||||
})
|
||||
data, total = AdminUserManager.SearchUsers(
|
||||
Keyword=keyword, Offset=offset, PageSize=page_size,
|
||||
)
|
||||
# data 用对象包裹(含分页元数据):
|
||||
# 前端 axios 拦截器解包 {code, data, ...} 为 result.data,
|
||||
# data 对象含 list/has_more/total,EntityPicker 从 list 取选项、从 has_more 判断是否还有更多
|
||||
@@ -60,16 +46,14 @@ class SiteUserResetPasswordView(ELTAuthenticatedAPIView):
|
||||
def post(self, request):
|
||||
ser = SiteUserResetPasswordSerializer(data=request.data)
|
||||
ser.IsValid(raise_exception=True)
|
||||
user_uuid_str = ser.ValidatedData['UserUUID']
|
||||
try:
|
||||
user_bytes = UUIDObj(user_uuid_str).bytes
|
||||
pw_obj = Password.objects.get(UserUUID=user_bytes)
|
||||
new_hash = bcrypt.hashpw(ser.ValidatedData['NewPassword'].encode(), bcrypt.gensalt()).decode()
|
||||
pw_obj.Password = new_hash
|
||||
pw_obj.save(update_fields=['Password'])
|
||||
return Response({'code': 0, 'msg': '密码重置成功'})
|
||||
except (Password.DoesNotExist, ValueError):
|
||||
return Response({'code': 1, 'msg': '用户不存在'}, status=404)
|
||||
from gvsdsdk.auth.managers import AdminUserManager
|
||||
ok, msg = AdminUserManager.ResetPassword(
|
||||
ser.ValidatedData['UserUUID'],
|
||||
ser.ValidatedData['NewPassword'],
|
||||
)
|
||||
if not ok:
|
||||
return Response({'code': 1, 'msg': msg}, status=404)
|
||||
return Response({'code': 0, 'msg': '密码重置成功'})
|
||||
|
||||
|
||||
class SiteUserVerifyPasswordView(ELTAuthenticatedAPIView):
|
||||
@@ -78,13 +62,14 @@ class SiteUserVerifyPasswordView(ELTAuthenticatedAPIView):
|
||||
def post(self, request):
|
||||
ser = SiteUserVerifyPasswordSerializer(data=request.data)
|
||||
ser.IsValid(raise_exception=True)
|
||||
try:
|
||||
user_bytes = UUIDObj(ser.ValidatedData['UserUUID']).bytes
|
||||
pw_obj = Password.objects.get(UserUUID=user_bytes)
|
||||
valid = bcrypt.checkpw(ser.ValidatedData['Password'].encode(), pw_obj.Password.encode())
|
||||
return Response({'code': 0, 'valid': valid})
|
||||
except (Password.DoesNotExist, ValueError):
|
||||
from gvsdsdk.auth.managers import AdminUserManager
|
||||
found, valid = AdminUserManager.VerifyPassword(
|
||||
ser.ValidatedData['UserUUID'],
|
||||
ser.ValidatedData['Password'],
|
||||
)
|
||||
if not found:
|
||||
return Response({'code': 1, 'msg': '用户不存在'}, status=404)
|
||||
return Response({'code': 0, 'valid': valid})
|
||||
|
||||
|
||||
class SiteUserEnableView(ELTAuthenticatedAPIView):
|
||||
@@ -93,14 +78,11 @@ class SiteUserEnableView(ELTAuthenticatedAPIView):
|
||||
def post(self, request):
|
||||
ser = SiteUserEnableSerializer(data=request.data)
|
||||
ser.IsValid(raise_exception=True)
|
||||
try:
|
||||
user_bytes = UUIDObj(ser.ValidatedData['UserUUID']).bytes
|
||||
user = User.objects.get(UserUUID=user_bytes)
|
||||
user.UserAccountLicense = 1
|
||||
user.save(update_fields=['UserAccountLicense'])
|
||||
return Response({'code': 0, 'msg': '账户已启用'})
|
||||
except (User.DoesNotExist, ValueError):
|
||||
return Response({'code': 1, 'msg': '用户不存在'}, status=404)
|
||||
from gvsdsdk.auth.managers import AdminUserManager
|
||||
ok, msg = AdminUserManager.SetAccountLicense(ser.ValidatedData['UserUUID'], 1)
|
||||
if not ok:
|
||||
return Response({'code': 1, 'msg': msg}, status=404)
|
||||
return Response({'code': 0, 'msg': '账户已启用'})
|
||||
|
||||
|
||||
class SiteUserDisableView(ELTAuthenticatedAPIView):
|
||||
@@ -109,14 +91,11 @@ class SiteUserDisableView(ELTAuthenticatedAPIView):
|
||||
def post(self, request):
|
||||
ser = SiteUserDisableSerializer(data=request.data)
|
||||
ser.IsValid(raise_exception=True)
|
||||
try:
|
||||
user_bytes = UUIDObj(ser.ValidatedData['UserUUID']).bytes
|
||||
user = User.objects.get(UserUUID=user_bytes)
|
||||
user.UserAccountLicense = 0
|
||||
user.save(update_fields=['UserAccountLicense'])
|
||||
return Response({'code': 0, 'msg': '账户已禁用'})
|
||||
except (User.DoesNotExist, ValueError):
|
||||
return Response({'code': 1, 'msg': '用户不存在'}, status=404)
|
||||
from gvsdsdk.auth.managers import AdminUserManager
|
||||
ok, msg = AdminUserManager.SetAccountLicense(ser.ValidatedData['UserUUID'], 0)
|
||||
if not ok:
|
||||
return Response({'code': 1, 'msg': msg}, status=404)
|
||||
return Response({'code': 0, 'msg': '账户已禁用'})
|
||||
|
||||
|
||||
class SiteUserEmailVerifyView(ELTAuthenticatedAPIView):
|
||||
@@ -125,14 +104,14 @@ class SiteUserEmailVerifyView(ELTAuthenticatedAPIView):
|
||||
def post(self, request):
|
||||
ser = SiteUserEmailVerifySerializer(data=request.data)
|
||||
ser.IsValid(raise_exception=True)
|
||||
try:
|
||||
user_bytes = UUIDObj(ser.ValidatedData['UserUUID']).bytes
|
||||
user = User.objects.get(UserUUID=user_bytes)
|
||||
user.UserEmail = ser.ValidatedData['Email']
|
||||
user.save(update_fields=['UserEmail'])
|
||||
return Response({'code': 0, 'msg': '邮箱验证已发送'})
|
||||
except (User.DoesNotExist, ValueError):
|
||||
return Response({'code': 1, 'msg': '用户不存在'}, status=404)
|
||||
from gvsdsdk.auth.managers import AdminUserManager
|
||||
ok, msg = AdminUserManager.SetUserEmail(
|
||||
ser.ValidatedData['UserUUID'],
|
||||
ser.ValidatedData['Email'],
|
||||
)
|
||||
if not ok:
|
||||
return Response({'code': 1, 'msg': msg}, status=404)
|
||||
return Response({'code': 0, 'msg': '邮箱验证已发送'})
|
||||
|
||||
|
||||
class SiteUserBackpackView(ELTAuthenticatedAPIView):
|
||||
@@ -214,19 +193,11 @@ class SiteUser2FAInfoView(ELTAuthenticatedAPIView):
|
||||
if not user_uuid_str:
|
||||
return Response({'code': 1, 'msg': '缺少UserUUID'}, status=400)
|
||||
try:
|
||||
user_bytes = UUIDObj(user_uuid_str).bytes
|
||||
UUIDObj(user_uuid_str).bytes
|
||||
except ValueError:
|
||||
return Response({'code': 1, 'msg': '无效的UserUUID'}, status=400)
|
||||
try:
|
||||
two_fa = User2FA.objects.get(UserUUID=user_bytes)
|
||||
return Response({
|
||||
'code': 0,
|
||||
'data': {
|
||||
'IsEnabled': two_fa.IsEnabled,
|
||||
'Verified': two_fa.Verified,
|
||||
'LastUsedAt': two_fa.LastUsedAt.isoformat() if two_fa.LastUsedAt else None,
|
||||
'CreateTime': two_fa.CreateTime.isoformat() if two_fa.CreateTime else None,
|
||||
},
|
||||
})
|
||||
except User2FA.DoesNotExist:
|
||||
from gvsdsdk.auth.managers import AdminUserManager
|
||||
info = AdminUserManager.Get2FAInfo(user_uuid_str)
|
||||
if info is None:
|
||||
return Response({'code': 0, 'data': None, 'msg': '该用户未配置2FA'})
|
||||
return Response({'code': 0, 'data': info})
|
||||
@@ -40,116 +40,38 @@ class UserDetailView(ELTAuthenticatedAPIView):
|
||||
return self._query_sub(server_name, user_uuid_str)
|
||||
|
||||
def _query_master(self, user_uuid_str):
|
||||
"""主服务器:直接 ORM 查询"""
|
||||
from gvsds.apps.core.models import User
|
||||
from gvsds.apps.gerp.models import UserRole, Role, RolePermission, Permission
|
||||
"""主服务器:经 gvsdsdk UserService 聚合查询"""
|
||||
from gvsdsdk.auth.user_service import UserService
|
||||
from gvsds.apps.gerp.models import Company
|
||||
|
||||
try:
|
||||
user_uuid = UUIDObj(user_uuid_str).bytes
|
||||
except Exception:
|
||||
return Response({'code': 1, 'msg': '无效的 UserUUID'})
|
||||
|
||||
# 用户基本信息
|
||||
try:
|
||||
user = User.objects.get(UserUUID=user_uuid)
|
||||
except User.DoesNotExist:
|
||||
detail = UserService.GetUserDetail(user_uuid)
|
||||
if detail is None:
|
||||
return Response({'code': 1, 'msg': '用户不存在'})
|
||||
|
||||
user_info = {
|
||||
'UserUUID': user_uuid_str,
|
||||
'UserName': user.UserName,
|
||||
'UserEmail': user.UserEmail or '',
|
||||
'UserEmailVerify': user.UserEmailVerify,
|
||||
'UserTele': user.UserTele or '',
|
||||
'UserGender': user.UserGender,
|
||||
'UserAccountLicense': user.UserAccountLicense,
|
||||
'UserPositionStatus': user.UserPositionStatus,
|
||||
'UserCreateTime': str(user.UserCreateTime) if user.UserCreateTime else '',
|
||||
'UserLastLoginDate': str(user.UserLastLoginDate) if user.UserLastLoginDate else '',
|
||||
'IsStaff': user.IsStaff,
|
||||
'IsSuperuser': user.IsSuperuser,
|
||||
}
|
||||
|
||||
# 角色列表(通过 UserRole.CompanyUUID 获取关联公司)
|
||||
from gvsdsdk.auth.managers import RoleManager
|
||||
roles = []
|
||||
user_roles = RoleManager.GetUserRoleDetails(user_uuid)
|
||||
for ur in user_roles:
|
||||
role = RoleManager.GetRoleByUUID(ur.RoleUUID)
|
||||
if role:
|
||||
company_name = ''
|
||||
company_short = ''
|
||||
if ur.CompanyUUID:
|
||||
from gvsds.apps.gerp.models import Company
|
||||
company = Company.objects.filter(CompanyUUID=ur.CompanyUUID).first()
|
||||
# 补全角色关联公司名称(SDK 返回 CompanyUUID,公司名称属 SAAS 侧数据)
|
||||
for role_item in detail['Roles']:
|
||||
company_uuid_str = role_item.get('CompanyUUID') or ''
|
||||
company_name = ''
|
||||
company_short = ''
|
||||
if company_uuid_str:
|
||||
try:
|
||||
company = Company.objects.filter(
|
||||
CompanyUUID=UUIDObj(company_uuid_str).bytes,
|
||||
).first()
|
||||
if company:
|
||||
company_name = company.CompanyName
|
||||
company_short = company.CompanyShort or ''
|
||||
roles.append({
|
||||
'RoleUUID': str(UUIDObj(role.RoleUUID)) if role.RoleUUID else '',
|
||||
'RoleName': role.RoleName,
|
||||
'RoleType': role.RoleType,
|
||||
'RoleStatus': role.RoleStatus,
|
||||
'AssignScope': role.AssignScope,
|
||||
'RoleDesc': role.RoleDesc or '',
|
||||
'CompanyName': company_name,
|
||||
'CompanyShort': company_short,
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
role_item['CompanyName'] = company_name
|
||||
role_item['CompanyShort'] = company_short
|
||||
|
||||
# 权限列表(通过 UserRole → RolePermission → Permission 链路查询,按公司过滤)
|
||||
permissions = []
|
||||
role_uuids = [ur.RoleUUID for ur in user_roles if ur.RoleUUID]
|
||||
company_uuids = [ur.CompanyUUID for ur in user_roles if ur.CompanyUUID]
|
||||
if role_uuids:
|
||||
rp_qs = RolePermission.objects.filter(
|
||||
RoleUUID__in=role_uuids
|
||||
)
|
||||
if company_uuids:
|
||||
rp_qs = rp_qs.filter(CompanyUUID__in=company_uuids)
|
||||
perm_uuids = rp_qs.values_list('PermUUID', flat=True)
|
||||
perms = Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1)
|
||||
seen = set()
|
||||
for p in perms:
|
||||
key = p.PermCode
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
permissions.append({
|
||||
'PermName': p.PermName,
|
||||
'PermCode': p.PermCode,
|
||||
'PermCategory': p.PermCategory or '',
|
||||
'PermDesc': p.PermDesc or '',
|
||||
})
|
||||
|
||||
# 订单列表(最近 20 条)
|
||||
orders = []
|
||||
try:
|
||||
from gvsdsdk.order.models import Order
|
||||
user_orders = Order.objects.filter(
|
||||
BuyerUserUUID=user_uuid
|
||||
).order_by('-CreateTime')[:20]
|
||||
for o in user_orders:
|
||||
orders.append({
|
||||
'OrderNO': o.OrderNO,
|
||||
'OrderTitle': o.OrderTitle,
|
||||
'OrderType': o.OrderType,
|
||||
'TotalAmount': str(o.TotalAmount),
|
||||
'PayAmount': str(o.PayAmount),
|
||||
'Currency': o.Currency,
|
||||
'OrderStatus': o.OrderStatus,
|
||||
'CreateTime': str(o.CreateTime) if o.CreateTime else '',
|
||||
})
|
||||
except Exception:
|
||||
pass # 订单表可能不存在
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'data': {
|
||||
'User': user_info,
|
||||
'Roles': roles,
|
||||
'Permissions': permissions,
|
||||
'Orders': orders,
|
||||
}
|
||||
})
|
||||
return Response({'code': 0, 'data': detail})
|
||||
|
||||
def _query_sub(self, server_name, user_uuid_str):
|
||||
"""子服务器:通过 form proxy 查询 User 表"""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""模块:gvsdsdk.minecraft.auth — EasyAuth 插件认证与绑定管理
|
||||
"""模块:gvsds.apps.mc.manager — MC 定制业务(EasyAuth 认证与绑定管理)
|
||||
|
||||
MC 相关为定制业务,自 gvsdsdk 迁回 gvsds 主工程(原 gvsdsdk.minecraft.auth)。
|
||||
提供:
|
||||
- ``VerifyPassword``:使用 bcrypt 校验游戏账号密码
|
||||
- ``EasyAuthManager``:游戏账号查询、密码验证、绑定标记读写
|
||||
@@ -7,6 +8,11 @@
|
||||
|
||||
import bcrypt
|
||||
|
||||
from gvsdsdk.models import UserRole
|
||||
from gvsdsdk.model_utils import UUIDObj
|
||||
|
||||
from gvsds.apps.mc.models import EasyAuth
|
||||
|
||||
|
||||
def VerifyPassword(plain_password: str, bcrypt_hash: str) -> bool:
|
||||
"""校验明文密码与 bcrypt hash 是否匹配
|
||||
@@ -39,7 +45,6 @@ class EasyAuthManager:
|
||||
Returns:
|
||||
``EasyAuth`` 实例;不存在则返回 ``None``
|
||||
"""
|
||||
from .models import EasyAuth
|
||||
if not username:
|
||||
return None
|
||||
return EasyAuth.objects.filter(UsernameLower=username.lower()).first()
|
||||
@@ -79,7 +84,6 @@ class EasyAuthManager:
|
||||
Returns:
|
||||
实际更新行数:1 成功,0 表示已被其他请求抢先绑定
|
||||
"""
|
||||
from .models import EasyAuth
|
||||
if easyauth is None:
|
||||
return 0
|
||||
return EasyAuth.objects.filter(
|
||||
@@ -90,7 +94,6 @@ class EasyAuthManager:
|
||||
@staticmethod
|
||||
def ClearBound(easyauth) -> int:
|
||||
"""清空 easyauth 的 UserRoleUUID(用于解绑场景)"""
|
||||
from .models import EasyAuth
|
||||
if easyauth is None:
|
||||
return 0
|
||||
return EasyAuth.objects.filter(
|
||||
@@ -109,14 +112,11 @@ class EasyAuthManager:
|
||||
因此反过来即可定位该 GVSDS 用户绑定的 MC 玩家。
|
||||
|
||||
Args:
|
||||
gvsds_user_uuid: GVDS 用户 UUID(bytes/str/UUIDObj 均可)
|
||||
gvsds_user_uuid: GVSDS 用户 UUID(bytes/str/UUIDObj 均可)
|
||||
|
||||
Returns:
|
||||
MC 玩家 UUID 字符串;未绑定或 easyauth 记录不存在时返回 None
|
||||
"""
|
||||
from .models import EasyAuth
|
||||
from gvsdsdk.models import UserRole
|
||||
from gvsdsdk.model_utils import UUIDObj
|
||||
if not gvsds_user_uuid:
|
||||
return None
|
||||
raw_bytes = UUIDObj(gvsds_user_uuid).bytes
|
||||
@@ -140,7 +140,6 @@ class EasyAuthManager:
|
||||
Returns:
|
||||
``EasyAuth`` 实例;不存在或入参为空则返回 ``None``
|
||||
"""
|
||||
from .models import EasyAuth
|
||||
if not player_uuid:
|
||||
return None
|
||||
return EasyAuth.objects.filter(PlayerUUID=player_uuid).first()
|
||||
+37
-8
@@ -3,18 +3,47 @@
|
||||
Minecraft 游戏用户映射与 EasyAuth 插件认证表。
|
||||
|
||||
- ``EasyAuth``:MC EasyAuth 插件认证表(managed=False,外部 schema)
|
||||
—— 自 gvsdsdk.minecraft.models 导入,供本 app 复用同一模型类定义
|
||||
—— 模型定义原在 gvsdsdk.minecraft.models(MC 为定制业务,已迁回主工程)
|
||||
|
||||
EasyAuth 表的 ``data`` 列为 JSON,包含 ``password``(bcrypt hash)、
|
||||
``last_ip``、``registration_date`` 等字段。新增 ``UserRoleUUID`` 列
|
||||
(BINARY(16))存绑定后创建的 UserRole 关联 UUID,用于 MC 插件侧反查 GVSDS 身份。
|
||||
|
||||
注:原 MCUser 表(mc_users,一对一映射)已废弃删除——GVSDS 用户与 MC 玩家
|
||||
的多对多绑定改走 UserRole + easyauth.UserRoleUUID。"""
|
||||
的多对多绑定改走 UserRole + easyauth.UserRoleUUID。
|
||||
|
||||
# EasyAuth 模型统一定义在 gvsdsdk.minecraft.models(app_label='mc',
|
||||
# 通过 gvsds/db_router.py 路由到 minecraft 库)。
|
||||
# 此处仅 import 子模块以触发 EasyAuth 类创建并注册到 mc app,**不** 在本
|
||||
# 模块命名空间引入 EasyAuth 名字——否则与 gvsdsdk.minecraft.models 中
|
||||
# 已注册的同名类触发 Django "Conflicting models" RuntimeError。
|
||||
import gvsdsdk.minecraft.models # noqa: F401
|
||||
模型 app_label='mc',通过 gvsds/db_router.py 路由到 minecraft 库。"""
|
||||
|
||||
from django.db import models
|
||||
|
||||
from gvsdsdk.model_base import QModel
|
||||
from gvsdsdk.models import UUIDField
|
||||
|
||||
|
||||
class EasyAuth(QModel):
|
||||
"""EasyAuth 认证表——MC EasyAuth 插件的玩家凭据
|
||||
|
||||
映射真实表 ``easyauth``(managed=False)。``data`` 列为 JSON,
|
||||
其中 ``password`` 字段为 bcrypt hash(``$2a$12$...``),
|
||||
验证时使用 ``bcrypt.checkpw``。
|
||||
|
||||
``UserRoleUUID`` 列由本系统 ALTER 添加(BINARY(16) 可空),
|
||||
存绑定后创建的 ``UserRole`` 关联 UUID,用于 MC 插件侧反查 GVSDS 身份。
|
||||
"""
|
||||
|
||||
AuthID = models.AutoField(primary_key=True, db_column='id', verbose_name='认证ID')
|
||||
Username = models.CharField(max_length=255, unique=True, db_column='username', verbose_name='玩家名')
|
||||
UsernameLower = models.CharField(max_length=255, db_column='username_lower', verbose_name='玩家名小写')
|
||||
PlayerUUID = models.CharField(max_length=255, null=True, blank=True, db_column='uuid', verbose_name='MC玩家UUID字符串')
|
||||
Data = models.JSONField(db_column='data', verbose_name='插件数据')
|
||||
UserRoleUUID = UUIDField(null=True, blank=True, db_column='UserRoleUUID', verbose_name='绑定用户角色关联UUID')
|
||||
|
||||
class Meta:
|
||||
db_table = 'easyauth'
|
||||
app_label = 'mc'
|
||||
managed = False
|
||||
verbose_name = 'EasyAuth认证'
|
||||
verbose_name_plural = 'EasyAuth认证'
|
||||
|
||||
def __str__(self):
|
||||
return self.Username
|
||||
@@ -13,7 +13,7 @@ from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from gvsdsdk.minecraft import EasyAuthManager
|
||||
from gvsds.apps.mc.manager import EasyAuthManager
|
||||
from gvsdsdk.auth import RoleManager
|
||||
|
||||
from gvsds.apps.gerp.models import Company
|
||||
@@ -83,7 +83,7 @@ class UserBindView(APIView):
|
||||
# 允许一个用户绑定多个不同玩家(多对多),但不允许重复绑定同一玩家。
|
||||
from gvsdsdk.models import UserRole as _UR
|
||||
from gvsdsdk.model_utils import UUIDObj as _UUIDObj
|
||||
from gvsdsdk.minecraft.models import EasyAuth as _EasyAuthModel
|
||||
from gvsds.apps.mc.models import EasyAuth as _EasyAuthModel
|
||||
_user_role_uuids = list(
|
||||
_UR.objects.filter(UserUUID=_UUIDObj(gvsds_user_uuid).bytes)
|
||||
.values_list('UserRoleUUID', flat=True)
|
||||
|
||||
+50
-80
@@ -1,8 +1,5 @@
|
||||
import re
|
||||
import uuid
|
||||
import datetime
|
||||
import io
|
||||
import bcrypt
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
@@ -10,12 +7,9 @@ from rest_framework import status
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.authentication import TokenAuthentication
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
from PIL import Image
|
||||
|
||||
from gvsds.apps.core.models import User, User2FA, UserAvatar
|
||||
from gvsds.apps.gerp.models import Password
|
||||
from gvsdsdk.model_utils import UUIDObj
|
||||
from gvsds.app_config import getlist as _app_getlist
|
||||
from .serializers import (
|
||||
@@ -113,9 +107,9 @@ class LoginView(APIView):
|
||||
username = serializer.ValidatedData['UserName']
|
||||
password = serializer.ValidatedData['UserPassword']
|
||||
|
||||
try:
|
||||
user = User.objects.get(UserName=username)
|
||||
except User.DoesNotExist:
|
||||
from gvsdsdk.auth.managers import AuthManager
|
||||
user = AuthManager.GetUserByUserName(username)
|
||||
if user is None:
|
||||
return Response({'code': 1, 'msg': '用户名或密码错误'}, status=401)
|
||||
|
||||
if not user.CheckPassword(password):
|
||||
@@ -124,22 +118,18 @@ class LoginView(APIView):
|
||||
if user.UserAccountLicense != 1:
|
||||
return Response({'code': 1, 'msg': '账户已被禁用'}, status=403)
|
||||
|
||||
try:
|
||||
two_fa = User2FA.objects.get(UserUUID=user.UserUUID)
|
||||
if two_fa.IsEnabled == 1:
|
||||
request.session['pending_2fa_user'] = UUIDObj(user.UserUUID).str
|
||||
return Response({
|
||||
'code': 2,
|
||||
'msg': '需要2FA验证',
|
||||
'Require2FA': True,
|
||||
'PendingKey': UUIDObj(user.UserUUID).str,
|
||||
})
|
||||
except User2FA.DoesNotExist:
|
||||
pass
|
||||
requires_2fa, _ = AuthManager.Check2FARequired(user.UserUUID)
|
||||
if requires_2fa:
|
||||
request.session['pending_2fa_user'] = UUIDObj(user.UserUUID).str
|
||||
return Response({
|
||||
'code': 2,
|
||||
'msg': '需要2FA验证',
|
||||
'Require2FA': True,
|
||||
'PendingKey': UUIDObj(user.UserUUID).str,
|
||||
})
|
||||
|
||||
elt = _IssueJwtTokens(user)
|
||||
user.UserLastLoginDate = datetime.datetime.utcnow()
|
||||
user.save(update_fields=['UserLastLoginDate'])
|
||||
AuthManager.UpdateLastLogin(user.UserUUID)
|
||||
|
||||
response = Response({
|
||||
'code': 0,
|
||||
@@ -172,18 +162,11 @@ class RegisterView(APIView):
|
||||
password = serializer.ValidatedData['UserPassword']
|
||||
email = serializer.ValidatedData['UserEmail']
|
||||
|
||||
if User.objects.filter(UserName=username).exists():
|
||||
from gvsdsdk.auth.managers import AuthManager
|
||||
if AuthManager.IsUserNameTaken(username):
|
||||
return Response({'code': 1, 'msg': '用户名已存在'}, status=409)
|
||||
|
||||
user = User(
|
||||
UserUUID=UUIDObj(uuid.uuid4()),
|
||||
UserName=username,
|
||||
UserEmail=email,
|
||||
UserAccountLicense=1,
|
||||
UserPositionStatus=1,
|
||||
)
|
||||
user.SetPassword(password)
|
||||
user.save()
|
||||
user = AuthManager.CreateUser(username, email, password)
|
||||
|
||||
# 为新用户创建默认角色并绑定到系统公司(GERP 后台)
|
||||
from gvsds.apps.gerp.form_service import create_default_role_for_user
|
||||
@@ -191,8 +174,7 @@ class RegisterView(APIView):
|
||||
|
||||
# 注册后自动登录:发放 JWT token
|
||||
elt = _IssueJwtTokens(user)
|
||||
user.UserLastLoginDate = datetime.datetime.utcnow()
|
||||
user.save(update_fields=['UserLastLoginDate'])
|
||||
AuthManager.UpdateLastLogin(user.UserUUID)
|
||||
|
||||
response = Response({
|
||||
'code': 0,
|
||||
@@ -242,14 +224,11 @@ class Verify2FAView(APIView):
|
||||
return Response({'code': 1, 'msg': '验证尝试次数过多,请15分钟后再试'}, status=429)
|
||||
|
||||
code = serializer.ValidatedData['Code']
|
||||
try:
|
||||
two_fa = User2FA.objects.get(UserUUID=UUIDObj(pending_user_uuid).bytes)
|
||||
except User2FA.DoesNotExist:
|
||||
from gvsdsdk.auth.managers import AuthManager
|
||||
enabled, _ = AuthManager.Check2FARequired(pending_user_uuid)
|
||||
if not enabled:
|
||||
return Response({'code': 1, 'msg': '2FA未设置'}, status=400)
|
||||
|
||||
import pyotp
|
||||
totp = pyotp.TOTP(two_fa.SecretKey)
|
||||
if not totp.verify(code, valid_window=1):
|
||||
if not AuthManager.Verify2FA(pending_user_uuid, code):
|
||||
# 验证失败,递增计数器
|
||||
cache.set(rate_limit_key, attempts + 1, self.BLOCK_DURATION_SECONDS)
|
||||
return Response({'code': 1, 'msg': '验证码错误'}, status=400)
|
||||
@@ -259,7 +238,7 @@ class Verify2FAView(APIView):
|
||||
|
||||
del request.session['pending_2fa_user']
|
||||
|
||||
user = User.objects.get(UserUUID=UUIDObj(pending_user_uuid).bytes)
|
||||
user = AuthManager.GetUserByUUID(pending_user_uuid)
|
||||
elt = _IssueJwtTokens(user)
|
||||
|
||||
response = Response({
|
||||
@@ -360,16 +339,15 @@ class GetUserAvatarView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request):
|
||||
UserUUID = request.user.UserUUID
|
||||
try:
|
||||
avatar = UserAvatar.objects.get(UserUUID=UserUUID)
|
||||
content_type = f'image/{avatar.AvatarFileType}'
|
||||
return HttpResponse(avatar.UserAvatar, content_type=content_type)
|
||||
except UserAvatar.DoesNotExist:
|
||||
default = Image.new('RGB', (256, 256), (200, 200, 200))
|
||||
buf = io.BytesIO()
|
||||
default.save(buf, format='PNG')
|
||||
return HttpResponse(buf.getvalue(), content_type='image/png')
|
||||
from gvsdsdk.auth.managers import AuthManager
|
||||
avatar_data = AuthManager.GetAvatar(request.user.UserUUID)
|
||||
if avatar_data is not None:
|
||||
avatar_bytes, file_type = avatar_data
|
||||
return HttpResponse(avatar_bytes, content_type=f'image/{file_type}')
|
||||
default = Image.new('RGB', (256, 256), (200, 200, 200))
|
||||
buf = io.BytesIO()
|
||||
default.save(buf, format='PNG')
|
||||
return HttpResponse(buf.getvalue(), content_type='image/png')
|
||||
|
||||
|
||||
class GetArgsUserAvatarView(APIView):
|
||||
@@ -382,15 +360,15 @@ class GetArgsUserAvatarView(APIView):
|
||||
UserUUID = UUIDObj(user_uuid_str).bytes
|
||||
if not UserUUID:
|
||||
return Response({'code': 1, 'msg': '无效的UserUUID'}, status=400)
|
||||
try:
|
||||
avatar = UserAvatar.objects.get(UserUUID=UserUUID)
|
||||
content_type = f'image/{avatar.AvatarFileType}'
|
||||
return HttpResponse(avatar.UserAvatar, content_type=content_type)
|
||||
except UserAvatar.DoesNotExist:
|
||||
default = Image.new('RGB', (256, 256), (200, 200, 200))
|
||||
buf = io.BytesIO()
|
||||
default.save(buf, format='PNG')
|
||||
return HttpResponse(buf.getvalue(), content_type='image/png')
|
||||
from gvsdsdk.auth.managers import AuthManager
|
||||
avatar_data = AuthManager.GetAvatar(UserUUID)
|
||||
if avatar_data is not None:
|
||||
avatar_bytes, file_type = avatar_data
|
||||
return HttpResponse(avatar_bytes, content_type=f'image/{file_type}')
|
||||
default = Image.new('RGB', (256, 256), (200, 200, 200))
|
||||
buf = io.BytesIO()
|
||||
default.save(buf, format='PNG')
|
||||
return HttpResponse(buf.getvalue(), content_type='image/png')
|
||||
|
||||
|
||||
class GetUserNameView(APIView):
|
||||
@@ -407,12 +385,14 @@ class ModifyPasswordView(APIView):
|
||||
serializer = ModifyPasswordSerializer(data=request.data)
|
||||
serializer.IsValid(raise_exception=True)
|
||||
|
||||
user = request.user
|
||||
if not user.CheckPassword(serializer.ValidatedData['OldPassword']):
|
||||
return Response({'code': 1, 'msg': '原密码错误'}, status=400)
|
||||
|
||||
user.SetPassword(serializer.ValidatedData['NewPassword'])
|
||||
user.save(update_fields=['UserPassword'])
|
||||
from gvsdsdk.auth.managers import AuthManager
|
||||
ok, msg = AuthManager.ChangePassword(
|
||||
request.user.UserUUID,
|
||||
serializer.ValidatedData['OldPassword'],
|
||||
serializer.ValidatedData['NewPassword'],
|
||||
)
|
||||
if not ok:
|
||||
return Response({'code': 1, 'msg': msg}, status=400)
|
||||
return Response({'code': 0, 'msg': '密码修改成功'})
|
||||
|
||||
|
||||
@@ -442,18 +422,8 @@ class ModifyAvatarView(APIView):
|
||||
return Response({'code': 1, 'msg': '图片处理失败'}, status=400)
|
||||
|
||||
detected_type = 'png' if save_format == 'PNG' else 'jpeg'
|
||||
UserUUID = request.user.UserUUID
|
||||
try:
|
||||
avatar = UserAvatar.objects.get(UserUUID=UserUUID)
|
||||
avatar.UserAvatar = avatar_bytes
|
||||
avatar.AvatarFileType = detected_type
|
||||
avatar.save(update_fields=['UserAvatar', 'AvatarFileType', 'AvatarUploadTime'])
|
||||
except UserAvatar.DoesNotExist:
|
||||
UserAvatar.objects.create(
|
||||
UserUUID=UserUUID,
|
||||
UserAvatar=avatar_bytes,
|
||||
AvatarFileType=detected_type,
|
||||
)
|
||||
from gvsdsdk.auth.managers import AuthManager
|
||||
AuthManager.SetAvatar(request.user.UserUUID, avatar_bytes, detected_type)
|
||||
|
||||
return Response({'code': 0, 'msg': '头像修改成功'})
|
||||
|
||||
|
||||
+3
-10
@@ -150,11 +150,6 @@ def _load_order():
|
||||
return _mod
|
||||
|
||||
|
||||
def _load_minecraft():
|
||||
from gvsdsdk import minecraft as _mod
|
||||
return _mod
|
||||
|
||||
|
||||
def _load_models():
|
||||
from gvsdsdk import models as _mod
|
||||
return _mod
|
||||
@@ -219,6 +214,9 @@ _LAZY_SYMBOLS = {
|
||||
'RoleManager': ('auth', 'RoleManager'),
|
||||
'ShopManager': ('auth', 'ShopManager'),
|
||||
'PositionManager': ('auth', 'PositionManager'),
|
||||
'AuthManager': ('auth', 'AuthManager'),
|
||||
'AdminUserManager': ('auth', 'AdminUserManager'),
|
||||
'UserService': ('auth', 'UserService'),
|
||||
|
||||
# ── 分佣引擎 ──────────────────────────────────────────
|
||||
'CommissionEngine': ('commission', 'CommissionEngine'),
|
||||
@@ -264,10 +262,6 @@ _LAZY_SYMBOLS = {
|
||||
'Order': ('order', 'Order'),
|
||||
'OrderItem': ('order', 'OrderItem'),
|
||||
|
||||
# ── Minecraft 插件扩展 ────────────────────────────────
|
||||
'EasyAuth': ('minecraft', 'EasyAuth'),
|
||||
'EasyAuthManager': ('minecraft', 'EasyAuthManager'),
|
||||
|
||||
# ── 数据模型 ──────────────────────────────────────────
|
||||
'User': ('models', 'User'),
|
||||
'Role': ('models', 'Role'),
|
||||
@@ -310,7 +304,6 @@ _LOADERS = {
|
||||
'commission': _load_commission,
|
||||
'payment': _load_payment,
|
||||
'order': _load_order,
|
||||
'minecraft': _load_minecraft,
|
||||
'models': _load_models,
|
||||
'fluent': _load_fluent,
|
||||
'model_base': _load_model_base,
|
||||
|
||||
@@ -6,7 +6,7 @@ __all__ = [
|
||||
'IsAdminUser', 'IsCompanyMember', 'CompanyScopedMixin', 'TenantScopedMixin', 'UserScopedMixin', 'PermissionVerifier',
|
||||
'IsAdminScopeDisabled', 'IsTenantScopeDisabled',
|
||||
'IsAdmin', 'IsSuperAdmin', 'IsTenantAdmin',
|
||||
'RoleManager',
|
||||
'RoleManager', 'ShopManager', 'PositionManager', 'AuthManager', 'AdminUserManager', 'UserService',
|
||||
]
|
||||
|
||||
|
||||
@@ -30,8 +30,14 @@ def __getattr__(name):
|
||||
val = getattr(_m, name)
|
||||
globals()[name] = val
|
||||
return val
|
||||
if name == 'RoleManager':
|
||||
from gvsdsdk.auth.managers import RoleManager
|
||||
globals()['RoleManager'] = RoleManager
|
||||
return RoleManager
|
||||
if name in ('RoleManager', 'ShopManager', 'PositionManager', 'AuthManager', 'AdminUserManager'):
|
||||
from gvsdsdk.auth import managers as _m
|
||||
val = getattr(_m, name)
|
||||
globals()[name] = val
|
||||
return val
|
||||
if name == 'UserService':
|
||||
from gvsdsdk.auth import user_service as _m
|
||||
val = getattr(_m, name)
|
||||
globals()[name] = val
|
||||
return val
|
||||
raise AttributeError(f"module 'gvsdsdk.auth' has no attribute {name!r}")
|
||||
+438
-2
@@ -1,8 +1,9 @@
|
||||
import uuid
|
||||
from django.utils import timezone as _timezone
|
||||
|
||||
from gvsdsdk.models import Role, UserRole, RolePermission, Permission, Position, PositionTemplate, PositionTemplatePermission
|
||||
from gvsdsdk.model_utils import _GetUserModel
|
||||
from gvsdsdk.models import Role, UserRole, RolePermission, Permission, Position, PositionTemplate, PositionTemplatePermission, Shop, RoleShop, User2FA, UserAvatar, Password
|
||||
from gvsdsdk.auth.crypto import HashPassword, VerifyPassword
|
||||
from gvsdsdk.model_utils import _GetUserModel, UUIDObj
|
||||
|
||||
# NOTE: 类型注解暂缓——RoleManager/PositionManager 静态方法较多,全量补充注解工作量较大且易引入回归,后续单独迭代处理。
|
||||
|
||||
@@ -167,6 +168,69 @@ class RoleManager:
|
||||
qs = qs.filter(CompanyUUID=CompanyUUID)
|
||||
return qs.exists()
|
||||
|
||||
@staticmethod
|
||||
def SaveRolePermissions(RoleUUID, CompanyUUID, PermUUIDs, Commit=True):
|
||||
"""批量保存角色权限——按 RoleUUID+CompanyUUID 维度增量更新
|
||||
|
||||
逻辑:不在目标列表中的现有关联删除;目标列表中未关联的新增;已存在的保留。
|
||||
|
||||
Args:
|
||||
RoleUUID: 角色 UUID(bytes/str/UUIDObj 均可)
|
||||
CompanyUUID: 公司 UUID
|
||||
PermUUIDs: 目标权限 UUID 列表(bytes/str 均可,内部去重并过滤无效项)
|
||||
Commit: 是否包在事务中提交(默认 True)
|
||||
|
||||
Returns:
|
||||
(Added, Removed, Kept, Total) 统计元组
|
||||
"""
|
||||
target_perm_uuids = set()
|
||||
for p in PermUUIDs or []:
|
||||
try:
|
||||
target_perm_uuids.add(UUIDObj(p).bytes)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
valid_perm_uuids = set(
|
||||
Permission.objects.filter(PermUUID__in=target_perm_uuids)
|
||||
.values_list('PermUUID', flat=True)
|
||||
) if target_perm_uuids else set()
|
||||
|
||||
def _apply():
|
||||
existing_qs = RolePermission.objects.filter(
|
||||
RoleUUID=RoleUUID, CompanyUUID=CompanyUUID,
|
||||
)
|
||||
existing_perm_uuids = set(existing_qs.values_list('PermUUID', flat=True))
|
||||
|
||||
to_delete = existing_perm_uuids - valid_perm_uuids
|
||||
if to_delete:
|
||||
existing_qs.filter(PermUUID__in=to_delete).delete()
|
||||
|
||||
to_add = valid_perm_uuids - existing_perm_uuids
|
||||
new_objs = [
|
||||
RolePermission(
|
||||
RolePermUUID=uuid.uuid4().bytes,
|
||||
RoleUUID=RoleUUID,
|
||||
PermUUID=p_uuid,
|
||||
CompanyUUID=CompanyUUID,
|
||||
)
|
||||
for p_uuid in to_add
|
||||
]
|
||||
if new_objs:
|
||||
RolePermission.objects.bulk_create(new_objs)
|
||||
|
||||
return (
|
||||
len(to_add),
|
||||
len(to_delete),
|
||||
len(existing_perm_uuids & valid_perm_uuids),
|
||||
len(valid_perm_uuids),
|
||||
)
|
||||
|
||||
if Commit:
|
||||
from django.db import transaction
|
||||
with transaction.atomic():
|
||||
return _apply()
|
||||
return _apply()
|
||||
|
||||
|
||||
class PositionManager:
|
||||
"""职务管理器——提供人事职务标签创建、岗位模板管理"""
|
||||
@@ -332,3 +396,375 @@ class PositionManager:
|
||||
).values_list('PermUUID', flat=True)
|
||||
return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1)
|
||||
|
||||
@staticmethod
|
||||
def HasTemplatePerm(TemplateUUID, PermUUID):
|
||||
"""检查岗位模板是否已关联指定权限"""
|
||||
return PositionTemplatePermission.objects.filter(
|
||||
PositionTemplateUUID=TemplateUUID, PermUUID=PermUUID,
|
||||
).exists()
|
||||
|
||||
|
||||
class ShopManager:
|
||||
"""商铺管理器——商铺实体与角色-商铺关联管理"""
|
||||
|
||||
@staticmethod
|
||||
def CreateShop(Name, CompanyUUID, Code='', ShopType=0,
|
||||
Address='', Contact='', Phone='', Desc=''):
|
||||
"""创建商铺
|
||||
|
||||
Args:
|
||||
Name: 商铺名称
|
||||
CompanyUUID: 所属公司 UUID(UUIDObj/str/bytes 均可)
|
||||
Code: 商铺编码
|
||||
ShopType: 商铺类型(0=默认)
|
||||
Address: 商铺地址
|
||||
Contact: 联系人
|
||||
Phone: 联系电话
|
||||
Desc: 描述
|
||||
|
||||
Returns:
|
||||
Shop 实例
|
||||
"""
|
||||
shop = Shop(
|
||||
ShopUUID=uuid.uuid4().bytes,
|
||||
CompanyUUID=CompanyUUID,
|
||||
ShopName=Name,
|
||||
ShopCode=Code,
|
||||
ShopType=ShopType,
|
||||
ShopStatus=1,
|
||||
ShopAddress=Address,
|
||||
ShopContact=Contact,
|
||||
ShopPhone=Phone,
|
||||
ShopDesc=Desc,
|
||||
CreateTime=_timezone.now(),
|
||||
UpdateTime=_timezone.now(),
|
||||
)
|
||||
shop.save()
|
||||
return shop
|
||||
|
||||
@staticmethod
|
||||
def GetShopByUUID(ShopUUID):
|
||||
"""按 UUID 查询商铺(不存在返回 None)"""
|
||||
return Shop.objects.filter(ShopUUID=ShopUUID).first()
|
||||
|
||||
@staticmethod
|
||||
def SearchShops(Keyword='', CompanyUUID=None, Limit=20):
|
||||
"""搜索商铺(按名称模糊匹配,可选公司过滤,仅启用)
|
||||
|
||||
Args:
|
||||
Keyword: 商铺名称关键字
|
||||
CompanyUUID: 可选,限定公司范围
|
||||
Limit: 返回条数上限
|
||||
|
||||
Returns:
|
||||
QuerySet[Shop]
|
||||
"""
|
||||
qs = Shop.objects.filter(ShopStatus=1)
|
||||
if Keyword:
|
||||
qs = qs.filter(ShopName__icontains=Keyword)
|
||||
if CompanyUUID is not None:
|
||||
qs = qs.filter(CompanyUUID=CompanyUUID)
|
||||
return qs[:Limit]
|
||||
|
||||
@staticmethod
|
||||
def GetShopsForRole(RoleUUID):
|
||||
"""获取角色关联的所有商铺"""
|
||||
shop_uuids = RoleShop.objects.filter(
|
||||
RoleUUID=RoleUUID,
|
||||
).values_list('ShopUUID', flat=True)
|
||||
return Shop.objects.filter(ShopUUID__in=shop_uuids)
|
||||
|
||||
@staticmethod
|
||||
def GetMainShopForRole(RoleUUID):
|
||||
"""获取角色的主商铺(IsMainShop=1;不存在返回 None)"""
|
||||
rs = RoleShop.objects.filter(RoleUUID=RoleUUID, IsMainShop=1).first()
|
||||
if not rs:
|
||||
return None
|
||||
return Shop.objects.filter(ShopUUID=rs.ShopUUID).first()
|
||||
|
||||
@staticmethod
|
||||
def AssignRole(RoleUUID, ShopUUID, IsMainShop=0):
|
||||
"""为角色关联商铺
|
||||
|
||||
Args:
|
||||
RoleUUID: 角色 UUID
|
||||
ShopUUID: 商铺 UUID
|
||||
IsMainShop: 是否设为主商铺(1=是)
|
||||
|
||||
Returns:
|
||||
RoleShop 实例
|
||||
"""
|
||||
rs = RoleShop(
|
||||
RoleShopUUID=uuid.uuid4().bytes,
|
||||
RoleUUID=RoleUUID,
|
||||
ShopUUID=ShopUUID,
|
||||
IsMainShop=IsMainShop,
|
||||
CreateTime=_timezone.now(),
|
||||
)
|
||||
rs.save()
|
||||
return rs
|
||||
|
||||
@staticmethod
|
||||
def RemoveRole(RoleUUID, ShopUUID):
|
||||
"""移除角色与商铺的关联;返回是否命中"""
|
||||
deleted, _ = RoleShop.objects.filter(
|
||||
RoleUUID=RoleUUID, ShopUUID=ShopUUID,
|
||||
).delete()
|
||||
return deleted > 0
|
||||
|
||||
|
||||
class AuthManager:
|
||||
"""用户认证管理器——账号查询、创建、密码、2FA 与头像"""
|
||||
|
||||
@staticmethod
|
||||
def GetUserByUserName(UserName):
|
||||
"""按用户名查询用户(不存在返回 None)"""
|
||||
if not UserName:
|
||||
return None
|
||||
User = _GetUserModel()
|
||||
return User.objects.filter(UserName=UserName).first()
|
||||
|
||||
@staticmethod
|
||||
def GetUserByUUID(UserUUID):
|
||||
"""按 UUID 查询用户(不存在返回 None)"""
|
||||
if not UserUUID:
|
||||
return None
|
||||
User = _GetUserModel()
|
||||
return User.objects.filter(UserUUID=UUIDObj(UserUUID).bytes).first()
|
||||
|
||||
@staticmethod
|
||||
def IsUserNameTaken(UserName):
|
||||
"""用户名是否已被占用"""
|
||||
if not UserName:
|
||||
return False
|
||||
User = _GetUserModel()
|
||||
return User.objects.filter(UserName=UserName).exists()
|
||||
|
||||
@staticmethod
|
||||
def CreateUser(UserName, Email, Password):
|
||||
"""创建用户(默认启用账户、在职)并设置 bcrypt 密码
|
||||
|
||||
Args:
|
||||
UserName: 用户名
|
||||
Email: 邮箱
|
||||
Password: 明文密码(内部做 bcrypt 哈希)
|
||||
|
||||
Returns:
|
||||
User 实例
|
||||
"""
|
||||
User = _GetUserModel()
|
||||
user = User(
|
||||
UserUUID=UUIDObj(uuid.uuid4()),
|
||||
UserName=UserName,
|
||||
UserEmail=Email,
|
||||
UserAccountLicense=1,
|
||||
UserPositionStatus=1,
|
||||
)
|
||||
user.SetPassword(Password)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def Check2FARequired(UserUUID):
|
||||
"""检查用户是否启用 2FA
|
||||
|
||||
Args:
|
||||
UserUUID: 用户 UUID(bytes/str/UUIDObj 均可)
|
||||
|
||||
Returns:
|
||||
(是否启用, User2FA 实例或 None)
|
||||
"""
|
||||
two_fa = User2FA.objects.filter(UserUUID=UUIDObj(UserUUID).bytes).first()
|
||||
if two_fa is None:
|
||||
return False, None
|
||||
return two_fa.IsEnabled == 1, two_fa
|
||||
|
||||
@staticmethod
|
||||
def Verify2FA(UserUUID, Code):
|
||||
"""校验 TOTP 2FA 验证码;返回是否匹配"""
|
||||
enabled, two_fa = AuthManager.Check2FARequired(UserUUID)
|
||||
if not enabled or two_fa is None or not two_fa.SecretKey or not Code:
|
||||
return False
|
||||
import pyotp
|
||||
return pyotp.TOTP(two_fa.SecretKey).verify(Code, valid_window=1)
|
||||
|
||||
@staticmethod
|
||||
def UpdateLastLogin(UserUUID):
|
||||
"""更新用户最后登录时间"""
|
||||
user = AuthManager.GetUserByUUID(UserUUID)
|
||||
if user is None:
|
||||
return
|
||||
user.UserLastLoginDate = _timezone.now()
|
||||
user.save(update_fields=['UserLastLoginDate'])
|
||||
|
||||
@staticmethod
|
||||
def ChangePassword(UserUUID, OldPassword, NewPassword):
|
||||
"""修改密码
|
||||
|
||||
Args:
|
||||
UserUUID: 用户 UUID
|
||||
OldPassword: 原密码(明文)
|
||||
NewPassword: 新密码(明文)
|
||||
|
||||
Returns:
|
||||
(成功?, 消息)
|
||||
"""
|
||||
user = AuthManager.GetUserByUUID(UserUUID)
|
||||
if user is None:
|
||||
return False, '用户不存在'
|
||||
if not user.CheckPassword(OldPassword):
|
||||
return False, '原密码错误'
|
||||
user.SetPassword(NewPassword)
|
||||
user.save(update_fields=['UserPassword'])
|
||||
return True, '密码修改成功'
|
||||
|
||||
@staticmethod
|
||||
def GetAvatar(UserUUID):
|
||||
"""获取用户头像
|
||||
|
||||
Args:
|
||||
UserUUID: 用户 UUID(bytes/str/UUIDObj 均可)
|
||||
|
||||
Returns:
|
||||
(头像 bytes, 文件类型);未设置返回 None
|
||||
"""
|
||||
avatar = UserAvatar.objects.filter(UserUUID=UUIDObj(UserUUID).bytes).first()
|
||||
if avatar is None:
|
||||
return None
|
||||
return avatar.UserAvatar, avatar.AvatarFileType
|
||||
|
||||
@staticmethod
|
||||
def SetAvatar(UserUUID, AvatarBytes, FileType):
|
||||
"""设置用户头像(更新或创建)
|
||||
|
||||
Args:
|
||||
UserUUID: 用户 UUID
|
||||
AvatarBytes: 头像图片二进制
|
||||
FileType: 文件类型(png/jpeg)
|
||||
|
||||
Returns:
|
||||
UserAvatar 实例
|
||||
"""
|
||||
avatar, _ = UserAvatar.objects.get_or_create(
|
||||
UserUUID=UUIDObj(UserUUID).bytes,
|
||||
)
|
||||
avatar.UserAvatar = AvatarBytes
|
||||
avatar.AvatarFileType = FileType
|
||||
avatar.save(update_fields=['UserAvatar', 'AvatarFileType', 'AvatarUploadTime'])
|
||||
return avatar
|
||||
|
||||
|
||||
class AdminUserManager:
|
||||
"""管理端用户管理器——用户搜索、密码重置、账户状态与邮箱管理"""
|
||||
|
||||
@staticmethod
|
||||
def SearchUsers(Keyword='', Offset=0, PageSize=20):
|
||||
"""搜索用户(按用户名模糊匹配,分页)
|
||||
|
||||
Args:
|
||||
Keyword: 用户名关键字
|
||||
Offset: 偏移量
|
||||
PageSize: 每页条数
|
||||
|
||||
Returns:
|
||||
(list[dict], total)
|
||||
"""
|
||||
User = _GetUserModel()
|
||||
qs = User.objects.all()
|
||||
if Keyword:
|
||||
qs = qs.filter(UserName__icontains=Keyword)
|
||||
total = qs.count()
|
||||
users = qs[Offset:Offset + PageSize]
|
||||
records = []
|
||||
for u in users:
|
||||
records.append({
|
||||
'UserUUID': UUIDObj(u.UserUUID).str,
|
||||
'UserName': u.UserName,
|
||||
'UserEmail': u.UserEmail or '',
|
||||
'UserEmailVerify': u.UserEmailVerify,
|
||||
'UserGender': u.UserGender,
|
||||
'UserAccountLicense': u.UserAccountLicense,
|
||||
'UserPositionStatus': u.UserPositionStatus,
|
||||
})
|
||||
return records, total
|
||||
|
||||
@staticmethod
|
||||
def GetUserByUUID(UserUUID):
|
||||
"""按 UUID 查询用户(不存在返回 None)"""
|
||||
return AuthManager.GetUserByUUID(UserUUID)
|
||||
|
||||
@staticmethod
|
||||
def ResetPassword(UserUUID, NewPassword):
|
||||
"""重置用户密码(写入独立 Password 表,bcrypt 哈希)
|
||||
|
||||
Returns:
|
||||
(成功?, 消息)
|
||||
"""
|
||||
user = AuthManager.GetUserByUUID(UserUUID)
|
||||
if user is None:
|
||||
return False, '用户不存在'
|
||||
pw_obj, _ = Password.objects.get_or_create(UserUUID=user.UserUUID)
|
||||
pw_obj.Password = HashPassword(NewPassword)
|
||||
pw_obj.save(update_fields=['Password'])
|
||||
return True, '密码重置成功'
|
||||
|
||||
@staticmethod
|
||||
def VerifyPassword(UserUUID, Password):
|
||||
"""校验用户密码(独立 Password 表)
|
||||
|
||||
Returns:
|
||||
(用户记录存在?, 密码是否匹配)
|
||||
"""
|
||||
pw_obj = Password.objects.filter(UserUUID=UUIDObj(UserUUID).bytes).first()
|
||||
if pw_obj is None:
|
||||
return False, False
|
||||
try:
|
||||
return True, VerifyPassword(Password, pw_obj.Password)
|
||||
except (ValueError, TypeError):
|
||||
return True, False
|
||||
|
||||
@staticmethod
|
||||
def SetAccountLicense(UserUUID, License):
|
||||
"""设置账户启用状态(License=1 启用,0 禁用)
|
||||
|
||||
Returns:
|
||||
(成功?, 消息)
|
||||
"""
|
||||
user = AuthManager.GetUserByUUID(UserUUID)
|
||||
if user is None:
|
||||
return False, '用户不存在'
|
||||
user.UserAccountLicense = License
|
||||
user.save(update_fields=['UserAccountLicense'])
|
||||
return True, '账户已启用' if License == 1 else '账户已禁用'
|
||||
|
||||
@staticmethod
|
||||
def SetUserEmail(UserUUID, Email):
|
||||
"""设置用户邮箱
|
||||
|
||||
Returns:
|
||||
(成功?, 消息)
|
||||
"""
|
||||
user = AuthManager.GetUserByUUID(UserUUID)
|
||||
if user is None:
|
||||
return False, '用户不存在'
|
||||
user.UserEmail = Email
|
||||
user.save(update_fields=['UserEmail'])
|
||||
return True, '邮箱验证已发送'
|
||||
|
||||
@staticmethod
|
||||
def Get2FAInfo(UserUUID):
|
||||
"""查询用户 2FA 信息(不含 SecretKey,避免密钥泄露)
|
||||
|
||||
Returns:
|
||||
dict;未配置返回 None
|
||||
"""
|
||||
two_fa = User2FA.objects.filter(UserUUID=UUIDObj(UserUUID).bytes).first()
|
||||
if two_fa is None:
|
||||
return None
|
||||
return {
|
||||
'IsEnabled': two_fa.IsEnabled,
|
||||
'Verified': two_fa.Verified,
|
||||
'LastUsedAt': two_fa.LastUsedAt.isoformat() if two_fa.LastUsedAt else None,
|
||||
'CreateTime': two_fa.CreateTime.isoformat() if two_fa.CreateTime else None,
|
||||
}
|
||||
|
||||
@@ -166,6 +166,54 @@ class CommissionEngine:
|
||||
logger.info(f'费率表已创建: {table_code}@tenant={self.TenantUUID}')
|
||||
return table
|
||||
|
||||
def ListRules(self, event_type=None):
|
||||
"""列出租户下的分佣规则(仅启用,按优先级降序)
|
||||
|
||||
Args:
|
||||
event_type: 可选,按事件类型过滤
|
||||
|
||||
Returns:
|
||||
QuerySet[CommissionRule]
|
||||
"""
|
||||
qs = CommissionRule.objects.filter(
|
||||
TenantUUID=self.TenantUUID, RuleStatus=1,
|
||||
)
|
||||
if event_type:
|
||||
qs = qs.filter(EventType=event_type)
|
||||
return qs.order_by('-RulePriority')
|
||||
|
||||
def DisableRule(self, rule_uuid):
|
||||
"""软禁用分佣规则(RuleStatus=0);返回是否命中"""
|
||||
updated = CommissionRule.objects.filter(
|
||||
TenantUUID=self.TenantUUID, RuleUUID=rule_uuid,
|
||||
).update(RuleStatus=0)
|
||||
return updated > 0
|
||||
|
||||
def ListRateTables(self):
|
||||
"""列出租户下的费率表(仅启用)
|
||||
|
||||
Returns:
|
||||
QuerySet[RateTable]
|
||||
"""
|
||||
return RateTable.objects.filter(
|
||||
TenantUUID=self.TenantUUID, TableStatus=1,
|
||||
)
|
||||
|
||||
def ListEvents(self, event_type=None, limit=50):
|
||||
"""列出租户下的分佣事件(按创建时间倒序)
|
||||
|
||||
Args:
|
||||
event_type: 可选,按事件类型过滤
|
||||
limit: 返回条数上限
|
||||
|
||||
Returns:
|
||||
QuerySet[CommissionEvent]
|
||||
"""
|
||||
qs = CommissionEvent.objects.filter(TenantUUID=self.TenantUUID)
|
||||
if event_type:
|
||||
qs = qs.filter(EventType=event_type)
|
||||
return qs.order_by('-CreateTime')[:limit]
|
||||
|
||||
def Dispatch(self, event_type, event_key, total_amount,
|
||||
source_user_uuid=None, participants=None,
|
||||
tier_index=0, payload=None, auto_settle=True):
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
"""gvsdsdk.minecraft — Minecraft 服务器插件扩展模块
|
||||
|
||||
提供 MC 插件共享表的 ORM 映射与认证服务:
|
||||
|
||||
- ``EasyAuth``:EasyAuth 插件认证表(managed=False,外部 schema)
|
||||
- ``EasyAuthManager``:游戏账号密码验证、查询、绑定标记管理
|
||||
|
||||
注:本模块的模型 ``app_label='mc'``,在主服务器(gvsds)侧通过
|
||||
``gvsds/db_router.py`` 路由到 minecraft 库;子服务器一般不需要访问。
|
||||
"""
|
||||
|
||||
from .models import EasyAuth
|
||||
from .auth import EasyAuthManager
|
||||
|
||||
__all__ = [
|
||||
'EasyAuth',
|
||||
'EasyAuthManager',
|
||||
]
|
||||
@@ -1,44 +0,0 @@
|
||||
"""模块:gvsdsdk.minecraft.models — MC 插件共享表 ORM 映射
|
||||
|
||||
当前仅包含 ``EasyAuth`` 表(EasyAuth 插件认证表)。
|
||||
|
||||
设计要点:
|
||||
- ``app_label='mc'``:模型归属 gvsds.apps.mc app,由 gvsds 主服务器的
|
||||
``gvsds/db_router.py`` 路由到 minecraft 库
|
||||
- ``managed=False``:表由 MC 插件管理,Django 不参与迁移
|
||||
- ``UserRoleUUID`` 列由本系统 ALTER 添加(BINARY(16) 可空),
|
||||
存绑定后创建的 ``UserRole`` 关联 UUID
|
||||
"""
|
||||
|
||||
from gvsdsdk.model_base import QModel
|
||||
from gvsdsdk.models import UUIDField
|
||||
from django.db import models
|
||||
|
||||
|
||||
class EasyAuth(QModel):
|
||||
"""EasyAuth 认证表——MC EasyAuth 插件的玩家凭据
|
||||
|
||||
映射真实表 ``easyauth``(managed=False)。``data`` 列为 JSON,
|
||||
其中 ``password`` 字段为 bcrypt hash(``$2a$12$...``),
|
||||
验证时使用 ``bcrypt.checkpw``。
|
||||
|
||||
``UserRoleUUID`` 列由本系统 ALTER 添加(BINARY(16) 可空),
|
||||
存绑定后创建的 ``UserRole`` 关联 UUID,用于 MC 插件侧反查 GVSDS 身份。
|
||||
"""
|
||||
|
||||
AuthID = models.AutoField(primary_key=True, db_column='id', verbose_name='认证ID')
|
||||
Username = models.CharField(max_length=255, unique=True, db_column='username', verbose_name='玩家名')
|
||||
UsernameLower = models.CharField(max_length=255, db_column='username_lower', verbose_name='玩家名小写')
|
||||
PlayerUUID = models.CharField(max_length=255, null=True, blank=True, db_column='uuid', verbose_name='MC玩家UUID字符串')
|
||||
Data = models.JSONField(db_column='data', verbose_name='插件数据')
|
||||
UserRoleUUID = UUIDField(null=True, blank=True, db_column='UserRoleUUID', verbose_name='绑定用户角色关联UUID')
|
||||
|
||||
class Meta:
|
||||
db_table = 'easyauth'
|
||||
app_label = 'mc'
|
||||
managed = False
|
||||
verbose_name = 'EasyAuth认证'
|
||||
verbose_name_plural = 'EasyAuth认证'
|
||||
|
||||
def __str__(self):
|
||||
return self.Username
|
||||
@@ -160,6 +160,49 @@ class PaymentManager:
|
||||
logger.info(f'支付网关配置: {platform}@tenant={self.TenantUUID}')
|
||||
return config
|
||||
|
||||
def ListConfigs(self, status=None):
|
||||
"""列出租户下的支付网关配置(默认按默认渠道优先、平台名排序)
|
||||
|
||||
Args:
|
||||
status: 可选,按配置状态过滤(1=启用,0=禁用)
|
||||
|
||||
Returns:
|
||||
QuerySet[PaymentGatewayConfig]
|
||||
"""
|
||||
qs = PaymentGatewayConfig.objects.filter(
|
||||
TenantUUID=self.TenantUUID,
|
||||
)
|
||||
if status is not None:
|
||||
qs = qs.filter(ConfigStatus=status)
|
||||
return qs.order_by('-ConfigDefault', 'Platform')
|
||||
|
||||
def GetConfigByUUID(self, config_uuid):
|
||||
"""按 ConfigUUID 查询租户下的配置(不存在返回 None)"""
|
||||
return PaymentGatewayConfig.objects.filter(
|
||||
ConfigUUID=config_uuid, TenantUUID=self.TenantUUID,
|
||||
).first()
|
||||
|
||||
def DisableConfig(self, platform):
|
||||
"""禁用租户指定平台的配置(ConfigStatus=0 并清空更新时间);返回是否命中"""
|
||||
updated = PaymentGatewayConfig.objects.filter(
|
||||
TenantUUID=self.TenantUUID, Platform=platform,
|
||||
).update(ConfigStatus=0, UpdateTime=None)
|
||||
return updated > 0
|
||||
|
||||
def SetConfigStatus(self, config_uuid, status):
|
||||
"""更新租户指定配置的状态;返回是否命中"""
|
||||
updated = PaymentGatewayConfig.objects.filter(
|
||||
ConfigUUID=config_uuid, TenantUUID=self.TenantUUID,
|
||||
).update(ConfigStatus=status)
|
||||
return updated > 0
|
||||
|
||||
def DeleteConfig(self, config_uuid):
|
||||
"""删除租户指定配置;返回是否命中"""
|
||||
deleted, _ = PaymentGatewayConfig.objects.filter(
|
||||
ConfigUUID=config_uuid, TenantUUID=self.TenantUUID,
|
||||
).delete()
|
||||
return deleted > 0
|
||||
|
||||
def Pay(self, platform, amount, subject='', body='',
|
||||
method='jsapi', open_id='', client_ip='',
|
||||
extra=None, expire_minutes=30):
|
||||
|
||||
Reference in new issue
Block a user