"""GVS-DSDK 数据模型模块 将 SAAS 主服务器的用户表(User)、权限表(Permission)、角色表(Role)、 用户角色关联表(UserRole)、角色权限关联表(RolePermission)、 岗位模板表(PositionTemplate)、岗位模板权限关联表(PositionTemplatePermission)、 人事职务标签表(Position)、员工部门关联表(UserDept)封装为独立的 Django ORM 模型, 供子服务器直接进行跨服务器 ORM 查询。 权限体系四大分层: 1. 组织人事层:Tenant → Company → Department,员工通过 UserDept 归属部门 2. 岗位模板层:PositionTemplate + PositionTemplatePermission,权限初始化快照 3. 角色权限载体层:Role + RolePermission,运行时鉴权实体 4. 鉴权规则层:操作权限校验(角色汇总)+ 数据权限校验(组织属性) 使用方式(子服务器): from gvsdsdk.models import User, Permission, Role, UserRole, RolePermission # 查询所有启用的用户 users = User.objects.using('saas').filter(UserAccountLicense=1) # 或配置 DatabaseRouter 后直接使用 users = User.objects.filter(UserAccountLicense=1) 设计原则: 1. 独立:不依赖 gvsds 内部模块,可在任意 Django 项目中独立使用 2. 非托管:managed=False,不创建/迁移表,仅映射已有表结构 3. PascalCase:字段名、方法名遵循帕斯卡命名法 4. 兼容:db_table、db_column 与 SAAS 主库完全一致 5. 租户隔离:所有权限相关数据依附于独立租户空间,不同租户互相不可见 """ import uuid as _uuid import datetime as _datetime from django.db import models from django.contrib.auth.models import BaseUserManager from gvsdsdk.model_base import QModel from gvsdsdk.model_utils import UUIDObj # === UUID 字段 === class FixedBinaryField(models.BinaryField): """定长二进制字段:映射 MySQL BINARY(N),可用于主键""" description = "Fixed-length binary field" def __init__(self, *args, **kwargs): self.binary_length = kwargs.pop('binary_length', 16) kwargs.setdefault('max_length', self.binary_length) kwargs.setdefault('editable', False) super().__init__(*args, **kwargs) def db_type(self, connection): if connection.vendor == 'mysql': return f'BINARY({self.binary_length})' elif connection.vendor == 'sqlite': return 'BLOB' elif connection.vendor == 'postgresql': return 'BYTEA' return f'BINARY({self.binary_length})' def rel_db_type(self, connection): return self.db_type(connection) def deconstruct(self): name, path, args, kwargs = super().deconstruct() kwargs['binary_length'] = self.binary_length return name, path, args, kwargs class UUIDField(FixedBinaryField): """UUID 字段:数据库存储为 BINARY(16),Python 侧自动转换 str/bytes/uuid.UUID""" description = "UUID stored as BINARY(16)" def __init__(self, *args, **kwargs): kwargs.setdefault('max_length', 16) kwargs.setdefault('editable', False) super().__init__(*args, **kwargs) def db_type(self, connection): vendor = connection.vendor if vendor == 'mysql': return 'BINARY(16)' elif vendor == 'sqlite': return 'BLOB' elif vendor == 'postgresql': return 'BYTEA' return 'BINARY(16)' def from_db_value(self, value, expression, connection): if value is None: return None if isinstance(value, UUIDObj): return value return UUIDObj(value) def get_prep_value(self, value): if value is None: return None if isinstance(value, UUIDObj): return value.bytes if isinstance(value, str): try: return _uuid.UUID(value).bytes except ValueError: return None if isinstance(value, _uuid.UUID): return value.bytes if isinstance(value, bytes): return value return None def get_prep_lookup(self, lookup_type, value): if lookup_type in ('exact', 'iexact', 'in'): if value is None: return None if lookup_type == 'in' and isinstance(value, (list, tuple)): return [self.get_prep_value(v) for v in value] return self.get_prep_value(value) return super().get_prep_lookup(lookup_type, value) def value_to_string(self, obj): value = self.value_from_object(obj) if isinstance(value, UUIDObj): return value.str or '' if isinstance(value, str): return value if isinstance(value, bytes) and len(value) == 16: return str(_uuid.UUID(bytes=value)) if isinstance(value, _uuid.UUID): return str(value) return '' def deconstruct(self): name, path, args, kwargs = super().deconstruct() del kwargs['max_length'] return name, path, args, kwargs # === QModel 基础模型 === # === 用户管理器 === class UserManager(BaseUserManager): """用户管理器——提供用户创建与超级用户创建。 方法名保留蛇形命名以兼容 Django 内部约定。""" def create_user(self, username, password=None, email='', **extra_fields): """创建普通用户""" if not username: raise ValueError('The Username field must be set') user = self.model( UserUUID=_uuid.uuid4().bytes, UserName=username, UserEmail=email or '', **extra_fields ) if password: user.SetPassword(password) user.save(using=self._db) return user def create_superuser(self, username, password=None, **extra_fields): """创建超级用户""" extra_fields.setdefault('UserAccountLicense', 1) extra_fields.setdefault('UserPositionStatus', 0) extra_fields.setdefault('IsStaff', True) extra_fields.setdefault('IsSuperuser', True) return self.create_user(username, password, **extra_fields) # === 用户表 === class User(QModel): """用户表——系统核心身份模型(映射 SAAS 主库 User 表)""" UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID', default=_uuid.uuid4) UserName = models.CharField(max_length=256, unique=True, db_column='UserName', verbose_name='用户名') UserPassword = models.CharField(max_length=60, default='', db_column='UserPassword', verbose_name='密码') UserPositionStatus = models.SmallIntegerField(default=0, db_column='UserPositionStatus', verbose_name='职位状态') UserPersonalSignature = models.CharField(max_length=1024, default='', db_column='UserPersonalSignature', verbose_name='个人签名') UserEmail = models.CharField(max_length=256, default='', db_column='UserEmail', verbose_name='邮箱') UserEmailVerify = models.SmallIntegerField(default=0, db_column='UserEmailVerify', verbose_name='邮箱验证') UserAccountLicense = models.SmallIntegerField(default=1, db_column='UserAccountLicense', verbose_name='账户许可') UserGender = models.SmallIntegerField(default=-1, db_column='UserGender', verbose_name='性别') UserTele = models.CharField(max_length=16, null=True, blank=True, db_column='UserTele', verbose_name='电话') UserCreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UserCreateTime', verbose_name='创建时间') UserLastLoginDate = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UserLastLoginDate', verbose_name='最后登录') # === 本地业务字段(去远程化后新增) === UserUID = models.CharField(max_length=7, unique=True, db_column='UserUID', verbose_name='用户业务ID', null=True, blank=True) OpenID = models.CharField(max_length=64, unique=True, db_column='OpenID', verbose_name='微信OpenID', null=True, blank=True) UnionID = models.CharField(max_length=64, db_column='UnionID', verbose_name='微信UnionID', null=True, blank=True) Avatar = models.CharField(max_length=500, default='a_long/morentouxiang.jpg', db_column='Avatar', verbose_name='头像') Phone = models.CharField(max_length=11, db_column='Phone', verbose_name='手机号', null=True, blank=True) PhoneVerified = models.BooleanField(default=False, db_column='PhoneVerified', verbose_name='手机号是否已认证') IP = models.CharField(max_length=50, db_column='IP', verbose_name='来访用户IP', null=True, blank=True) IsStaff = models.BooleanField(default=False, db_column='IsStaff', verbose_name='是否员工') IsSuperuser = models.BooleanField(default=False, db_column='IsSuperuser', verbose_name='是否超级用户') objects = UserManager() # Django auth 框架必需 USERNAME_FIELD = 'UserName' REQUIRED_FIELDS = ['UserUID'] class Meta: managed = True # 将由 QModelBase.__new__ 根据 GVSDSDK_MANAGE_TABLES 动态设置 app_label = 'gvsdsdk' db_table = 'User' verbose_name = '用户' verbose_name_plural = '用户' indexes = [ models.Index(fields=['UserUID']), models.Index(fields=['OpenID']), ] def __str__(self): return self.UserName @property def UUIDStr(self): """返回 UUID 字符串形式""" return UUIDObj(self.UserUUID).str @property def IsActive(self): """账户是否启用(UserAccountLicense == 1)""" return self.UserAccountLicense == 1 @property def IsOnJob(self): """是否在职(UserPositionStatus == 0)""" return self.UserPositionStatus == 0 @property def IsAnonymous(self): return False @property def IsAuthenticated(self): return True @property def PK(self): return UUIDObj(self.UserUUID) @property def ID(self): return UUIDObj(self.UserUUID) def SetPassword(self, RawPassword): """设置密码——bcrypt 哈希""" import bcrypt salt = bcrypt.gensalt(rounds=12) hashed = bcrypt.hashpw(RawPassword.encode('utf-8'), salt) self.UserPassword = hashed.decode('utf-8') def CheckPassword(self, RawPassword): """验证密码——兼容 bcrypt 哈希和明文存储""" import bcrypt stored = self.UserPassword or '' # bcrypt 哈希以 $2b$ 或 $2a$ 开头,长度固定 60 if stored.startswith(('$2b$', '$2a$')) and len(stored) == 60: try: return bcrypt.checkpw( RawPassword.encode('utf-8'), stored.encode('utf-8') ) except Exception: return False else: # 明文密码(旧数据兼容) return stored == RawPassword def GetTele(self): """获取电话,空则返回默认文本""" return self.UserTele or '未填写' def GetCompanies(self): """获取用户所属公司列表""" # Company 模型不在 gvsdsdk 中,此处返回空 QuerySet # 如需使用,请在子服务器中自行实现 return [] def GetRoles(self): """获取用户角色列表(需在同一 database 连接下使用)""" user_role_uuids = UserRole.objects.filter( UserUUID=self.UserUUID ).values_list('RoleUUID', flat=True) return Role.objects.filter(RoleUUID__in=user_role_uuids) def GetDepartments(self): """获取用户部门列表""" # Department 模型不在 gvsdsdk 中,此处返回空列表 # 如需使用,请在子服务器中自行实现 return [] def HasPermission(self, PermCode): """检查用户是否拥有指定权限(需在同一 database 连接下使用) 通过 UserRole → RolePermission → Permission 链路查询, 汇总所有角色的操作权限后判断。 """ user_role_uuids = UserRole.objects.filter( UserUUID=self.UserUUID ).values_list('RoleUUID', flat=True) perm_uuids = RolePermission.objects.filter( RoleUUID__in=user_role_uuids ).values_list('PermUUID', flat=True) return Permission.objects.filter( PermUUID__in=perm_uuids, PermCode=PermCode, PermStatus=1, ).exists() # ── Django auth 框架必需属性 ── @property def is_active(self): return self.IsActive @property def is_anonymous(self): return self.IsAnonymous @property def is_authenticated(self): return self.IsAuthenticated @property def pk(self): return self.PK @property def id(self): return self.ID def set_password(self, raw_password): self.SetPassword(raw_password) def check_password(self, raw_password): return self.CheckPassword(raw_password) # ── 用户类型推断(通过 UserRole → Role 推断,带实例缓存)── @property def UserType(self): """用户类型 — 通过 UserRole 推断(带实例缓存)""" if hasattr(self, '_cached_user_type'): return self._cached_user_type try: role_uuids = UserRole.objects.filter( UserUUID=self.UserUUID ).values_list('RoleUUID', flat=True) role_names = set( Role.objects.filter(RoleUUID__in=role_uuids) .values_list('RoleName', flat=True) ) # 优先级:管理员 > 客服 > 负责人 > 组长 > 审核官 > 商家 > 打手 for rn in role_names: if '管理员' in rn or 'admin' in rn.lower(): self._cached_user_type = 'admin' return 'admin' for rn in role_names: if '客服' in rn or 'kefu' in rn.lower(): self._cached_user_type = 'kefu' return 'kefu' for rn in role_names: if '负责人' in rn or '管事' in rn or 'guanshi' in rn.lower(): self._cached_user_type = 'guanshi' return 'guanshi' for rn in role_names: if '组长' in rn or 'zuzhang' in rn.lower(): self._cached_user_type = 'zuzhang' return 'zuzhang' for rn in role_names: if '审核' in rn or 'shenheguan' in rn.lower(): self._cached_user_type = 'shenheguan' return 'shenheguan' for rn in role_names: if '商家' in rn or 'shangjia' in rn.lower(): self._cached_user_type = 'shop' return 'shop' for rn in role_names: if '打手' in rn or 'dashou' in rn.lower(): self._cached_user_type = 'dashou' return 'dashou' self._cached_user_type = 'normal' return 'normal' except Exception: self._cached_user_type = 'normal' return 'normal' # ── 收款信息(存于 UserSensitiveData)── @property def PaymentQRCode(self): """收款码图片(存于 UserSensitiveData.zhifu)""" try: return self.sensitive_data.zhifu except Exception: return '' @PaymentQRCode.setter def PaymentQRCode(self, value): try: sd = self.sensitive_data sd.zhifu = value sd.save(update_fields=['zhifu']) except Exception: pass @property def PaymentAccount(self): """收款账号(存于 UserSensitiveData.skzhanghao)""" try: return self.sensitive_data.skzhanghao except Exception: return '' @PaymentAccount.setter def PaymentAccount(self, value): try: sd = self.sensitive_data sd.skzhanghao = value sd.save(update_fields=['skzhanghao']) except Exception: pass # ── 关联扩展表快捷访问 ── # DashouProfile / ShopProfile / GuanshiProfile / BossProfile / # AdminProfile / KefuProfile / ZuzhangProfile / ShenheguanProfile # 均由 OneToOneField related_name 直接提供反向关联访问 def HasRole(self, role_code): """检查用户是否拥有指定角色(支持角色名或 UserType 值)""" ROLE_CODE_MAP = { 'admin': '管理员', 'kefu': '客服', 'guanshi': '负责人', 'zuzhang': '组长', 'shenheguan': '审核官', 'shop': '商家', 'dashou': '打手', 'normal': '消费者', } target = ROLE_CODE_MAP.get(role_code, role_code) try: role_uuids = UserRole.objects.filter( UserUUID=self.UserUUID ).values_list('RoleUUID', flat=True) return Role.objects.filter( RoleUUID__in=role_uuids, RoleName=target ).exists() except Exception: return False # === 用户头像表 === class UserAvatar(QModel): """用户头像表(映射 SAAS 主库 UserAvatar 表)""" UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID') UserAvatar = models.BinaryField(editable=True, db_column='UserAvatar', verbose_name='头像数据') AvatarUploadTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='AvatarUploadTime', verbose_name='上传时间') AvatarFileType = models.CharField(max_length=16, default='png', db_column='AvatarFileType', verbose_name='文件类型') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'UserAvatar' verbose_name = '用户头像' verbose_name_plural = '用户头像' # === 用户双因素认证表 === class User2FA(QModel): """用户双因素认证表(映射 SAAS 主库 User2FA 表)""" User2FAUUID = UUIDField(primary_key=True, db_column='User2FAUUID', verbose_name='2FA UUID') UserUUID = UUIDField(db_index=True, db_column='UserUUID', verbose_name='用户UUID') SecretKey = models.CharField(max_length=64, db_column='SecretKey', verbose_name='密钥') IsEnabled = models.SmallIntegerField(default=0, db_column='IsEnabled', verbose_name='是否启用') Verified = models.SmallIntegerField(default=0, db_column='Verified', verbose_name='已验证') LastUsedAt = models.DateTimeField(null=True, blank=True, db_column='LastUsedAt', verbose_name='最后使用') CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'User2FA' verbose_name = '双因素认证' verbose_name_plural = '双因素认证' # === 双因素认证待验证表 === class UserPending2FA(QModel): """双因素认证待验证表(映射 SAAS 主库 UserPending2FA 表)""" PendingUUID = UUIDField(primary_key=True, db_column='PendingUUID', verbose_name='待确认UUID') UserUUID = UUIDField(unique=True, db_column='UserUUID', verbose_name='用户UUID') SecretKey = models.CharField(max_length=64, db_column='SecretKey', verbose_name='密钥') ExpireAt = models.DateTimeField(db_column='ExpireAt', verbose_name='过期时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'UserPending2FA' verbose_name = '2FA待确认' verbose_name_plural = '2FA待确认' # === 临时令牌表 === def _EltEndTimeDefault(): from django.conf import settings days = getattr(settings, 'ELT_TOKEN_LIFETIME_DAYS', 30) return _datetime.datetime.utcnow() + _datetime.timedelta(days=days) class EphemeralToken(QModel): """临时令牌表——用于 API 认证与会话管理(映射 SAAS 主库 EphemeralToken 表)""" ELTUUID = UUIDField(primary_key=True, db_column='ELTUUID', verbose_name='令牌UUID') ELTUserUUID = UUIDField(db_index=True, db_column='ELTUserUUID', verbose_name='用户UUID') ELTCreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='ELTCreateTime', verbose_name='创建时间') ELTEndTime = models.DateTimeField(default=_EltEndTimeDefault, db_column='ELTEndTime', verbose_name='过期时间') ELTType = models.SmallIntegerField(default=0, db_column='ELTType', verbose_name='令牌类型') ELTAllowIP = models.JSONField(default=list, db_column='ELTAllowIP', verbose_name='允许IP') ELTStatus = models.SmallIntegerField(default=1, db_index=True, db_column='ELTStatus', verbose_name='状态') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'EphemeralToken' verbose_name = '临时令牌' verbose_name_plural = '临时令牌' def IsExpired(self): """检查令牌是否过期""" return _datetime.datetime.utcnow() > self.ELTEndTime.replace(tzinfo=None) def IsValid(self): """检查令牌是否有效""" return self.ELTStatus == 1 and not self.IsExpired() # === 用户登录日志表 === class UserLoginLog(QModel): """用户登录日志表(映射 SAAS 主库 UserLoginLog 表)""" LogUUID = UUIDField(primary_key=True, db_column='LogUUID', verbose_name='日志UUID') UserUUID = UUIDField(db_index=True, db_column='UserUUID', verbose_name='用户UUID') LoginIP = models.CharField(max_length=39, db_column='LoginIP', verbose_name='登录IP') LoginPlatform = models.CharField(max_length=32, db_column='LoginPlatform', verbose_name='登录平台') LoginStatus = models.SmallIntegerField(db_column='LoginStatus', verbose_name='登录状态') LoginTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='LoginTime', verbose_name='登录时间') UserAgent = models.CharField(max_length=512, default='', db_column='UserAgent', verbose_name='用户代理') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'UserLoginLog' verbose_name = '登录日志' verbose_name_plural = '登录日志' # === 账户激活码表 === class AccountActivation(QModel): """账户激活码表(映射 SAAS 主库 AccountActivation 表)""" AccountActivationCode = FixedBinaryField(primary_key=True, binary_length=6, db_column='AccountActivationCode', verbose_name='激活码') UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID') CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'AccountActivation' verbose_name = '账户激活' verbose_name_plural = '账户激活' # === 角色表 === class Role(QModel): """角色表——权限分配单元(映射 SAAS 主库 Role 表) 角色是真正承载操作权限的实体,在单个租户内部独立存在, 不依附任何公司或部门。通过 AssignScope 控制角色的分配可见范围: - 0:全租户管理员可分发 - 1:仅指定公司管理员可分发(AssignScopeUUID = CompanyUUID) - 2:仅指定部门管理员可分发(AssignScopeUUID = DeptUUID) """ RoleUUID = UUIDField(primary_key=True, db_column='RoleUUID', verbose_name='角色UUID') TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID') RoleName = models.CharField(max_length=128, db_column='RoleName', verbose_name='角色名称') RoleType = models.SmallIntegerField(default=2, db_column='RoleType', verbose_name='角色类型') RoleStatus = models.SmallIntegerField(default=1, db_column='RoleStatus', verbose_name='角色状态') AssignScope = models.SmallIntegerField(default=0, db_column='AssignScope', verbose_name='分配可见范围') AssignScopeUUID = UUIDField(null=True, blank=True, db_column='AssignScopeUUID', verbose_name='分配范围UUID') RoleDesc = models.CharField(max_length=512, default='', db_column='RoleDesc', verbose_name='角色描述') CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'Role' verbose_name = '角色' verbose_name_plural = '角色' def __str__(self): return self.RoleName def GetPermissions(self): """获取角色拥有的权限列表(通过 RolePermission 中间表)""" perm_uuids = RolePermission.objects.filter( RoleUUID=self.RoleUUID ).values_list('PermUUID', flat=True) return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1) def GetUsers(self): """获取拥有该角色的用户列表""" user_uuids = UserRole.objects.filter( RoleUUID=self.RoleUUID ).values_list('UserUUID', flat=True) return User.objects.filter(UserUUID__in=user_uuids) # === 权限表 === class Permission(QModel): """权限表——操作权限定义(映射 SAAS 主库 Permission 表) 独立的权限定义表,租户内全局可用,不再挂在角色下。 通过 RolePermission 中间表与角色建立多对多关系, 通过 PositionTemplatePermission 中间表与岗位模板建立多对多关系。 """ PermUUID = UUIDField(primary_key=True, db_column='PermUUID', verbose_name='权限UUID') TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID') PermName = models.CharField(max_length=128, db_column='PermName', verbose_name='权限名称') PermCode = models.CharField(max_length=128, db_column='PermCode', verbose_name='权限键') PermCategory = models.CharField(max_length=64, default='', db_column='PermCategory', verbose_name='权限分类') PermStatus = models.SmallIntegerField(default=1, db_column='PermStatus', verbose_name='是否启用') PermDesc = models.CharField(max_length=512, default='', db_column='PermDesc', verbose_name='描述') CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'Permission' verbose_name = '权限' verbose_name_plural = '权限' def __str__(self): return f"{self.PermName} ({self.PermCode})" def GetRoles(self): """获取拥有该权限的角色列表""" role_uuids = RolePermission.objects.filter( PermUUID=self.PermUUID ).values_list('RoleUUID', flat=True) return Role.objects.filter(RoleUUID__in=role_uuids) # === 用户角色关联表 === class UserRole(QModel): """用户角色关联表——用户与角色的多对多映射(映射 SAAS 主库 UserRole 表) 通过 CompanyUUID 标识该用户-角色关联所属的公司, 用于菜单按公司差异化显示、数据权限按公司过滤等场景。 同一用户在不同公司下可绑定不同角色。 注意:UserUUID 和 RoleUUID 在 gvsds 中是 ForeignKey, 但此处改为 UUIDField 以保持独立性。 """ UserRoleUUID = UUIDField(primary_key=True, db_column='UserRoleUUID', verbose_name='关联UUID') # 原 gvsds 中为 ForeignKey(User),此处改为 UUIDField UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID') # 原 gvsds 中为 ForeignKey('Role'),此处改为 UUIDField RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID') CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID') CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'UserRole' verbose_name = '用户角色关联' verbose_name_plural = '用户角色关联' # === 角色权限关联表 === class RolePermission(QModel): """角色权限关联表——角色与操作权限的多对多映射(映射 SAAS 主库 RolePermission 表) 一个角色可以拥有多个权限,一个权限也可以被多个角色引用。 角色和具体操作权限为多对多绑定关系,一套权限集合可以打包在一个角色内统一维护。 """ RolePermUUID = UUIDField(primary_key=True, db_column='RolePermUUID', verbose_name='关联UUID') RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID') PermUUID = UUIDField(db_column='PermUUID', verbose_name='权限UUID') CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'RolePermission' verbose_name = '角色权限关联' verbose_name_plural = '角色权限关联' # === 用户 ABAC 表 === class UserABAC(QModel): """用户 ABAC 表——基于属性的访问控制(映射 SAAS 主库 UserABAC 表)""" UserABACUUID = UUIDField(primary_key=True, db_column='UserABACUUID', verbose_name='ABAC UUID') UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID') HierarchicalPermissionIdentifiers = models.CharField(max_length=256, db_column='HierarchicalPermissionIdentifiers', verbose_name='层级权限标识') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'UserABAC' verbose_name = '用户ABAC' verbose_name_plural = '用户ABAC' # === 密码表 === class Password(QModel): """密码表——独立密码存储(映射 SAAS 主库 Password 表)""" UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID') Password = models.CharField(max_length=1024, db_column='Password', verbose_name='密码哈希') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'Password' verbose_name = '密码' verbose_name_plural = '密码' # === 商铺表 === class Shop(QModel): """商铺表——SAAS 多商铺体系中的商铺实体 每个商铺属于一个公司(CompanyUUID),可关联多个角色(通过 RoleShop)。 """ ShopUUID = UUIDField(primary_key=True, db_column='ShopUUID', verbose_name='商铺UUID') CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID') ShopName = models.CharField(max_length=128, db_column='ShopName', verbose_name='商铺名称') ShopCode = models.CharField(max_length=64, default='', db_column='ShopCode', verbose_name='商铺编码') ShopType = models.SmallIntegerField(default=0, db_column='ShopType', verbose_name='商铺类型') ShopStatus = models.SmallIntegerField(default=1, db_column='ShopStatus', verbose_name='商铺状态') ShopAddress = models.CharField(max_length=512, default='', db_column='ShopAddress', verbose_name='商铺地址') ShopContact = models.CharField(max_length=128, default='', db_column='ShopContact', verbose_name='联系人') ShopPhone = models.CharField(max_length=32, default='', db_column='ShopPhone', verbose_name='联系电话') ShopDesc = models.CharField(max_length=512, default='', db_column='ShopDesc', verbose_name='描述') CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'Shop' verbose_name = '商铺' verbose_name_plural = '商铺' def __str__(self): return self.ShopName def GetRoles(self): """获取商铺关联的所有角色""" role_uuids = RoleShop.objects.filter( ShopUUID=self.ShopUUID ).values_list('RoleUUID', flat=True) return Role.objects.filter(RoleUUID__in=role_uuids) # === 角色商铺关联表 === class RoleShop(QModel): """角色商铺关联表——角色与商铺的多对多映射 一个角色可以管理多个商铺,一个商铺也可以被多个角色管理。 """ RoleShopUUID = UUIDField(primary_key=True, db_column='RoleShopUUID', verbose_name='关联UUID') RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID') ShopUUID = UUIDField(db_column='ShopUUID', verbose_name='商铺UUID') IsMainShop = models.SmallIntegerField(default=0, db_column='IsMainShop', verbose_name='主商铺') CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'RoleShop' verbose_name = '角色商铺关联' verbose_name_plural = '角色商铺关联' # === 岗位模板表 === class PositionTemplate(QModel): """岗位模板表——权限初始化快照模板(映射 SAAS 主库 PositionTemplate 表) 租户内全局可重复使用,定位是权限初始化快照模板, 不直接分配给员工、不参与运行时鉴权。 新建角色时可一键复制模板内的全部权限内容生成新角色。 模板后续调整只会作用于之后新创建的角色, 已有角色不会同步变更,实现模板复用和存量权限独立管控互不干扰。 """ PositionTemplateUUID = UUIDField(primary_key=True, db_column='PositionTemplateUUID', verbose_name='岗位模板UUID') TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID') TemplateName = models.CharField(max_length=128, db_column='TemplateName', verbose_name='模板名称') TemplateCode = models.CharField(max_length=64, default='', db_column='TemplateCode', verbose_name='模板编码') TemplateStatus = models.SmallIntegerField(default=1, db_column='TemplateStatus', verbose_name='模板状态') TemplateDesc = models.CharField(max_length=512, default='', db_column='TemplateDesc', verbose_name='模板描述') CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'PositionTemplate' verbose_name = '岗位模板' verbose_name_plural = '岗位模板' def __str__(self): return self.TemplateName def GetPermissions(self): """获取模板关联的权限列表""" perm_uuids = PositionTemplatePermission.objects.filter( PositionTemplateUUID=self.PositionTemplateUUID ).values_list('PermUUID', flat=True) return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1) def CopyToRole(self, RoleName, TenantUUID): """从模板复制权限到新角色(一次性复制,后续模板变更不影响已有角色)""" role = Role.objects.create( RoleUUID=_uuid.uuid4().bytes, TenantUUID=TenantUUID, RoleName=RoleName, ) template_perms = PositionTemplatePermission.objects.filter( PositionTemplateUUID=self.PositionTemplateUUID ) for tp in template_perms: RolePermission.objects.create( RolePermUUID=_uuid.uuid4().bytes, RoleUUID=role.RoleUUID, PermUUID=tp.PermUUID, ) return role # === 岗位模板权限关联表 === class PositionTemplatePermission(QModel): """岗位模板权限关联表——岗位模板与操作权限的多对多映射 仅作为复制源头,不参与运行时鉴权。 新建角色时一键复制模板权限到角色,复制后模板与角色无运行时绑定。 """ PTPermUUID = UUIDField(primary_key=True, db_column='PTPermUUID', verbose_name='关联UUID') PositionTemplateUUID = UUIDField(db_column='PositionTemplateUUID', verbose_name='岗位模板UUID') PermUUID = UUIDField(db_column='PermUUID', verbose_name='权限UUID') CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'PositionTemplatePermission' verbose_name = '岗位模板权限关联' verbose_name_plural = '岗位模板权限关联' # === 人事职务标签表 === class Position(QModel): """人事职务标签表——仅用于人事管理标注(映射 SAAS 主库 Position 表) 员工会标注对应的职务标签,该标签仅为人事标注信息, 不直接绑定权限,和角色、权限体系完全解耦。 人员调岗、升职只修改组织归属和人事职务标签,权限只需增删绑定角色即可,互不干扰。 """ PositionUUID = UUIDField(primary_key=True, db_column='PositionUUID', verbose_name='职务UUID') TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID') PositionName = models.CharField(max_length=128, db_column='PositionName', verbose_name='职务名称') PositionCode = models.CharField(max_length=64, default='', db_column='PositionCode', verbose_name='职务编码') PositionLevel = models.SmallIntegerField(default=0, db_column='PositionLevel', verbose_name='职务级别') PositionStatus = models.SmallIntegerField(default=1, db_column='PositionStatus', verbose_name='状态') PositionDesc = models.CharField(max_length=512, default='', db_column='PositionDesc', verbose_name='描述') CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'Position' verbose_name = '人事职务标签' verbose_name_plural = '人事职务标签' def __str__(self): return self.PositionName # === 员工部门关联表 === class UserDept(QModel): """员工部门关联表——员工与部门的多对多映射(映射 SAAS 主库 UserDept 表) 支持一人多部门兼职,同时标注该员工在对应部门的职务标签。 该层级仅用于组织架构展示、审批流转、人员统计, 同时为数据权限判断提供人员所属组织属性依据,不参与操作权限校验。 """ UserDeptUUID = UUIDField(primary_key=True, db_column='UserDeptUUID', verbose_name='关联UUID') UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID') DeptUUID = UUIDField(db_column='DeptUUID', verbose_name='部门UUID') PositionUUID = UUIDField(null=True, blank=True, db_column='PositionUUID', verbose_name='职务标签UUID') IsMainDept = models.SmallIntegerField(default=1, db_column='IsMainDept', verbose_name='是否主部门') CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') class Meta: managed = True app_label = 'gvsdsdk' db_table = 'UserDept' verbose_name = '员工部门关联' verbose_name_plural = '员工部门关联'