文章目录

  • 快速上手Django(八) -Django之 统一异常、Response处理
  • 一、统一Response处理
  • 二、统一异常处理
  • 1. 需求背景
  • 2. Django、drf统一异常处理
  • 3. Django、drf异常处理基础
  • 4. 纯django场景下
  • 5. 【重要】使用drf场景下,实现思路
  • 编写自定义异常处理方法
  • 在settings/dev.py文件中添加自定义异常处理模块
  • 三、工作常用总结
  • 自定义 serializers.ValidationError 的错误返回
  • 四、参考


快速上手Django(八) -Django之 统一异常、Response处理

一、统一Response处理

参考本人文章:Django 基础(3)-Django响应:rest_framework的Response、关于HttpResponse

思路:
不推荐使用HttpResponse,推荐自定义类 继承rest_framework 的 Response, 该Response继承了django原生的HttpResponse,来实现定制化需求。

直接自定义一个 MyJsonResponse 即可,我们在view中,直接return 这个类即可。

class MyJsonResponse(Response):
    def __init__(self, code=None, msg: Union[str, dict] = 'success',
                 data=None, status=None, template_name=None, headers=None,
                 exception=False, content_type=None, **kwargs):
        super().__init__(None, status=status)

        if isinstance(data, Serializer):
            msg = (
                'You passed a Serializer instance as data, but '
                'probably meant to pass serialized `.data` or '
                '`.error`. representation.'
            )
            raise AssertionError(msg)

        # 改变错误提示格式
        if isinstance(msg, dict):
            for k, v in msg.items():
                for i in v:
                    msg = "%s:%s" % (k, i)

        # 自定义返回格式
        if code is None:
            code = 2000
        self.data = {'code': code, 'msg': msg, 'data': data}
        self.data.update(kwargs)
        self.template_name = template_name
        self.exception = exception
        self.content_type = content_type

        if headers:
            for name, value in headers.items():
                self[name] = value

二、统一异常处理

Django 统一异常处理
参考URL: https://cloud.tencent.com/developer/article/1912568

1. 需求背景

在项目中统一异常处理,可以防止代码中有未捕获的异常出现。

我们想让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息,那么需要统一异常处理。

统一规范,方便前后端进行错误统一处理。同时可以更好的区分是因为网络异常,还是后端异常。

后端封装异常的好处是,如果后端异常HTTP RESP CODE是200,代表请求正确打到后端并取得了响应,如果不封装,那么如果出了异常,无法通过HTTP返回码正确区分。

当然也有人不喜欢这种后端异常都是 HTTP RESP CODE是200。其实这里还有一个非常重要的作用,就是一些异常我们要捕获出来,不要暴露给前端。 如果不喜欢的 所有后端异常都是 HTTP RESP CODE是200,这块全局处理,你也可以配置响应的 HTTP RESP CODE是 500,但是这块统一处理了,我们给前端返回的格式就统一了,这个点比较好!

这样,我们给前端返回的错误信息,就可以根据情况展示到界面上了,可以和前端沟通这种特定格式返回才可以展示到界面上~

2. Django、drf统一异常处理

Django与DRF结合的全局异常处理方案详解
参考URL: jb51.net/article/238484.htm
Django RestFramework 全局异常处理详解
参考URL: https://www.easck.com/cos/2022/0228/909945.shtml

3. Django、drf异常处理基础

Django 和 DRF(django rest framawork) 的结合在 python 后台中经常出现的组合。对于异常的全局处理,我们系统能有一个统一的解决方案,在开发环境能看到比较全的异常堆栈,而在生产环境能更好的给用户一个友好的提示

如果没有 DRF,我们只需要在 Django 中加一个中间件就可以解决全局异常的处理问题,但是 DRF 会帮我们处理一些异常并自动返回到客户端,因此我们要协调两者的异常处理策略

DRF 的异常都是继承自 APIException 这个类的,并且 DRF 跑出的异常会被 exception_handler 这个异常处理函数拦截(这个函数的位置在 /python3.7/site-packages/rest_framework/views.py中)

DRF 通过exception_handler 这个函数的文档签名我们知道,DRF 会处理所有继承自 APIException 的异常类,并且还会额外的处理 Django 内置的 Http404 和 PermissionDenied 异常,并将这些异常的处理结果返回到前端。

REST framework定义的异常:

  • APIException 所有异常的父类

APIException中默认的是 500状态码

  • ParseError 解析错误
  • AuthenticationFailed 认证失败
  • NotAuthenticated 尚未认证
  • PermissionDenied 权限决绝
  • NotFound 未找到
  • MethodNotAllowed 请求方式不支持
  • NotAcceptable 要获取的数据格式不支持
  • Throttled 超过限流次数
  • ValidationError 校验失败
    可以看到有些继承APIException 的异常类,覆写了 https状态码,如下
class ValidationError(APIException):
    status_code = status.HTTP_400_BAD_REQUEST

也就是说,很多的没有在上面列出来的异常,就需要我们在自定义异常中自己处理了

如果不再这些处理范围之内,函数会返回 None,这时候会给 Django 抛出一个 500 的服务器错误异常

Django 在 web 开发中往往会和 DRF 进行结合使用,DRF 有这自己的一套异常处理机制,我们可以对 DRF 中抛出的异常进行全局拦截,比如返回统一的 json 格式以便于前台进行处理。
但是我们同时在 Django 中使用后台 admin 管理数据的同时,不希望修改原有的异常返回。因此我们需要同时配置 Django 和 DRF 的全局异常来适应我们的需求。

4. 纯django场景下

整体思路:

  • 在 Django 项目中可以自定义中间件类 继承 django.middleware.common 下的 MiddlewareMixin 中间件类, 覆写process_exception函数。
  • setting.py 的MiddleWares中添加自定义的MiddleWare

axios的response格式 错误_python

process_exception(self, request, exception) 函数有两个参数,exception 是视图函数异常产生的 Exception 对象
process_exception 函数的执行顺序是按照 settings.py 中设置的中间件的顺序的倒序执行。( 如果注册的多个中间件类中包含process_exception函数的时候,调用的顺序跟注册的顺序是相反的
process_exception 函数只在视图函数中出现异常的时候才执行,它返回的值可以是 None,也可以是一个 HttpResponse 对象
如果返回 None,则继续由下一个中间件的 process_exception 方法来处理异常

5. 【重要】使用drf场景下,实现思路

第一步,自定义自己的异常处理函数
第二步,对 DRF 拦截的异常进行处理
第三步,将其他异常抛给 Django 处理

但是,感觉第三步,不是很需要,drf中定义我们自己业务异常处理,已经满足足够多的业务场景。

因此,这里我选择第一、二步骤!同时,结合自定义的异常和状态码枚举类,来统一和规范异常返回信息

编写自定义异常处理方法

在utils目录下创建exceptions.py文件,并编写自定义异常处理方法

from django.db import DatabaseError
from rest_framework.response import Response
from rest_framework.views import exception_handler
from rest_framework import status
from redis import RedisError
import logging
log = logging.getLogger("django")

def custom_exception_handler(exc, context):
    """
    自定义异常处理
    :param exc: 本次请求发生的异常信息
    :param context: 本次请求发送异常的执行上下文[ 本次请求的request对象,异常发送的时间,行号等...]
    :return: Response响应对象
    """
    response = exception_handler(exc, context)

    if response is None:
        """当response结果为None时,则当前程序运行的结果有2种可能: 
          1. 程序真的没有报错!
          2. 程序报错了,但是drf框架不识别!
        """
        view = context["view"]
        if isinstance(exc, DatabaseError) or isinstance(exc, RedisError):
            """数据库异常"""
            log.error('[%s] %s' % (view, exc))
            return Response("系统内部存储错误!",  status=status.HTTP_507_INSUFFICIENT_STORAGE)

    return response

参数为 exc、context,exc 为错误异常对象实例
context[‘view’] :视图类的对象
context[‘request’]:当前请求的对象
我们可以利用这些属性来生成日志,例如:

'ip地址为:%s的用户,访问:%s 视图类,报错了,请求地址是:%s'%(request.META.get('REMOTE_ADDR'),str(view),request.path)
在settings/dev.py文件中添加自定义异常处理模块

DRF 支持单独配置异常处理函数,因此第一步现在 setting 中指定自定义的异常处理函数的位置:

REST_FRAMEWORK = {
    # 异常处理
    'EXCEPTION_HANDLER': 'myxxx.utils.exceptions.custom_exception_handler',
}

三、工作常用总结

自定义 serializers.ValidationError 的错误返回

重写drf的ValidationError, 改变抛出异常的状态码
参考URL:

在使用DRF进行反序列过程中,总是需要校验字段,然后返回错误结果。

ValidationError 中默认的状态码是 400

这个章节,推荐查看原文,代码很完整,完善!

四、参考

Django 和 DRF(Django Rest Framework) 下的全局异常处理

Django 统一异常处理
参考URL: https://cloud.tencent.com/developer/article/1912568