笔者目前使用 Django 从事 SaaS 开发,同时开发和维护多个 SaaS 应用。在很多 SaaS 应用中都约定了错误码,有的用于处理登录态,有的用于标记业务逻辑状态。对于这种项目共性很强的特征,花时间学习和研究是非常有必要的。本篇主要讨论了错误码的用途、如何设计错误码、使用 Django 中间件如何实现异常的处理错误码的返回。
1. 错误码的用途
错误码是与错误信息关联的一组数字或字母,用于约定错误状态。
在 Web 应用中,一次接口的访问,涉及反向代理的转发、业务逻辑的处理、数据库的访问、模板的渲染、中间件的处理等环节,难免会出现各种各样的错误。同时,系统越复杂,访问的链路越长,模块越多,出错的可能性越高。
既然错误无法避免,那么就需要反馈错误信息。返回错误信息,一方面是为了在应用系统开发阶段,方便调试,添加相应的逻辑处理,提示用户;另一方面是应用系统运行时,可能会有潜在的异常风险,错误码能辅助定位和修复问题。
HTTP 状态码是最常见的,通过提前协商处理服务状态的约定编码。HTTP 状态码通常由三个数字组成,第一个数字定义了响应的类别,且只有五种可能值。
状态码 | 范围 | 含义 |
---|---|---|
1xx | 100-101 | 指示信息–表示请求已接收,继续处理 |
2xx | 200-206 | 成功–表示请求已被成功接收、理解、接受 |
3xx | 300-305 | 重定向–信息不完整需要进一步补充 |
4xx | 400-415 | 客户端错误–请求有语法错误或请求无法实现 |
5xx | 500-505 | 服务器端错误–服务器未能实现合法的请求 |
值得注意的是,HTTP 状态码可以通过小数的方式扩展,更加详细的描述服务器状态,比如,403 表示禁止访问,403.1 表示禁止可执行访问,403.2 表示禁止读访问。
通过 HTTP 状态码,客户端能够有效地获取服务器的响应状态,更好地处理异常情况、提示用户信息。错误码和 HTTP 状态码有异曲同工之处,不同的是错误码约定的是业务逻辑,而 HTTP 状态码约定的是服务器的响应状态。
在 Web 应用的通信过程中,如果 HTTP 状态码不足以表示服务器的响应状态,可以通过错误码来补充,比如,服务器返回 {'code': 500101, 'message': u'连接数据库错误'}
。HTTP 状态码是一种已经达成共识的约定编码,而错误码需要建立新的约定。在业务逻辑中,也可以通过 HTTP 状态码表示业务错误状态,比如使用 412 表示未满足前提条件。错误码和 HTTP 状态码有交集,但不能相互替代。
2. 如何设计错误码
2.1 一些公共平台的错误码
错误码主要分为两类
(1) 小于100 错误码,表示用户请求不符合基本校验,比如,字段校验、权限、频率等。
(2) 子错误码,以 “isp.” 开头,表示服务端异常,比如 “isp.remote-service-error”、“isp.remote-service-timeout"等。不同的服务,使用不同的头部。
还有一些特定约定的错误码,比如 801、802等。
错误码说明:
(1) ret = 0,正确返回
(2) ret > 0,调用 OpenAP I时发生错误,需要开发者进行相应的处理。
(3) -50 <= ret <= -1, 接口调用不能通过接口代理机校验,需要开发者进行相应的处理。
(4) ret <-50,系统内部错误
另外,腾讯开放平台提供的各种语言的 SDK 错误码含义相同。使用数字表示,比如 1801、1802、1803等。
以三位和四位的数字为主,下面是部分错误码:
错误代码 | 错误类型 | 说明 |
---|---|---|
0 | 成功 | 调用成功 |
401< | HTTP请求参数不符合要求 | HTTP请求参数不符合要求 |
503 | 调用额度已超出限制 | 调用额度已超出限制 |
504 | 服务故障 | 服务故障 |
4000 | 请求参数非法 | 缺少必要参数,或者参数值格式不正确,具体 |
6000 | 服务器内部错误 | 服务器内部出现错误,请稍后重试或者联系客服人员帮忙解决。 |
腾讯开放平台支付的错误码,以短横线连接三组数字表示。从错误码字母分析,是以短横线分隔不同模块,或者表示不同处理阶段,但是在官方文档上并没有明确说明。下面是部分错误码:
错误码:1003-498493-106
错误码:1003-498692-106
错误码:1025-1025-0
错误码:1043-10053-0
错误码:1058-498198-40000
错误码:1058-500952-40000
错误码:1058-500954-40000
错误码格式
|
|
错误代码说明,以 20502 为例
2 | 05 | 02 |
---|---|---|
服务级错误(1为系统级错误) | 服务模块代码 | 具体错误代码 |
部分错误码:
错误代码 | 错误信息 | 详细描述 |
---|---|---|
10014 | 服务模Insufficient app permissions | 应用的接口访问权限受限 |
20603 | List does not exists | 列表不存在 |
20701 | Repeated tag text | 不能提交相同的收藏标签 |
百度开发者中心的错误码,采用自增的方式编码。
错误代码 | 错误信息 | 详细描述 |
---|---|---|
0 | 成功 | Success |
1 | 未知错误 | Unknown error |
2 | 服务暂不可用 | Service temporarily unavailable |
100 | 请求参数无效 | Invalid parameter |
101 | api key无效 | Invalid API key |
102 | session key无效 | Session key invalid or no longer valid |
103 | call_id参数无效 | Invalid/Used call id parameter |
微信开发平台采用的是五位错误码。
错误代码 | 错误信息 | 详细描述 |
---|---|---|
40001 | invalid credential | 不合法的调用凭证 |
40008 | invalid message type | 不合法的message_type |
40016 | invalid button size | 不合法的菜单按钮个数 |
在微信支付相关的接口中,采用的是英文大写字母加下划线的方式编码。
错误代码 | 错误信息 | 详细描述 |
---|---|---|
NOAUTH | 商户无此接口权限 | 商户未开通此接口权限 |
ORDERPAID | 商户订单已支付,无需重复操作 | 商户订单已支付,无需更多操作 |
SYSTEMERROR | 系统错误 | 系统超时 |
2.2 好的错误码有哪些特征
- 长度足够短
在满足使用需求、考虑扩展的情况下,短的错误码更方便维护和更新。腾讯开放平台的错误码,就显得特别冗长,即使遇到过一次错误,第二次出现时,也很难让人想起来。
- 包含更多信息
新浪开放平台的错误码通过首位区分系统、服务级的错误。后面紧跟着模块代码和具体错误代码,非常容易定位错误。包含更多信息,意味着更长的错误码,这与长度足够短的建议相冲突。怎样选择合适的长度,需要考虑系统的复杂性。如果系统很复杂、需要表示很多状态,那么当然是优先满足系统需要,使用长的错误码,包含更多的错误信息。
字面能够望文生义
微信支付平台的错误码,就特别容易理解其含义。通过几个简单的动作和关键字组合,比如,NO、LACK、DATA、PARAMS,不需要错误码对照表,就能八九不离十的猜到错误含义。当然,还有些比较长的错误码,OUT_TRADE_NO_USED
, 编码和理解会比较费力。充分利用达成共识的编码
返回为0 ,表示请求正常,返回为 <0 ,表示异常,不需要文字的说明,使用达成共识的编码能显著降低沟通的成本。需要注意的是还有一个共识,特别是 Web 开发者,HTTP 状态码是最重要的编码共识。腾讯开放平台和微信开发平台都采用了大量 4XXX 表示客户端错误,而 5XXX 表示服务内部移除。不需要查看错误码对照表,开发者就能基本定位哪里发生了错误,再利用错误码对照表就能具体到程序逻辑的错误。
2.3 错误码设计
2.3.1 根据模块划分编码
第1位 | 第2-3位 | 第4-5位 |
---|---|---|
2 | 05 | 02 |
服务级错误(1为系统级错误) | 服务模块代码 | 具体错误代码 |
对错误码划分区段,利用不同区段表示不同模块,再进行错误编码。这种编码方式的错误码数量会受到一定的限制,例如,10100-10199 使用完时,就不得不占用 102、103开头的错误码。当然,也可以在设计错误码时,预留充足的编码空间。例如:
第1位 | 第2-4位 | 第5-8位 |
---|---|---|
2 | 050 | 0200 |
2.3.2 使用英文短语编码
常见的系统错误码,都是仅使用阿拉伯数字,例如,Windows 系统中的错误码编码就是从 0000 到 15999 递增。使用数字的好处是处理效率高,容易编码。但是,一个数字能表达的含义有限。如果能使用短语,直接给出错误提示,更加直接有效。
|
|
在代码层面,使用英文短语编码与数字编码的区别在于
|
|
2.3.3 使用状态图编码
应用系统的本质是一个有限状态机,而一个错误码表示的就是应用系统的一种错误状态。设计错误码,也就是对应用系统状态进行编码。
以一个简单的购物 Web 系统为例。应用系统只有三个逻辑模块,登录、前置条件检查、付款。
此时,该应用系统有三个 login、front、pay 三个节点。①②③④⑤六条路径。
|
|
如果新增了一个处理节点,exchange
|
|
通过状态图进行错误编码的好处是,能够非常准确的描述哪里出错,系统扩展时,只需要新增节点和领边。这里当然也可以使用数字进行编码,比如节点 login (100),front(101),路径 - ②⑤ (100101XXX)。
3. Django 如何处理异常
Debug = True 时,如果发生异常,Django 将程序运行时的相关信息回显在页面上,方便开发者调试。如下图:
Debug = False 时,如果发生异常,Django 返回自定义或者内置的 500、404等页面。如下图:
下面来看下 Django 是如何处理这些异常的:
3.1 Djang 对 request 的处理
以本地开发为例,当浏览器发起一次请求时,Django 中的 wsgi 创建一个 WSGIHandler 对象处理请求。在
WSGIHandler 对象中初始化环境变量,如果没有异常,则调用 self.get_response(request)
函数处理请求,返回 response 给 wsgi。
get_response
定义在 django.core.handlers.base.py 文件中,下面是处理流程。
|
|
这张图能比较好的呈现整个处理流程逻辑.
3.2 ExceptionBox
Django 的中间件支持一种 Exception 的写法。当发生未捕获处理的异常时,执行中间件中定义的函数 process_exception
,如果返回一个 response, 那么就可以结束整个流程。
在 Django 工程中,需要一个异常处理和错误码统一管理的模块。于是便有了 ExceptionBox。
数据的返回格式:
|
|
__init__.py
|
|
base.py
|
|
error.py
|
|
middleware.py
|
|
my_view.py
|
|