本节摘要:API 是程序与程序之间对话的窗口,REST 是当下最通行的设计风格:把数据抽象成资源,用 URL 定位资源、用 HTTP 方法表达操作、用状态码反馈结果。本节先讲 REST 的三条设计准则,再为轻记账落地一整套账目接口,用 curl 逐条验收。学完本节,你的后端从"只服务网页"升级为"服务一切客户端"。
阅读完本节,你应当能够:
v0.5 的架构里,页面和数据是焊死的:视图函数算好数据渲染进 HTML,能接它的只有浏览器。想给轻记账配个手机 App?想写个脚本每晚自动导入账单?都得重新造轮子。解法是把后端的能力封装成一组只进不出 JSON 的 HTTP 接口——网页、App、脚本一律调同一套接口取数据。"脸"随便换,"心脏"只有一颗。这就是前后端分离的实质:不是为了时髦,是为了客户端可替换。
REST 不是规范文书而是一套设计惯例,核心三条:
一、URL 定位资源,资源用名词复数。账目是资源,路径叫 records 而不是 getRecordList;某一笔账是 records 3。资源是"东西",不是"动作"。
二、HTTP 方法表达操作语义。4.1 节的 GET 读 POST 写在此全面铺开——查列表 GET records、查单条 GET records 3、新建 POST records、更新 PUT records 3、删除 DELETE records 3。同一路径,方法不同就是不同操作。
三、状态码与数据各表其意。结果好坏看状态码,数据细节看响应体。新建成功是 201 不是 200,参数不合法是 400,资源不存在是 404。

from flask import Flask, request, jsonify from sqlalchemy import select from sqlalchemy.orm import Session app = Flask(__name__) @app.get("/api/records") def list_records(): month = request.args.get("month") # 查询参数按条件过滤 page = request.args.get("page", 1, type=int) with Session(engine) as session: stmt = select(Record).where(Record.user_id == current_user_id()) if month: stmt = stmt.where(Record.date.like(f"{month}%")) stmt = stmt.order_by(Record.date.desc()).limit(20).offset((page - 1) * 20) records = session.scalars(stmt) return jsonify([ {"id": r.id, "item": r.item, "amount": r.amount, "date": r.date} for r in records ]) @app.post("/api/records") def create_record(): data = request.get_json(silent=True) or {} item = str(data.get("item", "")).strip() amount = data.get("amount") if not item or not isinstance(amount, (int, float)) or amount <= 0: return jsonify({"error": "item 与正数 amount 必填"}), 400 with Session(engine) as session: record = Record(item=item, amount=amount, date=today_str(), user_id=current_user_id()) session.add(record) session.commit() return jsonify({"id": record.id, "item": record.item, "amount": record.amount, "date": record.date}), 201 @app.delete("/api/records/<int:record_id>") def delete_record(record_id): with Session(engine) as session: record = session.get(Record, record_id) if record is None or record.user_id != current_user_id(): return jsonify({"error": "账目不存在"}), 404 session.delete(record) session.commit() return "", 204
几处设计决定值得咀嚼。统一错误格式:出错也返回 JSON——{"error": "..."} 配 400——客户端永远面对同一种数据形态,不用解析两种。分页用 limit 与 offset:接口天生要防"一次拉全表",列表接口从第一版就该分页。404 区分两种原因:不存在或不是你的账,都回 404——不向试探者暴露"这条存在但属于别人"的信息,这是安全与体验的平衡。路径加 api 前缀:与返回 HTML 的页面路由划清楚河汉界,中间件按前缀统一做认证与日志(下一节登场)。
接口写好了,浏览器地址栏只能测 GET,全套验收用命令行 HTTP 工具:
# 新建一笔账(JSON 正文,POST) curl -X POST 本机5000端口/api/records -H "Content-Type: application/json" -d "{\"item\":\"午饭\",\"amount\":20}" # 期望响应:{"id": 1, "item": "午饭", "amount": 20.0, "date": "2026-09-02"},状态 201 # 查列表,按月过滤 curl "本机5000端口/api/records?month=2026-09" # 删除第 1 笔,期望无正文、状态 204 curl -X DELETE 本机5000端口/api/records/1
五个接口逐一打一遍,对照矩阵核状态码。变式一:实现 PUT 更新接口,注意"不存在"与"不是你的"两种 404。变式二:给列表接口加 sort 参数(按金额或日期),想想参数校验失败该回什么。
接口通了,但 current_user_id 还是个空壳——任何人都能删别人的账。下一节装门禁:认证与授权。