From a006d577c865f800483aec8ffa7c9c7184a8025e Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 7 Aug 2026 03:12:18 +0800 Subject: [PATCH] "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)" --- __init__.py | 13 +- __pycache__/__init__.cpython-312.pyc | Bin 12353 -> 12235 bytes auth/__init__.py | 16 +- auth/__pycache__/__init__.cpython-312.pyc | Bin 1571 -> 1828 bytes auth/__pycache__/managers.cpython-312.pyc | Bin 16923 -> 35546 bytes auth/__pycache__/user_service.cpython-312.pyc | Bin 0 -> 4749 bytes auth/managers.py | 433 +++++++++++++++++- auth/user_service.py | 107 +++++ minecraft/__init__.py | 18 - .../__pycache__/__init__.cpython-312.pyc | Bin 736 -> 0 bytes minecraft/__pycache__/auth.cpython-312.pyc | Bin 6624 -> 0 bytes minecraft/__pycache__/models.cpython-312.pyc | Bin 2672 -> 0 bytes minecraft/auth.py | 146 ------ minecraft/models.py | 44 -- 14 files changed, 552 insertions(+), 225 deletions(-) create mode 100644 auth/__pycache__/user_service.cpython-312.pyc create mode 100644 auth/user_service.py delete mode 100644 minecraft/__init__.py delete mode 100644 minecraft/__pycache__/__init__.cpython-312.pyc delete mode 100644 minecraft/__pycache__/auth.cpython-312.pyc delete mode 100644 minecraft/__pycache__/models.cpython-312.pyc delete mode 100644 minecraft/auth.py delete mode 100644 minecraft/models.py diff --git a/__init__.py b/__init__.py index c78c9d5..c92f0dc 100644 --- a/__init__.py +++ b/__init__.py @@ -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, diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc index 76d35aab4413828918e874b573dafe37f3b3a175..517497d1b313de3f1b597b15f44584ece13855d3 100644 GIT binary patch delta 3896 zcmZXWd014}8HeX0qd4rF4#Ny6vMLcp#SNEX6%_#oL=l%^zAKClGk9k}aT}7DHtF7+ zE~eFVNo~_Mni!L4HlwC%Oq*$IT4LHJHf_`8pQHV^Put%)=U$BskG|aZ`@XZ?bIyIZ zvisVuififV4jX=My%XEJpw?#lK#27tGmw8}oU5qP;z1kb(+sB3Omcz+f>ksZoJZB*eKa4ep#|VVss$I(VsHu7F%9D^rDd??v;tg7 z_24Rsug0%6v=;PI1K3DSU^BIVt+WnoqxIkh+6cDOCa{A#!7kbi`luW1p)Fu9ZDrbN zG1NBdgKwvPa0l%KchPRpPXl0p2-Im0)8aHpLy#ct1@}=19HuZBp%HLD8DNxR;3$oO z2k0O;PKV5)4$~3%Q91_3X#%{T9snPtE-vz%%PlDg4AAmok zr@%9G7JQnX0ngF1;CXrue4btaFVID?{uk*b$jkH!_$pm8hkA`J!>`a)@OAnT_+$DB z_*42B_;Y##yhd+=zo1`&Z&3pL75y6g4P6J{rr(0Uqe<``x&gjRzX$(7e+1v7_r*xG z>L&e(KA=C-U+Ay&A^na1elq2>);C9Y>4mym_vl4>u|7+mt(VY0PEPMDrGFl`^_3;r z^m2X9-VSy%+r=uAbG6Fz^~t>{c}29M-`DQl>C{{k)OwoI;XM!&YWiNMCdV=>fE9E%y>q6H)4;A(k_5aQ&qEbR%<%ZUpy?`+|dEe{9syF^QF; zZNErnx9VZt@W=GVA%7^OhX?PZcQDQ6(RQ+8ms{J#>Rm_4d7!wL&siw8y5j*iy$c7NDEs2kRhLn5Anc(Y+d?nHwkn#0~KbgkJd9<#a) ze>mz7#DbCVJ%#&Zp)aP7G#}IhqaVwM*ww;P*RZHc6;uLU*yYpLMxCn!zIO0xilOaTl(N$w~Rfhlll;5ezhk1IGA>Xw`s( zF?w&dNxvsM#%{Xv3l4}HM@ulI`__4Dm(*Ep53)?p4A;2GrZ$Yn^yt0mLu`$w#C2Gv z+oE0?4u*T8ay@e7BkZuptsP}2JjL2EcF|Mpip$c?k&u4(#mmwOcFW_`?q`l7x8ng> zin^2I$>tO~kT#wYHOXQ?EJ;Srh=`+8H->}J zC^pcFpB3>G(e|U&PmAdEo}RX*mY^OYtN0laP8G#>=OlYSXTuV2OcR;f7Oz$QtSHYA z!A?C)!SLWma_2?PA#&a^f6Olqo$Tp35l<8Gjgi4%7_c(Wi%hmR5QvP1V_uv>w7a{I zxbcDipKk4~tVyB)E0w%wG#s!bsig~#Vw4*TGvx+1 z9~?36wB05W)5PX?4D78+F|NRks{*eBtfs7>x@x-mR>?!$EK+R(8wKQw#Q16*+s;2& zc1p8fM_>gjt9V$eVrMHZxVlAw_+A>~vuL~_a82MkJ6>7q5PzF!_#Yi(mn+|mbH_0) zJL&!8>_qN7ZWqsds?9zlF>?{0CN0HgcP0v!^6AnXHv7zka|utCmS$F_OUp1TGo@wO z?5;%NN}eq($Fy8&d8Xwbi;Dqgm^xD?on|)$(p=^ch0@$MdtM^HhI^zH+3eYgoGM-{ zZI;cRnaC>Rv!#{T>;(zeay~~|shak!GHK;zuHu*$$MBUldr_jekypv&T$?>dyo-6# zs?ARClQ!QRzDC*tvzvv|>f{iM`BG`iZ1&tlUNv7XZH3LApO~?Luas7AR<44@llW>= z*U02rO^#T~y_)m}I2KvS8p&A z;aLBGDp(vyN(Yl55qBm6+EJR1dc+INr!=KB|1oY`_mFKWNpDD?bFs6{+Wkl^=m)3!3;*NCOB5Sh<7OOgyRh^ z;a$o%!?EIp+^4+Ts_#jP%NcDk8{F*2Eh4azsdaR%ncdAka1LI5koHB{bdb! zZF!IKEpWNQUgcY@{5Iu%mTyZgXU?&2z6D8~UF6FybK^^xiAAqMPGRkiS zl+zSjZVjKx-l}od9G0Ppbl#9`%P5#kn=zSFGMVX~Om|LZxWqqbZt1ODM_STE%54`b HSWx*tY;=Ko delta 3835 zcmZXUd014}8HeX06OdIvoRvj(l!&{y%PKC&4vHWQd>0rTX7J8{5SJl|Y11Wb+SAR} z*jQ_uHZjqdXiUsv(k(G*rb{d}OKnrrwE5>~pQrt|zjMyqXi^?{x$pOVXFvB|>Al=n zd?h(K!GfQWOQDg3WfseaLd>7U=)=F~o-Hob!jyT$(v|PE&^)p+jqKzAo#X=Dlm%u} z4wy^x!927BlZv$-vH&G>6Mb%&p)q-`j8LX!*;8xlOZl?xt2Q`9C)C{&zE7(TuUcNM~68APqr=$qSCqUeHIQpq~QZ80`ZM3W6aT2PbGh zI7w6BJ#-*C)ImA~KTKh8n(hVfqx-=l^Z@t;`XcxseF^+BeFgj~Jp_J@!e7U~hv^&O zH;IAYqHlxWq3?pZg=%?Ug^fU11^f>qg zod%zzr@%9G7JQn{fzQyh;CZ?r*8dz`ggj3#fS2e+@FjXVI@Bw48U8B02L6J63I2+H z4gQ8+2j8G8;G6Uo_*;4#jL`4E-_sw!KhitkRr(V+Lw^RZ(YxS#^gj3j{RO;E)qfR3 z(ckDp`a9jAkLVxtG5zyc+$pWAKzHgc-K}To*?NwitIyZ-^nCi)v4pNd`UID@D8`}} z>jfjt?0B-1<;7-brRP@0cE-=n7@7!DkoK0JJCSrcVe9NsuU{W9JcFUq7-Jo7ztJeL z9tdhcu8|C0OYQXqJkcUL{8$*Xk)4N7Ip*S346M=3!%r@L-w6oH~Yw5Z0&)cX6cSHZ4cY%aBID+ z+mWsHu>+1Q&BKl&($CH#GQi$(Gs^w#>yC1&e;*gm(-en8}AS51ZV;GM~X>`}K} zJH#%!^R&b4J-0m^7KN!>g4Kc1F^_+$K_Bhc#XiWHOp9cCG}Sr~7}Kf2JL&bC`Fmx) zNjFBl!Js$bmvi>sZhxQ1C$XgUnW|I1BGLs%N!Az)TTht8$yTCy%_(;^w$9+ZQ&K#V74M9c;q$jDP7l5l(eX8eqZ z$BVXiR6C5bB0RgjeM?Q9SNDnYlD$05PUK~{XZYTf5D3m?&}R}rXW7m6O#7O+jl7jncG&JZ6pLwv#v z@!c}s6nIPEDoZO_ns5V;;xihYVhttlveZ&*m?wm_w3zF$X_1VD+$x^=T^8%Sh;12< zlNN8W+9Qrte73X%i`5pfui%N&lA@K#(o&+8snX_Htj>sQEl-n{9<>ZFzFyDaycB+Cms)8odo0Ydj zJK(L#+e~j)-T{{#b}H|Nt}7peWA|3_ zA?3qxIe=IB2ppGUIp3??XV#A@_rtNk4LqQHELwkSA6%TjVKxY=f)E^6ejOiIJ^{y; z*6{tx532fQ{E+g)a2$3yM|a}=*%V{3x+3n4yfQ`|AsiD}%d3^wz-4`HjJp4IX2E7v zP!Gq7m+>vix5BZ-EBH3$+u>O865gPEhgsjKyveL@R^9@~j;-RY%G=EL?a>KzAdnTw zuD;c&3U-{8xkcCcG{H(ai;NBJJpdzJUW<%&JZ2jTL)9a26Fmmd{xSOrE9kgxJy zkeIo)Sqd;9Hb$h2zMU@@>ku!?B?#Z&cm{#|~74d9w<%m>sk#Z-dJTwJYy1y;J#4xLn~bWIs88D83V@hsjUTAS@5ln;|#0gF14N7$D8>mCvyv+jg=tUWt6`YUG?!^L)XXMEP5#Ml z%nNNaS&PJgii*TRgan9?1QAj|LX+_pM=r!4#YNH(0iIHz`{IEZAYmGLtv6>dAoAtz;TZlX-=wLk>TXOtg@VPjJ}Lu FX#k+adG7!K delta 363 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 delta 3385 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 literal 0 HcmV?d00001 diff --git a/auth/managers.py b/auth/managers.py index 57fdf4e..7141b2e 100644 --- a/auth/managers.py +++ b/auth/managers.py @@ -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, + } + diff --git a/auth/user_service.py b/auth/user_service.py new file mode 100644 index 0000000..c37a150 --- /dev/null +++ b/auth/user_service.py @@ -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, + } diff --git a/minecraft/__init__.py b/minecraft/__init__.py deleted file mode 100644 index d54ebd4..0000000 --- a/minecraft/__init__.py +++ /dev/null @@ -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', -] diff --git a/minecraft/__pycache__/__init__.cpython-312.pyc b/minecraft/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index ac00e420ff3f971f7fdd0da3459919f36532cfa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 736 zcmY*X&u5g_E<(592$`d~fExZ@%~D{g|920kY%1_x3yv zK+AAOhJ4dbX3>@b9B=~;<8T2JaW{?`*dka&!c8Dda8^I=w#IA@d-OX7fd8({$=sbe zF%aZ^5_>B`{Zck5*Vxl?8}MNPzA9>|r)vG1w!RCi&$VI!zRr-OmP)IKTa@bxHMoTj zs_MZfy-;jtSE%bZT$J`Q4=XEb?Xh0k*30n$S+|iT$qN+2EV6RU8I3G63_2P)L#jan zqUtshA{%7=JhyO_6=c5KNW$$Go%=;f_AT=w+ik4>N9HnvC6iOD)wD!P|GEQH`>?#) z&c{0+lSoHPiED4)>*aR{wChh`DXZnz^iri=Dzww9sID*Q*{qh^8x)GFF~FNgNHZG_ z(*o<|g@qZ>H)Fh#gt?+xtB)In1e;Ao!qmeL9VM$Dt&t34o?Ya;v=oX)cxf&iV;D-` zFSj?VFj1lY^&cKQ>o&IF=k%CStton9Ppxfqa*Dna*Gos8OsS>ZBRl8=fdgw!jGBk= z)Cdn7Az4t2M4)BKVQboHnhW`8x@i+b952Y`ZIfWpFPJe#04oSIy$eoI3h2*r^PZb7 z=aTcS%zpGc1Y diff --git a/minecraft/__pycache__/auth.cpython-312.pyc b/minecraft/__pycache__/auth.cpython-312.pyc deleted file mode 100644 index 9efa3fbae3f659944d853f7627ec6474502cc2ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6624 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 diff --git a/minecraft/__pycache__/models.cpython-312.pyc b/minecraft/__pycache__/models.cpython-312.pyc deleted file mode 100644 index 1def8033ab51c614e221adae82d2eb37d64ab73d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2672 zcmbtWZ){Ul6n}kv?OwZeV}JM~WAe}jY>XxTi64xAG8sb%8_IxaCBDA)KG@jnAK&W( zOEea;X{U@$zzh(9MTO!<85&`(pnf#*%a$xzeTiSRuPvW|Ok=_qJ?Fi)ZpN4xZ?=2y zyZ4-PfA^f!XNWq{pnL4PRBf%EPL6EEJVbjp`qd+`e&o?|y|Sq}Lnj z35!8l*c0=HB1DuF?&rh_u44n2A3B!HT+{on=BCDqqbY9N_6{z8^1MFL=XB~fkLv@2 zxlF%)q+cH%hn>!1`kP{Uw0PlA z;qcdh#>qi@1Plq$C@%>CQF>-wD7en=hdqRR_I>^4G5zXU&VcO&Da9KKaxmV$ainnK zs=3bPCNudngZg`;`l(dmbf00B1FMGCdJ^E}h#C`RZ#0fTuU`R8JvqTue8A~b$16}m zLRi=g4SfA2iF?)lA~t=nID=+P%VoIZ4= zaB1rHjR82LbQ%5f$wKA>J$+gqKB^}_(lb+VQMh9H47A_>W%1An4o`F5LI!v&n*F5? z3@^+axZd6)N(7h9pa)H!m^~2DX!9$8FC&yl`7A&_npk{Rni83P24zb!rGe1IN|ZI5vUaf)ei1v!RuOjGF~*p?KK5T^ouqoaref9cFOoFab&=Zfy0KE?_?(9HvVXv^ zgw&5WjM1g@Ye3Eg+B9h-P2)=|xN415S_Y}KydqmiR`gN6CGq<_D>b&vaWw^wDt~Yl zszV~dACRT8W#$ybt8ny^p1!8PdpSRtb{c^-Q>$?1oSr_5>E`!~kWzw?QoxBtTm(Kq z4*C5=hzTa0a+6csmac7`Wt!;y*K(7W!AMk4lmii&pxEV+eEKVt3Tx`o>M#E|~ZDUVggj9H+>s49S63X2^Z z%%jl15Xr*$h1}#99-E<{F=17bG+RIt!o3T%ksxXfXq&{#e>eer^Uw=KYRY8;?W(H~ zlTlnC-udM9;>BadfiKK*f&v~!Za{#I*j28!sCYmo7;T<|VR7*ww2vWHpN%j+JN#G0 zZ65nQS2;Pn9b!z-*fQ%iIuvYI(H&(0GhCe>R%3Z19z3r(c^(RwDv2jE_hJOVe*hgg>Tas+HX+_eJZCIYH$}V4*tjXT*onf2h2S7kZ(O}t8 zFsE>iPxbst=tihFseLg1y>&qu?}U=gV#x58FlQ7|+M}R4CqD?iR>kv5Oy>E6OUiEZ zmR25yZ3VfIpt5z1LvIbdHO;Ov@N|0WWjt7J#tyH5W=JH+^D@>_%>gfa#CS0@J3jK8 z+cd|9Zb3Gd$KdbVxVl6X^P=yEJQK?=(@h_a}GFxmspiEh+b;`+MK4>*Yjywr+KD|6JYbnYz`f z$0uUn9h~;}&elnZ7qU(6;r6*E_e_&}!aDujw&~X*vrW-NN49>=@Upr3H8b^VQeB|% z#B|py)7@|XN>L51bYe@kx;gpeTy^tIb@TZ>lh4jpZ%MTM*tqJvJ4GhL)33&78xJP7 z{?6EJ>+e#~&DUCNEhc`14kVR5Flr5zUs?G0@&N%thk@2=7i}npPoVG^KO?*(VEEni z!eY 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() diff --git a/minecraft/models.py b/minecraft/models.py deleted file mode 100644 index 86a3f2d..0000000 --- a/minecraft/models.py +++ /dev/null @@ -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