路由回答了「谁来接单」,这一章解决「单子上写了什么、该怎么回话」。你要学会从请求里取出前端传来的参数(分页、筛选、表单、JSON 体、token),再用统一格式把结果交回去。学完这章,接口文档里那些 page、keyword、Authorization,你就都能一一对应到代码上了。
每次请求都打包成三段:请求行(方法+路径+协议)、请求头(元信息)、请求体(POST/PUT 才有的数据);服务器回的响应也是同样的三段结构。
POST /api/comment/add?from=detail HTTP/1.1
Host: 127.0.0.1:5000
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
{"article_id": 1, "content": "好诗!", "parent_id": null}
from flask import Flask, request
app = Flask(__name__)
@app.route("/api/debug/echo", methods=["GET", "POST"])
def echo():
print("方法:", request.method)
print("路径:", request.path)
print("查询参数:", request.args.to_dict())
print("客户端:", request.remote_addr)
print("浏览器:", request.headers.get("User-Agent"))
print("内容类型:", request.content_type)
return "ok"
URL 里 ? 后面的查询字符串会被 Flask 解析进 request.args。分页的 page、每页条数、搜索关键词都从这里取;取出来默认是字符串,记得用 type 转类型。
from flask import Flask, jsonify, request
app = Flask(__name__)
app.json.ensure_ascii = False
@app.route("/api/article/list", methods=["GET"])
def article_list():
# 取参数:名字对接口文档,没传就用默认值
page = request.args.get("page", 1, type=int)
page_size = request.args.get("page_size", 10, type=int)
category_id = request.args.get("category_id", type=int) # 不传就是 None
keyword = request.args.get("keyword", "").strip()
order_by = request.args.get("order_by", "latest")
# 顺手做一层保护:防止前端传 page=0 或 page_size=99999
page = max(page, 1)
page_size = min(max(page_size, 1), 50)
return jsonify({
"code": 200,
"msg": "success",
"data": {
"list": [],
"total": 0,
"page": page,
"page_size": page_size,
"category_id": category_id,
"keyword": keyword,
"order_by": order_by,
},
})
请求体有两种常见格式:表单用 request.form 按字段取,JSON 用 get_json() 整段转字典。看请求头 Content-Type 决定该用哪个,用错会取到 None。
from flask import Flask, jsonify, request
app = Flask(__name__)
app.json.ensure_ascii = False
@app.route("/api/comment/add", methods=["POST"])
def comment_add():
# ① 读 JSON 体。silent=True 表示:体不是合法 JSON 时返回 None,而不是直接抛 415
data = request.get_json(silent=True)
if not data:
return jsonify({"code": 400, "msg": "请求体必须是合法 JSON", "data": None})
# ② 取字段。用 .get(),不要用 data["xxx"],缺字段时不会崩
article_id = data.get("article_id")
content = (data.get("content") or "").strip()
parent_id = data.get("parent_id") # 为空表示直接评论作品
# ③ 校验必填
if not article_id:
return jsonify({"code": 400, "msg": "缺少参数:article_id", "data": None})
if not content:
return jsonify({"code": 400, "msg": "书评内容不能为空", "data": None})
if len(content) > 1000:
return jsonify({"code": 400, "msg": "书评最多 1000 字", "data": None})
# ④ 校验过了才进业务(第 6 章这里会插入 comment 表)
return jsonify({
"code": 200,
"msg": "发表成功",
"data": {"comment_id": 9, "article_id": article_id,
"content": content, "parent_id": parent_id},
})
# 表单格式(含文件上传)用 request.form / request.files,例如第 9 章的上传头像
@app.route("/api/user/avatar", methods=["POST"])
def upload_avatar():
nickname = request.form.get("nickname", "")
file = request.files.get("file") # 文件对象
if file is None:
return jsonify({"code": 400, "msg": "请选择要上传的图片", "data": None})
return jsonify({"code": 200, "msg": "上传成功",
"data": {"filename": file.filename, "nickname": nickname}})
from flask import Flask, jsonify, request
app = Flask(__name__)
app.json.ensure_ascii = False
@app.route("/api/user/profile", methods=["GET", "PUT"])
def user_profile():
# ① 从请求头取 Authorization,形如 "Bearer eyJhbGciOi..."
auth = request.headers.get("Authorization", "")
if not auth.startswith("Bearer "):
# 业务码 401:前端拦截器看到它会清 token 并跳登录页
return jsonify({"code": 401, "msg": "请先登录", "data": None})
token = auth[7:] # 去掉 "Bearer " 这 7 个字符,剩下的才是 token
# 第 8 章:这里会用 PyJWT 验签并解出 user_id,本章先不校验
if request.method == "PUT":
data = request.get_json(silent=True) or {}
nickname = (data.get("nickname") or "").strip()
if not nickname:
return jsonify({"code": 400, "msg": "昵称不能为空", "data": None})
return jsonify({"code": 200, "msg": "修改成功", "data": {"nickname": nickname}})
# GET:返回个人信息
return jsonify({
"code": 200,
"msg": "success",
"data": {
"id": 3,
"username": "zhangsan",
"nickname": "张三",
"email": "zhangsan@example.com",
"avatar": "/static/upload/avatar/3.png",
"intro": "爱读唐诗",
"gender": 1,
"created_at": "2026-10-12 10:30:00",
},
})
from flask import Flask, jsonify, make_response
app = Flask(__name__)
app.json.ensure_ascii = False
# ① 返回字符串:最原始,Content-Type 是 text/html
@app.route("/ping")
def ping():
return "pong"
# ② 返回 JSON:接口的主力写法
@app.route("/api/version")
def version():
return jsonify({"code": 200, "msg": "success", "data": {"version": "1.0"}})
# ③ 返回元组:(内容, 状态码) 或 (内容, 状态码, 响应头)
@app.route("/api/banner/detail")
def banner_detail():
# 记录不存在时返回 HTTP 404;业务码和 HTTP 码不必一样(见下一节说明)
return jsonify({"code": 404, "msg": "轮播图不存在", "data": None}), 404, {"X-Tip": "banner"}
# ④ make_response:需要精调响应对象时用
@app.route("/api/health")
def health():
resp = make_response(jsonify({"code": 200, "msg": "success",
"data": {"status": "ok"}}))
resp.status_code = 200
resp.headers["X-Server"] = "hiflask"
resp.set_cookie("visited", "1", max_age=3600) # 写 Cookie
return resp
无论成功失败,所有接口都包成同一个信封 {code, msg, data}。前端不用为每个接口写一套解析,只看 code 判断结果、从 data 取数据即可。
from flask import jsonify
def ok(data=None, msg="success"):
"""成功:code 固定 200"""
return jsonify({"code": 200, "msg": msg, "data": data})
def fail(code=400, msg="参数错误"):
"""失败:data 固定为 None,msg 直接给用户看"""
return jsonify({"code": code, "msg": msg, "data": None})
from flask import Flask, request
from utils.response import ok, fail # 上面那两个小函数
app = Flask(__name__)
app.json.ensure_ascii = False
@app.route("/api/comment/add", methods=["POST"])
def comment_add():
data = request.get_json(silent=True)
if not data:
return fail(400, "请求体必须是合法 JSON")
article_id = data.get("article_id")
content = (data.get("content") or "").strip()
if not article_id:
return fail(400, "缺少参数:article_id")
if not content:
return fail(400, "书评内容不能为空")
if len(content) > 1000:
return fail(400, "书评最多 1000 字")
# 第 6 章这里会插入 comment 表,拿到真实的自增主键
return ok({"comment_id": 9}, "发表成功")
@app.route("/api/comment/<int:cid>", methods=["DELETE"])
def comment_delete(cid):
# 未登录 / token 失效 → 401,前端拦截器收到就跳登录页
return fail(401, "登录已过期,请重新登录")
# ① 查询参数:作品列表,第 1 页、每页 2 条、只看唐诗
curl -s "http://127.0.0.1:5000/api/article/list?page=1&page_size=2&category_id=1" \
| python -m json.tool --indent 2
# ② JSON 体:发表一条书评(POST 一定要指定 Content-Type)
curl -s -X POST http://127.0.0.1:5000/api/comment/add \
-H "Content-Type: application/json" \
-d '{"article_id": 1, "content": "短短二十字,写尽思乡之情。"}' \
| python -m json.tool --indent 2
# ③ 请求头 token:个人信息(需要登录)
curl -s http://127.0.0.1:5000/api/user/profile \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...." \
| python -m json.tool --indent 2
from flask import Flask, jsonify, request
from utils.response import ok, fail
app = Flask(__name__)
app.json.ensure_ascii = False
ORDER_BY_WHITELIST = {"latest", "hot", "likes"}
# 第 1 题
@app.route("/api/article/list", methods=["GET"])
def article_list():
page = max(request.args.get("page", 1, type=int), 1)
page_size = min(max(request.args.get("page_size", 10, type=int), 1), 50)
keyword = request.args.get("keyword", "").strip()
order_by = request.args.get("order_by", "latest")
if order_by not in ORDER_BY_WHITELIST: # 白名单校验:只认这三个
order_by = "latest"
return ok({"list": [], "total": 0, "page": page,
"page_size": page_size, "keyword": keyword,
"order_by": order_by})
# 第 2 题
@app.route("/api/comment/list", methods=["GET"])
def comment_list():
article_id = request.args.get("article_id", type=int)
if not article_id:
return fail(400, "缺少参数:article_id")
page = max(request.args.get("page", 1, type=int), 1)
page_size = min(max(request.args.get("page_size", 10, type=int), 1), 50)
return ok({"list": [], "total": 0, "page": page, "page_size": page_size})
# 第 3 题(红影赏析)
@app.route("/api/movie/rate", methods=["POST"])
def movie_rate():
data = request.get_json(silent=True)
if not data:
return fail(400, "请求体必须是合法 JSON")
movie_id = data.get("movie_id")
score = data.get("score")
if not movie_id:
return fail(400, "缺少参数:movie_id")
if not isinstance(score, (int, float)) or not 0 <= score <= 10:
return fail(400, "score 必须是 0 到 10 之间的数字")
return ok({"movie_id": movie_id, "score": round(float(score), 1)}, "打分成功")
第 2 题的答案:page 是可以合理地默认的(不传就当第 1 页),而 article_id 缺失意味着「你根本没告诉我要看哪篇作品的书评」——这没法默认,只能报 400。判断标准是:这个参数缺了以后,接口还能给出一个用户想要的合理解释吗?