这一阶段的重构主要集中在接口返回码的格式统一上,优化了臃肿的各种判断及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():
……
上面将checkSign
和checkParam
封装后也引入了新的问题,怎么恰当的将SignErr和 ParamErr返回呢,怎么在写代码时尽量少些try-exception呢?
这里我们借用exception机制来实现:
首先在sequee_exception.py文件里定义我们会用的异常,这里拿InvalidParamException举例。
(sequee_exceptions.py)
class InvalidParamException(Exception):#自定义InvalidParamException,继承自Exception
def __init__(self):
Exception.__init__(self)
(sequee_web\__init__.py)
def checkParam(requirefields, param):
for i in requirefields:
if -1 == param.get(i, -1):#如果缺少param,则抛出异常
raise sequee_exceptions.InvalidParamException
pass
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))