python-flask 技能点使用-03 请求钩子实现审计日志
- 场景分析
使用python flask开发web系统,该系统是基于用户认证鉴权的web系统, 系统中涉及到关键数据的操作,因此需要针对业务操作进行记录(也就是审计日志),便于管理员后期查看,在基于java的Spring系列框架中我们可以借助于AOP面向切面的编程来完成,在使用Flask时可以借助于请求钩子 @app.before_request + @app.after_request 来实现,关键难点在于如何保证记录的是同一个请求URL(一个是请求前预记录,一个是请求后根据返回码进行补充)
- 代码实现
- 如何实现预记录+修改补充,在预记录时生成一个唯一性UUID标记本次URL请求
- 尝试一: 使用request.headers中扩充携带UUID,失败,报错说headers是不可变的
- 尝试二: 使用request.session中扩充携带UUID,与上面类似
- 尝试三: 通过HashMap携带,在后期分布式应用中不一定可靠
- 尝试四: 通过请参数中扩展,存在的问题 如何在PUT、POST、GET、DELETE不同请求方式中实现
- 定义数据库实体类
# 业务审计日志表
class BusinessAuditLog(db.Model):
__tablename__ = 'business_aduit_record'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
uuid = db.Column(db.String(50))
url = db.Column(db.String(50))
req_params = db.Column(db.String(5000))
req_method = db.Column(db.String(10))
is_succ = db.Column(db.String(10))
cost_time = db.Column(db.Integer)
resp_code = db.Column(db.String(10))
resp_data = db.Column(db.Text(5000))
resp_msg = db.Column(db.String(500))
user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
start_time = db.Column(db.DateTime(50), default=datetime.now)
end_time = db.Column(db.DateTime(50), default=datetime.now)
def __int__(self, url, req_params, req_method, user_id, start_time, uuid):
self.url = url
self.req_params = req_params
self.req_method = req_method
self.user_id = user_id
self.start_time = start_time
self.uuid = uuid
-
- 请求前预记录(携带token校验代码)
# 不需要验证的接口地址
no_use_auth_urls = ["/admin/user/logout", "/admin/user/login", "/admin/user/register", "/admin/user/code",
"/admin/menu/import", "/admin/menu/tree", "/admin/menu/tree/i18n", "/favicon.ico"]
# 在每一次请求之前调用,这时候已经有了请求,可以在这个方法里面做请求的校验
# 如果请求的校验不成功,可以直接在此方法中进行响应,直接return之后那么就不会执行视图函数了 目前必须在这个文件中才会执行
@app.before_request
def before_request():
url = request.url
base_url = request.base_url
root_url = request.root_url
host_url = request.host_url
full_path = request.full_path
f_path = full_path.split("?")
bus_url = str(f_path[0])
print("====================== ", bus_url)
is_static = bus_url.startswith("/static")
if bus_url not in no_use_auth_urls and is_static == False:
print('before_request bus_url : ', bus_url)
token = request.headers.get('Authorization')
user_id = UserToken.get_user_id_from_token(token)
if token is None:
return JsonResponse.fail(9999, 'request headers missing Authorization')
if not RedisUtil.validate_token(token):
return JsonResponse.fail(9401,
'request headers missing Authorization or Authorization is expired,need to login')
# 对于系统相关业务的GET请求不记录
method = request.method
if bus_url.startswith("/admin/") and method == 'GET':
return None
if is_static:
return None
req_uuid = str(uuid.uuid4())
req_data = str(request.data, encoding='utf-8')
start_time = time.time()
start_time_hm = int(round(start_time * 1000))
# GET/DELETE请求方式中存在的情况
if req_data is None or CommonTool.is_none(req_data) or req_data == '{}':
req_data = "{\"REQ_UUID\": \"" + req_uuid + "\",\"START_TIME\": " + str(start_time_hm) + "}"
else:
req_data_dict = json.loads(req_data)
req_data_dict['REQ_UUID'] = req_uuid
req_data_dict['START_TIME'] = str(start_time_hm)
req_data = json.dumps(req_data_dict)
req_data_b = req_data.encode(encoding='utf-8')
request.data = req_data_b
req_params = None
if method == 'DELETE' or method == 'GET':
req_params = request.args.to_dict().__str__()
else:
req_params = request.get_data(as_text=True)
db.session.add(BusinessAuditLog(url=bus_url, req_params=req_params, req_method=method, user_id=user_id,
start_time=datetime.now(), uuid=req_uuid))
db.session.commit()
return None
-
- 请求后补充更新
# 在执行完视图函数之后调用,并且会把视图函数生成的响应传入,故其函数需要有一个response参数,可以在此方法中对响应进行进一步处理
@app.after_request
def after_request(response):
full_path = request.full_path
f_path = full_path.split("?")
bus_url = str(f_path[0])
is_static = bus_url.startswith("/static")
if is_static or bus_url in no_use_auth_urls:
return response
response.headers['Content-Type'] = 'application/json'
res = response.json
resp_code = int(res['code'])
req_data = request.data
if resp_code != 9401 and CommonTool.is_not_none(req_data) and req_data is not None:
resp_msg = res['msg']
resp_data = res['data']
req_data = str(request.data, encoding='utf-8')
req_data_json = json.loads(req_data)
req_uuid = req_data_json['REQ_UUID']
start_time_hm = req_data_json['START_TIME']
end_time = time.time()
end_time_hm = int(round(end_time * 1000))
method = request.method
cost_time = end_time_hm - int(start_time_hm)
is_succ = None
if resp_code == 200:
is_succ = "Y"
else:
is_succ = "N"
BusinessAuditLog.query.filter(BusinessAuditLog.uuid == req_uuid,
BusinessAuditLog.req_method == method,
BusinessAuditLog.url == bus_url
).update(
{
"end_time": datetime.now(),
"cost_time": cost_time,
"resp_code": resp_code,
"resp_data": json.dumps(resp_data),
"resp_msg": resp_msg,
"is_succ": is_succ
}
)
db.session.commit()
return response

浙公网安备 33010602011771号