Skip to content

异常 ​

全局异常处理 ​

全局异常处理器的使用步骤:

  1. 定义全局异常处理器,就是定义一个类,在类上加上一个注解@RestControllerAdvice,它的作用就是告诉SpringBoot这是一个专门用于处理异常的类
  2. 定义全局异常处理方法,在类中定义一个方法来捕获异常,在这个方法上需要加上注解@ExceptionHandler,它的作用是来指定我们要捕获的是哪一类型的异常

在qk-management包中创建全局异常处理

代码示例如下:

java
package com.cghbh.handler;

import com.qk.common.Result;
import com.qk.exception.DataNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@Component
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        e.printStackTrace(); // 打印异常的调用栈信息(方便调试)
        log.error("程序执行出现异常,异常原因:{}", e.getMessage());
        return Result.error("服务器正忙请稍后重试.....");
    }
}

为了精确提示,我们还可以做下面的优化

java
package com.cghbh.handler;

import com.qk.common.Result;
import com.qk.exception.DataNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@Component
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(DuplicateKeyException.class)
    public Result handleDuplicateKeyException(DuplicateKeyException e) {
        e.printStackTrace(); // 打印异常的调用栈信息(方便调试)
        log.error("程序执行出现异常,异常原因:{}", e.getMessage());
        if (e.getMessage().contains("dept.name")) {
            return Result.error("部门名称已存在");
        }

        return Result.error("数据已经存在,请检查重试...");
    }

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        e.printStackTrace(); // 打印异常的调用栈信息(方便调试)
        log.error("程序执行出现异常,异常原因:{}", e.getMessage());
        return Result.error("服务器正忙请稍后重试.....");
    }
}

自定义异常 ​

1.定义一个自定义的异常类 ​

在之前的项目代码中我们在判断需要操作的部门数据不存在的时候抛出运行时异常,对于这种情况我们可以自定义一个DataNotFoundException,并在全局异常处理器中捕获并处理。

在qk-common模块中新建自定义异常类 DeptNotFoundException,自定义的异常都放公共模块,以供其他模块的使用。

创建出来的自定义异常类代码如下:

java
package com.cghbh.exception;

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

2.在代码中抛出异常 ​

程序中对应的位置抛出自定义异常。

java
package com.cghbh.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cghbh.common.PageResult;
import com.cghbh.dto.DeptPageDto;
import com.cghbh.entity.Dept;
import com.cghbh.exception.DeptNotFoundException;
import com.cghbh.mapper.DeptMapper;
import com.cghbh.service.DeptService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Slf4j
@RequiredArgsConstructor
@Service
public class DeptServiceImpl implements DeptService {
    private final DeptMapper deptMapper;

    @Override
    public Dept getDeptInfoById(Integer id) {
        Dept dept = deptMapper.selectById(id);
        if (dept == null) {
            // 在这里对异常进行抛出,但是我们不需要在这里try catch操作,将错误丢给上一层进行处理
            throw new DeptNotFoundException("当前部门不存在");
        }
        return dept;
    }
}

3.对抛出的异常处理 ​

全局异常处理器中可以对自定义异常进行处理。

java
package com.cghbh.handler;

import com.cghbh.common.Result;
import com.cghbh.exception.DeptNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@Component
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 这里捕获我们自定义的异常
    @ExceptionHandler(DeptNotFoundException.class)
    public Result handlerDeptNotFoundException(DeptNotFoundException e) {
        // 打印异常的调用栈信息方便代码调试
        e.printStackTrace();
        log.error("程序执行出现异常,异常原因:{}", e.getMessage());
        // 这里返回的数据需要保证格式,实际上和我们接口正常时返回的数据是差不多,相当于返回数据的操作这里的异常帮我们做好了
        return Result.error(e.getMessage());
    }
}

自定义业务异常-基本版 ​

自定义业务异常 ​

java
package com.cghbh.exception;

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

全局异常处理器 ​

java
package com.cghbh.handler;

import com.qk.common.Result;
import com.qk.exception.BusinessException;
import com.qk.exception.DataNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@Component
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(DataNotFoundException.class)
    public Result handleDataNotFoundException(DataNotFoundException e) {
        e.printStackTrace(); // 打印异常的调用栈信息(方便调试)
        log.error("程序执行出现异常,异常原因:{}", e.getMessage());
        return Result.error(e.getMessage());
    }

    @ExceptionHandler(BusinessException.class) 
    public Result handleBusinessException(BusinessException e) { 
        e.printStackTrace(); // 打印异常的调用栈信息(方便调试)
        log.error("程序执行出现异常,异常原因:{}", e.getMessage());
        return Result.error(e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        e.printStackTrace(); // 打印异常的调用栈信息(方便调试)
        log.error("程序执行出现异常,异常原因:{}", e.getMessage());
        return Result.error("服务器正忙请稍后重试.....");
    }
}

业务代码抛出异常 ​

在Service层中使用自定义的业务异常:

java
@Override
public void deleteById(Integer id) {
    Dept dept = deptMapper.selectById(id);
    if (dept == null) {
        throw new DataNotFoundException("需要删除的数据不存在");
    }
    // 根据部门id查询部门是否被关联
    // 查询当前部门的用户数量
    Long count = userMapper.selectCount(Wrappers.lambdaQuery(User.class).eq(User::getDeptId, dept.getId()));
    // 数量大于0说明被关联
    if (count > 0) {
        throw new BusinessException("部门被关联,不能被删除,请解除关联后重试");
    }

    // 删除部门
    deptMapper.deleteById(id);
}

自定义业务异常-加强版 ​

定义错误枚举 ​

java
package com.cghbh.common;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 错误码枚举
 * 约定:code=1 统一代表成功;0为通用失败;其他业务码 >1
 */
@Getter
@AllArgsConstructor
public enum ErrorCode {

    // 通用
    COMMON_FAIL(0, "操作失败"),
    SUCCESS(1, "操作成功"),

    // 用户相关(适配你User实体)
    USER_NOT_EXIST(10001, "用户不存在"),
    USER_NAME_EXIST(10002, "用户名已存在"),
    PASSWORD_ERROR(10003, "密码错误"),
    USER_DISABLED(10004, "用户已禁用"),
    OLD_PASSWORD_WRONG(10005, "原密码不正确"),

    // 部门
    DEPT_NOT_EXIST(20001, "部门不存在"),
    DEPT_NAME_EXIST(20002, "部门名称已存在"),

    // 权限
    NO_PERMISSION(40001, "无操作权限"),

    // 参数
    PARAM_ERROR(30001, "参数校验失败"),

    // 系统
    SYSTEM_ERROR(50001, "服务器内部异常");

    private final Integer code;
    private final String msg;
}

增强版Result ​

java
package com.cghbh.common;

import lombok.Data;

/**
 * 后端统一返回结果
 * 约定:code=1成功,0通用失败,其他为业务自定义错误码
 */
@Data
public class Result {

    private Integer code; // 编码:1成功,0为失败,其他业务错误码
    private String msg;   // 提示信息
    private Object data;  // 返回数据

    /**
     * 成功,无返回数据
     */
    public static Result success() {
        Result result = new Result();
        result.code = ErrorCode.SUCCESS.getCode();
        result.msg = ErrorCode.SUCCESS.getMsg();
        return result;
    }

    /**
     * 成功,携带返回数据
     */
    public static Result success(Object object) {
        Result result = new Result();
        result.data = object;
        result.code = ErrorCode.SUCCESS.getCode();
        result.msg = ErrorCode.SUCCESS.getMsg();
        return result;
    }

    /**
     * 失败:通用失败码0,自定义提示文本(兼容你原有写法)
     */
    public static Result error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = ErrorCode.COMMON_FAIL.getCode();
        return result;
    }

    /**
     * 失败:指定错误码+提示信息
     */
    public static Result error(Integer code, String msg) {
        Result result = new Result();
        result.code = code;
        result.msg = msg;
        return result;
    }

    /**
     * 失败:直接传入错误枚举(推荐使用)
     */
    public static Result error(ErrorCode errorCode) {
        Result result = new Result();
        result.code = errorCode.getCode();
        result.msg = errorCode.getMsg();
        return result;
    }

    /**
     * 失败:枚举为基础,覆盖提示消息(同一个错误码,不同提示)
     */
    public static Result error(ErrorCode errorCode, String msg) {
        Result result = new Result();
        result.code = errorCode.getCode();
        result.msg = msg;
        return result;
    }
}

配套自定义业务异常 BusinessException ​

java
package com.cghbh.common;

import lombok.Getter;

/**
 * 自定义业务异常
 */
@Getter
public class BusinessException extends RuntimeException {

    private final Integer code;

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public BusinessException(String message) {
        super(message);
        this.code = ErrorCode.COMMON_FAIL.getCode();
    }

    public BusinessException(ErrorCode errorCode) {
        super(errorCode.getMsg());
        this.code = errorCode.getCode();
    }

    public BusinessException(ErrorCode errorCode, String msg) {
        super(msg);
        this.code = errorCode.getCode();
    }
}

全局异常处理器 ​

java
package com.cghbh.common;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常统一处理
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 捕获自定义业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public Result businessExceptionHandler(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }

    /**
     * 兜底捕获系统未知异常
     */
    @ExceptionHandler(Exception.class)
    public Result exceptionHandler(Exception e) {
        e.printStackTrace();
        return Result.error(ErrorCode.SYSTEM_ERROR);
    }
}

使用示例 ​

java
// 原有写法,依然支持,不用改旧代码
return Result.error("用户名不能为空");

// 推荐枚举方式
return Result.error(ErrorCode.USER_NOT_EXIST);

// 枚举 + 自定义提示
return Result.error(ErrorCode.USER_NOT_EXIST, "该用户id:10 不存在");

// Service中抛异常
throw new BusinessException(ErrorCode.PASSWORD_ERROR);
throw new BusinessException("自定义提示,code=0");
throw new BusinessException(ErrorCode.USER_NOT_EXIST, "用户【admin】不存在");

返回示例 ​

json
{
    "code": 10001,
    "msg": "用户不存在",
    "data": null
}