This repository has been archived on 2026-05-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

4.2 KiB
Raw Permalink Blame History

tags, create time
tags create time
后端
API
RESTful
设计
2026-04-24 18:41

API 设计

概述

API(Application Programming Interface)是前后端协作的契约。好的 API 设计让前端开发者一看就懂、一用就会。差的 API 设计会让协作效率大幅下降。

思考题:设计一个 API 时,是应该"让所有人都符合你的设计"还是"让设计迁就所有使用场景"?这两者的边界在哪里?

正文

1. RESTful API 设计规范

资源命名:

  • URL 用名词复数,不用动词
  • 层级结构表示从属关系
  • 查询参数用于筛选和排序
GET    /api/users              → 获取用户列表
GET    /api/users/5            → 获取用户 5 的详情
GET    /api/users/5/posts      → 获取用户 5 的所有文章
GET    /api/users?role=admin   → 筛选管理员用户
GET    /api/users?sort=-created_at  → 按创建时间倒序
GET    /api/users?page=2&limit=20  → 分页

2. 标准响应格式

// 成功响应(200 OK)
{
    "code": 0,
    "message": "success",
    "data": {
        "id": 1,
        "name": "Alice",
        "email": "alice@example.com"
    }
}

// 成功响应(201 Created)
{
    "code": 0,
    "message": "created",
    "data": { "id": 5, ... }
}

// 错误响应(4xx / 5xx)
{
    "code": 1001,
    "message": "用户不存在",
    "data": null
}

提问: 错误码设计成数字好还是字符串好?数字更紧凑但可读性差,字符串更直观但传输量大。你怎么权衡?

3. 常见错误码设计

code HTTP 状态码 含义
0 200 成功
1001 404 资源不存在
1002 400 参数校验失败
1003 401 未认证
1004 403 无权限
1005 429 请求过于频繁
2001 500 服务器内部错误

4. 分页设计

GET /api/users?page=1&limit=20

响应:
{
    "code": 0,
    "data": [...],           // 当前页数据
    "pagination": {
        "page": 1,
        "limit": 20,
        "total": 100,
        "total_pages": 5
    }
}

核心概念: 客户端分页 vs 服务端分页。大数据量时必须服务端分页,否则性能灾难。

5. 版本控制

方案一:URL 路径(推荐)
/api/v1/users
/api/v2/users

方案二:请求头
Accept: application/vnd.api.v1+json

6. Go 后端 API 示例

package main

import (
    "encoding/json"
    "net/http"
    "time"
)

// 统一响应结构
type APIResponse struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data"`
}

func writeJSON(w http.ResponseWriter, status int, resp APIResponse) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(resp)
}

// 处理用户列表
func userListHandler(w http.ResponseWriter, r *http.Request) {
    // 1. 解析查询参数
    page, _ := strconv.Atoi(r.URL.Query().Get("page"))
    if page < 1 {
        page = 1
    }
    limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
    if limit == 0 {
        limit = 20
    }

    // 2. 查询数据库(省略)
    users, total := getUsersFromDB(page, limit)

    // 3. 返回统一格式
    writeJSON(w, http.StatusOK, APIResponse{
        Code:    0,
        Message: "success",
        Data: map[string]interface{}{
            "users": users,
            "pagination": map[string]int{
                "page":    page,
                "limit":   limit,
                "total":   total,
                "pages":   (total + limit - 1) / limit,
            },
        },
    })
}
graph TD
    A[HTTP Request] --> B{方法检查}
    B -->|GET| C[解析查询参数]
    B -->|POST| D[解析请求体]
    C --> E[参数校验]
    D --> E
    E --> F{校验通过?}
    F -->|否| G[返回 400 + 错误信息]
    F -->|是| H[查询/操作数据库]
    H --> I{操作成功?}
    I -->|否| J[返回对应错误码]
    I -->|是| K[返回 200/201 + 数据]

关联笔记