5.3 API 开发与设计:REST 风格


5.3 API 开发与设计:REST 风格

本节摘要:API 是程序与程序之间对话的窗口,REST 是当下最通行的设计风格:把数据抽象成资源,用 URL 定位资源、用 HTTP 方法表达操作、用状态码反馈结果。本节先讲 REST 的三条设计准则,再为轻记账落地一整套账目接口,用 curl 逐条验收。学完本节,你的后端从"只服务网页"升级为"服务一切客户端"。

读完这节能做什么

阅读完本节,你应当能够:

  1. 说出 API 与网页接口的区别,解释"前后端分离"的动因
  2. 按 REST 约定命名资源路径,正确选择 HTTP 方法
  3. 返回规范的 JSON 响应,用对 200、201、400、404 状态码
  4. 为轻记账实现账目的增删查 API 并逐条验证
  5. 理解错误响应的统一格式设计

为什么要把接口抽出来

v0.5 的架构里,页面和数据是焊死的:视图函数算好数据渲染进 HTML,能接它的只有浏览器。想给轻记账配个手机 App?想写个脚本每晚自动导入账单?都得重新造轮子。解法是把后端的能力封装成一组只进不出 JSON 的 HTTP 接口——网页、App、脚本一律调同一套接口取数据。"脸"随便换,"心脏"只有一颗。这就是前后端分离的实质:不是为了时髦,是为了客户端可替换

REST 三准则

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。

图 5-3 轻记账 API 的资源与方法矩阵

图 5-3 轻记账 API 的资源与方法矩阵

动手实现:轻记账的账目 API

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 参数(按金额或日期),想想参数校验失败该回什么。

易错点清单

  • 路径里写动词:/api/getRecords 违反资源命名,方法语义已够表达动作
  • 成功新建返回 200:REST 惯例是 201,语义精确度是 API 的教养
  • 响应结构随缘:每接一个字段换一种格式,客户端要写 N 套解析——定个统一信封并坚持
  • 接口无限流无认证就裸奔上线:本节的 current_user_id 是占位,下一节把它变真

本节要点回顾

  • API 解耦客户端:JSON 进出,网页、App、脚本共用一颗心脏
  • REST 三准则:名词复数路径、方法表语义、状态码表结果
  • 统一错误信封加正确状态码:201 新建、400 参数错、404 不存在、204 删除成功
  • 分页从第一版就做,limit 与 offset 是起步方案
  • 命令行工具是接口的手工验收台,五个接口逐条打通过

接口通了,但 current_user_id 还是个空壳——任何人都能删别人的账。下一节装门禁:认证与授权。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U