DRF - 自定义异常返回值

自定义异常处理机制

在之前学习源码时,我们知道:请求到来都会执行dispatch方法,在 try...except代码块中:

  • 触发:认证、权限、限流等

  • 执行视图

  • 上述过程如果出现异常,则由 self.handle_exception(exc)对异常处理并封装返回值,然后返回。

handle_exception源码

自定义exception_handler 

settings.py 公共

###########
# LOGGING #
###########
import os
BASE_LOG_DIR = BASE_DIR / 'log'
BASE_LOG_DIR.mkdir(exist_ok=True)

# The callable to use to configure logging
LOGGING_CONFIG = "logging.config.dictConfig"

# Custom logging configuration.
# 1. 定义字典
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,  # 删除已存在其他日志的Handler
    'formatters': {
        'standard': {
            'format': '{asctime} {levelname} {threadName} :{message}',
            'style': '{',
            "datefmt": '%Y-%m-%d %H:%M:%S %p',
        },
        'simple': {
            'format': '%(asctime)s %(levelname)s  %(message)s',
            'style': '%',
            "datefmt": '%Y-%m-%d',
        },
    },
    # "filters": {
    #     "dy": {
    #         "()": "django.utils.log.RequireDebugFalse"
    #     },
    #     "call": {
    #         "()": "django.utils.log.CallbackFilter",
    #         "callback": lambda record: len(record.msg) > 4
    #     }
    # },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'simple',
        },
        'run': {
            # 运行日志,按天自动分割
            "class": 'logging.handlers.TimedRotatingFileHandler',
            'formatter': 'standard',
            # 'filters': ["dy", 'call'],
            'filename': os.path.join(BASE_LOG_DIR, 'run.log'),
            'when': 'D',  # 根据天拆分日志
            'interval': 1,  # 1天
            'backupCount': 3,  # 保留备份
            "encoding": "utf-8"
        },
        'error': {
            # 错误日志,按照文件大小分割
            "class": 'logging.handlers.RotatingFileHandler',
            'formatter': 'standard',
            'filename': os.path.join(BASE_LOG_DIR, 'error.log'),
            'maxBytes': 1024 * 1025 * 50,  # 根据文件大小拆分日志 50M
            'backupCount': 5,
            "encoding": "utf-8"
        },
    },
    'loggers': {
        'run': {
            'handlers': ['run'],
            'level': "INFO",  # >=20 则触发日志
            'propagate': True
        },
        'error': {
            'handlers': ['console', 'error'],
            'level': "ERROR",  # >=40 则触发日志
            'propagate': False
        }
    },
    'root': {
        'handlers': ['console', ],
        'level': 'DEBUG',
        'propagate': True
    }
}

##################
# REST_FRAMEWORK #
##################
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'utils.exceptions.custom_exception_handler',
}
settings.py

utils/exceptions.py 公共

from rest_framework.exceptions import APIException


class ExtraException(APIException):
    def __init__(self, detail=None, ret_code=None, code=None):
        super().__init__(detail, code)
        self.ret_code = ret_code


from django.http import Http404
from django.db import DatabaseError
from redis import RedisError

from rest_framework.response import Response
from rest_framework import exceptions
from rest_framework.views import set_rollback, exception_handler

import logging

logger = logging.getLogger('error')


def custom_exception_handler(exc, context):
    view = context['view']
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
        exc.ret_code = 1001
    elif isinstance(exc, exceptions.PermissionDenied):
        exc = exceptions.PermissionDenied()
        exc.ret_code = 1002
    elif isinstance(exc, (exceptions.AuthenticationFailed, exceptions.NotAuthenticated)):
        exc.ret_code = 1003
    elif isinstance(exc, exceptions.Throttled):
        exc.ret_code = 1004
    elif isinstance(exc, exceptions.ValidationError):
        exc.ret_code = 1005
    elif isinstance(exc, DatabaseError):
        exc.ret_code = 1006
    elif isinstance(exc, RedisError):
        exc.ret_code = 1007

    # 只处理drf相关的异常

    headers = {}
    if isinstance(exc, exceptions.APIException):
        if getattr(exc, 'auth_header', None):
            headers['WWW-Authenticate'] = exc.auth_header
        if getattr(exc, 'wait', None):
            headers['Retry-After'] = '%d' % exc.wait

        detail = exc.detail
    elif isinstance(exc, DatabaseError):
        detail = "mysql数据库异常!"
    elif isinstance(exc, RedisError):
        detail = "redis数据库异常!"
    else:
        detail = str(exc)
        # detail = "请求异常"

    if isinstance(exc.detail, dict) and "code" in exc.detail:
        data = exc.detail
    else:
        # code = getattr(exc, 'ret_code', None) or -1
        code = getattr(exc, 'ret_code', -1)
        data = {'code': code, 'detail': detail}

    set_rollback()
    logger.error('异常视图:[%s], 错误信息:%s' % (view, str(exc)))
    return Response(data, headers=headers)
    # return Response(data, status=exc.status_code, headers=headers)
utils/exceptions.py

方式1: utils/viewsets.py   重写 finalize_response 实现没有异常时,返回值多code=0

from rest_framework.viewsets import GenericViewSet as DrfGenericViewSet
from rest_framework import mixins


class GenericViewSet(DrfGenericViewSet):
    def finalize_response(self, request, response, *args, **kwargs):
        response = super().finalize_response(request, response, *args, **kwargs)
        if response.exception:
            return response
        response.data = {'code': 0, 'data': response.data}
        return response


class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    pass
utils/viewsets.py

方式1: 视图使用

from rest_framework import serializers
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from rest_framework.permissions import BasePermission
from rest_framework.throttling import BaseThrottle
from . import models
from utils.exceptions import ExtraException




from utils.viewsets import ModelViewSet


class ExtraAuthentication(BaseAuthentication):
    def authenticate(self, request):
        raise exceptions.AuthenticationFailed("认证失败")   # 使用方式1
        # raise exceptions.AuthenticationFailed({"code": 1003, "detail": "认证失败"})   # 使用方式2

    def authenticate_header(self, request):
        return "api"


class ExtraPermission(BasePermission):
    def has_permission(self, request, view):
        return False

    def has_object_permission(self, request, view, obj):
        return False


class ExtraThrottle(BaseThrottle):
    def allow_request(self, request, view):
        return False


class DemoModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"


class DemoView(ModelViewSet):
    # authentication_classes = [ExtraAuthentication, ]
    # permission_classes = [ExtraPermission, ]
    # throttle_classes = [ExtraThrottle, ]
    queryset = models.UserInfo.objects.all()
    serializer_class = DemoModelSerializer

    def perform_create(self, serializer):
        # self.dispatch
        # 余额是否足够,自定义异常
        raise ExtraException("余额不足", 9999)  # 使用方式1   推荐
        # raise ExtraException(detail="余额不足", ret_code=9999)  # 使用方式1
        # raise ExtraException({"code": 9999, "detail": "余额不足"})  # 使用方式2
        # int("asdf")
        serializer.save()
View Code

方式2: 利用中间件实现没有异常时,返回值多code=0

utils/middlewares.py

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
from django.http import JsonResponse
from rest_framework.response import Response


class ReturnCodeMiddleware(MiddlewareMixin):
    def process_response(self, request, response):
        if not hasattr(response, 'exception'):
            return response

        if response.exception:
            return response

        response.data = {'code': 0, 'data': response.data}
        response._is_rendered = False
        response.content = response.render().content
        return response
utils/middlewares.py

方式2: settings.py中配置中间件

MIDDLEWARE = [
    ...
    "utils.middlewares.ReturnCodeMiddleware"
]

方式2: 视图使用

from rest_framework.viewsets import ModelViewSet
# from utils.viewsets import ModelViewSet

只处理drf相关的异常

from django.http import Http404

from rest_framework import exceptions
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import Throttled
from rest_framework.exceptions import PermissionDenied
from rest_framework.exceptions import NotAuthenticated
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.views import set_rollback


def exception_handler(exc, context):
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
        exc.ret_code = 1001
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()
        exc.ret_code = 1002
    elif isinstance(exc, (AuthenticationFailed, NotAuthenticated)):
        exc.ret_code = 1003
    elif isinstance(exc, Throttled):
        exc.ret_code = 1004
    elif isinstance(exc, ValidationError):
        exc.ret_code = 1005

    # 只处理drf相关的异常
    if isinstance(exc, exceptions.APIException):
        headers = {}
        if getattr(exc, 'auth_header', None):
            headers['WWW-Authenticate'] = exc.auth_header
        if getattr(exc, 'wait', None):
            headers['Retry-After'] = '%d' % exc.wait

        if isinstance(exc.detail, (list, dict)):
            data = exc.detail
        else:
            code = getattr(exc, 'ret_code', None) or -1
            data = {'code': code, 'detail': exc.detail}

        set_rollback()
        return Response(data, status=exc.status_code, headers=headers)
    return None
View Code

 

posted @ 2023-12-26 17:17  silencio。  阅读(68)  评论(0)    收藏  举报