1. 引言
RESTful API 已经成为现代 Web 服务交互的主流标准。无论是前后端分离架构、移动端应用,还是第三方开放平台,RESTful 风格都以其简洁、统一、可扩展的特性被广泛采用。本文将从实际应用的角度出发,通过丰富的代码实例,带你完整掌握 RESTful API 的设计、实现、调用与测试。
2. RESTful API 核心概念
REST(Representational State Transfer,表述性状态转移)是一种软件架构风格,由 Roy Fielding 在其博士论文中提出。它强调资源、无状态、统一接口等核心原则。
理解 RESTful API 需要先掌握以下几个关键概念:
- 资源(Resource):一切皆资源,每个资源通过 URI 唯一标识,例如
/users/123表示 id 为 123 的用户。 - 表述(Representation):资源在客户端和服务端之间传输的具体形式,常见的有 JSON、XML 等。
- 状态转移(State Transfer):通过 HTTP 方法(GET、POST、PUT、DELETE 等)对资源进行状态改变。
- 无状态(Stateless):服务端不保存客户端状态,每次请求都是独立的,状态由客户端维护。
3. RESTful API 设计规范
良好的 API 设计是项目成功的关键。下面从 URI 设计、HTTP 方法、状态码、版本管理等维度介绍实际项目中的设计规范。
3.1 URI 设计规范
URI 应使用名词而非动词,采用复数形式,层级关系用斜杠表示。以下是一个典型的资源 URI 设计示例:
# 资源集合 GET /api/v1/users # 获取用户列表 POST /api/v1/users # 创建新用户 单个资源 GET /api/v1/users/123 # 获取指定用户 PUT /api/v1/users/123 # 整体更新指定用户 PATCH /api/v1/users/123 # 部分更新指定用户 DELETE /api/v1/users/123 # 删除指定用户 子资源(嵌套关系) GET /api/v1/users/123/orders # 获取指定用户的订单列表 GET /api/v1/orders/456 # 获取指定订单3.2 HTTP 方法与语义
每个 HTTP 方法都有明确的语义,设计时应严格遵循:
| HTTP 方法 | 语义 | 幂等性 | 典型场景 |
|---|---|---|---|
| GET | 获取资源 | 是 | 查询列表、详情 |
| POST | 创建资源 | 否 | 新增记录、提交表单 |
| PUT | 整体更新资源 | 是 | 全量替换 |
| PATCH | 部分更新资源 | 否 | 修改个别字段 |
| DELETE | 删除资源 | 是 | 删除记录 |
3.3 状态码使用规范
合理使用 HTTP 状态码能让调用方快速判断请求结果。以下是实际项目中常用的状态码:
- 2xx 成功:200 OK、201 Created、204 No Content。
- 3xx 重定向:301 Moved Permanently、304 Not Modified。
- 4xx 客户端错误:400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found、409 Conflict、422 Unprocessable Entity。
- 5xx 服务端错误:500 Internal Server Error、502 Bad Gateway、503 Service Unavailable。
4. 后端实现:Spring Boot 实战
下面以 Java Spring Boot 为例,演示如何实现一个完整的 RESTful API。首先创建实体类:
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; private Integer age; // 无参构造、有参构造、getter/setter 省略 }接着创建 Repository 层:
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface UserRepository extends JpaRepository<User, Long> { }然后编写 Controller 层,这是 RESTful API 的核心:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.List; @RestController @RequestMapping("/api/v1/users") public class UserController { @Autowired private UserRepository userRepository; // 获取用户列表 @GetMapping public List<User> getAllUsers() { return userRepository.findAll(); } // 获取单个用户 @GetMapping("/{id}") public ResponseEntity<User> getUserById(@PathVariable Long id) { return userRepository.findById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } // 创建用户 @PostMapping @ResponseStatus(HttpStatus.CREATED) public User createUser(@Valid @RequestBody User user) { return userRepository.save(user); } // 整体更新用户 @PutMapping("/{id}") public ResponseEntity<User> updateUser(@PathVariable Long id, @Valid @RequestBody User user) { return userRepository.findById(id) .map(existing -> { user.setId(existing.getId()); return ResponseEntity.ok(userRepository.save(user)); }) .orElse(ResponseEntity.notFound().build()); } // 部分更新用户 @PatchMapping("/{id}") public ResponseEntity<User> patchUser(@PathVariable Long id, @RequestBody User user) { return userRepository.findById(id) .map(existing -> { if (user.getName() != null) existing.setName(user.getName()); if (user.getEmail() != null) existing.setEmail(user.getEmail()); if (user.getAge() != null) existing.setAge(user.getAge()); return ResponseEntity.ok(userRepository.save(existing)); }) .orElse(ResponseEntity.notFound().build()); } // 删除用户 @DeleteMapping("/{id}") public ResponseEntity<Void> deleteUser(@PathVariable Long id) { return userRepository.findById(id) .map(user -> { userRepository.delete(user); return ResponseEntity.noContent().build(); }) .orElse(ResponseEntity.notFound().build()); } }5. 前端调用:JavaScript Fetch 实战
前端通过 Fetch API 调用 RESTful API 是最常见的做法。下面演示完整的增删改查操作:
// 基础配置 const BASE_URL = '/api/v1/users'; // 获取用户列表 async function fetchUsers() { try { const response = await fetch(BASE_URL); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } const users = await response.json(); console.log('用户列表:', users); return users; } catch (error) { console.error('获取用户列表失败:', error); } } // 获取单个用户 async function fetchUserById(id) { try { const response = await fetch(${BASE_URL}/${id}); if (response.status === 404) { console.log('用户不存在'); return null; } if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } return await response.json(); } catch (error) { console.error('获取用户失败:', error); } } // 创建用户 async function createUser(userData) { try { const response = await fetch(BASE_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(userData) }); if (response.status === 201) { return await response.json(); } throw new Error(创建失败,状态码: ${response.status}); } catch (error) { console.error('创建用户失败:', error); } } // 更新用户(整体更新) async function updateUser(id, userData) { try { const response = await fetch(${BASE_URL}/${id}, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(userData) }); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } return await response.json(); } catch (error) { console.error('更新用户失败:', error); } } // 删除用户 async function deleteUser(id) { try { const response = await fetch(${BASE_URL}/${id}, { method: 'DELETE' }); if (response.status === 204) { console.log('删除成功'); return true; } throw new Error(删除失败,状态码: ${response.status}); } catch (error) { console.error('删除用户失败:', error); } }6. 认证与授权实践
实际生产环境中,RESTful API 通常需要认证与授权。JWT(JSON Web Token)是目前最流行的无状态认证方案。下面演示基于 Spring Security 和 JWT 的实现:
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Component; import java.util.Date; @Component public class JwtTokenProvider { private final String secretKey = "your-secret-key"; private final long validityInMilliseconds = 3600000; // 1小时 public String createToken(String username) { Date now = new Date(); Date validity = new Date(now.getTime() + validityInMilliseconds); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(validity) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); } public String getUsername(String token) { return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody() .getSubject(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } }前端在请求头中携带 Token:
// 登录获取 Token async function login(username, password) { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const data = await response.json(); localStorage.setItem('token', data.token); return data; } // 携带 Token 请求受保护资源 async function fetchProtectedData() { const token = localStorage.getItem('token'); const response = await fetch('/api/v1/protected', { headers: { 'Authorization': Bearer ${token} } }); if (response.status === 401) { // Token 失效,跳转登录页 window.location.href = '/login'; return; } return await response.json(); }7. 错误处理与统一响应格式
统一的响应格式和错误处理机制能显著提升 API 的易用性。下面给出一个实际项目中常用的统一响应结构:
public class ApiResponse<T> { private int status; private String message; private T data; private long timestamp; public ApiResponse(int status, String message, T data) { this.status = status; this.message = message; this.data = data; this.timestamp = System.currentTimeMillis(); } public static <T> ApiResponse<T> success(T data) { return new ApiResponse<>(200, "success", data); } public static <T> ApiResponse<T> error(int status, String message) { return new ApiResponse<>(status, message, null); } // getter/setter 省略 }全局异常处理器:
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ApiResponse<Void>> handleValidationException( MethodArgumentNotValidException ex) { String message = ex.getBindingResult().getFieldErrors().stream() .map(error -> error.getField() + ": " + error.getDefaultMessage()) .reduce((a, b) -> a + "; " + b) .orElse("参数校验失败"); return ResponseEntity.badRequest() .body(ApiResponse.error(400, message)); } @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ApiResponse<Void>> handleNotFoundException( ResourceNotFoundException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(ApiResponse.error(404, ex.getMessage())); } @ExceptionHandler(Exception.class) public ResponseEntity<ApiResponse<Void>> handleGenericException(Exception ex) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(ApiResponse.error(500, "服务器内部错误")); } }8. API 测试与调试
RESTful API 的测试可以使用 Postman、curl 或自动化测试框架。下面给出常用的 curl 命令示例:
# 获取用户列表 curl -X GET http://localhost:8080/api/v1/users 创建用户 curl -X POST http://localhost:8080/api/v1/users -H "Content-Type: application/json" -d '{"name": "张三", "email": "zhangsan@example.com", "age": 28}' 获取单个用户 curl -X GET http://localhost:8080/api/v1/users/1 更新用户 curl -X PUT http://localhost:8080/api/v1/users/1 -H "Content-Type: application/json" -d '{"name": "李四", "email": "lisi@example.com", "age": 30}' 删除用户 curl -X DELETE http://localhost:8080/api/v1/users/1使用 JUnit 和 MockMvc 进行自动化测试:
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.; @SpringBootTest @AutoConfigureMockMvc public class UserControllerTest { @Autowired private MockMvc mockMvc; @Test public void testGetAllUsers() throws Exception { mockMvc.perform(get("/api/v1/users")) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)); } @Test public void testCreateUser() throws Exception { String json = "{\"name\":\"王五\",\"email\":\"wangwu@example.com\",\"age\":25}"; mockMvc.perform(post("/api/v1/users") .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.name").value("王五")); } @Test public void testGetUserNotFound() throws Exception { mockMvc.perform(get("/api/v1/users/9999")) .andExpect(status().isNotFound()); } }9. 性能优化与最佳实践
生产环境中的 RESTful API 还需要关注性能与稳定性。以下是实际项目中总结的最佳实践:
- 分页与过滤