目录
flask 安装插件
alembic==1.6.2
aniso8601==9.0.1
click==8.0.0
Flask==1.1.2
Flask-Cors==3.0.10
Flask-JWT-Extended==4.2.1
Flask-Migrate==2.5.0
Flask-RESTful==0.3.8
Flask-Script==2.0.6
Flask-SQLAlchemy==2.5.1
greenlet==1.1.0
importlib-metadata==4.0.1
itsdangerous==2.0.0
Jinja2==3.0.0
Mako==1.1.4
MarkupSafe==2.0.0
mysqlclient==2.0.3
PyJWT==2.1.0
pypinyin==0.41.0
python-dateutil==2.8.1
python-editor==1.0.4
pytz==2021.1
six==1.16.0
SQLAlchemy==1.4.15
typing-extensions==3.10.0.0
Werkzeug==2.0.0
zipp==3.4.1
项目结构
flask本身并不提供项目结构,本身只是提供一个最基本的APP。因此,在项目开发时,需要先自己搭建项目:
可以按照功能模块划分为APP,当然,flask中称为蓝图,类似
django
,如下所示可以按照
MVC
模式划分,即所有的模型类在一起,所有的逻辑处理代码在一起,最后统一配置路由即可。
flaskProject
├── app
│ ├── __init__.py # 创建APP,挂载所有配置、第三方插件、路由
│ ├── extensions.py # 配置第三方插件
│ └── settings.py # 项目基本配置
├── goods # 蓝图,类似django的app
│ ├── __init__.py
│ ├── models.py # 创建模型类
│ ├── ser.py # 指定模型的序列化字段
│ ├── urls.py # 对每个视图配置路由
│ └── views.py # 视图处理
├── manage.py # 项目的管理文件
└── sqlite.db # 数据库
基本组件
配置文件
app/settings.py
import datetime
import os
class Config(object):
DEBUG = True # 调试模式
SQLALCHEMY_DATABASE_URI = 'mysql://root:mysql@127.0.0.1:3306/day08' # 数据库的地址
SQLALCHEMY_TRACK_MODIFICATIONS = False # 默认不追踪 mysql修改的信号量
SECRET_KEY = b'\xe6O$\xe8\t\xa5\x7f\xf4\x01#7K\x03\x91\x94d' # 指明加密的密钥
# 构建项目所在的 绝对路径,也就是 day08 的绝对路径
BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
# 静态资源存放路径
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
# 自定义的 图片上传路径
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(days=1) # 指明token的过期时间
# JWT_HEADER_TYPE = 'JWT' # 可以修改 请求头中 ,token字符串的 前缀
# 默认 Authorization: Bearer <token>
# 修改: Authorization: JWT <token>
第三方插件
flask项目中,需要使用
flask_sqlalchemy
数据库管理工具、flask_migrate
模型类迁移工具
app/extensions.py
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from flask_jwt_extended import JWTManager
# import pymysql
# pymysql.install_as_MySQLdb()
# 使用mysql数据库时,要么使用pymysql,然后这么配置
# 要么直接使用 mysqlclient
db = SQLAlchemy() # 创建db对象,方便后期模型类的创建
migrate = Migrate() # 创建迁移对象
cors = CORS()
jwt = JWTManager()
def config_extensions(app):
"""定义方法,在app初始化时,传入app参数,挂载到app上"""
db.init_app(app)
migrate.init_app(app,db=db)
cors.init_app(app)
jwt.init_app(app)
创建APP
一个 Flask 应用是一个
Flask
类的实例。应用的所有东西(例如配置 和 URL )都会和这个实例一起注册。创建一个 Flask 应用最粗暴直接的方法是在代码的最开始创建一个全局
Flask
实例。有的情况下这 样做是简单和有效的,但是当项目越来越大的时候就会有些力不从心了。可以在一个函数内部创建
Flask
实例来代替创建全局实例。这个函数被 称为 应用工厂 。所有应用相关的配置、注册和其他设置都会在函数内部完成, 然后返回这个应用。
app/__init__.py
from flask import Flask
from app.settings import config
from extensions import config_extensions
# 后期创建完模型类,导入到当前文件,让 app管理
# from goods.models import *
# 后期对视图配置路由之后,导入路由
# from goods.urls import *
def create_app(config_name=None):
app = Flask(__name__)
# 导入配置文件
app.config.from_object(config)
# 初始化工具(sqlalchemy,migrate)
config_extensions(app)
return app
管理项目
如果是单独的app文件,直接运行即可。当项目庞大时,可以借助
flask_script
来管理项目
manage.py
import os
from flask_migrate import MigrateCommand
from flask_script import Manager, Server
from app import create_app
config_name = os.getenv('FLASK_CONFIG' , 'default')
app = create_app(config_name)
manage = Manager(app)
manage.add_command('runserver', Server(host='localhost', port=5000))
manage.add_command('db', MigrateCommand)
if __name__ == '__main__':
manage.run()
工具集
自定义函数,将图片保存到本地
import os
from uuid import uuid4
from flask import current_app # current_app 属于应用上下文,代表项目中的app本身
def img_upload(img):
if not img:
return None
# 将图片名按照 . 进行切分, 找到最后一个元素,也就是 文件的后缀名
end_name = img.filename.rsplit('.')[-1]
# 通过文件的后缀名判断 身份为 合法的 图片
if end_name not in ['jpg', 'png', 'gif', 'jpeg']:
return None
# 将 图片对象 存入 本地,然后将 路径 存入 数据库
MEDIA = current_app.config['MEDIA_ROOT'] # 从app的配置项,取出 MEDIA的路径
filename = str(uuid4()) + '.' + end_name # 为了生成一个不重复的 文件名
img_path = os.path.join(MEDIA, filename) # 将路径和文件名拼接在一起,方便保存文件
img.save(img_path) # 将图片对象保存到 本地
return filename
子应用
在项目中创建一个包:
school
school/__init__.py
: 创建蓝图对象, 创建完成,需要 注册蓝图
from flask.blueprints import Blueprint
school_bp = Blueprint('school', __name__)
# 为了让蓝图,管理你的 模型类和视图,因此需要导入
from .models import *
from .views import *
模型类
from app.extensions import db
class Cate(db.Model):
__tablename__ = 'tb_cate'
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.String(10))
# 外键,关联自身的主键
parent_id = db.Column(db.Integer, db.ForeignKey('tb_cate.id'), nullable=True)
# 指明关联字段,和实质的表无关系,只是在查询时,实现关联查询
parent = db.relationship('Cate', remote_side=[id], backref="sub")
def __repr__(self):
return 'Cate: %s' % self.name
class Goods(db.Model):
__tablename__ = 'tb_goods'
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.String(50))
price = db.Column(db.DECIMAL(10, 2))
stock = db.Column(db.Integer, default=0)
sales = db.Column(db.Integer, default=0)
# 外键,关联分类表的主键
cate_id = db.Column(db.Integer, db.ForeignKey('tb_cate.id'))
# 指明关联字段,和实质的表无关系,只是在查询时,实现关联查询
cate = db.relationship('Cate', backref='goods')
def __repr__(self):
return 'Goods: %s' % self.name
命令
在项目的终端可以使用如下命令,操作项目
python manage.py runserver # 启动flask 服务
python manage.py db init # 只有第一次迁移时,才会执行 该命令, 执行完,会生成迁移文件
python manage.py db migrate # 生成迁移文件,出路第一次之外,任何修改模型类,都需要 执行该命令, 生成迁移文件
python manage.py db upgrade # 根据迁移文件,生成表
序列化字段
在序列化字段中,嵌套 列表、字典,不能直接嵌套,需要使用
List
、Nested
goods/ser.py
from flask_restful import fields
cate_fields = {
'id': fields.Integer,
'name': fields.String,
'parent': fields.String(attribute='parent.name'),
}
cate2_fields = {
'id': fields.Integer,
'name': fields.String,
'sub': fields.List(fields.Nested(cate_fields))
}
cate1_fields = {
'id': fields.Integer,
'name': fields.String,
'sub': fields.List(fields.Nested(cate2_fields))
}
goods_fields = {
'id': fields.Integer,
'name': fields.String,
'stock': fields.Integer,
'sales': fields.Integer,
'cate': fields.Nested({
'id': fields.Integer,
'name': fields.String
}),
}
goods_page_fields = {
'data': fields.Nested(goods_fields),
'total': fields.Integer,
'pages': fields.Integer
}
视图
goods/views.py
列表、增加
from flask_restful import Resource, reqparse, marshal_with
from .models import *
from .ser import *
class CateView(Resource):
parser = reqparse.RequestParser()
@marshal_with(cate_fields)
def post(self):
# 1. 解析校验参数
self.parser.add_argument('name', type=str, required=True, location=['form', 'json'])
self.parser.add_argument('parent_id', type=int, location=['form', 'json'])
# 2. 获取参数
args = self.parser.parse_args()
# 2.1 校验父类是否存在
parent_id = args.get('parent_id')
if parent_id:
Cate.query.get_or_404(parent_id)
# 3. 添加数据
cate = Cate(**args)
# 4. 添加事务
db.session.add(cate)
# 5. 提交事务
db.session.commit()
# 6. 按照序列化字段解析分类对象,并返回响应
return cate, 201
# 实现三级分类解析
@marshal_with(cate1_fields)
def get(self):
cates = Cate.query.all()
return cates, 200
详情、删除、修改
from flask_restful import Resource, reqparse, marshal_with
from .models import *
from .ser import *
class CateDetailView(Resource):
parser = reqparse.RequestParser()
@marshal_with(cate_fields)
def get(self, pk):
cate = Cate.query.get_or_404(pk)
return cate, 200
def delete(self, pk):
cate = Cate.query.get_or_404(pk)
db.session.delete(cate)
db.session.commit()
return None, 204
@marshal_with(cate_fields)
def put(self, pk):
cate = Cate.query.get_or_404(pk)
self.parser.add_argument('name', type=str, location=['form', 'json'], default=cate.name)
self.parser.add_argument('parent_id', type=int, location=['form', 'json'], default=cate.parent_id)
name = self.parser.parse_args().get('name')
parent_id = self.parser.parse_args().get('parent_id')
cate.name = name
cate.parent_id = parent_id
db.session.commit()
return cate, 201
列表分页、增加
from flask_restful import Resource, reqparse, marshal_with
from .models import *
from .ser import *
class GoodsView(Resource):
parser = reqparse.RequestParser()
@marshal_with(goods_fields)
def post(self):
self.parser.add_argument('name', type=str, location=['form', 'json'], required=True)
self.parser.add_argument('price', type=float, location=['form', 'json'], required=True)
self.parser.add_argument('stock', type=int, location=['form', 'json'], default=1000)
self.parser.add_argument('sales', type=int, location=['form', 'json'], default=0)
self.parser.add_argument('cate_id', type=int, location=['form', 'json'], required=True)
args = self.parser.parse_args()
goods = Goods(**args)
db.session.add(goods)
db.session.commit()
return goods, 201
@marshal_with(goods_page_fields)
def get(self):
self.parser.add_argument('cate_id', type=int, location='args')
self.parser.add_argument('page', type=int, default=1, location='args')
self.parser.add_argument('size', type=int, default=2, location='args')
args = self.parser.parse_args()
cate_id = args.get('cate_id')
page = args.get('page')
size = args.get('size')
pagination = Goods.query.filter_by(cate_id=cate_id).paginate(page, size, error_out=False) # 分页实现
# 注意:返回的数据结构需要和序列化字段结构一样
return {
"data": pagination.items, # 当前页的数据
'total': pagination.total, # 总的记录数
'pages': pagination.pages # 分页后的总页码
}, 200
列表推导式
from flask_restful import Resource, reqparse
from sqlalchemy import desc
from .models import *
class StuView(Resource):
def get(self):
# 1. 创建解析参数的对象
parser = reqparse.RequestParser()
# 2. 指明需要解析的参数
parser.add_argument('page', type=int, location='args', default=1, help='页码不合法')
# 3. 获取参数, 得到 字典 { 参数名: 参数值}
args = parser.parse_args()
# 4. 从字典中取出具体的 参数
page = args.get('page')
# 5. 先查询,后排序,再分页, 得到分页器对象
pagination = Stu.query.order_by(desc('addtime')).paginate(page=page, per_page=2)
# 6. 为了解析时间格式,还是 手动解析
stus = [
{
'id': stu.id,
'name': stu.name,
'gender': '男' if stu.gender else '女',
'age': stu.age,
'state': stu.state,
'cls': stu.cls.name,
'add_time': stu.addtime.strftime('%Y-%m-%d %H:%M:%S')
}
for stu in pagination.items
]
# 7. 返回响应数据
return {
'stus': stus,
'pages': pagination.pages
}
详情、删除、修改
from flask_restful import Resource, reqparse, marshal_with
from .models import *
from .ser import *
class GoodsDetailView(Resource):
parser = reqparse.RequestParser()
@marshal_with(goods_fields)
def get(self, pk):
goods = Goods.query.get_or_404(pk)
return goods, 200
def delete(self, pk):
goods = Goods.query.get_or_404(pk)
db.session.delete(goods)
db.session.commit()
return None, 204
@marshal_with(goods_fields)
def put(self, pk):
goods = Goods.query.get_or_404(pk)
self.parser.add_argument('name', type=str, location=['form', 'json'], default=goods.name)
self.parser.add_argument('price', type=float, location=['form', 'json'], default=goods.price)
self.parser.add_argument('stock', type=int, location=['form', 'json'], default=goods.stock)
self.parser.add_argument('sales', type=int, location=['form', 'json'], default=goods.sales)
self.parser.add_argument('cate_id', type=int, location=['form', 'json'], default=goods.cate_id)
name = self.parser.parse_args().get('name')
price = self.parser.parse_args().get('price')
stock = self.parser.parse_args().get('stock')
sales = self.parser.parse_args().get('sales')
cate_id = self.parser.parse_args().get('cate_id')
cate = Cate.query.get_or_404(cate_id)
goods.name = name
goods.cate_id = cate_id
goods.price = price
goods.stock = stock
goods.sales = sales
db.session.commit()
return goods, 201
分类转移
from flask_restful import Resource, reqparse, marshal_with
from .models import *
from .ser import *
class GoodsCateChangeView(Resource):
parser = reqparse.RequestParser()
@marshal_with(goods_fields)
def put(self, pk):
self.parser.add_argument('cate_id', type=int, required=True, location=['form', 'json'])
cate_id = self.parser.parse_args().get('cate_id')
cate = Cate.query.get_or_404(cate_id)
goods = Goods.query.get_or_404(pk)
goods.cate_id = cate.id
db.session.commit()
return goods, 201
模糊查询
class SearchView(Resource):
def get(self):
# 1. 创建解析参数的对象
parser = reqparse.RequestParser()
# 2. 指明需要解析的参数
parser.add_argument('text', type=str, location='args', help='关键字不合法')
# 3. 获取参数, 得到 字典 { 参数名: 参数值}
args = parser.parse_args()
# 4. 从字典中取出具体的 参数
text = args.get('text')
# 5. 根据关键字模糊查询, 得到学生的 查询集
stus = Stu.query.join(Cls, Cls.id == Stu.cls_id). \
filter(or_(
Cls.name.like('%{}%'.format(text)),
Stu.name.like('%{}%'.format(text))
)).all()
# 6. 循环解析
data = [
{
'id': stu.id,
'name': stu.name,
'gender': '男' if stu.gender else '女',
'age': stu.age,
'state': stu.state,
'cls': stu.cls.name,
'add_time': stu.addtime.strftime('%Y-%m-%d %H:%M:%S')
}
for stu in stus
]
return {
'stus': data
}
filter
比较
filter
中支持 python中的所有比较运算符:==
、!=
、>
、<
、>=
、<=
from flask_restful import Resource, reqparse, marshal_with
from .models import Goods
from .fields import goods_fields
class GoodsView(Resource):
@marshal_with(goods_fields)
def get(self):
# 1. 客户端传递 参数,
parser = reqparse.RequestParser()
parser.add_argument('sales', type=int, location=['args'], default=0)
sales = parser.parse_args().get('sales')
# 2. 查询销量大于该参数的所有商品
goods = Goods.query.filter(Goods.sales > sales).all()
return goods
注意: 过滤完成之后,还需要调用 all()
获取 查询的 结果
逻辑
from flask_restful import Resource, reqparse, marshal_with
from .models import Goods
from .fields import goods_fields
from sqlalchemy import or_, and_, not_
class GoodsView(Resource):
@marshal_with(goods_fields)
def get(self):
# 1. 客户端传递 参数,
parser = reqparse.RequestParser()
parser.add_argument('sales', type=int, location=['args'], default=0)
parser.add_argument('stock', type=int, location=['args'], default=0)
sales = parser.parse_args().get('sales')
stock = parser.parse_args().get('stock')
# 2. 查询销量大于 某个数 或 库存小于 某个数 的商品信息
goods = Goods.query.filter(or_(Goods.sales > sales, Goods.stock < stock)).all()
return goods
from flask_restful import Resource, reqparse, marshal_with
from .models import Goods
from .fields import goods_fields
from sqlalchemy import or_, and_, not_
class GoodsView(Resource):
@marshal_with(goods_fields)
def get(self):
# 1. 客户端传递 参数
parser = reqparse.RequestParser()
parser.add_argument('max_price', type=float, location=['args'])
parser.add_argument('min_price', type=float, location=['args'])
max_price = parser.parse_args().get('max_price')
min_price = parser.parse_args().get('min_price')
if (max_price is None) and (min_price is None):
# 上限 和 下限 都没有
goods = Goods.query.all()
elif (max_price is None) and (min_price is not None):
# 有 下限 ,没有 上限
goods = Goods.query.filter(Goods.price >= min_price).all()
elif (max_price is not None) and (min_price is None):
# 有 上限 ,没有 下限
goods = Goods.query.filter(Goods.price <= max_price).all()
else:
# 查询 价格在某个区间的商品信息
goods = Goods.query.filter(and_(Goods.price >= min_price, Goods.price <= max_price)).all()
return goods
模型类.query.filter(模型类.字段 > 值).all()
: 比较查询,支持所有比较运算符
模型类.query.filter(or_(条件1, 条件2)).all()
: 满足任何一个条件即可
模型类.query.filter(and_(条件1, 条件2)).all()
: 满足全部条件即可
模型类.qery.filter(模型类.字段.in_([1, 2, 3, 4]))
: 查询某个字段是否 在某个 集合中,用来实现 批量操作
判断
# 查询库存不为空 的商品信息
goods = Goods.query.filter(not_(Goods.stock.is_(None))).all()
goods = Goods.query.filter(~(Goods.stock.is_(None))).all()
# id在某个区间的商品信息
goods = Goods.query.filter(Goods.id.in_([1, 3, 6, 7])).all()
图片
添加图片
from flask import current_app, make_response
from sqlalchemy import desc
from werkzeug.datastructures import FileStorage
from flask_restful import Resource, reqparse, marshal_with
from .models import *
from utils.img import img_upload
import os
class NewsView(Resource):
def post(self):
# 1. 创建解析参数的对象
parser = reqparse.RequestParser()
# 2. 指明需要解析的参数
parser.add_argument('title', type=str, location='form', required=True)
parser.add_argument('content', type=str, location='form')
parser.add_argument('count', type=int, location='form', default=0)
parser.add_argument('cate_id', type=int, location='form', required=True)
parser.add_argument('img', type=FileStorage, location='files')
# 3. 获取具体的参数
args = parser.parse_args()
title = args.get('title')
content = args.get('content')
count = args.get('count')
cate_id = args.get('cate_id')
img = args.get('img')
# 利用自定义函数,将图片保存到本地
filename = img_upload(img)
# 4. 创建对象, 注意:图片存储的只是 从media之后的 图片路径
news = News(title=title, content=content, count=count, img=filename, cate_id=cate_id)
# 5. 添加到 事务中
db.session.add(news)
# 6. 提交事务
try:
db.session.commit()
except:
return {
'msg': '添加失败'
}, 500
# 7. 返回响应
return {
'id': news.id,
'title': news.title,
'img': news.img
}, 201
图片展示
图片展示,实际就是 将 图片内容读取为文件流,做为响应返回
http://127.0.0.1:5000/media/filename
方案一:
class ImgView(Resource):
def get(self, filename):
# 1. 从配置项中读取 media目录的 路径
MEDIA = current_app.config['MEDIA_ROOT']
# 2. 拼接除图片的完成路径
img_path = os.path.join(MEDIA, filename)
# 3. 按照二进制方式打开文件,读到的内容为 二进制文件流,方便接下来的网络传输
try:
with open(img_path, 'rb') as f:
img = f.read()
except FileNotFoundError:
return None, 404
# 4. 自定义响应
resp = make_response(img)
# 5. 声明响应体的类型 为 图片
resp.headers['Content-Type'] = 'image/png'
# 6. 返回响应
return resp
方案二:
class ImgView(Resource):
def get(self, filename):
return send_from_directory(Config.MEDIA_ROOT, filename)
用户
注册
from flask_restful import Resource, reqparse
from .models import *
from werkzeug.security import generate_password_hash, check_password_hash
from flask_jwt_extended import create_access_token,jwt_required
import re
class RegisterView(Resource):
def post(self):
# 1. 创建解析参数对象
parser = reqparse.RequestParser()
# 2. 指明需要解析的参数
parser.add_argument('username', type=str, location=['form', 'json'], required=True, help='输入合法的用户名')
parser.add_argument('password', type=str, location=['form', 'json'], required=True, help='输入安全的密码')
# 3. 获取解析后的参数
username = parser.parse_args().get('username')
password = parser.parse_args().get('password')
# 3.1 校验 用户名规则: 字母、数字、_组成, 字母开头,6-20位
if not re.match(r'^[a-zA-Z]\w{5,19}$', username):
return {
'msg': '用户名不合法'
}, 400
# 3.2 校验用户名是否重复
if User.query.filter_by(username=username).count():
return {
'msg': '该用户名已存在'
}
# 3.3 校验 密码规则: 必须存在 数字、 大写字母、小写字母, 8-20位
if not re.match(r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,20}$', password):
return {
'msg': '密码不安全'
}, 400
# 4. 对密码进行加密
new_password = generate_password_hash(password)
# 5. 创建用户对象
user = User(username=username, password=new_password)
# 6. 将用户对象添加到 事务中
db.session.add(user)
# 7. 提交事务
db.session.commit()
# 8. 注册成功
return {
'msg': '用户注册成功'
}
登录
登录成功,需要 生成 jwt字符串
在 flask扩展项中,挂载
flask_jwt_extend
插件在 项目的 配置中,指明
SECRET_KEY
或者JWT_SECRET_KEY
, 密钥可以使用os.urandom(n)
, 指明生成固定长度的密钥在 项目的 配置中,指明
JWT_ACCESS_TOKEN_EXPIRES
, 也就是 token的过期时间
class LoginView(Resource):
def post(self):
# 1. 创建解析参数对象
parser = reqparse.RequestParser()
# 2. 指明需要解析的参数
parser.add_argument('username', type=str, location=['form', 'json'], required=True, help='输入合法的用户名')
parser.add_argument('password', type=str, location=['form', 'json'], required=True, help='输入安全的密码')
# 3. 获取解析后的参数
username = parser.parse_args().get('username')
password = parser.parse_args().get('password')
# 4. 需要从数据库中查询 得到 用户对象
user = User.query.filter_by(username=username).first()
# 5. 如果用户不存在,返回错误信息
if not user:
return {
'msg': '用户名或密码错误'
}, 401
# 6. 校验 密码
if not check_password_hash(user.password, password):
return {
'msg': '用户名或密码错误'
}, 401
# 7. 生成token
token = create_access_token(identity={'id': user.id, 'username': user.username})
# 8. 返回token
return {
'token': token,
'usernamne': user.username
}
用户中心
通过 请求头中 的 token
进行身份认证, 使用 默认提供的 装饰器进行身份认证
from flask_restful import Resource, reqparse
from flask_jwt_extended import jwt_required, get_jwt_identity
class UserInfoView(Resource):
method_decorators = [jwt_required()]
def get(self):
# 1. 获取token中的 载荷信息, 也就是 上一步登录成功。生成token时,存入 token的信息
user = get_jwt_identity()
return user
路由
goods/urls.py
from app.extensions import api
from .views import *
api.add_resource(CateView, '/cate/')
api.add_resource(CateDetailView, '/cate/<int:pk>/')
api.add_resource(GoodsView, '/goods/'),
api.add_resource(GoodsDetailView, '/goods/<int:pk>/'),
api.add_resource(GoodsCateChangeView, '/goods/<int:pk>/cate/'),
api.add_resource(ImgView, '/media/<string:filename>')
Vue
axios请求拦截器
登录成功,Vue保存 token,但是下次请求时,需要在请求头中携带 token
可以在每次请求时,指明token
可以在
main.js
配置 axios请求拦截器,全局配置一次即可
axios.defaults.baseURL = 'http://127.0.0.1:5000/'
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么, 在发送请求之前,从本地读取 token,放到 请求头中
config.headers.Authorization = 'JWT ' + sessionStorage.getItem('token')
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
Vue.prototype.$axios = axios