flask開發restful api系列(1)

来源:http://www.cnblogs.com/yueerwanwan0204/archive/2016/03/28/5327912.html
-Advertisement-
Play Games

在此之前,向大家說明的是,我們整個框架用的是flask + sqlalchemy + redis。如果沒有開發過web,還是先去學習一下,這邊只是介紹如果從開發web轉換到開發移動端。如果flask還不是很熟悉,我建議先到這個網站簡單學習一下,非常非常簡單。http://dormousehole.r ...


  在此之前,向大家說明的是,我們整個框架用的是flask + sqlalchemy + redis。如果沒有開發過web,還是先去學習一下,這邊只是介紹如果從開發web轉換到開發移動端。如果flask還不是很熟悉,我建議先到這個網站簡單學習一下,非常非常簡單。http://dormousehole.readthedocs.org/en/latest/ 

  一直想寫一些特別的東西,能讓大家學習討論的東西。但目前網上的很多博客,老么就按照官方文檔照本宣讀,要麼直接搬代碼,什麼都不說明。我寫這個系列的博客,讓大家由淺入深,一步一步走向複雜結構,以及為啥要這麼走,其他方式可不可以等等。

  目前看來,移動開發最火,而我們python最適合開發移動的就是flask web框架,這款web框架非常清晰,可以簡單用,可以複雜用。最簡單的時候,一個py文件,就可以做一個項目;複雜的時候,利用藍圖,做各種版本控制,代碼結構自己完全控制,非常自由。

  首先,我們要知道,目前我們移動開發基本都在用restful api,什麼是restful api呢?百度百科一下:Web 應用程式最重要的 REST 原則是,客戶端和伺服器之間的交互在請求之間是無狀態的。從客戶端到伺服器的每個請求都必須包含理解請求所必需的信息。如果伺服器在請求之間的任何時間點重啟,客戶端不會得到通知。此外,無狀態請求可以由任何可用伺服器回答,這十分適合雲計算之類的環境。客戶端可以緩存數據以改進性能。

  說白了我們不能使用cookie,不能使用session了。如果稍微有點http經驗的人,都知道,很多時候,我們都把一些基本內容放在cookie裡面,伺服器每次讀取或者寫入的時候,伺服器端就直接設置session就可以了,這樣,每次,客戶端直接攜帶自己的cookie值上來,我們就知道它是誰,怎麼把數據給它。但restful api的風格不允許這樣,那伺服器應該採取何種方案呢?

  目前網上大多數做法是token方式,第一次登錄的時候,先提交用戶名密碼,伺服器收集到以後,先驗證一下,如果驗證通過了,這時候伺服器端基於用戶名、密碼、當前時間戳等內容,用md5或者des或者aes等加密方式,生成一個token值,然後把token值存放到redis裡面,記錄它對應哪個用戶,然後把這個token值發給客戶端。客戶端收到token值以後,下次訪問伺服器端任何介面的時候,直接攜帶這個token,伺服器端就知道它是誰了,該給它什麼數據。哪以何種方式給伺服器端呢?通常的做法就是把token值放在header裡面。當然這個不是絕對,你也可以放在body裡面,這個都是仁者見仁智者見智的事。好了,我們就先放在header頭裡面。

  以上說了這麼多,大家也煩了,直接上代碼,再解釋吧。

  首先,創建整個工程,為了最簡單,方便,我們直接就保留以下的py文件。

   

  看model.py裡面的代碼:

  

# coding:utf-8
from sqlalchemy import create_engine, ForeignKey, Column, Integer, String, Text, DateTime,\
    and_, or_, SmallInteger, Float, DECIMAL, desc, asc, Table, join, event
from sqlalchemy.orm import relationship, backref, sessionmaker, scoped_session, aliased, mapper
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
from sqlalchemy.orm.collections import attribute_mapped_collection
import datetime

engine = create_engine("mysql://root:[email protected]:3306/blog01?charset=utf8", pool_recycle=7200)

Base = declarative_base()

db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))

Base.query = db_session.query_property()


class User(Base):
    __tablename__ = 'user'

    id = Column('id', Integer, primary_key=True)
    phone_number = Column('phone_number', String(11), index=True)
    password = Column('password', String(30))
    nickname = Column('nickname', String(30), index=True, nullable=True)
    register_time = Column('register_time', DateTime, index=True, default=datetime.datetime.now)


if __name__ == '__main__':
    Base.metadata.create_all(engine)

運行一下,就創建user表了。

user表中,有電話號碼,phone_number;密碼,password;昵稱,nickname;register_time,註冊時間

  下麵是view.py代碼

 1 # coding:utf-8
 2 from flask import Flask, request, jsonify
 3 from model import User, db_session
 4 import hashlib
 5 import time
 6 import redis
 7 
 8 app = Flask(__name__)
 9 redis_store = redis.Redis(host='localhost', port=6380, db=4, password='dahai123')
10 
11 
12 @app.route('/')
13 def hello_world():
14     return 'Hello World!'
15 
16 
17 @app.route('/login', methods=['POST'])
18 def login():
19     phone_number = request.get_json().get('phone_number')
20     password = request.get_json().get('password')
21     user = User.query.filter_by(phone_number=phone_number).first()
22     if not user:
23         return jsonify({'code': 0, 'message': '沒有此用戶'})
24 
25     if user.password != password:
26         return jsonify({'code': 0, 'message': '密碼錯誤'})
27 
28     m = hashlib.md5()
29     m.update(phone_number)
30     m.update(password)
31     m.update(str(int(time.time())))
32     token = m.hexdigest()
33 
34     redis_store.hmset('user:%s' % user.phone_number, {'token': token, 'nickname': user.nickname, 'app_online': 1})
35     redis_store.set('token:%s' % token, user.phone_number)
36     redis_store.expire('token:%s' % token, 3600*24*30)
37 
38     return jsonify({'code': 1, 'message': '成功登錄', 'nickname': user.nickname, 'token': token})
39 
40 
41 @app.route('/user')
42 def user():
43     token = request.headers.get('token')
44     if not token:
45         return jsonify({'code': 0, 'message': '需要驗證'})
46     phone_number = redis_store.get('token:%s' % token)
47     if not phone_number or token != redis_store.hget('user:%s' % phone_number, 'token'):
48         return jsonify({'code': 2, 'message': '驗證信息錯誤'})
49 
50     nickname = redis_store.hget('user:%s' % phone_number, 'nickname')
51     return jsonify({'code': 1, 'nickname': nickname, 'phone_number': phone_number})
52 
53 
54 @app.route('/logout')
55 def logout():
56     token = request.headers.get('token')
57     if not token:
58         return jsonify({'code': 0, 'message': '需要驗證'})
59     phone_number = redis_store.get('token:%s' % token)
60     if not phone_number or token != redis_store.hget('user:%s' % phone_number, 'token'):
61         return jsonify({'code': 2, 'message': '驗證信息錯誤'})
62 
63     redis_store.delete('token:%s' % token)
64     redis_store.hmset('user:%s' % phone_number, {'app_online': 0})
65     return jsonify({'code': 1, 'message': '成功註銷'})
66 
67 
68 @app.teardown_request
69 def handle_teardown_request(exception):
70     db_session.remove()
71 
72 if __name__ == '__main__':
73     app.run(debug=True, host='0.0.0.0', port=5001)

 

下麵來逐個解釋一下,

首先,幾個import不用解釋了,註意把User和db_session 都import過來,然後定義一個redis的connection。

再接下來就login介面,直接post方法,獲取json格式數據,裡面有phone_number和password,接下來,資料庫查詢,如果沒有這個用戶,返回一個json格式,再接下來對比password,如果phone_number和password都正確,就用md5函數,生成一個token,這個token包含由phone_number、password、當前時間戳str(int(time.time()))生成,再返回。

看我每個返回的東西,首先,都是json格式,這個是目前大多數移動開發預設返回的格式;其次,每個返回,必定有個code,目前這邊有2個值,是0和1,其實可以看出來,0代表失敗,1代表成功,有0的地方必定要有message。每個返回一個code是必須的,但是值,可以自己定義。很多開發把http的code直接拿來用,也可以,比如成功返回,就200,沒有就404,禁止就403。這些都可以,只要伺服器端開發和客戶端開發開始就約定一個值就好,具體的值,只要能快速開發,都可以。

好了,返回格式先解釋到這,我們以後會繼續擴展。再看接下來的redis。

第一行,先用 user:13765505223 這種類型的作為每個用戶的key,值是一些基本的東西,其中app_online,代表上線了,這個app_online,其實很重要的,因為移動端開發跟web不同,要記錄移動端在登錄狀態,還是登出狀態。如果在登錄狀態,我們就可以從伺服器推送了,關於推送,我們以後會逐步講,這個先放在這邊。

第二行,用token:token 作為key,key裡面的值是具體的用戶電話號碼

第三行,把這個token設置一個過期時間,超過這個時間,就刪除,這個有需要的app可以設置一下。如果你的app的token想永遠不變,這行代碼可以註釋掉。

好了,目前login函數基本完成。

接下來看驗證函數user,和註銷函數logout,

user這個函數,這是來驗證登錄以後,有沒有數據,沒多少意義。

逐行分析,首先在header里找到這個token,如果沒有token,就返回失敗;其次,驗證redis,如果token所在的key value對裡面沒有值或者值錯誤,則返回失敗。然後返回具體的數據。非常簡單的一個函數。

下麵的logout函數也差不多,也是這樣,也是驗證,然後刪除token的key value對,再設置app_online為0,表示當前是註銷狀態。

最後一個很重要,這邊一定要記住,把這個函數寫上。如果沒有這個函數,每一個會話以後,db_session都不會清除,很多時候,資料庫改變了,前臺找不到,或者明明已經提交,資料庫還是沒有更改,或者長時間沒有訪問介面,mysql gong away,這樣的錯誤。總之,一定要加上。

好了,整個過程已經完成,下麵進入驗證狀態。首先我們在外面新建一個用戶,存到資料庫,然後寫個小腳本驗證一下。

>>> from model import User, db_session
>>> new_user = User(phone_number='12345678901', password='123456', nickname=u'測試用戶1')
>>> db_session.add(new_user)
>>> db_session.commit()

一個用戶已經創建好,接下來就是測試,這邊測試有2種方式,一個用IDE自帶的測試軟體測試,pycharm有很好的測試軟體;其次用小腳本方式測試,既然我們以後要不停的寫例子,就用小腳本測試吧,過程也非常簡單。

 1 # coding:utf-8
 2 import requests
 3 import json
 4 
 5 
 6 class APITest(object):
 7     def __init__(self, base_url):
 8         self.base_url = base_url
 9         self.headers = {}
10         self.token = None
11 
12     def login(self, phone_number, password, path='/login'):
13         payload = {'phone_number': phone_number, 'password': password}
14         self.headers = {'content-type': 'application/json'}
15         response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
16         response_data = json.loads(response.content)
17         self.token = response_data.get('token')
18         return response_data
19 
20     def user(self, path='/user'):
21         self.headers = {'token': self.token}
22         response = requests.get(url=self.base_url + path, headers=self.headers)
23         response_data = json.loads(response.content)
24         return response_data
25 
26     def logout(self, path='/logout'):
27         self.headers = {'token': self.token}
28         response = requests.get(url=self.base_url + path, headers=self.headers)
29         response_data = json.loads(response.content)
30         return response_data

 

寫一個很簡單的小腳本,就可以拉到命令行測試了,我們試試吧。

>>> from client import APITest
>>> api = APITest('http://127.0.0.1:5001')
>>> data = api.login('12345678901', '1234567')
>>> print data.get('message')
密碼錯誤
>>> data = api.login('12345678901', '123456')
>>> print data.get('message')
成功登錄
>>> data = api.user()
>>> print data
{u'phone_number': u'12345678901', u'code': 1, u'nickname': u'\u6d4b\u8bd5\u7528\u62371'}
>>> print nickname
Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name 'nickname' is not defined
>>> print data.get('nickname')
測試用戶1
>>> data = api.logout()
>>> print data
{u'message': u'\u6210\u529f\u6ce8\u9500', u'code': 1}
>>> print message
Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name 'message' is not defined
>>> print data.get('message')
成功註銷

 登錄成功的時候,我們進redis看看redis數據格式,比較直觀點。

127.0.0.1:6380[4]> keys *
1) "token:bbf73ab651a13a5bc5601cf01add2564"
2) "user:12345678901"
127.0.0.1:6380[4]> hgetall user:12345678901
1) "token"
2) "bbf73ab651a13a5bc5601cf01add2564"
3) "nickname"
4) "\xe6\xb5\x8b\xe8\xaf\x95\xe7\x94\xa8\xe6\x88\xb71"
5) "app_online"
6) "1"
127.0.0.1:6380[4]> get token:bbf73ab651a13a5bc5601cf01add2564
"12345678901"
127.0.0.1:6380[4]> 

 

嗯,一切都正常,但我們開發不能一切正常,就滿足。就這些代碼,我們有很多需要改進的地方,尤其是驗證token的那邊,是不是可以改進呢?redis設置的時候,一連串動作,如果這時候出錯,或者redis只設置到一半怎麼辦?這些問題,我們下一章繼續解決。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 繼續上一章所講,上一章我們最後面說道,雖然這個是很小的程式,但還有好幾個要優化的地方。先複製一下老的view.py代碼。 其中驗證token的方法,已經重疊了,python教我們,永遠不要重覆自己的代碼,這是很醜陋的行為。今天我們把它換成一個裝飾器,然後再把redis調整一下,看看代碼會不會簡潔很多 ...
  • 最近折騰微信掃碼支付,看了微信官方文檔,找了很多網頁,發現和文檔/demo不匹配,現在自己算是弄出來了(文件名稱有所更改),貼出來分享一下 一.將有用的官方lib文件和使用的相關文件放置到vendor當中 二.調用同一支付介面 WechatController ...
  • 原址:http://blog.chedushi.com/archives/7258 最近在用 xlrd 寫一個題庫自動導出的程式,但碰到一個比較 ugly 的問題。 程式要求是將 xls 文件中的數據導出成文本,但 xlrd 在讀取數據時,會將 xls 單元格中所有可能是數字的數據都自動轉換成 py ...
  • 代碼: 中間就遇見一個問題很讓我糾結,就是xlrd 在讀取數據時,會將 xls 單元格中所有可能是數字的數據都自動轉換成 python 的 float。這時候,我們通過 str(cell.value) 會得到 12.0 (假設 cell.value = 12.0)。 解決辦法:比如我的a=1,那麼x ...
  • 2015老男孩Python培訓第八期視頻教程,希望您通過本教程的學習,能學會常用方法和技巧。教程從基礎知識開始講解一直到後期的案例實戰,完全零基礎學習,從初學者的角度探討分析問題,循序漸進由易到難,確保每一位初學者都能融會貫通。從零基礎到開發的大神,您只要兩個字——堅持!屌絲逆襲模式已經開啟,各位小 ...
  • 繼上一篇【Python數據分析】Python3操作Excel-以豆瓣圖書Top250為例 對豆瓣圖書Top250進行爬取以後,鑒於還有一些問題沒有解決,所以進行了進一步的交流討論,這期間得到了一隻尼瑪的幫助與啟發,十分感謝! 上次存在的問題如下: 1.寫入不能繼續的問題 2.在Python IDLE ...
  • 小Alan在最近的開發中遇到了敏感詞過濾,便去網上查閱了很多敏感詞過濾的資料,在這裡也和大家分享一下自己的理解。 在寫之前,小Alan給大家推薦一篇來自http://cmsblogs.com/?p=1031的博文,也會參考部分內容來描述博文。 敏感詞過濾應該是不用給大家過多的解釋吧?講白了就是你在項 ...
  • ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...