REST API

前后端分离的最佳实践,是开发的一套标准或者是一套规范

  1. 每一个 URI 代表一种资源
  2. 客户端和服务器之间,传递这种资源的某种表现层
  3. 客户端通过四个 HTTP 动词(GET,POST,PUT,DELETE)对服务器端资源进行操作,实现 表现层状态转化

Restful基础

安装

pip install flask-restful

使用

ext/__init__.py

from flask_sqlalchemy import SQLAlchemy
from flask_restful import Api

db = SQLAlchemy()
# 1.创建 api 对象
api = Api()

apps/__init__.py

# 2.绑定 app
api.init_app(app=app)

view.py

from flask import Blueprint, request, render_template, flash, redirect, url_for
from flask_restful import Resource
from ext import api

user_bp = Blueprint('user', __name__, url_prefix='/api')

# 3.定义类视图
class UserResource(Resource):
    def get(self):
        return {'msg': '------------>get'}

    def post(self):
        pass

    def put(self):
        pass

    def delete(self):
        pass

# 4.绑定类视图,user为路由
api.add_resource(UserResource,'/user')

# url_map
Map([<Rule '/user' (HEAD, PUT, OPTIONS, GET, DELETE, POST) -> userresource>,
 <Rule '/static/<filename>' (OPTIONS, GET, HEAD) -> static>])

参数传递

数据格式化

view.py

# 格式化输出需要提前规范格式,字段同数据库,可少不可多
user_fields = {
    'id': fields.Integer,
    'username': fields.String,
    'password': fields.String,
    'udatetime': fields.DateTime
}


class UserResource(Resource):
    @marshal_with(user_fields)  # 将 user 按照 user_fields 的格式转化成一个序列化对象
    def get(self):
        user = User.query.all()
        return user  # user不是 str,list,int,不能直接返回,需要使用 @marshal——with

    def post(self):
        pass

    def put(self):
        pass

    def delete(self):
        pass

image-20210220212925974

endpoint
api.add_resource(UserResource, '/user', endpoint='alluser')
api.add_resource(UserSimpleResource, '/user/<int:uid>')

# url_map
Map([<Rule '/user' (DELETE, GET, PUT, POST, HEAD, OPTIONS) -> alluser>,
 <Rule '/static/<filename>' (GET, OPTIONS, HEAD) -> static>,
 <Rule '/user/<uid>' (DELETE, GET, PUT, POST, HEAD, OPTIONS) -> usersimpleresource>])
格式化输入RequestParser

对传输参数进行解析限制,类似于 wtform 提供的功能

# 参数解析
parser = reqparse.RequestParser()  # 创建解析对象
# location 限制只能通过 form 提交
parser.add_argument('username', type=str, required=True, help='必须输入用户名', location=['form'])
parser.add_argument('password', type=inputs.regex(r'^\d{6,12}$'), required=True, help='必须输入密码', location=['form'])
parser.add_argument('hobby', action='append')  # ['篮球','足球','游戏']
parser.add_argument('icon', type=FileStorage, location=['files'])
# required = True 代表为必填,location 代表只能通过 post 传参,help 为提示信息
# 上传文件,postman 需要使用 form-data 字段

......
class UserResource(Resource):
    @marshal_with(user_fields)  # 将 user 按照 user_fields 的格式转化成一个序列化对象
    def get(self):
        user = User.query.all()
        return user  # user不是 str,list,int,不能直接返回,需要使用 @marshal——with

    def post(self):
        args = parser.parse_args()
        username = args.get('username')
        password = args.get('password')
        boddy = args.get('hobby')
        icon = args.get('icon')
        print(icon)
        if icon:
            save_path = os.path.join(Config.UPLOAD_ICON_DIR, icon.filename)
            icon.save(save_path)
......
数据输出

Output Fields

# 格式化输出需要提前规范格式
user_fields = {
    'id': fields.Integer,
    'uid': fields.String(attribute='username', default='anony'),
    'password': fields.String,
    'udatetime': fields.DateTime
}
# 前端能看到的是:id,username,password,udatetime,如果不希望前端看到真是的字段名,可以使用 attribute = 模型中的字段名,前边的名字可以随意取了

自定义 Fields

class IsDelete(fields.Raw):
    def format(self, value):
        print('---------------------',value)
        return 'yes' if value else 'no'

# 格式化输出需要提前规范格式
user_fields = {
    'isdel':IsDelete(attribute='isdelete'),
}

# postman输出
[
    {
        "id": 1,
        "uid": "xiaoli",
        "password": "111111",
        "isdel": "no",
        "udatetime": "Sat, 20 Feb 2021 21:18:42 -0000"
    },
    {
        "id": 2,
        "uid": "xiaoming",
        "password": "1234565",
        "isdel": "yes",
        "udatetime": "Sat, 20 Feb 2021 21:19:46 -0000"
    }
]
  1. 必须继承自 Raw

  2. 重写方法:

    def format(self):

    ​ return 结果

URL

适用于有一个列表,点击某一项获取详情页面

定义两个 user_fields

user_fields_1 = {
    'id': fields.Integer,
    'url': fields.Url('singleuser', absolute=False)
}

user_fields = {
    'id': fields.Integer,
    'uid': fields.String(attribute='username', default='anony'),
    'password': fields.String,
    'isdel': IsDelete(attribute='isdelete'),
    'udatetime': fields.DateTime
}
.....

class UserResource(Resource):
    @marshal_with(user_fields_1) 
    def get(self):
        user = User.query.all()
        return user 

    def post(self):
        args = parser.parse_args()
        username = args.get('username')
        password = args.get('password')
        boddy = args.get('hobby')
        icon = args.get('icon')
        print(icon)
        if icon:
            save_path = os.path.join(Config.UPLOAD_ICON_DIR, icon.filename)
            icon.save(save_path)

    def put(self):
        pass

    def delete(self):
        pass


class UserSimpleResource(Resource):
    @marshal_with(user_fields)
    def get(self, id):
        user = User.query.get(id)
        return user

    def post(self, id):
        pass

    def put(self, id):
        pass

    def delete(self, id):
        pass


api.add_resource(UserResource, '/user', endpoint='alluser')
api.add_resource(UserSimpleResource, '/user/<int:id>', endpoint='singleuser')

复杂结构输出

json格式化:https://www.bejson.com/

套叠结构

第一种方法:marshal()

class UserFriendsResource(Resource):
    # 不需要装饰器
    def get(self, id):
        friends_list = Friends.query.filter(Friends.uid == id).all()
        user = User.query.get(id)

        friends = []
        for friend in friends_list:
            friends.append(User.query.get(friend.id))

        data = {
            'username': user.username,
            'nums': len(friends_list),
            'friends': marshal(friends_list, user_fields)
            # 把 friends_list 以 user_fields 定义的格式套叠输出
        }
        return data
    
api.add_resource(UserFriendsResource, '/friend/<int:id>')    

第二种方式:marshal_with()

user_friends_fields = {
    'username': fields.String,
    'nums': fields.Integer,
    'friends': fields.List(fields.Nested(user_fields))
    # 把 friends_list 以 user_fields 定义的格式套叠列表输出
}

class UserFriendsResource(Resource):
    @marshal_with(user_friends_fields)
    def get(self, id):
        friends_list = Friends.query.filter(Friends.uid == id).all()
        user = User.query.get(id)

        friends = []
        for friend in friends_list:
            friends.append(User.query.get(friend.id))

        data = {
            'username': user.username,
            'nums': len(friends_list),
            'friends': friends_list
        }
        return data

api.add_resource(UserFriendsResource, '/friend/<int:id>')

两种方式实现的效果相同

注意:data 必须是符合 json 格式

实例

模型继承

基模型

# apps/models/__init__.py
from datetime import datetime
from exts import db

class BaseModel(db.Model):
    __abstract__ = True
    # 作为抽象类,只能被继承,不能单独使用
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    datetime = db.Column(db.DateTime, default=datetime.now)

子模型

# apps/models/user_models.py
class NewsType(BaseModel):
    __tablename__ = 'news_type'
    type_name = db.Column(db.String(20), nullable=False)
API结合蓝图
from flask import Blueprint
from flask_restful import Api, marshal_with, fields, Resource
from apps.models.user_models import News, NewsType

news_bp = Blueprint('news', __name__)
api = Api(news_bp)
# 继承蓝图

types_fields = {
    'id': fields.Integer,
    'name': fields.String(attribute='type_name')
}

class NewsSearchResource(Resource):
    @marshal_with(types_fields)
    # 特别注意 括号里没有引号
    def get(self):
        news_list = NewsType.query.all()
        return news_list

api.add_resource(NewsSearchResource, '/news')
跨域

出现此报错即存在跨域问题

clipboard.png

JavaScript 同源策略,只有 协议+主机名+端口 相同才允许访问,也就是 JS 子能访问和操作自己域下的资源,不能访问和操作其他域下的资源,跨域问题针对 JS 和 Ajax,html 本身没有跨域问题

跨域问题解决

  1. 响应头添加 Header 允许访问

    response = make_response()
    response.headers['Access-Control-Allow-Origin']='*'
    
    response.headers['Access-Control-Allow-Methods']='GET,POST'
    
    response.headers['Access-Control-Allow-Headers']='x-request-with,Content-type'
    
  2. jsonp 只支持 get 请求不支持 post 请求

  3. HttpClient 内部转发

  4. 使用接口网关–nginx、springcloud zuul

第三方扩展flask-cors

  1. 安装

    pip install flask-cors
    
  2. 引入插件

    # exts/__init__.py
    from flask_cors import CORS
    cors = CORS()
    
    # apps/__init__. py
    def create_app():
        app = Flask(__name__, template_folder='../')
        cors.init_app(app=app, supprots_credentials=True)
        ......
    
jQuery 动态创建
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
</head>
<body>
<div id="container"></div>
</body>
<script>
    $(function () {
        url = 'http://127.0.0.1:5000/news';
        $.get(url, function (data) {
            for(i=0;i<=data.length;i++){
                $('#container').append('<p>'+data[i].name+'</p>')
            }
        });
    });
</script>
</html>

arset=“UTF-8”>
index

```
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐