flask:实现后端接口的封装和开发部分
HTTP 请求方法和响应
对照后面的代码案例,我们看看 Flask-Restful 是如何去发起请求并处理响应的。
from flask import Flask, request
from flask_restful import Api, Resourceapp = Flask(__name__)
api = Api(app)class HelloWord_Flask(Resource):def get(self):return {"message": "Hello World!"}def post(self):# 处理POST请求data = request.get_json() # 获取JSON数据return {"received": data}, 201 # 返回接收到的数据和状态码
api.add_resource(HelloWord_Flask, '/')if __name__ == '__main__':app.run(debug=True)
我们创建了一个 Flask 应用和一个 RESTful API 实例,定义了一个 HelloFlask 类作为资源,处理 GET 和 POST 请求,并且在根路径 (“/”) 上添加了这个资源。
POST 方法与 GET 方法不同,不能直接在浏览器输入 URL 来测试执行结果。我们可以借助工具 requests 来测试,它是 Python 的一个 HTTP 库(Python 的第三方库),可以通过 pip 来安装使用,它允许我们发送 HTTP 请求并获取响应。
你可能需要:RequestParser
对象-----自动验证和解析HTTP请求中的参数
常用参数选项
参数 | 描述 | 示例 |
---|---|---|
type |
参数类型 | str , int , float , bool |
required |
是否必需 | required=True |
help |
错误提示信息 | help='参数不能为空' |
default |
默认值 | default='guest' |
choices |
可选值列表 | choices=['male', 'female'] |
location |
参数位置 | location='args' (查询参数) |
如果我们flask这样
class HelloWord_Flask_two(Resource):# 创建一个RequestParser对象,用于解析请求参数,设置规则parser = reqparse.RequestParser()parser.add_argument('name', type=str, required=True, help='name参数不能为空')parser.add_argument('location', type=str, required=True, help='location参数不能为空')parser.add_argument('age', type=int, required=True, help='age参数不能为空')def get(self):return {"message": "Hello World!"}def post(self):# 处理post请求args = self.parser.parse_args() # 解析请求参数# 验证参数name = args['name']location = args['location']age = args['age']return {"name": name, "location": location, "age": age}, 201
api.add_resource(HelloWord_Flask_two, '/')
注:name = args[‘name’] 和 age = args[‘age’] 作用是获取请求参数中的 ‘name’ 值和 ‘age’ 值
验证:
import requests
#发送一个get请求
response = requests.get('http://localhost:5000/')
print(response.json())
#发送一个post请求
data = {"name": "Alice", "age": 30}
response = requests.post('http://localhost:5000/', json=data)
print("没有传location参数时的响应:")
print(response.json())
data2 = {"name": "Alice", "location": "Wonderland", "age": 30}
response = requests.post('http://localhost:5000/', json=data2)
print("传入所有参数时的响应:")
print(response.json())
令人更加兴奋的是:可以结合蓝图使用
文件目录
代码部分
这是api/models/user.py下的具体实现代码
from werkzeug.security import generate_password_hash, check_password_hash
class UserLogin(BaseModels,db.Model):
# 其中 BaseModels 是一个基础模型类,db.Model 是一个数据库模型类。模型里面包含的字段就是登录功能相关的字段信息。"""用户登录模型"""__tablename__ = 'user_login' # 指定表名为 user_loginid = db.Column(db.Integer, primary_key=True, autoincrement=True) # 主键,自增mobile = db.Column(db.String(16),unique=True,nullable=False) # 手机号,唯一且不能为空password_hash = db.Column(db.String(128),nullable=False) # 密码哈希值,不能为空user_id = db.Column(db.Integer,nullable=False) # 用户ID,不能为空last_login = db.Column(db.DateTime, default= datetime.now) # 最后登录时间last_login_stamp = db.Column(db.Integer) #最后登录时间戳@property# 将一个属性方法封装成属性def password(self):raise AttributeError('密码不可读(密码属性不可直接获取)')@password.setter# 定义password的setter方法def password(self, value):self.password_hash = generate_password_hash(value)# 传入的是明文,效验明文和数据库里的hash之后密码,正确为truedef check_password(self, password):return check_password_hash(self.password_hash, password)
你现在应该知道UserLogin 模型基类主要实现的功能。现在我们要把请求响应处理好,这样在接口请求之后,才能更好地贴合业务逻辑。建议你把 response_utils.py 存放在 api/utils/response_utils.py 这个文件路径上。
from flask import jsonify
class HttpCode(object):ok = 200 # 成功params_error = 400 # 参数错误server_error = 500 # 服务器错误auth_error = 401 # 认证错误method_error = 405 # 方法不允许db_error = 1001 # 自定义数据库错误
def rep_result(code,msg,data):#{"code"= 200,"msg" = 'baababaab',"data"={}}return jsonify({'code': code,'msg': msg,'data': data or {} # data为空时,返回{},就是确保data不为空})
def success(msg,data = None):return rep_result(code = HttpCode.ok,msg = msg,data = data)
def error(code,msg,data = None):return rep_result(code = code,msg = msg,data = data)
响应格式标准化:这个工具类确保所有API响应都遵循统一的JSON格式:
{"code": 200,"msg": "操作成功","data": {"user_id": 1,"username": "john"}
}
错误的响应为:
{"code": 400,"msg": "参数错误:用户名不能为空","data": {}
}
成功响应与错误响应的统一格式以及对应的 success() 和 error() 方法。rep_result 方法用来返回响应结果,该方法接受三个参数:code(状态码)、msg(提示信息)和 data(响应数据),并通过 jsonify 函数将结果转换成 JSON 格式并返回。
在后端开发中需要返回处理结果的时候,直接调用 success() 或 error() 方法即可。
我们需要创建一个 login.py 文件,文件存放路径我写在下面的api/modules/auth/login
from flask import current_app
from flask_restful import Resource, reqparse,inputs
from api.models.user import UserLogin
from api.utils.auth_helper import Auth
from api.utils.response_utils import error,HttpCode#Login 类继承自 Resource,表示这是一个RESTful资源
class Login(Resource):'''用户登录接口'''def post(self):# 创建解析对象parser = reqparse.RequestParser(bundle_errors=True)# 添加参数parser.add_argument('mobile',type = inputs.regex('1[3456789]\\d{9}'),nullable = False,location = ['form'] ,required = True,help = '请输入正确的手机号')parser.add_argument('password',type = str,required = True,nullable = False,location = ['form'],help = '密码参数不正确')# location=['form'] 表示从 HTTP请求的表单数据(Form Data) 中获取参数值。# 解析参数args = parser.parse_args()# 通过手机号取出用户对象try:user_login = UserLogin.query.filter(UserLogin.mobile == args.mobile).first()except Exception as e:current_app.logger.error(e)return error(code = HttpCode.db_error,msg = '数据库查询异常(查询手机号异常)')# 验证拿到这个手机号,是否在我们的登录信息中存在 异常捕获# 判断我们的用户信息不在时,返回错误信息的响应码if not user_login:return error(code=HttpCode.db_error,msg='用户不存在')return Auth().authenticate(args.mobile,args.password)
定义 LoginView 类作为资源,用于处理 POST 请求。在创建 RequestParser 对象时,参数 bundle_errors=True 能将请求参数校验时发生的所有错误,都打包成一个列表返回。随后我们向参数解析器 parser 中添加 mobile 和 password 参数。这些参数的含义你可以参考后面的表格。
完成视图函数之后,我们再看看蓝图层具体该如何实现。在蓝图层,注册名为 auth_blu 的蓝图,它的 URL 前缀是 /auth。这意味着,对于使用这个蓝图的路由,都需要以 /auth 开头。通过 add_resource 方法将 LoginView 视图和路径 ‘/login’ 绑定, LoginView 的完整 URL 变成了 ‘/auth/login’。
from flask import Blueprint
from flask_RESTful import Api
from api.modules.auth.login import LoginView
auth_blu = Blueprint('auth', __name__, url_prefix='/auth')
api = Api(auth_blu)
api.add_resource(LoginView, '/login')
在 api 的 init.py 文件中,添加对应的蓝图初始化
from api.modules.auth import auth_blu
app.register_blueprint(auth_blu)
api/modules/auth/
├── __init__.py
└── login.py # LoginView 类应该直接在这里
其他注意知识点:
@property 方法 - 像属性一样访问
class User:def __init__(self, name):self._name = name# 普通方法 - 需要加括号调用def get_name(self):return self._name# @property 方法 - 像属性一样访问@propertydef name(self):return self._name# 使用区别
user = User("Alice")# 普通方法调用
print(user.get_name()) # 需要括号 → "Alice"# @property 方法调用
print(user.name) # 不需要括号 → "Alice"
@property
def password(self):raise AttributeError('密码不可读')@password.setter # 👈 注意这里!
def password(self, value):self.password_hash = generate_password_hash(value)#当执行 user.password = "明文密码" 时
#会自动调用 @password.setter 装饰的方法
#将明文密码哈希后存储到 password_hash 属性中
class User:def __init__(self):self.password_hash = None@propertydef password(self):"""读取时:抛出错误"""raise AttributeError('密码不可读')@password.setter # 👈 设置器def password(self, value):"""赋值时:自动哈希密码"""self.password_hash = generate_password_hash(value)def check_password(self, plain_password):"""验证密码"""return check_password_hash(self.password_hash, plain_password)# 使用示例
user = User()# 赋值操作 → 触发 @password.setter
user.password = "my_password_123"
# 等价于:user.password("my_password_123") 但语法更优雅# 读取操作 → 触发 @property
# user.password # 会报错:AttributeError# 验证密码
result = user.check_password("my_password_123") # True
操作类型 | 装饰器 | 用途 | 示例 |
---|---|---|---|
读取 | @property |
定义获取属性时的行为 | print(user.password) |
赋值 | @属性名.setter |
定义设置属性时的行为 | user.password = "123" |
删除 | @属性名.deleter |
定义删除属性时的行为 | del user.password |
bundle_errors=True
bundle_errors=True
表示将所有参数验证错误打包在一起返回,而不是在遇到第一个错误时就停止验证。
对比演示
bundle_errors=False
parser = reqparse.RequestParser(bundle_errors=False) # 默认值parser.add_argument('mobile', required=True, help='手机号必填')
parser.add_argument('password', required=True, help='密码必填')
parser.add_argument('email', type=inputs.email, help='邮箱格式错误')# 如果mobile和password都为空,只会返回第一个错误
args = parser.parse_args()
响应结果
{"message": {"mobile": "手机号必填"}
}
// 只显示第一个错误,不知道password也有问题
bundle_errors=True
parser = reqparse.RequestParser(bundle_errors=True) # 启用错误打包parser.add_argument('mobile', required=True, help='手机号必填')
parser.add_argument('password', required=True, help='密码必填')
parser.add_argument('email', type=inputs.email, help='邮箱格式错误')# 即使有多个错误,也会一起返回
args = parser.parse_args()
响应结果
{"message": {"mobile": "手机号必填","password": "密码必填"}
}
// 同时显示所有错误字段