"chore(gvsdsdk): 清理过时点与死代码并修复运行期缺陷

- models: 修正过时文档、UserRole 遗留字段标注废弃、抽象 User 补 IsStaff/IsSuperuser
- models: CopyToRole 补 CompanyUUID 默认值,修复 RolePermission 插入必失败缺陷
- form_service: 对齐主项目值转换(Boolean/时区/空值),移除 NullBooleanField 遗留映射
- auth: 删除无引用的 GetDefaultAvatar 与 ShopManager/PositionManager,清理惰性导出
- db_router/model_base/msync: 简化模块集合、移除 typing_extensions 死兜底、复用 GenerateChallenge
- 删除残留文件(空测试文件、无源 pyc)"
This commit is contained in:
TermiNexus committed 2026-08-07 00:57:29 +08:00
1 parent 32a31a8220
commit e9bde4db76
19 files changed
+66 -220

No files matched your search

+1 -2
View File
@@ -13,11 +13,10 @@ GVSDS 分布式服务间通信 SDK,作为子服务器(如 ATestCServer)和
包结构:
gvsdsdk/
├── auth/ # 用户认证与权限模块
│ ├── cookie.py # 默认头像等工具
│ ├── crypto.py # 密码哈希 + AES 加密
│ ├── middleware.py # CurrentRoleMiddleware
│ ├── permissions.py # RBAC/ABAC 权限
│ └── managers.py # UserManager + RoleManager
│ └── managers.py # RoleManager(UserManager 在 models.py)
├── msync/ # MSYNC 服务间通信
├── commission/ # 分佣引擎
│ ├── models.py # 分佣数据模型
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -14
View File
@@ -1,21 +1,16 @@
"""gvsdsdk.auth — 用户认证与权限模块"""
__all__ = [
'GetDefaultAvatar',
'HashPassword', 'VerifyPassword', 'SymmetricEncrypt', 'SymmetricDecrypt', 'FileSHA1', 'FileSHA256', 'BytesSHA1', 'BytesSHA256',
'CurrentRoleMiddleware',
'IsAdminUser', 'IsCompanyMember', 'CompanyScopedMixin', 'TenantScopedMixin', 'UserScopedMixin', 'PermissionVerifier',
'IsAdminScopeDisabled', 'IsTenantScopeDisabled',
'IsAdmin', 'IsSuperAdmin', 'IsTenantAdmin',
'RoleManager', 'ShopManager', 'PositionManager',
'RoleManager',
]
def __getattr__(name):
if name == 'GetDefaultAvatar':
from gvsdsdk.auth.cookie import GetDefaultAvatar
globals()['GetDefaultAvatar'] = GetDefaultAvatar
return GetDefaultAvatar
if name in ('HashPassword', 'VerifyPassword', 'SymmetricEncrypt', 'SymmetricDecrypt', 'FileSHA1', 'FileSHA256', 'BytesSHA1', 'BytesSHA256'):
from gvsdsdk.auth import crypto as _m
val = getattr(_m, name)
@@ -39,12 +34,4 @@ def __getattr__(name):
from gvsdsdk.auth.managers import RoleManager
globals()['RoleManager'] = RoleManager
return RoleManager
if name == 'ShopManager':
from gvsdsdk.auth.managers import ShopManager
globals()['ShopManager'] = ShopManager
return ShopManager
if name == 'PositionManager':
from gvsdsdk.auth.managers import PositionManager
globals()['PositionManager'] = PositionManager
return PositionManager
raise AttributeError(f"module 'gvsdsdk.auth' has no attribute {name!r}")
Binary file not shown.
Binary file not shown.
Binary file not shown.
-11
View File
@@ -1,11 +0,0 @@
from django.http import HttpResponse
def GetDefaultAvatar():
from io import BytesIO
from PIL import Image
img = Image.new('RGB', (256, 256), (200, 200, 200))
buf = BytesIO()
img.save(buf, format='PNG')
buf.seek(0)
return HttpResponse(buf.read(), content_type='image/png')
+2 -151
View File
@@ -1,11 +1,10 @@
import uuid
from django.utils import timezone as _timezone
from gvsdsdk.models import Role, UserRole, RolePermission, Permission, Shop, RoleShop, Position, PositionTemplate, PositionTemplatePermission
from gvsdsdk.models import Role, UserRole, RolePermission, Permission
from gvsdsdk.model_utils import _GetUserModel
# NOTE: 类型注解(L8)暂缓——本模块含 RoleManager/ShopManager/PositionManager 共 20+ 静态方法,
# 全量补充参数与返回值注解工作量较大且易引入回归,后续单独迭代处理。
# NOTE: 类型注解暂缓——RoleManager 静态方法较多,全量补充注解工作量较大且易引入回归,后续单独迭代处理。
class RoleManager:
@@ -139,151 +138,3 @@ class RoleManager:
perm_uuids = qs.values_list('PermUUID', flat=True)
return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1)
class ShopManager:
"""商铺管理器——提供商铺创建、角色关联、查询"""
@staticmethod
def CreateShop(Name, CompanyUUID, Code='', ShopType=0, **Kwargs):
"""创建商铺"""
shop = Shop(
ShopUUID=uuid.uuid4().bytes,
CompanyUUID=CompanyUUID,
ShopName=Name,
ShopCode=Code,
ShopType=ShopType,
ShopStatus=1,
ShopAddress=Kwargs.get('ShopAddress', ''),
ShopContact=Kwargs.get('ShopContact', ''),
ShopPhone=Kwargs.get('ShopPhone', ''),
ShopDesc=Kwargs.get('ShopDesc', ''),
CreateTime=_timezone.now(),
UpdateTime=_timezone.now(),
)
shop.save()
return shop
@staticmethod
def AssignRole(RoleUUID, ShopUUID, IsMainShop=False):
"""为角色关联商铺"""
role_shop = RoleShop(
RoleShopUUID=uuid.uuid4().bytes,
RoleUUID=RoleUUID,
ShopUUID=ShopUUID,
IsMainShop=1 if IsMainShop else 0,
CreateTime=_timezone.now(),
)
role_shop.save()
return role_shop
@staticmethod
def RemoveRole(RoleUUID, ShopUUID):
"""移除角色与商铺的关联"""
deleted, _ = RoleShop.objects.filter(RoleUUID=RoleUUID, ShopUUID=ShopUUID).delete()
return deleted > 0
@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 GetRolesForShop(ShopUUID):
"""获取商铺关联的所有角色"""
role_uuids = RoleShop.objects.filter(ShopUUID=ShopUUID).values_list('RoleUUID', flat=True)
return Role.objects.filter(RoleUUID__in=role_uuids)
@staticmethod
def GetMainShopForRole(RoleUUID):
"""获取角色的主商铺"""
rs = RoleShop.objects.filter(RoleUUID=RoleUUID, IsMainShop=1).first()
if rs:
return Shop.objects.filter(ShopUUID=rs.ShopUUID).first()
return None
@staticmethod
def GetShopsForCompany(CompanyUUID):
"""获取公司下的所有商铺"""
return Shop.objects.filter(CompanyUUID=CompanyUUID, ShopStatus=1)
class PositionManager:
"""职务管理器——提供人事职务标签创建、岗位模板管理"""
@staticmethod
def CreatePosition(Name, TenantUUID, Code='', Level=0, **Kwargs):
"""创建人事职务标签"""
position = Position(
PositionUUID=uuid.uuid4().bytes,
TenantUUID=TenantUUID,
PositionName=Name,
PositionCode=Code,
PositionLevel=Level,
PositionStatus=1,
PositionDesc=Kwargs.get('PositionDesc', ''),
CreateTime=_timezone.now(),
UpdateTime=_timezone.now(),
)
position.save()
return position
@staticmethod
def GetPositionsForTenant(TenantUUID):
"""获取租户下的所有职务"""
return Position.objects.filter(TenantUUID=TenantUUID, PositionStatus=1)
@staticmethod
def CreatePositionTemplate(Name, TenantUUID, Code='', **Kwargs):
"""创建岗位模板"""
template = PositionTemplate(
PositionTemplateUUID=uuid.uuid4().bytes,
TenantUUID=TenantUUID,
TemplateName=Name,
TemplateCode=Code,
TemplateStatus=1,
TemplateDesc=Kwargs.get('TemplateDesc', ''),
CreateTime=_timezone.now(),
UpdateTime=_timezone.now(),
)
template.save()
return template
@staticmethod
def AssignPermToTemplate(TemplateUUID, PermUUID):
"""为岗位模板关联权限"""
ptp = PositionTemplatePermission(
PTPermUUID=uuid.uuid4().bytes,
PositionTemplateUUID=TemplateUUID,
PermUUID=PermUUID,
CreateTime=_timezone.now(),
)
ptp.save()
return ptp
@staticmethod
def RemovePermFromTemplate(TemplateUUID, PermUUID):
"""移除岗位模板与权限的关联"""
deleted, _ = PositionTemplatePermission.objects.filter(
PositionTemplateUUID=TemplateUUID, PermUUID=PermUUID
).delete()
return deleted > 0
@staticmethod
def GetPermsForTemplate(TemplateUUID):
"""获取岗位模板关联的所有权限"""
perm_uuids = PositionTemplatePermission.objects.filter(
PositionTemplateUUID=TemplateUUID
).values_list('PermUUID', flat=True)
return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1)
@staticmethod
def GetTemplatesForTenant(TenantUUID):
"""获取租户下的所有岗位模板"""
return PositionTemplate.objects.filter(TenantUUID=TenantUUID, TemplateStatus=1)
@staticmethod
def CopyTemplateToRole(TemplateUUID, RoleName, TenantUUID):
"""从岗位模板复制权限到新角色"""
template = PositionTemplate.objects.get(PositionTemplateUUID=TemplateUUID)
return template.CopyToRole(RoleName, TenantUUID)
+4 -10
View File
@@ -35,14 +35,8 @@ class SAASDatabaseRouter:
GVSDSDK_SAAS_READ_ONLY = True # 默认 True(子服务器只读)
"""
# gvsdsdk 内所有模块前缀
_GVSDSDK_MODULES = frozenset({
'gvsdsdk.models',
'gvsdsdk.commission.models',
'gvsdsdk.payment.models',
})
# gvsdsdk 内所有 app_label
# gvsdsdk 内所有模块前缀(startswith 判定覆盖全部子模块,
# 无需维护显式模块名清单,避免新增模块时遗漏)
_GVSDSDK_APP_LABELS = frozenset({
'gvsdsdk',
})
@@ -57,9 +51,9 @@ class SAASDatabaseRouter:
def _is_gvsdsdk_model(self, model):
"""判断模型是否属于 gvsdsdk"""
# 按模块路径判断
# 按模块路径判断(gvsdsdk.* 前缀全覆盖)
module = getattr(model, '__module__', '')
if module in self._GVSDSDK_MODULES or module.startswith('gvsdsdk.'):
if module.startswith('gvsdsdk.'):
return True
# 按 app_label 判断
app_label = getattr(model._meta, 'app_label', '')
+30 -8
View File
@@ -14,9 +14,8 @@
'MyModel2': MyModel2,
}
# 子服务器 views.py
from gvsdsdk.form_service import FormServiceAPIView
from .form_service import FormService
# 子服务器 views.py 中直接实例化 FormService 使用
# (FormServiceBase 提供 ORM 能力,视图层由子服务器自行封装)
"""
import uuid
@@ -87,6 +86,17 @@ class FormServiceBase:
if val is None or val == '':
if getattr(field, 'null', False):
return None
# 非空字段:数值类型返回默认值或0,布尔类型返回 False,其他类型返回空字符串
field_type = field.__class__.__name__
if field_type in ('SmallIntegerField', 'IntegerField', 'BigIntegerField',
'PositiveIntegerField', 'PositiveSmallIntegerField',
'FloatField', 'DecimalField'):
default = getattr(field, 'default', None)
if default is not None:
return default
return 0
if field_type == 'BooleanField':
return False
return val
field_type = field.__class__.__name__
if field_type == 'UUIDField' and isinstance(val, str):
@@ -115,12 +125,25 @@ class FormServiceBase:
return val
if field_type == 'DateTimeField' and isinstance(val, str):
try:
return datetime.datetime.fromisoformat(val)
dt = datetime.datetime.fromisoformat(val)
# USE_TZ=False + MySQL 不接受 aware datetime:
# 统一转 naive UTC(UTC-0 存储,展示时由前端按用户时区转换)
if dt.tzinfo is not None:
dt = dt.astimezone(datetime.timezone.utc).replace(tzinfo=None)
return dt
except (ValueError, TypeError):
return val
if hasattr(field, 'max_length') and field.max_length is not None and isinstance(val, str) and len(val) > field.max_length:
logger.warning('字段 %s 值超长(%d > %d),已截断', getattr(field, 'name', '?'), len(val), field.max_length)
val = val[:field.max_length]
if field_type in ('BooleanField', 'NullBooleanField') and isinstance(val, str):
low = val.strip().lower()
if low in ('true', '1', 't', 'yes'):
return True
if low in ('false', '0', 'f', 'no'):
return False
return val
max_length = getattr(field, 'max_length', None)
if max_length and isinstance(val, str) and len(val) > max_length:
logger.warning('字段 %s 值超长(%d > %d),已截断', getattr(field, 'name', '?'), len(val), max_length)
val = val[:max_length]
return val
@staticmethod
@@ -141,7 +164,6 @@ class FormServiceBase:
'FloatField': 'number',
'DecimalField': 'number',
'BooleanField': 'boolean',
'NullBooleanField': 'boolean',
'DateTimeField': 'datetime',
'DateField': 'date',
'TimeField': 'time',
+1 -6
View File
@@ -24,12 +24,7 @@
from __future__ import annotations
import re
from typing import Any, Callable, Dict, Generic, Optional, Tuple, Type, TypeVar, Union
try:
from typing import Self
except ImportError:
from typing_extensions import Self
from typing import Any, Callable, Dict, Generic, Optional, Self, Tuple, Type, TypeVar, Union
from django.db import models
from django.db.models import Q
+27 -12
View File
@@ -7,7 +7,7 @@
供子服务器直接进行跨服务器 ORM 查询。
权限体系四大分层:
1. 组织人事层:Tenant → Company → Department,员工通过 UserRole.DeptUUID 归属部门
1. 组织人事层:Tenant → Company → Department,员工通过 UserRoleDepartment/UserRoleWorkgroup 关联表归属部门/工作组
2. 岗位模板层:PositionTemplate + PositionTemplatePermission,权限初始化快照
3. 角色权限载体层:Role + RolePermission,运行时鉴权实体
4. 鉴权规则层:操作权限校验(角色汇总)+ 数据权限校验(组织属性)
@@ -15,11 +15,13 @@
使用方式(子服务器):
from gvsdsdk.models import Permission, Role, UserRole, RolePermission
# 查询所有启用的用户(需在业务子类中定义具体 User 模型)
users = User.objects.filter(UserAccountLicense=1)
# 查询所有启用的用户(User 为抽象基类,需经 _GetUserModel 获取具体模型)
from gvsdsdk.model_utils import _GetUserModel
UserModel = _GetUserModel()
users = UserModel.objects.filter(UserAccountLicense=1)
# 或配置 DatabaseRouter 后直接使用
users = User.objects.filter(UserAccountLicense=1)
users = UserModel.objects.filter(UserAccountLicense=1)
设计原则:
1. 独立:不依赖 gvsds 内部模块,可在任意 Django 项目中独立使用
@@ -29,8 +31,7 @@
5. 租户隔离:所有权限相关数据依附于独立租户空间,不同租户互相不可见
注意:User 为抽象基类,仅包含 SAAS 主库标准字段。
业务子类应继承 User 并在单独文件中添加业务字段和方法。
参见 gvsdsdk/business_models.py。
业务子类应继承 User 并添加业务字段与方法(如 gvsds.apps.core.models.User)。
"""
import uuid as _uuid
@@ -186,8 +187,7 @@ class User(QModel):
"""用户表——系统核心身份模型(抽象基类,映射 SAAS 主库 User 表标准字段)
本类为抽象基类,仅包含 SAAS 主库 User 表的标准字段和方法。
业务子类应继承本类,在单独文件中添加业务字段和方法。
参见 gvsdsdk/business_models.py。
业务子类应继承本类并添加业务字段与方法(如 gvsds.apps.core.models.User)。
"""
UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID', default=_uuid.uuid4)
@@ -202,6 +202,8 @@ class User(QModel):
UserTele = models.CharField(max_length=16, null=True, blank=True, db_column='UserTele', verbose_name='电话')
UserCreateTime = models.DateTimeField(default=_UtcNow, db_column='UserCreateTime', verbose_name='创建时间')
UserLastLoginDate = models.DateTimeField(default=_UtcNow, db_column='UserLastLoginDate', verbose_name='最后登录')
IsStaff = models.BooleanField(default=False, db_column='IsStaff', verbose_name='是否员工')
IsSuperuser = models.BooleanField(default=False, db_column='IsSuperuser', verbose_name='是否超级用户')
# Django auth 框架必需(由具体子类覆盖)
USERNAME_FIELD = 'UserName'
@@ -537,11 +539,13 @@ class UserRole(QModel):
通过 CompanyUUID 标识该用户-角色关联所属的公司,
用于菜单按公司差异化显示、数据权限按公司过滤等场景。
同一用户在不同公司下可绑定不同角色。
DeptUUID、WorkgroupUUID 为可选字段,用于将关联进一步限定到部门或工作组。
RoleType 标识该用户在部门/工作组中的角色类型:0=普通成员/部门员工,1=组长/部门负责人。
RoleName 为可选的角色名覆盖:非空时前端展示用此名,为空时回退到 Role.RoleName。
这避免了为每个用户单独建角色,又能让同角色在不同用户视角下显示差异化名称。
注意:DeptUUID、WorkgroupUUID 为遗留字段(已废弃)——部门/工作组归属已迁移到
UserRoleDepartment/UserRoleWorkgroup 关联表,字段仅保留兼容存量数据,禁止再次写入。
注意:UserUUID/RoleUUID/CompanyUUID/DeptUUID/WorkgroupUUID 在 gvsds 中是 ForeignKey,
但此处改为 UUIDField 以保持独立性(不支持 select_related 和关联属性访问)。
"""
@@ -719,14 +723,24 @@ class PositionTemplate(QModel):
).values_list('PermUUID', flat=True)
return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1)
def CopyToRole(self, RoleName, TenantUUID):
"""从模板复制权限到新角色(一次性复制,后续模板变更不影响已有角色)"""
def CopyToRole(self, RoleName, TenantUUID, CompanyUUID=None):
"""从模板复制权限到新角色(一次性复制,后续模板变更不影响已有角色)
Args:
RoleName: 新角色名称
TenantUUID: 新角色所属租户
CompanyUUID: 复制出的 RolePermission 所属公司。
缺省用系统公司 UUID(与主项目 RolePermission 创建一致),
RolePermission.CompanyUUID 为非空字段,漏传会导致插入失败。
"""
with transaction.atomic():
role = Role.objects.create(
RoleUUID=_uuid.uuid4().bytes,
TenantUUID=TenantUUID,
RoleName=RoleName,
)
if CompanyUUID is None:
CompanyUUID = _uuid.UUID('00000000-0000-0000-0000-000000000001')
template_perms = PositionTemplatePermission.objects.filter(
PositionTemplateUUID=self.PositionTemplateUUID
)
@@ -734,6 +748,7 @@ class PositionTemplate(QModel):
RolePermission(
RolePermUUID=_uuid.uuid4().bytes,
RoleUUID=role.RoleUUID,
CompanyUUID=CompanyUUID,
PermUUID=tp.PermUUID,
)
for tp in template_perms
@@ -793,4 +808,4 @@ class Position(QModel):
def __str__(self):
return self.PositionName
# (UserDept 表已废弃——员工部门关联改走 UserRole.DeptUUID,不再用独立表)
# (UserDept 表已废弃——员工部门归属已迁移到 UserRoleDepartment 关联表,不再用独立表)
Binary file not shown.
-6
View File
@@ -50,12 +50,6 @@ def _ResolveMasterURL(master_url):
return DEFAULT_MASTER_URL
def _GenerateChallenge():
"""生成认证挑战字符串——与 auth.GenerateChallenge 统一格式(时间戳:UUID)"""
import time as _time
return f'{_time.time():.6f}:{uuid.uuid4().hex}'
class ServiceClientError(Exception):
def __init__(self, Message, StatusCode=None, ResponseBody=None):
super().__init__(Message)
View File
Whitespace-only changes.