Flask前后端分离项目案例
简介
学习慕课课程,Flask前后端分离API后台接口的实现demo,前端可以接入小程序,暂时已经完成后台API基础架构,使用postman调试. git
重构部分:
- token校验模块
- auths认证模块
- scope权限模块,增加全局扫描器(参考
flask HTTPExceptions模块)
收获
- 我们可以接受定义时的复杂,但不能接受调用时的复杂
- 如果你觉得写代码厌倦,无聊,那你只是停留在功能的实现上,功能的实现很简单,你要追求的是更好的写法,抽象的艺术,不是机械的劳动而是要创造,要有自己的思考
Sqlalchemy中对类的创建都是用元类的方式,所以调用的时候都不用实例化,当我们重写__init__方法是需要调用orm.reconstrcut装饰器,才会执行实例化对象的构造函数- 权限等级模块的设计(
api访问权限),如超级管理员,管理员,普通用户,访客,这四者之间的关系,有包含的关系,所以可以考虑合并也可以考虑排除的方式来构建权限控制模块. 参考本项目中的app.libs.scope - 学的是解决问题的方法,首先要有深度,在去考虑广度,还要懂得迁移应用,形成自己的思维模型。
推荐阅读:
工作中如何做好技术积累
没有技术深度的苦恼
知识点复盘
初始化flask应用程序
app = Flask(__name__, static_folder='views/statics', static_url_path='/static', template_folder="templates")
创建Flask应用程序实例对象, 如果模块存在,会根据模块所在的目录去寻找静态文件和模块文件, 如果模块不存在,会默认使用app对象所在的项目目录
__name__表示以此模块所在的目录作为工作目录,就是静态文等从这个目录下去找static_folder指定静态文件存放相对路径 flask默认会用/进行分割然后取最后一个作为访问url类似Django中的STATICFILES_DIRSstatic_url_path指定访问静态文件的url地址前缀, 类似Django中的STATIC_URLtemplate_folder指定模板文件的目录
@property
def static_url_path(self):
"""The URL prefix that the static route will be accessible from.
If it was not configured during init, it is derived from
:attr:`static_folder`.
"""
if self._static_url_path is not None:
return self._static_url_path
if self.static_folder is not None:
basename = os.path.basename(self.static_folder)
return ("/" + basename).rstrip("/")
@static_url_path.setter
def static_url_path(self, value):
if value is not None:
value = value.rstrip("/")
self._static_url_path = value
Flask 中url相关底层类
BaseConverter子类:保存提取url参数匹配规则Rule类:记录一个url和一个视图函数的对应关系Map类:记录所有url地址和试图函数对应的关系Map(Rule, Rule, ....)MapAdapter类:执行url匹配的过程,其中有一个match方法,Rule.match(path, method)
自定义路由管理器
from flask import Flask
app = Flask(__name__)
from werkzeug.routing import BaseConverter
class RegexUrl(BaseConverter):
# 指定匹配参数时的正则表达式
# 如: # regex = '\d{6}'
def __init__(self, url_map, regex):
"""
:param url_map: flask会自动传递该参数
:param regex: 自定义的匹配规则
"""
super(RegexUrl, self).__init__(url_map)
self.regex = regex
# 在对应的试图函数之前调用
# 从url中提取出参数之后,会先调用to_python
# 会把提取出的值作为参数传递给to_pthon在返回给对应的试图
def to_python(self, value):
"""可以在这里做一些参数的类型转换"""
return value
# 调用url_for时会被调用, 用来处理url反向解析时url参数处理
# 返回值用来拼接url
def to_url(self, value):
"""对接收到参数做一些过滤等"""
return value
# 将自定义路由转换器类添加到转换器字典中
app.url_map.converters['re'] = RegexUrl
# 案例
@app.route('/user/<re("[a-z]{3}"):id>')
def hello(id):
return f'hello {id}'
if __name__ == '__main__':
app.run(debug=True)
全局异常捕获
AOP编程思想,面向切面编程,把事件统一在一个地方处理,在一个统一的出口做处理
errorhandler 在flask 1.0版本之前只支持填写对应的错误码,比如 @app.errorhandler(404)
在flask1.0版本之后就支持全局的异常捕获了@app.errorhandler(code_or_exception),有了这个之后,就可以在全局做一个异常捕获了,不用每个视图函数都做异常捕获。
@app.errorhandler(Exception)
def framework_error(e):
if isinstance(e, APIException):
return e
elif isinstance(e, HTTPException):
code = e.code
msg = e.description
error_code = 1007
return APIException(msg, code, error_code)
else:
if not current_app.config['DEBUG']:
return ServerError()
else:
raise e
异常类型
- 可预知的异常(已知异常)
- 完全没有意识的异常(未知异常)
abort函数
- abort(状态码) 是一个默认的抛出异常的方法
- 调用abort函数可以抛出一个指定状态码对应的异常信息
- abort函数会立即终止当前视图函数的运行**
模型对象的序列化
场景:我们有时候可能需要返回模型对象中的某些字段,或者全部字段,平时的做法就是将对象中的各个字段转为字典在返回jsonnify(data), 但是这样的写法可能在每个需要返回数据的试图函数中都写一个对应的字典。。对象转字典在返回。json默认是不能序列化对象的,一般我们的做法是 json.dumps(obj, default=lambda o: o.__dict__)但是 __dict__中只保存实例属性,我们的模型类基本定义的类属性。解决这个问题就要看jsonify中是如何做序列化的,然后怎么重写。
- 重写
JSONEncoder
from datetime import date
from flask import Flask as _Flask
from flask.json import JSONEncoder as _JSONEncoder
class JSONEncoder(_JSONEncoder):
"""
重写json序列化,使得模型类的可序列化
"""
def default(self, o):
if hasattr(o, 'keys') and hasattr(o, '__getitem__'):
return dict(o)
if isinstance(o, date):
return o.strftime('%Y-%m-%d')
super(JSONEncoder, self).default(o)
# 需要将重写的类绑定到应用程序中
class Flask(_Flask):
json_encoder = JSONEncoder
- 模型类的定义
class User(Base):
id = Column(Integer, primary_key=True)
email = Column(String(24), unique=True, nullable=False)
nickname = Column(String(24), unique=True)
auth = Column(SmallInteger, default=1)
_password = Column('password', String(100))
def keys(self):
return ['id', 'email', 'nickname', 'auth']
def __getitem__(self, item):
return getattr(self, item)
注意: 修改了
json_encode方法后,只要调用到flask.json模块的都会走这个方法
为什么要写keys和__getitem__方法
当我们使用
dict(object)操作一个对象的时候,dict首先会到实例中找keys的方法,将其返回列表的值作为key, 然后会根据object[key]获取对应的值,所以实例要实现__getitem__方法才可以使用中括号的方式调用属性
进阶写法 - 控制返回的字段
场景:当我们有一个Book的模型类,我们的api接口可能需要返回book的详情页所以就要返回所有字典,但另外一个接口可能只需要返回某几个字段。
class Book(Base):
id = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String(50), nullable=False)
author = Column(String(30), default='未名')
binding = Column(String(20))
publisher = Column(String(50))
price = Column(String(20))
pages = Column(Integer)
pubdate = Column(String(20))
isbn = Column(String(15), nullable=False, unique=True)
summary = Column(String(1000))
image = Column(String(50))
# orm实例化对象, 字段需要写在构造函数中,这样每个实例对象都会有自己的一份,删除增加都不会互相影响
@orm.reconstructor
def __init__(self):
self.fields = ['id', 'title', 'author', 'binding',
'publisher', 'price', 'pages', 'pubdate',
'isbn', 'summary', 'image']
def keys(self):
return self.fields if hasattr(self, 'fields') else []
def hide(self, *keys):
for key in keys:
self.fields.remove(key)
return self
def append(self, *keys):
for key in keys:
self.fields.append(key)
return self
@api.route('/search')
def search():
books = Book.query.filter().all() # 根据某些条件搜索的
books = [book.hide('summary') for book in books]
return jsonify(books)
@api,route('/<isbn>/detail')
def detail(isbn):
book = Book.query.filter_by(isbn=isbn).first_or_404()
return jsonify(book)
请求钩子函数
- before_first_request:在处理第一个请求前运行。
- before_request:在每次请求前运行。
- after_request:如果没有未处理的异常抛出,在每次请求后运行。
- teardown_request:在每次请求后运行,即使有未处理的异常抛出。
全局扫描器
模仿flask exceptions 预加载各个异常类的方式,将用户组自动加载进内存中,这样获取的话就更方便
str2obj = {}
level2str = {}
def iteritems(d, *args, **kwargs):
return iter(d.items(*args, **kwargs))
def _find_scope_group():
for _name, obj in iteritems(globals()):
try:
is_scope_obj = issubclass(obj, BaseScope)
except TypeError:
is_scope_obj = False
if not is_scope_obj or obj.level < 1:
continue
old_obj = str2obj.get(_name, None)
if old_obj is not None and issubclass(obj, old_obj):
continue
str2obj[_name] = obj
level2str[obj.level] = _name
# 模仿flask exceptions 预加载各个异常类的方式,将用户组自动加载进内存
_find_scope_group()
del _find_scope_group
常见bug
form正则校验注意事项
r'[1]{6, 25}$'
带空格和不带空格是两码事, 正则里面{,} 连续不带空格
r'[2]{6,25}$'
参考
Python Flask高级编程之RESTFul API前后端分离精讲
七月老师的课程挺好的,不是纯写代码,而是从问题入手,怎么把复杂问题简单化,从0到1。
Flask前后端分离项目案例的更多相关文章
- Yii框架和Vue的完美结合完成前后端分离项目
背景说明 本文假设你对Yii和Vue都比较熟悉,至少都在项目里用过,另外笔者新人,以后不定时放一些干货,欢迎程序媛关注 Yii是一个PHP全端框架,典型的mvc的项目结构,后端接口都是一个控制器里放了 ...
- docker-compose 部署 Vue+SpringBoot 前后端分离项目
一.前言 本文将通过docker-compose来部署前端Vue项目到Nginx中,和运行后端SpringBoot项目 服务器基本环境: CentOS7.3 Dokcer MySQL 二.docker ...
- 【Docker】1、 前后端分离项目 下载启动运行
人人开源前后端分离项目下载与配置 文章目录 人人开源前后端分离项目下载与配置 前后端分离框架介绍 后端项目下载与配置 1.renren-fast后台项目介绍 2.开发环境搭建 3.下载后端renren ...
- Aspnet Mvc 前后端分离项目手记(二)关于token认证
在前后端分离的项目中,首先我们要解决的问题就是身份认证 以往的时候,我们使用cookie+session,或者只用cookie来保持会话. 一,先来复习一下cookie和session 首先我们来复习 ...
- 《Spring Boot 入门及前后端分离项目实践》系列介绍
课程计划 课程地址点这里 本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 个部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 项目实践开发 ...
- 《Spring Boot 入门及前后端分离项目实践》目录
开篇词:SpringBoot入门及前后端分离项目实践导读 第02课:快速认识 Spring Boot 技术栈 第03课:开发环境搭建 第04课:快速构建 Spring Boot 应用 第05课:Spr ...
- linux --- 部署前后端分离项目
vue + uwsgi +nginx 部署前后端分离项目 准备项目 1.将前端vue项目包和后端django项目包上传服务器,通过lrzsz,直接从windows拖进linux中 2.解压缩操作 前端 ...
- List多个字段标识过滤 IIS发布.net core mvc web站点 ASP.NET Core 实战:构建带有版本控制的 API 接口 ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js 搭建前后端分离项目 Using AutoFac
List多个字段标识过滤 class Program{ public static void Main(string[] args) { List<T> list = new List& ...
- php前后端分离项目跨域问题解决办法
由于之前一直没有做过前后端分离项目,导致走了不少弯路,而且还采用了一种及其不优雅的方法 (在第一次请求的时候把服务器返回的session id保存起来,后续请求的时候把该session id作为参数传 ...
随机推荐
- MyEclipse中项目利用不同版本JDK编译注意点
一.发现问题本地用jdk.1.6编译,可服务器上市jdk1.5,其他服务器甚至是jdk1.4,不少语法和特性不一样.该服务器的jdk版本一般不现实因为是还有许多其他项目只有将本地安装上jdk1.5. ...
- JavaScript基础函数的配置对象Configuration Objects(020)
配置对象通常用在API库的实现中,当程序中需要编写要多次的模块,也可以采用这种模式.这种模式的好处是接口明确,扩展方便.比如,一个 addPerson在设计的最初需要两个参数作为初始化时人的姓名: f ...
- 《JavaScript高级程序设计》(第二版)
这本书的作者是 Nicholas C.Zakas ,博客地址是 http://www.nczonline.net/ ,大家可以去多关注,雅虎的前端工程师,是YUI的代码贡献者,可想而知这本书得含金量, ...
- 二.drf之使用序列化编写视图
总结:两功能序列化: a.拿到queryset --->idc = Idc.objects.all() b.将queryset给序列化成类---->serializer = IdcSeri ...
- Volatile关键字的解读
原子性 定义: 在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行. Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围 ...
- [Noip2016]蚯蚓 (单调队列)
题干 本题中,我们将用符号[c]表示对c向下取整,例如:[3.0」= [3.1」=[3.9」=3.蛐蛐国最近蚯蚓成灾了!隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓.蛐蛐国 ...
- Cow Relays,过N条边的最短路
题目链接 题意: 找从a到b的经过N条边的最短路 分析: 有点板子...方法:矩阵存,然后有个类似快速幂的思想,然后再加上离散化就好了. 没啥写的,只能说说矩阵了,我用的方法是先枚举i,j再枚举k,当 ...
- URL编码与二次encodeURI
转自:http://foryougeljh.iteye.com/blog/1456706 一般来说,URL只能使用英文字母.阿拉伯数字和某些标点符号,不能使用其他文字和符号.比如,世界上有英文字母的网 ...
- like's photos
wallhaven官网
- 基本数据类型--------------------集合set()
一.作用:集合.list.tuple.dict一样都可以存放多个值,但是集合主要用于:关系运算.去重 # 1.1 关系运算 friends1 = ["zero","kev ...