seaislee1209 b520b429c5
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m22s
feat: 密码管理 + 错误提示体系统一 (v0.9.2 & v0.9.3)
密码管理:用户自助修改密码(个人中心弹窗)、管理员重置用户密码(审计日志记录)
错误提示:补全火山 ARK 错误码映射(+7 个)、修复创建失败时前端不显示真实错误、
轮询失败走 ERROR_MESSAGES 映射、前端 catch 统一取后端 message

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 17:12:40 +08:00

146 lines
4.8 KiB
Python

from rest_framework import status
from rest_framework.decorators import api_view, permission_classes, throttle_classes
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.throttling import ScopedRateThrottle
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth import authenticate, get_user_model
from django.utils import timezone
from django.db.models import Sum
from .serializers import UserSerializer
from django.contrib.auth.hashers import check_password
User = get_user_model()
class LoginRateThrottle(ScopedRateThrottle):
scope = 'login'
@api_view(['POST'])
@permission_classes([AllowAny])
def register_view(request):
"""POST /api/v1/auth/register — disabled, all accounts created by admins."""
return Response(
{'error': 'registration_disabled', 'message': '公开注册已关闭,请联系管理员'},
status=status.HTTP_403_FORBIDDEN,
)
@api_view(['POST'])
@permission_classes([AllowAny])
@throttle_classes([LoginRateThrottle])
def login_view(request):
"""POST /api/v1/auth/login"""
username = request.data.get('username', '').strip()
password = request.data.get('password', '')
# Try authenticate with username first, then email
user = authenticate(username=username, password=password)
if user is None:
# Try email login
try:
user_by_email = User.objects.get(email=username)
user = authenticate(username=user_by_email.username, password=password)
except User.DoesNotExist:
pass
if user is None:
return Response(
{'error': 'invalid_credentials', 'message': '用户名或密码错误'},
status=status.HTTP_401_UNAUTHORIZED
)
refresh = RefreshToken.for_user(user)
return Response({
'user': UserSerializer(user).data,
'tokens': {
'access': str(refresh.access_token),
'refresh': str(refresh),
}
})
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def me_view(request):
"""GET /api/v1/auth/me — returns role, team info, and quota."""
user = request.user
today = timezone.now().date()
first_of_month = today.replace(day=1)
daily_seconds_used = user.generation_records.filter(
created_at__date=today
).aggregate(total=Sum('seconds_consumed'))['total'] or 0
monthly_seconds_used = user.generation_records.filter(
created_at__date__gte=first_of_month
).aggregate(total=Sum('seconds_consumed'))['total'] or 0
data = UserSerializer(user).data
data['quota'] = {
'daily_seconds_limit': user.daily_seconds_limit,
'daily_seconds_used': daily_seconds_used,
'monthly_seconds_limit': user.monthly_seconds_limit,
'monthly_seconds_used': monthly_seconds_used,
}
# Team info
team = user.team
if team:
# Team monthly consumption
from apps.generation.models import GenerationRecord
team_monthly_used = GenerationRecord.objects.filter(
user__team=team,
created_at__date__gte=first_of_month,
).aggregate(total=Sum('seconds_consumed'))['total'] or 0
data['team'] = {
'id': team.id,
'name': team.name,
'total_seconds_pool': team.total_seconds_pool,
'total_seconds_used': team.total_seconds_used,
'remaining_seconds': team.remaining_seconds,
'monthly_seconds_limit': team.monthly_seconds_limit,
'monthly_seconds_used': team_monthly_used,
'is_active': team.is_active,
}
data['team_disabled'] = not team.is_active
else:
data['team'] = None
data['team_disabled'] = False
return Response(data)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def change_password_view(request):
"""POST /api/v1/auth/change-password — user changes own password."""
old_password = request.data.get('old_password', '')
new_password = request.data.get('new_password', '')
if not old_password or not new_password:
return Response(
{'error': 'missing_fields', 'message': '请填写旧密码和新密码'},
status=status.HTTP_400_BAD_REQUEST,
)
if len(new_password) < 8:
return Response(
{'error': 'password_too_short', 'message': '新密码至少8位'},
status=status.HTTP_400_BAD_REQUEST,
)
if not check_password(old_password, request.user.password):
return Response(
{'error': 'wrong_password', 'message': '旧密码错误'},
status=status.HTTP_400_BAD_REQUEST,
)
request.user.set_password(new_password)
request.user.save()
return Response({'message': '密码修改成功'})