"feat(auth): add ShopManager/AuthManager/AdminUserManager/UserService & SaveRolePermissions

- ShopManager: shop CRUD + role-shop associations (fixes dangling ShopManager export)
- AuthManager: user lookup/create/password/2FA/avatar
- AdminUserManager: user search/reset-password/license/email/2FA info
- UserService.GetUserDetail: aggregate user+roles+permissions+orders
- RoleManager.SaveRolePermissions: atomic incremental role-permission sync
- remove gvsdsdk.minecraft (MC is custom business, moved to gvsds)"
This commit is contained in:
TermiNexus committed 2026-08-07 03:12:18 +08:00
1 parent f6842ee3c8
commit a006d577c8
14 files changed
+552 -225

No files matched your search

+3 -10
View File
@@ -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,
Binary file not shown.
+11 -5
View File
@@ -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}")
Binary file not shown.
Binary file not shown.
Binary file not shown.
+431 -2
View File
@@ -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:
"""职务管理器——提供人事职务标签创建、岗位模板管理"""
@@ -339,3 +403,368 @@ class PositionManager:
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,
}
+107
View File
@@ -0,0 +1,107 @@
"""gvsdsdk.auth.user_service — 用户信息服务
聚合用户基本信息、角色、权限与订单,供管理端用户详情展示。
角色权限语义与 ``RoleManager`` 保持一致;公司名称等 SAAS 专属数据
由调用方(gvsds 视图层)自行补充。
"""
from gvsdsdk.model_utils import UUIDObj
from gvsdsdk.auth.managers import AuthManager, RoleManager
class UserService:
"""用户信息服务——聚合用户、角色、权限、订单信息"""
@staticmethod
def GetUserDetail(UserUUID):
"""聚合查询用户详情
Args:
UserUUID: 用户 UUID(bytes/str/UUIDObj 均可)
Returns:
dict,含 ``User``/``Roles``/``Permissions``/``Orders`` 四段;
用户不存在返回 ``None``
"""
user = AuthManager.GetUserByUUID(UserUUID)
if user is None:
return None
user_info = {
'UserUUID': UUIDObj(user.UserUUID).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,
}
# 角色列表(保留行级信息:CompanyUUID、RoleName 覆盖)
roles = []
user_roles = RoleManager.GetUserRoleDetails(user.UserUUID)
for ur in user_roles:
role = RoleManager.GetRoleByUUID(ur.RoleUUID)
if role is None:
continue
roles.append({
'RoleUUID': UUIDObj(role.RoleUUID).str if role.RoleUUID else '',
'RoleName': ur.RoleName or role.RoleName,
'RoleType': role.RoleType,
'RoleStatus': role.RoleStatus,
'AssignScope': role.AssignScope,
'RoleDesc': role.RoleDesc or '',
'CompanyUUID': UUIDObj(ur.CompanyUUID).str if ur.CompanyUUID else '',
})
# 权限列表(按 PermCode 去重,仅启用权限)
permissions = []
seen = set()
for ur in user_roles:
if not ur.RoleUUID:
continue
for perm in RoleManager.GetPermissionsForRole(ur.RoleUUID):
key = perm.PermCode
if key in seen:
continue
seen.add(key)
permissions.append({
'PermName': perm.PermName,
'PermCode': perm.PermCode,
'PermCategory': perm.PermCategory or '',
'PermDesc': perm.PermDesc or '',
})
# 订单列表(最近 20 条,表不存在时静默跳过)
orders = []
try:
from gvsdsdk.order.models import Order
user_orders = Order.objects.filter(
BuyerUserUUID=user.UserUUID,
).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 # Order 表可能不存在
return {
'User': user_info,
'Roles': roles,
'Permissions': permissions,
'Orders': orders,
}
-18
View File
@@ -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',
]
Binary file not shown.
Binary file not shown.
Binary file not shown.
-146
View File
@@ -1,146 +0,0 @@
"""模块:gvsdsdk.minecraft.auth — EasyAuth 插件认证与绑定管理
提供:
- ``VerifyPassword``:使用 bcrypt 校验游戏账号密码
- ``EasyAuthManager``:游戏账号查询、密码验证、绑定标记读写
"""
import bcrypt
def VerifyPassword(plain_password: str, bcrypt_hash: str) -> bool:
"""校验明文密码与 bcrypt hash 是否匹配
Args:
plain_password: 玩家输入的明文密码
bcrypt_hash: ``easyauth.data.password`` 中存储的 bcrypt hash 字符串
Returns:
True 如果密码匹配;False 如果不匹配或 hash 格式异常
"""
if not plain_password or not bcrypt_hash:
return False
try:
return bcrypt.checkpw(plain_password.encode('utf-8'), bcrypt_hash.encode('utf-8'))
except (ValueError, TypeError):
return False
class EasyAuthManager:
"""EasyAuth 认证管理器——游戏账号查询、密码验证、绑定标记读写"""
@staticmethod
def GetByUsername(username: str):
"""按游戏名(不区分大小写)查询 EasyAuth 记录
Args:
username: 游戏名(原始大小写均可)
Returns:
``EasyAuth`` 实例;不存在则返回 ``None``
"""
from .models import EasyAuth
if not username:
return None
return EasyAuth.objects.filter(UsernameLower=username.lower()).first()
@staticmethod
def VerifyPassword(easyauth, plain_password: str) -> bool:
"""校验 EasyAuth 记录的密码
Args:
easyauth: ``EasyAuth`` 实例
plain_password: 明文密码
Returns:
True 如果密码匹配;False 如果不匹配、未设置密码或 hash 异常
"""
if easyauth is None:
return False
data = easyauth.Data or {}
password_hash = data.get('password') or ''
return VerifyPassword(plain_password, password_hash)
@staticmethod
def IsBound(easyauth) -> bool:
"""判断该游戏账号是否已绑定 GVSDS 用户"""
return easyauth is not None and easyauth.UserRoleUUID is not None
@staticmethod
def MarkBound(easyauth, user_role_uuid) -> int:
"""将 UserRoleUUID 写入 easyauth 作为绑定标记
使用乐观锁(``UserRoleUUID__isnull=True``)防止并发重复绑定。
Args:
easyauth: ``EasyAuth`` 实例
user_role_uuid: 新创建的 UserRole 关联 UUID(str / bytes / UUIDObj 均可)
Returns:
实际更新行数:1 成功,0 表示已被其他请求抢先绑定
"""
from .models import EasyAuth
if easyauth is None:
return 0
return EasyAuth.objects.filter(
AuthID=easyauth.AuthID,
UserRoleUUID__isnull=True,
).update(UserRoleUUID=user_role_uuid)
@staticmethod
def ClearBound(easyauth) -> int:
"""清空 easyauth 的 UserRoleUUID(用于解绑场景)"""
from .models import EasyAuth
if easyauth is None:
return 0
return EasyAuth.objects.filter(
AuthID=easyauth.AuthID,
).update(UserRoleUUID=None)
@staticmethod
def GetPlayerUUIDByGVSDSUser(gvsds_user_uuid):
"""GVSDS UserUUID → MC 玩家 UUID 字符串(varchar36)
反查路径:GVSDS UserUUID → 该用户的 UserRoleUUID 集合 →
easyauth 表中 UserRoleUUID 在该集合内的记录 → PlayerUUID。
与 MC 绑定流程(``UserBindView``)配套:绑定时会把新建的
UserRoleUUID 写入 easyauth.UserRoleUUID 列作为绑定标记,
因此反过来即可定位该 GVSDS 用户绑定的 MC 玩家。
Args:
gvsds_user_uuid: GVDS 用户 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
# 该 GVSDS 用户名下的所有 UserRoleUUID(跨公司/角色)
user_role_uuids = list(
UserRole.objects.filter(UserUUID=raw_bytes).values_list('UserRoleUUID', flat=True)
)
if not user_role_uuids:
return None
# easyauth 表反查绑定标记,定位 MC 玩家
ea = EasyAuth.objects.filter(UserRoleUUID__in=user_role_uuids).first()
return ea.PlayerUUID if ea else None
@staticmethod
def GetByPlayerUUID(player_uuid):
"""按 MC 玩家 UUID 字符串查询 EasyAuth 记录
Args:
player_uuid: MC 玩家 UUID 字符串(easyauth.uuid 列,varchar36)
Returns:
``EasyAuth`` 实例;不存在或入参为空则返回 ``None``
"""
from .models import EasyAuth
if not player_uuid:
return None
return EasyAuth.objects.filter(PlayerUUID=player_uuid).first()
-44
View File
@@ -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