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.mzph.cn/news/373351.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JS显示当前时间(包含农历时间)

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

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

转载于:https://www.cnblogs.com/weilu2/p/mybatis_spring_integration_basic_on_annotation.html

JVM:如何分析线程转储

本文将教您如何分析JVM线程转储&#xff0c;并查明问题的根本原因。 从我的角度来看&#xff0c;线程转储分析是掌握Java EE生产支持的任何个人最重要的技能。 您可以从线程转储快照中获取的信息量通常远远超出您的想象。 我的目标是与您分享我在过去10年中积累的有关线程转储分…

极光推送JPush的快速集成

首先到极光推送的官网上创建一个应用&#xff0c;填写对应的应用名和包名。 创建好之后下载Demo 提取Sdk里面的图片和xml等资源文件放自己项目的相应位置&#xff0c;然后要注意的是.so文件的放置位置&#xff1a; 在main目录下新建一个jniLibs文件夹&#xff0c;放在这个文件夹…

elk系列1之入门安装与基本操作

preface 我们每天都要查看服务器的日志&#xff0c;一方面是为了开发的同事翻找日志&#xff0c;另一方面是巡检服务器查看日志&#xff0c;而随着服务器数量以及越来越多的业务上线&#xff0c;日志越来越多&#xff0c;人肉运维相当痛苦了&#xff0c;此时&#xff0c;参考现…

Java 7 –反编译项目硬币

大家好&#xff0c;该是从2012年开始写作的时候了。正如您在其他博客中可能已经看到的那样&#xff0c;有一些更改可以使您使用Java编程时的开发人员生活变得更加轻松&#xff1a;Diamond运算符&#xff0c;Switchs中的Strings&#xff0c;尝试使用资源&#xff0c;多次捕获等 …

在Excel表里面插入背景图

工作中我们会经常用到MS Excel&#xff0c;通常我们打开MS Excel&#xff0c;里面的工作表都是空白单调的背景。当然了&#xff0c;MS Excel可以在工作簿里面插入背景图片。那么问题来了&#xff0c;如果你没有安装Microsoft Office&#xff0c;该如何在Excel文件里面插入好看的…

实现两级下拉框的联动

1.实现两级下拉框的联动。 功能&#xff1a;实现点击年级下拉框&#xff0c;加载对应科目的下拉框。 第一步&#xff1a;首先要加载年级下拉框中的数据。 01.在GradeDAL层&#xff08;数据访问层&#xff09;写一个方法&#xff0c;查询所有年级的信息。 /// <summary>//…

python连接SQL Server取多个结果集:Pymssql模块

基本的用法可以参考&#xff1a;python连接SQL Server&#xff1a;Pymssql模块 和上一篇文章中的代码&#xff0c;只取一个结果集不同&#xff0c;这次会一次运行2个sql语句&#xff0c;然后分别取出2个结果集&#xff0c;打印输出。 代码中有详细的注释&#xff0c;一看就明白…

Xen安全架构sHype/ACM策略配置图文教程

实验要求 1. 熟悉Xen虚拟化平台部署&#xff1b; 2. Xen sHype/ACM安全架构中的Simple TE和Chinese Wall策略及事实上现机制的分析与验证。 第1章 Xen环境部署 1.1 版本号选择 因为Ubuntu使用广泛。软件包易于下载。我们选择Ubuntu系统进行Xen部署…

java弹出虚拟键盘_JS实现电脑虚拟键盘的操作

本文实例为大家分享了JS实现电脑虚拟键盘的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下需求&#xff1a;1.当输入框光标聚焦时&#xff0c;电脑虚拟键盘弹出2.在输入框输入内容时&#xff0c;键盘跟着变化具体实现代码如下&#xff1a;Html部分&#xff1a;电脑键…

JBoss Drools –入门

这篇文章是关于我如何掌握JBoss Drools的 。 其背后的原因是&#xff1a;SAP收购了我公司当前的规则引擎&#xff0c;而Drools是我们将寻找的另一种选择&#xff0c;只要有人掌握了概念验证的技能即可。 尽管似乎有大量的文档&#xff0c;但是我总是会通过示例来发现它是有帮助…

android使用bintray发布aar到jcenter

前言 这两天心血来潮突然想把自己的android library的aar放到jcenter里面&#xff0c;这样一来自己便可以在任何时间任何地点通过internet得到自己的library的引用了&#xff0c;况且现在android studio已经默认使用jcenter的repositories作为依赖来源&#xff0c;以前的mavenc…

PHP 进程详解

PHP 进程详解PHP 进程详解 如下内容从《操作系统精髓与设计原理》中总结提炼得出&#xff0c;删除了大部分对于理解进程有干扰的文字&#xff0c;对进程知识结构进行的梳理。几乎所有内容为按照书本上摘抄下来的&#xff0c;我目前还总结提炼不出像作者这么深刻的见解。那么就先…

java都要caps标点_第 1 章 管理 Java CAPS 用户

第 1 章 管理 Java CAPS 用户在此处列出的主题提供了有关如何管理 Sun JavaTM Composite Application Platform Suite (Java CAPS) 中的用户的信息。如果您有任何问题&#xff0c;请参见 http://goldstar.stc.com/ 中的 Java CAPS Web 站点。管理系统信息库用户此类别包含以下用…

基于OpenCV 的美颜相机推送直播流

程序流程&#xff1a; 1.图像采集 先从opencv&#xff08;2.4.10版本&#xff09;采集回来摄像头的图像&#xff0c;是一帧一帧的 每一帧图像是一个矩阵&#xff0c;opencv中的mat 数据结构。 2.人脸的美化 人脸美化&#xff0c;我们用的皮肤检测&#xff0c;皮肤在颜色空间是特…

Spring线程池服务

线程池对于执行同步和异步过程非常重要。 本文介绍如何使用Spring开发和监视线程池服务。 创建线程池已通过两种替代方法进行了说明。 二手技术 &#xff1a; JDK 1.6.0_21 Spring3.0.5 Maven的3.0.2 步骤1&#xff1a;建立已完成的专案 创建一个Maven项目&#xff0c;如下…

我的世界java村民繁殖_我的世界:Java19w08a更新,村民加强守卫,小狐狸背叛

虽然这句话很绕口&#xff0c;其实意思很简单&#xff0c;那就是之前的隐身药水没有什么用&#xff0c;该被打还是被打。因为这个可是害惨了流浪商人&#xff0c;不过这次好了&#xff0c;流浪商人在喝了隐身药水之后就能安全度过晚上了。NO.3 皮革马铠马铠是属于马的盔甲&…

欧莱雅眉笔banner个人设计

眉笔名称&#xff1a;眉笔大师三头塑形眉笔 &#xff0c;之所以没用吧这个商品名称放大&#xff0c;是我觉得它是一个名称&#xff0c;而我把自己想的广告词“出彩只需一笔” 放大不仅能凸显出这只笔的强大&#xff0c;还表示了一种有了我这支眉笔你会更出彩更漂亮&#xff0c…

使用带有注释和JQuery的Spring MVC 3的Ajax

与Ajax一起工作对我来说一直很有趣&#xff01; 是不是 &#xff1f; 我将使您轻松将Ajax与Spring MVC 3和JQuery结合使用。 这篇文章将向您说明如何在工业编码的现实生活中使用Ajax。 和往常一样&#xff0c;我们将在Spring MVC 3框架中以Ajax的实际示例为例&#xff0c;并将其…