【Web API】RESTful API接口规范

文章目录

    • [一、 RESTful API接口规范](#一、 RESTful API接口规范)
    • [二、 构造RESTful API](#二、 构造RESTful API)
      • [2.1 客户端发送请求](#2.1 客户端发送请求)
        • [2.1.1 确定资源](#2.1.1 确定资源)
        • [2.1.2. 选择动作](#2.1.2. 选择动作)
        • [2.1.3. 添加查询条件(可选)](#2.1.3. 添加查询条件(可选))
        • [2.1.4. 版本控制](#2.1.4. 版本控制)
        • [2.1.5. 保持无状态](#2.1.5. 保持无状态)
      • [2.2 服务端给出响应](#2.2 服务端给出响应)
        • [2.2.1. 统一响应格式](#2.2.1. 统一响应格式)
        • [2.2.2. 返回合适的HTTP状态码](#2.2.2. 返回合适的HTTP状态码)
    • [三、RESTful 六大约束](#三、RESTful 六大约束)
    • [四、RESTful API 开发与测试](#四、RESTful API 开发与测试)
    • 五、总结

一、 RESTful API接口规范

REST:英文REpresentational State Transfer直译为表现层状态转移

RE(Representational)表现层
RE(Representational)表现层是指资源(Resource)的表现形式,

资源(Resource)指要操作的数据对象,比如用户、订单、商品、分类、文章、等等。用户列表列表是一个资源;某个具体的用户也是一个资源。表现层是指资源呈现出来的具体格式。比如同一个用户资源,可以用Json格式返回给客户端,也可以用XML格式返回,这就是不同的表现形式。

S(State)状态
ST(State)状态是指资源在某一时刻的状态。比如你登录网站后,服务器会在内存中记住你是谁,之后在网站上操作就不用再次登录了,这就是有状态。而无状态就是服务器不记录客户端的任何信息,每次请求都是独立的。

T(Transfer)转移

要注意转移是双向的,当你用GET请求时,服务器把资源的状态转移给客户端;当你用POST请求时,客户端把资源的新状态转移给服务器,从而改变服务器上资源的状态,

组合起来REST(Representational State Transfer)是一种软件架构风格,让客户端和服务器通过统一的接口以无状态的方式互相传递资源的表现层数据来查询或变更资源状态,而ful是个后缀就像powerful充满力量的一样,表示充满某个特性的。因此RESTful API 是指符合 REST 架构风格的API

注意:它不是协议,不是标准,不是强制规范,只是一种建议的设计风格,你可以遵循,也可以不遵循。


例如:你要做一个用户管理系统,对用户信息进行增删改查,用RESTful 风格的 API,如下所示:

  • GET /users/123 获取ID为1231用户
  • POST /users 创建新用户
  • PUT /users/123 修改用户 123
  • DELETE /users/123 删除用户 123

比较

操作 非 RESTful RESTful
获取用户 POST /getUser?id=1 GET /users/1
创建用户 POST /createUser POST /users
更新用户 POST /update_user PUT /users/1
删除用户 POST /delete/user DELETE /users/1

二、 构造RESTful API

2.1 客户端发送请求

2.1.1 确定资源

资源用URI统一资源标识符来表示,核心规则是用名词来表示资源,不用动词

  • 名称复数表示资源集合,如:
    • /users 表示用户列表;
    • /products 表示商品列表
  • 具体某个资源,就加上 ID ,如:
    • /users/123 表示ID为123的用户
  • 支持嵌套,如:
    • /users/123/orders 获取用户123的所有订单

注意:不建议嵌套层级太深


2.1.2. 选择动作

是指处理资源的方式, RESTful API主要通过不同的HTTP方法来表示增删改查操作。

  • GET /users/123 获取ID为1231用户
  • POST /users 创建新用户
  • PUT /users/123 修改用户 123
  • DELETE /users/123 删除用户 123

GET 查询资源

  • GET /users --- 查询所有用户
  • GET /users/123 --- 查询ID为1231用户

POST 创建 资源

  • POST /users --- 查询所有用户

PUT 完整更新资源

调用时需要提供资源的所有字段,多次执行结果相同(幂等性)。

  • PUT /users/123--- 完整更新用户123

PATCH 部分更新资源

通常用于更精细的操作。

  • PATCH /users/123 --- 只更新用户123的某些字段

DELETE 删除资源

  • DELETE /users/123 --- 删除用户123

2.1.3. 添加查询条件(可选)

有时我们需要更精确的筛选数据。

js 复制代码
 GET https://api.codefather.cm/users
  • 分页/users?page=2&limit=10 --- 查询第2页,每页10条用户数据
  • 筛选/users?gender=male&age=25 --- 查询性别为男性,年龄为25的用户
  • 排序/users?sort=created_at&order=desc --- 按照创建时间倒序排列用户

查询参数本身不是RESTful特有的,但Restful 风格强调把筛选排序分页这些操作都通过URL参数来表达,而不是在请求体里面传一堆复杂的Json对象。

Restful 风格

js 复制代码
GET https://api.codefather.cm/users?age=25&page=2&sort=created_at
  • 筛选 age=25
  • 分页 page=2
  • 排序 sort=created_at

非Restful 风格 - JSON对象 - 分页

json 复制代码
{
  "operation": "getUsers",
  "params":{
    "pagination": {
      "pageNumber": 1,
      "pageSize": 10
    }
  }
}

非Restful 风格 - JSON对象 - 过滤

json 复制代码
{
  "action": "searchUsers",
  "filters":[{
    "field": "gender",
    "operator": "equals",
    "value": "male",
  },{
    "field": "age",
    "operator": "equals",
    "value": "25",
  }]
}

非Restful 风格 - JSON对象 - 排序

json 复制代码
{
  "command": "listUsers",
  "options":{
    "sortBy": "create_at",
    "sortOrder": "desc",
  }
}

Restful 风格 的筛选排序分页URL 更清晰明了,而且浏览器CDN代理服务器都能直接根据URL来缓存响应结果,不同的URL可以分别缓存,更容易区分。


缓存示例1

  • /users?page=1
id name gae
1 张三 25
2 李四 26

缓存示例2

  • /users?page=2
id name gae
3 王五 27
4 赵六 28

2.1.4. 版本控制

随着业务发展,接口需要更新升级,为了不影响老用户,可以在URI中标明版本,这样老用户用v1的接口,新用户用v2的接口,互不影响。

  • /v1/users --- 第一版用户接口
  • /v2/users --- 第二版用户接口

完整示例

js 复制代码
GET http://api.codefather.cm/v2/users?age=25&page=2&sort=created_at

2.1.5. 保持无状态

无状态 (Stateless) 是 RESTful 架构的核心约束之一。 其含义是,服务器不应保存客户端的会话状态。每个来自客户端的请求都必须包含处理该请求所需的全部信息(如身份凭证、上下文等)。

典型实现方式是:

  1. 用户登录后,服务器生成一个唯一的身份凭证(如 Token),并将其返回给客户端。
  2. 此后客户端的每一个 请求都必须在HTTP标头(如 Authorization: Bearer <token>)中携带此凭证。
  3. 服务器不关心请求之间的关联,仅根据当前请求提供的 Token 和参数进行验证、处理并返回响应。

请求标头

js 复制代码
GET /orders
Header: Authorization: Bearer xxx

这样做带来的关键优势:

  • 极高的可扩展性: 由于请求完全独立,无需会话亲和性(Session Affinity),任何服务器实例都能处理任何请求。这使得通过简单地增加服务器节点来实现水平扩展变得非常容易。
  • 简化服务器设计: 服务器无需实现复杂的状态同步、会话存储和清理机制,降低了复杂度和资源消耗。
  • 提升可靠性: 单点故障的影响更小。客户端请求可以无缝地由其他可用服务器处理,无需担心会话丢失。
  • 便于缓存和负载均衡: 无状态的请求使得HTTP缓存机制(如CDN、反向代理)和标准的负载均衡策略能更高效地工作。

因此,"无状态"是构建大规模、高性能、可伸缩分布式服务的基石,它将状态管理的责任转移给客户端,使服务器集群变得简单而强大。


2.2 服务端给出响应

2.2.1. 统一响应格式

目前大多数 RESTful API 基本都使用 JSON 格式,因为轻量、容易解析,但并不是强制的,也可以用XML HTML 等格式,

JSON格式

json 复制代码
{
 "id": 123,
 "name": "张三",
 "email": "xxx@xxx.com",
}

XML格式

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<user>
  <id>123</id>
  <name>张三</name>
  <email>xxx@xxx.com</email>
</user>

HTML格式

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>用户信息</title>
  </head>
  <body>
    <div class="user-info ">
      <p><strong>ID:</strong> 123</p>
      <p><strong>姓名:</strong> 张三</p>
      <p><strong>邮箱:</strong> xxx@xxx.com</p>
    </div>
  </body>
</html>

2.2.2. 返回合适的HTTP状态码

响应需要带上合适的状态码,前端开发者或运维人员通过查看状态码,就能快速定位问题方向

http 复制代码
请求网址:http://api.codefather.cm/
请求方法:GET
状态代码:200 OK
远程地址:
引荐来源网址政策:strict-origin-when-cross-origin

HTTP 状态码有很多,大致可以分为5类:

  1. 1xx 系列:信息提示(用的少,了解即可)

    • 100 Continue:继续请求
  2. 2xx 系列:成功

    • 200 OK:请求成功,正常返回数据(用于 GET、PUT、PATCH)
    • 201 Created:创建成功(用于POST 请求),通常会在响应头的Location 中返回新的资源地址URI
    • 202 Accepted:请求已接受,但还在处理中(常用于异步操作)
    • 204 No Content:请求成功,但无返回数据(常用于 DELETE )
  3. 3xx 系列:重定向

    • 301 Moved Permanently:请求的资源已永久移动到新位置
    • 302 Found:请求的资源已临时移动到新位置
    • 304 Not Modified:资源未修改,客户端可以使用缓存。
  4. 4xx 系列:客户端错误

    • 400 Bad Request:请求参数格式错误
    • 401 Unauthorized:为验证身份,需要登录
    • 403 Forbidden:已认证但没有权限访问
    • 404 Not Found:资源不存在
    • 405 Method Not Allowed:请求方法不被允许(比如对只读资源用DELETE)
    • 409 Conflict:请求冲突(比如尝试创建已存在的资源)
    • 422 Unprocessable Entity:请求格式正确,但语义错误(比如邮箱格式不对)
    • 429 Too Many Requests:请求过于频繁,触发限流
  5. 5xx 系列:服务器错误

    • 500 Internal Server Error:服务器内部错误
    • 502 Bad Gateway:网关错误(服务器作为网关时,从上游服务器收到无效响应)
    • 503 Service Unavailable:服务器暂时不可用(通常是服务器维护或过载)
    • 504 Gateway Timeout:网关超时

三、RESTful 六大约束

1. Client-Server(客户端-服务器分离)

前后端各干各的活,前端负责展示,后端负责数据处理,互不干扰。

2. Stateless(无状态)

每次请求都是独立的,服务器不保持客户端的会话信息,必要信息都在请求中。

3. Cacheable(可缓存)

服务器的响应可以被标记为可缓存,客户端可以重用缓存数据,减少服务器压力。提升性能。

4. Layered System(分层系统)

客户端不需要知道直接连接的是服务器还是中间层,系统可以灵活加代理、网关、负载均衡器等。

5. Uniform Interface(统一接口)

所有资源都通过统一的接口访问,降低理解成本,提高可维护性。

6. Code-On-Demand(按需代码)

可选性,服务器可以返回可执行代码(比如JavaScript)给客户端执行,但实际工作中很少用。

很少有API能完美符合所有约束,大家可以灵活自行组合。甚至可以想下面所示代码一样,都用POST + 动词一把梭。

java 复制代码
//示例1:用户接口
app.post('/user',(req,res)=>{
  const {do,data}=req.body;
  res.json({do,result:db[do](data)});
})

//示例2:订单接口
@app.post('/order')
def handle(req):
  return globals()[f"{req.verb}_order"](req.data)

//示例3:文件接口
app.post('/file',(req,res)=>{
  const {act,...args}=req.body;
  res.json({act,data:actions[act](args)});
})

只要团队达成一致,舒服就行。就像刚才那个支付订单的例子.

虽然用名词符合RESTful 规范,但有同学会觉得用动词更直观易懂,也没问题。

  • POST /orders/123/pay 💗
  • POST /orders/123/payments

建议养成遵循RESTful 的好习惯,写出更漂亮的接口。


四、RESTful API 开发与测试

如何快速实现和检查 RESTful API?

1. 使用开发框架

几乎所有主流的开发框架都支持 RESTful API 的开发,他们能帮你自动处理很多细节。比如:

  • JavaSpring Boot
  • PythonDjango REST Framework
  • GoGin

这些框架虽然不强制你遵循 RESTful ,但用他们的特性开发起来轻松又规范,帮你省掉大量重复代码。


2. 使用IDE 插件

比如:

  • IDEARESTful Toolkit插件 ,可以快速查看和测试接口
  • VSCodeREST Client插件,可以直接在编辑器里测试接口

3. 利用AI生成
RESTful 有明确的设计规范,而AI最擅长处理这种有章可循的东西,比如直接让 DeepSeek

描述词

text 复制代码
帮我用`Spring Boot`,写一个用户管理的 `RESTful API`,

你只需简单描述一下,它就能生成规范的代码
输出结果

java 复制代码
package com.example.usermanagement.controller;

import com.example.usermanagement.dto.UserRequest;
import com.example.usermanagement.dto.UserResponse;
import com.example.usermanagement.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Tag(name = "用户管理", description = "用户管理相关API")
public class UserController {
    
    private final UserService userService;
    
    @PostMapping
    @Operation(summary = "创建用户")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<UserResponse> createUser(
            @Valid @RequestBody UserRequest userRequest) {
        UserResponse userResponse = userService.createUser(userRequest);
        return ResponseEntity.status(HttpStatus.CREATED).body(userResponse);
    }
    
    @GetMapping("/{id}")
    @Operation(summary = "根据ID获取用户")
    public ResponseEntity<UserResponse> getUserById(
            @Parameter(description = "用户ID") 
            @PathVariable Long id) {
        UserResponse userResponse = userService.getUserById(id);
        return ResponseEntity.ok(userResponse);
    }
    
    @GetMapping("/username/{username}")
    @Operation(summary = "根据用户名获取用户")
    public ResponseEntity<UserResponse> getUserByUsername(
            @Parameter(description = "用户名") 
            @PathVariable String username) {
        UserResponse userResponse = userService.getUserByUsername(username);
        return ResponseEntity.ok(userResponse);
    }
    
    @GetMapping
    @Operation(summary = "获取用户列表(分页)")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<Page<UserResponse>> getAllUsers(
            @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) 
            Pageable pageable) {
        Page<UserResponse> users = userService.getAllUsers(pageable);
        return ResponseEntity.ok(users);
    }
    
    @PutMapping("/{id}")
    @Operation(summary = "更新用户信息")
    public ResponseEntity<UserResponse> updateUser(
            @Parameter(description = "用户ID") 
            @PathVariable Long id,
            @Valid @RequestBody UserRequest userRequest) {
        UserResponse userResponse = userService.updateUser(id, userRequest);
        return ResponseEntity.ok(userResponse);
    }
    
    @PatchMapping("/{id}/status")
    @Operation(summary = "更新用户状态")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<UserResponse> updateUserStatus(
            @Parameter(description = "用户ID") 
            @PathVariable Long id,
            @RequestParam Boolean isActive) {
        UserResponse userResponse = userService.updateUserStatus(id, isActive);
        return ResponseEntity.ok(userResponse);
    }
    
    @DeleteMapping("/{id}")
    @Operation(summary = "删除用户")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<Void> deleteUser(
            @Parameter(description = "用户ID") 
            @PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

4. 生成文档

写完接口后还可以用Swagger 这类工具,自动生成漂亮的文档。直接甩给前端,对方一看就懂,还能在线测试接口,省去大量沟通成本


五、总结

RESTful API有以下优点:

  • 统一规范接口
  • 提高开发效率
  • 降低团队沟通成本

其他API规范 GraphQL, gRPC

相关推荐
ayqy贾杰1 小时前
Claude Fable 5 提示词泄漏,抓紧学习下
前端·后端·面试
智码看视界1 小时前
Day18 SpringBoot自动配置原理:从@SpringBootApplication开始
java·spring boot·后端·自动装配
程序员爱钓鱼1 小时前
Rust 常量与静态变量详解:const 与 static 的区别
后端
To_OC9 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic9 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy10 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志10 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble11 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯12 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
戮漠summer12 小时前
Missing Semester 计算机教育中缺失的一课 Lecture 01 Shell
开发语言·后端·scala