flask 项目重构总结(一)

简述

这一阶段的重构主要集中在接口返回码的格式统一上,优化了臃肿的各种判断及try-exception 机制。
主要思路,使用装饰器来封装常用代码;使用Python的exception机制,自定义一些Exception,在恰当的地方主动抛出异常,然后在顶层再catch住,进行统一的handle处理。

现存问题

在views.py文件里,我们都要检查url里的参数,原来写法如下:

(__init__.py)
def checkParam(requirefields, param):
    for i in requirefields:
        if -1 == param.get(i, -1):
            return False
    return True
(views.py)
  if not checkParam(["company_name", "sign", "userID"], param):
        return 'Missing Param'

这种写法使得“missing param” 很分散,每个接口都要复制一遍代码使得代码非常臃肿,并且增加了“missing param”消息体封装的难度。针对这两个问题,我们采用了如下方法来重构

解决方法

一 抽离常用代码

参考了文雨写的@frequency_limit,将检查param和sign的方法抽离出来:

def frequency_limit(func):
    @wraps(func)
    def check_frequency():
        userID = request.args.get('userID')
        if not r.exists(userID):
            raise sequee_exceptions.InvalidUser
        if int(r.get(userID)) <= 0:
            raise sequee_exceptions.AccessLimitationException
        r.decr(userID)
        return func()
    return check_frequency


def check_params_and_sign(func):
    @wraps(func)
    def check_it():
        param = request.args
        checkParam(["company_name", "sign", "userID"], param)
        checkSign(param.get("company_name"), param.get("userID"), param.get("sign"))
        return func()
    return check_it

这样,在使用时直接在每个函数的前面加上标签就可以使用:

@app.route('/icp_url/', methods=['GET'])
@frequency_limit
@check_params_and_sign
def icp_url():
       ……

二 引入自定义exception机制

上面将checkSigncheckParam 封装后也引入了新的问题,怎么恰当的将SignErr和 ParamErr返回呢,怎么在写代码时尽量少些try-exception呢?
这里我们借用exception机制来实现:

1 定义 exception

首先在sequee_exception.py文件里定义我们会用的异常,这里拿InvalidParamException举例。

(sequee_exceptions.py)
class InvalidParamException(Exception):#自定义InvalidParamException,继承自Exception
    def __init__(self):
        Exception.__init__(self)

2 恰当的地方抛异常

(sequee_web\__init__.py)
def checkParam(requirefields, param):
    for i in requirefields:
        if -1 == param.get(i, -1):#如果缺少param,则抛出异常
            raise sequee_exceptions.InvalidParamException
    pass

3 在顶层 catch住,并统一处理:

flask 提供了异常处理的机制@app.errorhandler(Exception)括号里指明该方法handle的异常类型。这里用了所有的异常父类-Exception

@app.errorhandler(Exception)
def hand_errs(e):
    logging.error(traceback.format_exc())
    # 参数错误
    if type(e) == sequee_exceptions.InvalidParamException:
        return json_message.err(json_message.INVALID_PARAM_ERR_MESSAGE)
    # 签名错误
    elif type(e) == sequee_exceptions.InvalidSignException:
        return json_message.err(json_message.INVALID_SIGN_ERR_MESSAGE)
    # 用户名错误
    elif type(e) == sequee_exceptions.InvalidUser:
        return json_message.err(json_message.INVALID_USER_ERR_MESSAGE)
    # 未授权用户
    elif type(e) == sequee_exceptions.UnauthorizedException:
        return json_message.err(json_message.UNAUTHORIZED_ERR_MESSAGE)
    # 访问达到上限
    elif type(e) == sequee_exceptions.AccessLimitationException:
        return json_message.err(json_message.ACCESS_LIMITATION_MESSAGE)

    # 内部错误,发邮件
    send_alert_email_to(mail_receive, mail_cc, traceback.format_exc())
    return json_message.err(json_message.INTERNAL_ERR_MESSAGE)

这样,在一般的逻辑里就不用考虑各种判断和检测了。同时代码也简洁不少:
最后的效果如下:


@app.route('/edu/school_common/', methods=['GET'])
@frequency_limit
@check_params_and_sign
def school_common_info():
    logging.debug('searching school_common info....')
    param = request.args
    db = mysql.Mysql(mysql.mysql_conf["data_engine"])
    conn = db.connect()
    auth = checkUser(param.get("userID"), conn, 'school_common')
    data = edu_query.query_edu(param.get("school_name"), 'putonggaoxiaobiao', conn)  # 有查数据库的权限,可以查库了
    if auth == 1:  # 全部数据可读
        return json_message.ok(data)
    else:  # 仅能读取部分数据,将所有结果过滤后返回
        return json_message.ok(filter_data_by_auth(data))

你可能感兴趣的:(python)