183 lines
4.2 KiB
Markdown
183 lines
4.2 KiB
Markdown
---
|
||
tags: [后端, API, RESTful, 设计]
|
||
create time: 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. 标准响应格式
|
||
|
||
```json
|
||
// 成功响应(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 示例
|
||
|
||
```go
|
||
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,
|
||
},
|
||
},
|
||
})
|
||
}
|
||
```
|
||
|
||
```mermaid
|
||
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 + 数据]
|
||
```
|
||
|
||
## 关联笔记
|
||
|
||
- [[HTTP 协议]] — API 设计的基础是理解 HTTP
|
||
- [[Go 后端基础]] — Go 实现 API 的具体方法
|
||
- [[数据库基础]] — API 的数据来源
|
||
- [[30.areas/finance/Investment lessons/2024.Current trading lessons.md]]
|