跳转至

544_设计RESTful API系统


一句话说明

RESTful API 是以资源为中心、用 HTTP 动词操作的接口规范,是现代 Web 后端开发的基础。


核心知识点

REST 六大约束

  1. 无状态:每次请求包含所有信息,服务器不保存会话
  2. 统一接口:标准 HTTP 方法(GET/POST/PUT/DELETE/PATCH)
  3. 客户端-服务器分离:前后端解耦
  4. 可缓存性:GET 请求结果可缓存
  5. 分层系统:客户端不感知中间层(LB/CDN)
  6. 按需代码(可选):服务器可传送可执行代码

HTTP 动词语义

动词含义幂等安全
GET获取资源
POST创建资源
PUT全量更新
PATCH部分更新
DELETE删除资源

HTTP 状态码速记

2xx 成功
  200 OK             - 通用成功
  201 Created        - 创建成功
  204 No Content     - 删除成功(无响应体)

4xx 客户端错误
  400 Bad Request    - 参数错误
  401 Unauthorized   - 未登录
  403 Forbidden      - 无权限(已登录但无权)
  404 Not Found      - 资源不存在
  409 Conflict       - 冲突(重复创建)
  422 Unprocessable  - 参数格式正确但业务校验失败
  429 Too Many Req   - 限流

5xx 服务端错误
  500 Internal Error - 服务器内部错误
  502 Bad Gateway    - 上游服务不可用
  503 Unavailable    - 服务暂时不可用

实战代码/设计图/模板

生信 API 设计示例(样本管理)

资源:/samples
操作示例:
  GET    /samples              - 列出所有样本(支持分页/过滤)
  POST   /samples              - 创建新样本
  GET    /samples/{id}         - 获取单个样本
  PUT    /samples/{id}         - 全量更新样本
  PATCH  /samples/{id}         - 部分更新(如修改状态)
  DELETE /samples/{id}         - 删除样本

子资源:
  GET    /samples/{id}/files   - 获取样本下所有文件
  POST   /samples/{id}/jobs    - 提交分析任务

FastAPI 实现示例

from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel
from typing import Optional
import uvicorn

app = FastAPI(title="生信平台 API", version="1.0")

class Sample(BaseModel):
    name: str
    project_id: str
    tissue_type: Optional[str] = None
    description: Optional[str] = None

class SampleResponse(Sample):
    id: str
    status: str
    created_at: str

# GET 列表(支持分页和过滤)
@app.get("/api/v1/samples", response_model=dict)
async def list_samples(
    project_id: Optional[str] = None,
    status: Optional[str] = None,
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100)
):
    """
    获取样本列表
    - 支持按项目ID、状态过滤
    - 支持分页
    """
    offset = (page - 1) * page_size
    samples, total = db.get_samples(
        project_id=project_id,
        status=status,
        offset=offset,
        limit=page_size
    )
    return {
        "data": samples,
        "meta": {
            "total": total,
            "page": page,
            "page_size": page_size,
            "total_pages": (total + page_size - 1) // page_size
        }
    }

# POST 创建
@app.post("/api/v1/samples", status_code=201)
async def create_sample(sample: Sample):
    """创建新样本"""
    # 校验项目是否存在
    if not db.project_exists(sample.project_id):
        raise HTTPException(status_code=404, detail="项目不存在")

    new_sample = db.create_sample(sample.dict())
    return {"data": new_sample, "message": "创建成功"}

# PATCH 部分更新
@app.patch("/api/v1/samples/{sample_id}")
async def update_sample(sample_id: str, updates: dict):
    """部分更新样本信息"""
    sample = db.get_sample(sample_id)
    if not sample:
        raise HTTPException(status_code=404, detail="样本不存在")

    updated = db.update_sample(sample_id, updates)
    return {"data": updated}

# 统一错误响应格式
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
    return {
        "success": False,
        "error": {
            "code": exc.status_code,
            "message": exc.detail
        }
    }

API 版本控制策略

方案1: URL版本(推荐)
  /api/v1/samples
  /api/v2/samples

方案2: Header版本
  Accept: application/vnd.myapi.v2+json

方案3: Query参数
  /api/samples?version=2

推荐:URL版本,直观且易于调试

面试常问点

问题参考答案
PUT vs PATCH 的区别?PUT全量替换,PATCH部分更新
如何设计分页?cursor分页 vs offset分页
API认证方式有哪些?JWT/OAuth2/API Key/Session
如何限流?令牌桶/漏桶,Redis计数
接口幂等性怎么实现?唯一请求ID,数据库唯一约束

速查表

URL设计规范:
  用名词不用动词:/samples 而非 /getSamples
  用复数:/samples 而非 /sample
  层级不超过3层:/projects/{id}/samples/{id}
  过滤用Query参数:?status=active&page=1

响应体标准格式:
  成功:{"data": {...}, "meta": {...}}
  错误:{"error": {"code": 400, "message": "..."}}
  列表:{"data": [...], "meta": {"total": 100, "page": 1}}

常用工具:
  文档:Swagger/OpenAPI 3.0
  测试:Postman / httpie / curl
  Mock:Mockoon / WireMock