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 与文章、文件接口。