from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework import serializers as drf_serializers from rest_framework_simplejwt.tokens import RefreshToken from drf_spectacular.utils import extend_schema, inline_serializer from apps.user.utils.jwt_redis import revoke_token from apps.user.audit import log_logout @extend_schema( summary='U7 退出登录', request=inline_serializer('LogoutRequest', fields={ 'refresh': drf_serializers.CharField(help_text='refresh token'), }), responses={200: inline_serializer('LogoutResponse', fields={ 'message': drf_serializers.CharField(), })}, tags=['认证'], ) @api_view(['POST']) @permission_classes([AllowAny]) def logout(request): """U7 退出登录 — 无论 token 是否合法均返回 200(防探测)""" refresh_raw = request.data.get('refresh', '') jti = None user_id = None if refresh_raw: try: token = RefreshToken(refresh_raw) jti = token.payload.get('jti') exp = token.payload.get('exp') user_id = token.payload.get('user_id') if jti and exp: revoke_token(jti, exp) except Exception: pass # 静默处理无效 token # 审计:优先取已认证用户,兜底取 token payload audit_uid = None if hasattr(request, 'user') and request.user and request.user.is_authenticated: audit_uid = request.user.id elif user_id: audit_uid = user_id if audit_uid: log_logout(audit_uid, jti=jti) return Response({'message': '已退出登录'})