Java 后台编写接口的正确姿势

147 阅读5分钟

在平时的业务开发中,你是否经常看到这样的接口:

  1. 只有 GET 和 POST 接口,很少看到 RESTful 定义的其它接口类型
  2. 有的接口疯狂定义 VO 和 Query 类进行前端参数的接收,也有接口在 GET 接口中定义大坨 RequestParam 字段
  3. 有的接口在 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 文档,进一步提升协作效率。