--- tags: [] aliases: - 1. 接口版本控制策略 (API Versioning Strategy) date created: 星期日, 十二月 7日 2025, 11:38:52 晚上 date modified: 星期日, 十二月 7日 2025, 11:42:18 晚上 --- # 七七八八的接口设计相关问题 ## 1. 接口版本控制策略 (API Versioning Strategy) **核心问题:** 当你发布了 V1 版本后,某天需要修改接口字段(比如把 `name` 拆分为 `first_name` 和 `last_name`),如何保证老版本的 App 不会崩溃? **三种主流流派:** 1. **URI Path Versioning (推荐):** - **格式:** `https://api.example.com/v1/users` - **优点:** 直观、易于调试、缓存友好。这也是 GitHub, Twitter, Google API 采用的主流方案。 - **落地:** 我们在 Gin 的 Router Group 中直接体现: Go ```bash v1 := r.Group("/api/v1") { v1.GET("/users", ...) } ``` 2. **Header Versioning:** - **格式:** Header 中添加 `Accept: application/vnd.myapi.v1+json` - **优点:** URL 干净。 - **缺点:** 调试麻烦(浏览器直接访问 URL 看不到结果),CDN 缓存配置复杂。**不推荐 MVP 阶段使用。** 3. **Query Parameter:** - **格式:** `/users?version=1` - **评价:** 看起来很土,通常不用于 RESTful API。 **👉 你的策略:** 坚定选择 **URI Path Versioning (`/api/v1`)**。只在发生**破坏性变更 (Breaking Change)** 时才升级到 v2。新增字段不算破坏性变更,不需要升级版本。 --- ## 2. HTTP 方法的精准语义 (Verbs Semantics) 很多新手只会用 `GET` 和 `POST`。企业级 API 必须精准区分以下方法的含义: |**方法**|**语义**|**幂等性 (Idempotency)**|**典型场景**| |---|---|---|---| |**GET**|获取资源|✅ 是|获取文章列表、详情| |**POST**|新建资源|❌ 否|发布新文章、提交评论| |**PUT**|**全量替换**资源|✅ 是|修改文章(客户端发送文章的完整 JSON,没传的字段会被置空)| |**PATCH**|**局部更新**资源|❌ 否 (理论上)|修改文章状态(只传 `{"status": "published"}`,其他字段不变)| |**DELETE**|删除资源|✅ 是|删除文章| ⚠️ 重点关注 PUT vs PATCH: 在 Go 语言中实现 PATCH 有点麻烦(因为 Go 的结构体默认值问题,你很难区分用户是传了 0 还是没传这个字段)。 - **最佳实践:** 对于 CMS 这种表单复杂的系统,**修改接口首选 `PUT` (全量)**,或者针对特定状态修改提供独立接口(如 `POST /articles/:id/publish`)。如果必须做 `PATCH`,DTO 需使用指针类型 `*string` 来判断是否为 `nil`。 --- ## 3. RESTful URL 设计模式 (Resource Naming) **原则:URL 中只出现名词,不出现动词。** - ❌ **反例 (RPC 风格 - 不要这么做):** - `/api/getUsers` - `/api/createUser` - `/api/deleteArticle?id=1` - ✅ **正例 (REST 风格):** - `GET /api/v1/users` (获取列表) - `POST /api/v1/users` (创建) - `DELETE /api/v1/articles/1` (删除 ID 为 1 的文章) **复杂关系的嵌套设计:** - _场景:_ 获取某篇文章下的评论。 - _设计:_ `GET /api/v1/articles/{article_id}/comments` - _场景:_ 获取某个作者的所有文章。 - _设计:_ `GET /api/v1/users/{user_id}/articles` --- ## 4. 列表接口三剑客:分页、排序、筛选 (Pagination, Sorting, Filtering) 你的 CMS 一定会有“文章列表”页面,这个接口是最复杂的。不要为每种查询都写一个新接口,要设计一个**通用的查询接口**。 **最佳实践标准:** 1. **分页 (Pagination):** - 使用 `page` (页码) 和 `page_size` (每页条数)。 - URL 示例: `/articles?page=2&page_size=20` - **注意:** 要限制 `page_size` 的最大值(如 100),防止恶意用户一次请求 100 万条数据把数据库打挂。 2. **排序 (Sorting):** - 使用 `sort` 参数。`-` 代表降序,无符号代表升序。 - URL 示例: `/articles?sort=-created_at` (按创建时间倒序) - URL 示例: `/articles?sort=view_count,-created_at` (先按浏览量升序,再按时间倒序) 3. **筛选 (Filtering):** - 直接使用字段名作为参数。 - URL 示例: `/articles?category_id=1&status=published` --- ## 5. 状态码与错误处理 (Status Codes & Error Handling) **不要永远只返回 200 OK!** 前端开发最恨的就是:HTTP 状态码是 200,结果 Body 里写着 `{"code": 500, "msg": "Error"}`。这会让监控系统失效。 **你需要遵守的“HTTP 状态码地图”:** - **2xx (成功):** - `200 OK`: 通用成功。 - `201 Created`: 创建成功 (POST 返回)。 - `204 No Content`: 删除成功 (DELETE 返回,不带 Body)。 - **4xx (客户端错误 - 前端背锅):** - `400 Bad Request`: 参数校验失败(如邮箱格式不对)。 - `401 Unauthorized`: 未登录/Token 过期。 - `403 Forbidden`: 登录了,但没权限(如普通用户想删文章)。 - `404 Not Found`: 资源不存在。 - **5xx (服务端错误 - 你背锅):** - `500 Internal Server Error`: 代码崩了/数据库挂了。 统一错误响应格式 (JSON Envelope): 无论发生什么错误,Body 必须保持结构一致,方便前端拦截: JSON ```bash { "code": 40001, // 业务错误码 (你在 pkg/e 定义的) "message": "标题已存在", // 给用户看的提示 "request_id": "abc-123" // 方便查日志的 Trace ID } ``` --- ## 6. 进阶知识:幂等性 (Idempotency) **概念:** 用户因为网络卡顿,连续点击了 3 次“发布文章”按钮,你的系统会创建 1 篇文章还是 3 篇? - **非幂等:** `POST`。点 3 次创建 3 篇。 - **幂等:** `PUT` / `DELETE`。改 3 次和改 1 次结果一样;删 3 次和删 1 次结果一样(都是没了)。 如何处理 POST 的幂等性? 对于关键业务(如支付、核心数据创建),通常要求前端在 Header 带一个 Idempotency-Key (UUID)。后端 Redis 记录这个 Key,如果短时间内收到相同的 Key,直接返回上次的结果,不执行数据库插入。 (注:MVP 阶段可以先不做,但面试或者做高可靠系统时必须知道) --- ## 总结:在你开始定义 API 之前 请在心中建立这套标准: 1. **URL:** `/api/v1/名词` 2. **Method:** 查询用 GET,新建用 POST,修改用 PUT,删除用 DELETE。 3. **List:** 永远记得预留 `page`, `page_size`。 4. **Response:** 永远返回统一的 JSON 结构,报错必须用 4xx/5xx 状态码。 **准备好了吗?我们可以开始基于这些原则,设计 User 模块的 API 了。**