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 安全防护的十二道金牌
- 全站HTTPS强制
- JWT令牌的自动续期机制
- 敏感参数加密处理:
@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) { /*...*/ }
}