Files
Django/gvsdsdk/models.py

1039 lines
41 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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='最后登录')
# === 本地业务字段(去远程化后新增) ===
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):
"""验证密码"""
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 id(self):
"""兼容旧代码的 id 属性,返回 UserUUID"""
return self.UserUUID
@property
def yonghuid(self):
"""兼容旧代码的 yonghuid 属性"""
return self.UserUID
@property
def openid(self):
"""兼容旧代码的 openid 属性"""
return self.OpenID
@property
def user_type(self):
"""兼容旧代码的 user_type 属性 — 通过 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'
@property
def pk(self):
return self.PK
@property
def id(self):
return self.ID
@property
def shoujihao_renzheng(self):
"""兼容旧代码 — 手机号是否已认证"""
return self.PhoneVerified
@shoujihao_renzheng.setter
def shoujihao_renzheng(self, value):
self.PhoneVerified = bool(value)
# ── 旧字段兼容属性UserMain → gvsdsdk.User 映射)──
@property
def avatar(self):
return self.Avatar
@avatar.setter
def avatar(self, value):
self.Avatar = value
@property
def phone(self):
return self.Phone
@phone.setter
def phone(self, value):
self.Phone = value
@property
def ip(self):
return self.IP
@ip.setter
def ip(self, value):
self.IP = value
@property
def last_login_time(self):
return self.UserLastLoginDate
@last_login_time.setter
def last_login_time(self, value):
self.UserLastLoginDate = value
@property
def zhifu(self):
"""兼容旧代码 — 收款码图片(存于 UserSensitiveData"""
try:
return self.sensitive_data.zhifu
except Exception:
return ''
@zhifu.setter
def zhifu(self, value):
try:
sd = self.sensitive_data
sd.zhifu = value
sd.save(update_fields=['zhifu'])
except Exception:
pass
@property
def skzhanghao(self):
"""兼容旧代码 — 收款账号(存于 UserSensitiveData"""
try:
return self.sensitive_data.skzhanghao
except Exception:
return ''
@skzhanghao.setter
def skzhanghao(self, value):
try:
sd = self.sensitive_data
sd.skzhanghao = value
sd.save(update_fields=['skzhanghao'])
except Exception:
pass
def set_password(self, raw_password):
self.SetPassword(raw_password)
def check_password(self, raw_password):
return self.CheckPassword(raw_password)
@property
def dashou_profile(self):
"""兼容旧代码"""
from users.models import UserDashou
return UserDashou.objects.filter(user=self).first()
@property
def shop_profile(self):
"""兼容旧代码"""
from users.models import UserShangjia
return UserShangjia.objects.filter(user=self).first()
@property
def guanshi_profile(self):
"""兼容旧代码"""
from users.models import UserGuanshi
return UserGuanshi.objects.filter(user=self).first()
@property
def boss_profile(self):
"""兼容旧代码"""
from users.models import UserBoss
return UserBoss.objects.filter(user=self).first()
@property
def admin_profile(self):
"""兼容旧代码"""
from users.models import AdminProfile
return AdminProfile.objects.filter(user=self).first()
@property
def kefu_profile(self):
"""兼容旧代码"""
from users.models import UserKefu
return UserKefu.objects.filter(user=self).first()
@property
def zuzhang_profile(self):
"""兼容旧代码"""
from users.models import UserZuzhang
return UserZuzhang.objects.filter(user=self).first()
@property
def shenheguan_profile(self):
"""兼容旧代码"""
from users.models import UserShenheguan
return UserShenheguan.objects.filter(user=self).first()
def has_role(self, role_code):
"""检查用户是否拥有指定角色(支持角色名或旧 user_type 值)"""
# 角色名映射:旧 user_type → 角色名
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 = '员工部门关联'