Preview
Skip to content
CodingDiary
返回

Spring Boot 全局异常处理

编辑页面
导读

前后端分离时代,接口返回格式必须统一!本文提供完整的 Spring Boot 全局异常处理方案:统一返回格式封装(Api.java)、状态码枚举(Status.java)、自定义业务异常类、404 异常配置、@ControllerAdvice 全局异常处理器。一套代码解决所有异常处理问题,直接复制到项目里用。

我们在做 Web 应用的时候,请求处理过程中发生错误异常是一个非常常见的情况,但是异常的处理方式和放回的异常内容并没有做一个统一的处理。在现在这种前后端分离的时代,不论响应成功还是失败的数据格式都需要保持一致。

1. 返回格式统一封装

Api.java

package com.xkcoding.scaffold.common;

import com.xkcoding.scaffold.common.status.Status;
import lombok.Data;

import java.io.Serializable;

/**
 * <p>
 * API 统一返回格式封装
 * </p>
 *
 * @package: com.xkcoding.scaffold.common
 * @description: API 统一返回格式封装
 * @author: yangkai.shen
 * @date: Created in 2018/8/2 下午6:15
 * @copyright: Copyright (c) 2018
 * @version: V1.0
 * @modified: yangkai.shen
 */
@Data
public class Api implements Serializable {

	private static final long serialVersionUID = 1672493411204459264L;

	/**
	 * 返回码
	 */
	private Integer code;

	/**
	 * 返回信息
	 */
	private String msg;

	/**
	 * 返回数据
	 */
	private Object data;

	/**
	 * 默认构造
	 */
	public Api() {
		this.code = Status.SUCCESS.getCode();
		this.msg = Status.SUCCESS.getMsg();
	}

	/**
	 * 构造 API 返回对象
	 *
	 * @param code 返回码
	 * @param msg  返回信息
	 * @param data 数据
	 */
	public Api(Integer code, String msg, Object data) {
		this.code = code;
		this.msg = msg;
		this.data = data;
	}

	/**
	 * 通用构造包含返回信息的 Api <br>
	 * 主要用于只包含返回信息,不包含数据时的返回
	 *
	 * @param code 状态码
	 * @param msg  信息
	 * @return Api
	 */
	public static Api of(int code, String msg, Object data) {
		return new Api(code, msg, data);
	}

	/**
	 * 通用构造包含返回信息的 Api <br>
	 * 主要用于只包含返回信息,不包含数据时的返回
	 *
	 * @param code 状态码
	 * @param msg  信息
	 * @return Api
	 */
	public static Api ofMessage(int code, String msg) {
		return new Api(code, msg, null);
	}

	/**
	 * 通用构造包含返回信息的 Api <br>
	 * 主要用于只包含返回信息,不包含数据时的返回
	 *
	 * @param msg 信息
	 * @return Api
	 */
	public static Api ofMessage(String msg) {
		return new Api(Status.SUCCESS.getCode(), msg, null);
	}

	/**
	 * 通用构造包含返回数据的 Api <br>
	 * 主要用于操作成功时的返回(不带数据)
	 *
	 * @return Api
	 */
	public static Api ofSuccess() {
		return new Api(Status.SUCCESS.getCode(), Status.SUCCESS.getMsg(), null);
	}

	/**
	 * 通用构造包含返回数据的 Api <br>
	 * 主要用于操作成功时的返回
	 *
	 * @param data 操作成功时需要返回的数据
	 * @return Api
	 */
	public static Api ofSuccess(Object data) {
		return new Api(Status.SUCCESS.getCode(), Status.SUCCESS.getMsg(), data);
	}

	/**
	 * 通过 Status 构造 Api <br>
	 * 主要用于出现异常时的返回
	 *
	 * @param status {@link Status}
	 * @return Api
	 */
	public static Api ofStatus(Status status) {
		return new Api(status.getCode(), status.getMsg(), null);
	}

}

2. 通用状态统一封装

Status.java

package com.xkcoding.scaffold.common.status;

import com.xkcoding.scaffold.common.BaseEnum;
import lombok.Getter;

/**
 * <p>
 * 状态码枚举
 * </p>
 *
 * @package: com.xkcoding.scaffold.common
 * @description: 状态码枚举
 * @author: yangkai.shen
 * @date: Created in 2018/8/2 下午7:49
 * @copyright: Copyright (c) 2018
 * @version: V1.0
 * @modified: yangkai.shen
 */
@Getter
public enum Status implements BaseEnum {
	/**
	 * 成功
	 */
	SUCCESS(200, "成功"),

	/**
	 * 请求错误
	 */
	BAD_REQUEST(400, "请求错误"),

	/**
	 * 尚未登录
	 */
	UNAUTHORIZED(401, "尚未登录"),

	/**
	 * 权限不够
	 */
	FORBIDDEN(403, "权限不够"),

	/**
	 * 请求不存在
	 */
	REQUEST_NOT_FOUND(404, "请求不存在"),

	/**
	 * 服务器内部错误
	 */
	INTERNAL_SERVER_ERROR(500, "服务器内部错误"),

	/**
	 * 用户名或密码错误
	 */
	USERNAME_OR_PASSWORD_ERROR(50000, "用户名或密码错误"),

	/**
	 * 用户不存在
	 */
	USER_NOT_EXIST(50001, "账号不存在"),

	/**
	 * 用户已被禁用
	 */
	USER_DISABLE(50002, "账号已被禁用"),

	/**
	 * 账号已删除
	 */
	USER_DELETED(50003, "账号已删除"),

	/**
	 * 验证码错误
	 */
	VERIFY_CODE_ERROR(50004, "验证码错误"),

	/**
	 * 登录成功
	 */
	LOGIN_SUCCESS(50005, "登录成功"),

	/**
	 * 退出成功
	 */
	LOGOUT_SUCCESS(50006, "退出成功"),

	/**
	 * 验证码异常
	 */
	CODE_ERROR(50007, "验证码异常"),

	/**
	 * 获取验证码的值失败
	 */
	CODE_GET_ERROR(50008, "获取验证码的值失败"),

	/**
	 * 验证码的值不能为空
	 */
	CODE_VALUE_NOT_NULL(50009, "验证码的值不能为空"),

	/**
	 * 验证码不存在
	 */
	CODE_NOT_FOUND(50010, "验证码不存在"),

	/**
	 * 验证码已过期
	 */
	CODE_IS_EXPIRED(50011, "验证码已过期"),

	/**
	 * 验证码不匹配
	 */
	CODE_NOT_MATCH(50012, "验证码不匹配"),

	/**
	 * 验证码发送异常
	 */
	CODE_SEND_ERROR(50012, "验证码发送异常");

	private Integer code;
	private String msg;

	Status(Integer code, String msg) {
		this.code = code;
		this.msg = msg;
	}
}

3. 自定义业务异常类

ScaffoldException.java

package com.xkcoding.scaffold.exception;

import com.xkcoding.scaffold.common.status.Status;
import lombok.Getter;

/**
 * <p>
 * 通用全局异常
 * </p>
 *
 * @package: com.xkcoding.scaffold.exception
 * @description: 通用全局异常
 * @author: yangkai.shen
 * @date: Created in 2018/8/2 下午7:59
 * @copyright: Copyright (c) 2018
 * @version: V1.0
 * @modified: yangkai.shen
 */
@Getter
public class ScaffoldException extends Exception {
	/**
	 * 异常码
	 */
	private Integer code;

	/**
	 * 返回信息
	 */
	private String msg;

	/**
	 * 返回内容
	 */
	private Object data;

	public ScaffoldException(Integer code, String msg) {
		super(msg);
		this.code = code;
		this.msg = msg;
	}

	public ScaffoldException(Integer code, String msg, Object data) {
		super(msg);
		this.code = code;
		this.msg = msg;
		this.data = data;
	}

	public ScaffoldException(Status status) {
		super(status.getMsg());
		this.code = status.getCode();
		this.msg = status.getMsg();
	}

	public ScaffoldException(Status status, Object data) {
		super(status.getMsg());
		this.code = status.getCode();
		this.msg = status.getMsg();
		this.data = data;
	}
}

4. 配置 Spring Boot

配置 application.yml 主要是为了对404错误作处理

spring:
  ### 开启404异常抛出 ###
  mvc:
    throw-exception-if-no-handler-found: true
  ### 关闭静态资源映射 ###
  resources:
    add-mappings: false

5. 统一异常处理

GlobalExceptionHandler.java

package com.xkcoding.scaffold.handler;

import com.xkcoding.scaffold.common.Api;
import com.xkcoding.scaffold.common.status.Status;
import com.xkcoding.scaffold.exception.ScaffoldException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.NoHandlerFoundException;

/**
 * <p>
 * 全局异常处理
 * </p>
 *
 * @package: com.xkcoding.scaffold.handler
 * @description: 全局异常处理
 * @author: yangkai.shen
 * @date: Created in 2018/8/2 下午8:05
 * @copyright: Copyright (c) 2018
 * @version: V1.0
 * @modified: yangkai.shen
 */
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(value = Exception.class)
	@ResponseBody
	public Api handlerException(Exception e) {
		if (e instanceof NoHandlerFoundException) {
			log.error("【全局异常拦截】NoHandlerFoundException: 请求方法 {}, 请求路径 {}", ((NoHandlerFoundException) e).getRequestURL(), ((NoHandlerFoundException) e).getHttpMethod());
			return Api.ofStatus(Status.REQUEST_NOT_FOUND);
		} else if (e instanceof ScaffoldException) {
			log.error("【全局异常拦截】ScaffoldException: 状态码 {}, 异常信息 {}", ((ScaffoldException) e).getCode(), e.getMessage());
			return new Api(((ScaffoldException) e).getCode(), e.getMessage(), ((ScaffoldException) e).getData());
		} else if (e instanceof AccessDeniedException) {
			log.error("【全局异常拦截】AccessDeniedException: 异常信息 {}", e.getMessage());
			return Api.ofStatus(Status.FORBIDDEN);
		}

		log.error("【全局异常拦截】: 异常信息 {} ", e.getMessage());
		return Api.ofStatus(Status.INTERNAL_SERVER_ERROR);
	}
}

Tip

异常处理的时候,务必在全局异常处理类中,打印异常日志打印异常日志打印异常日志

相关代码地址:https://github.com/xkcoding/scaffold


编辑页面
分享到:

上一篇
git 如何更新 fork 的项目到原项目的最新版本
下一篇
Java8 学习整理