import uuid import 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 """模块:Core 数据模型 定义系统核心表:用户、头像、双因素认证、登录日志、账户激活。 所有 ORM 字段与方法遵循帕斯卡命名法(首字母大写、缩写大写、单单词小写)。""" def _elt_end_time_default(): """已废弃:ELT 令牌过期时间默认值,仅为迁移历史兼容保留""" from gvsds.app_config import getint as _app_getint days = _app_getint('elt', 'TOKEN_LIFETIME_DAYS', fallback=30) return datetime.datetime.utcnow() + datetime.timedelta(days=days) class UUIDField(models.BinaryField): """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): """数据库读取:BINARY(16) → UUIDObj""" if value is None: return None if isinstance(value, UUIDObj): return value return UUIDObj(value) def get_prep_value(self, value): """写入数据库/查询:UUIDObj/str/bytes/uuid.UUID → BINARY(16)""" 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): """确保查询时也正确转换 UUID 字符串为 BINARY(16)""" 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): """序列化:返回 UUID 字符串""" 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 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=UUIDObj(uuid.uuid4()), 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): """用户表——系统核心身份模型""" UserUUID = UUIDField(primary_key=True, verbose_name='用户UUID') UserName = models.CharField(max_length=256, unique=True, verbose_name='用户名') UserPassword = models.CharField(max_length=60, default='', verbose_name='密码') # 无默认密码(防默认口令后门),须经注册/重置流程设置 UserPositionStatus = models.SmallIntegerField(default=0, verbose_name='职位状态') UserPersonalSignature = models.CharField(max_length=1024, default='尚未及提笔自叙,浅墨先勾鹤影', verbose_name='个人签名') UserEmail = models.CharField(max_length=256, default='', verbose_name='邮箱') UserEmailVerify = models.SmallIntegerField(default=0, verbose_name='邮箱验证') UserAccountLicense = models.SmallIntegerField(default=1, verbose_name='账户许可') UserGender = models.SmallIntegerField(default=-1, verbose_name='性别') UserTele = models.CharField(max_length=16, null=True, blank=True, verbose_name='电话') UserCreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间') UserLastLoginDate = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='最后登录') IsStaff = models.BooleanField(default=False, verbose_name='是否员工') IsSuperuser = models.BooleanField(default=False, verbose_name='是否超级用户') objects = UserManager() class Meta: db_table = 'User' app_label = 'core' verbose_name = '用户' verbose_name_plural = '用户' def __str__(self): return self.UserName @property def UUIDStr(self): """返回 UUID 字符串形式""" return UUIDObj(self.UserUUID).str @property def IsActive(self): return self.UserAccountLicense == 1 @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, raw_password): """设置密码——bcrypt 哈希""" import bcrypt salt = bcrypt.gensalt(rounds=12) hashed = bcrypt.hashpw(raw_password.encode('utf-8'), salt) self.UserPassword = hashed.decode('utf-8') def CheckPassword(self, raw_password): """验证密码""" import bcrypt try: return bcrypt.checkpw( raw_password.encode('utf-8'), self.UserPassword.encode('utf-8') ) except Exception: return False def GetTele(self): """获取电话,空则返回默认文本""" return self.UserTele or '未填写' def GetCompanies(self): """获取用户所属公司列表(通过 UserRole.CompanyUUID 直接关联)""" from gvsdsdk.auth.managers import CompanyManager return CompanyManager.GetCompaniesForUser(self.UserUUID) def GetRoles(self): """获取用户角色列表(角色不绑定公司/部门,租户内独立存在)""" from gvsdsdk.auth.managers import RoleManager return RoleManager.GetRolesForUser(self.UserUUID) def GetDepartments(self): """获取用户部门列表(通过 UserRoleDepartment 关联表)""" from gvsdsdk.auth.managers import DepartmentManager return DepartmentManager.GetDepartmentsForUser(self.UserUUID) def HasPermission(self, perm_code): """检查用户是否拥有指定权限 通过 UserRole → RolePermission → Permission 链路查询, 汇总所有角色的操作权限后判断(委托 gvsdsdk.RoleManager)。 """ from gvsdsdk.auth.managers import RoleManager return RoleManager.HasUserPermission(self.UserUUID, perm_code) # 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) class UserAvatar(QModel): """用户头像表""" UserUUID = UUIDField(primary_key=True, verbose_name='用户UUID') UserAvatar = models.BinaryField(editable=True, verbose_name='头像数据') AvatarUploadTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='上传时间') AvatarFileType = models.CharField(max_length=16, default='png', verbose_name='文件类型') class Meta: db_table = 'UserAvatar' app_label = 'core' verbose_name = '用户头像' verbose_name_plural = '用户头像' class User2FA(QModel): """用户双因素认证表""" User2FAUUID = UUIDField(primary_key=True, verbose_name='2FA UUID') UserUUID = UUIDField(db_index=True, verbose_name='用户UUID') SecretKey = models.CharField(max_length=64, verbose_name='密钥') IsEnabled = models.SmallIntegerField(default=0, verbose_name='是否启用') Verified = models.SmallIntegerField(default=0, verbose_name='已验证') LastUsedAt = models.DateTimeField(null=True, blank=True, verbose_name='最后使用') CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间') class Meta: db_table = 'User2FA' app_label = 'core' verbose_name = '双因素认证' verbose_name_plural = '双因素认证' class UserPending2FA(QModel): """双因素认证待验证表""" PendingUUID = UUIDField(primary_key=True, verbose_name='待确认UUID') UserUUID = UUIDField(unique=True, verbose_name='用户UUID') SecretKey = models.CharField(max_length=64, verbose_name='密钥') ExpireAt = models.DateTimeField(verbose_name='过期时间') class Meta: db_table = 'UserPending2FA' app_label = 'core' verbose_name = '2FA待确认' verbose_name_plural = '2FA待确认' class UserLoginLog(QModel): """用户登录日志表""" LogUUID = UUIDField(primary_key=True, verbose_name='日志UUID') UserUUID = UUIDField(db_index=True, verbose_name='用户UUID') LoginIP = models.CharField(max_length=39, verbose_name='登录IP') LoginPlatform = models.CharField(max_length=32, verbose_name='登录平台') LoginStatus = models.SmallIntegerField(verbose_name='登录状态') LoginTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='登录时间') UserAgent = models.CharField(max_length=512, default='', verbose_name='用户代理') class Meta: db_table = 'UserLoginLog' app_label = 'core' verbose_name = '登录日志' verbose_name_plural = '登录日志' class AccountActivation(QModel): """账户激活码表""" AccountActivationCode = models.BinaryField(primary_key=True, verbose_name='激活码') UserUUID = UUIDField(verbose_name='用户UUID') CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间') class Meta: db_table = 'AccountActivation' app_label = 'core' verbose_name = '账户激活' verbose_name_plural = '账户激活'