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]]
|