第3章:RESTful API 设计原则:资源、URI、CRUD 操作

好,我们直接切入正题。API 接口设计,说白了,就是定义一套你跟前端、跟第三方系统沟通的“暗号”。这套暗号如果设计得乱七八糟,后期维护起来,你想想看,得有多痛苦?

RESTful 不是什么高深莫测的玄学,它是一套很朴素的约定。核心就三个东西:资源(Resource)、URI、以及 CRUD 操作。把这三样理清了,接口设计就成功了一大半。

3.1 什么是“资源”?—— 你得先想清楚你要操作什么

我个人习惯,在设计接口之前,先不做技术实现,而是画一张业务领域的“实体图”。比如自动录入系统,它的核心资源是什么?说白了,就是任务(Task)、模板(Template)、记录(Record)。这些就是我们需要暴露给外界的“资源”。

资源是对业务数据的抽象。它不应该是一个数据库表,而是一个逻辑上的概念。例如,一个“导入任务”可能涉及多个表的数据,但在 API 层面,它就是一个完整的资源。

设计资源时,有个原则要记住:用名词,而不是动词。因为名词代表了实体,动词代表了动作。动作应该由 HTTP 方法(GET、POST 等)来表达。

经验之谈: 我在项目中遇到过一个团队,他们把所有操作都放到了一个名为“operation”的端点里,结果接口文档比《红楼梦》还厚。后来全部拆成资源式的,世界清净了。记住,越抽象、越面向业务的资源,复用性越高。

3.2 URI 是什么?—— 资源的门牌号

URI 就是资源的地址。好的 URI 设计,应该是自解释的。看到地址,你就大概知道这个端点是干什么的。

我们来看看一个典型的设计案例。对于“自动录入任务”这个资源,它的 URI 应该怎么设计?

动作意图 不推荐的 URI 推荐的 URI 说明
获取所有任务 /getTasks /api/v1/tasks 用名词复数,动词交给 HTTP 方法
获取单个任务 /getTaskById?id=5 /api/v1/tasks/5 路径参数传递 ID,语义清晰
创建新任务 /createTask /api/v1/tasks POST 请求,向集合追加资源
更新任务状态 /updateStatus /api/v1/tasks/5/status 子资源表达“资源的属性”

设计 URI 时,有几个铁律要注意:

  1. 统一前缀:比如 /api/v1/。前缀用于区分版本或服务类型,方便后续升级。
  2. 层级关系:用斜杠 / 表达资源之间的从属关系,但层级不要太深,3-4 层就够了。比如 /tasks/5/records 表示“某个任务下的所有记录”。
  3. 单数还是复数? 业内大部分实践倾向使用复数名词,如 /tasks、/templates。保持一致即可。
  4. 查询参数:用于过滤、排序、分页,这些不是资源标识符,而是修饰符。例如 /api/v1/tasks?status=success。

3.3 CRUD 操作 —— 用 HTTP 方法说话

现在我们有了资源(Task)和门牌号(/api/v1/tasks)。那怎么对它进行“增删改查”呢?这就要用到 HTTP 请求方法了。

最常见的四种操作,对应数据库里的 CRUD:

操作 HTTP 方法 URI 示例 语义
查询(Read) GET /api/v1/tasks 获取资源列表,幂等(多次请求不改变后端数据)
新增(Create) POST /api/v1/tasks 创建新资源,不幂等(每次请求都可能创建新资源)
替换(Update) PUT /api/v1/tasks/5 整体替换资源,幂等。请求体必须包含所有字段
部分更新(Partial) PATCH /api/v1/tasks/5 只更新资源的部分字段,通常用于修改状态
删除(Delete) DELETE /api/v1/tasks/5 删除资源,幂等。通常返回 204 No Content

核心要点: 不要把所有的操作都堆在 GET 和 POST 上。我曾见过一个遗留系统,硬生生用 GET 实现了删除功能,结果爬虫一个脚本跑下来,整个测试库被清空了。把语义和方法绑定好,这是安全性的基本功。

3.4 实战中的设计规范

纸上谈兵没意思,我们落地到实战。设计一个“自动录入系统”的接口,我们通常会定义这样几个端点:

  • GET /api/v1/tasks:查询所有任务列表,支持分页和状态过滤。
  • POST /api/v1/tasks:创建一个新的录入任务,请求体包含模板 ID、数据文件地址等。
  • GET /api/v1/tasks/{taskId}:获取特定任务的详细信息,包括执行进度、结果统计。
  • PATCH /api/v1/tasks/{taskId}/status:修改任务状态,常见操作如“暂停”、“终止”。
  • DELETE /api/v1/tasks/{taskId}:删除一个任务,如果任务正在执行,通常要先终止。

状态码也很重要。 不要所有成功都返回 200,所有失败都返回 500。要体现语义:

  • 200 OK:查询、更新成功。
  • 201 Created:资源创建成功。
  • 204 No Content:删除成功,或者操作返回为空。
  • 400 Bad Request:前端参数错误,比如缺少必填字段。
  • 404 Not Found:资源不存在。
  • 409 Conflict:资源冲突,比如重复提交。

3.5 入门案例:用代码落实 URI 与 CRUD

下面是一个用 Python Flask 框架写的小示例,演示如何为一个简单的 Task 资源实现 RESTful 接口:

from flask import Flask, jsonify, request, make_response

app = Flask(__name__)

# 模拟的内存数据库
tasks_db = [
    {'id': 1, 'title': '导入销售数据', 'status': 'pending'},
    {'id': 2, 'title': '同步客户表', 'status': 'success'}
]

@app.route('/api/v1/tasks', methods=['GET'])
def get_tasks():
    return jsonify(tasks_db)

@app.route('/api/v1/tasks/<int:task_id>', methods=['GET'])
def get_task(task_id):
    task = next((t for t in tasks_db if t['id'] == task_id), None)
    if task:
        return jsonify(task)
    return make_response(jsonify({'error': 'Task not found'}), 404)

@app.route('/api/v1/tasks', methods=['POST'])
def create_task():
    if not request.json or 'title' not in request.json:
        return make_response(jsonify({'error': 'Bad request'}), 400)
    new_task = {
        'id': tasks_db[-1]['id'] + 1 if tasks_db else 1,
        'title': request.json['title'],
        'status': 'pending'
    }
    tasks_db.append(new_task)
    return jsonify(new_task), 201

@app.route('/api/v1/tasks/<int:task_id>', methods=['DELETE'])
def delete_task(task_id):
    global tasks_db
    task = next((t for t in tasks_db if t['id'] == task_id), None)
    if task:
        tasks_db = [t for t in tasks_db if t['id'] != task_id]
        return '', 204
    return make_response(jsonify({'error': 'Not found'}), 404)

if __name__ == '__main__':
    app.run(debug=True)

这个例子虽然简单,但麻雀虽小五脏俱全。它严格遵守了名词 + 方法的约定,并且返回了正确的状态码。嗯,这里要注意一点:实际生产环境中,你千万别把数据全存在内存里,这里只是为了演示结构。

避坑指南: 我曾经接手过一个接口,所有请求都统一走 /api/do,通过请求体里的 action 字段来区分操作。比如 action: getTaskList,action: createTask。维护了半年后,代码耦合严重,每个接口函数都有上千行。这就是典型的“反 RESTful”设计,切忌。

3.6 本章知识结构图

最后,我们用一张图来总结一下 RESTful API 设计的核心三要素以及它们之间的关系:

RESTful API 核心三要素 RESTful API 设计核心三要素 资源 (Resource) Task, Template, Record URI (地址) /api/v1/tasks/{id} CRUD 操作 GET, POST, PUT, PATCH, DELETE RESTful 接口规范 核心原则:用 URI 标示一个名词(资源), 用 HTTP 方法表达对这个名词的 CRUD 操作。