handler specs
handler 规范
1 代码分层设计
MVC 的 View 已经单独剥离出去,
而剩下的 MC, 也逐渐演变成了 CSDM
controller-->service-->dao---->model
|
+------>sdk---->第三方服务
butterfly
├── conf
│ ├── fx
│ │ └── <app>_<resource>_<action>.toml
│ ├── app
│ │ └── <app>.toml
│ └── servicer
│ ├── mysql_<servicer>.py # 第三方服务配置
│ ├── redis_<servicer>.py # 第三方服务配置
│ └── xx_<xxx>.py # config
└── handlers
└── <app>
├── api_<resource>_<action1>.py # controller
├── api_<resource>_<action2>.py # controller
├── lib_<servicer>.py # service
├── model_xxx.py # dao/model
├── sdk_xxx.py # 第三方服务
├── retstat.py # handler 返回值状态字段
├── const.py # handler 常量定义,避免出现魔数(Magic number)
└── __init__.py
1.1 函数命名
<资源>_<行为>
2 接口规范
默认
返回值如果是 dict 则会自动转换为 json 协议,即响应头 ("Content-Type","application/json")
否则则会返回 HTML 格式,即响应头 ("Content-Type","text/html")
json(JavaScript Object Notation)
2.1 接口格式组成
Butterfly 框架接口返回包含状态信息字段 stat 和数据信息字段 data
关于状态字段
butterfly 默认以 stat 作为状态信息字段
butterfly-fe 前端支持后端以 stat(str) 或者 code(int) + message(str) 方式作为状态信息返回数据
即以下通用接口,如果使用其他框架实现后端逻辑,同时要对接 butterfly-fe 时,可以将返回接口中的 stat(str), 替换为 code(int) + message(str)
例子
// butterfly
{
"stat": "OK",
"data": {}
}
// 使用其他框架,接入 butterfly-fe 的话,也可以使用如下实现
{
"code": 0,
"message": "success",
"data": {}
}
2.2 接口格式例子
2.2.1 通用接口格式
请求成功接口格式
{
"stat": "OK", // str
"data": {name: "liming"} // object
{list: [{name: "liming"}, {name: "fengchengcheng"}]}
{} // 如果是提交表单这类 API,没有数据需要返回的话,可以返回空的 object
}
请求失败接口格式
{
"stat": "ERR", // str
"data": {} // 空的 object
}
2.2.2 登录跳转接口格式
登录跳转接口
当接口有鉴权,前端请求数据发现权限校验不通过时,需要让用户跳转到登录地址,因为是 ajax 请求,如果直接返回 302,登录地址和当前地址不一致时候,可能存在跨域问题。
这时候要求接口按照如下格式返回数据,前端在收到对应数据格式之后,在前端做跳转,绕过跨域问题。
{
"stat": "ERR_UNAUTHORIZED", // str
"data": {
'redirect': 'http://xx.baidu.com' // 需要跳转到的登录页
}
}
2.2.3 表格分页数据接口格式
适用于需要后端分页的情况
参数
page_index: 页索引, 从 1 开始
page_size : 每页展示个数
GET /products?page_index=1&page_size=15
{
"stat": "OK", // str
"data": {
"list": [{...}], // 具体数据
"total": 20, // 总数
}
}
2.2.4 图表展示接口格式
适用于前端展示图表数据
{
"stat": "OK", // str
"data": {
"list": [
{
"key": "2020-04-30",
"value": 25, // int
},
{
"key": "2020-05-01",
"value": 22,
},
{
"key": "2020-05-02",
"value": 23,
}
],
"name": "异常实例个数", // list 中的 value 说明
"title": "异常实例统计" // 图表标题
}
}
3 装饰器
3.1 handler 装饰器
# coding=utf8
"""
# Description:
api demo
"""
import logging
from xlib import retstat
from xlib.util import wrapt
from xlib.util import funcsigs
from xlib.middleware import funcattr
from xlib.httpgateway import Request
__info = "demo"
__version = "1.0.1"
logger = logging.getLogger("service")
def requested(method):
"""
装饰器:限制请求方法
"""
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
"""
Args:
- wrapped:被装饰的函数或类方法
- instance:
- 如果被装饰者为普通类方法,该值为类实例
- 如果被装饰者为 classmethod 类方法,该值为类
- 如果被装饰者为类/函数/静态方法,该值为 None
- args:调用时的位置参数(注意没有 * 符号)
- kwargs:调用时的关键字参数(注意没有 ** 符号)
"""
# 提取原 func 中的 req, 不论使用位置参数还是关键字参数
sig = funcsigs.signature(wrapped)
kargs = dict(zip([k for k in sig.parameters if k not in kwargs], args))
req = kargs.get("req", kwargs.get("req"))
if req.wsgienv.get("REQUEST_METHOD") != method:
req.error_str = "method not support"
return retstat.ERR, {}, [(__info, __version)]
return wrapped(*args, **kwargs)
return wrapper
@funcattr.api
@requested("GET")
def hello(req, str_info):
"""
demo
"""
isinstance(req, Request)
logger.info("str_info={str_info}".format(str_info=str_info))
return retstat.OK, {"str_info": str_info}, [(__info, __version)]
Last updated