详解Flask前后端分离项目案例
简介
学习慕课课程,Flask前后端分离API后台接口的实现demo,前端可以接入小程序,暂时已经完成后台API基础架构,使用postman调试.git
重构部分:
- ken校验模块
- auths认证模块
- scope权限模块,增加全局扫描器(参考flaskHTTPExceptions模块)
收获
- 我们可以接受定义时的复杂,但不能接受调用时的复杂
- 如果你觉得写代码厌倦,无聊,那你只是停留在功能的实现上,功能的实现很简单,你要追求的是更好的写法,抽象的艺术,不是机械的劳动而是要创造,要有自己的思考
- 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_DIRS
- static_url_path指定访问静态文件的url地址前缀,类似Django中的STATIC_URL
- template_folder指定模板文件的目录
@property defstatic_url_path(self): """TheURLprefixthatthestaticroutewillbeaccessiblefrom. Ifitwasnotconfiguredduringinit,itisderivedfrom :attr:`static_folder`. """ ifself._static_url_pathisnotNone: returnself._static_url_path ifself.static_folderisnotNone: basename=os.path.basename(self.static_folder) return("/"+basename).rstrip("/") @static_url_path.setter defstatic_url_path(self,value): ifvalueisnotNone: 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)
自定义路由管理器
fromflaskimportFlask app=Flask(__name__) fromwerkzeug.routingimportBaseConverter classRegexUrl(BaseConverter): #指定匹配参数时的正则表达式 #如:#regex='\d{6}' def__init__(self,url_map,regex): """ :paramurl_map:flask会自动传递该参数 :paramregex:自定义的匹配规则 """ super(RegexUrl,self).__init__(url_map) self.regex=regex #在对应的试图函数之前调用 #从url中提取出参数之后,会先调用to_python #会把提取出的值作为参数传递给to_pthon在返回给对应的试图 defto_python(self,value): """可以在这里做一些参数的类型转换""" returnvalue #调用url_for时会被调用,用来处理url反向解析时url参数处理 #返回值用来拼接url defto_url(self,value): """对接收到参数做一些过滤等""" returnvalue #将自定义路由转换器类添加到转换器字典中 app.url_map.converters['re']=RegexUrl #案例 @app.route('/user/') defhello(id): returnf'hello{id}' if__name__=='__main__': app.run(debug=True)
全局异常捕获
AOP编程思想,面向切面编程,把事件统一在一个地方处理,在一个统一的出口做处理
errorhandler在flask1.0版本之前只支持填写对应的错误码,比如@app.errorhandler(404)
在flask1.0版本之后就支持全局的异常捕获了@app.errorhandler(code_or_exception),有了这个之后,就可以在全局做一个异常捕获了,不用每个视图函数都做异常捕获。
@app.errorhandler(Exception) defframework_error(e): ifisinstance(e,APIException): returne elifisinstance(e,HTTPException): code=e.code msg=e.description error_code=1007 returnAPIException(msg,code,error_code) else: ifnotcurrent_app.config['DEBUG']: returnServerError() else: raisee
异常类型
- 可预知的异常(已知异常)
- 完全没有意识的异常(未知异常)
- abort函数
- abort(状态码)是一个默认的抛出异常的方法
- 调用abort函数可以抛出一个指定状态码对应的异常信息
- abort函数会立即终止当前视图函数的运行**
模型对象的序列化
场景:我们有时候可能需要返回模型对象中的某些字段,或者全部字段,平时的做法就是将对象中的各个字段转为字典在返回jsonnify(data),但是这样的写法可能在每个需要返回数据的试图函数中都写一个对应的字典。。对象转字典在返回。json默认是不能序列化对象的,一般我们的做法是json.dumps(obj,default=lambdao:o.__dict__)但是__dict__中只保存实例属性,我们的模型类基本定义的类属性。解决这个问题就要看jsonify中是如何做序列化的,然后怎么重写。
重写JSONEncoder
fromdatetimeimportdate fromflaskimportFlaskas_Flask fromflask.jsonimportJSONEncoderas_JSONEncoder classJSONEncoder(_JSONEncoder): """ 重写json序列化,使得模型类的可序列化 """ defdefault(self,o): ifhasattr(o,'keys')andhasattr(o,'__getitem__'): returndict(o) ifisinstance(o,date): returno.strftime('%Y-%m-%d') super(JSONEncoder,self).default(o) #需要将重写的类绑定到应用程序中 classFlask(_Flask): json_encoder=JSONEncoder
模型类的定义
classUser(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)) defkeys(self): return['id','email','nickname','auth'] def__getitem__(self,item): returngetattr(self,item)
注意:修改了json_encode方法后,只要调用到flask.json模块的都会走这个方法
为什么要写keys和__getitem__方法
当我们使用dict(object)操作一个对象的时候,dict首先会到实例中找keys的方法,将其返回列表的值作为key,然后会根据object[key]获取对应的值,所以实例要实现__getitem__方法才可以使用中括号的方式调用属性
进阶写法-控制返回的字段
场景:当我们有一个Book的模型类,我们的api接口可能需要返回book的详情页所以就要返回所有字典,但另外一个接口可能只需要返回某几个字段。
classBook(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'] defkeys(self): returnself.fieldsifhasattr(self,'fields')else[] defhide(self,*keys): forkeyinkeys: self.fields.remove(key) returnself defappend(self,*keys): forkeyinkeys: self.fields.append(key) returnself @api.route('/search') defsearch(): books=Book.query.filter().all()#根据某些条件搜索的 books=[book.hide('summary')forbookinbooks] returnjsonify(books) @api,route('//detail') defdetail(isbn): book=Book.query.filter_by(isbn=isbn).first_or_404() returnjsonify(book)
请求钩子函数
- before_first_request:在处理第一个请求前运行。
- before_request:在每次请求前运行。
- after_request:如果没有未处理的异常抛出,在每次请求后运行。
- teardown_request:在每次请求后运行,即使有未处理的异常抛出。
全局扫描器
模仿flaskexceptions预加载各个异常类的方式,将用户组自动加载进内存中,这样获取的话就更方便
str2obj={} level2str={} defiteritems(d,*args,**kwargs): returniter(d.items(*args,**kwargs)) def_find_scope_group(): for_name,objiniteritems(globals()): try: is_scope_obj=issubclass(obj,BaseScope) exceptTypeError: is_scope_obj=False ifnotis_scope_objorobj.level<1: continue old_obj=str2obj.get(_name,None) ifold_objisnotNoneandissubclass(obj,old_obj): continue str2obj[_name]=obj level2str[obj.level]=_name #模仿flaskexceptions预加载各个异常类的方式,将用户组自动加载进内存 _find_scope_group() del_find_scope_group
常见bug
form正则校验注意事项
r'{6,25}$'
带空格和不带空格是两码事,正则里面{,}连续不带空格
r'{6,25}$'
参考
PythonFlask高级编程之RESTFulAPI前后端分离精讲
到此这篇关于详解Flask前后端分离项目案例的文章就介绍到这了,更多相关Flask前后端分离内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!