Spring Boot RESTful API 设计指南:查询接口规范与最佳实践
引言
在 Spring Boot 开发中,查询接口的设计直接影响着系统的可用性、可维护性和性能。本文将深入探讨如何规范设计查询接口,包括 GET/POST 的选择、参数定义、校验规则等,并提供可落地的代码示例。
一、GET 与 POST 的选择标准
1.1 何时使用 GET 请求
GET 请求是幂等的,适合用于不修改服务器状态的查询操作:
// 商品列表查询示例
@GetMapping("/products")
public ResponseEntity<Page<Product>> queryProducts(
@RequestParam(required = false) String name,
@RequestParam(required = false) String category,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
// 分页查询逻辑
}
优势:
- 可被缓存
- 参数可见,便于调试
- 支持浏览器直接访问
限制:
- URL 长度有限(约 2048 字符)
- 参数只能简单键值对
1.2 何时使用 POST 请求
当查询条件复杂时,POST 更合适:
// 复杂商品搜索示例
@PostMapping("/products/search")
public ResponseEntity<Page<Product>> searchProducts(
@RequestBody ProductSearchDTO searchDTO) {
// 复杂查询逻辑
}
// 搜索DTO定义
@Data
public class ProductSearchDTO {
private String keyword;
private List<String> categories;
private PriceRange priceRange;
private SortCondition sort;
@Data
public static class PriceRange {
private BigDecimal min;
private BigDecimal max;
}
}
适用场景:
- 参数包含嵌套对象
- 需要传递数组/集合
- 查询条件超过 10 个字段
- 涉及敏感数据(如身份证号查询)
二、参数设计规范
2.1 基础查询参数
推荐格式:
@GetMapping("/orders")
public Page<Order> queryOrders(
@RequestParam @DateTimeFormat(iso = ISO.DATE) LocalDate startDate,
@RequestParam @DateTimeFormat(iso = ISO.DATE) LocalDate endDate,
@RequestParam(defaultValue = "0") @Min(0) int page,
@RequestParam(defaultValue = "20") @Max(100) int size) {
// 查询逻辑
}
规范要点:
- 时间参数明确格式(推荐 ISO 8601)
- 分页参数统一命名(page/size)
- 添加基础校验注解
2.2 复杂查询参数
标准DTO示例:
@Data
public class AdvancedSearchDTO {
@NotBlank
private String queryType; // 搜索类型:精确/模糊
@Size(max = 10)
private List<@Pattern(regexp = "^[A-Za-z0-9]+$") String> codes;
@Valid
private TimeRange createTime;
@Data
public static class TimeRange {
@PastOrPresent
private LocalDateTime start;
@FutureOrPresent
private LocalDateTime end;
}
}
Controller使用:
@PostMapping("/data/advanced-search")
public SearchResult advancedSearch(
@Valid @RequestBody AdvancedSearchDTO dto) {
// 参数自动校验
}
三、高级设计模式
3.1 动态查询实现
方案一:QueryDSL 动态查询
@GetMapping("/dynamic")
public List<User> dynamicQuery(
@RequestParam(required = false) String name,
@RequestParam(required = false) Integer age) {
BooleanBuilder builder = new BooleanBuilder();
if (name != null) {
builder.and(user.name.contains(name));
}
if (age != null) {
builder.and(user.age.eq(age));
}
return queryFactory.selectFrom(user)
.where(builder)
.fetch();
}
方案二:Specification 动态查询
@PostMapping("/spec-search")
public Page<User> specSearch(
@RequestBody UserSpecification spec,
Pageable pageable) {
return userRepository.findAll(spec, pageable);
}
3.2 全局参数处理
统一分页参数处理:
@ControllerAdvice
public class PaginationAdvice implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new PageableHandlerMethodArgumentResolver() {
@Override
public Pageable resolveArgument(...) {
Pageable pageable = super.resolveArgument(...);
return PageRequest.of(
pageable.getPageNumber(),
Math.min(pageable.getPageSize(), 100),
pageable.getSort());
}
});
}
}
四、安全与性能优化
4.1 安全规范
敏感参数处理:
@PostMapping("/secure-search") public ResponseEntity<?> secureSearch( @Encrypted @RequestBody SensitiveSearchDTO dto) { // 自动解密处理 }
SQL 注入防护:
- 使用 JPA/Hibernate 参数绑定
- 禁止字符串拼接 SQL
4.2 性能优化
分页最佳实践:
@GetMapping("/optimized") public Slice<Data> optimizedQuery( Pageable pageable, @RequestParam String filter) { return repository.findByFilter(filter, PageRequest.of( pageable.getPageNumber(), Math.min(pageable.getPageSize(), 50))); }
响应压缩:
# application.properties server.compression.enabled=true server.compression.mime-types=application/json
五、文档化与测试
5.1 Swagger 集成
@Operation(summary = "用户复杂查询")
@PostMapping("/users/advanced-search")
public Page<User> advancedUserSearch(
@Parameter(description = "查询条件", required = true)
@RequestBody UserSearchDTO dto,
@Parameter(description = "分页参数")
Pageable pageable) {
// 实现逻辑
}
5.2 测试用例
MockMVC 测试示例:
@Test
void testQueryWithParams() throws Exception {
mockMvc.perform(get("/api/products")
.param("category", "electronics")
.param("page", "0")
.param("size", "10"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").isArray());
}
结语
良好的查询接口设计需要平衡以下因素:
- 语义明确:准确表达接口用途
- 参数规范:统一命名和结构
- 安全可靠:防止注入和越权
- 性能高效:合理分页和缓存
- 易于维护:完善的文档和测试
建议团队制定统一的《接口设计规范》,并使用 Swagger 等工具维护接口文档。实际开发中应根据业务场景灵活选择技术方案,避免教条主义。