主题
REST 接口开发
一、统一返回结构(先定义好)
前端约定 code / message / data,后端先定义一个通用返回类:
java
package com.mall.common;
import lombok.Data;
@Data
public class Result<T> {
private Integer code; // 0 成功,非 0 失败
private String message;
private T data;
public static <T> Result<T> ok(T data) {
Result<T> r = new Result<>();
r.setCode(0);
r.setMessage("success");
r.setData(data);
return r;
}
public static <T> Result<T> fail(String message) {
Result<T> r = new Result<>();
r.setCode(500);
r.setMessage(message);
return r;
}
}二、第一个 Controller
java
package com.mall.controller;
import com.mall.common.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // 返回 JSON 的 Controller
@RequestMapping("/api/hello") // 统一前缀
public class HelloController {
@GetMapping("/world")
public Result<String> hello() {
return Result.ok("Hello Full Stack!");
}
}重启后访问 http://localhost:8080/api/hello/world:
json
{ "code": 0, "message": "success", "data": "Hello Full Stack!" }三、注解与参数绑定(重点)
后端接收前端参数有几种方式,必须分清:
① 路径参数 @PathVariable(REST 风格)
java
@GetMapping("/product/{id}")
public Result<Product> getById(@PathVariable Long id) {
return Result.ok(productService.getById(id));
}② 查询参数 @RequestParam(?keyword=手机)
java
@GetMapping("/product/list")
public Result<Page<Product>> list(
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
return Result.ok(productService.list(keyword, page, size));
}③ JSON 请求体 @RequestBody(POST 的数据)
java
@PostMapping("/order")
public Result<Order> create(@RequestBody OrderCreateDTO dto) {
return Result.ok(orderService.create(dto));
}④ 请求头 @RequestHeader
java
@GetMapping("/user/info")
public Result<UserVO> userInfo(@RequestHeader("Authorization") String token) {
return Result.ok(userService.info(token));
}四、@RequestMapping 与 HTTP 方法
java
@RestController
@RequestMapping("/api/cart") // 类上定义资源前缀
public class CartController {
@GetMapping // GET /api/cart 查
public Result<List<CartItemVO>> list() { ... }
@PostMapping // POST /api/cart 增
public Result<Void> add(@RequestBody CartAddDTO dto) { ... }
@PatchMapping("/{id}") // PATCH /api/cart/{id} 局部改
public Result<Void> updateCount(@PathVariable Long id,
@RequestBody CartUpdateDTO dto) { ... }
@DeleteMapping("/{id}") // DELETE /api/cart/{id} 删
public Result<Void> remove(@PathVariable Long id) { ... }
}五、DTO 与 VO:别让实体裸奔
为什么要 DTO/VO
直接把 Product 实体返回给前端,会把 deleted、stock 等敏感/多余字段暴露出去。企业规范是:
- DTO:接收前端入参(只含需要的字段 + 校验注解)
- VO:返回给前端的数据(按需裁剪、组合字段)
java
// dto/OrderCreateDTO.java —— 接收前端入参
@Data
public class OrderCreateDTO {
@NotNull(message = "商品不能为空")
private List<Long> cartIds;
@NotBlank(message = "收货地址不能为空")
private String address;
}java
// vo/ProductVO.java —— 返回给前端
@Data
public class ProductVO {
private Long id;
private String name;
private BigDecimal price;
private Integer stock;
private String image;
private String categoryName; // 从分类表 join 出来
}六、分页接口(MyBatis-Plus 的 Page)
java
@GetMapping("/product/list")
public Result<IPage<ProductVO>> list(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
Page<Product> p = new Page<>(page, size);
IPage<Product> result = productMapper.selectPage(p, new LambdaQueryWrapper<>());
// 组装成 Page<ProductVO> 返回
return Result.ok(productService.pageVO(p));
}前端拿到的结构(与 TS 里的 PageResult<T> 对应):
json
{
"code": 0,
"records": [ { "id": 1, "name": "手机" } ],
"total": 120,
"size": 10,
"current": 1
}七、验收
用 Postman / Apifox 测试:
GET http://localhost:8080/api/hello/world → code=0
GET http://localhost:8080/api/product/list?page=1&size=10
POST http://localhost:8080/api/cart body: {"productId":1,"count":2}本章核心
记住四件事:Result 统一返回、@GetMapping 等四个注解、DTO 接收 / VO 返回、分页用 Page。后面所有接口都是这个套路。