SpringBoot3.x入门到精通系列:2.4 RESTful API设计
SpringBoot 3.x RESTful API设计
🎯 RESTful API设计原则
REST (Representational State Transfer) 是一种架构风格,用于设计网络应用程序的API。
核心原则
- 资源导向 - 一切皆资源,每个资源都有唯一的URI
- 统一接口 - 使用标准的HTTP方法
- 无状态 - 每个请求都包含处理该请求所需的所有信息
- 可缓存 - 响应应该明确标识是否可缓存
- 分层系统 - 客户端无需知道是否直接连接到最终服务器
🌐 HTTP方法与CRUD操作
HTTP方法 | CRUD操作 | 描述 | 示例 |
---|---|---|---|
GET | Read | 获取资源 | GET /api/users |
POST | Create | 创建资源 | POST /api/users |
PUT | Update | 完整更新资源 | PUT /api/users/1 |
PATCH | Update | 部分更新资源 | PATCH /api/users/1 |
DELETE | Delete | 删除资源 | DELETE /api/users/1 |
📋 URL设计规范
1. 资源命名规范
// ✅ 好的设计
GET /api/users // 获取用户列表
GET /api/users/123 // 获取特定用户
POST /api/users // 创建用户
PUT /api/users/123 // 更新用户
DELETE /api/users/123 // 删除用户// 嵌套资源
GET /api/users/123/orders // 获取用户的订单
POST /api/users/123/orders // 为用户创建订单// ❌ 不好的设计
GET /api/getUsers // 动词形式
POST /api/createUser // 动词形式
GET /api/user_list // 下划线
2. 实际API设计示例
@RestController
@RequestMapping("/api/v1")
@CrossOrigin(origins = "*")
public class UserApiController {private final UserService userService;public UserApiController(UserService userService) {this.userService = userService;}/*** 获取用户列表* GET /api/v1/users?page=0&size=10&sort=name,asc*/@GetMapping("/users")public ResponseEntity<PagedResponse<UserDTO>> getUsers(@RequestParam(defaultValue = "0") int page,@RequestParam(defaultValue = "10") int size,@RequestParam(defaultValue = "id") String sort,@RequestParam(required = false) String search) {PagedResponse<UserDTO> users = userService.findAll(page, size, sort, search);return ResponseEntity.ok(users);}/*** 根据ID获取用户* GET /api/v1/users/{id}*/@GetMapping("/users/{id}")public ResponseEntity<UserDTO> getUserById(@PathVariable Long id) {UserDTO user = userService.findById(id);return ResponseEntity.ok(user);}/*** 创建用户* POST /api/v1/users*/@PostMapping("/users")public ResponseEntity<UserDTO> createUser(@RequestBody @Valid CreateUserRequest request,HttpServletRequest httpRequest) {UserDTO user = userService.create(request);// 返回201状态码和Location头URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(user.getId()).toUri();return ResponseEntity.created(location).body(user);}/*** 完整更新用户* PUT /api/v1/users/{id}*/@PutMapping("/users/{id}")public ResponseEntity<UserDTO> updateUser(@PathVariable Long id,@RequestBody @Valid UpdateUserRequest request) {UserDTO user = userService.update(id, request);return ResponseEntity.ok(user);}/*** 部分更新用户* PATCH /api/v1/users/{id}*/@PatchMapping("/users/{id}")public ResponseEntity<UserDTO> patchUser(@PathVariable Long id,@RequestBody Map<String, Object> updates) {UserDTO user = userService.patch(id, updates);return ResponseEntity.ok(user);}/*** 删除用户* DELETE /api/v1/users/{id}*/@DeleteMapping("/users/{id}")public ResponseEntity<Void> deleteUser(@PathVariable Long id) {userService.delete(id);return ResponseEntity.noContent().build();}/*** 批量删除用户* DELETE /api/v1/users?ids=1,2,3*/@DeleteMapping("/users")public ResponseEntity<BatchDeleteResponse> batchDeleteUsers(@RequestParam List<Long> ids) {BatchDeleteResponse response = userService.batchDelete(ids);return ResponseEntity.ok(response);}
}
📊 统一响应格式
1. 成功响应格式
public class ApiResponse<T> {private boolean success;private String message;private T data;private long timestamp;public static <T> ApiResponse<T> success(T data) {return new ApiResponse<>(true, "操作成功", data, System.currentTimeMillis());}public static <T> ApiResponse<T> success(String message, T data) {return new ApiResponse<>(true, message, data, System.currentTimeMillis());}// 构造函数、getter和setterpublic ApiResponse(boolean success, String message, T data, long timestamp) {this.success = success;this.message = message;this.data = data;this.timestamp = timestamp;}// getter和setter方法public boolean isSuccess() { return success; }public void setSuccess(boolean success) { this.success = success; }public String getMessage() { return message; }public void setMessage(String message) { this.message = message; }public T getData() { return data; }public void setData(T data) { this.data = data; }public long getTimestamp() { return timestamp; }public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
}
2. 分页响应格式
public class PagedResponse<T> {private List<T> content;private int page;private int size;private long totalElements;private int totalPages;private boolean first;private boolean last;public PagedResponse(List<T> content, int page, int size, long totalElements) {this.content = content;this.page = page;this.size = size;this.totalElements = totalElements;this.totalPages = (int) Math.ceil((double) totalElements / size);this.first = page == 0;this.last = page >= totalPages - 1;}// getter和setter方法public List<T> getContent() { return content; }public void setContent(List<T> content) { this.content = content; }public int getPage() { return page; }public void setPage(int page) { this.page = page; }public int getSize() { return size; }public void setSize(int size) { this.size = size; }public long getTotalElements() { return totalElements; }public void setTotalElements(long totalElements) { this.totalElements = totalElements; }public int getTotalPages() { return totalPages; }public void setTotalPages(int totalPages) { this.totalPages = totalPages; }public boolean isFirst() { return first; }public void setFirst(boolean first) { this.first = first; }public boolean isLast() { return last; }public void setLast(boolean last) { this.last = last; }
}
3. 错误响应格式
public class ErrorResponse {private boolean success = false;private String error;private String message;private int status;private String path;private long timestamp;private List<FieldError> fieldErrors;public ErrorResponse(String error, String message, int status, String path) {this.error = error;this.message = message;this.status = status;this.path = path;this.timestamp = System.currentTimeMillis();}public static class FieldError {private String field;private Object rejectedValue;private String message;public FieldError(String field, Object rejectedValue, String message) {this.field = field;this.rejectedValue = rejectedValue;this.message = message;}// getter和setter方法public String getField() { return field; }public void setField(String field) { this.field = field; }public Object getRejectedValue() { return rejectedValue; }public void setRejectedValue(Object rejectedValue) { this.rejectedValue = rejectedValue; }public String getMessage() { return message; }public void setMessage(String message) { this.message = message; }}// getter和setter方法public boolean isSuccess() { return success; }public void setSuccess(boolean success) { this.success = success; }public String getError() { return error; }public void setError(String error) { this.error = error; }public String getMessage() { return message; }public void setMessage(String message) { this.message = message; }public int getStatus() { return status; }public void setStatus(int status) { this.status = status; }public String getPath() { return path; }public void setPath(String path) { this.path = path; }public long getTimestamp() { return timestamp; }public void setTimestamp(long timestamp) { this.timestamp = timestamp; }public List<FieldError> getFieldErrors() { return fieldErrors; }public void setFieldErrors(List<FieldError> fieldErrors) { this.fieldErrors = fieldErrors; }
}
🔧 HTTP状态码使用
1. 常用状态码
状态码 | 含义 | 使用场景 |
---|---|---|
200 | OK | 成功获取资源 |
201 | Created | 成功创建资源 |
204 | No Content | 成功删除资源 |
400 | Bad Request | 请求参数错误 |
401 | Unauthorized | 未认证 |
403 | Forbidden | 无权限 |
404 | Not Found | 资源不存在 |
409 | Conflict | 资源冲突 |
422 | Unprocessable Entity | 验证失败 |
500 | Internal Server Error | 服务器错误 |
2. 状态码使用示例
@RestController
@RequestMapping("/api/v1/users")
public class UserController {@GetMapping("/{id}")public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {try {UserDTO user = userService.findById(id);return ResponseEntity.ok(user); // 200} catch (UserNotFoundException e) {return ResponseEntity.notFound().build(); // 404}}@PostMappingpublic ResponseEntity<UserDTO> createUser(@RequestBody @Valid CreateUserRequest request) {try {UserDTO user = userService.create(request);URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(user.getId()).toUri();return ResponseEntity.created(location).body(user); // 201} catch (EmailAlreadyExistsException e) {return ResponseEntity.status(HttpStatus.CONFLICT).build(); // 409}}@DeleteMapping("/{id}")public ResponseEntity<Void> deleteUser(@PathVariable Long id) {try {userService.delete(id);return ResponseEntity.noContent().build(); // 204} catch (UserNotFoundException e) {return ResponseEntity.notFound().build(); // 404}}
}
🔍 API版本控制
1. URL路径版本控制
@RestController
@RequestMapping("/api/v1/users")
public class UserV1Controller {// v1版本的用户API
}@RestController
@RequestMapping("/api/v2/users")
public class UserV2Controller {// v2版本的用户API
}
2. 请求头版本控制
@RestController
@RequestMapping("/api/users")
public class UserController {@GetMapping(headers = "API-Version=1")public ResponseEntity<UserV1DTO> getUserV1(@PathVariable Long id) {// v1版本逻辑}@GetMapping(headers = "API-Version=2")public ResponseEntity<UserV2DTO> getUserV2(@PathVariable Long id) {// v2版本逻辑}
}
📝 API文档
1. 使用OpenAPI 3.0 (Swagger)
<dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.2.0</version>
</dependency>
2. API文档注解
@RestController
@RequestMapping("/api/v1/users")
@Tag(name = "用户管理", description = "用户相关的API接口")
public class UserController {@Operation(summary = "获取用户列表", description = "分页获取用户列表")@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "成功获取用户列表"),@ApiResponse(responseCode = "400", description = "请求参数错误")})@GetMappingpublic ResponseEntity<PagedResponse<UserDTO>> getUsers(@Parameter(description = "页码,从0开始") @RequestParam(defaultValue = "0") int page,@Parameter(description = "每页大小") @RequestParam(defaultValue = "10") int size) {PagedResponse<UserDTO> users = userService.findAll(page, size);return ResponseEntity.ok(users);}@Operation(summary = "创建用户", description = "创建新的用户")@PostMappingpublic ResponseEntity<UserDTO> createUser(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "用户创建请求", required = true)@RequestBody @Valid CreateUserRequest request) {UserDTO user = userService.create(request);return ResponseEntity.status(201).body(user);}
}
🔗 下一篇
在下一篇文章中,我们将学习SpringBoot的数据访问与JPA,了解如何进行数据库操作。
本文关键词: RESTful API, HTTP方法, 状态码, API设计, 版本控制, OpenAPI