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. 资源命名规范
java
// ✅ 好的设计
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设计示例
java
@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. 成功响应格式
java
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和setter
public 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. 分页响应格式
java
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. 错误响应格式
java
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. 状态码使用示例
java
@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
}
}
@PostMapping
public 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路径版本控制
java
@RestController
@RequestMapping("/api/v1/users")
public class UserV1Controller {
// v1版本的用户API
}
@RestController
@RequestMapping("/api/v2/users")
public class UserV2Controller {
// v2版本的用户API
}
2. 请求头版本控制
java
@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)
xml
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
</dependency>
2. API文档注解
java
@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 = "请求参数错误")
})
@GetMapping
public 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 = "创建新的用户")
@PostMapping
public 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