基於Flask開發企業級REST API應用(三)

關於我
編程界的一名小小程序猿,目前在一個創業團隊任team lead,技術棧涉及Android、Python、Java和Go,這個也是咱們團隊的主要技術棧。
Github:github.com/hylinux1024
微信公衆號:angrycodehtml

前兩章把程序的結構以及API的協議基本上搭建起來了。本文開始不打算對每一個模塊接口都進行實現,由於基本上都是業務邏輯代碼,並且整篇文章都把代碼貼出來,那將是一個災難。linux

《上一章》登陸受權模塊的接口進行了實現,在寫本篇文字的時候,我也把用戶模塊的用戶列表、用戶信息查詢、更新用戶信息等接口進行了實現。寫到這裏的時候我發現,有不少重複的邏輯。好比說,登陸參數校驗、錯誤信息處理等這些邏輯,其實這些邏輯能夠進行統一處理。git

0x00 統一錯誤處理

客戶端若是訪問了如下這個沒有定義的接口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."
}
複製代碼

這樣就跟咱們的定義的數據結構接口協議保持一致。

0x01 統一驗證用戶token

因爲系統中有不少接口是須要用戶登陸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_tokenrequire_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
複製代碼

0x02 單元測試

因爲對以前的@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)
複製代碼

這個是對首頁列表的加載的測試,比較簡單。

0x03 小結一下

在項目開發過程當中,對於重複的邏輯應該要抽象封裝

Don't repeat yourself 複製代碼

而如何封裝就要看我的功力了,我以爲除了多學習,多看源碼,幾乎沒有其它捷徑。

0x04 學習資料

相關文章
相關標籤/搜索