flask開發restful api系列(1)

  在此之前,向大家說明的是,我們整個框架用的是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 datetimeengine = create_engine("mysql://root:a12345678@127.0.0.1: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只設置到一半怎么辦?這些問題,我們下一章繼續解決。

轉載于:https://www.cnblogs.com/yueerwanwan0204/p/5327912.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/373351.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/373351.shtml
英文地址,請注明出處:http://en.pswp.cn/news/373351.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Apache Commons Lang StringUtils

因此&#xff0c;認為最好談論我喜歡的另一個Java庫。 它已經存在了一段時間&#xff0c;也許不是最令人興奮的庫&#xff0c;但是它非常有用。 我可能每天都使用它。 org.apache.commons.lang.StringUtils StringUtils是Apache Commons Lang&#xff08; http://commons.apac…

JEE7:展望新時代

計劃于2012年下半年發布的Java EE 7預計的JSR都已啟動并正在運行。 Java EE 7發行版是日期驅動的&#xff0c;它將反映該行業遷移到云中時不斷變化的需求&#xff1a;任何未準備就緒的內容將推遲到Java EE 8中使用 。 這是Java EE 7平臺中不同規范的關鍵功能的更新和摘要。 1。…

Cocos2d-JS項目之UI界面的優化

測試環境&#xff1a; iphone4、iOS6.1.2、chrome 37.2062.60&#xff0c;Cocos2d-js 3.6 之前寫了不少&#xff0c;實際項目也按這個去優化了&#xff0c;也有效果&#xff0c;但到最后才發現&#xff0c;尼瑪&#xff0c;之前都搞錯了&#xff0c;之所以有效果是歪打正著。。…

java數_java大數

java大數還是很好用的&#xff01;基本加入&#xff1a;import java.math.BigInteger;import jave.math.BigDecimal;分別是大數和大浮點數。首先讀入可以用&#xff1a;Scanner input new Scanner(System.in);BigInteger a input.nextBigInteger();這樣讀還是很方便的當然還有…

【Qt之Quick模塊】6. QML語法詳解_2類型系統

描述 在QML文檔中對象層次結構的定義中可能使用的類型可以來自各種來源。它們可能是: 由QML語言原生提供通過QML模塊通過c注冊由QML模塊作為QML文檔提供 此外&#xff0c;應用程序開發人員可以通過直接注冊c類型&#xff0c;或者通過在QML文檔中定義可重用的組件(然后可以導…

JS顯示當前時間(包含農歷時間)

時間格式&#xff1a; JavaScript代碼&#xff1a; var sWeek new Array("星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六");var dNow new Date();var CalendarData new Arra…

Maven原型創建技巧

我最近需要為姜黃SOA項目創建一些Maven原型。 對于不了解的人來說&#xff0c; Maven原型是一種基于一些預先罐裝的項目模板生成項目的方法。 對于當前的姜黃SOA原型&#xff0c;它將創建一個多模塊Maven項目&#xff0c;該項目包含Interface和Service項目以及基本的WSDL和適當…

MyBatis操作指南-與Spring集成(基于注解)

轉載于:https://www.cnblogs.com/weilu2/p/mybatis_spring_integration_basic_on_annotation.html

Windows mysql boost_Win7下Boost庫的安裝

Boost庫是C領域公認的經過千錘百煉的知名C類庫&#xff0c;涉及編程中的方方面面&#xff0c;簡單記錄一下使用時的安裝過程1.boost庫的下載boost庫官網主頁&#xff1a;www.boost.org2.安裝將下載的壓縮包解壓到指定的目錄3.建立編譯工具bjam.exe在源碼目錄下執行bootstrap.ba…

5.2與終端進行對話

Linux提供了一個特殊的設備 /dev/tty &#xff0c;該設備始終是指向當前終端或者當前的登錄會話。 FILE* output fopen("/dev/tty", "w"); //向終端寫入字符串 fprintf(output, "%s\n", "world"); FILE* input fopen("/dev/tty…

JVM:如何分析線程轉儲

本文將教您如何分析JVM線程轉儲&#xff0c;并查明問題的根本原因。 從我的角度來看&#xff0c;線程轉儲分析是掌握Java EE生產支持的任何個人最重要的技能。 您可以從線程轉儲快照中獲取的信息量通常遠遠超出您的想象。 我的目標是與您分享我在過去10年中積累的有關線程轉儲分…

極光推送JPush的快速集成

首先到極光推送的官網上創建一個應用&#xff0c;填寫對應的應用名和包名。 創建好之后下載Demo 提取Sdk里面的圖片和xml等資源文件放自己項目的相應位置&#xff0c;然后要注意的是.so文件的放置位置&#xff1a; 在main目錄下新建一個jniLibs文件夾&#xff0c;放在這個文件夾…

c遺傳算法的終止條件一般_Matlab2 :Matlab遺傳算法(GA)優4~-r-具箱是基于基本操作 聯合開發網 - pudn.com...

Matlab2所屬分類&#xff1a;matlab例程開發工具&#xff1a;PDF文件大小&#xff1a;115KB下載次數&#xff1a;76上傳日期&#xff1a;2007-09-07 20:04:29上 傳 者&#xff1a;錢廣說明&#xff1a; &#xff1a;Matlab遺傳算法(GA)優4~-r-具箱是基于基本操作及終止條件、二…

用程序輸出表格

做應用程序的時候經常需要輸出excel表格&#xff0c;除了不同語言接口完善程度不同的麻煩以及要添加各種外部引用的麻煩之外還要考慮應用環境不同的office版本的問題&#xff0c;實在麻煩 當只需要輸出數據&#xff0c;不管顏色什么的的話&#xff0c;有個避免上述麻煩的辦法就…

elk系列1之入門安裝與基本操作

preface 我們每天都要查看服務器的日志&#xff0c;一方面是為了開發的同事翻找日志&#xff0c;另一方面是巡檢服務器查看日志&#xff0c;而隨著服務器數量以及越來越多的業務上線&#xff0c;日志越來越多&#xff0c;人肉運維相當痛苦了&#xff0c;此時&#xff0c;參考現…

Java 7 –反編譯項目硬幣

大家好&#xff0c;該是從2012年開始寫作的時候了。正如您在其他博客中可能已經看到的那樣&#xff0c;有一些更改可以使您使用Java編程時的開發人員生活變得更加輕松&#xff1a;Diamond運算符&#xff0c;Switchs中的Strings&#xff0c;嘗試使用資源&#xff0c;多次捕獲等 …

git安裝后找不見版本_無法安裝最新版本的Gitlab

這個問題不是代碼,而是Gitlab的安裝.我現在試圖安裝Gitlab幾天沒有成功.實際上,很久以前,他們提出了使用wget的方法非常好,但由于我們必須使用Curl的方法,所以不可能比下載更進一步.在Ubuntu 14.04機器上,以下命令有效&#xff1a;curl https://packages.gitlab.com/install/re…

在Excel表里面插入背景圖

工作中我們會經常用到MS Excel&#xff0c;通常我們打開MS Excel&#xff0c;里面的工作表都是空白單調的背景。當然了&#xff0c;MS Excel可以在工作簿里面插入背景圖片。那么問題來了&#xff0c;如果你沒有安裝Microsoft Office&#xff0c;該如何在Excel文件里面插入好看的…

Java 7:復制和移動文件和目錄

這篇文章是我關于Java 7 java.nio.file軟件包的系列文章的繼續&#xff0c;這次涵蓋了文件的復制和移動以及完整的目錄樹。 如果您曾經對Java缺少copy和move方法感到沮喪&#xff0c;那么請繼續閱讀&#xff0c;以免麻煩。 涵蓋范圍中包括非常有用的Files.walkFileTree方法。 但…

java 五子棋項目_Java項目如何實現五子棋小游戲

Java項目如何實現五子棋小游戲發布時間&#xff1a;2020-07-21 14:53:06來源&#xff1a;億速云閱讀&#xff1a;77作者&#xff1a;小豬小編這次要給大家分享的是Java項目如何實現五子棋小游戲&#xff0c;文章內容豐富&#xff0c;感興趣的小伙伴可以來了解一下&#xff0c;希…