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=1200 + 列表
POST创建(非幂等)POST /api/v1/users201 + 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。
笔记加载中…