RESTful 设计规范:资源命名、状态码语义、分页与错误体设计

RESTful 是一种「以资源为中心、用方法表达动作」的接口约定。它对客户端友好、对服务端约束清晰。本章给出一套可直接照抄的设计规范,HTTP 语义以 RFC 9110 为准。

1. 资源与 URL 命名

  • 资源用名词复数:/users/articles;避免动词式接口(不要 /getUser)。
  • 层级表达从属:/users/{id}/posts
  • 动作交给方法,不在 URL 里写 ?action=delete

2. 方法与状态码语义

方法语义成功状态码
GET读,无副作用200
POST新建,不保证幂等201 Created
PUT整体替换200
PATCH局部更新200
DELETE删除204 No Content

校验失败 400;未认证 401;无权限 403;资源不存在 404;冲突(如重名)409;服务器异常 500。422 常用于「语义校验未过」,团队约定一致即可。

3. 统一错误体

错误返回固定结构,客户端只解析一种格式:

{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "用户不存在",
    "field": "id"
  }
}

Go 侧用一个结构体 + 辅助函数生成:

type ErrBody struct {
    Error struct {
        Code    string `json:"code"`
        Message string `json:"message"`
        Field   string `json:"field,omitempty"`
    } `json:"error"`
}

func fail(c *gin.Context, status int, code, msg string) {
    var b ErrBody
    b.Error.Code, b.Error.Message = code, msg
    c.AbortWithStatusJSON(status, b)
}

code 用稳定机器码(前端可 switch),message 给人看,field 指明是哪个参数出错。

4. 分页设计

列表接口建议统一分页参数与响应形状:

type Page[T any] struct {
    Items []T `json:"items"`
    Total int64 `json:"total"`
    Page  int  `json:"page"`
    Size  int  `json:"page_size"`
}

// 请求参数解析与钳制
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
if page < 1 { page = 1 }
if size > 100 { size = 100 } // 防超大请求
offset := (page - 1) * size

数据量大或要求实时一致时改用游标(cursor)分页,按业务取舍。

5. 其他约定

  • 版本放路径前缀:/api/v1/...,破坏性变更升 v2;
  • GET 不产生副作用;写接口尽量可重试(PUT/DELETE 天然幂等);
  • 过滤/排序用 query 参数:?status=done&sort=-created_at
  • 响应按需裁剪字段(DTO),别把整张表结构全量外泄。

注意点

  • 状态码与 body 语义要一致:201 必须真的创建成功,别拿 200 糊弄。
  • 错误 code 一经发布别乱改,前端可能已做分支判断。
  • 分页从 1 还是 0 开始要写进接口文档并全局一致。

小结

规范的价值在于「不用猜」:URL 命名、状态码、错误体、分页四项定死,前后端协作就顺畅了。接下来的三个实战章节会按本规范实现 Todo 与文章、文件接口。

笔记加载中…