进行了 GVSDSDK 迁移,可能存在诸多问题

This commit is contained in:
2026-06-17 20:37:47 +08:00
parent 72770ad73b
commit d00f0d08e5
132 changed files with 161593 additions and 4827 deletions

View File

@@ -92,27 +92,34 @@ class RoleManager:
return User.objects.filter(UserUUID__in=user_uuids)
@staticmethod
def AssignPermission(RoleUUID, PermUUID):
"""为角色分配权限"""
def AssignPermission(RoleUUID, PermUUID, CompanyUUID):
"""为角色分配权限(指定公司)"""
rp = RolePermission(
RolePermUUID=uuid.uuid4().bytes,
RoleUUID=RoleUUID,
PermUUID=PermUUID,
CompanyUUID=CompanyUUID,
CreateTime=datetime.datetime.utcnow(),
)
rp.save()
return rp
@staticmethod
def RemovePermission(RoleUUID, PermUUID):
def RemovePermission(RoleUUID, PermUUID, CompanyUUID=None):
"""移除角色的指定权限"""
deleted, _ = RolePermission.objects.filter(RoleUUID=RoleUUID, PermUUID=PermUUID).delete()
qs = RolePermission.objects.filter(RoleUUID=RoleUUID, PermUUID=PermUUID)
if CompanyUUID is not None:
qs = qs.filter(CompanyUUID=CompanyUUID)
deleted, _ = qs.delete()
return deleted > 0
@staticmethod
def GetPermissionsForRole(RoleUUID):
"""获取角色的所有权限"""
perm_uuids = RolePermission.objects.filter(RoleUUID=RoleUUID).values_list('PermUUID', flat=True)
def GetPermissionsForRole(RoleUUID, CompanyUUID=None):
"""获取角色的所有权限(可按公司过滤)"""
qs = RolePermission.objects.filter(RoleUUID=RoleUUID)
if CompanyUUID is not None:
qs = qs.filter(CompanyUUID=CompanyUUID)
perm_uuids = qs.values_list('PermUUID', flat=True)
return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1)

View File

@@ -48,7 +48,7 @@ def PermissionVerifier(permission, attributes, user=None, request=None):
"""权限验证器——混合 ABAC + RBAC 检查
RBAC 仅检查当前角色CurrentRoleUUID Cookie 指定的角色)的权限,
通过 RolePermission → Permission 链路查询。
通过 RolePermission → Permission 链路查询,按当前公司过滤
超级管理员/员工直接放行。
"""
if not permission:
@@ -63,6 +63,7 @@ def PermissionVerifier(permission, attributes, user=None, request=None):
return True
current_role = getattr(request, 'current_role', None) if request else None
current_company = getattr(request, 'current_company_uuid', None) if request else None
for attr in attributes:
# ABAC 检查
@@ -73,13 +74,16 @@ def PermissionVerifier(permission, attributes, user=None, request=None):
if abac_match:
continue
# RBAC 检查:仅检查当前角色
# RBAC 检查:仅检查当前角色,按公司过滤权限
if current_role and isinstance(current_role, Role):
if current_role.RoleStatus != 1:
return False
perm_uuids = RolePermission.objects.filter(
rp_qs = RolePermission.objects.filter(
RoleUUID=current_role.RoleUUID
).values_list('PermUUID', flat=True)
)
if current_company:
rp_qs = rp_qs.filter(CompanyUUID=current_company)
perm_uuids = rp_qs.values_list('PermUUID', flat=True)
rbac_result = Permission.objects.filter(
PermUUID__in=perm_uuids,
PermCode=attr,
@@ -94,9 +98,12 @@ def PermissionVerifier(permission, attributes, user=None, request=None):
RoleUUID__in=user_role_uuids,
RoleStatus=1,
).values_list('RoleUUID', flat=True)
perm_uuids = RolePermission.objects.filter(
rp_qs = RolePermission.objects.filter(
RoleUUID__in=active_role_uuids
).values_list('PermUUID', flat=True)
)
if current_company:
rp_qs = rp_qs.filter(CompanyUUID=current_company)
perm_uuids = rp_qs.values_list('PermUUID', flat=True)
rbac_result = Permission.objects.filter(
PermUUID__in=perm_uuids,
PermCode=attr,

View File

@@ -1,8 +1,8 @@
# Generated by Django 6.0.5 on 2026-06-13 11:43
# Generated by Django 4.2.27 on 2026-06-17 17:07
import datetime
import gvsdsdk.models
from django.db import migrations, models
import gvsdsdk.models
class Migration(migrations.Migration):
@@ -16,9 +16,9 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='AccountActivation',
fields=[
('AccountActivationCode', gvsdsdk.models.FixedBinaryField(binary_length=6, db_column='AccountActivationCode', max_length=6, primary_key=True, serialize=False)),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)),
('AccountActivationCode', gvsdsdk.models.FixedBinaryField(binary_length=6, db_column='AccountActivationCode', max_length=6, primary_key=True, serialize=False, verbose_name='激活码')),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', verbose_name='用户UUID')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
],
options={
'verbose_name': '账户激活',
@@ -30,13 +30,13 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='EphemeralToken',
fields=[
('ELTUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ELTUUID', primary_key=True, serialize=False)),
('ELTUserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ELTUserUUID', db_index=True)),
('ELTCreateTime', models.DateTimeField(db_column='ELTCreateTime', default=datetime.datetime.utcnow)),
('ELTEndTime', models.DateTimeField(db_column='ELTEndTime', default=gvsdsdk.models._elt_end_time_default)),
('ELTType', models.SmallIntegerField(db_column='ELTType', default=0)),
('ELTAllowIP', models.JSONField(db_column='ELTAllowIP', default=list)),
('ELTStatus', models.SmallIntegerField(db_column='ELTStatus', db_index=True, default=1)),
('ELTUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ELTUUID', primary_key=True, serialize=False, verbose_name='令牌UUID')),
('ELTUserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ELTUserUUID', db_index=True, verbose_name='用户UUID')),
('ELTCreateTime', models.DateTimeField(db_column='ELTCreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
('ELTEndTime', models.DateTimeField(db_column='ELTEndTime', default=gvsdsdk.models._EltEndTimeDefault, verbose_name='过期时间')),
('ELTType', models.SmallIntegerField(db_column='ELTType', default=0, verbose_name='令牌类型')),
('ELTAllowIP', models.JSONField(db_column='ELTAllowIP', default=list, verbose_name='允许IP')),
('ELTStatus', models.SmallIntegerField(db_column='ELTStatus', db_index=True, default=1, verbose_name='状态')),
],
options={
'verbose_name': '临时令牌',
@@ -48,8 +48,8 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Password',
fields=[
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', primary_key=True, serialize=False)),
('Password', models.CharField(db_column='Password', max_length=1024)),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', primary_key=True, serialize=False, verbose_name='用户UUID')),
('Password', models.CharField(db_column='Password', max_length=1024, verbose_name='密码哈希')),
],
options={
'verbose_name': '密码',
@@ -61,12 +61,14 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Permission',
fields=[
('PermUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PermUUID', primary_key=True, serialize=False)),
('PermName', models.CharField(db_column='PermName', max_length=128)),
('PermCode', models.CharField(db_column='PermCode', max_length=128)),
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID')),
('PermStatus', models.SmallIntegerField(db_column='PermStatus', default=1)),
('PermDesc', models.CharField(db_column='PermDesc', default='', max_length=512)),
('PermUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PermUUID', primary_key=True, serialize=False, verbose_name='权限UUID')),
('TenantUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='TenantUUID', verbose_name='租户UUID')),
('PermName', models.CharField(db_column='PermName', max_length=128, verbose_name='权限名称')),
('PermCode', models.CharField(db_column='PermCode', max_length=128, verbose_name='权限键')),
('PermCategory', models.CharField(db_column='PermCategory', default='', max_length=64, verbose_name='权限分类')),
('PermStatus', models.SmallIntegerField(db_column='PermStatus', default=1, verbose_name='是否启用')),
('PermDesc', models.CharField(db_column='PermDesc', default='', max_length=512, verbose_name='描述')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
],
options={
'verbose_name': '权限',
@@ -75,16 +77,72 @@ class Migration(migrations.Migration):
'managed': True,
},
),
migrations.CreateModel(
name='Position',
fields=[
('PositionUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PositionUUID', primary_key=True, serialize=False, verbose_name='职务UUID')),
('TenantUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='TenantUUID', verbose_name='租户UUID')),
('PositionName', models.CharField(db_column='PositionName', max_length=128, verbose_name='职务名称')),
('PositionCode', models.CharField(db_column='PositionCode', default='', max_length=64, verbose_name='职务编码')),
('PositionLevel', models.SmallIntegerField(db_column='PositionLevel', default=0, verbose_name='职务级别')),
('PositionStatus', models.SmallIntegerField(db_column='PositionStatus', default=1, verbose_name='状态')),
('PositionDesc', models.CharField(db_column='PositionDesc', default='', max_length=512, verbose_name='描述')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
('UpdateTime', models.DateTimeField(db_column='UpdateTime', default=datetime.datetime.utcnow, verbose_name='更新时间')),
],
options={
'verbose_name': '人事职务标签',
'verbose_name_plural': '人事职务标签',
'db_table': 'Position',
'managed': True,
},
),
migrations.CreateModel(
name='PositionTemplate',
fields=[
('PositionTemplateUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PositionTemplateUUID', primary_key=True, serialize=False, verbose_name='岗位模板UUID')),
('TenantUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='TenantUUID', verbose_name='租户UUID')),
('TemplateName', models.CharField(db_column='TemplateName', max_length=128, verbose_name='模板名称')),
('TemplateCode', models.CharField(db_column='TemplateCode', default='', max_length=64, verbose_name='模板编码')),
('TemplateStatus', models.SmallIntegerField(db_column='TemplateStatus', default=1, verbose_name='模板状态')),
('TemplateDesc', models.CharField(db_column='TemplateDesc', default='', max_length=512, verbose_name='模板描述')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
('UpdateTime', models.DateTimeField(db_column='UpdateTime', default=datetime.datetime.utcnow, verbose_name='更新时间')),
],
options={
'verbose_name': '岗位模板',
'verbose_name_plural': '岗位模板',
'db_table': 'PositionTemplate',
'managed': True,
},
),
migrations.CreateModel(
name='PositionTemplatePermission',
fields=[
('PTPermUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PTPermUUID', primary_key=True, serialize=False, verbose_name='关联UUID')),
('PositionTemplateUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PositionTemplateUUID', verbose_name='岗位模板UUID')),
('PermUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PermUUID', verbose_name='权限UUID')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
],
options={
'verbose_name': '岗位模板权限关联',
'verbose_name_plural': '岗位模板权限关联',
'db_table': 'PositionTemplatePermission',
'managed': True,
},
),
migrations.CreateModel(
name='Role',
fields=[
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID', primary_key=True, serialize=False)),
('CompanyUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='CompanyUUID')),
('DeptUUID', gvsdsdk.models.UUIDField(binary_length=16, blank=True, db_column='DeptUUID', null=True)),
('RoleName', models.CharField(db_column='RoleName', max_length=128)),
('RoleType', models.SmallIntegerField(db_column='RoleType', default=2)),
('RoleStatus', models.SmallIntegerField(db_column='RoleStatus', default=1)),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)),
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID', primary_key=True, serialize=False, verbose_name='角色UUID')),
('TenantUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='TenantUUID', verbose_name='租户UUID')),
('RoleName', models.CharField(db_column='RoleName', max_length=128, verbose_name='角色名称')),
('RoleType', models.SmallIntegerField(db_column='RoleType', default=2, verbose_name='角色类型')),
('RoleStatus', models.SmallIntegerField(db_column='RoleStatus', default=1, verbose_name='角色状态')),
('AssignScope', models.SmallIntegerField(db_column='AssignScope', default=0, verbose_name='分配可见范围')),
('AssignScopeUUID', gvsdsdk.models.UUIDField(binary_length=16, blank=True, db_column='AssignScopeUUID', null=True, verbose_name='分配范围UUID')),
('RoleDesc', models.CharField(db_column='RoleDesc', default='', max_length=512, verbose_name='角色描述')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
],
options={
'verbose_name': '角色',
@@ -94,55 +152,69 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
name='RoleDept',
name='RolePermission',
fields=[
('RoleDeptUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleDeptUUID', primary_key=True, serialize=False)),
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID')),
('DeptUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='DeptUUID')),
('IsMainDept', models.SmallIntegerField(db_column='IsMainDept', default=1)),
('RolePermUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RolePermUUID', primary_key=True, serialize=False, verbose_name='关联UUID')),
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID', verbose_name='角色UUID')),
('PermUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PermUUID', verbose_name='权限UUID')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
],
options={
'verbose_name': '角色部门关联',
'verbose_name_plural': '角色部门关联',
'db_table': 'RoleDept',
'verbose_name': '角色权限关联',
'verbose_name_plural': '角色权限关联',
'db_table': 'RolePermission',
'managed': True,
},
),
migrations.CreateModel(
name='User',
name='RoleShop',
fields=[
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', primary_key=True, serialize=False)),
('UserName', models.CharField(db_column='UserName', max_length=256, unique=True)),
('UserPassword', models.CharField(db_column='UserPassword', default='', max_length=60)),
('UserPositionStatus', models.SmallIntegerField(db_column='UserPositionStatus', default=0)),
('UserPersonalSignature', models.CharField(db_column='UserPersonalSignature', default='', max_length=1024)),
('UserEmail', models.CharField(db_column='UserEmail', default='', max_length=256)),
('UserEmailVerify', models.SmallIntegerField(db_column='UserEmailVerify', default=0)),
('UserAccountLicense', models.SmallIntegerField(db_column='UserAccountLicense', default=1)),
('UserGender', models.SmallIntegerField(db_column='UserGender', default=-1)),
('UserTele', models.CharField(blank=True, db_column='UserTele', max_length=16, null=True)),
('UserCreateTime', models.DateTimeField(db_column='UserCreateTime', default=datetime.datetime.utcnow)),
('UserLastLoginDate', models.DateTimeField(db_column='UserLastLoginDate', default=datetime.datetime.utcnow)),
('IsStaff', models.BooleanField(db_column='IsStaff', default=False)),
('IsSuperuser', models.BooleanField(db_column='IsSuperuser', default=False)),
('RoleShopUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleShopUUID', primary_key=True, serialize=False, verbose_name='关联UUID')),
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID', verbose_name='角色UUID')),
('ShopUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ShopUUID', verbose_name='商铺UUID')),
('IsMainShop', models.SmallIntegerField(db_column='IsMainShop', default=0, verbose_name='主商铺')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
],
options={
'verbose_name': '用户',
'verbose_name_plural': '用户',
'db_table': 'User',
'verbose_name': '角色商铺关联',
'verbose_name_plural': '角色商铺关联',
'db_table': 'RoleShop',
'managed': True,
},
),
migrations.CreateModel(
name='Shop',
fields=[
('ShopUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ShopUUID', primary_key=True, serialize=False, verbose_name='商铺UUID')),
('CompanyUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='CompanyUUID', verbose_name='公司UUID')),
('ShopName', models.CharField(db_column='ShopName', max_length=128, verbose_name='商铺名称')),
('ShopCode', models.CharField(db_column='ShopCode', default='', max_length=64, verbose_name='商铺编码')),
('ShopType', models.SmallIntegerField(db_column='ShopType', default=0, verbose_name='商铺类型')),
('ShopStatus', models.SmallIntegerField(db_column='ShopStatus', default=1, verbose_name='商铺状态')),
('ShopAddress', models.CharField(db_column='ShopAddress', default='', max_length=512, verbose_name='商铺地址')),
('ShopContact', models.CharField(db_column='ShopContact', default='', max_length=128, verbose_name='联系人')),
('ShopPhone', models.CharField(db_column='ShopPhone', default='', max_length=32, verbose_name='联系电话')),
('ShopDesc', models.CharField(db_column='ShopDesc', default='', max_length=512, verbose_name='描述')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
('UpdateTime', models.DateTimeField(db_column='UpdateTime', default=datetime.datetime.utcnow, verbose_name='更新时间')),
],
options={
'verbose_name': '商铺',
'verbose_name_plural': '商铺',
'db_table': 'Shop',
'managed': True,
},
),
migrations.CreateModel(
name='User2FA',
fields=[
('User2FAUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='User2FAUUID', primary_key=True, serialize=False)),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', db_index=True)),
('SecretKey', models.CharField(db_column='SecretKey', max_length=64)),
('IsEnabled', models.SmallIntegerField(db_column='IsEnabled', default=0)),
('Verified', models.SmallIntegerField(db_column='Verified', default=0)),
('LastUsedAt', models.DateTimeField(blank=True, db_column='LastUsedAt', null=True)),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)),
('User2FAUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='User2FAUUID', primary_key=True, serialize=False, verbose_name='2FA UUID')),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', db_index=True, verbose_name='用户UUID')),
('SecretKey', models.CharField(db_column='SecretKey', max_length=64, verbose_name='密钥')),
('IsEnabled', models.SmallIntegerField(db_column='IsEnabled', default=0, verbose_name='是否启用')),
('Verified', models.SmallIntegerField(db_column='Verified', default=0, verbose_name='已验证')),
('LastUsedAt', models.DateTimeField(blank=True, db_column='LastUsedAt', null=True, verbose_name='最后使用')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
],
options={
'verbose_name': '双因素认证',
@@ -154,9 +226,9 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='UserABAC',
fields=[
('UserABACUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserABACUUID', primary_key=True, serialize=False)),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID')),
('HierarchicalPermissionIdentifiers', models.CharField(db_column='HierarchicalPermissionIdentifiers', max_length=256)),
('UserABACUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserABACUUID', primary_key=True, serialize=False, verbose_name='ABAC UUID')),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', verbose_name='用户UUID')),
('HierarchicalPermissionIdentifiers', models.CharField(db_column='HierarchicalPermissionIdentifiers', max_length=256, verbose_name='层级权限标识')),
],
options={
'verbose_name': '用户ABAC',
@@ -168,10 +240,10 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='UserAvatar',
fields=[
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', primary_key=True, serialize=False)),
('UserAvatar', models.BinaryField(db_column='UserAvatar', editable=True)),
('AvatarUploadTime', models.DateTimeField(db_column='AvatarUploadTime', default=datetime.datetime.utcnow)),
('AvatarFileType', models.CharField(db_column='AvatarFileType', default='png', max_length=16)),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', primary_key=True, serialize=False, verbose_name='用户UUID')),
('UserAvatar', models.BinaryField(db_column='UserAvatar', editable=True, verbose_name='头像数据')),
('AvatarUploadTime', models.DateTimeField(db_column='AvatarUploadTime', default=datetime.datetime.utcnow, verbose_name='上传时间')),
('AvatarFileType', models.CharField(db_column='AvatarFileType', default='png', max_length=16, verbose_name='文件类型')),
],
options={
'verbose_name': '用户头像',
@@ -180,16 +252,33 @@ class Migration(migrations.Migration):
'managed': True,
},
),
migrations.CreateModel(
name='UserDept',
fields=[
('UserDeptUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserDeptUUID', primary_key=True, serialize=False, verbose_name='关联UUID')),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', verbose_name='用户UUID')),
('DeptUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='DeptUUID', verbose_name='部门UUID')),
('PositionUUID', gvsdsdk.models.UUIDField(binary_length=16, blank=True, db_column='PositionUUID', null=True, verbose_name='职务标签UUID')),
('IsMainDept', models.SmallIntegerField(db_column='IsMainDept', default=1, verbose_name='是否主部门')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
],
options={
'verbose_name': '员工部门关联',
'verbose_name_plural': '员工部门关联',
'db_table': 'UserDept',
'managed': True,
},
),
migrations.CreateModel(
name='UserLoginLog',
fields=[
('LogUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='LogUUID', primary_key=True, serialize=False)),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', db_index=True)),
('LoginIP', models.CharField(db_column='LoginIP', max_length=39)),
('LoginPlatform', models.CharField(db_column='LoginPlatform', max_length=32)),
('LoginStatus', models.SmallIntegerField(db_column='LoginStatus')),
('LoginTime', models.DateTimeField(db_column='LoginTime', default=datetime.datetime.utcnow)),
('UserAgent', models.CharField(db_column='UserAgent', default='', max_length=512)),
('LogUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='LogUUID', primary_key=True, serialize=False, verbose_name='日志UUID')),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', db_index=True, verbose_name='用户UUID')),
('LoginIP', models.CharField(db_column='LoginIP', max_length=39, verbose_name='登录IP')),
('LoginPlatform', models.CharField(db_column='LoginPlatform', max_length=32, verbose_name='登录平台')),
('LoginStatus', models.SmallIntegerField(db_column='LoginStatus', verbose_name='登录状态')),
('LoginTime', models.DateTimeField(db_column='LoginTime', default=datetime.datetime.utcnow, verbose_name='登录时间')),
('UserAgent', models.CharField(db_column='UserAgent', default='', max_length=512, verbose_name='用户代理')),
],
options={
'verbose_name': '登录日志',
@@ -201,10 +290,10 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='UserPending2FA',
fields=[
('PendingUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PendingUUID', primary_key=True, serialize=False)),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', unique=True)),
('SecretKey', models.CharField(db_column='SecretKey', max_length=64)),
('ExpireAt', models.DateTimeField(db_column='ExpireAt')),
('PendingUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PendingUUID', primary_key=True, serialize=False, verbose_name='待确认UUID')),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', unique=True, verbose_name='用户UUID')),
('SecretKey', models.CharField(db_column='SecretKey', max_length=64, verbose_name='密钥')),
('ExpireAt', models.DateTimeField(db_column='ExpireAt', verbose_name='过期时间')),
],
options={
'verbose_name': '2FA待确认',
@@ -216,10 +305,11 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='UserRole',
fields=[
('UserRoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserRoleUUID', primary_key=True, serialize=False)),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID')),
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)),
('UserRoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserRoleUUID', primary_key=True, serialize=False, verbose_name='关联UUID')),
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', verbose_name='用户UUID')),
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID', verbose_name='角色UUID')),
('CompanyUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='CompanyUUID', verbose_name='公司UUID')),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
],
options={
'verbose_name': '用户角色关联',
@@ -228,4 +318,37 @@ class Migration(migrations.Migration):
'managed': True,
},
),
migrations.CreateModel(
name='User',
fields=[
('UserUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', primary_key=True, serialize=False, verbose_name='用户UUID')),
('UserName', models.CharField(db_column='UserName', max_length=256, unique=True, verbose_name='用户名')),
('UserPassword', models.CharField(db_column='UserPassword', default='', max_length=60, verbose_name='密码')),
('UserPositionStatus', models.SmallIntegerField(db_column='UserPositionStatus', default=0, verbose_name='职位状态')),
('UserPersonalSignature', models.CharField(db_column='UserPersonalSignature', default='', max_length=1024, verbose_name='个人签名')),
('UserEmail', models.CharField(db_column='UserEmail', default='', max_length=256, verbose_name='邮箱')),
('UserEmailVerify', models.SmallIntegerField(db_column='UserEmailVerify', default=0, verbose_name='邮箱验证')),
('UserAccountLicense', models.SmallIntegerField(db_column='UserAccountLicense', default=1, verbose_name='账户许可')),
('UserGender', models.SmallIntegerField(db_column='UserGender', default=-1, verbose_name='性别')),
('UserTele', models.CharField(blank=True, db_column='UserTele', max_length=16, null=True, verbose_name='电话')),
('UserCreateTime', models.DateTimeField(db_column='UserCreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')),
('UserLastLoginDate', models.DateTimeField(db_column='UserLastLoginDate', default=datetime.datetime.utcnow, verbose_name='最后登录')),
('UserUID', models.CharField(blank=True, db_column='UserUID', max_length=7, null=True, unique=True, verbose_name='用户业务ID')),
('OpenID', models.CharField(blank=True, db_column='OpenID', max_length=64, null=True, unique=True, verbose_name='微信OpenID')),
('UnionID', models.CharField(blank=True, db_column='UnionID', max_length=64, null=True, verbose_name='微信UnionID')),
('Avatar', models.CharField(db_column='Avatar', default='a_long/morentouxiang.jpg', max_length=500, verbose_name='头像')),
('Phone', models.CharField(blank=True, db_column='Phone', max_length=11, null=True, verbose_name='手机号')),
('PhoneVerified', models.BooleanField(db_column='PhoneVerified', default=False, verbose_name='手机号是否已认证')),
('IP', models.CharField(blank=True, db_column='IP', max_length=50, null=True, verbose_name='来访用户IP')),
('IsStaff', models.BooleanField(db_column='IsStaff', default=False, verbose_name='是否员工')),
('IsSuperuser', models.BooleanField(db_column='IsSuperuser', default=False, verbose_name='是否超级用户')),
],
options={
'verbose_name': '用户',
'verbose_name_plural': '用户',
'db_table': 'User',
'managed': True,
'indexes': [models.Index(fields=['UserUID'], name='User_UserUID_a656ea_idx'), models.Index(fields=['OpenID'], name='User_OpenID_2a920d_idx')],
},
),
]

View File

@@ -1,90 +0,0 @@
# Generated by Django 6.0.5 on 2026-06-13 12:29
import datetime
import gvsdsdk.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gvsdsdk', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Position',
fields=[
('PositionUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PositionUUID', primary_key=True, serialize=False)),
('CompanyUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='CompanyUUID')),
('PositionName', models.CharField(db_column='PositionName', max_length=128)),
('PositionCode', models.CharField(db_column='PositionCode', default='', max_length=64)),
('PositionLevel', models.SmallIntegerField(db_column='PositionLevel', default=0)),
('PositionStatus', models.SmallIntegerField(db_column='PositionStatus', default=1)),
('PositionDesc', models.CharField(db_column='PositionDesc', default='', max_length=512)),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)),
('UpdateTime', models.DateTimeField(db_column='UpdateTime', default=datetime.datetime.utcnow)),
],
options={
'verbose_name': '职务',
'verbose_name_plural': '职务',
'db_table': 'Position',
'managed': True,
},
),
migrations.CreateModel(
name='RolePosition',
fields=[
('RolePositionUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RolePositionUUID', primary_key=True, serialize=False)),
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID')),
('PositionUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='PositionUUID')),
('IsMainPosition', models.SmallIntegerField(db_column='IsMainPosition', default=0)),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)),
],
options={
'verbose_name': '角色职务关联',
'verbose_name_plural': '角色职务关联',
'db_table': 'RolePosition',
'managed': True,
},
),
migrations.CreateModel(
name='RoleShop',
fields=[
('RoleShopUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleShopUUID', primary_key=True, serialize=False)),
('RoleUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='RoleUUID')),
('ShopUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ShopUUID')),
('IsMainShop', models.SmallIntegerField(db_column='IsMainShop', default=0)),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)),
],
options={
'verbose_name': '角色商铺关联',
'verbose_name_plural': '角色商铺关联',
'db_table': 'RoleShop',
'managed': True,
},
),
migrations.CreateModel(
name='Shop',
fields=[
('ShopUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='ShopUUID', primary_key=True, serialize=False)),
('CompanyUUID', gvsdsdk.models.UUIDField(binary_length=16, db_column='CompanyUUID')),
('ShopName', models.CharField(db_column='ShopName', max_length=128)),
('ShopCode', models.CharField(db_column='ShopCode', default='', max_length=64)),
('ShopType', models.SmallIntegerField(db_column='ShopType', default=0)),
('ShopStatus', models.SmallIntegerField(db_column='ShopStatus', default=1)),
('ShopAddress', models.CharField(db_column='ShopAddress', default='', max_length=512)),
('ShopContact', models.CharField(db_column='ShopContact', default='', max_length=128)),
('ShopPhone', models.CharField(db_column='ShopPhone', default='', max_length=32)),
('ShopDesc', models.CharField(db_column='ShopDesc', default='', max_length=512)),
('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow)),
('UpdateTime', models.DateTimeField(db_column='UpdateTime', default=datetime.datetime.utcnow)),
],
options={
'verbose_name': '商铺',
'verbose_name_plural': '商铺',
'db_table': 'Shop',
'managed': True,
},
),
]

View File

@@ -186,17 +186,33 @@ class User(QModel):
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 = False
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
@@ -304,6 +320,66 @@ class User(QModel):
def is_authenticated(self):
return self.IsAuthenticated
@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
@@ -318,6 +394,73 @@ class User(QModel):
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
# === 用户头像表 ===
@@ -330,7 +473,7 @@ class UserAvatar(QModel):
AvatarFileType = models.CharField(max_length=16, default='png', db_column='AvatarFileType', verbose_name='文件类型')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'UserAvatar'
verbose_name = '用户头像'
@@ -351,7 +494,7 @@ class User2FA(QModel):
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'User2FA'
verbose_name = '双因素认证'
@@ -369,7 +512,7 @@ class UserPending2FA(QModel):
ExpireAt = models.DateTimeField(db_column='ExpireAt', verbose_name='过期时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'UserPending2FA'
verbose_name = '2FA待确认'
@@ -396,7 +539,7 @@ class EphemeralToken(QModel):
ELTStatus = models.SmallIntegerField(default=1, db_index=True, db_column='ELTStatus', verbose_name='状态')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'EphemeralToken'
verbose_name = '临时令牌'
@@ -425,7 +568,7 @@ class UserLoginLog(QModel):
UserAgent = models.CharField(max_length=512, default='', db_column='UserAgent', verbose_name='用户代理')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'UserLoginLog'
verbose_name = '登录日志'
@@ -442,7 +585,7 @@ class AccountActivation(QModel):
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'AccountActivation'
verbose_name = '账户激活'
@@ -472,7 +615,7 @@ class Role(QModel):
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'Role'
verbose_name = '角色'
@@ -516,7 +659,7 @@ class Permission(QModel):
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'Permission'
verbose_name = '权限'
@@ -555,7 +698,7 @@ class UserRole(QModel):
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'UserRole'
verbose_name = '用户角色关联'
@@ -577,7 +720,7 @@ class RolePermission(QModel):
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'RolePermission'
verbose_name = '角色权限关联'
@@ -594,7 +737,7 @@ class UserABAC(QModel):
HierarchicalPermissionIdentifiers = models.CharField(max_length=256, db_column='HierarchicalPermissionIdentifiers', verbose_name='层级权限标识')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'UserABAC'
verbose_name = '用户ABAC'
@@ -610,7 +753,7 @@ class Password(QModel):
Password = models.CharField(max_length=1024, db_column='Password', verbose_name='密码哈希')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'Password'
verbose_name = '密码'
@@ -639,7 +782,7 @@ class Shop(QModel):
UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'Shop'
verbose_name = '商铺'
@@ -671,7 +814,7 @@ class RoleShop(QModel):
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'RoleShop'
verbose_name = '角色商铺关联'
@@ -700,7 +843,7 @@ class PositionTemplate(QModel):
UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'PositionTemplate'
verbose_name = '岗位模板'
@@ -750,7 +893,7 @@ class PositionTemplatePermission(QModel):
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'PositionTemplatePermission'
verbose_name = '岗位模板权限关联'
@@ -778,7 +921,7 @@ class Position(QModel):
UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'Position'
verbose_name = '人事职务标签'
@@ -806,7 +949,7 @@ class UserDept(QModel):
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
managed = True
app_label = 'gvsdsdk'
db_table = 'UserDept'
verbose_name = '员工部门关联'

View File

@@ -0,0 +1,532 @@
"""MSYNC ORM 中转处理器
子服务器端注册 Socket.IO 事件处理器,接收主服务器发来的 ORM 查询请求,
在本地数据库执行 SQL 并返回结果。
用法(子服务器端):
from gvsdsdk.msync.orm_handler import ORMRelayHandler
handler = ORMRelayHandler()
handler.Register(sio_client) # 注册到 MSyncSocketClient 实例
"""
import datetime
import logging
import uuid
logger = logging.getLogger(__name__)
class ORMRelayHandler:
"""ORM 中转处理器——在子服务器端执行本地 SQL 查询
通过 MSYNC Socket.IO 事件协议,主服务器将 ORM 请求转发给子服务器,
子服务器在本地数据库执行查询后返回结果。
"""
def Register(self, sio_client):
"""将 ORM 事件处理器注册到 MSyncSocketClient 实例
Args:
sio_client: MSyncSocketClient 实例,需要有 _sio 属性
"""
sio = sio_client._sio
@sio.on('msync:orm:tables', namespace='/msync')
def on_orm_tables(data):
return self.HandleTables(data)
@sio.on('msync:orm:columns', namespace='/msync')
def on_orm_columns(data):
return self.HandleColumns(data)
@sio.on('msync:orm:list', namespace='/msync')
def on_orm_list(data):
return self.HandleList(data)
@sio.on('msync:orm:add', namespace='/msync')
def on_orm_add(data):
return self.HandleAdd(data)
@sio.on('msync:orm:modify', namespace='/msync')
def on_orm_modify(data):
return self.HandleModify(data)
@sio.on('msync:orm:delete', namespace='/msync')
def on_orm_delete(data):
return self.HandleDelete(data)
logger.info('[ORM] ORM 中转处理器已注册')
def _GetConnection(self):
"""获取本地数据库连接Django default"""
from django.db import connections
conn = connections['default']
conn.ensure_connection()
return conn
# ─── 事件处理 ──────────────────────────────────────────────
def HandleTables(self, data):
"""列出本地数据库中所有用户表"""
try:
conn = self._GetConnection()
except Exception as e:
return {'code': 1, 'msg': f'数据库连接失败: {e}'}
try:
with conn.cursor() as cursor:
tables = self._ListTables(cursor)
return {'code': 0, 'msg': 'success', 'data': tables}
except Exception as e:
logger.error(f'[ORM] HandleTables 异常: {e}')
return {'code': 1, 'msg': f'查询失败: {e}'}
def HandleColumns(self, data):
"""列出指定表的列信息"""
table_name = (data or {}).get('Table', '')
if not table_name:
return {'code': 1, 'msg': '缺少 Table 参数'}
try:
conn = self._GetConnection()
except Exception as e:
return {'code': 1, 'msg': f'数据库连接失败: {e}'}
try:
with conn.cursor() as cursor:
columns = self._ListColumns(cursor, table_name)
return {'code': 0, 'msg': 'success', 'data': columns}
except Exception as e:
logger.error(f'[ORM] HandleColumns 异常: {e}')
return {'code': 1, 'msg': f'查询失败: {e}'}
def HandleList(self, data):
"""分页查询表数据"""
table_name = (data or {}).get('Table', '')
if not table_name:
return {'code': 1, 'msg': '缺少 Table 参数'}
try:
conn = self._GetConnection()
except Exception as e:
return {'code': 1, 'msg': f'数据库连接失败: {e}'}
try:
with conn.cursor() as cursor:
result = self._QueryTable(cursor, table_name, data or {})
if result.get('error'):
return {'code': 1, 'msg': result['error'], 'data': None}
return {'code': 0, 'msg': 'success', 'data': result}
except Exception as e:
logger.error(f'[ORM] HandleList 异常: {e}')
return {'code': 1, 'msg': f'查询失败: {e}'}
def HandleAdd(self, data):
"""新增记录"""
table_name = (data or {}).get('Table', '')
record = (data or {}).get('Record', {})
if not table_name:
return {'code': 1, 'msg': '缺少 Table 参数'}
try:
conn = self._GetConnection()
except Exception as e:
return {'code': 1, 'msg': f'数据库连接失败: {e}'}
try:
with conn.cursor() as cursor:
result = self._AddRecord(cursor, table_name, record)
return result
except Exception as e:
logger.error(f'[ORM] HandleAdd 异常: {e}')
return {'code': 1, 'msg': f'新增失败: {e}'}
def HandleModify(self, data):
"""修改记录"""
table_name = (data or {}).get('Table', '')
record = (data or {}).get('Record', {})
if not table_name:
return {'code': 1, 'msg': '缺少 Table 参数'}
try:
conn = self._GetConnection()
except Exception as e:
return {'code': 1, 'msg': f'数据库连接失败: {e}'}
try:
with conn.cursor() as cursor:
result = self._ModifyRecord(cursor, table_name, record)
return result
except Exception as e:
logger.error(f'[ORM] HandleModify 异常: {e}')
return {'code': 1, 'msg': f'修改失败: {e}'}
def HandleDelete(self, data):
"""删除记录"""
table_name = (data or {}).get('Table', '')
record = (data or {}).get('Record', {})
if not table_name:
return {'code': 1, 'msg': '缺少 Table 参数'}
try:
conn = self._GetConnection()
except Exception as e:
return {'code': 1, 'msg': f'数据库连接失败: {e}'}
try:
with conn.cursor() as cursor:
result = self._DeleteRecord(cursor, table_name, record)
return result
except Exception as e:
logger.error(f'[ORM] HandleDelete 异常: {e}')
return {'code': 1, 'msg': f'删除失败: {e}'}
# ─── SQL 执行 ──────────────────────────────────────────────
def _ListTables(self, cursor):
"""列出所有用户表,含 i18n 标题"""
cursor.execute(
"SELECT TABLE_NAME, TABLE_ROWS, TABLE_COMMENT FROM information_schema.TABLES "
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE' "
"ORDER BY TABLE_NAME"
)
from django.apps import apps
# 构建表名→中文标题映射
model_verbose = {}
for model in apps.get_models():
meta = model._meta
# 优先使用 table_title自定义 Meta 属性),其次 verbose_name
table_title = getattr(meta, 'table_title', None)
title = str(table_title) if table_title else (str(meta.verbose_name) if meta.verbose_name else '')
if title:
model_verbose[meta.object_name] = title
model_verbose[meta.db_table] = title
result = []
for row in cursor.fetchall():
table_name, table_rows, table_comment = row
# 优先级TABLE_COMMENT > table_title/verbose_name > 表名
title = table_comment or model_verbose.get(table_name) or table_name
result.append({'name': table_name, 'title': title, 'count': table_rows or 0})
return result
def _ListColumns(self, cursor, table_name):
"""列出指定表的列信息"""
cursor.execute(
"SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA, COLUMN_TYPE, COLUMN_COMMENT "
"FROM information_schema.COLUMNS "
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s "
"ORDER BY ORDINAL_POSITION",
[table_name]
)
model_verbose = self._FindModelVerboseNames(table_name)
logger.info(f'[ORM] _ListColumns table={table_name} model_verbose={model_verbose}')
columns = []
for row in cursor.fetchall():
col_name, data_type, is_nullable, col_key, extra, col_type, col_comment = row
is_pk = col_key == 'PRI'
is_auto = 'auto_increment' in (extra or '').lower()
if data_type in ('int', 'bigint', 'tinyint', 'smallint', 'mediumint'):
ftype = 'number'
elif data_type in ('float', 'double', 'decimal'):
ftype = 'number'
elif data_type in ('datetime', 'timestamp'):
ftype = 'datetime'
elif data_type in ('date',):
ftype = 'date'
elif data_type in ('time',):
ftype = 'time'
elif data_type in ('json',):
ftype = 'textarea'
elif data_type in ('text', 'mediumtext', 'longtext', 'tinytext'):
ftype = 'textarea'
elif data_type in ('binary', 'varbinary', 'blob'):
ftype = 'uuid'
else:
ftype = 'text'
title = col_comment or model_verbose.get(col_name) or col_name
columns.append({
'field': col_name,
'title': title,
'type': ftype,
'editable': not is_pk and not is_auto,
'sortable': True,
'filterable': True,
'primaryKey': is_pk,
'hideInForm': is_pk,
'required': is_nullable == 'NO' and not is_auto,
'dbType': col_type,
})
return columns
def _QueryTable(self, cursor, table_name, params):
"""分页查询表数据"""
cursor.execute(
"SELECT COUNT(*) FROM information_schema.TABLES "
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s",
[table_name]
)
if cursor.fetchone()[0] == 0:
return {'records': [], 'total': 0, 'error': f'表不存在: {table_name}'}
col_info = self._GetTableColumns(cursor, table_name)
pk_cols = {k: v for k, v in col_info.items() if v['is_pk']}
where_parts = []
where_values = []
for pk_name in pk_cols:
pk_val = params.get(pk_name, '')
if pk_val:
where_parts.append(f'`{pk_name}` = %s')
where_values.append(self._ConvertValueForDB(pk_val, pk_cols[pk_name]))
keyword = params.get('keyword', '')
if not where_parts and keyword:
cursor.execute(
"SELECT COLUMN_NAME FROM information_schema.COLUMNS "
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s "
"AND DATA_TYPE IN ('varchar', 'char', 'text', 'mediumtext', 'longtext')",
[table_name]
)
text_cols = [row[0] for row in cursor.fetchall()]
if text_cols:
conditions = [f'`{c}` LIKE %s' for c in text_cols]
or_clause = ' OR '.join(conditions)
where_parts.append(f'({or_clause})')
where_values.extend([f'%{keyword}%'] * len(text_cols))
where_clause = (' WHERE ' + ' AND '.join(where_parts)) if where_parts else ''
if where_clause:
cursor.execute(f'SELECT COUNT(*) FROM `{table_name}`{where_clause}', where_values)
total = cursor.fetchone()[0]
else:
cursor.execute(f'SELECT COUNT(*) FROM `{table_name}`')
total = cursor.fetchone()[0]
try:
page = max(int(params.get('PageIndex', 1)), 1)
except (ValueError, TypeError):
page = 1
try:
page_size = min(max(int(params.get('PageSize', 20)), 1), 200)
except (ValueError, TypeError):
page_size = 20
offset = (page - 1) * page_size
cursor.execute(
f'SELECT * FROM `{table_name}`{where_clause} ORDER BY 1 LIMIT %s OFFSET %s',
where_values + [page_size, offset]
)
desc = [d[0] for d in cursor.description]
records = []
for row in cursor.fetchall():
record = {}
for i, val in enumerate(row):
val = self._SerializeValue(val)
record[desc[i]] = val
records.append(record)
return {'records': records, 'total': total, 'page': page, 'PageSize': page_size}
def _AddRecord(self, cursor, table_name, record):
"""新增记录"""
if not self._ValidateTableName(cursor, table_name):
return {'code': 1, 'msg': f'{table_name} 不存在'}
col_info = self._GetTableColumns(cursor, table_name)
fields = []
values = []
for key, val in record.items():
if key not in col_info:
continue
if col_info[key]['is_auto']:
continue
fields.append(f'`{key}`')
values.append(self._ConvertValueForDB(val, col_info[key]))
if not fields:
return {'code': 1, 'msg': '没有可插入的字段'}
placeholders = ', '.join(['%s'] * len(fields))
sql = f'INSERT INTO `{table_name}` ({", ".join(fields)}) VALUES ({placeholders})'
cursor.execute(sql, values)
return {'code': 0, 'msg': '新增成功'}
def _ModifyRecord(self, cursor, table_name, record):
"""修改记录"""
if not self._ValidateTableName(cursor, table_name):
return {'code': 1, 'msg': f'{table_name} 不存在'}
col_info = self._GetTableColumns(cursor, table_name)
pk_cols = {k: v for k, v in col_info.items() if v['is_pk']}
if not pk_cols:
return {'code': 1, 'msg': '表没有主键,无法定位记录'}
set_parts = []
set_values = []
for key, val in record.items():
if key not in col_info or key in pk_cols:
continue
set_parts.append(f'`{key}` = %s')
set_values.append(self._ConvertValueForDB(val, col_info[key]))
if not set_parts:
return {'code': 0, 'msg': '没有需要更新的字段'}
where_parts = []
where_values = []
for pk_name in pk_cols:
if pk_name not in record:
return {'code': 1, 'msg': f'缺少主键字段: {pk_name}'}
where_parts.append(f'`{pk_name}` = %s')
where_values.append(self._ConvertValueForDB(record[pk_name], pk_cols[pk_name]))
sql = f'UPDATE `{table_name}` SET {", ".join(set_parts)} WHERE {" AND ".join(where_parts)}'
cursor.execute(sql, set_values + where_values)
if cursor.rowcount == 0:
return {'code': 1, 'msg': '记录不存在或未变更'}
return {'code': 0, 'msg': '修改成功'}
def _DeleteRecord(self, cursor, table_name, record):
"""删除记录"""
if not self._ValidateTableName(cursor, table_name):
return {'code': 1, 'msg': f'{table_name} 不存在'}
col_info = self._GetTableColumns(cursor, table_name)
pk_cols = {k: v for k, v in col_info.items() if v['is_pk']}
if not pk_cols:
return {'code': 1, 'msg': '表没有主键,无法定位记录'}
where_parts = []
where_values = []
for pk_name in pk_cols:
if pk_name not in record:
return {'code': 1, 'msg': f'缺少主键字段: {pk_name}'}
where_parts.append(f'`{pk_name}` = %s')
where_values.append(self._ConvertValueForDB(record[pk_name], pk_cols[pk_name]))
sql = f'DELETE FROM `{table_name}` WHERE {" AND ".join(where_parts)}'
cursor.execute(sql, where_values)
if cursor.rowcount == 0:
return {'code': 1, 'msg': '记录不存在'}
return {'code': 0, 'msg': '删除成功'}
# ─── 工具方法 ──────────────────────────────────────────────
def _ValidateTableName(self, cursor, table_name):
"""验证表名存在"""
cursor.execute(
"SELECT COUNT(*) FROM information_schema.TABLES "
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s",
[table_name]
)
return cursor.fetchone()[0] > 0
def _GetTableColumns(self, cursor, table_name):
"""获取表的列信息"""
cursor.execute(
"SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, COLUMN_KEY, EXTRA, IS_NULLABLE "
"FROM information_schema.COLUMNS "
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s "
"ORDER BY ORDINAL_POSITION",
[table_name]
)
cols = {}
for row in cursor.fetchall():
col_name, data_type, column_type, col_key, extra, is_nullable = row
cols[col_name] = {
'type': data_type,
'column_type': column_type or '',
'is_pk': col_key == 'PRI',
'is_auto': 'auto_increment' in (extra or '').lower(),
'is_nullable': is_nullable == 'YES',
}
return cols
def _ConvertValueForDB(self, value, col_info):
"""将前端传来的值转换为数据库可接受的类型"""
if value is None or value == '':
if col_info['is_nullable']:
return None
return ''
data_type = col_info['type']
if data_type in ('int', 'bigint', 'tinyint', 'smallint', 'mediumint'):
return int(value)
elif data_type in ('float', 'double', 'decimal'):
return float(value)
elif data_type in ('binary', 'varbinary'):
import re
hex_str = re.sub(r'[^0-9a-fA-F]', '', str(value))
if len(hex_str) == 32:
return uuid.UUID(hex_str).bytes
return value
return value
def _SerializeValue(self, val):
"""将数据库值序列化为 JSON 安全格式"""
if isinstance(val, bytes):
if len(val) == 16:
try:
return str(uuid.UUID(bytes=val))
except ValueError:
return val.hex()
return val.hex()
elif isinstance(val, datetime.datetime):
return val.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(val, datetime.date):
return val.isoformat()
elif isinstance(val, datetime.time):
return val.strftime('%H:%M:%S')
elif isinstance(val, (bytearray, memoryview)):
return bytes(val).hex()
return val
def _FindModelVerboseNames(self, table_name):
"""从 Django 模型中获取字段的 verbose_name"""
from django.apps import apps
verbose_map = {}
try:
for model in apps.get_models():
if model._meta.db_table == table_name:
logger.info(f'[ORM] _FindModelVerboseNames matched model={model.__name__} db_table={model._meta.db_table}')
for f in model._meta.get_fields():
# 获取数据库列名
col_name = None
if hasattr(f, 'column') and f.column:
col_name = f.column
elif hasattr(f, 'db_column') and f.db_column:
col_name = f.db_column
elif hasattr(f, 'attname'):
col_name = f.attname
if not col_name:
continue
vn = getattr(f, 'verbose_name', None)
if vn and str(vn) != col_name:
verbose_map[col_name] = str(vn)
logger.debug(f'[ORM] {col_name} -> {str(vn)}')
break
else:
# 没有匹配的模型
available = [m._meta.db_table for m in apps.get_models()]
logger.warning(f'[ORM] _FindModelVerboseNames: 未找到 db_table={table_name} 的模型, 可用表: {available[:20]}...')
except Exception as e:
logger.error(f'[ORM] _FindModelVerboseNames 异常: {e}')
return verbose_map