1. 理解RESTful风格的DNA

记得第一次接触WebService时看到的大段XML和SOAP协议吗?那种感觉就像用摩斯密码点外卖。直到Roy Fielding博士提出REST架构风格,才让我们意识到API设计的本质是"像网页一样交互"。

REST的核心约束中,无状态通信统一接口是最具革命性的。例如在电商系统中:

// 错误示范:带状态的接口设计
@GetMapping("/nextProduct")
public Product getNextItem(HttpSession session) {
    Integer index = (Integer) session.getAttribute("browseIndex");
    // 维护浏览状态的逻辑...
}

// 正确示范:无状态设计
@GetMapping("/products/{id}")
public Product getProduct(@PathVariable String id) {
    return productService.findById(id);
}

(技术栈:Spring Boot 2.7 + Spring Web)

2. 你必须知道的API设计规范

2.1 资源命名的艺术

好的资源命名应当像街道地址一样明确。试着对比这两个URI:

  • /api/getUserOrders?userId=123
  • /api/users/123/orders

后者遵循"资源-子资源"的层级关系,配合HTTP方法可以组成完整的语义:

@RestController
@RequestMapping("/api/users/{userId}/orders")
public class OrderController {
    
    // 获取用户所有订单
    @GetMapping
    public List<Order> getUserOrders(@PathVariable String userId) { /*...*/ }
    
    // 创建新订单
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Order createOrder(@RequestBody OrderCreateDTO dto) { /*...*/ }
}

2.2 HTTP动词的精准使用

别把GET当瑞士军刀使用,观察这两个典型错误:

// 反模式1:用GET执行写操作
@GetMapping("/users/{id}/disable")
public void disableUser(@PathVariable String id) { /*...*/ }

// 反模式2:过度使用POST
@PostMapping("/users/search")
public List<User> searchUsers(@RequestBody SearchCondition condition) { /*...*/ }

正确姿势应该这样处理:

@PutMapping("/users/{id}/status")
public void updateUserStatus(@PathVariable String id, @RequestParam boolean active) {
    // 使用PUT更新资源状态
}

@GetMapping("/users")
public List<User> searchUsers(@RequestParam(required = false) String name) {
    // GET请求带查询参数
}

3. 状态码的智慧游戏

3.1 那些年我们误用的状态码

一个真实的排查案例:某支付系统总返回200但附带错误消息,导致前端需要逐条解析响应体。修复后的正确实践:

@PostMapping("/payments")
public ResponseEntity<PaymentResult> createPayment(@Valid @RequestBody PaymentRequest request) {
    try {
        PaymentResult result = paymentService.process(request);
        return ResponseEntity.created(new URI("/payments/" + result.getId()))
               .body(result);
    } catch (InsufficientBalanceException e) {
        return ResponseEntity.status(HttpStatus.CONFLICT)
               .body(new PaymentResult(e.getMessage()));
    }
}

3.2 状态码的完整使用指南

参考某银行API规范中的状态码对照表:

状态码 使用场景 示例响应
202 异步任务已接受处理 { "taskId": "5x8c3", "statusUrl": "/tasks/5x8c3" }
429 调用频率超出限制 { "code": 42901, "message": "每分钟限100次请求" }

对应的Java实现:

@GetMapping("/reports")
public ResponseEntity<?> generateReport() {
    ReportTask task = reportService.createTask();
    return ResponseEntity.accepted()
           .header("Location", "/tasks/" + task.getId())
           .body(Map.of("taskId", task.getId()));
}

4. Spring生态系统的高级技巧

4.1 HATEOAS的实际应用

在微服务架构中,使用HATEOAS实现服务发现:

@GetMapping("/orders/{id}")
public EntityModel<Order> getOrder(@PathVariable String id) {
    Order order = orderService.findById(id);
    return EntityModel.of(order,
        linkTo(methodOn(OrderController.class).getOrder(id)).withSelfRel(),
        linkTo(methodOn(PaymentController.class).getPayment(order.getPaymentId())).withRel("payment")
    );
}

4.2 参数校验的正确姿势

结合JSR-380规范进行验证:

public class UserCreateDTO {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 4, max = 20)
    private String username;
    
    @Email
    private String email;
}

@PostMapping("/users")
public ResponseEntity<?> createUser(@Valid @RequestBody UserCreateDTO dto) {
    // 校验失败时会自动抛出MethodArgumentNotValidException
}

5. 企业级API开发必知事项

5.1 版本控制的三层策略

某电商平台采用的版本控制方案:

// URL版本控制
@GetMapping("/v2/products/{id}")

// Header版本控制
@GetMapping(value = "/products/{id}", headers = "X-API-Version=2")

// Content Negotiation
@GetMapping(value = "/products/{id}", produces = "application/vnd.company.v2+json")

5.2 安全防护的十二道金牌

  1. 全站HTTPS强制
  2. JWT令牌的自动续期机制
  3. 敏感参数加密处理:
@GetMapping("/users")
public List<User> searchUsers(@EncryptedParam String phone) {
    // 自动解密电话号码
}

6. RESTful API的开发进阶

6.1 性能优化三板斧

某物流系统的缓存策略:

@GetMapping("/tracking/{id}")
@Cacheable(value = "trackingInfo", unless = "#result.status == 'IN_TRANSIT'")
public TrackingInfo getTrackingInfo(@PathVariable String id) {
    return trackingService.getInfo(id);
}

6.2 错误处理的工程化方案

全局异常处理器:

@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
               .body(new ErrorResponse("RESOURCE_NOT_FOUND", ex.getMessage()));
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationErrors(MethodArgumentNotValidException ex) {
        // 提取校验错误信息...
    }
}

7. 常见应用场景分析

7.1 企业内部系统集成

在财务系统中处理银企直连:

@RestController
@RequestMapping("/api/v1/bank-connector")
public class BankConnectorController {
    
    @PostMapping("/payment-notify")
    public ResponseEntity<Void> handlePaymentNotification(@RequestBody BankNotification notification) {
        // 处理银行回调通知
        return ResponseEntity.ok().build();
    }
}

7.2 微服务架构下的API设计

服务间通信的契约示例:

@FeignClient(name = "inventory-service")
public interface InventoryServiceClient {
    
    @GetMapping("/api/inventory/{sku}")
    ResponseEntity<InventoryInfo> getInventory(@PathVariable String sku);
    
    @PutMapping("/api/inventory/{sku}/lock")
    void lockInventory(@PathVariable String sku, @RequestParam int quantity);
}

8. 技术方案对比与选择

8.1 REST vs GraphQL

订单查询的场景对比:

// RESTful实现
@GetMapping("/orders/{id}?fields=id,totalAmount,items.product.name")

// GraphQL实现(需额外集成)
@PostMapping("/graphql")
public ResponseEntity<Object> graphql(@RequestBody String query) {
    // 使用GraphQLJava执行查询
}

8.2 Spring Web MVC vs JAX-RS

两种实现的差异示例:

// Spring MVC风格
@RestController
public class SpringProductController {
    @GetMapping("/products/{id}")
    public Product getProduct(@PathVariable String id) { /*...*/ }
}

// JAX-RS风格
@Path("/products")
public class JaxRsProductResource {
    @GET
    @Path("/{id}")
    public Response getProduct(@PathParam("id") String id) { /*...*/ }
}