上一章那个 / 只是个门牌。真实的「古典文学网」后端有十几个接口:作品列表、作品详情、书评、收藏、登录……把「哪条 URL 交给哪段代码处理」这件事安排明白,就是路由。本章你会写出带参数的动态路由、用 HTTP 方法区分动作、用 url_for 反推地址,并知道一个尾部斜杠能坑掉你半小时。
路由的本质是一张「URL → 函数」的对照表。请求进来后,Flask 按 URL 找到对应函数并调用,把函数返回值发回浏览器。
from flask import Flask, jsonify
app = Flask(__name__)
app.json.ensure_ascii = False
@app.route("/")
def index():
return jsonify({"msg": "古典文学网 API 已启动"})
@app.route("/api/banner/list")
def banner_list():
"""首页轮播图:对应 banner 表的三条种子数据"""
return jsonify({
"code": 200,
"msg": "success",
"data": [
{"id": 1, "title": "品读古典文学,涵养中国心灵",
"image": "/static/images/banner1.jpg"},
{"id": 2, "title": "一场跨越千年的对话",
"image": "/static/images/banner2.jpg"},
{"id": 3, "title": "写下你的书评,与同好相遇",
"image": "/static/images/banner3.jpg"},
],
})
@app.route("/api/category/list")
def category_list():
"""分类列表:对应 category 表的种子数据"""
return jsonify({
"code": 200,
"msg": "success",
"data": [
{"id": 1, "name": "唐诗", "description": "唐代诗歌,格律严谨、气象恢宏"},
{"id": 2, "name": "宋词", "description": "宋代词作,婉约与豪放并存"},
{"id": 3, "name": "元曲", "description": "元代散曲与杂剧,通俗生动"},
{"id": 4, "name": "散文", "description": "先秦至明清的散文名篇"},
{"id": 5, "name": "小说", "description": "章回体与笔记体小说"},
],
})
if __name__ == "__main__":
app.run(debug=True)
URL 里的变量用 <int:id> 这样的写法捕获,尖括号里的转换器决定参数类型。请求 /article/7 时,<int:id> 会把 7 转成整数传给 detail(id=7)。
# ① int:只匹配整数(最常用),URL 里出现非数字直接 404
@app.route("/api/article/detail/<int:aid>")
def article_detail(aid):
# 进来的 aid 已经是 int,可以直接参与运算,不用 int() 转换
return jsonify({"code": 200, "msg": "success", "data": {"id": aid}})
# ② string:匹配一段不含斜杠的文本(默认类型,不写也行)
@app.route("/api/user/<string:username>")
def user_info(username):
return jsonify({"code": 200, "msg": "success", "data": {"username": username}})
# ③ float:匹配小数
@app.route("/api/rank/<float:score>")
def rank(score):
return jsonify({"code": 200, "msg": "success", "data": {"score": score}})
# ④ path:允许匹配带斜杠的完整路径,适合文件下载
@app.route("/api/download/<path:filepath>")
def download(filepath):
return jsonify({"code": 200, "msg": "success", "data": {"filepath": filepath}})
同一个资源地址 /article,配不同的 HTTP 方法(GET/POST/PUT/DELETE)就代表「查/增/改/删」四种动作——这正是 RESTful 风格的核心思路。
from flask import Flask, jsonify, request
app = Flask(__name__)
app.json.ensure_ascii = False
# 写法一:一个接口只干一件事,最清晰 —— 推荐
@app.route("/api/comment/list", methods=["GET"])
def comment_list():
return jsonify({"code": 200, "msg": "success",
"data": {"list": [], "total": 0, "page": 1, "page_size": 10}})
@app.route("/api/comment/add", methods=["POST"])
def comment_add():
data = request.get_json()
return jsonify({"code": 200, "msg": "发表成功", "data": {"comment_id": 9}})
# 写法二:同一个路径、同一个函数处理两种方法,用 request.method 分流
@app.route("/api/user/profile", methods=["GET", "PUT"])
def user_profile():
if request.method == "GET":
return jsonify({"code": 200, "msg": "success",
"data": {"id": 3, "username": "zhangsan", "nickname": "张三"}})
# 走到这里就是 PUT:修改资料
return jsonify({"code": 200, "msg": "修改成功", "data": None})
url_for 按端点名反向生成真实地址。路由规则改了,用 url_for 生成的链接会自动跟着更新,避免满项目手改字符串。
from flask import Flask, jsonify, url_for
app = Flask(__name__)
app.json.ensure_ascii = False
@app.route("/api/article/detail/<int:aid>")
def article_detail(aid):
return jsonify({"code": 200, "msg": "success", "data": {"id": aid}})
@app.route("/api/article/list")
def article_list():
rows = [
{"id": 1, "title": "静夜思", "author": "李白"},
{"id": 3, "title": "念奴娇·赤壁怀古", "author": "苏轼"},
]
for row in rows:
# 不用手拼 "/api/article/detail/" + str(row["id"])
row["detail_url"] = url_for("article_detail", aid=row["id"])
return jsonify({
"code": 200,
"msg": "success",
"data": {"list": rows, "total": 6, "page": 1, "page_size": 10},
})
@app.route("/")
def index():
# 静态文件也能生成地址:static 是 Flask 自带的端点
avatar = url_for("static", filename="upload/avatar/3.png")
# 额外参数会变成查询字符串:/api/article/list?page=2
more = url_for("article_list", page=2)
return jsonify({"code": 200, "msg": "success",
"data": {"avatar": avatar, "more": more}})
# 规则带尾部斜杠:访问不带斜杠的地址时,Flask 会自动 308 重定向过去
@app.route("/article/")
def article_page():
return "带斜杠的规则"
# 规则不带尾部斜杠:访问带斜杠的地址会直接 404,没有任何提示
@app.route("/article")
def article_page():
return "不带斜杠的规则"
@app.route("/api/article/detail/<int:aid>", methods=["GET"])
def article_detail(aid):
"""作品详情:先用手写数据,第 6 章换成查 article 表"""
articles = {
1: {"id": 1, "title": "静夜思", "author": "李白", "dynasty": "唐",
"category_name": "唐诗", "views": 329, "likes": 42},
3: {"id": 3, "title": "念奴娇·赤壁怀古", "author": "苏轼", "dynasty": "宋",
"category_name": "宋词", "views": 412, "likes": 58},
}
article = articles.get(aid)
if article is None:
# 业务失败也返回 HTTP 200,成败看 code —— 按接口文档的统一约定
return jsonify({"code": 404, "msg": "作品不存在", "data": None})
return jsonify({"code": 200, "msg": "success", "data": article})
from flask import Flask, jsonify, url_for, request
app = Flask(__name__)
app.json.ensure_ascii = False
# 第 1 题
@app.route("/api/comment/list/<int:article_id>", methods=["GET"])
def comment_list(article_id):
return jsonify({
"code": 200,
"msg": "success",
"data": {"list": [], "total": 0, "page": 1, "page_size": 10},
})
# 第 2 题
@app.route("/api/favorite/toggle", methods=["GET", "POST"])
def favorite_toggle():
if request.method == "GET":
return jsonify({"code": 400, "msg": "请用 POST 提交", "data": None})
return jsonify({"code": 200, "msg": "操作成功", "data": {"favorited": True}})
# 第 3 题(红影赏析)
@app.route("/api/movie/detail/<int:mid>", methods=["GET"])
def movie_detail(mid):
return jsonify({
"code": 200,
"msg": "success",
"data": {"id": mid, "title": "流浪地球", "genre": "科幻", "rating": 8.4},
})
@app.route("/api/movie/list")
def movie_list():
rows = [
{"id": 1, "title": "流浪地球"},
{"id": 2, "title": "星际穿越"},
]
for row in rows:
row["detail_url"] = url_for("movie_detail", mid=row["id"])
return jsonify({"code": 200, "msg": "success", "data": {"list": rows}})
第 2 题的答案:只写 @app.route("/api/favorite/toggle") 时,这个路由只收 GET,POST 访问会得到 405 METHOD NOT ALLOWED(不是 404)。第 3 题的关键:路由的写法、变量规则、url_for 的用法全都一样,变的只是路径里的业务名。
做完点「检查本节」看解析与得分;做错可重做。