DRF 异常处理 #

一、异常概述 #

1.1 DRF异常层次 #

text
DRF异常层次
├── APIException (基类)
│   ├── ParseError
│   ├── AuthenticationFailed
│   ├── NotAuthenticated
│   ├── PermissionDenied
│   ├── NotFound
│   ├── MethodNotAllowed
│   ├── NotAcceptable
│   ├── UnsupportedMediaType
│   ├── Throttled
│   ├── ValidationError
│   └── 自定义异常

1.2 异常处理流程 #

text
视图抛出异常
      │
      ▼
exception_handler()
      │
      ▼
返回Response或None

二、内置异常 #

2.1 常用异常 #

python
from rest_framework.exceptions import (
    APIException,
    NotFound,
    PermissionDenied,
    ValidationError,
    AuthenticationFailed,
    NotAuthenticated,
    Throttled,
)

2.2 使用示例 #

python
from rest_framework.exceptions import NotFound, PermissionDenied

class ArticleViewSet(viewsets.ModelViewSet):
    def retrieve(self, request, pk=None):
        try:
            article = Article.objects.get(pk=pk)
        except Article.DoesNotExist:
            raise NotFound('文章不存在')
        
        if not article.is_published and request.user != article.author:
            raise PermissionDenied('无权访问')
        
        serializer = self.get_serializer(article)
        return Response(serializer.data)

2.3 ValidationError #

python
from rest_framework.exceptions import ValidationError

class ArticleViewSet(viewsets.ModelViewSet):
    def create(self, request):
        if not request.data.get('title'):
            raise ValidationError({'title': ['标题不能为空']})
        
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        return Response(serializer.data, status=201)

三、自定义异常 #

3.1 创建自定义异常 #

python
from rest_framework.exceptions import APIException
from rest_framework import status

class ArticleNotFound(APIException):
    status_code = status.HTTP_404_NOT_FOUND
    default_detail = '文章不存在'
    default_code = 'article_not_found'

class ArticleAlreadyPublished(APIException):
    status_code = status.HTTP_400_BAD_REQUEST
    default_detail = '文章已发布,无法重复操作'
    default_code = 'article_already_published'

class QuotaExceeded(APIException):
    status_code = status.HTTP_429_TOO_MANY_REQUESTS
    default_detail = '已超出配额限制'
    default_code = 'quota_exceeded'

3.2 使用自定义异常 #

python
class ArticleViewSet(viewsets.ModelViewSet):
    @action(detail=True, methods=['post'])
    def publish(self, request, pk=None):
        article = self.get_object()
        
        if article.is_published:
            raise ArticleAlreadyPublished()
        
        article.is_published = True
        article.save()
        
        return Response({'status': 'published'})

四、全局异常处理 #

4.1 自定义异常处理器 #

python
from rest_framework.views import exception_handler
from rest_framework.response import Response
from rest_framework import status
import logging

logger = logging.getLogger(__name__)

def custom_exception_handler(exc, context):
    response = exception_handler(exc, context)
    
    if response is not None:
        custom_response = {
            'success': False,
            'error': {
                'code': response.status_code,
                'message': get_error_message(response.data),
                'details': response.data
            }
        }
        
        response.data = custom_response
    
    logger.error(f'Exception: {exc}, Context: {context}')
    
    return response

def get_error_message(data):
    if isinstance(data, str):
        return data
    if isinstance(data, list):
        return data[0] if data else '未知错误'
    if isinstance(data, dict):
        for key, value in data.items():
            if isinstance(value, list):
                return f'{key}: {value[0]}'
            return f'{key}: {value}'
    return '未知错误'

4.2 配置异常处理器 #

python
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'core.exceptions.custom_exception_handler',
}

4.3 响应格式 #

json
{
    "success": false,
    "error": {
        "code": 404,
        "message": "文章不存在",
        "details": {
            "detail": "文章不存在"
        }
    }
}

五、异常处理最佳实践 #

5.1 业务异常 #

python
class BusinessException(APIException):
    status_code = status.HTTP_400_BAD_REQUEST
    
    def __init__(self, message, code=None):
        self.default_detail = message
        self.default_code = code or 'business_error'
        super().__init__()

class ArticleViewSet(viewsets.ModelViewSet):
    def perform_create(self, serializer):
        if self.request.user.article_count >= 10:
            raise BusinessException('已达到文章数量上限', 'article_limit_exceeded')
        serializer.save(author=self.request.user)

5.2 错误码系统 #

python
class ErrorCode:
    VALIDATION_ERROR = 'VALIDATION_ERROR'
    NOT_FOUND = 'NOT_FOUND'
    PERMISSION_DENIED = 'PERMISSION_DENIED'
    AUTHENTICATION_FAILED = 'AUTHENTICATION_FAILED'
    RATE_LIMITED = 'RATE_LIMITED'
    BUSINESS_ERROR = 'BUSINESS_ERROR'

class ErrorDetail:
    def __init__(self, message, code):
        self.message = message
        self.code = code

class APIException(APIException):
    def __init__(self, detail=None, code=None):
        if isinstance(detail, ErrorDetail):
            self.detail = {
                'message': detail.message,
                'code': detail.code
            }
        else:
            super().__init__(detail)

六、总结 #

本章学习了DRF异常处理:

  • 内置异常:APIException及其子类
  • 自定义异常:创建业务异常
  • 全局处理:统一异常处理器
  • 最佳实践:错误码系统

掌握异常处理,构建健壮的API系统!

最后更新:2026-03-28