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)

如下接口规范参考 baidu/NoahV 接口规范

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