RESTful 设计在 Gin 项目中的落地
一句话结论:RESTful 的核心是“URL 表达资源、HTTP 方法表达动作、状态码表达结果”:名词复数 URL、按资源嵌套子资源、GET 安全幂等、POST 创建、PUT 全量替换、PATCH 部分更新、DELETE 删除,配合版本前缀 /api/v1 与分组中间件落地。Gin 里用 r.Group 组织资源路由、用 ShouldBind+状态码与统一响应体(呼应错误码章节)收口。它不是银弹:复杂动作(支付、转账、批量操作)应使用资源子路径动作或保留 RPC 风格子路径,别硬拆。
URL 与方法设计
| 方法 | 语义 | 典型路径 | 期望结果 |
|---|---|---|---|
| GET | 查询(安全幂等) | GET /api/v1/users?page=1 | 200 + 列表 |
| POST | 创建(非幂等) | POST /api/v1/users | 201 + Location |
| PUT | 全量替换(幂等) | PUT /api/v1/users/{id} | 200/204 |
| PATCH | 部分更新 | PATCH /api/v1/users/{id} | 200/204 |
| DELETE | 删除(幂等) | DELETE /api/v1/users/{id} | 204 |
- 资源命名:复数名词,子资源用嵌套 /users/{id}/orders;动词只在“动作化”场景用子路径:POST /api/v1/orders/{id}/pay。
- 版本:/api/v1 前缀 + 分组;破坏性变更升大版本,向后兼容用字段增补。
- 反模式:GET 改数据、URL 写动词(/getUser)、所有请求都返回 200 把错误塞 body、不分页无限返回。
Gin 落地骨架
api := r.Group("/api/v1", AuthMiddleware(), TraceMiddleware())
{
users := api.Group("/users")
users.GET("", ListUsers) // 过滤/分页走 query
users.POST("", CreateUser) // 201 + Location: /api/v1/users/{id}
users.GET("/:id", GetUser)
users.PUT("/:id", ReplaceUser) // 全量
users.PATCH("/:id", UpdateUser) // 部分
users.DELETE("/:id", DeleteUser) // 204
users.POST("/:id/avatar", UploadAvatar) // 动作子路径
}
func GetUser(c *gin.Context) {
u, err := userService.Get(c, c.Param("id"))
if err != nil { resp.Error(c, err); return } // 统一错误码响应
resp.OK(c, u)
}
追问记忆点
- 追问:PUT 和 PATCH 怎么选?——客户端提交全量字段用 PUT;只提交变更字段用 PATCH,服务端按非零/指针字段合并。
- 追问:删除用 204 还是 200?——成功且无返回体用 204;需要返回被删对象可 200,团队约定一致即可。
- 追问:为什么有的团队“一切 200 + 业务码”?——网关/老客户端友好、网络层无歧义;但会丢掉 HTTP 语义,对外 API 推荐 HTTP 状态码 + body 错误码。
- 记忆点:名词资源 + 方法表意 + 状态码收口 + 版本与分组;动作与批量场景允许偏离纯 REST。