關於我
編程界的一名小小程序猿,目前在一個創業團隊任team lead,技術棧涉及Android、Python、Java和Go,這個也是咱們團隊的主要技術棧。
Github:github.com/hylinux1024
微信公衆號:angrycodehtml
前兩章把程序的結構以及API
的協議基本上搭建起來了。本文開始不打算對每一個模塊接口都進行實現,由於基本上都是業務邏輯代碼,並且整篇文章都把代碼貼出來,那將是一個災難。linux
《上一章》對登陸受權模塊的接口進行了實現,在寫本篇文字的時候,我也把用戶模塊的用戶列表、用戶信息查詢、更新用戶信息等接口進行了實現。寫到這裏的時候我發現,有不少重複的邏輯。好比說,登陸參數校驗、錯誤信息處理等這些邏輯,其實這些邏輯能夠進行統一處理。git
客戶端若是訪問了如下這個沒有定義的接口github
http://127.0.0.1:5000/api/auth/something
複製代碼
將返回如下信息sql
Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
複製代碼
或者有一些數據庫操做出錯,也會致使服務器的內部錯誤數據庫
Internal Server Error
The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
複製代碼
這些信息對使用這個系統API
的客戶端來講不是很友好,咱們但願經過結構化的json
數據進行返回。編程
要對這種http
協議的錯誤信息請求統一處理或者實現自定義的錯誤頁面,就須要用到@errorhandler
這個裝飾器。json
在app.py
中,增長如下兩個方法flask
@app.errorhandler(404)
def not_found_error(error):
return make_response_error(404, error.description)
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return make_response_error(500, error.description)
複製代碼
當請求一個不存在的url
時,咱們的系統應該返回相似如下的信息小程序
{
"code": 404,
"msg": "The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again."
}
複製代碼
這樣就跟咱們的定義的數據結構接口協議保持一致。
因爲系統中有不少接口是須要用戶登陸token
才能訪問的,因此每一個接口都進行登陸token
的驗證。
打開users.py
模塊,如下接口都有token
驗證的邏輯
@bp.route('/show', endpoint='show')
def show_user_info():
uid = request.args.get('userId')
peer_id = request.args.get('peerId')
token = request.args.get('token', '')
if not UserInfo.check_token(uid, token):
return make_response_error(504, 'no operation permission')
...
# 省略沒必要要的代碼
return make_response_ok(data)
@bp.route('/hot/list', endpoint='list')
def list_hot_user():
uid = request.args.get('userId')
token = request.args.get('token', '')
if not UserInfo.check_token(uid, token):
return make_response_error(504, 'no operation permission')
...
# 省略沒必要要的代碼
return make_response_ok(obj)
@bp.route('/update', methods=["POST"], endpoint="update")
def update_user():
uid = request.form.get('userId', '')
token = request.form.get('token', '')
if not UserInfo.check_token(uid, token):
return make_response_error(504, 'no operation permission')
...
# 省略沒必要要的代碼
return make_response_ok(data={"data": user.id})
複製代碼
上面三個接口都有相同的驗證token
的邏輯
if not UserInfo.check_token(uid, token):
return make_response_error(504, 'no operation permission')
複製代碼
而這個系統的接口遠不止這些,若是每一個接口都寫相同的邏輯代碼,看起來也不怎麼優雅。
是否是能夠跟前面定義的@validsign
裝飾器同樣,定義一個@require_token
的裝飾器呢?
答案是確定的。
但這裏我想直接修改@validsign
這個裝飾器函數,給它添加一個參數@validsign(require_token=True)
這種方式,使用起來應該會更加簡潔。
def validsign(require_token=False, require_sign=True):
""" 驗證簽名,token信息 :param require_token: 是否驗證token :param require_sign: 是否驗證簽名 :return: """
def decorator(func):
def wrapper():
params = _get_request_params()
if require_sign:
appkey = params.get('appkey')
sign = params.get('sign')
csign = signature(params)
if not appkey:
return make_response_error(300, 'appkey is none.')
if csign != sign:
return make_response_error(500, 'signature is error.')
if require_token:
token = params.get('token')
uid = params.get('userId')
if not UserInfo.check_token(uid, token):
return make_response_error(504, 'no operation permission')
return func()
return wrapper
return decorator
複製代碼
經過參數require_token
和require_sign
能夠比較靈活的控制接口的驗證邏輯,對開發過程當中調試也是頗有幫助的。
這裏把token
對驗證邏輯封裝在UserInfo
裏面了,這是一個靜態方法
@staticmethod
def check_token(uid, token):
if not token or not uid:
return False
user = UserInfo.query.filter_by(id=uid).first()
if not user:
return False
if not user.user_auth:
return False
return user.user_auth.token == token
複製代碼
因爲對以前的@validsign
裝飾器函數進行修改了,單元測試能夠驗證咱們的修改不會影響到具體的業務邏輯,能夠保證在原來的基礎上進行修改,這是一種保守主義的作事方法。
一樣地新添加的模塊users.py
也須要相應的單元測試功能。
def test_hotlist(self):
import math
nonce = math.floor(random.uniform(100000, 1000000))
params = {'phone': '18922986865', 'userId': '100784', 'appkey': '432ABZ',
'token': '575f680ddbd0d494a1b5fad8497293d2',
'timestamp': datetime.now().timestamp(),
'nonce': nonce}
sign = signature(params)
params['sign'] = sign
respdata = self.app.get("/api/user/hot/list", data=params)
self.assertEqual(200, respdata.status_code)
resp = respdata.json
self.assertEqual(0, resp['code'], respdata.data)
self.assertIsNotNone(resp['data'], respdata.data)
複製代碼
這個是對首頁列表的加載的測試,比較簡單。
在項目開發過程當中,對於重複的邏輯應該要抽象封裝
Don't repeat yourself 複製代碼
而如何封裝就要看我的功力了,我以爲除了多學習,多看源碼,幾乎沒有其它捷徑。
flask
官方文檔models
關係映射相關文檔