Skip to content

REST API 实践 ​

实验目标 ​

构建完整的 RESTful API,掌握 Spring MVC 的核心功能。

实验 1:完整 CRUD API ​

java
@RestController
@RequestMapping("/api/products")
public class ProductController {
    
    @Autowired
    private ProductService productService;
    
    @GetMapping
    public ResponseEntity<List<Product>> getAllProducts(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size
    ) {
        Page<Product> products = productService.findAll(PageRequest.of(page, size));
        return ResponseEntity.ok(products.getContent());
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<Product> getProduct(@PathVariable Long id) {
        return productService.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }
    
    @PostMapping
    public ResponseEntity<Product> createProduct(@Valid @RequestBody Product product) {
        Product saved = productService.save(product);
        return ResponseEntity
            .created(URI.create("/api/products/" + saved.getId()))
            .body(saved);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<Product> updateProduct(
        @PathVariable Long id,
        @Valid @RequestBody Product product
    ) {
        if (!productService.exists(id)) {
            return ResponseEntity.notFound().build();
        }
        product.setId(id);
        return ResponseEntity.ok(productService.save(product));
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
        if (!productService.exists(id)) {
            return ResponseEntity.notFound().build();
        }
        productService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

实验 2:数据验证 ​

java
public class Product {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @NotBlank(message = "Product name is required")
    @Size(min = 3, max = 100)
    private String name;
    
    @NotNull
    @DecimalMin(value = "0.01", message = "Price must be greater than 0")
    private BigDecimal price;
    
    @Min(0)
    private Integer stock;
    
    // getters and setters
}

@ControllerAdvice
public class ValidationExceptionHandler {
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidation(
        MethodArgumentNotValidException ex
    ) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error ->
            errors.put(error.getField(), error.getDefaultMessage())
        );
        return ResponseEntity.badRequest().body(errors);
    }
}

测试:

bash
# 创建产品(成功)
curl -X POST http://localhost:8080/api/products \
  -H "Content-Type: application/json" \
  -d '{"name":"Laptop","price":999.99,"stock":10}'

# 创建产品(验证失败)
curl -X POST http://localhost:8080/api/products \
  -H "Content-Type: application/json" \
  -d '{"name":"","price":-10,"stock":-5}'

实验 3:查询和过滤 ​

java
@GetMapping("/search")
public List<Product> searchProducts(
    @RequestParam(required = false) String name,
    @RequestParam(required = false) BigDecimal minPrice,
    @RequestParam(required = false) BigDecimal maxPrice
) {
    return productService.search(name, minPrice, maxPrice);
}

// Service 层
public List<Product> search(String name, BigDecimal minPrice, BigDecimal maxPrice) {
    Specification<Product> spec = (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        
        if (name != null && !name.isEmpty()) {
            predicates.add(cb.like(cb.lower(root.get("name")), "%" + name.toLowerCase() + "%"));
        }
        
        if (minPrice != null) {
            predicates.add(cb.greaterThanOrEqualTo(root.get("price"), minPrice));
        }
        
        if (maxPrice != null) {
            predicates.add(cb.lessThanOrEqualTo(root.get("price"), maxPrice));
        }
        
        return cb.and(predicates.toArray(new Predicate[0]));
    };
    
    return productRepository.findAll(spec);
}

实验 4:内容协商 ​

java
@GetMapping(value = "/{id}", produces = {
    MediaType.APPLICATION_JSON_VALUE,
    MediaType.APPLICATION_XML_VALUE
})
public Product getProduct(@PathVariable Long id) {
    return productService.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
}

测试:

bash
# JSON 响应
curl -H "Accept: application/json" http://localhost:8080/api/products/1

# XML 响应
curl -H "Accept: application/xml" http://localhost:8080/api/products/1

实验 5:分页和排序 ​

java
@GetMapping
public Page<Product> getProducts(
    @PageableDefault(size = 10, sort = "name", direction = Sort.Direction.ASC)
    Pageable pageable
) {
    return productService.findAll(pageable);
}

测试:

bash
# 第一页,每页 10 条
curl "http://localhost:8080/api/products?page=0&size=10"

# 按价格降序排序
curl "http://localhost:8080/api/products?sort=price,desc"

# 多字段排序
curl "http://localhost:8080/api/products?sort=price,desc&sort=name,asc"

验收标准 ​

✅ CRUD 操作正常
✅ 数据验证正确
✅ 查询和过滤功能完整
✅ 分页和排序正常
✅ 返回正确的 HTTP 状态码

返回第二章概览

用心记录每一步成长