学习目标与前置
建议用时:60-90 分钟准备:已有 Spring Boot 项目
- 编写 RestController 路由,返回 JSON。
- 使用 @Valid + Hibernate Validator 做参数校验。
- 用 @ControllerAdvice 统一异常与响应结构。
- 用 HandlerInterceptor 记录耗时并做简单鉴权。
Step 1:定义 DTO 与统一响应
flowchart LR A[请求] --> B[Controller 接收 DTO] B --> C[参数校验 @Valid] C --> D[业务处理/服务层] D --> E[统一响应 ApiResp] E --> F[异常走 @ControllerAdvice 返回错误码]
import jakarta.validation.constraints.NotBlank;
record TodoReq(@NotBlank(message = "标题必填") String title, boolean done) {}
record ApiResp<T>(int code, String msg, T data) {
static <T> ApiResp<T> ok(T data) { return new ApiResp<>(0, "ok", data); }
static ApiResp<Void> err(int code, String msg) { return new ApiResp<>(code, msg, null); }
}
说明:使用 record 简化数据类;code=0 表示成功,其他为业务错误。
Step 2:编写 Controller
@RestController
@RequestMapping("/api/todos")
public class TodoController {
private final List<TodoReq> todos = new CopyOnWriteArrayList<>();
@PostMapping
public ApiResp<TodoReq> create(@Valid @RequestBody TodoReq req) {
todos.add(req);
return ApiResp.ok(req);
}
@GetMapping
public ApiResp<List<TodoReq>> list() {
return ApiResp.ok(todos);
}
}
说明:@RequestBody 解析 JSON,@Valid 触发校验。
Step 3:统一异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResp<Void>> handleValid(MethodArgumentNotValidException ex) {
String msg = ex.getBindingResult().getFieldError().getDefaultMessage();
return ResponseEntity.badRequest().body(ApiResp.err(400, msg));
}
}
说明:捕获参数校验异常,返回 400 与统一格式。
Step 4:拦截器记录耗时与简单鉴权
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
private final Logger log = LoggerFactory.getLogger("ReqTime");
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
req.setAttribute("t0", System.currentTimeMillis());
// 简单鉴权:POST 需要头 X-Token
if ("POST".equals(req.getMethod()) && req.getHeader("X-Token") == null) {
res.setStatus(401);
return false;
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) {
long cost = System.currentTimeMillis() - (long) req.getAttribute("t0");
log.info("{} {} cost={}ms", req.getMethod(), req.getRequestURI(), cost);
}
});
}
}
说明:可扩展为统一日志或链路追踪;鉴权逻辑可后续替换为 Session/JWT。
Step 5:运行与验证
- 启动应用,POST /api/todos 不带 X-Token,预期 401。
- 带 X-Token 发送 POST,正常返回;故意缺少 title,返回 400 和错误信息。
- GET /api/todos,查看统一响应结构
{code,msg,data}。 - 查看控制台日志的耗时信息。
常见问题与排查
- 400 校验失败但信息为空:确认 message 是否填写;检查 BindingResult 读取逻辑。
- 拦截器未生效:确认配置类被扫描;检查 addInterceptors 是否注册。
- 响应中文乱码:确保返回 JSON 时设置 UTF-8(默认 Spring Boot 已处理)。
课堂练习
- 给 TodoReq 增加
priority字段,范围 1-5,校验不通过时返回 400。 - 为 GET /api/todos 增加分页参数 page/size,并返回对应子集。
课后巩固
- 自定义 BizException,统一处理返回 409,并在代码中模拟抛出。
- 给拦截器增加 header 白名单,或改为基于 Session/JWT 的鉴权(衔接后续安全章节)。