赞
踩
在项目中统一异常处理,可以防止代码中有未捕获的异常出现。本文介绍如何在
Django
项目中进行统一异常的处理,再结合状态码枚举类对项目异常信息进行日志记录。
在
Django
项目中可以自定义 中间件类 继承django.middleware.common
下的MiddlewareMixin
中间件类,重写process_exception
方法的异常处理逻辑,然后在项目配置下的 中间件中注册 即可进行全局异常处理。
我是在项目自定义的 utils
包下 middlewares.py
模块中下进行中间件的编写。
# middlewares.py #!/usr/bin/python3 # -*- coding: utf-8 -*- # @Author: Hui # @Desc: { 项目中间件模块 } # @Date: 2021/09/24 8:18 from django.middleware.common import MiddlewareMixin class ExceptionMiddleware(MiddlewareMixin): """统一异常处理中间件""" def process_exception(self, request, exception): """ 统一异常处理 :param request: 请求对象 :param exception: 异常对象 :return: """ # 异常处理 print(exception) return None
这里暂时先简单进行异常输出,来模拟异常处理。最后不要忘记 在配置文件中注册中间件。django
项目默认的配置文件是 settings.py
我这里只是把配置文件单独放到了 settings
包下然后改了文件名。
process_exception
方法介绍process_exception
方法只有在视图函数中出现异常了才执行。该方法的返回值可以是一个 None
也可以是一个 HttpResponse
对象。
None
,页面会报 500 状态码错误,视图函数不会执行。HttpResponse
对象,则是对应的响应信息,页面不会报错。中间件中的方法
方法 | 作用 |
---|---|
process_request(self,request) | 在视图函数之前执行 |
process_view(self, request, view_func, view_args, view_kwargs) | 视图函数之前,process_request 方法之后执行 |
process_exception(self, request, exception) | 视图函数中出现异常了才执行 |
process_response(self, request, response) | 视图函数之后执行 |
下面一图就能比较好的呈现 django
整个处理流程逻辑
更多的中间件细节可以去 Django 官方文档 进行了解。
结合自定义的异常和状态码枚举类,进行异常日志信息和业务逻辑的处理。
# exceptions.py #!/usr/bin/python3 # -*- coding: utf-8 -*- # @Author: Hui # @Desc: { 项目异常模块 } # @Date: 2021/09/24 8:14 class CommonException(Exception): """公共异常类""" def __init__(self, enum_cls): self.code = enum_cls.code self.errmsg = enum_cls.errmsg self.enum_cls = enum_cls # 状态码枚举类 super().__init__() class BusinessException(CommonException): """业务异常类""" pass class APIException(CommonException): """接口异常类""" pass
# enums.py #!/usr/bin/python3 # -*- coding: utf-8 -*- # @Author: Hui # @Desc: { 项目枚举类模块 } # @Date: 2021/09/23 23:37 from enum import Enum class StatusCodeEnum(Enum): """状态码枚举类""" OK = (0, '成功') ERROR = (-1, '错误') SERVER_ERR = (500, '服务器异常') IMAGE_CODE_ERR = (4001, '图形验证码错误') THROTTLING_ERR = (4002, '访问过于频繁') NECESSARY_PARAM_ERR = (4003, '缺少必传参数') USER_ERR = (4004, '用户名错误') PWD_ERR = (4005, '密码错误') CPWD_ERR = (4006, '密码不一致') MOBILE_ERR = (4007, '手机号错误') SMS_CODE_ERR = (4008, '短信验证码有误') ALLOW_ERR = (4009, '未勾选协议') SESSION_ERR = (4010, '用户未登录') REGISTER_FAILED_ERR = (4011, '注册失败') DB_ERR = (5000, '数据库错误') EMAIL_ERR = (5001, '邮箱错误') TEL_ERR = (5002, '固定电话错误') NODATA_ERR = (5003, '无数据') NEW_PWD_ERR = (5004, '新密码错误') OPENID_ERR = (5005, '无效的openid') PARAM_ERR = (5006, '参数错误') STOCK_ERR = (5007, '库存不足') @property def code(self): """获取状态码""" return self.value[0] @property def errmsg(self): """获取状态码信息""" return self.value[1]
自定义的异常类用于区分系统异常和业务来进行单独处理。
状态码枚举则是用来记录对应的异常信息。
状态码枚举类的设计可以查阅 巧用Python 枚举类设计状态码信息
统一前后端交互数据和异常信息结果。
# result.py #!/usr/bin/python3 # -*- coding: utf-8 -*- # @Author: Hui # @Desc: { 项目信息返回结果模块 } # @Date: 2021/09/23 22:10 from .enums import StatusCodeEnum class R(object): """ 统一项目信息返回结果类 """ def __init__(self): self.code = None self.errmsg = None self._data = dict() @staticmethod def ok(): """ 组织成功响应信息 :return: """ r = R() r.code = StatusCodeEnum.OK.code r.errmsg = StatusCodeEnum.OK.errmsg return r @staticmethod def error(): """ 组织错误响应信息 :return: """ r = R() r.code = StatusCodeEnum.ERROR.code r.errmsg = StatusCodeEnum.ERROR.errmsg return r @staticmethod def server_error(): """ 组织服务器错误信息 :return: """ r = R() r.code = StatusCodeEnum.SERVER_ERR.code r.errmsg = StatusCodeEnum.SERVER_ERR.errmsg return r @staticmethod def set_result(enum): """ 组织对应枚举类的响应信息 :param enum: 状态枚举类 :return: """ r = R() r.code = enum.code r.errmsg = enum.errmsg return r def data(self, key=None, obj=None): """统一后端返回的数据""" if key: self._data[key] = obj context = { 'code': self.code, 'errmsg': self.errmsg, 'data': self._data } return context
# middlewares.py #!/usr/bin/python3 # -*- coding: utf-8 -*- # @Author: Hui # @Desc: { 项目中间件模块 } # @Date: 2021/09/24 8:18 import logging from django.db import DatabaseError from django.http.response import JsonResponse from django.http import HttpResponseServerError from django.middleware.common import MiddlewareMixin from meiduo_mall.utils.result import R from meiduo_mall.utils.enums import StatusCodeEnum from meiduo_mall.utils.exceptions import BusinessException logger = logging.getLogger('django') class ExceptionMiddleware(MiddlewareMixin): """统一异常处理中间件""" def process_exception(self, request, exception): """ 统一异常处理 :param request: 请求对象 :param exception: 异常对象 :return: """ if isinstance(exception, BusinessException): # 业务异常处理 data = R.set_result(exception.enum_cls).data() return JsonResponse(data) elif isinstance(exception, DatabaseError): # 数据库异常 r = R.set_result(StatusCodeEnum.DB_ERR) logger.error(r.data(), exc_info=True) return HttpResponseServerError(StatusCodeEnum.SERVER_ERR.errmsg) elif isinstance(exception, Exception): # 服务器异常处理 r = R.server_error() logger.error(r.data(), exc_info=True) return HttpResponseServerError(r.errmsg) return None
让我们来看一段注册校验功能业务逻辑
def verify_params(self, request): """ 校验注册信息 :param request: 注册请求对象 :return: response_ret """ # 接受参数 self.username = request.POST.get('username') self.password = request.POST.get('password') self.confirm_pwd = request.POST.get('confirm_pwd') self.mobile = request.POST.get('mobile') self.allow = request.POST.get('allow') if not all(all_args): # raise BusinessException(StatusCodeEnum.PARAM_ERR) response_ret = http.HttpResponseForbidden('参数错误') return response_ret # 用户名 5-20个字符 if not re.match(r'^[a-zA-Z0-9_]{5,20}', self.username): response_ret = http.HttpResponseForbidden('用户名不规范') return response_ret # 密码 8-20个字符 if not re.match(r'^[a-zA-Z0-9]{8,20}', self.password): response_ret = http.HttpResponseForbidden('密码不规范') return response_ret # 两次密码一致性 if self.password != self.confirm_pwd: response_ret = http.HttpResponseForbidden('两次密码不一致') return response_ret # 手机号合法性 if not re.match(r'^1[3-9]\d{9}$', self.mobile): response_ret = http.HttpResponseForbidden('手机号码不合法') return response_ret # 是否勾选用户协议 if self.allow != 'on': response_ret = http.HttpResponseForbidden('请勾选用户协议') return response_ret return response_ret
通过抛异常和设置状态码枚举来处理
def verify_params(self, request): """ 校验注册信息 :param request: 注册请求对象 :return: response_ret """ # 接受参数 self.username = request.POST.get('username') self.password = request.POST.get('password') self.confirm_pwd = request.POST.get('confirm_pwd') self.mobile = request.POST.get('mobile') self.allow = request.POST.get('allow') # 校验参数 all_args = [self.username, self.password, self.confirm_pwd, self.mobile, self.allow] if not all(all_args): raise BusinessException(StatusCodeEnum.PARAM_ERR) # 用户名 5-20个字符 if not re.match(r'^[a-zA-Z0-9_]{5,20}', self.username): raise BusinessException(StatusCodeEnum.USER_ERR) # 密码 8-20个字符 if not re.match(r'^[a-zA-Z0-9]{8,20}', self.password): raise BusinessException(StatusCodeEnum.PWD_ERR) # 两次密码一致性 if self.password != self.confirm_pwd: raise BusinessException(StatusCodeEnum.CPWD_ERR) # 手机号合法性 if not re.match(r'^1[3-9]\d{9}$', self.mobile): raise BusinessException(StatusCodeEnum.MOBILE_ERR) # 是否勾选用户协议 if self.allow != 'on': raise BusinessException(StatusCodeEnum.ALLOW_ERR)
try ... except ...
代码块例如在对数据库进行操作时,为了防止数据库发生了意外的异常导致系统崩溃,通常加上
try ... except ...
来记录异常信息。然而配置了全局异常处理,则可以不用管理。
# 创建用户 try: user = User.objects.create_user( username=self.username, password=self.password, mobile=self.mobile, ) except DatabaseError as e: logger.error(e) # 有了全局的异常处理 user = User.objects.create_user( username=self.username, password=self.password, mobile=self.mobile, )
注意:如果需要通过异常捕获来处理一些业务信息,则不可避免,如事务回滚等
可能通过文章方式不好理解其思想,大家可以通过项目源代码的方式来参考。
美多商城 https://gitee.com/huiDBK/meiduo_project/tree/master
✍ 用 Code 谱写世界,让生活更有趣。❤️
✍ 万水千山总是情,点赞再走行不行。❤️
✍ 码字不易,还望各位大侠多多支持。❤️
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。