加入了 GVSDSDK 模块,进行了 QModel 兼容层的尝试,生产环境可用
This commit is contained in:
813
gvsdsdk/models.py
Normal file
813
gvsdsdk/models.py
Normal file
@@ -0,0 +1,813 @@
|
||||
"""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')
|
||||
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='最后登录')
|
||||
IsStaff = models.BooleanField(default=False, db_column='IsStaff', verbose_name='是否员工')
|
||||
IsSuperuser = models.BooleanField(default=False, db_column='IsSuperuser', verbose_name='是否超级用户')
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
app_label = 'gvsdsdk'
|
||||
db_table = 'User'
|
||||
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):
|
||||
"""账户是否启用(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):
|
||||
"""验证密码"""
|
||||
import bcrypt
|
||||
try:
|
||||
return bcrypt.checkpw(
|
||||
RawPassword.encode('utf-8'),
|
||||
self.UserPassword.encode('utf-8')
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
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)
|
||||
|
||||
|
||||
# === 用户头像表 ===
|
||||
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
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 = False
|
||||
app_label = 'gvsdsdk'
|
||||
db_table = 'UserDept'
|
||||
verbose_name = '员工部门关联'
|
||||
verbose_name_plural = '员工部门关联'
|
||||
Reference in New Issue
Block a user