Skip to content

统一异常处理与参数校验

一、自定义业务异常

业务规则校验失败(库存不足、用户名重复、余额不够)都属于业务异常,用一个统一异常类:

java
// common/BizException.java
package com.mall.common;

public class BizException extends RuntimeException {
    public BizException(String message) {
        super(message);
    }
}

业务代码里直接抛:

java
throw new BizException("库存不足");

二、全局异常处理器

用一个 @RestControllerAdvice 捕获所有异常,统一转成 Result 返回,前端就能统一拿到 message

java
// common/GlobalExceptionHandler.java
package com.mall.common;

import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 业务异常 → 返回业务错误消息
    @ExceptionHandler(BizException.class)
    public Result<Void> handleBiz(BizException e) {
        log.warn("业务异常: {}", e.getMessage());
        return Result.fail(e.getMessage());
    }

    // 参数校验失败 → 返回第一条校验消息
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleValid(MethodArgumentNotValidException e) {
        FieldError fieldError = e.getBindingResult().getFieldError();
        return Result.fail(fieldError == null ? "参数错误" : fieldError.getDefaultMessage());
    }

    // 兜底异常 → 500,防止错误信息泄露到前端
    @ExceptionHandler(Exception.class)
    public Result<Void> handleOther(Exception e) {
        log.error("系统异常", e);
        return Result.fail("系统繁忙,请稍后重试");
    }
}

好处

  1. Controller 不用每个方法都写 try/catch
  2. 错误信息统一格式,前端拦截器好统一提示。
  3. 兜底异常不把堆栈暴露给用户(安全),只记日志。

三、参数校验(Bean Validation)

DTO 上加注解,REST 章节@Valid 就会自动触发校验。

java
// dto/RegisterDTO.java
package com.mall.dto;

import jakarta.validation.constraints.*;
import lombok.Data;

@Data
public class RegisterDTO {

    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 20, message = "用户名长度 3~20 位")
    private String username;

    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 32, message = "密码长度 6~32 位")
    private String password;

    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;
}
java
// dto/OrderCreateDTO.java
@Data
public class OrderCreateDTO {
    @NotEmpty(message = "请选择要结算的商品")
    private List<Long> cartIds;

    @NotBlank(message = "收货地址不能为空")
    private String address;
}

常用校验注解:

注解作用
@NotBlank字符串非空(去空格后)
@NotEmpty集合/数组非空
@NotNull非 null
@Size(min,max)字符串/集合长度
@Min / @Max数值范围
@Positive必须为正数
@Email邮箱格式
@Pattern正则匹配

四、Controller 启用校验

java
@PostMapping("/cart")
public Result<Void> add(@RequestBody @Valid CartAddDTO dto) {
    cartService.add(userId(), dto);
    return Result.ok(null);
}
  • 校验失败 → 抛 MethodArgumentNotValidException → 被全局处理器捕获 → 返回第一条 message
  • 前端 Axios 拦截器 ElMessage.error(res.message) 就能弹出中文提示。

五、日志记录

@Slf4j 打印日志,线上排查问题就靠它:

java
@Slf4j
@Service
public class OrderService {

    public Order create(...) {
        log.info("用户 {} 创建订单,商品数 {}", userId, dto.getCartIds().size());
        // 异常时
        log.error("下单失败,userId={}, 原因={}", userId, e.getMessage());
    }
}

日志分级:info(正常业务)、warn(可预期问题)、error(异常)。生产环境按级别分级存储。

六、验收

  • [ ] 提交空用户名 → 返回 "用户名不能为空"
  • [ ] 提交错误手机号 → 返回 "手机号格式不正确"
  • [ ] 加超库存 → 返回 "xx 库存不足"
  • [ ] 故意制造异常 → 返回 "系统繁忙" 且控制台有 error 日志

基于 MIT 协议发布,可自由学习与修改