213 lines
6.5 KiB
Python
213 lines
6.5 KiB
Python
"""
|
||
用户模块视图 - App端用户
|
||
"""
|
||
from rest_framework import viewsets, status
|
||
from rest_framework.decorators import action
|
||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||
from rest_framework_simplejwt.tokens import RefreshToken
|
||
from django.db.models import Count
|
||
|
||
from utils.response import success, error
|
||
from apps.admins.authentication import AppJWTAuthentication, AdminJWTAuthentication
|
||
from apps.admins.permissions import IsAdminUser
|
||
from .models import User
|
||
from .serializers import (
|
||
UserSerializer,
|
||
UserDetailSerializer,
|
||
PhoneLoginSerializer,
|
||
UpdateUserSerializer
|
||
)
|
||
|
||
|
||
def get_app_tokens(user):
|
||
"""
|
||
为App用户生成JWT Token
|
||
在token中添加 user_type='app' 以区分管理员
|
||
"""
|
||
refresh = RefreshToken.for_user(user)
|
||
# 添加自定义声明
|
||
refresh['user_type'] = 'app'
|
||
refresh['phone'] = user.phone
|
||
|
||
return {
|
||
'access': str(refresh.access_token),
|
||
'refresh': str(refresh),
|
||
}
|
||
|
||
|
||
class AuthViewSet(viewsets.ViewSet):
|
||
"""认证视图集 - App端"""
|
||
|
||
permission_classes = [AllowAny]
|
||
|
||
@action(detail=False, methods=['post'], url_path='phone-login')
|
||
def phone_login(self, request):
|
||
"""
|
||
手机号一键登录
|
||
POST /api/v1/auth/phone-login
|
||
"""
|
||
serializer = PhoneLoginSerializer(data=request.data)
|
||
if not serializer.is_valid():
|
||
return error(message=str(serializer.errors))
|
||
|
||
phone = serializer.validated_data['phone']
|
||
|
||
# 获取或创建用户
|
||
user, created = User.objects.get_or_create(
|
||
phone=phone,
|
||
defaults={'nickname': f'用户{phone[-4:]}'}
|
||
)
|
||
|
||
if not user.is_active:
|
||
return error(code=101, message='账号已被禁用')
|
||
|
||
# 生成JWT Token(带user_type='app'标识)
|
||
tokens = get_app_tokens(user)
|
||
|
||
return success(data={
|
||
'user': UserSerializer(user).data,
|
||
'token': tokens,
|
||
'is_new_user': created
|
||
}, message='登录成功')
|
||
|
||
@action(detail=False, methods=['post'], url_path='refresh')
|
||
def refresh_token(self, request):
|
||
"""
|
||
刷新Token
|
||
POST /api/v1/auth/refresh
|
||
"""
|
||
refresh_token = request.data.get('refresh')
|
||
if not refresh_token:
|
||
return error(message='refresh token不能为空')
|
||
|
||
try:
|
||
refresh = RefreshToken(refresh_token)
|
||
# 验证是否为app token
|
||
user_type = refresh.get('user_type', 'app')
|
||
if user_type not in ['app', None]:
|
||
return error(code=103, message='无效的用户Token')
|
||
|
||
return success(data={
|
||
'access': str(refresh.access_token),
|
||
'refresh': str(refresh),
|
||
})
|
||
except Exception as e:
|
||
return error(code=103, message='Token已过期或无效')
|
||
|
||
|
||
class UserViewSet(viewsets.ViewSet):
|
||
"""用户视图集 - App端"""
|
||
|
||
authentication_classes = [AppJWTAuthentication]
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
@action(detail=False, methods=['get'])
|
||
def me(self, request):
|
||
"""
|
||
获取当前用户信息
|
||
GET /api/v1/users/me
|
||
"""
|
||
return success(data=UserSerializer(request.user).data)
|
||
|
||
@action(detail=False, methods=['put'])
|
||
def update_me(self, request):
|
||
"""
|
||
更新当前用户信息
|
||
PUT /api/v1/users/update_me
|
||
"""
|
||
serializer = UpdateUserSerializer(request.user, data=request.data, partial=True)
|
||
if serializer.is_valid():
|
||
serializer.save()
|
||
return success(data=UserSerializer(request.user).data, message='更新成功')
|
||
return error(message=str(serializer.errors))
|
||
|
||
|
||
class AdminUserManageViewSet(viewsets.ViewSet):
|
||
"""App用户管理视图集 - 管理端"""
|
||
|
||
authentication_classes = [AdminJWTAuthentication]
|
||
permission_classes = [IsAdminUser]
|
||
|
||
def list(self, request):
|
||
"""
|
||
用户列表
|
||
GET /api/admin/users
|
||
"""
|
||
queryset = User.objects.all().order_by('-created_at')
|
||
|
||
# 搜索条件
|
||
phone = request.query_params.get('phone')
|
||
nickname = request.query_params.get('nickname')
|
||
is_active = request.query_params.get('is_active')
|
||
|
||
if phone:
|
||
queryset = queryset.filter(phone__contains=phone)
|
||
if nickname:
|
||
queryset = queryset.filter(nickname__contains=nickname)
|
||
if is_active is not None:
|
||
queryset = queryset.filter(is_active=(is_active.lower() == 'true'))
|
||
|
||
# 标注统计数据
|
||
queryset = queryset.annotate(
|
||
spirit_count=Count('spirits', distinct=True),
|
||
device_count=Count('user_devices', distinct=True)
|
||
)
|
||
|
||
# 分页
|
||
page = int(request.query_params.get('page', 1))
|
||
page_size = int(request.query_params.get('page_size', 10))
|
||
start = (page - 1) * page_size
|
||
end = start + page_size
|
||
|
||
total = queryset.count()
|
||
users = queryset[start:end]
|
||
|
||
# 手动构造带统计的数据
|
||
items = []
|
||
for user in users:
|
||
data = UserSerializer(user).data
|
||
data['spirit_count'] = getattr(user, 'spirit_count', 0)
|
||
data['device_count'] = getattr(user, 'device_count', 0)
|
||
items.append(data)
|
||
|
||
return success(data={
|
||
'total': total,
|
||
'items': items
|
||
})
|
||
|
||
def retrieve(self, request, pk=None):
|
||
"""
|
||
用户详情
|
||
GET /api/admin/users/{id}
|
||
"""
|
||
try:
|
||
user = User.objects.get(pk=pk)
|
||
except User.DoesNotExist:
|
||
return error(message='用户不存在')
|
||
|
||
data = UserDetailSerializer(user).data
|
||
data['spirit_count'] = user.spirits.count()
|
||
data['device_count'] = user.user_devices.count()
|
||
|
||
return success(data=data)
|
||
|
||
@action(detail=True, methods=['post'], url_path='toggle-status')
|
||
def toggle_status(self, request, pk=None):
|
||
"""
|
||
启用/禁用用户
|
||
POST /api/admin/users/{id}/toggle-status
|
||
"""
|
||
try:
|
||
user = User.objects.get(pk=pk)
|
||
except User.DoesNotExist:
|
||
return error(message='用户不存在')
|
||
|
||
user.is_active = not user.is_active
|
||
user.save()
|
||
|
||
return success(
|
||
data=UserSerializer(user).data,
|
||
message='已启用' if user.is_active else '已禁用'
|
||
)
|
||
|