feat(auth): User.DefaultRoleUUID 默认登录角色(登录自动激活)

- core.User 新增 DefaultRoleUUID(UUIDField 可空)+ 手写迁移 0004
- _SetCurrentRoleCookie 登录/注册优先激活默认角色(须持有且启用),否则回退首个角色
- GetUserRoles 返回 IsDefault 标记;新增 SetDefaultRole 接口(空 RoleUUID=清除默认)
This commit is contained in:
TermiNexus committed 2026-08-22 01:23:34 +08:00
1 parent b1ccb9e0e1
commit ec43c4dfe6
7 files changed
+61 -8

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>银河锋锐(GVSDS)</title>
<link rel="icon" href="https://hb.cdn.gvsds.com/assets/icon/ICON.png">
<script type="module" crossorigin src="/GVSite/index-CK-5nual.js"></script>
<script type="module" crossorigin src="/GVSite/index-DhQua3qJ.js"></script>
<link rel="stylesheet" crossorigin href="/GVSite/index-DRWk7H-y.css">
</head>
<body>
+2 -2
View File
@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>银河锋锐(GVSDS)</title>
<link rel="icon" href="https://hb.cdn.gvsds.com/assets/icon/ICON.png">
<script type="module" crossorigin src="/GVSite/index-tyrWQVZX.js"></script>
<link rel="stylesheet" crossorigin href="/GVSite/index-BY1hqLY-.css">
<script type="module" crossorigin src="/GVSite/index-CK-5nual.js"></script>
<link rel="stylesheet" crossorigin href="/GVSite/index-DRWk7H-y.css">
</head>
<body>
<div id="app"></div>
@@ -0,0 +1,18 @@
from django.db import migrations
import gvsds.apps.core.models
class Migration(migrations.Migration):
dependencies = [
('core', '0003_remove_ephemeraltoken'),
]
operations = [
migrations.AddField(
model_name='user',
name='DefaultRoleUUID',
field=gvsds.apps.core.models.UUIDField(blank=True, null=True, verbose_name='默认角色UUID'),
),
]
+2
View File
@@ -137,6 +137,8 @@ class User(QModel):
UserLastLoginDate = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='最后登录')
IsStaff = models.BooleanField(default=False, verbose_name='是否员工')
IsSuperuser = models.BooleanField(default=False, verbose_name='是否超级用户')
# 默认登录角色:登录/注册后自动激活的角色(空 = 取用户首个角色)
DefaultRoleUUID = UUIDField(null=True, blank=True, verbose_name='默认角色UUID')
objects = UserManager()
+4
View File
@@ -33,6 +33,10 @@ class SwitchRoleSerializer(PascalCaseSerializer):
RoleUUID = serializers.CharField()
class SetDefaultRoleSerializer(PascalCaseSerializer):
RoleUUID = serializers.CharField(required=False, allow_blank=True, default='')
class ModifyPasswordSerializer(PascalCaseSerializer):
OldPassword = serializers.CharField(max_length=256, required=False)
NewPassword = serializers.CharField(max_length=256, required=False)
+1
View File
@@ -6,6 +6,7 @@ urlpatterns = [
path('register/', views.RegisterView.as_view(), name='register'),
path('verify-2fa/', views.Verify2FAView.as_view(), name='verify_2fa'),
path('switch-role/', views.SwitchRoleView.as_view(), name='switch_role'),
path('set-default-role/', views.SetDefaultRoleView.as_view(), name='set_default_role'),
path('user-roles/', views.GetUserRolesView.as_view(), name='user_roles'),
path('user-info/', views.GetUserInfoView.as_view(), name='user_info'),
path('user-avatar/', views.GetUserAvatarView.as_view(), name='user_avatar'),
+33 -5
View File
@@ -14,7 +14,7 @@ from gvsdsdk.model_utils import UUIDObj
from gvsds.app_config import getlist as _app_getlist
from .serializers import (
LoginSerializer, RegisterSerializer, Verify2FASerializer,
SwitchRoleSerializer, ModifyPasswordSerializer,
SwitchRoleSerializer, ModifyPasswordSerializer, SetDefaultRoleSerializer,
)
_ALLOWED_DOMAINS = set(_app_getlist('domain', 'KNOWN_DOMAINS', fallback=[
@@ -82,13 +82,22 @@ def _ClearJwtCookies(response):
def _SetCurrentRoleCookie(response, user):
"""查询用户首个角色并设置 CurrentRoleUUID cookie"""
"""设置 CurrentRoleUUID cookie:优先用户默认角色(DefaultRoleUUID,须持有且启用),否则取首个角色"""
from gvsdsdk.auth.managers import RoleManager
first_role = RoleManager.GetRolesForUser(user.UserUUID).first()
if first_role and first_role.RoleUUID:
role_uuid = ''
default_uuid = getattr(user, 'DefaultRoleUUID', None)
if default_uuid:
role = RoleManager.GetRoleByUUID(default_uuid)
if role and role.RoleStatus == 1 and RoleManager.HasRole(user.UserUUID, default_uuid):
role_uuid = UUIDObj(default_uuid).str
if not role_uuid:
first_role = RoleManager.GetRolesForUser(user.UserUUID).first()
if first_role and first_role.RoleUUID:
role_uuid = UUIDObj(first_role.RoleUUID).str
if role_uuid:
max_age = int(settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'].total_seconds())
response.set_cookie(
'CurrentRoleUUID', UUIDObj(first_role.RoleUUID).str,
'CurrentRoleUUID', role_uuid,
domain=settings.SESSION_COOKIE_DOMAIN, max_age=max_age,
secure=True, httponly=False, samesite='None',
)
@@ -273,6 +282,22 @@ class SwitchRoleView(APIView):
return response
class SetDefaultRoleView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
data = request.data.copy()
serializer = SetDefaultRoleSerializer(data=data)
serializer.IsValid(raise_exception=True)
role_uuid = serializer.ValidatedData['RoleUUID'].strip()
from gvsdsdk.auth.managers import RoleManager
if role_uuid and not RoleManager.HasRole(request.user.UserUUID, role_uuid):
return Response({'code': 1, 'msg': '无权设置该角色为默认'}, status=403)
request.user.DefaultRoleUUID = UUIDObj(role_uuid).bytes if role_uuid else None
request.user.save(update_fields=['DefaultRoleUUID'])
return Response({'code': 0, 'msg': '默认登录角色已更新'})
class GetUserRolesView(APIView):
permission_classes = [IsAuthenticated]
@@ -281,6 +306,8 @@ class GetUserRolesView(APIView):
# 角色不绑定公司/部门,通过 UserRole.CompanyUUID 获取关联公司
user_roles = RoleManager.GetUserRoleDetails(request.user.UserUUID)
current_role_uuid = request.COOKIES.get('CurrentRoleUUID', '')
default_uuid = getattr(request.user, 'DefaultRoleUUID', None)
default_role_uuid = UUIDObj(default_uuid).str if default_uuid else ''
roles = []
for ur in user_roles:
# 两步查询:UserRole.RoleUUID 是 UUIDField,需先查 Role 表
@@ -309,6 +336,7 @@ class GetUserRolesView(APIView):
'CompanyShort': company_short,
'CompanyUUID': company_uuid_str,
'active': UUIDObj(role.RoleUUID).str == current_role_uuid,
'IsDefault': UUIDObj(role.RoleUUID).str == default_role_uuid,
})
return Response({'code': 0, 'data': {'roles': roles}})