在平时的业务开发中,你是否经常看到这样的接口:
- 只有 GET 和 POST 接口,很少看到 RESTful 定义的其它接口类型
- 有的接口疯狂定义 VO 和 Query 类进行前端参数的接收,也有接口在 GET 接口中定义大坨 RequestParam 字段
- 有的接口在 Controller 里写业务逻辑,有的接口却不管 Service 返回而直接响应 sucess
究竟应该怎么写一个正确而优雅的接口?
为什么使用 REST API?
REST(Representational State Transfer)是一种基于 HTTP 协议的接口设计风格,其核心优势在于:
- 标准化:通过 HTTP 方法(GET/POST/PUT/DELETE)明确操作语义,降低前后端沟通成本。
- 解耦与复用:前后端分离开发,同一接口可被 Web、App、小程序等多个客户端复用。
- 轻量高效:数据通常以 JSON 格式传输,结构清晰且兼容性强。
- 扩展性:支持通过版本号(如
/api/v1/user)管理接口迭代。
在编写正确且优雅的 REST API 接口时,可以从以下几个方面入手:
使用正确的 HTTP 方法
根据 RESTful 原则,选择合适的 HTTP 动词:
- GET:获取资源。
- POST:创建资源。
- PUT:更新资源。
- DELETE:删除资源。
- PATCH:部分更新资源。
- HEAD:获取响应头信息。
- OPTIONS:获取资源的访问策略。
我的建议是除了 GET 和 POST 之外,可以将 PUT 和 DELETE 接口也纳入你的接口定义中。
设计清晰的资源路径
资源路径应简洁明了,使用名词而非动词,并保持单数形式:
- 正确:
/users/{id} - 错误:
/getUserById
处理请求参数
- 查询参数:使用
@RequestParam或@ModelAttribute,适合 GET 请求。 - 请求体参数:使用
@RequestBody,适合 POST、PUT 等方法。 - 路径变量:使用
@PathVariable,适合资源标识符。
分层设计
- Controller 层:处理 HTTP 请求,转换数据,调用 Service 层。
- Service 层:处理业务逻辑。
- DAO 层:处理数据访问。
统一响应格式
使用统一的响应结构,包括状态码、消息和数据部分:
{
"code": 200,
"message": "Success",
"data": {}
}
文档和测试
- 使用 Swagger 等工具生成 API 文档。
- 编写单元测试和集成测试,确保接口稳定。
异常处理
- 使用
@ExceptionHandler处理全局异常。 - 统一返回错误信息,避免直接暴露内部错误。
通过以上步骤,可以编写出正确且优雅的 REST API 接口。
参数接收方式
路径参数:@PathVariable
从 URL 路径中获取动态参数,必须提供该参数,否则将返回 404 错误,常用于资源标识(一般都是接收 id):
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return ResponseEntity.ok(user);
}
调用示例:GET /api/user/1001
查询参数:@RequestParam
从 URL 的 ? 后获取参数,适用于过滤、排序、分页等场景,可设置参数为可选(required = false):
@GetMapping("/list")
public List<User> listUsers(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size
) {
return userService.findByPage(page, size);
}
调用示例:GET /api/user/list?page=2&size=20
JSON 参数:@RequestBody(核心)
接收前端发送的 JSON 请求体,需定义与 JSON 结构匹配的 DTO 类:
@PostMapping("/create")
public ResponseEntity<User> createUser(@RequestBody UserDTO userDTO) {
User user = userService.create(userDTO);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
DTO 类示例(使用 Lombok 简化代码):
@Data // 自动生成 Getter/Setter
@NoArgsConstructor
public class UserDTO {
private String username;
private String email;
}
筛选参数我咋接?
众所周知,接收前端例如 id、page、keyword 等参数时,我们可以选择使用 @RequestParam 定义多个参数接收,也可以定义 Query 类进行接收,这两种方式我分别该如何使用呢?
使用 @RequestParam 注解的情况
- 参数较少:当需要接收的参数数量较少(如 1-3 个)时,使用
@RequestParam注解较为简洁和直接,代码量少,易于理解。 - 快速开发:在需要快速开发或测试时,使用
@RequestParam注解可以避免额外的类定义,节省时间。 - 独立参数:当参数之间没有明显的关联或结构时,使用
@RequestParam注解逐个接收参数更为合适。
使用 Query 类的情况
- 参数较多:当需要接收的参数数量较多(如 4 个及以上),使用 Query 类可以将多个参数封装到一个对象中,使代码更清晰和易于维护。
- 复杂逻辑:如果参数之间有特定的业务逻辑或关系,使用 Query 类可以更好地组织代码,提高可读性和可扩展性。
- 数据校验:结合 Hibernate Validator 等工具,可以在 Query 类中定义数据校验规则,增强输入数据的合法性检查。
@RequestBody 使用要点
请求约束
- 仅用于 POST/PUT/PATCH 请求。
- 请求头需设置 Content-Type: application/json。
JSON 转换规则
- Spring Boot 通过 Jackson 库将 JSON 字段与 Java 对象属性自动映射。
- 字段名需严格一致(或通过
@JsonProperty显式指定) 。
常见问题解决
- 400 错误:检查 JSON 结构是否与 DTO 匹配。
- 415 错误:确认请求头 Content-Type 是否正确。
- 空值问题:使用
@NotNull注解 + 全局异常处理校验参数。
最佳实践建议
统一响应格式
使用 ResponseEntity 或自定义 Result 类封装响应数据:
public class Result<T> {
private int code;
private String message;
private T data;
// 构造方法 + 静态工厂方法
}
参数校验
结合 @Valid 和 Hibernate Validator 实现自动校验:
@PostMapping("/create")
public Result<User> createUser(@Valid @RequestBody UserDTO dto) {
// 业务逻辑
}
DTO 校验注解示例:
public class UserDTO {
@NotBlank(message = "用户名不能为空")
private String username;
@Email(message = "邮箱格式错误")
private String email;
}
接口版本管理
在 URL 或请求头中增加版本号,避免接口升级冲突:
@RestController
@RequestMapping("/api/v1/user")
public class UserControllerV1 { ... }
提示:实际开发中,建议结合 Swagger 生成 API 文档,进一步提升协作效率。