DRF - 自定义异常返回值
自定义异常处理机制
在之前学习源码时,我们知道:请求到来都会执行dispatch方法,在 try...except代码块中:
-
触发:认证、权限、限流等
-
执行视图
-
上述过程如果出现异常,则由
self.handle_exception(exc)






自定义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', }
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)
方式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
方式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()
方式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
方式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

浙公网安备 33010602011771号